Private/Utilities/Common.ps1
|
function Get-VSOProperty { param($Object, [string]$Name, $Default = $null) if ($null -eq $Object) { return $Default } if ($Object -is [Collections.IDictionary]) { if ($Object.Contains($Name)) { return $Object[$Name] }; return $Default } $p = $Object.PSObject.Properties[$Name] if ($null -eq $p) { return $Default } return $p.Value } function Assert-VSOPlatform { param($Context) if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitOperatingSystem) { Throw-VSOError $Context Platform } if (-not [Environment]::Is64BitProcess) { Throw-VSOError $Context Platform } # Windows 11 client uses build 22000 or later. Server has a separate product type. $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop if ([int]$os.BuildNumber -lt 22000 -or [int]$os.ProductType -ne 1) { Throw-VSOError $Context Platform } $processors=@(Get-CimInstance -ClassName Win32_Processor -Property Architecture -ErrorAction Stop) # Win32_Processor distinguishes x64 (9) from ARM64 (12), including emulation. if ($processors.Count -eq 0 -or @($processors|Where-Object {[int]$_.Architecture -ne 9}).Count -gt 0) { Throw-VSOError $Context Platform } } function Test-VSOAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() try { return ([Security.Principal.WindowsPrincipal]::new($identity)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } finally { $identity.Dispose() } } function Assert-VSOQuietConfirmation { param($Context,$Parameters) if ($Context.Quiet -and $Parameters.ContainsKey('Confirm') -and $Parameters['Confirm']) { Throw-VSOError $Context QuietConfirm } } function Get-VSOSystemDirectory { param($Context) $path=[Environment]::GetFolderPath([Environment+SpecialFolder]::System) if (-not $path -or -not [IO.Directory]::Exists($path)) { Throw-VSOError $Context Platform } Assert-VSONoReparse $path $Context return $path } function Write-VSOProgress { param($Context, [string]$Key, [long]$Done = -1, [long]$Total = -1, [switch]$Completed) if ($Context.Quiet) { return } if ($Completed) { Write-Progress -Id 732 -ParentId 731 -Activity $Context.Text[$Key] -Completed Write-Progress -Id 731 -Activity $Context.Text.Activity -Completed return } Write-Progress -Id 731 -Activity $Context.Text.Activity -Status $Context.Text[$Key] $p = @{ Id = 732; ParentId = 731; Activity = $Context.Text[$Key]; Status = $Context.Text[$Key] } if ($Done -ge 0 -and $Total -gt 0) { $p.PercentComplete = [int][Math]::Min(100, [Math]::Floor(100.0 * $Done / $Total)) } else { $p.PercentComplete = -1 } Write-Progress @p } function Read-VSOJson { param([string]$Path, $Context, [int]$MaximumBytes = 2097152) Assert-VSONoReparse $Path $Context $file = Get-Item -LiteralPath $Path -Force -ErrorAction Stop if ($file.PSIsContainer -or $file.Length -gt $MaximumBytes) { Throw-VSOError $Context Metadata } try { return ([IO.File]::ReadAllText($Path, [Text.Encoding]::UTF8) | ConvertFrom-Json -ErrorAction Stop) } catch { Throw-VSOError $Context Metadata @($_.Exception.Message) } } function Write-VSOJson { param([string]$Path, $Value, $Context) Assert-VSONoReparse $Path $Context $temp = $Path + '.' + [guid]::NewGuid().ToString('N') + '.tmp' try { [IO.File]::WriteAllText($temp, ($Value | ConvertTo-Json -Depth 15), [Text.UTF8Encoding]::new($false)) # Windows PowerShell converts $null to an empty String for this overload. # NullString preserves the native null backup name required by File.Replace. if ([IO.File]::Exists($Path)) { [IO.File]::Replace($temp, $Path, [Management.Automation.Language.NullString]::Value) } else { [IO.File]::Move($temp, $Path) } } finally { if ([IO.File]::Exists($temp)) { [IO.File]::Delete($temp) } } } function Get-VSOHash { param([string]$Path) return (Get-FileHash -LiteralPath $Path -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant() } function Get-VSOSourceTextHash { param([string]$Path,$Context) Assert-VSONoReparse $Path $Context if (([IO.FileInfo]::new($Path)).Length -gt 16777216) { Throw-VSOError $Context Integrity @('source text size') } # Upstream's protected memory host hashes UTF-8 source text without the disk BOM. # This identity is distinct from the package/receipt byte hash. $text=[IO.File]::ReadAllText($Path,[Text.Encoding]::UTF8) $sha=[Security.Cryptography.SHA256]::Create() try { return ([BitConverter]::ToString($sha.ComputeHash(([Text.UTF8Encoding]::new($false)).GetBytes($text)))).Replace('-','').ToLowerInvariant() } finally { $sha.Dispose() } } function ConvertTo-VSOVersion { param([string]$Version, $Context) $value = $Version -replace '^[vV]', '' if ($value -notmatch '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z.-]+))?$') { Throw-VSOError $Context Version @($Version) } $major=[string]$Matches[1]; $minor=[string]$Matches[2]; $patch=[string]$Matches[3] $pre = [string]$Matches[4] foreach ($piece in @($pre -split '\.')) { if ($piece -match '^0[0-9]+$') { Throw-VSOError $Context Version @($Version) } } return [pscustomobject]@{ Text=$value; Major=$major; Minor=$minor; Patch=$patch; Pre=$pre } } function Compare-VSOVersion { param([string]$Left, [string]$Right, $Context) $a = ConvertTo-VSOVersion $Left $Context; $b = ConvertTo-VSOVersion $Right $Context foreach ($part in @('Major','Minor','Patch')) { $comparison=Compare-VSONumeric $a.$part $b.$part; if ($comparison) { return $comparison } } if ($a.Pre -ceq $b.Pre) { return 0 }; if (-not $a.Pre) { return 1 }; if (-not $b.Pre) { return -1 } $x = @($a.Pre -split '\.'); $y = @($b.Pre -split '\.') for ($i=0; $i -lt [Math]::Min($x.Count,$y.Count); $i++) { if ($x[$i] -ceq $y[$i]) { continue } $xn=$x[$i] -match '^\d+$'; $yn=$y[$i] -match '^\d+$' if ($xn -and $yn) { return (Compare-VSONumeric $x[$i] $y[$i]) } if ($xn) { return -1 }; if ($yn) { return 1 } return [Math]::Sign([StringComparer]::Ordinal.Compare($x[$i],$y[$i])) } return [Math]::Sign($x.Count-$y.Count) } function Compare-VSONumeric { param([string]$Left,[string]$Right) if ($Left.Length -ne $Right.Length) { return [Math]::Sign($Left.Length-$Right.Length) } return [Math]::Sign([StringComparer]::Ordinal.Compare($Left,$Right)) } |