ergosteur

ergosteur / Repair Winget

Last active 1 month ago

Like 0

Repairs and configures Winget for enterprise environments (Intune/SCCM/RMM) with unified Standalone/Detect/Remediate modes.

Repair-Winget.ps1 Raw
1<#
2.SYNOPSIS
3 Repairs and configures Winget for enterprise environments (Intune/SCCM/RMM) with unified Standalone/Detect/Remediate modes.
4
5.DESCRIPTION
6 This script is designed to be used in three modes, controlled by the $ExecutionMode variable:
7
8 $ExecutionMode = "Standalone" # For manual/RMM/SCCM use (default)
9 $ExecutionMode = "Detect" # For Intune detection scripts
10 $ExecutionMode = "Remediate" # For Intune remediation scripts
11
12 - Standalone:
13 Runs full repair logic with SYSTEM-aware defaults, logging, and no special exit codes.
14
15 - Detect:
16 Runs health checks only (no changes), and exits:
17 0 = Winget is healthy
18 1 = Winget needs remediation
19
20 - Remediate:
21 Runs full repair logic and exits:
22 0 = Remediation succeeded
23 1 = Remediation failed
24
25 When running as SYSTEM (Intune/SCCM default), the script automatically applies
26 enterprise-safe defaults unless explicitly overridden via parameters:
27
28 -AllUsers:$true
29 -ProvisionForNewUsers:$true
30 -DisableMsStoreSource:$true
31 -ResetSources:$true
32 -Force:$true
33 -Silent:$true
34
35 All parameters can be explicitly overridden, even in SYSTEM context.
36
37.PARAMETER WhatIf
38 Simulates actions without making changes (primarily for Standalone mode).
39
40.PARAMETER Silent
41 Suppresses non-error output. Automatically enabled in SYSTEM context unless overridden.
42
43.PARAMETER Force
44 Reapplies all fixes even if they appear correct. Automatically enabled in SYSTEM context.
45
46.PARAMETER AllUsers
47 Applies PATH and Winget settings to all user profiles. Automatically enabled in SYSTEM context.
48
49.PARAMETER ProvisionForNewUsers
50 Applies PATH and Winget settings to the default profile (C:\Users\Default).
51 Automatically enabled in SYSTEM context.
52
53.PARAMETER ResetSources
54 Resets all Winget sources before applying configuration.
55 Automatically enabled in SYSTEM context.
56
57.PARAMETER DisableMsStoreSource
58 Disables the msstore Winget source (default: $true).
59 Automatically enabled in SYSTEM context unless overridden.
60
61.PARAMETER LogFile
62 Optional log file path. If not provided and running as SYSTEM,
63 defaults to: C:\ProgramData\WingetRepair\WingetRepair.log
64
65.EXAMPLE
66 # Standalone / RMM / SCCM
67 $ExecutionMode = "Standalone"
68 .\Repair-Winget.ps1 -LogFile "C:\ProgramData\WingetRepair\WingetRepair.log"
69
70.EXAMPLE
71 # Intune Detect script
72 $ExecutionMode = "Detect"
73 .\Repair-Winget.ps1
74
75.EXAMPLE
76 # Intune Remediate script
77 $ExecutionMode = "Remediate"
78 .\Repair-Winget.ps1
79
80#>
81
82# -------------------------------
83# Parameter definitions
84# -------------------------------
85
86param(
87 [switch]$Simulate,
88 [switch]$Silent,
89 [switch]$Force,
90 [switch]$AllUsers,
91 [switch]$ProvisionForNewUsers,
92 [switch]$ResetSources,
93 [bool]$DisableMsStoreSource = $true,
94 [string]$LogFile
95)
96# -------------------------------
97# USER-ADJUSTABLE EXECUTION MODE
98# -------------------------------
99# Valid values: "Standalone", "Detect", "Remediate"
100if (-not $ExecutionMode) {
101 $ExecutionMode = "Standalone"
102}
103
104# -------------------------------
105# Detect SYSTEM context
106# -------------------------------
107$IsSystem = $env:USERNAME -eq "SYSTEM"
108
109# -------------------------------
110# Apply SYSTEM defaults (Option A)
111# -------------------------------
112if ($IsSystem) {
113 if (-not $PSBoundParameters.ContainsKey("Silent")) { $Silent = $true }
114 if (-not $PSBoundParameters.ContainsKey("Force")) { $Force = $true }
115 if (-not $PSBoundParameters.ContainsKey("AllUsers")) { $AllUsers = $true }
116 if (-not $PSBoundParameters.ContainsKey("ProvisionForNewUsers")) { $ProvisionForNewUsers = $true }
117 if (-not $PSBoundParameters.ContainsKey("ResetSources")) { $ResetSources = $true }
118 if (-not $PSBoundParameters.ContainsKey("DisableMsStoreSource")) { $DisableMsStoreSource = $true }
119
120 if (-not $PSBoundParameters.ContainsKey("LogFile")) {
121 $logDir = "C:\ProgramData\WingetRepair"
122 if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null }
123 $LogFile = "$logDir\WingetRepair.log"
124 }
125}
126
127# -------------------------------
128# Logging helpers
129# -------------------------------
130function Write-Log($msg) {
131 if ($LogFile) {
132 Add-Content -Path $LogFile -Value ("[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg")
133 }
134}
135
136function Write-Info($msg) {
137 Write-Log $msg
138 if (-not $Silent) { Write-Host $msg -ForegroundColor Cyan }
139}
140
141function Write-Warn($msg) {
142 Write-Log "WARNING: $msg"
143 if (-not $Silent) { Write-Host $msg -ForegroundColor Yellow }
144}
145
146function Write-Good($msg) {
147 Write-Log $msg
148 if (-not $Silent) { Write-Host $msg -ForegroundColor Green }
149}
150
151Write-Info "=== Winget Repair Script Starting ==="
152Write-Info ("ExecutionMode: " + $ExecutionMode)
153Write-Info ("Running as: " + $env:USERNAME)
154
155# -------------------------------
156# Fix PATH for a user profile
157# -------------------------------
158function Fix-UserPath {
159 param(
160 [string]$UserProfile
161 )
162
163 $windowsApps = "$UserProfile\AppData\Local\Microsoft\WindowsApps"
164 $currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
165
166 if ($Force -or $currentPath -notlike "*$windowsApps*") {
167 Write-Info "Adding WindowsApps to PATH for $UserProfile"
168 if (-not $Simulate) {
169 $newPath = $currentPath + ";" + $windowsApps
170 [Environment]::SetEnvironmentVariable("Path", $newPath, "User")
171 }
172 } else {
173 Write-Info "WindowsApps already present in PATH for $UserProfile"
174 }
175}
176
177# -------------------------------
178# Test Winget health (for Detect mode)
179# -------------------------------
180function Test-WingetHealth {
181 Write-Info "Testing Winget health..."
182
183 # 1. Check App Installer
184 $appInstaller = Get-AppxPackage Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue
185 if (-not $appInstaller) {
186 Write-Warn "Health check failed: App Installer is missing."
187 return $false
188 }
189
190 # 2. Check winget command
191 try {
192 $v = winget --version
193 if (-not $v) {
194 Write-Warn "Health check failed: winget --version returned no output."
195 return $false
196 }
197 } catch {
198 Write-Warn "Health check failed: winget command not working."
199 return $false
200 }
201
202 # 3. Check msstore source state if we expect it disabled
203 if ($DisableMsStoreSource) {
204 try {
205 $sources = winget source list
206 if ($sources -match "msstore" -and $sources -notmatch "Disabled") {
207 Write-Warn "Health check failed: msstore source is not disabled."
208 return $false
209 }
210 } catch {
211 Write-Warn "Health check: unable to query sources, assuming unhealthy."
212 return $false
213 }
214 }
215
216 Write-Good "Winget health check passed."
217 return $true
218}
219
220# -------------------------------
221# Full repair logic
222# -------------------------------
223function Repair-Winget {
224 Write-Info "Starting full Winget repair..."
225
226 # Apply PATH fixes
227 if ($AllUsers) {
228 Write-Info "Applying PATH fix to all user profiles..."
229 $profiles = Get-ChildItem "C:\Users" | Where-Object { $_.PSIsContainer }
230 foreach ($p in $profiles) {
231 Fix-UserPath -UserProfile $p.FullName
232 }
233 } else {
234 Fix-UserPath -UserProfile $env:USERPROFILE
235 }
236
237 # Provision default profile
238 if ($ProvisionForNewUsers) {
239 $defaultProfile = "C:\Users\Default"
240 Write-Info "Provisioning default profile at $defaultProfile"
241 Fix-UserPath -UserProfile $defaultProfile
242 }
243
244 # Ensure App Installer exists
245 $appInstaller = Get-AppxPackage Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue
246 if (-not $appInstaller) {
247 Write-Warn "App Installer is missing. Cannot repair in headless mode. Exiting."
248 return $false
249 }
250
251 Write-Info "App Installer found: $($appInstaller.Version)"
252
253 # Re-register App Installer
254 Write-Info "Re-registering App Installer..."
255 if (-not $Simulate) {
256 try {
257 Add-AppxPackage -Register "$($appInstaller.InstallLocation)\AppxManifest.xml" -DisableDevelopmentMode -ErrorAction Stop
258 }
259 catch {
260 Write-Warn "Re-register failed (App Installer likely in use). Continuing..."
261 }
262 }
263
264 # Reset Winget sources
265 if ($ResetSources) {
266 Write-Info "Resetting Winget sources..."
267 if (-not $Simulate) {
268 winget source reset --force
269 }
270 }
271
272 # Configure msstore source
273 if ($DisableMsStoreSource) {
274 Write-Info "Disabling msstore source..."
275 if (-not $Simulate) {
276 winget source disable msstore
277 }
278 } else {
279 Write-Info "Enabling msstore source..."
280 if (-not $Simulate) {
281 winget source enable msstore
282 }
283 }
284
285 # Winget settings
286 Write-Info "Enabling certificate pinning bypass..."
287 if (-not $Simulate) {
288 winget settings --enable BypassCertificatePinningForMicrosoftStore
289 }
290
291 Write-Info "Accepting agreements..."
292 if (-not $Simulate) {
293 winget source update
294 winget list --accept-source-agreements --accept-package-agreements | Out-Null
295 }
296
297 # Upgrade App Installer
298 Write-Info "Upgrading App Installer from winget source..."
299 if (-not $Simulate) {
300 winget upgrade Microsoft.AppInstaller --source winget --include-unknown --accept-source-agreements --accept-package-agreements
301 }
302
303 # Verify winget
304 Write-Info "Testing winget after repair..."
305 try {
306 if (-not $Simulate) {
307 $v = winget --version
308 Write-Good "Winget is working: $v"
309 }
310 } catch {
311 Write-Warn "Winget still not responding after repair."
312 return $false
313 }
314
315 Write-Info "Winget repair completed successfully."
316 return $true
317}
318
319# -------------------------------
320# Mode dispatch
321# -------------------------------
322switch ($ExecutionMode.ToLower()) {
323 "detect" {
324 $healthy = Test-WingetHealth
325 if ($healthy) {
326 Write-Info "Detect mode: Winget is healthy. Exiting with code 0."
327 exit 0
328 } else {
329 Write-Warn "Detect mode: Winget is NOT healthy. Exiting with code 1."
330 exit 1
331 }
332 }
333
334 "remediate" {
335 $result = Repair-Winget
336 if ($result) {
337 Write-Info "Remediate mode: Repair succeeded. Exiting with code 0."
338 exit 0
339 } else {
340 Write-Warn "Remediate mode: Repair failed. Exiting with code 1."
341 exit 1
342 }
343 }
344
345 default {
346 # Standalone / RMM / SCCM mode
347 $result = Repair-Winget
348 if ($result) {
349 Write-Info "Standalone mode: Repair completed."
350 exit 0
351 } else {
352 Write-Warn "Standalone mode: Repair encountered issues."
353 exit 1
354 }
355 }
356}
357