Skip to content

Commit 1744bde

Browse files
build: discover VS 2026 Insiders and exclude BuildTools in vswhere lookup (#47462)
## Summary of the Pull Request Fixes `tools\build\build-common.ps1` so a clean `tools\build\build-essentials.cmd` (or `build.ps1`) run discovers and uses **Visual Studio 2026 Insiders** without any manual `Enter-VsDevShell` preamble. Today the script lands on the first VS 2022 BuildTools instance it finds (which lacks the C++ workload) and every native `.vcxproj` errors with `MSB4086: $(PlatformToolsetVersion) evaluates to ""` during NuGet restore. Two scoped commits, **only `tools\build\build-common.ps1` is touched** (+58 / −36 net). ## PR Checklist - [ ] Closes: #xxx - [x] **Communication:** I''ve discussed this with core contributors already. If the work hasn''t been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places (n/a — build-script change only) - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ### What''s broken `build-common.ps1`''s `vswhere` lookup runs **without `-prerelease`**, so VS 2026 Insiders / Preview installs are invisible to it. The explicit fallback path list also only contains VS 2022 entries. On a machine that has VS 2022 BuildTools (typical for CI hosts and many dev boxes) but only has VS 2026 in *Insiders* form, the script: 1. Picks `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools` because it''s a candidate `vswhere` returned and it''s in the fallback list. 2. Enters its DevShell — but BuildTools has no C++ workload installed, so `$(VCToolsInstallDir)` and `$(PlatformToolsetVersion)` are unset. 3. NuGet restore over `PowerToys.slnx` fans out to every `.vcxproj` and they each hit `Microsoft.CodeAnalysis.targets(401,15): error MSB4086: A numeric comparison was attempted on "$(PlatformToolsetVersion)" that evaluates to "" instead of a number, in condition "''$(PlatformToolsetVersion)''<''120''".` The build dies before any project compiles. This was reproduced today against `origin/main` — confirming this is a pre-existing breakage independent of any in-flight feature work. ### What this PR changes Commit **`30acf72c` — Fix build scripts to discover VS 2026 / Insiders installations** - Adds `-prerelease` to `vswhere` calls, tried **before** the stable lookup so prerelease VS is preferred when it''s the only one with a working C++ workload. - Adds VS 2026 year-name and internal-version (`18\Insiders`) paths to the explicit fallback list. - Keeps VS 2022 paths as the final fallback so existing setups keep working. - Priority order: `prerelease+VC tools` → `prerelease` → `stable+VC tools` → `stable`. Commit **`18b27209` — build: simplify VS environment initialization with VS2022/VS2026 support** - Adds `-prerelease` to `vswhere` (consolidates with the above; the prerelease query subsumes the stable one now). - Restricts the SKU query to `Community` / `Professional` / `Enterprise` (explicitly excludes `BuildTools`) so the script can no longer accidentally pick a SKU without the C++ workload. - Reduces vswhere from **4 calls to 2**. - Removes the destructive BuildTools env-var cleanup block (no longer needed once vswhere refuses to return BuildTools in the first place). - Adds `VsDevCmd.bat` exit-code validation so a partial DevShell init fails loudly instead of silently producing a half-initialized environment. - Adds VS 2022 / VS 2026 Preview entries to the explicit fallback list. ### Why two commits instead of a squash The first commit is the minimum-viable fix that addresses the reported MSB4086 failure. The second commit is a follow-up cleanup that''s only safe **once** prerelease is preferred and BuildTools is excluded. Splitting them keeps each commit independently revert-able if a regression shows up on a specific dev environment shape. ## Validation Steps Performed | Step | Result | |---|---| | Reproduce on `main`: cold `tools\build\build-essentials.cmd` in a non-DevShell PowerShell | **MSB4086** on `Microsoft.CommandPalette.Extensions.vcxproj`, `Microsoft.Terminal.UI.vcxproj`, `PowerToys.MeasureToolCore.vcxproj`, `PowerRenameUI.vcxproj` — confirmed broken | | With this branch checked out: same cold `build-essentials.cmd` invocation | `[VS] vswhere found: ... C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe` → `[VS] Checking candidate: C:\Program Files\Microsoft Visual Studio\18\Insiders` → `[VS] Entered Visual Studio DevShell at C:\Program Files\Microsoft Visual Studio\18\Insiders` — VS 2026 Insiders selected directly, restore step proceeds past the MSB4086 wall | | Manual workaround pre-init via `Import-Module Microsoft.VisualStudio.DevShell.dll` + `Enter-VsDevShell` | Now **unnecessary** — the script self-initializes correctly | After this fix, the next failure mode the build hits is unrelated NuGet feed coverage for in-flight WinAppSDK upgrade work, which is the gated dependency this PR was extracted from to keep this change atomic. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 05cd66c commit 1744bde

1 file changed

Lines changed: 53 additions & 7 deletions

File tree

tools/build/build-common.ps1

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,22 @@ function Get-DefaultPlatform {
176176
return 'x64'
177177
}
178178

179+
function Test-VsHasNativeTools {
180+
# Returns $true when the current process environment was initialized with a
181+
# usable native (C++) toolchain. Any VS instance (full SKU or Build Tools)
182+
# without the C++ workload leaves these unset, which is the original
183+
# MSB4086 ($(PlatformToolsetVersion) empty) failure mode we are guarding
184+
# against.
185+
if (-not $env:VCToolsInstallDir) { return $false }
186+
if (-not (Test-Path $env:VCToolsInstallDir)) { return $false }
187+
return $true
188+
}
189+
179190
function Ensure-VsDevEnvironment {
180191
$OriginalLocationForVsInit = Get-Location
181192
try {
182193

183-
if ($env:VSINSTALLDIR -or $env:VCINSTALLDIR -or $env:DevEnvDir -or $env:VCToolsInstallDir) {
194+
if ($env:VSINSTALLDIR -or $env:VCINSTALLDIR) {
184195
Write-Host "[VS] VS developer environment already present"
185196
return $true
186197
}
@@ -193,22 +204,44 @@ function Ensure-VsDevEnvironment {
193204
$vswhere = $vswhereCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
194205
if ($vswhere) { Write-Host "[VS] vswhere found: $vswhere" } else { Write-Host "[VS] vswhere not found" }
195206

207+
# Probe for a Visual Studio install with the C++ workload. Selection is
208+
# capability-based (-requires VC.Tools.x86.x64), not SKU-based: full VS
209+
# SKUs and Build Tools are equally valid as long as the C++ workload is
210+
# installed. -prerelease lets vswhere see Preview/Insiders alongside GA.
211+
$vsProducts = @('Microsoft.VisualStudio.Product.Community',
212+
'Microsoft.VisualStudio.Product.Professional',
213+
'Microsoft.VisualStudio.Product.Enterprise',
214+
'Microsoft.VisualStudio.Product.BuildTools')
215+
196216
$instPaths = @()
197217
if ($vswhere) {
198-
# First try with the VC tools requirement (preferred)
199-
try { $p = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null; if ($p) { $instPaths += $p } } catch {}
200-
# Fallback: try without -requires to find any VS installations
218+
# Newest VS instance with the C++ VC tools workload (stable + prerelease).
219+
try { $p = & $vswhere -latest -prerelease -products $vsProducts -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null; if ($p) { $instPaths += $p } } catch {}
220+
# Last-resort fallback if no instance reports the VC workload (still
221+
# validated below by Test-VsHasNativeTools to skip unusable installs).
201222
if (-not $instPaths) {
202-
try { $p2 = & $vswhere -latest -products * -property installationPath 2>$null; if ($p2) { $instPaths += $p2 } } catch {}
223+
try { $p2 = & $vswhere -latest -prerelease -products $vsProducts -property installationPath 2>$null; if ($p2) { $instPaths += $p2 } } catch {}
203224
}
204225
}
205226

206-
# Add explicit common year-based candidates as a last resort
227+
# Add explicit common candidates as a last resort (newest first)
207228
if (-not $instPaths) {
208229
$explicit = @(
230+
"$env:ProgramFiles\Microsoft Visual Studio\2026\Insiders",
231+
"$env:ProgramFiles\Microsoft Visual Studio\2026\Preview",
232+
"$env:ProgramFiles\Microsoft Visual Studio\2026\Community",
233+
"$env:ProgramFiles\Microsoft Visual Studio\2026\Professional",
234+
"$env:ProgramFiles\Microsoft Visual Studio\2026\Enterprise",
235+
"$env:ProgramFiles\Microsoft Visual Studio\18\Insiders",
236+
"$env:ProgramFiles\Microsoft Visual Studio\18\Preview",
237+
"$env:ProgramFiles\Microsoft Visual Studio\18\Community",
238+
"$env:ProgramFiles\Microsoft Visual Studio\18\Professional",
239+
"$env:ProgramFiles\Microsoft Visual Studio\18\Enterprise",
240+
"$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Preview",
209241
"$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Community",
210242
"$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Professional",
211243
"$env:ProgramFiles (x86)\Microsoft Visual Studio\2022\Enterprise",
244+
"$env:ProgramFiles\Microsoft Visual Studio\2022\Preview",
212245
"$env:ProgramFiles\Microsoft Visual Studio\2022\Community",
213246
"$env:ProgramFiles\Microsoft Visual Studio\2022\Professional",
214247
"$env:ProgramFiles\Microsoft Visual Studio\2022\Enterprise"
@@ -221,7 +254,8 @@ function Ensure-VsDevEnvironment {
221254
return $false
222255
}
223256

224-
# Try each candidate installation path until one works
257+
# Try each candidate installation path until one yields a working native
258+
# toolchain (validated post-init via Test-VsHasNativeTools).
225259
foreach ($inst in $instPaths) {
226260
if (-not $inst) { continue }
227261
Write-Host "[VS] Checking candidate: $inst"
@@ -234,6 +268,10 @@ function Ensure-VsDevEnvironment {
234268
# Call Enter-VsDevShell using only the install path to avoid parameter name differences
235269
try {
236270
Enter-VsDevShell -VsInstallPath $inst -ErrorAction Stop
271+
if (-not (Test-VsHasNativeTools)) {
272+
Write-Warning "[VS] DevShell entered $inst but VC tools (VCToolsInstallDir) not present - skipping"
273+
continue
274+
}
237275
Write-Host "[VS] Entered Visual Studio DevShell at $inst"
238276
return $true
239277
} catch {
@@ -249,12 +287,20 @@ function Ensure-VsDevEnvironment {
249287
Write-Host "[VS] Running VsDevCmd.bat and importing environment from $vsDevCmd"
250288
try {
251289
$cmdOut = cmd.exe /c "`"$vsDevCmd`" && set"
290+
if ($LASTEXITCODE -ne 0) {
291+
Write-Warning "[VS] VsDevCmd.bat exited with code $LASTEXITCODE at $inst"
292+
continue
293+
}
252294
foreach ($line in $cmdOut) {
253295
$parts = $line -split('=',2)
254296
if ($parts.Length -eq 2) {
255297
try { [Environment]::SetEnvironmentVariable($parts[0], $parts[1], 'Process') } catch {}
256298
}
257299
}
300+
if (-not (Test-VsHasNativeTools)) {
301+
Write-Warning "[VS] VsDevCmd.bat imported $inst but VC tools (VCToolsInstallDir) not present - skipping"
302+
continue
303+
}
258304
Write-Host "[VS] Imported environment from VsDevCmd.bat at $inst"
259305
return $true
260306
} catch {

0 commit comments

Comments
 (0)