Runtime/OSDApp7Zip.ps1
|
# Shared by the module, PreInstall and Runner. Loading this file performs no I/O. function Get-OSDApp7ZipAssetName { param([Parameter(Mandatory)][string]$Version, [Parameter(Mandatory)][ValidateSet('x64','x86','arm64')][string]$Architecture) if ($Version -notmatch '^\d{2}\.\d{2}$') { throw 'Unsupported 7Zip release version format.' } $suffix = switch ($Architecture) { 'x64' { '-x64' } 'arm64' { '-arm64' } 'x86' { '' } } '7z{0}{1}.exe' -f $Version.Replace('.',''), $suffix } function Get-OSDApp7ZipRelease { [CmdletBinding()] param([Parameter(Mandatory)][ValidateSet('x64','x86','arm64')][string]$Architecture) $release = Invoke-RestMethod -Uri 'https://api.github.com/repos/ip7z/7zip/releases/latest' -Headers @{ Accept='application/vnd.github+json'; 'User-Agent'='OSDApps' } -TimeoutSec 60 -ErrorAction Stop if ($release.draft -ne $false -or $release.prerelease -ne $false) { throw '7Zip stable release metadata is invalid.' } $version = [string]$release.tag_name $name = Get-OSDApp7ZipAssetName -Version $version -Architecture $Architecture $assets = @($release.assets | Where-Object { [string]$_.name -ceq $name }) if ($assets.Count -ne 1) { throw "7Zip release must contain exactly one '$name' asset." } $asset = $assets[0] $expectedUri = "https://github.com/ip7z/7zip/releases/download/$version/$name" if ([string]$asset.browser_download_url -cne $expectedUri -or [string]$asset.digest -notmatch '^sha256:[a-fA-F0-9]{64}$' -or [long]$asset.size -le 0) { throw '7Zip release asset has an unexpected URL, missing SHA-256 or invalid size.' } [pscustomobject]@{ Version=$version; Architecture=$Architecture; PackageUri=$expectedUri; Sha256=([string]$asset.digest).Substring(7); Size=[long]$asset.size } } function Assert-OSDApp7ZipGeneration { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Root, [Parameter(Mandatory)][ValidateSet('x64','x86','arm64')][string]$Architecture, [psobject]$Release ) $info = Get-Content -LiteralPath (Join-Path $Root 'CacheInfo.json') -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $name = Get-OSDApp7ZipAssetName -Version ([string]$info.Version) -Architecture $Architecture $expectedUri = "https://github.com/ip7z/7zip/releases/download/$($info.Version)/$name" if ([string]$info.Id -ne '7Zip' -or [string]$info.Architecture -ne $Architecture -or [string]$info.PackageUri -cne $expectedUri -or [string]$info.Sha256 -notmatch '^[a-fA-F0-9]{64}$' -or [long]$info.Size -le 0) { throw '7Zip cache metadata is invalid or incompatible.' } if ($Release -and ([string]$info.Version -ne [string]$Release.Version -or [string]$info.Sha256 -ne [string]$Release.Sha256 -or [long]$info.Size -ne [long]$Release.Size -or [string]$info.PackageUri -cne [string]$Release.PackageUri)) { throw '7Zip cache does not match the current official release.' } $package = Join-Path $Root 'Package.exe' if ((Get-Item -LiteralPath $package -ErrorAction Stop).Length -ne [long]$info.Size -or (Get-FileHash -LiteralPath $package -Algorithm SHA256 -ErrorAction Stop).Hash -ne [string]$info.Sha256) { throw '7Zip package size or SHA-256 does not match the recorded release.' } # Validate the PE architecture as well as the release asset mapping. $stream = [IO.File]::OpenRead($package) $reader = [IO.BinaryReader]::new($stream) try { if ($stream.Length -lt 64 -or $reader.ReadUInt16() -ne 0x5A4D) { throw '7Zip package is not a Windows executable.' } $stream.Position = 60 $offset = $reader.ReadInt32() if ($offset -lt 64 -or $offset -gt ($stream.Length - 6)) { throw '7Zip PE header is invalid.' } $stream.Position = $offset if ($reader.ReadUInt32() -ne 0x4550) { throw '7Zip PE signature is invalid.' } $expectedMachine = switch ($Architecture) { 'x64' { 0x8664 } 'x86' { 0x14c } 'arm64' { 0xaa64 } } if ($reader.ReadUInt16() -ne $expectedMachine) { throw '7Zip executable architecture does not match the requested architecture.' } } finally { $reader.Dispose(); $stream.Dispose() } $info } function Sync-OSDApp7ZipGeneration { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Root, [Parameter(Mandatory)][ValidateSet('x64','x86','arm64')][string]$Architecture, [Parameter(Mandatory)][scriptblock]$Download, [ValidateRange(0, [double]::MaxValue)][double]$MinimumFreeSpaceGB = 1 ) # Callback is invoked from the transaction build scope. $downloadAction = $Download Restore-OSDAppCacheGeneration -Root $Root -Validate { param($candidate) Assert-OSDApp7ZipGeneration -Root $candidate -Architecture $Architecture } | Out-Null $release = Get-OSDApp7ZipRelease -Architecture $Architecture $current = $false try { Assert-OSDApp7ZipGeneration -Root $Root -Architecture $Architecture -Release $release | Out-Null; $current = $true } catch { Write-Verbose "7Zip cache requires acquisition: $($_.Exception.Message)" } if (-not $current) { Invoke-OSDAppBuiltInCacheTransaction -Root $Root -ExpectedBytes $release.Size -MinimumFreeSpaceGB $MinimumFreeSpaceGB -Build { param($candidate) & $downloadAction $release.PackageUri (Join-Path $candidate 'Package.exe') | Out-Null [ordered]@{ Id='7Zip'; Cached=$true; Version=$release.Version; VersionSource='GitHubRelease'; Architecture=$Architecture; PackageUri=$release.PackageUri; Sha256=$release.Sha256; Size=$release.Size; SourcePolicy='Evergreen'; SyncMethod='ReleaseSha256'; SyncedAt=(Get-Date).ToUniversalTime().ToString('o') } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $candidate 'CacheInfo.json') -Encoding UTF8 } -Validate { param($candidate) Assert-OSDApp7ZipGeneration -Root $candidate -Architecture $Architecture -Release $release } | Out-Null } [pscustomobject]@{ Id='7Zip'; Version=$release.Version; Architecture=$Architecture; Root=$Root; Updated=(-not $current) } } |