Migrating to the SCCM UDI for OSD, Part 2a: Driver Handling

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

Frequent readers of this blog (Anyone? Anyone? Bueller?) will recall my epic series on driver handling under MDT/LiteTouch. For everyone else, you will have to trust me that we came up with an effective and sustainable set of scripts for managing drivers under LiteTouch.

In transitioning to UDI, I hit some pretty serious roadblocks. Notably, UDI does not implement a usable environment variable that can be used to specify the SCCM driver group or package that you want to inject into or make available to your operating system during deployment. Under LTI, we added a stock “Inject Drivers” task sequence step, and used the task sequence variables “DriverGroup001” to specify the path on the deployment share that contained the drivers for the current model of computer.

Under SCCM/UDI, there is no such task sequence variable. Oh sure, the documentation does make reference to a variable “OSDAutoApplyDriverCategoryList” which is used in the “Auto Apply Drivers” task sequence step. But if you set this variable within your running task sequence, it gets ignored by the task sequence step at execution time. While you may find many blogs that provide information to the contrary (by Ben Hunter, no less), my experimentation suggests otherwise. I can see “OSDAutoApplyDriverCategoryList” get set by my script (it shows up in the logs), and then the settings get completely ignored when the driver injection takes place. An MS consultant confirmed this finding, stating that using OSDAutoApplyDriverCategoryList to control driver injection is “impossible”.

“What about using the ‘Apply Driver Package’ task sequence step instead?”, do I hear you ask? Well, that sounds like a good idea, except that ‘Apply Driver Package’ does not support a Task Sequence variable that allows you to set the Driver Package that you wish to apply to the OS. To use this step, you need one ‘Apply Driver Package’ action for each make/model/OS that you support in your environment! To make matters worse, you need to set a condition on each step so that it only runs on a supported model. For us, that means (at present) 75 separate task sequence steps with 75 separate condition statements. That’s a lot of pointing and clicking, and I am not going to do it.

Being the bull-headed guy that I am, I thought that I should be able to script this job out. Probably this was not the best use of my time this month, but I did it anyway, and below you can see the fruits of my labor, such as they are. Programming: the art of doing in 2000 lines of code and 20 hours of work what could have been done in two hours with a mouse and a reference guide.

So here is the UVM SCCM Driver Handling Solution, presented in six “easy parts”… First, we needed to get our drivers into SCCM. After wasting several hours with the SCCM PowerShell cmdlets (boo!), I found the following script which does just what I need using raw WMI calls under PowerShell:

http://blog.coretech.dk/kea/automate-importing-and-creating-drivers-packages-in-sccm-2012-r2/

Many thanks to the team at CoreTech for this code. I had to do some lite modifications to get the script to work in our site. The modified code is included in-line, below:

# From: http://blog.coretech.dk/kea/automate-importing-and-creating-drivers-packages-in-sccm-2012-r2/

# Imports drivers into SCCM from the directory specified in $sourceDir
# Creates driver packages in the directory specified in $packageDir
# The script does not use any MS-provided Configuration Manager PowerShell cmdlets, nor does it use any Configuration Manager DLL files/assemblies.
# It's all implemented in WMI, which is good because it bypasses several bugs in SCCM 2012 R2 CU3 that were preventing this process from working when we tried it with the CM PowerShell cmdlets!

# Drivers will be grouped into Administrative Categories and corresponding Driver Packages based on the folder structure of $sourceDir:

# root
#  |
#  +-> Win7
#  |    |
#  |    +- > Latitude E6500
#  |    +- > Optiplex 780
#  |
#  +-> WinPE
#       |
#	    +-> 5.0-x86
#       +-> 5.0-x64

# Will create the following Categories/Groups:
#  Win7-Latitude E6500
#  Win7-Optiplex 780
#  WinPE-5.0-x86
#  WinPE-5.0-x64

# The granularity of the category names can be changed by nesting more "get-childitem | foreach-object" loops into the function "SDS-ProcessFolder".
# Re-processing or existing folders can be forced by removing the "*.hash" files from the import source as follows:
# > Set-Location $sourceDir
# > gci -recurse -include *.hash | remove-item -force -confirm:$false

[string] $CMServer = "confman3"
[string] $SiteCode = "UVM"
[string] $sourceDir = "\\confman3\sources\drivers\import"
[string] $packageDir = "\\confman3\sources\drivers\packages"
[int] $currentDepth = 1

Function Clean-DriverDir {
    param ([string]$dir)
	# Clean up "cruft" files that lead to duplicate drivers in the share:
	Write-Custom "Cleaning extraneous files from $dir" -ForegroundColor Cyan
	$delItems = gci -recurse -Include version.txt,release.dat,cachescrubbed.txt,btpmwin.inf -LiteralPath $dir
	Write-Custom "Found " $delItems.count " files to delete..." -ForegroundColor Yellow
	if ($delItems.count -ne 0) {
		$delItems | remove-Item -force -confirm:$false
		$delItems = gci -recurse -Include version.txt,release.dat,cachescrubbed.txt,btpmwin.inf -LiteralPath $dir
		Write-Custom "New count for extraneous files: " $delItems.count -ForegroundColor Yellow
	}	
}

Function Get-SCCMDriverCategory
{
    [CmdletBinding()]
    PARAM
    (
        [Parameter(Position=1)] $categoryName
    )

    # Build the appropriate filter to return all categories or just one specified by name
    $filter = "CategoryTypeName = 'DriverCategories'"
    if ($categoryName -eq "" -or $categoryName -eq $null)
    {
        Write-Debug "Retriving all categories"
    }
    else
    {
        $filter += " and LocalizedCategoryInstanceName = '" + $categoryName + "'"
    }

    # Retrieve the matching list
    Get-SCCMObject SMS_CategoryInstance -filter $filter
}

Function New-SCCMDriverCategory
{
    [CmdletBinding()]
    PARAM
    (
        [Parameter(Position=1)] $categoryName
    )

    # Create a SMS_Category_LocalizedProperties instance
    $localizedClass = [wmiclass]"\\$sccmServer\$($sccmNamespace):SMS_Category_LocalizedProperties"

    # Populate the localized settings to be used with the new driver instance
    $localizedSetting = $localizedClass.psbase.CreateInstance()
    $localizedSetting.LocaleID =  1033 
    $localizedSetting.CategoryInstanceName = $categoryName
    [System.Management.ManagementObject[]] $localizedSettings += $localizedSetting

    # Create the unique ID
    $categoryGuid = [System.Guid]::NewGuid().ToString()
    $uniqueID = "DriverCategories:$categoryGuid"

    # Build the parameters for creating the collection
    $arguments = @{CategoryInstance_UniqueID = $uniqueID; LocalizedInformation = $localizedSettings; SourceSite = $sccmSiteCode; CategoryTypeName = 'DriverCategories'}

    # Create the new instance
    set-wmiinstance -class SMS_CategoryInstance -arguments $arguments -computername $sccmServer -namespace $sccmNamespace
}

Function New-SCCMDriverPackage
{
    [CmdletBinding()]
    PARAM
    (
        [Parameter(Position=1)] $name, 
        [Parameter(Position=2)] $description,
        [Parameter(Position=3)] $sourcePath
    )

    # Build the parameters for creating the collection
    $arguments = @{Name = $name; Description = $description; PkgSourceFlag = 2; PkgSourcePath = $sourcePath}
    $newPackage = set-wmiinstance -class SMS_DriverPackage -arguments $arguments -computername $sccmServer -namespace $sccmNamespace
    
    # Hack - for some reason without this we don't get the PackageID value
    $hack = $newPackage.PSBase | select * | out-null
    
    # Return the package
    $newPackage
}

Function New-SCCMFolder            
{            
  Param(                      
    $FolderName,
    $FolderType,            
    $ParentFolderID = 0
  )            
    
  If ($FolderType -eq "Device") { $FolderType = 5000 }
  If ($FolderType -eq "User") { $FolderType = 5001 }
                
  $SMSFolderClass = "SMS_ObjectContainerNode"             
  $Colon = ":"            
                    
  $WMIConnection = [WMIClass]"\\$sccmServer\$sccmNamespace$Colon$SMSFolderClass"            
  $CreateFolder = $WMIConnection.psbase.CreateInstance()            
  $CreateFolder.Name = $FolderName            
  $CreateFolder.ObjectType = $FolderType            
  $CreateFolder.ParentContainerNodeid = $ParentFolderID            
  $FolderResult = $CreateFolder.Put()
  
  $FolderID = $FolderResult.RelativePath.Substring($FolderResult.RelativePath.Length - 8, 8)
  
  $FolderID            
                
}

Function Move-SCCMObject            
{            
  Param(                    
    $SourceFolderID = 0,            
    $TargetFolderID,            
    $ObjectID,            
    $ObjectType           
  )
          
  If ($ObjectType -eq "Device") { $ObjectType = 5000 }
  If ($ObjectType -eq "User") { $ObjectType = 5001 }           
                      
  $Method = "MoveMembers"            
  $SMSObjectClass = "SMS_ObjectContainerItem"            
  $Colon = ":"            
                    
  $WMIConnection = [WMIClass]"\\$sccmServer\$sccmNamespace$Colon$SMSObjectClass"            
  $InParams = $WMIConnection.psbase.GetMethodParameters("MoveMembers")            
  $InParams.ContainerNodeID = $SourceFolderId            
  $InParams.InstanceKeys = $ObjectID           
  $InParams.ObjectType = $ObjectType            
  $InParams.TargetContainerNodeID = $TargetFolderID            
  $null = $WMIConnection.psbase.InvokeMethod($Method,$InParams,$null)
           
}

Function Get-ContentHash
{
    Param (
        $File,
        [ValidateSet("sha1","md5")]
        [string]$Algorithm="md5"
    )
	
    $content = "$($file.Name)$($file.Length)"
    $algo = [type]"System.Security.Cryptography.md5"
	$crypto = $algo::Create()
    $hash = [BitConverter]::ToString($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($content))).Replace("-", "")
    $hash
}

Function Get-FolderHash
{
    Param (
        [string]$Folder=$(throw("You must specify a folder to get the checksum of.")),
        [ValidateSet("sha1","md5")]
        [string]$Algorithm="md5"
    )
    
     $content = @()
	Get-ChildItem $Folder -Recurse -Exclude "*.hash" | % { $content += Get-ContentHash $_ $Algorithm }
   
    $algo = [type]"System.Security.Cryptography.$Algorithm"
	$crypto = $algo::Create()
	$hash = [BitConverter]::ToString($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($content))).Replace("-", "")
    
    $hash
}

Function Write-Custom($message, [System.ConsoleColor]$foregroundcolor)  
{  
	
	For ($i = 2; $i -le $currentDepth; $i++)
	{
		$tab += "`t"
	}
	
	$currentColor = $Host.UI.RawUI.ForegroundColor  
	if ($foregroundcolor)
	{
		$Host.UI.RawUI.ForegroundColor = $foregroundcolor
	}
	if ($message)  
	{  
		Write-Output "$($tab)$($message)"
	}  
	$Host.UI.RawUI.ForegroundColor = $currentColor 
}

Function Write-Headline($message)
{

	$dot = "------------------------------------------------------------------------------------------------------------"
	
	For ($i = 2; $i -le $currentDepth; $i++)
	{
		$dot = $dot.Substring(0, $dot.Length-8)
	}
	Write-Custom " "
	Write-Custom "$($dot)"
	Write-Custom "$($message)"
	Write-Custom "$($dot)"
}

Function New-SCCMConnection {

    [CmdletBinding()]
    PARAM
    (
        [Parameter(Position=1)] $serverName,
        [Parameter(Position=2)] $siteCode
    )


    # Clear the results from any previous execution

    Clear-Variable -name sccmServer -errorAction SilentlyContinue
    Clear-Variable -name sccmNamespace -errorAction SilentlyContinue
    Clear-Variable -name sccmSiteCode -errorAction SilentlyContinue
    Clear-Variable -name sccmConnection -errorAction SilentlyContinue


    # If the $serverName is not specified, use "."

    if ($serverName -eq $null -or $serverName -eq "")
    {
        $serverName = "."
    }


    # Get the pointer to the provider for the site code

    if ($siteCode -eq $null -or $siteCode -eq "")
    {
        Write-Verbose "Getting provider location for default site on server $serverName"
        $providerLocation = get-wmiobject -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -namespace "root\sms" -computername $serverName -errorAction Stop
    }
    else
    {
        Write-Verbose "Getting provider location for site $siteName on server $serverName"
        $providerLocation = get-wmiobject -query "select * from SMS_ProviderLocation where SiteCode = '$siteCode'" -namespace "root\sms" -computername $serverName -errorAction Stop
    }


    # Split up the namespace path

    $parts = $providerLocation.NamespacePath -split "\\", 4
    Write-Verbose "Provider is located on $($providerLocation.Machine) in namespace $($parts[3])"
    $global:sccmServer = $providerLocation.Machine
    $global:sccmNamespace = $parts[3]
    $global:sccmSiteCode = $providerLocation.SiteCode


     # Make sure we can get a connection

    $global:sccmConnection = [wmi]"${providerLocation.NamespacePath}"
    Write-Verbose "Successfully connected to the specified provider"
}

function Get-SCCMObject {

    [CmdletBinding()]
    PARAM
    (
        [Parameter(Position=1)] $class, 
        [Parameter(Position=2)] $filter
    )

    if ($filter -eq $null -or $filter -eq "")
    {
        get-wmiobject -class $class -computername $sccmServer -namespace $sccmNamespace
    }
    else
    {
        get-wmiobject -query "select * from $class where $filter" -computername $sccmServer -namespace $sccmNamespace
    }
}


Function Import-SCCMDriverStore
{
	PARAM
    (
    [Parameter(Position=1)] $DriverStore,
    [Parameter(Position=3)] $CMPackageSource,
		#[Parameter(Position=4)] $PackageDepth,
		#[Parameter(Position=5)] $FolderDepth = ($packageDepth - 1),
		#[Parameter(Position=6)] $NameDepth = 1,
		[switch] $cleanup
    )
	
	
	if ($cleanup)
    {
		$currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() )
		if (!$currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ))
		{
			Write-Custom "You need to run Powershell as Administrator, to use the -Mirror switch." Red
			return;
		}
	
	}

	Write-Headline "Started Importing Driver Store: $($driverStore)"
	
	Get-ChildItem $driverStore | ? {$_.psIsContainer -eq $true} | % {
	
		$global:CurrentDepth = 1

		SDS-ProcessFolder $_
		
	
	}
	
}

Function SDS-ProcessFolder($path)
{
	$FolderPath = $path.FullName.Substring($DriverStore.Length+1, $path.FullName.Length-($DriverStore.Length+1))
	$FolderName = $path.FullName.Substring($DriverStore.Length+1, $path.FullName.Length-($DriverStore.Length+1))
	Write-Headline "Processing Folder: $($FolderName)"
	$CMFolderID = SDS-Folder $path 0
	Get-ChildItem $_.FullName | ? {$_.psIsContainer -eq $true} | % {
		$CurrentDepth = 2
		SDS-ProcessPackage $_ $FolderPath $CMFolderID
	}
}

