Private/FileOperations.ps1

function Test-SurfaceWindowsPlatform {
    if ($PSVersionTable.PSVersion.Major -ge 6) { return [bool]$IsWindows }
    return ([Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT)
}

function ConvertTo-SurfaceSafePathPart {
    param([Parameter(Mandatory)][string]$Name)

    $result = $Name
    foreach ($char in [IO.Path]::GetInvalidFileNameChars()) {
        $result = $result.Replace([string]$char, '_')
    }
    return ($result -replace '\s+', ' ').Trim()
}

function Save-SurfaceRemoteFile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Uri,
        [Parameter(Mandatory)][string]$Path
    )

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

    $partial = "$Path.partial"
    Remove-Item -LiteralPath $partial -Force -ErrorAction SilentlyContinue

    try {
        if (Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue) {
            Start-BitsTransfer -Source $Uri -Destination $partial -ErrorAction Stop
        }
        else {
            $params = @{
                Uri         = $Uri
                OutFile     = $partial
                ErrorAction = 'Stop'
                Headers     = @{ 'User-Agent' = 'SurfaceWinPEDrivers/1.0' }
            }
            if ($PSVersionTable.PSVersion.Major -lt 6) { $params.UseBasicParsing = $true }
            Invoke-WebRequest @params
        }

        if (-not (Test-Path -LiteralPath $partial)) { throw "Download did not create '$partial'." }
        if ((Get-Item -LiteralPath $partial).Length -le 0) { throw "Downloaded file is empty: $Uri" }

        Move-Item -LiteralPath $partial -Destination $Path -Force
    }
    finally {
        Remove-Item -LiteralPath $partial -Force -ErrorAction SilentlyContinue
    }
}

function Expand-SurfaceMsi {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$MsiPath,
        [Parameter(Mandatory)][string]$DestinationPath
    )

    if (-not (Test-SurfaceWindowsPlatform)) {
        throw 'MSI extraction requires Windows.'
    }

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

    $logPath = Join-Path $DestinationPath 'msiexec.log'
    $arguments = @(
        '/a', "`"$MsiPath`"",
        "TARGETDIR=`"$DestinationPath`"",
        '/qn',
        '/L*v', "`"$logPath`""
    )

    $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList $arguments -Wait -PassThru
    if ($process.ExitCode -ne 0) {
        throw "msiexec failed with exit code $($process.ExitCode). Log: $logPath"
    }
}

function Resolve-SurfaceExtractedFolder {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$ExtractedPath,
        [Parameter(Mandatory)][string]$FolderName
    )

    $surfaceUpdate = Join-Path (Join-Path $ExtractedPath 'SurfaceUpdate') $FolderName
    if (Test-Path -LiteralPath $surfaceUpdate -PathType Container) { return $surfaceUpdate }

    $matches = @(
        Get-ChildItem -LiteralPath $ExtractedPath -Directory -Recurse -ErrorAction SilentlyContinue |
            Where-Object Name -ieq $FolderName |
            Select-Object -ExpandProperty FullName
    )

    if ($matches.Count -eq 1) { return $matches[0] }
    if ($matches.Count -eq 0) { return $null }

    throw "Multiple extracted folders named '$FolderName' were found: $($matches -join '; ')"
}

function Copy-SurfaceWinPEFolder {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$SourcePath,
        [Parameter(Mandatory)][string]$DestinationRoot
    )

    $name = Split-Path -Leaf $SourcePath
    $destination = Join-Path $DestinationRoot $name
    if (Test-Path -LiteralPath $destination) {
        throw "Duplicate WinPE output folder '$name' would be created."
    }

    Copy-Item -LiteralPath $SourcePath -Destination $DestinationRoot -Recurse -Force
}

function Add-SurfaceRequiredPackage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][psobject]$Package,
        [Parameter(Mandatory)][string]$WorkingPath,
        [Parameter(Mandatory)][string]$DestinationRoot
    )

    if (-not $Package.DownloadUrl) {
        throw "Required package '$($Package.Folder)' has no download URL."
    }

    $archivePath = Join-Path $WorkingPath ((ConvertTo-SurfaceSafePathPart $Package.Folder) + '.zip')
    $extractPath = Join-Path $WorkingPath ((ConvertTo-SurfaceSafePathPart $Package.Folder) + '-expanded')

    Save-SurfaceRemoteFile -Uri $Package.DownloadUrl -Path $archivePath
    New-Item -ItemType Directory -Path $extractPath -Force | Out-Null
    Expand-Archive -LiteralPath $archivePath -DestinationPath $extractPath -Force

    $folder = @(
        Get-ChildItem -LiteralPath $extractPath -Directory -Recurse -ErrorAction SilentlyContinue |
            Where-Object Name -ieq $Package.Folder |
            Select-Object -ExpandProperty FullName
    )

    if ($folder.Count -ne 1) {
        throw "Required package folder '$($Package.Folder)' was expected exactly once in '$archivePath' but $($folder.Count) matches were found."
    }

    Copy-SurfaceWinPEFolder -SourcePath $folder[0] -DestinationRoot $DestinationRoot
}

function Get-SurfaceOutputMetadata {
    param([Parameter(Mandatory)][string]$ModelPath)

    $metadataPath = Join-Path $ModelPath '.surfacewinpe.json'
    if (-not (Test-Path -LiteralPath $metadataPath)) { return $null }

    try { return (Get-Content -LiteralPath $metadataPath -Raw | ConvertFrom-Json) }
    catch { return $null }
}