If you provision Windows build agents for CI/CD, you need a repeatable, reliable, and fast way to install and update tooling. The Windows Package Manager—better known as WinGet—solves exactly that: one command-line interface to search, install, upgrade, remove, and configure software on Windows. In this guide, you’ll learn how to use WinGet to create consistent Windows build machines across pipelines, images, and self-hosted runners. You’ll get practical examples, ready-to-use commands, end-to-end YAML pipeline samples, and tips to avoid common pitfalls.
You’ll also see how to combine WinGet with PowerShell, schedule automated updates, use it in Intune or offline/isolated networks, and pin exact versions to protect your builds.
Overview of the Use Case
-
What this does in plain language
When you run builds on Windows—on GitHub Actions, Azure Pipelines, Jenkins, TeamCity, or self-hosted agents—you need the exact tools and versions: compilers, SDKs, runtimes, CLIs, and supporting utilities. WinGet lets you declare and apply that state on any Windows machine with simple, idempotent commands. You can: -
Provision a fresh agent from scratch with a consistent set of packages
-
Update existing agents in bulk during maintenance windows
-
Enforce specific versions for reproducible builds
-
Configure machine settings via WinGet Configuration (YAML)
-
Automate everything in your CI/CD pipeline
-
Typical scenarios
-
Fresh installs on ephemeral runners (e.g., GitHub Actions self-hosted runners)
-
Golden image creation (e.g., Packer images for Windows Server 2022/Windows 11)
-
Bulk updates on a fleet of build servers (scheduled or orchestrated)
-
Onboarding new dev machines to match your build environment
-
CI stages that need deterministic toolchains (pinned versions)
Quick Reference Table
| Command | Purpose | Example Output |
|---|---|---|
| winget –info | Show WinGet, source, and OS info | Windows Package Manager v1.7.x OS Version: 10.0.22631 Sources: winget, msstore |
| winget search |
Find a package and its ID | Name Id Version Git Git.Git 2.46.0 |
| winget show –id |
Show details, installers, switches | Found Git [Git.Git] Version 2.46.0 Installer SHA256: … |
| winget list | List installed packages WinGet sees | Name Id Version Microsoft .NET… Microsoft.DotNet… 8.0.401 |
| winget install –id |
Install package silently | Successfully installed |
| winget install –id |
Install a specific version (if available) | Installed |
| winget upgrade –all –silent | Upgrade all packages non-interactively | Upgraded 12 of 12 packages |
| winget export –output packages.json –include-versions | Export installed packages to JSON | Exported package list to packages.json |
| winget import –import-file packages.json –accept-* –silent | Install packages from export file | Import succeeded |
| winget source reset –name winget –force | Fix broken sources/index | Successfully reset |
| winget pin add –id |
Pin version(s) to protect builds | Pin added: Git.Git 2.46.* |
| winget configure –file .\config.yaml | Apply Windows Package Manager Configuration | Configuration applied successfully |
Key Concepts and Prerequisites
-
What is WinGet?
The Windows Package Manager CLI, winget, is Microsoft’s official package manager for Windows. It installs software from trusted sources (primarily the community-curated winget source), supports silent unattended installs, and provides import/export and configuration capabilities. -
Required tools
-
Windows 10 1809+ or Windows 11; Windows Server 2022 recommended for build agents
-
WinGet CLI (Windows Package Manager). On Windows 11 and modern Server builds, it’s typically preinstalled via App Installer. Otherwise, install from the Microsoft Store or the official GitHub release.
-
PowerShell 5.1 or PowerShell 7.x for scripting
-
Administrator rights for machine-wide installs and drivers/frameworks
-
Internet access to the winget source and vendor CDNs (or a curated internal repo for offline scenarios)
-
Verify your WinGet version
Use this to check features available on your machine: -
winget –info
Look for:
-
Windows Package Manager version (1.4+ supports pinning; 1.6+ supports improvements to import; 1.7+ adds more robust features)
-
Source information and settings path
-
Installing WinGet on servers/offline hosts
If your server cannot access the Microsoft Store:
- Download App Installer (MSIX), VCLibs, and UI.Xaml dependencies from the official sources or the winget-cli GitHub releases page.
- Install with PowerShell (Admin):
- Add-AppxPackage -Path .\Microsoft.VCLibs.x64.14.00.Desktop.appx
- Add-AppxPackage -Path .\Microsoft.UI.Xaml.2.7.appx
- Add-AppxPackage -Path .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Alternatively, use the winget-cli release bundle instructions from the project’s README.
Step-by-Step Guide
The following steps walk you through provisioning a Windows build agent with WinGet. We’ll cover installing prerequisites, pinning versions, exporting/importing package lists, and applying machine configuration with YAML.
- Ensure WinGet is available and up-to-date
- winget –info
- winget source update
- winget settings
Best practice: on build agents, disable the Microsoft Store source if your environment blocks it or if you want to restrict to the community repository. In your settings file (opened by winget settings), you can manage sources and behavior. At minimum, ensure the winget source is enabled and working.
- Warm up sources and verify connectivity
- winget source list
- winget search Git
If the search returns results quickly, the index is healthy. If you get source errors, jump to the Troubleshooting section.
- Install foundational tooling (silent, exact, machine scope)
Use IDs rather than names to avoid ambiguity. Include acceptance flags to run non-interactively.
- Set-ExecutionPolicy Bypass -Scope Process -Force
- $CommonFlags = “–silent –accept-package-agreements –accept-source-agreements –disable-interactivity –exact –source winget”
- winget install –id Git.Git $CommonFlags
- winget install –id Microsoft.DotNet.SDK.8 $CommonFlags
- winget install –id Python.Python.3.12 $CommonFlags –scope machine
- winget install –id OpenJS.NodeJS.LTS $CommonFlags –scope machine
- winget install –id 7zip.7zip $CommonFlags
- winget install –id Microsoft.PowerShell $CommonFlags –scope machine
- winget install –id Microsoft.VisualStudio.2022.BuildTools $CommonFlags –override “–quiet –wait –norestart –nocache –installPath C:\BuildTools –add Microsoft.VisualStudio.Workload.VCTools;includeRecommended”
Notes:
- Use –exact and –id to avoid picking a similarly named package.
- Use –scope machine to install system-wide when supported.
- For Visual Studio Build Tools, pass workloads via –override with the vendor’s silent flags.
- For Microsoft PowerShell (PowerShell 7), machine-scope ensures availability for all users.
- Pin critical versions for reproducible builds
Pinning prevents winget upgrade –all from bumping a package unexpectedly.
- winget pin add –id Git.Git –version 2.46.*
- winget pin add –id Microsoft.DotNet.SDK.8 –version 8.0.401
You can list or export your pins:
- winget pin list
- winget pin export –output pins.json
- Export your package list for reuse
Run this on a “golden” build agent to capture your desired toolset.
- winget export –output .\packages.json –include-versions
A sample export looks like:
{
“CreationDate”: “2025-09-01T12:00:00Z”,
“Sources”: [
{
“Packages”: [
{ “PackageIdentifier”: “Git.Git”, “Version”: “2.46.0” },
{ “PackageIdentifier”: “Microsoft.DotNet.SDK.8”, “Version”: “8.0.401” },
{ “PackageIdentifier”: “Python.Python.3.12”, “Version”: “3.12.6” },
{ “PackageIdentifier”: “OpenJS.NodeJS.LTS”, “Version”: “20.16.0” },
{ “PackageIdentifier”: “7zip.7zip”, “Version”: “24.07” },
{ “PackageIdentifier”: “Microsoft.PowerShell”, “Version”: “7.4.4” }
],
“SourceDetails”: { “Argument”: “https://cdn.winget.microsoft.com/cache“, “Identifier”: “Microsoft.Winget.Source_8wekyb3d8bbwe” },
“SourceIdentifier”: “winget”
}
]
}
- Import that package list on any new agent
- winget import –import-file .\packages.json –accept-package-agreements –accept-source-agreements –silent
This will install missing packages and skip those already matching the requested version.
- Apply machine configuration with WinGet Configuration (YAML)
WinGet can apply configurations such as enabling features, setting environment variables, ensuring services, and installing packages in one go. Create a config.yaml:
config.yaml
Windows Package Manager Configuration
Apply with: winget configure –file .\config.yaml –accept-configuration-agreements
properties:
configurationVersion: 1.1
Metadata for auditing
metadata:
name: BuildAgent-Base
description: Base image for Windows build agents
owner: BuildOps
version: 1.0.0
Parameters for reuse (optional)
parameters:
BuildToolsPath: “C:\BuildTools”
Resources define things to converge
resources:
Ensure packages are installed (same as winget install)
-
resource: Microsoft.WinGet.DSC/WinGetPackage
id: install-git
directives:
description: Install Git
allowPrerelease: false
settings:
id: Git.Git
source: winget
ensure: Present
installBehavior: Silent -
resource: Microsoft.WinGet.DSC/WinGetPackage
id: install-dotnet-sdk
settings:
id: Microsoft.DotNet.SDK.8
ensure: Present
installBehavior: Silent -
resource: PSDscResources/Environment
id: set-env
directives:
description: Add C:\Tools to PATH for all users
settings:
Name: PATH
Value: “C:\Tools”
Ensure: Present
Path: true
Target: Machine -
resource: PSDscResources/Registry
id: disable-telemetry
directives:
description: Example registry setting
settings:
Key: “HKLM:\Software\Contoso\Build”
Ensure: Present
ValueName: “TelemetryOptOut”
ValueData: 1
ValueType: Dword
Apply it non-interactively:
- winget configure –file .\config.yaml –accept-configuration-agreements
- Validate installs and configuration
- winget list
- git –version
- dotnet –info
- python –version
- node –version
- Handle reboots in CI
Some installers request a reboot. Detect and postpone reboot until the end of a stage:
- $pending = (Get-ItemProperty “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending” -ErrorAction SilentlyContinue) -or (Get-ItemProperty “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired” -ErrorAction SilentlyContinue)
- if ($pending) { Write-Host “Reboot required”; exit 3010 } # or set a pipeline variable
Troubleshooting
-
Hash mismatch or signature validation errors
Cause: TLS inspection, proxy rewriting installers, or corrupted downloads.
Fix: -
Use a trusted network segment or bypass vendor domains from TLS interception.
-
Use local manifests and internal mirrors for fully controlled content.
-
Rebuild the source index:
- winget source reset –name winget –force
- winget source update
-
If necessary, download and install with vendor MSI directly for the affected package.
-
Multiple installers found / ambiguous package
Cause: Package has multiple architectures or locales.
Fix: -
Specify architecture and exact ID:
- winget show –id Git.Git
- winget install –id Git.Git –architecture x64 –exact –source winget –silent –accept-package-agreements –accept-source-agreements
-
Source index errors or slow queries
Fix: -
winget source reset –name winget –force
-
winget source update
-
Clear local cache by removing the source folder while WinGet is closed (advanced; typically not needed).
-
“Package not found” or outdated manifests
Cause: Stale index or package recently updated.
Fix: -
winget source update
-
winget search
-
For a specific version, verify availability via:
- winget show –id
- winget show –id
-
Installer prompts despite –silent
Cause: Some packages ignore default silent parameters.
Fix: -
Add vendor-specific overrides:
- winget install –id Microsoft.VisualStudio.2022.BuildTools –silent –override “–quiet –wait –norestart”
-
Consult winget show –id
for the supported silent switches. -
Non-admin installs where machine scope required
Fix: -
Run elevated:
- Start-Process winget -ArgumentList ‘install –id 7zip.7zip –silent –accept-package-agreements –accept-source-agreements –scope machine’ -Verb RunAs -Wait
-
Upgrade failures during maintenance
Fix: -
winget upgrade –all –silent –include-unknown
-
If a specific package fails, try:
- winget upgrade –id
–silent - winget uninstall –id
&& winget install –id
- winget upgrade –id
Automation Tips
-
PowerShell bootstrap script for new build agents
Use a single script that ensures WinGet, preps sources, installs, pins, and logs: -
$ErrorActionPreference = “Stop”
-
$flags = “–exact –source winget –silent –accept-package-agreements –accept-source-agreements –disable-interactivity”
-
Write-Host “Warming up sources…”
-
winget source update
-
Write-Host “Installing baseline tools…”
-
$packages = @(
-
“Git.Git”,
-
“Microsoft.DotNet.SDK.8”,
-
“Python.Python.3.12”,
-
“OpenJS.NodeJS.LTS”,
-
“7zip.7zip”,
-
“Microsoft.PowerShell”
-
)
-
foreach ($p in $packages) {
-
Write-Host “Installing $p”
-
winget install –id $p $flags
-
}
-
Write-Host “Pinning critical versions…”
-
winget pin add –id Git.Git –version 2.46.*
-
winget pin add –id Microsoft.DotNet.SDK.8 –version 8.0.401
-
Write-Host “Done.”
-
Schedule recurring updates with Task Scheduler
Create a scheduled task that runs: -
winget upgrade –all –silent –accept-package-agreements –accept-source-agreements –include-unknown
Log output to a file for auditing:
-
winget upgrade –all –silent –accept-package-agreements –accept-source-agreements –include-unknown >> “C:\Logs\winget-upgrades.log” 2>&1
-
Azure Pipelines (Windows agents) example
Use a script step to apply your packages and configuration:
steps:
-
task: PowerShell@2
displayName: Provision build agent with WinGet
inputs:
targetType: ‘inline’
script: |
$flags = “–exact –source winget –silent –accept-package-agreements –accept-source-agreements –disable-interactivity”
winget –info
winget source update
winget install –id Git.Git $flags
winget install –id Microsoft.DotNet.SDK.8 $flags
winget install –id Microsoft.VisualStudio.2022.BuildTools $flags –override “–quiet –wait –norestart –nocache –installPath C:\BuildTools –add Microsoft.VisualStudio.Workload.VCTools”
winget pin add –id Git.Git –version 2.46.*
winget pin add –id Microsoft.DotNet.SDK.8 –version 8.0.401 -
GitHub Actions (self-hosted or windows-latest) example
name: Provision Windows Build Agent
on: [push]
jobs:
build:
runs-on: windows-latest
steps:- name: Checkout
uses: actions/checkout@v4 - name: Provision with WinGet
shell: pwsh
run: |
$flags = “–exact –source winget –silent –accept-package-agreements –accept-source-agreements –disable-interactivity”
winget source update
winget install –id Git.Git $flags
winget install –id Microsoft.DotNet.SDK.8 $flags
winget configure –file ..ci\config.yaml –accept-configuration-agreements - name: Validate
shell: pwsh
run: |
git –version
dotnet –info
- name: Checkout
-
Jenkinsfile example
pipeline {
agent any
stages {
stage(‘Provision’) {
steps {
powershell ”’
$flags = “–exact –source winget –silent –accept-package-agreements –accept-source-agreements –disable-interactivity”
winget source update
winget install –id Git.Git $flags
winget install –id Microsoft.DotNet.SDK.8 $flags
”’
}
}
stage(‘Build’) {
steps {
bat ‘dotnet –info’
}
}
}
} -
Intune with WinGet
Microsoft Intune supports Winget-based app deployment. You can: -
Add apps by WinGet ID (e.g., Git.Git) and select required assignments
-
Choose install/uninstall commands (WinGet uses the manifest’s silent switches)
-
Monitor compliance and success per device
This is ideal for keeping dev workstations aligned with your build agents. -
Offline and air-gapped scenarios
Use a curated internal repository: -
Pre-download installers:
- winget download –id Git.Git –version 2.46.0 –download-directory \fileserver\winget-cache
-
Create local manifests referencing those installers:
- winget install –manifest \fileserver\manifests\git\2.46.0.yaml
-
Host your own internal WinGet source or rely on local manifests + signed MSIs.
This provides fully deterministic installs without external internet access.
Best Practices
-
Prefer IDs, not names
Use –id with –exact (e.g., Git.Git) to avoid ambiguous results. -
Silent, non-interactive installs
Always include –silent, –accept-package-agreements, –accept-source-agreements, and –disable-interactivity when running in CI. -
Version pinning
Use winget pin to keep critical tools fixed. Stretch pins with minor wildcards (e.g., 8.0.*) to get security updates while staying within your supported major/minor. -
Capture a reference state
On a known-good agent, run winget export –include-versions and commit packages.json to your repo for traceability. -
Reusable configuration
Use winget configure with YAML to express environment settings, registry keys, features, and packages together. Parameterize for different agent roles. -
Scope and architecture
Prefer –scope machine and –architecture x64 for build agents, when supported by the package. -
Override when necessary
Some installers need explicit switches via –override (Visual Studio Build Tools, some drivers). Document them in your repo’s scripts. -
Validate and log
After provisioning, run a validation step (git –version, dotnet –info) and persist logs (Task Scheduler or pipeline artifacts). For verbose logs, use: -
winget –verbose-logs
-
Keep sources healthy
If you see slow searches or install errors, reset and update sources: -
winget source reset –name winget –force
-
winget source update
-
Security and compliance
If your enterprise uses TLS inspection, bypass vendor CDNs for installer downloads to avoid hash mismatches, or mirror the installers internally and use local manifests.
Conclusion
WinGet gives you a unified, scriptable, and reliable way to provision Windows build agents in CI/CD. From installing toolchains silently to pinning versions and applying configuration via YAML, the Windows Package Manager reduces drift and speeds up onboarding and maintenance. Start by verifying winget –info, warm up sources, install a minimal toolset with machine scope, pin the versions that matter, and export your package list for reuse. With a few well-placed scripts and YAML files, you can make your Windows build agents deterministic, repeatable, and easy to maintain.
FAQ Section
Is WinGet safe for production CI/CD environments?
Yes. WinGet uses signed manifests, validates installer hashes, and honors vendor silent parameters. To make it more deterministic, pin versions, export/import your package list, and use internal mirrors for offline/controlled environments.
How do I force a specific version of a tool?
Use –version when installing, and then pin it:
- winget install –id Git.Git –version 2.46.0 –silent –accept-package-agreements –accept-source-agreements
- winget pin add –id Git.Git –version 2.46.*
What if WinGet says multiple installers are available?
Specify architecture, exact ID, and source:
- winget install –id Git.Git –architecture x64 –exact –source winget –silent –accept-package-agreements –accept-source-agreements
Can I use WinGet without internet access?
Yes, but you need to prepare. Pre-download installers with winget download, create local manifests pointing to the internal file paths, and install with –manifest. Alternatively, host an internal WinGet source or package repository.
How do I keep agents updated without breaking builds?
Schedule winget upgrade –all during maintenance windows, and use winget pin to lock critical build dependencies. Regularly review updates in a staging environment before rolling them to production agents.
