Category Archives: System Imaging and Deployment

Migrating to the SCCM UDI for OSD, part 6c: Operations – Applications

Continued from:
http://blog.uvm.edu/jgm/2015/03/12/sccm-udi-6b-opsos/

The following is a procedure for updating Application information in the UVM UDI environment.  Use it as a template for your own operations:

  1. When adding a new application (or app version): Categorize the application:
    1. In the SCCM console under Software Library->Application Management->Applications, select the new application and get “properties”.
    2. Under the “General Information” tab, Click “Select” next to the “Administrative Categories”.  Add the new application to an existing App Category or create a new category, as appropriate.  Applications that are categorized will be added to UDI. Non-categorized applications will not be available.
  2. Update the UDI config files:
    1. Run C:\local\scripts\CM-TaskSequences\build-UDIAppList.ps1.
    2. Locate the “MDT 2013 Files” package in the SCCM console under Software Library->Application Management->Packages, and run an “Update Distribution Points” action.  Verify that distribution was successful before proceeding.

And that is the whole story of the SCCM/UDI migration here at UVM to date. Clearly there is room for improvement. I will try to keep this series updated with revisions as we make them. As always, I hope that these posts will be of help to others in similar situations. If any of the code in this series makes its way into your environment, please let me know. I also am happy to answer any [short] questions about the topics covered here.

-J. Greg Mackinnon | Systems Administrator | University of Vermont
Phone: 802-656-8251 | Web: http://www.uvm.edu/~jgm


Series Index:

Migrating to the SCCM UDI for OSD, part 6b: Operations – Operating Systems

Continued from part 6a:
http://blog.uvm.edu/jgm/2015/03/12/sccm-udi-6a-opsdrivers/

The following is a procedure for updating Operating System image data in UDI that is specific to the UVM environment.  Use it as a template for your own operations:

      1. Add the new WIM image to SCCM. The WIM must contain only one OS image (e.g. at index 1).
      2. Run c:\local\scripts\CM-TaskSequences\build-udiInfoFiles.ps1.
        (This make a CSV files available to the UDI client that matches the OS Image Name to the version of the OS within the image.) (Required for driver injection.)
      3. Run c:\local\scripts\CM-TaskSequences\build-udiImageList.ps1.
        (This updates the UDI XML-based control file that generates the OS selection options to the end-user in the UDI Wizard.)
      4. Run c:\local\scripts\Update-OSApplyTaskSequence.ps1. When prompted, provide the name of the UDI task sequence to be updated.
        (This script updates the task sequence so that the OS selected in the UDI Wizard can be applied to the UDI target machine.)
      5. Locate the “MDT 2013 Files” package in the SCCM console under Software Library->Application Management->Packages, and run an “Update Distribution Points” action.  Verify that distribution was successful before proceeding.
        (This makes the new CSV info files and Wizard configs available to end-users.)

Next:
http://blog.uvm.edu/jgm/2015/03/12/sccm-udi-6c-opsapps/


Series Index:

Migrating to the SCCM UDI for OSD, part 6a: Operations – Drivers

Continued from part 5:
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-5-quirks/

The following is a operational procedure for updating applications, specific to the UVM environment. Use it as a template for managing your own procedures:

  1. If adding a new model: Establish the driver package source:
    1. Download the driver packages from Dell (or Lenovo). You will need different packages for each supported OS.
    2. Determine the WMI model name:
      1. Wmic computersystem get model -or-
      2. Get-WmiObject -class win32_computersystem
        (or more verbosely get-wmiobject -namespace root\cimv2 -class win32_computersystem -property model)
    3. Create a new folder under the matching OS version folder (i.e. Win7 or Win8) within the driver import source directory: “\\confman3\sources\drivers\import“. The name of this new folder must match the model information discovered in step 2. Extract the new drivers into this folder.
  2. If updating a model: Update the driver source:
    1. Locate the source folder for the driver package to update. Remove all contents of the directory including the “.hash” file in the root.
    2. Extract the new drivers into this folder.
  3. If updating a model: Cleanup existing drivers in SCCM:
    1. Find the package to be updated in the SCCM management console. Delete it.
    2. Right-click and get the properties on any driver in the SCCM console. Call up the list of driver categories, and delete the category for the driver to be updated.
  4. Import the drivers from source:
    1. Run C:\local\scripts\CM-Drivers\ImportDrivers.ps1 to import the new drivers into the SCCM environment. The script will create new packages and driver categories for each new folder that you created.
    2. Wait.
  5. Distribute the drivers:
    1. Refresh the SCCM console to reveal the new driver packages and categories.
    2. Right-click the driver package and select “Distribute Content”. Distribute the drivers to “Confman2″.
    3. Monitor the distribution process in the bottom pane of the SCCM console. Make sure distribution succeeds before proceeding.
  6. If adding a model/package: Update the MDT files with the new driver information:
    1. Run C:\local\scripts\CM-TaskSequences\build-UdiInfoFiles.ps1 on Confman3
    2. Locate the “MDT 2013 Files” package in the SCCM console under Software Library->Application Management->Packages, and run an “Update Distribution Points” action. Verify that distribution was successful before proceeding. This will publish a new list of drivers packages to the UDI clients.
  7. If adding a model/package: Update the OS Installation Task Sequence:
    1. Run C:\local\scripts\CM-TaskSequences\Update-DriverInjectionTaskSequence.ps1. Specify the name of the Task Sequence to update when prompted, or provide the name using the “-name” parameter. This script will update the Task Sequence to allow for injection of the new driver package, if one is available for the current model.
  8. If adding or updating WinPE, Peripheral, or “Other” drivers: Update the boot media:
    1. In the SCCM console under Software Library->Operating Systems->Drivers, select “Saved Searches”, then select WinPE 64-bit or WinPE 32-bit. Once the drivers have been filtered, Select All, and then right-click and select Edit->Boot Images. Select the MDT boot image for your architecture (32-bit or 64-bit).
    2. Select “Task Sequences” in the console, then select “Create Task Sequence Media”:
      1. Select to create “Bootable Media”
      2. Select “Site Based Media”
      3. Select “CD/DVD Set”, and specify the path: \\CONFMAN3\sources\os\boot\mdt\iso\UDI-x64.iso
      4. Clear “Protect media with password”, and select “Import PKI certificate”. Select a certificate and password from our super-secret source location. Select “Allow user device affinity with auto-approval”. (If the certificate is expired, see the separate procedure below for generating a new workgroup computer certificate.)
      5. For boot media, select the MDT boot image that you updated in part 8a. For distribution point, select CONFMAN2. For Management Point, select CONFMAN3.
      6. Complete the wizard, and then distribute the boot media to the usual locations (\\files\software\deploy).

As noted above, you need a custom Workstation Authentication certificate to generate the boot media.  If your certificate has expired or you want to generate a new certificate for any other reason, use the following procedure, adopted from:

