Skip to content

Instantly share code, notes, and snippets.

@cdgco
Last active March 12, 2024 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cdgco/907ac1358fa35c3dcf1d7b42f152a2f2 to your computer and use it in GitHub Desktop.
Save cdgco/907ac1358fa35c3dcf1d7b42f152a2f2 to your computer and use it in GitHub Desktop.
MECM Driver Injection
<#
.SYNOPSIS
This script automates the process of installing drivers specific to the computer model and the Windows version.
It matches the computer model to a driver folder, checks for the appropriate Windows version, and installs the drivers from the matched folder.
.DESCRIPTION
The script retrieves the computer model and a specified Windows version. It then searches a given directory for a matching model and Windows version folder.
If a direct match is found, it copies the drivers to the system drive under C:\Drivers and initiates the installation.
If no direct match is found, it looks for the closest lower version available. If still no match is found, it defaults to the base model directory.
The drivers are installed silently without requiring user interaction.
.PARAMETERS
- BaseDir: The base directory where driver folders are located. It's expected to contain subfolders named after computer models.
- ComputerModel: Automatically retrieved model of the current computer.
- SpecVer: The specific Windows version to match against driver folders, passed as an argument to the script.
- ShowDialogs: A switch parameter to enable or disable the display of popup dialogs.
.EXAMPLE
.\DriverInjection.ps1 -BaseDir "I:" -SpecVer 11 -ShowDialogs
This example searches the "I:\" for a folder matching the current computer's model and Windows version 11, copies the matching drivers to the system drive, and installs them.
.NOTES
- Author: Carter Roeser
- Last Updated: 2024-03-08
- Source: https://cjroeser.com/2024/03/08/injecting-drivers-in-mecm-task-sequences/
- Please ensure PowerShell Execution Policy allows script execution and that all paths and dependencies are correctly configured in your environment.
- Designed to be used in an MECM task sequence, where automation of driver installation is crucial. Will fail if the Task Sequence Environment COM object is not available.
- Make sure to adjust the script paths and environment variables according to your specific deployment environment.
#>
param(
[string]$BaseDir,
[double]$SpecVer = 11,
[switch]$ShowDialogs
)
Write-Host "Beginning Execution of Driver Installation Script"
# Retrieve the computer model and set up the task sequence environment
try {
$TSEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment
$TargetSystemDrive = $TSEnv.Value('OSDTargetSystemDrive')
$TSEnv.Value('DriversInstalled') = "False"
$TSProgressUI = New-Object -ComObject Microsoft.SMS.TSProgressUI
$ComputerModel = (Get-CimInstance -ClassName Win32_ComputerSystem).Model
Add-Type -AssemblyName PresentationFramework
}
catch {
Write-Error "Task Sequence Environment COM object not available. Ensure the script is running in an MECM task sequence."
exit
}
# Function: Show-PopupDialog
# Purpose: Displays a customizable popup dialog to the user. Used for error messages and confirmation prompts.
# Parameters:
# - message: The text message to display in the popup.
# - title: The title of the popup dialog window.
# Notes: If the ShowDialogs switch is not set, the function returns early. If the user clicks 'Cancel', the script exits.
function Show-PopupDialog {
param(
[string]$Message,
[string]$Title
)
# If ShowDialogs switch is not set, return early
if ($ShowDialogs -eq $false) {
Write-Host "Skipping popup dialog"
return
}
# Close the TS progress dialog if it's open
if ($TSProgressUI) {
Write-Host "Closing TS progress dialog"
$TSProgressUI.CloseProgressDialog()
}
Write-Host "Displaying popup dialog"
# Display the popup dialog and capture the user's response
$popup = [System.Windows.MessageBox]::Show($Message, $Title, "OKCancel", "Error")
Write-Host "Button clicked: $popup"
if ($popup -eq 2) {
Write-Host "User clicked 'Cancel'. Exiting script."
exit
}
}
# Function: Install-Drivers
# Purpose: Installs drivers from a specified directory into the system drive.
# Notes: Uses Add-WindowsDriver cmdlet to add drivers to the offline Windows image on the target system drive. Sets a task sequence variable upon successful completion.
function Install-Drivers {
Write-Host "Installing drivers from $TargetSystemDrive\Drivers"
# Attempt to install drivers using Add-WindowsDriver cmdlet
try {
Add-WindowsDriver -Path $TargetSystemDrive -Driver "$TargetSystemDrive\Drivers" -Recurse -ForceUnsigned
$TSEnv.Value('DriversInstalled') = "True"
}
catch {
Write-Error "An error occurred during driver installation: $_"
}
}
# Function: Copy-Drivers
# Purpose: Copies driver files from the source directory to the target system drive's Drivers directory.
# Parameters:
# - SourceDir: The directory containing the driver files to be copied.
# Notes: Creates the target directory if it does not exist and then copies all files from the source, maintaining directory structure.
function Copy-Drivers {
param([string]$SourceDir)
$DestDir = "$TargetSystemDrive\Drivers"
Write-Host "Copying drivers from $SourceDir to $DestDir"
try {
# Create the target directory if it does not exist
if (-not (Test-Path $DestDir)) {
Write-Host "Creating Directory: $DestDir"
New-Item -ItemType Directory -Force -Path $DestDir | Out-Null
}
# Copy all files from the source directory to the target directory
Get-ChildItem -Path $SourceDir -Recurse -Force | ForEach-Object {
# Construct the destination path by replacing the source directory with the target directory
$sourcePath = $_.FullName
$destPath = $sourcePath.Replace($SourceDir, $DestDir)
# Create the destination directory if the source is a directory
if ($_.PSIsContainer) {
if (-not (Test-Path $destPath)) {
Write-Host "Creating Directory: $destPath"
New-Item -ItemType Directory -Force -Path $destPath -ErrorAction SilentlyContinue | Out-Null
}
}
# Copy the file to the destination
else {
Write-Host "Copying driver: $sourcePath to $destPath"
Copy-Item -Path $sourcePath -Destination $destPath -Force -ErrorAction SilentlyContinue | Out-Null
}
}
}
catch {
Write-Error "An error occurred during driver copying: $_"
}
Write-Output "Drivers successfully copied from $SourceDir to $DestDir"
}
# Function: Test-DriverStoreConnection
# Purpose: Verifies connectivity to the specified driver store directory.
# Parameters:
# - BaseDir: The base directory of the driver store to check for connectivity.
# Notes: Displays a popup dialog if the connection fails, allowing the user to retry or cancel.
function Test-DriverStoreConnection {
param([string]$BaseDir)
$connection = $False
# Loop until the connection is established or the user cancels
while ($connection -eq $False) {
Write-Host "Checking driver store connection: $BaseDir"
# If the connection fails, display an error dialog and prompt the user to retry or cancel
if ((Test-Path -Path $BaseDir) -eq $False) {
Write-Host "Driver store connection failed. Displaying error dialog."
Show-PopupDialog -Title "Driver Error" -Message "Error connecting to driver store ($BaseDir)`n`nPlease check the connection and click 'OK' to try again, or click 'Cancel' to skip driver installation."
}
# If the connection is successful, set the connection flag to True
else {
Write-Host "Driver store connection successful."
$connection = $True
}
}
}
# Function: Test-DriverFolder
# Purpose: Checks for the existence of a specific driver folder within the base directory.
# Parameters:
# - BaseDir: The base directory where driver folders are located.
# - ComputerModel: The computer model used to construct the driver folder path.
# Notes: Displays a popup dialog if the folder does not exist, allowing the user to retry or cancel.
function Test-DriverFolder {
param([string]$BaseDir, [string]$ComputerModel)
$folder = $False
$modelDirPath = Join-Path -Path $BaseDir -ChildPath $ComputerModel
# Loop until the folder is found or the user cancels
while ($folder -eq $False) {
Write-Host "Checking for driver folder: $modelDirPath"
# If the folder does not exist, display an error dialog and prompt the user to retry or cancel
if ((Test-Path -Path $modelDirPath) -eq $False) {
Write-Host "Driver folder not found. Displaying error dialog."
Show-PopupDialog -Title "Driver Folder Not Found" -Message "Please upload drivers to:`n$modelDirPath`n`nClick 'OK' to check again or 'Cancel' to skip driver installation."
}
# If the folder exists, set the folder flag to True
else {
Write-Host "Driver folder found: $modelDirPath"
$folder = $True
}
}
}
# Function: Find-And-Copy-Drivers
# Purpose: Searches for the best match driver folder based on the computer model and Windows version, then copies and installs the drivers.
# Parameters:
# - BaseDir: The base directory where driver folders are located.
# - ComputerModel: The computer model for which drivers are being installed.
# - SpecVer: The Windows version to match against driver folders.
# Notes: Attempts to find an exact match first. If not found, it searches for the closest lower version available. Displays popup dialogs for user interaction during the process.
function Find-And-Copy-Drivers {
param(
[string]$BaseDir,
[string]$ComputerModel,
[double]$SpecVer
)
$driver = $False
Write-Host "Searching for drivers in $BaseDir for model $ComputerModel and Windows version $SpecVer"
# Check for driver store connection and folder existence
if ($ShowDialogs) {
Test-DriverStoreConnection -BaseDir $BaseDir
Test-DriverFolder -BaseDir $BaseDir -ComputerModel $ComputerModel
}
# Loop until the driver is found or the user cancels
while ($driver -eq $False) {
try {
$driverPath = Join-Path -Path $BaseDir -ChildPath "$ComputerModel\win$SpecVer"
$modelDir = Join-Path -Path $BaseDir -ChildPath $ComputerModel
$folders = Get-ChildItem -Path $modelDir -Directory -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^win(\d+)' }
} catch {
Write-Error "Unable to read from driver store: $_"
exit
}
Write-Host "Checking for driver folder: $driverPath"
# If the driver folder exists, install the drivers and exit the loop
if (Test-Path -Path $driverPath) {
Write-Host "Found Exact Driver Match: $driverPath"
Copy-Drivers -SourceDir $driverPath
Install-Drivers
exit
}
$versions = $folders | ForEach-Object { [double]($_.Name -replace '^win', '') } | Sort-Object -Descending
$matchedVersion = $versions | Where-Object { $_ -le $SpecVer } | Select-Object -First 1
# If a lower version match is found, copy the drivers and exit the loop
if ($matchedVersion) {
Write-Host "Found Closest Driver Match: $matchedVersion"
$matchedPath = Join-Path -Path $modelDir -ChildPath ("win" + $matchedVersion)
Copy-Drivers -SourceDir $matchedPath
Install-Drivers
exit
}
# If no match is found, use the base model folder and exit the loop
else {
Write-Host "No Driver Match Found. Using Base Model Folder."
if ($ShowDialogs) {
Show-PopupDialog -Title "Driver Folder Not Found" -Message "Please upload drivers to:`n$modelDir`n`nClick 'OK' to check again or 'Cancel' to skip driver installation."
} else {
exit
}
}
}
}
Find-And-Copy-Drivers -BaseDir $BaseDir -ComputerModel $ComputerModel -SpecVer $SpecVer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment