private/WinPEDrivers/CloudWinPEDriver/Save-CloudWinPEDriver.ps1

#Requires -PSEdition Core

function Save-CloudWinPEDriver {
    <#
    .SYNOPSIS
        Internal helper that selects and downloads WinPE driver packages to
        $env:ProgramData\OSDeployCore.
 
    .DESCRIPTION
        Internal helper used by Update-OSDeployWinPEDrivers. It resolves available WinPE driver
        packages (from Get-CloudWinPEDriver) and by default processes all matching packages directly.
 
        When -Interactive is specified, an Out-GridView is displayed showing Architecture, Version,
        download status, and expansion status for each entry, and the user selects which packages
        to process before any download begins.
 
        Downloads are delegated to per-driver private functions (Save-CloudWinPEDriver{Name})
        that handle the specific download and expansion logic for each vendor. A JSON
        metadata file is written to
        $env:ProgramData\OSDeployCore\Repository\winpe-drivers\{Architecture}\{Name}-{Version}\package.json after
        each successful download.
 
        When -Interactive is not specified, all matching packages are processed directly and
        Out-GridView is not used.
 
    .PARAMETER Name
        One or more source names to download. Tab-completion excludes Disabled sources.
        When omitted, all non-Disabled sources are presented in the GridView.
 
    .PARAMETER DriverPackage
        A OSDeployWinPEDriver.Package object from Get-CloudWinPEDriver. Bypasses the GridView.
 
    .PARAMETER Force
        Re-download even if the file already exists and the checksum matches.
 
    .PARAMETER SkipCatalogRefresh
        Internal switch used when the caller already refreshed the requested catalog entries.
 
    .PARAMETER SkipWifiDrivers
        Exclude Wi-Fi driver packages (sources whose name matches 'wifi' or 'wireless') from
        download and processing. Automatically set when no imported OS sources are present,
        because ADK WinPE does not support wireless hardware.
 
    .PARAMETER Interactive
        Internal switch used by Update-OSDeployWinPEDrivers to open the Out-GridView picker so the
        user can select which packages to download.
 
    .OUTPUTS
        [System.IO.FileInfo] The downloaded file(s).
 
    .NOTES
        Author: David Segura
        Version: 0.1.0
    #>

    [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(ParameterSetName = 'ByName')]
        [switch]$SkipCatalogRefresh,

        [Parameter(ParameterSetName = 'ByName')]
        [switch]$SkipWifiDrivers,

        [Parameter(ParameterSetName = 'ByName')]
        [switch]$Interactive
    )

    begin {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Starting"

        $currentPrincipal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
        if (-not $WhatIfPreference -and -not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    [System.UnauthorizedAccessException]::new('Update-OSDeployWinPEDrivers 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-CloudWinPEDriver -Name $Name -SkipCatalogRefresh:$SkipCatalogRefresh
            }
            else {
                Get-CloudWinPEDriver -SkipCatalogRefresh:$SkipCatalogRefresh
            }

            if ($SkipWifiDrivers) {
                $candidates = $candidates | Where-Object { $_.Name -notmatch 'wifi|wireless' }
            }

            $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:OSDeployCoreRepositoryPath 'winpe-drivers' |
                                Join-Path -ChildPath $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-OSDeployWinPEDriversProgress "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-OSDeployWinPEDriversProgress "$downloadsPath is currently using $downloadsSizeGB GB"
                }

                if (-not $needsAction) {
                    Write-OSDeployWinPEDriversProgress "All driver packages are downloaded, expanded, and up to date."
                    return
                }

                # Write-OSDeployWinPEDriversProgress "Select Drivers in GridView to download and expand"
                # Write-OSDeployWinPEDriversProgress "Downloads are saved to $($Script:OSDeployCorePath)\cache\downloads"
                # Write-OSDeployWinPEDriversProgress "Expanded content is saved to $($script:OSDeployCoreRepositoryPath)\winpe-drivers"
                $selected = $needsAction |
                    Select-Object -Property Id, Name, Architecture, ReleaseDate, Version, FileSizeMB, DownloadUri |
                    Out-GridView -Title "[$(Get-Date -format s)] OSDeployCore: Select winpe driver packages to add to the Library $($Script:OSDeployCorePath)" -PassThru
                if (-not $selected) {
                    Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] No 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)] [$($MyInvocation.MyCommand.Name)] No matching drivers found for processing."
                    return
                }

                Write-Verbose "[$(Get-Date -format s)] [$($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)] [$($MyInvocation.MyCommand.Name)] No DownloadUri for '$($package.Name)'. Skipping."
                continue
            }

            $action = "Download '$($package.FileName)'"
            if (-not $PSCmdlet.ShouldProcess($package.DownloadUri, $action)) {
                continue
            }

            $downloadedFile = switch -Wildcard ($package.Name) {
                'intel-ethernet'              { Save-CloudWinPEDriverIntelEthernet -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'intel-wifi'                  { Save-CloudWinPEDriverIntelWifi -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'microsoft-windows-ethernet'  { Save-CloudWinPEDriverWindowsEthernet -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'microsoft-windows-wifi'      { Save-CloudWinPEDriverWindowsWifi -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'dell'                        { Save-CloudWinPEDriverOemDell -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'hp'                          { Save-CloudWinPEDriverOemHp -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'vmware'                      { Save-CloudWinPEDriverOemVMwareAMD64 -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                'vmware-arm64'                { Save-CloudWinPEDriverOemVMwareARM64 -DriverPackage $package -Force:$Force -DownloadOnly:$DownloadOnly }
                default {
                    Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] No save function registered for '$($package.Name)'. Skipping."
                    $null
                }
            }

            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 }
            $finalDriverPath = Join-Path $script:OSDeployCoreRepositoryPath 'winpe-drivers' |
                               Join-Path -ChildPath $arch |
                               Join-Path -ChildPath "$($package.Name)-$version"
            $jsonPath        = Join-Path $script:OSDeployCoreRepositoryPath 'winpe-drivers' |
                               Join-Path -ChildPath $arch |
                               Join-Path -ChildPath "$($package.Name)-$version" |
                               Join-Path -ChildPath 'package.json'

            Write-OSDeployWinPEDriversProgress -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 "[$(Get-Date -format s)] [$($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 "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Complete"
    }
}