Public/New-SurfaceWinPEManifest.ps1
|
function New-SurfaceWinPEManifest { <# .SYNOPSIS Builds a current Surface Windows PE driver manifest without downloading driver packs. .DESCRIPTION Resolves Microsoft's current Windows PE import guidance against the official Surface driver catalog and the newest supported Surface driver pack MSI for each selected model. The selected MSI can be Win10- or Win11-named; the OS label is metadata because the package is used only as a source for the WinPE driver folders Microsoft documents. With -Validate, the cmdlet also verifies the driver-pack and required prerequisite package URLs without downloading their contents. .PARAMETER All Include every model currently published on Microsoft's Surface Windows PE guidance page. .PARAMETER Model One or more exact Surface model names returned by Get-SurfaceWinPEModel. .PARAMETER InputObject Surface model objects accepted from the pipeline. .PARAMETER Path Destination JSON manifest path. .PARAMETER Validate Test that every model is matched, has Import folders, resolves a supported Surface driver pack MSI, and that the driver-pack and required prerequisite package URLs are reachable. The manifest is written before a validation error is thrown. .EXAMPLE New-SurfaceWinPEManifest -All -Path .\SurfaceWinPE.Manifest.json -Validate .EXAMPLE Get-SurfaceWinPEModel | Out-GridView -PassThru | New-SurfaceWinPEManifest -Path .\SurfaceWinPE.Manifest.json -Validate #> [CmdletBinding(DefaultParameterSetName = 'All')] param( [Parameter(Mandatory, ParameterSetName = 'All')] [switch]$All, [Parameter(Mandatory, ParameterSetName = 'Named')] [string[]]$Model, [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Pipeline')] [psobject]$InputObject, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Path, [switch]$Validate ) begin { $pipelineModels = [System.Collections.Generic.List[object]]::new() } process { if ($PSCmdlet.ParameterSetName -eq 'Pipeline') { $pipelineModels.Add($InputObject) } } end { $discovered = @(Get-SurfaceDiscovery) switch ($PSCmdlet.ParameterSetName) { 'All' { $selected = $discovered } 'Named' { $selected = foreach ($name in $Model) { $matches = @($discovered | Where-Object Model -eq $name) if ($matches.Count -eq 0) { throw "Surface model was not found in current Microsoft WinPE guidance: $name" } if ($matches.Count -gt 1) { throw "Surface model name is not unique: $name" } $matches[0] } } 'Pipeline' { $selected = foreach ($item in $pipelineModels) { if ($item.PSObject.Properties['Model']) { $matches = @($discovered | Where-Object Model -eq ([string]$item.Model)) if ($matches.Count -eq 1) { $matches[0]; continue } } throw 'Pipeline input must be an object returned by Get-SurfaceWinPEModel.' } } } $urlValidationCache = @{} $manifestModels = @() $failureCount = 0 foreach ($surface in @($selected)) { Write-Verbose "Resolving $($surface.Model)" $errors = [System.Collections.Generic.List[string]]::new() $driverPack = $null $driverValidation = $null $requiredPackageValidation = @() if ($surface.Status -ne 'Matched') { $errors.Add("Discovery status is $($surface.Status).") } if (@($surface.ImportFolders).Count -eq 0) { $errors.Add('Microsoft WinPE guidance did not contain any Import folders.') } foreach ($package in @($surface.RequiredPackages)) { if (-not $package.DownloadUrl) { $errors.Add("Required prerequisite package '$($package.Folder)' has no download URL.") } } if ($surface.Status -eq 'Matched') { try { $driverPack = Get-SurfaceLatestDriverPack ` -DownloadCenterId $surface.DownloadCenterId ` -DefaultCpuVendor $surface.CpuVendor ` -DefaultArchitecture $surface.Architecture if (-not $driverPack) { $errors.Add('No supported Surface driver pack MSI was found on the Microsoft Download Center page.') } } catch { $errors.Add("Failed to resolve Download Center metadata: $($_.Exception.Message)") } } if ($Validate -and $driverPack) { if (-not $urlValidationCache.ContainsKey($driverPack.DownloadUrl)) { $urlValidationCache[$driverPack.DownloadUrl] = Test-SurfaceUri -Uri $driverPack.DownloadUrl } $driverValidation = $urlValidationCache[$driverPack.DownloadUrl] if (-not $driverValidation.Success) { $errors.Add("Driver pack URL validation failed: $($driverPack.DownloadUrl)") } } foreach ($package in @($surface.RequiredPackages)) { $validation = $null if ($Validate -and $package.DownloadUrl) { if (-not $urlValidationCache.ContainsKey($package.DownloadUrl)) { $urlValidationCache[$package.DownloadUrl] = Test-SurfaceUri -Uri $package.DownloadUrl } $validation = $urlValidationCache[$package.DownloadUrl] if (-not $validation.Success) { $errors.Add("Required package URL validation failed for '$($package.Folder)': $($package.DownloadUrl)") } } $requiredPackageValidation += [pscustomobject]@{ Name = $package.Name Folder = $package.Folder DownloadUrl = $package.DownloadUrl ArchiveType = $package.ArchiveType Required = $true UrlValidation = $validation } } $configurationHash = Get-SurfaceConfigurationHash ` -ImportFolders @($surface.ImportFolders) ` -RequiredPackages @($surface.RequiredPackages) $entryStatus = if ($errors.Count -eq 0) { 'Ready' } else { 'Failed' } if ($entryStatus -eq 'Failed') { $failureCount++ } $manifestModels += [pscustomobject]@{ Manufacturer = 'Microsoft' Model = $surface.Model Architecture = $surface.Architecture CpuVendor = $surface.CpuVendor DiscoveryStatus = $surface.Status DownloadModel = $surface.DownloadModel DownloadCenterId = $surface.DownloadCenterId DetailsUrl = $surface.DetailsUrl WinPE = [pscustomobject]@{ ImportFolders = @($surface.ImportFolders) ImportFolderSource = $surface.ImportFolderSource RequiredPackages = @($requiredPackageValidation) ConfigurationHash = $configurationHash } DriverPack = $driverPack Validation = [pscustomobject]@{ Status = $entryStatus Errors = @($errors) DriverPackUrl = $driverValidation ImportFolderCount = @($surface.ImportFolders).Count RequiredPackageCount = @($surface.RequiredPackages).Count } } } # Also record official Surface download entries that do not currently have a WinPE recipe. # These are not errors, but make changes on either Microsoft source visible in a dry run. $downloadCatalog = @(Get-SurfaceDriverDownloadCatalog) $winpeKeys = @($discovered | ForEach-Object ModelKey) $driverOnly = @( $downloadCatalog | Where-Object { $_.ModelKey -notin $winpeKeys } | Select-Object Model, DownloadCenterId, DetailsUrl ) $manifest = [pscustomobject]@{ SchemaVersion = 2 GeneratedAtUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') Sources = [pscustomobject]@{ WinPEGuidance = $script:WinPEDocumentationUrl DriverCatalog = $script:DriverCatalogUrl } Discovery = [pscustomobject]@{ WinPEModelCount = $discovered.Count SelectedModelCount = @($selected).Count MatchedCount = @($discovered | Where-Object Status -eq 'Matched').Count UnmatchedCount = @($discovered | Where-Object Status -eq 'Unmatched').Count AmbiguousCount = @($discovered | Where-Object Status -eq 'Ambiguous').Count DriverCatalogOnly = $driverOnly DriverCatalogOnlyCount = $driverOnly.Count } Validation = [pscustomobject]@{ Performed = [bool]$Validate FailureCount = $failureCount Status = if ($failureCount -eq 0) { 'Passed' } else { 'Failed' } } Models = $manifestModels } $parent = Split-Path -Parent $Path if ($parent -and -not (Test-Path -LiteralPath $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } $manifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $Path -Encoding UTF8 Write-Verbose "Manifest written to $Path" Write-Output $manifest if ($Validate -and $failureCount -gt 0) { throw "Surface WinPE manifest validation failed for $failureCount model(s). The manifest was written to '$Path' with details." } } } |