public/Invoke-OctaTaskBrowser.ps1
|
function Invoke-OctaTaskBrowser { <# .SYNOPSIS Lists scheduled tasks and can enable/disable/run/stop/delete one by TaskPath\TaskName. FR-044. Enable/disable reuse the same snapshot + Run Record mechanism as Invoke-OctaStartupManager - Undo-OctaRun needs no changes. #> [CmdletBinding()] param( [string]$Enable, [string]$Disable, [string]$Run, [string]$Stop, [string]$Delete, [switch]$Yes ) function Split-OctaTaskFullName { param([string]$FullName) $lastSlash = $FullName.LastIndexOf('\') # A name with no backslash at all (user typed just "MyTask") has no path component - # LastIndexOf returns -1, which used to yield TaskPath = '' and a confusing downstream # failure. Default to the root task path, which is where such a task would actually live. if ($lastSlash -lt 0) { return @{ TaskPath = '\'; TaskName = $FullName } } return @{ TaskPath = $FullName.Substring(0, $lastSlash + 1) TaskName = $FullName.Substring($lastSlash + 1) } } function Test-OctaTaskExists { param([hashtable]$Parts) return [bool](Get-ScheduledTask -TaskPath $Parts.TaskPath -TaskName $Parts.TaskName -ErrorAction SilentlyContinue) } if (-not ($Enable -or $Disable -or $Run -or $Stop -or $Delete)) { $tasks = @(Get-ScheduledTask -ErrorAction SilentlyContinue) foreach ($t in $tasks) { Write-Host ("{0,-10} {1}{2}" -f $t.State, $t.TaskPath, $t.TaskName) } return $tasks } if ($Enable -or $Disable) { $fullName = if ($Enable) { $Enable } else { $Disable } $parts = Split-OctaTaskFullName -FullName $fullName # ponytail: check the task actually exists BEFORE doing anything. Enable/Disable-ScheduledTask # only raise a non-terminating error for an unknown task, so this used to create a run # folder, write a Run Record with a null snapshot, and return Status = 'Success' for a task # that was never touched (verified: '\NoExiste\TareaFantasma123' reported Success). if (-not (Test-OctaTaskExists -Parts $parts)) { return [pscustomobject]@{ Status = 'NotFound'; Message = "Scheduled task not found: $fullName" } } # ponytail: named $runFolder, NOT $run - PowerShell variable names are case-insensitive, # so the old `$run = New-OctaRunFolder` silently overwrote this function's own [string]$Run # parameter with a folder object. Harmless only because this branch returns before the # `if ($Run)` block below ever reads it; renaming removes the landmine entirely. $runFolder = New-OctaRunFolder $snapshot = Save-OctaActionSnapshot -Action (New-OctaAction -TargetType ScheduledTask -TargetIdentifier "$($parts.TaskPath)|$($parts.TaskName)" -CurrentValue 'n/a' -PlannedValue 'n/a') -RunFolder $runFolder.Path try { if ($Enable) { Enable-ScheduledTask -TaskPath $parts.TaskPath -TaskName $parts.TaskName -ErrorAction Stop | Out-Null } else { Disable-ScheduledTask -TaskPath $parts.TaskPath -TaskName $parts.TaskName -ErrorAction Stop | Out-Null } } catch { return [pscustomobject]@{ Status = 'Error'; Message = $_.Exception.Message } } Save-OctaRunRecord -RunFolder $runFolder.Path -RunRecord ([pscustomobject]@{ RunId = $runFolder.RunId Timestamp = (Get-Date).ToString('o') CategoriesApplied = @('task-browser') RestorePointStatus = 'NotAttempted' RestorePointSequenceNumber = $null ActionSnapshots = @($snapshot) IrreversibleActionRefs = @() Status = 'Completed' }) return [pscustomobject]@{ Status = 'Success'; RunId = $runFolder.RunId } } if ($Run) { $parts = Split-OctaTaskFullName -FullName $Run if (-not (Test-OctaTaskExists -Parts $parts)) { return [pscustomobject]@{ Status = 'NotFound'; Message = "Scheduled task not found: $Run" } } try { Start-ScheduledTask -TaskPath $parts.TaskPath -TaskName $parts.TaskName -ErrorAction Stop } catch { return [pscustomobject]@{ Status = 'Error'; Message = $_.Exception.Message } } return [pscustomobject]@{ Status = 'Success'; Message = "Started $Run" } } if ($Stop) { $parts = Split-OctaTaskFullName -FullName $Stop if (-not (Test-OctaTaskExists -Parts $parts)) { return [pscustomobject]@{ Status = 'NotFound'; Message = "Scheduled task not found: $Stop" } } try { Stop-ScheduledTask -TaskPath $parts.TaskPath -TaskName $parts.TaskName -ErrorAction Stop } catch { return [pscustomobject]@{ Status = 'Error'; Message = $_.Exception.Message } } return [pscustomobject]@{ Status = 'Success'; Message = "Stopped $Stop" } } if ($Delete) { $parts = Split-OctaTaskFullName -FullName $Delete # Checked before prompting, so the user is never asked to confirm deleting something # that isn't there. if (-not (Test-OctaTaskExists -Parts $parts)) { return [pscustomobject]@{ Status = 'NotFound'; Message = "Scheduled task not found: $Delete" } } Write-Host "Deleting a scheduled task is irreversible (no snapshot can re-create its definition)." if (-not $Yes) { $response = Read-Host "Delete $Delete ? (y/N)" if ($response -notin @('y', 'Y', 's', 'S')) { return [pscustomobject]@{ Status = 'Cancelled' } } } try { Unregister-ScheduledTask -TaskPath $parts.TaskPath -TaskName $parts.TaskName -Confirm:$false -ErrorAction Stop } catch { return [pscustomobject]@{ Status = 'Error'; Message = $_.Exception.Message } } return [pscustomobject]@{ Status = 'Success'; Message = "Deleted $Delete" } } } |