Public/Add-OSDAppCMTraceOpen.ps1

function Add-OSDAppCMTraceOpen {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [ValidateSet('x64','arm64')][string]$Architecture = 'x64',
        [bool]$DisableUpdateChecks = $true,
        [ValidateRange(1,120)][int]$InstallTimeoutMinutes = 10,
        [string]$WindowsPath
    )

    $target = Resolve-OSDAppWindowsPath -WindowsPath $WindowsPath
    $cache = Get-OSDAppCachePath
    $source = Join-Path $cache ('BuiltIn\CMTraceOpen\' + $Architecture)
    $infoPath = Join-Path $source 'CacheInfo.json'
    if (-not (Test-Path -LiteralPath $infoPath -PathType Leaf)) {
        throw 'CMTrace Open is not cached. Run Sync-OSDAppCMTraceOpen first.'
    }

    $info = Get-Content -LiteralPath $infoPath -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
    if ($info.Id -ne 'CMTraceOpen' -or $info.Architecture -ne $Architecture -or
        [string]$info.Version -notmatch '^\d+\.\d+\.\d+$' -or
        [string]$info.Sha256 -notmatch '^[a-fA-F0-9]{64}$') {
        throw 'Invalid CMTrace Open cache metadata.'
    }

    $vendorFileName = 'CMTrace-Open_{0}_{1}.msi' -f $info.Version,$Architecture
    if ([string]$info.AssetName -cne $vendorFileName) {
        throw 'CMTrace Open cached asset name differs from the official release.'
    }

    $sourceMsi = Join-Path $source $vendorFileName
    if (-not (Test-Path -LiteralPath $sourceMsi -PathType Leaf)) {
        # Legacy cache support: OSDApps previously renamed the verified vendor MSI.
        $sourceMsi = Join-Path $source 'Package.msi'
    }
    if (-not (Test-Path -LiteralPath $sourceMsi -PathType Leaf) -or
        (Get-FileHash -LiteralPath $sourceMsi -Algorithm SHA256).Hash -ne [string]$info.Sha256) {
        throw 'CMTrace Open cached MSI is missing or failed SHA-256 verification.'
    }

    # Sync (full Windows) validates GitHub's release SHA-256 and Authenticode.
    # SetupComplete validates SHA-256 and Authenticode again. WinPE only stages
    # content already matched to the signed-release cache hash.
    $stage = Join-Path $target 'Windows\Temp\OSDApps'
    $relativeRoot = 'BuiltIn\CMTraceOpen\' + $Architecture
    $destination = Join-Path $stage $relativeRoot
    if (-not $PSCmdlet.ShouldProcess($destination, 'Stage verified CMTrace Open MSI')) { return }

    New-Item -ItemType Directory -Path (Split-Path $destination -Parent) -Force -ErrorAction Stop | Out-Null
    if (Test-Path -LiteralPath $destination) {
        Remove-Item -LiteralPath $destination -Recurse -Force -ErrorAction Stop
    }
    New-Item -ItemType Directory -Path $destination -Force -ErrorAction Stop | Out-Null
    $stagedMsi = Join-Path $destination $vendorFileName
    Copy-Item -LiteralPath $sourceMsi -Destination $stagedMsi -Force -ErrorAction Stop
    Copy-Item -LiteralPath $infoPath -Destination (Join-Path $destination 'CacheInfo.json') -Force -ErrorAction Stop
    if ((Get-FileHash -LiteralPath $stagedMsi -Algorithm SHA256).Hash -ne [string]$info.Sha256) {
        throw 'CMTrace Open staged MSI failed SHA-256 verification.'
    }

    $manifestPath = Join-Path $stage 'DeviceManifest.json'
    $manifest = if (Test-Path -LiteralPath $manifestPath -PathType Leaf) {
        Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
    }
    else {
        [pscustomobject]@{SchemaVersion=1;StagedAt=(Get-Date).ToUniversalTime().ToString('o');Apps=@()}
    }
    $app = [pscustomobject]@{
        Id='CMTraceOpen'; DisplayName='CMTrace Open'; Source='BuiltIn'
        PackageType='Application'; Type='VendorMsi'; Vendor='CMTrace Open'
        Version=[string]$info.Version; Architecture=$Architecture
        Package=(Join-Path $relativeRoot $vendorFileName)
        ExpectedSha256=[string]$info.Sha256
        MsiProperties=@(if ($DisableUpdateChecks) { 'DISABLEUPDATECHECKS=1' })
        InstallTimeoutMinutes=$InstallTimeoutMinutes
    }
    $manifest = Set-OSDAppManifestApp -Manifest $manifest -App $app
    $manifest = Set-OSDAppManifestRuntimeConfiguration -Manifest $manifest
    $manifest.StagedAt = (Get-Date).ToUniversalTime().ToString('o')
    $manifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $manifestPath -Encoding UTF8 -ErrorAction Stop
    Add-OSDAppSetupComplete -WindowsPath $target -StagedRelativePath 'Windows\Temp\OSDApps' -Confirm:$false | Out-Null

    [pscustomobject]@{
        PSTypeName='OSDApps.StagedApp'
        Name='CMTraceOpen'; Version=[string]$info.Version; Source='BuiltIn'
        PackageType='Application'; WindowsPath=$target; StagedPath=$destination
    }
}