http://ittherapist.net/2014/01/16/sccm-2012-r2-os-deployment-with-pki-https/

  1. From a workstation that has access to your Certificate Authority web interface, open Internet Explorer using your admin account, and access:

    https://caServer.domain.edu/certsrv/

    Logon as a user with rights to generate workgroup certificates.  Currently this is scoped to high-level admins in our organization.

    1. Add the site to the “Trusted sites” zone in your Internet Settings Control Panel.
    2. Activate “compatibility view” if your CA is still running on Server 2008 R2, otherwise the required ActiveX controls will not load.
    3. Select “Request a certificate”.
    4. Select “Create and submit a request to this CA”.
    5. Select the template: “UVM – SCCM Workgroup or WinPE Client Authentication”
      • Note: This is a lightly-modified copy of the stock “Workstation Authentication” certificate template.  As per MS requirements, the certificate has been forced to use the legacy “Server 2003″ certificate format, not the Vista+ “CNG” format.
      • An Enterprise Admin can add permissions to the template to allow enrollment rights for additional users/groups, if necessary.
    6. Fill in the identifying information and friendly name for the certificate.  Make sure to select the option to “Mark keys as exportable”, then click “Submit”.
    7. When prompted, install the certificate.  The certificate will be installed into the “Personal” or “My” store for the user running Internet Explorer.
  2. Run MMC.EXE as the user who requested the certificate:
    1. Add the “Certificates” snap-in for the current user.
    2. Navigate to the “personal” store.  Locate the new certificate.
    3. Right-click the certificate and select All Tasks -> Export.
      1. In the certificate export wizard, select the option “Yes, export the private key.”
      2. Select Personal Information Exchange – PCKS #12 (.PFX), and ensure that “Include all certificates in the certification path if possible” AND “Export all extended properties” are checked, then click Next.
      3. Type in a password and confirm it in the boxes provided on the Password screen, then click Next. (Save this password for later use)
      4. Browse for a location to which to export the certificate.  Make sure that it is somewhere accessible from SCCM, give it a name (i.e. ‘WinPE-Cert.pfx’) and click Save.
  3. Use this new certificate file when completing step 8.4, above.

Next:
http://blog.uvm.edu/jgm/2015/03/12/sccm-udi-6b-opsos/


Series Index:

Migrating to the SCCM UDI for OSD, part 5: Quirks

Continued from part 4:
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-4-apps/

In the previous parts of this blog series I have shown how PowerShell and VBScript can be used to automate Driver, Application, and Operating System selection and installation in MDT/UDI. In this final post I will discuss a few final UDI quirks that we worked though without using scripts.

UDI Boot Media:

We experienced an odd problem during our OS Deployments when booting from SCCM/UDI media. The deployment process starts, as documented, with a dialog allowing the user to configure the networking environment for OS deployment, followed by a Task Sequence selection dialog. After selecting the task sequence, we expected some scripts to run, followed by the initiation of the UDI Wizard dialogs. Instead what we found was that UDI would first format an partition the local drive, copy a new WinPE environment to the drive, and then restart into WinPE running from local disk before brining up the UDI Wizard. This created a substantial delay between Task Sequence selection and the start of the Wizard. My impression was that users would find this delay unacceptable.

Our MS consultant suggested that this was happening because the revision of the UDI/WinPE environment on our boot media did not match boot image referenced by our active task sequence. To test this, we rebuilt all of the boot images in our environment from the Windows 8.1 ADK sources, then created a new MDT boot image from the ADK image, created a new task sequence referencing the ADK image, and finally generated new UDI Boot Media with the MDT boot image as a source. After completing these actions and updating our distribution points, we were able to get the UDI Wizard to start after Task Sequence selection without first requiring a reboot.

Improper OS Drive Letter Assignment

In our first deployments, we found that at the end of the Task Sequence, our operating system was left running on a D: or E: drive, instead of the usual “C:” drive. Knowing full well that this could cause massive end user confusion, I set out to correct this problem.

I was able to find a few potential solutions in Michael Neihaus’s always dependable TechNet blog:
http://blogs.technet.com/b/configurationmgr/archive/2014/04/28/how-to-ensure-that-windows-installs-on-c-during-a-system-center-2012-configuration-manager-osd-task-sequence.aspx

After weighing the options, I chose to try the “Delete Mounted Devices from registry” option. This required me to import a custom task sequence into our SCCM instance, and then copy/paste the required Task Sequence steps into our production Task Sequence. This approach instantly was successful, so I am sticking with it. Thanks again, Michael.

Failure of UserExit Script Processing

Under MDT/LTI, we made use of the “User Exit Script” capabilities of MDT to generate unique default computer names for UDI clients. Cursory inspection of the docs suggested that User Exit script functionality also is supported under SCCM/UDI, so I decided to transplant our script. However, after doing so I found that the default presented in the UDI Wizard was #GenUniComp()#, rather than a generated computer name.

#GenUniComp()# is the value assigned in our CustomSettings.ini file for the OSDComputerName variable. However, MDI/UDI is supposed to “exit” any code placed between the hash tags (#) out to the Windows Scripting Host. “GenUniComp” is the name of a VBScript function that I defined in our “ZUVMUserExit.vbs” User Exit script. The function works well enough under MDT/LTI, so what is going wrong in UDI?

To diagnose this problem, it helps to know a bit about how User Exit scripts are processed. “UserExit” is set as a variable in the CustomSettings.ini for your environment. This variable is detected and processed by the ZTIGather.wsf script that is run several times during each MDT/LTI and SCCM/UDI deployment. To troubleshoot problems with the script, you should look to the SCCM/UDI log files generated by ZTIGather.wsf. When I examined the ZTIGather logs, I could find no reference at all to the UserExit variable getting processed by the ZTIGather script. The next logical question is, why not?

Logically, the problem is not likely to be caused by an error in the script. First, the same script works just fine under MDT/LTI. Additionally, the script uses the MDT logging functions, so if it had executed, we would have expected to see at least some log output. I thus assumed that the script was not getting executed at all. I started looking at the ZTIGather logs from the top, and found reported “processing [Settings]…” followed immediately by “processing [Default]…”. Interestingly, I had defined the UserExit variable under the [Settings] section of CustomSettings.ini, which worked find under MDT/LTI. However, the SCCM version of ZTIGather appears to process only “Priority” and “Properties” settings under the [Settings] section. My UserExit definition was getting ignored because it was in the wrong place. After moving the “UserExit” definition to the [Default] section, UDI started to process the GenUniComp() function of my User Exit script just as it had under MDT/UDI.


In the final section of this series, I will present operational instructions for the use of these scripts in our environment. These documents are tailored for the UVM environment, but might serve as a template for instructions at other sites.

Next:
http://blog.uvm.edu/jgm/2015/03/12/sccm-udi-6a-opsdrivers/


Series Index:

Migrating to the SCCM UDI for OSD, part 4: Applications

Continued from part 3b:
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-3b-os/

Previously in this series I demonstrated how you can manage Operating System selection and application in UDI, and one (rather complicated) method for managing drivers in SCCM/UDI. To complete our UDI experience, I now present a handy script for automation of populating select SCCM applications into UDI.

There is some conventional wisdom floating around out there in the ‘tubes that you should not deploy the new SCCM 2012-style “Applications” in SCCM OSD Task Sequences. Instead, it is asserted that you should use only traditional application “Packages”. Since we have developed all or our application installer using the new “Application” objects, I have chosen to ignore this guidance. Fortunately, it appears that the earlier bugs that led to dispensing of this advice have been corrected in the 2012 R2 CU2-CU3 timeframe.

The script below will make use of Configuration Manager application tags to determine which Applications should be populated into UDI. Additionally, the tags are used to generate application groups in the UDI wizard. If you don’t want an application to appear in UDI, don’t tag it. Applications can have multiple tags, and thus can end up getting defined more than once un UDI. This does not appear to cause any problems during deployment.

This script will re-write the entire UDI designer “.app” file, which is an XML-formatted document that defines all of the applications and groups that will be displayed during the UDI Wizard (the default name for this file is ‘UDIWizard_Config.xml.app’). Because I am writing out the entire file, I chose to use the System.Xml.XmlTextWriter .Net Framework class to do the heavy lifting for me. I probably could have used System.Xml.XmlDocument (which I used in part 3 of this series for updating the main UDI XML control file), but the XmlTextWriter seemed to be a more direct route to getting the job done in this case.

