MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Concurrent;5using System.Collections.Generic;6using System.Globalization;7using System.Management.Automation.Host;8using System.Management.Automation.Language;9using System.Management.Automation.Remoting.Internal;10using System.Management.Automation.Runspaces;11using System.Management.Automation.Security;12using System.Threading;13 14using Dbg = System.Management.Automation.Diagnostics;15 16namespace System.Management.Automation.PSTasks17{18 #region PSTask19 20 /// <summary>21 /// Class to encapsulate synchronous running scripts in parallel.22 /// </summary>23 internal sealed class PSTask : PSTaskBase24 {25 #region Members26 27 private readonly PSTaskDataStreamWriter _dataStreamWriter;28 29 #endregion30 31 #region Constructor32 33 /// <summary>34 /// Initializes a new instance of the <see cref="PSTask"/> class.35 /// </summary>36 /// <param name="scriptBlock">Script block to run in task.</param>37 /// <param name="usingValuesMap">Using values passed into script block.</param>38 /// <param name="dollarUnderbar">Dollar underbar variable value.</param>39 /// <param name="currentLocationPath">Current working directory.</param>40 /// <param name="dataStreamWriter">Cmdlet data stream writer.</param>41 public PSTask(42 ScriptBlock scriptBlock,43 Dictionary<string, object> usingValuesMap,44 object dollarUnderbar,45 string currentLocationPath,46 PSTaskDataStreamWriter dataStreamWriter)47 : base(48 scriptBlock,49 usingValuesMap,50 dollarUnderbar,51 currentLocationPath)52 {53 _dataStreamWriter = dataStreamWriter;54 }55 56 #endregion57 58 #region Overrides59 60 /// <summary>61 /// Initialize PowerShell object.62 /// </summary>63 protected override void InitializePowershell()64 {65 // Writer data stream handlers66 _output.DataAdded += (sender, args) => HandleOutputData();67 _powershell.Streams.Error.DataAdded += (sender, args) => HandleErrorData();68 _powershell.Streams.Warning.DataAdded += (sender, args) => HandleWarningData();69 _powershell.Streams.Verbose.DataAdded += (sender, args) => HandleVerboseData();70 _powershell.Streams.Debug.DataAdded += (sender, args) => HandleDebugData();71 _powershell.Streams.Progress.DataAdded += (sender, args) => HandleProgressData();72 _powershell.Streams.Information.DataAdded += (sender, args) => HandleInformationData();73 74 // State change handler75 _powershell.InvocationStateChanged += (sender, args) => HandleStateChanged(args);76 }77 78 #endregion79 80 #region Writer data stream handlers81 82 private void HandleOutputData()83 {84 foreach (var item in _output.ReadAll())85 {86 _dataStreamWriter.Add(87 new PSStreamObject(PSStreamObjectType.Output, item));88 }89 }90 91 private void HandleErrorData()92 {93 foreach (var item in _powershell.Streams.Error.ReadAll())94 {95 _dataStreamWriter.Add(96 new PSStreamObject(PSStreamObjectType.Error, item));97 }98 }99 100 private void HandleWarningData()101 {102 foreach (var item in _powershell.Streams.Warning.ReadAll())103 {104 _dataStreamWriter.Add(105 new PSStreamObject(PSStreamObjectType.Warning, item.Message));106 }107 }108 109 private void HandleVerboseData()110 {111 foreach (var item in _powershell.Streams.Verbose.ReadAll())112 {113 _dataStreamWriter.Add(114 new PSStreamObject(PSStreamObjectType.Verbose, item.Message));115 }116 }117 118 private void HandleDebugData()119 {120 foreach (var item in _powershell.Streams.Debug.ReadAll())121 {122 _dataStreamWriter.Add(123 new PSStreamObject(PSStreamObjectType.Debug, item.Message));124 }125 }126 127 private void HandleInformationData()128 {129 foreach (var item in _powershell.Streams.Information.ReadAll())130 {131 _dataStreamWriter.Add(132 new PSStreamObject(PSStreamObjectType.Information, item));133 }134 }135 136 private void HandleProgressData()137 {138 foreach (var item in _powershell.Streams.Progress.ReadAll())139 {140 _dataStreamWriter.Add(141 new PSStreamObject(PSStreamObjectType.Progress, item));142 }143 }144 145 #endregion146 147 #region Event handlers148 149 private void HandleStateChanged(PSInvocationStateChangedEventArgs stateChangeInfo)150 {151 if (_dataStreamWriter != null)152 {153 // Treat any terminating exception as a non-terminating error record154 var newStateInfo = stateChangeInfo.InvocationStateInfo;155 if (newStateInfo.Reason != null)156 {157 var errorRecord = new ErrorRecord(158 newStateInfo.Reason,159 "PSTaskException",160 ErrorCategory.InvalidOperation,161 this);162 163 _dataStreamWriter.Add(164 new PSStreamObject(PSStreamObjectType.Error, errorRecord));165 }166 }167 168 RaiseStateChangedEvent(stateChangeInfo);169 }170 171 #endregion172 }173 174 /// <summary>175 /// Class to encapsulate asynchronous running scripts in parallel as jobs.176 /// </summary>177 internal sealed class PSJobTask : PSTaskBase178 {179 #region Members180 181 private readonly Job _job;182 183 #endregion184 185 #region Constructor186 187 /// <summary>188 /// Initializes a new instance of the <see cref="PSJobTask"/> class.189 /// </summary>190 /// <param name="scriptBlock">Script block to run.</param>191 /// <param name="usingValuesMap">Using variable values passed to script block.</param>192 /// <param name="dollarUnderbar">Dollar underbar variable value for script block.</param>193 /// <param name="currentLocationPath">Current working directory.</param>194 /// <param name="job">Job object associated with task.</param>195 public PSJobTask(196 ScriptBlock scriptBlock,197 Dictionary<string, object> usingValuesMap,198 object dollarUnderbar,199 string currentLocationPath,200 Job job) : base(201 scriptBlock,202 usingValuesMap,203 dollarUnderbar,204 currentLocationPath)205 {206 _job = job;207 }208 209 #endregion210 211 #region Overrides212 213 /// <summary>214 /// Initialize PowerShell object.215 /// </summary>216 protected override void InitializePowershell()217 {218 // Job data stream handlers219 _output.DataAdded += (sender, args) => HandleJobOutputData();220 _powershell.Streams.Error.DataAdded += (sender, args) => HandleJobErrorData();221 _powershell.Streams.Warning.DataAdded += (sender, args) => HandleJobWarningData();222 _powershell.Streams.Verbose.DataAdded += (sender, args) => HandleJobVerboseData();223 _powershell.Streams.Debug.DataAdded += (sender, args) => HandleJobDebugData();224 _powershell.Streams.Information.DataAdded += (sender, args) => HandleJobInformationData();225 226 // State change handler227 _powershell.InvocationStateChanged += (sender, args) => HandleStateChanged(args);228 }229 230 #endregion231 232 #region Job data stream handlers233 234 private void HandleJobOutputData()235 {236 foreach (var item in _output.ReadAll())237 {238 _job.Output.Add(item);239 _job.Results.Add(240 new PSStreamObject(PSStreamObjectType.Output, item));241 }242 }243 244 private void HandleJobErrorData()245 {246 foreach (var item in _powershell.Streams.Error.ReadAll())247 {248 _job.Error.Add(item);249 _job.Results.Add(250 new PSStreamObject(PSStreamObjectType.Error, item));251 }252 }253 254 private void HandleJobWarningData()255 {256 foreach (var item in _powershell.Streams.Warning.ReadAll())257 {258 _job.Warning.Add(item);259 _job.Results.Add(260 new PSStreamObject(PSStreamObjectType.Warning, item.Message));261 }262 }263 264 private void HandleJobVerboseData()265 {266 foreach (var item in _powershell.Streams.Verbose.ReadAll())267 {268 _job.Verbose.Add(item);269 _job.Results.Add(270 new PSStreamObject(PSStreamObjectType.Verbose, item.Message));271 }272 }273 274 private void HandleJobDebugData()275 {276 foreach (var item in _powershell.Streams.Debug.ReadAll())277 {278 _job.Debug.Add(item);279 _job.Results.Add(280 new PSStreamObject(PSStreamObjectType.Debug, item.Message));281 }282 }283 284 private void HandleJobInformationData()285 {286 foreach (var item in _powershell.Streams.Information.ReadAll())287 {288 _job.Information.Add(item);289 _job.Results.Add(290 new PSStreamObject(PSStreamObjectType.Information, item));291 }292 }293 294 #endregion295 296 #region Event handlers297 298 private void HandleStateChanged(PSInvocationStateChangedEventArgs stateChangeInfo)299 {300 RaiseStateChangedEvent(stateChangeInfo);301 }302 303 #endregion304 305 #region Properties306 307 /// <summary>308 /// Gets Debugger.309 /// </summary>310 public Debugger Debugger311 {312 get => _powershell.Runspace.Debugger;313 }314 315 #endregion316 }317 318 /// <summary>319 /// Base class to encapsulate running a PowerShell script concurrently in a cmdlet or job context.320 /// </summary>321 internal abstract class PSTaskBase : IDisposable322 {323 #region Members324 325 private readonly ScriptBlock _scriptBlockToRun;326 private readonly Dictionary<string, object> _usingValuesMap;327 private readonly object _dollarUnderbar;328 private readonly int _id;329 private readonly string _currentLocationPath;330 private Runspace _runspace;331 protected PowerShell _powershell;332 protected PSDataCollection<PSObject> _output;333 334 public const string RunspaceName = "PSTask";335 336 private static int s_taskId;337 338 #endregion339 340 #region Events341 342 /// <summary>343 /// Event that fires when the task running state changes.344 /// </summary>345 public event EventHandler<PSInvocationStateChangedEventArgs> StateChanged;346 347 internal void RaiseStateChangedEvent(PSInvocationStateChangedEventArgs args)348 {349 StateChanged.SafeInvoke(this, args);350 }351 352 #endregion353 354 #region Properties355 356 /// <summary>357 /// Gets current running state of the task.358 /// </summary>359 public PSInvocationState State360 {361 get362 {363 PowerShell ps = _powershell;364 if (ps != null)365 {366 return ps.InvocationStateInfo.State;367 }368 369 return PSInvocationState.NotStarted;370 }371 }372 373 /// <summary>374 /// Gets Task Id.375 /// </summary>376 public int Id { get => _id; }377 378 /// <summary>379 /// Gets Task Runspace.380 /// </summary>381 public Runspace Runspace { get => _runspace; }382 383 #endregion384 385 #region Constructor386 387 private PSTaskBase()388 {389 _id = Interlocked.Increment(ref s_taskId);390 }391 392 /// <summary>393 /// Initializes a new instance of the <see cref="PSTaskBase"/> class.394 /// </summary>395 /// <param name="scriptBlock">Script block to run.</param>396 /// <param name="usingValuesMap">Using variable values passed to script block.</param>397 /// <param name="dollarUnderbar">Dollar underbar variable value.</param>398 /// <param name="currentLocationPath">Current working directory.</param>399 protected PSTaskBase(400 ScriptBlock scriptBlock,401 Dictionary<string, object> usingValuesMap,402 object dollarUnderbar,403 string currentLocationPath) : this()404 {405 _scriptBlockToRun = scriptBlock;406 _usingValuesMap = usingValuesMap;407 _dollarUnderbar = dollarUnderbar;408 _currentLocationPath = currentLocationPath;409 }410 411 #endregion412 413 #region Abstract Methods414 415 /// <summary>416 /// Initialize PowerShell object.417 /// </summary>418 protected abstract void InitializePowershell();419 420 #endregion421 422 #region IDisposable423 424 /// <summary>425 /// Dispose PSTaskBase instance.426 /// </summary>427 public void Dispose()428 {429 _powershell.Dispose();430 _output.Dispose();431 }432 433 #endregion434 435 #region Public Methods436 437 /// <summary>438 /// Start task.439 /// </summary>440 /// <param name="runspace">Runspace used to run task.</param>441 public void Start(Runspace runspace)442 {443 if (_powershell != null)444 {445 Dbg.Assert(false, "A PSTask can be started only once.");446 return;447 }448 449 Dbg.Assert(runspace != null, "Task runspace cannot be null.");450 _runspace = runspace;451 452 // If available, set current working directory on the runspace.453 // Temporarily set the newly created runspace as the thread default runspace for any needed module loading.454 if (_currentLocationPath != null)455 {456 var oldDefaultRunspace = Runspace.DefaultRunspace;457 try458 {459 Runspace.DefaultRunspace = runspace;460 var context = new CmdletProviderContext(runspace.ExecutionContext)461 {462 // _currentLocationPath denotes the current path as-is, and should not be attempted expanded.463 SuppressWildcardExpansion = true464 };465 runspace.ExecutionContext.SessionState.Internal.SetLocation(_currentLocationPath, context);466 }467 catch (DriveNotFoundException)468 {469 // Allow task to run if current drive is not available.470 }471 finally472 {473 Runspace.DefaultRunspace = oldDefaultRunspace;474 }475 }476 477 // Create the PowerShell command pipeline for the provided script block478 // The script will run on the provided Runspace in a new thread by default479 _powershell = PowerShell.Create(runspace);480 481 // Initialize PowerShell object data streams and event handlers482 _output = new PSDataCollection<PSObject>();483 InitializePowershell();484 485 // Start the script running in a new thread486 _powershell.AddScript(_scriptBlockToRun.ToString());487 _powershell.Commands.Commands[0].DollarUnderbar = _dollarUnderbar;488 if (_usingValuesMap != null && _usingValuesMap.Count > 0)489 {490 _powershell.AddParameter(Parser.VERBATIM_ARGUMENT, _usingValuesMap);491 }492 493 _powershell.BeginInvoke<object, PSObject>(input: null, output: _output);494 }495 496 /// <summary>497 /// Signals the running task to stop.498 /// </summary>499 public void SignalStop() => _powershell?.BeginStop(null, null);500 501 #endregion502 }503 504 #endregion505 506 #region PSTaskDataStreamWriter507 508 /// <summary>509 /// Class that handles writing task data stream objects to a cmdlet.510 /// </summary>511 internal sealed class PSTaskDataStreamWriter : IDisposable512 {513 #region Members514 515 private readonly PSCmdlet _cmdlet;516 private readonly PSDataCollection<PSStreamObject> _dataStream;517 private readonly int _cmdletThreadId;518 519 #endregion520 521 #region Properties522 523 /// <summary>524 /// Gets wait-able handle that signals when new data has been added to525 /// the data stream collection.526 /// </summary>527 /// <returns>Data added wait handle.</returns>528 internal WaitHandle DataAddedWaitHandle529 {530 get => _dataStream.WaitHandle;531 }532 533 #endregion534 535 #region Constructor536 537 private PSTaskDataStreamWriter() { }538 539 /// <summary>540 /// Initializes a new instance of the <see cref="PSTaskDataStreamWriter"/> class.541 /// </summary>542 /// <param name="psCmdlet">Parent cmdlet.</param>543 public PSTaskDataStreamWriter(PSCmdlet psCmdlet)544 {545 _cmdlet = psCmdlet;546 _cmdletThreadId = Environment.CurrentManagedThreadId;547 _dataStream = new PSDataCollection<PSStreamObject>();548 }549 550 #endregion551 552 #region Public Methods553 554 /// <summary>555 /// Add data stream object to the writer.556 /// </summary>557 /// <param name="streamObject">Data stream object to write.</param>558 public void Add(PSStreamObject streamObject)559 {560 _dataStream.Add(streamObject);561 }562 563 /// <summary>564 /// Write all objects in data stream collection to the cmdlet data stream.565 /// </summary>566 public void WriteImmediate()567 {568 CheckCmdletThread();569 570 foreach (var item in _dataStream.ReadAll())571 {572 item.WriteStreamObject(cmdlet: _cmdlet, overrideInquire: true);573 }574 }575 576 /// <summary>577 /// Waits for data stream objects to be added to the collection, and writes them578 /// to the cmdlet data stream.579 /// This method returns only after the writer has been closed.580 /// </summary>581 public void WaitAndWrite()582 {583 CheckCmdletThread();584 585 while (true)586 {587 _dataStream.WaitHandle.WaitOne();588 WriteImmediate();589 590 if (!_dataStream.IsOpen)591 {592 WriteImmediate();593 break;594 }595 }596 }597 598 /// <summary>599 /// Closes the stream writer.600 /// </summary>601 public void Close()602 {603 _dataStream.Complete();604 }605 606 #endregion607 608 #region Private Methods609 610 private void CheckCmdletThread()611 {612 if (Environment.CurrentManagedThreadId != _cmdletThreadId)613 {614 throw new PSInvalidOperationException(InternalCommandStrings.PSTaskStreamWriterWrongThread);615 }616 }617 618 #endregion619 620 #region IDisposable621 622 /// <summary>623 /// Dispose the stream writer.624 /// </summary>625 public void Dispose()626 {627 _dataStream.Dispose();628 }629 630 #endregion631 }632 633 #endregion634 635 #region PSTaskPool636 637 /// <summary>638 /// Pool for running PSTasks, with limit of total number of running tasks at a time.639 /// </summary>640 internal sealed class PSTaskPool : IDisposable641 {642 #region Members643 644 private readonly ManualResetEvent _addAvailable;645 private readonly int _sizeLimit;646 private readonly ManualResetEvent _stopAll;647 private readonly object _syncObject;648 private readonly Dictionary<int, PSTaskBase> _taskPool;649 private readonly ConcurrentQueue<Runspace> _runspacePool;650 private readonly ConcurrentDictionary<int, Runspace> _activeRunspaces;651 private readonly WaitHandle[] _waitHandles;652 private readonly bool _useRunspacePool;653 private bool _isOpen;654 private bool _stopping;655 private int _createdRunspaceCount;656 657 private const int AddAvailable = 0;658 private const int Stop = 1;659 660 #endregion661 662 #region Constructor663 664 private PSTaskPool() { }665 666 /// <summary>667 /// Initializes a new instance of the <see cref="PSTaskPool"/> class.668 /// </summary>669 /// <param name="size">Total number of allowed running objects in pool at one time.</param>670 /// <param name="useNewRunspace">When true, a new runspace object is created for the task instead of reusing one from the pool.</param>671 public PSTaskPool(672 int size,673 bool useNewRunspace)674 {675 _sizeLimit = size;676 _useRunspacePool = !useNewRunspace;677 _isOpen = true;678 _syncObject = new object();679 _addAvailable = new ManualResetEvent(true);680 _stopAll = new ManualResetEvent(false);681 _waitHandles = new WaitHandle[]682 {683 _addAvailable, // index 0684 _stopAll, // index 1685 };686 _taskPool = new Dictionary<int, PSTaskBase>(size);687 _activeRunspaces = new ConcurrentDictionary<int, Runspace>();688 if (_useRunspacePool)689 {690 _runspacePool = new ConcurrentQueue<Runspace>();691 }692 }693 694 #endregion695 696 #region Events697 698 /// <summary>699 /// Event that fires when pool is closed and drained of all tasks.700 /// </summary>701 public event EventHandler<EventArgs> PoolComplete;702 703 #endregion704 705 #region Properties706 707 /// <summary>708 /// Gets a value indicating whether a pool is currently open for accepting tasks.709 /// </summary>710 public bool IsOpen711 {712 get => _isOpen;713 }714 715 /// <summary>716 /// Gets a value of the count of total runspaces allocated.717 /// </summary>718 public int AllocatedRunspaceCount719 {720 get => _createdRunspaceCount;721 }722 723 #endregion724 725 #region IDisposable726 727 /// <summary>728 /// Dispose task pool.729 /// </summary>730 public void Dispose()731 {732 _addAvailable.Dispose();733 _stopAll.Dispose();734 735 DisposeRunspaces();736 }737 738 /// <summary>739 /// Dispose runspaces.740 /// </summary>741 internal void DisposeRunspaces()742 {743 foreach (var item in _activeRunspaces)744 {745 item.Value.Dispose();746 }747 748 _activeRunspaces.Clear();749 }750 751 #endregion752 753 #region Public Methods754 755 /// <summary>756 /// Method to add a task to the pool.757 /// If the pool is full, then this method blocks until space is available.758 /// This method is not multi-thread safe and assumes only one thread waits and adds tasks.759 /// </summary>760 /// <param name="task">Task to be added to pool.</param>761 /// <returns>True when task is successfully added.</returns>762 public bool Add(PSTaskBase task)763 {764 if (!_isOpen)765 {766 return false;767 }768 769 // Block until either space is available, or a stop is commanded770 var index = WaitHandle.WaitAny(_waitHandles);771 772 switch (index)773 {774 case AddAvailable:775 var runspace = GetRunspace(task.Id);776 task.StateChanged += HandleTaskStateChangedDelegate;777 lock (_syncObject)778 {779 if (!_isOpen)780 {781 return false;782 }783 784 _taskPool.Add(task.Id, task);785 if (_taskPool.Count == _sizeLimit)786 {787 _addAvailable.Reset();788 }789 790 task.Start(runspace);791 }792 793 return true;794 795 case Stop:796 return false;797 798 default:799 return false;800 }801 }802 803 /// <summary>804 /// Add child job task to task pool.805 /// </summary>806 /// <param name="childJob">Child job to be added to pool.</param>807 /// <returns>True when child job is successfully added.</returns>808 public bool Add(PSTaskChildJob childJob)809 {810 return Add(childJob.Task);811 }812 813 /// <summary>814 /// Signals all running tasks to stop and closes pool for any new tasks.815 /// </summary>816 public void StopAll()817 {818 _stopping = true;819 820 // Accept no more input821 Close();822 _stopAll.Set();823 824 // Stop all running tasks825 PSTaskBase[] tasksToStop;826 lock (_syncObject)827 {828 tasksToStop = new PSTaskBase[_taskPool.Values.Count];829 _taskPool.Values.CopyTo(tasksToStop, 0);830 }831 832 foreach (var task in tasksToStop)833 {834 task.Dispose();835 }836 837 // Dispose all active runspaces838 DisposeRunspaces();839 _stopping = false;840 }841 842 /// <summary>843 /// Closes the pool and prevents any new tasks from being added.844 /// </summary>845 public void Close()846 {847 _isOpen = false;848 CheckForComplete();849 }850 851 #endregion852 853 #region Private Methods854 855 private void HandleTaskStateChangedDelegate(object sender, PSInvocationStateChangedEventArgs args) => HandleTaskStateChanged(sender, args);856 857 private void HandleTaskStateChanged(object sender, PSInvocationStateChangedEventArgs args)858 {859 var task = sender as PSTaskBase;860 Dbg.Assert(task != null, "State changed sender must always be PSTaskBase");861 var stateInfo = args.InvocationStateInfo;862 switch (stateInfo.State)863 {864 // Look for completed state and remove865 case PSInvocationState.Completed:866 case PSInvocationState.Stopped:867 case PSInvocationState.Failed:868 ReturnRunspace(task);869 lock (_syncObject)870 {871 _taskPool.Remove(task.Id);872 if (_taskPool.Count == (_sizeLimit - 1))873 {874 _addAvailable.Set();875 }876 }877 878 task.StateChanged -= HandleTaskStateChangedDelegate;879 if (!_stopping || stateInfo.State != PSInvocationState.Stopped)880 {881 // StopAll disposes tasks.882 task.Dispose();883 }884 885 CheckForComplete();886 break;887 }888 }889 890 private void CheckForComplete()891 {892 bool isTaskPoolComplete;893 lock (_syncObject)894 {895 isTaskPoolComplete = !_isOpen && _taskPool.Count == 0;896 }897 898 if (isTaskPoolComplete)899 {900 try901 {902 PoolComplete.SafeInvoke(903 this,904 new EventArgs());905 }906 catch907 {908 Dbg.Assert(false, "Exceptions should not be thrown on event thread");909 }910 }911 }912 913 private Runspace GetRunspace(int taskId)914 {915 var runspaceName = string.Create(CultureInfo.InvariantCulture, $"{PSTask.RunspaceName}:{taskId}");916 917 if (_useRunspacePool && _runspacePool.TryDequeue(out Runspace runspace))918 {919 if (runspace.RunspaceStateInfo.State == RunspaceState.Opened &&920 runspace.RunspaceAvailability == RunspaceAvailability.Available)921 {922 try923 {924 runspace.ResetRunspaceState();925 runspace.Name = runspaceName;926 return runspace;927 }928 catch929 {930 // If the runspace cannot be reset for any reason, remove it.931 }932 }933 934 RemoveActiveRunspace(runspace);935 }936 937 // Create and initialize a new Runspace938 var iss = InitialSessionState.CreateDefault2();939 switch (SystemPolicy.GetSystemLockdownPolicy())940 {941 case SystemEnforcementMode.Enforce:942 iss.LanguageMode = PSLanguageMode.ConstrainedLanguage;943 break;944 945 case SystemEnforcementMode.Audit:946 // In audit mode, CL restrictions are not enforced and instead audit947 // log entries are created.948 iss.LanguageMode = PSLanguageMode.ConstrainedLanguage;949 break;950 951 case SystemEnforcementMode.None:952 iss.LanguageMode = PSLanguageMode.FullLanguage;953 break;954 }955 956 runspace = RunspaceFactory.CreateRunspace(iss);957 runspace.Name = runspaceName;958 _activeRunspaces.TryAdd(runspace.Id, runspace);959 runspace.Open();960 _createdRunspaceCount++;961 962 return runspace;963 }964 965 private void ReturnRunspace(PSTaskBase task)966 {967 var runspace = task.Runspace;968 Dbg.Assert(runspace != null, "Task runspace cannot be null.");969 if (_useRunspacePool &&970 runspace.RunspaceStateInfo.State == RunspaceState.Opened &&971 runspace.RunspaceAvailability == RunspaceAvailability.Available)972 {973 _runspacePool.Enqueue(runspace);974 return;975 }976 977 RemoveActiveRunspace(runspace);978 }979 980 private void RemoveActiveRunspace(Runspace runspace)981 {982 runspace.Dispose();983 _activeRunspaces.TryRemove(runspace.Id, out Runspace _);984 }985 986 #endregion987 }988 989 #endregion990 991 #region PSTaskJobs992 993 /// <summary>994 /// Job for running ForEach-Object parallel task child jobs asynchronously.995 /// </summary>996 public sealed class PSTaskJob : Job997 {998 #region Members999 1000 private readonly PSTaskPool _taskPool;1001 private bool _isOpen;1002 private bool _stopSignaled;1003 1004 #endregion1005 1006 #region Properties1007 1008 /// <summary>1009 /// Gets a value of the count of total runspaces allocated.1010 /// </summary>1011 public int AllocatedRunspaceCount1012 {1013 get => _taskPool.AllocatedRunspaceCount;1014 }1015 1016 #endregion1017 1018 #region Constructor1019 1020 private PSTaskJob() { }1021 1022 /// <summary>1023 /// Initializes a new instance of the <see cref="PSTaskJob"/> class.1024 /// </summary>1025 /// <param name="command">Job command text.</param>1026 /// <param name="throttleLimit">Pool size limit for task job.</param>1027 /// <param name="useNewRunspace">When true, a new runspace object is created for the task instead of reusing one from the pool.</param>1028 internal PSTaskJob(1029 string command,1030 int throttleLimit,1031 bool useNewRunspace) : base(command, string.Empty)1032 {1033 _taskPool = new PSTaskPool(throttleLimit, useNewRunspace);1034 _isOpen = true;1035 PSJobTypeName = nameof(PSTaskJob);1036 1037 _taskPool.PoolComplete += (sender, args) => HandleTaskPoolComplete(sender, args);1038 }1039 1040 #endregion1041 1042 #region Overrides1043 1044 /// <summary>1045 /// Gets Location.1046 /// </summary>1047 public override string Location1048 {1049 get => "PowerShell";1050 }1051 1052 /// <summary>1053 /// Gets HasMoreData.1054 /// </summary>1055 public override bool HasMoreData1056 {1057 get1058 {1059 foreach (var childJob in ChildJobs)1060 {1061 if (childJob.HasMoreData)1062 {1063 return true;1064 }1065 }1066 1067 return false;1068 }1069 }1070 1071 /// <summary>1072 /// Gets StatusMessage.1073 /// </summary>1074 public override string StatusMessage1075 {1076 get => string.Empty;1077 }1078 1079 /// <summary>1080 /// Stops running job.1081 /// </summary>1082 public override void StopJob()1083 {1084 _stopSignaled = true;1085 SetJobState(JobState.Stopping);1086 1087 _taskPool.StopAll();1088 SetJobState(JobState.Stopped);1089 }1090 1091 /// <summary>1092 /// Disposes task job.1093 /// </summary>1094 /// <param name="disposing">Indicates disposing action.</param>1095 protected override void Dispose(bool disposing)1096 {1097 if (disposing)1098 {1099 _taskPool.Dispose();1100 }1101 1102 base.Dispose(disposing);1103 }1104 1105 #endregion1106 1107 #region Internal Methods1108 1109 /// <summary>1110 /// Add a child job to the collection.1111 /// </summary>1112 /// <param name="childJob">Child job to add.</param>1113 /// <returns>True when child job is successfully added.</returns>1114 internal bool AddJob(PSTaskChildJob childJob)1115 {1116 if (!_isOpen)1117 {1118 return false;1119 }1120 1121 ChildJobs.Add(childJob);1122 return true;1123 }1124 1125 /// <summary>1126 /// Closes this parent job to adding more child jobs and starts1127 /// the child jobs running with the provided throttle limit.1128 /// </summary>1129 internal void Start()1130 {1131 _isOpen = false;1132 SetJobState(JobState.Running);1133 1134 // Submit jobs to the task pool, blocking when throttle limit is reached.1135 // This thread will end once all jobs reach a finished state by either running1136 // to completion, terminating with error, or stopped.1137 System.Threading.ThreadPool.QueueUserWorkItem(1138 (_) =>1139 {1140 foreach (var childJob in ChildJobs)1141 {1142 _taskPool.Add((PSTaskChildJob)childJob);1143 }1144 1145 _taskPool.Close();1146 });1147 }1148 1149 #endregion1150 1151 #region Private Methods1152 1153 private void HandleTaskPoolComplete(object sender, EventArgs args)1154 {1155 try1156 {1157 if (_stopSignaled)1158 {1159 SetJobState(JobState.Stopped, new PipelineStoppedException());1160 return;1161 }1162 1163 // Final state will be 'Complete', only if all child jobs completed successfully.1164 JobState finalState = JobState.Completed;1165 foreach (var childJob in ChildJobs)1166 {1167 if (childJob.JobStateInfo.State != JobState.Completed)1168 {1169 finalState = JobState.Failed;1170 break;1171 }1172 }1173 1174 SetJobState(finalState);1175 1176 // Release job task pool runspace resources.1177 (sender as PSTaskPool).DisposeRunspaces();1178 }1179 catch (ObjectDisposedException)1180 { }1181 }1182 1183 #endregion1184 }1185 1186 /// <summary>1187 /// PSTaskChildJob debugger wrapper.1188 /// </summary>1189 internal sealed class PSTaskChildDebugger : Debugger1190 {1191 #region Members1192 1193 private readonly Debugger _wrappedDebugger;1194 private readonly string _jobName;1195 1196 #endregion1197 1198 #region Constructor1199 1200 private PSTaskChildDebugger() { }