Parallel Task Execution For ILM/FIM With VBScript

In my quest for a better way to customize run profiles for FIM, I thought I’d utilize the scripting method since I’ve heard there are problems running the MASequencer resource kit utility on FIM2010. So, I turned to the tried-and-true VBScript. I looked at PowerShell, but since I’m much more familiar with VB, PowerShell looked incomprehensible to me for what I wanted to do.

I want to multithread a script. Can it be done? Well, yes as it turns out.

Visiting the Microsoft Script Repository, I was able to find a sample script which would launch another command in parallel. What it does is fairly simple. It loops through a script multiple times until the maximum allowed thread count is reached and then waits until one of those threads is done before launching another one.

I changed it a bit.

What I needed it to do was to process delta imports (or full) in parallel, wait until the last one was finished, then launch delta sync (or full) in serial until all were done, then launch an export for each MA simultaneously. Now with the MASequencer, you can do the first two easily but attempting the second requires you to have two separate configurations and launch them separately. Not painful by any means, but slightly cumbersome. Of course, my new method isn’t any more elegant in terms of executing separate files, but I think it’s a little more unified in some ways.

In order to prepare the environment for running profiles this way, you must first export each stage of each run profile that you want to run into a VBScript. Highlight the proper run profile and click the “Script” button below and save as type *.VBS for each of your initial staging steps. Do this in the order you want them to run and name them “DI1.VBS”, “DI2.VBS”, etc. Do this also for your Export run profiles and name them similarly: “E1.VBS”, “E2.VBS”, etc. Save all scripts to a single directory, let’s call it “C:\Runs”.

image

(NOTE: if one of your delta import run profiles contains a sync step, make sure you do not overlap with another synchronization of the same object type or you will likely get database locking errors.)

The scripts will look something like this (I have removed all the comment/remark lines):

Const PktPrivacy = 6
Set Locator = CreateObject("WbemScripting.SWbemLocator")
Set Service = GetObject("winmgmts:{authenticationLevel=PktPrivacy}!root/MicrosoftIdentityIntegrationServer")

Set MASet = Service.ExecQuery("select * from MIIS_ManagementAgent where Guid = '{9DFD37D9-7852-4FC7-8F23-2FDC6E87250A}'")

for each MA in MASet
MA.Execute("Delta Stage")
next

Personally, I like to do all of my imports separate from the syncs, but there might be reasons why this won’t be possible in all cases.

I then took the sample from the link at the top and modified it and came up with this one, which actually works:

Const PktPrivacy = 6

Set Locator = CreateObject("WbemScripting.SWbemLocator")

Set Service = GetObject("winmgmts:{authenticationLevel=PktPrivacy}!root/MicrosoftIdentityIntegrationServer")

Dim cThread
Dim i
Dim count
Dim scriptName
count = 0

Set oWshShell = WScript.CreateObject("WScript.Shell")

Set MASet = Service.ExecQuery("select * from MIIS_ManagementAgent")

for each MA in MASet
count = count + 1
next

' Create threads

i = 0
cThread = 0

redim aExec(count)

' Run imports in parallel

for each MA in MASet
set aExec(i) = oWshShell.Exec("wscript.exe DI" & i + 1 &".vbs")
cThread = cThread + 1
i = i + 1
next

WaitForThreads(0)

' Run sync routines in serial

for each MA in MASet
MA.Execute("Delta Sync")
next

i = 0
cThread = 0

' Run exports in parallel

for each MA in MASet
set aExec(i) = oWshShell.Exec("wscript.exe E" & i + 1 &".vbs")
cThread = cThread + 1
i = i + 1
next

wscript.quit(0)

' Subroutine for checking for completed threads

Public Sub WaitForThreads(byVal Max)

    dim n
do while cThread > Max
n = 0
cThread = i 
wscript.sleep 200
do while n < i
if aExec(n).Status <> 0 then
cThread = cThread - 1
end if
n = n + 1
loop
loop
end sub

Save this file into the same directory you saved the run profile scripts into. In this case, “C:\Runs”. This will be the main execution shell.

There are several sections. The first loop runs the delta import files starting with “DI” in sequence. The second runs a delta sync for each MA in the metaverse (you can configure this like the delta stage profiles section if you wish even more control, but for simplicity in my development environment, I’ve just done it this way since my sync run order is irrelevant in this case). The third runs the export, while the fourth is a subroutine that waits for thread completion.

I call the WaitForThreads routine with a zero so that all threads are complete before continuing on to the next section. You could even use the routine to limit the number of concurrent imports or exports as well by inserting “WaitForThreads(4)” into the “for each MA” loop, where 4 would be the number you want running at once.

Now, you may be wondering why the import and export sections launch a separate command and script instead of using the MA.Execute method. I tried that at first, but that method launches the MA run profile, but does not release it or return the proper return code that I need to process the threads.

The most important feature of running things this way is the flexibility. This script is completely customizable. I will likely be using this method at my next customer engagement.

I hope this helps you in some small way.

[UPDATE 11/10/10]: I've found a bug in the code. I changed the WaitForThreads routine and added the line "cThread = i". What this does is reset the numer of threads to the number of running processes so that the script can run through again and count the terminated processes. Previously, this would end prematurely by cumulatively counting the same terminated thread multiple times until the thread count equaled "Max" (or 0 in my specific case). It works now with the reset line.