Windows dev environment weekly maintenance — one command for MSYS2 · winget · toolchains
A weekly PowerShell script that updates MSYS2 pacman, winget, Scoop, npm globals, rustup, cargo binaries, pipx, and Flutter.
A Windows dev environment is always a patchwork of dependencies — IDEs from winget, gcc/make/git from MSYS2, CLIs from scoop, globals from npm/pip/cargo. Each has a different update command, and forgetting them eventually breaks a Tauri build with something nonsensical like ld.exe: cannot find -lcrypt32.
I think the Windows environment is uniquely prone to "it used to work" failures, not because Windows is less stable, but rather because the dev toolchain is assembled from more independent sources than on Mac or Linux — and each source has its own update cadence. Because those cadences don't align, a weekly consolidated update is the only practical way to keep everything in sync. Without it, drift is the default.
This article is the 8-step integrated PowerShell script that prevents that. Run it once a week — done.
TL;DR
MSYS2 pacman→winget→Scoop→npm -g→rustup→cargo install-update→pipx→flutter- MSYS2 is step 1: it's the OS of your build environment on Windows. Skip it and native builds collapse like dominoes
- Each step auto-skips if the tool isn't installed; one failure doesn't stop the rest
- ~100 lines of PowerShell, run by double-clicking
update-system.bat
Why MSYS2 is step 1
Windows's winget deals with user apps (Chrome, Slack…) and final dev tool artifacts. But the environment that actually compiles code lives inside MSYS2 — gcc, make, autotools, openssl headers, libsqlite, zlib via pacman.
Skip a month and you'll see:
- Rust + Tauri cross-compiles break on OpenSSL header mismatch
- Python C extension builds (
pip install pillow, etc.) fail on freetype version skew - Any MinGW-based build breaks due to ABI mismatch in dependent libraries
That's why pacman -Syuu (double u — sync including downgrades) is always the first step.
Prerequisites
- Windows 10 1709+ / 11 (winget built-in)
- For the full 8 steps: MSYS2, pipx, cargo-update, Flutter pre-installed
- Scoop optional — auto-SKIP if absent
One-time prep
Administrator PowerShell:
# MSYS2 (~700MB) — the build env's OS
winget install MSYS2.MSYS2
# pipx (Python global package manager)
python -m pip install --user pipx
python -m pipx ensurepath
# cargo-update extension
cargo install cargo-update
# Flutter (~1GB)
winget install Flutter.Flutter
# If not in winget: https://docs.flutter.dev/get-started/install/windowsAfter MSYS2 install, open the MSYS2 shell once and run pacman -Syuu to sync base packages.
8 Update Steps
| # | Step | Command | Notes |
|---|---|---|---|
| 1 | MSYS2 | pacman -Syuu | OS of the build env — #1 |
| 2 | winget | winget upgrade --all | system packages |
| 3 | Scoop | scoop update * | only if installed |
| 4 | npm globals | npm update -g | global npm packages |
| 5 | Rust toolchain | rustup update | stable channel |
| 6 | Cargo binaries | cargo install-update -a | needs cargo-update |
| 7 | pipx | pipx upgrade-all | Python global tools |
| 8 | Flutter SDK | flutter upgrade | current channel's latest |
The integrated script
Save two files:
update-system.ps1 (the logic):
<#
.SYNOPSIS
Bulk system-tool update — Windows
.DESCRIPTION
Refresh build env / package managers / language toolchains in one shot.
.NOTES
Run: update-system.bat (admin recommended)
Target: Windows 10/11
#>
$ErrorActionPreference = "Continue"
$script:Total = 8
$script:Step = 0
function Step-Header {
param([string]$Message)
$script:Step++
Write-Host ""
Write-Host ("[{0}/{1}] {2}" -f $script:Step, $script:Total, $Message) -ForegroundColor Cyan
}
function Has-Command {
param([string]$Cmd)
$null = Get-Command $Cmd -ErrorAction SilentlyContinue
return $?
}
function Write-OK { param([string]$m) Write-Host " OK $m" -ForegroundColor Green }
function Write-Skip { param([string]$m) Write-Host " SKIP $m" -ForegroundColor DarkGray }
function Write-Warn { param([string]$m) Write-Host " WARN $m" -ForegroundColor Yellow }
function Write-Fail { param([string]$m) Write-Host " FAIL $m" -ForegroundColor Red }
Write-Host "━━━ System update (Windows) ━━━" -ForegroundColor Cyan
# 1. MSYS2 (most important — OS of the build environment)
Step-Header "MSYS2 (pacman -Syuu) — critical"
$msys2Candidates = @(
"C:\msys64\usr\bin\bash.exe",
"D:\msys64\usr\bin\bash.exe",
"C:\tools\msys64\usr\bin\bash.exe"
)
$msys2Bash = $msys2Candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($msys2Bash) {
Write-Host " → using: $msys2Bash" -ForegroundColor DarkGray
& $msys2Bash -lc "pacman -Syuu --noconfirm"
if ($LASTEXITCODE -eq 0) {
Write-OK "pacman -Syuu done (rerun once if core packages updated)"
} else {
Write-Fail "pacman -Syuu (after core updates, restart shell + rerun)"
}
} else {
Write-Skip "MSYS2 not installed (checked: C:\msys64, D:\msys64, C:\tools\msys64)"
}
# 2. winget
Step-Header "winget (system packages)"
if (Has-Command winget) {
winget upgrade --all --silent --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -eq 0) { Write-OK "winget upgrade --all" }
else { Write-Fail "winget upgrade --all (some packages may need admin/interactive)" }
} else {
Write-Skip "winget not installed"
}
# 3. Scoop (optional)
Step-Header "Scoop (if installed)"
if (Has-Command scoop) {
scoop update
scoop update *
Write-OK "scoop update / update *"
} else {
Write-Skip "scoop not installed"
}
# 4. npm globals
Step-Header "npm globals"
if (Has-Command npm) {
npm update -g
if ($LASTEXITCODE -eq 0) { Write-OK "npm update -g" } else { Write-Fail "npm update -g" }
} else {
Write-Skip "npm not installed"
}
# 5. Rust toolchain
Step-Header "Rust toolchain (rustup)"
if (Has-Command rustup) {
rustup update
if ($LASTEXITCODE -eq 0) { Write-OK "rustup update" } else { Write-Fail "rustup update" }
} else {
Write-Skip "rustup not installed"
}
# 6. Cargo binaries
Step-Header "Cargo binaries (cargo install-update -a)"
if (Has-Command cargo) {
cargo install-update --version 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
cargo install-update -a
if ($LASTEXITCODE -eq 0) { Write-OK "cargo install-update -a" }
else { Write-Fail "cargo install-update -a" }
} else {
Write-Warn "cargo-update missing (install: cargo install cargo-update)"
}
} else {
Write-Skip "cargo not installed"
}
# 7. pipx
Step-Header "pipx (Python global tools)"
if (Has-Command pipx) {
pipx upgrade-all
if ($LASTEXITCODE -eq 0) { Write-OK "pipx upgrade-all" } else { Write-Fail "pipx upgrade-all" }
} else {
Write-Skip "pipx not installed"
}
# 8. Flutter SDK
Step-Header "Flutter SDK (flutter upgrade)"
if (Has-Command flutter) {
flutter upgrade
if ($LASTEXITCODE -eq 0) { Write-OK "flutter upgrade" } else { Write-Fail "flutter upgrade" }
} else {
Write-Skip "flutter not installed"
}
Write-Host ""
Write-Host "━━━ Done ━━━" -ForegroundColor Cyan
Write-Host "Checks: winget upgrade / npm outdated -g / pacman -Qu (MSYS2)" -ForegroundColor DarkGrayupdate-system.bat (double-click entry point):
@echo off
title System Update
echo ============================================
echo System Update (Windows)
echo MSYS2 + winget + npm + rustup + cargo + pipx
echo ============================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0update-system.ps1" %*
if %errorlevel% neq 0 (
echo.
echo [ERROR] Update failed. Check log above.
) else (
echo.
echo [DONE] System update complete.
)
pauseSave the .ps1 file as UTF-8 with BOM. Without BOM, non-ASCII output is mangled by Windows PowerShell 5.1. In VS Code, click encoding in the bottom-right → "Save with Encoding" → "UTF-8 with BOM".
Example output
Double-click update-system.bat (accept UAC). First run is 5–10 min (MSYS2 + winget). Subsequent runs are 1–3 min.
━━━ System update (Windows) ━━━
[1/8] MSYS2 (pacman -Syuu) — critical
→ using: C:\msys64\usr\bin\bash.exe
:: Synchronizing package databases...
:: Starting core system upgrade...
...
OK pacman -Syuu done (rerun once if core packages updated)
[2/8] winget (system packages)
Microsoft.VisualStudioCode 1.99.2 → 1.100.0
GitHub.cli 2.65.0 → 2.66.1
...
OK winget upgrade --all
[3/8] Scoop (if installed)
SKIP scoop not installed
[4/8] npm globals
OK npm update -g
...
━━━ Done ━━━
Checks: winget upgrade / npm outdated -g / pacman -Qu (MSYS2)
Automation — just a reminder via Task Scheduler
Full automation is not recommended. winget occasionally pops EULA dialogs, and MSYS2 core updates need a shell restart that breaks unattended runs. A reminder, not an auto-run:
Task Scheduler via PowerShell:
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument '-NoProfile -Command "[System.Reflection.Assembly]::LoadWithPartialName(\"System.Windows.Forms\"); [System.Windows.Forms.MessageBox]::Show(\"Weekly update-system reminder\", \"Dev Maintenance\")"'
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Friday -At 10:00am
Register-ScheduledTask -TaskName "Dev Update Reminder" `
-Action $action -Trigger $trigger `
-Description "Weekly reminder to run update-system"Every Friday 10:00, one message box. You decide when to run.
Gotchas
- MSYS2 core updates require shell restart + rerun: When pacman updates itself or core libs (libcurl, openssl), it stops the transaction and asks for a rerun. Run
update-system.batonce more after the first run completes. - Some winget packages need interactive input: Most go silent with
--silent, but new EULAs block. Install those packages manually. - Flutter may not be in the winget repo: When
Flutter.Flutterisn't listed, grab the zip from docs.flutter.dev and add to PATH. - Scoop is optional: If you intentionally use only winget or only scoop, the other step SKIPs cleanly. No conflict risk.
- The second
uin-Syuu: Syncs downgrades. MSYS2 sometimes downgrades packages intentionally;-Syualone accumulates conflicts.
Mac users?
Same idea covering brew + language toolchains + Flutter + CocoaPods → Mac dev environment weekly maintenance.
Summary
- On Windows, MSYS2 update is priority #1
- Update tools one by one → you'll forget
- One integrated script → forget all you want, just run it
- Automate only as a reminder, never the run itself
- Weekly once prevents build breaks, certificate expiry, and SDK drift
Keep reading
- Node.js on Windows — Native vs WSL2, which should you use?
Differences between native Windows Node.js and WSL2 Linux Node.js — setup, troubleshooting, and a decision table by project type.
- WSLg — Running Linux GUI apps on Windows
Set up WSL2's GUI integration (WSLg) so Linux desktop apps open as native Windows windows. No external X server or VcXsrv needed.
- Docker on Windows — Docker Desktop vs native WSL2 + license pitfalls
Two paths for Docker on Windows: Docker Desktop vs installing the docker engine inside WSL2. Cost / performance / license differences.