Driver Import

SCCM Bloated Driver Import

This is something that’s been heavily talked about:

This isn’t new.

Unlike some organizations, law firms are sometimes slower to react to things.  Since time is money (for real!) stability is of the utmost importance.  That said, we’re often slow to do certain things no matter how [seemingly] trivial it may be.

We’re still on CU1 and even though we’ve applied KB3084586, after importing drivers, we still noticed quite a bit of bloat.  Sure, some Driver Package were smaller than the source driver files, others were slightly larger (about 10% larger), a handful were 2-3x as large and a couple were as much as 10x the size.  (The Lenovo P500/P700/P900 drivers are about 2GB in size and after the import the Driver Package was 20GB – yikes!)

I opened a case with Microsoft just to confirm that applying CU2 or upgrading to v1511+ would resolve the issue once and for all and after they did their due diligence, they confirmed this.

How do we import drivers then?
How do we apply drivers during OSD?

Well if you don’t already know, there’s a very simple workaround:

  1. Create a Package containing your drivers
  2. Add a Run Command step in your OSD TS that uses that package
  3. The command line should be:
    Dism.exe /image:%OSDisk%\ /Add-Driver /Driver:. /recurse

I won’t bother detailing these steps since Model-Technology did such a fine job documenting it; I think it’s also where I first heard about this method.
After some quick testing we implemented it and it’s been working fine.

We’re getting ready to pilot Windows 10 and we got some new equipment recently that requires adding some drivers to the boot media in order for things to work (NIC, storage on some platforms with RAID controllers) so I began thinking about how we should proceed knowing that applying CU2 or upgrading to v1511 would, understandably, require quite a bit of planning and approval before execution.

Jason Sandys wrote a PowerShell script that enables one to import drivers, bypassing whatever bloat bug is present when done via the GUI.  I took a look at Jason’s script and modified it heavily for our needs and environment.  We have some up-and-coming Engineers in our organization who are also learning about PowerShell, so I tried to make it easy enough for them to use when they start helping us import drivers.

I’m not suggesting this is the best method – it’s just what works for us based on how things have been done prior to my arrival.  With Windows 10 on the horizon, we may opt for a simpler directory structure.

Doesn’t matter where you run this from but it needs access to the Configuration Manager module.

The script accepts a slew of arguments but I leave it up to you to do your own validation.

Jason’s version created really granular categories, like

  • Manufacturer
  • Architecture
  • OS
  • Model
  • etc.

We’re not knocking it at all, but while I appreciated the granularity, we felt it cluttered the category UI, so we settled on a simpler category format:

  • Manufacturer OS Architecture Model

Maybe you have some other format you want to use in addition to the above or a combination of the two.  Whatever the need, the script is pretty flexible in its current form get super granular or a little simpler.

KNOWN ISSUES

  • I made a last minute addition to provide feedback via console outside of the progress bar and that’s not fully tested.  In several executions it worked as expected, but one of my testers reported getting a substring error during execution.  We confirmed everything imported correctly making this fall squarely in my ‘purely an eye-sore’ category.
    I doubt I’ll ever come back & fix this but if I do, I’ll update accordingly.
    .
  • ALSO: Sometimes an autorun.inf or other invalid .inf is included and the import process throws an error.  This is expected for invalid driver files.  I considered adding an exclude for autorun.inf but for now it’s just another eye-sore.
[CmdletBinding()]
Param
(
   [Parameter(Mandatory=$false)]
        [string]$Model,

   [Parameter(Mandatory=$false)]
        [ValidateSet("HP","Lenovo","Dell","HGttG")]
        [string]$Vendor,

   [Parameter(Mandatory=$false)]
        [ValidateSet("Win7","Win8","Win10")]
        [string]$OS,

   [Parameter(Mandatory=$false)]
        [ValidateSet("x86","x64")]
        [string]$Architecture,

   [Parameter(Mandatory=$false)]
        [string]$ImportSource,

   [Parameter(Mandatory=$false)]
        [string]$PackageSourceRoot = "\\path\to\Driver Packages",

    [Parameter(Mandatory=$false)]
        [string]$UserCategory,

    [Parameter(Mandatory=$false)]
        [bool]$GranularUserCategories = $false,

    [Parameter(Mandatory=$false)]
        [ValidateSet('yourpss.f.q.d.n','yourpss')]
        [string]$SCCMServer,

    [Parameter(Mandatory=$false)]
        [bool]$GranularModelCategories = $false
)  

