Share via


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:

  1. I built the process to manage the task sequence items around the major milestones (OS deployment, etc.)
  2. 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.
  3. 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.
  4. This functionality is enabled with a combination of custom code additions to LiteTouch.vbs and LTIUtility.vbs and TS group nomenclature.
  5. Sub Task sequences should be built from Custom task sequence templates, no other templates have been tested.
  6. This process was build and tested for Network builds. (AKA not media).
  7. 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

  1. The group name should always begin with "SUBTS". This is how the code identifies the Group as a Pointer for a Sub Task sequence.
  2. The SUBTS_ID should be populated with the ID of the task sequence you wish to use as a sub task sequence.
  3. 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.