/// <summary> /// Create powershell instance for each Job and run it on Ruspacepool. /// Use Tasks to create threads and collect the results. /// If Force parameter is not used and first Job, perform Error check before Queuing remaining Jobs /// </summary> protected override void ProcessRecord() { try { jobNum++; do { try { //Create a powershell instance for each input object and it will be processed on a seperate thread PowerShell powerShell = PowerShell.Create(); IDictionary psParams = RunspaceMethods.FindParams(proxyFunction, ScriptToRun, commandName, InputObject); //If command supplied doesn't take any input, bail out. //This check can be removed if this cmdlet needs to run 'something' in parallel, but just didn't find a use case yet if (psParams.Count <= 0) { throw new Exception("No Parameters were used on the command, Please verify the command."); } if (jobNum == 1) { LogHelper.LogProgress("Queuing First Job", this, quiet: Quiet.IsPresent); LogHelper.Log(fileVerboseLogTypes, "Paramaters used for the first job:", this, NoFileLogging.IsPresent); foreach (DictionaryEntry param in psParams) { string paramValues = null; if (param.Value is Array) { foreach (var val in param.Value as Array) { paramValues += val?.ToString() + ", "; } } else { paramValues = param.Value?.ToString(); } LogHelper.Log(fileVerboseLogTypes, string.Format("{0} - {1}", param.Key.ToString(), paramValues), this, NoFileLogging.IsPresent); } } if (cmdInfo.CommandType == CommandTypes.ExternalScript) { powerShell.AddScript(((ExternalScriptInfo)cmdInfo).ScriptContents).AddParameters(psParams); } else { powerShell.AddCommand(commandName).AddParameters(psParams); } powerShell.RunspacePool = runspacePool; //Creates the task and the continuation tasks Task <PSDataCollection <PSObject> > task = Task <PSDataCollection <PSObject> > .Factory.FromAsync(powerShell.BeginInvoke(), r => powerShell.EndInvoke(r)); task.ContinueWith(t => { var currentJob = CheckandCreateJob( jobName: InputObject.ToString(), task: t, jobID: jobNum, ps: powerShell); currentJob.IsFaulted = true; currentJob.Exceptions = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted); //Continuation tasks may have already created the Job, check before creating it. Job job = CheckandCreateJob( jobName: InputObject.ToString(), task: task, jobID: jobNum, ps: powerShell); if (!jobs.TryAdd(job.ID, job)) { if (jobNum == 1) { //We might retry the Job 1 during Error Check, so ignore the error and replace the first job jobs.TryRemove(jobs.First().Key, out Job removedJob); jobs.TryAdd(job.ID, job); } else { throw new Exception(string.Format("Unable to add job ID: {0}", job.ID)); } } if (!Async.IsPresent && (jobs.Count == BatchSize || (!this.Force.IsPresent && (jobNum == 1)))) { CollectJobs(this, jobs, jobNum, ProgressBarStage, Force, ReturnasJobObject, AppendJobNameToResult, jobNum); } if (Async.IsPresent) { WriteObject(job); } } catch (AggregateException ae) when(jobNum == 1 && ae.InnerExceptions.Where(ie => ie is CommandNotFoundException).Count() > 0) { IEnumerable <Exception> exceptions = ae.InnerExceptions.Where(ie => ie is CommandNotFoundException); List <string> debugStrings = new List <string>(); foreach (Exception exception in exceptions) { CommandInfo commandInfo = RunspaceMethods.GetCommandInfo(((CommandNotFoundException)exception).CommandName); if (commandInfo == null) { throw (CommandNotFoundException)exception; } RunspaceMethods.ModuleDetails moduleDetails = RunspaceMethods.GetModuleDetails(commandInfo, debugStrings); LogHelper.LogDebug(debugStrings, this); LogHelper.Log(fileVerboseLogTypes, exception.Message, this, NoFileLogging); if (moduleDetails.IsFromRemotingModule) { LogHelper.Log(fileVerboseLogTypes, "The command is from a remotePS connection, cannot load local and remote runspaces together", this, NoFileLogging); throw exception; } RunspaceMethods.LoadISSWithModuleDetails(moduleDetails, runspacePool.InitialSessionState); } runspacePool = RunspaceMethods.ResetRunspacePool(1, MaxThreads, runspacePool.InitialSessionState.Clone(), new DummyCustomPSHost()); LogHelper.LogDebug("Resetting Runspace to load the new modules", this); LogHelper.Log(fileVerboseLogTypes, "Retrying first job", this, NoFileLogging); runspacePool.Open(); } } while (!Force.IsPresent && jobNum == 1 && jobs.FirstOrDefault().Value?.IsFaulted == true); if (!Force.IsPresent && (jobs.Values.Where(f => f.IsFaulted == true).Count() / jobNum * 100) > promptOnPercentFaults) { if (!ShouldContinue(string.Format("More than {0}% of the jobs are faulted, Do you want to continue with processing rest of the Inputs?", promptOnPercentFaults), "Most Jobs Faulted")) { throw new Exception("Most jobs faulted"); } } } catch (Exception e) { ErrorRecord error = new ErrorRecord(e, e.GetType().Name, ErrorCategory.InvalidData, this); LogHelper.Log(fileVerboseLogTypes, error.Exception.ToString(), this, NoFileLogging.IsPresent); CleanupObjs(this, proxyFunction, runspacePool, e is PipelineStoppedException); ThrowTerminatingError(error); } }
ConcurrentDictionary <int, Job> jobs = new ConcurrentDictionary <int, Job>();//(MaxParallel, MaxParallel); /// <summary> /// Discover the commad passed and prepare the RunspacePool /// </summary> protected override void BeginProcessing() { List <string> debugStrings = new List <string>(); //System.Globalization.CultureInfo //Environment.GetFolderPath(Environment.SpecialFolder.) //Cmdlet will work properly if the Job input is from pipeline, if Input was specified as a parameter just error out if (!MyInvocation.ExpectingInput) { throw new Exception("Cmdlet will only accept input from Pipeline. Please see examples"); } LogHelper.LogProgress("Starting script", this, quiet: Quiet.IsPresent); if (Async.IsPresent) { LogHelper.Log(fileWarningLogTypes, "Async switch is used, Use Get-InvokeAllJobsStatus to check the job status and Receive-InvokeAllJobs to collect the results", this, NoFileLogging.IsPresent); LogHelper.Log(fileWarningLogTypes, "No Error check done when Async switch is used. If additional PS modules are required to run the command, please specify them using -ModulestoLoad switch", this, NoFileLogging.IsPresent); } if (BatchSize < MaxThreads) { //TODo: Log warning not error LogHelper.Log(fileErrorLogTypes, new ErrorRecord(new Exception(string.Format("The Batchsize {0} is less than the MaxThreads {1}. If you have reduced the Batchsize as a result of throttling, " + "this is ok, else increase the BatchSize for better performance", BatchSize, MaxThreads)), "BadParameterValues", ErrorCategory.InvalidData, this), this, NoFileLogging.IsPresent); } try { LogHelper.Log(fileVerboseLogTypes, "Starting script", this, NoFileLogging.IsPresent); LogHelper.Log(fileVerboseLogTypes, MyInvocation.Line, this, NoFileLogging.IsPresent); cmdInfo = RunspaceMethods.CmdDiscovery(ScriptToRun, out commandName); if (cmdInfo == null) { throw new Exception("Unable to find the command specified to Invoke, please make sure required modules or functions are loaded to PS Session"); } if (supportedCommandTypes.Contains(cmdInfo.CommandType)) { LogHelper.Log(fileVerboseLogTypes, String.Format("The supplied command {0} is of type {1}", commandName, cmdInfo.CommandType.ToString()), this, NoFileLogging.IsPresent); } else { string notSupportedErrorString = "Invoke-All doesn't implement methods to run this command, Supported types are PSScripts, PSCmdlets and PSFunctions"; LogHelper.Log(fileVerboseLogTypes, notSupportedErrorString, this, NoFileLogging.IsPresent); throw new Exception(notSupportedErrorString); } proxyFunction = RunspaceMethods.CreateProxyFunction(cmdInfo, debugStrings); LogHelper.LogDebug(debugStrings, this); if (proxyFunction == null) { throw new Exception("Unable to create proxyfunction, please restart the powershell session and try again"); } else { LogHelper.Log(fileVerboseLogTypes, string.Format("Created ProxyFunction {0}", proxyFunction.Name), this, NoFileLogging.IsPresent); } IList <SessionStateVariableEntry> stateVariableEntries = new List <SessionStateVariableEntry>(); if (CopyLocalVariables.IsPresent) { string psGetUDVariables = @" function Get-UDVariable { get-variable | where-object {(@( 'FormatEnumerationLimit', 'MaximumAliasCount', 'MaximumDriveCount', 'MaximumErrorCount', 'MaximumFunctionCount', 'MaximumVariableCount', 'PGHome', 'PGSE', 'PGUICulture', 'PGVersionTable', 'PROFILE', 'PSSessionOption' ) -notcontains $_.name) -and ` (([psobject].Assembly.GetType('System.Management.Automation.SpecialVariables').GetFields('NonPublic,Static') ` | Where-Object FieldType -eq ([string]) | ForEach-Object GetValue $null)) -notcontains $_.name } } Get-UDVariable "; IEnumerable <PSVariable> variables = ScriptBlock.Create(psGetUDVariables).Invoke().Select(v => v.BaseObject as PSVariable); variables.ToList().ForEach(v => stateVariableEntries.Add(new SessionStateVariableEntry(v.Name, v.Value, null))); LogHelper.Log(fileVerboseLogTypes, string.Format("Will copy {0} local variables to the Runspace. Use -debug switch to see the details", stateVariableEntries.Count), this, NoFileLogging.IsPresent); } //Create a runspace pool with the cmdInfo collected //Using Dummy PSHost to avoid any messages to the Host. Job will collect all the output on the powershell streams runspacePool = RunspaceMethods.CreateRunspacePool( commandInfo: cmdInfo, pSHost: new DummyCustomPSHost(), maxRunspaces: MaxThreads, debugStrings: out debugStrings, loadAllTypedata: LoadAllTypeDatas.IsPresent, useRemotePS: (PSSession)UseRemotePSSession?.BaseObject, modules: ModulestoLoad, snapIns: PSSnapInsToLoad, variableEntries: stateVariableEntries ); LogHelper.LogDebug(debugStrings, this); runspacePool.ThreadOptions = PSThreadOptions.ReuseThread; runspacePool.Open(); LogHelper.LogDebug("Opened RunspacePool", this); //for Logging Purposes, create a powershell instance and log the modules that are *actually* loaded on the runspace if (runspacePool.ConnectionInfo == null) { using (PowerShell tempPS = PowerShell.Create()) { tempPS.RunspacePool = runspacePool; var modules = tempPS.AddCommand("Get-Module").Invoke(); var moduleNames = from module in modules select((PSModuleInfo)(module.BaseObject)).Name; LogHelper.LogDebug(new List <string>() { "Modules found on the RunspacePool:" }, this); LogHelper.LogDebug(moduleNames.ToList(), this); tempPS.Commands.Clear(); var loadedVariables = tempPS.AddCommand("Get-Variable").Invoke(); var varsValues = from varValue in loadedVariables select((PSVariable)(varValue.BaseObject)).Name; LogHelper.LogDebug(new List <string>() { "Vars found on the RunspacePool:" }, this); LogHelper.LogDebug(varsValues.ToList(), this); } } else { if (ModulestoLoad != null || PSSnapInsToLoad != null) { LogHelper.Log(fileWarningLogTypes, "No additional modules that were specified to be loaded will be loaded as it is not supported when using remote PSSession", this, NoFileLogging.IsPresent); } } } catch (Exception e) { ErrorRecord error = new ErrorRecord(e, e.GetType().Name, ErrorCategory.InvalidData, this); //Log any debug strings LogHelper.LogDebug(debugStrings, this); LogHelper.Log(fileVerboseLogTypes, error.Exception.ToString(), this, NoFileLogging.IsPresent); CleanupObjs(this, proxyFunction, runspacePool, false); ThrowTerminatingError(error); } }