Created
February 19, 2026 18:19
-
-
Save ergosteur/e46e53cfd9784506d7b60b69dc7d3dee to your computer and use it in GitHub Desktop.
Repairs and configures Winget for enterprise environments (Intune/SCCM/RMM) with unified Standalone/Detect/Remediate modes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Repairs and configures Winget for enterprise environments (Intune/SCCM/RMM) with unified Standalone/Detect/Remediate modes. | |
| .DESCRIPTION | |
| This script is designed to be used in three modes, controlled by the $ExecutionMode variable: | |
| $ExecutionMode = "Standalone" # For manual/RMM/SCCM use (default) | |
| $ExecutionMode = "Detect" # For Intune detection scripts | |
| $ExecutionMode = "Remediate" # For Intune remediation scripts | |
| - Standalone: | |
| Runs full repair logic with SYSTEM-aware defaults, logging, and no special exit codes. | |
| - Detect: | |
| Runs health checks only (no changes), and exits: | |
| 0 = Winget is healthy | |
| 1 = Winget needs remediation | |
| - Remediate: | |
| Runs full repair logic and exits: | |
| 0 = Remediation succeeded | |
| 1 = Remediation failed | |
| When running as SYSTEM (Intune/SCCM default), the script automatically applies | |
| enterprise-safe defaults unless explicitly overridden via parameters: | |
| -AllUsers:$true | |
| -ProvisionForNewUsers:$true | |
| -DisableMsStoreSource:$true | |
| -ResetSources:$true | |
| -Force:$true | |
| -Silent:$true | |
| All parameters can be explicitly overridden, even in SYSTEM context. | |
| .PARAMETER WhatIf | |
| Simulates actions without making changes (primarily for Standalone mode). | |
| .PARAMETER Silent | |
| Suppresses non-error output. Automatically enabled in SYSTEM context unless overridden. | |
| .PARAMETER Force | |
| Reapplies all fixes even if they appear correct. Automatically enabled in SYSTEM context. | |
| .PARAMETER AllUsers | |
| Applies PATH and Winget settings to all user profiles. Automatically enabled in SYSTEM context. | |
| .PARAMETER ProvisionForNewUsers | |
| Applies PATH and Winget settings to the default profile (C:\Users\Default). | |
| Automatically enabled in SYSTEM context. | |
| .PARAMETER ResetSources | |
| Resets all Winget sources before applying configuration. | |
| Automatically enabled in SYSTEM context. | |
| .PARAMETER DisableMsStoreSource | |
| Disables the msstore Winget source (default: $true). | |
| Automatically enabled in SYSTEM context unless overridden. | |
| .PARAMETER LogFile | |
| Optional log file path. If not provided and running as SYSTEM, | |
| defaults to: C:\ProgramData\WingetRepair\WingetRepair.log | |
| .EXAMPLE | |
| # Standalone / RMM / SCCM | |
| $ExecutionMode = "Standalone" | |
| .\Repair-Winget.ps1 -LogFile "C:\ProgramData\WingetRepair\WingetRepair.log" | |
| .EXAMPLE | |
| # Intune Detect script | |
| $ExecutionMode = "Detect" | |
| .\Repair-Winget.ps1 | |
| .EXAMPLE | |
| # Intune Remediate script | |
| $ExecutionMode = "Remediate" | |
| .\Repair-Winget.ps1 | |
| #> | |
| # ------------------------------- | |
| # Parameter definitions | |
| # ------------------------------- | |
| param( | |
| [switch]$Simulate, | |
| [switch]$Silent, | |
| [switch]$Force, | |
| [switch]$AllUsers, | |
| [switch]$ProvisionForNewUsers, | |
| [switch]$ResetSources, | |
| [bool]$DisableMsStoreSource = $true, | |
| [string]$LogFile | |
| ) | |
| # ------------------------------- | |
| # USER-ADJUSTABLE EXECUTION MODE | |
| # ------------------------------- | |
| # Valid values: "Standalone", "Detect", "Remediate" | |
| if (-not $ExecutionMode) { | |
| $ExecutionMode = "Standalone" | |
| } | |
| # ------------------------------- | |
| # Detect SYSTEM context | |
| # ------------------------------- | |
| $IsSystem = $env:USERNAME -eq "SYSTEM" | |
| # ------------------------------- | |
| # Apply SYSTEM defaults (Option A) | |
| # ------------------------------- | |
| if ($IsSystem) { | |
| if (-not $PSBoundParameters.ContainsKey("Silent")) { $Silent = $true } | |
| if (-not $PSBoundParameters.ContainsKey("Force")) { $Force = $true } | |
| if (-not $PSBoundParameters.ContainsKey("AllUsers")) { $AllUsers = $true } | |
| if (-not $PSBoundParameters.ContainsKey("ProvisionForNewUsers")) { $ProvisionForNewUsers = $true } | |
| if (-not $PSBoundParameters.ContainsKey("ResetSources")) { $ResetSources = $true } | |
| if (-not $PSBoundParameters.ContainsKey("DisableMsStoreSource")) { $DisableMsStoreSource = $true } | |
| if (-not $PSBoundParameters.ContainsKey("LogFile")) { | |
| $logDir = "C:\ProgramData\WingetRepair" | |
| if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } | |
| $LogFile = "$logDir\WingetRepair.log" | |
| } | |
| } | |
| # ------------------------------- | |
| # Logging helpers | |
| # ------------------------------- | |
| function Write-Log($msg) { | |
| if ($LogFile) { | |
| Add-Content -Path $LogFile -Value ("[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg") | |
| } | |
| } | |
| function Write-Info($msg) { | |
| Write-Log $msg | |
| if (-not $Silent) { Write-Host $msg -ForegroundColor Cyan } | |
| } | |
| function Write-Warn($msg) { | |
| Write-Log "WARNING: $msg" | |
| if (-not $Silent) { Write-Host $msg -ForegroundColor Yellow } | |
| } | |
| function Write-Good($msg) { | |
| Write-Log $msg | |
| if (-not $Silent) { Write-Host $msg -ForegroundColor Green } | |
| } | |
| Write-Info "=== Winget Repair Script Starting ===" | |
| Write-Info ("ExecutionMode: " + $ExecutionMode) | |
| Write-Info ("Running as: " + $env:USERNAME) | |
| # ------------------------------- | |
| # Fix PATH for a user profile | |
| # ------------------------------- | |
| function Fix-UserPath { | |
| param( | |
| [string]$UserProfile | |
| ) | |
| $windowsApps = "$UserProfile\AppData\Local\Microsoft\WindowsApps" | |
| $currentPath = [Environment]::GetEnvironmentVariable("Path", "User") | |
| if ($Force -or $currentPath -notlike "*$windowsApps*") { | |
| Write-Info "Adding WindowsApps to PATH for $UserProfile" | |
| if (-not $Simulate) { | |
| $newPath = $currentPath + ";" + $windowsApps | |
| [Environment]::SetEnvironmentVariable("Path", $newPath, "User") | |
| } | |
| } else { | |
| Write-Info "WindowsApps already present in PATH for $UserProfile" | |
| } | |
| } | |
| # ------------------------------- | |
| # Test Winget health (for Detect mode) | |
| # ------------------------------- | |
| function Test-WingetHealth { | |
| Write-Info "Testing Winget health..." | |
| # 1. Check App Installer | |
| $appInstaller = Get-AppxPackage Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue | |
| if (-not $appInstaller) { | |
| Write-Warn "Health check failed: App Installer is missing." | |
| return $false | |
| } | |
| # 2. Check winget command | |
| try { | |
| $v = winget --version | |
| if (-not $v) { | |
| Write-Warn "Health check failed: winget --version returned no output." | |
| return $false | |
| } | |
| } catch { | |
| Write-Warn "Health check failed: winget command not working." | |
| return $false | |
| } | |
| # 3. Check msstore source state if we expect it disabled | |
| if ($DisableMsStoreSource) { | |
| try { | |
| $sources = winget source list | |
| if ($sources -match "msstore" -and $sources -notmatch "Disabled") { | |
| Write-Warn "Health check failed: msstore source is not disabled." | |
| return $false | |
| } | |
| } catch { | |
| Write-Warn "Health check: unable to query sources, assuming unhealthy." | |
| return $false | |
| } | |
| } | |
| Write-Good "Winget health check passed." | |
| return $true | |
| } | |
| # ------------------------------- | |
| # Full repair logic | |
| # ------------------------------- | |
| function Repair-Winget { | |
| Write-Info "Starting full Winget repair..." | |
| # Apply PATH fixes | |
| if ($AllUsers) { | |
| Write-Info "Applying PATH fix to all user profiles..." | |
| $profiles = Get-ChildItem "C:\Users" | Where-Object { $_.PSIsContainer } | |
| foreach ($p in $profiles) { | |
| Fix-UserPath -UserProfile $p.FullName | |
| } | |
| } else { | |
| Fix-UserPath -UserProfile $env:USERPROFILE | |
| } | |
| # Provision default profile | |
| if ($ProvisionForNewUsers) { | |
| $defaultProfile = "C:\Users\Default" | |
| Write-Info "Provisioning default profile at $defaultProfile" | |
| Fix-UserPath -UserProfile $defaultProfile | |
| } | |
| # Ensure App Installer exists | |
| $appInstaller = Get-AppxPackage Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue | |
| if (-not $appInstaller) { | |
| Write-Warn "App Installer is missing. Cannot repair in headless mode. Exiting." | |
| return $false | |
| } | |
| Write-Info "App Installer found: $($appInstaller.Version)" | |
| # Re-register App Installer | |
| Write-Info "Re-registering App Installer..." | |
| if (-not $Simulate) { | |
| try { | |
| Add-AppxPackage -Register "$($appInstaller.InstallLocation)\AppxManifest.xml" -DisableDevelopmentMode -ErrorAction Stop | |
| } | |
| catch { | |
| Write-Warn "Re-register failed (App Installer likely in use). Continuing..." | |
| } | |
| } | |
| # Reset Winget sources | |
| if ($ResetSources) { | |
| Write-Info "Resetting Winget sources..." | |
| if (-not $Simulate) { | |
| winget source reset --force | |
| } | |
| } | |
| # Configure msstore source | |
| if ($DisableMsStoreSource) { | |
| Write-Info "Disabling msstore source..." | |
| if (-not $Simulate) { | |
| winget source disable msstore | |
| } | |
| } else { | |
| Write-Info "Enabling msstore source..." | |
| if (-not $Simulate) { | |
| winget source enable msstore | |
| } | |
| } | |
| # Winget settings | |
| Write-Info "Enabling certificate pinning bypass..." | |
| if (-not $Simulate) { | |
| winget settings --enable BypassCertificatePinningForMicrosoftStore | |
| } | |
| Write-Info "Accepting agreements..." | |
| if (-not $Simulate) { | |
| winget source update | |
| winget list --accept-source-agreements --accept-package-agreements | Out-Null | |
| } | |
| # Upgrade App Installer | |
| Write-Info "Upgrading App Installer from winget source..." | |
| if (-not $Simulate) { | |
| winget upgrade Microsoft.AppInstaller --source winget --include-unknown --accept-source-agreements --accept-package-agreements | |
| } | |
| # Verify winget | |
| Write-Info "Testing winget after repair..." | |
| try { | |
| if (-not $Simulate) { | |
| $v = winget --version | |
| Write-Good "Winget is working: $v" | |
| } | |
| } catch { | |
| Write-Warn "Winget still not responding after repair." | |
| return $false | |
| } | |
| Write-Info "Winget repair completed successfully." | |
| return $true | |
| } | |
| # ------------------------------- | |
| # Mode dispatch | |
| # ------------------------------- | |
| switch ($ExecutionMode.ToLower()) { | |
| "detect" { | |
| $healthy = Test-WingetHealth | |
| if ($healthy) { | |
| Write-Info "Detect mode: Winget is healthy. Exiting with code 0." | |
| exit 0 | |
| } else { | |
| Write-Warn "Detect mode: Winget is NOT healthy. Exiting with code 1." | |
| exit 1 | |
| } | |
| } | |
| "remediate" { | |
| $result = Repair-Winget | |
| if ($result) { | |
| Write-Info "Remediate mode: Repair succeeded. Exiting with code 0." | |
| exit 0 | |
| } else { | |
| Write-Warn "Remediate mode: Repair failed. Exiting with code 1." | |
| exit 1 | |
| } | |
| } | |
| default { | |
| # Standalone / RMM / SCCM mode | |
| $result = Repair-Winget | |
| if ($result) { | |
| Write-Info "Standalone mode: Repair completed." | |
| exit 0 | |
| } else { | |
| Write-Warn "Standalone mode: Repair encountered issues." | |
| exit 1 | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment