If you had to take a fresh Windows install from bare metal to fully provisioned in minutes, you’d reach for the Windows Package Manager. The CLI—known as winget—is Microsoft’s supported, safe, and scriptable way to install, upgrade, and configure apps on Windows 10/11. In this guide, you’ll get a practical, end‑to‑end “WinGet provisioning script” approach: ready‑to‑paste commands, a reusable YAML configuration, a JSON import/export workflow, and automation patterns for Intune, CI/CD, and offline scenarios. Follow along and you can go from zero to “ready” in about 15 minutes on typical broadband.
This is the most complete guide online for this topic: you’ll get clear steps, known pitfalls and fixes, and best practices to keep your automation reliable and fast.
Overview of the Use Case
What we’re solving in plain language
- Provisioning means taking a clean Windows device and installing everything needed—browsers, developer tools, productivity apps, runtimes—without manual clicking.
- WinGet is the supported Microsoft way to do unattended, repeatable installs and upgrades via command line. It downloads verified installers from trusted sources, enforces hashes, and can run silent installs.
Common scenarios where this shines
- Fresh installs and reimages: New hardware, repurposed devices, or lab machines.
- Bulk updates: Keep fleets consistent with winget upgrade –all.
- CI/CD automation: Prepare build agents with exact versions.
- Developer onboarding: One script to bootstrap a workstation.
- Intune/Endpoint Manager: Zero‑touch provisioning with WinGet.
- Offline or restricted networks: Use local manifests or a private source.
Quick Reference Table
| Command | Purpose | Example Output |
|---|---|---|
| winget –info | Show WinGet and source versions | Windows Package Manager v1.6.x…; Sources: winget, msstore |
| winget source list | List configured repositories | Name Argument Type; winget https://… Rest; msstore … |
| winget source reset –force | Rebuild local source index | Successfully reset source(s) |
| winget source update | Update package indexes | Updating all sources… Done |
| winget search chrome | Find packages | Google.Chrome (winget) |
| winget show –id Google.Chrome | View details/versions | Found Google.Chrome Version: 129.0… |
| winget install –id Google.Chrome –silent –accept-package-agreements –accept-source-agreements | Silent install | Successfully installed Google Chrome |
| winget list | List installed packages | Name Version Available Source |
| winget list –upgrade-available | Show apps with updates | 12 upgrades available |
| winget upgrade –all –include-unknown –silent | Upgrade everything | Upgraded 10/10 packages |
| winget export -o apps.json –include-versions | Export your app list | Wrote export file to .\apps.json |
| winget import -i .\apps.json –accept-package-agreements –silent | Import and install list | Installing 30 packages… |
| winget uninstall –id App.Id | Uninstall a package | Successfully uninstalled |
| winget settings –open | Edit settings JSON | Opens default editor |
| winget configure –apply .\config.dsc.yaml | Apply a configuration | Configuration applied successfully |
Key Concepts and Prerequisites
What you need
- Windows 10 1809+ or Windows 11. Best experience on Windows 10 21H1+ or any Windows 11.
- The App Installer package from the Microsoft Store (this provides winget).
- PowerShell 5.1+ (installed by default) or PowerShell 7.x for advanced parallelism.
- Administrator rights for machine‑wide installs and system changes.
How to verify your setup
- Check WinGet version and sources:
winget –info
Confirm the Windows Package Manager version (1.4+ recommended; 1.6+ ideal) and that you have “winget” and “msstore” sources.
If winget is missing
- On corporate or offline images, App Installer might not be present. You can install it from the Store or sideload the MSIX bundle:
- Store: Open Microsoft Store, search “App Installer” by Microsoft, and install.
- Offline: Download “Microsoft.DesktopAppInstaller” MSIX bundle and dependencies from Microsoft’s official distribution (e.g., via the Microsoft Store for Business/Intune or a trusted internal mirror), then:
Add-AppxPackage -Path .\Microsoft.DesktopAppInstaller_*.msixbundle
Step-by-Step Guide
Step 1: Update your sources and baseline the system
- Refresh indexes and ensure your catalogs are current:
winget source update
winget source list - Reset if you encounter indexing problems or corruption:
winget source reset –force
winget source update
Step 2: Optional — Export your current app list
If you’re migrating a machine or building a canonical list from a “golden” device:
winget export -o .\apps.json –include-versions
This produces a JSON file containing all packages WinGet recognizes on that machine, including exact versions if available.
Step 3: Create your provisioning list (JSON import method)
Create a curated list of packages to install on a fresh device. Keep it small, clean, and vetted.
Example apps.json (commented inline below, but note: JSON does not allow comments; remove comments before use):
{
“$schema”: “https://aka.ms/winget-packages.schema.2.0.json“,
“CreationDate”: “2025-01-01T12:00:00Z”,
“Sources”: [
{
“SourceDetails”: {
“Argument”: “https://cdn.winget.microsoft.com/cache“,
“Identifier”: “Microsoft.Winget.Source_8wekyb3d8bbwe”,
“Name”: “winget”,
“Type”: “Microsoft.PreIndexed.Package”
},
“Packages”: [
{ “Id”: “Microsoft.PowerShell”, “Version”: “7.4.2” },
{ “Id”: “Git.Git”, “Version”: “2.46.0” },
{ “Id”: “Microsoft.VisualStudioCode”, “Version”: “1.93.0” },
{ “Id”: “Google.Chrome” },
{ “Id”: “7zip.7zip” },
{ “Id”: “Microsoft.DotNet.SDK.8”, “Version”: “8.0.401” },
{ “Id”: “Microsoft.VCRedist.2015+.x64” },
{ “Id”: “Notepad++.Notepad++” },
{ “Id”: “Docker.DockerDesktop” },
{ “Id”: “Python.Python.3.12” }
]
}
]
}
Notes:
- Pin versions for critical tooling (PowerShell, .NET SDK) to ensure reproducible builds. Leave evergreen apps unpinned (Chrome, 7-Zip) to get latest security patches.
- Use exact IDs to avoid ambiguity. You can find an ID with:
winget search “Visual Studio Code”
winget show –id Microsoft.VisualStudioCode
Step 4: Install from your provisioning list
Run the import in unattended mode:
winget import -i .\apps.json –silent –accept-package-agreements –accept-source-agreements
Tips:
- Use –silent to minimize prompts.
- Add –ignore-unavailable if you have packages not present on current sources.
- Add –disable-interactivity in environments where any UI is unacceptable.
Step 5: Optional — Provision with a YAML configuration
For more than “install these apps”—for example, configuring settings, enabling Windows features, or pinning scopes—use winget configure. This applies a YAML configuration based on DSC resources.
Example config.dsc.yaml (with inline comments)
Winget configuration file
Applies apps with specific options and sets a few system preferences
Run with: winget configure –apply .\config.dsc.yaml
Requires WinGet 1.5+ for best experience
configuration:
version: 1.1
properties:
resources:
Install VS Code for all users (machine scope)
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: VSCode
directives:
description: Install Visual Studio Code
settings:
id: Microsoft.VisualStudioCode
source: winget
installOptions:
preferences:
scope: machine
installer:
# Pass installer-specific arguments if needed
override: "--silent"
ensure: Present
# Install Git with a specific version (pinning)
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: Git
settings:
id: Git.Git
version: 2.46.0
source: winget
installOptions:
preferences:
scope: machine
ensure: Present
# Install PowerShell 7 for all users
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: PowerShell7
settings:
id: Microsoft.PowerShell
source: winget
installOptions:
preferences:
scope: machine
ensure: Present
Run it:
winget configure –apply .\config.dsc.yaml –accept-configuration-agreements
Step 6: Add single packages or critical dependencies
When you need to add one app quickly:
winget install –id Microsoft.VisualStudio.2022.Community –source winget –silent –accept-package-agreements –accept-source-agreements
Advanced options:
- Use –version to pin:
winget install –id Python.Python.3.12 –version 3.12.5
- Use –architecture x64 or –scope machine for clarity:
winget install –id Git.Git –architecture x64 –scope machine –silent
- Use –override to pass installer arguments (MSI/EXE specifics):
winget install –id 7zip.7zip –override “/S”
Step 7: Upgrade and validate
After provisioning, bring everything up to date and verify:
winget list –upgrade-available
winget upgrade –all –include-unknown –silent –accept-package-agreements
winget list | findstr /i “Visual Studio Code”
Step 8: Create an all-in-one provisioning PowerShell script
Here is a “ready in 15 minutes” script that you can run as Administrator. It handles prerequisites, source updates, a curated install set, upgrades, and logging.
provision.ps1:
Requires: Administrator
Purpose: Provision a Windows machine with core apps using WinGet
$ErrorActionPreference = “Stop”
$log = Join-Path $PSScriptRoot “provision-$(Get-Date -Format yyyyMMdd-HHmmss).log”
Start-Transcript -Path $log -Append
function Assert-Admin {
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole] “Administrator”)
if (-not $isAdmin) { throw “Please run this script as Administrator.” }
}
function Ensure-WinGet {
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
Write-Host “WinGet not found. Please install ‘App Installer’ from Microsoft Store, then re-run.”
exit 1
}
}
Assert-Admin
Ensure-WinGet
Update sources
winget source update
Optional: reset if needed
winget source reset –force; winget source update
Recommended: use machine scope when available and install silently
$apps = @(
@{ Id = “Microsoft.PowerShell”; Version = “7.4.2” },
@{ Id = “Git.Git”; Version = “2.46.0” },
@{ Id = “Microsoft.VisualStudioCode” },
@{ Id = “Google.Chrome” },
@{ Id = “7zip.7zip” },
@{ Id = “Microsoft.DotNet.SDK.8”; Version = “8.0.401” },
@{ Id = “Microsoft.VCRedist.2015+.x64” },
@{ Id = “Notepad++.Notepad++” },
@{ Id = “Python.Python.3.12” }
)
foreach ($app in $apps) {
$id = $app.Id
$ver = $app.Version
Write-Host “Installing $id $ver”
$params = @(“install”, “–id”, $id, “–silent”, “–accept-package-agreements”, “–accept-source-agreements”, “–scope”, “machine”, “–source”, “winget”)
if ($ver) { $params += @(“–version”, $ver) }
Retry logic for transient network hiccups
$attempt = 0
do {
$attempt++
try {
winget @params
$ok = $true
} catch {
Write-Warning "Attempt $attempt failed for $id. $($_.Exception.Message)"
Start-Sleep -Seconds 5
$ok = $false
}
} while (-not $ok -and $attempt -lt 3)
if (-not $ok) { Write-Error "Failed to install $id after 3 attempts"; }
}
Upgrade anything else to latest
winget upgrade –all –include-unknown –silent –accept-package-agreements
Stop-Transcript
Write-Host “Provisioning complete. Log: $log”
Run it:
powershell.exe -ExecutionPolicy Bypass -File .\provision.ps1
Troubleshooting
Hash mismatch or “Installer hash does not match”
Why it happens:
- The upstream vendor replaced the installer; the hash in the WinGet manifest doesn’t match.
Fix steps:
- Update/refresh sources, then retry:
winget source update
winget install –id <Package.Id> –silent –accept-package-agreements - If the issue persists, wait for the manifest to be updated or install a specific version known-good:
winget show –id <Package.Id> –versions
winget install –id <Package.Id> –version <X.Y.Z> –silent - If you fully trust the source and understand the risk, check release notes or use the vendor’s installer temporarily, then return to WinGet later.
Multiple installers found / ambiguous package
Symptoms:
- WinGet prompts for architecture/scope or returns “multiple installers found.”
Fix:
- Specify exact –id and –architecture:
winget show # Find the exact Id
winget install –id Git.Git –architecture x64 –scope machine –silent - Prefer the “winget” source to avoid Store duplicates:
winget install –id <Package.Id> –source winget
Source index errors or “failed to open source”
Fix:
winget source reset –force
winget source update
If behind a proxy:
- Configure system proxy or add proxy env vars:
setx HTTPS_PROXY https://user:pass@proxy:443
setx HTTP_PROXY http://user:pass@proxy:80 - Then re-run the source update.
Installers require interaction despite –silent
- Some packages don’t fully support quiet switches or use nonstandard flags. Use –override to pass the correct arguments:
winget install –id Some.Vendor.App –override “/quiet /norestart”
- Check the installer type via:
winget show –id Some.Vendor.App
Then use MSI (/qn), InstallShield (/s /v”/qn”), or vendor‑specific flags accordingly.
“Package not found” or ID changes
- Re-search and confirm the new ID:
winget search “Vendor AppName”
winget show –id New.Package.Id - Update your JSON/YAML lists.
Upgrade failures due to unknown installers
- Use:
winget upgrade –all –include-unknown –silent
- Manually reinstall with winget install –id –force if the product was installed outside WinGet and lacks metadata.
Automation Tips
Combine WinGet with PowerShell for reliability
- Add retries, logs, and limited concurrency (PowerShell 7+):
$apps = “Git.Git”,”Microsoft.VisualStudioCode”,”7zip.7zip”,”Google.Chrome”
$throttle = 3
$script = {
param($id)
winget install –id $id –silent –accept-package-agreements –accept-source-agreements –scope machine –source winget
}$apps | ForEach-Object -Parallel { & $using:script $_ } -ThrottleLimit $throttle
- Note: Too much parallelism can cause conflicts; keep throttle modest (2–4).
Schedule recurring upgrades with Task Scheduler
- Create a task to run monthly or weekly:
- Action: Program/script:
powershell.exe
- Arguments:
-ExecutionPolicy Bypass -NoProfile -WindowStyle Hidden -Command “winget upgrade –all –include-unknown –silent –accept-package-agreements”
- Run with highest privileges, trigger on a schedule, and only when on AC power for laptops.
- Action: Program/script:
Intune/MEM integration
- Use WinGet packages as Intune “Win32 app” commands or leverage Intune’s native Windows Package Manager support (where available).
- Example install command:
winget install –id Git.Git –silent –accept-package-agreements –accept-source-agreements –source winget
- Detection rules: Registry or file existence (e.g., Git’s install path).
- For broader device setup, push your provisioning script as a Win32 app or a PowerShell script policy and run during ESP autopilot.
CI/CD runners
- Use winget import during runner bootstrap to ensure consistent toolchains:
winget source update
winget import -i .\apps.json –silent –accept-package-agreements –accept-source-agreements - Cache build tools in the runner image and only install deltas to keep job time short.
- Pin versions for compilers/SDKs to ensure reproducibility.
Offline or restricted environments
Options, from simplest to most robust:
- Local manifests with direct installers:
- Clone or craft a local manifest for a package and install via:
winget install –manifest .\manifests\MyPackage.yaml
- Point the manifest’s installer URL to an internal file share or web server accessible on your network.
- Clone or craft a local manifest for a package and install via:
- Private WinGet source:
- Host a private REST source and add it:
winget source add –name Contoso –arg https://winget.contoso.local/api
winget source update - Publish your curated manifests to this source.
- Host a private REST source and add it:
- Hybrid approach:
- Fall back to vendor installers if WinGet is blocked, then realign later with WinGet installs for updates.
Logging and telemetry
- Capture logs for audits and support:
Start-Transcript .\winget-session.log
winget upgrade –all –silent –include-unknown
Stop-Transcript - WinGet also writes logs to:
$env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir
Best Practices
- Prefer exact IDs and pin critical versions
- Use winget show –id –versions to see history and pin with –version when builds must be reproducible.
- Keep lists minimal and modular
- Maintain separate JSON files for roles (dev.json, design.json, ops.json) and import what you need per device class.
- Use machine scope by default
- –scope machine ensures apps are available to all users. Avoid per‑user installs for shared or multi‑user devices.
- Embrace silent installs
- Always pass –silent and agreement flags (–accept-package-agreements, –accept-source-agreements) to avoid hangs in automation.
- Verify installer switches
- For stubborn packages, add –override with the proper quiet flags based on installer type; test locally before scaling out.
- Source hygiene
- Run winget source update regularly; use winget source reset –force when indices corrupt.
- Curate and review
- Periodically re‑validate your apps.json and YAML configuration against upstream changes. Replace deprecated IDs and remove unneeded apps to keep provisioning time short.
- Document and version your configs
- Store provisioning files in Git. Tag known‑good sets (e.g., v2025.01) and reference them in automation so you can roll back.
- Security and trust
- Rely on the default “winget” source for vetted manifests. Be cautious with overrides or private sources—review manifests and verify hashes.
- Measure time-to-ready
- Log start/end times per stage. Use parallel installs sparingly to keep “bare metal to ready in 15 minutes” realistic on your network.
Conclusion
With winget, provisioning Windows is predictable, fast, and safe. You now have everything needed to build a robust “WinGet provisioning script”: validated sources, silent install switches, import/export for reproducibility, an optional YAML configuration for more control, and automations for fleets and CI/CD. Start with the quick script, evolve to role‑based JSON/YAML lists, and you’ll consistently go from bare metal to fully ready in minutes—without clicking through installers.
FAQ
How do I find the correct package ID to avoid installing the wrong app? (H4)
- Use:
winget search “”
winget show –id <Candidate.Id>Confirm the Publisher, Installer Type, and Source. Prefer exact IDs from the “winget” source rather than the Store when you need deterministic installs.
What’s the difference between winget import/export and winget configure? (H4)
- winget export/import: Focused on app lists—install or reproduce a set of applications (optionally version‑pinned).
- winget configure: A YAML‑driven, DSC‑style system that can also control scopes, options, and possibly other machine configuration tasks via resources. Use import/export for simple app sets; use configure for richer, declarative provisioning.
Is winget safe for enterprise use? (H4)
- Yes. WinGet is developed and supported by Microsoft. The default “winget” source uses manifests with validated metadata and installer hashes. You can also create a private source to fully curate what is available in your organization.
How do I make installs truly unattended? (H4)
- Always pass –silent, –accept-package-agreements, and –accept-source-agreements. For installers that still prompt, add –override with the appropriate quiet switches (e.g., MSI “/qn”, EXE “/S” or vendor‑specific flags). Test each app once interactively to capture the correct switches.
Can I speed this up further? (H4)
- Yes. Use a local mirror/private source to reduce download latency, and cautiously run a small number of installs in parallel (PowerShell 7’s ForEach‑Object -Parallel with a low throttle). Also, split heavy installs (like Docker Desktop or Visual Studio) into separate stages so they run while lighter apps complete.
Try the commands in this guide on a test machine first, then adapt the provisioning script and configuration files to your environment. With a clean list, proper silent switches, and a touch of automation, WinGet will take your devices from bare metal to ready in about 15 minutes.
