MDT Sub Task Sequences: Nesting
The idea of being able to maintain a single copy of a series of tasks for reuse across different task sequences or even within a single task sequence appealed to the programmer in me and I always kept it on my to-do list. Around the office, we joked that SOME DAY we would have Object Oriented Task Sequences (OOTS).
OOTSs have so fundamentally changed how you can build and maintain an MDT environment.
So, here goes. But first a few stipulations:
- I built the process to manage the task sequence items around the major milestones (OS deployment, etc.)
- Sub Task sequences must be built with a top level group - This is for simplicity sake, allowing the whole TS XML to be obtained from a top level node.
- Changes are not dynamic. Just Like with any task sequence, once the TS Engine has consumed the XML, no changes are propagated to running task sequences.
- This functionality is enabled with a combination of custom code additions to LiteTouch.vbs and LTIUtility.vbs and TS group nomenclature.
- Sub Task sequences should be built from Custom task sequence templates, no other templates have been tested.
- This process was build and tested for Network builds. (AKA not media).
- The processing of Sub Task sequences is recursive, so .. you can nest sub task sequences. I have never gone deeper than 3 levels though... heh TaskSequenceseption.
A sub task sequence is defined in a main task sequence using a MDT Group Object. The Naming convention of the group defines the link to the Sub Task Sequence.
Group names should follow this colon delimited format:
SUBTS:SUBTS_ID:DESCRIPTION
- The group name should always begin with "SUBTS". This is how the code identifies the Group as a Pointer for a Sub Task sequence.
- The SUBTS_ID should be populated with the ID of the task sequence you wish to use as a sub task sequence.
- This is a Description field for use in identifying the Sub TS or its location in the main TS. It is only here for Labeling and debugging purposes.
For our above reference in the Main task sequence, we can see how the SubTS_ID Position is populated with a corresponding Task sequence ID of our sub task sequence.
You must create a top level group in your Sub Task sequence. If a top-level group is not defined, the code will select the first group defined. This could lead to unintended consequences. Below you can see that for the Application install Sub Task sequence we have created a top-level group and made task sequences actions members of this group.
So, pretty simple stuff here… To make this all work, we need to add a few bits of code to our core MDT Scripts.
First let’s add the necessary functions to our ZTIUtility.vbs script from the Deployment Share scripts directory. The Utility Class starts around Line 1385 and ends around 4055 in the script included with MDT 2013 Update 2.
The following custom code should be added before the “End Class” Line (Thus making it part of the Utility Class).
Note: Be careful of word wrapping if you are cutting and pasting from this site: some of the logging lines are a bit long.
'*****************Custom Code*********************
Function InsertSubTS(sTSPath)
Dim xmlDoc, ElementList, bFoundSub, px
px = "CUSTOM CODE (SUB TS): "
oLogging.CreateEntry px & "STARTING" , LogTypeInfo
bFoundSub = False
Set xmlDoc = CreateObject("Microsoft.XMLDOM")
xmlDoc.setProperty "SelectionLanguage", "XPath"
xmlDoc.async = False
xmlDoc.load(sTSPath)
oLogging.CreateEntry px & "Running InsertSubTS with Path: " & sTSPath , LogTypeInfo
Set xmlDoc = ParseTS(xmlDoc)
xmlDoc.Save(sTSPath)
If bFoundSub = False Then
oLogging.CreateEntry px & "Completed Processing TS for SubTS injection - None found", LogTypeInfo
End If
End Function
Function ParseTS(xmlTS)
Dim i, sTS, px
Dim ElementList : Set ElementList = xmlTS.SelectNodes("//*")
px = "CUSTOM CODE (SUB TS): "
For i=0 TO ElementList.length - 1
If InStr(UCase(ElementList.item(i).getAttribute("name")), "SUBTS:") Then
oLogging.CreateEntry px & "Found a Reference to a SUB TS: " & ElementList.item(i).getAttribute("name") , LogTypeInfo
If UBound(Split(ElementList.item(i).getAttribute("name"),":")) > 1 Then
oLogging.CreateEntry px & "Group Name formated correctly. Looking for Sub TS " & Split(ElementList.item(i).getAttribute("name"),":")(1) , LogTypeInfo
Set sTS = GetSubTS(Split(ElementList.item(i).getAttribute("name"),":")(1))
If Not sTS Is Nothing Then
ParseTS(sTS)
ElementList.item(i).appendChild(sTS)
oLogging.CreateEntry px & "Successfully Injected Sub TS" , LogTypeInfo
Else
oLogging.CreateEntry px & "FAILED to return a valid Sub TS - Injection Failed" , LogTypeError
End If
Else
oLogging.CreateEntry px & "SUB TS Was not defined correctly in Calling TS - ensure your Group Pointer is named 'SUBTS:<SUBTS ID>:Description' " , LogTypeInfo
End If
End If
Next
Set ParseTS = xmlTS
End Function
Function GetSubTS(SubTSName)
Dim xDoc, kids, k, eList, e, px
px = "CUSTOM CODE (SUB TS): "
Set eList = Nothing
Dim oFS : Set oFS = CreateObject("Scripting.FileSystemObject")
Dim sSubPath : sSubPath = oEnvironment.Item("DeployRoot") & "\Control\" & SubTSName
oLogging.CreateEntry px & "Checking for SubTS Location at: " & sSubPath, LogTypeInfo
If oFS.FolderExists(sSubPath) Then
oLogging.CreateEntry px & "Folder location was found", LogTypeInfo
Set xDoc = CreateObject("Microsoft.XMLDOM")
xDoc.setProperty "SelectionLanguage", "XPath"
xDoc.async = False
xDoc.load(sSubPath & "\ts.xml")
oLogging.CreateEntry px & "Reading SUB TS XML Data", LogTypeInfo
Set eList = xDoc.SelectSingleNode("//sequence/group")
If Not eList is Nothing Then
oLogging.CreateEntry px & "Top Level Group Found: parsing data back for Injection", LogTypeInfo
Set GetSubTS = eList
Else
oLogging.CreateEntry px & "No Top Level group was defined in the Sub Task Sequence referenced" , LogTypeError
Set GetSubTS = Nothing
End If
Else
oLogging.CreateEntry px & " Couldn't find SUBTS Folder with name specified in the Calling Task Sequence", LogTypeError
End If
End Function
'*****************End Custom Code**********************
Next, we'll make a quick change to the LiteTouch.vbs script. Find the block of code that deals with the TS.XML file. This should be around line 795. Modify this block of code with a single line to call our insertSubTS function. This should be completed before the comment noting “Copy the Variables .dat file to where the Task sequence can find it”.
The block of code should look like this when completed.
If not oFSO.FileExists(sTSPath & "\TS.XML") then
' Copy the appropriate TS.XML file
oUtility.VerifyPathExists sTSPath
oLogging.CreateEntry " -- Copying " & oEnvironment.Item("DeployRoot") & "\Control\" & oEnvironment.Item("TaskSequenceID") & "\TS.XML to " & sTSPath, LogTypeInfo
oFSO.CopyFile oEnvironment.Item("DeployRoot") & "\Control\" & oEnvironment.Item("TaskSequenceID") & "\TS.XML", sTSPath & "\TS.XML", true
'*********************CUSTOM CODE*********************************************
oUtility.InsertSubTS(sTSPath & "\TS.XML")
'*********************END CUSTOM CODE******************************************
' Copy the VARIABLES.DAT to where the task sequence can find it
If oFSO.FileExists(oEnvironment.PersistPath & "\" & oEnvironment.PersistFile) then
oLogging.CreateEntry "Copying " & oEnvironment.PersistPath & "\" & oEnvironment.PersistFile & " to " & sTSPath & "\VARIABLES.DAT", LogTypeInfo
oFSO.CopyFile oEnvironment.PersistPath & "\" & oEnvironment.PersistFile, sTSPath & "\VARIABLES.DAT", True
End If
End If
UPDATE YOUR DEPLOYMENT MEDIA ! - to copy the new versions of the scripts to your PE Wim.
That's It! I hope this proves useful for others.