]> vilimpoc.org git repositories - dotfiles/commitdiff
dotfiles: drop the non-elevated ETW tracing, it never worked master
authorMax Vilimpoc <max@vilimpoc.org>
Tue, 1 Sep 2026 11:01:04 +0000 (13:01 +0200)
committerMax Vilimpoc <max@vilimpoc.org>
Tue, 1 Sep 2026 11:01:04 +0000 (13:01 +0200)
Putting an ordinary account into Performance Log Users was supposed to
buy user-mode ETW collection without a UAC prompt. Testing the actual
workflow says otherwise: xperf and VTune want an administrator account
and an elevated prompt, and everything short of that gets blocked
somewhere. The kernel logger was already known to be admin-only; the
rest turned out not to be worth the machinery either.

So the -TraceUser and -EtwRightsOnly parameters, the group membership
step, and the UAC launch line that existed only to carry an argument
across the elevation boundary all come out. The launch line goes back to
the single-argument form it had before.

Kept, because they are useful whether or not collection is elevated: the
Windows Performance Toolkit step that detects xperf/wpr/wpa, reports
their versions and puts them on the machine PATH, and the VTune step
that reports whether it is installed.

The README keeps the negative result rather than quietly losing it, and
gains the commands to undo what earlier revisions left on a box - the
group membership, the privilege, and the ACE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gCzbrN6p3emFUyufuJsyq

README.md
setup-windows-7-test-env.bat [new file with mode: 0644]
setup-windows-no-uac.ps1
setup-windows-with-uac.ps1
setup-windows.bat

index dfe024e6efa0f2594889dcfe22b7e2875302e414..7c986501ecf46e0774cc835dd779883fad3bcc3f 100644 (file)
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@ throwaway VM reachable from a Linux host.
 | --- | --- |
 | `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. Can also be run directly from an Administrator prompt. |
+| `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
 
