How to Use Windows Package Manager (winget) on Windows Server 2025
Windows Package Manager — winget — is Microsoft’s first-party command-line package manager, built into Windows 10/11 and now available on Windows Server 2025. Unlike third-party tools such as Chocolatey, winget is an inbox component that ships with the App Installer package, requires no separate licence, and integrates directly with the Windows Package Manager Community Repository (a curated catalogue of over 6,000 packages). For server administrators, winget offers a lightweight, scriptable alternative for software installation, system inventory, and machine-rebuild workflows, with the added ability to create custom YAML manifests for internal applications and use import/export JSON files to replicate an exact software loadout across multiple servers. This guide covers every major winget feature relevant to Windows Server 2025 administration.
Prerequisites
- Windows Server 2025 (winget ships with the OS; verify with
winget --version) - PowerShell 7.4 or later for scripting
- App Installer 1.22 or later (the package that provides winget) — update via Microsoft Store or offline import
- Internet access to the Windows Package Manager Community Repository, or an internal repository configured via winget settings
- Administrator rights for system-wide package installation
Step 1: Installing winget on Windows Server 2025
Windows Server 2025 includes winget via the App Installer package. In most deployments it will already be present, but if you are working with a minimal/Core installation, or need to deploy winget to air-gapped servers, you must import the App Installer MSIX bundle and its dependencies manually.
# Check if winget is already installed
try {
$wingetVersion = winget --version
Write-Host "winget is installed: $wingetVersion"
}
catch {
Write-Host "winget not found. Installing via App Installer package..."
}
# --- Online update (internet-connected server) ---
# winget itself can update its hosting package
# winget upgrade --id Microsoft.AppInstaller --silent --accept-package-agreements
# --- Offline install for air-gapped servers ---
# Download these files on an internet-connected machine and copy to the air-gapped server:
# 1. Microsoft.UI.Xaml MSIX (dependency)
# https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx
# 2. App Installer MSIXBUNDLE
# https://aka.ms/getwinget
$dependencies = @(
'C:offlineMicrosoft.VCLibs.x64.14.00.Desktop.appx',
'C:offlineMicrosoft.UI.Xaml.2.8_x64.appx'
)
$appInstaller = 'C:offlineMicrosoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle'
# Install dependencies first
foreach ($dep in $dependencies) {
Add-AppxPackage -Path $dep -ErrorAction SilentlyContinue
}
# Install App Installer (provides winget)
Add-AppxPackage -Path $appInstaller
# Reload PATH and verify
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + `
"$env:LOCALAPPDATAMicrosoftWindowsApps"
winget --version
Step 2: Searching for Packages
The winget search command queries the configured repositories for packages matching a name, keyword, or package ID. The package ID (e.g., Git.Git) is the stable identifier used in scripts; display names can change between versions.
# Search by name
winget search "Visual Studio Code"
# Search by ID (exact or partial)
winget search --id Git.Git
# Search in a specific source
winget search --id Notepad++ --source winget
# Output as JSON for scripting
winget search --id git --output json | ConvertFrom-Json | Select-Object -ExpandProperty Packages
# Find available versions of a specific package
winget show --id Git.Git --versions
Step 3: Installing Packages Silently
For server automation, always use the --silent flag (no UI), --accept-package-agreements (accept licences non-interactively), and --accept-source-agreements (accept source terms). Specify the exact package ID with --id to avoid ambiguity when multiple packages match a search term.
# Install a single package silently
winget install --id Git.Git --silent --accept-package-agreements --accept-source-agreements
# Install a specific version
winget install --id Git.Git --version 2.47.1 --silent --accept-package-agreements
# Install to a custom location (not all packages support this)
winget install --id Notepad++.Notepad++ --silent --accept-package-agreements `
--location "C:ToolsNotepadPP"
# Install multiple packages in sequence (PowerShell loop)
$packages = @(
'Git.Git',
'Notepad++.Notepad++',
'7zip.7zip',
'Curl.Curl',
'Microsoft.PowerShell',
'Microsoft.DotNet.Runtime.8'
)
foreach ($pkg in $packages) {
Write-Host "Installing $pkg..."
winget install --id $pkg --silent --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -notin @(0, -1978335189)) { # -1978335189 = already installed
Write-Warning "winget install '$pkg' returned exit code $LASTEXITCODE"
}
}
Step 4: Upgrading Packages
The winget upgrade command checks installed packages against the repository and installs newer versions. This is particularly useful for security patching — you can run a nightly scheduled task that upgrades all packages.
# List all packages with available upgrades
winget upgrade
# Upgrade a specific package
winget upgrade --id Git.Git --silent --accept-package-agreements
# Upgrade ALL installed packages silently (use with caution on production servers)
winget upgrade --all --silent --accept-package-agreements --accept-source-agreements
# Scheduled task: nightly upgrade of security-critical packages
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' -Argument @'
-NonInteractive -Command "winget upgrade --id Git.Git --silent --accept-package-agreements --accept-source-agreements; winget upgrade --id 7zip.7zip --silent --accept-package-agreements"
'@
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 1)
Register-ScheduledTask -TaskName 'winget-nightly-upgrade' `
-Action $action -Trigger $trigger -Settings $settings `
-RunLevel Highest -User 'SYSTEM' -Force
Write-Host "Scheduled nightly winget upgrade task registered."
Step 5: Listing and Inspecting Installed Packages
The winget list command shows all installed software — not just packages installed via winget, but also traditionally installed applications sourced from the Windows registry. This makes it a powerful inventory tool.
# List all installed packages
winget list
# Filter by name
winget list --name git
# Output as JSON and parse with PowerShell
$installed = winget list --output json | ConvertFrom-Json
$installed.Sources | ForEach-Object {
$_.Packages | Select-Object Id, Name, Version, AvailableVersion
}
# Get detailed information about a specific package
winget show --id Git.Git
# Check if a specific package is installed
$gitInstalled = winget list --id Git.Git 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "Git is installed."
} else {
Write-Host "Git is not installed."
}
Step 6: Creating winget Manifests for Internal Applications
To distribute internal applications through winget, you create a YAML manifest that describes the installer. Manifests follow the Windows Package Manager Community Repository schema and can be hosted in a local repository pointed to by a custom winget source. A complete manifest is split into several YAML files, but the single-file version works well for internal use.
# MyCompany.InternalApp.yaml
# Save as: manifests/m/MyCompany/InternalApp/3.5.1/MyCompany.InternalApp.yaml
PackageIdentifier: MyCompany.InternalApp
PackageVersion: 3.5.1
PackageName: MyCompany Internal Application
Publisher: MyCompany Ltd
License: Proprietary
ShortDescription: Internal line-of-business application for MyCompany.
Description: |-
The MyCompany Internal Application provides order management, customer
lookup, and reporting for all MyCompany branch offices.
Tags:
- mycompany
- internal
- business
Installers:
- Architecture: x64
InstallerType: exe
InstallerUrl: https://artifacts.example.com/internalapp/3.5.1/InternalApp-3.5.1-x64.exe
InstallerSha256: ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890
InstallerSwitches:
Silent: /S
SilentWithProgress: /S
InstallLocation: /D=<INSTALLPATH>
ExpectedReturnCodes:
- InstallerReturnCode: 3010
ReturnResponse: softReboot
ManifestType: singleton
ManifestVersion: 1.6.0
# Add a local/internal winget source pointing to a folder of manifests
# The source folder must follow the manifests/letter/Publisher/App/Version/ structure
$internalSourcePath = 'https://wpm.example.com/winget' # or a UNC path / local path
winget source add --name 'mycompany' --arg $internalSourcePath --type 'Microsoft.Rest'
# For a file-system source (folder):
winget source add --name 'mycompany-local' `
--arg 'C:WingetRepo' `
--type 'Microsoft.PreIndexed.Package'
# Validate your manifest before deploying
winget validate --manifest 'C:WingetRepomanifestsmMyCompanyInternalApp3.5.1MyCompany.InternalApp.yaml'
# Install from the internal source
winget install --id MyCompany.InternalApp --source mycompany --silent --accept-package-agreements
Step 7: Exporting and Importing Package Lists for Machine Rebuild
The winget export command creates a JSON file describing all currently installed packages and their versions. The complementary winget import command reads that JSON and installs all listed packages, making it possible to rebuild a server to an identical software state after a fresh OS installation.
# Export current machine's package list
winget export --output 'C:backupwinget-packages.json' --include-versions
# The resulting JSON looks like:
# {
# "$schema": "https://aka.ms/winget-packages.schema.2.0.json",
# "CreationDate": "2026-05-17T...",
# "Sources": [
# {
# "Packages": [
# { "PackageIdentifier": "Git.Git", "Version": "2.47.1" },
# { "PackageIdentifier": "Notepad++.Notepad++", "Version": "8.7.1" }
# ],
# "SourceDetails": { "Argument": "...", "Identifier": "...", "Name": "winget", "Type": "..." }
# }
# ],
# "WinGetVersion": "1.9.25200"
# }
# Copy winget-packages.json to the new server, then import
winget import --import-file 'C:backupwinget-packages.json' `
--accept-package-agreements --accept-source-agreements --no-upgrade
# To ignore packages not available in the current sources:
winget import --import-file 'C:backupwinget-packages.json' `
--accept-package-agreements --accept-source-agreements --ignore-unavailable
# Automate export as part of a daily backup scheduled task
$exportAction = New-ScheduledTaskAction -Execute 'winget.exe' `
-Argument 'export --output C:backupwinget-snapshot.json --include-versions'
Register-ScheduledTask -TaskName 'winget-daily-export' `
-Action $exportAction `
-Trigger (New-ScheduledTaskTrigger -Daily -At '02:00') `
-User 'SYSTEM' -RunLevel Highest -Force
Step 8: winget vs Chocolatey — When to Use Each
Both winget and Chocolatey are legitimate, production-ready tools for Windows package management, but they have different strengths. Understanding when to reach for each one helps you build a coherent automation strategy rather than fighting the tool.
- Use winget when: you want a zero-dependency, Microsoft-maintained tool already present on Windows Server 2025; you are managing a small to medium fleet without a dedicated package server; you want to audit installed software across all registry sources (not just Chocolatey-managed packages); or you need to create manifests for internal apps without setting up a NuGet feed infrastructure.
- Use Chocolatey when: you need PowerShell DSC integration (
cChoco); you want a mature ecosystem of community-maintained install scripts with silent-install logic already handled; you need an internal NuGet feed with package internalisation (air-gapped); or you need rollback and package pinning features that winget does not yet support. - Use both together: winget for initial bootstrapping and OS-level software, Chocolatey for enterprise application management with DSC drift correction. They coexist on the same machine without conflict.
# Side-by-side example: use winget for simple installs, choco for DSC-managed packages
# Bootstrap a new Windows Server 2025 node
# Step 1 — winget: fast baseline installs
winget install --id Git.Git --silent --accept-package-agreements
winget install --id Microsoft.PowerShell --silent --accept-package-agreements
winget install --id 7zip.7zip --silent --accept-package-agreements
# Step 2 — Chocolatey: DSC-managed application stack
Set-ExecutionPolicy Bypass -Scope Process -Force
Invoke-Expression ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# Apply DSC configuration that installs and enforces application versions
Start-DscConfiguration -Path 'C:DSCWebServerBaseline' -Wait -Verbose -Force
Windows Package Manager winget represents a significant step forward in Microsoft’s commitment to scriptable, repeatable Windows administration. On Windows Server 2025, it is ready for production use as an installation, upgrade, and inventory tool, and its manifest system makes it viable for internal application distribution without requiring a full NuGet infrastructure. Combined with import/export for machine rebuild scenarios and a scheduled upgrade task for security patching, winget gives server administrators a native, lightweight package management capability that complements — and in many scenarios replaces — the need for third-party tools. Start with winget for your baseline tooling and evaluate Chocolatey for the DSC integration and internal feed capabilities that winget does not yet match.