| File | Purpose |
| --- | --- |
-| `setup-windows.bat` | Entry point. Runs the winget installs, then the non-elevated script, then launches the elevated half and prints its log. |
+| `setup-windows.bat` | Entry point. Runs the winget installs, then launches the elevated half and prints its log, then runs the non-elevated script. |
| `setup-windows-no-uac.ps1` | The non-elevated, per-user half: WinMerge and BinSkim on the user `PATH`, and the global git config (identity, plus `core.sshCommand`). Can also be run directly from an ordinary prompt. |
-| `setup-windows-with-uac.ps1` | The elevated half, started via UAC by the batch file. Enables `ssh-agent`, installs the OpenSSH Client and Server capabilities and starts `sshd`, unpacks the `rsync-windows` release zip for this architecture (`rsync.exe` plus the `ssh.exe` it runs) into `C:\Tools\rsync` on the machine `PATH`, then installs Visual Studio 2022 Community with the required components, the WDK, and the Windows Performance Toolkit. Can also be run directly from an Administrator prompt. |
+| `setup-windows-with-uac.ps1` | The elevated half, started via UAC by the batch file. Enables `ssh-agent`, installs the OpenSSH Client and Server capabilities and starts `sshd`, unpacks the `rsync-windows` release zip for this architecture (`rsync.exe` plus the `ssh.exe` it runs) into `C:\Tools\rsync` on the machine `PATH`, then installs Visual Studio 2022 Community with the required components, the WDK, and the Windows Performance Toolkit, and reports whether Intel VTune Profiler is present. It also puts one ordinary account into `Performance Log Users`, so it can run user-mode ETW sessions unelevated. Can also be run directly from an Administrator prompt — pass `-TraceUser DOMAIN\user` to name that account, and `-EtwRightsOnly` to do that step alone. |
+| `setup-windows-7-test-env.bat` | Prepares a **Windows 7 VM** as a test target driven from the host by `VBoxManage guestcontrol`. Copy it into the guest and run it there; it is idempotent, so re-run it after any snapshot restore. The per-user half needs no UAC (crash-dialog suppression, no screen blanking, a staging directory, the shared folder on `Z:`); the machine-wide half is skipped with a notice unless run elevated inside the guest. It then reports what the box can actually test: DWM composition, printers, audio capture devices. |
## Usage
- The elevated half writes a transcript to `setup-windows-uac.log` next to the
script; the batch file prints it when the elevated window closes. The log is
gitignored, as it contains local paths.
-- **git uses the Windows SSH client.** `setup-windows-no-uac.ps1` sets
- `core.sshCommand` to `%WINDIR%/System32/OpenSSH/ssh.exe`. Git for Windows
- otherwise prefers its own bundled MSYS2 `ssh.exe`, which cannot reach the
- Windows `ssh-agent` service that the elevated half enables - Win32-OpenSSH
- publishes the agent on a named pipe the MSYS2 build does not speak. Without
- this, keys loaded with `ssh-add` from PowerShell are invisible to `git`, and a
- push falls back to hunting for a key file and prompting for its passphrase.
- The value uses forward slashes on purpose: git parses `core.sshCommand` with
- shell quoting rules, in which a backslash is an escape character.
+- **git uses a Win32-OpenSSH client.** `setup-windows-no-uac.ps1` sets
+ `core.sshCommand`. Git for Windows otherwise prefers its own bundled MSYS2
+ `ssh.exe`, which cannot reach the Windows `ssh-agent` service that the elevated
+ half enables - Win32-OpenSSH publishes the agent on a named pipe the MSYS2
+ build does not speak. Without this, keys loaded with `ssh-add` from PowerShell
+ are invisible to `git`, and a push falls back to hunting for a key file and
+ prompting for its passphrase. The value uses forward slashes on purpose: git
+ parses `core.sshCommand` with shell quoting rules, in which a backslash is an
+ escape character.
+- **Which `ssh.exe` git gets.** `C:\Tools\rsync\ssh.exe` — the fast build the
+ elevated half unpacks — if it is there, `%WINDIR%/System32/OpenSSH/ssh.exe`
+ otherwise. It is the same client with the same `~/.ssh`, agent and
+ `known_hosts`; the difference is the stdin pump, and without it anything git
+ *pushes* is capped at ~17 MB/s. Candidates are tried by **running** them
+ (`ssh -V`), not by `Test-Path`: the fast build needs a `libcrypto.dll` that an
+ image without the OpenSSH Client capability does not have, and a client that
+ will not start should be found here rather than on the next `git push`.
+ This is also why `setup-windows.bat` now runs the elevated half **first** — the
+ fast `ssh.exe` has to exist before the git config step can prefer it. The
+ non-elevated half still runs even when the elevated one failed; it just falls
+ back. Re-run `setup-windows-no-uac.ps1` on its own at any time to re-pick.
- All three scripts are idempotent — re-running skips anything already installed.
BinSkim in particular checks NuGet for the newest stable version *before*
downloading: the package is a self-contained .NET build well over 100 MB, and
(`.../releases/download/<tag>/<asset>`) instead.
- Visual Studio is installed in three labelled passes (base workload, Clang/LLVM,
XP toolset) so a failure identifies which component group is responsible.
+- **Windows Performance Analyzer is not part of Visual Studio.** VS has its own
+ Performance Profiler, which is a different, `.diagsession`-based tool and
+ cannot open an `.etl`. WPA ships with `xperf` and `wpr` in the Windows
+ Performance Toolkit, which exists in exactly two places: as an optional
+ *feature* of the Windows SDK (`OptionId.WindowsPerformanceToolkit`) and inside
+ the Windows ADK, which bundles the same toolkit. Whether the SDK install that
+ Visual Studio performs selects that feature varies by version, so the elevated
+ half **detects first** — `%ProgramFiles(x86)%\Windows Kits\10\Windows
+ Performance Toolkit`, its 64-bit twin, and the ADK location — and only falls
+ back to `winget install Microsoft.WindowsADK` when nothing is there. It then
+ re-asserts that directory on the machine `PATH` (the toolkit's own installer
+ usually does this, and the Start Menu gets *Windows Kits > Windows Performance
+ Toolkit* shortcuts for WPA and WPR). To install just the toolkit instead of the
+ whole ADK, run the standalone SDK setup with
+ `winsdksetup.exe /features OptionId.WindowsPerformanceToolkit /q`. A newer WPA
+ also exists in the Microsoft Store (`winget install --id 9N0W1B2BXGNZ --source
+ msstore`); it is not installed here because the Store source needs an
+ interactive, signed-in session, which the unattended elevated half does not
+ have.
+- **Intel VTune Profiler is reported, not installed.** The elevated half prints
+ whether it is on the box, its version, and the path to `vtune.exe`; if it is
+ missing it prints the download page instead (and says so if the CPU is not
+ Intel). Automating the install is not worth it here: the offline installer is a
+ ~750 MB download from a URL carrying a per-release GUID with no "latest"
+ redirect behind it, so every new build would mean editing a hard-coded link,
+ and it is only worth having on Intel silicon since hardware event-based
+ sampling reads Intel PMU counters. It does install unattended if you want it
+ scripted elsewhere:
+
+ ```powershell
+ intel-vtune-<version>_offline.exe -a --silent --cli --eula accept
+ ```
+
+ **Running it needs no elevation, but hardware sampling does.** A standard user
+ gets the User-Mode Sampling analyses — `vtune -collect hotspots` and threading
+ — and they work: measured here, collection and finalization, exit 0. Hardware
+ event-based sampling (`uarch-exploration`, `memory-access`, `hotspots -knob
+ sampling-mode=hw`) wants administrator, and VTune says so in a warning at the
+ top of every unelevated run. Note the failure it actually gives is *"cannot
+ recognize the processor"*, which reads like a hardware problem and is not one:
+ the drivers (`sepdrv5`, `sepdal`, `vtss`) are installed and running, and VTune
+ identifies the PMU through them. Unlike ETW there is no group to join for this
+ — the Linux driver can be handed to a `vtune` group, but on Windows the
+ documented answer is to run as administrator.
+
+- **User-mode ETW tracing without a UAC prompt — and the kernel logger's hard
+ limit.** Out of the box a standard user cannot start *any* event tracing
+ session, not even a user-mode one naming a single provider:
+
+ ```text
+ xperf -start T -on Microsoft-Windows-Kernel-Process -f trace.etl
+ -> Access is denied. (0x5)
+ ```
+
+ Session control is checked against the security descriptor ETW keeps per
+ provider GUID under `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`, whose
+ default grants those rights to SYSTEM, Administrators, the service accounts and
+ `BUILTIN\Performance Log Users` — and to nobody else. So the elevated half puts
+ the account named by `-TraceUser` into that group, and the command above then
+ works unelevated; enabling another account later is just
+ `net localgroup "Performance Log Users" <user> /add`. Membership is read into
+ the access token **at logon**, so sign out and back in first — any new logon
+ does it, and an `ssh` login into the box is the quick way to check without
+ dropping the desktop.
+
+ **Kernel traces are not available this way, and cannot be made to be.**
+ `xperf -on base` and `wpr -start` drive the *NT Kernel Logger*, which is
+ reserved for Administrators and LocalSystem — Microsoft documents Performance
+ Log Users access as explicitly not extending to it. That was measured rather
+ than assumed, and the negative result is recorded here so nobody repeats the
+ experiment: with the account in the group, `SeSystemProfilePrivilege` ("Profile
+ system performance") granted to that group, and an explicit ACE giving the
+ group `TRACELOG_ACCESS_KERNEL_LOGGER` on `SystemTraceControlGuid` — all three
+ in place, across a reboot — `xperf -on base` still answered `NT Kernel Logger:
+ Access is denied. (0x5)`. It is not a check an ACE overrides. The privilege
+ grant and the ACE were dropped again rather than left on the box earning
+ nothing: CPU sampling and whole-system traces are elevated work, with `xperf`,
+ `wpr` or VTune from an Administrator prompt.
+
+ Analysis was never affected — `wpa.exe` opens an existing `.etl` as a plain
+ user. This is only about collection.
+
+ The step runs **first** in the elevated half, and `-EtwRightsOnly` runs it and
+ nothing else, which matters because a full run is dominated by the three Visual
+ Studio passes that take minutes even with nothing to do:
+
+ ```powershell
+ Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass',
+ '-File','<repo>\setup-windows-with-uac.ps1','-TraceUser','DOMAIN\user','-EtwRightsOnly'
+ ```
+
+ If you ran an earlier revision of this script, it left both of the grants above
+ on the box. Undo the privilege in `secpol.msc` > Local Policies > User Rights
+ Assignment > "Profile system performance" by removing Performance Log Users.
+ The ACEs sit in the `{9e814aad-3204-11d2-9a82-006008a86939}` value under
+ `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`: strip the `LU` entries
+ from that descriptor rather than deleting the value, which also carries entries
+ for SYSTEM, Administrators and two service accounts.
- The scripts were extracted from a native Windows project, so the component
selection is tuned for that: Spectre-mitigated runtimes, the v141/XP toolset,
and driver-kit headers. Trim the component lists in the `.ps1` if you don't