Concurrent Processing (or faux multithreading)

Have you ever needed to run a command against 2000 workstations and thought, "Man that batch file is going to run forever!"  You might even worry the data will be too stale to matter when it finally gets to you.  I’ve run into this situation multiple times and the fix was to write a quick and dirty app in .NET that uses some simple multithreading.  I just change around some strings, recompile, and I’m off to the races.  Not a programmer you say?  Well, you need to learn a little.  And if .NET isn’t your thing, learn some VBScript and PowerShell.  Next year I plan to start abandoning VBScript <sniff> and moving to PowerShell, but because PowerSehll is not standard on all Windows systems yet it is easier to write i VBScript.  So what I provide for you here is a simple VBScript that will run multiple processes concurrently.  This is not multti-threading, so it uses more resources than my .NET code does, but it’s pretty darn cool.
 
So let’s start with the main piece the Class we create to handle our new process.
 
Class RunCmd
  Private p_Shell
  Private p_Exec
  Private p_Name
  Private p_Available
  Private p_ExitCode
  Public PID
  Public EC_Retrieved
 
  Private Sub Class_Initialize()
    Set p_Shell = CreateObject("WScript.Shell")
    p_Exec = Empty
    p_Available = "True"
    EC_Retrieved = ""
  End Sub
 
  Public Sub Open(cmd)
    Set p_Exec = p_Shell.Exec(cmd)
    PID = p_Exec.ProcessID
    p_Available = "False"
    p_ExitCode = ""
    EC_Retrieved = "False"
  End Sub
 
  Public Property Let Name(tempname)
    p_Name = tempname
  End Property
 
  Public Property Get Name
    Name = p_name
  End Property
 
  Public Property Get Available
    If Not IsEmpty(p_Exec) Then
      If p_Exec.Status <> 0 Then
        p_Available = "True"
        p_ExitCode = p_Exec.ExitCode
      End If
    End If
    Available = p_Available
  End Property
 
  Public Property Get ExitCode
    ExitCode = p_ExitCode
    EC_Retrieved = "True"
  End Property
End Class
 
The class has the constructor, one method, and 5 properties.  Next we look at how I used them.
 
First we set up a sample array of computers to test against.  This can be easily read in from Active Directory, a text file, or database table.  Then we create an array to store new instances of our RunCmd class.  In VBScript we cannot use the For Each…Next statement to populate each array element with a new instance of our class, me must use the standard For…Next which requires us to specify the number of items in our array.
 
Computers = Array("comp1","comp2","comp3","comp4","comp5","comp6","comp7")
Dim RunningProcesses(50)
for i = 0 to UBound(RunningProcesses)
  Set RunningProcesses(i) = New RunCmd
Next
 
After the array of RunCmd objetcs is set up it’s time to move to the main loop where the work is started.  StartProcess is called, which verifies the RunCmd object stored in that array element is not currently running an active process and if it is not it starts a new process to connect to the current computer fom the Computers array.  Once the process is started we move to the CheckProcess function which checks the number of objects in the RunningProcesses array have active processes running.  For the example I have set the number to 3.  So once 3 active processes have been reached it will loop until less than 3 are running.  As the CheckProcesses it calls it will retrieve data from the objects that have data.  So far the only thing I am capturing os the ExitCode.  I have not attempted to capture the StdOut as normally VBScript will wait for the process to finish, and we are try to avoid that!
 
For Each computer in computers
  StartProcess(computer)
  Do While CheckProcesses() >= 3
    WScript.Sleep 500
  Loop
Next
 
Once we have looped througn all of the computer we want to connect to we could still have processes in play, so we continue to run CheckProcesses while until we have 0 processes running.
 
Do While CheckProcesses() > 0
Loop
 
Here is the code for the StartProcess subroutine.  We pass the name of the computer we are attempting to connect to then we pass a command to to the Open method of object p.  When the method Open runs it assigns the PID of the process we started to the PID property of the object.
 
Sub StartProcess(cname)
  For Each p in RunningProcesses
    If p.Available = "True" Then
      p.open("cmd /c sc \\" & cname & " query snare|find /i ""_name""")
      p.Name = cname
      WScript.StdOut.WriteLine "Process Started: " & p.pid
      Exit For
    End iF
  Next
End Sub
 
The CheckProcess function is where we determine how many processes are running and grab the exit codes of completed processes.  When we get the Available property it check to see if the p_Exec object inside of our RunCmd object has a status other than 0, which means it has completed.  If it has not completed it increments the ProcessCount variable.  If it has completed it checks the EC_Retrieved property to see if we have already retrieved the ExitCode of the process.  If we have not retrieve it yet access the ExitCode property.  When we request this property it turns the EC_Retrieved property flag to True.  You can then code for whatever the expected error code might be.
 
Function CheckProcesses
  ProcessCount = 0
  For Each p in RunningProcesses
    If p.Available = "False" Then
      ProcessCount = ProcessCount + 1
    Else
      If p.EC_Retrieved = "False" Then
        If p.ExitCode = "1" Then
          WScript.StdOut.WriteLine p.Name & " – Snare is not installed."
        ElseIf p.ExitCode = "0" Then
          WScript.StdOut.WriteLine p.Name & " – Snare is installed."
        End If
      End If
    End If
  Next
  CheckProcesses = ProcessCount
End Function

 

There’s a lot of application for this code, and it’s a fun tutorial (I think) for creating your own classes in VBScript. Making your own class is a powerful tool and it can be used in tons of different applications.  Feel free to post a comment if you have questions or you’d like more examples!

EDIT: I have posted the complete code in the VBScripts download section!
Advertisements

Looking into the MAPI properties of a message object

Recently I had a reason to look into the available MAPI properties of a message object.   Microsoft pointed me to MFCMAPI, a tool available for free at codeplex.com.  The tool allows you to walk through your email profile, drilling down through all sorts of intersting objects.  The screenshot below shows my Inbox, and the specific properties of a message.  Notice cool flags like PR_REPLY_REQUESTED.  Unfortunately the property I was hoping to find was a timestamp for when the message was first opened and it doesn’t exist yet.  I did learn however that Exchange 2007 SP2 adds a message auditing feature that allows you to audit such actions as message opened.  Read more about Exchange auditing here.

Download MFCMAPI here.

Fun VBScript tip

When writing a VBScript designed to run at a command prompt (or "console") you some times want to not have to write data to a new line.
 
For instance, you might want to write a "." each time you loop through a array or collection so that you know something is happening.  Using WScript.Echo each use generates a CR\LF, much like using the vbCrLf constant or the textstream.writeline method.
 
This is where the WScript.StdOut.Write method comes in handy.  While not well documented this method works exactly like we need it to.  Take the following example where rsNames is a result set from an ADO query:
 
 
 
Do While Not rsNames.EOF
  If rsNames.Fields.Item(0).Value = "Bob" Then
    ‘ Write a period to show work then start a new line
    WScript.StdOut.WriteLine "."
    Wscript.StdOut.WriteLine "I found Bob!"
    Exit Do
  Else
    ‘ Write a period to show work
    Wscript.StdOut.Write "."
  End If
Loop
 
And if you really want to have some fun you could go for the "rotating line":
 
a = 0
b = 10000000
c = "\"
do while a < b
  if c = "\" then
    c = "|"
  elseif c = "|" then
    c = "/"
  elseif c = "/" then
    c = "-"
  elseif c = "-" then
    c = "\"
  end if
  wscript.stdout.write chr(08) & c
  wscript.sleep(100)
loop
 
Just for clarification chr(08) is a backspace and the sleep for 100 milliseconds makes the rotation direction of the line more obvious. : )
 
Have fun!

Fun DOS tip

I’m sure nobody writes batch files any more, right?

Well unfortunately I need to as part of our unattended POS system installation.  I was recently trying to clean up scripts and was irritated by the number of duplicate lines, one for writing to a log and the other for displaying to the screen.  After a few minutes of playing around and a couple of Internet searches I came up with the following:


