From 0d69ee9a244b0855a73a168c56a37726d798b505 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:25:00 -0700 Subject: [PATCH 01/20] script windows common --- .../windows/_common/apply-configuration.ps1 | 123 +++++++++++++ .../_common/assert-winget-configure.ps1 | 154 ++++++++++++++++ .../_common/enable-winget-configure.ps1 | 167 ++++++++++++++++++ .../scripts/windows/_common/invoke-retry.ps1 | 45 +++++ .../scripts/windows/_common/preflight.ps1 | 36 ++++ .../scripts/windows/_common/refresh-path.ps1 | 52 ++++++ 6 files changed, 577 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/_common/apply-configuration.ps1 create mode 100644 WindowsDevScripts/scripts/windows/_common/assert-winget-configure.ps1 create mode 100644 WindowsDevScripts/scripts/windows/_common/enable-winget-configure.ps1 create mode 100644 WindowsDevScripts/scripts/windows/_common/invoke-retry.ps1 create mode 100644 WindowsDevScripts/scripts/windows/_common/preflight.ps1 create mode 100644 WindowsDevScripts/scripts/windows/_common/refresh-path.ps1 diff --git a/WindowsDevScripts/scripts/windows/_common/apply-configuration.ps1 b/WindowsDevScripts/scripts/windows/_common/apply-configuration.ps1 new file mode 100644 index 00000000..9f8b9494 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/_common/apply-configuration.ps1 @@ -0,0 +1,123 @@ +<# +.SYNOPSIS + Apply a winget DSC configuration file with retry, refresh PATH in the current + session, verify a list of expected commands, and emit the CI sentinel. + +.DESCRIPTION + Flow-level `install.ps1` files are thin shims: the real install logic lives + in each flow's `configuration.winget`. This helper centralizes the glue + that CI needs around `winget configure`: + + 1. `winget configure --file ` with exponential-backoff retry + (shared helper; flaky network is common on hosted runners). Always + passes `--accept-configuration-agreements` and `--disable-interactivity`. + Note: `--accept-package-agreements` is NOT a valid flag on + `winget configure` (only on `winget install`). Package-agreement + consent for packages installed by DSC resources flows through + `--accept-configuration-agreements`. + 2. Re-read machine+user PATH from the registry into `$env:Path` so the + caller's *current* PowerShell session can see freshly installed + executables (winget updates the registry but not running processes). + 3. Assert each command in `-RequireCommands` resolves on PATH. + 4. Print `INSTALL_OK: ` as the final line; CI asserts on this. + +.PARAMETER Id + Flow id, only used in log prefixes and the final sentinel line. + +.PARAMETER ConfigFile + Path to the winget DSC YAML config for the flow. + +.PARAMETER RequireCommands + Commands that must resolve on PATH after configuration has been applied. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory)] [string] $Id, + [Parameter(Mandatory)] [string] $ConfigFile, + # AllowEmptyCollection: Windows PowerShell 5.1 rejects empty arrays + # bound to Mandatory parameters. Some flows (e.g. mac-comfort-shell) + # have no post-install CLI to verify - the DSC only installs a font + # and pwsh - so they legitimately pass @() here. + [Parameter(Mandatory)] [AllowEmptyCollection()] [string[]] $RequireCommands +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Fix #15: force UTF-8 on this process's console + external-program +# pipes. Windows PowerShell 5.1 defaults to the ANSI code page (1252 on +# en-US) for `[Console]::OutputEncoding`, which mangles winget's +# braille-pattern spinner glyphs into scrolling mojibake. winget writes +# UTF-8; matching it up front lets the carriage-return overwrites in +# the spinner work as intended and gives readable progress output. +# Safe no-op on pwsh 7 where UTF-8 is already the default. +try { + $utf8NoBom = [System.Text.UTF8Encoding]::new($false) + [Console]::OutputEncoding = $utf8NoBom + $OutputEncoding = $utf8NoBom +} catch { + # Some hosts (e.g. certain CI agents with redirected stdout) reject + # the assignment. Not worth failing the whole flow over cosmetics. + Write-Verbose "Could not force UTF-8 console encoding: $($_.Exception.Message)" +} + +$common = Split-Path -Parent $PSCommandPath +. (Join-Path $common 'invoke-retry.ps1') + +# Hard-fail fast if `winget configure` isn't available on this host. Every +# flow in this repo -- and the CmdPal extension that launches them -- uses +# `winget configure` as its only install path, so this is a stop-the-world +# prerequisite, not a warn-and-continue diagnostic. +# +# Fix #16: if the assert fails on first try, auto-invoke the canonical +# remediation (`enable-winget-configure.ps1`) once and then re-assert. +# The remediation runs `winget configure --enable` and installs +# Microsoft.VCRedist.2015+.x64, which covers the two failure modes a +# fresh VM actually hits. The remediation script itself self-elevates +# via UAC when needed; when we're already elevated (the install.ps1 +# entry point in practice) it runs in-process and does not pause. +$assertScript = Join-Path $common 'assert-winget-configure.ps1' +$enableScript = Join-Path $common 'enable-winget-configure.ps1' +try { + & $assertScript +} +catch { + Write-Host '' + Write-Host "--- winget configure not available; auto-remediating via $enableScript ---" -ForegroundColor Yellow + Write-Host " (reason: $($_.Exception.Message.Split([Environment]::NewLine)[0]))" -ForegroundColor DarkGray + Write-Host '' + & $enableScript + # Re-assert; surface the original failure mode if remediation did + # not actually fix it. + & $assertScript +} + +if (-not (Test-Path -LiteralPath $ConfigFile)) { + throw "DSC config file not found: $ConfigFile" +} + +Write-Host "--- $Id flow: winget configure --file $ConfigFile ---" + +Invoke-Retry -Name "winget configure $Id" -ScriptBlock { + winget configure ` + --file $ConfigFile ` + --accept-configuration-agreements ` + --disable-interactivity + if ($LASTEXITCODE -ne 0) { + throw "winget configure failed with exit code $LASTEXITCODE" + } +} + +# winget updates the registry copy of PATH but not the PATH of this already +# running PowerShell process. Rehydrate so subsequent CI steps see new tools. +& (Join-Path $common 'refresh-path.ps1') + +foreach ($cmd in $RequireCommands) { + if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) { + throw "$cmd not found on PATH after applying $ConfigFile" + } + Write-Host "$cmd : $(& $cmd --version 2>&1 | Select-Object -First 1)" +} + +Write-Host "INSTALL_OK: $Id" diff --git a/WindowsDevScripts/scripts/windows/_common/assert-winget-configure.ps1 b/WindowsDevScripts/scripts/windows/_common/assert-winget-configure.ps1 new file mode 100644 index 00000000..dd660d6d --- /dev/null +++ b/WindowsDevScripts/scripts/windows/_common/assert-winget-configure.ps1 @@ -0,0 +1,154 @@ +<# +.SYNOPSIS + Hard-fail preflight: assert that `winget configure` is available on this + host. Every Windows flow in this repo -- and the CmdPal extension that + launches them -- uses `winget configure` as its only install path. If it + isn't wired up, there is nothing useful to do but bail with an + actionable message. + +.DESCRIPTION + Failure modes this catches, in order of likelihood: + + 1. winget (Microsoft.DesktopAppInstaller) is not installed at all, or + is too old / broken to expose the `configure` subcommand. + 2. The `configuration` experimental feature flag is turned off in + `winget settings` (`experimentalFeatures.configuration = false`). + Only relevant on winget < 1.6; harmless to check on newer builds. + 3. Group Policy / MDM has disabled configure via the ADMX policy + `EnableWindowsPackageManagerConfiguration` (registry value + `HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppInstaller\ + EnableWindowsPackageManagerConfiguration`, `0` = disabled). + 4. Running in a non-interactive context where the AppInstaller COM + server cannot spin up (headless service accounts, SSH sessions + without a desktop). We can't always detect this -- we just surface + it as a fallback hint when the other checks pass but configure + still errors. + + This script never "warns and continues" -- the whole point of this repo + is `winget configure`, so a failure here is a stop-the-world condition. + +.PARAMETER Quiet + Suppress the "OK" line on success. Error output is always emitted. +#> + +[CmdletBinding()] +param( + [switch] $Quiet +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# UTF-8 console encoding, matching winget's output. Without this, a +# `winget configure --help` probe under Windows PowerShell 5.1 prints +# garbled glyphs in the error message we surface. See issue #15. +try { + $utf8NoBom = [System.Text.UTF8Encoding]::new($false) + [Console]::OutputEncoding = $utf8NoBom + $OutputEncoding = $utf8NoBom +} catch { + Write-Verbose "Could not force UTF-8 console encoding: $($_.Exception.Message)" +} + +function Test-ConfigurePolicyAllowed { + # Returns $true if the GPO key is absent or set to anything other than 0. + # Returns $false ONLY when the key is explicitly 0 (disabled by policy). + $keyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppInstaller' + try { + $val = (Get-ItemProperty -Path $keyPath -Name 'EnableWindowsPackageManagerConfiguration' -ErrorAction Stop).EnableWindowsPackageManagerConfiguration + return [int]$val -ne 0 + } catch { + # Key or value absent => not policy-restricted. + return $true + } +} + +# 1. winget itself must be present. +$wingetCmd = Get-Command winget -ErrorAction SilentlyContinue +if (-not $wingetCmd) { + throw @" +winget is not installed or not on PATH. + +This repository's flows (and the CmdPal extension) require Windows Package +Manager (winget) with the `configure` subcommand. To fix: + + 1. Install / update 'App Installer' from the Microsoft Store, or + 2. Grab the latest MSIX from + https://github.com/microsoft/winget-cli/releases/latest + (look for Microsoft.DesktopAppInstaller_*.msixbundle). + +Then re-run your command. +"@ +} + +# 2. GPO / MDM check first -- cheapest, and its error message is the most +# actionable, so surface it before the subprocess call. +if (-not (Test-ConfigurePolicyAllowed)) { + throw @" +`winget configure` is disabled by Group Policy on this machine. + +Registry key: + HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppInstaller + EnableWindowsPackageManagerConfiguration = 0 + +This is ADMX policy 'Enable Windows Package Manager Configuration' +(Computer Configuration > Administrative Templates > Windows Components > +App Installer). If this is your box, set the policy to 'Enabled' (or +delete the value) and reboot. If the box is domain-managed, file a +ticket with IT -- every flow in this repo depends on configure being +allowed. +"@ +} + +# 3. Probe the configure subcommand itself. `--help` is a pure no-op that +# exits 0 iff the subcommand is wired up and the experimental flag +# (if required) is on. +$helpOutput = & winget configure --help 2>&1 +$helpExit = $LASTEXITCODE + +$looksRecognized = ($helpExit -eq 0) -and ($helpOutput -join "`n") -match '(?i)configuration|configure' + +if (-not $looksRecognized) { + throw @" +`winget configure --help` did not succeed on this machine +(exit=$helpExit). winget itself is present ($($wingetCmd.Source)) but the +`configure` subcommand is not wired up. + +Output from `winget configure --help`: +-------- +$($helpOutput -join [Environment]::NewLine) +-------- + +To fix, run the canonical remediation script (elevates via UAC): + + scripts\windows\_common\enable-winget-configure.ps1 + +It runs `winget configure --enable` and installs the required +Microsoft.VCRedist.2015+.x64 dependency, then re-verifies. The CmdPal +extension's red banner launches the same script. If you prefer to run +the steps by hand: + + winget configure --enable + winget install -s winget --id Microsoft.VCRedist.2015+.x64 `` + --accept-package-agreements --accept-source-agreements + +Still failing after the script? Likely causes: + + 1. App Installer itself is too old to know `--enable`. Update it: + winget source update + winget upgrade --id Microsoft.AppInstaller --accept-source-agreements --accept-package-agreements + or install the latest MSIX from + https://github.com/microsoft/winget-cli/releases/latest + + 2. You are running from a non-interactive session (SSH, Scheduled + Task 'run whether user is logged on or not', headless service + account). winget's configure path needs the AppInstaller COM + server, which requires an interactive desktop. Re-run from a + foreground PowerShell / Windows Terminal window. +"@ +} + +if (-not $Quiet) { + $ver = (& winget --version) 2>$null + Write-Host "winget configure: available ($ver)" +} diff --git a/WindowsDevScripts/scripts/windows/_common/enable-winget-configure.ps1 b/WindowsDevScripts/scripts/windows/_common/enable-winget-configure.ps1 new file mode 100644 index 00000000..387292c2 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/_common/enable-winget-configure.ps1 @@ -0,0 +1,167 @@ +<# +.SYNOPSIS + One-shot "fix it" script: turn on `winget configure` on a machine where + it isn't working yet. + +.DESCRIPTION + This is the single remediation path for the three failure modes that + `assert-winget-configure.ps1` detects. The CmdPal extension's red + "winget configure is unavailable" banner launches this script; humans + can also run it by hand. Keeping the logic here (not duplicated in C#) + means any future tweak -- e.g. dropping the VCRedist install once + AppInstaller ships it transitively -- only has to happen in one place. + + What it does, in order: + + 1. Self-elevates via `Start-Process -Verb RunAs` if not already admin. + `winget configure --enable` flips a machine-wide flag and needs + elevation; `Microsoft.VCRedist.2015+.x64` likewise. + 2. Runs `winget configure --enable` -- the supported first-party way + to turn the `configure` subcommand on. Ignores "already enabled" + errors so re-runs are a safe no-op. + 3. Installs `Microsoft.VCRedist.2015+.x64` -- the PackageManager + configure path transitively depends on the 2015+ x64 redistributable + (AppInstaller does not always pull it in on its own). Skipped when + already present. + 4. Re-runs the assert to confirm the fix took. + +.PARAMETER NoElevate + Internal switch used by the self-elevation path to avoid infinite + re-elevation loops. Do not set by hand. + +.PARAMETER SkipVCRedist + Skip step (3). Useful once Microsoft ships a configure path that no + longer needs VCRedist -- flip this on and we keep the rest of the + remediation. + +.EXAMPLE + # From a normal PowerShell -- triggers a UAC prompt, then runs. + .\enable-winget-configure.ps1 + +.EXAMPLE + # From an already-elevated PowerShell (e.g. inside a VM bootstrap). + .\enable-winget-configure.ps1 -NoElevate +#> + +[CmdletBinding()] +param( + [switch] $NoElevate, + [switch] $SkipVCRedist, + + # Internal: set only by the self-elevation path below so the exit + # pause fires only when we're running in a fresh window that would + # otherwise close. Not part of the public surface; users running the + # script themselves (elevated or not) should not pass this. + [switch] $FromRelaunch +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Force UTF-8 on the console + external pipe encodings. Windows +# PowerShell 5.1 defaults to the ANSI code page (1252) which mangles +# winget's braille-pattern spinner glyphs into scrolling mojibake. +# Safe no-op on pwsh 7. See issue #15. +try { + $utf8NoBom = [System.Text.UTF8Encoding]::new($false) + [Console]::OutputEncoding = $utf8NoBom + $OutputEncoding = $utf8NoBom +} catch { + Write-Verbose "Could not force UTF-8 console encoding: $($_.Exception.Message)" +} + +# Also force the OS-level console code page to 65001 (UTF-8) via chcp. +# [Console]::OutputEncoding alone is not always sufficient under Windows +# PowerShell 5.1 -- particularly in a freshly-spawned elevated conhost, +# where winget's own stdout goes through the OS console code page (1252 +# by default on en-US). That causes the VCRedist download progress bar's +# block glyphs (U+2588) to render as "ûÆ" mojibake. See issue #22. +try { + $null = cmd /c 'chcp 65001 >nul 2>&1' +} catch { } + +function Test-IsAdmin { + $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($id) + return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) +} + +if (-not (Test-IsAdmin)) { + if ($NoElevate) { + throw 'Not running as Administrator and -NoElevate was passed. Re-launch from an elevated PowerShell.' + } + + Write-Host '' + Write-Host 'This fix needs to run elevated (UAC prompt will appear).' -ForegroundColor Yellow + Write-Host 'Launching an elevated PowerShell...' -ForegroundColor Yellow + Write-Host '' + + $forwardedArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $PSCommandPath, '-NoElevate', '-FromRelaunch') + if ($SkipVCRedist) { $forwardedArgs += '-SkipVCRedist' } + + try { + Start-Process -FilePath 'pwsh.exe' -ArgumentList $forwardedArgs -Verb RunAs -Wait + } catch { + # Fall back to Windows PowerShell 5.1 if pwsh isn't installed. + Start-Process -FilePath 'powershell.exe' -ArgumentList $forwardedArgs -Verb RunAs -Wait + } + return +} + +Write-Host '' +Write-Host '=== enable-winget-configure ===' -ForegroundColor Cyan +Write-Host '' + +# --- Step 1: winget configure --enable ---------------------------------- +Write-Host 'Step 1/3: winget configure --enable' -ForegroundColor Cyan +try { + & winget configure --enable --disable-interactivity --accept-source-agreements 2>&1 | Write-Host + if ($LASTEXITCODE -ne 0) { + # Some winget builds return non-zero on "already enabled" -- inspect + # stderr instead of hard-failing on the exit code alone. + Write-Host " (exit=$LASTEXITCODE -- if already enabled this is benign)" -ForegroundColor DarkYellow + } +} catch { + Write-Warning "winget configure --enable raised: $($_.Exception.Message)" +} + +# --- Step 2: VCRedist 2015+ x64 ----------------------------------------- +if ($SkipVCRedist) { + Write-Host '' + Write-Host 'Step 2/3: SKIPPED (via -SkipVCRedist)' -ForegroundColor DarkYellow +} else { + Write-Host '' + Write-Host 'Step 2/3: winget install Microsoft.VCRedist.2015+.x64' -ForegroundColor Cyan + & winget install ` + --source winget ` + --id 'Microsoft.VCRedist.2015+.x64' ` + --accept-package-agreements ` + --accept-source-agreements ` + --disable-interactivity 2>&1 | Write-Host + if ($LASTEXITCODE -ne 0) { + Write-Host " (exit=$LASTEXITCODE -- if already installed this is benign)" -ForegroundColor DarkYellow + } +} + +# --- Step 3: re-run the assert to confirm the fix took ------------------ +Write-Host '' +Write-Host 'Step 3/3: verifying winget configure is now available' -ForegroundColor Cyan +$assert = Join-Path $PSScriptRoot 'assert-winget-configure.ps1' +if (Test-Path -LiteralPath $assert) { + & $assert +} else { + Write-Warning "assert-winget-configure.ps1 not found next to this script; skipping verify." +} + +Write-Host '' +Write-Host 'All done. You can close this window.' -ForegroundColor Green +Write-Host '' + +# Pause only if we self-elevated into a fresh window that would +# otherwise close before the user could read the output. When invoked +# directly from the user's own shell (elevated or not), the window is +# under the user's control and the pause is pure friction. +if ($FromRelaunch -and $Host.Name -eq 'ConsoleHost') { + Write-Host 'Press any key to exit...' -ForegroundColor DarkGray + try { [void][System.Console]::ReadKey($true) } catch { Start-Sleep -Seconds 5 } +} diff --git a/WindowsDevScripts/scripts/windows/_common/invoke-retry.ps1 b/WindowsDevScripts/scripts/windows/_common/invoke-retry.ps1 new file mode 100644 index 00000000..6e089ad7 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/_common/invoke-retry.ps1 @@ -0,0 +1,45 @@ +<# +.SYNOPSIS + Retry a script block with exponential backoff. Intended for flaky network + operations such as `winget install` or package-registry pulls. + +.EXAMPLE + . "$PSScriptRoot/invoke-retry.ps1" + Invoke-Retry -Name 'winget install Node.js' -ScriptBlock { + winget install --id OpenJS.NodeJS.LTS --silent ` + --accept-package-agreements --accept-source-agreements + if ($LASTEXITCODE -ne 0) { throw "winget exited $LASTEXITCODE" } + } +#> + +$ErrorActionPreference = 'Stop' + +function Invoke-Retry { + [CmdletBinding()] + param( + [Parameter(Mandatory)] [scriptblock] $ScriptBlock, + [string] $Name = 'operation', + [int] $MaxAttempts = 3, + [int] $InitialDelaySeconds = 5 + ) + + $attempt = 0 + $delay = $InitialDelaySeconds + while ($true) { + $attempt++ + try { + Write-Host "[invoke-retry] ${Name}: attempt $attempt/$MaxAttempts" + & $ScriptBlock + Write-Host "[invoke-retry] ${Name}: success on attempt $attempt" + return + } catch { + if ($attempt -ge $MaxAttempts) { + Write-Host "[invoke-retry] ${Name}: giving up after $attempt attempts" + throw + } + Write-Warning "[invoke-retry] ${Name}: attempt $attempt failed: $($_.Exception.Message). Retrying in ${delay}s..." + Start-Sleep -Seconds $delay + $delay = $delay * 2 + } + } +} diff --git a/WindowsDevScripts/scripts/windows/_common/preflight.ps1 b/WindowsDevScripts/scripts/windows/_common/preflight.ps1 new file mode 100644 index 00000000..66cade7c --- /dev/null +++ b/WindowsDevScripts/scripts/windows/_common/preflight.ps1 @@ -0,0 +1,36 @@ +<# +.SYNOPSIS + Log the runner's state before a flow runs. Pure diagnostics; never fails CI. +#> + +$ErrorActionPreference = 'Continue' + +Write-Host '==== preflight ====' +Write-Host "Date (UTC): $((Get-Date).ToUniversalTime().ToString('o'))" +Write-Host "Host: $env:COMPUTERNAME" +Write-Host "User: $env:USERNAME" +Write-Host "PowerShell: $($PSVersionTable.PSVersion)" +try { + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction Stop + Write-Host "OS: $($os.Caption) $($os.Version) build $($os.BuildNumber)" +} catch { + Write-Host "OS: (Get-CimInstance failed: $($_.Exception.Message))" +} + +try { + $wv = (winget --version) 2>$null + Write-Host "winget: $wv" +} catch { + Write-Host "winget: not available" +} + +try { + $drive = Get-PSDrive -Name C -ErrorAction Stop + $freeGb = [math]::Round($drive.Free / 1GB, 2) + $usedGb = [math]::Round($drive.Used / 1GB, 2) + Write-Host "Disk C: free=${freeGb}GB used=${usedGb}GB" +} catch { + Write-Host "Disk: (query failed: $($_.Exception.Message))" +} + +Write-Host '==== /preflight ====' diff --git a/WindowsDevScripts/scripts/windows/_common/refresh-path.ps1 b/WindowsDevScripts/scripts/windows/_common/refresh-path.ps1 new file mode 100644 index 00000000..f229c5c3 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/_common/refresh-path.ps1 @@ -0,0 +1,52 @@ +<# +.SYNOPSIS + Refresh $env:PATH (and a few related vars) in the current PowerShell session + by re-reading Machine + User environment from the registry. + +.DESCRIPTION + Windows installers (including winget packages) update the registry copy of + PATH but do not update the PATH of already-running processes. Without this, + a script that installs Node.js will not see `node.exe` in the same session. +#> + +$ErrorActionPreference = 'Stop' + +function Get-EnvFromRegistry { + param( + [Parameter(Mandatory)] [ValidateSet('Machine', 'User')] [string] $Scope, + [Parameter(Mandatory)] [string] $Name + ) + if ($Scope -eq 'Machine') { + $key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + } else { + $key = 'HKCU:\Environment' + } + try { + return (Get-ItemProperty -Path $key -Name $Name -ErrorAction Stop).$Name + } catch { + return $null + } +} + +$machinePath = Get-EnvFromRegistry -Scope Machine -Name 'Path' +$userPath = Get-EnvFromRegistry -Scope User -Name 'Path' + +$combined = @($machinePath, $userPath) | + Where-Object { $_ } | + ForEach-Object { $_.TrimEnd(';') } | + Where-Object { $_ } | + ForEach-Object { $_ -split ';' } | + Where-Object { $_ -and $_.Trim() } | + Select-Object -Unique + +$env:Path = ($combined -join ';') + +# Some tools read these instead of (or in addition to) PATH. +foreach ($n in @('PATHEXT', 'PSModulePath')) { + $m = Get-EnvFromRegistry -Scope Machine -Name $n + $u = Get-EnvFromRegistry -Scope User -Name $n + $v = @($m, $u) | Where-Object { $_ } | ForEach-Object { $_.TrimEnd(';') } | Where-Object { $_ } + if ($v) { Set-Item -Path "Env:$n" -Value ($v -join ';') } +} + +Write-Host "[refresh-path] PATH rehydrated ($($combined.Count) entries)" From 283122277b6e3703842e60ded213692577b40d6c Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:33:26 -0700 Subject: [PATCH 02/20] script windows dotnet --- .../windows/dotnet/configuration.winget | 52 +++++++++++++++++++ .../scripts/windows/dotnet/install.ps1 | 26 ++++++++++ 2 files changed, 78 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/dotnet/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/dotnet/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/dotnet/configuration.winget b/WindowsDevScripts/scripts/windows/dotnet/configuration.winget new file mode 100644 index 00000000..cf02b533 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/dotnet/configuration.winget @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the .NET developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do .NET +# development. Apply it directly with: +# +# winget configure --file scripts/windows/dotnet/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# `--accept-package-agreements` is intentionally NOT in the canonical +# invocation above: each `Microsoft.WinGet/Package` resource below sets +# `acceptAgreements: true`, which causes the dscv3 resource to internally +# pass both `--accept-source-agreements` and `--accept-package-agreements` +# to its own winget install call. See AGENTS.md section 4. +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Resources: +# - DotNetSdk: installs the .NET 10 SDK via winget. The SDK ships the +# cross-platform runtimes plus the `dotnet` CLI used to build +# and run the hello-world. The WinForms and WinUI flows use +# the same package id and pair it with workload-specific +# project settings (UseWindowsForms=true, the Windows App +# SDK targeting pack); this flow installs only the core SDK +# so it can serve as a foundation for cross-platform +# scenarios (e.g. web-api-csharp). +# +# Note on the package id: +# winget's community repo does NOT publish an unversioned `Microsoft.DotNet` +# meta package; `WinGetPackage` uses `--exact` resolution, so a versioned id +# is required. `Microsoft.DotNet.SDK.10` tracks the .NET 10 LTS line — bump +# this when the next LTS ships and .NET 10 reaches EOL. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: DotNetSdk + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: Microsoft.DotNet.SDK.10 + source: winget + acceptAgreements: true diff --git a/WindowsDevScripts/scripts/windows/dotnet/install.ps1 b/WindowsDevScripts/scripts/windows/dotnet/install.ps1 new file mode 100644 index 00000000..be6a87d5 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/dotnet/install.ps1 @@ -0,0 +1,26 @@ +<# +.SYNOPSIS + Apply the .NET winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the .NET flow is + `configuration.winget` in this directory - a winget DSC configuration that + declaratively installs the .NET 10 SDK via winget. + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `dotnet`, + * verify `dotnet` resolves, and + * emit `INSTALL_OK: dotnet` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'dotnet' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('dotnet') From 47d97ec68d18c7e91cfcbce01ae4eda7420b89c3 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:37:12 -0700 Subject: [PATCH 03/20] script windows go --- .../scripts/windows/go/configuration.winget | 53 +++++++++++++++++++ .../scripts/windows/go/install.ps1 | 26 +++++++++ 2 files changed, 79 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/go/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/go/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/go/configuration.winget b/WindowsDevScripts/scripts/windows/go/configuration.winget new file mode 100644 index 00000000..56c44270 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/go/configuration.winget @@ -0,0 +1,53 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the Go developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do Go +# development. Apply it directly with: +# +# winget configure --file scripts/windows/go/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# `--accept-package-agreements` is intentionally NOT in the canonical +# invocation above: the `Microsoft.WinGet/Package` resource below sets +# `acceptAgreements: true`, which causes the dscv3 resource to internally +# pass both `--accept-source-agreements` and `--accept-package-agreements` +# to its own winget install call. See AGENTS.md section 4. +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Resources: +# - Go: installs the Go toolchain via winget. Includes the `go` CLI +# (which fronts `go build`, `go run`, `go test`, `go fmt`, etc.) +# plus the standard library. The hello-world is exercised with +# `go run` so no separate build step is required. +# +# Note on the package id (deliberate exception to AGENTS.md section 6): +# AGENTS.md section 6 directs new flows to use minor-versioned ids +# (e.g. `Python.Python.3.13`). Go is the deliberate exception: +# winget's community repo publishes Go *only* as the unversioned +# meta-package `GoLang.Go`, which is a rolling pointer at the latest +# stable release (currently 1.26.x). There is no `GoLang.Go.1.26` +# equivalent — `winget show --id GoLang.Go.1.26 --exact` returns +# "no package found". Using `GoLang.Go` here is the only way to +# resolve under `--exact`. When upstream begins publishing +# minor-versioned ids, swap this for the pinned form. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Go + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: GoLang.Go + source: winget + acceptAgreements: true diff --git a/WindowsDevScripts/scripts/windows/go/install.ps1 b/WindowsDevScripts/scripts/windows/go/install.ps1 new file mode 100644 index 00000000..e9414362 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/go/install.ps1 @@ -0,0 +1,26 @@ +<# +.SYNOPSIS + Apply the Go winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the Go flow is + `configuration.winget` in this directory - a winget DSC configuration that + declaratively installs the Go toolchain via winget. + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `go`, + * verify `go` resolves, and + * emit `INSTALL_OK: go` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'go' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('go', 'gofmt') From 58b78a359002d55c855d3c9c2ed61959a094b785 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:38:03 -0700 Subject: [PATCH 04/20] script windows java --- .../scripts/windows/java/configuration.winget | 57 +++++++++++++++++++ .../scripts/windows/java/install.ps1 | 26 +++++++++ 2 files changed, 83 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/java/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/java/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/java/configuration.winget b/WindowsDevScripts/scripts/windows/java/configuration.winget new file mode 100644 index 00000000..68de1c82 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/java/configuration.winget @@ -0,0 +1,57 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the Java developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do Java +# development. Apply it directly with: +# +# winget configure --file scripts/windows/java/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# `--accept-package-agreements` is intentionally NOT in the canonical +# invocation above: the `Microsoft.WinGet/Package` resource below sets +# `acceptAgreements: true`, which causes the dscv3 resource to internally +# pass both `--accept-source-agreements` and `--accept-package-agreements` +# to its own winget install call. See AGENTS.md section 4. +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Resources: +# - OpenJdk: installs the Microsoft Build of OpenJDK 21 (LTS) via winget. +# Adds `java`, `javac`, `jar`, and friends to PATH. +# +# Note on the package id: +# `Microsoft.OpenJDK.21` is the current LTS line. Microsoft also publishes +# `Microsoft.OpenJDK.11`, `.17`, and `.25` — bump when a new LTS ships and +# 21 reaches Microsoft's documented end-of-support window. +# +# Note on Maven / Gradle (deliberately omitted): +# The original plan called for installing Maven alongside the JDK, but +# `Apache.Maven` is NOT published in the winget community repo +# (`winget search maven` returns only forks for unrelated projects). +# Rather than wrap an apache.org tarball-download in a `Script` resource, +# this flow installs only the JDK and relies on the modern Java +# convention: each project ships its own pinned build tool via the +# `mvnw` (Maven Wrapper) or `gradlew` (Gradle Wrapper) script in its +# root. The `web-api-java` scenario flow that builds on top of this one +# will use `mvnw` from a vendored start.spring.io output, so no +# system-wide Maven install is required. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: OpenJdk + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: Microsoft.OpenJDK.21 + source: winget + acceptAgreements: true diff --git a/WindowsDevScripts/scripts/windows/java/install.ps1 b/WindowsDevScripts/scripts/windows/java/install.ps1 new file mode 100644 index 00000000..e6cdef0a --- /dev/null +++ b/WindowsDevScripts/scripts/windows/java/install.ps1 @@ -0,0 +1,26 @@ +<# +.SYNOPSIS + Apply the Java winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the Java flow is + `configuration.winget` in this directory - a winget DSC configuration that + declaratively installs the Microsoft Build of OpenJDK 21 (LTS) via winget. + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `java`, + * verify `java` and `javac` resolve, and + * emit `INSTALL_OK: java` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'java' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('java', 'javac') From 7bea2a051c071e1251605f4ba154fd52bfc8814d Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:39:27 -0700 Subject: [PATCH 05/20] script windows php --- .../scripts/windows/php/configuration.winget | 37 +++++++++++++++++++ .../scripts/windows/php/install.ps1 | 26 +++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/php/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/php/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/php/configuration.winget b/WindowsDevScripts/scripts/windows/php/configuration.winget new file mode 100644 index 00000000..fdd2d29b --- /dev/null +++ b/WindowsDevScripts/scripts/windows/php/configuration.winget @@ -0,0 +1,37 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the PHP developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do PHP +# development. Apply it directly with: +# +# winget configure --file scripts/windows/php/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Note on the package id: +# winget's community repo does NOT publish an unversioned `PHP.PHP` meta +# package; `WinGetPackage` uses `--exact` resolution, so a versioned id is +# required. `PHP.PHP.8.5` is the current stable line — bump this when +# upstream ships a new supported minor and the old one reaches EOL. (We had +# briefly pinned 8.4 but its manifest's installer URL started returning 404; +# 8.5 matches the build pre-installed on `windows-latest` runners anyway.) +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Php + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: PHP.PHP.8.5 + source: winget diff --git a/WindowsDevScripts/scripts/windows/php/install.ps1 b/WindowsDevScripts/scripts/windows/php/install.ps1 new file mode 100644 index 00000000..69cadbd3 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/php/install.ps1 @@ -0,0 +1,26 @@ +<# +.SYNOPSIS + Apply the PHP winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the PHP flow is + `configuration.winget` in this directory — a winget DSC configuration that + declaratively installs PHP via winget. + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `php`, + * verify `php` resolves, and + * emit `INSTALL_OK: php` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'php' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('php') From 3b5abea018c54fc13975189737b8ad1790e8e474 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:41:20 -0700 Subject: [PATCH 06/20] script windows python --- .../windows/python/configuration.winget | 60 +++++++++++++++++++ .../scripts/windows/python/install.ps1 | 27 +++++++++ 2 files changed, 87 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/python/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/python/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/python/configuration.winget b/WindowsDevScripts/scripts/windows/python/configuration.winget new file mode 100644 index 00000000..f03304c7 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/python/configuration.winget @@ -0,0 +1,60 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the Python developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do Python +# development. Apply it directly with: +# +# winget configure --file scripts/windows/python/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# `--accept-package-agreements` is intentionally NOT in the canonical +# invocation above: each `Microsoft.WinGet/Package` resource below sets +# `acceptAgreements: true`, which causes the dscv3 resource to internally +# pass both `--accept-source-agreements` and `--accept-package-agreements` +# to its own winget install call. See AGENTS.md §4. +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Resources: +# - Python: installs CPython 3.13 via winget. The installer adds `python`, +# `python3`, and `pip` to the user PATH. +# - Uv: installs uv (Astral's fast Python package / project manager) via +# winget. Adds `uv` and `uvx` to the user PATH. Kept as a sibling +# resource — uv is standalone and does not require Python to be +# installed first, but developers of this flow will almost always +# want both, so they ship together. +# +# Note on the Python package id: +# winget's community repo does NOT publish an unversioned `Python.Python` +# meta package; `WinGetPackage` uses `--exact` resolution, so a versioned id +# is required. `Python.Python.3.13` is the current stable line — bump this +# when upstream ships a new supported minor and the old one reaches EOL. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Python + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: Python.Python.3.13 + source: winget + acceptAgreements: true + + - name: Uv + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: astral-sh.uv + source: winget + acceptAgreements: true diff --git a/WindowsDevScripts/scripts/windows/python/install.ps1 b/WindowsDevScripts/scripts/windows/python/install.ps1 new file mode 100644 index 00000000..38864ee6 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/python/install.ps1 @@ -0,0 +1,27 @@ +<# +.SYNOPSIS + Apply the Python winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the Python flow is + `configuration.winget` in this directory — a winget DSC configuration that + declaratively installs CPython 3.13 and uv via winget. + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `python`, + `pip`, and `uv`, + * verify those commands resolve, and + * emit `INSTALL_OK: python` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'python' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('python', 'pip', 'uv') From d03b879511cf3f7e2db50f88dcfbb76e9f95e296 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:45:31 -0700 Subject: [PATCH 07/20] script windows rust --- .../scripts/windows/rust/configuration.winget | 79 +++++++++++++++++++ .../scripts/windows/rust/install.ps1 | 27 +++++++ 2 files changed, 106 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/rust/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/rust/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/rust/configuration.winget b/WindowsDevScripts/scripts/windows/rust/configuration.winget new file mode 100644 index 00000000..7e37a48c --- /dev/null +++ b/WindowsDevScripts/scripts/windows/rust/configuration.winget @@ -0,0 +1,79 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the Rust developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do Rust +# development. Apply it directly with: +# +# winget configure --file scripts/windows/rust/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# `--accept-package-agreements` is intentionally NOT in the canonical +# invocation above: the `Microsoft.WinGet/Package` resource below sets +# `acceptAgreements: true`, which causes the dscv3 resource to internally +# pass both `--accept-source-agreements` and `--accept-package-agreements` +# to its own winget install call. See AGENTS.md section 4. +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Why two resources (Rustup + post-install command): +# `Rustlang.Rustup` is the canonical Rust install path — it ships +# `rustup-init.exe`, which places `rustup` on PATH but does not by +# itself guarantee the actual toolchain (rustc, cargo, ...) is +# installed. The follow-up `rustup default stable` step does the heavy +# lifting: on first run it downloads and installs the stable toolchain +# for the host triple (x86_64-pc-windows-msvc by default) and sets it +# as the active toolchain; on subsequent runs it is a no-op. This is +# the dscv3-native way to express that step — `RunCommandOnSet` is the +# same escape hatch the winui flow uses to install adapter modules. +# AGENTS.md section 3's hint about falling back to v0.2 + `Script` +# only applies to v2 class-based `PSDscResources/Script`; native +# dscv3 `Microsoft.DSC.Transitional/RunCommandOnSet` is preferred. +# +# Resources (in application order, enforced by dependsOn): +# - Rustup: installs `Rustlang.Rustup` via winget, +# which gives us the `rustup` toolchain +# manager on user PATH. +# - InstallStableToolchain: invokes `pwsh` to refresh PATH from the +# registry (winget updates the registry but +# not running processes) and then runs +# `rustup default stable`. Idempotent: on +# first run installs + defaults stable; +# on subsequent runs a no-op. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - type: Microsoft.WinGet/Package + name: Rustup + properties: + id: Rustlang.Rustup + source: winget + acceptAgreements: true + metadata: + description: Install the rustup toolchain manager + winget: + securityContext: elevated + + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: InstallStableToolchain + dependsOn: + - Rustup + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": $machine = [Environment]::GetEnvironmentVariable('Path','Machine'); $user = [Environment]::GetEnvironmentVariable('Path','User'); $env:Path = (($machine, $user) | Where-Object { $_ }) -join ';'; if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { throw 'rustup not found on PATH after Rustup install; cannot install toolchain.' }; & rustup default stable; if ($LASTEXITCODE -ne 0) { throw "rustup default stable failed with exit code $LASTEXITCODE" } + treatAsArray: true + metadata: + description: Install and default the stable Rust toolchain via rustup + diff --git a/WindowsDevScripts/scripts/windows/rust/install.ps1 b/WindowsDevScripts/scripts/windows/rust/install.ps1 new file mode 100644 index 00000000..60ce0269 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/rust/install.ps1 @@ -0,0 +1,27 @@ +<# +.SYNOPSIS + Apply the Rust winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the Rust flow is + `configuration.winget` in this directory - a winget DSC configuration that + installs rustup via winget and then runs `rustup default stable` to bring + in the stable Rust toolchain (rustc, cargo, ...). + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `cargo`, + * verify `rustc` and `cargo` resolve, and + * emit `INSTALL_OK: rust` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'rust' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('rustc', 'cargo') From c8941b446d63813a68515f1e39377e3705ebb36f Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:49:17 -0700 Subject: [PATCH 08/20] script windows typescript --- .../windows/typescript/configuration.winget | 70 +++++++++++++++++++ .../scripts/windows/typescript/install.ps1 | 27 +++++++ 2 files changed, 97 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/typescript/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/typescript/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/typescript/configuration.winget b/WindowsDevScripts/scripts/windows/typescript/configuration.winget new file mode 100644 index 00000000..f6e6620c --- /dev/null +++ b/WindowsDevScripts/scripts/windows/typescript/configuration.winget @@ -0,0 +1,70 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +# +# Core artifact for the TypeScript developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to do TypeScript +# development. Apply it directly with: +# +# winget configure --file scripts/windows/typescript/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Resources: +# - Node: installs Node.js LTS via winget. +# - InstallTypeScript: runs `npm install --global typescript` so that `tsc` +# ends up on PATH. DSC `Script` resources execute +# PowerShell, which is the idiomatic way to express +# "do something winget alone can't" inside a DSC config. +# +properties: + configurationVersion: 0.2.0 + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: Node + directives: + description: Install Node.js LTS (provides node + npm) + allowPrerelease: false + settings: + id: OpenJS.NodeJS.LTS + source: winget + + - resource: PSDscResources/Script + id: InstallTypeScript + dependsOn: + - Node + directives: + description: Install the TypeScript compiler globally via npm + settings: + # DSC contract: + # TestScript -> $true means "already in desired state, skip SetScript" + # SetScript -> bring the system into desired state + # GetScript -> return a hashtable describing current state (unused here) + # + # We refresh PATH from the registry at the top of each script because + # the Node resource above updates machine PATH in the registry, but the + # PowerShell process evaluating this resource inherited its PATH at + # launch and will not otherwise see the freshly-installed npm.exe. + GetScript: | + return @{ Result = '' } + TestScript: | + $machine = [Environment]::GetEnvironmentVariable('Path','Machine') + $user = [Environment]::GetEnvironmentVariable('Path','User') + $env:Path = (($machine, $user) | Where-Object { $_ }) -join ';' + return [bool](Get-Command tsc -ErrorAction SilentlyContinue) + SetScript: | + $machine = [Environment]::GetEnvironmentVariable('Path','Machine') + $user = [Environment]::GetEnvironmentVariable('Path','User') + $env:Path = (($machine, $user) | Where-Object { $_ }) -join ';' + if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { + throw 'npm not found on PATH after Node install; cannot install typescript.' + } + & npm install --global --no-fund --no-audit typescript + if ($LASTEXITCODE -ne 0) { + throw "npm install --global typescript failed with exit code $LASTEXITCODE" + } diff --git a/WindowsDevScripts/scripts/windows/typescript/install.ps1 b/WindowsDevScripts/scripts/windows/typescript/install.ps1 new file mode 100644 index 00000000..4ac4f305 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/typescript/install.ps1 @@ -0,0 +1,27 @@ +<# +.SYNOPSIS + Apply the TypeScript winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the TypeScript flow + is `configuration.winget` in this directory — a winget DSC configuration + that declaratively installs Node.js LTS and, via a PSDscResources/Script + resource, globally installs the TypeScript compiler. + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see new tools, + * verify the expected commands resolve, and + * emit `INSTALL_OK: typescript` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'typescript' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('node', 'npm', 'tsc') From 5bf1ec522c6f418e7eb556dd3e057d0b5c16127d Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:53:43 -0700 Subject: [PATCH 09/20] script windows winforms --- .../windows/winforms/configuration.winget | 34 +++++++++++++++++++ .../scripts/windows/winforms/install.ps1 | 27 +++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/winforms/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/winforms/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/winforms/configuration.winget b/WindowsDevScripts/scripts/windows/winforms/configuration.winget new file mode 100644 index 00000000..cd9436f9 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/winforms/configuration.winget @@ -0,0 +1,34 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the WinForms developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to build and run +# a minimal Windows Forms (.NET) application. Apply it directly with: +# +# winget configure --file scripts/windows/winforms/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above, rehydrates PATH in the caller's session, and emits an +# `INSTALL_OK: ` sentinel for the test harness. All install logic lives +# here. +# +# Resources: +# - DotNetSdk: installs the .NET 10 SDK via winget. The SDK ships the Windows +# Desktop targeting pack needed by `UseWindowsForms=true` +# projects, so no separate workload install is required. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: DotNetSdk + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + properties: + id: Microsoft.DotNet.SDK.10 + source: winget diff --git a/WindowsDevScripts/scripts/windows/winforms/install.ps1 b/WindowsDevScripts/scripts/windows/winforms/install.ps1 new file mode 100644 index 00000000..dab2fc61 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/winforms/install.ps1 @@ -0,0 +1,27 @@ +<# +.SYNOPSIS + Apply the WinForms winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the WinForms flow + is `configuration.winget` in this directory — a winget DSC configuration + that declaratively installs the .NET 10 SDK (which includes the Windows + Desktop targeting pack used by `UseWindowsForms=true` projects). + + The shim exists only to: + * apply the DSC config with retry (hosted-runner networks are flaky), + * rehydrate PATH in the current session so later CI steps see `dotnet`, + * verify `dotnet` resolves, and + * emit `INSTALL_OK: winforms` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'winforms' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('dotnet') From 7bac3e9dc86b5fac1381fcd2aff25b58df5f6229 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:55:12 -0700 Subject: [PATCH 10/20] script windows winui --- .../windows/winui/configuration.winget | 160 ++++++++++++++++++ .../scripts/windows/winui/install.ps1 | 33 ++++ 2 files changed, 193 insertions(+) create mode 100644 WindowsDevScripts/scripts/windows/winui/configuration.winget create mode 100644 WindowsDevScripts/scripts/windows/winui/install.ps1 diff --git a/WindowsDevScripts/scripts/windows/winui/configuration.winget b/WindowsDevScripts/scripts/windows/winui/configuration.winget new file mode 100644 index 00000000..f6112960 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/winui/configuration.winget @@ -0,0 +1,160 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +# +# Core artifact for the WinUI 3 (Windows App SDK) developer flow on Windows. +# +# This is a winget DSC configuration file: a declarative, idempotent, and +# non-interactive description of the machine state required to build and run +# modern WinUI 3 / Windows App SDK applications. Apply it directly with: +# +# winget configure --file scripts/windows/winui/configuration.winget ` +# --accept-configuration-agreements ` +# --disable-interactivity +# +# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the +# command above (through `_common/apply-configuration.ps1`), rehydrates PATH +# in the caller's session, and emits an `INSTALL_OK: ` sentinel for the +# test harness. All install logic lives here. +# +# Onboarding source: +# https://learn.microsoft.com/windows/apps/get-started/start-here +# +# Why dscv3 with v2 class-based resources: +# The WinUI onboarding needs three PowerShell DSC v2 class-based modules +# (`Microsoft.Windows.Developer`, `Microsoft.Windows.Settings`, +# `Microsoft.VisualStudio.DSC`) that are not exposed as native dscv3 +# resources. The dscv3 processor hosts v2 class-based resources through its +# PowerShell adapter, so the preferred v3 document shape still applies — we +# just need each adapter module installed before its consumer runs. We do +# that with `Microsoft.DSC.Transitional/RunCommandOnSet` + `dependsOn`. +# Package install uses native `Microsoft.WinGet/Package` (see AGENTS.md §3). +# +# Package-agreement acceptance: +# `Microsoft.WinGet/Package` sets `acceptAgreements: true` per-resource +# (AGENTS.md §4). `--accept-configuration-agreements` (passed by the shim) +# consents to the configuration document itself. +# +# Resources (in application order, enforced by dependsOn): +# - Adapter modules: Install-Module on first Set for each v2 class-based +# module so its resource type is resolvable by the adapter. +# - OsVersion assertion: fail fast on pre-Win10 1809 (minimum supported by +# the Windows App SDK). +# - DeveloperMode: required for unpackaged/sideloaded Windows App SDK apps +# and for a normal WinUI inner-loop (F5 deploy). +# - Visual Studio: installs VS 2026 Community (channelId VisualStudio.18.Release +# → productId Microsoft.VisualStudio.Product.Community); the IDE is what +# a human onboarding to WinUI is expected to use for design, debugging, +# and XAML hot reload. +# - WinAppCLI: installs the Windows App SDK CLI (`winappcli`), which +# provides `wincreate` / `winbuild` helpers used in the Windows App SDK +# onboarding for project scaffolding and template-driven builds. +# - VSComponents: adds the workloads/components the WinUI C# onboarding doc +# calls out: .NET Desktop Development (ManagedDesktop), Universal Windows +# Platform Development (Universal), and the Windows App SDK C# component +# group (WindowsAppSDK.Cs). These pull in MSBuild targets, the Windows SDK +# ref pack, and the WinUI 3 project templates. +# +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: Microsoft.Windows.Developer.Module + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": if (-not (Get-Module -ListAvailable -Name Microsoft.Windows.Developer)) { Install-Module -Name Microsoft.Windows.Developer -Confirm:$False -Force -AllowPrerelease -AllowClobber } + treatAsArray: true + metadata: + description: Ensure Microsoft.Windows.Developer module is installed + + - type: Microsoft.Windows.Developer/OsVersion + name: OsVersion + dependsOn: + - Microsoft.Windows.Developer.Module + properties: + MinVersion: '10.0.17763' + metadata: + description: Verify min OS version (Windows 10 1809+) + allowPrerelease: true + + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: Microsoft.Windows.Settings.Module + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": if (-not (Get-Module -ListAvailable -Name Microsoft.Windows.Settings)) { Install-Module -Name Microsoft.Windows.Settings -Confirm:$False -Force -AllowPrerelease -AllowClobber } + treatAsArray: true + metadata: + description: Ensure Microsoft.Windows.Settings module is installed + + - type: Microsoft.Windows.Settings/WindowsSettings + name: DeveloperMode + dependsOn: + - Microsoft.Windows.Settings.Module + properties: + DeveloperMode: true + metadata: + description: Enable Developer Mode + allowPrerelease: true + winget: + securityContext: elevated + + - type: Microsoft.WinGet/Package + name: VisualStudio + properties: + id: Microsoft.VisualStudio.Community + source: winget + acceptAgreements: true + metadata: + description: Install Visual Studio 2026 Community + winget: + securityContext: elevated + + - type: Microsoft.WinGet/Package + name: WinAppCLI + properties: + id: Microsoft.WinAppCLI + source: winget + acceptAgreements: true + metadata: + description: Install Windows App SDK CLI (winappcli) + winget: + securityContext: elevated + + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: Microsoft.VisualStudio.DSC.Module + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": if (-not (Get-Module -ListAvailable -Name Microsoft.VisualStudio.DSC)) { Install-Module -Name Microsoft.VisualStudio.DSC -Confirm:$False -Force -AllowPrerelease -AllowClobber } + treatAsArray: true + metadata: + description: Ensure Microsoft.VisualStudio.DSC module is installed + + - type: Microsoft.VisualStudio.DSC/VSComponents + name: VSWorkloads + dependsOn: + - VisualStudio + - Microsoft.VisualStudio.DSC.Module + properties: + productId: Microsoft.VisualStudio.Product.Community + channelId: VisualStudio.18.Release + components: + - Microsoft.VisualStudio.Workload.ManagedDesktop + - Microsoft.VisualStudio.Workload.Universal + - Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs + metadata: + description: Install required VS workloads (ManagedDesktop, Universal, WindowsAppSDK.Cs) + allowPrerelease: true + winget: + securityContext: elevated diff --git a/WindowsDevScripts/scripts/windows/winui/install.ps1 b/WindowsDevScripts/scripts/windows/winui/install.ps1 new file mode 100644 index 00000000..59fbe856 --- /dev/null +++ b/WindowsDevScripts/scripts/windows/winui/install.ps1 @@ -0,0 +1,33 @@ +<# +.SYNOPSIS + Apply the WinUI 3 winget DSC configuration on Windows. + +.DESCRIPTION + This script is a thin CI/dev shim. The core artifact for the WinUI 3 flow + is `configuration.winget` in this directory — a dscv3 winget DSC config + that mirrors the canonical Microsoft Learn onboarding + (https://learn.microsoft.com/windows/apps/get-started/start-here): + * asserts minimum OS version, + * enables Developer Mode, + * installs Visual Studio 2026 Community, and + * adds the .NET Desktop, UWP, and Windows App SDK C# workloads/components. + + The shim exists only to: + * apply the DSC config with retry via `_common/apply-configuration.ps1` + (which passes `--accept-configuration-agreements` and + `--disable-interactivity`), + * rehydrate PATH in the current session so later CI steps see `dotnet`, + * verify `dotnet` resolves, and + * emit `INSTALL_OK: winui` for the test harness. +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +& (Join-Path $PSScriptRoot '..\_common\apply-configuration.ps1') ` + -Id 'winui' ` + -ConfigFile (Join-Path $PSScriptRoot 'configuration.winget') ` + -RequireCommands @('dotnet') From 7773564c7fb4d3cf1175f6dbb6859d90cd63ff74 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 14:58:24 -0700 Subject: [PATCH 11/20] Added linux dir --- .../scripts/linux/php/install.sh | 81 +++++++++++++ .../scripts/linux/python/install.sh | 108 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 WindowsDevScripts/scripts/linux/php/install.sh create mode 100644 WindowsDevScripts/scripts/linux/python/install.sh diff --git a/WindowsDevScripts/scripts/linux/php/install.sh b/WindowsDevScripts/scripts/linux/php/install.sh new file mode 100644 index 00000000..1b0574fc --- /dev/null +++ b/WindowsDevScripts/scripts/linux/php/install.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# ============================================================================= +# PHP install script for Debian/Ubuntu (incl. WSL). +# +# Mirrors the Windows PHP flow: this is the "install" half — CI then drives +# tests/_harness via manifest.yml to prove the install actually works. +# +# Responsibilities: +# * Install the php CLI via apt (idempotent — no-op if already present). +# * Retry apt-get on transient network failures (hosted runners are flaky). +# * Verify `php` resolves on PATH. +# * Emit `INSTALL_OK: php` as the final line, which CI asserts on. +# ============================================================================= + +set -euo pipefail + +ID="php" + +log() { printf '[%s] %s\n' "$ID" "$*"; } + +# ---- retry helper ----------------------------------------------------------- +# Exponential backoff (5s, 10s, 20s, 40s) — same spirit as _common/invoke-retry.ps1. +retry() { + local -i n=0 max=5 delay=5 + while true; do + if "$@"; then + return 0 + fi + n=$((n + 1)) + if (( n >= max )); then + log "command failed after ${n} attempts: $*" + return 1 + fi + log "attempt ${n} failed; retrying in ${delay}s: $*" + sleep "${delay}" + delay=$((delay * 2)) + done +} + +# ---- sudo selection --------------------------------------------------------- +# Works whether we're invoked as root (container) or a sudoer (CI / WSL default). +SUDO=() +if [[ "$(id -u)" -ne 0 ]]; then + if command -v sudo >/dev/null 2>&1; then + SUDO=(sudo -E) + else + log "ERROR: not running as root and 'sudo' is not available" + exit 1 + fi +fi + +# ---- sanity: this script targets Debian/Ubuntu ------------------------------ +if ! command -v apt-get >/dev/null 2>&1; then + log "ERROR: apt-get not found; this flow currently supports Debian/Ubuntu (incl. WSL)." + exit 1 +fi + +# ---- fast path: already installed ------------------------------------------ +if command -v php >/dev/null 2>&1; then + log "php already installed: $(php --version | head -n1)" + echo "INSTALL_OK: ${ID}" + exit 0 +fi + +# ---- install --------------------------------------------------------------- +export DEBIAN_FRONTEND=noninteractive + +log "apt-get update" +retry "${SUDO[@]}" apt-get update -y + +log "apt-get install php-cli" +retry "${SUDO[@]}" apt-get install -y --no-install-recommends php-cli + +# ---- verify ---------------------------------------------------------------- +if ! command -v php >/dev/null 2>&1; then + log "ERROR: php not found on PATH after install" + exit 1 +fi + +log "$(php --version | head -n1)" +echo "INSTALL_OK: ${ID}" diff --git a/WindowsDevScripts/scripts/linux/python/install.sh b/WindowsDevScripts/scripts/linux/python/install.sh new file mode 100644 index 00000000..c019c1b6 --- /dev/null +++ b/WindowsDevScripts/scripts/linux/python/install.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# ============================================================================= +# Python install script for Debian/Ubuntu (incl. WSL). +# +# Mirrors the Windows Python flow: this is the "install" half — CI then drives +# tests/_harness via manifest.yml to prove the install actually works. +# +# Responsibilities: +# * Install CPython 3 + pip + the `python-is-python3` alias via apt +# (idempotent — apt no-ops when packages are already present). The alias +# is what lets the manifest's `python tests/python/hello.py` run command +# be identical across Windows and Linux. +# * Install uv (Astral's Python package/project manager) via the official +# standalone installer — uv is not packaged in Debian/Ubuntu apt repos +# yet, and `astral.sh/uv/install.sh` is the Astral-documented method. +# * Retry network operations on transient failure (hosted runners are flaky). +# * Ensure $HOME/.local/bin (uv's install dir) is on PATH for subsequent +# CI steps via $GITHUB_PATH. +# * Verify `python`, `pip3`, and `uv` resolve on PATH. +# * Emit `INSTALL_OK: python` as the final line, which CI asserts on. +# ============================================================================= + +set -euo pipefail + +ID="python" + +log() { printf '[%s] %s\n' "$ID" "$*"; } + +# ---- retry helper ----------------------------------------------------------- +# Exponential backoff (5s, 10s, 20s, 40s) — same spirit as _common/invoke-retry.ps1. +retry() { + local -i n=0 max=5 delay=5 + while true; do + if "$@"; then + return 0 + fi + n=$((n + 1)) + if (( n >= max )); then + log "command failed after ${n} attempts: $*" + return 1 + fi + log "attempt ${n} failed; retrying in ${delay}s: $*" + sleep "${delay}" + delay=$((delay * 2)) + done +} + +# ---- sudo selection --------------------------------------------------------- +# Works whether we're invoked as root (container) or a sudoer (CI / WSL default). +SUDO=() +if [[ "$(id -u)" -ne 0 ]]; then + if command -v sudo >/dev/null 2>&1; then + SUDO=(sudo -E) + else + log "ERROR: not running as root and 'sudo' is not available" + exit 1 + fi +fi + +# ---- sanity: this script targets Debian/Ubuntu ------------------------------ +if ! command -v apt-get >/dev/null 2>&1; then + log "ERROR: apt-get not found; this flow currently supports Debian/Ubuntu (incl. WSL)." + exit 1 +fi + +# ---- install python + pip + python-is-python3 alias ------------------------- +export DEBIAN_FRONTEND=noninteractive + +log "apt-get update" +retry "${SUDO[@]}" apt-get update -y + +log "apt-get install python3 python3-pip python-is-python3" +retry "${SUDO[@]}" apt-get install -y --no-install-recommends \ + python3 python3-pip python-is-python3 + +# ---- install uv ------------------------------------------------------------- +# Installer default target is $HOME/.local/bin/uv. Setting UV_INSTALL_DIR +# explicitly documents intent and makes the PATH addition below unambiguous. +UV_INSTALL_DIR="${UV_INSTALL_DIR:-$HOME/.local/bin}" +export UV_INSTALL_DIR + +if command -v uv >/dev/null 2>&1; then + log "uv already installed: $(uv --version)" +else + log "installing uv via astral.sh/uv/install.sh into ${UV_INSTALL_DIR}" + retry bash -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' +fi + +# Make uv visible in THIS process for the verification step below. +export PATH="${UV_INSTALL_DIR}:${PATH}" + +# Propagate to subsequent GitHub Actions steps, if running in CI. +if [[ -n "${GITHUB_PATH:-}" ]]; then + echo "${UV_INSTALL_DIR}" >> "${GITHUB_PATH}" +fi + +# ---- verify ---------------------------------------------------------------- +for cmd in python pip3 uv; do + if ! command -v "$cmd" >/dev/null 2>&1; then + log "ERROR: $cmd not found on PATH after install" + exit 1 + fi +done + +log "$(python --version 2>&1 | head -n1)" +log "$(pip3 --version | head -n1)" +log "$(uv --version)" +echo "INSTALL_OK: ${ID}" From 368ae0380c5974dc88fecae7ff355d17fe0b3711 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 22:12:50 +0000 Subject: [PATCH 12/20] Fix check-spelling: add 31 unrecognized words to expect files and bash pattern Agent-Logs-Url: https://github.com/microsoft/winget-dsc/sessions/c2f14708-3e4a-4181-8634-3ecc78c13992 Co-authored-by: AmelBawa-msft <104940545+AmelBawa-msft@users.noreply.github.com> --- .github/actions/spelling/expect/generic_terms.txt | 9 +++++++++ .github/actions/spelling/expect/software.txt | 14 ++++++++++++++ .github/actions/spelling/expect/windows_terms.txt | 8 ++++++++ .github/actions/spelling/patterns.txt | 3 +++ 4 files changed, 34 insertions(+) diff --git a/.github/actions/spelling/expect/generic_terms.txt b/.github/actions/spelling/expect/generic_terms.txt index 9cc94fa6..1269ebec 100644 --- a/.github/actions/spelling/expect/generic_terms.txt +++ b/.github/actions/spelling/expect/generic_terms.txt @@ -21,3 +21,12 @@ usr versioning VGpu vse +debian +euo +linux +pipefail +sudoer +tarball +utf +vendored +versioned diff --git a/.github/actions/spelling/expect/software.txt b/.github/actions/spelling/expect/software.txt index 6eac7d03..b2e2164d 100644 --- a/.github/actions/spelling/expect/software.txt +++ b/.github/actions/spelling/expect/software.txt @@ -18,3 +18,17 @@ tokio lldb Rustlang vadimcn +CPython +gofmt +gradlew +javac +Jdk +mojibake +mvnw +rustc +tsc +uvx +winappcli +winbuild +wincreate +winforms diff --git a/.github/actions/spelling/expect/windows_terms.txt b/.github/actions/spelling/expect/windows_terms.txt index 94e05c0e..55efcfc6 100644 --- a/.github/actions/spelling/expect/windows_terms.txt +++ b/.github/actions/spelling/expect/windows_terms.txt @@ -19,3 +19,11 @@ cursorindicator packagetype BSODs Audiosrv +ADMX +chcp +conhost +MDM +PATHEXT +Redist +sideloaded +UWP diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index 07fb2363..adabb209 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -74,3 +74,6 @@ name\:\s+.+$ # Store ID 9PNRBTZXMB4Z + +# set arguments +\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* From 1e528ee3e6ad35f6378d806c0aafa692a9aa6189 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 5 May 2026 15:35:29 -0700 Subject: [PATCH 13/20] Remove files --- .../scripts/linux/php/install.sh | 81 ------------- .../scripts/linux/python/install.sh | 108 ------------------ 2 files changed, 189 deletions(-) delete mode 100644 WindowsDevScripts/scripts/linux/php/install.sh delete mode 100644 WindowsDevScripts/scripts/linux/python/install.sh diff --git a/WindowsDevScripts/scripts/linux/php/install.sh b/WindowsDevScripts/scripts/linux/php/install.sh deleted file mode 100644 index 1b0574fc..00000000 --- a/WindowsDevScripts/scripts/linux/php/install.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================= -# PHP install script for Debian/Ubuntu (incl. WSL). -# -# Mirrors the Windows PHP flow: this is the "install" half — CI then drives -# tests/_harness via manifest.yml to prove the install actually works. -# -# Responsibilities: -# * Install the php CLI via apt (idempotent — no-op if already present). -# * Retry apt-get on transient network failures (hosted runners are flaky). -# * Verify `php` resolves on PATH. -# * Emit `INSTALL_OK: php` as the final line, which CI asserts on. -# ============================================================================= - -set -euo pipefail - -ID="php" - -log() { printf '[%s] %s\n' "$ID" "$*"; } - -# ---- retry helper ----------------------------------------------------------- -# Exponential backoff (5s, 10s, 20s, 40s) — same spirit as _common/invoke-retry.ps1. -retry() { - local -i n=0 max=5 delay=5 - while true; do - if "$@"; then - return 0 - fi - n=$((n + 1)) - if (( n >= max )); then - log "command failed after ${n} attempts: $*" - return 1 - fi - log "attempt ${n} failed; retrying in ${delay}s: $*" - sleep "${delay}" - delay=$((delay * 2)) - done -} - -# ---- sudo selection --------------------------------------------------------- -# Works whether we're invoked as root (container) or a sudoer (CI / WSL default). -SUDO=() -if [[ "$(id -u)" -ne 0 ]]; then - if command -v sudo >/dev/null 2>&1; then - SUDO=(sudo -E) - else - log "ERROR: not running as root and 'sudo' is not available" - exit 1 - fi -fi - -# ---- sanity: this script targets Debian/Ubuntu ------------------------------ -if ! command -v apt-get >/dev/null 2>&1; then - log "ERROR: apt-get not found; this flow currently supports Debian/Ubuntu (incl. WSL)." - exit 1 -fi - -# ---- fast path: already installed ------------------------------------------ -if command -v php >/dev/null 2>&1; then - log "php already installed: $(php --version | head -n1)" - echo "INSTALL_OK: ${ID}" - exit 0 -fi - -# ---- install --------------------------------------------------------------- -export DEBIAN_FRONTEND=noninteractive - -log "apt-get update" -retry "${SUDO[@]}" apt-get update -y - -log "apt-get install php-cli" -retry "${SUDO[@]}" apt-get install -y --no-install-recommends php-cli - -# ---- verify ---------------------------------------------------------------- -if ! command -v php >/dev/null 2>&1; then - log "ERROR: php not found on PATH after install" - exit 1 -fi - -log "$(php --version | head -n1)" -echo "INSTALL_OK: ${ID}" diff --git a/WindowsDevScripts/scripts/linux/python/install.sh b/WindowsDevScripts/scripts/linux/python/install.sh deleted file mode 100644 index c019c1b6..00000000 --- a/WindowsDevScripts/scripts/linux/python/install.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================= -# Python install script for Debian/Ubuntu (incl. WSL). -# -# Mirrors the Windows Python flow: this is the "install" half — CI then drives -# tests/_harness via manifest.yml to prove the install actually works. -# -# Responsibilities: -# * Install CPython 3 + pip + the `python-is-python3` alias via apt -# (idempotent — apt no-ops when packages are already present). The alias -# is what lets the manifest's `python tests/python/hello.py` run command -# be identical across Windows and Linux. -# * Install uv (Astral's Python package/project manager) via the official -# standalone installer — uv is not packaged in Debian/Ubuntu apt repos -# yet, and `astral.sh/uv/install.sh` is the Astral-documented method. -# * Retry network operations on transient failure (hosted runners are flaky). -# * Ensure $HOME/.local/bin (uv's install dir) is on PATH for subsequent -# CI steps via $GITHUB_PATH. -# * Verify `python`, `pip3`, and `uv` resolve on PATH. -# * Emit `INSTALL_OK: python` as the final line, which CI asserts on. -# ============================================================================= - -set -euo pipefail - -ID="python" - -log() { printf '[%s] %s\n' "$ID" "$*"; } - -# ---- retry helper ----------------------------------------------------------- -# Exponential backoff (5s, 10s, 20s, 40s) — same spirit as _common/invoke-retry.ps1. -retry() { - local -i n=0 max=5 delay=5 - while true; do - if "$@"; then - return 0 - fi - n=$((n + 1)) - if (( n >= max )); then - log "command failed after ${n} attempts: $*" - return 1 - fi - log "attempt ${n} failed; retrying in ${delay}s: $*" - sleep "${delay}" - delay=$((delay * 2)) - done -} - -# ---- sudo selection --------------------------------------------------------- -# Works whether we're invoked as root (container) or a sudoer (CI / WSL default). -SUDO=() -if [[ "$(id -u)" -ne 0 ]]; then - if command -v sudo >/dev/null 2>&1; then - SUDO=(sudo -E) - else - log "ERROR: not running as root and 'sudo' is not available" - exit 1 - fi -fi - -# ---- sanity: this script targets Debian/Ubuntu ------------------------------ -if ! command -v apt-get >/dev/null 2>&1; then - log "ERROR: apt-get not found; this flow currently supports Debian/Ubuntu (incl. WSL)." - exit 1 -fi - -# ---- install python + pip + python-is-python3 alias ------------------------- -export DEBIAN_FRONTEND=noninteractive - -log "apt-get update" -retry "${SUDO[@]}" apt-get update -y - -log "apt-get install python3 python3-pip python-is-python3" -retry "${SUDO[@]}" apt-get install -y --no-install-recommends \ - python3 python3-pip python-is-python3 - -# ---- install uv ------------------------------------------------------------- -# Installer default target is $HOME/.local/bin/uv. Setting UV_INSTALL_DIR -# explicitly documents intent and makes the PATH addition below unambiguous. -UV_INSTALL_DIR="${UV_INSTALL_DIR:-$HOME/.local/bin}" -export UV_INSTALL_DIR - -if command -v uv >/dev/null 2>&1; then - log "uv already installed: $(uv --version)" -else - log "installing uv via astral.sh/uv/install.sh into ${UV_INSTALL_DIR}" - retry bash -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' -fi - -# Make uv visible in THIS process for the verification step below. -export PATH="${UV_INSTALL_DIR}:${PATH}" - -# Propagate to subsequent GitHub Actions steps, if running in CI. -if [[ -n "${GITHUB_PATH:-}" ]]; then - echo "${UV_INSTALL_DIR}" >> "${GITHUB_PATH}" -fi - -# ---- verify ---------------------------------------------------------------- -for cmd in python pip3 uv; do - if ! command -v "$cmd" >/dev/null 2>&1; then - log "ERROR: $cmd not found on PATH after install" - exit 1 - fi -done - -log "$(python --version 2>&1 | head -n1)" -log "$(pip3 --version | head -n1)" -log "$(uv --version)" -echo "INSTALL_OK: ${ID}" From 3263b811062df77c7de81ae63dac518f8c88f961 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 22:38:36 +0000 Subject: [PATCH 14/20] Revert patterns.txt change from check-spelling fix Agent-Logs-Url: https://github.com/microsoft/winget-dsc/sessions/ad6f0681-58d4-43f7-8b4c-85aba387f285 Co-authored-by: AmelBawa-msft <104940545+AmelBawa-msft@users.noreply.github.com> --- .github/actions/spelling/patterns.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index adabb209..07fb2363 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -74,6 +74,3 @@ name\:\s+.+$ # Store ID 9PNRBTZXMB4Z - -# set arguments -\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* From 249e9af40c11d0f988fdb1c5e5dd3f4683398c3d Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 6 May 2026 14:00:27 -0700 Subject: [PATCH 15/20] WIP --- .../windows/dotnet/configuration.winget | 33 ------- .../scripts/windows/go/configuration.winget | 34 ------- .../scripts/windows/java/configuration.winget | 38 -------- .../scripts/windows/php/configuration.winget | 20 +--- .../windows/python/configuration.winget | 41 -------- .../scripts/windows/rust/configuration.winget | 42 --------- .../windows/typescript/configuration.winget | 93 ++++++------------- .../windows/winforms/configuration.winget | 27 +++--- .../windows/winui/configuration.winget | 49 ---------- 9 files changed, 41 insertions(+), 336 deletions(-) diff --git a/WindowsDevScripts/scripts/windows/dotnet/configuration.winget b/WindowsDevScripts/scripts/windows/dotnet/configuration.winget index cf02b533..2afd433a 100644 --- a/WindowsDevScripts/scripts/windows/dotnet/configuration.winget +++ b/WindowsDevScripts/scripts/windows/dotnet/configuration.winget @@ -1,42 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the .NET developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do .NET -# development. Apply it directly with: -# # winget configure --file scripts/windows/dotnet/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# `--accept-package-agreements` is intentionally NOT in the canonical -# invocation above: each `Microsoft.WinGet/Package` resource below sets -# `acceptAgreements: true`, which causes the dscv3 resource to internally -# pass both `--accept-source-agreements` and `--accept-package-agreements` -# to its own winget install call. See AGENTS.md section 4. -# -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Resources: -# - DotNetSdk: installs the .NET 10 SDK via winget. The SDK ships the -# cross-platform runtimes plus the `dotnet` CLI used to build -# and run the hello-world. The WinForms and WinUI flows use -# the same package id and pair it with workload-specific -# project settings (UseWindowsForms=true, the Windows App -# SDK targeting pack); this flow installs only the core SDK -# so it can serve as a foundation for cross-platform -# scenarios (e.g. web-api-csharp). -# -# Note on the package id: -# winget's community repo does NOT publish an unversioned `Microsoft.DotNet` -# meta package; `WinGetPackage` uses `--exact` resolution, so a versioned id -# is required. `Microsoft.DotNet.SDK.10` tracks the .NET 10 LTS line — bump -# this when the next LTS ships and .NET 10 reaches EOL. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: diff --git a/WindowsDevScripts/scripts/windows/go/configuration.winget b/WindowsDevScripts/scripts/windows/go/configuration.winget index 56c44270..3b7d5131 100644 --- a/WindowsDevScripts/scripts/windows/go/configuration.winget +++ b/WindowsDevScripts/scripts/windows/go/configuration.winget @@ -1,43 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the Go developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do Go -# development. Apply it directly with: -# # winget configure --file scripts/windows/go/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# `--accept-package-agreements` is intentionally NOT in the canonical -# invocation above: the `Microsoft.WinGet/Package` resource below sets -# `acceptAgreements: true`, which causes the dscv3 resource to internally -# pass both `--accept-source-agreements` and `--accept-package-agreements` -# to its own winget install call. See AGENTS.md section 4. -# -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Resources: -# - Go: installs the Go toolchain via winget. Includes the `go` CLI -# (which fronts `go build`, `go run`, `go test`, `go fmt`, etc.) -# plus the standard library. The hello-world is exercised with -# `go run` so no separate build step is required. -# -# Note on the package id (deliberate exception to AGENTS.md section 6): -# AGENTS.md section 6 directs new flows to use minor-versioned ids -# (e.g. `Python.Python.3.13`). Go is the deliberate exception: -# winget's community repo publishes Go *only* as the unversioned -# meta-package `GoLang.Go`, which is a rolling pointer at the latest -# stable release (currently 1.26.x). There is no `GoLang.Go.1.26` -# equivalent — `winget show --id GoLang.Go.1.26 --exact` returns -# "no package found". Using `GoLang.Go` here is the only way to -# resolve under `--exact`. When upstream begins publishing -# minor-versioned ids, swap this for the pinned form. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: diff --git a/WindowsDevScripts/scripts/windows/java/configuration.winget b/WindowsDevScripts/scripts/windows/java/configuration.winget index 68de1c82..055d140b 100644 --- a/WindowsDevScripts/scripts/windows/java/configuration.winget +++ b/WindowsDevScripts/scripts/windows/java/configuration.winget @@ -1,47 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the Java developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do Java -# development. Apply it directly with: -# # winget configure --file scripts/windows/java/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# `--accept-package-agreements` is intentionally NOT in the canonical -# invocation above: the `Microsoft.WinGet/Package` resource below sets -# `acceptAgreements: true`, which causes the dscv3 resource to internally -# pass both `--accept-source-agreements` and `--accept-package-agreements` -# to its own winget install call. See AGENTS.md section 4. -# -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Resources: -# - OpenJdk: installs the Microsoft Build of OpenJDK 21 (LTS) via winget. -# Adds `java`, `javac`, `jar`, and friends to PATH. -# -# Note on the package id: -# `Microsoft.OpenJDK.21` is the current LTS line. Microsoft also publishes -# `Microsoft.OpenJDK.11`, `.17`, and `.25` — bump when a new LTS ships and -# 21 reaches Microsoft's documented end-of-support window. -# -# Note on Maven / Gradle (deliberately omitted): -# The original plan called for installing Maven alongside the JDK, but -# `Apache.Maven` is NOT published in the winget community repo -# (`winget search maven` returns only forks for unrelated projects). -# Rather than wrap an apache.org tarball-download in a `Script` resource, -# this flow installs only the JDK and relies on the modern Java -# convention: each project ships its own pinned build tool via the -# `mvnw` (Maven Wrapper) or `gradlew` (Gradle Wrapper) script in its -# root. The `web-api-java` scenario flow that builds on top of this one -# will use `mvnw` from a vendored start.spring.io output, so no -# system-wide Maven install is required. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: diff --git a/WindowsDevScripts/scripts/windows/php/configuration.winget b/WindowsDevScripts/scripts/windows/php/configuration.winget index fdd2d29b..46ff4f16 100644 --- a/WindowsDevScripts/scripts/windows/php/configuration.winget +++ b/WindowsDevScripts/scripts/windows/php/configuration.winget @@ -1,28 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the PHP developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do PHP -# development. Apply it directly with: -# # winget configure --file scripts/windows/php/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Note on the package id: -# winget's community repo does NOT publish an unversioned `PHP.PHP` meta -# package; `WinGetPackage` uses `--exact` resolution, so a versioned id is -# required. `PHP.PHP.8.5` is the current stable line — bump this when -# upstream ships a new supported minor and the old one reaches EOL. (We had -# briefly pinned 8.4 but its manifest's installer URL started returning 404; -# 8.5 matches the build pre-installed on `windows-latest` runners anyway.) -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: @@ -35,3 +16,4 @@ resources: properties: id: PHP.PHP.8.5 source: winget + acceptAgreements: true \ No newline at end of file diff --git a/WindowsDevScripts/scripts/windows/python/configuration.winget b/WindowsDevScripts/scripts/windows/python/configuration.winget index f03304c7..73bef0e6 100644 --- a/WindowsDevScripts/scripts/windows/python/configuration.winget +++ b/WindowsDevScripts/scripts/windows/python/configuration.winget @@ -1,41 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the Python developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do Python -# development. Apply it directly with: -# # winget configure --file scripts/windows/python/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# `--accept-package-agreements` is intentionally NOT in the canonical -# invocation above: each `Microsoft.WinGet/Package` resource below sets -# `acceptAgreements: true`, which causes the dscv3 resource to internally -# pass both `--accept-source-agreements` and `--accept-package-agreements` -# to its own winget install call. See AGENTS.md §4. -# -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Resources: -# - Python: installs CPython 3.13 via winget. The installer adds `python`, -# `python3`, and `pip` to the user PATH. -# - Uv: installs uv (Astral's fast Python package / project manager) via -# winget. Adds `uv` and `uvx` to the user PATH. Kept as a sibling -# resource — uv is standalone and does not require Python to be -# installed first, but developers of this flow will almost always -# want both, so they ship together. -# -# Note on the Python package id: -# winget's community repo does NOT publish an unversioned `Python.Python` -# meta package; `WinGetPackage` uses `--exact` resolution, so a versioned id -# is required. `Python.Python.3.13` is the current stable line — bump this -# when upstream ships a new supported minor and the old one reaches EOL. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: @@ -49,12 +17,3 @@ resources: id: Python.Python.3.13 source: winget acceptAgreements: true - - - name: Uv - type: Microsoft.WinGet/Package - metadata: - securityContext: elevated - properties: - id: astral-sh.uv - source: winget - acceptAgreements: true diff --git a/WindowsDevScripts/scripts/windows/rust/configuration.winget b/WindowsDevScripts/scripts/windows/rust/configuration.winget index 7e37a48c..5fb06a6c 100644 --- a/WindowsDevScripts/scripts/windows/rust/configuration.winget +++ b/WindowsDevScripts/scripts/windows/rust/configuration.winget @@ -1,51 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the Rust developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do Rust -# development. Apply it directly with: -# # winget configure --file scripts/windows/rust/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# `--accept-package-agreements` is intentionally NOT in the canonical -# invocation above: the `Microsoft.WinGet/Package` resource below sets -# `acceptAgreements: true`, which causes the dscv3 resource to internally -# pass both `--accept-source-agreements` and `--accept-package-agreements` -# to its own winget install call. See AGENTS.md section 4. -# -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Why two resources (Rustup + post-install command): -# `Rustlang.Rustup` is the canonical Rust install path — it ships -# `rustup-init.exe`, which places `rustup` on PATH but does not by -# itself guarantee the actual toolchain (rustc, cargo, ...) is -# installed. The follow-up `rustup default stable` step does the heavy -# lifting: on first run it downloads and installs the stable toolchain -# for the host triple (x86_64-pc-windows-msvc by default) and sets it -# as the active toolchain; on subsequent runs it is a no-op. This is -# the dscv3-native way to express that step — `RunCommandOnSet` is the -# same escape hatch the winui flow uses to install adapter modules. -# AGENTS.md section 3's hint about falling back to v0.2 + `Script` -# only applies to v2 class-based `PSDscResources/Script`; native -# dscv3 `Microsoft.DSC.Transitional/RunCommandOnSet` is preferred. -# -# Resources (in application order, enforced by dependsOn): -# - Rustup: installs `Rustlang.Rustup` via winget, -# which gives us the `rustup` toolchain -# manager on user PATH. -# - InstallStableToolchain: invokes `pwsh` to refresh PATH from the -# registry (winget updates the registry but -# not running processes) and then runs -# `rustup default stable`. Idempotent: on -# first run installs + defaults stable; -# on subsequent runs a no-op. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: diff --git a/WindowsDevScripts/scripts/windows/typescript/configuration.winget b/WindowsDevScripts/scripts/windows/typescript/configuration.winget index f6e6620c..fe604404 100644 --- a/WindowsDevScripts/scripts/windows/typescript/configuration.winget +++ b/WindowsDevScripts/scripts/windows/typescript/configuration.winget @@ -1,70 +1,35 @@ -# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 -# -# Core artifact for the TypeScript developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to do TypeScript -# development. Apply it directly with: +# yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # # winget configure --file scripts/windows/typescript/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Resources: -# - Node: installs Node.js LTS via winget. -# - InstallTypeScript: runs `npm install --global typescript` so that `tsc` -# ends up on PATH. DSC `Script` resources execute -# PowerShell, which is the idiomatic way to express -# "do something winget alone can't" inside a DSC config. -# -properties: - configurationVersion: 0.2.0 - resources: - - resource: Microsoft.WinGet.DSC/WinGetPackage - id: Node - directives: - description: Install Node.js LTS (provides node + npm) - allowPrerelease: false - settings: - id: OpenJS.NodeJS.LTS - source: winget +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Node + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + description: Install Node.js LTS (provides node + npm) + properties: + id: OpenJS.NodeJS.LTS + source: winget + acceptAgreements: true - - resource: PSDscResources/Script - id: InstallTypeScript - dependsOn: - - Node - directives: - description: Install the TypeScript compiler globally via npm - settings: - # DSC contract: - # TestScript -> $true means "already in desired state, skip SetScript" - # SetScript -> bring the system into desired state - # GetScript -> return a hashtable describing current state (unused here) - # - # We refresh PATH from the registry at the top of each script because - # the Node resource above updates machine PATH in the registry, but the - # PowerShell process evaluating this resource inherited its PATH at - # launch and will not otherwise see the freshly-installed npm.exe. - GetScript: | - return @{ Result = '' } - TestScript: | - $machine = [Environment]::GetEnvironmentVariable('Path','Machine') - $user = [Environment]::GetEnvironmentVariable('Path','User') - $env:Path = (($machine, $user) | Where-Object { $_ }) -join ';' - return [bool](Get-Command tsc -ErrorAction SilentlyContinue) - SetScript: | - $machine = [Environment]::GetEnvironmentVariable('Path','Machine') - $user = [Environment]::GetEnvironmentVariable('Path','User') - $env:Path = (($machine, $user) | Where-Object { $_ }) -join ';' - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - throw 'npm not found on PATH after Node install; cannot install typescript.' - } - & npm install --global --no-fund --no-audit typescript - if ($LASTEXITCODE -ne 0) { - throw "npm install --global typescript failed with exit code $LASTEXITCODE" - } + - name: InstallTypeScript + type: Microsoft.DSC.Transitional/RunCommandOnSet + dependsOn: + - Node + metadata: + description: Install the TypeScript compiler globally via npm + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": $machine = [Environment]::GetEnvironmentVariable('Path','Machine'); $user = [Environment]::GetEnvironmentVariable('Path','User'); $env:Path = (($machine, $user) | Where-Object { $_ }) -join ';'; if (Get-Command tsc -ErrorAction SilentlyContinue) { exit 0 }; if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { throw 'npm not found on PATH after Node install; cannot install typescript.' }; & npm install --global --no-fund --no-audit typescript; if ($LASTEXITCODE -ne 0) { throw "npm install --global typescript failed with exit code $LASTEXITCODE" } + treatAsArray: true diff --git a/WindowsDevScripts/scripts/windows/winforms/configuration.winget b/WindowsDevScripts/scripts/windows/winforms/configuration.winget index cd9436f9..20534cd6 100644 --- a/WindowsDevScripts/scripts/windows/winforms/configuration.winget +++ b/WindowsDevScripts/scripts/windows/winforms/configuration.winget @@ -1,25 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the WinForms developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to build and run -# a minimal Windows Forms (.NET) application. Apply it directly with: -# # winget configure --file scripts/windows/winforms/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above, rehydrates PATH in the caller's session, and emits an -# `INSTALL_OK: ` sentinel for the test harness. All install logic lives -# here. -# -# Resources: -# - DotNetSdk: installs the .NET 10 SDK via winget. The SDK ships the Windows -# Desktop targeting pack needed by `UseWindowsForms=true` -# projects, so no separate workload install is required. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: @@ -32,3 +16,14 @@ resources: properties: id: Microsoft.DotNet.SDK.10 source: winget + acceptAgreements: true + + - name: VisualStudioCommunity + type: Microsoft.WinGet/Package + metadata: + securityContext: elevated + description: Install Visual Studio Community Edition for WinForms development + properties: + id: Microsoft.VisualStudio.Community + source: winget + acceptAgreements: true diff --git a/WindowsDevScripts/scripts/windows/winui/configuration.winget b/WindowsDevScripts/scripts/windows/winui/configuration.winget index f6112960..75d8e245 100644 --- a/WindowsDevScripts/scripts/windows/winui/configuration.winget +++ b/WindowsDevScripts/scripts/windows/winui/configuration.winget @@ -1,58 +1,9 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json # -# Core artifact for the WinUI 3 (Windows App SDK) developer flow on Windows. -# -# This is a winget DSC configuration file: a declarative, idempotent, and -# non-interactive description of the machine state required to build and run -# modern WinUI 3 / Windows App SDK applications. Apply it directly with: -# # winget configure --file scripts/windows/winui/configuration.winget ` # --accept-configuration-agreements ` # --disable-interactivity # -# The `install.ps1` alongside this file is a thin CI/dev shim that invokes the -# command above (through `_common/apply-configuration.ps1`), rehydrates PATH -# in the caller's session, and emits an `INSTALL_OK: ` sentinel for the -# test harness. All install logic lives here. -# -# Onboarding source: -# https://learn.microsoft.com/windows/apps/get-started/start-here -# -# Why dscv3 with v2 class-based resources: -# The WinUI onboarding needs three PowerShell DSC v2 class-based modules -# (`Microsoft.Windows.Developer`, `Microsoft.Windows.Settings`, -# `Microsoft.VisualStudio.DSC`) that are not exposed as native dscv3 -# resources. The dscv3 processor hosts v2 class-based resources through its -# PowerShell adapter, so the preferred v3 document shape still applies — we -# just need each adapter module installed before its consumer runs. We do -# that with `Microsoft.DSC.Transitional/RunCommandOnSet` + `dependsOn`. -# Package install uses native `Microsoft.WinGet/Package` (see AGENTS.md §3). -# -# Package-agreement acceptance: -# `Microsoft.WinGet/Package` sets `acceptAgreements: true` per-resource -# (AGENTS.md §4). `--accept-configuration-agreements` (passed by the shim) -# consents to the configuration document itself. -# -# Resources (in application order, enforced by dependsOn): -# - Adapter modules: Install-Module on first Set for each v2 class-based -# module so its resource type is resolvable by the adapter. -# - OsVersion assertion: fail fast on pre-Win10 1809 (minimum supported by -# the Windows App SDK). -# - DeveloperMode: required for unpackaged/sideloaded Windows App SDK apps -# and for a normal WinUI inner-loop (F5 deploy). -# - Visual Studio: installs VS 2026 Community (channelId VisualStudio.18.Release -# → productId Microsoft.VisualStudio.Product.Community); the IDE is what -# a human onboarding to WinUI is expected to use for design, debugging, -# and XAML hot reload. -# - WinAppCLI: installs the Windows App SDK CLI (`winappcli`), which -# provides `wincreate` / `winbuild` helpers used in the Windows App SDK -# onboarding for project scaffolding and template-driven builds. -# - VSComponents: adds the workloads/components the WinUI C# onboarding doc -# calls out: .NET Desktop Development (ManagedDesktop), Universal Windows -# Platform Development (Universal), and the Windows App SDK C# component -# group (WindowsAppSDK.Cs). These pull in MSBuild targets, the Windows SDK -# ref pack, and the WinUI 3 project templates. -# $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json metadata: winget: From 9a78fc89d2805e055aba74bd333ac03f24116f82 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 6 May 2026 15:08:47 -0700 Subject: [PATCH 16/20] WIP --- .../windows/winui/configuration.winget | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/WindowsDevScripts/scripts/windows/winui/configuration.winget b/WindowsDevScripts/scripts/windows/winui/configuration.winget index 75d8e245..431893f9 100644 --- a/WindowsDevScripts/scripts/windows/winui/configuration.winget +++ b/WindowsDevScripts/scripts/windows/winui/configuration.winget @@ -80,32 +80,18 @@ resources: securityContext: elevated - type: Microsoft.DSC.Transitional/RunCommandOnSet - name: Microsoft.VisualStudio.DSC.Module + name: VSWorkloads + dependsOn: + - VisualStudio properties: executable: pwsh arguments: "0": -NoProfile "1": -NoLogo "2": -Command - "3": if (-not (Get-Module -ListAvailable -Name Microsoft.VisualStudio.DSC)) { Install-Module -Name Microsoft.VisualStudio.DSC -Confirm:$False -Force -AllowPrerelease -AllowClobber } + "3": $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'; if (-not (Test-Path $vswhere)) { throw 'vswhere.exe not found.' }; $setup = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\setup.exe'; if (-not (Test-Path $setup)) { throw 'Visual Studio setup.exe not found.' }; $installPath = & $vswhere -latest -products * -property installationPath; if (-not $installPath) { throw 'Visual Studio installation path could not be determined.' }; & $setup modify --installPath $installPath --channelId VisualStudio.18.Release --productId Microsoft.VisualStudio.Product.Community --add Microsoft.VisualStudio.Workload.ManagedDesktop --add Microsoft.VisualStudio.Workload.Universal --add Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs --quiet --norestart --wait; if ($LASTEXITCODE -ne 0) { throw "Visual Studio workload installation failed with exit code $LASTEXITCODE." } treatAsArray: true metadata: - description: Ensure Microsoft.VisualStudio.DSC module is installed - - - type: Microsoft.VisualStudio.DSC/VSComponents - name: VSWorkloads - dependsOn: - - VisualStudio - - Microsoft.VisualStudio.DSC.Module - properties: - productId: Microsoft.VisualStudio.Product.Community - channelId: VisualStudio.18.Release - components: - - Microsoft.VisualStudio.Workload.ManagedDesktop - - Microsoft.VisualStudio.Workload.Universal - - Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs - metadata: - description: Install required VS workloads (ManagedDesktop, Universal, WindowsAppSDK.Cs) - allowPrerelease: true + description: Install required VS workloads with Visual Studio Installer winget: securityContext: elevated From 33265d019943523e215f02f3d47c321b93097e77 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 6 May 2026 15:39:08 -0700 Subject: [PATCH 17/20] Use powershell instead of pwsh --- WindowsDevScripts/scripts/windows/rust/configuration.winget | 2 +- .../scripts/windows/typescript/configuration.winget | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WindowsDevScripts/scripts/windows/rust/configuration.winget b/WindowsDevScripts/scripts/windows/rust/configuration.winget index 5fb06a6c..24fde793 100644 --- a/WindowsDevScripts/scripts/windows/rust/configuration.winget +++ b/WindowsDevScripts/scripts/windows/rust/configuration.winget @@ -25,7 +25,7 @@ resources: dependsOn: - Rustup properties: - executable: pwsh + executable: powershell arguments: "0": -NoProfile "1": -NoLogo diff --git a/WindowsDevScripts/scripts/windows/typescript/configuration.winget b/WindowsDevScripts/scripts/windows/typescript/configuration.winget index fe604404..38e1dc7f 100644 --- a/WindowsDevScripts/scripts/windows/typescript/configuration.winget +++ b/WindowsDevScripts/scripts/windows/typescript/configuration.winget @@ -26,7 +26,7 @@ resources: metadata: description: Install the TypeScript compiler globally via npm properties: - executable: pwsh + executable: powershell arguments: "0": -NoProfile "1": -NoLogo From 3677fb4ef2f16c8c9996180f72104e416aac22df Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 6 May 2026 16:08:38 -0700 Subject: [PATCH 18/20] Adde pwsh dep --- .../scripts/windows/winui/configuration.winget | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/WindowsDevScripts/scripts/windows/winui/configuration.winget b/WindowsDevScripts/scripts/windows/winui/configuration.winget index 431893f9..2d315512 100644 --- a/WindowsDevScripts/scripts/windows/winui/configuration.winget +++ b/WindowsDevScripts/scripts/windows/winui/configuration.winget @@ -9,8 +9,21 @@ metadata: winget: processor: dscv3 resources: + - type: Microsoft.WinGet/Package + name: PowerShell7 + properties: + id: Microsoft.PowerShell + source: winget + acceptAgreements: true + metadata: + description: Install PowerShell 7 + winget: + securityContext: elevated + - type: Microsoft.DSC.Transitional/RunCommandOnSet name: Microsoft.Windows.Developer.Module + dependsOn: + - PowerShell7 properties: executable: pwsh arguments: @@ -34,6 +47,8 @@ resources: - type: Microsoft.DSC.Transitional/RunCommandOnSet name: Microsoft.Windows.Settings.Module + dependsOn: + - PowerShell7 properties: executable: pwsh arguments: @@ -83,6 +98,7 @@ resources: name: VSWorkloads dependsOn: - VisualStudio + - PowerShell7 properties: executable: pwsh arguments: From 2ca4a52590c2525db3d88c22f7620f6746dd68d2 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 6 May 2026 17:09:52 -0700 Subject: [PATCH 19/20] Add more stesp to winforms --- .../windows/winforms/configuration.winget | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/WindowsDevScripts/scripts/windows/winforms/configuration.winget b/WindowsDevScripts/scripts/windows/winforms/configuration.winget index 20534cd6..db518193 100644 --- a/WindowsDevScripts/scripts/windows/winforms/configuration.winget +++ b/WindowsDevScripts/scripts/windows/winforms/configuration.winget @@ -9,10 +9,75 @@ metadata: winget: processor: dscv3 resources: + - type: Microsoft.WinGet/Package + name: PowerShell7 + properties: + id: Microsoft.PowerShell + source: winget + acceptAgreements: true + metadata: + description: Install PowerShell 7 + winget: + securityContext: elevated + + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: Microsoft.Windows.Developer.Module + dependsOn: + - PowerShell7 + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": if (-not (Get-Module -ListAvailable -Name Microsoft.Windows.Developer)) { Install-Module -Name Microsoft.Windows.Developer -Confirm:$False -Force -AllowPrerelease -AllowClobber } + treatAsArray: true + metadata: + description: Ensure Microsoft.Windows.Developer module is installed + + - type: Microsoft.Windows.Developer/OsVersion + name: OsVersion + dependsOn: + - Microsoft.Windows.Developer.Module + properties: + MinVersion: '10.0.17763' + metadata: + description: Verify min OS version (Windows 10 1809+) + allowPrerelease: true + + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: Microsoft.Windows.Settings.Module + dependsOn: + - PowerShell7 + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": if (-not (Get-Module -ListAvailable -Name Microsoft.Windows.Settings)) { Install-Module -Name Microsoft.Windows.Settings -Confirm:$False -Force -AllowPrerelease -AllowClobber } + treatAsArray: true + metadata: + description: Ensure Microsoft.Windows.Settings module is installed + + - type: Microsoft.Windows.Settings/WindowsSettings + name: DeveloperMode + dependsOn: + - Microsoft.Windows.Settings.Module + properties: + DeveloperMode: true + metadata: + description: Enable Developer Mode + allowPrerelease: true + winget: + securityContext: elevated + - name: DotNetSdk type: Microsoft.WinGet/Package metadata: - securityContext: elevated + description: Install .NET SDK 10 + winget: + securityContext: elevated properties: id: Microsoft.DotNet.SDK.10 source: winget @@ -21,9 +86,28 @@ resources: - name: VisualStudioCommunity type: Microsoft.WinGet/Package metadata: - securityContext: elevated description: Install Visual Studio Community Edition for WinForms development + winget: + securityContext: elevated properties: id: Microsoft.VisualStudio.Community source: winget acceptAgreements: true + + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: VSWorkloads + dependsOn: + - VisualStudioCommunity + - PowerShell7 + properties: + executable: pwsh + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'; if (-not (Test-Path $vswhere)) { throw 'vswhere.exe not found.' }; $setup = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\setup.exe'; if (-not (Test-Path $setup)) { throw 'Visual Studio setup.exe not found.' }; $installPath = & $vswhere -latest -products * -property installationPath; if (-not $installPath) { throw 'Visual Studio installation path could not be determined.' }; & $setup modify --installPath $installPath --channelId VisualStudio.18.Release --productId Microsoft.VisualStudio.Product.Community --add Microsoft.VisualStudio.Workload.ManagedDesktop --quiet --norestart --wait; if ($LASTEXITCODE -ne 0) { throw "Visual Studio workload installation failed with exit code $LASTEXITCODE." } + treatAsArray: true + metadata: + description: Install required VS workloads for WinForms development + winget: + securityContext: elevated From cc239c548f107140cf1b10403da96ecf26e7d13e Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 6 May 2026 18:27:01 -0700 Subject: [PATCH 20/20] Add tests --- WindowsDevScripts/tests/_harness/run-flow.ps1 | 109 +++++++++ .../tests/_harness/run-server.ps1 | 211 ++++++++++++++++++ WindowsDevScripts/tests/dotnet/Program.cs | 7 + WindowsDevScripts/tests/dotnet/expected.txt | 1 + WindowsDevScripts/tests/dotnet/hello.csproj | 22 ++ WindowsDevScripts/tests/go/expected.txt | 1 + WindowsDevScripts/tests/go/hello.go | 14 ++ WindowsDevScripts/tests/java/Hello.java | 13 ++ WindowsDevScripts/tests/java/expected.txt | 1 + WindowsDevScripts/tests/php/expected.txt | 1 + WindowsDevScripts/tests/php/hello.php | 2 + WindowsDevScripts/tests/python/expected.txt | 1 + WindowsDevScripts/tests/python/hello.py | 1 + WindowsDevScripts/tests/rust/Cargo.toml | 15 ++ WindowsDevScripts/tests/rust/expected.txt | 1 + WindowsDevScripts/tests/rust/src/main.rs | 9 + .../tests/typescript/expected.txt | 1 + WindowsDevScripts/tests/typescript/hello.ts | 2 + WindowsDevScripts/tests/winforms/Program.cs | 14 ++ WindowsDevScripts/tests/winforms/expected.txt | 1 + WindowsDevScripts/tests/winforms/hello.csproj | 23 ++ WindowsDevScripts/tests/winui/Program.cs | 12 + WindowsDevScripts/tests/winui/expected.txt | 1 + WindowsDevScripts/tests/winui/hello.csproj | 41 ++++ 24 files changed, 504 insertions(+) create mode 100644 WindowsDevScripts/tests/_harness/run-flow.ps1 create mode 100644 WindowsDevScripts/tests/_harness/run-server.ps1 create mode 100644 WindowsDevScripts/tests/dotnet/Program.cs create mode 100644 WindowsDevScripts/tests/dotnet/expected.txt create mode 100644 WindowsDevScripts/tests/dotnet/hello.csproj create mode 100644 WindowsDevScripts/tests/go/expected.txt create mode 100644 WindowsDevScripts/tests/go/hello.go create mode 100644 WindowsDevScripts/tests/java/Hello.java create mode 100644 WindowsDevScripts/tests/java/expected.txt create mode 100644 WindowsDevScripts/tests/php/expected.txt create mode 100644 WindowsDevScripts/tests/php/hello.php create mode 100644 WindowsDevScripts/tests/python/expected.txt create mode 100644 WindowsDevScripts/tests/python/hello.py create mode 100644 WindowsDevScripts/tests/rust/Cargo.toml create mode 100644 WindowsDevScripts/tests/rust/expected.txt create mode 100644 WindowsDevScripts/tests/rust/src/main.rs create mode 100644 WindowsDevScripts/tests/typescript/expected.txt create mode 100644 WindowsDevScripts/tests/typescript/hello.ts create mode 100644 WindowsDevScripts/tests/winforms/Program.cs create mode 100644 WindowsDevScripts/tests/winforms/expected.txt create mode 100644 WindowsDevScripts/tests/winforms/hello.csproj create mode 100644 WindowsDevScripts/tests/winui/Program.cs create mode 100644 WindowsDevScripts/tests/winui/expected.txt create mode 100644 WindowsDevScripts/tests/winui/hello.csproj diff --git a/WindowsDevScripts/tests/_harness/run-flow.ps1 b/WindowsDevScripts/tests/_harness/run-flow.ps1 new file mode 100644 index 00000000..f3eabef4 --- /dev/null +++ b/WindowsDevScripts/tests/_harness/run-flow.ps1 @@ -0,0 +1,109 @@ +<# +.SYNOPSIS + Build + run a hello-world and diff its stdout against an expected file. + +.DESCRIPTION + Invoked by CI after a flow's install script has run. Keeps the "does the + install actually produce a working toolchain?" question down to a single + assertion per flow. + +.PARAMETER Id + Flow id, used only for log prefixes. + +.PARAMETER Build + Shell command to build the hello-world. Empty string skips the build step + (useful for interpreted languages). + +.PARAMETER Run + Shell command whose stdout is compared against -Expected. + +.PARAMETER Expected + Path to a file containing the exact expected stdout. + +.NOTES + Commands run with the repository root as the working directory (the harness + does not change it). Output comparison normalizes CRLF->LF and trims trailing + whitespace on each line plus trailing blank lines. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory)] [string] $Id, + [Parameter()] [string] $Build = '', + [Parameter(Mandatory)] [string] $Run, + [Parameter(Mandatory)] [string] $Expected +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Write-Section { + param([string] $Text) + Write-Host "" + Write-Host "==== [$Id] $Text ====" +} + +function Normalize-Output { + param([string] $Text) + if ($null -eq $Text) { return '' } + $Text = $Text -replace "`r`n", "`n" + $Text = $Text -replace "`r", "`n" + $lines = $Text -split "`n" + $lines = $lines | ForEach-Object { $_.TrimEnd() } + # Trim trailing blank lines. + $i = $lines.Count - 1 + while ($i -ge 0 -and [string]::IsNullOrEmpty($lines[$i])) { $i-- } + if ($i -lt 0) { return '' } + return ($lines[0..$i] -join "`n") +} + +function Invoke-Shell { + param( + [Parameter(Mandatory)] [string] $Command, + [Parameter(Mandatory)] [string] $Label + ) + Write-Host "> $Command" + if ($IsWindows -or ($null -eq $IsWindows)) { + # Windows PowerShell 5.1 doesn't define $IsWindows but is always Windows. + $output = & cmd.exe /d /c $Command 2>&1 | Out-String + } else { + # Useful for local testing on non-Windows hosts. + $output = & bash -c $Command 2>&1 | Out-String + } + $exit = $LASTEXITCODE + Write-Host $output + if ($exit -ne 0) { + throw "$Label failed with exit code $exit" + } + return $output +} + +if (-not (Test-Path -LiteralPath $Expected)) { + throw "Expected-output file not found: $Expected" +} +$expectedText = Normalize-Output (Get-Content -LiteralPath $Expected -Raw) + +if (-not [string]::IsNullOrWhiteSpace($Build)) { + Write-Section 'build' + [void](Invoke-Shell -Command $Build -Label 'build') +} else { + Write-Section 'build (skipped)' +} + +Write-Section 'run' +$runOutput = Invoke-Shell -Command $Run -Label 'run' +$actualText = Normalize-Output $runOutput + +Write-Section 'assert' +if ($actualText -ceq $expectedText) { + Write-Host "OK: stdout matches $Expected" + Write-Host "FLOW_OK: $Id" + exit 0 +} + +Write-Host '--- expected ---' +Write-Host $expectedText +Write-Host '--- actual ---' +Write-Host $actualText +Write-Host '--- end ---' +throw "Flow '$Id' stdout did not match expected output in $Expected" diff --git a/WindowsDevScripts/tests/_harness/run-server.ps1 b/WindowsDevScripts/tests/_harness/run-server.ps1 new file mode 100644 index 00000000..b3eb045c --- /dev/null +++ b/WindowsDevScripts/tests/_harness/run-server.ps1 @@ -0,0 +1,211 @@ +<# +.SYNOPSIS + Build a server scenario, start it in the background, hit an endpoint, + and persist the response body to a file. + +.DESCRIPTION + Companion to tests/_harness/run-flow.ps1 for scenario flows that ship a + real Web API as their hello-world (web-api-csharp, web-api-ts, + web-api-python, web-api-java). + + Designed to be the manifest's `build` step. The matching `run` step is + a simple `type ` (or `cmd /c type ...`) that emits the + persisted body for run-flow.ps1 to diff against expected.txt. + + Why the split: when pwsh runs as `pwsh -File ...` under cmd.exe, its + Write-Host / "host display" output is folded into the process's stdout + (there is no interactive console host to capture it separately). The + outer harness merges stderr into stdout via `2>&1` and diffs the lot. + So we cannot route diagnostics around the diff at runtime. Persisting + the body to disk and emitting only the file contents in the run step + keeps the diff clean and lets diagnostics flow freely through the + build step's normal stdout. + + Lifecycle on each invocation: + 1. (Optional) run a synchronous build command (e.g. `dotnet build` or + `mvn package`). + 2. Start the server command in a hidden child process. Server stdout + and stderr are redirected to log files in a per-run temp dir. + 3. Poll HealthUrl until any 2xx/3xx response, or HealthTimeoutSeconds + elapses. If the server process exits during polling the script + fails immediately and surfaces the exit code. + 4. Issue a GET to RequestUrl (defaults to HealthUrl) and capture the + response body. + 5. Always: kill the server process tree via `taskkill /F /T /PID`. + This handles `dotnet run`, `mvnw spring-boot:run`, `node`, and + `uvicorn`, which all spawn child processes that Stop-Process + alone leaves running. + 6. Write the response body verbatim to OutputFile. + 7. On any failure, dump the captured server log files to stdout so + they show up in the harness's build-step output. + +.PARAMETER Id + Flow id, used in log prefixes only. + +.PARAMETER Build + Optional shell command (run via cmd.exe /d /c) to build the scenario + before starting the server. Empty string skips the build step. + +.PARAMETER Start + Shell command (run via cmd.exe /d /c) that starts the server in the + foreground. Wrapped in a hidden child process by this script. + +.PARAMETER HealthUrl + URL polled until the server is ready. Any 2xx or 3xx response is + considered ready. Polled at 500ms intervals. + +.PARAMETER RequestUrl + Optional URL whose response body is persisted. Defaults to HealthUrl. + Use a separate URL when the health endpoint is not the endpoint you + want to diff. + +.PARAMETER OutputFile + Path where the response body is written verbatim. Parent directory is + created if missing. The manifest's `run` step is expected to be + `type ` so run-flow.ps1 sees only the body. + +.PARAMETER HealthTimeoutSeconds + Maximum time to wait for HealthUrl readiness before failing. Default 60. + +.PARAMETER ShutdownGraceSeconds + Maximum time to wait for the server process tree to exit after taskkill. + Default 10. + +.NOTES + Commands run with the repository root as the working directory, matching + the run-flow.ps1 contract. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory)] [string] $Id, + [Parameter()] [string] $Build = '', + [Parameter(Mandatory)] [string] $Start, + [Parameter(Mandatory)] [string] $HealthUrl, + [Parameter()] [string] $RequestUrl = '', + [Parameter(Mandatory)] [string] $OutputFile, + [Parameter()] [int] $HealthTimeoutSeconds = 60, + [Parameter()] [int] $ShutdownGraceSeconds = 10 +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +if ([string]::IsNullOrWhiteSpace($RequestUrl)) { + $RequestUrl = $HealthUrl +} + +function Write-Section { + param([string] $Text) + Write-Host "" + Write-Host "==== [$Id] $Text ====" +} + +function Stop-ServerTree { + param([System.Diagnostics.Process] $Proc, [int] $GraceSeconds) + if ($null -eq $Proc) { return } + if ($Proc.HasExited) { return } + # taskkill /T kills the full child tree; Stop-Process alone leaves the + # actual server (a child of cmd.exe / dotnet / mvnw / etc.) running. + & taskkill.exe /F /T /PID $Proc.Id 2>&1 | Out-Null + if (-not $Proc.HasExited) { + $Proc.WaitForExit($GraceSeconds * 1000) | Out-Null + } +} + +function Write-ServerLogs { + param([string] $StdoutPath, [string] $StderrPath) + Write-Host "--- server stdout ($StdoutPath) ---" + if (Test-Path -LiteralPath $StdoutPath) { + Get-Content -LiteralPath $StdoutPath | ForEach-Object { Write-Host $_ } + } else { + Write-Host "(no stdout log)" + } + Write-Host "--- server stderr ($StderrPath) ---" + if (Test-Path -LiteralPath $StderrPath) { + Get-Content -LiteralPath $StderrPath | ForEach-Object { Write-Host $_ } + } else { + Write-Host "(no stderr log)" + } + Write-Host "--- end server logs ---" +} + +if (-not [string]::IsNullOrWhiteSpace($Build)) { + Write-Section 'build' + Write-Host "> $Build" + & cmd.exe /d /c $Build + if ($LASTEXITCODE -ne 0) { + throw "Build failed with exit code $LASTEXITCODE" + } +} + +$logStamp = Get-Date -Format 'yyyyMMdd-HHmmss' +$logDir = Join-Path ([System.IO.Path]::GetTempPath()) "wdss-$Id-$logStamp" +New-Item -ItemType Directory -Path $logDir -Force | Out-Null +$outLog = Join-Path $logDir 'server.out.log' +$errLog = Join-Path $logDir 'server.err.log' + +Write-Section 'start server' +Write-Host "> $Start" +Write-Host "logs: $logDir" + +$serverProc = Start-Process -FilePath cmd.exe ` + -ArgumentList '/d', '/c', $Start ` + -PassThru -WindowStyle Hidden ` + -RedirectStandardOutput $outLog ` + -RedirectStandardError $errLog +Write-Host "server pid: $($serverProc.Id)" + +try { + Write-Section "wait for $HealthUrl (timeout ${HealthTimeoutSeconds}s)" + $deadline = (Get-Date).AddSeconds($HealthTimeoutSeconds) + $ready = $false + $attempts = 0 + while ((Get-Date) -lt $deadline) { + $attempts++ + if ($serverProc.HasExited) { + throw "Server process exited prematurely with exit code $($serverProc.ExitCode) after $attempts health attempts" + } + try { + $resp = Invoke-WebRequest -Uri $HealthUrl -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop + if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 400) { + $ready = $true + break + } + } catch { + # Server not ready yet (connection refused, 5xx, etc.); keep polling. + } + Start-Sleep -Milliseconds 500 + } + if (-not $ready) { + throw "Server at $HealthUrl did not become ready within ${HealthTimeoutSeconds}s ($attempts attempts)" + } + Write-Host "server ready after $attempts health attempt(s)" + + Write-Section "GET $RequestUrl" + $bodyResp = Invoke-WebRequest -Uri $RequestUrl -UseBasicParsing -TimeoutSec 30 + Write-Host "HTTP $($bodyResp.StatusCode) $($bodyResp.StatusDescription)" + + Write-Section "persist response body to $OutputFile" + $outputDir = Split-Path -Parent $OutputFile + if (-not [string]::IsNullOrWhiteSpace($outputDir) -and -not (Test-Path -LiteralPath $outputDir)) { + New-Item -ItemType Directory -Path $outputDir -Force | Out-Null + } + # WriteAllText leaves bytes verbatim; the harness normalizes CRLF on diff. + [System.IO.File]::WriteAllText($OutputFile, $bodyResp.Content) + Write-Host "wrote $($bodyResp.Content.Length) char(s)" +} +catch { + Write-ServerLogs -StdoutPath $outLog -StderrPath $errLog + throw +} +finally { + Write-Section 'stop server' + Stop-ServerTree -Proc $serverProc -GraceSeconds $ShutdownGraceSeconds + if ($null -ne $serverProc -and $serverProc.HasExited) { + Write-Host "server pid $($serverProc.Id) exited with code $($serverProc.ExitCode)" + } elseif ($null -ne $serverProc) { + Write-Host "warning: server pid $($serverProc.Id) did not exit cleanly within ${ShutdownGraceSeconds}s" + } +} + diff --git a/WindowsDevScripts/tests/dotnet/Program.cs b/WindowsDevScripts/tests/dotnet/Program.cs new file mode 100644 index 00000000..b98cbbb0 --- /dev/null +++ b/WindowsDevScripts/tests/dotnet/Program.cs @@ -0,0 +1,7 @@ +// Hello-world probe for the .NET flow. +// +// The simplest possible thing that proves the .NET 10 SDK installed and +// produced a working `dotnet` toolchain. Top-level statement; matches the +// `dotnet new console` template's idiomatic shape. + +Console.WriteLine("Hello, world!"); diff --git a/WindowsDevScripts/tests/dotnet/expected.txt b/WindowsDevScripts/tests/dotnet/expected.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/WindowsDevScripts/tests/dotnet/expected.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/WindowsDevScripts/tests/dotnet/hello.csproj b/WindowsDevScripts/tests/dotnet/hello.csproj new file mode 100644 index 00000000..4249fcb5 --- /dev/null +++ b/WindowsDevScripts/tests/dotnet/hello.csproj @@ -0,0 +1,22 @@ + + + + + + Exe + net10.0 + HelloDotNet + hello + enable + enable + false + + + diff --git a/WindowsDevScripts/tests/go/expected.txt b/WindowsDevScripts/tests/go/expected.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/WindowsDevScripts/tests/go/expected.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/WindowsDevScripts/tests/go/hello.go b/WindowsDevScripts/tests/go/hello.go new file mode 100644 index 00000000..7e6af991 --- /dev/null +++ b/WindowsDevScripts/tests/go/hello.go @@ -0,0 +1,14 @@ +// Hello-world probe for the Go flow. +// +// `go run` accepts a single .go file directly without a surrounding module, +// so this file is intentionally standalone (no go.mod). If the toolchain +// install was incomplete, `go run` would fail and the harness would flag +// the flow broken. + +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world!") +} diff --git a/WindowsDevScripts/tests/java/Hello.java b/WindowsDevScripts/tests/java/Hello.java new file mode 100644 index 00000000..8491d186 --- /dev/null +++ b/WindowsDevScripts/tests/java/Hello.java @@ -0,0 +1,13 @@ +// Hello-world probe for the Java flow. +// +// Exercised via JDK 11+'s single-file source-code launcher (JEP 330): +// `java tests/java/Hello.java` compiles and runs this file in one step, +// so no explicit `javac` build is needed. If the JDK install was +// incomplete, the launcher would fail and the harness would flag the +// flow broken. + +public class Hello { + public static void main(String[] args) { + System.out.println("Hello, world!"); + } +} diff --git a/WindowsDevScripts/tests/java/expected.txt b/WindowsDevScripts/tests/java/expected.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/WindowsDevScripts/tests/java/expected.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/WindowsDevScripts/tests/php/expected.txt b/WindowsDevScripts/tests/php/expected.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/WindowsDevScripts/tests/php/expected.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/WindowsDevScripts/tests/php/hello.php b/WindowsDevScripts/tests/php/hello.php new file mode 100644 index 00000000..923530d7 --- /dev/null +++ b/WindowsDevScripts/tests/php/hello.php @@ -0,0 +1,2 @@ + + + + + + Exe + net10.0-windows + true + HelloWinForms + hello + enable + enable + false + + + diff --git a/WindowsDevScripts/tests/winui/Program.cs b/WindowsDevScripts/tests/winui/Program.cs new file mode 100644 index 00000000..af88d976 --- /dev/null +++ b/WindowsDevScripts/tests/winui/Program.cs @@ -0,0 +1,12 @@ +// Hello-world probe for the WinUI 3 flow. +// +// We don't show a window (CI runners are headless for interactive UI), but we +// *do* reference a WinUI type and read back its name — this forces the +// Microsoft.WinUI projection assembly (shipped by the Microsoft.WindowsAppSDK +// NuGet) to actually load. If the WinAppSDK restore was incomplete, the +// `typeof` below would fail and the harness would flag the flow broken. + +using System; + +var name = typeof(Microsoft.UI.Xaml.Application).Name; +Console.WriteLine($"WinUI: {name}"); diff --git a/WindowsDevScripts/tests/winui/expected.txt b/WindowsDevScripts/tests/winui/expected.txt new file mode 100644 index 00000000..64b5b631 --- /dev/null +++ b/WindowsDevScripts/tests/winui/expected.txt @@ -0,0 +1 @@ +WinUI: Application diff --git a/WindowsDevScripts/tests/winui/hello.csproj b/WindowsDevScripts/tests/winui/hello.csproj new file mode 100644 index 00000000..958ee01b --- /dev/null +++ b/WindowsDevScripts/tests/winui/hello.csproj @@ -0,0 +1,41 @@ + + + + + + Exe + net10.0-windows10.0.19041.0 + HelloWinUI + hello + enable + enable + false + None + false + + + + + + +