Max Vilimpoc [Sat, 29 Aug 2026 17:46:46 +0000 (19:46 +0200)]
dotfiles: report Intel VTune rather than installing it
The elevated half now prints whether VTune is on the box, its version and the
path to vtune.exe, and prints the download page when it is not -- adding that
the CPU is not Intel, when it is not.
Not automated on purpose. The offline installer is a ~750 MB download from
registrationcenter-download.intel.com/akdlm/IRC_NAS/<guid>/, and that GUID is
per-release with no "latest" redirect behind it, so every new build would mean
editing a hard-coded link in a script whose whole point is running unattended on
a fresh box. It also only earns its place on Intel silicon, since hardware
event-based sampling reads Intel PMU counters. The unattended incantation is
recorded in the comment and the README for anyone who does want to script it:
-a --silent --cli --eula accept
Detection reads the two Uninstall hives rather than probing a path, so it
follows the install wherever it went, and the CLI path goes through the oneAPI
`latest` junction so it stays right across upgrades. Exercised against the
2026.2.0 install on this box: reports the version and a vtune.exe that exists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMh8i2QzkHNdE3MkKfcaT6
Max Vilimpoc [Sat, 29 Aug 2026 17:38:00 +0000 (19:38 +0200)]
dotfiles: let the group read the kernel logger ACL it was granted
The rights mask handed to EventAccessControl was 0x0FE1 -- the WMI and TRACELOG
rights and nothing else. The SYSTEM and Administrators entries on that GUID
carry 0x120FFF, and the missing 0x120000 is READ_CONTROL and SYNCHRONIZE:
without READ_CONTROL the group cannot read back the descriptor it was just
added to, so EventAccessQuery answers "access denied" whether or not the grant
landed, which makes it useless as the one cheap probe available from the
unelevated account. Now 0x120FE1.
Also corrected, in the step and the README: the ACE is machine state, and a
logon does nothing for it. ETW reads these descriptors into a cache, so a
reboot is what is expected to put it into effect -- the ACE is in the descriptor
(D:...(A;;0xfe1;;;LU)) and xperf -on base is still denied from a fresh shell on
the running system. A first run therefore wants both: a new logon for the group
membership and the privilege, a reboot for this.
Not yet confirmed: whether the reboot is in fact sufficient.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMh8i2QzkHNdE3MkKfcaT6
Max Vilimpoc [Sat, 29 Aug 2026 17:30:32 +0000 (19:30 +0200)]
dotfiles: give the kernel logger's own ACL to Performance Log Users
The group membership and SeSystemProfilePrivilege were necessary and not
sufficient. Measured on this box after signing in with both in place:
xperf -start X -on Microsoft-Windows-Kernel-Process -> exit 0, trace written
xperf -on base -> NT Kernel Logger:
Access is denied. (0x5)
wpr -start GeneralProfile -> 0x80070005
The user-mode session proves the group fixed session control, and wpr's error
moving off "Failed to enable the policy to profile system performance" proves
the privilege took. What is left is the kernel logger itself: it does not use
ETW's default per-GUID descriptor, and the explicit one on
SystemTraceControlGuid does not mention Performance Log Users.
EventAccessQuery on that GUID returns access denied outright from the account,
which is the tell.
So add an ACE for the group with EventAccessControl (EventSecurityAddDACL, so
the entries Windows relies on stay put), carrying the controller rights
including TRACELOG_ACCESS_KERNEL_LOGGER -- the right that names this particular
session. It goes to the group like the privilege does, keeping membership the
single switch, and the log now records the resulting DACL.
The step moves to the FRONT of the elevated script and gains -EtwRightsOnly,
which runs it and exits. It is seconds of LSA and registry work, where a full
run is dominated by three Visual Studio passes that take minutes with nothing to
do -- and iterating on this needed a way to apply it without paying for those.
`exit` inside the try still runs the finally, so the transcript is stopped and
the log left readable by the non-elevated caller.
The README now states the cost plainly: a member of that group can capture
system-wide kernel traces, including paths and command lines from every account
on the box.
Exercised under Windows PowerShell 5.1: the script parses, the EtwAcl interop
compiles, the rights mask reads 0x0FE1, and both EventAccessControl and
EventAccessQuery return a clean "access denied" from a non-elevated shell rather
than marshalling garbage. The grant itself still needs an elevated run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMh8i2QzkHNdE3MkKfcaT6
Max Vilimpoc [Sat, 29 Aug 2026 17:17:40 +0000 (19:17 +0200)]
dotfiles: stop the UAC launch line mangling its own arguments
Adding -TraceUser to the elevated launch gave it a second quoted argument, and
the "".."" doubling used to get quotes through cmd only survives ONE. With
two, the quote-state parsing merges the tail into the -File value, so the
elevated PowerShell was handed
and refused it -- "failed because the file does not have a '.ps1' extension" --
exiting -196608 (0xFFFD0000) before Start-Transcript could run. The batch file
then reported no elevated log and guessed at a cancelled UAC prompt, which is
the one thing that had not happened.
Both values now travel in the environment and the quotes the child needs are
built as [char]34 inside PowerShell, so the command line in the batch file
carries no quote characters of its own beyond the outer pair.
Exercised through cmd against a probe script in a directory with a space in its
name: -File binds, -TraceUser arrives as LATISLAB\Claude, and the child's exit
code still propagates. Against the real script with -Verb RunAs dropped, it now
gets as far as the #Requires elevation check, which is where a non-elevated run
should stop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMh8i2QzkHNdE3MkKfcaT6
Max Vilimpoc [Sat, 29 Aug 2026 16:43:11 +0000 (18:43 +0200)]
dotfiles: let a standard account collect ETW traces
Windows Performance Analyzer is not a Visual Studio component -- VS's own
Performance Profiler is a different, .diagsession-based tool that cannot open an
.etl -- it ships with xperf and wpr in the Windows Performance Toolkit, either
as an optional Windows SDK feature (OptionId.WindowsPerformanceToolkit) or
inside the ADK, which bundles the same toolkit. The WPT step already installed
it; it now detects the toolkit DIRECTORY rather than just xperf.exe, prints the
version of each tool it found (wpa.exe included, since that is the one people
come looking for), and re-asserts the machine PATH entry -- comparing with the
trailing backslash trimmed, because the toolkit's own installer writes one and a
second spelling of the same directory is just noise.
Collection is the half that did not work for an ordinary account, and it fails
in two distinct ways because two distinct things are missing:
xperf -on base -> NT Kernel Logger: Access is denied. (0x5)
wpr -start GeneralProfile -> Failed to enable the policy to profile system
performance.
Controlling ANY event tracing session -- a user-mode one naming a single
provider included, which is the case that shows this is not only about the
kernel -- is checked against the security descriptor ETW keeps per provider
GUID, whose default grants the session-control rights to SYSTEM, Administrators,
the service accounts and BUILTIN\Performance Log Users, and to nobody else.
Switching on the kernel/system provider on top of that needs
SeSystemProfilePrivilege, held by default only by Administrators and
NT SERVICE\WdiServiceHost, and that is the one wpr names in its error.
So grant the privilege to the GROUP and put the account in the group:
membership alone becomes the switch, and enabling the next account is one
net localgroup away with no policy edit. LsaAddAccountRights rather than a
secedit round-trip -- it adds exactly one right to exactly one SID and is a
no-op when already held, where secedit re-applies every user right on the box to
fix one of them. SeDebugPrivilege is deliberately not granted: neither CPU
sampling nor walking stacks in your own processes needs it, and it is equivalent
to handing out administrator.
setup-windows.bat passes -TraceUser across the UAC boundary. Accepting that
prompt with an administrator's credentials runs the elevated half AS that
administrator, so it cannot otherwise tell whose box this is.
Two limits, both documented at the step and in the README. A privilege and a
group membership are read into the access token at LOGON, so the account has to
sign out and back in -- any new logon does, and an ssh login into the box is the
quick way to check without dropping the desktop. And this only helps a
NON-ADMIN account: UAC hands an administrator a filtered token keeping five
harmless privileges, so an admin's ordinary shell still cannot trace however the
policy reads. Analysis was never affected; wpa.exe opens an existing .etl as a
plain user.
Exercised under Windows PowerShell 5.1, which is what the batch file launches:
the script parses, the LSA interop compiles under the in-box CodeDom compiler,
the SID marshalling round-trips S-1-5-32-559, and LsaOpenPolicy fails cleanly
with "Access is denied" from a non-elevated shell. Get-LocalGroup -SID resolves
the localised group name, and the toolkit detection finds the SDK's WPT and
correctly reports its PATH entry as already present. The grants themselves are
unverified: they need an elevated run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMh8i2QzkHNdE3MkKfcaT6
Max Vilimpoc [Fri, 28 Aug 2026 11:22:07 +0000 (13:22 +0200)]
dotfiles: put vswhere.exe on the user PATH
The Visual Studio installer drops vswhere.exe in
%ProgramFiles(x86)%\Microsoft Visual Studio\Installer and nothing adds
that directory to the PATH, so build scripts that locate VS with it -
and VsDevCmd.bat itself - print "'vswhere.exe' is not recognized" on
every run. New VsWhere step in the non-elevated half; it is skipped
with a warning when no VS installer is present.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhsGf8BtmQE1PtS1CAKns4
Max Vilimpoc [Tue, 1 Sep 2026 10:49:52 +0000 (12:49 +0200)]
dotfiles: pin WiX to 5.0.2, the last release that owes no fee
winget install WiXToolset.WiXCLI has no version selector, so it installed
7.0.0. From 6.0 onward the WiX package carries OSMFEULA.txt on top of the
Microsoft Reciprocal License: an Open Source Maintenance Fee agreement
charging a monthly fee to anyone using the prebuilt binaries as part of
revenue-generating activity with annual gross revenue >= US$10,000.
The fee buys the binaries; it is not a license fee and restricts nothing
about what we package. MS-RL is file-scoped and never reached the MSIs
WiX builds under any version. Pinning to 5.0.2 is about the invoice.
Install it as a .NET global tool instead, which takes a --version, and
add the .NET 10 SDK it needs. The MSBuild half still has to be pinned in
each .wixproj; the comment says so where someone will look for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gCzbrN6p3emFUyufuJsyq
Max Vilimpoc [Wed, 26 Aug 2026 12:40:08 +0000 (14:40 +0200)]
dotfiles: a Windows 7 test-target setup script
Provisioning a Windows 7 VM so it can be driven from the host by VBoxManage
guestcontrol takes a handful of settings that are easy to forget and easy to
get wrong, and every one of them here was learned by hitting it:
- Windows Error Reporting's dialog blocks a guestcontrol call until the host's
timeout. A harness that scores a timeout as a failure then invents bugs that
do not exist, so DontShowUI is not a nicety.
- The host watches the guest with controlvm screenshotpng, so a screen saver or
a blanked monitor makes every screenshot useless.
- Bulk transfer over the VirtualBox shared folder is far faster than a copyto
per file, but the mapping is per-user and does not survive into a new
interactive session.
The readiness report is the other half of the point. A test run against a box
with no printer, or no audio capture device, reads as "the software under test
refused" when the truth is "this VM never had one". It reports DWM composition
for the same reason: with composition off, SetWindowDisplayAffinity fails with
error 8 for every value, so anything testing screen-capture protection is
testing nothing -- and Windows 7 forces the Basic theme while the install is
not activated, which is exactly the state a throwaway test VM is usually in.
Two shapes in the DWM check are load-bearing and are commented as such. Under
guestcontrol, redirecting PowerShell's stdout to a file (or capturing it with
for /f) hangs the run until the host times out, so PowerShell prints the verdict
and the batch does not capture it. And the if/else must stay on ONE line: split
across two, PowerShell treats the file as an incomplete command and waits on
stdin forever. Two plausible cheaper checks are also wrong here, measured, and
are called out so nobody re-introduces them: dwm.exe keeps running with
composition off, and HKCU\...\DWM\Composition is the stored preference rather
than the live state. Both report ENABLED while the API returns false.
The elevated half is skipped with a notice rather than attempted, because a
guestcontrol-launched process gets a UAC-filtered token even for an account in
Administrators and cannot elevate itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLgzr9B13msJhCLpnmjNJS
Max Vilimpoc [Mon, 31 Aug 2026 15:25:11 +0000 (17:25 +0200)]
dotfiles: install the common tools every project on these machines expects
ffmpeg, adb and python3-venv join shellcheck in the optional set. They are
common across the projects here rather than specific to any one of them, so
installing them once from this script means a per-project setup script only has
to CHECK for them instead of carrying its own package-manager logic and its own
sudo. That is exactly what RAWcorder's setup-linux-tests.sh now does.
python3-venv is the one that is easy to miss. Debian and Ubuntu split venv and
ensurepip out of python3, so `python3 -m venv` fails on a stock Ubuntu with an
error telling you to apt-get it; Arch bundles both into python, so there is
nothing to add there. Both names go through pick_pkg, like everything else here.
ffprobe is checked but not installed: it has no package of its own and ships
inside ffmpeg, so checking it separately also catches a stripped-down distro
build that leaves it out.
What stays out of this script, and the comment now says so: anything
project-shaped. No virtualenvs, no per-repo Python packages, no test tooling.
Those belong beside the repo that needs them.
Also fixes a latent SC2318 in install_agi_deb. It read
local ver="$1" tmp="$2" deb="$tmp/agi.deb" depends
and the words of a `local` are expanded before `local` runs, so $tmp there is
the OUTER scope's tmp, not the parameter being assigned beside it. The caller
passes AGI_TMP and no outer tmp exists, so deb resolved to "/agi.deb" -- the
download would have landed in the filesystem root, which the sudo re-exec would
have permitted. Never observed, because AGI has not been installed from the .deb
path on this machine. The script passes shellcheck on itself again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXBBHFrwVKRsAMhtS7EkCR
Max Vilimpoc [Mon, 31 Aug 2026 09:20:55 +0000 (11:20 +0200)]
dotfiles: install AGI from setup-linux.sh, on Ubuntu and on CachyOS
The script died on anything without apt-get, which is most of the machines it
now has to run on, so the package layer is a two-line wrapper over apt-get and
pacman and the rsync lists gained their Arch spellings -- base-devel, pkgconf,
and the libraries without the -dev half Arch does not split out.
AGI ships a .deb and a .zip per release; Debian and Ubuntu get the .deb, Arch
and CachyOS the .zip unpacked to /opt/agi, since there is no AUR package for it
(the AUR RPC knows no agi, agi-bin or android-graphics-inspector). The .zip
route replays the .desktop and MIME files the .deb installs, so both routes
leave the same system behind, and only ever replaces a /opt/agi that carries a
build.properties.
The .deb cannot be installed as it stands. It declares "Depends:
openjdk-22-jre, libgtk-3-0, libwebkit2gtk-4.1-0" and Ubuntu has never packaged
openjdk-22 -- jammy through resolute ship 11, 17, 21 and 25 -- so apt has
nothing to resolve that against. Nothing in the package needs 22 either: it
bundles no JRE, the Go launcher settles for >= 11, and the highest class file
version in gapic.jar is 63, i.e. Java 19. openjdk-21-jre satisfies both and
exists on every release from 22.04 on, so the install picks a JRE that is
actually in the archive and rewrites that one control field to name it.
Only the control member is rebuilt, with ar; dpkg-deb -R/-b would unpack and
recompress 230MB of payload for a one-line edit. Verified against the real
84MB package: member order preserved, Depends retargeted, and dpkg-deb still
parses the result. Should any of that fail, the zip installer is the fallback,
so the .deb path can only cost time.
libgtk-3-0 became libgtk-3-0t64 in 24.04 and SWT wants webkit 4.1 rather than
the 4.0 only jammy has, so both are chosen by asking the archive rather than by
name. adb is installed too: it is not a hard dependency, but gapis carries the
string "adb could not be found from ANDROID_HOME or PATH", which is all AGI can
do without one.
--agi-only and --no-agi split the two halves; --minimal implies --no-agi.
AGI_VERSION pins a release, otherwise the version comes from the releases API
with the last-checked 3.3.3 as the offline fallback, and a re-run whose
/opt/agi/build.properties already matches skips the 85MB download. No pacman
-Sy anywhere -- refreshing the database without upgrading is how an Arch box
ends up half-broken -- so a stale database is reported rather than worked
around.
Exercised on CachyOS: every --dry-run path, the version lookup, and AGI 3.3.3
itself starting against the installed JRE with gtk3 and webkit2gtk-4.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqwcPKuLX7xML2DZqHnNJY
Max Vilimpoc [Fri, 28 Aug 2026 00:05:27 +0000 (02:05 +0200)]
dotfiles: install VirtualBox with the other winget packages
The test VMs this box drives are VirtualBox ones -- it is why sshd's firewall
rule is widened to every profile, a host-only or bridged adapter being routinely
classified Public -- so the hypervisor belongs in the same list as everything
else the box is provisioned with, rather than being the one install left to do
by hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D4ozcQcqMSJBi2Ez4Pfxyc
Max Vilimpoc [Fri, 28 Aug 2026 00:04:08 +0000 (02:04 +0200)]
dotfiles: give git the ssh.exe that pushes at line rate
core.sshCommand pointed at the in-box System32 client, which reads its stdin
3KB at a time -- the same limit that held an rsync push to ~17MB/s, and it
applies to anything git pushes through it too. The elevated half now unpacks a
client without that limit beside rsync.exe, so prefer it and keep the in-box one
as the fallback. Same client from the same source: same ~/.ssh, same ssh-agent
named pipe, same known_hosts.
Candidates are tried by RUNNING them rather than by Test-Path. The fast build
carries no libcrypto of its own -- it links the one the OpenSSH Client
capability puts in System32 -- so on an image without that capability it is a
file that exists and a binary that will not start, and the next `git push` is a
bad place to discover it. Two traps in doing that from this script: with
$ErrorActionPreference = 'Stop' a native command's stderr becomes a terminating
error, and `ssh -V` writes its version to stderr, so the WORKING client is the
one that looks broken; and an exe that cannot start never sets $LASTEXITCODE,
leaving the stale 0 from the last native command that did run to read as
success. Hence the EAP dance and the explicit $global:LASTEXITCODE = $null.
setup-windows.bat runs the elevated half first for this: the fast ssh.exe has to
be on disk before the git config step can prefer it. The non-elevated half
still runs when the elevated one failed -- nothing in it depends on that half,
and the fallback is the client Windows already has -- and the elevated exit code
is still what the batch file exits with.
Exercised against real binaries under Windows PowerShell 5.1, with
GIT_CONFIG_GLOBAL pointed at a scratch file: fast client present picks
10.0p2, install directory empty falls back to the in-box 9.5p1, a candidate
that exits non-zero and one that cannot start at all both warn and fall back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D4ozcQcqMSJBi2Ez4Pfxyc
Max Vilimpoc [Thu, 27 Aug 2026 23:56:49 +0000 (01:56 +0200)]
dotfiles: install the rsync release's ssh.exe beside rsync.exe
The rsync-windows release is now one zip per architecture -- rsync.exe, the
ssh.exe it runs, and the licence texts, under exactly those names -- rather
than a bare rsync.exe, so the pinned .../download/v3.5.0-g521ad8ad/rsync.exe
this script fetched no longer exists.
Fetch rsync-windows-x64.zip or rsync-windows-x86.zip by OS bitness, verify it
against the .sha256 published beside it, unpack to a scratch directory and move
out the four files we asked for -- so a future release adding a fifth cannot
quietly drop it onto the machine PATH. The two exes go in together on purpose:
rsync.exe prefers an ssh.exe in its own directory, and the release builds one
because the client Windows ships reads its stdin 3KB at a time, which holds a
transfer *from* the box at ~17MB/s however fast the link is. Nothing else
about it differs -- same ~/.ssh, same ssh-agent, same known_hosts -- and a bare
`ssh` still resolves to the in-box client, which sits ahead of C:\Tools\rsync
on the PATH.
That ssh.exe links against the libcrypto.dll the OpenSSH Client capability puts
in System32, and ships no copy of its own, so that capability is now installed
here rather than assumed: it was already the thing ssh-agent, the git
core.sshCommand in the non-elevated half, and rsync's own transport all depend
on. Where it is absent, or its LibreSSL is older than the 3.8.2 the release is
built against, the script says so and installs rsync alone.
The URL follows the releases/latest/download/ redirect rather than the API,
whose unauthenticated 60/hour per-IP limit a provisioning run behind a shared
NAT can genuinely exhaust; pin the tag in $RsyncUrl to hold a box on a build.
Verified end to end against the live release under Windows PowerShell 5.1 --
which is what the .bat launches, and which refuses to Expand-Archive anything
not named .zip, hence "download-$RsyncAsset" for the scratch file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D4ozcQcqMSJBi2Ez4Pfxyc
Max Vilimpoc [Wed, 26 Aug 2026 10:33:18 +0000 (12:33 +0200)]
dotfiles: point git at the Windows SSH client, split out the non-elevated half
git config core.sshCommand -> %WINDIR%/System32/OpenSSH/ssh.exe, so git shares
the Windows ssh-agent that the elevated half enables. Git for Windows otherwise
prefers its bundled MSYS2 ssh.exe, which cannot reach that agent (Win32-OpenSSH
publishes it on a named pipe the MSYS2 build does not speak), leaving keys added
with `ssh-add` invisible to git.
The .bat already carried a bare version of this line, but it was inert on a fresh
box: `winget install Git.Git` runs a few lines above it, so that cmd session's
PATH predates the install and `git config` only printed "not recognized" before
carrying on. Resolve git.exe explicitly (PATH, then the standard install roots)
before configuring anything.
Move the PowerShell-driven per-user work - BinSkim, the WinMerge PATH edit, and
the git config - out of the .bat into setup-windows-no-uac.ps1, mirroring
setup-windows-with-uac.ps1. It is standalone-runnable, takes -Skip to re-run a
subset, runs each step independently (a failure warns, the rest still run, exit 1
if any did), and warns when run elevated, since every step writes per-user state
that would otherwise land in the administrator's profile. The .bat is left as
winget installs plus two script calls.
Two behaviour changes while moving that code:
- The git identity is empty strings rather than PLACEHOLDER_NAME, and is skipped
when unset instead of being written. The placeholder appeared both in the
assignment and in the check that guarded it, so a find/replace over the name -
exactly what the README told you to do - silently disabled the guard.
- The BinSkim version marker is written on the up-to-date path too. Previously it
was written only after a download, so an install predating the marker re-derived
its version from BinSkim.exe's ProductVersion on every run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019avXmzifMt4wJnJzQDUJCU
Max Vilimpoc [Tue, 25 Aug 2026 10:45:40 +0000 (12:45 +0200)]
dotfiles: sync the Windows provisioning scripts
Three changes made in the other copy of these scripts, ported back so
the two do not drift. The scripts are now byte-identical apart from a few
naming lines and the one divergence that is deliberate: this copy keeps
the PLACEHOLDER git identity, which the README tells you to edit before
running.
OpenSSH Server. Installed from the Windows on-demand capability (10/1809
and later), set Automatic, started, and reachable on all firewall
profiles. That last part is the one worth having: the capability ships
its own inbound rule, but it is Private-only on some images, and a VM's
host-only or bridged adapter gets classified Public more often than not
-- which presents as a service that is plainly running and plainly
unreachable.
That rule is adopted rather than duplicated. OpenSSH-Server-In-TCP is the
name the capability itself uses, so a second rule beside it under another
name would leave the narrow one in place and merely work around it, while
one under the same name would collide. Widen it to all profiles if it
exists, create it if it does not. One rule either way, under the name the
platform expects.
rsync. Windows ships the SSH transport and nothing to run over it, so
`rsync host:path` has no remote end. The nuket/rsync-windows build is
downloaded to C:\Tools\rsync and added to the machine PATH. Not "Program
Files", because the fallback when PATH lookup fails is --rsync-path and a
path with spaces is painful to quote through two shells. Machine rather
than user PATH, because the remote end runs as `rsync --server ...` in a
non-interactive session with no login shell: Win32-OpenSSH composes that
environment from the registry, so a machine entry resolves there and does
so for every account on the box. sshd is restarted after the write, since
the running service holds the environment it started with.
BinSkim now checks before it fetches. The .nupkg is a self-contained .NET
build -- 141 MB at 4.4.9.11 -- and the old code downloaded it every run
before working out it had nothing to do. The flat-container index is a
few KB of JSON; take the newest non-prerelease and compare against
nupkg-version.txt beside the installed tool. The download URL now
interpolates the version we checked, rather than the v2 /package/<id>
endpoint that redirects to whatever is newest right now. The PATH append
moved out of the download branch so a lost PATH entry no longer costs
141 MB to repair.
Both new sections warn rather than throw: a box that cannot run sshd
should still finish provisioning the toolchain it came for.
README picks up the remote-access notes, including the authorized_keys
ACL requirement and the separate file that accounts in the Administrators
group need.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Max Vilimpoc [Wed, 5 Aug 2026 09:48:21 +0000 (11:48 +0200)]
dotfiles: Windows dev-box provisioning scripts
Extracted from a native Windows project so the box setup can be reused
and versioned on its own.
setup-windows.bat runs the non-elevated half (winget installs, user PATH
edits for WinMerge and BinSkim, global git config) and then launches
setup-windows-with-uac.ps1 elevated, printing its transcript when the
elevated window closes.
setup-windows-with-uac.ps1 enables ssh-agent and installs Visual Studio
2022 Community in three labelled passes (base C++ workload, Clang/LLVM,
v141 + Windows XP toolset), the WDK 10.0.26100, and the Windows
Performance Toolkit.
The global git identity is PLACEHOLDER_NAME / PLACEHOLDER_EMAIL and must
be edited before the script is run. The runtime transcript
(setup-windows-uac.log) is gitignored: it embeds local machine paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>