ECHO %DATE% – %TIME% – The action succeeded or failed, blah, blah, blah >> %LOG% >&2

Very convenient for removing extra lines of code that cluttered the script.

Windows 7

First I suppose I should say I am the world’s worst blogger.  I have just been so busy I haven’t written anything in months.  I know people have been here because I have 2000 or so hits, but no one suggests what they might like to know about.  : (
 
I have been running Windows 7 at work for months, and today I got my official Windows 7 Ultimate to use at home.  The install process was great and in about an hour I was surfing the web and Facebooking.  So far I can find no complaints.  I think it runs faster than Vista, I love the new Taskbar, and I haven’t found any of my apps to have issues.  I have installed Office 2007, configured my email, and run the apps.  Everything opened very fast and seemed to work as usual.  I look forward to finding some new things to try over the next few weeks.
 
Oh, and I love the Porsche theme I downloaded from Microsoft.  : )

Has Powershell arrived?

I’d have to say yes.  I have been holding out for quite a long time when it comes to Powershell as it really didn’t seem to be taking hold in the marketplace.  I didn’t know anyone who was coding with it or even installing it on their systems.  With Windows 7 and Windows Server 2008 R2 releases looming on the horizon I decided to give it a second look.  The Powershell 2 CTP3 is out, and since I am using Windows 7 RC1, it is preinstalled. 
 
I took a Microsoft Powershell class this week and I am very impressed.  There is a huge reduction in the amount of coding necessary to complete many function, very specifically WMI.  I use WMI for all sorts of tasks and reducing complexity is always good.
 
My pet project right now is to create a script to execute a command against every computer object in a domain and parse the results.  The hard part?  Doing it on 50 systems simultaneously.  The harder part?  Monitoring the commands and starting new jobs as old ones completed.
 
But not with Powershell 2!  New cmdlets Start-Job, Get-Job, and Receive-Job make this a snap!  I am almost done testing a script to automate running ad hoc commands against several hundred systems in a child domain.  My testing has gone very well and right now I am just working on logic to parse the results and determine success or failure.
 
I hope to have the script posted by Tuesday.

WMIC and You, a perfect match

I always get asked how to pull information from a remote system so that RDP or VNC doesn’t have to be used.  I always respond with "WMI, dude!" and I always get frowns.  Seems a lot of folks really don’t like writing vbscripts!  I can’t image why, personally I love them.  But if you hate writing scripts just to get info from one computer, here’s the tool for you: WMIC.  It’s a command line tool for using WMI and it can be very handy.  Here are some of the query I find helpful.  If you exclude the "/NODE:<COMPUTERNAME>" you can run all these queries locally as well.
 
1.  WMIC /NODE:<COMPUTERNAME> BIOS GET SERIALNUMBER
2.  WMIC /NODE:<COMPUTERNAME> COMPUTERSYSTEM GET MANUFACTURER, MODEL
3.  WMIC /NODE:<COMPUTERNAME> COMPUTERSYSTEM GET TOTALPHYSICALMEMORY
4.  WMIC /NODE:<COMPUTERNAME> PRODUCT GET NAME, VERSION
5.  WMIC /NODE:<COMPUTERNAME> DISKDRIVE GET MODEL, SIZE
6.  WMIC /NODE:<COMPUTERNAME> LOGICALDISK WHERE DRIVETYPE="3" GET NAME, SIZE, FREESPACE
7.  WMIC /NODE:<COMPUTERNAME> OS GET CAPTION, VERSION
8.  WMIC /NODE:<COMPUTERNAME> NICCONFIG
 
There are tons of stuff you can do with this.  Type WMIC at a command prompt and then /? at the WMIC prompt to list available aliases (like BIOS and OS).  To list all the properties without having to scroll around type WMIC <ALIAS> LIST FULL.  To find out what methods can be called type WMIC <ALIAS> CALL.
 
Useful WMIC links: