Windows Package Manager on Windows Server 2022

Windows Package Manager, commonly referred to as winget, is Microsoft’s official command-line package manager introduced with Windows 10 1809 and progressively improved in subsequent releases. On Windows Server 2022, winget provides a straightforward way to install, upgrade, and remove applications from Microsoft’s curated package catalog without manually downloading installers or writing custom PowerShell scripts for each tool. This guide covers getting winget working on Server 2022, using it for day-to-day package management, scripting deployments, integrating with DSC through winget configure, and understanding its limitations in enterprise scenarios.

Installing App Installer on Windows Server 2022

Unlike Windows 10 and 11, Windows Server 2022 does not include winget (packaged as App Installer) by default. There are two installation paths: the Microsoft Store and a manual sideload.

Via Microsoft Store (if Store is available):

# Open the Microsoft Store and search for "App Installer", or use this direct link in a browser:
# ms-windows-store://pdp/?productid=9NBLGGH4NNS1

Via sideload (recommended for Server Core or environments without Store access):

# Download the latest App Installer MSIX bundle
# Check https://github.com/microsoft/winget-cli/releases for the latest version
$wingetVersion = "1.8.1911"
$downloadUrl = "https://github.com/microsoft/winget-cli/releases/download/v$wingetVersion/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
$outputPath = "$env:TEMPAppInstaller.msixbundle"

Invoke-WebRequest -Uri $downloadUrl -OutFile $outputPath

# Install dependencies first (Microsoft.VCLibs and Microsoft.UI.Xaml)
$vcLibsUrl = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"
$vcLibsPath = "$env:TEMPVCLibs.appx"
Invoke-WebRequest -Uri $vcLibsUrl -OutFile $vcLibsPath
Add-AppxPackage -Path $vcLibsPath

# Install the App Installer bundle
Add-AppxPackage -Path $outputPath

After installation, verify winget is available. You may need to open a new terminal session for the PATH to update:

winget --version
# v1.8.1911

On a fresh Server 2022 installation, the first run of winget will prompt you to agree to the Microsoft Store source terms. Accept them to enable the full catalog:

winget source update
# Updating all sources...
# Done

Searching for Packages

The winget search command queries the configured sources and returns matching package IDs, versions, and sources:

# Search by name
winget search git

# Search for an exact package ID
winget search --id Git.Git --exact

# Show detailed information about a package
winget show --id Git.Git

# List all available versions of a package
winget show --id Git.Git --versions

The package ID is the canonical identifier used in all winget commands. Always use the ID (e.g., Git.Git, Microsoft.VisualStudioCode) rather than the display name to avoid ambiguity and ensure consistent behavior in scripts.

Installing, Upgrading, and Uninstalling Packages

Basic package operations are straightforward:

# Install a package
winget install --id Git.Git -e --source winget

# Install silently without UI prompts
winget install --id Git.Git -e --silent

# Install a specific version
winget install --id Microsoft.DotNet.SDK.8 --version 8.0.301 -e

# Accept license agreements automatically (useful in scripts)
winget install --id 7zip.7zip -e --accept-package-agreements --accept-source-agreements

# Upgrade a specific package
winget upgrade --id Git.Git -e

# Upgrade all installed packages to their latest versions
winget upgrade --all --accept-package-agreements --accept-source-agreements

# Uninstall a package
winget uninstall --id Git.Git -e

List all currently installed packages that winget can see (including packages not installed via winget):

winget list

# Filter installed packages
winget list --name "Microsoft"

# Check which installed packages have available upgrades
winget upgrade

Exporting and Importing Package Manifests

One of the most powerful winget features for infrastructure-as-code is the ability to export all installed packages to a JSON file and then replay that installation on another machine. This enables reproducible server builds and baseline configurations.

Export the current machine’s packages:

winget export -o C:Configspackages.json --accept-source-agreements
# Export succeeded. Exported 47 packages.

The output JSON looks like this:

{
  "$schema": "https://aka.ms/winget-packages.schema.2.0.json",
  "CreationDate": "2024-05-17T10:00:00.000-00:00",
  "Sources": [
    {
      "Packages": [
        { "PackageIdentifier": "Git.Git" },
        { "PackageIdentifier": "Microsoft.VisualStudioCode" },
        { "PackageIdentifier": "7zip.7zip" },
        { "PackageIdentifier": "Microsoft.DotNet.SDK.8" }
      ],
      "SourceDetails": {
        "Argument": "https://cdn.winget.microsoft.com/cache",
        "Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
        "Name": "winget",
        "Type": "Microsoft.PreIndexed.Package"
      }
    }
  ],
  "WinGetVersion": "1.8.1911"
}

Import and install all packages on a new machine:

winget import -i C:Configspackages.json --accept-package-agreements --accept-source-agreements
# Verifying packages...
# Installing Git.Git...
# Installing Microsoft.VisualStudioCode...
# [continues for all packages]

You can commit the packages.json file to a Git repository alongside other infrastructure configuration. When provisioning a new server, clone the repo and run winget import as part of the setup script.

Adding Custom and Private Winget Sources

Winget supports custom sources, allowing enterprises to host a private package catalog. A private winget source is a REST API endpoint or a pre-indexed package folder following the winget source schema. The WinGet REST Source project on GitHub provides a reference implementation.

# Add a private source
winget source add --name "internal" --arg "https://winget.example.com/api/v2" --type "Microsoft.Rest"

# List configured sources
winget source list

# Remove a source
winget source remove --name "msstore"

# Reset sources to defaults
winget source reset --force

