Private/Save-OSDStageRegistryStaging.ps1

function Save-OSDStageRegistryStaging {
    [CmdletBinding()]
    param([Parameter(Mandatory)]$Operation, [string]$WindowsPath)
    if (-not (Test-OSDStageWinPE)) { throw 'Stage registry changes in WinPE after OSDCloud, not on the preparation PC.' }
    $target = Resolve-OSDStageWindowsPath -WindowsPath $WindowsPath
    $stage = Join-Path $target 'Windows\Temp\OSDStage'
    $manifestPath = Join-Path $stage 'DeviceManifest.json'
    $packageRoot = Join-Path $stage 'Packages\RegistrySettings'
    $archivePath = Join-Path $packageRoot 'Package.zip'
    $moduleRoot = Split-Path $PSScriptRoot -Parent
    $manifest = if (Test-Path -LiteralPath $manifestPath -PathType Leaf) {
        $m = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
        if ($null -eq $m -or $m.PSObject.Properties.Name -notcontains 'Packages') { throw 'Existing device manifest is invalid.' }
        $m
    } else { [pscustomobject]@{SchemaVersion=2; StagedAt=(Get-Date).ToUniversalTime().ToString('o'); Packages=@()} }
    $manifest = Set-OSDStageManifestRuntimeConfiguration -Manifest $manifest
    $previous = @($manifest.Packages | Where-Object Id -eq 'RegistrySettings')
    if ($previous.Count -gt 1) { throw 'Duplicate RegistrySettings entry in device manifest.' }
    $operations = @()
    Add-Type -AssemblyName System.IO.Compression -ErrorAction Stop
    Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
    if ($previous.Count -eq 1) {
        $entry = $previous[0]
        if ($entry.Source -ne 'Repository' -or $entry.Type -ne 'RepositoryPackage' -or
            $entry.PackageType -ne 'Script' -or $entry.RegistryStageSchema -ne 1 -or
            -not (Test-Path -LiteralPath $archivePath -PathType Leaf) -or
            (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash -ne $entry.Payload.Sha256) {
            throw 'Existing RegistrySettings is not an intact locally staged registry script. Nothing was replaced.'
        }
        $zip = [IO.Compression.ZipFile]::OpenRead($archivePath)
        try {
            $configEntry = $zip.GetEntry('Package/config.json')
            if ($null -eq $configEntry -or $configEntry.Length -gt 16MB) { throw 'Missing or oversized registry configuration in script package.' }
            $reader = [IO.StreamReader]::new($configEntry.Open(),[Text.Encoding]::UTF8)
            try { $config = $reader.ReadToEnd() | ConvertFrom-Json -ErrorAction Stop }
            finally { $reader.Dispose() }
            Assert-OSDStageRegistryConfiguration -Configuration $config
            $operations = @($config.Operations)
        } finally { $zip.Dispose() }
    }
    elseif (Test-Path -LiteralPath $packageRoot) { throw 'Unowned RegistrySettings staging directory exists. Nothing was replaced.' }
    $operations = @($operations | Where-Object {
        -not ($_.Action -eq $Operation.Action -and $_.Path -ieq $Operation.Path -and
            $_.Name -ieq $Operation.Name -and $_.View -eq $Operation.View)
    }) + @($Operation)
    $config = [pscustomobject]@{
        SchemaVersion=2; DeploymentId=$manifest.DeploymentId
        ResultPath=(Join-Path (Split-Path ([string]$manifest.Runtime.ResultPath) -Parent) 'Registry-Result.json')
        Operations=@($operations)
    }
    Assert-OSDStageRegistryConfiguration -Configuration $config
    $scripts = @(
        @{ Source=(Join-Path $moduleRoot 'Runtime\Invoke-OSDStageRegistry.ps1'); Entry='Package/Install.ps1' }
        @{ Source=(Join-Path $moduleRoot 'Runtime\OSDStageRegistry.ps1'); Entry='Package/OSDStageRegistry.ps1' }
    )
    foreach ($script in $scripts) {
        if (-not (Test-Path -LiteralPath $script.Source -PathType Leaf)) { throw 'Registry runtime script is missing from this module.' }
    }
    New-Item -ItemType Directory -Path (Split-Path $packageRoot -Parent) -Force | Out-Null
    $transaction = Join-Path (Split-Path $packageRoot -Parent) ('.registry-' + [guid]::NewGuid().ToString('N'))
    $backup = $transaction + '.previous'
    $manifestTemp = $manifestPath + '.' + [guid]::NewGuid().ToString('N') + '.tmp'
    $promoted = $false; $committed = $false; $backedUp = $false
    try {
        New-Item -ItemType Directory -Path $transaction -Force | Out-Null
        $candidate = Join-Path $transaction 'Package.zip'
        $zip = [IO.Compression.ZipFile]::Open($candidate,[IO.Compression.ZipArchiveMode]::Create)
        try {
            foreach ($script in $scripts) {
                $stream = $zip.CreateEntry($script.Entry).Open()
                try { $bytes=[IO.File]::ReadAllBytes($script.Source); $stream.Write($bytes,0,$bytes.Length) }
                finally { $stream.Dispose() }
            }
            $stream = $zip.CreateEntry('Package/config.json').Open()
            try { $bytes=[Text.Encoding]::UTF8.GetBytes(($config | ConvertTo-Json -Depth 15)); $stream.Write($bytes,0,$bytes.Length) }
            finally { $stream.Dispose() }
        } finally { $zip.Dispose() }
        $hash = (Get-FileHash -LiteralPath $candidate -Algorithm SHA256).Hash
        # Reuse the existing verified local script-package runner, not a new execution engine.
        $app = [pscustomobject]@{
            Id='RegistrySettings'; DisplayName='Registry settings'; Vendor='OSDStage'
            Source='Repository'; Type='RepositoryPackage'; PackageType='Script'; Version='1.0.0'; Architecture='any'
            Payload=[pscustomobject]@{Sha256=$hash}; SuccessCodes=@(0)
            RegistryStageSchema=1; RegistryOperationCount=$operations.Count
        }
        $manifest = Set-OSDStageManifestApp -Manifest $manifest -App $app
        $manifest.StagedAt = (Get-Date).ToUniversalTime().ToString('o')
        $manifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $manifestTemp -Encoding UTF8
        Add-OSDStageSetupComplete -WindowsPath $target -Confirm:$false -ErrorAction Stop | Out-Null
        if (Test-Path -LiteralPath $packageRoot) { Move-Item -LiteralPath $packageRoot -Destination $backup -ErrorAction Stop; $backedUp=$true }
        Move-Item -LiteralPath $transaction -Destination $packageRoot -ErrorAction Stop
        $promoted = $true
        Move-Item -LiteralPath $manifestTemp -Destination $manifestPath -Force -ErrorAction Stop
        $committed = $true
    }
    finally {
        if (-not $committed) {
            if ($promoted -and (Test-Path -LiteralPath $packageRoot)) { Remove-Item -LiteralPath $packageRoot -Recurse -Force }
            if ($backedUp) { Move-Item -LiteralPath $backup -Destination $packageRoot -ErrorAction Stop }
        }
        if ($committed -and (Test-Path -LiteralPath $backup)) { Remove-Item -LiteralPath $backup -Recurse -Force }
        if (Test-Path -LiteralPath $transaction) { Remove-Item -LiteralPath $transaction -Recurse -Force }
        if (Test-Path -LiteralPath $manifestTemp) { Remove-Item -LiteralPath $manifestTemp -Force }
    }
    [pscustomobject]@{
        PSTypeName='OSDStage.StagedApp'; Name='RegistrySettings'; PackageType='Script'; Source='Repository'
        WindowsPath=$target; StagedPath=$packageRoot; Version='1.0.0'; OperationCount=$operations.Count
        RegistryPath=$Operation.Path; RegistryView=$Operation.View; Action=$Operation.Action
    }
}