Public/Sync-OSDAppCMTraceOpen.ps1
|
function Sync-OSDAppCMTraceOpen { [CmdletBinding(SupportsShouldProcess)] param([ValidateSet('x64','arm64')][string]$Architecture='x64') if (Test-OSDAppWinPE) { throw 'Run CMTrace Open synchronization on full Windows.' } $cachePath = Get-OSDAppCachePath $target = Join-Path $cachePath ('BuiltIn\CMTraceOpen\' + $Architecture) if (-not $PSCmdlet.ShouldProcess($target, 'Synchronize official CMTrace Open MSI')) { return } [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $release = Invoke-RestMethod -Uri 'https://api.github.com/repos/adamgell/cmtraceopen/releases/latest' -Headers @{ 'User-Agent'='OSDApps'; Accept='application/vnd.github+json' } -ErrorAction Stop if ($release.draft -or $release.prerelease -or [string]$release.tag_name -notmatch '^v(?<Version>\d+\.\d+\.\d+)$') { throw 'CMTrace Open latest release is not a supported stable release.' } $version = $Matches.Version $name = 'CMTrace-Open_{0}_{1}.msi' -f $version,$Architecture $assets = @($release.assets | Where-Object { $_.name -ceq $name }) if ($assets.Count -ne 1) { throw ('Expected one MSI asset: {0}' -f $name) } $asset = $assets[0] $uri = 'https://github.com/adamgell/cmtraceopen/releases/download/v' + $version + '/' + $name if ([string]$asset.browser_download_url -cne $uri -or [string]$asset.digest -notmatch '^sha256:(?<Hash>[a-fA-F0-9]{64})$') { throw 'Unexpected release asset URI or missing official SHA-256 digest.' } $expected = $Matches.Hash.ToUpperInvariant() $active = Join-Path $target $name $legacy = Join-Path $target 'Package.msi' $infoPath = Join-Path $target 'CacheInfo.json' $info = if (Test-Path -LiteralPath $infoPath -PathType Leaf) { try { Get-Content -LiteralPath $infoPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { $null } } else { $null } # Migrate an earlier OSDApps cache that renamed the vendor MSI to Package.msi. # Keep the verified bytes and metadata; only restore the publisher's filename. $existing = if (Test-Path -LiteralPath $active -PathType Leaf) { $active } elseif (Test-Path -LiteralPath $legacy -PathType Leaf) { $legacy } else { $null } if ($info -and $info.Version -eq $version -and $info.Sha256 -eq $expected -and [string]$info.AssetName -ceq $name -and $existing -and (Get-FileHash -LiteralPath $existing -Algorithm SHA256).Hash -eq $expected) { $signature = Get-AuthenticodeSignature -FilePath $existing if ($signature.Status -ne 'Valid') { throw 'CMTrace Open cached MSI signature invalid.' } if ($existing -eq $legacy) { $renameLock = Enter-OSDAppCacheLock -CachePath $cachePath -Operation 'CMTrace Open vendor filename migration' try { if (Test-Path -LiteralPath $active -PathType Leaf) { throw 'Cannot migrate CMTrace Open: both legacy and vendor MSI files exist.' } Rename-Item -LiteralPath $legacy -NewName $name -ErrorAction Stop } finally { if ($renameLock) { $renameLock.Dispose() } } } return [pscustomobject]@{Id='CMTraceOpen';Version=$version;Architecture=$Architecture;Updated=$false;CachePath=$target} } $lock = Enter-OSDAppCacheLock -CachePath $cachePath -Operation 'CMTrace Open cache synchronization' $candidate = $target + '.osdapps-new-' + [guid]::NewGuid().ToString('N') $backup = $target + '.osdapps-old-' + [guid]::NewGuid().ToString('N') try { New-Item -ItemType Directory -Path $candidate -Force -ErrorAction Stop | Out-Null $msi = Join-Path $candidate $name Invoke-WebRequest -Uri $uri -UseBasicParsing -OutFile $msi -ErrorAction Stop if ((Get-FileHash -LiteralPath $msi -Algorithm SHA256).Hash -ne $expected) { throw 'Downloaded CMTrace Open MSI hash does not match GitHub release digest.' } $signature = Get-AuthenticodeSignature -FilePath $msi if ($signature.Status -ne 'Valid') { throw ('MSI Authenticode validation failed: {0}' -f $signature.Status) } [ordered]@{ Id='CMTraceOpen'; Version=$version; Architecture=$Architecture Sha256=$expected; AssetName=$name; SourceUri=$uri Signer=[string]$signature.SignerCertificate.Subject SyncedAt=(Get-Date).ToUniversalTime().ToString('o') } | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $candidate 'CacheInfo.json') -Encoding UTF8 if (Test-Path -LiteralPath $target) { Move-Item -LiteralPath $target -Destination $backup -ErrorAction Stop } try { Move-Item -LiteralPath $candidate -Destination $target -ErrorAction Stop } catch { if (Test-Path -LiteralPath $backup) { Move-Item -LiteralPath $backup -Destination $target -ErrorAction SilentlyContinue } throw } if (Test-Path -LiteralPath $backup) { Remove-Item -LiteralPath $backup -Recurse -Force } [pscustomobject]@{Id='CMTraceOpen';Version=$version;Architecture=$Architecture;Updated=$true;CachePath=$target} } finally { if (Test-Path -LiteralPath $candidate) { Remove-Item -LiteralPath $candidate -Recurse -Force -ErrorAction SilentlyContinue } if ($lock) { $lock.Dispose() } } } |