Compare commits

...

6 Commits

Author SHA1 Message Date
718c21ce6d remove nested helper and use single locale-safe parser
- Delete inline Invoke-DismWithProgress inside Invoke-WindowsUpdateRepair
- Keep one global helper with integer-only % parsing (handles “4.9%”, “4,9%”)
2025-09-19 16:16:44 +01:00
e3c2d6c074 fix(dism-progress): make percent parser locale-safe and resilient
- Accept “4.9%” and “4,9%”; strip %; use integer part only
- Prevent conversion warnings; steady progress output
2025-09-19 16:09:01 +01:00
2e1fc4816a Added windows update fix 2025-09-19 15:58:19 +01:00
f4d27570e5 changed menu name 2025-09-18 14:22:25 +01:00
6886d42010 removed unused menu options 2025-09-18 12:51:22 +01:00
a03eb1af4c removed ping host and show IP as not needed in admin menu 2025-09-18 12:50:45 +01:00
2 changed files with 197 additions and 22 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
/SRGAdminMenu.lnk

View File

@ -122,6 +122,160 @@ function Remove-ProvisionedAndExisting {
} }
} }
function Invoke-WindowsUpdateRepair {
param([string]$Computer)
$isLocal = [string]::IsNullOrWhiteSpace($Computer) -or $Computer -in @('localhost','127.0.0.1','.')
if ($isLocal) {
# ==== LOCAL run (with real progress) ====
function Test-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
if (-not (Test-Admin)) { throw "You must run this elevated (Administrator)." }
Write-Host "=== Step 1: DISM (ScanHealth) ===" -ForegroundColor Cyan
$e1 = Invoke-DismWithProgress -Arguments @('/Online','/Cleanup-Image','/ScanHealth')
if ($e1 -ne 0) { throw "DISM ScanHealth failed with code $e1" }
Write-Host "`n=== Step 1b: DISM (RestoreHealth) ===" -ForegroundColor Cyan
$e2 = Invoke-DismWithProgress -Arguments @('/Online','/Cleanup-Image','/RestoreHealth','/NoRestart')
if ($e2 -ne 0) { throw "DISM RestoreHealth failed with code $e2" }
Write-Host "`n=== Step 2: SFC ===" -ForegroundColor Cyan
& sfc.exe /scannow | Out-Host
Write-Host "`n=== Step 3: Reset Windows Update components ===" -ForegroundColor Cyan
$services = 'wuauserv','bits','cryptsvc','msiserver'
foreach ($s in $services) { try { Stop-Service -Name $s -Force -ErrorAction Stop } catch { } }
$sd = Join-Path $env:SystemRoot 'SoftwareDistribution'
$cat = Join-Path $env:SystemRoot 'System32\catroot2'
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
if (Test-Path $sd) { try { Rename-Item -Path $sd -NewName ("SoftwareDistribution.bak_$ts") -ErrorAction Stop } catch { } }
if (Test-Path $cat) { try { Rename-Item -Path $cat -NewName ("catroot2.bak_$ts") -ErrorAction Stop } catch { } }
foreach ($s in $services) { try { Start-Service -Name $s -ErrorAction Stop } catch { } }
Write-Host "`n=== Step 4: Trigger update scan ===" -ForegroundColor Cyan
foreach ($cmd in @(
{ & usoclient.exe StartScan },
{ & usoclient.exe StartDownload },
{ & usoclient.exe StartInstall },
{ & wuauclt.exe /detectnow },
{ & wuauclt.exe /reportnow }
)) { try { & $cmd | Out-Null } catch { } }
Write-Host "`nWindows Update repair sequence completed." -ForegroundColor Green
return
}
# ==== REMOTE run ====
$sb = {
function Test-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
if (-not (Test-Admin)) { throw "You must run this elevated (Administrator)." }
Write-Host "=== Step 1: DISM (ScanHealth) ==="
& dism.exe /Online /Cleanup-Image /ScanHealth | Out-Host
Write-Host "`n=== Step 1b: DISM (RestoreHealth) ==="
& dism.exe /Online /Cleanup-Image /RestoreHealth /NoRestart | Out-Host
Write-Host "`n=== Step 2: SFC ==="
& sfc.exe /scannow | Out-Host
Write-Host "`n=== Step 3: Reset Windows Update components ==="
$services = 'wuauserv','bits','cryptsvc','msiserver'
foreach ($s in $services) { try { Stop-Service -Name $s -Force -ErrorAction Stop } catch { } }
$sd = Join-Path $env:SystemRoot 'SoftwareDistribution'
$cat = Join-Path $env:SystemRoot 'System32\catroot2'
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
if (Test-Path $sd) { try { Rename-Item -Path $sd -NewName ("SoftwareDistribution.bak_$ts") -ErrorAction Stop } catch { } }
if (Test-Path $cat) { try { Rename-Item -Path $cat -NewName ("catroot2.bak_$ts") -ErrorAction Stop } catch { } }
foreach ($s in $services) { try { Start-Service -Name $s -ErrorAction Stop } catch { } }
Write-Host "`n=== Step 4: Trigger update scan ==="
foreach ($cmd in @(
{ & usoclient.exe StartScan },
{ & usoclient.exe StartDownload },
{ & usoclient.exe StartInstall },
{ & wuauclt.exe /detectnow },
{ & wuauclt.exe /reportnow }
)) { try { & $cmd | Out-Null } catch { } }
Write-Host "`nWindows Update repair sequence completed."
}
Invoke-Remote -Computer $Computer -ScriptBlock $sb
}
function Invoke-DismWithProgress {
<#
Runs DISM with a real progress bar.
Returns: DISM exit code (0 = success)
Note: Progress display is local-only (remoting doesn't surface Write-Progress well).
#>
param(
[Parameter(Mandatory)][string[]]$Arguments
)
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
$outf = Join-Path $env:TEMP "dism_out_$ts.txt"
$errf = Join-Path $env:TEMP "dism_err_$ts.txt"
if (-not ($Arguments -match '/LogPath:')) {
$logFile = Join-Path $env:TEMP "dism_log_$ts.log"
$Arguments += "/LogPath:$logFile"
}
$p = Start-Process -FilePath dism.exe `
-ArgumentList ($Arguments -join ' ') `
-NoNewWindow `
-RedirectStandardOutput $outf `
-RedirectStandardError $errf `
-PassThru
$lastPct = -1
while (-not $p.HasExited) {
if (Test-Path $outf) {
$content = Get-Content -Path $outf -Raw -ErrorAction SilentlyContinue
if ($content) {
# Match "NN", optional decimal with . or , then %, allowing whitespace
# Examples: "4.9%", "4,9 %", "62%"
$m = [regex]::Matches($content, '(\d{1,3})(?:[.,]\d+)?\s*%')
if ($m.Count -gt 0) {
$pctText = $m[$m.Count-1].Groups[1].Value # integer part only
$pct = 0
[void][int]::TryParse($pctText, [ref]$pct)
if ($pct -gt 100) { $pct = 100 }
if ($pct -ne $lastPct) {
Write-Progress -Activity "DISM $($Arguments -join ' ')" -Status "$pct%" -PercentComplete $pct
$lastPct = $pct
}
} else {
Write-Progress -Activity "DISM $($Arguments -join ' ')" -Status "Working..." -PercentComplete 0
}
}
}
Start-Sleep -Milliseconds 400
}
Write-Progress -Activity "DISM $($Arguments -join ' ')" -Completed
$exit = $p.ExitCode
if ($exit -ne 0) {
Write-Warning "DISM exited with code $exit"
if (Test-Path $errf) { Get-Content $errf -Tail 25 | Write-Warning }
}
return $exit
}
# ------- task functions (menu sections) ------- # ------- task functions (menu sections) -------
function Do-ConnectRemote { function Do-ConnectRemote {
@ -207,18 +361,6 @@ function Do-RemoveAppxMenu {
} while ($true) } while ($true)
} }
function Do-ShowIP {
Get-NetIPAddress -AddressFamily IPv4 |
Select-Object InterfaceAlias, IPAddress |
Sort-Object InterfaceAlias |
Format-Table -AutoSize
}
function Do-PingHost {
$h = Read-Host "Enter host or IP"
if ($h) { Test-Connection -TargetName $h -Count 4 | Format-Table Address, Latency -Auto }
}
function Do-RestartServiceMenu { function Do-RestartServiceMenu {
do { do {
Clear-Host Clear-Host
@ -275,27 +417,59 @@ function do-wingetupgrades {
winget upgrade --all --silent --accept-source-agreements --accept-package-agreements winget upgrade --all --silent --accept-source-agreements --accept-package-agreements
} }
function do-winupdatefix {
do {
Clear-Host
Write-Host "=== Windows Update Repair ===" -ForegroundColor Cyan
Write-Host " 1) Local machine"
Write-Host " 2) Remote machine"
Write-Host " 0) Back`n"
$t = Read-Host "Choose target"
switch ($t) {
'1' {
$go = Read-Host "This will run DISM/SFC and reset WU components on THIS machine. Proceed? (Y/N)"
if ($go -in @('Y','y')) {
try { Invoke-WindowsUpdateRepair } catch { Write-Warning $_ }
}
Read-Host "`nPress Enter to continue" | Out-Null
}
'2' {
$comp = Read-Host "Enter remote computer name"
if ([string]::IsNullOrWhiteSpace($comp)) { break }
$go = Read-Host "Run repair on '$comp'? This requires admin on that machine. Proceed? (Y/N)"
if ($go -in @('Y','y')) {
try { Invoke-WindowsUpdateRepair -Computer $comp } catch { Write-Warning $_ }
}
Read-Host "`nPress Enter to continue" | Out-Null
}
'0' { return } # leave submenu back to main menu
default {
Write-Host "Invalid choice." -ForegroundColor Yellow
Start-Sleep -Milliseconds 800
}
}
} while ($true)
}
# ------- Main Menu ------- # ------- Main Menu -------
do { do {
Clear-Host Clear-Host
Write-Host "=== My Admin Menu ===" -ForegroundColor Cyan Write-Host "=== SRG Admin Menu ===" -ForegroundColor Cyan
Write-Host " 1) Connect to a machine (Enter-PSSession as current user)" Write-Host " 1) Connect to a machine (Enter-PSSession as current user)"
Write-Host " 2) Remove Windows apps (local or remote)" Write-Host " 2) Remove Windows apps (local or remote)"
Write-Host " 3) Show IP addresses" Write-Host " 3) Restart a service (submenu)"
Write-Host " 4) Ping a host" write-Host " 4) Run Winget Upgrades"
Write-Host " 5) Restart a service (submenu)" write-Host " 5) Run Windows Update Repair"
write-Host " 6) Run Winget Upgrades"
Write-Host " 0) Quit`n" Write-Host " 0) Quit`n"
$choice = Read-Host "Choose an option" $choice = Read-Host "Choose an option"
switch ($choice) { switch ($choice) {
'1' { Do-ConnectRemote } '1' { Do-ConnectRemote }
'2' { Do-RemoveAppxMenu } '2' { Do-RemoveAppxMenu }
'3' { Do-ShowIP; Read-Host "`nPress Enter to return to menu" | Out-Null } '3' { Do-RestartServiceMenu }
'4' { Do-PingHost; Read-Host "`nPress Enter to return to menu" | Out-Null } '4' { do-wingetupgrades }
'5' { Do-RestartServiceMenu } '5' { do-winupdatefix }
'6' { do-wingetupgrades }
'0' { exit } '0' { exit }
default { default {
Write-Host "Invalid choice." -ForegroundColor Yellow Write-Host "Invalid choice." -ForegroundColor Yellow