Note that in this code I am again using the SCCM PowerShell cmdlets. In this case, I am using Get-CMCategory and Get-CMApplication. These commands easily could be replaced with simple WMI queries (as seen in part 3 of this series). I also am using some SCCM managed code. I call the “Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer” class in order to use the “Deserialize” method. This method converts the large volume of data in the SDMPackageXML attribute of the application object into somewhat more accessible PowerShell objects.

To use this script in your environment, you will need to update the $outPath, $CMSiteCode, and $CMBinPath variables to match your environment. After running the script, you will need to redistribute the content of your MDT Files package to your distribution points. You UDI clients will read the new data as soon as it is made available on your DP.

# build-UDIImageList.ps1
# J. Greg Mackinnon
# Created: 2015-01-26
# Updated: 2015-03-17 - Added host output to indicate task progress.  Removed block comments for blogging clarity.
# Populates the UDI Configuration Wizard XML APP file with all categorized applications gathered 
# from Configuration Manager.  
# Requires: a local installation of the Configuration Manager administration tools.
#   Modify $outPath, $CMSiteCode, and $CMBinPath to match your environment.
Set-PSDebug -Strict

[string] $outPath = 'O:\sources\os\mdt\files\Scripts\UDIWizard_Config.xml.app'

[string] $CMSiteCode = 'UVM'
[string] $CMBinPath = 'F:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\'
[string] $CMModName = 'ConfigurationManager.psd1'
[string] $CMModPath = Join-Path -Path $CMBinPath -ChildPath $CMModName

[string] $CMDrive = $CMSiteCode + ':\'

Import-Module -Name $CMModPath | out-null
Push-Location $CMDrive

#Gather current "Administrative Categories" used to classify Applications, capture to an Array of strings:
write-host "Gathering Application Categories..."
[String[]] $CMAppGroups = Get-CMCategory -CategoryType AppCategories | % {$_.LocalizedCategoryInstanceName}

[int] $appCount = 1

# Output XML requires: 
    # DisplayName (map to LocalizedDisplayName)
    # Name, 
    # Guid (with ScopeID, ApplicationGUID... maps to "ModelName" CMApplication Property), 
    # description (optional? map to LocalizedDescription), 
    # type (deployment type?), 
    # ProductID (which can be found in the sdmpackagexml.deploymenttypes[#].installer.productcode)
# All separated into "ApplicationGroup" stanzas, with name= attributes, I think we can map this to "LocalizedCategoryInstanceNames"


#$xmlDoc = New-Object System.Xml.XmlDocument # Note this is the same as the 1 type accelerator
$utf8 = New-Object System.Text.UTF8Encoding
# Create The Document Writer:
$XmlWriter = New-Object System.XMl.XmlTextWriter($outPath,$utf8)
$xmlWriter.Formatting = "Indented"
$xmlWriter.Indentation = "5"
$xmlWriter.WriteStartDocument()
$xmlWriter.WriteStartElement("Applications")
$xmlWriter.WriteAttributeString('RootDisplayName','Applications')

write-host "Gathering CM Applications..."
$CMApps = Get-CMApplication | Select-Object -Property LocalizedDisplayName,LocalizedDescription,ModelName,LocalizedCategoryInstanceNames,IsLatest,IsExpired,IsSuperseded,SDMPackageXML | ? {$_.IsLatest -and ($_.IsExpired -eq $false) -and ($_.IsSuperseded -eq $false)}

write-host
foreach ($group in $CMAppGroups) {
    $xmlWriter.WriteStartElement('ApplicationGroup')
    $xmlWriter.WriteAttributeString('Name',$group)
    write-host "Processing group: $group" -ForegroundColor yellow
    
    foreach ($app in $CMApps) {
        if ($app.LocalizedCategoryInstanceNames.contains($group)) {
            write-host "    Adding application " $app.LocalizedDisplayName -ForegroundColor cyan
            $xmlWriter.WriteStartElement('Application')
            $xmlWriter.WriteAttributeString('DisplayName',$app.LocalizedDisplayName)
            $xmlWriter.WriteAttributeString('State','enabled')
            $xmlWriter.WriteAttributeString('Id',[string]$appCount)
            $xmlWriter.WriteAttributeString('Name',$app.LocalizedDisplayName)
            $xmlWriter.WriteAttributeString('Guid',$app.ModelName)

            $appXml = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::Deserialize($app.SDMPackageXML,$true)

                $xmlWriter.WriteStartElement('Setter')
                $xmlWriter.WriteAttributeString('Property','description')
                $xmlWriter.WriteEndElement()

                $xmlWriter.WriteStartElement('Dependencies')
                $xmlWriter.WriteEndElement()

                $xmlWriter.WriteStartElement('Filters')
                $xmlWriter.WriteEndElement()

                $xmlWriter.WriteStartElement('ApplicationMappings')
                
                    $xmlWriter.WriteStartElement('Match')           
                    $xmlWriter.WriteAttributeString('Type','WMI')
                    $xmlWriter.WriteAttributeString('OperatorCondition','OR')
                    $xmlWriter.WriteAttributeString('DisplayName',$app.LocalizedDisplayName)
                        $xmlWriter.WriteStartElement('Setter')
                        $xmlWriter.WriteAttributeString('Property','Name')
                            $xmlWriter.WriteString($app.LocalizedDisplayName)
                        $xmlWriter.WriteEndElement() # <-- End Setter
                    $xmlWriter.WriteEndElement() # <-- End Match
                    foreach ($type in $appXml.DeploymentTypes) {
                        $xmlWriter.WriteStartElement('Match')
                        $xmlWriter.WriteAttributeString('Type','MSI')
                        $xmlWriter.WriteAttributeString('OperatorCondition','OR')
                        $xmlWriter.WriteAttributeString('DisplayName',$app.LocalizedDisplayName)
                            $xmlWriter.WriteStartElement('Setter')
                            $xmlWriter.WriteAttributeString('Property','ProductId')
                                if ($type.Installer.Technology -match 'MSI') {
                                    $xmlWriter.WriteString($type.Installer.ProductCode)
                                } else {
                                    $xmlWriter.WriteString(' ')
                                }
                            $xmlWriter.WriteEndElement() # <-- End Setter
                        $xmlWriter.WriteEndElement() # <-- End Match
                    }
                $xmlWriter.writeEndElement() # <-- End ApplicationMappings

            $xmlWriter.WriteEndElement() # <-- End Application
            $appCount++ # Increment the appCount by one for use in the "ID" Application element property.
        }
    }
    $XmlWriter.WriteEndElement() # <-- End ApplicationGroup
}

$xmlWriter.WriteEndElement() # <-- End Application
$xmlWriter.WriteEndDocument() # <-- End XML Document
 
# Finish The Document
$xmlWriter.Finalize
$xmlWriter.Flush
$xmlWriter.Close()

Pop-Location

And that is all of the new code that I needed to write for SCCM/UDI. In the final part of this series, I will discuss a few SCCM/UDI quirks that were easily corrected without custom coding:
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-5-quirks/


Series Index:

Migrating to the SCCM UDI for OSD, part 3b: Operating System Selection (Continued)

Continued from part 3a:
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-3a-os/

In part 3a of this series, I provided a script that automates updating of the OS selection dialog boxes in UDI. I also noted that while UDI presents that OS selection, the UDI client ignores the selection and instead installs whichever OS image was specified at image creation time. This was very frustrating because I was able to verify that the syntax of the generated UDI XML file was correct. In examining the task sequence logs, I even could see that the “OSDImageName” and “OSDImageIndex” variables had been set as expected. Yet still UDI would not apply the select image. Why?

