public/Show-PSTaskTree.ps1
|
function Show-PSTaskTree { <# .SYNOPSIS Displays a tree visualization of completed tasks and their hierarchy. .DESCRIPTION Creates a visual representation of task execution, showing parent-child relationships, execution time, and status for all tasks that were run. .PARAMETER LogPath The path to the log file to analyze. If not specified, uses the active log, or the last completed log. .EXAMPLE Show-PSTaskTree Output example: Root Task [2.5s] ✓ ├── Subtask 1 [1.2s] ✓ │ ├── Nested Task A [0.3s] ✓ │ └── Nested Task B [0.8s] ✓ └── Subtask 2 [0.8s] ✗ └── Nested Task C [0.8s] ✗ #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] [string]$LogPath ) begin { # Tree drawing characters $chars = @{ Corner = "└──" Branch = "├──" Vertical = "│ " Space = " " } } process { if (-not $LogPath) { $LogPath = if ($script:PSTaskLoggingState.IsLogging) { $script:PSTaskLoggingState.LogPath } else { $script:PSTaskLoggingState.LastLogPath } } if (-not $LogPath) { Write-Warning "No log file available to analyze" return } # Parse log file to build task tree $tasks = @{} $rootTasks = [System.Collections.ArrayList]::new() $currentNesting = 0 $nestingStack = [System.Collections.Stack]::new() # Suppress output from ArrayList operations by assigning to $null $null = Get-Content $LogPath -ErrorAction SilentlyContinue | ForEach-Object { if ($_ -match "\[(.*?)\] \[INFO\] TASK START - (.*)") { $timestamp = [datetime]::ParseExact($matches[1], "yyyy-MM-dd HH:mm:ss", $null) $taskName = $matches[2] $task = @{ Name = $taskName StartTime = $timestamp EndTime = $null Status = $null Children = [System.Collections.ArrayList]@() Nesting = $currentNesting Parent = if ($nestingStack.Count -gt 0) { $nestingStack.Peek() } else { $null } } $tasks[$taskName] = $task if ($currentNesting -eq 0) { $null = $rootTasks.Add($task) } elseif ($task.Parent) { $null = $tasks[$task.Parent.Name].Children.Add($task) } $nestingStack.Push($task) $currentNesting++ } elseif ($_ -match "\[(.*?)\] \[INFO\] TASK END - (.*) - (Success|Warning|Failure)") { $timestamp = [datetime]::ParseExact($matches[1], "yyyy-MM-dd HH:mm:ss", $null) $taskName = $matches[2] $status = $matches[3] if ($tasks.ContainsKey($taskName)) { $tasks[$taskName].EndTime = $timestamp $tasks[$taskName].Status = $status if ($nestingStack.Count -gt 0 -and $nestingStack.Peek().Name -eq $taskName) { $nestingStack.Pop() $currentNesting-- } } } } function Write-TaskNode { param($Task, $Prefix = "", $Connector = "") $duration = $Task.EndTime - $Task.StartTime $statusSymbol = if ($script:Config.StatusChars.($Task.Status)) { $script:Config.StatusChars.($Task.Status) } else { $script:Config.StatusChars.Default } $statusColor = if ($script:Config.StatusColors.($Task.Status)) { $script:Config.StatusColors.($Task.Status) } else { $script:Config.StatusColors.Default } $line = "{0}{1}{2} [{3:N1}s] {4}" -f $Prefix, $Connector, $Task.Name, $duration.TotalSeconds, $statusSymbol Write-Host $line -ForegroundColor $statusColor $childPrefix = if (-not $Connector) { $Prefix } elseif ($Connector -eq $chars.Corner) { $Prefix + $chars.Space } else { $Prefix + $chars.Vertical } for ($i = 0; $i -lt $Task.Children.Count; $i++) { $isLast = ($i -eq $Task.Children.Count - 1) $childConnector = if ($isLast) { $chars.Corner } else { $chars.Branch } Write-TaskNode -Task $Task.Children[$i] -Prefix $childPrefix -Connector $childConnector } } $rootTasks | ForEach-Object { Write-TaskNode $_ } } } |