If(($Model) -and ($Vendor) -and ($OS) -and ($Architecture) -and ($ImportSource))
    {
        # Create a hash table array from the command line arguments
        $Drivers = @{$Model = $Vendor,$OS,$Architecture,$ImportSource;}
    }
Else
    {
        # Format of the hash table array:
                    # 'MODELA'                     = 'Vendor','OSv1','Arch','path\to\drivers\OSv1\Arch\modela'
                    # 'MODELB MODELC'              = 'Vendor','OSv1','Arch','path\to\drivers\OSv1\Arch\modelb_modelc'
                    # 'MODELD MODELE MODELF'       = 'Vendor','OSv2','Arch','path\to\drivers\OSv2\Arch\modeld_modele_modelf'

        $Drivers = @{#"FakeModel0 FakeModel1"      = 'Dell',  'Win7',' x64',"\\path\to\Driver Source\Dell\Win7\x64\Fake_Models_0_1";
                     #"FakeModel2 FakeModel3"      = 'HP',    'Win8',' x86',"\\path\to\Driver Source\HP\Win7\x86\Fake_Models_2_3";
                     #"FakeModel6 FakeModel7"      = 'Lenovo','Win10','x64',"\\path\to\Driver Source\Lenovo\Win10\x64\Fake_Models_6_7";
                     #"FakeModel8 FakeModel9"      = 'Dell',  'Win7', 'x64',"\\path\to\Driver Source\Lenovo\Win7\x86\Fake_Models_8_9";
                     #"Model42"                    = 'HGttG', 'Win7', 'x64',"\\path\to\Driver Source\HGttG\Win7\x64\Fake_Models_8_9";
                     #"FOR MANUAL SCRIPT EDITING"  = 'VENDOR','OS','ARCHITECTURE',"PATH\TO\DRIVERS";
                    }
    }

# Make sure we're not in the SCCM PSDrive otherwise Get-Item/Test-path fails
if($presentLocation) { if((Get-Location).Path -ne $presentLocation) { Set-Location $presentLocation } }
Else { Set-Location "$env:windir\System32" }

If(-not $Drivers.Count -ge 1) { write-error "ERROR: NO DRIVERS IN HASH TABLE ARRAY"; Set-Location $presentLocation; Exit 1 }

