Runtime/OSDStageDriver.ps1
|
function Get-OSDStageDriverInfFiles { [CmdletBinding()] param([Parameter(Mandatory)][string]$PayloadRoot) $root=Get-Item -LiteralPath $PayloadRoot -Force -ErrorAction Stop if (-not $root.PSIsContainer -or ($root.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'Driver payload must be a directory without reparse points.' } $pending=[Collections.Generic.Stack[string]]::new() $pending.Push($root.FullName) $infs=[Collections.Generic.List[IO.FileInfo]]::new() while ($pending.Count -gt 0) { foreach ($entry in @(Get-ChildItem -LiteralPath $pending.Pop() -Force -ErrorAction Stop)) { if ($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Driver payloads cannot contain reparse points.' } if ($entry.PSIsContainer) { $pending.Push($entry.FullName) } elseif ($entry.Extension -ieq '.inf') { $infs.Add($entry) } } } if ($infs.Count -eq 0) { throw 'Driver packages require at least one .inf file under Package/.' } $infs | Sort-Object FullName } function Invoke-OSDStagePnPUtil { [CmdletBinding()] param( [Parameter(Mandatory)][string]$InfPath, [switch]$Install ) if ($InfPath.Contains('"')) { throw 'Invalid quote in INF path.' } $systemDirectory=if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) { 'Sysnative' } else { 'System32' } $executable=Join-Path $env:SystemRoot ($systemDirectory+'\pnputil.exe') if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) { throw 'Windows PnPUtil was not found.' } $arguments='/add-driver "'+$InfPath+'"' if ($Install) { $arguments+=' /install' } $start=[Diagnostics.ProcessStartInfo]::new() $start.FileName=$executable $start.Arguments=$arguments $start.WorkingDirectory=Split-Path $InfPath -Parent $start.UseShellExecute=$false $start.CreateNoWindow=$true $start.RedirectStandardOutput=$true $start.RedirectStandardError=$true $encoding=[Text.Encoding]::GetEncoding([Globalization.CultureInfo]::CurrentCulture.TextInfo.OEMCodePage) $start.StandardOutputEncoding=$encoding $start.StandardErrorEncoding=$encoding $process=[Diagnostics.Process]::new() $process.StartInfo=$start try { if (-not $process.Start()) { throw 'PnPUtil did not start.' } # Drain both streams concurrently so verbose vendor output cannot block # the child process. Do not interpret localized output as machine state. $stdout=$process.StandardOutput.ReadToEndAsync() $stderr=$process.StandardError.ReadToEndAsync() $process.WaitForExit() [pscustomobject]@{ Command=('pnputil.exe '+$arguments) ExitCode=$process.ExitCode StandardOutput=$stdout.GetAwaiter().GetResult() StandardError=$stderr.GetAwaiter().GetResult() } } finally { $process.Dispose() } } function Invoke-OSDStageDriverPackage { [CmdletBinding()] param( [Parameter(Mandatory)][string]$PayloadRoot, [Parameter(Mandatory)][string]$PackageId, [Parameter(Mandatory)][string]$DeploymentId, [Parameter(Mandatory)][string]$ResultPath ) $items=[Collections.Generic.List[object]]::new() $failure=$null try { # This helper is only used by the full-Windows SetupComplete runner. if (Test-Path -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\MiniNT') { throw 'Driver import must run in full Windows during SetupComplete, not in WinPE.' } $infs=@(Get-OSDStageDriverInfFiles -PayloadRoot $PayloadRoot) $resolvedRoot=(Get-Item -LiteralPath $PayloadRoot).FullName.TrimEnd('\','/') foreach ($inf in $infs) { $item=[ordered]@{ Inf=$inf.FullName.Substring($resolvedRoot.Length+1) Status='Failed' DriverStoreStatus='NotAttempted' DeviceInstallStatus='NotAttempted' DeviceBindingConfirmed=$null RestartNeeded=$false StoreCommand=$null InstallCommand=$null Error=$null } try { # Separate store and device operations: exit 259 from /install # says no device was updated, not whether store import succeeded. $item.DriverStoreStatus='Attempting' $stage=Invoke-OSDStagePnPUtil -InfPath $inf.FullName $item.StoreCommand=$stage $item.RestartNeeded=($stage.ExitCode -eq 3010) if ($stage.ExitCode -notin @(0,3010)) { $item.DriverStoreStatus='Failed' throw "Driver Store import failed with exit code $($stage.ExitCode)." } $item.DriverStoreStatus='AddedOrAlreadyPresent' $item.DeviceInstallStatus='Attempting' $install=Invoke-OSDStagePnPUtil -InfPath $inf.FullName -Install $item.InstallCommand=$install $item.RestartNeeded=($item.RestartNeeded -or $install.ExitCode -eq 3010) switch ([int]$install.ExitCode) { 0 { # Success alone does not prove a new binding to a device. $item.DeviceInstallStatus='SucceededBindingNotReported' } 259 { $item.DeviceInstallStatus='NoMatchingDeviceOrBetterDriver' $item.DeviceBindingConfirmed=$false } 3010 { $item.DeviceInstallStatus='InstalledRebootRequired' $item.DeviceBindingConfirmed=$true } default { $item.DeviceInstallStatus='Failed' throw "Device driver installation failed with exit code $($install.ExitCode)." } } $item.Status='Succeeded' } catch { if ($item.DriverStoreStatus -eq 'Attempting') { $item.DriverStoreStatus='Failed' } if ($item.DeviceInstallStatus -eq 'Attempting') { $item.DeviceInstallStatus='Failed' } $item.Error=$_.Exception.Message } $items.Add([pscustomobject]$item) } } catch { $failure=$_.Exception.Message } $failed=@($items | Where-Object Status -eq Failed).Count $restartNeeded=@($items | Where-Object RestartNeeded).Count -gt 0 $exitCode=if ($failure -or $failed) { 1 } elseif ($restartNeeded) { 3010 } else { 0 } $record=[ordered]@{ SchemaVersion=2;DeploymentId=$DeploymentId;PackageId=$PackageId Status=if ($failure -or $failed) { 'Failed' } else { 'Succeeded' } InfCount=$items.Count SuccessfulInfCount=($items.Count-$failed) FailedInfCount=$failed DriverStoreReadyCount=@($items | Where-Object DriverStoreStatus -eq AddedOrAlreadyPresent).Count NoDeviceChangeCount=@($items | Where-Object DeviceInstallStatus -eq NoMatchingDeviceOrBetterDriver).Count RestartNeeded=$restartNeeded RebootRequested=$false Items=@($items.ToArray()) Error=if ($failure) { $failure } elseif ($failed) { "$failed driver INF operation(s) failed." } else { $null } } New-Item -ItemType Directory -Path (Split-Path $ResultPath -Parent) -Force -ErrorAction Stop | Out-Null $record | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $ResultPath -Encoding UTF8 -ErrorAction Stop [pscustomobject]@{ ExitCode=$exitCode;Integrity='Sha256Validated';RestartNeeded=$restartNeeded;ResultPath=$ResultPath } } |