private/Invoke-SpinnerTask.ps1
|
function Invoke-SpinnerTask { <# .SYNOPSIS Executes a task with a spinner animation. .DESCRIPTION The Invoke-SpinnerTask function runs a given script block while displaying a spinner animation in the console. It also handles logging of the task's progress and any errors that occur. .PARAMETER Name The name of the task to be executed. .PARAMETER ScriptBlock The script block containing the code to be executed as part of the task. .PARAMETER Indent The number of spaces to indent the task display. .PARAMETER StopOnFailure When specified, re-throws the error after displaying failure status to cascade the failure to parent tasks. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [scriptblock]$ScriptBlock, [Parameter(Mandatory = $false)] [int]$Indent = 0, [Parameter(Mandatory = $false)] [switch]$StopOnFailure ) process { $taskTimer = [System.Diagnostics.Stopwatch]::StartNew() Write-PSTaskLog "TASK START - $Name" # Save current cursor state $originalCursorVisible = [Console]::CursorVisible [Console]::CursorVisible = $false # Get current cursor position $taskPosition = $Host.UI.RawUI.CursorPosition # Generate unique ID for this task $taskId = [Guid]::NewGuid().ToString() # Initialize task state in synchronized hashtable $script:PSTaskJobs.Tasks[$taskId] = @{ Active = $true Position = @{ X = $taskPosition.X + $Indent Y = $taskPosition.Y } Name = $Name } $taskError = $null try { # Write initial task name Write-TaskName -Name $Name -Indent $Indent # Move cursor down one line for output $newY = $Host.UI.RawUI.CursorPosition.Y + 1 [Console]::SetCursorPosition(0, $newY) # Start spinner in background job $spinnerJob = Start-ThreadJob -Name "PSTask_$taskId" -ScriptBlock { param($taskId, $scroll, $delay, $color, $jobs) try { $i = 0 while ($jobs.Tasks[$taskId].Active) { [ConsoleBufferWriter]::WriteTextAtPosition( "$($scroll[$i])", $jobs.Tasks[$taskId].Position.X, $jobs.Tasks[$taskId].Position.Y, $color ) $i = ($i + 1) % $scroll.Length Start-Sleep -Milliseconds $delay } } catch { # Log error but don't throw Write-PSTaskLog "Spinner update error: $_" -Level "ERROR" } } -ArgumentList $taskId, $script:Config.Spinner.Chars, $script:Config.Spinner.Delay, $script:Config.Spinner.Color, $script:PSTaskJobs # Execute main task $output = . $ScriptBlock *>&1 # Process output with null check and string validation $hasErrors = $false $output | Where-Object { $_ -ne $null } | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $hasErrors = $true $fullErrorMessage = Format-ErrorForLog $_ if (![string]::IsNullOrWhiteSpace($fullErrorMessage)) { Write-PSTaskLog $fullErrorMessage -Level "ERROR" } } else { $message = $_.ToString() if (![string]::IsNullOrWhiteSpace($message)) { Write-PSTaskLog $message } } } $status = if ($hasErrors) { "Warning" } else { "Success" } } catch { $status = "Failure" $taskError = $_ $fullErrorMessage = Format-ErrorForLog $_ if (![string]::IsNullOrWhiteSpace($fullErrorMessage)) { Write-PSTaskLog $fullErrorMessage -Level "ERROR" } } finally { # Mark task as inactive $script:PSTaskJobs.Tasks[$taskId].Active = $false # Clean up spinner job if ($spinnerJob) { Stop-Job -Job $spinnerJob -ErrorAction SilentlyContinue Remove-Job -Job $spinnerJob -ErrorAction SilentlyContinue } # Write final status $taskTimer.Stop() Write-FinalStatus -Name $Name -Status $status -Indent $Indent -Duration $taskTimer.Elapsed # Remove task from collection $script:PSTaskJobs.Tasks.Remove($taskId) # Restore cursor visibility [Console]::CursorVisible = $originalCursorVisible $durationText = "{0:N1}s" -f $taskTimer.Elapsed.TotalSeconds Write-PSTaskLog "TASK END - $Name - $status ($durationText)" } # Show error message in console so the user can see why it failed if ($taskError) { Write-Host " $($taskError.Exception.Message)" -ForegroundColor $script:Config.StatusColors.Failure } # Re-throw after cleanup so parent tasks can catch the failure if ($StopOnFailure -and $taskError) { throw $taskError } } } |