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.