I found multiple explanations for this phenomenon, none in the MDT/UDI documentation:
http://kb.matthewtrotter.com/?p=94
http://www.myitforum.com/forums/SCCM-2012-MDT-2012-UDI-Image-Selection-m238011.aspx

The upshot is, while UDI wizard will set the “OSDImageName” variable, the SCCM Task Sequence ignores it by design. This issue is exposed in the Task Sequence reference:
https://technet.microsoft.com/en-us/library/hh273365.aspx

The observant reader will notice that the “Apply Operating System Image” task sequence step does not contain a variable for supplying an image name or path. You only can provide an image index. It looks like MS only supports selection of different images indexed in one monolithic WIM image. I am not enamored with this limitation, so I have authored the following script which will create one “Apply Operating System Image” for each OS in the SCCM inventory. Each step will have a condition that will allow the step to run only if the OSDImageName environment variable matches the name of the image specified in the task sequence step. Note that we are assuming only one image per WIM file in this case, so the image index of the selected WIM has to be ‘1’.

Note that this script requires the “UVM-ConfigurationManager.psm1″ module file, which can be found in this post:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2c-drivers/

# Update-OSApplyTaskSequence:
# Created 2015-03-09, by J. Greg Mackinnon
# Updated 2015-03-12 - Added ability to update an existing, full OS installation Task Sequence.
#                    - Also removes pre-existing "Apply Apply Operating System Image " step.  

# Script will update the SCCM Task Sequence named in the $TSPackageName variable with a conditional 
# "Apply OS Image" step for each OS currently defined in SCCM.  

# Why?  Because unless all of your OS images are contained within a single WIM file, the SCCM deployment
# sequence will ignore any OS Image selection that you make in UDI.  Instead, we will have to rely on 
# UDI to set the "OSDImageName", and we will apply OS images based on that information.  
# NOTE: We are ASSUMING one image per WIM file, so the image index value will always be set to "1".

#WMI Classes associated with CM Task Sequences (that are relevant to us):
#   SMS_TaskSequencePackage                <-- The master Task Sequence object
#   SMS_TaskSequence                       <-- Each Task Sequence Package has one of these.
#   SMS_TaskSequence_Group                 <-- Logical groups of actions in the sequence.
#   SMS_TaskSequence_Condition             <-- A condition that can be attached to an action or group
#      SMS_TaskSequence_WMIConditionExpression        <-- Use "Model" MDT TS Variable instead!
#      SMS_TaskSequence_MakeModelConditionExpression  <-- Does not exist in the GUI! Do not use!
#      SMS_TaskSequence_VariableConditionExpression   <-- Condition based on a TS variable.
#      SMS_TaskSequence_ApplyOperatingSystemAction    <-- Apply an Operating System Image

# See them all by running:
#    Get-WmiObject -list -Namespace $namespace | select -property name | ? -Property name -Match "SMS_TaskSequence"

# Helpful resources:
#    The authority... MSDN on programming task sequences (VBScript and C#):
#      https://msdn.microsoft.com/en-us/library/jj217977.aspx
#    Describes how to expand properties from SMS objects with "lazy" properties:
#      http://trevorsullivan.net/2010/09/28/powershell-configmgr-wmi-provider-feat-lazy-properties/
#    Describes using the [wmi] type accelerator to retrieve WMI objects by absolute path:
#      http://windowsitpro.com/scripting/type-accelerators-useful-undocumented-feature-powershell-10
#    Describes the difference between [wmi] and [wmiclass] objects:
#      http://tfl09.blogspot.com/2008/12/powershells-wmiclass-type-accelerator.html

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$True,Position=1)][string]$name,
    [Parameter(Position=2)][string]$namespace
)

Set-PSDebug -Strict

######## Begin Site-Specific Settings: ########
#Name of the TaskSequence to configure (must already exist):
[string]$TSPackageName = $name

#Name of the MDT Settings Package that contains the unattend.xml file to be used during deployment:
[string] $MDTSettingsName = 'MDT 2013 Settings'

#Name of the new Apply OS Images Group to be applied to the Task Sequence:
[string]$TSGroupName = 'UVM Apply Operating System Image Group'
######### End Site-Specific Settings: #########

Import-Module 'c:\local\scripts\UVM-ConfigurationManager.psm1'

#Set default value for $namespace, if not provided as a parameter:
if ((-not (Test-Path Variable:\namespace)) -or !$namespace) {[string]$namespace = Get-SMSSiteNamespace}

########################################################
########## Begin Create New OS Apply TS Group ##########
# Get the Name and PackageID for all OS image packages currently defined in SCCM, and put them into an array:
[array]$OSPackages = @()
$OSPackages = Get-WmiObject -Namespace $namespace -Query "Select Name,PackageID from SMS_ImagePackage" | Select-Object -Property Name,PackageID | Sort-Object -Property Name 
#Instantiate a new, empty TS Group:
$newTSGroup = New-SMSObject -class SMS_TaskSequence_Group
#Configure the group:
$newTSGroup.Name = $TSGroupName
$newTSGroup.Description = "Copy this group into a task sequence to replace all pre-existing 'Apply Operating System Image' actions."
#Get the PackageID for our MDT Settings Package.  We will need this later when defining the "Apply Operating System Image" TS Step:
[string] $MDTSettingsID = $(Get-WmiObject  -namespace $namespace -query "Select PackageID from SMS_Package where NAME = '$MDTSettingsName'").PackageID
foreach ($package in $OSPackages) {
    #Add an action to run each Apply Operating System Image Action:
    $ApplyOSTSAction = New-SMSObject -class SMS_TaskSequence_ApplyOperatingSystemAction
    $ApplyOSTSAction.Name = ("Apply OS Package: " + $package.name)
    $ApplyOSTSAction.ConfigFileName = "unattend.xml"
    $ApplyOSTSAction.ConfigFilePackage = $MDTSettingsID
    $ApplyOSTSAction.Description = "Conditionally applies this OS Image Package to the local drive."
    $ApplyOSTSAction.DestinationVariable = "OSDisk"
    $ApplyOSTSAction.ImagePackageID = $package.PackageID
    $ApplyOSTSAction.ImageIndex = "1"
    #Create a Task Sequence Condition object:
    $TSCondition = New-SMSObject -namespace $namespace -class SMS_TaskSequence_Condition
    #Create a Task Sequence Condition Expression object:
    $TSConditionExp = New-SMSObject -namespace $namespace -class SMS_TaskSequence_VariableConditionExpression
    $TSConditionExp.Operator = "equals"
    $TSConditionExp.Value = $package.name
    $TSConditionExp.Variable = "OSDImageName"
    #Add the condition expression to the "operands" attribute of the condition object:
    $TSCondition.Operands = @($TSConditionExp) #  Multiple conditions are possible, use an array.
    #Add the Condition object to the condition attribute of the Group object:
    $ApplyOSTSAction.Condition = $TSCondition #  Only one condition, not an array.
    $TSConditionExp.Dispose()
    $TSCondition.Dispose()
    $newTSGroup.Steps += @($ApplyOSTSAction)
}
########### End Create New OS Apply TS Group ###########
########################################################