For simpler internal distribution, you can serve a local folder of MSIX/EXE packages paired with YAML manifests by setting up a pre-indexed source. The wingetcreate tool helps create manifests for existing installers:

winget install Microsoft.WingetCreate -e
wingetcreate new https://internal.example.com/installers/MyApp-1.0.0.exe

Winget Configure: DSC Integration

Winget 1.4 and later includes the winget configure command, which applies a Desired State Configuration file written in YAML. This integrates with PowerShell DSC v3 resources (via the WinGet DSC module) and allows you to declare not just packages but also Windows features, registry settings, and custom scripts in a single configuration document:

# dev-environment.dsc.yaml
properties:
  configurationVersion: 0.2.0
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: install-git
      directives:
        description: Install Git for Windows
        allowPrerelease: false
      settings:
        id: Git.Git
        source: winget

    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: install-vscode
      directives:
        description: Install Visual Studio Code
      settings:
        id: Microsoft.VisualStudioCode
        source: winget

    - resource: PSDscResources/WindowsFeature
      id: enable-iis
      directives:
        description: Enable IIS Web Server role
      settings:
        Name: Web-Server
        Ensure: Present

Apply the configuration:

winget configure --file dev-environment.dsc.yaml --accept-configuration-agreements

# Or apply without interactive prompts for CI/CD use
winget configure --file dev-environment.dsc.yaml --accept-configuration-agreements --disable-interactivity

Validate the file syntax before applying:

winget configure validate --file dev-environment.dsc.yaml

Scripting Winget in PowerShell

Because winget is a console application, PowerShell scripts call it directly and check $LASTEXITCODE for success or failure. A reusable function that wraps winget installs with error handling:

function Install-WingetPackage {
    param(
        [Parameter(Mandatory)][string]$PackageId,
        [string]$Version,
        [string]$Source = "winget"
    )

    $args = @(
        "install",
        "--id", $PackageId,
        "--exact",
        "--source", $Source,
        "--accept-package-agreements",
        "--accept-source-agreements",
        "--silent"
    )

    if ($Version) {
        $args += @("--version", $Version)
    }

    Write-Host "Installing $PackageId..."
    & winget @args

    if ($LASTEXITCODE -eq 0) {
        Write-Host "$PackageId installed successfully."
    } elseif ($LASTEXITCODE -eq -1978335189) {
        # 0x8A150015 — package already installed
        Write-Host "$PackageId is already installed. Skipping."
    } else {
        throw "winget install failed for $PackageId. Exit code: $LASTEXITCODE"
    }
}

# Batch install baseline tools
$packages = @(
    "Git.Git",
    "Microsoft.DotNet.SDK.8",
    "Microsoft.VisualStudioCode",
    "7zip.7zip",
    "Notepad++.Notepad++"
)

foreach ($pkg in $packages) {
    Install-WingetPackage -PackageId $pkg
}

Winget in Unattended Server Setup

For automating software installation during initial server provisioning, place winget calls in a setup script that runs as part of a Sysprep answer file, a VM customization script, or an Azure VM extension. The key requirement for unattended use is suppressing all interactive prompts:

# server-setup.ps1 — run as SYSTEM or Administrator during provisioning
param([string]$ConfigFile = "C:Setuppackages.json")

# Ensure winget sources are up to date
winget source update --accept-source-agreements 2>&1 | Out-Null

# Import baseline packages
if (Test-Path $ConfigFile) {
    winget import -i $ConfigFile `
        --accept-package-agreements `
        --accept-source-agreements `
        --ignore-unavailable
    if ($LASTEXITCODE -ne 0) {
        Write-Warning "Some packages may not have installed correctly. Exit code: $LASTEXITCODE"
    }
} else {
    Write-Error "Package manifest not found: $ConfigFile"
    exit 1
}

Write-Host "Server software setup complete."

The --ignore-unavailable flag tells winget to continue installing available packages even if some package IDs in the manifest cannot be found in the configured sources, which prevents a single missing package from blocking the rest of the installation.

Limitations of Winget in Enterprise Environments

Winget has several important limitations that affect its suitability as an enterprise package management solution:

No approval workflow: Winget installs whatever version is in the catalog without approval gates or version pinning enforcement. In a regulated environment this is a compliance risk without additional controls.

Limited offline support: Winget primarily fetches installers from the internet at install time. There is no built-in mechanism to cache or pre-stage installers for air-gapped environments, unlike Chocolatey’s --use-package-parameters-cacheLocation or internal feeds.

No central management dashboard: There is no enterprise console for reporting which machines have which versions installed, scheduling upgrades across a fleet, or viewing compliance status. Managing hundreds of machines requires additional tooling (SCCM, Intune, Ansible, or custom scripts).

SYSTEM account limitations: Winget installs packages for the current user’s context by default. Some packages require the --scope machine flag for system-wide installation, but not all packages support machine scope:

winget install --id Git.Git --scope machine --silent

Catalog coverage: The winget community catalog covers thousands of popular applications, but many enterprise-internal or niche applications will not be present. You must create and host your own manifests for those, which adds operational overhead.

No dependency management: Unlike apt or yum on Linux, winget has no dependency resolution. If package A requires package B, you must install them in the correct order manually or in your script.

For many Windows Server 2022 use cases — developer workstations, build agents, and simple application servers — winget’s simplicity and Microsoft support make it an excellent choice. For organizations that need policy enforcement, offline operation, internal package hosting, and fleet-wide reporting, Chocolatey for Business or SCCM/Configuration Manager remains the more capable solution.