@@ -117,6 +118,85 @@ throwaway VM reachable from a Linux host.
   (`.../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
+  ```
+
+  **Run it from an administrator account, elevated.** Hardware event-based
+  sampling (`uarch-exploration`, `memory-access`, `hotspots -knob
+  sampling-mode=hw`) requires it, and VTune warns about that at the top of every
+  unelevated run. Worth knowing that the failure it gives there 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. There is no group to join to get around it —
+  the Linux driver can be handed to a `vtune` group, but on Windows the
+  documented answer is to run as administrator.
+
+- **Collect traces from an elevated Administrator session. Non-elevated
+  collection was tried here and abandoned.** The attempt was to put one ordinary
+  account into `BUILTIN\Performance Log Users`, which appears in the default
+  security descriptors ETW keeps per provider GUID under
+  `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`, and collect without a UAC
+  prompt. It does not survive contact with the real workflow: `xperf -on base`
+  and `wpr -start` drive the *NT Kernel Logger*, reserved for Administrators and
+  LocalSystem, and granting the group `SeSystemProfilePrivilege` ("Profile system
+  performance") plus an explicit ACE for `TRACELOG_ACCESS_KERNEL_LOGGER` on
+  `SystemTraceControlGuid` — all three in place, across a reboot — still answered
+
+  ```text
+  xperf: error: NT Kernel Logger: Access is denied. (0x5).
+  ```
+
+  It is not a check an ACE overrides, and the same wall turned up often enough
+  elsewhere that the whole approach was dropped rather than carried as a
+  half-working path. **Sign in to an administrator account and run `xperf`, `wpr`
+  and VTune from an elevated prompt.** Analysis is the exception and never needed
+  any of this: `wpa.exe` opens an existing `.etl` as a plain user.
+
+  If an earlier revision of these scripts ran on a box, it left that account in
+  the group. Take it back out with:
+
+  ```powershell
+  net localgroup "Performance Log Users" DOMAIN\user /delete
+  ```
+
+  Two revisions also granted the privilege and the ACE. 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
diff --git a/setup-windows-7-test-env.bat b/setup-windows-7-test-env.bat
new file mode 100644 (file)
index 0000000..376d4dd
--- /dev/null
@@ -0,0 +1,163 @@
+@echo off
+setlocal
+
+rem ---------------------------------------------------------------------------
+rem setup-windows-7-test-env.bat - prepare a Windows 7 VM as a test target for
+rem native Windows software driven from the host by VBoxManage guestcontrol.
+rem
+rem Copy this file into the guest and run it there. It is idempotent: run it
+rem again after a rollback, or after any snapshot restore, to get the same box.
+rem
+rem Split in two halves, like the other provisioning scripts here:
+rem   - the per-user half runs as you and needs no UAC prompt
+rem   - the machine-wide half needs elevation and is SKIPPED (with a notice)
+rem     when this runs unelevated, so an unattended run never blocks on a prompt
+rem
+rem That split matters more than usual here: a guestcontrol-launched process gets
+rem a UAC-filtered token even for an account in Administrators, so the elevated
+rem half can only be done by running this from an interactive Administrator
+rem prompt inside the guest. Everything the host harness actually needs is in
+rem the per-user half.
+rem ---------------------------------------------------------------------------
+
+echo(
+echo ============================================================
+echo  Windows 7 test-target setup
+echo ============================================================
+
+rem --- Are we elevated? "net session" is the cheapest reliable probe. ---
+set "ELEVATED=0"
+net session >nul 2>&1
+if "%ERRORLEVEL%"=="0" set "ELEVATED=1"
+
+rem === PER-USER HALF (no UAC needed) =========================================
+
+echo(
+echo [user] Suppressing crash/hard-error dialogs
+rem A modal Windows Error Reporting dialog in the guest blocks a guestcontrol
+rem call until its timeout: the run looks like a hang, and a harness that scores
+rem a timeout as a failure will invent bugs that are not there. DontShowUI makes
+rem a crashing test process die immediately and return its exit code instead.
+reg add "HKCU\Software\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f >nul
+if errorlevel 1 echo   WARN: could not set DontShowUI
+
+echo [user] Disabling the screen saver and monitor blanking
+rem The host watches this VM's screen with "VBoxManage controlvm screenshotpng".
+rem A blanked screen makes every screenshot useless.
+reg add "HKCU\Control Panel\Desktop" /v ScreenSaveActive /t REG_SZ /d 0 /f >nul
+reg add "HKCU\Control Panel\Desktop" /v ScreenSaverIsSecure /t REG_SZ /d 0 /f >nul
+reg add "HKCU\Control Panel\Desktop" /v SCRNSAVE.EXE /t REG_SZ /d "" /f >nul 2>&1
+
+echo [user] Creating the staging directory C:\bb
+if not exist "C:\bb" mkdir "C:\bb"
+if not exist "C:\bb" echo   WARN: could not create C:\bb
+
+echo [user] Mapping Z: to the VirtualBox shared folder "Downloads"
+rem Bulk file transfer over a shared folder is far faster than a copyto per file.
+rem The mapping is per-user and must be re-made in each new interactive session,
+rem which is exactly why it lives in this script rather than in a host-side note.
+if exist Z:\ goto :zdone
+net use Z: \vboxsvr\Downloads /persistent:yes >nul 2>&1
+if errorlevel 1 echo   WARN: could not map Z: - is the shared folder attached to this VM?
+:zdone
+
+rem === MACHINE-WIDE HALF (needs elevation) ===================================
+
+if "%ELEVATED%"=="0" goto :skipadmin
+
+echo(
+echo [admin] Suppressing the system hard-error dialog (ErrorMode=2)
+reg add "HKLM\SYSTEM\CurrentControlSet\Control\Windows" /v ErrorMode /t REG_DWORD /d 2 /f >nul
+if errorlevel 1 echo   WARN: could not set ErrorMode
+
+echo [admin] Disabling sleep and monitor timeout on AC
+powercfg -change -monitor-timeout-ac 0 >nul 2>&1
+powercfg -change -standby-timeout-ac 0 >nul 2>&1
+powercfg -change -disk-timeout-ac 0 >nul 2>&1
+powercfg -change -hibernate-timeout-ac 0 >nul 2>&1
+
+echo [admin] Turning off Windows Update automatic install
+rem An update reboot in the middle of a test run destroys the run and, worse,
+rem silently changes the system under test between two comparable results.
+reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v AUOptions /t REG_DWORD /d 1 /f >nul 2>&1
+goto :adminend
+
+:skipadmin
+echo(
+echo [admin] SKIPPED - this process is not elevated.
+echo         Run this script from an Administrator prompt INSIDE the guest to
+echo         apply: ErrorMode=2, sleep/monitor timeouts, Windows Update policy.
+echo         A guestcontrol-launched process cannot elevate itself, so these
+echo         cannot be done from the host harness.
+:adminend
+
+rem === READINESS REPORT ======================================================
+rem Everything below only reports. A test that silently runs against a box
+rem missing a device reads as "the software under test refused" when the truth is
+rem "this VM never had one" - so the harness must know, up front, what is here.
+
+echo(
+echo ============================================================
+echo  Readiness
+echo ============================================================
+echo(
+echo -- OS --
+ver
+echo arch=%PROCESSOR_ARCHITECTURE%  user=%USERNAME%  elevated=%ELEVATED%
+
+echo(
+echo -- Desktop Window Manager composition --
+rem SetWindowDisplayAffinity requires DWM composition. With composition OFF it
+rem fails with error 8 for every value, so any screen-capture protection under
+rem test is a silent no-op and its checks fail for a reason that has nothing to
+rem do with the code. Windows 7 runs the Basic theme (composition off) whenever
+rem the install is not activated.
+rem
+rem Ask the API, not a proxy for it. Two plausible-looking shortcuts are both
+rem WRONG on this box, measured: dwm.exe keeps running with composition off, and
+rem HKCU\...\DWM\Composition is the stored preference, not the live state. Both
+rem say ENABLED while DwmIsCompositionEnabled returns false.
+rem
+rem Two shapes to keep, both learned the hard way against guestcontrol:
+rem   - PowerShell PRINTS the verdict; the batch does not capture it. Redirecting
+rem     its stdout to a file, or capturing through for /f, hangs the whole run
+rem     until the host's timeout fires.
+rem   - the if/else is ONE line. Split across two, PowerShell treats the file as
+rem     an incomplete command, waits on stdin and never exits. The <nul below is
+rem     belt and braces for that whole class of hang.
+set "DWMPS=%TEMP%\w7env-dwm.ps1"
+> "%DWMPS%" echo Add-Type -TypeDefinition @^"
+>>"%DWMPS%" echo using System;
+>>"%DWMPS%" echo using System.Runtime.InteropServices;
+>>"%DWMPS%" echo public class D { [DllImport("dwmapi.dll")] public static extern int DwmIsCompositionEnabled(out bool e); }
+>>"%DWMPS%" echo ^"@
+>>"%DWMPS%" echo $e = $false
+>>"%DWMPS%" echo [void][D]::DwmIsCompositionEnabled([ref]$e)
+>>"%DWMPS%" echo if ($e) { "  composition = ENABLED - screen-capture affinity is testable." } else { "  composition = DISABLED - SetWindowDisplayAffinity fails with error 8 for every value, so a screen-capture-protection test here is testing nothing. Windows 7 forces the Basic theme while the install is not activated; activate it to test that path." }
+powershell -NoProfile -ExecutionPolicy Bypass -File "%DWMPS%" <nul
+del "%DWMPS%" >nul 2>&1
+
+echo(
+echo -- Printers (a print test needs at least one) --
+rem wmic, not PowerShell: no quoting to get wrong, and it is in the base install.
+wmic printer get Name,Default /format:table 2>nul | findstr /R /V "^$"
+if errorlevel 1 echo   NONE - print tests will report "could not create a printer DC"
+
+echo(
+echo -- Audio capture devices (a microphone test needs at least one) --
+wmic sounddev get Name,Status /format:table 2>nul | findstr /R /V "^$"
+if errorlevel 1 echo   NONE - enable audio for this VM in its VirtualBox settings
+
+echo(
+echo -- Shared folder --
+if exist Z:\ (echo   Z: mapped) else (echo   Z: NOT mapped)
+
+echo(
+echo -- Staging directory --
+if exist C:\bb (echo   C:\bb present) else (echo   C:\bb MISSING)
+
+echo(
+echo ============================================================
+echo  Done. Re-run this after any snapshot restore.
+echo ============================================================
+endlocal
index f6500b8973361bfba96915fed5c2185ccf44ff90..11973000cdcdddc1a2663165d6f514c9e7e54058 100644 (file)
 
   What this installs / configures:
     - WinMerge on the user PATH
+    - vswhere.exe on the user PATH: the Visual Studio installer puts it in
+      %ProgramFiles(x86)%\Microsoft Visual Studio\Installer, which nothing adds
+      to the PATH, so build scripts (and VsDevCmd.bat itself) complain that
+      'vswhere.exe' is not recognized
     - BinSkim (binary hardening analyzer) in %LOCALAPPDATA%\Programs\BinSkim,
       on the user PATH
     - Global git identity, and core.sshCommand pointed at a Win32-OpenSSH
@@ -31,7 +35,7 @@ param(
     # for several, dot-call the script or use -Command:
     #     .\setup-windows-no-uac.ps1 -Skip BinSkim,GitConfig
     #     powershell -File .\setup-windows-no-uac.ps1 -Skip BinSkim
-    [ValidateSet('WinMerge', 'BinSkim', 'GitConfig')]
+    [ValidateSet('WinMerge', 'VsWhere', 'BinSkim', 'GitConfig')]
     [string[]] $Skip = @()
 )
 
@@ -90,6 +94,22 @@ function Add-WinMergeToUserPath {
     Add-ToUserPath $dir
 }
 
+function Add-VsWhereToUserPath {
+    # vswhere.exe is how scripts locate Visual Studio (MSBuild, VsDevCmd.bat,
+    # the Windows SDK), and the VS installer drops it in a fixed directory that
+    # is never on the PATH. VsDevCmd.bat itself prints "'vswhere.exe' is not
+    # recognized" on every run without it. The directory is fixed by contract
+    # (32-bit Program Files, no version in the path), so there is nothing to
+    # search for: if it is missing, Visual Studio is not installed.
+    Write-Step 'vswhere on the user PATH'
+    $dir = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer'
+    if (-not (Test-Path (Join-Path $dir 'vswhere.exe'))) {
+        Write-Warning "vswhere.exe not found in $dir (no Visual Studio installer present); user PATH unchanged."
+        return
+    }
+    Add-ToUserPath $dir
+}
+
 function Install-BinSkim {
     # BinSkim checks the exact mitigations the native project enables in
     # CMakeLists.txt (CFG/XFG, CET, ASLR/HighEntropyVA, DEP, /GS, stack cookies,
@@ -288,6 +308,7 @@ if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::
 
 $steps = [ordered]@{
     WinMerge  = { Add-WinMergeToUserPath }
+    VsWhere   = { Add-VsWhereToUserPath }
     BinSkim   = { Install-BinSkim }
     GitConfig = { Set-GlobalGitConfig }
 }
index 8d990f5de3dfc43d7fc621c3040773d18400975e..9f3d8aeec8f5173718df2cbdccc133a5883c2e68 100644 (file)
@@ -15,6 +15,8 @@
     - Visual Studio 2022 Community (C++ desktop workload, Spectre libs, WDK VSIX,\r
       Win11 SDK 26100, Clang/LLVM, and the v141 + Windows XP targeting toolset)\r
     - Windows Driver Kit 10.0.26100\r
+    - Windows Performance Toolkit - xperf, wpr and Windows Performance Analyzer\r
+      (wpa.exe) - on the machine PATH\r
 \r
   Change $VsInstallerUrl below to the Professional or Enterprise bootstrapper if needed:\r
     Professional : https://aka.ms/vs/17/release/vs_professional.exe\r
@@ -448,30 +450,48 @@ if ($WdkInstalledRoot -and $WdkInstalledRoot -match [regex]::Escape($WdkVersion)
 }\r
 \r
 # ---------------------------------------------------------------------------\r
-# Windows Performance Toolkit (xperf / WPA / wpr) -- ETW CPU + loader profiling,\r
-# used by the perf/ measurement scripts. WPT is an OPTIONAL Windows SDK feature\r
-# that the VS "Windows 11 SDK" component does NOT select, so a fresh box lacks it.\r
-# The Windows ADK bundles WPT and winget owns the (versioned) download URL, so it\r
-# is the most reliable source. Idempotent (skips if xperf is already present in\r
-# either the SDK or ADK location) and non-fatal so it never aborts provisioning.\r
-# Lighter alternative if you don't want the full ADK: install the Windows SDK's\r
-# "Windows Performance Toolkit" optional feature via winsdksetup.exe /features\r
-# OptionId.WindowsPerformanceToolkit.\r
+# Windows Performance Toolkit: xperf, wpr, and Windows Performance Analyzer\r
+# (wpa.exe) -- ETW CPU + loader profiling and the GUI that reads the traces.\r
+#\r
+# WPA is NOT a Visual Studio component and has no relationship to VS's own\r
+# Performance Profiler (a separate, .diagsession-based tool that cannot open an\r
+# .etl). It ships in exactly two places: as an optional FEATURE of the Windows\r
+# SDK ("Windows Performance Toolkit", OptionId.WindowsPerformanceToolkit), and\r
+# in the Windows ADK, which bundles the same toolkit. Whether the SDK install\r
+# that Visual Studio performs happens to select that feature varies with the VS\r
+# and SDK version - when it does, WPT lands in\r
+# %ProgramFiles(x86)%\Windows Kits\10\Windows Performance Toolkit and the SDK\r
+# puts that directory on the machine PATH itself - so this step DETECTS first\r
+# and only falls back to installing the ADK (winget owns the versioned download\r
+# URL, which makes it the reliable source) when nothing is there. That fallback\r
+# is a large download; to install just the toolkit instead, run the standalone\r
+# SDK setup with\r
+#     winsdksetup.exe /features OptionId.WindowsPerformanceToolkit /q\r
+#\r
+# There is also a newer WPA in the Microsoft Store (`winget install --id\r
+# 9N0W1B2BXGNZ --source msstore`), which updates independently of the SDK. It is\r
+# not installed here: the Store package needs an interactive, signed-in session,\r
+# which is exactly what this elevated, unattended half does not have.\r
+#\r
+# Idempotent and non-fatal - it never aborts provisioning.\r
 # ---------------------------------------------------------------------------\r
-Write-Step 'Windows Performance Toolkit (xperf / WPA)'\r
-$wptRoots = @(\r
-    (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Windows Performance Toolkit\xperf.exe'),\r
-    (Join-Path $env:ProgramFiles          'Windows Kits\10\Windows Performance Toolkit\xperf.exe'),\r
-    (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Assessment and Deployment Kit\Windows Performance Toolkit\xperf.exe')\r
+Write-Step 'Windows Performance Toolkit (xperf / wpr / WPA)'\r
+$WptDirs = @(\r
+    (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Windows Performance Toolkit'),\r
+    (Join-Path $env:ProgramFiles        'Windows Kits\10\Windows Performance Toolkit'),\r
+    (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Assessment and Deployment Kit\Windows Performance Toolkit')\r
 )\r
-$xperf = $wptRoots | Where-Object { Test-Path $_ } | Select-Object -First 1\r
-if ($xperf) {\r
-    Write-Host "    OK: WPT already present ($xperf)" -ForegroundColor Green\r
+function Find-WptDir { $script:WptDirs | Where-Object { Test-Path (Join-Path $_ 'xperf.exe') } | Select-Object -First 1 }\r
+\r
+$WptDir = Find-WptDir\r
+if ($WptDir) {\r
+    Write-Host "    OK: WPT already present ($WptDir)" -ForegroundColor Green\r
 } else {\r
     try {\r
         winget install --id Microsoft.WindowsADK --exact --silent --disable-interactivity `\r
             --accept-source-agreements --accept-package-agreements\r
         Write-Host '    Windows ADK (includes Windows Performance Toolkit) installed.'\r
+        $WptDir = Find-WptDir\r
     } catch {\r
         Write-Warning "WPT install failed: $($_.Exception.Message)"\r
         Write-Warning 'Install manually: winget install Microsoft.WindowsADK, or add the'\r
@@ -479,6 +499,78 @@ if ($xperf) {
     }\r
 }\r
 \r
+if ($WptDir) {\r
+    # Report what actually landed. wpa.exe is the piece people come looking for\r
+    # and it is the one that is absent if a trimmed toolkit ever shows up.\r
+    foreach ($tool in 'xperf.exe', 'wpr.exe', 'wpa.exe', 'wpaexporter.exe') {\r
+        $p = Join-Path $WptDir $tool\r
+        if (Test-Path $p) {\r
+            Write-Host "    $tool $((Get-Item $p).VersionInfo.ProductVersion)"\r
+        } else {\r
+            Write-Warning "$tool is missing from $WptDir"\r
+        }\r
+    }\r
+\r
+    # The WPT installer normally adds this to the machine PATH itself (and the\r
+    # Start Menu gets "Windows Kits > Windows Performance Toolkit" shortcuts for\r
+    # WPA and WPR). Re-assert it anyway: on the machine PATH rather than a user\r
+    # one so it also resolves for the non-interactive sshd sessions this box is\r
+    # driven through, which build their environment from the registry PATH.\r
+    # Compared trailing-backslash-insensitively - the installer's own entry has\r
+    # one, and adding a second spelling of the same directory is just noise.\r
+    $m = [Environment]::GetEnvironmentVariable('Path', 'Machine')\r
+    if (-not $m) { $m = '' }\r
+    $have = ($m -split ';') | Where-Object { $_.TrimEnd('\') -eq $WptDir.TrimEnd('\') }\r
+    if ($have) {\r
+        Write-Host "    OK: $WptDir already in the machine PATH"\r
+    } else {\r
+        $new = if ($m.Trim()) { $m.TrimEnd(';') + ';' + $WptDir } else { $WptDir }\r
+        [Environment]::SetEnvironmentVariable('Path', $new, 'Machine')\r
+        Write-Host "    Added $WptDir to the machine PATH (restart shells to pick it up)."\r
+    }\r
+}\r
+\r
+\r
+# ---------------------------------------------------------------------------\r
+# Intel VTune Profiler - reported, not installed\r
+#\r
+# Deliberately NOT automated, unlike everything above. The offline installer is\r
+# a ~750 MB download from a URL carrying a per-release GUID\r
+# (registrationcenter-download.intel.com/akdlm/IRC_NAS/<guid>/intel-vtune-<ver>_offline.exe)\r
+# with no "latest" redirect behind it, so every new build means editing a\r
+# hard-coded link in here - and it is only worth having on Intel silicon, since\r
+# hardware event-based sampling reads Intel PMU counters. Not a good trade for a\r
+# script that has to keep working unattended on any box.\r
+#\r
+# So this step only reports. To install it, take the Windows offline installer\r
+# from\r
+#     https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler-download.html\r
+# and run it elevated; it installs unattended with\r
+#     intel-vtune-<version>_offline.exe -a --silent --cli --eula accept\r
+# ---------------------------------------------------------------------------\r
+Write-Step 'Intel VTune Profiler (status only)'\r
+$UninstallKeys = @(\r
+    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'\r
+    'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'\r
+)\r
+$vtune = Get-ItemProperty $UninstallKeys -ErrorAction SilentlyContinue |\r
+         Where-Object { $_.DisplayName -match 'VTune' } |\r
+         Select-Object -First 1\r
+if ($vtune) {\r
+    Write-Host "    Installed: $($vtune.DisplayName.Trim()) $($vtune.DisplayVersion)" -ForegroundColor Green\r
+    # The oneAPI layout keeps a `latest` junction beside the versioned directory,\r
+    # so this path stays right across upgrades.\r
+    $VTuneCli = Join-Path $vtune.InstallLocation 'vtune\latest\bin64\vtune.exe'\r
+    if (Test-Path $VTuneCli) { Write-Host "    CLI: $VTuneCli" }\r
+} else {\r
+    Write-Host '    Not installed.' -ForegroundColor Yellow\r
+    Write-Host '    https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler-download.html' -ForegroundColor Yellow\r
+    $cpu = (Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1).Manufacturer\r
+    if ($cpu -and $cpu -notmatch 'Intel') {\r
+        Write-Host "    (This CPU reports itself as '$cpu' - VTune's hardware event-based sampling wants Intel silicon.)" -ForegroundColor Yellow\r
+    }\r
+}\r
+\r
 # ---------------------------------------------------------------------------\r
 Write-Host "`nAll done." -ForegroundColor Green\r
 Write-Host 'If a reboot was flagged above, restart before opening VS or building drivers.'\r
index cc12420fec43622a20029b9c97eb00f082b69bd7..54d6956d7630718567c5764737e2aeca11c503d5 100644 (file)
@@ -6,18 +6,50 @@
 \r
 @rem --- Non-admin (per-user) winget installs ---\r
 winget install Anthropic.ClaudeCode\r
+winget install ar51an.iPerf3\r
+winget install Brave.Brave\r
 winget install Git.Git\r
+winget install Google.AndroidGPUInspector\r
+winget install Microsoft.DotNet.SDK.10\r
 winget install Microsoft.PowerShell Microsoft.Sysinternals.ProcessExplorer Microsoft.Sysinternals.ProcessMonitor Microsoft.Sysinternals.SDelete Microsoft.VisualStudioCode Microsoft.WindowsTerminal\r
+winget install NASM.NASM\r
 winget install Oracle.VirtualBox\r
 winget install Python.Python.3.13\r
 winget install WinMerge.WinMerge\r
-winget install WiXToolset.WiXCLI\r
 \r
 @rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the\r
 @rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs,\r
 @rem which it reads). The installer elevates via UAC.\r
 winget install OpenCppCoverage.OpenCppCoverage\r
 \r
+@rem --- WiX 5.0.2, pinned on purpose ---\r
+@rem WiX packages our proprietary software into MSIs, and the version is pinned to keep that\r
+@rem free of a fee. 5.0.2 is the last release distributed under the Microsoft Reciprocal\r
+@rem License alone. From 6.0 onward the package also carries OSMFEULA.txt, an Open Source\r
+@rem Maintenance Fee agreement: a monthly fee owed by anyone who uses the PREBUILT BINARIES\r
+@rem as part of revenue-generating activity and has annual gross revenue >= US$10,000.\r
+@rem\r
+@rem It is a fee for the binaries, not a restriction on what we ship - MS-RL is file-scoped\r
+@rem and never reached the MSIs WiX builds, under any version - but 5.0.2 owes nothing.\r
+@rem The 6.x/7.x SOURCE is still MS-RL too, so self-compiling is another way out; a pin is\r
+@rem the cheaper one. This replaces `winget install WiXToolset.WiXCLI`, which has no version\r
+@rem selector and so installs the latest (7.0.0 today, EULA and all).\r
+@rem\r
+@rem PIN THE MSBUILD SIDE TOO. A .wixproj referencing WixToolset.Sdk without a version\r
+@rem resolves to the latest - 7.x, same EULA - and nothing here constrains it. Pin it in the\r
+@rem project: <Project Sdk="WixToolset.Sdk/5.0.2">.\r
+@rem\r
+@rem dotnet.exe is called by full path: winget put the SDK on the machine PATH a few lines\r
+@rem ago, but this cmd session inherited its environment before that and cannot see it.\r
+@rem install-then-update is for re-runs - install fails once the tool is there, and update\r
+@rem then holds it at exactly 5.0.2 - which keeps this script idempotent like the rest.\r
+set "DOTNET_EXE=%ProgramFiles%\dotnet\dotnet.exe"\r
+"%DOTNET_EXE%" tool install --global wix --version 5.0.2 || "%DOTNET_EXE%" tool update --global wix --version 5.0.2\r
+\r
+@rem Report what the pin actually produced. By full path again, and because the shim lands in\r
+@rem a directory this session's PATH predates: expect "5.0.2+<commit>", not 7.x.\r
+"%USERPROFILE%\.dotnet\tools\wix.exe" --version\r
+\r
 @rem ---------------------------------------------------------------------------\r
 @rem No package manager needed for the Windows build\r
 @rem\r