######################################################
############# Begin Retrieve Existing TS #############
#Run the WQL queries required to get the fully-populated Task Sequence Package object (no loosely bound parameters)
[string]$filter = "name = '" + $TSPackageName + "'"
$TSP = Get-SMSFullObject -namespace $namespace -class SMS_TaskSequencePackage -filter $filter
#Get a class object for the object retrieved above.  This will allow access to static properties and methods not available in individual WMI objects.
$TSPClass = New-SMSClass -smsObject $TSP 
#For Task Sequence Packages, the GetSequence method allows us to get the sequence associated with a package.
#(Under CM 2012, each Package has one (and only one) Task Sequence)
#  Q: Why do this? The WMI object retrieved above already has a property "sequence", which contains all groups/steps in XML format.  Why can't we use that?
#  A: Because this is XML data that will be challenging to manipulate!  CM has separate classes for more controlled TS step manipulation.
#Note1: Interestingly, the retrieved object as a property "TaskSequence", which is the actual Task Sequence.  WHY!?!?!
#Note2: We also could use "New-SMSObject" to create an entirely new Task Sequence.
$TS = $TSPClass.GetSequence($TSP).TaskSequence
############## End Retrieve Existing TS ##############
######################################################

#Locate the Task Sequence items that need to be modified:
[int32]$exeIndex = Get-SMSTSStepIndex -TSObject $TS -TSStepName 'Execute Task Sequence'
$ExeTSGroup = $TS.steps[$exeIndex]
[int32]$instIndex = Get-SMSTSStepIndex -TSObject $ExeTSGroup -TSStepName 'Install'
$instTSGroup = $ExeTSGroup.steps[$instIndex]

#Remove the existing "Apply OS Image" step, if it exists:
[string]$osApplyStepName = 'Apply Operating System Image'
if (Test-SMSTSStep -TSObject $instTSGroup -TSStepName $osApplyStepName) {
    Remove-SMSTSStep -TSObject $instTSGroup -TSStepName $osApplyStepName
}

#Remove the existing UVM Driver Group (if it exists):
if (Test-SMSTSStep -TSObject $instTSGroup -TSStepName $TSGroupName) {
    Remove-SMSTSStep -TSObject $instTSGroup -TSStepName $TSGroupName
}

#Identify the position within the task sequence group where we will add our new UVM OS Apply Group:
[int]$i = [int]$(Get-SMSTSStepIndex -TSObject $instTSGroup -TSStepName 'Set Variable for Drive Letter') + 1

#Add the new OS Apply Group to the Install Group after the position discovered in the previous step:
Add-SMSTSStep -TSObject $instTSGroup -TSStep $NewTSGroup -StepIndex $i
$newTSGroup.Dispose()

#Walk back up the task sequence tree, updating each parent group with the revised child groups:
Remove-SMSTSStep -TSObject $ExeTSGroup -TSStepName $instTSGroup.Name
Add-SMSTSStep -TSObject $ExeTSGroup -TSStep $instTSGroup -StepIndex $instIndex
$instTSGroup.Dispose()
Remove-SMSTSStep -TSObject $TS -TSStepName $ExeTSGroup.Name 
Add-SMSTSStep -TSObject $TS -TSStep $ExeTSGroup -StepIndex $exeIndex
$ExeTSGroup.Dispose()

#The moment of truth...
#Use the Task Sequence Package class "SetSequence" method to add our new or updated sequence to the task sequence package object:
try {
    $TSPClass.SetSequence($TSP,$TS)
} catch  [System.Management.Automation.MethodInvocationException] {
    [string] $out = "Could not commit the Task Sequence to the Task Sequence Package. "
    $out += "Perhaps this Package is open for editing elsewhere? "
    $out += "Check the Management Point SMSProv.log for details. "
    Write-Error $out
}
#At this point, our Task Sequence has been updated, and the new steps will be available to clients!

# Dispose of all remaining objects: (Do we really need to do this?)
$TS.Dispose()
$TSP.Dispose()
$TSPClass.Dispose()

The script will create a single group of Apply Operating System tasks (one task for each OS Image Package in the infrastructure) in the Task Sequence, using the group name supplied in the $TSPackageName variable. If the group already exists, it will be updated. Additionally, if the stock "Apply Operating System Image" step is still present, it will be removed. Each time you add or remove an OS Image, you will need to re-run the script and re-copy/paste the actions.

We have now dealt with handling Drivers and Operating Systems in SCCM/UDI OS deployment. That leaves managing UDI application selection, which I will discuss in part four of this series, coming up next.

http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-4-apps/


Series Index:

Migrating to the SCCM UDI for OSD, part 3a: Operating System Selection

Continued from part 2f:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2f/

In this part of the series, I will make it possible for the end-user to select from a list of available operating system images. I will provide required scripting logic to update this selection automatically, and I will provide an additional script to make these selections work as expected. (The out-of-box UDI image selection process makes no intuitive sense at all, as we shall soon see.)

Under MDT/LTI, we used Task Sequences as the unit to control the selection of operating systems by the end user. This was necessary because the LTI wizard did not provide an operating system selection dialog. We could have authored our own OS selection dialog, but back when UVM was new to MDT (then BDD) I was a wet-behind-the-ears programmer, and programming of BDD was more difficult. However, under UDI we have the option to allow the user to select different operating systems within the UDI Wizard, so we no longer need one Task Sequence per operating system. I think that this is a positive development, although maintenance of this option proved to be more difficult than expected.

First challenge: Programmatic updating of OS Selections in the UDI Wizard

Anyone who has done experimentation with UDI surely is familiar with the UDI Designer tool. This tool provides a GUI which generates a somewhat large XML file that controls the options that are presented to UDI clients. The UDI designer allows the administrator to select the objects within SCCM that will be presented to the end-user. While this presentation granularity may be desirable for some, it presents a maintenance challenge for us. Any time an object is updated in the SCCM inventory, we need to update the UDI dialogs as well, and Microsoft provides no out-of-box means of updating these links. While this is only a minor problem for OS Image updates, it is a major hassle for Application updates. Since we really needed to solve this problem for Application updates, adding logic for operating systems was an easy extension.

To solve this problem, we need to script the reconfiguration of our UDI wizard XML file. Microsoft likes to claim that PowerShell provides “powerful” XML handling capabilities. In my experience, this claim is debatable as the built-in cmdlets have limited XML formatting capabilities. However, the .NET framework upon which PowerShell is built does provide many classes for XML handling. In this script we will be using the ” type accelerator. This accelerator represents the System.Xml.XmlDocument .NET Class, for which full documentation is available in MSDN:
https://msdn.microsoft.com/en-us/library/system.xml.xmldocument(v=vs.110).aspx

I also use the “Get-CMOperatingSystemImage” SCCM PowerShell cmdlet. As mentioned previously, I recommend avoiding the use of the cmdlets as they behave unpredictably. However, this particular cmdlets appears to work as well as we need it to. If you hate it, the commands could replaced with a WMI calls, although you would need to first discover the OS images:

$images = Get-WmiObject -namespace root/sms/site_[SiteCode] -Class SMS_ImagePackage | %{$_.__Path}

And then retrieve the “full object”, since the above query will not retrieve the required “ImageProperty” attribute (which Microsoft calls a “loosely bound” attribute):

$fullImages = @()
$fullImages += $images | % {[wmi] $_}

To use this script in your environment, you would need to update the $udiXmlIn and $udiXmlOut paths to match the desired locations of the UDIWizard_Config.xml in your environment. These paths can be the same, if desired. You also will need to update the $CMSiteCode and $CMBinPath variables.

# build-UDIImageList.ps1
# J. Greg Mackinnon
# Created: 2015-01-26
# Updated: 2015-03-17 - Added host output to indicate task progress.
# Populates the UDI Configuration Wizard XML file with all Operating System images gathered from
# Configuration Manager.  
# Requires: a local installation of the Configuration Manager administration tools.
#   Modify $udiXmlIn, $udiXmlOut, $CMSiteCode, and $CMBinPath to match your environment.

