]> vilimpoc.org git repositories - dotfiles/blobdiff - setup-windows-with-uac.ps1
dotfiles: stop the ssh.exe check from deleting the ssh.exe it verifies
[dotfiles] / setup-windows-with-uac.ps1
index c935e2d7c58eb911d35bdb10770b28acb7a96581..4b7ea948dccce4a7c6b51b0b256499c10fd77716 100644 (file)
     - OpenSSH Server (sshd) capability: automatic + started + inbound TCP 22\r
     - rsync for Windows (nuket/rsync-windows) in C:\Tools\rsync, on the machine\r
       PATH: rsync.exe plus the ssh.exe it runs, out of the release zip for this\r
-      architecture (there is no ARM64 zip; ARM64 gets the x64 one, and the\r
-      ssh.exe is kept only if it actually starts)\r
+      architecture - x86, x64 and arm64 are all published, so both binaries are\r
+      native everywhere. The ssh.exe is kept only if it actually starts.\r
     - Visual Studio Community (C++ desktop workload, x86/x64 AND ARM64 build\r
-      tools, Spectre libs, WDK VSIX, Win11 SDK 26100, Clang/LLVM, and - on x64\r
-      only - the v141 + Windows XP targeting toolset). VS 2022 on x64, VS 2026\r
-      on ARM64; see $VsChannel.\r
-    - Windows Driver Kit 10.0.26100\r
+      tools, Spectre libs, Win11 SDK 26100, and - on x64 only - the WDK VSIX,\r
+      Clang/LLVM, and the v141 + Windows XP targeting toolset). VS 2022 on x64,\r
+      VS 2026 on ARM64; see $VsChannel.\r
+    - Windows Driver Kit 10.0.26100 (x64 only)\r
     - Windows Performance Toolkit - xperf, wpr and Windows Performance Analyzer\r
       (wpa.exe) - on the machine PATH\r
 \r
   18 (VS 2026) on ARM64, where that toolset cannot exist anyway.\r
 \r
   ARCHITECTURE. Runs on x64 and on ARM64 (Windows 11 on Arm). On ARM64 the\r
-  Visual Studio installer, MSVC and clang-cl are all native ARM64 and\r
-  cross-compile ARM64/x64/x86 targets; what changes is called out at each step,\r
-  and $HostArch below is what drives it.\r
+  Visual Studio installer and MSVC are native ARM64 and cross-compile\r
+  ARM64/x64/x86 targets.\r
+\r
+  The two architectures deliberately provision DIFFERENT things. x64 is the full\r
+  workstation; ARM64 is a constrained VM set up as a single-compiler build box -\r
+  MSVC only, with clang-cl and the WDK dropped there. Neither is dropped for lack\r
+  of an ARM64 build; see the comments on $ClangComponents and the WDK step.\r
+  What changes is called out at each step, and $HostArch below is what drives it.\r
 #>\r
 \r
 $ErrorActionPreference = 'Stop'\r
@@ -37,22 +42,30 @@ $ErrorActionPreference = 'Stop'
 # ---------------------------------------------------------------------------\r
 # Host architecture\r
 #\r
-# RuntimeInformation.OSArchitecture rather than PROCESSOR_ARCHITECTURE: this\r
-# script can be launched by a 32-bit or an emulated x64 PowerShell, either of\r
-# which reports the emulated architecture in the environment variable while this\r
-# API still reports the real one. Values seen here: X64, Arm64, X86.\r
+# Read from the machine environment in the registry, because this script can be\r
+# launched by a 32-bit or an emulated x64 PowerShell and BOTH of the obvious\r
+# sources report the emulated architecture in that case. Measured on this box,\r
+# from the x64 PowerShell an x64 shell resolves to:\r
+#     [RuntimeInformation]::OSArchitecture      X64     <- wrong\r
+#     $env:PROCESSOR_ARCHITECTURE               AMD64   <- wrong\r
+#     HKLM\...\Session Manager\Environment      ARM64   <- right\r
+# OSArchitecture is documented as the OS's architecture, and on .NET Core it is;\r
+# under .NET Framework on Prism it is not, so it is kept only as a fallback.\r
 #\r
 # $IsArm64 gates:\r
-#   - which rsync-windows release zip is fetched (there is no ARM64 one)\r
+#   - which rsync-windows release zip is fetched (x86 / x64 / arm64, all native)\r
 #   - which Visual Studio generation is driven ($VsChannel: 18 on Arm, 17 on x64)\r
-#   - the Visual Studio component groups (ARM64 build tools in; the v141/XP\r
-#     toolset out, because it has no ARM64-hosted compiler and Microsoft does not\r
-#     ship Windows XP targeting for Arm hosts. Clang/LLVM is installed on every\r
-#     architecture and is native on Arm, so it is NOT gated here)\r
+#   - the Visual Studio component groups: ARM64 build tools in; the v141/XP\r
+#     toolset out because it has no ARM64-hosted compiler and Microsoft does not\r
+#     ship Windows XP targeting for Arm hosts; Clang/LLVM and the WDK VSIX out by\r
+#     policy rather than by limitation - both are available and native on Arm\r
+#   - whether the Windows Driver Kit is installed at all\r
 #   - what the VTune and WPT steps report\r
 # ---------------------------------------------------------------------------\r
-$HostArch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()\r
-$IsArm64  = ($HostArch -eq 'Arm64')\r
+$rawArch  = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -ErrorAction SilentlyContinue).PROCESSOR_ARCHITECTURE\r
+if (-not $rawArch) { $rawArch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() }\r
+$IsArm64  = ($rawArch -eq 'ARM64')\r
+$HostArch = if ($IsArm64) { 'Arm64' } elseif ($rawArch -eq 'AMD64') { 'X64' } else { $rawArch }\r
 \r
 function Write-Step([string]$Msg) {\r
     Write-Host "`n==> $Msg" -ForegroundColor Cyan\r
@@ -308,20 +321,23 @@ try {
 # ---------------------------------------------------------------------------\r
 Write-Step 'rsync for Windows'\r
 $RsyncRepo  = 'nuket/rsync-windows'\r
-# The release publishes exactly two assets - x64 and x86 - and no ARM64 one, so\r
-# on ARM64 the x64 build is the right pick: it runs under Prism, and an emulated\r
-# x64 rsync still moves data far faster than the ~17MB/s stdin cap that the whole\r
-# reason for using this build is to avoid. (The transfer is I/O-bound on the\r
-# socket, not on emulated CPU.) Selected off $HostArch rather than\r
-# Is64BitOperatingSystem, which answers "true" on ARM64 and so cannot tell the\r
-# two 64-bit cases apart.\r
+# The release publishes x86, x64 and - since v3.5.0-gdeeda96f - arm64, so every\r
+# architecture this script runs on gets a native build. ARM64 used to take the\r
+# x64 zip under emulation, which was defensible (a transfer is bound by the\r
+# socket, not by emulated CPU) but cost the release's ssh.exe: that x64 binary\r
+# could not load the ARM64 libcrypto.dll in System32 and had to be deleted on\r
+# every run. The arm64 asset ships an ARM64 rsync.exe AND an ARM64 ssh.exe, so\r
+# both halves are now native and the fast client survives.\r
+#\r
+# Selected off $HostArch rather than Is64BitOperatingSystem, which answers "true"\r
+# on ARM64 and so cannot tell the two 64-bit cases apart.\r
 $RsyncAsset = switch ($HostArch) {\r
     'X64'   { 'rsync-windows-x64.zip' }\r
-    'Arm64' { 'rsync-windows-x64.zip' }\r
+    'Arm64' { 'rsync-windows-arm64.zip' }\r
     default { 'rsync-windows-x86.zip' }\r
 }\r
 if ($IsArm64) {\r
-    Write-Host '    ARM64: no native asset is published, using the x64 build under emulation.' -ForegroundColor Yellow\r
+    Write-Host '    ARM64: using the native arm64 asset (rsync.exe and ssh.exe both ARM64).' -ForegroundColor Green\r
 }\r
 # The /releases/latest/download/ redirect rather than the API: unauthenticated\r
 # API calls are rate-limited to 60/hour per IP, which a provisioning run behind a\r
@@ -347,15 +363,11 @@ try {
         if ($v -and ([version]($v -replace '[^0-9.]', '')) -lt [version]'3.8.2') {\r
             Write-Warning "$SysCrypto is LibreSSL $v; the release's ssh.exe is built against 3.8.2 (Windows OpenSSH Client 9.5). Update Windows, or expect ssh.exe not to start."\r
         }\r
-        if ($IsArm64) {\r
-            # On ARM64 the presence of libcrypto.dll proves less than it does on\r
-            # x64: System32 holds the ARM64 build of it, and the ssh.exe in the\r
-            # zip is x64. Whether an emulated x64 process can load that DLL comes\r
-            # down to whether it is a plain ARM64 binary or an ARM64X one - not\r
-            # something worth deciding by parsing the PE header, when running the\r
-            # binary answers it outright. Unpack it, then run it (below).\r
-            Write-Host '    ARM64: the x64 ssh.exe will be verified by running it, not by assuming.' -ForegroundColor Yellow\r
-        }\r
+        # No architecture special-case here any more: the asset chosen above\r
+        # matches the host, so the release's ssh.exe and System32's libcrypto.dll\r
+        # are the same architecture on every box. The run-it check below still\r
+        # happens on all of them - it is cheap, and it is what caught the ARM64\r
+        # mismatch back when this took the x64 zip.\r
     }\r
 \r
     # Download and unpack beside the targets, not over them, so an interrupted\r
@@ -409,11 +421,14 @@ try {
     # This matters more than it looks. rsync.exe prefers an ssh.exe sitting in its\r
     # own directory, so a present-but-unstartable one does not degrade to the\r
     # in-box client - it breaks rsync outright, and the error you get is a remote\r
-    # shell that died rather than anything naming ssh.exe. The two ways to land\r
-    # there are a missing/old System32 libcrypto.dll (x64 boxes) and an ARM64 box\r
-    # whose ARM64 libcrypto cannot be loaded by this x64 binary. Removing it is\r
-    # the repair in both cases: rsync then falls back to the ssh on the PATH,\r
-    # which on ARM64 is the native in-box client.\r
+    # shell that died rather than anything naming ssh.exe. The way to land there\r
+    # is a missing or too-old System32 libcrypto.dll. Removing it is the repair:\r
+    # rsync then falls back to the ssh on the PATH, which is the in-box client.\r
+    #\r
+    # This also used to fire on every ARM64 run, when the x64 asset was the only\r
+    # 64-bit one published and its ssh.exe could not load the ARM64 libcrypto\r
+    # (exit 0xC0000135, STATUS_DLL_NOT_FOUND). The arm64 asset fixed that at the\r
+    # source; the check stays because it is how that was found in the first place.\r
     #\r
     # EAP back to Continue for the call: ssh -V writes its version to STDERR, and\r
     # under $ErrorActionPreference = 'Stop' a native command's stderr becomes a\r
@@ -421,13 +436,26 @@ try {
     # $global:LASTEXITCODE is cleared first because an exe that cannot start at\r
     # all throws without setting one, and the stale 0 from the previous native\r
     # command would otherwise read as success.\r
+    #\r
+    # DRAIN THE OUTPUT BEFORE READING $LASTEXITCODE. `... | Select-Object -First 1`\r
+    # was the original spelling here, and it is a trap: Select-Object stops the\r
+    # pipeline the instant it has its one object, and that is when ssh.exe has\r
+    # WRITTEN its banner - not when it has exited. PowerShell then tears the\r
+    # pipeline down, kills the still-running process, and reports -1. Measured on a\r
+    # healthy binary on an x64 box: 24 of 25 runs reported exit -1 through\r
+    # Select-Object, and 25 of 25 reported 0 when the output was collected first.\r
+    #\r
+    # So this check was deleting a perfectly good ssh.exe on almost every run, on\r
+    # every architecture, and the resulting fallback to the in-box client - the\r
+    # ~17MB/s one this whole step exists to avoid - looked like a property of the\r
+    # release rather than a bug in the test. Collect, THEN take the first line.\r
     $SshExe = Join-Path $RsyncDir 'ssh.exe'\r
     if ($WantSsh -and (Test-Path $SshExe)) {\r
         $prevEap = $ErrorActionPreference\r
         $ErrorActionPreference = 'Continue'\r
         $global:LASTEXITCODE = $null\r
         $sshVer = $null\r
-        try { $sshVer = (& $SshExe -V 2>&1 | Select-Object -First 1) } catch { }\r
+        try { $sshOut = (& $SshExe -V 2>&1); $sshVer = $sshOut | Select-Object -First 1 } catch { }\r
         finally { $ErrorActionPreference = $prevEap }\r
         if ($LASTEXITCODE -eq 0) {\r
             Write-Host "    ssh.exe runs: $sshVer"\r
@@ -501,29 +529,39 @@ $BaseComponents = @(
 \r
     # Windows 11 SDK — build number must match the WDK below\r
     'Microsoft.VisualStudio.Component.Windows11SDK.26100'\r
-\r
-    # WDK Visual Studio extension (VSIX). The silent wdksetup.exe /quiet does NOT\r
-    # install this (it only prompts interactively), so it must be added here.\r
-    'Component.Microsoft.Windows.DriverKit'\r
 )\r
 \r
+# WDK Visual Studio extension (VSIX). The silent wdksetup.exe /quiet does NOT\r
+# install this (it only prompts interactively), so it has to be asked for here.\r
+#\r
+# Follows the WDK itself: x64 only. On ARM64 the kit is not installed (see the\r
+# WDK step for why), and this VSIX on its own is just the driver project\r
+# templates and property pages with no headers, libs or tools behind them.\r
+if (-not $IsArm64) {\r
+    $BaseComponents += 'Component.Microsoft.Windows.DriverKit'\r
+}\r
+\r
 # Clang/LLVM toolset (ClangCL, used in CMakePresets.json). Two parts: the Clang\r
 # compiler itself, plus the MSBuild integration providing the "ClangCL" toolset.\r
 #\r
-# Installed on every architecture, ARM64 included, and native there - not an\r
-# emulated x64 compiler. Two things establish that: the VSIX is\r
+# x64 ONLY, and NOT because ARM64 cannot have it - it can, natively. The VSIX is\r
 # productArch=neutral with no chip/machineArch restriction, so the Arm installer\r
-# offers it; and MSVC's VC\Tools\Llvm tree is partitioned by HOST architecture\r
-# (bin = x86, x64\bin = x64, ARM64\bin = ARM64) with genuine ARM64 binaries\r
-# already in ARM64\bin, which is where clang-cl.exe lands. Unlike the v141/XP\r
-# group below, nothing technical is in the way.\r
+# offers it, and MSVC's VC\Tools\Llvm tree is partitioned by HOST architecture\r
+# (bin = x86, x64\bin = x64, ARM64\bin = ARM64) with genuine ARM64 binaries in\r
+# ARM64\bin, which is where clang-cl.exe lands. It was installed and working here\r
+# before this split.\r
 #\r
-# This is the compiler diversity the box is after: MSVC and clang-cl over the\r
-# same sources, both native.\r
-$ClangComponents = @(\r
-    'Microsoft.VisualStudio.Component.VC.Llvm.Clang'\r
-    'Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset'\r
-)\r
+# It is dropped on ARM64 because that machine is a constrained VM provisioned as\r
+# a single-compiler build box: MSVC and nothing else. Compiler diversity - MSVC,\r
+# VS clang-cl and upstream LLVM over the same sources - now lives entirely on the\r
+# x64 machine, which is the one with room for three toolchains. Empty here means\r
+# Invoke-VsModify is never called for this group.\r
+$ClangComponents = if ($IsArm64) { @() } else {\r
+    @(\r
+        'Microsoft.VisualStudio.Component.VC.Llvm.Clang'\r
+        'Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset'\r
+    )\r
+}\r
 \r
 # Windows XP targeting (v141_xp toolset, used in CMakePresets.json). The v141\r
 # (VS2017) build tools provide the 14.16 compiler that the XP toolset wraps;\r
@@ -619,7 +657,14 @@ Write-Host "    OK: bootstrapper is a PE ($([math]::Round((Get-Item $VsBootstrap
 # offered a hard failure here would cost you the whole toolchain over a component\r
 # you can add later from the installer UI.\r
 Invoke-VsModify -Label 'base toolset + workload' -Ids $BaseComponents\r
-Invoke-VsModify -Label 'Clang / LLVM'            -Ids $ClangComponents -Optional\r
+if ($IsArm64) {\r
+    Write-Step 'Visual Studio: Clang / LLVM'\r
+    Write-Host '    Skipped on ARM64 by choice, not by limitation: a native ARM64 clang-cl is' -ForegroundColor Yellow\r
+    Write-Host '    offered here and works. This box is provisioned with MSVC alone; the' -ForegroundColor Yellow\r
+    Write-Host '    multi-compiler builds belong on the x64 machine.' -ForegroundColor Yellow\r
+} else {\r
+    Invoke-VsModify -Label 'Clang / LLVM' -Ids $ClangComponents -Optional\r
+}\r
 if ($IsArm64) {\r
     Write-Step 'Visual Studio: Windows XP (v141 + WinXP)'\r
     Write-Host '    Skipped on ARM64: the v141 (14.16) toolset ships x86/x64-hosted compilers only,' -ForegroundColor Yellow\r
@@ -680,13 +725,21 @@ if (-not $InstallPath) {
     $clangArm = Test-Path (Join-Path $llvmRoot 'ARM64\bin\clang-cl.exe')\r
     $clangX64 = Test-Path (Join-Path $llvmRoot 'x64\bin\clang-cl.exe')\r
     $clangX86 = Test-Path (Join-Path $llvmRoot 'bin\clang-cl.exe')\r
-    if ($clangArm -or $clangX64 -or $clangX86) {\r
-        Write-Host "    clang-cl: $(@(if ($clangArm) {'ARM64'}; if ($clangX64) {'x64'}; if ($clangX86) {'x86'}) -join ', ')" -ForegroundColor Green\r
-        # On ARM64 the x64 build would still run, under emulation - so say plainly\r
-        # whether the NATIVE one is the one that landed.\r
-        if ($IsArm64 -and -not $clangArm) {\r
-            Write-Warning 'No ARM64-hosted clang-cl - the x64 one would run under emulation. Re-run the Clang/LLVM pass, or add "C++ Clang tools for Windows" in the installer.'\r
+    $clangAny = $clangArm -or $clangX64 -or $clangX86\r
+    if ($IsArm64) {\r
+        # Not installed here by design, so its absence is the expected result and\r
+        # must not read as a fault. Its PRESENCE is the thing worth a line: a box\r
+        # provisioned before this split still carries it, and the component stays\r
+        # until it is explicitly removed - the installer never uninstalls what you\r
+        # simply stopped asking for.\r
+        if ($clangAny) {\r
+            Write-Host '    clang-cl: present but no longer provisioned on ARM64 - left over from an earlier run.' -ForegroundColor Yellow\r
+            Write-Host '    Remove it with: vs_installer.exe modify --installPath "<path>" --remove Microsoft.VisualStudio.Component.VC.Llvm.Clang --remove Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --quiet' -ForegroundColor Yellow\r
+        } else {\r
+            Write-Host '    clang-cl: n/a on ARM64 (MSVC only by design; use the x64 box for ClangCL).' -ForegroundColor DarkGray\r
         }\r
+    } elseif ($clangAny) {\r
+        Write-Host "    clang-cl: $(@(if ($clangArm) {'ARM64'}; if ($clangX64) {'x64'}; if ($clangX86) {'x86'}) -join ', ')" -ForegroundColor Green\r
     } else {\r
         Write-Warning 'clang-cl not found - the ClangCL presets will fail. Add the "C++ Clang tools for Windows" component.'\r
     }\r
@@ -715,6 +768,26 @@ if (-not $InstallPath) {
 # Build 26100 matches the Windows 11 SDK installed above.\r
 # Provides IddCx (iddcx.h / iddcx.lib) and UMDF 2.x for Indirect Display Drivers.\r
 # linkid=2335869 -> WDK 26100.6584 (per Microsoft "Other WDK Downloads").\r
+#\r
+# x64 ONLY. Two reasons, and the second is the one that decided it:\r
+#\r
+#   1. The ARM64 box builds user-mode software; no driver is developed on it.\r
+#\r
+#   2. This exact WDK cannot install next to the SDK that VS 2026 brings. The\r
+#      WDK requires the MATCHING Windows SDK revision, and revisions are not\r
+#      side-by-side - Include\10.0.26100.0 is one directory whichever revision\r
+#      wrote it. VS 2026 installs SDK 26100.7705; linkid=2335869 is WDK\r
+#      26100.6584, which is the kit paired with VS 2022. So wdksetup.exe aborts:\r
+#      exit 15605, WER signature "WDK / 10.1.26100.6584 / Install / 0x80070642"\r
+#      (0x642 = 1602, ERROR_INSTALL_USEREXIT - a silent install cancelling\r
+#      itself on a failed prerequisite check). It is not an ARM64 or emulation\r
+#      problem; the same pairing fails on x64.\r
+#\r
+#      If a driver ever does need building here, the fix is the WDK that matches\r
+#      this generation - Microsoft's "Supported and other WDK downloads" table\r
+#      pairs VS 2026 with the 28000.x kit - not this link.\r
+#\r
+# On x64 with VS 2022 the SDK and this WDK are the matched pair, so it installs.\r
 # ---------------------------------------------------------------------------\r
 $WdkVersion = '10.0.26100'\r
 # Both hives. The WDK installer is a 32-bit program, so on x64 it writes under\r
@@ -728,7 +801,13 @@ $WdkInstalledRoot = @(
 ) | ForEach-Object { (Get-ItemProperty $_ -ErrorAction SilentlyContinue).WdkBinRootVersioned } |\r
     Where-Object { $_ } | Select-Object -First 1\r
 \r
-if ($WdkInstalledRoot -and $WdkInstalledRoot -match [regex]::Escape($WdkVersion)) {\r
+if ($IsArm64) {\r
+    Write-Step 'Windows Driver Kit'\r
+    Write-Host '    Skipped on ARM64: this box builds user-mode software only, and WDK 26100.6584' -ForegroundColor Yellow\r
+    Write-Host '    cannot install beside the 26100.7705 SDK that Visual Studio 2026 brings - the' -ForegroundColor Yellow\r
+    Write-Host '    kit needs the matching SDK revision and they are not side-by-side. See the' -ForegroundColor Yellow\r
+    Write-Host '    comment above this step for the exit code and what to use instead.' -ForegroundColor Yellow\r
+} elseif ($WdkInstalledRoot -and $WdkInstalledRoot -match [regex]::Escape($WdkVersion)) {\r
     # Re-running wdksetup.exe for an already-present version returns exit code\r
     # 2008 (maintenance mode / nothing to do), which is not a real failure.\r
     Write-Step "WDK $WdkVersion already installed - skipping ($WdkInstalledRoot)"\r