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.
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 in a domino
- 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
- Per-tool manual updates → you 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