Dynamic application detection with Lite Touch

I've given a variety of presentations at past TechEd and MMS events (as well as our own internal TechReady conferences) talking about making deployments more dynamic.  One of the scenarios discussed was dynamic package installation with ConfigMgr 2007.  The idea is that you want to install the same software in the new OS that was present in the old OS (assuming that list is different on each computer, most always the case), so the user can be immediately productive once the deployment is complete.  The basic steps:

  • Use hardware inventory to detect what software is present on the machine, using Add/Remove Programs (Programs and Features for you Windows Vista-types) data.
  • Translate Add/Remove Programs entries to package ID and program name references (XXX00001:Program Name).
  • Tell the task sequence to install the resulting list of package ID and program name pairs.

Something similar can be done with Lite Touch, but you can't use inventory.  Instead, you just have to inspect the machine to see what is present and select the appropriate applications based on what you found.  (Granted, this only works for refreshes because you need access to the current OS to get the list of apps.  This isn't the case with the ConfigMgr solution, since the inventory is already on the server.  But that's OK in this case - if you want more capabilities, you really need to look at moving to ConfigMgr anyway.)

First, you need a way to detect the presence of applications.  The same mechanism used with ConfigMgr works here too: look at the Add/Remove Programs entries.  But where can you specify the ARP key name that should be associated with an application?  MDT (and BDD 2007 before that) provides a place to do that:


Just specify the key name in the "Uninstall registry key name" box.  (Yes, for MSI-based installers this will be a GUID.)  Normally this field is used during the deployment to keep MDT from trying to install an application that is already present on the machine, but we can leverage it here too. 

Then, you need to tell MDT what to do with the information.  That requires a little scripting.  Save the VBScript code below (watch out for line wrapping) to a file called LTIAppDetect.vbs and place that file in the \Distribution\Scripts folder:

Option Explicit

Function UserExit(sType, sWhen, sDetail, bSkip)

    Dim oXMLDoc, oNode, oKey
    Dim sValueName, sKey, sValue
    Dim oApplications

    ' Only do this before processing the rule contents

    If sWhen <> "BEFORE" then
        UserExit = Success
        Exit Function
    End if

    ' Load the applications XML File

    Set oXMLDoc = CreateObject("MSXML2.DOMDocument")   
    oXMLDoc.load oEnvironment.Item("ResourceRoot") & "\Control\applications.xml"

    ' Enumerate the applications

    For each oNode in oXMLDoc.selectNodes("//application")

        ' Retrieve the uninstall key node if present

        Set oKey = oNode.selectSingleNode("UninstallKey")

        ' If one was specified, look for the key in the registry

        If not (oKey is nothing) then

            ' Get the key name

            sKey = oKey.Text

            ' Check if the registry key exists by looking for well-known values

            For each sValueName in Array("DisplayName", "UninstallString", "QuietUninstallString")

                sValue = empty
                on error resume next
                    sValue = oShell.RegRead("HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\" & sKey & "\" & sValueName)
                on error goto 0

                If IsEmpty(sValue) then
                    On error resume next
                        sValue = oShell.RegRead("HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" & sKey & "\" & sValueName)
                    On error goto 0
                End if

                ' If a value was found, add the application's GUID to the list so it gets pre-selected by the wizard

                If not isempty(sValue) then

                    oLogging.CreateEntry "Uninstall registry key found for application " & oNode.selectSingleNode("Name").Text & ", application is present and will be reinstalled.", LogTypeInfo

                    ' Add the GUID if it doesn't already exist in the list

                    Set oApplications = oEnvironment.ListItem("Applications")
                    If not oApplications.Exists(oNode.Attributes.getNamedItem("guid").value) then
                        oApplications.Add oNode.Attributes.getNamedItem("guid").value, ""
                        oEnvironment.ListItem("Applications") = oApplications
                    End if

                    Exit For

                End if


            oLogging.CreateEntry "UninstallKey not specified for application " & oNode.selectSingleNode("Name").Text, LogTypeInfo

        End if


    UserExit = Success

End Function

Now you need to tell MDT to call the script.  Because we want to run this before displaying the Deployment Wizard, we can't add it to the task sequence.  Instead, we'll set this up as a user exit to be called during the gathering process.  Modify CustomSettings.ini to include this:


(Don't remove anything, just add the one new UserExit line).  Now, when you perform a Lite Touch deployment, ZTIGather.wsf will call the LTIAppDetect.vbs user exit script, it will find what applications you have currently (at least those with "uninstall registry key" matches in the MDT applications list), and it will update the "Applications" list to include those applications.  These will then show up in the Deployment Wizard pre-selected, and as a result will be installed by the task sequence once the new OS is installed.  (If the user really doesn't want them any more, they can de-select them in the wizard.)


A couple of notes:

  • Unlike with the ConfigMgr dynamic package solution which uses a SQL table to translate uninstall registry key names to package:program references, this setup doesn't easily allow you to specify something like "If a machine has Acrobat Reader 7 or 8, install Acrobat 8" because you can't specify multiple uninstall registry key names in the application configuration.  If you wanted to work around this, you could put the key names in the comment field, one per line, and then modify the script to use those.
  • If you don't want the user to be able to de-select the applications in the wizard, change the script so that it populates the "MandatoryApplications" list instead of the "Applications" list.  If you do this, the items will show up checked and disabled in the wizard.