[string] $udiXmlIn = 'O:\sources\os\mdt\files\Scripts\UDIWizard_Config.xml'
[string] $udiXmlOut = 'O:\sources\os\mdt\files\Scripts\UDIWizard_Config.xml'


[string] $CMSiteCode = 'UVM'

[string] $CMBinPath = 'F:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\'
[string] $CMModName = 'ConfigurationManager.psd1'
[string] $CMModPath = Join-Path -Path $CMBinPath -ChildPath $CMModName

[string] $CMDrive = $CMSiteCode + ':\'

Import-Module -Name $CMModPath | Out-Null

Push-Location $CMDrive

write-host "Gathering OS Images from SCCM..." -ForegroundColor Yellow
$osImages = Get-CMOperatingSystemImage | select -Property Name,ImageProperty

write-host "Loading the current UDI Wizard configuration file..." -ForegroundColor Yellow
1$udiXml = Get-Content $udiXmlIn
$dataElement = ($udixml.wizard.pages.page | ? -Property Name -eq 'VolumePage').data
# XPath variation, not working for some reason:
#$imgSel = $udiXml.SelectNodes("wizard/pages/page[@Name=""VolumePage""]")

#Clear the existing Nodes:
$dataElement.RemoveAll()
#Add the name/Imageselection attribute back in to the element:
$dataElement.SetAttribute('Name','ImageSelection')

write-host
foreach ($image in $osImages) {
    #Create a new DataItem element for each OS Image:
    $dataItemElement = $udiXml.CreateElement('DataItem')
    $dataElement.AppendChild($dataItemElement) | out-null

    # Read information from the existing image:
    1$imageXml = $image.ImageProperty
    [string]$ImageName = $image.Name
    [string]$Index = $imageXml.WIM.IMAGE.Index
    [string]$archNumber = ($imageXml.WIM.IMAGE.Property | ? -Property Name -eq 'Architecture').'#text'
    if ($archNumber -eq '9') {
        [string]$Architecture = 'amd64'
    } elseif ($archNumber -eq '0') {
        [string]$Architecture = 'x86'
    } else {
        [string]$Architecture = ''
    }
    #The UDI DisplayName value does not need to be tied to a property in SCCM, 
    # but we will use the matching Display Name in the SCCM GUI, which is mapped out below:
    [string]$DisplayName = $imageXml.WIM.IMAGE.name
    
    write-host "Adding image named: $DisplayName" -ForegroundColor Yellow

    #Add collected image info to a new array
    [array[]]$setters = @(
        @('DisplayName', $DisplayName),
        @('Index', $Index),
        @('Architecture', $Architecture),
        @('ImageName', $ImageName)
    )

    #Now feed data from the info array as "setter" elements under the "DataItem" element:
    foreach ($setter in $setters) {
        write-host "    Adding element: '"$setter[0]"' with property '"$setter[1]"'" -ForegroundColor cyan
        $setterElement = $udiXml.CreateElement('Setter')
        $setterElement.SetAttribute('Property',$setter[0])
        $setterElement.InnerText = $setter[1]
        $dataItemElement.AppendChild($setterElement) | out-null
    }
}

$udiXml.Save($udiXmlOut)

Pop-Location

After running this script, our UDI Wizard correctly displays all available OS Images in our environment. However, we found that the selections were not being honored by the UDI process. The fault is not in this script, but rather in the logic used by UDI. We will explore the fix for this in the next part of the series (3b).

Next: Operating Systems – Update the Task Sequence with new OS Image data
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-3b-os/


Series Index:

Migrating to the SCCM UDI for OSD, Part 2f: Driver Handling (concluded)

Continued from part 2e:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2e/

So, my driver handling routine is probably looking pretty scary right now. There are a lot of scripts that need to be run, and I have not discussed where to put them, or in which order they should be executed.  In part 6 of this series, I will provide playbook procedures for using the scripts.  Here I will provide a sys admin’s overview of where the scripts should be stored, and how they work with each other.

Script location and customization:

  1. Copy ImportDrivers.ps1, Update-DriverInjectionTaskSequence.ps1, UVM-ConfigurationManager.psm1, Create-UDIInfoFiles.ps1, and Build-UDIImageList.ps1 (see part 3) to a local directory on your Management Point or Site Server.  We use “c:\local\scripts”, with “CM-[Category]” subdirectories for each category of script.  I keep dependencies such as the “UVM-ConfigurationManager.psm1 in the “Script Root” directory.
  2. Review the starting sections for each file for local references that you might need to customize for your site.
  3. Update the path to the “UVM-ConfigurationManager.psm1″ PowerShell module in the Create-UDIInfoFiles script.
  4. Update the path to the “UVM-ConfigurationManager.psm1″ file in the Update-DriverInjectionTaskSequence script.
  5. Update the path to your MDT Files package source in the Create-UDIInfoFiles script.
  6. Copy the ZUVMDetectDriverPackage.wsf file into the “Scripts” directory of your MDT Files package.

Script sequence and dependency chains:

  1. Run “ImportDrivers.ps1″ – creates the Driver Packages and groups referenced by all future scripts and operations.  Note that the script does not distribute the packages that get created.  You must do this manually.  Anyone want to share a tip on programmatic package distribution?
  2. The remaining server side scripts can be run in any order.  After running the scripts, you must re-distribute your MDT Files to your distribution points or the UDI Task Sequence will fail with missing file or missing dependency errors.

I think that covers the procedure. I will work on streamlining the process to make it simpler to implement.

On to the handling of operating system images in UDI…

Next: Operating Systems – Update the OS Image List in the UDI Wizard:
http://blog.uvm.edu/jgm/2015/03/10/sccm-udi-3a-os/


Series Index:

Migrating to the SCCM UDI for OSD, Part 2e: Driver Handling (continued)

Continued from part 2d:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2d/

In the previously explored client-side SCCM script, we saw some CSV files referenced by the client. These CSV files are not a natural part of the UDI environment, but must be generated by server-side script. What is in these files? Information about the driver packages and OS images that are defined in the SCCM database. Why not just have the client query SCCM for this information? Because to do so, we would need to inject privileged credentials into the WinPE environment, and I don’t want to do that. Creating the CSV files is a safe way to expose SCCM database info.

In authoring these posts, I see that at present the ‘zUVM-DriverCategories.csv’ gets generated twice… once when I run the ImportDrivers.ps1, and then again when running this script. Clearly this redundancy is not required. As I think about it, I probably should retire this script and merge the creation of the zUVM-OSImages.csv file into the script that updates our UDI Wizard display. I guess that will have to wait for version 2.0 of this guide.

This approach comes with the requirement that we update the CSV files any time a new OS or driver package is added to SCCM. We will get to the automation of the update process in a bit. But first, here is the UDI client CSV info file generation script, in PowerShell, of course:

# Build-UDIInfoFiles.ps1
# J. Greg Mackinnon, 2015-01-03
# Builds two files to be used by the zUVMDetectDriverPackage.wsf script that runs in WinPE during ZTI/UDI client installations.
#   YOU MUST ALSO RUN "build-UDIImageList.ps1" to ensure that the images in the UDI Wizard are identical to the images used by the zUVMSetDriverCategories script.
#   YOU MUST run this script after each run of the ImportDrivers.ps1 script.
# These are CSV files contain the following information gathered from Configuration Manager:
#   - a list of all current OS Images and a matching text string to indicate the major OS version.
#   - a list Driver Categories names, with matching Driver Category IDs.
# Requires: Configuration Manager administration tools (including CM PowerShell), and access to the SCCM server using WMI.
# Update $outFile to change the output file names ($outFile is defined twice in the script, rather unprofessionally, really).
# Update $CMSiteCode, $CMBinPath to run in a different server environment.

set-psdebug -strict

###############################################################################
######################### Start Build Driver Info File ########################
    #Build a CSV consisting of each SCCM Driver Category Name, and its corresponding UniqueID 
    [string] $computer = $env:COMPUTERNAME 
    [string] $CMSiteCode = 'UVM'

    [string] $CMBinPath = 'F:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\'
    [string] $CMModName = 'ConfigurationManager.psd1'
    [string] $CMModPath = Join-Path -Path $CMBinPath -ChildPath $CMModName

    [string] $CMDrive = $CMSiteCode + ':\'

    # First output file - Driver Categories:
    [string] $outFile = 'O:\sources\os\mdt\files\Scripts\zUVM-DriverCategories.csv'

    #Cleanup existing file:
    if (Test-Path $outFile) {Remove-Item $outFile -Force -Confirm:$false}
    [string] $namespace = "root\SMS\site_" + $CMSiteCode
    
    #Note: Since we don't actually use the Category UniqueID anymore, a safer approach would be to import a list of SMS_DriverPackage objects instead:
    # Get-WmiObject -Namespace $namespace -Class SMS_DriverPackage -Property name | Select-Object -Property name | Sort-Object -Property name

    [string] $query = "select LocalizedCategoryInstanceName,CategoryInstance_UniqueID from sms_categoryinstance WHERE CategoryTypeName ='DriverCategories'"
    $wmiDriverCats = Get-WmiObject -ComputerName $computer -namespace $namespace -query $query | Sort-Object -Property LocalizedCategoryInstanceName

    #Generate CSV file with CategoryName,UniqueID:
    $driverCats = $wmiDriverCats | Select-Object -Property LocalizedCategoryInstanceName,CategoryInstance_UniqueID
    foreach ($cat in $driverCats) {
        $outStr = $cat.LocalizedCategoryInstanceName + ',' + $cat.CategoryInstance_UniqueID
        $outStr | Out-File -FilePath $outFile -Append -Encoding ascii
    }
########################## End Build Driver Info File #########################
###############################################################################


###############################################################################
########################### Start Build OS Info File ##########################
    #Second output file - OSImage information:
    [string] $outFile = 'O:\sources\os\mdt\files\Scripts\zUVM-OSImages.csv'
    if (Test-Path $outFile) {Remove-Item $outFile -Force -Confirm:$false}

    #We /could/ (and probably should) use WMI here to query root/sms/site_[siteCode]/ImagePackage 
    # (and corresponding ImagePackageInfo), but I am being lazy and will stick with the xml parsing code I already wrote:

    #Load the Configuration Manager PS module, needed for the CM cmdlet:
    Import-Module -Name $CMModPath

    Push-Location
    Set-Location $CMDrive
    $OSImages = Get-CMOperatingSystemImage
    Pop-Location

    foreach ($image in $OSImages) {
        1$imageXml = $image.ImageProperty
        $OSVer = ($imageXml.WIM.IMAGE.Property | ? -Property name -eq 'OS version').'#text'
        if ($OSVer -match '^6\.2|^6\.3') {
            [string] $winVer = 'Win8'
        } elseif ($OSVer -match '^6\.1') {
            [string] $winVer = 'Win7'
        } else {
            [string] $winVer = 'unknown'
        }
        $outStr = $image.Name + ',' + $winVer
        $OutStr | Out-File $outFile -Append -Encoding ascii
    }
############################ End Build OS Info File ###########################
###############################################################################

You may note that this code uses the Get-CMOperatingSystem PowerShell module. Earlier I said that you should not use these PS modules. I did mean it, but I wrote this code before I discovered that the module is evil, and I don’t feel like re-writing the snippet. Call me lazy.

Observant readers may have noticed in the comments the text that reads: “YOU MUST ALSO RUN ‘build-UDIImageList.ps1′ to ensure that the images in the UDI Wizard are identical to the images used by the zUVMSetDriverCategories script.” The build-UDIImageList.ps1 script is a direct component of this driver handling routine. Instead, this script is used to populate SCCM OS Images into the UDI dialogs. However, since our driver handling logic is sensitive to the OS that was selected for installation, we now have a dependency on the our UDI dialog building process. I will present this script in part 3 of this blog series, where I will discuss OS Image selection in UDI.

But first we need to wrap up our discussion of driver handling, which I will do in part 2f, next…

Next: Drivers – File Structure and Dependencies
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2f-drivers/


Series Index:

Migrating to the SCCM UDI for OSD, Part 2d: Driver Handling (continued)

Continued from part 2c:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2c/

All of the code that we have explored so far has been server-side PowerShell. In this next snippet, we will be seeing client-side VBScript that will be executed in the WinPE environment, while the task sequence is running. In part 2b, I present a Task Sequence builder script. That script created a Task Sequence step with the following command embedded:
cscript.exe %DeployRoot%\Scripts\ZUVMDetectDriverPackage.wsf

This script uses Model data generated by the “ZTIGather” script (part of MDT), and also some CSV information files that we will explore in part 2e of this post. The data is analyzed to determine which SCCM Driver Packages should be used by this client, and this information is specified in the custom TS variable “UVMDriverPackage”.

I have provided some additional logic to workaround annoying device model names like “Latitude E5440 without vPro” (the with/without vPro models use the same drivers.), and “Venue 11 Pro 7138″ (which uses the same drivers as the 7130).

When we start deploying Windows 10 OS Images, the script will need some minor updates to handle the Win10 driver pools. I already have logic for this in my production MDT/LTI client-side scripts. I just need to port that code over here.

The original code is a “wsf” file which contains XML headers and footers in addition to VBScript code. Since my WordPress instance hates in-line XML, I have had to strip these tags. The original code is available as an attachment to this post. Owing again to WordPress tomfoolery, I have had to disguise this file as a “.png”. If you wish to use the file, download it, and rename the extension to “.wsf”:

ZUVMDetectDriverPackageZUVMDetectDriverPackage.wsf

Option Explicit
RunNewInstance

