We have been using the Microsoft Deployment Toolkit (MDT) in LTI/Lite Touch mode here at the University for a long time now. Why, we used it to deploy XP back when MDT still was called the Business Desktop Deployment Solution Accelerator (BDD). In this time, we have gone though several different driver management methods. Gone are the nightmare days of dealing with OEMSETUP files, $OEM$ directories, can elaborate “DriverPack” injection scripts for XP (thank goodness).
With the release of Vista, we moved from a PnP free-for-all model of driver detection. After Windows 8.0, we found we really needed to separate our drivers by operating system. Thus, we created Win7, Win8, and WinPE driver selection profiles.
But now we are finding that driver sprawl is becoming a major issue again. On many new systems we run though a seemingly successful deployment, but end up with a non-responsive touch screen, a buggy track pad, and (sometimes) a very unstable system.
Starting this week, we are trying a new hybrid driver management approach. We will create a driver folder for each computer model sold though our computer depot. I have developed a custom bit of VBScript to check to see if the hardware being deployed to is a known model. Driver injection will be restricted to this model if a match is found. The script contains additional logic to detect support for both Windows 7 and Windows 8 variants, and to select the most current drivers detected. Unknown models will fall back on the PnP free-for-all detection method.
Here is how it works…
- Create a new group in your OS deployment task sequence named “Custom Driver Inject”, or something similar. Grouping all actions together will allow easier transfer of these custom actions to other Task Sequences:
- Under this new group, add a new action of type “Set Task Sequence Variable”. Name the variable “TargetOS”,and set the value to the OS that you are deploying from this task sequence. You must follow the same naming convention that you use in your Out-of-box driver folder. I use Win(X), where (X) is the major OS version of the drivers in the folder. In this example, I have chose “Win8”:
- Add an action of type “Run Command Line”. Name this action “Supported Model Check”. Under the Command line field, enter “cscript “%SCRIPTROOT%\ZUVMCheckModel.wsf”. (We will import this script into the deployment share later on.)
- Add a sub-group named “Supported Model Actions”. Under the “Options” tab, add a condition of type “Task Sequence Variable”. Use the variable “SupportedModel”, the Condition “equals”, and the Value “YES”. (The SupportedModel variable gets set by the CheckModel script run in the previous step.):
- Under this new group, add a new action of type “Set Task Sequence Variable”. Name this task “Set Variable DriverGroup002”. Under “Task Sequence Variable”, set “DriverGroup002”, and set the value to “Models\%TargetOS%\%Model%”. (Note: You could use “DriverGroup001”, but I already am using this variable to hold a default group of drivers that I want added to all systems. The value “%TargetOS%\%Model%” defines the path to the driver group in the deployment share. If you use a different folder structure, you will need to modify this path.):
- Create a new task of type “Inject Drivers”. Name this task “Inject Model-Specific Drivers”. For the selection profile, select “Nothing”. Be sure to select “Install all drivers from the selection profile”. (NOTE: The dialog implies that we will be injecting only divers from a selection profile. In fact, this step will inject drivers from any paths defined in any present “DriverGroupXXX” Task Sequence variables.)
- Now, under our original Custom Driver Inject group, add a new task of type “Inject Drivers”. Choose from the selection profile “All Drivers”, or use a different fallback selection profile that suits the needs of your task sequence. This time, select “Install only matching drivers from the selection profile”:
Under the “Options” tab, add the condition where the “Task Sequence Variable” named “Supported Model” equals “NO”:
This step will handle injection of matching drivers into hardware models for which we do not have a pre-defined driver group.
- Optionally, you now can open the “CustomSettings.ini” file and add the following to your “Default” section:
(I have a “Peripherals” driver group configured which contains USB Ethernet drivers used in our environment. These are a necessity when deploying to hardware that does not have an embedded Ethernet port, such as the Dell XPS 12 and XPS 13. You also could add common peripherals with complicated drivers such as a DisplayLink docking station or a Dell touch screen monitor.)
- Add the “ZUVMCheckMedia.wsf” script to the “Scripts” folder of your deployment share. The code for this script is included below. I think the script should be fairly easy to adapt for your environment.
- Finally, structure your “Out-of-Box Drivers” folder to contain a “Models” folder, and a single folder for each matching hardware model in your environment. I get most of our driver collections from Dell:
(NOTE: Thanks Dell!)
The real challenge of maintaining this tree is in getting the model names right. Use “wmic computersystem get model” to discover the model string for any new systems in your environment. A table of a few current models I have been working with is included below.
Dell Marketing Model Name to WMI Name Translator Table:
- Dell XPS 12 (first generation) – “XPS 12 9Q23”
- Dell XPS 12 (second generation) – “XPS 12-9Q33”
- Dell XPS 13 (first generation) – “Dell System XPS L321X”
- Dell XPS 13 (second generation) – “Dell System XPS L322X”
- Dell XPS 14 – “XPS L421Q”
- Dell Latitude 10 – “Latitude 10 – ST2”
- VMware Virtual Machine – “VMware Virtual Platform”
- Microsoft Hyper-V Virtual Machine – “Virtual Machine”
A fun nuance we encountered last week was a Latitude E5430 model that contained “no-vPro” after the model number. Dell does not provide separate driver CABs for vPro/non-vPro models, so I added a regular expression test for Latitudes, and strip any cruft after the model number. There is one more problem down…
The following site contains a list of older model name translations:
As you can see, most Latitudes and Optiplexes follow sane and predictable model name conventions. I wish the same were true for the XPS.
Finally, I am indebted to the following sources for their generously detailed posts on driver management. Without their help, I doubt I would have been able to make this solution fly:
Jeff Hughes of the Windows Enterprise Support Server Core Team:
Andrew Barnes (aka Scriptimus Prime), whose posts on MDT driver management give the basics DriverGroups and model selection:
AND, of automating driver import into MDT (written for MDT 2012… some changes required for 2013):
The incredible Michael Neihaus, who in this post discusses the use of DriverGroups and Selection Profiles:
And finally Eric Schloss of the “Admin Nexus”, who give me the idea of developing a fallback for systems that do not match a known model. It was this key bit of smarts that gave me the confidence to move forward with a model-specific driver grouping strategy:
(NOTE: WordPress stripped off the WSF headers and footers from my script. These are the first three and last two lines in the script. If you copy from this post, please remember to place greater than and less than tags around these lines before running, as indicated in the comments.)
' Uncomment and wrap each of the following three lines in less than/greater than characters to convert them to tags. 'job id="ZUVMCheckModel" 'script language="VBScript" src="ZTIUtility.vbs"/ 'script language="VBScript" Option Explicit RunNewInstance '//-------------------------------------------------------- '// Main Class '//-------------------------------------------------------- Class ZUVMCheckModel '//—————————————————————————- '// Constructor to initialize needed global objects '//—————————————————————————- Private Sub Class_Initialize End Sub '//-------------------------------------------------------- '// Main routine '//-------------------------------------------------------- Function Main() ' //******************************************************* ' // ' // File: ZTIUVMCheckModel.wsf ' // ' // Purpose: Checks the model of this system against ' // a list of known machine models. Returns ' // TRUE if a matching model is detected. ' // ' // Usage: cscript ZUVMCheckModel.wsf /Model: [/debug:true] ' // ' //******************************************************* 'Use the following lines for debugging only. 'oEnvironment.Item("TargetOS") = "Win7" 'oEnvironment.item("DeployRoot") = "c:\local\mdt" 'oEnvironment.Item("Model") = "Latitude E6500 some annoying variation" 'End debug Params Dim aModels() 'Array of models taken from DriverGroups.xml Dim bOldDrivers 'Boolean indicating drivers present for an older OS version Dim i 'Generic integer for looping Dim j 'Generic integer for looping Dim iRetVal 'Return code variable Dim iMaxOS 'Integer representing the highest matching OS driver store Dim oRegEx Dim oMatch Dim match Dim oXMLDoc 'XML Document Object, for reading DriverGroups.xml Dim Root,NodeList,Elem 'Objects in support of oXMLdoc Dim sDGPath 'Path to DriverGroups.xml file Dim sInitModel 'String representing the initial value of ' oEnvironment.Item("Model") Dim sItem 'Item in aModels array. Dim sMaxOS 'OS Name of highest matching OS driver store Dim sOSFound 'OS Name for a given matching driver set. oLogging.CreateEntry "Begin ZUVMCheckModel...", LogTypeInfo 'Set the default values: oEnvironment.Item("SupportedModel") = "NO" iMaxOS = CInt(Right(oEnvironment.Item("TargetOS"),1)) 'wscript.echo "Default value for iMaxOS = " & iMaxOS bOldDrivers = false sInitModel = oEnvironment.Item("Model") 'wscript.echo "sInitModel value = " & sInitModel Set oRegEx = New RegExp oRegEx.Global = True oRegEx.IgnoreCase = True 'Modify the detected model name to handle known variations: oRegEx.pattern = "Latitude" if oRegEx.test(sInitModel) then oLogging.CreateEntry "Model is a Latitude. Cleaning up the model name...", LogTypeInfo oRegEx.pattern = " " set oMatch = oRegEx.Execute(sInitModel) 'wscript.echo "oMatch Count is: " & oMatch.count if oMatch.Count > 1 then i = oMatch.item(1).FirstIndex oEnvironment.Item("Model") = Left(sInitModel,i) 'wscript.echo """"&oEnvironment.Item("Model")&"""" end if end if 'Check for DriverGroups.xml file, which will contain the supported model list: iRetVal = Failure iRetVal = oUtility.FindFile("DriverGroups.xml", sDGPath) if iRetVal Success then oLogging.CreateEntry "DriverGroups file not found. ", LogTypeError exit function end if oLogging.CreateEntry "Path to DriverGroups.xml: " & sDGPath, LogTypeInfo 'Parse the DriverGroups.xml file: oLogging.CreateEntry "Parsing DriverGroups.xml...", LogTypeInfo Set oXMLDoc = CreateObject("Msxml2.DOMDocument") oXMLDoc.setProperty "SelectionLanguage", "XPath" oXMLDoc.load(sDGPath) Set Root = oXMLDoc.documentElement Set NodeList = Root.getElementsByTagName("Name") oLogging.CreateEntry "NodeList Member Count is: " & NodeList.length, LogTypeInfo 'oLogging.CreateEntry "NodeList.Length variant type is: " & TypeName(NodeList.Length), LogTypeInfo i = CInt(NodeList.length) - 1 ReDim aModels(i) 'Resize aModels to hold all matching DriverGroup items. 'oLogging.CreateEntry "List of Available Driver Groups:", LogTypeInfo i = 0 For Each Elem In NodeList if InStr(Elem.Text,"Models\") then aModels(i) = Mid(Elem.Text,8) 'Add text after "Models\" 'oLogging.CreateEntry aModels(i), LogTypeInfo i = i + 1 end if Next oLogging.CreateEntry "End Parsing DriverGroups.xml.", LogTypeInfo 'Loop through the list of supported models to find a match: oLogging.CreateEntry "Checking discovered driver groups for match to: " & oenvironment.Item("Model"), LogTypeInfo For Each sItem in aModels oLogging.CreateEntry "Checking Driver Group: " & sItem, LogTypeInfo i = InStr(1, sItem, oEnvironment.Item("Model"), vbTextCompare) 'wscript.echo TypeName(i) 'i is a "Long" number type. If i 0 Then oLogging.CreateEntry "Matching Model found.", LogTypeInfo j = InStr(sItem,"\") sOSFound = Left(sItem,j-1) 'wscript.echo "sOSFound = " & sOSFound if (InStr(1,sOSFound,oEnvironment.Item("TargetOS"),vbTextCompare) 0) then oLogging.CreateEntry "Drivers matching the requested OS are available. Exiting with success.", LogTypeInfo oEnvironment.Item("SupportedModel") = "YES" iRetVal = Success Main = iRetVal Exit Function end if if iMaxOS > CInt(Right(sOSFound,1)) then iMaxOS = CInt(Right(sOSFound,1)) 'wscript.echo "iMaxOS = " & iMaxOS sMaxOS = sOSFound bOldDrivers = true 'wscript.echo "sMaxOS = " & sMaxOS end if End If Next If bOldDrivers Then 'Run if sMaxOS is defined... set a boolean when this is defined and test against that? oLogging.CreateEntry "Model drivers were found for an OS older than the one selected...", LogTypeWarning oEnvironment.Item("SupportedModel") = "YES" oEnvironment.Item("TargetOS") = sMaxOS Else oLogging.CreateEntry "No matching drivers were found for this model.", LogTypeInfo End If oLogging.CreateEntry "End ZUVMCheckModel.", LogTypeInfo iRetVal = Success Main = iRetVal End Function End Class ' Uncomment and wrap each of the following two lines in less than/greater than characters to convert them to tags. '/script '/job