Foreach($Pair in ($Drivers.GetEnumerator()))
    {
        Write-Host "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"

        $Model = $Pair.Name
        $DriverDetails = $Pair.Value -split ','
        $Vendor = $DriverDetails[0]
        $OS = $DriverDetails[1]
        $Architecture = $DriverDetails[2]
        $ImportSource = $DriverDetails[3]
        #$packageName = "$Vendor $Model - $OS $Architecture"
        $packageName = "$Vendor $OS $Architecture $Model"
        $packageSourceLocation = "$PackageSourceRoot\$Vendor\$OS`_$Architecture`_$($Model.Replace(' ','_'))"
        $UserCategory = "$Vendor $OS $Architecture $Model"

        # Verify Driver Source exists.
        Write-Host "Checking Import Source [$ImportSource]"

        If (Get-Item $ImportSource -ErrorAction SilentlyContinue)
            {
                $presentLocation = (Get-Location)

                # Get driver files
                write-host "Building List of Drivers..."
                $infFiles = Get-ChildItem -Path $ImportSource -Recurse -Filter "*.inf"
                write-host "Found [$($infFiles.Count)] INF files"

                # Import ConfigMgr module
                if(!(Get-Module -Name ConfigurationManager)) { Import-Module (Join-Path $Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-4) 'ConfigurationManager.psd1') }

                $PSD = Get-PSDrive -PSProvider CMSite

                Set-Location "$($PSD):"

                $driverPackage = Get-CMDriverPackage -Name $packageName

                If ($driverPackage)
                {
                    Write-Warning "Driver Package Directory Already Exists [$packageName]"
                }
                else
                {
                    If (Get-Item FileSystem::$packageSourceLocation -ErrorAction SilentlyContinue)
                    {
                        Write-Error "ERROR: Package Source Location Already exists [$packageSourceLocation] "
                        Set-Location $presentLocation; Exit 1
                    }
                    else
                    {
                        Write-Host "Creating New Driver Package Source Directory [$packageSourceLocation]"
                        New-Item -ItemType Directory FileSystem::$packageSourceLocation | Out-Null
                    }

                    Write-Host "Creating New Empty Driver Package for [$packageName]"
                    $driverPackage = New-CMDriverPackage -Name $packageName -Path $packageSourceLocation
                }

                if($UserCategory)
                    {
                        if($GranularUserCategories -eq $true)
                            {
                                $usrCategory = @()
                                foreach($userCategory in $UserCategory -split ' ')
                                    {
                                        $tmpUserCategory = Get-CMCategory -Name $UserCategory
                                        if(-not $tmpUserCategory) { $usrCategory += New-CMCategory -CategoryType DriverCategories -Name $UserCategory }
                                        Else { $usrCategory += $tmpUserCategory }
                                    }
                            }
                        Else
                            {
                                $usrCategory = Get-CMCategory -Name $UserCategory
                                if(-not $usrCategory) { $usrCategory = New-CMCategory -CategoryType DriverCategories -Name $UserCategory }
                            }
                    }

                if($GranularModelCategories -eq $true)
                    {
                        $modelCategory = @()
                        foreach($Mdl in $Model -split ' ')
                            {
                                $tmpModelCategory = Get-CMCategory -Name $Mdl
                                if(-not $tmpModelCategory) { $modelCategory += New-CMCategory -CategoryType DriverCategories -Name $Mdl }
                                Else { $modelCategory += $tmpModelCategory }
                            }

                        $architectureCategory = Get-CMCategory -Name $Architecture
                        if(-not $architectureCategory) { $architectureCategory = New-CMCategory -CategoryType DriverCategories -Name $Architecture }

                        $osCategory = Get-CMCategory -Name $OS
                        if(-not $osCategory) { $osCategory = New-CMCategory -CategoryType DriverCategories -Name $OS }

                        $vendorCategory = Get-CMCategory -Name $Vendor
                        if(-not $vendorCategory) { $vendorCategory = New-CMCategory -CategoryType DriverCategories -Name $Vendor }
                    }

                if($UserCategory) { $categories = @((Get-CMCategory -Name $Model), (Get-CMCategory -Name $Architecture), (Get-CMCategory -Name $OS), (Get-CMCategory -Name $Vendor), (Get-CMCategory -Name $UserCategory)) }
                Else { $categories = @((Get-CMCategory -Name $Model), (Get-CMCategory -Name $Architecture), (Get-CMCategory -Name $OS), (Get-CMCategory -Name $Vendor)) }

                $totalInfCount = $infFiles.count
                $driverCounter = 0
                $driversIds = @()
                $driverSourcePaths = @()

                foreach($driverFile in $infFiles)
                    {
                        $Activity = "Importing Drivers for [$Model]"
                        $CurrentOperation = "Importing: $($driverFile.Name)"
                        $Status = "($($driverCounter + 1) of $totalInfCount)"
                        $PercentComplete = ($driverCounter++ / $totalInfCount * 100)
                        Write-Progress -Id 1 -Activity $Activity -CurrentOperation $CurrentOperation -Status $Status -PercentComplete $PercentComplete

                        if($PercentComplete -gt 0) { $PercentComplete = $PercentComplete.ToString().Substring(0,$PercentComplete.ToString().IndexOf(".")) }
                        write-host "$Status :: $Activity :: $CurrentOperation $PercentComplete%"

                        try
                            {
                                $importedDriver = (Import-CMDriver -UncFileLocation $driverFile.FullName -ImportDuplicateDriverOption AppendCategory -EnableAndAllowInstall $True -AdministrativeCategory $categories | Select *)
                                #$importedDriver = (Import-CMDriver -UncFileLocation $driverFile.FullName -ImportDuplicateDriverOption AppendCategory -EnableAndAllowInstall $True -AdministrativeCategory $categories -DriverPackage $driverPackage -UpdateDistributionPointsforDriverPackage $False | Select *)

                                <#                                 if($importedDriver)                                     {                                         Write-Progress -Id 1 -Activity $Activity -CurrentOperation "Adding to [$packageName] driver package [$($driverFile.Name)]:" -Status "($driverCounter of $totalInfCount)" -PercentComplete ($driverCounter / $totalInfCount * 100)                                         Add-CMDriverToDriverPackage -DriverId $importedDriverID -DriverPackageName $packageName                                         write-host "importedDriver.CI_ID [$($importedDriver.CI_ID)]"                                         if($SCCMServer) { $driverContent = Get-WmiObject -Namespace "root\SMS\Site_$PSD" -class SMS_CIToContent -Filter "CI_ID='$($importedDriver.CI_ID)'" -ComputerName $SCCMServer }                                         else { $driverContent = Get-WmiObject -Namespace "root\sms\site_$PSD" -class SMS_CIToContent -Filter "CI_ID='$($importedDriver.CI_ID)'" }                                         write-host "driverContent.ContentID [$($driverContent.ContentID)]"                                         $driversIds += $driverContent.ContentID                                         write-host "ContentSourcePath [$($importedDriver.ContentSourcePath)]"                                         $driverSourcePaths += $importedDriver.ContentSourcePath                                     }                                 #>
                                Clear-Variable importedDriver -ErrorAction SilentlyContinue
                            }
                        catch
                            {
                                Write-Host "ERROR: Failed to Import Driver for [$Model]: [$($driverFile.FullName)]" -ForegroundColor Red
                                Write-Host "`$_ is: $_" -ForegroundColor Red
                                Write-Host "`$_.exception is: $($_.exception)" -ForegroundColor Red
                                Write-Host "Exception type: $($_.exception.GetType().FullName)" -ForegroundColor Red
                                Write-Host "TargetObject: $($error[0].TargetObject)" -ForegroundColor Red
                                Write-Host "CategoryInfo: $($error[0].CategoryInfo)" -ForegroundColor Red
                                Write-Host "InvocationInfo: $($error[0].InvocationInfo)" -ForegroundColor Red
                                write-host
                            }
                    }

                Write-Progress -Id 1 -Activity $Activity -Completed

                <#                 $rawDriverPackage = Get-WmiObject -Namespace "root\sms\site_$($PSD)" -ClassName SMS_DriverPackage -Filter "PackageID='$($driverPackage.PackageID)'"                 $result = $rawDriverPackage.AddDriverContent($driversIds, $driverSourcePaths, $false)                 if($result.ReturnValue -ne 0)                     {                         Write-Error "Could not import: $($result.ReturnValue)."                     }                 #>

                Set-Location $presentLocation
            }
        else
            {
                Write-Warning "Cannot Continue: Driver Source not found [$ImportSource]"
            }

        write-host "Finised Processing Drivers for [$Model]"
        Write-Host "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
        Write-Host
    }

Myself and several of my colleagues have used this to import loads of drivers for a slew of hardware and it’s worked fine for us.

Again, many many thanks to Jason for the core of this script.

Advertisements