Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions WinToolkit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ $AppConfig.URLs.DriverOverridesJson = "$RepoRawBase/assets/DriverOverrides.jso
$AppConfig.URLs.DirectXWebSetup = "$RepoRawBase/assets/dxwebsetup.exe"
$AppConfig.URLs.LanguagesRawUrl = "$RepoBase/languages"
$AppConfig.URLs.LanguagesApiUrl = "https://api.github.com/repos/Magnetarman/WinToolkit/contents/languages?ref=$Branch"
$Global:Spinners = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.ToCharArray()
$Global:MsgStyles = @{
Success = @{ Icon = '✅'; Color = 'Green' }
Warning = @{ Icon = '⚠️'; Color = 'Yellow' }
Expand Down Expand Up @@ -477,6 +478,13 @@ function Write-ProgressUpdate {
Clear-ProgressLine
Show-ProgressBar -Activity $Activity -Status $Status -Percent $Percent -Icon $Icon -Spinner $Spinner -Color $Color
}
function Get-SpinnerChar {
param([ref]$Index)
if (-not $Global:Spinners -or $Global:Spinners.Length -eq 0) { return '' }
$position = $Index.Value % $Global:Spinners.Length
$Index.Value++
return $Global:Spinners[$position]
}
function Show-Header {
param([string]$SubTitle)
if ($Global:GuiSessionActive) { return }
Expand Down Expand Up @@ -776,7 +784,7 @@ function Invoke-ExternalCommandWithLog {
if ($Activity) {
$spinnerIndex = 0; $percent = 0
while (-not $proc.HasExited -and ($TimeoutSeconds -eq 0 -or ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds)) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
if ($percent -lt 90) { $percent += Get-Random -Minimum 1 -Maximum 3 }
Write-ProgressUpdate -Activity $Activity -Status (Get-SourceTextLoc 'uiText.executing0Seconds' -Args @($elapsed)) -Percent $percent -Icon '⏳' -Spinner $spinner
Expand Down Expand Up @@ -814,6 +822,11 @@ function Invoke-ExternalCommandWithLog {
catch {
$exitCode = if ($null -ne $exitCode) { $exitCode } else { -1 }
if ($_.Exception.Message -match 'Timeout') { $timedOut = $true }
if ($proc -and -not $proc.HasExited) {
try { $proc.Kill() } catch {
Write-Warning "wintoolkit-modules\60-Module.Processes.ps1, Invoke-ExternalCommandWithLog 4: $($_.Exception.Message)"
}
}
Write-ToolkitLog -Level 'ERROR' -Message (Get-SourceTextLoc 'uiText.exceptionWhileRunningExternalCommand') -Context @{
Command = $Command; Arguments = $Arguments; WorkingDir = $WorkingDirectory
TimeoutSec = $TimeoutSeconds; ContextKey = $LogContextKey
Expand Down Expand Up @@ -870,7 +883,7 @@ function Invoke-WithSpinner {
if ($Timer) {
$totalSeconds = $TimeoutSeconds
for ($i = $totalSeconds; $i -gt 0; $i--) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$percent = if ($PercentUpdate) { & $PercentUpdate } else { [math]::Round((($totalSeconds - $i) / $totalSeconds) * 100) }
Write-ProgressUpdate -Activity (Get-SourceTextLoc 'uiText.01Seconds' -Args @($Activity, $i)) -Status '' -Percent $percent -Icon '⏳' -Spinner $spinner -Color 'Yellow'
Start-Sleep -Seconds 1
Expand All @@ -880,7 +893,7 @@ function Invoke-WithSpinner {
}
elseif ($Process -and $result -and $result.GetType().Name -eq 'Process') {
while (-not $result.HasExited -and ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
$percent = if ($PercentUpdate) { & $PercentUpdate } elseif ($percent -lt 90) { $percent + (Get-Random -Minimum 1 -Maximum 3) } else { $percent }
Write-ProgressUpdate -Activity $Activity -Status (Get-SourceTextLoc 'uiText.executing0Seconds' -Args @($elapsed)) -Percent $percent -Icon '⏳' -Spinner $spinner
Expand All @@ -901,7 +914,7 @@ function Invoke-WithSpinner {
elseif ($Job -and $result -is [System.Management.Automation.Job]) {
try {
while ($result.State -eq 'Running' -and ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
$percent = if ($PercentUpdate) { & $PercentUpdate } elseif ($percent -lt 90) { $percent + (Get-Random -Minimum 1 -Maximum 3) } else { $percent }
Write-ProgressUpdate -Activity $Activity -Status (Get-SourceTextLoc 'uiText.executing0Seconds' -Args @($elapsed)) -Percent $percent -Icon '⏳' -Spinner $spinner
Expand Down Expand Up @@ -2945,7 +2958,7 @@ function WinBackupDriver {
-Job -TimeoutSeconds 800 -Action $compressionAction
if ($compressionSucceeded -eq $true -and (Test-Path $archivePath -PathType Leaf)) {
$compressedSizeMB = [Math]::Round((Get-Item $archivePath).Length / 1MB, 2)
$compressionRatio = [Math]::Round((1 - $compressedSizeMB / $totalSizeMB) * 100, 1)
$compressionRatio = if ($totalSizeMB -gt 0) { [Math]::Round((1 - $compressedSizeMB / $totalSizeMB) * 100, 1) } else { 0 }
Write-StyledMessage -Type 'Success' -Text (Get-SourceTextLoc 'toolText.compressionCompleted0MbReduction1' -Args @($compressedSizeMB, $compressionRatio))
return $archivePath
}
Expand Down Expand Up @@ -4678,7 +4691,7 @@ function Uninstall-Office {
$spinnerIndex = 0
while ((Get-Process -Name $blockingProcesses -ErrorAction SilentlyContinue) -and ((Get-Date) - $waitStart).TotalSeconds -lt 2700) {
$elapsed = [math]::Round(((Get-Date) - $waitStart).TotalSeconds, 1)
$spinner = if ($Global:Spinners) { $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] } else { '' }
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
Write-ProgressUpdate -Activity (Get-SourceTextLoc 'toolText.removingOffice') -Status (Get-SourceTextLoc 'toolText.inProgress0Seconds' -Args @($elapsed)) -Percent 90 -Icon '⏳' -Spinner $spinner
Start-Sleep -Milliseconds 500
}
Expand Down
2 changes: 1 addition & 1 deletion tools/Uninstall-Office.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ function Uninstall-Office {
$spinnerIndex = 0
while ((Get-Process -Name $blockingProcesses -ErrorAction SilentlyContinue) -and ((Get-Date) - $waitStart).TotalSeconds -lt 2700) {
$elapsed = [math]::Round(((Get-Date) - $waitStart).TotalSeconds, 1)
$spinner = if ($Global:Spinners) { $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] } else { '' }
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
Write-ProgressUpdate -Activity (Get-SourceTextLoc 'toolText.removingOffice') -Status (Get-SourceTextLoc 'toolText.inProgress0Seconds' -Args @($elapsed)) -Percent 90 -Icon '⏳' -Spinner $spinner
Start-Sleep -Milliseconds 500
}
Expand Down
4 changes: 3 additions & 1 deletion tools/WinBackupDriver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ function WinBackupDriver {

if ($compressionSucceeded -eq $true -and (Test-Path $archivePath -PathType Leaf)) {
$compressedSizeMB = [Math]::Round((Get-Item $archivePath).Length / 1MB, 2)
$compressionRatio = [Math]::Round((1 - $compressedSizeMB / $totalSizeMB) * 100, 1)
# A payload of a few KB rounds $totalSizeMB to 0 (2 decimals on /1MB), which
# would make the ratio below divide by zero and abort the whole backup.
$compressionRatio = if ($totalSizeMB -gt 0) { [Math]::Round((1 - $compressedSizeMB / $totalSizeMB) * 100, 1) } else { 0 }
Write-StyledMessage -Type 'Success' -Text (Get-SourceTextLoc 'toolText.compressionCompleted0MbReduction1' -Args @($compressedSizeMB, $compressionRatio))
return $archivePath
}
Expand Down
4 changes: 4 additions & 0 deletions wintoolkit-modules/10-Module.Localization.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@


# Frame set used by every spinner loop (Invoke-WithSpinner / Invoke-ExternalCommandWithLog).
# It MUST stay initialized here: those loops index it with '% $Global:Spinners.Length'
# and a null/empty array would raise 'Attempted to divide by zero.' on the first tick.
$Global:Spinners = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.ToCharArray()
$Global:MsgStyles = @{
Success = @{ Icon = '✅'; Color = 'Green' }
Warning = @{ Icon = '⚠️'; Color = 'Yellow' }
Expand Down
23 changes: 23 additions & 0 deletions wintoolkit-modules/20-Module.UI.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,29 @@ function Write-ProgressUpdate {
}


function Get-SpinnerChar {
<#
.SYNOPSIS
Returns the next spinner frame, advancing the caller's index by reference.
.DESCRIPTION
DRY/safe accessor for $Global:Spinners. Every spinner loop indexes the frame
set with '% $Global:Spinners.Length': if the array is missing or empty that
modulo evaluates Length as 0 and throws 'Attempted to divide by zero.',
aborting the operation it was meant to animate. This helper degrades to an
empty string instead, so a missing frame set never breaks a real operation.
.PARAMETER Index
Reference to the caller's spinner counter; incremented on each call.
#>
param([ref]$Index)

if (-not $Global:Spinners -or $Global:Spinners.Length -eq 0) { return '' }

$position = $Index.Value % $Global:Spinners.Length
$Index.Value++
return $Global:Spinners[$position]
}


function Show-Header {
<#
.SYNOPSIS
Expand Down
15 changes: 11 additions & 4 deletions wintoolkit-modules/60-Module.Processes.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function Invoke-ExternalCommandWithLog {
if ($Activity) {
$spinnerIndex = 0; $percent = 0
while (-not $proc.HasExited -and ($TimeoutSeconds -eq 0 -or ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds)) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
if ($percent -lt 90) { $percent += Get-Random -Minimum 1 -Maximum 3 }
Write-ProgressUpdate -Activity $Activity -Status (Get-SourceTextLoc 'uiText.executing0Seconds' -Args @($elapsed)) -Percent $percent -Icon '⏳' -Spinner $spinner
Expand Down Expand Up @@ -115,6 +115,13 @@ function Invoke-ExternalCommandWithLog {
catch {
$exitCode = if ($null -ne $exitCode) { $exitCode } else { -1 }
if ($_.Exception.Message -match 'Timeout') { $timedOut = $true }
# An exception raised while the spinner was animating (or any other early
# failure) would otherwise leave the child process running unattended.
if ($proc -and -not $proc.HasExited) {
try { $proc.Kill() } catch {
Write-Warning "wintoolkit-modules\60-Module.Processes.ps1, Invoke-ExternalCommandWithLog 4: $($_.Exception.Message)"
}
}
Write-ToolkitLog -Level 'ERROR' -Message (Get-SourceTextLoc 'uiText.exceptionWhileRunningExternalCommand') -Context @{
Command = $Command; Arguments = $Arguments; WorkingDir = $WorkingDirectory
TimeoutSec = $TimeoutSeconds; ContextKey = $LogContextKey
Expand Down Expand Up @@ -187,7 +194,7 @@ function Invoke-WithSpinner {
if ($Timer) {
$totalSeconds = $TimeoutSeconds
for ($i = $totalSeconds; $i -gt 0; $i--) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$percent = if ($PercentUpdate) { & $PercentUpdate } else { [math]::Round((($totalSeconds - $i) / $totalSeconds) * 100) }
Write-ProgressUpdate -Activity (Get-SourceTextLoc 'uiText.01Seconds' -Args @($Activity, $i)) -Status '' -Percent $percent -Icon '⏳' -Spinner $spinner -Color 'Yellow'
Start-Sleep -Seconds 1
Expand All @@ -197,7 +204,7 @@ function Invoke-WithSpinner {
}
elseif ($Process -and $result -and $result.GetType().Name -eq 'Process') {
while (-not $result.HasExited -and ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
$percent = if ($PercentUpdate) { & $PercentUpdate } elseif ($percent -lt 90) { $percent + (Get-Random -Minimum 1 -Maximum 3) } else { $percent }
Write-ProgressUpdate -Activity $Activity -Status (Get-SourceTextLoc 'uiText.executing0Seconds' -Args @($elapsed)) -Percent $percent -Icon '⏳' -Spinner $spinner
Expand All @@ -218,7 +225,7 @@ function Invoke-WithSpinner {
elseif ($Job -and $result -is [System.Management.Automation.Job]) {
try {
while ($result.State -eq 'Running' -and ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) {
$spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length]
$spinner = Get-SpinnerChar -Index ([ref]$spinnerIndex)
$elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
$percent = if ($PercentUpdate) { & $PercentUpdate } elseif ($percent -lt 90) { $percent + (Get-Random -Minimum 1 -Maximum 3) } else { $percent }
Write-ProgressUpdate -Activity $Activity -Status (Get-SourceTextLoc 'uiText.executing0Seconds' -Args @($elapsed)) -Percent $percent -Icon '⏳' -Spinner $spinner
Expand Down
Loading