WinGet Guides

The Ultimate WinGet Provisioning Script: From Bare Metal to Ready in 15 Minutes

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

See also  Private WinGet Source: How to Host Your Own Repository

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

See also  Schedule Auto-Updates for All Apps with WinGet + Task Scheduler

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:

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.

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.
  • 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.
  • Hybrid approach:
    • Fall back to vendor installers if WinGet is blocked, then realign later with WinGet installs for updates.
See also  WinGet in CI/CD: Provision Windows Build Agents the Right Way

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.

About the author

Jonathan Dudamel

Jonathan Dudamel

I'm Jonathan Dudamel, an experienced IT specialist and network engineer passionate about all things Windows. I have deep expertise in Microsoft project management, virtualization (VMware ESXi and Hyper-V), and Microsoft’s hybrid platform. I'm also skilled with Microsoft O365, Azure ADDS, and Windows Server environments from 2003 through 2022.

My strengths include Microsoft network infrastructure, VMware platforms, CMMS, ERP systems, and server administration (2016/2022).