Public/Save-SurfaceWinPEDriver.ps1

function Save-SurfaceWinPEDriver {
    <#
    .SYNOPSIS
        Downloads and extracts the required Windows PE drivers for selected Surface models.
 
    .DESCRIPTION
        Dynamically resolves the selected Surface models against Microsoft's current guidance,
        selects the newest supported Surface driver pack MSI, extracts it, copies the published
        WinPE Import folders that are present, and adds any required prerequisite packages such
        as SurfaceHidMini_WinPE_Intel or SurfaceHidMini_WinPE_ARM.
 
        The selected Surface MSI can be Win10- or Win11-named. The OS label is metadata rather
        than a compatibility gate because the MSI is used only as a source for the Windows PE
        driver folders Microsoft documents for that Surface model.
 
        Microsoft Learn guidance and the currently published Surface MSI can occasionally be
        temporarily out of sync. When Learn lists an Import folder that is not present in the
        current MSI, the cmdlet emits a warning, records the mismatch in the output metadata,
        and continues with the published folders that are present. Required prerequisite packages
        remain mandatory and still cause the build to fail when they cannot be resolved.
 
        Only the final WinPE driver folders are kept in the output path. MSI and extraction data
        are stored in a temporary working directory. The working directory is removed after a
        successful build and preserved on failure for troubleshooting.
 
        Major processing stages are shown by default. Use -Quiet to suppress these status messages.
 
    .PARAMETER Model
        One or more exact model names returned by Get-SurfaceWinPEModel.
 
    .PARAMETER InputObject
        Surface model objects accepted from the pipeline.
 
    .PARAMETER Path
        Root output directory. Each selected Surface model receives its own subfolder.
 
    .PARAMETER Force
        Rebuild the output even when the current model folder already matches the latest driver
        pack version and current Microsoft WinPE configuration.
 
    .PARAMETER Quiet
        Suppress user-facing processing status messages. Warnings and result objects are still returned.
 
    .EXAMPLE
        Save-SurfaceWinPEDriver -Model 'Surface Laptop 8 - Intel' -Path 'C:\WinPE\Surface'
 
    .EXAMPLE
        Get-SurfaceWinPEModel |
            Out-GridView -Title 'Select Surface models' -PassThru |
            Save-SurfaceWinPEDriver -Path 'C:\WinPE\Surface'
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Named')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Named')]
        [string[]]$Model,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Pipeline')]
        [psobject]$InputObject,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [switch]$Force,

        [switch]$Quiet
    )

    begin {
        if (-not (Test-SurfaceWindowsPlatform)) {
            throw 'Save-SurfaceWinPEDriver requires Windows because Surface MSI packages are extracted with msiexec.exe.'
        }
        $pipelineModels = [System.Collections.Generic.List[object]]::new()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Pipeline') {
            $pipelineModels.Add($InputObject)
        }
    }

    end {
        Write-SurfaceStatus -Message 'Reading current Microsoft Surface WinPE guidance and driver catalog...' -Quiet:$Quiet
        $discovered = @(Get-SurfaceDiscovery)
        $selected = @()

        if ($PSCmdlet.ParameterSetName -eq 'Named') {
            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" }
                $selected += $matches[0]
            }
        }
        else {
            foreach ($item in $pipelineModels) {
                if (-not $item.PSObject.Properties['Model']) {
                    throw 'Pipeline input must be an object returned by Get-SurfaceWinPEModel.'
                }
                $matches = @($discovered | Where-Object Model -eq ([string]$item.Model))
                if ($matches.Count -ne 1) {
                    throw "Could not uniquely resolve pipeline model '$($item.Model)' against current Microsoft guidance."
                }
                $selected += $matches[0]
            }
        }

        if (-not (Test-Path -LiteralPath $Path)) {
            New-Item -ItemType Directory -Path $Path -Force | Out-Null
        }

        $modelIndex = 0
        foreach ($surface in $selected) {
            $modelIndex++
            $prefix = '[{0}/{1}] {2}' -f $modelIndex, $selected.Count, $surface.Model

            if ($surface.Status -ne 'Matched') {
                throw "Cannot download '$($surface.Model)': discovery status is $($surface.Status)."
            }
            if (@($surface.ImportFolders).Count -eq 0) {
                throw "Cannot download '$($surface.Model)': Microsoft WinPE guidance returned no Import folders."
            }
            foreach ($package in @($surface.RequiredPackages)) {
                if (-not $package.DownloadUrl) {
                    throw "Cannot download '$($surface.Model)': required package '$($package.Folder)' has no download URL."
                }
            }

            Write-SurfaceStatus -Message "$prefix - Resolving latest Microsoft driver pack..." -Quiet:$Quiet
            $driverPack = Get-SurfaceLatestDriverPack `
                -DownloadCenterId $surface.DownloadCenterId `
                -DefaultCpuVendor $surface.CpuVendor `
                -DefaultArchitecture $surface.Architecture

            if (-not $driverPack) {
                throw "No supported Surface driver pack MSI was found for '$($surface.Model)'."
            }

            $packSize = Format-SurfaceFileSize -Bytes $driverPack.FileSizeBytes
            Write-SurfaceStatus -Message "$prefix - Driver pack $($driverPack.DriverPackVersion) selected ($packSize)." -Quiet:$Quiet
            Write-SurfaceStatus -Message "$prefix - Microsoft WinPE recipe contains $(@($surface.ImportFolders).Count) import folder(s) and $(@($surface.RequiredPackages).Count) prerequisite package(s)." -Quiet:$Quiet

            $configurationHash = Get-SurfaceConfigurationHash `
                -ImportFolders @($surface.ImportFolders) `
                -RequiredPackages @($surface.RequiredPackages)

            $modelFolderName = ConvertTo-SurfaceSafePathPart $surface.Model
            $modelPath = Join-Path $Path $modelFolderName
            $metadata = Get-SurfaceOutputMetadata -ModelPath $modelPath

            if (-not $Force -and $metadata -and
                $metadata.DriverPackVersion -eq $driverPack.DriverPackVersion -and
                $metadata.ConfigurationHash -eq $configurationHash) {

                $existingMissingFolders = @()
                if ($metadata.PSObject.Properties['MissingImportFolders']) {
                    $existingMissingFolders = @($metadata.MissingImportFolders)
                }

                if ($existingMissingFolders.Count -gt 0) {
                    Write-Warning ("[{0}] Output is current, but Microsoft Learn lists Import folder(s) that are not present in the current Surface MSI: {1}. This indicates that the Learn guidance and published driver pack are not fully synchronized. The missing folders were skipped when this output was built." -f $surface.Model, ($existingMissingFolders -join ', '))
                    Write-SurfaceStatus -Message "$prefix - Output is already current with $($existingMissingFolders.Count) missing Microsoft Learn import folder(s). No download or rebuild required." -Quiet:$Quiet
                    $currentStatus = 'CurrentWithWarnings'
                }
                else {
                    Write-SurfaceStatus -Message "$prefix - Output is already current. No download or rebuild required." -Quiet:$Quiet
                    $currentStatus = 'Current'
                }

                $result = [pscustomobject]@{
                    PSTypeName        = 'SurfaceWinPEDrivers.Result'
                    Model             = $surface.Model
                    Architecture      = $surface.Architecture
                    DriverPackVersion = $driverPack.DriverPackVersion
                    OsBuildNumber     = $driverPack.OsBuildNumber
                    Status            = $currentStatus
                    Path              = $modelPath
                }
                $result.PSObject.TypeNames.Insert(0, 'SurfaceWinPEDrivers.Result')
                Write-Output $result
                continue
            }

            if (-not $PSCmdlet.ShouldProcess($surface.Model, "Download and build WinPE drivers in '$modelPath'")) {
                continue
            }

            $workingPath = Join-Path ([IO.Path]::GetTempPath()) ('SurfaceWinPEDrivers-' + [guid]::NewGuid().ToString('N'))
            $msiPath = Join-Path $workingPath $driverPack.DriverPackFileName
            $extractPath = Join-Path $workingPath 'extracted'
            $stagePath = Join-Path $workingPath 'output'
            $buildSucceeded = $false

            try {
                New-Item -ItemType Directory -Path $workingPath -Force | Out-Null
                New-Item -ItemType Directory -Path $stagePath -Force | Out-Null

                Write-SurfaceStatus -Message "$prefix - Downloading $($driverPack.DriverPackFileName) ($packSize)..." -Quiet:$Quiet
                Save-SurfaceRemoteFile -Uri $driverPack.DownloadUrl -Path $msiPath
                Write-SurfaceStatus -Message "$prefix - Driver pack download completed." -Quiet:$Quiet

                Write-SurfaceStatus -Message "$prefix - Extracting Surface MSI..." -Quiet:$Quiet
                Expand-SurfaceMsi -MsiPath $msiPath -DestinationPath $extractPath
                Write-SurfaceStatus -Message "$prefix - MSI extraction completed." -Quiet:$Quiet

                Write-SurfaceStatus -Message "$prefix - Validating $(@($surface.ImportFolders).Count) published WinPE import folder(s)..." -Quiet:$Quiet
                $resolvedFolders = @{}
                $missingFolders = [System.Collections.Generic.List[string]]::new()
                foreach ($folderName in @($surface.ImportFolders)) {
                    $resolved = Resolve-SurfaceExtractedFolder -ExtractedPath $extractPath -FolderName $folderName
                    if ($resolved) { $resolvedFolders[$folderName] = $resolved }
                    else { $missingFolders.Add($folderName) }
                }

                if ($resolvedFolders.Count -eq 0) {
                    throw "None of the WinPE Import folders published by Microsoft for '$($surface.Model)' were found in the current Surface MSI."
                }

                $buildWarnings = [System.Collections.Generic.List[object]]::new()
                if ($missingFolders.Count -gt 0) {
                    $missingText = $missingFolders -join ', '
                    $warningMessage = "Microsoft Learn lists the following WinPE Import folder(s) for '$($surface.Model)', but they are not present in the currently published Surface MSI $($driverPack.DriverPackVersion): $missingText. Microsoft Learn guidance and Surface driver packs can temporarily be out of sync. These folders will be skipped and the build will continue with the published folders that are present. Required prerequisite packages remain mandatory."
                    Write-Warning $warningMessage
                    $buildWarnings.Add([pscustomobject]@{
                        Type    = 'MicrosoftGuidanceMismatch'
                        Message = $warningMessage
                    })
                    Write-SurfaceStatus -Message "$prefix - Continuing with $($resolvedFolders.Count) of $(@($surface.ImportFolders).Count) published import folder(s)." -Quiet:$Quiet
                }
                else {
                    Write-SurfaceStatus -Message "$prefix - All published WinPE import folders were found." -Quiet:$Quiet
                }

                Write-SurfaceStatus -Message "$prefix - Copying selected WinPE driver folders..." -Quiet:$Quiet
                foreach ($folderName in @($surface.ImportFolders)) {
                    if ($resolvedFolders.ContainsKey($folderName)) {
                        Copy-SurfaceWinPEFolder -SourcePath $resolvedFolders[$folderName] -DestinationRoot $stagePath
                    }
                }

                foreach ($package in @($surface.RequiredPackages)) {
                    Write-SurfaceStatus -Message "$prefix - Downloading and adding prerequisite: $($package.Folder)..." -Quiet:$Quiet
                    Add-SurfaceRequiredPackage -Package $package -WorkingPath $workingPath -DestinationRoot $stagePath
                    Write-SurfaceStatus -Message "$prefix - Prerequisite added: $($package.Folder)." -Quiet:$Quiet
                }

                Write-SurfaceStatus -Message "$prefix - Writing output metadata..." -Quiet:$Quiet
                $outputMetadata = [pscustomobject]@{
                    SchemaVersion        = 1
                    GeneratedAtUtc       = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
                    Model                = $surface.Model
                    Architecture         = $surface.Architecture
                    DownloadCenterId     = $surface.DownloadCenterId
                    DriverPackFileName   = $driverPack.DriverPackFileName
                    DriverPackVersion    = $driverPack.DriverPackVersion
                    DriverPackOsName     = $driverPack.OsName
                    OsBuildNumber        = $driverPack.OsBuildNumber
                    ConfigurationHash    = $configurationHash
                    ImportFolders        = @($surface.ImportFolders)
                    ImportFolderSource   = $surface.ImportFolderSource
                    MissingImportFolders = @($missingFolders)
                    RequiredPackages     = @($surface.RequiredPackages | Select-Object Name, Folder, DownloadUrl, ArchiveType)
                    Warnings             = @($buildWarnings)
                    Sources              = [pscustomobject]@{
                        WinPEGuidance = $script:WinPEDocumentationUrl
                        DriverCatalog = $script:DriverCatalogUrl
                        DriverPack    = $driverPack.DetailsUrl
                    }
                }
                $outputMetadata | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $stagePath '.surfacewinpe.json') -Encoding UTF8

                Write-SurfaceStatus -Message "$prefix - Publishing completed driver set to '$modelPath'..." -Quiet:$Quiet
                if (Test-Path -LiteralPath $modelPath) {
                    Remove-Item -LiteralPath $modelPath -Recurse -Force
                }
                Move-Item -LiteralPath $stagePath -Destination $modelPath

                $buildSucceeded = $true
                if ($missingFolders.Count -gt 0) {
                    Write-SurfaceStatus -Message "$prefix - Completed with warnings." -Quiet:$Quiet
                    $resultStatus = 'SavedWithWarnings'
                }
                else {
                    Write-SurfaceStatus -Message "$prefix - Completed successfully." -Quiet:$Quiet
                    $resultStatus = 'Saved'
                }

                $result = [pscustomobject]@{
                    PSTypeName        = 'SurfaceWinPEDrivers.Result'
                    Model             = $surface.Model
                    Architecture      = $surface.Architecture
                    DriverPackVersion = $driverPack.DriverPackVersion
                    OsBuildNumber     = $driverPack.OsBuildNumber
                    Status            = $resultStatus
                    Path              = $modelPath
                }
                $result.PSObject.TypeNames.Insert(0, 'SurfaceWinPEDrivers.Result')
                Write-Output $result
            }
            catch {
                Write-SurfaceStatus -Message "$prefix - Build failed. Temporary working directory preserved for troubleshooting: '$workingPath'." -Quiet:$Quiet
                if (Test-Path -LiteralPath $extractPath) {
                    Write-SurfaceStatus -Message "$prefix - Extracted MSI content is available at: '$extractPath'." -Quiet:$Quiet
                }
                throw
            }
            finally {
                if ($buildSucceeded -and (Test-Path -LiteralPath $workingPath)) {
                    Remove-Item -LiteralPath $workingPath -Recurse -Force -ErrorAction SilentlyContinue
                }
            }
        }
    }
}