Public/Invoke-Task.ps1
|
function Invoke-Task { <# .SYNOPSIS Executes tasks on remote computers in parallel. .DESCRIPTION The primary cmdlet for executing tasks or script blocks across multiple computers in parallel. Supports throttling, timeouts, retries, progress reporting, and multiple output formats. .PARAMETER Computers Target computer names, IP addresses, or file paths. Accepts pipeline input. Supports automatic file detection and parsing: - .txt files: One computer per line (lines starting with # are ignored) - .csv files: Use filepath:ColumnName to specify column, or first column is used - .xlsx/.xls files: Use filepath:ColumnName to specify column, or first column is used Can mix computer names and file paths in the same parameter. .PARAMETER ComputerFile (Deprecated - use -Computers with file path instead) Path to a file containing computer names (one per line). .PARAMETER TaskName Name of a defined task to execute (e.g., "File.TestPathExists"). .PARAMETER ScriptBlock Ad-hoc script block to execute on targets. .PARAMETER TaskParameters Hashtable of parameters to pass to the task or script block. .PARAMETER Credential PSCredential for remote authentication. .PARAMETER ThrottleLimit Maximum concurrent executions (default from config). .PARAMETER Timeout Timeout per target in seconds (default from config). .PARAMETER RetryCount Number of retry attempts on failure (default from config). .PARAMETER Protocol Remote protocol (WinRM, SSH, Auto). .PARAMETER ShowProgress Display progress bar during execution. .PARAMETER OutputFormat Export format (CSV, JSON, XML, Excel, None). .PARAMETER OutputPath Path to save exported results. .PARAMETER PassThru Return results to pipeline even when exporting. .EXAMPLE Invoke-Task -Computers "Server1","Server2" -TaskName "System.GetUptime" Executes the GetUptime task on two servers. .EXAMPLE Invoke-Task -Computers "C:\servers.txt" -TaskName "System.GetUptime" Reads computer names from a text file and executes the task. .EXAMPLE Invoke-Task -Computers "C:\inventory.csv:ComputerName" -TaskName "System.GetBasicInfo" Reads computer names from the "ComputerName" column in a CSV file. .EXAMPLE Invoke-Task -Computers "C:\servers.xlsx:HostName","Server99" -TaskName "Network.TestPort" -TaskParameters @{TargetHost="8.8.8.8";Port=53} Mixes computers from Excel file and direct computer name with task parameters. .EXAMPLE Invoke-Task -Computers "Server1" -TaskName "File.TestPathExists" -TaskParameters @{Path="C:\Temp"} Executes a task with parameters. .EXAMPLE Invoke-Task -Computers "C:\servers.csv" -TaskName "System.GetBasicInfo" -OutputFormat Excel -OutputPath results.xlsx Reads from CSV (uses first column) and exports results to Excel. .EXAMPLE $cred = Get-Credential Invoke-Task -Computers "Server1","Server2" -TaskName "System.GetUptime" -Credential $cred Executes task on remote servers using provided credentials for authentication. .OUTPUTS TbTaskResult[] - Array of task execution results. #> [CmdletBinding(DefaultParameterSetName = "TaskName")] param( [Parameter(Mandatory, ParameterSetName = "TaskName", ValueFromPipeline, ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = "ScriptBlock", ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("Computer", "ComputerName", "CN")] [string[]]$Computers, [Parameter(ParameterSetName = "TaskName")] [Parameter(ParameterSetName = "ScriptBlock")] [Alias("File")] [string]$ComputerFile, [Parameter(Mandatory, ParameterSetName = "TaskName", Position = 0)] [string]$TaskName, [Parameter(Mandatory, ParameterSetName = "ScriptBlock", Position = 0)] [scriptblock]$ScriptBlock, [Parameter()] [Alias("Parameters", "Args")] [hashtable]$TaskParameters = @{}, [Parameter()] [System.Management.Automation.PSCredential]$Credential, [Parameter()] [ValidateRange(1, 256)] [int]$ThrottleLimit, [Parameter()] [ValidateRange(1, 3600)] [int]$Timeout, [Parameter()] [ValidateRange(0, 10)] [int]$RetryCount, [Parameter()] [ValidateSet("WinRM", "SSH", "Auto")] [string]$Protocol = "Auto", [Parameter()] [switch]$ShowProgress, [Parameter()] [ValidateSet("CSV", "JSON", "XML", "Excel", "None")] [string]$OutputFormat = "None", [Parameter()] [string]$OutputPath, [Parameter()] [switch]$PassThru ) begin { # Generate unique run ID $runId = [guid]::NewGuid().ToString() $allComputers = [System.Collections.ArrayList]::new() Write-Verbose "Invoke-Task started. RunId: $runId" # Load configuration defaults $config = Get-TbConfig if (-not $ThrottleLimit) { $ThrottleLimit = $config.Execution.DefaultThrottle } if (-not $Timeout) { $Timeout = $config.Execution.DefaultTimeout } if ($PSBoundParameters.ContainsKey("RetryCount") -eq $false) { $RetryCount = $config.Execution.DefaultRetryCount } # Log invocation Write-TbLog -Message "Invoke-Task started" -Level Info -RunId $runId -Data @{ ParameterSet = $PSCmdlet.ParameterSetName TaskName = $TaskName ThrottleLimit = $ThrottleLimit Timeout = $Timeout RetryCount = $RetryCount } # Validate task if using TaskName $taskDefinition = $null if ($PSCmdlet.ParameterSetName -eq "TaskName") { try { $taskDefinition = Get-TaskDefinition -TaskName $TaskName -ErrorAction Stop if (-not $taskDefinition) { throw "Task '$TaskName' not found" } Write-Verbose "Loaded task definition: $TaskName (Version: $($taskDefinition.Version))" # Check compatibility if (-not (Test-TaskCompatibility -TaskDefinition $taskDefinition)) { throw "Task '$TaskName' is not compatible with current environment" } # Load task script $ScriptBlock = [scriptblock]::Create((Get-Content -Path $taskDefinition.FullScriptPath -Raw)) # Use task-specific timeout/retry if defined if ($taskDefinition.Timeout -and -not $PSBoundParameters.ContainsKey("Timeout")) { $Timeout = $taskDefinition.Timeout } if ($taskDefinition.RetryCount -and -not $PSBoundParameters.ContainsKey("RetryCount")) { $RetryCount = $taskDefinition.RetryCount } } catch { Write-Error "Failed to load task "$TaskName": $_" Write-TbLog -Message "Failed to load task" -Level Error -RunId $runId -TaskName $TaskName -ErrorRecord $_ return } } # Load computers from file if specified if ($ComputerFile) { if (-not (Test-Path $ComputerFile)) { Write-Error "Computer file not found: $ComputerFile" return } try { $fileComputers = Get-Content -Path $ComputerFile | Where-Object { $_ -and $_.Trim() -and -not $_.StartsWith("#") } | ForEach-Object { $_.Trim() } $allComputers.AddRange($fileComputers) Write-Verbose "Loaded $($fileComputers.Count) computers from file: $ComputerFile" } catch { Write-Error "Failed to read computer file: $_" return } } } process { # Collect computers from pipeline or parameter if ($Computers) { foreach ($item in $Computers) { # Extract base path (without :column suffix) $basePath = if ($item -match "^(.+?):\w+$") { $Matches[1] } else { $item } # Check if this is a file that exists if ((Test-Path -Path $basePath -PathType Leaf -ErrorAction SilentlyContinue)) { $extension = [System.IO.Path]::GetExtension($basePath).ToLower() # Check if it"s a supported file format if ($extension -in ".txt", ".csv", ".xlsx", ".xls") { # It"s a file - parse it try { $fileComputers = Get-ComputersFromFile -FilePath $item $allComputers.AddRange($fileComputers) Write-Verbose "Loaded $($fileComputers.Count) computers from file: $item" } catch { Write-Error "Failed to load computers from file "$item": $_" return } } else { # File exists but not a supported format - treat as computer name $allComputers.Add($item) | Out-Null } } else { # Not a file or doesn"t exist - treat as computer name $allComputers.Add($item) | Out-Null } } } } end { if ($allComputers.Count -eq 0) { Write-Error "No computers specified. Use -Computers or -ComputerFile parameter." return } # Remove duplicates $uniqueComputers = $allComputers | Select-Object -Unique Write-Verbose "Processing $($uniqueComputers.Count) unique computer(s)" try { # Create work items $workItems = @() foreach ($computer in $uniqueComputers) { $workItem = [TbWorkItem]::new($computer, $TaskName) $workItem.ScriptBlock = $ScriptBlock $workItem.TaskParameters = $TaskParameters $workItem.Timeout = $Timeout $workItem.RetryCount = $RetryCount $workItem.Credential = $Credential $workItems += $workItem } Write-Verbose "Created $($workItems.Count) work items" # Execute work queue $progressActivity = if ($TaskName) { "Executing Task: $TaskName" } else { "Executing Script Block" } $results = Start-TbWorkQueue -WorkItems $workItems ` -ThrottleLimit $ThrottleLimit ` -RunId $runId ` -ShowProgress:$ShowProgress ` -ProgressActivity $progressActivity Write-Verbose "Execution completed. Total results: $($results.Count)" # Generate summary $successCount = ($results | Where-Object { $_.IsSuccess() }).Count $failureCount = ($results | Where-Object { -not $_.IsSuccess() }).Count Write-Host "" Write-Host "Execution Summary:" -ForegroundColor Cyan Write-Host " Total Computers: $($results.Count)" -ForegroundColor White Write-Host " Successful: $successCount" -ForegroundColor Green Write-Host " Failed: $failureCount" -ForegroundColor $(if ($failureCount -gt 0) { "Red" } else { "White" }) Write-Host "" Write-TbLog -Message "Invoke-Task completed" -Level Info -RunId $runId -Data @{ TotalComputers = $results.Count Successful = $successCount Failed = $failureCount } # Export results if requested if ($OutputFormat -ne "None" -or $OutputPath) { if (-not $OutputPath) { $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $fileName = if ($TaskName) { "TaskResults_${TaskName}_${timestamp}" } else { "TaskResults_${timestamp}" } $extension = switch ($OutputFormat) { "CSV" { "csv" } "JSON" { "json" } "XML" { "xml" } "Excel" { "xlsx" } default { "csv" } } $OutputPath = Join-Path (Get-Location) "$fileName.$extension" } try { Export-TaskResult -Results $results -OutputPath $OutputPath -Format $OutputFormat Write-Host "Results exported to: $OutputPath" -ForegroundColor Green } catch { Write-Warning "Failed to export results: $_" } } # Return results if ($OutputFormat -eq "None" -or $PassThru) { return $results } } catch { Write-Error "Task execution failed: $_" Write-TbLog -Message "Invoke-Task failed" -Level Error -RunId $runId -ErrorRecord $_ throw } } } |