bridgerator 1.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -15
- package/assets/bridgerator.ico +0 -0
- package/assets/icon-16.png +0 -0
- package/assets/icon-20.png +0 -0
- package/assets/icon-24.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon-32.png +0 -0
- package/assets/icon-48.png +0 -0
- package/assets/icon-64.png +0 -0
- package/assets/logo.svg +1 -0
- package/install.ps1 +465 -43
- package/npm-shrinkwrap.json +8621 -0
- package/package.json +55 -49
- package/recipes/backup-folder.json +31 -0
- package/recipes/clean-downloads.json +48 -0
- package/recipes/disk-report.json +66 -0
- package/recipes/sort-folder.json +49 -0
- package/recipes/update-apps.json +25 -0
- package/src/ask-lib.ps1 +230 -0
- package/src/ask.mjs +51 -0
- package/src/ask.ps1 +17 -0
- package/src/automations.mjs +673 -0
- package/src/batch.mjs +221 -0
- package/src/bridge.mjs +743 -29
- package/src/cli.mjs +561 -53
- package/src/confirm.mjs +418 -0
- package/src/dc-config-hooks.mjs +18 -8
- package/src/dc-isolate.mjs +5 -0
- package/src/delegate.mjs +555 -0
- package/src/docs-tool.mjs +494 -0
- package/src/doctor.mjs +200 -0
- package/src/events.mjs +21 -0
- package/src/extra-tools.mjs +122 -0
- package/src/guard.mjs +34 -0
- package/src/notice.mjs +86 -23
- package/src/pair.mjs +41 -6
- package/src/pc-check.mjs +112 -0
- package/src/pc-check.ps1 +103 -0
- package/src/pc-text.mjs +94 -0
- package/src/pdf-font.mjs +245 -0
- package/src/photos-tool.mjs +187 -0
- package/src/photos.ps1 +145 -0
- package/src/plan.mjs +90 -0
- package/src/policy.mjs +293 -109
- package/src/queue.mjs +194 -0
- package/src/recent.mjs +48 -0
- package/src/recipes.mjs +108 -0
- package/src/recycle.mjs +185 -0
- package/src/recycle.ps1 +110 -0
- package/src/screen-tool.mjs +185 -0
- package/src/screen.ps1 +285 -0
- package/src/search-tool.mjs +189 -0
- package/src/search.ps1 +129 -0
- package/src/settings-tool.mjs +186 -0
- package/src/setup-tool.mjs +129 -0
- package/src/software-tool.mjs +185 -0
- package/src/telemetry.mjs +86 -0
- package/src/tool-lib.mjs +177 -0
- package/src/tool-text.mjs +45 -0
- package/src/tray.mjs +97 -0
- package/src/tray.ps1 +359 -0
- package/src/undo.mjs +313 -0
- package/src/win.mjs +17 -0
- package/ui/app.css +388 -0
- package/ui/app.js +868 -0
- package/ui/i18n/en.json +598 -0
- package/ui/i18n/ru.json +598 -0
- package/ui/index.html +26 -0
- package/vendor/webview2/LICENSE.txt +27 -0
- package/vendor/webview2/Microsoft.Web.WebView2.Core.dll +0 -0
- package/vendor/webview2/Microsoft.Web.WebView2.WinForms.dll +0 -0
- package/vendor/webview2/NOTICE.txt +85 -0
- package/vendor/webview2/SHA256SUMS.txt +4 -0
- package/vendor/webview2/SOURCE.txt +1 -0
- package/vendor/webview2/arm64/WebView2Loader.dll +0 -0
- package/vendor/webview2/x64/WebView2Loader.dll +0 -0
package/install.ps1
CHANGED
|
@@ -1,84 +1,506 @@
|
|
|
1
1
|
# Bridgerator installer for Windows (PowerShell 5.1+). Source: npm package "bridgerator" (https://www.npmjs.com/package/bridgerator)
|
|
2
2
|
# Usage (one line, from your Bridgerator account page):
|
|
3
3
|
# & ([scriptblock]::Create((irm https://bridgerator.recondeck.com/install.ps1)))
|
|
4
|
-
# A browser tab opens: sign in and confirm this computer
|
|
4
|
+
# A browser tab opens: sign in and confirm this computer - no key to paste. Run again to update
|
|
5
5
|
# (keeps the key and folders). Servers / headless PCs: add -Token <device key> from the account page.
|
|
6
|
-
# What it does:
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# What it does: downloads the signed Bridgerator app for Windows (bridgerator.exe + its files, no Node.js
|
|
7
|
+
# needed) from bridgerator.recondeck.com, checks it (SHA256 from the download list, every file), puts it
|
|
8
|
+
# into %LOCALAPPDATA%\Bridgerator\app, then runs `bridgerator.exe setup` (pairing in the browser if there
|
|
9
|
+
# is no key yet, config + hidden autostart at logon for THIS user, no admin rights),
|
|
9
10
|
# then opens the welcome page in the default browser (skip with -NoBrowser).
|
|
11
|
+
# An older Bridgerator (npm package, 1.0.x) is replaced; its key and settings are kept.
|
|
12
|
+
# -UseNpm: the npm package instead (installs Node.js 24 LTS if there is no Node.js 22+ - portable zip, no
|
|
13
|
+
# admin - then the bridgerator package, SHA256-checked, no install scripts).
|
|
14
|
+
# -NoTelemetry: no anonymous usage counts (see https://bridgerator.recondeck.com/privacy).
|
|
15
|
+
# Works under the default Restricted execution policy: only built-in cmdlets and .NET are used.
|
|
10
16
|
param(
|
|
11
17
|
[string]$Token,
|
|
12
|
-
[string]$Package
|
|
18
|
+
[string]$Package,
|
|
19
|
+
[string]$Sha256,
|
|
20
|
+
[string]$AppZip,
|
|
13
21
|
[string[]]$Allow,
|
|
14
22
|
[string[]]$Deny,
|
|
15
|
-
[switch]$
|
|
23
|
+
[switch]$UseNpm,
|
|
24
|
+
[switch]$NoBrowser,
|
|
25
|
+
[switch]$NoTelemetry
|
|
16
26
|
)
|
|
17
27
|
$ErrorActionPreference = 'Stop'
|
|
18
|
-
|
|
28
|
+
# SHA256 of the bridgerator.tgz published next to this script (written by release.sh).
|
|
29
|
+
$PackageSha256 = 'b6ffe62e7db46474024f0b40e4a24401640cf98e7115064bc35a8818b4ff660b'
|
|
30
|
+
# SHA256 of bridgerator-win.json, the Windows app download list published next to this script
|
|
31
|
+
# (written by scripts/build-exe.mjs / release.sh). It names the zip per architecture and its SHA256.
|
|
32
|
+
$WinManifestSha256 = 'fe43cedaf2d078c72685b3cb5985848f0ddb52b94a5738b101056875b7a67efc'
|
|
33
|
+
$Site = 'https://bridgerator.recondeck.com'
|
|
34
|
+
# The npm package comes from the same site as this script (a staging copy rewrites only $Site / $Relay).
|
|
35
|
+
$DefaultPackage = "$Site/bridgerator.tgz"
|
|
36
|
+
if (-not $Package) { $Package = $DefaultPackage }
|
|
37
|
+
# Relay for setup; empty = the bridge's default (prod). Only a staging build of this script sets both lines.
|
|
38
|
+
$Relay = ''
|
|
39
|
+
$Root = Join-Path $env:LOCALAPPDATA 'Bridgerator'
|
|
40
|
+
$AppDir = Join-Path $Root 'app'
|
|
41
|
+
$NpmPkg = Join-Path $env:APPDATA 'npm\node_modules\bridgerator'
|
|
42
|
+
# The npm package: asked for (-UseNpm), or a package given (-Package <tgz>, as before 1.0.9).
|
|
43
|
+
$npmMode = [bool]$UseNpm -or $PSBoundParameters.ContainsKey('Package')
|
|
44
|
+
$firstInstall = -not (Test-Path (Join-Path $env:APPDATA 'Bridgerator\config.json'))
|
|
45
|
+
$paired = $Token -or -not $firstInstall
|
|
46
|
+
# All temporary files go here, never to %TEMP%: on a Cyrillic & co. user name %TEMP% is an 8.3 short
|
|
47
|
+
# path (C:\Users\3C8A~1\...) that Move-Item / Remove-Item can't handle. Removed in the end, always.
|
|
48
|
+
$Work = Join-Path $env:LOCALAPPDATA "Bridgerator\tmp-$([guid]::NewGuid().ToString('N'))"
|
|
49
|
+
function Remove-Work { try { if ([IO.Directory]::Exists($Work)) { [IO.Directory]::Delete($Work, $true) } } catch {} }
|
|
50
|
+
$NpmFlags = @('--no-audit', '--no-fund', '--update-notifier=false', '--loglevel=error')
|
|
51
|
+
$Sys32 = Join-Path $env:SystemRoot 'System32'
|
|
19
52
|
|
|
53
|
+
# Private Node.js (portable zip) used when there is no Node.js 22+; it goes FIRST on the PATH so an
|
|
54
|
+
# old system Node does not shadow it.
|
|
55
|
+
$NodeDir = Join-Path $env:LOCALAPPDATA 'Bridgerator\node'
|
|
20
56
|
function Refresh-Path {
|
|
21
|
-
$
|
|
57
|
+
$first = if (Test-Path (Join-Path $NodeDir 'node.exe')) { $NodeDir }
|
|
58
|
+
$env:Path = (@($first, [Environment]::GetEnvironmentVariable('Path', 'Machine'), [Environment]::GetEnvironmentVariable('Path', 'User')) | Where-Object { $_ }) -join ';'
|
|
59
|
+
}
|
|
60
|
+
# Where a 1.0.x npm package can be: npm's usual global folder, or the installer's private Node.js (its
|
|
61
|
+
# global folder is the Node folder itself). -> the package folders that exist.
|
|
62
|
+
function Get-NpmBridges { @($NpmPkg, (Join-Path $NodeDir 'node_modules\bridgerator')) | Where-Object { [IO.File]::Exists((Join-Path $_ 'package.json')) } }
|
|
63
|
+
# One spelling per folder: full path, / -> \, no trailing \ (except a drive root: C:\), 8.3 short parts (C:\Users\3C8A~1) made long.
|
|
64
|
+
# npm's prefix and %APPDATA% can name the same folder differently; compared case-insensitively (Test-SamePath).
|
|
65
|
+
function Get-LongPath([string]$p) {
|
|
66
|
+
$p = [IO.Path]::GetFullPath($p.Replace('/', '\')).TrimEnd('\')
|
|
67
|
+
if ($p -match '^[A-Za-z]:$') { $p += '\' } # a drive root keeps its \ (C: alone = that drive's current folder)
|
|
68
|
+
$parent = [IO.Path]::GetDirectoryName($p)
|
|
69
|
+
if (-not $parent -or -not $p.Contains('~')) { return $p }
|
|
70
|
+
$base = Get-LongPath $parent; $leaf = [IO.Path]::GetFileName($p)
|
|
71
|
+
# The file system answers a short name with the long one.
|
|
72
|
+
try { $hit = @([IO.Directory]::GetFileSystemEntries($base, $leaf)); if ($hit.Count -eq 1) { return $hit[0].TrimEnd('\') } } catch {}
|
|
73
|
+
return (Join-Path $base $leaf)
|
|
74
|
+
}
|
|
75
|
+
function Test-SamePath([string]$a, [string]$b) { $a -and $b -and [string]::Equals((Get-LongPath $a), (Get-LongPath $b), [StringComparison]::OrdinalIgnoreCase) }
|
|
76
|
+
function Test-InFolder([string]$p, [string]$dir) { if (-not $p) { return $false }; $l = Get-LongPath $p; $d = Get-LongPath $dir; [string]::Equals($l, $d, [StringComparison]::OrdinalIgnoreCase) -or $l.StartsWith($d.TrimEnd('\') + '\', [StringComparison]::OrdinalIgnoreCase) }
|
|
77
|
+
|
|
78
|
+
# User PATH through the registry: keeps it REG_EXPAND_SZ with %VARS% unexpanded.
|
|
79
|
+
function Add-UserPath([string]$dir, [switch]$First) {
|
|
80
|
+
$k = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
|
|
81
|
+
$userPath = [string]$k.GetValue('Path', '', 'DoNotExpandEnvironmentNames')
|
|
82
|
+
$parts = @($userPath -split ';' | Where-Object { $_ })
|
|
83
|
+
$key = $dir.TrimEnd('\')
|
|
84
|
+
$keys = @($parts | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_).TrimEnd('\') })
|
|
85
|
+
# -First: also moves an entry that is already there to the front (an older bridgerator.cmd must not shadow it).
|
|
86
|
+
if ($keys -notcontains $key -or ($First -and $keys[0] -ne $key)) {
|
|
87
|
+
$rest = @(for ($i = 0; $i -lt $parts.Count; $i++) { if ($keys[$i] -ne $key) { $parts[$i] } })
|
|
88
|
+
$new = if ($First) { @($dir) + $rest } else { $rest + @($dir) }
|
|
89
|
+
$k.SetValue('Path', ($new -join ';'), [Microsoft.Win32.RegistryValueKind]::ExpandString)
|
|
90
|
+
# Tell Explorer the environment changed (new windows see the new PATH).
|
|
91
|
+
[Environment]::SetEnvironmentVariable('BRIDGERATOR_PATH_REFRESH', '1', 'User'); [Environment]::SetEnvironmentVariable('BRIDGERATOR_PATH_REFRESH', $null, 'User')
|
|
92
|
+
}
|
|
93
|
+
$k.Close()
|
|
22
94
|
}
|
|
23
95
|
|
|
96
|
+
function Get-Sha256([string]$file) {
|
|
97
|
+
$f = [IO.File]::OpenRead($file)
|
|
98
|
+
try { -join ([Security.Cryptography.SHA256]::Create().ComputeHash($f) | ForEach-Object { $_.ToString('x2') }) } finally { $f.Close() }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Same matcher as `bridgerator.cmd stop` (src/cli.mjs stopPs) - for versions that don't have `stop` yet:
|
|
102
|
+
# this Windows session only; the autostart loop first, then node/bridgerator/rg running from the package
|
|
103
|
+
# folder. $pkg = the package folder (npm: ...\node_modules\bridgerator; app: %LOCALAPPDATA%\Bridgerator\app),
|
|
104
|
+
# $cmd = what runs its `stop` (bridgerator.cmd / bridgerator.exe).
|
|
105
|
+
function Stop-Bridge([string]$pkgDir, [string]$cmd) {
|
|
106
|
+
$cli = Join-Path $pkgDir 'src\cli.mjs'
|
|
107
|
+
# Its own `stop` first; if that fails (e.g. node.exe of an npm install not found), the matcher below.
|
|
108
|
+
if ((Test-Path $cli) -and (Test-Path $cmd) -and ((Get-Content $cli -Raw) -match "cmd === 'stop'")) { & $cmd stop | Out-Null; if ($LASTEXITCODE -eq 0) { Stop-OurWebView; return } }
|
|
109
|
+
$me = (Get-Process -Id $PID).SessionId
|
|
110
|
+
$pkg = $pkgDir.ToLower().TrimEnd('\') + '\'
|
|
111
|
+
$vbs = (Join-Path $env:APPDATA 'Bridgerator\run-hidden.vbs').ToLower()
|
|
112
|
+
$all = @(Get-CimInstance Win32_Process -Filter "Name='wscript.exe' OR Name='node.exe' OR Name='bridgerator.exe' OR Name='rg.exe' OR Name='powershell.exe'" -ErrorAction SilentlyContinue |
|
|
113
|
+
Where-Object { $s = ("$($_.CommandLine) $($_.ExecutablePath)").ToLower().Replace('/', '\'); $_.SessionId -eq $me -and $_.ProcessId -ne $PID -and ($s.Contains($pkg) -or $s.Contains($vbs)) } |
|
|
114
|
+
Sort-Object { $_.Name -ne 'wscript.exe' })
|
|
115
|
+
$all | Where-Object { $_.Name -ne 'powershell.exe' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
|
116
|
+
# The tray helper removes its icon itself once the bridge is gone (a kill leaves a ghost icon): 2 s, then the kill.
|
|
117
|
+
$tray = @($all | Where-Object { $_.Name -eq 'powershell.exe' }); $end = (Get-Date).AddSeconds(2)
|
|
118
|
+
while ($tray.Count -and (Get-Date) -lt $end -and @($tray | Where-Object { Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue }).Count) { Start-Sleep -Milliseconds 200 }
|
|
119
|
+
$tray | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
|
120
|
+
Stop-OurWebView
|
|
121
|
+
}
|
|
122
|
+
# The old window's WebView2 processes (msedgewebview2.exe with our user data folder, this session): an
|
|
123
|
+
# older `stop` leaves them running (V-8).
|
|
124
|
+
function Stop-OurWebView {
|
|
125
|
+
$me = (Get-Process -Id $PID).SessionId
|
|
126
|
+
$wvd = ('--user-data-dir=' + (Join-Path $env:LOCALAPPDATA 'Bridgerator\webview')).ToLower()
|
|
127
|
+
$wv = @(Get-CimInstance Win32_Process -Filter "Name='msedgewebview2.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.SessionId -eq $me }); $ids = @($wv | Where-Object { "$($_.CommandLine)".ToLower().Replace('"', '').Contains($wvd) } | ForEach-Object { $_.ProcessId })
|
|
128
|
+
$wv | Where-Object { $ids -contains $_.ProcessId -or $ids -contains $_.ParentProcessId } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Back to the bridge that ran before (an update that failed must not leave the PC unreachable).
|
|
132
|
+
function Start-OldBridge {
|
|
133
|
+
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
134
|
+
foreach ($n in @("Bridgerator-$sid", 'Bridgerator')) { & (Join-Path $Sys32 'schtasks.exe') /Run /TN $n 2>$null | Out-Null; if ($LASTEXITCODE -eq 0) { return } }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
$admin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
138
|
+
# Bridgerator runs with the rights of the signed-in user, never as Administrator.
|
|
139
|
+
if ($admin) { Write-Host 'Run this in a normal (non-admin) PowerShell window: this one runs as Administrator.' -ForegroundColor Yellow; return }
|
|
140
|
+
|
|
24
141
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
25
142
|
$ProgressPreference = 'SilentlyContinue'
|
|
26
143
|
|
|
27
|
-
# No winget on many clean Windows installs: take the official Node.js LTS zip into the user's
|
|
144
|
+
# No winget on many clean Windows installs: take the official Node.js 24 LTS zip into the user's
|
|
28
145
|
# profile (no admin rights), checked against nodejs.org SHASUMS256, and put it on the user PATH.
|
|
29
146
|
function Install-PortableNode {
|
|
30
|
-
$dir =
|
|
147
|
+
$dir = $NodeDir
|
|
31
148
|
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
|
|
32
|
-
$rel = (Invoke-RestMethod 'https://nodejs.org/dist/index.json') | Where-Object { $_.lts -and ($_.files -contains "win-$arch-zip") } | Select-Object -First 1
|
|
33
|
-
if (-not $rel) { throw 'Could not find a Node.js LTS build. Install Node.js
|
|
149
|
+
$rel = (Invoke-RestMethod 'https://nodejs.org/dist/index.json') | Where-Object { $_.lts -and $_.version -like 'v24.*' -and ($_.files -contains "win-$arch-zip") } | Select-Object -First 1
|
|
150
|
+
if (-not $rel) { throw 'Could not find a Node.js 24 LTS build. Install Node.js 22+ from https://nodejs.org and run this again.' }
|
|
34
151
|
$name = "node-$($rel.version)-win-$arch"
|
|
35
|
-
Write-Host "
|
|
36
|
-
$tmp = Join-Path $
|
|
37
|
-
|
|
152
|
+
Write-Host "Downloading Node.js $($rel.version) for Bridgerator into $dir ..."
|
|
153
|
+
$tmp = Join-Path $Work 'node'
|
|
154
|
+
[void][IO.Directory]::CreateDirectory($tmp)
|
|
38
155
|
try {
|
|
39
156
|
$zip = Join-Path $tmp "$name.zip"
|
|
40
157
|
Invoke-WebRequest "https://nodejs.org/dist/$($rel.version)/$name.zip" -OutFile $zip -UseBasicParsing
|
|
41
158
|
$sums = (Invoke-WebRequest "https://nodejs.org/dist/$($rel.version)/SHASUMS256.txt" -UseBasicParsing).Content
|
|
42
159
|
$want = ($sums -split "`n" | Where-Object { $_ -match "\s$name\.zip$" }) -replace '\s.*$', ''
|
|
43
|
-
$
|
|
44
|
-
if (-not $want -or $got -ne $want.Trim().ToUpper()) { throw 'Node.js download failed the checksum check.' }
|
|
160
|
+
if (-not $want -or (Get-Sha256 $zip) -ne $want.Trim().ToLower()) { throw 'Node.js download failed the checksum check.' }
|
|
45
161
|
Write-Host 'Node.js checksum OK (SHA256, nodejs.org)'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
162
|
+
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
163
|
+
[IO.Compression.ZipFile]::ExtractToDirectory($zip, $tmp)
|
|
164
|
+
# Everything running from the Node folder (the old bridge, its engine child) holds node.exe: matched by the folder itself.
|
|
165
|
+
if ([IO.Directory]::Exists($dir)) { Stop-Bridge $dir (Join-Path $dir 'bridgerator.cmd'); [IO.Directory]::Delete($dir, $true) }
|
|
166
|
+
[void][IO.Directory]::CreateDirectory((Split-Path $dir))
|
|
167
|
+
[IO.Directory]::Move((Join-Path $tmp $name), $dir)
|
|
168
|
+
} finally { try { [IO.Directory]::Delete($tmp, $true) } catch {} }
|
|
169
|
+
# npm's cache of this private Node.js: removed by `bridgerator.cmd uninstall --all` only if we created it.
|
|
170
|
+
$cache = Join-Path $env:LOCALAPPDATA 'npm-cache'
|
|
171
|
+
if (-not (Test-Path $cache)) { Set-Content -Path (Join-Path $env:LOCALAPPDATA 'Bridgerator\created-npm-cache') -Value $cache }
|
|
172
|
+
Add-UserPath $dir -First
|
|
53
173
|
Refresh-Path
|
|
54
174
|
}
|
|
55
175
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
176
|
+
# ---- The Windows app (default): bridgerator.exe + its files, no Node.js. LOGIC: critical-logic/install-release.md
|
|
177
|
+
function Remove-UserPath([string]$dir) {
|
|
178
|
+
$k = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true)
|
|
179
|
+
if (-not $k) { return }
|
|
180
|
+
try {
|
|
181
|
+
if ($null -ne $k.GetValue('Path')) {
|
|
182
|
+
$kind = $k.GetValueKind('Path'); $old = [string]$k.GetValue('Path', '', 'DoNotExpandEnvironmentNames')
|
|
183
|
+
$new = ($old -split ';' | Where-Object { $_ -and [Environment]::ExpandEnvironmentVariables($_).TrimEnd('\') -ne $dir.TrimEnd('\') }) -join ';'
|
|
184
|
+
if ($new -ne $old) { $k.SetValue('Path', $new, $kind) }
|
|
185
|
+
}
|
|
186
|
+
} finally { $k.Close() }
|
|
187
|
+
}
|
|
59
188
|
|
|
60
|
-
#
|
|
61
|
-
|
|
189
|
+
# An antivirus scan or the search indexer can hold a file for a moment.
|
|
190
|
+
function Move-Dir([string]$from, [string]$to) {
|
|
191
|
+
for ($i = 1; ; $i++) { try { [IO.Directory]::Move($from, $to); return } catch { if ($i -ge 10) { throw }; Start-Sleep -Milliseconds 500 } }
|
|
192
|
+
}
|
|
193
|
+
function Remove-Dir([string]$dir) {
|
|
194
|
+
for ($i = 1; ; $i++) { try { if ([IO.Directory]::Exists($dir)) { [IO.Directory]::Delete($dir, $true) }; return } catch { if ($i -ge 10) { throw }; Start-Sleep -Milliseconds 500 } }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function Get-Arch {
|
|
198
|
+
$a = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
|
|
199
|
+
if ($a -eq 'ARM64') { 'arm64' } elseif ($a -eq 'AMD64') { 'x64' } else { $null }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# bridgerator-win.json (its own SHA256 already checked) -> the zip for this architecture.
|
|
203
|
+
function Read-AppManifest([string]$file, [string]$arch) {
|
|
204
|
+
$m = [IO.File]::ReadAllText($file, [Text.Encoding]::UTF8) | ConvertFrom-Json
|
|
205
|
+
$e = $m.$arch
|
|
206
|
+
if (-not $e -or [string]$e.file -notmatch "^bridgerator-[0-9]+\.[0-9]+\.[0-9]+-win-$arch\.zip$" -or [string]$e.sha256 -notmatch '^[0-9a-f]{64}$' -or [string]$m.version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') { throw "The Bridgerator download list has no build for this PC ($arch)." }
|
|
207
|
+
@{ file = [string]$e.file; sha256 = [string]$e.sha256; version = [string]$m.version; size = [long]$e.size }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# The unpacked app: every file of its SHA256SUMS.txt must match (an antivirus that removed or changed a
|
|
211
|
+
# file shows here), then the new bridgerator.exe must start and name its version - all before the
|
|
212
|
+
# running Bridgerator is touched.
|
|
213
|
+
function Test-App([string]$dir, [string]$version) {
|
|
214
|
+
$sums = Join-Path $dir 'SHA256SUMS.txt'
|
|
215
|
+
if (-not [IO.File]::Exists($sums)) { throw 'The Bridgerator download is incomplete (no SHA256SUMS.txt).' }
|
|
216
|
+
$sha = [Security.Cryptography.SHA256]::Create(); $n = 0; $top = @{}
|
|
217
|
+
foreach ($line in [IO.File]::ReadAllLines($sums)) {
|
|
218
|
+
if (-not $line) { continue }
|
|
219
|
+
if ($line -notmatch '^([0-9a-f]{64}) \*([^:*?"<>|]+)$') { throw 'The Bridgerator file list is damaged.' }
|
|
220
|
+
$want = $Matches[1]; $rel = $Matches[2]
|
|
221
|
+
if ($rel.StartsWith('/') -or ($rel -split '/') -contains '..') { throw 'The Bridgerator file list is damaged.' }
|
|
222
|
+
$f = Join-Path $dir ($rel -replace '/', '\')
|
|
223
|
+
$h = $null
|
|
224
|
+
if ([IO.File]::Exists($f)) { $s = [IO.File]::OpenRead($f); try { $h = [BitConverter]::ToString($sha.ComputeHash($s)).Replace('-', '').ToLower() } finally { $s.Close() } }
|
|
225
|
+
if ($h -ne $want) { throw "A Bridgerator file failed the checksum check: $rel (an antivirus may have removed it)." }
|
|
226
|
+
if ($rel -notmatch '/') { $top[$rel] = $true }
|
|
227
|
+
$n++
|
|
228
|
+
}
|
|
229
|
+
# The folder goes FIRST on the user PATH: only listed files in its root (nothing extra can shadow a command).
|
|
230
|
+
foreach ($f in [IO.Directory]::GetFiles($dir)) { $nm = [IO.Path]::GetFileName($f); if ($nm -ne 'SHA256SUMS.txt' -and -not $top.ContainsKey($nm)) { throw "The Bridgerator download has an unexpected file: $nm." } }
|
|
231
|
+
if ($n -lt 100 -or -not [IO.File]::Exists((Join-Path $dir 'bridgerator.exe'))) { throw 'The Bridgerator download is incomplete.' }
|
|
232
|
+
$v = (& (Join-Path $dir 'bridgerator.exe') --version | Out-String).Trim()
|
|
233
|
+
if ($LASTEXITCODE -ne 0 -or -not $v -or ($version -and $v -ne $version)) { throw "The new Bridgerator did not start (answer: '$v'). An antivirus may block it - try again later, or run this command with -UseNpm." }
|
|
234
|
+
return $v
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Download + checks -> the checked app folder inside the work folder. Custom -AppZip (https or a local
|
|
238
|
+
# file) only with its -Sha256; the default one by the download list whose SHA256 this script carries.
|
|
239
|
+
function Get-App {
|
|
240
|
+
if ($AppZip) {
|
|
241
|
+
$want = $Sha256.Trim().ToLower(); $version = $null; $src = $AppZip
|
|
242
|
+
} else {
|
|
243
|
+
$mf = Join-Path $Work 'bridgerator-win.json'
|
|
244
|
+
Invoke-WebRequest "$Site/bridgerator-win.json" -OutFile $mf -UseBasicParsing
|
|
245
|
+
if ((Get-Sha256 $mf) -ne $WinManifestSha256) { throw 'The Bridgerator download list failed the checksum check - try again in a minute.' }
|
|
246
|
+
$arch = Get-Arch
|
|
247
|
+
$m = Read-AppManifest $mf $arch
|
|
248
|
+
$want = $m.sha256; $version = $m.version; $src = "$Site/$($m.file)"
|
|
249
|
+
Write-Host "Downloading Bridgerator $version for Windows ($arch, $([math]::Round($m.size / 1MB)) MB)..."
|
|
250
|
+
}
|
|
251
|
+
$zip = $src
|
|
252
|
+
if ($src -match '^https://') { $zip = Join-Path $Work 'app.zip'; Invoke-WebRequest $src -OutFile $zip -UseBasicParsing }
|
|
253
|
+
if ((Get-Sha256 $zip) -ne $want) { throw 'The Bridgerator download failed the checksum check - try again in a minute.' }
|
|
254
|
+
Write-Host 'Download checksum OK (SHA256). Unpacking and checking the files...'
|
|
255
|
+
$dir = Join-Path $Work 'app'
|
|
256
|
+
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
257
|
+
[IO.Compression.ZipFile]::ExtractToDirectory($zip, $dir)
|
|
258
|
+
if ($zip -ne $src) { [IO.File]::Delete($zip) }
|
|
259
|
+
$v = Test-App $dir $version
|
|
260
|
+
Write-Host "Bridgerator $v checked."
|
|
261
|
+
return $dir
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# Stops whatever Bridgerator runs (this app, or the npm package of 1.0.x) and puts the checked app in
|
|
265
|
+
# place; the previous app folder stays as app.old-<guid> until the new one is set up. -> that folder or $null.
|
|
266
|
+
function Switch-App([string]$new) {
|
|
267
|
+
$old = $null
|
|
268
|
+
try {
|
|
269
|
+
if ([IO.File]::Exists((Join-Path $AppDir 'bridgerator.exe'))) { Stop-Bridge $AppDir (Join-Path $AppDir 'bridgerator.exe') }
|
|
270
|
+
foreach ($p in Get-NpmBridges) { Stop-Bridge $p (Join-Path (Split-Path (Split-Path $p)) 'bridgerator.cmd') }
|
|
271
|
+
if ([IO.Directory]::Exists($AppDir)) { $o = Join-Path $Root "app.old-$([guid]::NewGuid().ToString('N'))"; Move-Dir $AppDir $o; $old = $o }
|
|
272
|
+
} catch { Start-OldBridge; throw }
|
|
273
|
+
try { Move-Dir $new $AppDir } catch { if ($old) { try { Move-Dir $old $AppDir } catch {} }; Start-OldBridge; throw }
|
|
274
|
+
return $old
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# The npm package of Bridgerator 1.0.x after the move to the app (its key and settings stay in
|
|
278
|
+
# %APPDATA%\Bridgerator), and the private Node.js its installer added (same rules as uninstall --all).
|
|
279
|
+
function Remove-NpmBridge([string[]]$keep = @()) {
|
|
280
|
+
foreach ($p in Get-NpmBridges) {
|
|
281
|
+
# The package this run installed is never removed, however npm spells its folder.
|
|
282
|
+
if (@($keep | Where-Object { Test-SamePath $_ $p }).Count) { continue }
|
|
283
|
+
$npmDir = Split-Path (Split-Path $p)
|
|
284
|
+
try {
|
|
285
|
+
Remove-Dir $p
|
|
286
|
+
foreach ($n in 'bridgerator', 'bridgerator.cmd', 'bridgerator.ps1') { $f = Join-Path $npmDir $n; if ([IO.File]::Exists($f)) { [IO.File]::Delete($f) } }
|
|
287
|
+
Write-Host 'Removed the old Bridgerator npm package (key and settings kept).'
|
|
288
|
+
} catch { Write-Host "Could not remove the old Bridgerator npm package $p ($_) - it is not used any more." }
|
|
289
|
+
}
|
|
290
|
+
if (-not @($keep | Where-Object { Test-InFolder $_ $NodeDir }).Count) { Remove-PrivateNode }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# The private Node.js of an installer (folder, its PATH entry, npm's cache when we created it - marker file).
|
|
294
|
+
function Remove-PrivateNode {
|
|
295
|
+
# Other global npm packages installed with it (not npm / corepack / Bridgerator): the folder stays.
|
|
296
|
+
$mods = Join-Path $NodeDir 'node_modules'
|
|
297
|
+
$foreign = @(if ([IO.Directory]::Exists($mods)) { [IO.Directory]::GetDirectories($mods) | ForEach-Object { [IO.Path]::GetFileName($_) } | Where-Object { @('npm', 'corepack', 'bridgerator', '.bin') -notcontains $_ } })
|
|
298
|
+
if ($foreign.Count) { Write-Host "Kept the Node.js the installer added ($NodeDir): other programs use it ($($foreign -join ', '))." }
|
|
299
|
+
elseif ([IO.Directory]::Exists($NodeDir)) {
|
|
300
|
+
try { Remove-UserPath $NodeDir; Remove-Dir $NodeDir; Write-Host "Removed the Node.js the installer added ($NodeDir)." } catch { Write-Host "Could not remove $NodeDir ($_) - it is not used any more." }
|
|
301
|
+
}
|
|
302
|
+
$marker = Join-Path $Root 'created-npm-cache'
|
|
303
|
+
if ([IO.File]::Exists($marker)) {
|
|
304
|
+
$c = ([IO.File]::ReadAllText($marker)).Trim()
|
|
305
|
+
if ($c -and $c.TrimEnd('\') -eq (Join-Path $env:LOCALAPPDATA 'npm-cache')) { try { Remove-Dir $c } catch { Write-Host "Could not remove npm's cache $c ($_)." } }
|
|
306
|
+
[IO.File]::Delete($marker)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
# After `bridgerator.exe setup`: 0 = done (the old folder goes; the npm package of 1.0.x too); 3 = a clear
|
|
311
|
+
# stop before anything changed (pairing code expired): the new app stays, the old link runs again; any
|
|
312
|
+
# other failure puts the previous app folder back and starts it.
|
|
313
|
+
function Complete-App([int]$rc, $old, [bool]$fromNpm) {
|
|
314
|
+
if ($rc -ne 0 -and $rc -ne 3) {
|
|
315
|
+
if ($old) {
|
|
316
|
+
try { Stop-Bridge $AppDir (Join-Path $AppDir 'bridgerator.exe'); Remove-Dir $AppDir; Move-Dir $old $AppDir } catch { Write-Host "Could not put the previous Bridgerator back ($_) - run this command again." }
|
|
317
|
+
}
|
|
318
|
+
Start-OldBridge; return
|
|
319
|
+
}
|
|
320
|
+
if ($old) { try { Remove-Dir $old } catch { Write-Host "Could not remove the previous app folder $old ($_)." } }
|
|
321
|
+
if ($rc -ne 0) { Start-OldBridge; return }
|
|
322
|
+
if ($fromNpm) { Remove-NpmBridge }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
# One install at a time for this user (a second run would stop the first one's setup and roll it back).
|
|
326
|
+
$lock = New-Object Threading.Mutex($false, "Local\Bridgerator-Install-$([Security.Principal.WindowsIdentity]::GetCurrent().User.Value)")
|
|
327
|
+
$locked = $false
|
|
328
|
+
try { $locked = $lock.WaitOne(0) } catch [Threading.AbandonedMutexException] { $locked = $true }
|
|
329
|
+
if (-not $locked) { Write-Host 'Another Bridgerator install is running in another window - wait until it ends.' -ForegroundColor Yellow; $lock.Dispose(); return }
|
|
330
|
+
$nodeFresh = $false
|
|
331
|
+
# -UseNpm: a bridge this run stopped (the app, a 1.0.x in the other npm folder, the one npm replaces) runs again on any failure.
|
|
332
|
+
$restartOld = $false
|
|
333
|
+
function Undo-NpmRun {
|
|
334
|
+
# A Node.js this run put in goes again (folder, PATH entry, npm-cache marker).
|
|
335
|
+
if ($nodeFresh) { Stop-Bridge $NodeDir (Join-Path $NodeDir 'bridgerator.cmd'); Remove-PrivateNode }
|
|
336
|
+
if ($restartOld) { Start-OldBridge }
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
[void][IO.Directory]::CreateDirectory($Work)
|
|
340
|
+
if ($npmMode) {
|
|
341
|
+
# The package: downloaded, checked against the SHA256 above, installed without running any
|
|
342
|
+
# install scripts of it or its dependencies (no telemetry, no downloads at install time).
|
|
343
|
+
$tgz = $Package
|
|
344
|
+
# The package is always checked: our default needs the hash written by release.sh, any other
|
|
345
|
+
# package needs -Sha256; downloads only over https.
|
|
346
|
+
if ($Package -match '^http://') { Write-Host 'The package address must start with https://' -ForegroundColor Yellow; return }
|
|
347
|
+
if ($Package -ne $DefaultPackage -and -not $Sha256) { Write-Host 'A custom -Package needs its -Sha256 too.' -ForegroundColor Yellow; return }
|
|
348
|
+
if ($Package -eq $DefaultPackage -and -not $Sha256 -and -not $PackageSha256) { Write-Host 'This copy of the installer has no package checksum - use the install command from your account page.' -ForegroundColor Yellow; return }
|
|
349
|
+
$want = if ($Sha256) { $Sha256 } else { $PackageSha256 }
|
|
350
|
+
$dl = $null
|
|
351
|
+
if ($Package -match '^https://') {
|
|
352
|
+
$dl = Join-Path $Work 'bridgerator.tgz'
|
|
353
|
+
Invoke-WebRequest $Package -OutFile $dl -UseBasicParsing
|
|
354
|
+
$tgz = $dl
|
|
355
|
+
}
|
|
356
|
+
if ((Get-Sha256 $tgz) -ne $want.Trim().ToLower()) { throw 'The Bridgerator package failed the checksum check - try again in a minute.' }
|
|
357
|
+
Write-Host 'Package checksum OK (SHA256)'
|
|
358
|
+
|
|
359
|
+
# Node.js only after the package passed its check (a failed check must leave nothing behind).
|
|
360
|
+
if (Test-Path (Join-Path $NodeDir 'node.exe')) { $env:Path = "$NodeDir;$env:Path" }
|
|
361
|
+
$major = 0
|
|
362
|
+
if (Get-Command node -ErrorAction SilentlyContinue) { $major = [int]((& node -v).TrimStart('v').Split('.')[0]) }
|
|
363
|
+
if ($major -lt 22) {
|
|
364
|
+
if ($major) { Write-Host "Node.js $(& node -v) is too old (22+ needed) - Bridgerator gets its own copy, your Node.js stays as is." }
|
|
365
|
+
$nodeFresh = -not [IO.Directory]::Exists($NodeDir)
|
|
366
|
+
Install-PortableNode
|
|
367
|
+
}
|
|
368
|
+
# Whatever Node.js runs npm now: its folder stays (the private one is removed only when it is not in use).
|
|
369
|
+
$nodeInUse = Split-Path ((Get-Command node -ErrorAction SilentlyContinue).Source)
|
|
370
|
+
|
|
371
|
+
$prefix = (& npm.cmd prefix -g --update-notifier=false | Out-String).Trim()
|
|
62
372
|
$env:PUPPETEER_SKIP_DOWNLOAD = '1'
|
|
63
|
-
Write-Host
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
373
|
+
Write-Host 'Installing Bridgerator - this usually takes 2-6 minutes, please wait...'
|
|
374
|
+
try {
|
|
375
|
+
# The running bridge keeps working during the update; only when a locked file blocks npm it is stopped.
|
|
376
|
+
& npm.cmd install -g $tgz --ignore-scripts @NpmFlags
|
|
377
|
+
if ($LASTEXITCODE -ne 0) {
|
|
378
|
+
Write-Host 'Retrying with the running Bridgerator stopped...'
|
|
379
|
+
$restartOld = $true; Stop-Bridge (Join-Path $prefix 'node_modules\bridgerator') (Join-Path $prefix 'bridgerator.cmd')
|
|
380
|
+
& npm.cmd install -g $tgz --ignore-scripts @NpmFlags
|
|
381
|
+
if ($LASTEXITCODE -ne 0) { throw 'npm could not install Bridgerator (its message is above). Check the internet connection - npm needs registry.npmjs.org - and run the install command again, or install without -UseNpm.' }
|
|
382
|
+
}
|
|
383
|
+
} finally { if ($dl) { try { [IO.File]::Delete($dl) } catch {} } }
|
|
384
|
+
# Back from the app to the npm package: the app stops now (one bridge per PC), its folder goes after setup.
|
|
385
|
+
$dropApp = [IO.File]::Exists((Join-Path $AppDir 'bridgerator.exe'))
|
|
386
|
+
if ($dropApp) { $restartOld = $true; Stop-Bridge $AppDir (Join-Path $AppDir 'bridgerator.exe') }
|
|
387
|
+
# A 1.0.x package in the OTHER npm folder (npm's own / the private Node.js) stops too; it goes after setup.
|
|
388
|
+
$newPkg = Join-Path $prefix 'node_modules\bridgerator'
|
|
389
|
+
$keepNpm = @($newPkg, $nodeInUse)
|
|
390
|
+
foreach ($p in Get-NpmBridges) { if (-not (Test-SamePath $p $newPkg)) { $restartOld = $true; Stop-Bridge $p (Join-Path (Split-Path (Split-Path $p)) 'bridgerator.cmd') } }
|
|
391
|
+
} else {
|
|
392
|
+
# The app: plain stops first (nothing downloaded yet), then download + every check (Get-App); the running
|
|
393
|
+
# Bridgerator is stopped only right before setup (Switch-App).
|
|
394
|
+
if ($AppZip -match '^http://') { Write-Host 'The app address must start with https://' -ForegroundColor Yellow; return }
|
|
395
|
+
if ($AppZip -and -not $Sha256) { Write-Host 'A custom -AppZip needs its -Sha256 too.' -ForegroundColor Yellow; return }
|
|
396
|
+
if (-not $AppZip -and -not $WinManifestSha256) { Write-Host 'This copy of the installer has no app checksum - use the install command from your account page.' -ForegroundColor Yellow; return }
|
|
397
|
+
if (-not $AppZip -and -not (Get-Arch)) { Write-Host 'Bridgerator needs 64-bit Windows (x64 or ARM64).' -ForegroundColor Yellow; return }
|
|
398
|
+
# A 1.0.x npm package (npm's folder or the private Node.js) or the old installer's private Node.js: removed after setup.
|
|
399
|
+
$fromNpm = [bool](Get-NpmBridges) -or [IO.Directory]::Exists($NodeDir)
|
|
400
|
+
$newApp = Get-App
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# WebView2 runtime (the tray window). Usually part of Windows 11; if missing, Microsoft's Evergreen
|
|
404
|
+
# bootstrapper is downloaded, its Authenticode signature checked (valid, signed by Microsoft
|
|
405
|
+
# Corporation) and run per user without admin. Any failure only means the tray opens the account
|
|
406
|
+
# page instead of its window.
|
|
407
|
+
function Test-WebView2 {
|
|
408
|
+
foreach ($k in 'HKCU:\Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'HKLM:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}') {
|
|
409
|
+
try { $pv = (Get-ItemProperty $k -ErrorAction Stop).pv; if ($pv -and $pv -ne '0.0.0.0') { return $true } } catch {}
|
|
410
|
+
}
|
|
411
|
+
return $false
|
|
412
|
+
}
|
|
413
|
+
function Test-MicrosoftSigned([string]$file) {
|
|
414
|
+
# Get-AuthenticodeSignature is not available under the Restricted policy: WinVerifyTrust directly.
|
|
415
|
+
Add-Type -TypeDefinition @'
|
|
416
|
+
using System; using System.Runtime.InteropServices;
|
|
417
|
+
public static class BrTrust {
|
|
418
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct FileInfo { public int cbStruct; public string path; public IntPtr hFile; public IntPtr known; }
|
|
419
|
+
[StructLayout(LayoutKind.Sequential)] struct Data { public int cbStruct; public IntPtr policy; public IntPtr sip; public int ui; public int revocation; public int choice; public IntPtr file; public int action; public IntPtr state; public IntPtr url; public int prov; public int context; public IntPtr sig; }
|
|
420
|
+
[DllImport("wintrust.dll", CharSet = CharSet.Unicode)] static extern int WinVerifyTrust(IntPtr hwnd, ref Guid action, ref Data data);
|
|
421
|
+
public static int Verify(string path) {
|
|
422
|
+
var fi = new FileInfo { cbStruct = Marshal.SizeOf(typeof(FileInfo)), path = path };
|
|
423
|
+
IntPtr pfi = Marshal.AllocHGlobal(fi.cbStruct); Marshal.StructureToPtr(fi, pfi, false);
|
|
424
|
+
// WTD_REVOKE_WHOLECHAIN + WTD_REVOCATION_CHECK_CHAIN: a revoked certificate anywhere in the chain fails.
|
|
425
|
+
var d = new Data { cbStruct = Marshal.SizeOf(typeof(Data)), ui = 2, revocation = 1, choice = 1, file = pfi, action = 1, prov = 0x40 };
|
|
426
|
+
var g = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE");
|
|
427
|
+
int r = WinVerifyTrust(new IntPtr(-1), ref g, ref d);
|
|
428
|
+
d.action = 2; WinVerifyTrust(new IntPtr(-1), ref g, ref d);
|
|
429
|
+
Marshal.FreeHGlobal(pfi); return r;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
'@
|
|
433
|
+
if ([BrTrust]::Verify($file) -ne 0) { return $false }
|
|
434
|
+
$cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2 ([Security.Cryptography.X509Certificates.X509Certificate]::CreateFromSignedFile($file))
|
|
435
|
+
if ($cert.Subject -notmatch '(^|, )O=Microsoft Corporation(,|$)') { return $false }
|
|
436
|
+
# The chain must end in a Microsoft root and nothing in it may be revoked.
|
|
437
|
+
$chain = New-Object Security.Cryptography.X509Certificates.X509Chain
|
|
438
|
+
$chain.ChainPolicy.RevocationMode = 'Online'; $chain.ChainPolicy.RevocationFlag = 'EntireChain'
|
|
439
|
+
if (-not $chain.Build($cert)) { return $false }
|
|
440
|
+
$root = $chain.ChainElements[$chain.ChainElements.Count - 1].Certificate
|
|
441
|
+
return $root.Subject -match 'CN=Microsoft Root Certificate Authority' -and $root.Subject -match 'O=Microsoft Corporation'
|
|
442
|
+
}
|
|
443
|
+
if (-not (Test-WebView2)) {
|
|
444
|
+
try {
|
|
445
|
+
Write-Host 'Installing the Microsoft WebView2 runtime for the Bridgerator window...'
|
|
446
|
+
$wvb = Join-Path $Work 'MicrosoftEdgeWebview2Setup.exe'
|
|
447
|
+
Invoke-WebRequest 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile $wvb -UseBasicParsing
|
|
448
|
+
# Held open (others may only read) from the check until it has run: it can't be swapped in between.
|
|
449
|
+
$hold = [IO.File]::Open($wvb, 'Open', 'Read', 'Read')
|
|
450
|
+
try {
|
|
451
|
+
if (-not (Test-MicrosoftSigned $wvb)) { throw 'the WebView2 installer is not signed by Microsoft' }
|
|
452
|
+
$p = Start-Process -FilePath $wvb -ArgumentList '/silent', '/install' -Wait -PassThru
|
|
453
|
+
} finally { $hold.Dispose() }
|
|
454
|
+
if ($p.ExitCode -ne 0) { throw "exit code $($p.ExitCode)" }
|
|
455
|
+
# A policy / antivirus can block it while the bootstrapper still ends with 0.
|
|
456
|
+
if (-not (Test-WebView2)) { throw 'blocked on this PC' }
|
|
457
|
+
} catch { Write-Host "WebView2 runtime not installed ($_) - Bridgerator works anyway; its window opens the account page instead." -ForegroundColor Yellow }
|
|
458
|
+
}
|
|
67
459
|
|
|
68
460
|
$setupArgs = @('setup')
|
|
69
461
|
if ($Token) { $setupArgs += @('--token', $Token) }
|
|
462
|
+
if ($Relay) { $setupArgs += @('--relay', $Relay) }
|
|
70
463
|
foreach ($a in $Allow) { $setupArgs += @('--allow', $a) }
|
|
71
464
|
foreach ($d in $Deny) { $setupArgs += @('--deny', $d) }
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
if (
|
|
465
|
+
if ($NoTelemetry) { $setupArgs += '--no-telemetry' }
|
|
466
|
+
# Language of the window on a first config (setup keeps a language the person chose).
|
|
467
|
+
$lang = if ([Globalization.CultureInfo]::CurrentUICulture.Name -like 'ru*' -or [Globalization.CultureInfo]::CurrentCulture.Name -like 'ru*') { 'ru' } else { 'en' }
|
|
468
|
+
$setupArgs += @('--language', $lang)
|
|
469
|
+
# The app: only now the running Bridgerator stops and the checked folder goes in place.
|
|
470
|
+
if (-not $npmMode) { $oldApp = Switch-App $newApp }
|
|
471
|
+
try {
|
|
472
|
+
# Called by full path (the PATH of this window may not have the folder yet); the folder goes on
|
|
473
|
+
# the user PATH so `bridgerator.cmd` works in new windows.
|
|
474
|
+
if ($npmMode) { Add-UserPath $prefix; $br = Join-Path $prefix 'bridgerator.cmd' } else { Add-UserPath $AppDir -First; $br = Join-Path $AppDir 'bridgerator.exe' }
|
|
475
|
+
Refresh-Path
|
|
476
|
+
if (-not $paired) { Write-Host 'Next: a browser tab opens - sign in with the same account you use for Bridgerator and click "Connect this computer".' }
|
|
477
|
+
& $br @setupArgs
|
|
478
|
+
$rc = $LASTEXITCODE
|
|
479
|
+
} catch { if (-not $npmMode) { Complete-App 1 $oldApp $fromNpm }; throw }
|
|
480
|
+
if (-not $npmMode) { Complete-App $rc $oldApp $fromNpm }
|
|
481
|
+
elseif ($rc -eq 0) {
|
|
482
|
+
if ($dropApp) { try { Remove-Dir $AppDir; Remove-UserPath $AppDir } catch { Write-Host "Could not remove the Bridgerator app folder $AppDir ($_)." } }
|
|
483
|
+
Remove-NpmBridge $keepNpm
|
|
484
|
+
}
|
|
485
|
+
# 3 = stopped with a clear message already shown (pairing code expired, ...): no red error on top;
|
|
486
|
+
# -UseNpm rolls back like a failure (fresh Node.js out, the stopped bridge back).
|
|
487
|
+
if ($rc -eq 3) { if ($npmMode) { Undo-NpmRun }; return }
|
|
488
|
+
if ($rc -ne 0) { throw 'bridgerator setup failed' }
|
|
75
489
|
Write-Host 'Done. Bridgerator starts in the background in about 10 seconds. Check any time with: bridgerator.cmd status'
|
|
490
|
+
Write-Host 'Open ChatGPT and say: Set up Bridgerator'
|
|
76
491
|
|
|
77
|
-
# Next steps (connect ChatGPT, example prompts) on the welcome page
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
492
|
+
# Next steps (connect ChatGPT, example prompts) on the welcome page of the site this script came from -
|
|
493
|
+
# after the FIRST install only; skipped for -NoBrowser and non-interactive sessions. Russian Windows
|
|
494
|
+
# (display language or regional format) gets the Russian page.
|
|
495
|
+
$welcome = "$Site/$(if ($lang -eq 'ru') { 'ru/' })welcome/"
|
|
496
|
+
if ($firstInstall) {
|
|
497
|
+
if (-not $NoBrowser -and [Environment]::UserInteractive) {
|
|
498
|
+
try { Start-Process $welcome } catch { Write-Host "Next steps: $welcome" }
|
|
499
|
+
} else {
|
|
500
|
+
Write-Host "Next steps: $welcome"
|
|
501
|
+
}
|
|
84
502
|
}
|
|
503
|
+
} catch {
|
|
504
|
+
if ($npmMode) { Undo-NpmRun }
|
|
505
|
+
throw
|
|
506
|
+
} finally { Remove-Work; try { $lock.ReleaseMutex() } catch {}; $lock.Dispose() }
|