Function SDS-Folder($folder, $parentID)  
{	
		
		$CMFolder = Get-SCCMObject -Class "SMS_ObjectContainerNode" -Filter "Name = `"$($folder.Name)`" AND ParentContainerNodeID = $($parentID) AND ObjectType = 23"
		
		If ($CMFolder)
		{
			$CMFolderID = $CMFolder.ContainerNodeID
		}
		Else
		{
			$CMFolderID = New-SCCMFolder -FolderName $folder.Name -FolderType 23 -ParentFolderID $parentID
			#Write-Custom "Created SCCM folder $($folder.Name) ($($SCCMFolderID))"
		}
		$CMFolderID
}


Function SDS-ProcessPackage($package, $folderPath, $folderID)
{
	$PackageName = $package.FullName.Substring($DriverStore.Length+1, $package.FullName.Length-($DriverStore.Length+1))
	
	#$PackageName = $PackageName.Substring($NameIndex+1, $PackageName.Length-($NameIndex+1))
	$PackageName = $PackageName.Replace("\","-")
	
	Write-Headline "Processing Driver Package: $($PackageName)"
	$PackageHash = Get-FolderHash $package.FullName
	If (Get-ChildItem $package.FullName -Filter "$($PackageHash).hash")
	{
		Write-Custom "No changes has been made to this Driver Package. Skipping." DarkGray
	}
	Else
	{
		#Cleanup the source directory to avoid import of duplicate drivers:
		Clean-DriverDir($package.FullName)
		
		$CMCategory = Get-SCCMDriverCategory -categoryName $PackageName
		if ($CMCategory -eq $null)
		{
			$CMCategory = New-SCCMDriverCategory $PackageName
			Write-Custom "Created new driver category $($PackageName)"
		}
		

		$CMPackageTrue = (get-wmiobject -query "Select * from SMS_DriverPackage join SMS_ObjectContainerItem ON SMS_ObjectContainerItem.InstanceKey = SMS_DriverPackage.PackageID WHERE SMS_ObjectContainerItem.ObjectType = 23 AND SMS_ObjectContainerItem.ContainerNodeID = `"$($folderID)`" AND SMS_DriverPackage.Name = `"$($PackageName)`"" -computername $sccmServer -namespace $sccmNamespace).SMS_DriverPackage
		if ($CMPackageTrue -eq $null) { $CMPackageTrue = get-wmiobject -query "Select * from SMS_DriverPackage join SMS_ObjectContainerItem ON SMS_ObjectContainerItem.InstanceKey = SMS_DriverPackage.PackageID WHERE SMS_ObjectContainerItem.ObjectType = 23 AND SMS_ObjectContainerItem.ContainerNodeID = `"$($folderID)`" AND SMS_DriverPackage.Name = `"$($PackageName)`"" -computername $sccmServer -namespace $sccmNamespace }
		$CMPackage = get-wmiobject -query "Select * from SMS_DriverPackage WHERE SMS_DriverPackage.PackageID = `"$($CMPackageTrue.PackageID)`"" -computername $sccmServer -namespace $sccmNamespace
		
		if ($CMPackage -eq $null)
		{
			Write-Custom "Creating new driver package $($PackageName)"
			$CMPackageSource = "$($CMPackageSource)\$($folderPath)\$($PackageName)"
			#$CMPackageSource = "$($CMPackageSource)\$($PackageName)"
			if (Test-Path $CMPackageSource)
				{
				if((Get-Item $CMPackageSource | %{$_.GetDirectories().Count + $_.GetFiles().Count}) -gt 0)
				{
					if ($cleanup)
					{
						Write-Custom "Folder already exists, removing content" Yellow
						dir $driverPackageSource | remove-item -recurse -force
					}
					else
					{
						Write-Custom "Folder already exists, remove it manually." Red
						return
					}
				}
			}
			else
			{
				$null = MkDir $CMPackageSource
			}
			
			$CMPackage = New-SCCMDriverPackage -name $PackageName -sourcePath $CMPackageSource
			Move-SCCMObject -TargetFolderID $folderID -ObjectID $CMPackage.PackageID -ObjectType 23
		}
		else
		{
			Write-Custom "Existing driver package $($PackageName) ($($CMPackage.PackageID)) retrieved." DarkGray
		}
		
		#$CurrentDepth += 1
		
		#$driverPackageContent = get-wmiobject -computername $sccmServer -namespace $sccmNamespace -query "SELECT * FROM SMS_Driver WHERE CI_ID IN (SELECT CTC.CI_ID FROM SMS_CIToContent AS CTC JOIN SMS_PackageToContent AS PTC ON CTC.ContentID=PTC.ContentID JOIN SMS_DriverPackage AS Pkg ON PTC.PackageID=Pkg.PackageID WHERE Pkg.PackageID='$($CMPackage.PackageID)')"
		#Get-ChildItem $package.FullName -Filter *.inf -recurse | Import-SCCMDriver -category $CMCategory -package $CMPackage | % {
		
		
		#}
		
		Get-ChildItem $package.FullName -Filter *.inf -recurse | % { SDS-ImportDriver $_ $CMCategory $CMPackage }
		
		Get-ChildItem $package.FullName -Filter "*.hash"  | Remove-Item 
		$null = New-Item "$($package.FullName)\$($PackageHash).hash" -type file 
	}
}

Function SDS-ImportDriver($dv, $category, $package)
{

		# Split the path
        $driverINF = split-path $dv.FullName -leaf 
        $driverPath = split-path $dv.FullName

        # Create the class objects needed
        $driverClass = [WmiClass]("\\$sccmServer\$($sccmNamespace):SMS_Driver")
        $localizedClass = [WmiClass]("\\$sccmServer\$($sccmNamespace):SMS_CI_LocalizedProperties")

        # Call the CreateFromINF method
        $driver = $null
        $InParams = $driverClass.psbase.GetMethodParameters("CreateFromINF")
        $InParams.DriverPath = $driverPath
        $InParams.INFFile = $driverINF
        try
        {
            $R = $driverClass.PSBase.InvokeMethod("CreateFromINF", $inParams, $Null)

            # Get the display name out of the result
            $driverXML = [XML]$R.Driver.SDMPackageXML
            $displayName = $driverXML.DesiredConfigurationDigest.Driver.Annotation.DisplayName.Text

            # Populate the localized settings to be used with the new driver instance
            $localizedSetting = $localizedClass.psbase.CreateInstance()
            $localizedSetting.LocaleID =  1033 
            $localizedSetting.DisplayName = $displayName
            $localizedSetting.Description = ""
            [System.Management.ManagementObject[]] $localizedSettings += $localizedSetting

            # Create a new driver instance (one tied to the right namespace) and copy the needed 
            # properties to it.
            $driver = $driverClass.CreateInstance()
            $driver.SDMPackageXML = $R.Driver.SDMPackageXML
            $driver.ContentSourcePath = $R.Driver.ContentSourcePath
            $driver.IsEnabled = $true
            $driver.LocalizedInformation = $localizedSettings
            $driver.CategoryInstance_UniqueIDs = @($category.CategoryInstance_UniqueID)

            # Put the driver instance
            $null = $driver.Put()
        
            Write-Custom "New Driver: $($displayName)"
        }
        catch [System.Management.Automation.MethodInvocationException]
        {
            $e = $_.Exception.GetBaseException()
            if ($e.ErrorInformation.ErrorCode -eq 183)
            {
                
                # Look for a match on the CI_UniqueID    
                $query = "select * from SMS_Driver where CI_UniqueID = '" + $e.ErrorInformation.ObjectInfo + "'"
                $driver = get-WMIObject -query $query.Replace("\", "/") -computername $sccmServer -namespace $sccmNamespace         
 
				Write-Custom "Duplicate Driver: $($driver.LocalizedDisplayName)" DarkGray
	
                # Set the category
                if (-not $driver)
                {
                    Write-Custom "`tUnable to import and no existing driver found." Yellow
                    return
                }
                elseif ($driver.CategoryInstance_UniqueIDs -contains $category.CategoryInstance_UniqueID)
                {
                    Write-Verbose "Existing driver is already in the specified category."
                }
                else
                {
                    $driver.CategoryInstance_UniqueIDs += $category.CategoryInstance_UniqueID
                    $null = $driver.Put()
                    Write-Verbose "Adding driver to category"
                }
            }
            else
            {
                Write-Custom "`tUnexpected error, skipping INF $($infFile): $($e.ErrorInformation.Description) $($e.ErrorInformation.ErrorCode)" Yellow
                return
            }
        }
        
        # Hack - for some reason without this we don't get the CollectionID value
		$hack = $driver.PSBase | select * | out-null

        # If a package was specified, add the driver to it
        if ($package -ne $null)
        {
			$driverPackageContent = get-wmiobject -computername $sccmServer -namespace $sccmNamespace -query "SELECT * FROM SMS_Driver WHERE CI_ID IN (SELECT CTC.CI_ID FROM SMS_CIToContent AS CTC JOIN SMS_PackageToContent AS PTC ON CTC.ContentID=PTC.ContentID JOIN SMS_DriverPackage AS Pkg ON PTC.PackageID=Pkg.PackageID WHERE Pkg.PackageID='$($package.PackageID)')"
            
			$doesDriverExist = $driverPackageContent | ? {$_.CI_UniqueID -eq $driver.CI_UniqueID}
			if ($doesDriverExist -eq $null)
			{
				# Add the driver to the package since it's not already there
				Write-Verbose "Adding driver to package"
				$null = Add-SCCMDriverPackageContent -package $package -driver $driver
			}

        }

        # Write the driver object to the pipeline
        #$driver

}

function Add-SCCMDriverPackageContent
{
    [CmdletBinding()]
    PARAM
    (
        [Parameter(Position=1)] $package,
        [Parameter(Position=2, ValueFromPipeline=$true)] $driver
    )

    Process
    {
        # Get the list of content IDs
        $idlist = @()
        $ci = $driver.CI_ID
        
        $i = 1
		$ids = Get-SCCMObject -class SMS_CIToContent -filter "CI_ID = '$ci'"

        if ($ids -eq $null)
        {
            Write-Warning "Warning: Driver not found in SMS_CIToContent"
        }
        foreach ($id in $ids)
        {
            $idlist += $id.ContentID
        }

        # Build a list of content source paths (one entry in the array)
        $sources = @($driver.ContentSourcePath)

        # Invoke the method
        try
        {
            $package.AddDriverContent($idlist, $sources, $false)
        }
        catch [System.Management.Automation.MethodInvocationException]
        {
            $e = $_.Exception.GetBaseException()
            if ($e.ErrorInformation.ErrorCode -eq 1078462229)
            {
                Write-Verbose "Driver is already in the driver package (possibly because there are multiple INFs in the same folder or the driver already was added from a different location): $($e.ErrorInformation.Description)"
            }
        }
    }

}

New-SCCMConnection $CMServer $SiteCode
Import-SCCMDriverStore $sourceDir $packageDir

#    This section formerly contained logic that has been moved to "Build-UDIInfoFiles.ps1",
#    which creates CSV files containing SCCM database info for use by UDI clients.

My version changes the import directory format a bit, adds some in-line documentation, and moves the local site variables to the top of the file. I also included a source-tree cleanup command that we used under MDT/LTI that removed useless Dell info files from the import structure. This helped reduce duplicate driver imports under LTI, but it may not be applicable under UDI.

Because of nuances in WordPress, I have had to split part 2 of this post into multiple pages. Driver scripting continues in part 2b:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2b-drivers/


Series Index: