Private/setup-accessibility-checker.ps1
|
<# .SYNOPSIS One-time setup: fetches the Open XML SDK DLLs into scripts/lib/. .DESCRIPTION Downloads DocumentFormat.OpenXml (and required transitive deps) directly from nuget.org as .nupkg archives, extracts the runtime DLLs into scripts/lib/, and cleans up the temp folder. Idempotent — re-running is a no-op when the pinned DLL already exists. .NOTES Pinned versions are the single source of truth for SDK versions used by all check-*-accessibility.ps1 scripts. Bump $Packages to upgrade. #> [CmdletBinding()] param( [switch] $Force ) $ErrorActionPreference = 'Stop' $InformationPreference = 'Continue' # Pinned packages. DocumentFormat.OpenXml v3 split the assembly — the # Framework package is a transitive dependency we must also fetch. # # Sha512 is the SHA-512 of the .nupkg as published on nuget.org. TLS + version # pinning alone do not prove the bytes match the expected artifact (compromised # feed, proxy cache, or local trust-store issue could swap content), so each # download is hashed and rejected if it does not match. $Packages = @( @{ Name = 'DocumentFormat.OpenXml' Version = '3.0.2' Sha512 = 'd521e5e470ae0ca9a00742f43a005ca21ca415190a511d478e3c40e2110b77335422dbffeff8283f70169ddbbd0b91ad26e385be56ed2eb96bffc7316230a501' } @{ Name = 'DocumentFormat.OpenXml.Framework' Version = '3.0.2' Sha512 = 'e124628d7e7e1c84774bd3f0244c1c96b71c5e19f608c218402d440f9f2aad102296a0a38f4e6f9fd16de48cef0b0d5f6c66dd33a17a4dfafe08efb16c3b8aa4' } ) $LibDir = Join-Path $PSScriptRoot 'lib' $MainDll = Join-Path $LibDir 'DocumentFormat.OpenXml.dll' if ((Test-Path $MainDll) -and -not $Force) { Write-Information "Open XML SDK already present at $LibDir (use -Force to refresh)." exit 0 } if (-not (Test-Path $LibDir)) { New-Item -ItemType Directory -Path $LibDir | Out-Null } $tempDir = Join-Path ([IO.Path]::GetTempPath()) ("openxml-sdk-fetch-" + [Guid]::NewGuid()) New-Item -ItemType Directory -Path $tempDir | Out-Null # Pick a target framework folder compatible with the host runtime. # # Windows PowerShell 5.1 (PSEdition 'Desktop') runs on .NET Framework 4.x. # When a package ships a net4x folder, prefer it over netstandard2.0: the # net4x build is self-contained, whereas netstandard2.0 on .NET Framework # pulls in NuGet shims (e.g. System.IO.Packaging) that we'd otherwise have # to bundle. Modern pwsh (PSEdition 'Core') prefers netstandard2.0 because # the .NET runtime resolves those shims natively. # # Either edition can also load net5+ folders when the host runtime version # matches, so we keep a host-major fallback for packages that ship only # modern targets. function Select-BestFramework { param([string[]] $FrameworkNames) $isDesktop = $PSVersionTable.PSEdition -eq 'Desktop' # On Windows PowerShell 5.1, a net4x build is the cleanest match — no # transitive deps, no shim assemblies. Pick the highest net4* available. if ($isDesktop) { $net4 = foreach ($n in $FrameworkNames) { if ($n -match '^net4(\d+)$') { [pscustomobject]@{ Name = $n; Sub = [int]$Matches[1] } } } if ($net4) { return ($net4 | Sort-Object -Property Sub -Descending | Select-Object -First 1).Name } # net46 is conventionally written 'net46' (no minor), handle that too. if ($FrameworkNames -contains 'net46') { return 'net46' } if ($FrameworkNames -contains 'net45') { return 'net45' } if ($FrameworkNames -contains 'net40') { return 'net40' } if ($FrameworkNames -contains 'net35') { return 'net35' } } if ($FrameworkNames -contains 'netstandard2.0') { return 'netstandard2.0' } if ($FrameworkNames -contains 'netstandard2.1') { return 'netstandard2.1' } # netstandard1.x is loadable on .NET Framework 4.6.1+ and netcoreapp 2.0+. $netstandard = $FrameworkNames | Where-Object { $_ -match '^netstandard\d' } | Sort-Object -Descending if ($netstandard) { return $netstandard | Select-Object -First 1 } # Only net*/lib targets remain. Pick the highest major <= the host runtime. $hostMajor = if ($isDesktop) { 4 } else { [Environment]::Version.Major } $compatible = foreach ($n in $FrameworkNames) { if ($n -match '^net(\d+)(?:\.(\d+))?') { $major = [int]$Matches[1] # Folder names like "net48" / "net472" are .NET Framework 4.x. if ($major -ge 5 -and $major -le $hostMajor) { [pscustomobject]@{ Name = $n; Major = $major } } elseif ($major -eq 4 -and $hostMajor -eq 4) { [pscustomobject]@{ Name = $n; Major = $major } } } } if ($compatible) { return ($compatible | Sort-Object -Property Major -Descending | Select-Object -First 1).Name } # Nothing compatible — let the caller fail loudly rather than copy a # DLL that will only error at Add-Type time. return $null } try { $copied = @{} foreach ($pkg in $Packages) { $name = $pkg.Name $version = $pkg.Version $url = "https://www.nuget.org/api/v2/package/$name/$version" $nupkg = Join-Path $tempDir "$name.$version.nupkg" $extract = Join-Path $tempDir "$name.$version" Write-Information "Downloading $name $version..." Invoke-WebRequest -Uri $url -OutFile $nupkg -UseBasicParsing $expected = $pkg.Sha512 if ([string]::IsNullOrWhiteSpace($expected)) { throw "No pinned SHA-512 for $name $version. Refusing to install an unverified package." } # Get-FileHash returns uppercase; pinned values are lowercase. $actual = (Get-FileHash -LiteralPath $nupkg -Algorithm SHA512).Hash if ($actual -ine $expected) { throw "SHA-512 mismatch for $name $version.`nExpected: $expected`nActual: $actual" } Write-Information "Extracting $name..." # Windows PowerShell 5.1's Expand-Archive rejects any file whose # extension is not literally .zip — a .nupkg is just a zip, so we # rename in place first. pwsh 7+ is permissive but accepts this too. $zip = [IO.Path]::ChangeExtension($nupkg, '.zip') Move-Item -LiteralPath $nupkg -Destination $zip Expand-Archive -LiteralPath $zip -DestinationPath $extract -Force $libRoot = Join-Path $extract 'lib' if (-not (Test-Path $libRoot)) { throw "Package $name does not contain a lib/ directory." } $frameworks = Get-ChildItem -Path $libRoot -Directory | ForEach-Object Name $best = Select-BestFramework -FrameworkNames $frameworks if (-not $best) { throw "Package $name has no usable framework folder under lib/." } $bestPath = Join-Path $libRoot $best Write-Information " Using $best" Get-ChildItem -Path $bestPath -Filter '*.dll' | ForEach-Object { $dest = Join-Path $LibDir $_.Name Copy-Item -Path $_.FullName -Destination $dest -Force $copied[$_.Name] = $true } } if (-not (Test-Path $MainDll)) { throw "DocumentFormat.OpenXml.dll not found after extraction. Inspect $tempDir." } Write-Information "" Write-Information "Open XML SDK installed to $LibDir" Write-Information "Files:" $copied.Keys | Sort-Object | ForEach-Object { Write-Information " $_" } } finally { if (Test-Path $tempDir) { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } } |