How to Automatically Download the Latest Windows Device Drivers
Automatically download the latest device drivers during Windows OS deployment (Dell, Lenovo, HP, etc.).
Imagine never needing to worry about drivers during Windows Operating System deployment ever again.
No need to manually download drivers. No need to maintain driver folders on your deployment shares. No need to update drivers whenever something breaks…
The Problem
Every Windows deployment environment seems to manage OS drivers differently. Whether it’s dumping every driver into a single folder in your deployment share, one driver folder per OS, or one driver folder per model, these all require manual maintenance of your deployment share. What if there was a way to automatically, reproducibly, and consistently download OS drivers at deployment time…?
The Solution
Scrape manufacturer websites using RegEx for direct driver download URLs. RegEx works well for this but…

With that out of the way, I’ll start by sharing the entire script, then describing it in more detail below. I’m always open to suggestions, so feel free to open a pull request or comment below!
| |
| |
| < |
| .SYNOPSIS |
| Downloads and installs Windows drivers directly from Manufacturer websites. |
| .DESCRIPTION |
| This script is meant to be invoked during Windows Operating System Deployment |
| to install drivers from common Hardware Manufacturers such as Lenovo, Dell, |
| HP, etc. This script *can* also be used to update drivers of live systems, |
| but may lead to system instability caused by replacing storage, network, |
| display, etc. drivers. |
| .PARAMETER Manufacturer |
| The Manufacturer of the device. Must be one of 'LENOVO', 'DELL', 'HP', 'MICROSOFT'. |
| .PARAMETER Model |
| The literal regex string that matches a URL on the Manufacturer's website |
| pointing to the exact download URL of the driver. |
| .PARAMETER SkipDownload |
| [OPTIONAL] Don't download drivers from the Manufacturer website. Just execute |
| drivers that were previously downloaded. |
| .PARAMETER SkipPnP |
| [OPTIONAL] Don't install drivers via pnp. |
| .PARAMETER SkipCleanup |
| [OPTIONAL] Don't cleanup temporary folders or registry keys. |
| .EXAMPLE |
| .\install_model_drivers.ps1 -Manufacturer Lenovo -Model 20y0 |
| |
| Downloads Lenovo driver installer matching the regex 20y0 exact string to |
| "$env:TEMP\Lenovo" then installs the drivers from the expanded installer. |
| .EXAMPLE |
| .\install_model_drivers.ps1 -Manufacturer HP -Model Z440 |
| |
| Downloads HP driver installer matching the regex Z440 exact string to |
| "$env:TEMP\HP" then installs the drivers from the expanded installer. |
| .EXAMPLE |
| .\install_model_drivers.ps1 -Manufacturer Dell -Model 9380 |
| |
| Downloads Dell driver installer matching the regex 9380 exact string to |
| "$env:TEMP\Dell" then installs the drivers from the expanded installer. |
| .EXAMPLE |
| .\install_model_drivers.ps1 -Manufacturer Microsoft -Model |
| 'Surface Laptop 4 with Intel Processor' |
| |
| Downloads Microsoft driver installer matching the regex ' |
| Surface Laptop 4 with Intel Processor' exact string to "$env:TEMP\Microsoft" |
| then installs the drivers via the msi installer. |
| |
| |
| [CmdletBinding()] |
| param ( |
| [Parameter(Mandatory)] |
| [string]$Manufacturer, |
| [Parameter(Mandatory)] |
| [string]$Model, |
| [Parameter()] |
| [switch]$SkipDownload, |
| [Parameter()] |
| [switch]$SkipPnP, |
| [Parameter()] |
| [switch]$SkipCleanup |
| ) |
| |
| function Get-URL { |
| [CmdletBinding()] |
| Param ( |
| [Parameter(Mandatory)] |
| [string]$URI, |
| [Parameter(Mandatory)] |
| [string]$FileRegEx, |
| [Parameter(Mandatory)] |
| [int]$MatchIndex |
| ) |
| try { |
| Write-Host "Getting driver download URL from [$URI]" |
| $req = Invoke-WebRequest -Uri $URI -UseBasicParsing |
| } catch { |
| throw "Failed to navigate to [$URI]" |
| } |
| |
| $regex_search = $req.Content -match $FileRegEx |
| if (-not $regex_search) { |
| throw "Failed to find a match for [$FileRegEx] in [$URI]" |
| } |
| |
| |
| |
| |
| $file_name = ($Matches[$MatchIndex] -split '/')[-1] |
| |
| return $Matches[$MatchIndex], $file_name |
| } |
| |
| function Get-RegexMatch { |
| [CmdletBinding()] |
| Param ( |
| [Parameter(Mandatory)] |
| [string]$Manufacturer, |
| [Parameter(Mandatory)] |
| [string]$Model |
| ) |
| |
| |
| |
| |
| switch ($Manufacturer) { |
| 'LENOVO' { |
| $manufacturer_uri = 'https://download.lenovo.com/cdrt/td/catalogv2.xml' |
| |
| $file_regex = "https.*?$Model.*?exe" |
| $match_index = 0 |
| } |
| 'DELL' { |
| $manufacturer_uri = 'https://www.dell.com/support/kbdoc/en-uk/000180534/dell-family-driver-packs' |
| |
| $file_regex = "(?:$Model.*?[\s\S]*?)(https.*?zip)" |
| $match_index = 1 |
| } |
| 'HP' { |
| $manufacturer_uri = 'https://hpia.hpcloud.hp.com/downloads/driverpackcatalog/HP_Driverpack_Matrix_x64.html' |
| |
| $file_regex = "(?:$Model.*?)(https.*?exe)" |
| $match_index = 1 |
| } |
| |
| 'MICROSOFT' { |
| $manufacturer_uri = 'https://learn.microsoft.com/en-us/surface/manage-surface-driver-and-firmware-updates' |
| |
| $file_regex = "(\d{6})(?:`" data-linktype=`"external`">$Model)" |
| $match_index = 1 |
| $id, $null = Get-URL -URI $manufacturer_uri -FileRegEx $file_regex -MatchIndex $match_index |
| |
| $manufacturer_uri = "https: |
| |
| $file_regex = '(http.*?\.msi)' |
| $match_index = 0 |
| } |
| default { throw "Manufacturer [$Manufacturer] is not (yet) supported..." } |
| } |
| |
| return Get-URL -URI $manufacturer_uri -FileRegEx $file_regex -MatchIndex $match_index |
| } |
| |
| function Get-Installer { |
| [CmdletBinding()] |
| Param ( |
| [Parameter(Mandatory)] |
| [string]$DownloadURI, |
| [Parameter(Mandatory)] |
| [string]$InstallerName |
| ) |
| try { |
| Write-Host "Downloading [$DownloadURI] to [$InstallerName]" |
| Write-Warning 'This may take a while...' |
| Invoke-WebRequest -Uri $DownloadURI -UseBasicParsing -OutFile $InstallerName |
| } catch { |
| throw "Failed to download [$DownloadURI] to [$InstallerName]" |
| } |
| } |
| |
| function Invoke-Installer { |
| [CmdletBinding()] |
| Param ( |
| [Parameter(Mandatory)] |
| [string]$Manufacturer, |
| [Parameter(Mandatory)] |
| [string]$InstallerName, |
| [Parameter()] |
| [string]$Destination |
| ) |
| |
| switch ($Manufacturer) { |
| 'Lenovo' { |
| $arg_list = @('/SP-', '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', "/DIR=$Destination", '/LOG') |
| } |
| 'Dell' { |
| try { |
| Write-Host "Expanding [$InstallerName] to [$Destination]" |
| Expand-Archive -Path $InstallerName -DestinationPath $Destination -Force -ErrorAction Stop |
| return |
| } catch { |
| throw "Failed to expand Dell zip: $_" |
| } |
| } |
| 'HP' { |
| $arg_list = @('-s', '-f', $Destination) |
| } |
| 'MICROSOFT' { |
| $arg_list = @('/qn', '/norestart', '/l*v', "$Destination\driver_install.log") |
| } |
| default { throw "Manufacturer [$Manufacturer] is not (yet) supported..." } |
| } |
| |
| Write-Host "Executing [$InstallerName $arg_list]" |
| Start-Process -FilePath $InstallerName -ArgumentList $arg_list -Wait |
| } |
| |
| |
| function Invoke-PnP { |
| [CmdletBinding()] |
| Param ( |
| [Parameter(Mandatory)] |
| [string]$Destination |
| ) |
| |
| $driver_paths = Get-ChildItem $Destination -Recurse -Filter '*.inf' |
| |
| if (-not $driver_paths) { |
| throw "Failed to locate any drivers (*.inf) in [$Destination]" |
| } |
| |
| try { |
| Write-Host "Installing all drivers under [$Destination]..." |
| |
| |
| New-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UnattendSettings\PnPUnattend\DriverPaths' -Name 1 -Force | Out-Null |
| New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UnattendSettings\PnPUnattend\DriverPaths\1' -Name Path -Value $Destination -Force | Out-Null |
| |
| $pnp = "$env:WINDIR\system32\PnPUnattend.exe" |
| |
| Write-Host "Executing [$pnp auditSystem /L]" |
| $process = Start-Process -FilePath $pnp -ArgumentList @('auditSystem', '/L') -NoNewWindow -PassThru -Wait |
| } catch { |
| throw "Failed to install driver with exit code [$($process.ExitCode)]: $_" |
| } |
| |
| return $process.ExitCode |
| } |
| |
| |
| $TEMP_PATH = "$env:TEMP\$Manufacturer" |
| |
| |
| $OldProgressPreference = $ProgressPreference |
| $global:ProgressPreference = 'SilentlyContinue' |
| |
| try { |
| if ($SkipDownload) { |
| Write-Host 'Skipping download...' |
| } else { |
| if (-not (Test-Path $TEMP_PATH)) { |
| Write-Host "Creating directory: [$TEMP_PATH]" |
| New-Item -ItemType Directory $TEMP_PATH -Force | Out-Null |
| } |
| |
| $regex_uri, $file_name = Get-RegexMatch -Manufacturer $Manufacturer -Model $Model |
| $installer = "$TEMP_PATH\$file_name" |
| |
| Get-Installer -DownloadURI $regex_uri -InstallerName $installer |
| |
| Invoke-Installer -Manufacturer $Manufacturer -InstallerName $installer -Destination $TEMP_PATH |
| } |
| |
| |
| if ($SkipPnP -or ($Model -like '*Surface*')) { |
| Write-Host 'Skipping PnP installation...' |
| } else { |
| Invoke-PnP -Destination $TEMP_PATH |
| } |
| } catch { |
| throw $_ |
| } finally { |
| if ($SkipCleanup) { |
| Write-Host 'Skipping cleanup...' |
| } else { |
| Write-Host 'Cleaning up...' |
| Remove-Item $TEMP_PATH -Force -Recurse -ErrorAction Continue |
| |
| if ($Model -notlike '*Surface*') { |
| Remove-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UnattendSettings\PnPUnattend\DriverPaths\1' -Recurse -Force |
| } |
| } |
| |
| $ProgressPreference = $OldProgressPreference |
| } |
The ideal solution is automated and doesn’t require updating when new OS drivers are released. Let’s get into how the sauce is made:
What it does
Refer to the code above for the most up-to-date information.
I wrote this script to simplify deploying drivers in my environment. What began as an idea to use RegEx to scrape driver URLs quickly turned into a fully automated solution (thanks Powershell!). This script is slightly modified for GitHub to include all the steps necessary to download, install, and deploy OS drivers.
Automating driver downloads from manufacturers is no discovery. As far as I can tell, manufacturers have been publishing discoverable web endpoints (of varying usefulness…) for driver downloads for over a decade now. Recently, I’ve seen these endpoints improve to the point that this script is now possible. What I haven’t seen is a single script that does everything from scraping the endpoint, to downloading, to installing, without the need to jump through intermediary cab files. What this solution provides is simplicity. No need to worry about OS drivers beyond specifying the manufacturer and model.
The scripted solution can be run directly from your system being deployed in WinPE or WinRE. The Manufacturer parameter tells the script which website to query for drivers. The Model parameter tells the script to query the manufacturer’s website for the exact string of your query and find the associated driver download link.
Once the download link for the driver pack is obtained, the script downloads the file to the local $env:TEMP directory and leverages pnpunattend.exe to install all drivers in that directory. Finally, the script cleans up files placed in $env:TEMP and exits.
What it doesn’t do
Install WinPE drivers (only OS drivers).
Parting Words
This is where I acknowledge that downloading drivers from the manufacturer’s website during deployment may increase deployment times (vs. downloading from an internal endpoint). This is a worthwhile trade-off for the time saved from managing individual driver packs in deployment shares. While this script applies OS drivers before booting into the OS during deployment, I would also recommend running Windows Updates once booted into the OS for the first time.
This script also assumes that manufacturers test their drivers before deployment (not always the case), and that the “latest” OS version drivers will work on your systems. You’ll have to discuss if this added automation is a worthwhile trade-off with your team. Heck, feel free to fork this script entirely to add resiliency, or submit PRs to improve the script itself.
Hopefully, this script saves you some time that could be better used on automation, rather than fumbling with OS driver deployment 💻🔂⏩