I've made some significant improvements to my powershell project. My prior post had the basics here: http://forum.driverpacks.net/viewtopic. … 275#p59275
The below code seems to run just fine in Win10/Win11 post-install environments, but should work in Win7/8 also. Because of syntax differences between powershell versions, I've added an OS descriminator to handle the different command options. I may toy with placing everything in the $WinPEDriver$ folder at the root of the install medium to make the drivers available during the OS install process for a future version.
It has basic logging and is launched by a "helper" install.bat.
Install.bat
@echo OFF
SET WorkingDir=%cd%
SET PSPath='%WorkingDir%\Install-SpecifiedDrivers.ps1'
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& %PSPath%"
Install-SpecifiedDrivers.ps1
# ========================
# Version 2.2 by Erik Hansen
# Universal driver installer with privilege elevation using existing Microsoft OS tools.
# Install and/or upgrade any drivers specified in the .\drivers directory using pnputil.exe
# Recursively finds each driver installer file INF in the specified folder and uses pnputil to inject the driver into the running OS.
# ========================
# Don't execute this .ps1 script directly, use "helper script" Install_1st.bat
# ========================
# =============================================================================
# Self-Elevation Check - MUST BE AT THE TOP OF THE SCRIPT
# =============================================================================
#
# 1. Check if the current session is running as an Administrator.
# 2. If not, it re-launches the script in a new, elevated PowerShell window.
# 3. The original, non-elevated script then immediately exits.
# =============================================================================
# Get the identity of the current user
# =============================================================================
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$windowsPrincipal = [System.Security.Principal.WindowsPrincipal]$currentUser
# Check if the user is a member of the 'Administrators' group
if (-not $windowsPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
# If not an admin, re-launch the script with elevated privileges
try {
Write-Warning "This script requires administrator privileges. Attempting to re-launch with elevation..."
# Build the arguments to pass to the new process
$arguments = "& '$PSCommandPath'"
# Start a new PowerShell process with the 'RunAs' verb to trigger UAC
Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments -ErrorAction Stop
# Exit the current, non-elevated script
# The 'exit' is crucial to prevent the rest of the script from running in the non-admin context and failing.
exit
}
catch {
Write-Error "Failed to re-launch with elevated privileges. Please right-click the script and choose 'Run as Administrator'."
# Pause to allow the user to read the error before the window closes
Start-Sleep -Seconds 10
exit
}
}
# If the script reaches this point, it is confirmed to be running as an administrator.
Write-Host "Script is running with administrator privileges."
# =============================================================================
# End of Elevation Check
# =============================================================================
# =============================================================================
# Begin actual work
# =============================================================================
# Set string variables, paths, and environment
# Explicitly sets the log path. If running from a read-only device, change this path to a writeable directory.
$TempLogs = "$env:HOMEDRIVE\Temp\Logs\Install-SpecifiedDrivers_LOG.txt"
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
# Start log. Remove the '-Append' option to create new log from scratch.
Start-Transcript -Path $TempLogs -Append
# Get all INF files from the drivers directory
# Explicitly sets the path to the drivers directory. Change this to where your drivers are stored.
$driverFiles = Get-ChildItem -Path "$PSScriptRoot\drivers" -Recurse -Filter "*.inf"
Write-Host "Found $($driverFiles.Count) driver packages. Checking for installation or upgrade..."
# =============================================================================
# --- OS Version-Aware Driver Enumeration ---
# =============================================================================
$installedDrivers = $null
# Get the Major version of the OS. Windows 7 is 6.1, Windows 8 is 6.2, Win 8.1 is 6.3, Win 10/11 are 10.0
$osMajorVersion = [System.Environment]::OSVersion.Version.Major
if ($osMajorVersion -ge 10 -or ($osMajorVersion -eq 6 -and [System.Environment]::OSVersion.Version.Minor -ge 2)) {
# OS is Windows 8 or newer (including 10 and 11)
Write-Host "OS is Windows 8 or newer. Using 'Get-WindowsDriver'..."
$installedDrivers = Get-WindowsDriver -Online | Where-Object { $_.ProviderName -ne "Microsoft" }
}
else {
# OS is Windows 7. Use the WMI fallback.
Write-Host "OS is Windows 7. Using 'Get-WmiObject' fallback..."
# The property names from WMI are different, so we use calculated properties
# to make the output object look the same as the one from Get-WindowsDriver.
$installedDrivers = Get-WmiObject Win32_PnPSignedDriver | `
Where-Object { $_.Manufacturer -ne "Microsoft" } | `
Select-Object @{Name="ProviderName"; Expression={$_.Manufacturer}}, @{Name="Version"; Expression={$_.DriverVersion}}
}
if ($installedDrivers -eq $null) {
Write-Warning "Could not retrieve a list of installed drivers. The upgrade logic will assume no drivers are installed."
}
# =============================================================================
# --- Begin Driver Enumeration and Comparison ---
# =============================================================================
foreach ($infFile in $driverFiles) {
try {
# Read the INF file content as an array of lines
$infLines = Get-Content -Path $infFile.FullName -ErrorAction Stop
# Find the initial Provider line and Driver Version using regex
$providerLine = $infLines | Select-String -Pattern "^\s*Provider\s*=\s*(.+)"
$versionLine = $infLines | Select-String -Pattern "^\s*DriverVer\s*=\s*(.+)"
if (-not ($providerLine -and $versionLine)) {
Write-Warning "Could not find a valid Provider or DriverVer line in $($infFile.Name). Skipping."
continue
}
# --- Resolve the Provider token two-step---
# 1. Get the initial token (e.g., "%ManufacturerName%")
$providerToken = $providerLine.Matches[0].Groups[1].Value.Trim()
$resolvedProvider = ""
# 2. Check if it's a token that needs resolving
if ($providerToken.StartsWith('%') -and $providerToken.EndsWith('%')) {
# It's a token. Let's find its value in the [Manufacturer] section.
# Remove the '%' characters to get the lookup key (e.g., "ManufacturerName")
$lookupKey = $providerToken -replace '%'
# Find the start of the [Manufacturer] section
$mfgSectionLineNum = ($infLines | Select-String -Pattern '^\s*\[Manufacturer\]' -List).LineNumber
if ($mfgSectionLineNum) {
# Search for the key only in the lines *after* the [Manufacturer] section header
# The pattern looks for "key = value,..." and captures "value"
$resolvedLine = $infLines[$mfgSectionLineNum..($infLines.Count - 1)] | `
Select-String -Pattern "^\s*$lookupKey\s*=\s*([^,]+)" | `
Select-Object -First 1
# From here, use the $resolvedProvider variable for either option
if ($resolvedLine) {
# The actual provider name is the first captured group
$resolvedProvider = $resolvedLine.Matches[0].Groups[1].Value.Trim()
}
}
} else {
# It's not a token, so the value is literal.
$resolvedProvider = $providerToken
}
if ([string]::IsNullOrWhiteSpace($resolvedProvider)) {
Write-Warning "Could not resolve provider name for token '$providerToken' in $($infFile.Name). Skipping."
continue
}
# ----------------------------------------------
# The rest of the logic remains the same, using the now-correct $infProvider
$infProvider = $resolvedProvider
$infVersionString = ($versionLine.Matches[0].Groups[1].Value.Split(',')[1]).Trim()
$infVersion = [System.Version]$infVersionString
# =============================================================================
# --- Begin Driver Injection using pnputil.exe ---
# =============================================================================
# Rack and stack the INF file content array and only process driver files that are newer than already installed.
Write-Host "Processing driver: $($infFile.Name) | Provider: $infProvider | Version: $infVersion"
$matchingInstalledDriver = $installedDrivers | Where-Object { $_.ProviderName -eq $infProvider } | Sort-Object -Property Version -Descending | Select-Object -First 1
if ($matchingInstalledDriver) {
$installedVersion = [System.Version]$matchingInstalledDriver.Version
# We matched a newer driver than what is installed on the system. Yay!
Write-Host "Found installed driver from provider '$infProvider' with version $installedVersion."
if ($infVersion -gt $installedVersion) {
Write-Host "Newer version $infVersion found for $($infFile.Name). Upgrading..."
pnputil.exe /add-driver $infFile.FullName /install
Write-Host "SUCCESS: Upgraded driver from $($infFile.FullName)."
} else {
Write-Host "Installed version ($installedVersion) is the same or newer. No action needed."
}
} else {
# We didn't find an existing driver to match the scanned driver. Pushing the driver through pnputil and let the system sort it out.
# If it works, great. If it fails, not our problem.
Write-Host "No existing driver found for provider '$infProvider'. Installing..."
pnputil.exe /add-driver $infFile.FullName /install
Write-Host "SUCCESS: Installed new driver from $($infFile.FullName)."
}
}
catch {
Write-Error "An error occurred while processing $($infFile.FullName): $_"
}
Write-Host "---"
}
# Stop log
Stop-Transcript