'//--------------------------------------------------------
'// Main Class
'//--------------------------------------------------------
Class zUVMDetectDriverPackage
    
    '//--------------------------------------------------------
    '//  Constructor to initialize needed global objects
    '//--------------------------------------------------------
    Private Sub Class_Initialize
    End Sub
    
    '//--------------------------------------------------------
    '// Main routine
    '//--------------------------------------------------------

    Function Main()
    ' //*******************************************************
    ' //
    ' // File: ZUVMDetectDriverPackage.wsf
    ' //
    ' // Purpose: Script to determine the name of the driver package to be 
    ' //   applied to the operating system in the running task sequence.  Reads
    ' //   driver category and OS Image information from files generated 
    ' //   in PowerShell.
    ' //
    ' // Usage: cscript zUVM-SetDriverCategories.wsf [/Model:ComputerModel] [/OSDImageName:WIMImageName] [/debug:true]
    ' //     Model and OSDImageName arguments are for debugging, and will override the variables in the current deployment environment.
    ' //
    ' //*******************************************************

        Dim catFile, catFilePath, csProp, dict_key, imageFile, imageFilePath, OSDImageName, Make, Model, Out, WinVer 'String variables
        Dim driverCats, OSImages, oMatch, oRegEx 'Objects
		Dim i, iRetVal

        Set driverCats = createObject("Scripting.Dictionary")
        Set OSImages = createObject("Scripting.Dictionary")
		
		'Set initial value for "UVMDriverCatSet".  We will set this to "YES" if we find a matching driver category later.
		oEnvironment.Item("UVMDriverPackageDetected") = "NO"

        catFile = "zUVM-DriverCategories.csv"
        imageFile = "zUVM-OSImages.csv"
		
		'Test to see if image/driver information files are present.  Exit if they cannot be found.
		'These two if clauses should be implemented as functions.
		iRetVal = oUtility.FindFile(catFile, catFilePath)
		if iRetVal  Success then
			oLogging.CreateEntry catFile & " file not found. ", LogTypeError
			iRetVal = Failure
			Main = iRetVal
			exit function
		end if 
		oLogging.CreateEntry "Path to " & catFile &": " & catFilePath, LogTypeInfo

		iRetVal = oUtility.FindFile(imageFile, imageFilePath)
		if iRetVal  Success then
			oLogging.CreateEntry imageFile & " file not found. ", LogTypeError
			iRetVal = Failure
			Main = iRetVal
			exit function
		end if 
		oLogging.CreateEntry "Path to " & imageFile &": " & imageFilePath, LogTypeInfo
		
        'Load the information from the files into scripting dictionary objects.
        oLogging.CreateEntry  "Loading the driver categories info file into memory...",LogTypeInfo
		call load_dict(driverCats,catFilePath)
        oLogging.CreateEntry  "Loading the OS Images info file into memory...",LogTypeInfo
        call load_dict(OSImages,imageFilePath)
		
		'The following two "If Wscript.arguments..." clauses could be implemented as functions... I'll do that when I have time (Ha!).
		'Use MDT to get Model information:
		'(Supply a valid image name in the /OSDImageName: argument, such as "Windows 8.1 Update 1 64-bit with Office 2013" for debugging)
		If Wscript.arguments.named.Exists("OSDImageName") Then
			Out = "OSDImageName argument provided on the command line. Setting OSDImageName to the supplied argument instead."
			oLogging.CreateEntry Out, LogTypeWarning
			OSDImageName =  Wscript.arguments.named.Item("OSDImageName")
		Else
			OSDImageName = oEnvironment.Item("OSDImageName")
		End If
		
		'Special handling for the OSDImageName variable... the script should not continue if OSDImageName still is not defined.
		'wscript.echo "OSDImageName is of length: " & Len(OSDImageName)
		oLogging.CreateEntry "Selected OSDImageName now is: " & OSDImageName, LogTypeInfo
		If Len(OSDImageName) = 0 Then
			oLogging.CreateEntry "OSDImageName is not defined in the MDT environment, and was not provided on the command line.  Exiting...", LogTypeError
			iRetVal = Failure
			Main = iRetVal
			exit function
		End If
		
		If Wscript.arguments.named.Exists("Model") Then
			oLogging.CreateEntry "Model argument provided on the command line.  Overriding oEnvironment setting.", LogTypeWarning
			Model =  Wscript.arguments.named.Item("Model")
		Else
			Model = oEnvironment.Item("Model")
		End If
		oLogging.CreateEntry "Model variable now is: " & Model, LogTypeInfo
        
		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(Model) then
            oLogging.CreateEntry "Model is a Latitude.  Cleaning up the model name...", LogTypeInfo
            oRegEx.pattern = " "
            set oMatch = oRegEx.Execute(Model)
            'wscript.echo "oMatch Count is: " & oMatch.count
            if oMatch.Count > 1 then
				i = oMatch.item(1).FirstIndex
				Model = Left(Model,i)
				oLogging.CreateEntry "Model is now: " & Model, LogTypeInfo
            end if
        end if
        oRegEx.pattern = "Venue 11 Pro 713"
        if oRegEx.test(Model) then
            oLogging.CreateEntry "Model is a Venue 11 Pro 713x.  Cleaning up the model name...", LogTypeInfo
            oRegEx.pattern = "713"
            set oMatch = oRegEx.Execute(Model)
            'wscript.echo "oMatch Count is: " & oMatch.count
            i = oMatch.item(0).FirstIndex
            'wscript.echo "index of match is: " & i
            Model = Left(Model,i+3)
            oLogging.CreateEntry "Model is now: " & Model, LogTypeInfo
        end if
		
		'Lookup the Windows version string for this image from the OSImages dictionary (loaded from file).
		'Exit if the image is not listed.
        If OSImages.Exists(OSDImageName) Then
            WinVer = OSImages.Item(OSDImageName)
        Else
			oLogging.CreateEntry "Selected image was not found in the CSV list of available images.", LogTypeError
			oLogging.CreateEntry "Searched for: " & OSDImageName, LogTypeError
			iRetVal = Failure
			Main = iRetVal
            exit function
        End If

        dict_key = WinVer & "-" & Model
		oLogging.CreateEntry "Looking for driver category entry: " & dict_key, LogTypeInfo
        If driverCats.Exists(dict_key) Then
            'Lookup the driver category ID string from the driverCats dictionary (loaded from file):
			Out = CStr("Found entry for " & dict_key)
            oLogging.CreateEntry Out, LogTypeInfo
            oEnvironment.Item("UVMDriverPackageDetected") = "YES"
            oEnvironment.Item("UVMDriverPackage") = dict_key
        'If we are dealing with a Windows 8 image, then check for Windows 7 drivers when Win8 drivers can't be found:
        ' (Note: In MDT/LTI I had logic to set a "MaxOS" version, and a For or While loop to count down to the lowest supported OS.
        '  We should do that here, too.)
        ElseIf InStr(WinVer,"Win8") Then
			Out = "No entry found for " & dict_key & " in the Windows 8 driver categories.  Now looking in the Windows 7 driver categories."
			oLogging.CreateEntry Out, LogTypeWarning
            WinVer = "Win7"
            dict_key = WinVer & "-" & Model
			If driverCats.Exists(dict_key) Then
				Out =  CStr("Found entry for " & dict_key)
                oEnvironment.Item("UVMDriverPackageDetected") = "YES"
                oEnvironment.Item("UVMDriverPackage") = dict_key
				oLogging.CreateEntry Out, LogTypeInfo
			Else 
				Out = "No entry found for this model. Leaving UVMDriverPackageDetected set to 'NO'."
				oLogging.CreateEntry Out, LogTypeInfo
			End If
        Else
			Out = "No entry found for this model. Leaving UVMDriverPackageDetected set to 'NO'."
			oLogging.CreateEntry Out, LogTypeInfo
        End If
		
		iRetVal = Success
		Main = iRetVal

    End Function

    Function load_dict(dict_name,file_name)
        Dim objFSO,objText,line,pair_array,index,item
        Const ForReading = 1, ForWriting = 2, ForAppending = 8, ReadOnly = 1

        Set objFSO = CreateObject("Scripting.FileSystemObject")  'Create file object
        oLogging.CreateEntry "Opening the script dictionary source file '" &  file_name & "'.",LogTypeInfo
        Set objText = objFSO.OpenTextFile(file_name, ForReading) 'Open for read

        Do Until objText.AtEndOfStream        'Read to end of file
            line = objText.ReadLine                  'Read line from file
            'Uncomment for debugging:
            'oLogging.CreateEntry "Loading '" & line &"'.",LogTypeInfo
            pair_array = split(line,",")             'Split Index-Item pair
            index = pair_array(0) 'Decode Index
            item  = pair_array(1) 'Decode Item
            dict_name.Add index,item  'Add to Dictionary
        Loop                                      'Read another line
        objText.Close
    End Function

End Class

In part 2E of this post, I will discuss the final bits of server-side code required to generate the CSV information files used by this client-side script…

Next: Drivers – Providing SCCM database info to clients
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2e-drivers/


Series Index: