Classes/CommandExecutor.ps1
# In charge of executing and tracking progress of commands class CommandExecutor { [RoleManager]$RoleManager [int]$HistoryToKeep = 100 [int]$ExecutedCount = 0 # Recent history of commands executed [System.Collections.ArrayList]$History = (New-Object System.Collections.ArrayList) # Plugin commands get executed as PowerShell jobs # This is to keep track of those hidden [hashtable]$_JobTracker = @{} CommandExecutor([RoleManager]$RoleManager) { $this.RoleManager = $RoleManager } # Invoke a command # Should this live in the Plugin or in the main bot class? [CommandResult]ExecuteCommand([PluginCommand]$PluginCmd, [ParsedCommand]$ParsedCommand, [String]$UserId) { $command = $pluginCmd.Command # Our result $r = [CommandResult]::New() # Verify command is not disabled if (-not $Command.Enabled) { $err = [CommandDisabled]::New("Command [$($Command.Name)] is disabled") $r.Success = $false $r.Errors += $err Write-Error -Exception $err return $r } # Verify that all mandatory parameters have been provided for "command" type bot commands # This doesn't apply to commands triggered from regex matches, timers, or events if ($command.Trigger.Type -eq [TriggerType]::Command) { if (-not $this.ValidateMandatoryParameters($ParsedCommand, $command)) { $msg = "Mandatory parameters for [$($Command.Name)] not provided.`nUsage:`n" foreach ($usage in $Command.Usage) { $msg += " $usage`n" } $err = [CommandRequirementsNotMet]::New($msg) $r.Success = $false $r.Errors += $err Write-Error -Exception $err return $r } } # If command is [command] type verify that the caller is authorized to execute command if ($command.Trigger.Type -eq [TriggerType]::Command) { $authorized = $command.IsAuthorized($UserId, $this.RoleManager) } else { $authorized = $true } if ($authorized) { $jobDuration = Measure-Command -Expression { if ($existingCommand.AsJob) { $job = $command.Invoke($ParsedCommand, $true) # TODO # Tracking the job will be used later so we can continue on # without having to wait for the job to complete #$this.TrackJob($job) $job | Wait-Job # Capture all the streams $r.Streams.Error = $job.ChildJobs[0].Error.ReadAll() $r.Streams.Information = $job.ChildJobs[0].Information.ReadAll() $r.Streams.Verbose = $job.ChildJobs[0].Verbose.ReadAll() $r.Streams.Warning = $job.ChildJobs[0].Warning.ReadAll() $r.Output = $job.ChildJobs[0].Output.ReadAll() Write-Verbose -Message "Command results: `n$($r | ConvertTo-Json)" # Determine if job had any terminating errors if ($job.State -eq 'Failed' -or $r.Streams.Error.Count -gt 0) { $r.Success = $false } else { $r.Success = $true } } else { try { $hash = $command.Invoke($ParsedCommand, $false) $r.Errors = $hash.Error $r.Streams.Error = $hash.Error $r.Streams.Information = $hash.Information $r.Streams.Warning = $hash.Warning $r.Output = $hash.Output if ($r.Errors.Count -gt 0) { $r.Success = $false } else { $r.Success = $true } } catch { $r.Success = $false $r.Errors = $_.Exception.Message $r.Streams.Error = $_.Exception.Message } } } $r.Duration = $jobDuration # Add command result to history if ($command.KeepHistory) { $this.AddToHistory($PluginCmd.ToString(), $UserId, $r, $ParsedCommand) } } else { $r.Success = $false $r.Authorized = $false $r.Errors += [CommandNotAuthorized]::New("Command [$($Command.Name)] was not authorized for user [$($UserId)]") } # Track number of commands executed if ($r.Success) { $this.ExecutedCount++ } return $r } [void]TrackJob($job) { if (-not $this._JobTracker.ContainsKey($job.Name)) { $this._JobTracker.Add($job.Name, $job) } } # Add command result to history [void]AddToHistory([string]$CommandName, [string]$UserId, [CommandResult]$Result, [ParsedCommand]$ParsedCommand) { #$this.History += [CommandHistory]::New($CommandName, $UserId, $Result, $ParsedCommand) if ($this.History.Count -ge $this.HistoryToKeep) { $this.History.RemoveAt(0) > $null } $this.History.Add([CommandHistory]::New($CommandName, $UserId, $Result, $ParsedCommand)) } # Validate that all mandatory parameters have been provided [bool]ValidateMandatoryParameters([ParsedCommand]$ParsedCommand, [Command]$Command) { $functionInfo = $Command.FunctionInfo $matchedParamSet = $null $validated = $false foreach ($parameterSet in $functionInfo.ParameterSets) { Write-Verbose -Message "[CommandExecutor:ValidateMandatoryParameters] Validating parameters for parameter set [$($parameterSet.Name)]" $mandatoryParameters = @($parameterSet.Parameters | where IsMandatory -eq $true).Name if ($mandatoryParameters.Count -gt 0) { # Remove each provided mandatory parameter from the list # so we can find any that will have to be coverd by positional parameters Write-Verbose -Message "Provided named parameters: $($ParsedCommand.NamedParameters.Keys | Format-List | Out-String)" foreach ($providedNamedParameter in $ParsedCommand.NamedParameters.Keys ) { Write-Verbose -Message "Named parameter [$providedNamedParameter] provided" $mandatoryParameters = @($mandatoryParameters | Where-Object {$_ -ne $providedNamedParameter}) } if ($mandatoryParameters.Count -gt 0) { if ($ParsedCommand.PositionalParameters.Count -lt $mandatoryParameters.Count) { $validated = $false } else { $validated = $true } } else { $validated = $true } } else { $validated = $true } Write-Verbose -Message "[CommandExecutor:ValidateMandatoryParameters] Valid parameters for parameterset [$($parameterSet.Name)] [$($validated.ToString())]" if ($validated) { break } } return $validated } } |