├── Invoke-CMApplyDriverPackage.ps1 ├── Invoke-CMApplyDriverPackage_Legacy.ps1 ├── LICENSE └── README.md /Invoke-CMApplyDriverPackage_Legacy.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Download driver package (regular package) matching computer model, manufacturer and operating system. 4 | 5 | .DESCRIPTION 6 | This script will determine the model of the computer, manufacturer and operating system being deployed and then query 7 | the specified endpoint for ConfigMgr WebService for a list of Packages. It then sets the OSDDownloadDownloadPackages variable 8 | to include the PackageID property of a package matching the computer model. If multiple packages are detect, it will select 9 | most current one by the creation date of the packages. 10 | 11 | .PARAMETER URI 12 | Set the URI for the ConfigMgr WebService. 13 | 14 | .PARAMETER SecretKey 15 | Specify the known secret key for the ConfigMgr WebService. 16 | 17 | .PARAMETER DeploymentType 18 | Define a different deployment scenario other than the default behavior. Choose between BareMetal (default), OSUpgrade, DriverUpdate or PreCache (Same as OSUpgrade but only downloads the package content). 19 | 20 | .PARAMETER Filter 21 | Define a filter used when calling ConfigMgr WebService to only return objects matching the filter. 22 | 23 | .PARAMETER OperationalMode 24 | Define the operational mode, either Production or Pilot, for when calling ConfigMgr WebService to only return objects matching the selected operational mode. 25 | 26 | .PARAMETER UseDriverFallback 27 | Specify if the script is to be used with a driver fallback package when a driver package for SystemSKU or computer model could not be detected. 28 | 29 | .PARAMETER DriverInstallMode 30 | Specify whether to install drivers using DISM.exe with recurse option or spawn a new process for each driver. 31 | 32 | .PARAMETER DebugMode 33 | Use this switch when running script outside of a Task Sequence. 34 | 35 | .PARAMETER TSPackageID 36 | Specify the Task Sequence PackageID when running in debug mode. 37 | 38 | .PARAMETER Manufacturer 39 | Override the automatically detected computer manufacturer when running in debug mode. 40 | 41 | .PARAMETER ComputerModel 42 | Override the automatically detected computer model when running in debug mode. 43 | 44 | .PARAMETER SystemSKU 45 | Override the automatically detected SystemSKU when running in debug mode. 46 | 47 | .PARAMETER OSImageTSVariableName 48 | Specify a Task Sequence variable name that should contain a value for an OS Image package ID that will be used to override automatic detection. 49 | 50 | .PARAMETER TargetOSVersion 51 | Define the value that will be used as the target operating system version e.g. 18363. 52 | 53 | .PARAMETER OSVersionFallback 54 | Use this switch to check for drivers packages that matches earlier versions of Windows than what's detected from web service call. 55 | 56 | .EXAMPLE 57 | # Detect, download and apply drivers during OS deployment with ConfigMgr: 58 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" 59 | 60 | # Detect, download and apply drivers during OS deployment with ConfigMgr and use a driver fallback package: 61 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -UseDriverFallback 62 | 63 | # Detect and download drivers during OS upgrade with ConfigMgr: 64 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -DeploymentType OSUpgrade 65 | 66 | # Detect, download and update with latest drivers for an existing operating system using ConfigMgr: 67 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -DeploymentType DriverUpdate 68 | 69 | # Detect, download and apply drivers during OS deployment with ConfigMgr when using multiple Apply Operating System steps in the task sequence: 70 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -OSImageTSVariableName "OSImageVariable" 71 | 72 | # Detect and download (pre-caching content) during OS upgrade with ConfigMgr: 73 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -DeploymentType "PreCache" 74 | 75 | # Run in a debug mode for testing purposes (to be used locally on the computer model): 76 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -DebugMode -TSPackageID "P0100001" 77 | 78 | # Run in a debug mode for testing purposes and overriding the automatically detected computer details (could be executed basically anywhere): 79 | .\Invoke-CMApplyDriverPackage.ps1 -URI "http://CM01.domain.com/ConfigMgrWebService/ConfigMgr.asmx" -SecretKey "12345" -Filter "Drivers" -DebugMode -TSPackageID "P0100001" -Manufacturer "HP" -ComputerModel "HP EliteBook 820 G5" -SystemSKU "1234" 80 | 81 | .NOTES 82 | FileName: Invoke-CMApplyDriverPackage.ps1 83 | Author: Nickolaj Andersen / Maurice Daly 84 | Contact: @NickolajA / @MoDaly_IT 85 | Created: 2017-03-27 86 | Updated: 2020-08-07 87 | 88 | Minimum required version of ConfigMgr WebService: 1.6.0 89 | Contributors: @CodyMathis123, @JamesMcwatty 90 | 91 | Version history: 92 | 1.0.0 - (2017-03-27) Script created 93 | 1.0.1 - (2017-04-18) Updated script with better support for multiple vendor entries 94 | 1.0.2 - (2017-04-22) Updated script with support for multiple operating systems driver packages, e.g. Windows 8.1 and Windows 10 95 | 1.0.3 - (2017-05-03) Updated script with support for manufacturer specific Windows 10 versions for HP and Microsoft 96 | 1.0.4 - (2017-05-04) Updated script to trim any white spaces trailing the computer model detection from WMI 97 | 1.0.5 - (2017-05-05) Updated script to pull the model for Lenovo systems from the correct WMI class 98 | 1.0.6 - (2017-05-22) Updated script to detect the proper package based upon OS Image version referenced in task sequence when multiple packages are detected 99 | 1.0.7 - (2017-05-26) Updated script to filter OS when multiple model matches are found for different OS platforms 100 | 1.0.8 - (2017-06-26) Updated script with improved computer name matching when filtering out packages returned from the web service 101 | 1.0.9 - (2017-08-25) Updated script to read package description for Microsoft models in order to match the WMI value contained within 102 | 1.1.0 - (2017-08-29) Updated script to only check for the OS build version instead of major, minor, build and revision for HP systems. $OSImageVersion will now only contain the most recent version if multiple OS images is referenced in the Task Sequence 103 | 1.1.1 - (2017-09-12) Updated script to match the system SKU for Dell, Lenovo and HP models. Added architecture check for matching packages 104 | 1.1.2 - (2017-09-15) Replaced computer model matching with SystemSKU. Added script with support for different exit codes 105 | 1.1.3 - (2017-09-18) Added support for downloading package content instead of setting OSDDownloadDownloadPackages variable 106 | 1.1.4 - (2017-09-19) Added support for installing driver package directly from this script instead of running a seperate DISM command line step 107 | 1.1.5 - (2017-10-12) Added support for in full OS driver maintenance updates 108 | 1.1.6 - (2017-10-29) Fixed an issue when detecting Microsoft manufacturer information 109 | 1.1.7 - (2017-10-29) Changed the OSMaintenance parameter from a string to a switch object, make sure that your implementation of this is amended in any task sequence steps 110 | 1.1.8 - (2017-11-07) Added support for driver fallback packages when the UseDriverFallback param is used 111 | 1.1.9 - (2017-12-12) Added additional output for failure to detect system SKU value from WMI 112 | 1.2.0 - (2017-12-14) Fixed an issue where the HP packages would not properly be matched against the OS image version returned by the web service 113 | 1.2.1 - (2018-01-03) IMPORTANT - OSMaintenance switch has been replaced by the DeploymentType parameter. In order to support the default behavior (BareMetal), OSUpgrade and DriverUpdate operational 114 | modes for the script, this change was required. Update your task sequence configuration before you use this update. 115 | 2.0.0 - (2018-01-10) Updates include support for machines with blank system SKU values and the ability to run BIOS & driver updates in the FULL OS 116 | 2.0.1 - (2018-01-18) Fixed a regex issue when attempting to fallback to computer model instead of SystemSKU 117 | 2.0.2 - (2018-01-24) Re-constructed the logic for matching driver package to begin with computer model or SystemSKU (SystemSKU takes precedence before computer model) and improved the logging when matching for driver packages 118 | 2.0.3 - (2018-01-25) Added a fix for multiple manufacturer package matches not working for Windows 7. Fixed an issue where SystemSKU was used and multiple driver packages matched. Added script line logging when the script cought an exception. 119 | 2.0.4 - (2018-01-26) Changed from using a foreach loop to a for loop in reverse to remove driver packages that was matched by SystemSKU but does not match the computer model 120 | 2.0.5 - (2018-01-29) Replaced Add-Content with Out-File for issue with file lock causing not all log entries to be written to the ApplyDriverPackage.log file 121 | 2.0.6 - (2018-02-21) Updated to cater for the presence of underscores in Microsoft Surface models 122 | 2.0.7 - (2018-02-25) Added support for a DebugMode switch for running script outside of a task sequence for driver package detection 123 | 2.0.8 - (2018-02-25) Added a check to bail out the script if computer model and SystemSKU are null or an empty string 124 | 2.0.9 - (2018-05-07) Removed exit code 34 event. DISM will now continue to process drivers if a single or multiple failures occur in order to proceed with the task sequence 125 | 2.1.0 - (2018-06-01) IMPORTANT: From this version, ConfigMgr WebService 1.6 is required. Added a new parameter named OSImageTSVariableName that accepts input of a task sequence variable. This task sequence variable should contain the OS Image package ID of 126 | the desired Operating System Image selected in an Apply Operating System step. This new functionality allows for using multiple Apply Operating System steps in a single task sequence. Added Panasonic for manufacturer detection. 127 | Improved logic with fallback from SystemSKU to computer model. Script will now fall back to computer model if there was no match to the SystemSKU. This still requires that the SystemSKU contains a value and is not null or empty, otherwise 128 | the logic will directly fall back to computer model. A new parameter named DriverInstallMode has been added to control how drivers are installed for BareMetal deployment. Valid inputs are Single or Recurse. 129 | 2.1.1 - (2018-08-28) Code tweaks and changes for Windows build to version switch in the Driver Automation Tool. Improvements to the SystemSKU reverse section for HP models and multiple SystemSKU values from WMI 130 | 2.1.2 - (2018-08-29) Added code to handle Windows 10 version specific matching and also support matching for the name only 131 | 2.1.3 - (2018-09-03) Code tweak to Windows 10 version matching process 132 | 2.1.4 - (2018-09-18) Added support to override the task sequence package ID retrieved from _SMSTSPackageID when the Apply Operating System step is in a child task sequence 133 | 2.1.5 - (2018-09-18) Updated the computer model detection logic that replaces parts of the string from the PackageName property to retrieve the computer model only 134 | 2.1.6 - (2019-01-28) Fixed an issue with the recurse injection of drivers for a single detected driver package that was using an unassigned variable 135 | 2.1.7 - (2019-02-13) Added support for Windows 10 version 1809 in the Get-OSDetails function 136 | 2.1.8 - (2019-02-13) Added trimming of manufacturer and models data gathering from WMI 137 | 2.1.9 - (2019-03-06) Added support for non-terminating error when no matching driver packages where detected for OSUpgrade and DriverUpdate deployment types 138 | 2.2.0 - (2019-03-08) Fixed an issue when attempting to run the script with -DebugMode switch that would cause it to break when it couldn't load the TS environment 139 | 2.2.1 - (2019-03-29) New deployment type named 'PreCache' that allows the script to run in a pre-caching mode in a content pre-cache task sequence. When this deployment type is used, content will only be downloaded if it doesn't already 140 | exist in the CCMCache. New parameter OperationalMode (defaults to Production) for better handling driver packages set for Pilot or Production deployment. 141 | 2.2.2 - (2019-05-14) Improved the Surface model detection from WMI 142 | 2.2.3 - (2019-05-14) Fixed an issue when multiple matching driver packages for a given model would only attempt to format the computer model name correctly for HP computers 143 | 2.2.4 - (2019-08-09) Fixed an issue on OperationalMode Production to filter out pilot and retired packages 144 | 2.2.5 - (2019-12-02) Added support for Windows 10 1903, 1909 and additional matching for Microsoft Surface devices (DAT 6.4.0 or neweer) 145 | 2.2.6 - (2020-02-06) Fixed an issue where the single driver injection mode for BareMetal deployments would fail if there was a space in the driver inf name 146 | 2.2.7 - (2020-02-10) Added a new parameter named TargetOSVersion. Use this parameter when DeploymentType is OSUpgrade and you don't want to rely on the OS version detected from the imported Operating System Upgrade Package or Operating System Image objects. 147 | This parameter should mainly be used as an override and was implemented due to drivers for Windows 10 1903 were incorrectly detected when deploying or upgrading to Windows 10 1909 using imported source files, not for a 148 | reference image for Windows 10 1909 as the Enablement Package would have flipped the build change to 18363 in such an image. 149 | 3.0.0 - (2020-03-14) A complete re-written version of the script. Includes a much improved logging functionality. Script is now divided into phases, which are represented in the ApplyDriverPackage.log that will provide a better troubleshooting experience. 150 | Added support for AZW and Fujitsu computer manufacturer by request from the community. Extended DebugMode to allow for overriding computer details, which allows the script to be tested against any model and it doesn't require to be tested 151 | directly on the model itself. 152 | 3.0.1 - (2020-03-25) Added TargetOSVersion parameter to be allowed to used in DebugMode. Fixed an issue where DebugMode would not be allowed to run on virtual machines. Fixed an issue where ComputerDetectionMethod script variable would be set to ComputerModel from 153 | SystemSKU in case it couldn't match on the first driver package, leading to HP driver packages would always fail since they barely never match on the ComputerModel (they include 'Base Model', 'Notebook PC' etc.) 154 | 3.0.2 - (2020-03-29) Fixed a spelling mistake in the Manufacturer parameter. 155 | 3.0.3 - (2020-03-31) Small update to the Filter parameter's default value, it's now 'Drivers' instead of 'Driver'. Also added '64 bits' and '32 bits' to the translation function for the OS architecture of the current running task sequence. 156 | 3.0.4 - (2020-04-09) Changed the translation function for the OS architecture of the current running task sequence into using wildcard support instead of adding language specified values 157 | 3.0.5 - (2020-04-30) Added 7-Zip self extracting exe support for compressed driver packages 158 | 3.0.6 - (2020-07-24) Added support for Windows 10 version 2004 and additional logging for when constructing custom driver package objects for matching process 159 | 3.0.7 - (2020-08-05) Fixed a bug that would cause the script to crash in case the SKU input string from the driver package properties would contain a space character instead of a comma 160 | 3.0.8 - (2020-08-07) Fixed an issue where the Confirm-SystemSKU function would cause the script to crash if the SystemSKU data was improperly conformed, for instance with duplicate entries 161 | 3.0.9 - (2020-09-10) IMPORTANT: This update addresses a change in Driver Automation Tool version 6.4.9 that comes with a change in naming HP driver packages such as 'Drivers - HP EliteBook x360 1030 G2 Base Model - Windows 10 1909 x64' instead of Hewlett-Packard in the name. 162 | Before changing to version 3.0.9 of this script, ensure Driver Automation Tool have been executed and all HP driver packages now reflect these changes. 163 | 3.1.0 - (2020-10-27) Updated with support for Windows 10 version 2009. 164 | #> 165 | [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = "Execute")] 166 | param ( 167 | [parameter(Mandatory = $true, ParameterSetName = "Execute", HelpMessage = "Set the URI for the ConfigMgr WebService.")] 168 | [parameter(Mandatory = $true, ParameterSetName = "Debug")] 169 | [ValidateNotNullOrEmpty()] 170 | [string]$URI, 171 | 172 | [parameter(Mandatory = $true, ParameterSetName = "Execute", HelpMessage = "Specify the known secret key for the ConfigMgr WebService.")] 173 | [parameter(Mandatory = $true, ParameterSetName = "Debug")] 174 | [ValidateNotNullOrEmpty()] 175 | [string]$SecretKey, 176 | 177 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Define a different deployment scenario other than the default behavior. Choose between BareMetal (default), OSUpgrade, DriverUpdate or PreCache (Same as OSUpgrade but only downloads the package content).")] 178 | [parameter(Mandatory = $false, ParameterSetName = "Debug")] 179 | [ValidateSet("BareMetal", "OSUpgrade", "DriverUpdate", "PreCache")] 180 | [string]$DeploymentType = "BareMetal", 181 | 182 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Define a filter used when calling ConfigMgr WebService to only return objects matching the filter.")] 183 | [parameter(Mandatory = $false, ParameterSetName = "Debug")] 184 | [ValidateNotNullOrEmpty()] 185 | [string]$Filter = "Drivers", 186 | 187 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Define the operational mode, either Production or Pilot, for when calling ConfigMgr WebService to only return objects matching the selected operational mode.")] 188 | [parameter(Mandatory = $false, ParameterSetName = "Debug")] 189 | [ValidateNotNullOrEmpty()] 190 | [ValidateSet("Production", "Pilot")] 191 | [string]$OperationalMode = "Production", 192 | 193 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Specify if the script is to be used with a driver fallback package when a driver package for SystemSKU or computer model could not be detected.")] 194 | [parameter(Mandatory = $false, ParameterSetName = "Debug")] 195 | [switch]$UseDriverFallback, 196 | 197 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Specify whether to install drivers using DISM.exe with recurse option or spawn a new process for each driver.")] 198 | [ValidateNotNullOrEmpty()] 199 | [ValidateSet("Single", "Recurse")] 200 | [string]$DriverInstallMode = "Recurse", 201 | 202 | [parameter(Mandatory = $true, ParameterSetName = "Debug", HelpMessage = "Use this switch when running script outside of a Task Sequence.")] 203 | [switch]$DebugMode, 204 | 205 | [parameter(Mandatory = $true, ParameterSetName = "Debug", HelpMessage = "Specify the Task Sequence PackageID when running in debug mode.")] 206 | [ValidateNotNullOrEmpty()] 207 | [string]$TSPackageID, 208 | 209 | [parameter(Mandatory = $false, ParameterSetName = "Debug", HelpMessage = "Override the automatically detected computer manufacturer when running in debug mode.")] 210 | [ValidateNotNullOrEmpty()] 211 | [ValidateSet("Hewlett-Packard", "HP", "Dell", "Lenovo", "Microsoft", "Fujitsu", "Panasonic", "Viglen", "AZW")] 212 | [string]$Manufacturer, 213 | 214 | [parameter(Mandatory = $false, ParameterSetName = "Debug", HelpMessage = "Override the automatically detected computer model when running in debug mode.")] 215 | [ValidateNotNullOrEmpty()] 216 | [string]$ComputerModel, 217 | 218 | [parameter(Mandatory = $false, ParameterSetName = "Debug", HelpMessage = "Override the automatically detected SystemSKU when running in debug mode.")] 219 | [ValidateNotNullOrEmpty()] 220 | [string]$SystemSKU, 221 | 222 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Specify a Task Sequence variable name that should contain a value for an OS Image package ID that will be used to override automatic detection.")] 223 | [ValidateNotNullOrEmpty()] 224 | [string]$OSImageTSVariableName, 225 | 226 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Specify a task sequence package ID for a child task sequence. Should only be used when the Apply Operating System step is in a child task sequence.")] 227 | [ValidateNotNullOrEmpty()] 228 | [string]$OverrideTSPackageID, 229 | 230 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Define the value that will be used as the target operating system version e.g. 18363.")] 231 | [parameter(Mandatory = $false, ParameterSetName = "Debug")] 232 | [ValidateNotNullOrEmpty()] 233 | [string]$TargetOSVersion, 234 | 235 | [parameter(Mandatory = $false, ParameterSetName = "Execute", HelpMessage = "Use this switch to check for drivers packages that matches earlier versions of Windows than what's detected from web service call.")] 236 | [parameter(Mandatory = $false, ParameterSetName = "Debug")] 237 | [switch]$OSVersionFallback 238 | ) 239 | Begin { 240 | # Load Microsoft.SMS.TSEnvironment COM object 241 | if ($PSCmdLet.ParameterSetName -like "Execute") { 242 | try { 243 | $TSEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction Continue 244 | } 245 | catch [System.Exception] { 246 | Write-Warning -Message "Unable to construct Microsoft.SMS.TSEnvironment object" 247 | } 248 | } 249 | } 250 | Process { 251 | # Set Log Path 252 | switch ($DeploymentType) { 253 | "OSUpgrade" { 254 | $LogsDirectory = Join-Path -Path $env:SystemRoot -ChildPath "Temp" 255 | } 256 | "DriverUpdate" { 257 | $LogsDirectory = Join-Path -Path $env:SystemRoot -ChildPath "Temp" 258 | } 259 | Default { 260 | if (-not($PSCmdLet.ParameterSetName -like "Execute")) { 261 | $LogsDirectory = Join-Path -Path $env:SystemRoot -ChildPath "Temp" 262 | } 263 | else { 264 | $LogsDirectory = $Script:TSEnvironment.Value("_SMSTSLogPath") 265 | } 266 | } 267 | } 268 | 269 | # Functions 270 | function Write-CMLogEntry { 271 | param ( 272 | [parameter(Mandatory = $true, HelpMessage = "Value added to the log file.")] 273 | [ValidateNotNullOrEmpty()] 274 | [string]$Value, 275 | 276 | [parameter(Mandatory = $true, HelpMessage = "Severity for the log entry. 1 for Informational, 2 for Warning and 3 for Error.")] 277 | [ValidateNotNullOrEmpty()] 278 | [ValidateSet("1", "2", "3")] 279 | [string]$Severity, 280 | 281 | [parameter(Mandatory = $false, HelpMessage = "Name of the log file that the entry will written to.")] 282 | [ValidateNotNullOrEmpty()] 283 | [string]$FileName = "ApplyDriverPackage.log" 284 | ) 285 | # Determine log file location 286 | $LogFilePath = Join-Path -Path $LogsDirectory -ChildPath $FileName 287 | 288 | # Construct time stamp for log entry 289 | if (-not(Test-Path -Path 'variable:global:TimezoneBias')) { 290 | [string]$global:TimezoneBias = [System.TimeZoneInfo]::Local.GetUtcOffset((Get-Date)).TotalMinutes 291 | if ($TimezoneBias -match "^-") { 292 | $TimezoneBias = $TimezoneBias.Replace('-', '+') 293 | } 294 | else { 295 | $TimezoneBias = '-' + $TimezoneBias 296 | } 297 | } 298 | $Time = -join @((Get-Date -Format "HH:mm:ss.fff"), $TimezoneBias) 299 | 300 | # Construct date for log entry 301 | $Date = (Get-Date -Format "MM-dd-yyyy") 302 | 303 | # Construct context for log entry 304 | $Context = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) 305 | 306 | # Construct final log entry 307 | $LogText = "" 308 | 309 | # Add value to log file 310 | try { 311 | Out-File -InputObject $LogText -Append -NoClobber -Encoding Default -FilePath $LogFilePath -ErrorAction Stop 312 | } 313 | catch [System.Exception] { 314 | Write-Warning -Message "Unable to append log entry to ApplyDriverPackage.log file. Error message at line $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.Message)" 315 | } 316 | } 317 | 318 | function Invoke-Executable { 319 | param ( 320 | [parameter(Mandatory = $true, HelpMessage = "Specify the file name or path of the executable to be invoked, including the extension")] 321 | [ValidateNotNullOrEmpty()] 322 | [string]$FilePath, 323 | 324 | [parameter(Mandatory = $false, HelpMessage = "Specify arguments that will be passed to the executable")] 325 | [ValidateNotNull()] 326 | [string]$Arguments 327 | ) 328 | 329 | # Construct a hash-table for default parameter splatting 330 | $SplatArgs = @{ 331 | FilePath = $FilePath 332 | NoNewWindow = $true 333 | Passthru = $true 334 | ErrorAction = "Stop" 335 | } 336 | 337 | # Add ArgumentList param if present 338 | if (-not ([System.String]::IsNullOrEmpty($Arguments))) { 339 | $SplatArgs.Add("ArgumentList", $Arguments) 340 | } 341 | 342 | # Invoke executable and wait for process to exit 343 | try { 344 | $Invocation = Start-Process @SplatArgs 345 | $Handle = $Invocation.Handle 346 | $Invocation.WaitForExit() 347 | } 348 | catch [System.Exception] { 349 | Write-Warning -Message $_.Exception.Message; break 350 | } 351 | 352 | return $Invocation.ExitCode 353 | } 354 | 355 | function Invoke-CMDownloadContent { 356 | param ( 357 | [parameter(Mandatory = $true, ParameterSetName = "NoPath", HelpMessage = "Specify a PackageID that will be downloaded.")] 358 | [Parameter(ParameterSetName = "CustomPath")] 359 | [ValidateNotNullOrEmpty()] 360 | [ValidatePattern("^[A-Z0-9]{3}[A-F0-9]{5}$")] 361 | [string]$PackageID, 362 | 363 | [parameter(Mandatory = $true, ParameterSetName = "NoPath", HelpMessage = "Specify the download location type.")] 364 | [Parameter(ParameterSetName = "CustomPath")] 365 | [ValidateNotNullOrEmpty()] 366 | [ValidateSet("Custom", "TSCache", "CCMCache")] 367 | [string]$DestinationLocationType, 368 | 369 | [parameter(Mandatory = $true, ParameterSetName = "NoPath", HelpMessage = "Save the download location to the specified variable name.")] 370 | [Parameter(ParameterSetName = "CustomPath")] 371 | [ValidateNotNullOrEmpty()] 372 | [string]$DestinationVariableName, 373 | 374 | [parameter(Mandatory = $true, ParameterSetName = "CustomPath", HelpMessage = "When location type is specified as Custom, specify the custom path.")] 375 | [ValidateNotNullOrEmpty()] 376 | [string]$CustomLocationPath 377 | ) 378 | # Set OSDDownloadDownloadPackages 379 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDownloadPackages to: $($PackageID)" -Severity 1 380 | $TSEnvironment.Value("OSDDownloadDownloadPackages") = "$($PackageID)" 381 | 382 | # Set OSDDownloadDestinationLocationType 383 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationLocationType to: $($DestinationLocationType)" -Severity 1 384 | $TSEnvironment.Value("OSDDownloadDestinationLocationType") = "$($DestinationLocationType)" 385 | 386 | # Set OSDDownloadDestinationVariable 387 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationVariable to: $($DestinationVariableName)" -Severity 1 388 | $TSEnvironment.Value("OSDDownloadDestinationVariable") = "$($DestinationVariableName)" 389 | 390 | # Set OSDDownloadDestinationPath 391 | if ($DestinationLocationType -like "Custom") { 392 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationPath to: $($CustomLocationPath)" -Severity 1 393 | $TSEnvironment.Value("OSDDownloadDestinationPath") = "$($CustomLocationPath)" 394 | } 395 | 396 | # Invoke download of package content 397 | try { 398 | if ($TSEnvironment.Value("_SMSTSInWinPE") -eq $false) { 399 | Write-CMLogEntry -Value " - Starting package content download process (FullOS), this might take some time" -Severity 1 400 | $ReturnCode = Invoke-Executable -FilePath (Join-Path -Path $env:windir -ChildPath "CCM\OSDDownloadContent.exe") 401 | } 402 | else { 403 | Write-CMLogEntry -Value " - Starting package content download process (WinPE), this might take some time" -Severity 1 404 | $ReturnCode = Invoke-Executable -FilePath "OSDDownloadContent.exe" 405 | } 406 | 407 | # Match on return code 408 | if ($ReturnCode -eq 0) { 409 | Write-CMLogEntry -Value " - Successfully downloaded package content with PackageID: $($PackageID)" -Severity 1 410 | } 411 | } 412 | catch [System.Exception] { 413 | Write-CMLogEntry -Value " - An error occurred while attempting to download package content. Error message: $($_.Exception.Message)" -Severity 3 414 | 415 | # Throw terminating error 416 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 417 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 418 | } 419 | 420 | return $ReturnCode 421 | } 422 | 423 | function Invoke-CMResetDownloadContentVariables { 424 | # Set OSDDownloadDownloadPackages 425 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDownloadPackages to a blank value" -Severity 1 426 | $TSEnvironment.Value("OSDDownloadDownloadPackages") = [System.String]::Empty 427 | 428 | # Set OSDDownloadDestinationLocationType 429 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationLocationType to a blank value" -Severity 1 430 | $TSEnvironment.Value("OSDDownloadDestinationLocationType") = [System.String]::Empty 431 | 432 | # Set OSDDownloadDestinationVariable 433 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationVariable to a blank value" -Severity 1 434 | $TSEnvironment.Value("OSDDownloadDestinationVariable") = [System.String]::Empty 435 | 436 | # Set OSDDownloadDestinationPath 437 | Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationPath to a blank value" -Severity 1 438 | $TSEnvironment.Value("OSDDownloadDestinationPath") = [System.String]::Empty 439 | } 440 | 441 | function Get-OSImageData { 442 | # Determine how to get the SMSTSPackageID value 443 | if ($PSCmdLet.ParameterSetName -eq "Execute") { 444 | if ($Script:PSBoundParameters["OverrideTSPackageID"]) { 445 | $SMSTSPackageID = $OverrideTSPackageID 446 | } 447 | else { 448 | $SMSTSPackageID = $TSEnvironment.Value("_SMSTSPackageID") 449 | } 450 | } 451 | else { 452 | $SMSTSPackageID = $TSPackageID 453 | } 454 | 455 | try { 456 | # Determine OS Image information for running task sequence from web service 457 | Write-CMLogEntry -Value " - Attempting to detect OS Image data from task sequence with PackageID: $($SMSTSPackageID)" -Severity 1 458 | $OSImages = $WebService.GetCMOSImageForTaskSequence($SecretKey, $SMSTSPackageID) 459 | if ($OSImages -ne $null) { 460 | if (($OSImages | Measure-Object).Count -ge 2) { 461 | # Determine behavior when detecting OS Image data 462 | if ($Script:PSBoundParameters["OSImageTSVariableName"]) { 463 | # Select OS Image object matching the value from the task sequence variable passed to the OSImageTSVariableName parameter 464 | Write-CMLogEntry -Value " - Multiple OS Image objects detected. Objects will be matched against provided task sequence variable name '$($OSImageTSVariableName)' to determine the correct object" -Severity 1 465 | $OSImageTSVariableValue = $TSEnvironment.Value("$($OSImageTSVariableName)") 466 | foreach ($OSImage in $OSImages) { 467 | if ($OSImage.PackageID -like $OSImageTSVariableValue) { 468 | # Handle support for target OS version override from parameter input 469 | if ($Script:PSBoundParameters["TargetOSVersion"]) { 470 | $OSBuild = "10.0.$($TargetOSVersion).1" 471 | } 472 | else { 473 | $OSBuild = $OSImage.Version 474 | } 475 | 476 | # Create custom object for return value 477 | $PSObject = [PSCustomObject]@{ 478 | OSVersion = $OSBuild 479 | OSArchitecture = $OSImage.Architecture 480 | } 481 | 482 | # Handle return value 483 | return $PSObject 484 | } 485 | } 486 | } 487 | else { 488 | # Select the first object returned from web service call 489 | Write-CMLogEntry -Value " - Multiple OS Image objects detected and OSImageTSVariableName was not specified. Selecting the first OS Image object from web service call" -Severity 1 490 | $OSImage = $OSImages | Sort-Object -Descending | Select-Object -First 1 491 | 492 | # Handle support for target OS version override from parameter input 493 | if ($Script:PSBoundParameters["TargetOSVersion"]) { 494 | $OSBuild = "10.0.$($TargetOSVersion).1" 495 | } 496 | else { 497 | $OSBuild = $OSImage.Version 498 | } 499 | 500 | # Create custom object for return value 501 | $PSObject = [PSCustomObject]@{ 502 | OSVersion = $OSBuild 503 | OSArchitecture = $OSImage.Architecture 504 | } 505 | 506 | # Handle return value 507 | return $PSObject 508 | } 509 | } 510 | else { 511 | # Handle support for target OS version override from parameter input 512 | if ($Script:PSBoundParameters["TargetOSVersion"]) { 513 | $OSBuild = "10.0.$($TargetOSVersion).1" 514 | } 515 | else { 516 | $OSBuild = $OSImages.Version 517 | } 518 | 519 | # Create custom object for return value 520 | $PSObject = [PSCustomObject]@{ 521 | OSVersion = $OSBuild 522 | OSArchitecture = $OSImages.Architecture 523 | } 524 | 525 | # Handle return value 526 | return $PSObject 527 | } 528 | } 529 | else { 530 | Write-CMLogEntry -Value " - Call to ConfigMgr WebService returned empty OS Image data. Error message: $($_.Exception.Message)" -Severity 3 531 | 532 | # Throw terminating error 533 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 534 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 535 | } 536 | } 537 | catch [System.Exception] { 538 | Write-CMLogEntry -Value " - An error occured while calling ConfigMgr WebService to get OS Image data. Error message: $($_.Exception.Message)" -Severity 3 539 | 540 | # Throw terminating error 541 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 542 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 543 | } 544 | } 545 | 546 | function Get-OSArchitecture { 547 | param ( 548 | [parameter(Mandatory = $true, HelpMessage = "OS architecture data to be translated.")] 549 | [ValidateNotNullOrEmpty()] 550 | [string]$InputObject 551 | ) 552 | switch -Wildcard ($InputObject) { 553 | "9" { 554 | $OSImageArchitecture = "x64" 555 | } 556 | "0" { 557 | $OSImageArchitecture = "x86" 558 | } 559 | "64*" { 560 | $OSImageArchitecture = "x64" 561 | } 562 | "32*" { 563 | $OSImageArchitecture = "x86" 564 | } 565 | default { 566 | Write-CMLogEntry -Value " - Unable to translate OS architecture using input object: $($InputObject)" -Severity 3 567 | 568 | # Throw terminating error 569 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 570 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 571 | } 572 | } 573 | 574 | # Handle return value from function 575 | return $OSImageArchitecture 576 | } 577 | 578 | function Get-OSDetails { 579 | param ( 580 | [parameter(Mandatory = $true, HelpMessage = "Windows build number must be provided")] 581 | [ValidateNotNullOrEmpty()] 582 | [string]$InputObject 583 | ) 584 | 585 | # Get operating system name and from build number 586 | switch -Wildcard ($InputObject) { 587 | "10.0*" { 588 | $OSName = "Windows 10" 589 | switch (([System.Version]$InputObject).Build) { 590 | "19042" { 591 | $OSVersion = 2009 592 | } 593 | "19041" { 594 | $OSVersion = 2004 595 | } 596 | "18363" { 597 | $OSVersion = 1909 598 | } 599 | "18362" { 600 | $OSVersion = 1903 601 | } 602 | "17763" { 603 | $OSVersion = 1809 604 | } 605 | "17134" { 606 | $OSVersion = 1803 607 | } 608 | "16299" { 609 | $OSVersion = 1709 610 | } 611 | "15063" { 612 | $OSVersion = 1703 613 | } 614 | "14393" { 615 | $OSVersion = 1607 616 | } 617 | } 618 | } 619 | default { 620 | Write-CMLogEntry -Value " - Unable to translate OS name and version using input object: $($InputObject)" -Severity 3 621 | 622 | # Throw terminating error 623 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 624 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 625 | } 626 | } 627 | 628 | # Handle return value from function 629 | if (($OSName -ne $null) -and ($OSVersion -ne $null)) { 630 | $PSObject = [PSCustomObject]@{ 631 | OSName = $OSName 632 | OSVersion = $OSVersion 633 | } 634 | return $PSObject 635 | } 636 | else { 637 | Write-CMLogEntry -Value " - Unable to translate OS name and version. Both properties did not contain any values" -Severity 3 638 | 639 | # Throw terminating error 640 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 641 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 642 | } 643 | } 644 | 645 | function New-TerminatingErrorRecord { 646 | param( 647 | [parameter(Mandatory=$true, HelpMessage="Specify the exception message details.")] 648 | [ValidateNotNullOrEmpty()] 649 | [string]$Message, 650 | 651 | [parameter(Mandatory=$false, HelpMessage="Specify the violation exception causing the error.")] 652 | [ValidateNotNullOrEmpty()] 653 | [string]$Exception = "System.Management.Automation.RuntimeException", 654 | 655 | [parameter(Mandatory=$false, HelpMessage="Specify the error category of the exception causing the error.")] 656 | [ValidateNotNullOrEmpty()] 657 | [System.Management.Automation.ErrorCategory]$ErrorCategory = [System.Management.Automation.ErrorCategory]::NotImplemented, 658 | 659 | [parameter(Mandatory=$false, HelpMessage="Specify the target object causing the error.")] 660 | [ValidateNotNullOrEmpty()] 661 | [string]$TargetObject = ([string]::Empty) 662 | ) 663 | # Construct new error record to be returned from function based on parameter inputs 664 | $SystemException = New-Object -TypeName $Exception -ArgumentList $Message 665 | $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @($SystemException, $ErrorID, $ErrorCategory, $TargetObject) 666 | 667 | # Handle return value 668 | return $ErrorRecord 669 | } 670 | 671 | function Connect-WebService { 672 | # Construct new web service proxy 673 | try { 674 | $WebService = New-WebServiceProxy -Uri $URI -ErrorAction Stop 675 | Write-CMLogEntry -Value " - Successfully connected to ConfigMgr WebService at URI: $($URI)" -Severity 1 676 | 677 | # Handle return value 678 | return $WebService 679 | } 680 | catch [System.Exception] { 681 | Write-CMLogEntry -Value " - Unable to establish a connection to ConfigMgr WebService. Error message: $($_.Exception.Message)" -Severity 3 682 | 683 | # Throw terminating error 684 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 685 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 686 | } 687 | } 688 | 689 | function Get-OSImageDetails { 690 | $OSImageDetails = [PSCustomObject]@{ 691 | Architecture = $null 692 | Name = $null 693 | Version = $null 694 | } 695 | 696 | switch ($DeploymentType) { 697 | "BareMetal" { 698 | # Get OS Image data 699 | $OSImageData = Get-OSImageData 700 | 701 | # Get OS data 702 | $OSImageVersion = $OSImageData.OSVersion 703 | $OSArchitecture = $OSImageData.OSArchitecture 704 | 705 | # Translate operating system name from version 706 | $OSDetails = Get-OSDetails -InputObject $OSImageVersion 707 | $OSImageDetails.Name = $OSDetails.OSName 708 | $OSImageDetails.Version = $OSDetails.OSVersion 709 | 710 | # Translate operating system architecture from web service response 711 | $OSImageDetails.Architecture = Get-OSArchitecture -InputObject $OSArchitecture 712 | } 713 | "OSUpgrade" { 714 | # Get OS Image data 715 | $OSImageData = Get-OSImageData 716 | 717 | # Get OS data 718 | $OSImageVersion = $OSImageData.OSVersion 719 | $OSArchitecture = $OSImageData.OSArchitecture 720 | 721 | # Translate operating system name from version 722 | $OSDetails = Get-OSDetails -InputObject $OSImageVersion 723 | $OSImageDetails.Name = $OSDetails.OSName 724 | $OSImageDetails.Version = $OSDetails.OSVersion 725 | 726 | # Translate operating system architecture from web service response 727 | $OSImageDetails.Architecture = Get-OSArchitecture -InputObject $OSArchitecture 728 | } 729 | "DriverUpdate" { 730 | # Get OS data 731 | $OSImageVersion = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty Version 732 | $OSArchitecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty OSArchitecture 733 | 734 | # Translate operating system name from version 735 | $OSDetails = Get-OSDetails -InputObject $OSImageVersion 736 | $OSImageDetails.Name = $OSDetails.OSName 737 | $OSImageDetails.Version = $OSDetails.OSVersion 738 | 739 | # Translate operating system architecture from running operating system 740 | $OSImageDetails.Architecture = Get-OSArchitecture -InputObject $OSArchitecture 741 | } 742 | "PreCache" { 743 | # Get OS Image data 744 | $OSImageData = Get-OSImageData 745 | 746 | # Get OS data 747 | $OSImageVersion = $OSImageData.OSVersion 748 | $OSArchitecture = $OSImageData.OSArchitecture 749 | 750 | # Translate operating system name from version 751 | $OSDetails = Get-OSDetails -InputObject $OSImageVersion 752 | $OSImageDetails.Name = $OSDetails.OSName 753 | $OSImageDetails.Version = $OSDetails.OSVersion 754 | 755 | # Translate operating system architecture from web service response 756 | $OSImageDetails.Architecture = Get-OSArchitecture -InputObject $OSArchitecture 757 | } 758 | } 759 | 760 | # Handle output to log file for OS image details 761 | Write-CMLogEntry -Value " - Target operating system name detected as: $($OSImageDetails.Name)" -Severity 1 762 | Write-CMLogEntry -Value " - Target operating system architecture detected as: $($OSImageDetails.Architecture)" -Severity 1 763 | Write-CMLogEntry -Value " - Target operating system build version detected as: $($OSImageVersion)" -Severity 1 764 | Write-CMLogEntry -Value " - Target operating system translated version detected as: $($OSImageDetails.Version)" -Severity 1 765 | 766 | # Handle return value 767 | return $OSImageDetails 768 | } 769 | 770 | function Get-DriverPackages { 771 | param( 772 | [parameter(Mandatory = $true, HelpMessage = "Specify the web service object returned from Connect-WebService function.")] 773 | [ValidateNotNullOrEmpty()] 774 | [PSCustomObject]$WebService 775 | ) 776 | try { 777 | # Retrieve driver packages but filter out matches depending on script operational mode 778 | switch ($OperationalMode) { 779 | "Production" { 780 | $Packages = $WebService.GetCMPackage($SecretKey, $Filter) | Where-Object { $_.PackageName -notmatch "Pilot" -and $_.PackageName -notmatch "Retired" } 781 | } 782 | "Pilot" { 783 | $Packages = $WebService.GetCMPackage($SecretKey, $Filter) | Where-Object { $_.PackageName -match "Pilot" } 784 | } 785 | } 786 | 787 | # Handle return value 788 | if ($Packages -ne $null) { 789 | Write-CMLogEntry -Value " - Retrieved a total of '$(($Packages | Measure-Object).Count)' driver packages from web service matching operational mode: $($OperationalMode)" -Severity 1 790 | return $Packages 791 | } 792 | else { 793 | Write-CMLogEntry -Value " - Retrieved a total of '0' driver packages from web service matching operational mode: $($OperationalMode)" -Severity 3 794 | 795 | # Throw terminating error 796 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 797 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 798 | } 799 | } 800 | catch [System.Exception] { 801 | Write-CMLogEntry -Value " - An error occurred while calling ConfigMgr WebService for a list of available driver packages. Error message: $($_.Exception.Message)" -Severity 3 802 | 803 | # Throw terminating error 804 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 805 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 806 | } 807 | } 808 | 809 | function Get-ComputerData { 810 | # Create a custom object for computer details gathered from local WMI 811 | $ComputerDetails = [PSCustomObject]@{ 812 | Manufacturer = $null 813 | Model = $null 814 | SystemSKU = $null 815 | FallbackSKU = $null 816 | } 817 | 818 | # Gather computer details based upon specific computer manufacturer 819 | $ComputerManufacturer = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Manufacturer).Trim() 820 | switch -Wildcard ($ComputerManufacturer) { 821 | "*Microsoft*" { 822 | $ComputerDetails.Manufacturer = "Microsoft" 823 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 824 | $ComputerDetails.SystemSKU = Get-WmiObject -Namespace "root\wmi" -Class "MS_SystemInformation" | Select-Object -ExpandProperty SystemSKU 825 | } 826 | "*HP*" { 827 | $ComputerDetails.Manufacturer = "HP" 828 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 829 | $ComputerDetails.SystemSKU = (Get-CIMInstance -ClassName "MS_SystemInformation" -NameSpace "root\WMI").BaseBoardProduct.Trim() 830 | } 831 | "*Hewlett-Packard*" { 832 | $ComputerDetails.Manufacturer = "HP" 833 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 834 | $ComputerDetails.SystemSKU = (Get-CIMInstance -ClassName "MS_SystemInformation" -NameSpace "root\WMI").BaseBoardProduct.Trim() 835 | } 836 | "*Dell*" { 837 | $ComputerDetails.Manufacturer = "Dell" 838 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 839 | $ComputerDetails.SystemSKU = (Get-CIMInstance -ClassName "MS_SystemInformation" -NameSpace "root\WMI").SystemSku.Trim() 840 | [string]$OEMString = Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty OEMStringArray 841 | $ComputerDetails.FallbackSKU = [regex]::Matches($OEMString, '\[\S*]')[0].Value.TrimStart("[").TrimEnd("]") 842 | } 843 | "*Lenovo*" { 844 | $ComputerDetails.Manufacturer = "Lenovo" 845 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystemProduct" | Select-Object -ExpandProperty Version).Trim() 846 | $ComputerDetails.SystemSKU = ((Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).SubString(0, 4)).Trim() 847 | } 848 | "*Panasonic*" { 849 | $ComputerDetails.Manufacturer = "Panasonic Corporation" 850 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 851 | $ComputerDetails.SystemSKU = (Get-CIMInstance -ClassName "MS_SystemInformation" -NameSpace "root\WMI").BaseBoardProduct.Trim() 852 | } 853 | "*Viglen*" { 854 | $ComputerDetails.Manufacturer = "Viglen" 855 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 856 | $ComputerDetails.SystemSKU = (Get-WmiObject -Class "Win32_BaseBoard" | Select-Object -ExpandProperty SKU).Trim() 857 | } 858 | "*AZW*" { 859 | $ComputerDetails.Manufacturer = "AZW" 860 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 861 | $ComputerDetails.SystemSKU = (Get-CIMInstance -ClassName "MS_SystemInformation" -NameSpace root\WMI).BaseBoardProduct.Trim() 862 | } 863 | "*Fujitsu*" { 864 | $ComputerDetails.Manufacturer = "Fujitsu" 865 | $ComputerDetails.Model = (Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty Model).Trim() 866 | $ComputerDetails.SystemSKU = (Get-WmiObject -Class "Win32_BaseBoard" | Select-Object -ExpandProperty SKU).Trim() 867 | } 868 | } 869 | 870 | # Handle overriding computer details if debug mode and additional parameters was specified 871 | if ($Script:PSCmdlet.ParameterSetName -like "Debug") { 872 | if (-not([string]::IsNullOrEmpty($Manufacturer))) { 873 | $ComputerDetails.Manufacturer = $Manufacturer 874 | } 875 | if (-not([string]::IsNullOrEmpty($ComputerModel))) { 876 | $ComputerDetails.Model = $ComputerModel 877 | } 878 | if (-not([string]::IsNullOrEmpty($SystemSKU))) { 879 | $ComputerDetails.SystemSKU = $SystemSKU 880 | } 881 | } 882 | 883 | # Handle output to log file for computer details 884 | Write-CMLogEntry -Value " - Computer manufacturer determined as: $($ComputerDetails.Manufacturer)" -Severity 1 885 | Write-CMLogEntry -Value " - Computer model determined as: $($ComputerDetails.Model)" -Severity 1 886 | 887 | # Handle output to log file for computer SystemSKU 888 | if (-not([string]::IsNullOrEmpty($ComputerDetails.SystemSKU))) { 889 | Write-CMLogEntry -Value " - Computer SystemSKU determined as: $($ComputerDetails.SystemSKU)" -Severity 1 890 | } 891 | else { 892 | Write-CMLogEntry -Value " - Computer SystemSKU determined as: " -Severity 2 893 | } 894 | 895 | # Handle output to log file for Fallback SKU 896 | if (-not([string]::IsNullOrEmpty($ComputerDetails.FallBackSKU))) { 897 | Write-CMLogEntry -Value " - Computer Fallback SystemSKU determined as: $($ComputerDetails.FallBackSKU)" -Severity 1 898 | } 899 | 900 | # Handle return value from function 901 | return $ComputerDetails 902 | } 903 | 904 | function Get-ComputerSystemType { 905 | $ComputerSystemType = Get-WmiObject -Class "Win32_ComputerSystem" | Select-Object -ExpandProperty "Model" 906 | if ($ComputerSystemType -notin @("Virtual Machine", "VMware Virtual Platform", "VirtualBox", "HVM domU", "KVM")) { 907 | Write-CMLogEntry -Value " - Supported computer platform detected, script execution allowed to continue" -Severity 1 908 | } 909 | else { 910 | if ($Script:PSBoundParameters["DebugMode"]) { 911 | Write-CMLogEntry -Value " - Unsupported computer platform detected, virtual machines are not supported but will be allowed in DebugMode" -Severity 2 912 | } 913 | else { 914 | Write-CMLogEntry -Value " - Unsupported computer platform detected, virtual machines are not supported" -Severity 3 915 | 916 | # Throw terminating error 917 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 918 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 919 | } 920 | } 921 | } 922 | 923 | function Get-OperatingSystemVersion { 924 | if ($DeploymentType -like "DriverUpdate") { 925 | $OperatingSystemVersion = Get-WmiObject -Class "Win32_OperatingSystem" | Select-Object -ExpandProperty "Version" 926 | if ($OperatingSystemVersion -like "10.0.*") { 927 | Write-CMLogEntry -Value " - Supported operating system version currently running detected, script execution allowed to continue" -Severity 1 928 | } 929 | else { 930 | Write-CMLogEntry -Value " - Unsupported operating system version detected, this script is only supported on Windows 10 and above" -Severity 3 931 | 932 | # Throw terminating error 933 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 934 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 935 | } 936 | } 937 | } 938 | 939 | function Test-ComputerDetails { 940 | param( 941 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer details object from Get-ComputerDetails function.")] 942 | [ValidateNotNullOrEmpty()] 943 | [PSCustomObject]$InputObject 944 | ) 945 | # Construct custom object for computer details validation 946 | $Script:ComputerDetection = [PSCustomObject]@{ 947 | "ModelDetected" = $false 948 | "SystemSKUDetected" = $false 949 | } 950 | 951 | if (($InputObject.Model -ne $null) -and (-not([System.String]::IsNullOrEmpty($InputObject.Model)))) { 952 | Write-CMLogEntry -Value " - Computer model detection was successful" -Severity 1 953 | $ComputerDetection.ModelDetected = $true 954 | } 955 | 956 | if (($InputObject.SystemSKU -ne $null) -and (-not([System.String]::IsNullOrEmpty($InputObject.SystemSKU)))) { 957 | Write-CMLogEntry -Value " - Computer SystemSKU detection was successful" -Severity 1 958 | $ComputerDetection.SystemSKUDetected = $true 959 | } 960 | 961 | if (($ComputerDetection.ModelDetected -eq $false) -and ($ComputerDetection.SystemSKUDetected -eq $false)) { 962 | Write-CMLogEntry -Value " - Computer model and SystemSKU values are missing, script execution is not allowed since required values to continue could not be gathered" -Severity 3 963 | 964 | # Throw terminating error 965 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 966 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 967 | } 968 | else { 969 | Write-CMLogEntry -Value " - Computer details successfully verified" -Severity 1 970 | } 971 | } 972 | 973 | function Set-ComputerDetectionMethod { 974 | if ($ComputerDetection.SystemSKUDetected -eq $true) { 975 | Write-CMLogEntry -Value " - Determined primary computer detection method: SystemSKU" -Severity 1 976 | return "SystemSKU" 977 | } 978 | else { 979 | Write-CMLogEntry -Value " - Determined fallback computer detection method: ComputerModel" -Severity 1 980 | return "ComputerModel" 981 | } 982 | } 983 | 984 | function Confirm-DriverPackage { 985 | param( 986 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer details object from Get-ComputerDetails function.")] 987 | [ValidateNotNullOrEmpty()] 988 | [PSCustomObject]$ComputerData, 989 | 990 | [parameter(Mandatory = $true, HelpMessage = "Specify the OS Image details object from Get-OSImageDetails function.")] 991 | [ValidateNotNullOrEmpty()] 992 | [PSCustomObject]$OSImageData, 993 | 994 | [parameter(Mandatory = $true, HelpMessage = "Specify the driver package object to be validated.")] 995 | [ValidateNotNullOrEmpty()] 996 | [System.Object[]]$DriverPackage, 997 | 998 | [parameter(Mandatory = $false, HelpMessage = "Set to True to check for drivers packages that matches earlier versions of Windows than what's detected from web service call.")] 999 | [ValidateNotNullOrEmpty()] 1000 | [bool]$OSVersionFallback = $false 1001 | ) 1002 | # Sort all driver package objects by package name property 1003 | $DriverPackages = $DriverPackage | Sort-Object -Property PackageName 1004 | $DriverPackagesCount = ($DriverPackages | Measure-Object).Count 1005 | Write-CMLogEntry -Value " - Initial count of driver packages before starting filtering process: $($DriverPackagesCount)" -Severity 1 1006 | 1007 | # Filter out driver packages that does not match with the vendor 1008 | Write-CMLogEntry -Value " - Filtering driver package results to detected computer manufacturer: $($ComputerData.Manufacturer)" -Severity 1 1009 | $DriverPackages = $DriverPackages | Where-Object { $_.PackageManufacturer -like $ComputerData.Manufacturer } 1010 | $DriverPackagesCount = ($DriverPackages | Measure-Object).Count 1011 | Write-CMLogEntry -Value " - Count of driver packages after filter processing: $($DriverPackagesCount)" -Severity 1 1012 | 1013 | # Filter out driver packages that does not contain any value in the package description 1014 | Write-CMLogEntry -Value " - Filtering driver package results to only include packages that have details added to the description field" -Severity 1 1015 | $DriverPackages = $DriverPackages | Where-Object { $_.PackageDescription -ne ([string]::Empty) } 1016 | $DriverPackagesCount = ($DriverPackages | Measure-Object).Count 1017 | Write-CMLogEntry -Value " - Count of driver packages after filter processing: $($DriverPackagesCount)" -Severity 1 1018 | 1019 | foreach ($DriverPackageItem in $DriverPackages) { 1020 | try { 1021 | # Construct custom object to hold values for current driver package properties used for matching with current computer details 1022 | $DriverPackageDetails = [PSCustomObject]@{ 1023 | PackageName = $DriverPackageItem.PackageName 1024 | PackageID = $DriverPackageItem.PackageID 1025 | PackageVersion = $DriverPackageItem.PackageVersion 1026 | DateCreated = $DriverPackageItem.PackageCreated 1027 | Manufacturer = $DriverPackageItem.PackageManufacturer 1028 | Model = $null 1029 | SystemSKU = $DriverPackageItem.PackageDescription.Split(":").Replace("(", "").Replace(")", "")[1] 1030 | OSName = $null 1031 | OSVersion = $null 1032 | Architecture = $null 1033 | } 1034 | } 1035 | catch [System.Exception] { 1036 | Write-CMLogEntry -Value " - Failed to construct custom object to hold values for current driver package properties used for matching with current computer details" -Severity 1 1037 | 1038 | # Throw terminating error 1039 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1040 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1041 | } 1042 | 1043 | # Add driver package model details depending on manufacturer to custom driver package details object 1044 | # - Hewlett-Packard computer models include 'HP' in the model property and requires special attention for detecting the proper model value from the driver package name property 1045 | switch ($DriverPackageItem.PackageManufacturer) { 1046 | "Hewlett-Packard" { 1047 | $DriverPackageDetails.Model = $DriverPackageItem.PackageName.Replace("Hewlett-Packard", "HP").Replace(" - ", ":").Split(":").Trim()[1] 1048 | } 1049 | "HP" { 1050 | $DriverPackageDetails.Model = $DriverPackageItem.PackageName.Replace(" - ", ":").Split(":").Trim()[1] 1051 | } 1052 | default { 1053 | $DriverPackageDetails.Model = $DriverPackageItem.PackageName.Replace($DriverPackageItem.PackageManufacturer, "").Replace(" - ", ":").Split(":").Trim()[1] 1054 | } 1055 | } 1056 | 1057 | # Add driver package OS architecture details to custom driver package details object 1058 | if ($DriverPackageItem.PackageName -match "^.*(?(x86|x64)).*") { 1059 | $DriverPackageDetails.Architecture = $Matches.Architecture 1060 | } 1061 | 1062 | # Add driver package OS name details to custom driver package details object 1063 | if ($DriverPackageItem.PackageName -match "^.*Windows.*(?(10)).*") { 1064 | $DriverPackageDetails.OSName = -join@("Windows ", $Matches.OSName) 1065 | } 1066 | 1067 | # Add driver package OS version details to custom driver package details object 1068 | if ($DriverPackageItem.PackageName -match "^.*Windows.*(?(\d){4}).*") { 1069 | $DriverPackageDetails.OSVersion = $Matches.OSVersion 1070 | } 1071 | 1072 | # Set counters for logging output of how many matching checks was successfull 1073 | $DetectionCounter = 0 1074 | if ($DriverPackageDetails.OSVersion -ne $null) { 1075 | $DetectionMethodsCount = 4 1076 | } 1077 | else { 1078 | $DetectionMethodsCount = 3 1079 | } 1080 | Write-CMLogEntry -Value "[DriverPackage:$($DriverPackageItem.PackageID)]: Processing driver package with $($DetectionMethodsCount) detection methods: $($DriverPackageItem.PackageName)" -Severity 1 1081 | 1082 | switch ($ComputerDetectionMethod) { 1083 | "SystemSKU" { 1084 | # Attempt to match against SystemSKU 1085 | $ComputerDetectionMethodResult = Confirm-SystemSKU -DriverPackageInput $DriverPackageDetails.SystemSKU -ComputerData $ComputerData -ErrorAction Stop 1086 | 1087 | # Fall back to using computer model as the detection method instead of SystemSKU 1088 | if ($ComputerDetectionMethodResult.Detected -eq $false) { 1089 | $ComputerDetectionMethodResult = Confirm-ComputerModel -DriverPackageInput $DriverPackageDetails.Model -ComputerData $ComputerData 1090 | } 1091 | } 1092 | "ComputerModel" { 1093 | # Attempt to match against computer model 1094 | $ComputerDetectionMethodResult = Confirm-ComputerModel -DriverPackageInput $DriverPackageDetails.Model -ComputerData $ComputerData 1095 | } 1096 | } 1097 | 1098 | if ($ComputerDetectionMethodResult.Detected -eq $true) { 1099 | # Increase detection counter since computer detection was successful 1100 | $DetectionCounter++ 1101 | 1102 | # Attempt to match against OS name 1103 | $OSNameDetectionResult = Confirm-OSName -DriverPackageInput $DriverPackageDetails.OSName -OSImageData $OSImageData 1104 | if ($OSNameDetectionResult -eq $true) { 1105 | # Increase detection counter since OS name detection was successful 1106 | $DetectionCounter++ 1107 | 1108 | $OSArchitectureDetectionResult = Confirm-Architecture -DriverPackageInput $DriverPackageDetails.Architecture -OSImageData $OSImageData 1109 | if ($OSArchitectureDetectionResult -eq $true) { 1110 | # Increase detection counter since OS architecture detection was successful 1111 | $DetectionCounter++ 1112 | 1113 | if ($DriverPackageDetails.OSVersion -ne $null) { 1114 | # Handle if OS version should check for fallback versions or match with data from OSImageData variable 1115 | if ($OSVersionFallback -eq $true) { 1116 | $OSVersionDetectionResult = Confirm-OSVersion -DriverPackageInput $DriverPackageDetails.OSVersion -OSImageData $OSImageData -OSVersionFallback $true 1117 | } 1118 | else { 1119 | $OSVersionDetectionResult = Confirm-OSVersion -DriverPackageInput $DriverPackageDetails.OSVersion -OSImageData $OSImageData 1120 | } 1121 | 1122 | if ($OSVersionDetectionResult -eq $true) { 1123 | # Increase detection counter since OS version detection was successful 1124 | $DetectionCounter++ 1125 | 1126 | # Match found for all critiera including OS version 1127 | Write-CMLogEntry -Value "[DriverPackage:$($DriverPackageItem.PackageID)]: Driver package was created on: $($DriverPackageDetails.DateCreated)" -Severity 1 1128 | Write-CMLogEntry -Value "[DriverPackage:$($DriverPackageItem.PackageID)]: Match found between driver package and computer for $($DetectionCounter)/$($DetectionMethodsCount) checks, adding to list for post-processing of matched driver packages" -Severity 1 1129 | 1130 | # Update the SystemSKU value for the custom driver package details object to account for multiple values from original driver package data 1131 | if ($ComputerDetectionMethod -like "SystemSKU") { 1132 | $DriverPackageDetails.SystemSKU = $ComputerDetectionMethodResult.SystemSKUValue 1133 | } 1134 | 1135 | # Add custom driver package details object to list of driver packages for post-processing 1136 | $DriverPackageList.Add($DriverPackageDetails) | Out-Null 1137 | } 1138 | else { 1139 | Write-CMLogEntry -Value "[DriverPackage:$($DriverPackageItem.PackageID)]: Skipping driver package since only $($DetectionCounter)/$($DetectionMethodsCount) checks was matched" -Severity 2 1140 | } 1141 | } 1142 | else { 1143 | # Match found for all critiera except for OS version, assuming here that the vendor does not provide OS version specific driver packages 1144 | Write-CMLogEntry -Value "[DriverPackage:$($DriverPackageItem.PackageID)]: Driver package was created on: $($DriverPackageDetails.DateCreated)" -Severity 1 1145 | Write-CMLogEntry -Value "[DriverPackage:$($DriverPackageItem.PackageID)]: Match found between driver package and computer, adding to list for post-processing of matched driver packages" -Severity 1 1146 | 1147 | # Update the SystemSKU value for the custom driver package details object to account for multiple values from original driver package data 1148 | if ($ComputerDetectionMethod -like "SystemSKU") { 1149 | $DriverPackageDetails.SystemSKU = $ComputerDetectionMethodResult.SystemSKUValue 1150 | } 1151 | 1152 | # Add custom driver package details object to list of driver packages for post-processing 1153 | $DriverPackageList.Add($DriverPackageDetails) | Out-Null 1154 | } 1155 | } 1156 | } 1157 | } 1158 | } 1159 | } 1160 | 1161 | function Confirm-FallbackDriverPackage { 1162 | param( 1163 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer details object from Get-ComputerDetails function.")] 1164 | [ValidateNotNullOrEmpty()] 1165 | [PSCustomObject]$ComputerData, 1166 | 1167 | [parameter(Mandatory = $true, HelpMessage = "Specify the OS Image details object from Get-OSImageDetails function.")] 1168 | [ValidateNotNullOrEmpty()] 1169 | [PSCustomObject]$OSImageData, 1170 | 1171 | [parameter(Mandatory = $true, HelpMessage = "Specify the web service object returned from Connect-WebService function.")] 1172 | [ValidateNotNullOrEmpty()] 1173 | [PSCustomObject]$WebService 1174 | ) 1175 | if ($Script:DriverPackageList.Count -eq 0) { 1176 | Write-CMLogEntry -Value " - Previous validation process could not find a match for a specific driver package, starting fallback driver package matching process" -Severity 1 1177 | 1178 | try { 1179 | # Attempt to retrieve fallback driver packages from ConfigMgr WebService 1180 | $FallbackDriverPackages = $WebService.GetCMPackage($SecretKey, "Driver Fallback Package") | Where-Object { $_.PackageName -notmatch "Pilot" -and $_.PackageName -notmatch "Retired" } 1181 | 1182 | if ($FallbackDriverPackages -ne $null) { 1183 | Write-CMLogEntry -Value " - Retrieved a total of '$(($FallbackDriverPackages | Measure-Object).Count)' fallback driver packages from web service matching 'Driver Fallback Package' within the name" -Severity 1 1184 | 1185 | # Sort all fallback driver package objects by package name property 1186 | $FallbackDriverPackages = $FallbackDriverPackages | Sort-Object -Property PackageName 1187 | 1188 | # Filter out driver packages that does not match with the vendor 1189 | Write-CMLogEntry -Value " - Filtering fallback driver package results to detected computer manufacturer: $($ComputerData.Manufacturer)" -Severity 1 1190 | $FallbackDriverPackages = $FallbackDriverPackages | Where-Object { $_.PackageManufacturer -like $ComputerData.Manufacturer } 1191 | 1192 | foreach ($DriverPackageItem in $FallbackDriverPackages) { 1193 | # Construct custom object to hold values for current driver package properties used for matching with current computer details 1194 | $DriverPackageDetails = [PSCustomObject]@{ 1195 | PackageName = $DriverPackageItem.PackageName 1196 | PackageID = $DriverPackageItem.PackageID 1197 | DateCreated = $DriverPackageItem.PackageCreated 1198 | Manufacturer = $DriverPackageItem.PackageManufacturer 1199 | OSName = $null 1200 | Architecture = $null 1201 | } 1202 | 1203 | # Add driver package OS architecture details to custom driver package details object 1204 | if ($DriverPackageItem.PackageName -match "^.*(?(x86|x64)).*") { 1205 | $DriverPackageDetails.Architecture = $Matches.Architecture 1206 | } 1207 | 1208 | # Add driver package OS name details to custom driver package details object 1209 | if ($DriverPackageItem.PackageName -match "^.*Windows.*(?(10)).*") { 1210 | $DriverPackageDetails.OSName = -join@("Windows ", $Matches.OSName) 1211 | } 1212 | 1213 | # Set counters for logging output of how many matching checks was successfull 1214 | $DetectionCounter = 0 1215 | $DetectionMethodsCount = 2 1216 | 1217 | Write-CMLogEntry -Value "[DriverPackageFallback:$($DriverPackageItem.PackageID)]: Processing fallback driver package with $($DetectionMethodsCount) detection methods: $($DriverPackageItem.PackageName)" -Severity 1 1218 | 1219 | # Attempt to match against OS name 1220 | $OSNameDetectionResult = Confirm-OSName -DriverPackageInput $DriverPackageDetails.OSName -OSImageData $OSImageData 1221 | if ($OSNameDetectionResult -eq $true) { 1222 | # Increase detection counter since OS name detection was successful 1223 | $DetectionCounter++ 1224 | 1225 | $OSArchitectureDetectionResult = Confirm-Architecture -DriverPackageInput $DriverPackageDetails.Architecture -OSImageData $OSImageData 1226 | if ($OSArchitectureDetectionResult -eq $true) { 1227 | # Increase detection counter since OS architecture detection was successful 1228 | $DetectionCounter++ 1229 | 1230 | # Match found for all critiera including OS version 1231 | Write-CMLogEntry -Value "[DriverPackageFallback:$($DriverPackageItem.PackageID)]: Fallback driver package was created on: $($DriverPackageDetails.DateCreated)" -Severity 1 1232 | Write-CMLogEntry -Value "[DriverPackageFallback:$($DriverPackageItem.PackageID)]: Match found for fallback driver package with $($DetectionCounter)/$($DetectionMethodsCount) checks, adding to list for post-processing of matched fallback driver packages" -Severity 1 1233 | 1234 | # Add custom driver package details object to list of fallback driver packages for post-processing 1235 | $DriverPackageList.Add($DriverPackageDetails) | Out-Null 1236 | } 1237 | } 1238 | } 1239 | } 1240 | else { 1241 | Write-CMLogEntry -Value " - Retrieved a total of '0' fallback driver packages from web service matching operational mode: $($OperationalMode)" -Severity 3 1242 | 1243 | # Throw terminating error 1244 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1245 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1246 | } 1247 | } 1248 | catch [System.Exception] { 1249 | Write-CMLogEntry -Value " - An error occurred while calling ConfigMgr WebService for a list of available fallback driver packages. Error message: $($_.Exception.Message)" -Severity 3 1250 | 1251 | # Throw terminating error 1252 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1253 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1254 | } 1255 | } 1256 | else { 1257 | Write-CMLogEntry -Value " - Driver fallback process will not continue since driver packages matching computer model detection logic of '$($ComputerDetectionMethod)' was found" -Severity 1 1258 | $Script:SkipFallbackDriverPackageValidation = $true 1259 | } 1260 | } 1261 | 1262 | function Confirm-OSVersion { 1263 | param( 1264 | [parameter(Mandatory = $true, HelpMessage = "Specify the OS version value from the driver package object.")] 1265 | [ValidateNotNullOrEmpty()] 1266 | [string]$DriverPackageInput, 1267 | 1268 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer data object.")] 1269 | [ValidateNotNullOrEmpty()] 1270 | [PSCustomObject]$OSImageData, 1271 | 1272 | [parameter(Mandatory = $false, HelpMessage = "Set to True to check for drivers packages that matches earlier versions of Windows than what's detected from web service call.")] 1273 | [ValidateNotNullOrEmpty()] 1274 | [bool]$OSVersionFallback = $false 1275 | ) 1276 | if ($OSVersionFallback -eq $true) { 1277 | if ([int]$DriverPackageInput -lt [int]$OSImageData.Version) { 1278 | # OS version match found where driver package input was less than input from OSImageData version 1279 | Write-CMLogEntry -Value " - Matched operating system version: $($DriverPackageInput)" -Severity 1 1280 | return $true 1281 | } 1282 | else { 1283 | # OS version match was not found 1284 | return $false 1285 | } 1286 | } 1287 | else { 1288 | if ($DriverPackageInput -like $OSImageData.Version) { 1289 | # OS version match found 1290 | Write-CMLogEntry -Value " - Matched operating system version: $($OSImageData.Version)" -Severity 1 1291 | return $true 1292 | } 1293 | else { 1294 | # OS version match was not found 1295 | return $false 1296 | } 1297 | } 1298 | } 1299 | 1300 | function Confirm-Architecture { 1301 | param( 1302 | [parameter(Mandatory = $true, HelpMessage = "Specify the Architecture value from the driver package object.")] 1303 | [ValidateNotNullOrEmpty()] 1304 | [string]$DriverPackageInput, 1305 | 1306 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer data object.")] 1307 | [ValidateNotNullOrEmpty()] 1308 | [PSCustomObject]$OSImageData 1309 | ) 1310 | if ($DriverPackageInput -like $OSImageData.Architecture) { 1311 | # OS architecture match found 1312 | Write-CMLogEntry -Value " - Matched operating system architecture: $($OSImageData.Architecture)" -Severity 1 1313 | return $true 1314 | } 1315 | else { 1316 | # OS architecture match was not found 1317 | return $false 1318 | } 1319 | } 1320 | 1321 | function Confirm-OSName { 1322 | param( 1323 | [parameter(Mandatory = $true, HelpMessage = "Specify the OS name value from the driver package object.")] 1324 | [ValidateNotNullOrEmpty()] 1325 | [string]$DriverPackageInput, 1326 | 1327 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer data object.")] 1328 | [ValidateNotNullOrEmpty()] 1329 | [PSCustomObject]$OSImageData 1330 | ) 1331 | if ($DriverPackageInput -like $OSImageData.Name) { 1332 | # OS name match found 1333 | Write-CMLogEntry -Value " - Matched operating system name: $($OSImageData.Name)" -Severity 1 1334 | return $true 1335 | } 1336 | else { 1337 | # OS name match was not found 1338 | return $false 1339 | } 1340 | } 1341 | 1342 | function Confirm-ComputerModel { 1343 | param( 1344 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer model value from the driver package object.")] 1345 | [ValidateNotNullOrEmpty()] 1346 | [string]$DriverPackageInput, 1347 | 1348 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer data object.")] 1349 | [ValidateNotNullOrEmpty()] 1350 | [PSCustomObject]$ComputerData 1351 | ) 1352 | # Construct custom object for return value 1353 | $ModelDetectionResult = [PSCustomObject]@{ 1354 | Detected = $null 1355 | } 1356 | 1357 | if ($DriverPackageInput -like $ComputerData.Model) { 1358 | # Computer model match found 1359 | Write-CMLogEntry -Value " - Matched computer model: $($ComputerData.Model)" -Severity 1 1360 | 1361 | # Set properties for custom object for return value 1362 | $ModelDetectionResult.Detected = $true 1363 | 1364 | return $ModelDetectionResult 1365 | } 1366 | else { 1367 | # Computer model match was not found 1368 | # Set properties for custom object for return value 1369 | $ModelDetectionResult.Detected = $false 1370 | 1371 | return $ModelDetectionResult 1372 | } 1373 | } 1374 | 1375 | function Confirm-SystemSKU { 1376 | param( 1377 | [parameter(Mandatory = $true, HelpMessage = "Specify the SystemSKU value from the driver package object.")] 1378 | [ValidateNotNullOrEmpty()] 1379 | [string]$DriverPackageInput, 1380 | 1381 | [parameter(Mandatory = $true, HelpMessage = "Specify the computer data object.")] 1382 | [ValidateNotNullOrEmpty()] 1383 | [PSCustomObject]$ComputerData 1384 | ) 1385 | 1386 | # Handle multiple SystemSKU's from driver package input and determine the proper delimiter 1387 | if ($DriverPackageInput -match ",") { 1388 | $SystemSKUDelimiter = "," 1389 | } 1390 | if ($DriverPackageInput -match ";") { 1391 | $SystemSKUDelimiter = ";" 1392 | } 1393 | 1394 | # Remove any space characters from driver package input data, replace them with a comma instead and ensure there's no duplicate entries 1395 | $DriverPackageInputArray = $DriverPackageInput.Replace(" ", ",").Split($SystemSKUDelimiter) | Select-Object -Unique 1396 | 1397 | # Construct custom object for return value 1398 | $SystemSKUDetectionResult = [PSCustomObject]@{ 1399 | Detected = $null 1400 | SystemSKUValue = $null 1401 | } 1402 | 1403 | # Attempt to determine if the driver package input matches with the computer data input and account for multiple SystemSKU's by separating them with the detected delimiter 1404 | if (-not([string]::IsNullOrEmpty($SystemSKUDelimiter))) { 1405 | # Construct table for keeping track of matched SystemSKU items 1406 | $SystemSKUTable = @{} 1407 | 1408 | # Attempt to match for each SystemSKU item based on computer data input 1409 | foreach ($SystemSKUItem in $DriverPackageInputArray) { 1410 | if ($ComputerData.SystemSKU -match $SystemSKUItem) { 1411 | # Add key value pair with match success 1412 | $SystemSKUTable.Add($SystemSKUItem, $true) 1413 | 1414 | # Set custom object property with SystemSKU value that was matched on the detection result object 1415 | $SystemSKUDetectionResult.SystemSKUValue = $SystemSKUItem 1416 | } 1417 | else { 1418 | # Add key value pair with match failure 1419 | $SystemSKUTable.Add($SystemSKUItem, $false) 1420 | } 1421 | } 1422 | 1423 | # Check if table contains a matched SystemSKU 1424 | if ($SystemSKUTable.Values -contains $true) { 1425 | # SystemSKU match found based upon multiple items detected in computer data input 1426 | Write-CMLogEntry -Value " - Matched SystemSKU: $($ComputerData.SystemSKU)" -Severity 1 1427 | 1428 | # Set custom object property that SystemSKU value that was matched on the detection result object 1429 | $SystemSKUDetectionResult.Detected = $true 1430 | 1431 | return $SystemSKUDetectionResult 1432 | } 1433 | else { 1434 | # SystemSKU match was not found based upon multiple items detected in computer data input 1435 | # Set properties for custom object for return value 1436 | $SystemSKUDetectionResult.SystemSKUValue = "" 1437 | $SystemSKUDetectionResult.Detected = $false 1438 | 1439 | return $SystemSKUDetectionResult 1440 | } 1441 | } 1442 | elseif ($DriverPackageInput -match $ComputerData.SystemSKU) { 1443 | # SystemSKU match found based upon single item detected in computer data input 1444 | Write-CMLogEntry -Value " - Matched SystemSKU: $($ComputerData.SystemSKU)" -Severity 1 1445 | 1446 | # Set properties for custom object for return value 1447 | $SystemSKUDetectionResult.SystemSKUValue = $ComputerData.SystemSKU 1448 | $SystemSKUDetectionResult.Detected = $true 1449 | 1450 | return $SystemSKUDetectionResult 1451 | } 1452 | elseif ((-not([string]::IsNullOrEmpty($ComputerData.FallbackSKU))) -and ($DriverPackageInput -match $ComputerData.FallbackSKU)) { 1453 | # SystemSKU match found using FallbackSKU value using detection method OEMString, this should only be valid for Dell 1454 | Write-CMLogEntry -Value " - Matched SystemSKU: $($ComputerData.FallbackSKU)" -Severity 1 1455 | 1456 | # Set properties for custom object for return value 1457 | $SystemSKUDetectionResult.SystemSKUValue = $ComputerData.FallbackSKU 1458 | $SystemSKUDetectionResult.Detected = $true 1459 | 1460 | return $SystemSKUDetectionResult 1461 | } 1462 | else { 1463 | # None of the above methods worked to match SystemSKU from driver package input with computer data input 1464 | # Set properties for custom object for return value 1465 | $SystemSKUDetectionResult.SystemSKUValue = "" 1466 | $SystemSKUDetectionResult.Detected = $false 1467 | 1468 | return $SystemSKUDetectionResult 1469 | } 1470 | } 1471 | 1472 | function Confirm-DriverPackageList { 1473 | switch ($DriverPackageList.Count) { 1474 | 0 { 1475 | Write-CMLogEntry -Value " - Amount of driver packages detected by validation process: $($DriverPackageList.Count)" -Severity 2 1476 | 1477 | if ($Script:PSBoundParameters["OSVersionFallback"]) { 1478 | Write-CMLogEntry -Value " - Validation process detected empty list of matched driver packages, however OSVersionFallback switch was passed on the command line" -Severity 2 1479 | Write-CMLogEntry -Value " - Starting re-matching process of driver packages for older Windows versions" -Severity 1 1480 | 1481 | # Attempt to match all drivers packages again but this time where OSVersion from driver packages is lower than what's detected from web service call 1482 | Write-CMLogEntry -Value "[DriverPackageFallback]: Starting driver package OS version fallback matching phase" -Severity 1 1483 | Confirm-DriverPackage -ComputerData $ComputerData -OSImageData $OSImageDetails -DriverPackage $DriverPackages -OSVersionFallback $true 1484 | 1485 | if ($DriverPackageList.Count -ge 1) { 1486 | # Sort driver packages descending based on OSVersion, DateCreated properties and select the most recently created one 1487 | $Script:DriverPackageList = $DriverPackageList | Sort-Object -Property OSVersion, DateCreated -Descending | Select-Object -First 1 1488 | 1489 | Write-CMLogEntry -Value " - Selected driver package '$($DriverPackageList[0].PackageID)' with name: $($DriverPackageList[0].PackageName)" -Severity 1 1490 | Write-CMLogEntry -Value " - Successfully completed validation after fallback process and detected a single driver package, script execution is allowed to continue" -Severity 1 1491 | Write-CMLogEntry -Value "[DriverPackageFallback]: Completed driver package OS version fallback matching phase" -Severity 1 1492 | } 1493 | else { 1494 | if ($Script:PSBoundParameters["UseDriverFallback"]) { 1495 | Write-CMLogEntry -Value " - Validation process detected an empty list of matched driver packages, however the UseDriverFallback parameter was specified" -Severity 1 1496 | } 1497 | else { 1498 | Write-CMLogEntry -Value " - Validation after fallback process failed with empty list of matched driver packages, script execution will be terminated" -Severity 3 1499 | 1500 | # Throw terminating error 1501 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1502 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1503 | } 1504 | } 1505 | } 1506 | else { 1507 | if ($Script:PSBoundParameters["UseDriverFallback"]) { 1508 | Write-CMLogEntry -Value " - Validation process detected an empty list of matched driver packages, however the UseDriverFallback parameter was specified" -Severity 1 1509 | } 1510 | else { 1511 | Write-CMLogEntry -Value " - Validation failed with empty list of matched driver packages, script execution will be terminated" -Severity 3 1512 | 1513 | # Throw terminating error 1514 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1515 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1516 | } 1517 | } 1518 | } 1519 | 1 { 1520 | Write-CMLogEntry -Value " - Amount of driver packages detected by validation process: $($DriverPackageList.Count)" -Severity 1 1521 | Write-CMLogEntry -Value " - Successfully completed validation with a single driver package, script execution is allowed to continue" -Severity 1 1522 | } 1523 | default { 1524 | Write-CMLogEntry -Value " - Amount of driver packages detected by validation process: $($DriverPackageList.Count)" -Severity 1 1525 | 1526 | if ($ComputerDetectionMethod -like "SystemSKU") { 1527 | if (($DriverPackageList | Where-Object { $_.SystemSKU -notlike $DriverPackageList[0].SystemSKU }) -eq $null) { 1528 | Write-CMLogEntry -Value " - NOTICE: Computer detection method is currently '$($ComputerDetectionMethod)', and multiple packages have been matched with the same SystemSKU value" -Severity 1 1529 | Write-CMLogEntry -Value " - NOTICE: This is a supported scenario where the vendor use the same driver package for multiple models" -Severity 1 1530 | Write-CMLogEntry -Value " - NOTICE: Validation process will automatically choose the most recently created driver package, even if it means that the computer model names may not match" -Severity 1 1531 | 1532 | # Sort driver packages descending based on DateCreated property and select the most recently created one 1533 | $Script:DriverPackageList = $DriverPackageList | Sort-Object -Property DateCreated -Descending | Select-Object -First 1 1534 | 1535 | Write-CMLogEntry -Value " - Selected driver package '$($DriverPackageList[0].PackageID)' with name: $($DriverPackageList[0].PackageName)" -Severity 1 1536 | Write-CMLogEntry -Value " - Successfully completed validation with multiple detected driver packages, script execution is allowed to continue" -Severity 1 1537 | } 1538 | else { 1539 | # This should not be possible, but added to handle output to log file for user to reach out to the developers 1540 | Write-CMLogEntry -Value " - WARNING: Computer detection method is currently '$($ComputerDetectionMethod)', and multiple packages have been matched but with different SystemSKU value" -Severity 2 1541 | Write-CMLogEntry -Value " - WARNING: This should not be a possible scenario, please reach out to the developers of this script" -Severity 2 1542 | 1543 | # Throw terminating error 1544 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1545 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1546 | } 1547 | } 1548 | else { 1549 | Write-CMLogEntry -Value " - NOTICE: Computer detection method is currently '$($ComputerDetectionMethod)', and multiple packages have been matched with the same Model value" -Severity 1 1550 | Write-CMLogEntry -Value " - NOTICE: Validation process will automatically choose the most recently created driver package by the DateCreated property" -Severity 1 1551 | 1552 | # Sort driver packages descending based on DateCreated property and select the most recently created one 1553 | $Script:DriverPackageList = $DriverPackageList | Sort-Object -Property DateCreated -Descending | Select-Object -First 1 1554 | Write-CMLogEntry -Value " - Selected driver package '$($DriverPackageList[0].PackageID)' with name: $($DriverPackageList[0].PackageName)" -Severity 1 1555 | } 1556 | } 1557 | } 1558 | } 1559 | 1560 | function Confirm-FallbackDriverPackageList { 1561 | if ($Script:SkipFallbackDriverPackageValidation -eq $false) { 1562 | switch ($DriverPackageList.Count) { 1563 | 0 { 1564 | Write-CMLogEntry -Value " - Amount of fallback driver packages detected by validation process: $($DriverPackageList.Count)" -Severity 3 1565 | Write-CMLogEntry -Value " - Validation failed with empty list of matched fallback driver packages, script execution will be terminated" -Severity 3 1566 | 1567 | # Throw terminating error 1568 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1569 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1570 | } 1571 | 1 { 1572 | Write-CMLogEntry -Value " - Amount of fallback driver packages detected by validation process: $($DriverPackageList.Count)" -Severity 1 1573 | Write-CMLogEntry -Value " - Successfully completed validation with a single driver package, script execution is allowed to continue" -Severity 1 1574 | } 1575 | default { 1576 | Write-CMLogEntry -Value " - Amount of fallback driver packages detected by validation process: $($DriverPackageList.Count)" -Severity 1 1577 | Write-CMLogEntry -Value " - NOTICE: Multiple fallback driver packages have been matched, validation process will automatically choose the most recently created fallback driver package by the DateCreated property" -Severity 1 1578 | 1579 | # Sort driver packages descending based on DateCreated property and select the most recently created one 1580 | $Script:DriverPackageList = $DriverPackageList | Sort-Object -Property DateCreated -Descending | Select-Object -First 1 1581 | Write-CMLogEntry -Value " - Selected fallback driver package '$($DriverPackageList[0].PackageID)' with name: $($DriverPackageList[0].PackageName)" -Severity 1 1582 | } 1583 | } 1584 | } 1585 | else { 1586 | Write-CMLogEntry -Value " - Fallback driver package validation process is being skipped since 'SkipFallbackDriverPackageValidation' variable was set to True" -Severity 1 1587 | } 1588 | } 1589 | 1590 | function Invoke-DownloadDriverPackageContent { 1591 | Write-CMLogEntry -Value " - Attempting to download content files for matched driver package: $($DriverPackageList[0].PackageName)" -Severity 1 1592 | 1593 | # Depending on current deployment type, attempt to download driver package content 1594 | switch ($DeploymentType) { 1595 | "PreCache" { 1596 | $DownloadInvocation = Invoke-CMDownloadContent -PackageID $DriverPackageList[0].PackageID -DestinationLocationType "CCMCache" -DestinationVariableName "OSDDriverPackage" 1597 | } 1598 | default { 1599 | $DownloadInvocation = Invoke-CMDownloadContent -PackageID $DriverPackageList[0].PackageID -DestinationLocationType "Custom" -DestinationVariableName "OSDDriverPackage" -CustomLocationPath "%_SMSTSMDataPath%\DriverPackage" 1600 | } 1601 | } 1602 | 1603 | # If download process was successful, meaning exit code from above function was 0, return the download location path 1604 | if ($DownloadInvocation -eq 0) { 1605 | $DriverPackageContentLocation = $TSEnvironment.Value("OSDDriverPackage01") 1606 | Write-CMLogEntry -Value " - Driver package content files was successfully downloaded to: $($DriverPackageContentLocation)" -Severity 1 1607 | 1608 | # Handle return value for successful download of driver package content files 1609 | return $DriverPackageContentLocation 1610 | } 1611 | else { 1612 | Write-CMLogEntry -Value " - Driver package content download process returned an unhandled exit code: $($DownloadInvocation)" -Severity 3 1613 | 1614 | # Throw terminating error 1615 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1616 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1617 | } 1618 | } 1619 | 1620 | function Install-DriverPackageContent { 1621 | param( 1622 | [parameter(Mandatory = $true, HelpMessage = "Specify the full local path to the downloaded driver package content.")] 1623 | [ValidateNotNullOrEmpty()] 1624 | [string]$ContentLocation 1625 | ) 1626 | # Detect if downloaded driver package content is a compressed archive that needs to be extracted before drivers are installed 1627 | $DriverPackageCompressedFile = Get-ChildItem -Path $ContentLocation -Filter "DriverPackage.*" | Select-Object -ExpandProperty Name 1628 | if (-not([string]::IsNullOrEmpty($DriverPackageCompressedFile))) { 1629 | Write-CMLogEntry -Value " - Downloaded driver package content contains a compressed archive with driver content" -Severity 1 1630 | 1631 | # Detect if compressed format is Windows native zip or 7-Zip exe 1632 | switch -wildcard ($DriverPackageCompressedFile) { 1633 | "*.zip" { 1634 | try { 1635 | # Expand compressed driver package archive file 1636 | Write-CMLogEntry -Value " - Attempting to decompress driver package content file: $($DriverPackageCompressedContent)" -Severity 1 1637 | Write-CMLogEntry -Value " - Decompression destination: $($ContentLocation)" -Severity 1 1638 | Expand-Archive -Path $DriverPackageCompressedContent -DestinationPath $ContentLocation -Force -ErrorAction Stop 1639 | Write-CMLogEntry -Value " - Successfully decompressed driver package content file" -Severity 1 1640 | } 1641 | catch [System.Exception] { 1642 | Write-CMLogEntry -Value " - Failed to decompress driver package content file. Error message: $($_.Exception.Message)" -Severity 3 1643 | 1644 | # Throw terminating error 1645 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1646 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1647 | } 1648 | 1649 | try { 1650 | # Remove compressed driver package archive file 1651 | if (Test-Path -Path $DriverPackageCompressedContent) { 1652 | Remove-Item -Path $DriverPackageCompressedContent -Force -ErrorAction Stop 1653 | } 1654 | } 1655 | catch [System.Exception] { 1656 | Write-CMLogEntry -Value " - Failed to remove compressed driver package content file after decompression. Error message: $($_.Exception.Message)" -Severity 3 1657 | 1658 | # Throw terminating error 1659 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1660 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1661 | } 1662 | } 1663 | "*.exe" { 1664 | Write-CMLogEntry -Value " - Attempting to decompress 7-Zip driver package content file: $($DriverPackageCompressedFile)" -Severity 1 1665 | Write-CMLogEntry -Value " - Decompression destination: $($ContentLocation)" -Severity 1 1666 | $ReturnCode = Invoke-Executable -FilePath (Join-Path -Path $ContentLocation -ChildPath $DriverPackageCompressedFile) -Arguments "-o`"$($ContentLocation)`" -y" 1667 | 1668 | # Validate 7-Zip driver extraction 1669 | if ($ReturnCode -eq 0) { 1670 | Write-CMLogEntry -Value " - Successfully decompressed 7-Zip driver package content file" -Severity 1 1671 | } 1672 | else { 1673 | Write-CMLogEntry -Value " - An error occurred while decompressing 7-Zip driver package content file. Return code from self-extracing executable: $($ReturnCode)" -Severity 3 1674 | 1675 | # Throw terminating error 1676 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1677 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1678 | } 1679 | } 1680 | } 1681 | } 1682 | 1683 | switch ($DeploymentType) { 1684 | "BareMetal" { 1685 | # Apply drivers recursively from downloaded driver package location 1686 | Write-CMLogEntry -Value " - Attempting to apply drivers using dism.exe located in: $($ContentLocation)" -Severity 1 1687 | 1688 | # Determine driver injection method from parameter input 1689 | switch ($DriverInstallMode) { 1690 | "Single" { 1691 | try { 1692 | Write-CMLogEntry -Value " - DriverInstallMode is currently set to: $($DriverInstallMode)" -Severity 1 1693 | 1694 | # Get driver full path and install each driver seperately 1695 | $DriverINFs = Get-ChildItem -Path $ContentLocation -Recurse -Filter "*.inf" -ErrorAction Stop | Select-Object -Property FullName, Name 1696 | if ($DriverINFs -ne $null) { 1697 | foreach ($DriverINF in $DriverINFs) { 1698 | # Install specific driver 1699 | Write-CMLogEntry -Value " - Attempting to install driver: $($DriverINF.FullName)" -Severity 1 1700 | $ApplyDriverInvocation = Invoke-Executable -FilePath "dism.exe" -Arguments "/Image:$($TSEnvironment.Value('OSDTargetSystemDrive'))\ /Add-Driver /Driver:`"$($DriverINF.FullName)`"" 1701 | 1702 | # Validate driver injection 1703 | if ($ApplyDriverInvocation -eq 0) { 1704 | Write-CMLogEntry -Value " - Successfully installed driver using dism.exe" -Severity 1 1705 | } 1706 | else { 1707 | Write-CMLogEntry -Value " - An error occurred while installing driver. Continuing with warning code: $($ApplyDriverInvocation). See DISM.log for more details" -Severity 2 1708 | } 1709 | } 1710 | } 1711 | else { 1712 | Write-CMLogEntry -Value " - An error occurred while enumerating driver paths, downloaded driver package does not contain any INF files" -Severity 3 1713 | 1714 | # Throw terminating error 1715 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1716 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1717 | } 1718 | } 1719 | catch [System.Exception] { 1720 | Write-CMLogEntry -Value " - An error occurred while installing drivers. See DISM.log for more details" -Severity 2 1721 | 1722 | # Throw terminating error 1723 | $ErrorRecord = New-TerminatingErrorRecord -Message ([string]::Empty) 1724 | $PSCmdlet.ThrowTerminatingError($ErrorRecord) 1725 | } 1726 | } 1727 | "Recurse" { 1728 | Write-CMLogEntry -Value " - DriverInstallMode is currently set to: $($DriverInstallMode)" -Severity 1 1729 | 1730 | # Apply drivers recursively 1731 | $ApplyDriverInvocation = Invoke-Executable -FilePath "dism.exe" -Arguments "/Image:$($TSEnvironment.Value('OSDTargetSystemDrive'))\ /Add-Driver /Driver:$($ContentLocation) /Recurse" 1732 | 1733 | # Validate driver injection 1734 | if ($ApplyDriverInvocation -eq 0) { 1735 | Write-CMLogEntry -Value " - Successfully installed drivers recursively in driver package content location using dism.exe" -Severity 1 1736 | } 1737 | else { 1738 | Write-CMLogEntry -Value " - An error occurred while installing drivers. Continuing with warning code: $($ApplyDriverInvocation). See DISM.log for more details" -Severity 2 1739 | } 1740 | } 1741 | } 1742 | } 1743 | "OSUpgrade" { 1744 | # For OSUpgrade, don't attempt to install drivers as this is handled by setup.exe when used together with OSDUpgradeStagedContent 1745 | Write-CMLogEntry -Value " - Driver package content downloaded successfully and located in: $($ContentLocation)" -Severity 1 1746 | 1747 | # Set OSDUpgradeStagedContent task sequence variable 1748 | Write-CMLogEntry -Value " - Attempting to set OSDUpgradeStagedContent task sequence variable with value: $($ContentLocation)" -Severity 1 1749 | $TSEnvironment.Value("OSDUpgradeStagedContent") = "$($ContentLocation)" 1750 | Write-CMLogEntry -Value " - Successfully completed driver package staging process" -Severity 1 1751 | } 1752 | "DriverUpdate" { 1753 | # Apply drivers recursively from downloaded driver package location 1754 | Write-CMLogEntry -Value " - Driver package content downloaded successfully, attempting to apply drivers using pnputil.exe located in: $($ContentLocation)" -Severity 1 1755 | $ApplyDriverInvocation = Invoke-Executable -FilePath "powershell.exe" -Arguments "pnputil /add-driver $(Join-Path -Path $ContentLocation -ChildPath '*.inf') /subdirs /install | Out-File -FilePath (Join-Path -Path $($LogsDirectory) -ChildPath 'Install-Drivers.txt') -Force" 1756 | Write-CMLogEntry -Value " - Successfully installed drivers" -Severity 1 1757 | } 1758 | "PreCache" { 1759 | # Driver package content downloaded successfully, log output and exit script 1760 | Write-CMLogEntry -Value " - Driver package content successfully downloaded and pre-cached to: $($ContentLocation)" -Severity 1 1761 | } 1762 | } 1763 | } 1764 | 1765 | ############ 1766 | # NOTES 1767 | # - Add support for HP's driver software like hotkey etc 1768 | 1769 | Write-CMLogEntry -Value "[ApplyDriverPackage]: Apply Driver Package process initiated" -Severity 1 1770 | if ($PSCmdLet.ParameterSetName -like "Debug") { 1771 | Write-CMLogEntry -Value " - Apply driver package process initiated in debug mode" -Severity 1 1772 | } 1773 | Write-CMLogEntry -Value " - Apply driver package deployment type: $($DeploymentType)" -Severity 1 1774 | Write-CMLogEntry -Value " - Apply driver package operational mode: $($OperationalMode)" -Severity 1 1775 | 1776 | # Set script error preference variable 1777 | $ErrorActionPreference = "Stop" 1778 | 1779 | # Construct array list for matched drivers packages 1780 | $DriverPackageList = New-Object -TypeName "System.Collections.ArrayList" 1781 | 1782 | # Set initial values that control whether some functions should be executed or not 1783 | $SkipFallbackDriverPackageValidation = $false 1784 | 1785 | try { 1786 | Write-CMLogEntry -Value "[PrerequisiteChecker]: Starting environment prerequisite checker" -Severity 1 1787 | 1788 | # Determine if running on supported computer system type 1789 | Get-ComputerSystemType 1790 | 1791 | # Determine if running on supported operating system version 1792 | Get-OperatingSystemVersion 1793 | 1794 | # Determine computer manufacturer, model, SystemSKU and FallbackSKU 1795 | $ComputerData = Get-ComputerData 1796 | 1797 | # Validate required computer details have successfully been gathered from WMI 1798 | Test-ComputerDetails -InputObject $ComputerData 1799 | 1800 | # Determine the computer detection method to be used for matching against driver packages 1801 | $ComputerDetectionMethod = Set-ComputerDetectionMethod 1802 | 1803 | Write-CMLogEntry -Value "[PrerequisiteChecker]: Completed environment prerequisite checker" -Severity 1 1804 | Write-CMLogEntry -Value "[WebService]: Starting ConfigMgr WebService phase" -Severity 1 1805 | 1806 | # Connect and establish connection to ConfigMgr WebService 1807 | $WebService = Connect-WebService 1808 | 1809 | # Retrieve available driver packages from web service 1810 | $DriverPackages = Get-DriverPackages -WebService $WebService 1811 | 1812 | # Determine the OS image version and architecture values based upon deployment type 1813 | # Detection are performed according to the following rules: 1814 | # - BareMetal: From the Operating System Image defined in the running task sequence 1815 | # - OSUpgrade: From the Operating System Upgrade Package defined in the running task sequence 1816 | # - DriverUpdate: From the running operating system 1817 | # OS image version can be overriden by using the TargetOSVersion parameter for BareMetal and OSUpgrade deployment types and is handled within the functions dependant to the executed function below 1818 | $OSImageDetails = Get-OSImageDetails 1819 | 1820 | Write-CMLogEntry -Value "[WebService]: Completed ConfigMgr WebService phase" -Severity 1 1821 | Write-CMLogEntry -Value "[DriverPackage]: Starting driver package matching phase" -Severity 1 1822 | 1823 | # Match detected driver packages from web service call with computer details and OS image details gathered previously 1824 | Confirm-DriverPackage -ComputerData $ComputerData -OSImageData $OSImageDetails -DriverPackage $DriverPackages 1825 | 1826 | Write-CMLogEntry -Value "[DriverPackage]: Completed driver package matching phase" -Severity 1 1827 | Write-CMLogEntry -Value "[DriverPackageValidation]: Starting driver package validation phase" -Severity 1 1828 | 1829 | # Validate that at least one driver package was matched against computer data 1830 | # Check if multiple driver packages were detected and ensure the most recent one by sorting after the DateCreated property from original web service call 1831 | Confirm-DriverPackageList 1832 | 1833 | Write-CMLogEntry -Value "[DriverPackageValidation]: Completed driver package validation phase" -Severity 1 1834 | 1835 | # Handle UseDriverFallback parameter if it was passed on the command line and attempt to detect if there's any available fallback packages 1836 | # This function will only run in the case that the parameter UseDriverFallback was specified and if the $DriverPackageList is empty at the point of execution 1837 | if ($PSBoundParameters["UseDriverFallback"]) { 1838 | Write-CMLogEntry -Value "[DriverPackageFallback]: Starting fallback driver package detection phase" -Severity 1 1839 | 1840 | # Match detected fallback driver packages from web service call with computer details and OS image details 1841 | Confirm-FallbackDriverPackage -ComputerData $ComputerData -OSImageData $OSImageDetails -WebService $WebService 1842 | 1843 | Write-CMLogEntry -Value "[DriverPackageFallback]: Completed fallback driver package detection phase" -Severity 1 1844 | Write-CMLogEntry -Value "[DriverPackageFallbackValidation]: Starting fallback driver package validation phase" -Severity 1 1845 | 1846 | # Validate that at least one fallback driver package was matched against computer data 1847 | Confirm-FallbackDriverPackageList 1848 | 1849 | Write-CMLogEntry -Value "[DriverPackageFallbackValidation]: Completed fallback driver package validation phase" -Severity 1 1850 | } 1851 | 1852 | # At this point, the code below here is not allowed to be executed in debug mode, as it requires access to the Microsoft.SMS.TSEnvironment COM object 1853 | if ($PSCmdLet.ParameterSetName -like "Execute") { 1854 | Write-CMLogEntry -Value "[DriverPackageDownload]: Starting driver package download phase" -Severity 1 1855 | 1856 | # Attempt to download the matched driver package content files from distribution point 1857 | $DriverPackageContentLocation = Invoke-DownloadDriverPackageContent 1858 | 1859 | Write-CMLogEntry -Value "[DriverPackageDownload]: Completed driver package download phase" -Severity 1 1860 | Write-CMLogEntry -Value "[DriverPackageInstall]: Starting driver package install phase" -Severity 1 1861 | 1862 | # Depending on deployment type, take action accordingly when applying the driver package files 1863 | Install-DriverPackageContent -ContentLocation $DriverPackageContentLocation 1864 | 1865 | Write-CMLogEntry -Value "[DriverPackageInstall]: Completed driver package install phase" -Severity 1 1866 | } 1867 | else { 1868 | Write-CMLogEntry -Value " - Script has successfully completed debug mode" -Severity 1 1869 | } 1870 | } 1871 | catch [System.Exception] { 1872 | Write-CMLogEntry -Value "[ApplyDriverPackage]: Apply Driver Package process failed, please refer to previous error or warning messages" -Severity 3 1873 | 1874 | # Main try-catch block was triggered, this should cause the script to fail with exit code 1 1875 | exit 1 1876 | } 1877 | } 1878 | End { 1879 | if ($PSCmdLet.ParameterSetName -eq "Execute") { 1880 | # Reset OSDDownloadContent.exe dependant variables for further use of the task sequence step 1881 | Invoke-CMResetDownloadContentVariables 1882 | } 1883 | 1884 | # Write final output to log file 1885 | Write-CMLogEntry -Value "[ApplyDriverPackage]: Completed Apply Driver Package process" -Severity 1 1886 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MSEndpointMgr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern Driver Management 2 | 3 | For implementation instructions, please go to https://www.msendpointmgr.com/modern-driver-management --------------------------------------------------------------------------------