# 只读取 Windows Installer 已登记的同版本 Node.js 缓存;不执行安装、不访问网络。 $ErrorActionPreference = 'Stop' [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false) $expectedVersion = $env:AGC_STAGING_NODE_VERSION if ($expectedVersion -notmatch '^\d+\.\d+\.\d+$') { throw 'Invalid Node version' } # WinVerifyTrust 强制仅使用本地证书缓存,禁止吊销/证书 URL 网络检索。 Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public static class AgcOfflineSignature { [StructLayout(LayoutKind.Sequential)] struct FileInfo { public uint Size; public IntPtr Path; public IntPtr File; public IntPtr Subject; } [StructLayout(LayoutKind.Sequential)] struct TrustData { public uint Size; public IntPtr Policy; public IntPtr Sip; public uint Ui; public uint Revocation; public uint Choice; public IntPtr File; public uint StateAction; public IntPtr State; public IntPtr Url; public uint Flags; public uint Context; } [DllImport("wintrust.dll", ExactSpelling=true, PreserveSig=true)] static extern int WinVerifyTrust(IntPtr window, ref Guid action, ref TrustData data); public static bool Verify(string path) { IntPtr name = Marshal.StringToCoTaskMemUni(path); IntPtr file = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FileInfo))); try { var info = new FileInfo { Size=(uint)Marshal.SizeOf(typeof(FileInfo)), Path=name }; Marshal.StructureToPtr(info, file, false); var data = new TrustData { Size=(uint)Marshal.SizeOf(typeof(TrustData)), Ui=2, Choice=1, File=file, Flags=0x1000|0x10 }; var action = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); return WinVerifyTrust(new IntPtr(-1), ref action, ref data) == 0; } finally { Marshal.FreeHGlobal(file); Marshal.FreeCoTaskMem(name); } } } '@ function Read-Property($database, [string]$name) { $view = $database.OpenView("SELECT ``Value`` FROM ``Property`` WHERE ``Property`` = '$name'") try { [void]$view.Execute() $record = $view.Fetch() if ($null -ne $record) { return $record.StringData(1) } return '' } finally { [void]$view.Close() } } $installer = New-Object -ComObject WindowsInstaller.Installer $cacheRoot = [System.IO.Path]::GetFullPath((Join-Path ([Environment]::GetFolderPath('Windows')) 'Installer')) $registrations = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' ) $products = Get-ItemProperty $registrations -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq 'Node.js' -and $_.DisplayVersion -eq $expectedVersion -and $_.PSChildName -match '^\{[0-9A-Fa-f-]{36}\}$' } | Select-Object -ExpandProperty PSChildName -Unique foreach ($product in $products) { try { if ($installer.ProductInfo($product, 'ProductName') -ne 'Node.js') { continue } if ($installer.ProductInfo($product, 'VersionString') -ne $expectedVersion) { continue } $package = [System.IO.Path]::GetFullPath($installer.ProductInfo($product, 'LocalPackage')) if (-not [string]::Equals([System.IO.Path]::GetDirectoryName($package), $cacheRoot, [StringComparison]::OrdinalIgnoreCase)) { continue } if ([System.IO.Path]::GetExtension($package) -ne '.msi') { continue } $entry = Get-Item -LiteralPath $package -Force $cache = Get-Item -LiteralPath $cacheRoot -Force if (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -or ($cache.Attributes -band [IO.FileAttributes]::ReparsePoint)) { continue } if (-not [AgcOfflineSignature]::Verify($package)) { continue } $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromSignedFile($package)) if ($certificate.Subject -notmatch '(^|,\s*)O=OpenJS Foundation(,|$)') { continue } $database = $installer.OpenDatabase($package, 0) if ((Read-Property $database 'ProductName') -ne 'Node.js') { continue } if ((Read-Property $database 'ProductVersion') -ne $expectedVersion) { continue } if ((Read-Property $database 'ProductCode') -ne $product) { continue } $manufacturer = Read-Property $database 'Manufacturer' if ($manufacturer -notin @('Node.js Foundation', 'OpenJS Foundation')) { continue } $view = $database.OpenView('SELECT `Text` FROM `Control` WHERE `Dialog_` = ''LicenseAgreementDlg'' AND `Control` = ''LicenseText''') try { [void]$view.Execute() $record = $view.Fetch() if ($null -eq $record) { continue } $content = $record.StringData(1) } finally { [void]$view.Close() } if (-not $content.StartsWith('{\rtf') -or $content.Length -gt 1048576) { continue } if (-not $content.Contains('Node.js') -or -not $content.Contains('Permission is hereby granted')) { continue } [pscustomobject]@{ productName = 'Node.js'; version = $expectedVersion; manufacturer = $manufacturer signatureVerified = $true; signer = 'OpenJS Foundation'; format = 'rtf'; content = $content } | ConvertTo-Json -Compress exit 0 } catch { # 单个损坏/无权限缓存不能绕过验证;继续查找其它已登记候选。 continue } } throw 'No matching trusted installed Node.js license'