If you run Windows at scale or you simply want a rock‑solid workstation, keeping apps on a specific version is critical. The Windows Package Manager — better known as winget — makes this possible through versioned installs and package pinning. In this guide you’ll learn, step by step, how to pin app versions with winget to create stable, repeatable environments. You’ll get practical examples, ready‑to‑use commands, and complete scripts you can drop into automation pipelines or Intune.
You’ll learn how to:
- Install a specific package version with winget
- Pin that version so routine upgrades won’t break your baseline
- Export/import your environment and use YAML WinGet Configuration for declarative version locks
- Troubleshoot common pinning and installation errors
- Automate the entire flow for fresh installs, bulk updates, CI/CD, and Intune deployments
Overview of the Use Case
What does “pin app versions with winget” mean?
Pinning tells winget to hold a package at a chosen version and ignore upgrades unless you explicitly allow them. That way, routine winget upgrade commands won’t unexpectedly bump versions and break dependencies, scripts, or compliance requirements.
In plain language: pinning is a version lock. Once you pin an app, winget won’t upgrade it during bulk updates unless you override the pin.
When do you need version pinning?
- Fresh OS or device enrollments: Install a known‑good baseline of apps at fixed versions.
- Staged rollouts: QA pins a version; production devices follow after testing.
- CI/CD runners and build agents: Lock toolchains (e.g., Git, Node.js, Python) to prevent “works yesterday, breaks today.”
- Highly regulated or validated environments: Maintain documented versions for audits and change control.
- Labs, VDI, or classroom machines: Keep classroom software consistent across devices and semesters.
- Air‑gapped/offline or bandwidth‑constrained sites: Precache installers and keep versions stable to reduce churn.
Quick Reference Table
| Command | Purpose | Example Output |
|---|---|---|
| winget –info | Show winget version and environment details | Windows Package Manager v1.6.x; Logs: C:\Users…\DiagOutputDir |
| winget search |
Find a package ID and source | Name Id Version Source; Git Git.Git 2.44.0 winget |
| winget show –id |
Show details, including available versions | Versions: 2.44.0; 2.43.0; 2.42.0 |
| winget install –id |
Install a specific version | Successfully installed |
| winget pin add –id |
Pin a package to a version | Added pin: Git.Git = 2.44.0 |
| winget pin list | List all pinned packages | Name Id Pinned Version; Git Git.Git 2.44.0 |
| winget pin remove –id |
Remove a pin | Removed pin: Git.Git |
| winget upgrade | List available upgrades (excludes pinned) | Name Id Version Available Source |
| winget upgrade –all | Upgrade everything (excludes pinned) | Upgraded 5 of 5 packages (0 pinned skipped) |
| winget upgrade –include-pinned –id |
Override pin for one upgrade | Upgraded Git.Git from 2.44.0 to 2.45.1 |
| winget source reset –force | Fix source index issues | Successfully reset all sources |
| winget export -o packages.json –include-versions | Export installed packages with versions | Wrote packages.json |
Note: Output varies by version and package; the samples are indicative.
Key Concepts and Prerequisites
What you need
- A recent version of the Windows Package Manager (winget). Package pinning requires a modern release (1.6+ recommended).
- PowerShell 5.1+ or PowerShell 7+ for scripting and automation.
- Administrator rights for system‑level installs (some packages require elevation).
- Network access to your sources (e.g., winget community repo, Microsoft Store, or your internal source).
How to check your winget version
Run:
winget –info
Look for:
- Windows Package Manager vX.Y.Z
- Logs path (helpful for troubleshooting)
- Sources (e.g., winget, msstore)
If winget says the pin command is unknown, update the App Installer from the Microsoft Store or install the latest winget package from Microsoft and retry.
Sources and scope considerations
- winget source “winget” (Community): Most EXE/MSI packages, supports versioned installs and pinning.
- winget source “msstore” (Microsoft Store): Apps often auto‑update via Store; version pinning is limited or not applicable.
- Scope matters: Use –scope machine for all‑users installs and –scope user for per‑user installs, depending on the package.
Where pins live
Pins are stored per user on the local machine by winget. They affect winget’s behavior for that user unless you run winget as another user or in a system context. In shared or multi‑user systems, set pins in the context that will perform upgrades.
Step-by-Step Guide
1) Identify the correct package ID
Use search to find the canonical package ID:
winget search git
If there are many results or name collisions, filter and use exact matches:
winget search –source winget –name Git
winget show –id Git.Git
From winget show, confirm architecture, installer type, and available versions.
Tips:
- Prefer –id with -e/–exact to avoid ambiguity.
- Check architecture: add –architecture x64 if needed.
- If multiple sources exist, specify –source winget.
2) Install a specific version (versioned install)
To install Git 2.44.0 silently for all users:
winget install –id Git.Git –version 2.44.0 –scope machine –silent –source winget –accept-package-agreements –accept-source-agreements
Notes:
-
–silent uses the manifest’s silent switches (recommended for automation).
-
Some packages accept –override to pass custom installer switches, e.g.:
winget install –id Some.App –version 1.2.3 –override “/quiet /norestart”
-
If the version is not found, verify it is listed in winget show and that you’re targeting the correct source and architecture.
3) Pin the installed version
Once the app is installed, pin it to the version you want:
winget pin add –id Git.Git –version 2.44.0
Verify:
winget pin list
You should see the package and the pinned version.
Notes:
-
Pinning without specifying a version typically pins the currently installed version; specifying the version is explicit and safer.
-
Use -e/–exact to avoid pinning the wrong package when names are similar:
winget pin add –id Git.Git -e –version 2.44.0
4) Run upgrades safely (pinned packages are skipped)
Routine upgrades will now skip pinned packages:
winget upgrade
Or bulk upgrade:
winget upgrade –all
Pinned packages will be listed as skipped or simply not shown in the upgrade list, depending on your winget version.
5) Temporarily override the pin for a one‑time upgrade
You have two main options:
-
Override pin in a one‑off command:
winget upgrade –id Git.Git –include-pinned
-
Remove pin, upgrade, re‑pin:
winget pin remove –id Git.Git
winget upgrade –id Git.Git
winget pin add –id Git.Git –version
6) Bulk pinning for a baseline
Pin multiple packages at once with a simple PowerShell loop:
$baseline = @{
“Git.Git” = “2.44.0”
“Microsoft.VisualStudioCode” = “1.91.0”
“Python.Python.3.11” = “3.11.9”
“OpenJS.NodeJS.LTS” = “18.20.3”
}
foreach ($id in $baseline.Keys) {
$ver = $baseline[$id]
Install exact version if missing
if (-not (winget list --id $id | Select-String $ver)) {
winget install --id $id --version $ver --source winget --silent --accept-package-agreements --accept-source-agreements
}
# Pin the version
winget pin add --id $id --version $ver
}
winget pin list
7) Export your installed packages (with versions)
Create a versioned inventory:
winget export -o .\packages.json –include-versions
Sample packages.json (truncated):
{
“CreationDate”: “2025-10-10T10:05:12.345Z”,
“Sources”: [
{
“Packages”: [
{ “Id”: “Git.Git”, “Version”: “2.44.0” },
{ “Id”: “Microsoft.VisualStudioCode”, “Version”: “1.91.0” }
],
“SourceDetails”: { “Argument”: “https://cdn.winget.microsoft.com/cache“, “Identifier”: “Microsoft.Winget.Source_8wekyb3d8bbwe” }
}
]
}
Import on another machine:
winget import -i .\packages.json –accept-source-agreements –accept-package-agreements
Note: Import installs versions but does not itself create pins. After import, run pin commands or use Configuration (next step).
8) Use YAML WinGet Configuration to declaratively lock versions
WinGet Configuration lets you define a desired state in a YAML file and apply it repeatably. It’s a powerful complement to pinning.
Sample config.yaml:
WinGet Configuration: lock specific app versions
Apply with: winget configure -f .\config.yaml –accept-configuration-agreements
properties:
configurationVersion: 0.2.0
resources:
-
resource: Microsoft.WinGet.DSC/WinGetPackage
id: Git
directives:
description: Install Git 2.44.0 silently (machine scope)
settings:
id: Git.Git
version: 2.44.0
source: winget
scope: machine
installMode: silent -
resource: Microsoft.WinGet.DSC/WinGetPackage
id: VSCode
settings:
id: Microsoft.VisualStudioCode
version: 1.91.0
source: winget
installMode: silent -
resource: Microsoft.WinGet.DSC/WinGetPackage
id: Python311
settings:
id: Python.Python.3.11
version: 3.11.9
source: winget
scope: machine
installMode: silent
Apply it:
winget configure -f .\config.yaml –accept-configuration-agreements
Notes:
- Configuration enforces the specified versions when you apply it.
- It does not automatically create pins, but it’s a declarative way to maintain version locks; rerun the configuration to reconcile drift.
- Keep your YAML under version control and code review changes before rolling to production.
9) Optional: Prefetch installers for offline or slow networks
If you need offline installs, pre‑download installers to a cache share:
winget download –id Git.Git –version 2.44.0 -o \fileserver\cache\git-2.44.0
Repeat for each package/version. You can then install directly from the cached installers using their native silent switches, or set up an internal winget source. For fully offline winget usage, consider hosting a private REST source and pushing manifests with wingetcreate.
10) Housekeeping: list, remove, and reset pins
-
List pins:
winget pin list
-
Remove a specific pin:
winget pin remove –id Git.Git
-
Reset all pins (use cautiously):
winget pin reset
Troubleshooting
Common issues and exact fixes
-
Problem: “Installer hash mismatch”
-
Cause: Manifest updated, cached installer changed, or corrupted download.
-
Fix:
winget source update
winget upgrade –id–version –source winget Clear cache if needed (path from winget –info logs) or re‑download:
winget download –id
–version -o C:\Temp\WingetCache\ \
-
-
Problem: “Multiple installers found” or ambiguous package/name
-
Fix: Use exact ID, architecture, and source:
winget install –id
-e –architecture x64 –source winget
winget show –id-e
-
-
Problem: “No applicable installer found” for the requested version
-
Fix: Confirm the version exists for your architecture/scope:
winget show –id
| more Try a different version or architecture:
winget install –id
–version –architecture x64
-
-
Problem: “Source index is corrupt” or update failures
-
Fix:
winget source reset –force
winget source update
-
-
Problem: Pinned package still upgrades
-
Fix: Ensure you didn’t pass –include-pinned. Verify pin with:
winget pin list
If the ID differs (Name vs Id mismatch), re‑pin with exact ID:
winget pin add –id <Exact.Id> -e –version
-
-
Problem: Admin/UAC prompts block automation
-
Fix: Run the shell elevated or launch an elevated session:
Start-Process PowerShell -Verb RunAs
In automation, run as SYSTEM or use a scheduled task set to “Run with highest privileges.”
-
-
Problem: Microsoft Store apps ignore pins or versions
- Fix: Store apps typically auto‑update via Store. Prefer winget “winget” source packages for pinning, or use policy to control Store updates.
Automation Tips
Automate installs and pins with PowerShell
This script ensures specific versions are installed and pinned, and logs everything:
Baseline versions
$baseline = @{
“Git.Git” = “2.44.0”
“Microsoft.VisualStudioCode” = “1.91.0”
“Python.Python.3.11” = “3.11.9”
}
$log = “C:\ProgramData\WingetBaseline\baseline-$(Get-Date -Format yyyyMMdd-HHmmss).log”
New-Item -ItemType Directory -Force -Path (Split-Path $log) | Out-Null
“Starting baseline at $(Get-Date)” | Tee-Object -FilePath $log -Append
foreach ($id in $baseline.Keys) {
$ver = $baseline[$id]
“Processing $id $ver” | Tee-Object -FilePath $log -Append
$installed = winget list --id $id | Select-String $ver
if (-not $installed) {
winget install --id $id --version $ver --source winget --silent --accept-package-agreements --accept-source-agreements `
| Tee-Object -FilePath $log -Append
}
winget pin add --id $id --version $ver `
| Tee-Object -FilePath $log -Append
}
“Pins:” | Tee-Object -FilePath $log -Append
winget pin list | Tee-Object -FilePath $log -Append
“Completed baseline at $(Get-Date)” | Tee-Object -FilePath $log -Append
Schedule it with Task Scheduler (system context, at startup)
Create a scheduled task that runs as SYSTEM with highest privileges:
$action = New-ScheduledTaskAction -Execute “powershell.exe” -Argument “-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Apply-Baseline.ps1”
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId “SYSTEM” -RunLevel Highest
Register-ScheduledTask -TaskName “Winget Baseline” -Action $action -Trigger $trigger -Principal $principal
Use in Intune
-
Option 1: Use “Microsoft Store app (new)” and/or WinGet integration for app deployment, then run a device or user PowerShell script to pin versions:
- Deploy your apps (winget source packages where possible).
- Assign a PowerShell script with the pin commands to target groups.
-
Option 2: Use WinGet Configuration:
-
Store config.yaml in a secure repo.
-
Deploy a script that downloads config.yaml locally and runs:
winget configure -f C:\Path\config.yaml –accept-configuration-agreements
-
CI/CD (build agents, self‑hosted runners)
- Pre‑stage your toolchain via a versioned install script and pin it.
- Run with –silent, –accept-package-agreements, –accept-source-agreements to prevent prompts.
- Cache installers with winget download in the pipeline cache to speed up builds.
- If the agent image is ephemeral, run the script at provision time.
Offline or bandwidth‑constrained sites
- Pre‑download installers with winget download to a central share.
- For full offline repeatability, host an internal winget REST source and publish vetted manifests/versions.
- Pin installed versions to avoid unexpected updates when the network link is restored.
Best Practices
-
Prefer IDs over names
- Use –id with -e/–exact to target the correct package every time.
-
Separate install and pin steps
- Install the exact version you want, then pin it. This makes your intent explicit.
-
Keep a versioned baseline manifest
- Maintain a baseline (JSON via winget export or a YAML WinGet Configuration). Store in version control, review changes, and tag releases.
-
Use silent installs in automation
- Favor –silent and provide –accept-package-agreements and –accept-source-agreements to avoid prompts.
-
Verify architecture and scope
- Always specify –architecture and –scope when repeatability matters across machines.
-
Don’t pin everything forever
- Pin only what you must keep stable. Regularly update and re‑pin to pick up security fixes.
-
Test pins and upgrades in a staging ring
- Trial upgrades, then promote versions to production by updating your pins/baseline.
-
Document exceptions
- Microsoft Store apps often don’t honor pinning. Use policies or replace them with winget community packages where feasible.
-
Monitor logs
- Check the log path from winget –info for install/pinning failures, especially when running at scale.
Conclusion
Pinning app versions with winget is a simple, robust way to build and maintain stable Windows environments. Install the exact version you need, pin it, and run routine winget upgrades with confidence knowing your baselines won’t drift. For larger fleets, pair pins with a YAML WinGet Configuration file and automation scripts to make your environment reproducible, testable, and auditable. Try the commands in this guide on a test machine and you’ll see how quickly you can lock down versions without giving up the power and convenience of the Windows Package Manager.
FAQ Section
What’s the difference between installing a version and pinning it?
A versioned install sets a specific version now, but future winget upgrade commands may move it forward. Pinning tells winget to hold that package at the pinned version and skip it during upgrades unless you explicitly override with –include-pinned.
Can I pin Microsoft Store apps?
Generally, no. Store apps update through the Microsoft Store service and often don’t support versioned installs or winget pin behavior. Prefer packages from the “winget” community source if you need pinning, or control Store updates via policy.
How do I find which versions are available to install?
Use:
winget show –id
Look for the “Versions” section. Not all packages maintain historical versions; winget can only install versions that exist in the source’s manifests for your architecture.
Where are pins stored, and are they per user?
Pins are stored locally by winget and are applied per user context. If you run winget as another user or as SYSTEM, that context has its own pins. For shared devices, apply pins in the context that performs upgrades (e.g., SYSTEM via scheduled task).
How do I update a pinned package to a newer version?
Either temporarily override the pin:
winget upgrade –id
Or remove and recreate the pin:
winget pin remove –id
winget upgrade –id
winget pin add –id
