WinGet Guides

WinGet in CI/CD: Provision Windows Build Agents the Right Way

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 –silent Install package silently Successfully installed
winget install –id –version Install a specific version (if available) Installed v
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 –version 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:

  1. Download App Installer (MSIX), VCLibs, and UI.Xaml dependencies from the official sources or the winget-cli GitHub releases page.
  2. 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
See also  The Ultimate WinGet Provisioning Script: From Bare Metal to Ready in 15 Minutes

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.

  1. 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.

  1. 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.

  1. 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.
  1. 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
  1. 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”
}
]
}

  1. 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.

  1. 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

See also  Export/Import Your App List with WinGet (and Keep It Clean)

Apply it non-interactively:

  • winget configure –file .\config.yaml –accept-configuration-agreements
  1. Validate installs and configuration
  • winget list
  • git –version
  • dotnet –info
  • python –version
  • node –version
  1. 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
  • 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

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
  • 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.

See also  Handling Winget Install Conflicts and Silent Switches Like a Pro

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.

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).