Post-deployment Driver Installation using PowerShell scripts.

  • Hello,

    I just wanted to share how I’ve been handling driver installation to have 1 image work for over 20 different models. I’ve done this for both Windows 7 and Windows 10 with (mostly) great results.

    I’d argue that the most important step is to make sure your image contains all of the NIC drivers for all the models that you need to support. Drivers are going to be hosted on a network share. No NIC drivers means no Internet.

    Go ahead and download all of the NIC drivers for all of your models or find driver packs online. Once downloaded extract the files to a common folder. In the root of the folder create a PowerShell script with the following line. Run the script. (You may need to adjust your Execution Policy to run scripts. Google it.)

    Get-ChildItem -Path $PSScriptRoot -Recurse | Where-Object -Property Extension -EQ ".inf" | ForEach { PnPUtil.exe -a $PSItem.FullName } 

    This will do a recursive search for all files that have the .inf extension and use pnputil to add them to Windows driver store. During sysprep Windows will detect your hardware and pull the correct driver from the driver store.

    Besides the NIC drivers, there are only 3 files that I add to the image. My unattend.xml, SetupComplete.cmd, and SetupComplete.ps1. The rest are pulled from a network share.

    I assume you already have a working answer file, I’m not going to go into that or how to use sysprep.

    SetupComplete.cmd - Located in C:\Windows\Setup\Scripts\ - You’ll need to create the Scripts folder.

    ECHO Initializing Driver Installation Script
    START /wait PowerShell.exe -Command "& '%~dpn0.ps1'"
    ECHO Removing configuration files.
    CD %dp0
    DEL SetupComplete.ps1
    CD %windir%\System32\Sysprep
    DEL unattend.xml

    This will launch a PowerShell script with the same name as the SetupComplete.cmd file, stored in the same location. SetupComplete.ps1.
    After SetupComplete.ps1 is finished running it will get deleted. The unattend.xml file also gets deleted.

    SetupComplete.ps1 - Located in C:\Windows\Setup\Scripts\

    # Credentials used to access network drive.
    $Username = "Domain\Username"
    $Password = "Password" | ConvertTo-SecureString -AsPlainText -Force
    $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $Password
    $RootPath = "$env:HOMEDRIVE\Drivers\"
    # Function to determine if is reachable, used to verify
    # that NIC drivers are working. Waits up to 30 seconds.
    Function Wait-ForConnection {
        $wait = $true
        $count = 30
        Write-Host "Establishing network connection." -NoNewline
        while($wait) {
            $connection = Test-Connection -ErrorAction SilentlyContinue
            if ($connection) {
                Write-Host "Connection established!" -ForegroundColor Green
                $wait = $false
            } else {
                Write-Host " ." -NoNewline
                $count = ($count -1)
                Start-Sleep -Seconds 1
            if ($count -le 0) {
                $wait = $false
                Write-Host "Unable to establish a network connection." -ForegroundColor Red
    Write-Host "Mounting network drive."
    New-PSDrive -Name "Deploy" -PSProvider FileSystem -Root \\ShareName\Windows10 -Credential $Credential | Out-Null
    $Deploy = Test-Path -Path Deploy:\ -ErrorAction SilentlyContinue
    if ($Deploy) {
        Write-Host "Successfully mounted network drive." -ForegroundColor Green
        if (!(Test-Path -Path $RootPath)) {
            New-Item -Path $RootPath -ItemType Directory | Out-Null
        $Controller = "Deploy:\Controller.ps1"
        Copy-Item -Path $Controller -Destination "$env:HOMEDRIVE\Drivers\Controller.ps1" -Force
        $Controller = "$env:HOMEDRIVE\Drivers\Controller.ps1"
        Write-Host "Running script " -NoNewline 
        Write-Host "Controller.ps1 " -NoNewline -ForegroundColor Yellow
        Invoke-Expression -Command $Controller
        # Do other stuff, etc...
        Write-Host "Unmounting network drive."
        Remove-PSDrive -Name "Deploy"
    } else { 
        Write-Host "Failed to mount network drive." -ForegroundColor Red
    Start-Sleep -Seconds 5

    First off, this will check to see if you can reach (Change this to something relevant to you).
    If you can reach it then your NIC is probably working.
    Second, it will mount a network drive using supplied credentials. The account only needs Read access to the share and it’s contents.
    Third, it will copy another PowerShell script that I’ve titled Controller from the network share to a local directory. It’ll then run that script.
    Last, after Controller.ps1 has finished doing it’s thing we unmount the network drive and restart the computer.

    Controller.ps1 - Stored in the root of the network share you mounted in the last step.
    Note: This is a shortened version. I removed somethings for the purpose of this guide.

    $Model = (Get-WmiObject -Class Win32_ComputerSystem).Model
    Write-Host "Model identified as: " -NoNewline -ForegroundColor DarkYellow
    Write-Host $Model -ForegroundColor Yellow
    $file = ""
    switch ($Model) {
        "OptiPlex 7040" { $file = "dell7040.ps1"; break; }
        "Precision 3510" { $file = "dell3510.ps1"; break; }
        "Precision T1700" { $file = "dell1700.ps1"; break; }
        "Precision Tower 7810" { $file = "dell7810.ps1"; break; }
        "HP Compaq Pro 6300 SFF" { $file = "hp6300.ps1"; break; }
        "VirtualBox" { $file = "virtualbox.ps1"; break; }
        default { 
            # There were multiple numbers for these models.
            If ($model -like '*6710b*') { $file = "hp6710.ps1"; }
            ElseIf ($model -like '*6730b*') { $file = "hp6730.ps1"; }
    $filePath = $RootPath + "Model.ps1"
    Copy-Item -Path "Deploy:\ModelScripts\$file" -Destination $filePath -Force
    Invoke-Expression -Command $filePath
    if ((Get-WmiObject -Class Win32_ComputerSystem).Manufacturer -match "Dell") {
        # May do some Dell BIOS settings here.
    Write-Host "Downloading FOG Client from FOGServer." -ForegroundColor Yellow
    $url = "http://<FOGSERVER>/fog/client/download.php?newclient"
    $outfile = "$RootPath + FOGService.msi"
    (New-Object System.Net.WebClient).DownloadFile($url, $outfile)
    Write-Host "Installing FOG Client." -ForegroundColor Cyan
    $ArgumentList = "/i $outfile /quiet USETRAY=""0"" WEBADDRESS=""<FOGSERVER>"" WEBROOT=""/fog"" /norestart"
    Start-Process -FilePath MSIEXEC.exe -ArgumentList $ArgumentList -Wait
    Write-Host "Creating a scheduled task to start the FOGClient after reboot."
    $TaskPath = "$env:windir\Setup\StartFOG.ps1 "
    Copy-Item -Path "Deploy:\StartFOG.ps1" -Destination $TaskPath
    $Action = New-ScheduledTaskAction -Execute PowerShell.exe -Argument "-Command ""& $TaskPath """
    $Trigger = New-ScheduledTaskTrigger -AtStartup
    Register-ScheduledTask -Action $Action -Trigger $Trigger -TaskName "StartFOG" -User "NT AUTHORITY\SYSTEM" -RunLevel Highest -Force
    Write-Host "Deleting downloaded driver files."
    Remove-Item -Path $RootPath -Recurse -Force

    Here we use a quick WMI query to get the model number of the PC and use a switch to pick the model specific driver installation script. You’ll want to run the query on each machine beforehand to find out exactly how it’s stored.

    The beauty of this is that it’s flexible. You can easily add support for new models as long as the NIC drivers are on the image.

    hp6300.ps1 - Located in Deploy:\ModelScripts\ - You’ll make similiar script for each model. This is a very simple one.

    Write-Host "$Model Driver Installation" -ForegroundColor DarkGreen
    $chipset = $RootPath + "chipset.exe"
    $audio = $RootPath + "0008-64bit_Win7_Win8_Win81_Win10_R281\Setup.exe"
    Write-Host "Downloading Intel Chipset Drivers."
    Copy-Item -Path "Deploy:\Drivers\chipset\Intel\SetupChipset.exe" -Destination $chipset -Force
    Write-Host "Downloading Realtek HD Audio Drivers."
    Copy-Item -Path "Deploy:\Drivers\audio\Realtek\0008-64bit_Win7_Win8_Win81_Win10_R281\" -Destination $RootPath -Recurse -Force
    Write-Host "Installing Intel Chipset Support Drivers."
    Start-Process -FilePath $chipset -ArgumentList "/s /norestart" -Wait
    Write-Host "Installing Realtek HD Audio Driver."
    Start-Process -FilePath $audio -ArgumentList "/s" -Wait
    Start-Sleep 5

    Doesn’t really get much simplier than this one. Most drivers Windows 10 already picked up. The process is basically the same for every driver, as long as they are properly signed.

    This should be enough information to get you started, if you want to do it this way. I like doing it this way because it gives me a high level of control over everything. I can specify exactly what version gets installed onto each model. You could even query the serial number of the PC and install device specify software, if you really wanted to get down to that level of detail (FOG snapins would obviously be easier though)

  • @Avaryan Awesome tutorial, thank you for giving back.