setup-windows-no-uac.ps1 takes the 'user' steps - per-user configuration
THIS IS THE ONE PLACE a step's availability is decided. -X64Only, with its
- reason, keeps a step off ARM64; -Admin keeps it off an account that cannot
- elevate. The planner shows both as "[-]" rows with the reason, the batch file
+ reason, keeps a step off ARM64; -NotInVM, with its reason, keeps it off a
+ Windows that is itself a virtual machine; -Admin keeps it off an account that
+ cannot elevate. The planner shows each as a "[-]" row with the reason, the batch file
prints the same reason in the log where the step would have run, and the two
halves apply them again when run on their own. This replaces the :x64only
subroutine that used to sit at the bottom of setup-windows.bat and the
[string] $Package,
# Not on ARM64, because <this>. Shown in the plan and in the run log.
[string] $X64Only,
+ # Not when this Windows is a guest in a virtual machine, because <this>.
+ # Shown in the plan and in the run log, like -X64Only.
+ [string] $NotInVM,
# Needs an account that can elevate: a machine-wide installer, or part
# of the elevated half.
[switch] $Admin,
Text = $shown
Package = $Package
X64Only = $X64Only
+ NotInVM = $NotInVM
Admin = [bool]$Admin
Needs = $Needs
}
New-SetupStep NASM U bat -Package NASM.NASM `
-X64Only 'x86/x64-only assembler; MSVC armasm64 is the native one'
New-SetupStep VirtualBox U bat -Package Oracle.VirtualBox -Text 'for the Windows 7 test VM' `
- -X64Only 'no ARM64 host build, so no Windows 7 x86 test VM either'
+ -X64Only 'no ARM64 host build, so no Windows 7 x86 test VM either' `
+ -NotInVM 'the test VMs belong on the physical host, not nested in a guest'
# --- setup-windows.bat: machine-wide installers, despite running unelevated ---
New-SetupStep DotNetSdk M bat -Admin -Package Microsoft.DotNet.SDK.10 -Text 'for the WiX tooling'
New-SetupStep VTune E uac -Admin -Text 'Intel VTune Profiler: report whether it is installed (installs nothing)'
# --- setup-windows-no-uac.ps1: per-user configuration ---
+ # First, so the PATH it fixes is in place before anything reports on it.
+ New-SetupStep LocalBinPath C user -Text '%USERPROFILE%\.local\bin on the user PATH (where Claude Code''s launcher lives)'
New-SetupStep WinMergePath C user -Needs WinMerge -Text 'WinMerge on the user PATH' `
-X64Only 'WinMerge is not installed on ARM64'
New-SetupStep VsWhere C user -Needs VisualStudio -Text 'vswhere.exe on the user PATH'
function Get-SetupStepBlocker {
# Why this step cannot run on this machine at all, or $null if it can.
# Architecture first: an x64-only step is off ARM64 before elevation is even
- # consulted, which is the more useful reason to show.
- param($Step, [bool] $IsArm64, [string] $Elevation)
+ # consulted, which is the more useful reason to show. Then the machine
+ # itself, then the account.
+ param($Step, [bool] $IsArm64, [string] $Elevation, [string] $VirtualMachine)
if ($IsArm64 -and $Step.X64Only) { return "not on ARM64 - $($Step.X64Only)" }
+ if ($VirtualMachine -and $Step.NotInVM) {
+ return "not in a virtual machine ($VirtualMachine) - $($Step.NotInVM)"
+ }
if ($Elevation -eq 'NOLUA' -and $Step.Admin) {
return 'needs an administrator - this account cannot elevate'
}
$null
}
+function Get-SetupVirtualMachine {
+ # The hypervisor this Windows is running under as a guest, by name, or $null
+ # on physical hardware.
+ #
+ # Read from the SMBIOS strings the firmware reports - Win32_ComputerSystem's
+ # Manufacturer and Model - which every common hypervisor fills in with its own
+ # name. NOT from HypervisorPresent: that is true on a PHYSICAL box too as soon
+ # as Hyper-V, WSL2, Windows Sandbox or virtualization-based security is on,
+ # which is most Windows 11 machines - the host OS then runs in the root
+ # partition, above a hypervisor, without being anyone's guest.
+ #
+ # A lookup that fails says "physical": this only ever takes steps away, and a
+ # step wrongly offered on a VM is one you can switch off in the plan.
+ $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
+ if (-not $cs) { return $null }
+ $smbios = "$($cs.Manufacturer) | $($cs.Model)"
+ # Hyper-V's pair is matched whole: Microsoft is also the manufacturer of
+ # every Surface, and "Virtual Machine" is the model only in a guest. Azure
+ # guests report the same pair.
+ $known = [ordered]@{
+ 'innotek|VirtualBox' = 'VirtualBox'
+ 'VMware' = 'VMware'
+ '^Microsoft Corporation \| Virtual Machine$' = 'Hyper-V'
+ 'QEMU|KVM|Bochs' = 'QEMU/KVM'
+ 'Parallels' = 'Parallels'
+ '\bXen\b|HVM domU' = 'Xen'
+ 'Amazon EC2' = 'Amazon EC2'
+ 'Google Compute Engine' = 'Google Compute Engine'
+ }
+ foreach ($pattern in $known.Keys) {
+ if ($smbios -match $pattern) { return $known[$pattern] }
+ }
+ $null
+}
+
function Split-SetupNames {
# `powershell -File` hands a whole "A,B,C" to an array parameter as ONE
# string, so split here and accept either spelling.
[string[]] $Skip = @(),
[hashtable] $Aliases = @{},
[bool] $IsArm64,
- [string] $Elevation
+ [string] $Elevation,
+ # The hypervisor's name when this Windows is a guest (see
+ # Get-SetupVirtualMachine), $null or empty on physical hardware.
+ [string] $VirtualMachine
)
$want = if ($null -eq $Only) { @($Steps.Id) } else { @(Resolve-SetupStepNames $Only $Steps $Aliases) }
$drop = @(Resolve-SetupStepNames $Skip $Steps $Aliases)
$named = @(Split-SetupNames $Only | ForEach-Object { if ($Aliases.ContainsKey($_)) { $Aliases[$_] } else { $_ } })
foreach ($s in $Steps) {
- $why = Get-SetupStepBlocker $s $IsArm64 $Elevation
+ $why = Get-SetupStepBlocker $s $IsArm64 $Elevation $VirtualMachine
if ($why -and $named -contains $s.Id) {
Write-Warning "$($s.Id) was asked for but cannot run here: $why"
}