private/Save-WinPEDriverPackageCore.ps1
|
#Requires -PSEdition Core function Save-WinPEDriverPackageCore { <# .SYNOPSIS Selects, downloads, and optionally expands WinPE driver packages .DESCRIPTION Resolves WinPE driver packages from Get-WinPEDriverPackageCore and delegates each package to its vendor-specific save function. By default, all packages matching Name, Architecture, and Wi-Fi filters are processed without a picker. Interactive mode displays package status in Out-GridView and processes only the selected packages that require download or expansion. The function requires an elevated session except during -WhatIf processing. Download and cache changes are gated by ShouldProcess, but delegated helpers may replace or remove expanded driver content beneath the architecture-specific Boot-Assets driver directory. .PARAMETER Name Specifies one or more source names in the ByName parameter set. The argument completer excludes disabled sources and sources without a download or update URI. When omitted, all available sources are considered. .PARAMETER DriverPackage Specifies an OSDeployWinPEDriver.Package object in the ByPipeline parameter set. This parameter is mandatory, accepts pipeline input, and bypasses name filtering and the interactive picker. .PARAMETER Force Forces the delegated helper to download the package again. Additional force behavior, such as re-expansion, depends on the package type. .PARAMETER DownloadOnly Downloads the package without expanding it into the managed driver library. .PARAMETER Architecture Limits packages to amd64 or arm64. When omitted, both architectures are eligible. .PARAMETER SkipCatalogRefresh Indicates that the caller already refreshed the requested catalog entries. This parameter is available only in the ByName parameter set. .PARAMETER SkipWifiDrivers Excludes sources whose names contain wifi or wireless. This parameter is available only in the ByName parameter set. .PARAMETER Interactive Displays an Out-GridView multi-selection picker for packages that are not already complete. This parameter is available only in the ByName parameter set. .EXAMPLE PS> Save-WinPEDriverPackageCore -Name 'Dell' -Architecture amd64 -Interactive Displays matching Dell amd64 packages that require action and processes the selected packages. .INPUTS System.Management.Automation.PSCustomObject. Accepts OSDeployWinPEDriver.Package objects through DriverPackage. .OUTPUTS System.IO.FileInfo. Returns each package file produced by a delegated save function. Returns no object when no package is selected or requires action. .NOTES Author: David Segura Company: Recast Software Version: 0.1.0 Date: 2026-08-28 Requires an elevated Windows session for actual downloads. Out-GridView is required only when Interactive is specified. .LINK Update-OSDeployCoreDrivers #> [CmdletBinding( SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByName' )] [OutputType([System.IO.FileInfo])] param ( [Parameter( Position = 0, ParameterSetName = 'ByName', HelpMessage = 'Source name to download.' )] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $global:OSDeployModule.WinPEDrivers.PSObject.Properties | Where-Object { ($_.Value.UpdateUri -or $_.Value.DownloadUri) -and -not $_.Value.Disabled -and $_.Name -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name) } })] [string[]]$Name, [Parameter( Mandatory, ValueFromPipeline, ParameterSetName = 'ByPipeline' )] [PSTypeName('OSDeployWinPEDriver.Package')] [PSCustomObject]$DriverPackage, [Parameter()] [switch]$Force, [Parameter()] [switch]$DownloadOnly, [Parameter()] [ValidateSet('amd64', 'arm64')] [System.String] $Architecture, [Parameter(ParameterSetName = 'ByName')] [switch]$SkipCatalogRefresh, [Parameter(ParameterSetName = 'ByName')] [switch]$SkipWifiDrivers, [Parameter(ParameterSetName = 'ByName')] [switch]$Interactive ) begin { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Starting" if (-not $WhatIfPreference -and -not (Test-IsAdministrator)) { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( [System.UnauthorizedAccessException]::new('Update-OSDeployCoreDrivers requires an elevated (Administrator) PowerShell session to save driver content.'), 'ElevationRequired', [System.Management.Automation.ErrorCategory]::PermissionDenied, $null ) ) } } process { $packages = if ($PSCmdlet.ParameterSetName -eq 'ByName') { $candidates = if ($Name) { Get-WinPEDriverPackageCore -Name $Name -SkipCatalogRefresh:$SkipCatalogRefresh } else { Get-WinPEDriverPackageCore -SkipCatalogRefresh:$SkipCatalogRefresh } if ($SkipWifiDrivers) { $candidates = $candidates | Where-Object { $_.Name -notmatch 'wifi|wireless' } } if ($Architecture) { $candidates = @($candidates | Where-Object { [string]::IsNullOrWhiteSpace($_.Architecture) -or $_.Architecture -eq $Architecture }) } $gridItems = $candidates | ForEach-Object { $pkg = $_ $idPrefix = ($pkg.Name -split '-')[0] $downloadPath = Join-Path (Join-Path $script:OSDeployCoreDownloadsPath $idPrefix) $pkg.FileName if ($null -eq $pkg.Version) { $pkg.Version = $pkg.PackageId } $expandDir = Join-Path $script:OSDeployBootAssetsPath "winpedrivers-$($pkg.Architecture)" | Join-Path -ChildPath "$($pkg.Name)-$($pkg.Version)" [PSCustomObject]@{ Id = $pkg.Id Architecture = $pkg.Architecture ReleaseDate = $pkg.ReleaseDate Version = $pkg.Version FileName = $pkg.FileName FileSizeMB = $pkg.FileSizeMB DownloadUri = $pkg.DownloadUri ExpandedPath = $expandDir Name = "$($pkg.Name)-$($pkg.Version)" Downloaded = (Test-Path -Path $downloadPath) ? 'Yes' : 'No' Expanded = (Get-ChildItem -Path $expandDir -Recurse -File -ErrorAction SilentlyContinue | Select-Object -First 1) ? 'Yes' : 'No' } } if ($Interactive) { $upToDate = $gridItems | Where-Object { $_.Downloaded -eq 'Yes' -and $_.Expanded -eq 'Yes' } $needsAction = $gridItems | Where-Object { $_.Downloaded -ne 'Yes' -or $_.Expanded -ne 'Yes' } foreach ($item in $upToDate) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Complete: $($item.Name)" } $downloadsPath = $script:OSDeployCoreDownloadsPath if (Test-Path -Path $downloadsPath) { $downloadsSizeBytes = (Get-ChildItem -Path $downloadsPath -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum $downloadsSizeGB = [math]::Round($downloadsSizeBytes / 1GB, 1) Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] $downloadsPath is currently using $downloadsSizeGB GB" } if (-not $needsAction) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] All driver packages are downloaded, expanded, and up to date." return } # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Select Drivers in GridView to download and expand" # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Downloads are saved to $($Script:OSDeployCorePath)\cache\downloads" # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Expanded content is saved to the architecture-specific winpedrivers cache" $selected = $needsAction | Select-Object -Property Id, Name, Architecture, ReleaseDate, Version, FileSizeMB, DownloadUri | Out-GridView -Title "[$(Get-Date -Format s)] Select WinPE Drivers to add to $($Script:OSDeployCorePath)" -PassThru if (-not $selected) { Write-Warning "[$(Get-Date -Format s)] No WinPE Drivers selected." return } $selectedKeys = $selected | ForEach-Object { $_.Name } $candidates | Where-Object { "$($_.Name)-$($_.Version)" -in $selectedKeys } } else { if (-not $candidates) { Write-Warning "[$(Get-Date -Format s)] No matching WinPE Drivers found for processing." return } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing all matching packages without Out-GridView" $candidates } } else { $DriverPackage } foreach ($package in $packages) { if ([string]::IsNullOrWhiteSpace($package.DownloadUri)) { Write-Warning "[$(Get-Date -Format s)] No DownloadUri for '$($package.Name)'. Skipping." continue } $action = "Download '$($package.FileName)'" if (-not $PSCmdlet.ShouldProcess($package.DownloadUri, $action)) { continue } $downloadedFile = $null try { $downloadedFile = switch -Wildcard ($package.Name) { 'intel-ethernet' { Save-WinPEDriverPackageIntelEthernet -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'intel-wifi' { Save-WinPEDriverPackageIntelWifi -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'microsoft-windows-ethernet' { Save-WinPEDriverPackageWindowsLofEthernet -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'microsoft-windows-wifi' { Save-WinPEDriverPackageWindowsLofWifi -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'dell' { Save-WinPEDriverPackageDell -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'hp' { Save-WinPEDriverPackageHp -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'vmware' { Save-WinPEDriverPackageVMwareAmd64 -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } 'vmware-arm64' { Save-WinPEDriverPackageVMwareArm64 -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly -ErrorAction Stop } default { Write-Warning "[$(Get-Date -Format s)] No save function registered for '$($package.Name)'. Skipping." $null } } } catch { $isWafChallenge = $_.FullyQualifiedErrorId -like 'WafChallenge*' -or $_.Exception.Message -match 'AWS WAF challenge' if ($isWafChallenge) { $idPrefix = ($package.Name -split '-')[0] $manualPath = Join-Path (Join-Path $script:OSDeployCoreDownloadsPath $idPrefix) $package.FileName $agreementUri = $null $sourceConfig = $global:OSDeployModule.WinPEDrivers.PSObject.Properties[$package.Name].Value if ($sourceConfig -and -not [string]::IsNullOrWhiteSpace($sourceConfig.UpdateUri)) { $agreementUri = $sourceConfig.UpdateUri } elseif (-not [string]::IsNullOrWhiteSpace($package.DownloadUri)) { $agreementUri = $package.DownloadUri } Write-Warning "[$(Get-Date -Format s)] $($package.Name) download skipped: $($_.Exception.Message)" Write-Warning "[$(Get-Date -Format s)] Continue processing other packages. Manual $($package.Name) steps:" Write-Warning "[$(Get-Date -Format s)] 1. Open this URL and accept the agreement: $agreementUri" Write-Warning "[$(Get-Date -Format s)] 2. Download '$($package.FileName)' in a browser." Write-Warning "[$(Get-Date -Format s)] 3. Place the file in '$manualPath'." Write-Warning "[$(Get-Date -Format s)] 4. Rerun: Update-OSDeployCoreDrivers -Name '$($package.Name)'." } else { Write-Warning "[$(Get-Date -Format s)] Failed to process '$($package.Name)': $($_.Exception.Message). Continuing with remaining packages." } continue } if (-not $downloadedFile) { continue } if ($DownloadOnly) { $downloadedFile continue } $arch = if ([string]::IsNullOrWhiteSpace($package.Architecture)) { 'amd64' } else { $package.Architecture } $version = if ([string]::IsNullOrWhiteSpace($package.Version)) { $package.PackageId } else { $package.Version } $jsonPath = Join-Path $script:OSDeployBootAssetsPath "winpedrivers-$arch" | Join-Path -ChildPath "$($package.Name)-$version" | Join-Path -ChildPath 'package.json' Write-HostDateTimeDarkGray -Message "$jsonPath" if (-not (Test-Path -Path $jsonPath)) { $jsonDir = Split-Path -Path $jsonPath -Parent if (-not (Test-Path -Path $jsonDir)) { New-Item -ItemType Directory -Path $jsonDir -Force | Out-Null } $metadata = [ordered]@{ Name = $package.Name Architecture = $arch Id = $package.Id PackageId = $package.PackageId Version = $package.Version ReleaseDate = $package.ReleaseDate FileName = $package.FileName FileSizeMB = $package.FileSizeMB DownloadUri = $package.DownloadUri ExpandCommand = $package.ExpandCommand Checksums = $package.Checksums DownloadedOn = (Get-Date -Format 'yyyy-MM-dd') } try { $metadata | ConvertTo-Json -Depth 5 | Set-Content -Path $jsonPath -Encoding UTF8 -ErrorAction Stop Write-Verbose "[$($MyInvocation.MyCommand.Name)] Metadata written to '$jsonPath'" } catch { Write-Error "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Failed to write package.json: $($_.Exception.Message)" } } $downloadedFile } } end { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete" } } |