@@ -58,9 +58,10 @@ $script:Strings = @{
5858 WingetRequired = " Winget is required. Install 'App Installer' from the Microsoft Store."
5959 WingetFound = ' Winget found'
6060 WingetMissing = ' Winget not found on PATH.'
61- ManagingSources = ' Managing winget sources...'
62- RemovingMsstore = ' Removing msstore source (performance)...'
63- RefreshingSources = ' Refreshing winget source index...'
61+ ManagingSources = ' Managing winget sources'
62+ ProgSources = ' Managing Winget Sources'
63+ RemovingMsstore = ' Removing msstore source (performance)'
64+ RefreshingSources = ' Refreshing winget source index'
6465 SourcesFailed = ' Could not manage winget sources'
6566
6667 # Phase names (console + progress bar)
@@ -98,7 +99,6 @@ $script:Strings = @{
9899 AlreadyInstalled = ' Already installed'
99100 InstallFail = ' Failed'
100101 InstallError = ' Error installing'
101- CapNotInstalled = ' Not installed'
102102 CapRemoving = ' Removing capability'
103103 CapError = ' Error with capability'
104104 McAfeeNone = ' No McAfee products found.'
@@ -175,9 +175,10 @@ $script:Strings = @{
175175 WingetRequired = " Se requiere Winget. Instale 'App Installer' desde Microsoft Store."
176176 WingetFound = ' Winget encontrado'
177177 WingetMissing = ' Winget no encontrado en el PATH.'
178- ManagingSources = ' Administrando fuentes de winget...'
179- RemovingMsstore = ' Eliminando fuente msstore (rendimiento)...'
180- RefreshingSources = ' Actualizando indice de fuentes winget...'
178+ ManagingSources = ' Administrando fuentes de winget'
179+ ProgSources = ' Administrando Fuentes de Winget'
180+ RemovingMsstore = ' Eliminando fuente msstore (rendimiento)'
181+ RefreshingSources = ' Actualizando indice de fuentes winget'
181182 SourcesFailed = ' No se pudieron administrar las fuentes de winget'
182183
183184 # Phase names
@@ -215,7 +216,6 @@ $script:Strings = @{
215216 AlreadyInstalled = ' Ya instalado'
216217 InstallFail = ' Fallo'
217218 InstallError = ' Error al instalar'
218- CapNotInstalled = ' No instalado'
219219 CapRemoving = ' Eliminando capacidad'
220220 CapError = ' Error con capacidad'
221221 McAfeeNone = ' No se encontraron productos McAfee.'
@@ -306,6 +306,14 @@ function T {
306306 return $Key # fall back to the key name itself if missing
307307}
308308
309+ # ConvertTo-HtmlSafe — encodes special chars so exception messages / paths
310+ # don't break the HTML report structure
311+ function ConvertTo-HtmlSafe {
312+ param ([string ]$Text )
313+ if (-not $Text ) { return ' ' }
314+ $Text -replace ' &' , ' &' -replace ' <' , ' <' -replace ' >' , ' >' -replace ' "' , ' "'
315+ }
316+
309317# ================================
310318# Logging
311319# ================================
@@ -457,16 +465,19 @@ function Test-Winget {
457465function Initialize-WingetSources {
458466 Write-Log (T ' ManagingSources' )
459467 try {
460- Set-PhaseProgress - Activity (T ' ManagingSources ' ) - Status (T ' ProgSourcesList' ) - Current 1 - Total 3
468+ Set-PhaseProgress - Activity (T ' ProgSources ' ) - Status (T ' ProgSourcesList' ) - Current 1 - Total 2
461469 $sources = winget source list 2> $null
462470
463471 if ($sources -match ' msstore' ) {
464- Set-PhaseProgress - Activity (T ' ManagingSources' ) - Status (T ' RemovingMsstore' ) - Current 2 - Total 3
472+ # msstore present — extend to 3 steps
473+ Set-PhaseProgress - Activity (T ' ProgSources' ) - Status (T ' RemovingMsstore' ) - Current 2 - Total 3
465474 Write-Log (T ' RemovingMsstore' )
466475 winget source remove -- name msstore 2> $null | Out-Null
476+ Set-PhaseProgress - Activity (T ' ProgSources' ) - Status (T ' ProgSourcesUpdate' ) - Current 3 - Total 3
477+ } else {
478+ Set-PhaseProgress - Activity (T ' ProgSources' ) - Status (T ' ProgSourcesUpdate' ) - Current 2 - Total 2
467479 }
468480
469- Set-PhaseProgress - Activity (T ' ManagingSources' ) - Status (T ' ProgSourcesUpdate' ) - Current 3 - Total 3
470481 Write-Log (T ' RefreshingSources' )
471482 winget source update -- name winget 2> $null | Out-Null
472483 }
@@ -614,7 +625,7 @@ function Remove-WindowsCapabilities {
614625 $script :Summary.CapabilitiesRemoved ++
615626 Add-Result - Section (T ' ProgCaps' ) - Item $cap - Status ' OK' - Detail (T ' Removed' )
616627 } else {
617- Write-Log " $ ( T ' CapNotInstalled ' ) : $cap "
628+ Write-Log " $ ( T ' NotInstalled ' ) : $cap "
618629 Add-Result - Section (T ' ProgCaps' ) - Item $cap - Status ' SKIPPED' - Detail (T ' NotInstalled' )
619630 }
620631 }
@@ -668,17 +679,17 @@ function Remove-McAfeeProducts {
668679
669680 try {
670681 if ($uninstallString -match ' ^"([^"]+)"\s*(.*)$' ) {
671- $exe = $Matches [1 ]
672- $args = $Matches [2 ]
682+ $exe = $Matches [1 ]
683+ $uninstallArgs = $Matches [2 ]
673684 } else {
674- $parts = $uninstallString.Split (' ' , 2 )
675- $exe = $parts [0 ]
676- $args = if ($parts.Length -gt 1 ) { $parts [1 ] } else { ' ' }
685+ $parts = $uninstallString.Split (' ' , 2 )
686+ $exe = $parts [0 ]
687+ $uninstallArgs = if ($parts.Length -gt 1 ) { $parts [1 ] } else { ' ' }
677688 }
678- if ($args -notmatch ' /S|/silent|/quiet' ) { $args += ' /S /quiet' }
689+ if ($uninstallArgs -notmatch ' /S|/silent|/quiet' ) { $uninstallArgs += ' /S /quiet' }
679690
680691 Write-Log " $ ( T ' McAfeeUninstall' ) : $displayName "
681- Start-Process - FilePath $exe - ArgumentList $args - Wait - WindowStyle Hidden - ErrorAction Stop
692+ Start-Process - FilePath $exe - ArgumentList $uninstallArgs - Wait - WindowStyle Hidden - ErrorAction Stop
682693 Write-Log " $ ( T ' McAfeeRemoved' ) : $displayName " - Level ' SUCCESS'
683694 $script :Summary.McAfeeRemoved ++
684695 Add-Result - Section (T ' ProgMcAfee' ) - Item $displayName - Status ' OK' - Detail (T ' McAfeeRemoved' )
@@ -818,17 +829,17 @@ function Export-HtmlReport {
818829
819830 $sections = $script :Results | Group-Object { $_.Section }
820831 $tableRows = foreach ($section in $sections ) {
821- " <tr class='section-header'><td colspan='3'>$ ( $section.Name ) </td></tr>"
832+ " <tr class='section-header'><td colspan='3'>$ ( ConvertTo-HtmlSafe $section.Name ) </td></tr>"
822833 foreach ($r in $section.Group ) {
823834 $css = switch ($r.Status ) { ' OK' {' status-ok' } ' SKIPPED' {' status-skipped' } ' WARN' {' status-warn' } ' FAILED' {' status-failed' } }
824835 $icon = switch ($r.Status ) { ' OK' {' ✓' } ' SKIPPED' {' —' } ' WARN' {' ⚠' } ' FAILED' {' ✗' } }
825- " <tr><td>$ ( $r.Item ) </td><td class='$css '>$icon $ ( $r.Status ) </td><td>$ ( $r.Detail ) </td></tr>"
836+ " <tr><td>$ ( ConvertTo-HtmlSafe $r.Item ) </td><td class='$css '>$icon $ ( $r.Status ) </td><td>$ ( ConvertTo-HtmlSafe $r.Detail ) </td></tr>"
826837 }
827838 }
828839
829840 $logRows = ($script :EventLog | Select-Object - Last 200 ) | ForEach-Object {
830841 $css = switch ($_.Level ) { ' ERROR' {' log-error' } ' WARN' {' log-warn' } ' SUCCESS' {' log-success' } ' SECTION' {' log-section' } default {' ' } }
831- " <tr class='$css '><td>$ ( $_.Timestamp ) </td><td>$ ( $_.Level ) </td><td>$ ( $_.Message ) </td></tr>"
842+ " <tr class='$css '><td>$ ( $_.Timestamp ) </td><td>$ ( $_.Level ) </td><td>$ ( ConvertTo-HtmlSafe $_.Message ) </td></tr>"
832843 }
833844
834845 # Localized HTML labels
@@ -996,13 +1007,13 @@ function Write-ConsoleSummary {
9961007 Write-Log $border - Level ' SECTION'
9971008 Write-Log (T ' SumTitle' ) - Level ' SECTION'
9981009 Write-Log $border - Level ' SECTION'
999- Write-Log " $ ( T ' SumAppsOK' ) : $ ( $script :Summary.AppsInstalled ) "
1000- Write-Log " $ ( T ' SumAppsFail' ) : $ ( $script :Summary.AppsFailed ) "
1001- Write-Log " $ ( T ' SumAppx' ) : $ ( $script :Summary.AppxRemoved ) "
1002- Write-Log " $ ( T ' SumCaps' ) : $ ( $script :Summary.CapabilitiesRemoved ) "
1003- Write-Log " $ ( T ' SumConfigOK' ) : $ ( $script :Summary.HardeningApplied ) "
1004- Write-Log " $ ( T ' SumConfigFail' ) : $ ( $script :Summary.HardeningFailed ) "
1005- Write-Log " $ ( T ' SumMcAfee' ) : $ ( $script :Summary.McAfeeRemoved ) "
1010+ Write-Log " $ ( ( T ' SumAppsOK' ) ) : $ ( $script :Summary.AppsInstalled ) "
1011+ Write-Log " $ ( ( T ' SumAppsFail' ) ) : $ ( $script :Summary.AppsFailed ) "
1012+ Write-Log " $ ( ( T ' SumAppx' ) ) : $ ( $script :Summary.AppxRemoved ) "
1013+ Write-Log " $ ( ( T ' SumCaps' ) ) : $ ( $script :Summary.CapabilitiesRemoved ) "
1014+ Write-Log " $ ( ( T ' SumConfigOK' ) ) : $ ( $script :Summary.HardeningApplied ) "
1015+ Write-Log " $ ( ( T ' SumConfigFail' ) ) : $ ( $script :Summary.HardeningFailed ) "
1016+ Write-Log " $ ( ( T ' SumMcAfee' ) ) : $ ( $script :Summary.McAfeeRemoved ) "
10061017 Write-Log $border - Level ' SECTION'
10071018}
10081019
@@ -1012,20 +1023,20 @@ function Write-ConsoleSummary {
10121023
10131024# Overall progress weights (must total 100)
10141025# Phases: Init=5, Bloatware=35, Apps=40, Config=15, Report=5
1015- $pct = @ { Init = 5 ; BloatStart = 5 ; BloatEnd = 40 ; AppsStart = 40 ; AppsEnd = 80 ; ConfigStart = 80 ; ConfigEnd = 95 ; Done = 100 }
1026+ $script :PhasePct = @ { Init = 5 ; BloatStart = 5 ; BloatEnd = 40 ; AppsStart = 40 ; AppsEnd = 80 ; ConfigStart = 80 ; ConfigEnd = 95 ; Done = 100 }
10161027
10171028try {
1018- Set-OverallProgress - Status (T ' ManagingSources' ) - Percent $pct .Init
1029+ Set-OverallProgress - Status (T ' ManagingSources' ) - Percent $script :PhasePct .Init
10191030
10201031 if (-not (Test-Winget )) {
10211032 Write-Log (T ' WingetRequired' ) - Level ' ERROR'
1022- exit 1
1033+ throw ' Winget not available '
10231034 }
10241035
10251036 Initialize-WingetSources
10261037
10271038 if (-not $SkipBloatwareRemoval ) {
1028- Set-OverallProgress - Status (T ' PhaseBloatware' ) - Percent $pct .BloatStart
1039+ Set-OverallProgress - Status (T ' PhaseBloatware' ) - Percent $script :PhasePct .BloatStart
10291040 Write-Log " === $ ( T ' PhaseBloatware' ) ===" - Level ' SECTION'
10301041 $bloatwarePatterns = @ (
10311042 ' Copilot' , ' Outlook' , ' Quick Assist' , ' Remote Desktop' ,
@@ -1036,27 +1047,27 @@ try {
10361047 Remove-AppxPackages
10371048 Remove-WindowsCapabilities
10381049 Remove-McAfeeProducts
1039- Set-OverallProgress - Status " $ ( T ' PhaseBloatware' ) - Complete" - Percent $pct .BloatEnd
1050+ Set-OverallProgress - Status " $ ( T ' PhaseBloatware' ) - Complete" - Percent $script :PhasePct .BloatEnd
10401051 Write-Log " === $ ( T ' PhaseBloatware' ) COMPLETE ===" - Level ' SUCCESS'
10411052 } else {
10421053 Write-Log (T ' SkipBloatware' )
10431054 }
10441055
10451056 if (-not $SkipAppInstall ) {
1046- Set-OverallProgress - Status (T ' PhaseApps' ) - Percent $pct .AppsStart
1057+ Set-OverallProgress - Status (T ' PhaseApps' ) - Percent $script :PhasePct .AppsStart
10471058 Write-Log " === $ ( T ' PhaseApps' ) ===" - Level ' SECTION'
10481059 Install-StandardApps
1049- Set-OverallProgress - Status " $ ( T ' PhaseApps' ) - Complete" - Percent $pct .AppsEnd
1060+ Set-OverallProgress - Status " $ ( T ' PhaseApps' ) - Complete" - Percent $script :PhasePct .AppsEnd
10501061 Write-Log " === $ ( T ' PhaseApps' ) COMPLETE ===" - Level ' SUCCESS'
10511062 } else {
10521063 Write-Log (T ' SkipApps' )
10531064 }
10541065
10551066 if (-not $SkipSystemConfig ) {
1056- Set-OverallProgress - Status (T ' PhaseConfig' ) - Percent $pct .ConfigStart
1067+ Set-OverallProgress - Status (T ' PhaseConfig' ) - Percent $script :PhasePct .ConfigStart
10571068 Write-Log " === $ ( T ' PhaseConfig' ) ===" - Level ' SECTION'
10581069 Set-SystemConfiguration
1059- Set-OverallProgress - Status " $ ( T ' PhaseConfig' ) - Complete" - Percent $pct .ConfigEnd
1070+ Set-OverallProgress - Status " $ ( T ' PhaseConfig' ) - Complete" - Percent $script :PhasePct .ConfigEnd
10601071 Write-Log " === $ ( T ' PhaseConfig' ) COMPLETE ===" - Level ' SUCCESS'
10611072 } else {
10621073 Write-Log (T ' SkipConfig' )
@@ -1070,13 +1081,9 @@ try {
10701081 ' SUCCESS'
10711082 }
10721083
1073- Set-OverallProgress - Status (T ' PhaseReporting' ) - Percent $pct .ConfigEnd
1084+ Set-OverallProgress - Status (T ' PhaseReporting' ) - Percent $script :PhasePct .ConfigEnd
10741085 Export-HtmlReport - OverallStatus $overallStatus
10751086
1076- # Clear both progress bars cleanly
1077- Write-Progress - Id 1 - Activity ' ' - Completed
1078- Write-Progress - Id 0 - Activity ' ' - Completed
1079-
10801087 Write-Log " ===== $ ( T ' Completed' ) =====" - Level ' SUCCESS'
10811088 Write-Host " `n *** $ ( T ' SetupComplete' ) ***" - ForegroundColor Green
10821089 Write-Host " Log : $LogPath " - ForegroundColor Gray
@@ -1086,11 +1093,12 @@ try {
10861093}
10871094catch {
10881095 Write-Log " $ ( T ' CriticalError' ) : $ ( $_.Exception.Message ) " - Level ' ERROR'
1089- try {
1090- Write-Progress - Id 1 - Activity ' ' - Completed
1091- Write-Progress - Id 0 - Activity ' ' - Completed
1092- Export-HtmlReport - OverallStatus ' FAILED'
1093- } catch {}
1096+ try { Export-HtmlReport - OverallStatus ' FAILED' } catch {}
10941097 Write-Host " `n *** $ ( T ' SetupFailed' ) : $LogPath ***" - ForegroundColor Red
10951098 exit 1
10961099}
1100+ finally {
1101+ # Always clear progress bars — runs on success, failure, and Ctrl+C
1102+ Write-Progress - Id 1 - Activity ' ' - Completed
1103+ Write-Progress - Id 0 - Activity ' ' - Completed
1104+ }
0 commit comments