MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Linq;6using System.Management.Automation.Host;7using System.Management.Automation.Internal;8using System.Threading;9#if LEGACYTELEMETRY10using Microsoft.PowerShell.Telemetry.Internal;11#endif12using Dbg = System.Management.Automation.Diagnostics;13 14#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings15 16namespace System.Management.Automation.Runspaces17{18 /// <summary>19 /// Runspaces is base class for different kind of Runspaces.20 /// </summary>21 /// <remarks>There should be a class derived from it for each type of22 /// Runspace. Types of Runspace which we support are Local, X-AppDomain,23 /// X-Process and X-Machine.</remarks>24 internal abstract class RunspaceBase : Runspace25 {26 #region constructors27 28 /// <summary>29 /// Initialize powershell AssemblyLoadContext and register the 'Resolving' event, if it's not done already.30 /// If powershell is hosted by a native host such as DSC, then PS ALC may be initialized via 'SetPowerShellAssemblyLoadContext' before loading S.M.A.31 /// </summary>32 /// <remarks>33 /// We do this both here and during the initialization of the 'ClrFacade' type.34 /// This is because we want to make sure the assembly/library resolvers are:35 /// 1. registered before any script/cmdlet can run.36 /// 2. registered before 'ClrFacade' gets used for assembly related operations.37 ///38 /// The 'ClrFacade' type may be used without a Runspace created, for example, by calling type conversion methods in the 'LanguagePrimitive' type.39 /// And at the mean time, script or cmdlet may run without the 'ClrFacade' type initialized.40 /// That's why we attempt to create the singleton of 'PowerShellAssemblyLoadContext' at both places.41 /// </remarks>42 static RunspaceBase()43 {44 if (PowerShellAssemblyLoadContext.Instance is null)45 {46 PowerShellAssemblyLoadContext.InitializeSingleton(string.Empty, throwOnReentry: false);47 }48 }49 50 /// <summary>51 /// Construct an instance of an Runspace using a custom52 /// implementation of PSHost.53 /// </summary>54 /// <param name="host">The explicit PSHost implementation.</param>55 /// <exception cref="System.ArgumentNullException">56 /// Host is null.57 /// </exception>58 /// <exception cref="System.ArgumentNullException">59 /// host is null.60 /// </exception>61 protected RunspaceBase(PSHost host)62 {63 if (host == null)64 {65 throw PSTraceSource.NewArgumentNullException(nameof(host));66 }67 68 InitialSessionState = InitialSessionState.CreateDefault();69 Host = host;70 }71 72 /// <summary>73 /// Construct an instance of an Runspace using a custom74 /// implementation of PSHost.75 /// </summary>76 /// <param name="host">The explicit PSHost implementation.</param>77 /// <exception cref="System.ArgumentNullException">78 /// Host is null.79 /// </exception>80 /// <exception cref="System.ArgumentNullException">81 /// host is null.82 /// </exception>83 /// <param name="initialSessionState">84 /// configuration information for this runspace instance.85 /// </param>86 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "OK to call ThreadOptions")]87 protected RunspaceBase(PSHost host, InitialSessionState initialSessionState)88 {89 if (host == null)90 {91 throw PSTraceSource.NewArgumentNullException(nameof(host));92 }93 94 if (initialSessionState == null)95 {96 throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState));97 }98 99 Host = host;100 InitialSessionState = initialSessionState.Clone();101 this.ThreadOptions = initialSessionState.ThreadOptions;102 this.ApartmentState = initialSessionState.ApartmentState;103 }104 105 /// <summary>106 /// Construct an instance of an Runspace using a custom107 /// implementation of PSHost.108 /// </summary>109 /// <param name="host">110 /// The explicit PSHost implementation111 /// </param>112 /// <param name="initialSessionState">113 /// configuration information for this runspace instance.114 /// </param>115 /// <param name="suppressClone">116 /// If true, don't make a copy of the initial session state object.117 /// </param>118 /// <exception cref="System.ArgumentNullException">119 /// Host is null.120 /// </exception>121 /// <exception cref="System.ArgumentNullException">122 /// host is null.123 /// </exception>124 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "OK to call ThreadOptions")]125 protected RunspaceBase(PSHost host, InitialSessionState initialSessionState, bool suppressClone)126 {127 if (host == null)128 {129 throw PSTraceSource.NewArgumentNullException(nameof(host));130 }131 132 if (initialSessionState == null)133 {134 throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState));135 }136 137 Host = host;138 if (suppressClone)139 {140 InitialSessionState = initialSessionState;141 }142 else143 {144 InitialSessionState = initialSessionState.Clone();145 }146 147 this.ThreadOptions = initialSessionState.ThreadOptions;148 this.ApartmentState = initialSessionState.ApartmentState;149 }150 151 /// <summary>152 /// The host implemented PSHost interface.153 /// </summary>154 protected PSHost Host { get; }155 156 /// <summary>157 /// InitialSessionState information for this runspace.158 /// </summary>159 public override InitialSessionState InitialSessionState { get; }160 161 #endregion constructors162 163 #region properties164 165 /// <summary>166 /// Return version of this runspace.167 /// </summary>168 public override Version Version { get; } = PSVersionInfo.PSVersion;169 170 private RunspaceStateInfo _runspaceStateInfo = new RunspaceStateInfo(RunspaceState.BeforeOpen);171 172 /// <summary>173 /// Retrieve information about current state of the runspace.174 /// </summary>175 public override RunspaceStateInfo RunspaceStateInfo176 {177 get178 {179 lock (SyncRoot)180 {181 // Do not return internal state.182 return _runspaceStateInfo.Clone();183 }184 }185 }186 187 /// <summary>188 /// Gets the current availability of the Runspace.189 /// </summary>190 public override RunspaceAvailability RunspaceAvailability191 {192 get { return _runspaceAvailability; }193 194 protected set { _runspaceAvailability = value; }195 }196 197 private RunspaceAvailability _runspaceAvailability = RunspaceAvailability.None;198 199 /// <summary>200 /// Object used for synchronization.201 /// </summary>202 protected internal object SyncRoot { get; } = new object();203 204 /// <summary>205 /// Information about the computer where this runspace is created.206 /// </summary>207 public override RunspaceConnectionInfo ConnectionInfo208 {209 get210 {211 // null refers to local case for path212 return null;213 }214 }215 216 /// <summary>217 /// Original Connection Info that the user passed.218 /// </summary>219 public override RunspaceConnectionInfo OriginalConnectionInfo220 {221 get { return null; }222 }223 224 #endregion properties225 226 #region Open227 228 /// <summary>229 /// Open the runspace synchronously.230 /// </summary>231 /// <exception cref="InvalidRunspaceStateException">232 /// RunspaceState is not BeforeOpen233 /// </exception>234 public override void Open()235 {236 CoreOpen(true);237 }238 239 /// <summary>240 /// Open the runspace Asynchronously.241 /// </summary>242 /// <exception cref="InvalidRunspaceStateException">243 /// RunspaceState is not BeforeOpen244 /// </exception>245 public override void OpenAsync()246 {247 CoreOpen(false);248 }249 250 /// <summary>251 /// Opens the runspace.252 /// </summary>253 /// <param name="syncCall">If true runspace is opened synchronously254 /// else runspaces is opened asynchronously255 /// </param>256 /// <exception cref="InvalidRunspaceStateException">257 /// RunspaceState is not BeforeOpen258 /// </exception>259 private void CoreOpen(bool syncCall)260 {261 bool etwEnabled = RunspaceEventSource.Log.IsEnabled();262 if (etwEnabled)263 {264 RunspaceEventSource.Log.OpenRunspaceStart();265 }266 267 lock (SyncRoot)268 {269 // Call fails if RunspaceState is not BeforeOpen.270 if (RunspaceState != RunspaceState.BeforeOpen)271 {272 InvalidRunspaceStateException e =273 new InvalidRunspaceStateException274 (275 StringUtil.Format(RunspaceStrings.CannotOpenAgain, new object[] { RunspaceState.ToString() }),276 RunspaceState,277 RunspaceState.BeforeOpen278 );279 throw e;280 }281 282 SetRunspaceState(RunspaceState.Opening);283 }284 285 // Raise event outside the lock286 RaiseRunspaceStateEvents();287 288 OpenHelper(syncCall);289 if (etwEnabled)290 {291 RunspaceEventSource.Log.OpenRunspaceStop();292 }293 294#if LEGACYTELEMETRY295 // We report startup telemetry when opening the runspace - because this is the first time296 // we are really using PowerShell. This isn't the cleanest place though, because297 // sometimes there are many runspaces created - the callee ensures telemetry is only298 // reported once. Note that if the host implements IHostProvidesTelemetryData, we rely299 // on the host calling ReportStartupTelemetry.300 if (this.Host is not IHostProvidesTelemetryData)301 {302 TelemetryAPI.ReportStartupTelemetry(null);303 }304#endif305 }306 307 /// <summary>308 /// Derived class's open implementation.309 /// </summary>310 protected abstract void OpenHelper(bool syncCall);311 312 #endregion open313 314 #region close315 /// <summary>316 /// Close the runspace synchronously.317 /// </summary>318 /// <remarks>319 /// Attempts to execute pipelines after a call to close will fail.320 /// </remarks>321 /// <exception cref="InvalidRunspaceStateException">322 /// RunspaceState is BeforeOpen or Opening323 /// </exception>324 public override void Close()325 {326 CoreClose(true);327 }328 329 /// <summary>330 /// Close the runspace Asynchronously.331 /// </summary>332 /// <remarks>333 /// Attempts to execute pipelines after a call to334 /// close will fail.335 /// </remarks>336 /// <exception cref="InvalidRunspaceStateException">337 /// RunspaceState is BeforeOpen or Opening338 /// </exception>339 public override void CloseAsync()340 {341 CoreClose(false);342 }343 344 /// <summary>345 /// Close the runspace.346 /// </summary>347 /// <param name="syncCall">If true runspace is closed synchronously348 /// else runspaces is closed asynchronously349 /// </param>350 /// <exception cref="InvalidRunspaceStateException">351 /// RunspaceState is BeforeOpen or Opening352 /// </exception>353 /// <exception cref="InvalidOperationException">354 /// If SessionStateProxy has some method call in progress355 /// </exception>356 private void CoreClose(bool syncCall)357 {358 bool alreadyClosing = false;359 360 lock (SyncRoot)361 {362 if (RunspaceState == RunspaceState.Closed ||363 RunspaceState == RunspaceState.Broken)364 {365 return;366 }367 else if (RunspaceState == RunspaceState.BeforeOpen)368 {369 SetRunspaceState(RunspaceState.Closing, null);370 SetRunspaceState(RunspaceState.Closed, null);371 372 RaiseRunspaceStateEvents();373 374 return;375 }376 else if (RunspaceState == RunspaceState.Opening)377 {378 // Wait till the runspace is opened - This is set in DoOpenHelper()379 // Release the lock before we wait380 Monitor.Exit(SyncRoot);381 try382 {383 RunspaceOpening.Wait();384 }385 finally386 {387 // Acquire the lock before we carry on with the rest operations388 Monitor.Enter(SyncRoot);389 }390 }391 392 if (_bSessionStateProxyCallInProgress)393 {394 throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.RunspaceCloseInvalidWhileSessionStateProxy);395 }396 397 if (RunspaceState == RunspaceState.Closing)398 {399 alreadyClosing = true;400 }401 else402 {403 if (RunspaceState != RunspaceState.Opened)404 {405 InvalidRunspaceStateException e =406 new InvalidRunspaceStateException407 (408 StringUtil.Format(RunspaceStrings.RunspaceNotInOpenedState, RunspaceState.ToString()),409 RunspaceState,410 RunspaceState.Opened411 );412 throw e;413 }414 415 SetRunspaceState(RunspaceState.Closing);416 }417 }418 419 if (alreadyClosing)420 {421 // Already closing is set to true if Runspace is already422 // in closing. In this case wait for runspace to close.423 // This can happen in two scenarios:424 // 1) User calls Runspace.Close from two threads.425 // 2) In remoting, some error from data structure handler layer can start426 // runspace closure. At the same time, user can call427 // remove runspace.428 //429 430 if (syncCall)431 {432 WaitForFinishofPipelines();433 }434 435 return;436 }437 438 // Raise Event outside the lock439 RaiseRunspaceStateEvents();440 441 // Call the derived class implementation to do the actual work442 CloseHelper(syncCall);443 }444 445 /// <summary>446 /// Derived class's close implementation.447 /// </summary>448 /// <param name="syncCall">If true runspace is closed synchronously449 /// else runspaces is closed asynchronously450 /// </param>451 protected abstract void CloseHelper(bool syncCall);452 453 #endregion close454 455 #region Disconnect-Connect456 457 /// <summary>458 /// Disconnects the runspace synchronously.459 /// </summary>460 public override void Disconnect()461 {462 //463 // Disconnect operation is not supported on local runspaces.464 //465 throw new InvalidRunspaceStateException(466 RunspaceStrings.DisconnectNotSupported);467 }468 469 /// <summary>470 /// Disconnects the runspace asynchronously.471 /// </summary>472 public override void DisconnectAsync()473 {474 //475 // Disconnect operation is not supported on local runspaces.476 //477 throw new InvalidRunspaceStateException(478 RunspaceStrings.DisconnectNotSupported);479 }480 481 /// <summary>482 /// Connects a runspace to its remote counterpart synchronously.483 /// </summary>484 public override void Connect()485 {486 //487 // Connect operation is not supported on local runspaces.488 //489 throw new InvalidRunspaceStateException(490 RunspaceStrings.ConnectNotSupported);491 }492 493 /// <summary>494 /// Connects a runspace to its remote counterpart asynchronously.495 /// </summary>496 public override void ConnectAsync()497 {498 //499 // Connect operation is not supported on local runspaces.500 //501 throw new InvalidRunspaceStateException(502 RunspaceStrings.ConnectNotSupported);503 }504 505 /// <summary>506 /// Creates a pipeline object in the Disconnected state.507 /// </summary>508 /// <returns>Pipeline.</returns>509 public override Pipeline CreateDisconnectedPipeline()510 {511 //512 // Disconnect-Connect is not supported on local runspaces.513 //514 throw new InvalidRunspaceStateException(515 RunspaceStrings.DisconnectConnectNotSupported);516 }517 518 /// <summary>519 /// Creates a powershell object in the Disconnected state.520 /// </summary>521 /// <returns>PowerShell.</returns>522 public override PowerShell CreateDisconnectedPowerShell()523 {524 //525 // Disconnect-Connect is not supported on local runspaces.526 //527 throw new InvalidRunspaceStateException(528 RunspaceStrings.DisconnectConnectNotSupported);529 }530 531 /// <summary>532 /// Returns Runspace capabilities.533 /// </summary>534 /// <returns>RunspaceCapability.</returns>535 public override RunspaceCapability GetCapabilities()536 {537 return RunspaceCapability.Default;538 }539 540 #endregion541 542 #region CreatePipeline543 544 /// <summary>545 /// Create an empty pipeline.546 /// </summary>547 /// <returns>An empty pipeline.</returns>548 public override Pipeline CreatePipeline()549 {550 return CoreCreatePipeline(null, false, false);551 }552 553 /// <summary>554 /// Create a pipeline from a command string.555 /// </summary>556 /// <param name="command">A valid command string.</param>557 /// <returns>558 /// A pipeline pre-filled with Commands specified in commandString.559 /// </returns>560 /// <exception cref="ArgumentNullException">561 /// command is null562 /// </exception>563 public override Pipeline CreatePipeline(string command)564 {565 if (command == null)566 {567 throw PSTraceSource.NewArgumentNullException(nameof(command));568 }569 570 return CoreCreatePipeline(command, false, false);571 }572 573 /// <summary>574 /// Create a pipeline from a command string.575 /// </summary>576 /// <param name="command">A valid command string.</param>577 /// <param name="addToHistory">If true command is added to history.</param>578 /// <returns>579 /// A pipeline pre-filled with Commands specified in commandString.580 /// </returns>581 /// <exception cref="ArgumentNullException">582 /// command is null583 /// </exception>584 public override Pipeline CreatePipeline(string command, bool addToHistory)585 {586 if (command == null)587 {588 throw PSTraceSource.NewArgumentNullException(nameof(command));589 }590 591 return CoreCreatePipeline(command, addToHistory, false);592 }593 594 /// <summary>595 /// Creates a nested pipeline.596 /// </summary>597 /// <remarks>598 /// Nested pipelines are needed for nested prompt scenario. Nested599 /// prompt requires that we execute new pipelines( child pipelines)600 /// while current pipeline (lets call it parent pipeline) is blocked.601 /// </remarks>602 public override Pipeline CreateNestedPipeline()603 {604 return CoreCreatePipeline(null, false, true);605 }606 607 /// <summary>608 /// Creates a nested pipeline.609 /// </summary>610 /// <param name="command">A valid command string.</param>611 /// <param name="addToHistory">If true command is added to history.</param>612 /// <returns>613 /// A pipeline pre-filled with Commands specified in commandString.614 /// </returns>615 /// <exception cref="ArgumentNullException">616 /// command is null617 /// </exception>618 public override Pipeline CreateNestedPipeline(string command, bool addToHistory)619 {620 if (command == null)621 {622 throw PSTraceSource.NewArgumentNullException(nameof(command));623 }624 625 return CoreCreatePipeline(command, addToHistory, true);626 }627 628 /// <summary>629 /// Create a pipeline from a command string.630 /// </summary>631 /// <param name="command">A valid command string or string.Empty.</param>632 /// <param name="addToHistory">If true command is added to history.</param>633 /// <param name="isNested">True for nested pipeline.</param>634 /// <returns>635 /// A pipeline pre-filled with Commands specified in commandString.636 /// </returns>637 protected abstract Pipeline CoreCreatePipeline(string command, bool addToHistory, bool isNested);638 639 #endregion CreatePipeline640 641 #region state change event642 643 /// <summary>644 /// Event raised when RunspaceState changes.645 /// </summary>646 public override event EventHandler<RunspaceStateEventArgs> StateChanged;647 648 /// <summary>649 /// Event raised when the availability of the Runspace changes.650 /// </summary>651 public override event EventHandler<RunspaceAvailabilityEventArgs> AvailabilityChanged;652 653 /// <summary>654 /// Returns true if there are any subscribers to the AvailabilityChanged event.655 /// </summary>656 internal override bool HasAvailabilityChangedSubscribers657 {658 get { return this.AvailabilityChanged != null; }659 }660 661 /// <summary>662 /// Raises the AvailabilityChanged event.663 /// </summary>664 protected override void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e)665 {666 EventHandler<RunspaceAvailabilityEventArgs> eh = this.AvailabilityChanged;667 668 if (eh != null)669 {670 try671 {672 eh(this, e);673 }674 catch (Exception)675 {676 }677 }678 }679 680 /// <summary>681 /// Retrieve the current state of the runspace.682 /// <see cref="RunspaceState"/>683 /// </summary>684 protected RunspaceState RunspaceState685 {686 get687 {688 return _runspaceStateInfo.State;689 }690 }691 692 /// <summary>693 /// This is queue of all the state change event which have occurred for694 /// this runspace. RaiseRunspaceStateEvents raises event for each695 /// item in this queue. We don't raise events from with SetRunspaceState696 /// because SetRunspaceState is often called from with in the a lock.697 /// Raising event with in a lock introduces chances of deadlock in GUI698 /// applications.699 /// </summary>700 private Queue<RunspaceEventQueueItem> _runspaceEventQueue = new Queue<RunspaceEventQueueItem>();701 702 private sealed class RunspaceEventQueueItem703 {704 public RunspaceEventQueueItem(RunspaceStateInfo runspaceStateInfo, RunspaceAvailability currentAvailability, RunspaceAvailability newAvailability)705 {706 this.RunspaceStateInfo = runspaceStateInfo;707 this.CurrentRunspaceAvailability = currentAvailability;708 this.NewRunspaceAvailability = newAvailability;709 }710 711 public RunspaceStateInfo RunspaceStateInfo;712 public RunspaceAvailability CurrentRunspaceAvailability;713 public RunspaceAvailability NewRunspaceAvailability;714 }715 716 // This is to notify once runspace has been opened (RunspaceState.Opened)717 internal ManualResetEventSlim RunspaceOpening = new ManualResetEventSlim(false);718 719 /// <summary>720 /// Set the new runspace state.721 /// </summary>722 /// <param name="state">The new state.</param>723 /// <param name="reason">An exception indicating the state change is the724 /// result of an error, otherwise; null.725 /// </param>726 /// <remarks>727 /// Sets the internal runspace state information member variable. It also728 /// adds RunspaceStateInfo to a queue.729 /// RaiseRunspaceStateEvents raises event for each item in this queue.730 /// </remarks>731 protected void SetRunspaceState(RunspaceState state, Exception reason)732 {733 lock (SyncRoot)734 {735 if (state != RunspaceState)736 {737 _runspaceStateInfo = new RunspaceStateInfo(state, reason);738 739 // Add _runspaceStateInfo to _runspaceEventQueue.740 // RaiseRunspaceStateEvents will raise event for each item741 // in this queue.742 // Note:We are doing clone here instead of passing the member743 // _runspaceStateInfo because we donot want outside744 // to change our runspace state.745 RunspaceAvailability previousAvailability = _runspaceAvailability;746 747 this.UpdateRunspaceAvailability(_runspaceStateInfo.State, false);748 749 _runspaceEventQueue.Enqueue(750 new RunspaceEventQueueItem(751 _runspaceStateInfo.Clone(),752 previousAvailability,753 _runspaceAvailability));754 }755 }756 }757 758 /// <summary>759 /// Set the current runspace state - no error.760 /// </summary>761 /// <param name="state">The new state.</param>762 protected void SetRunspaceState(RunspaceState state)763 {764 this.SetRunspaceState(state, null);765 }766 767 /// <summary>768 /// Raises events for changes in runspace state.769 /// </summary>770 protected void RaiseRunspaceStateEvents()771 {772 Queue<RunspaceEventQueueItem> tempEventQueue = null;773 EventHandler<RunspaceStateEventArgs> stateChanged = null;774 bool hasAvailabilityChangedSubscribers = false;775 776 lock (SyncRoot)777 {778 stateChanged = this.StateChanged;779 hasAvailabilityChangedSubscribers = this.HasAvailabilityChangedSubscribers;780 781 if (stateChanged != null || hasAvailabilityChangedSubscribers)782 {783 tempEventQueue = _runspaceEventQueue;784 _runspaceEventQueue = new Queue<RunspaceEventQueueItem>();785 }786 else787 {788 // Clear the events if there are no EventHandlers. This789 // ensures that events do not get called for state790 // changes prior to their registration.791 _runspaceEventQueue.Clear();792 }793 }794 795 if (tempEventQueue != null)796 {797 while (tempEventQueue.Count > 0)798 {799 RunspaceEventQueueItem queueItem = tempEventQueue.Dequeue();800 801 if (hasAvailabilityChangedSubscribers && queueItem.NewRunspaceAvailability != queueItem.CurrentRunspaceAvailability)802 {803 this.OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(queueItem.NewRunspaceAvailability));804 }805 806#pragma warning disable 56500807 // Exception raised by events are not error condition for runspace808 // object.809 if (stateChanged != null)810 {811 try812 {813 stateChanged(this, new RunspaceStateEventArgs(queueItem.RunspaceStateInfo));814 }815 catch (Exception)816 {817 }818 }819#pragma warning restore 56500820 }821 }822 }823 824 #endregion state change event825 826 #region running pipeline management827 828 /// <summary>829 /// In RemoteRunspace, it is required to invoke pipeline830 /// as part of open call (i.e. while state is Opening).831 /// If this property is true, runspace state check is832 /// not performed in AddToRunningPipelineList call.833 /// </summary>834 protected bool ByPassRunspaceStateCheck { get; set; }835 836 private readonly object _pipelineListLock = new object();837 838 /// <summary>839 /// List of pipeline which are currently executing in this runspace.840 /// </summary>841 protected List<Pipeline> RunningPipelines { get; } = new List<Pipeline>();842 843 /// <summary>844 /// Add the pipeline to list of pipelines in execution.845 /// </summary>846 /// <param name="pipeline">Pipeline to add to the847 /// list of pipelines in execution</param>848 /// <exception cref="InvalidRunspaceStateException">849 /// Thrown if the runspace is not in the Opened state.850 /// <see cref="RunspaceState"/>.851 /// </exception>852 /// <exception cref="ArgumentNullException">Thrown if853 /// <paramref name="pipeline"/> is null.854 /// </exception>855 internal void AddToRunningPipelineList(PipelineBase pipeline)856 {857 Dbg.Assert(pipeline != null, "caller should validate the parameter");858 859 lock (_pipelineListLock)860 {861 if (!ByPassRunspaceStateCheck && RunspaceState != RunspaceState.Opened)862 {863 InvalidRunspaceStateException e =864 new InvalidRunspaceStateException865 (866 StringUtil.Format(RunspaceStrings.RunspaceNotOpenForPipeline, RunspaceState.ToString()),867 RunspaceState,868 RunspaceState.Opened869 );870 throw e;871 }872 873 // Add the pipeline to list of Executing pipeline.874 // Note:_runningPipelines is always accessed with the lock so875 // there is no need to create a synchronized version of list876 RunningPipelines.Add(pipeline);877 _currentlyRunningPipeline = pipeline;878 }879 }880 881 /// <summary>882 /// Remove the pipeline from list of pipelines in execution.883 /// </summary>884 /// <param name="pipeline">Pipeline to remove from the885 /// list of pipelines in execution</param>886 /// <exception cref="ArgumentNullException">887 /// Thrown if <paramref name="pipeline"/> is null.888 /// </exception>889 internal void RemoveFromRunningPipelineList(PipelineBase pipeline)890 {891 Dbg.Assert(pipeline != null, "caller should validate the parameter");892 893 lock (_pipelineListLock)894 {895 Dbg.Assert(RunspaceState != RunspaceState.BeforeOpen,896 "Runspace should not be before open when pipeline is running");897 898 // Remove the pipeline to list of Executing pipeline.899 // Note:_runningPipelines is always accessed with the lock so900 // there is no need to create a synchronized version of list901 RunningPipelines.Remove(pipeline);902 903 // Update the running pipeline904 if (RunningPipelines.Count == 0)905 {906 _currentlyRunningPipeline = null;907 }908 else909 {910 _currentlyRunningPipeline = RunningPipelines[RunningPipelines.Count - 1];911 }912 913 pipeline.PipelineFinishedEvent.Set();914 }915 }916 917 /// <summary>918 /// Waits till all the pipelines running in the runspace have919 /// finished execution.920 /// </summary>921 internal bool WaitForFinishofPipelines()922 {923 // Take a snapshot of list of active pipelines.924 // Note:Before we enter to this CloseHelper routine925 // CoreClose has already set the state of Runspace926 // to closing. So no new pipelines can be executed on this927 // runspace and so no new pipelines will be added to928 // _runningPipelines. However we still need to lock because929 // running pipelines can be removed from this.930 PipelineBase[] runningPipelines;931 932 lock (_pipelineListLock)933 {934 runningPipelines = RunningPipelines.Cast<PipelineBase>().ToArray();935 }936 937 if (runningPipelines.Length > 0)938 {939 WaitHandle[] waitHandles = new WaitHandle[runningPipelines.Length];940 941 for (int i = 0; i < runningPipelines.Length; i++)942 {943 waitHandles[i] = runningPipelines[i].PipelineFinishedEvent;944 }945 946 // WaitAll for multiple handles on a STA (single-thread apartment) thread is not supported as WaitAll will prevent the message pump to run947 if (runningPipelines.Length > 1 && Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)948 {949 // We use a worker thread to wait for all handles, and the STA thread can just wait on the worker thread -- the worker950 // threads from the ThreadPool are MTA.951 using (ManualResetEvent waitAllIsDone = new ManualResetEvent(false))952 {953 Tuple<WaitHandle[], ManualResetEvent> stateInfo = new Tuple<WaitHandle[], ManualResetEvent>(waitHandles, waitAllIsDone);954 955 ThreadPool.QueueUserWorkItem(new WaitCallback(956 (object state) =>957 {958 var tuple = (Tuple<WaitHandle[], ManualResetEvent>)state;959 WaitHandle.WaitAll(tuple.Item1);960 tuple.Item2.Set();961 }),962 stateInfo);963 return waitAllIsDone.WaitOne();964 }965 }966 967 return WaitHandle.WaitAll(waitHandles);968 }969 else970 {971 return true;972 }973 }974 975 /// <summary>976 /// Stops all the running pipelines.977 /// </summary>978 protected void StopPipelines()979 {980 PipelineBase[] runningPipelines;981 982 lock (_pipelineListLock)983 {984 runningPipelines = RunningPipelines.Cast<PipelineBase>().ToArray();985 }986 987 if (runningPipelines.Length > 0)988 {989 // Start from the most recent pipeline.990 for (int i = runningPipelines.Length - 1; i >= 0; i--)991 {992 runningPipelines[i].Stop();993 }994 }995 }996 997 internal bool CanRunActionInCurrentPipeline()998 {999 lock (_pipelineListLock)1000 {1001 // If we have no running pipeline, or if the currently running pipeline is1002 // the same as the current thread, then execute the action.1003 var pipelineRunning = _currentlyRunningPipeline as PipelineBase;1004 return pipelineRunning == null ||1005 Thread.CurrentThread == pipelineRunning.NestedPipelineExecutionThread;1006 }1007 }1008 1009 /// <summary>1010 /// Gets the currently executing pipeline.1011 /// </summary>1012 /// <remarks>Internal because it is needed by invoke-history</remarks>1013 internal override Pipeline GetCurrentlyRunningPipeline()1014 {1015 return _currentlyRunningPipeline;1016 }1017 1018 private Pipeline _currentlyRunningPipeline = null;1019 1020 /// <summary>1021 /// This method stops all the pipelines which are nested1022 /// under specified pipeline.1023 /// </summary>1024 /// <param name="pipeline"></param>1025 /// <returns></returns>1026 internal void StopNestedPipelines(Pipeline pipeline)1027 {1028 List<Pipeline> nestedPipelines = null;1029 1030 lock (_pipelineListLock)1031 {1032 // first check if this pipeline is in the list of running1033 // pipelines. It is possible that pipeline has already1034 // completed.1035 if (!RunningPipelines.Contains(pipeline))1036 {1037 return;1038 }1039 1040 // If this pipeline is currently running pipeline,1041 // then it does not have nested pipelines1042 if (GetCurrentlyRunningPipeline() == pipeline)1043 {1044 return;1045 }1046 1047 // Build list of nested pipelines1048 nestedPipelines = new List<Pipeline>();1049 for (int i = RunningPipelines.Count - 1; i >= 0; i--)1050 {1051 if (RunningPipelines[i] == pipeline)1052 break;1053 nestedPipelines.Add(RunningPipelines[i]);1054 }1055 }1056 1057 foreach (Pipeline np in nestedPipelines)1058 {1059 try1060 {1061 np.Stop();1062 }1063 catch (InvalidPipelineStateException)1064 {1065 }1066 }1067 }1068 1069 internal1070 void1071 DoConcurrentCheckAndAddToRunningPipelines(PipelineBase pipeline, bool syncCall)1072 {1073 // Concurrency check should be done under runspace lock1074 lock (SyncRoot)1075 {1076 if (_bSessionStateProxyCallInProgress)1077 {1078 throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.NoPipelineWhenSessionStateProxyInProgress);1079 }1080 1081 // Delegate to pipeline to do check if it is fine to invoke if another1082 // pipeline is running.1083 pipeline.DoConcurrentCheck(syncCall, SyncRoot, true);1084 // Finally add to the list of running pipelines.1085 AddToRunningPipelineList(pipeline);1086 }1087 }1088 1089 // PowerShell support for async notifications happen through the1090 // CheckForInterrupts() method on ParseTreeNode. These are only called when1091 // the engine is active (and processing,) so the Pulse() method1092 // executes the equivalent of a NOP so that async events1093 // can be processed when the engine is idle.1094 internal void Pulse()1095 {1096 // If we don't already have a pipeline running, pulse the engine.1097 bool pipelineCreated = false;1098 if (GetCurrentlyRunningPipeline() == null)1099 {1100 lock (SyncRoot)1101 {1102 if (GetCurrentlyRunningPipeline() == null)1103 {1104 // This is a pipeline that does the least amount possible.1105 // It evaluates a constant, and results in the execution of only two parse tree nodes.1106 // We don't need to void it, as we aren't using the results. In addition, voiding1107 // (as opposed to ignoring) is 1.6x slower.1108 try1109 {1110 PulsePipeline = (PipelineBase)CreatePipeline("0");1111 PulsePipeline.IsPulsePipeline = true;1112 pipelineCreated = true;1113 }1114 catch (ObjectDisposedException)1115 {1116 // Ignore. The runspace is closing. The event was not processed,1117 // but this should not crash PowerShell.1118 }1119 }1120 }1121 }1122 1123 // Invoke pipeline outside the runspace lock.1124 // A concurrency check will be made on the runspace before this1125 // pipeline is invoked.1126 if (pipelineCreated)1127 {1128 try1129 {1130 PulsePipeline.Invoke();1131 }1132 catch (PSInvalidOperationException)1133 {1134 // Ignore. A pipeline was created between the time1135 // we checked for it, and when we invoked the pipeline.1136 // This is unlikely, but taking a lock on the runspace1137 // means that OUR invoke will not be able to run.1138 }1139 catch (InvalidRunspaceStateException)1140 {1141 // Ignore. The runspace is closing. The event was not processed,1142 // but this should not crash PowerShell.1143 }1144 catch (ObjectDisposedException)1145 {1146 // Ignore. The runspace is closing. The event was not processed,1147 // but this should not crash PowerShell.1148 }1149 }1150 }1151 1152 internal PipelineBase PulsePipeline { get; private set; }1153 1154 #endregion running pipeline management1155 1156 #region session state proxy1157 1158 // Note: When SessionStateProxy calls are in progress,1159 // pipeline cannot be invoked. Also when pipeline is in1160 // progress, SessionStateProxy calls cannot be made.1161 private bool _bSessionStateProxyCallInProgress;1162 1163 /// <summary>1164 /// This method ensures that SessionStateProxy call is allowed and if1165 /// allowed it sets a variable to disallow further SessionStateProxy or1166 /// pipeline calls.1167 /// </summary>1168 private void DoConcurrentCheckAndMarkSessionStateProxyCallInProgress()1169 {1170 lock (SyncRoot)1171 {1172 if (RunspaceState != RunspaceState.Opened)1173 {1174 InvalidRunspaceStateException e =1175 new InvalidRunspaceStateException1176 (1177 StringUtil.Format(RunspaceStrings.RunspaceNotInOpenedState, RunspaceState.ToString()),1178 RunspaceState,1179 RunspaceState.Opened1180 );1181 throw e;1182 }1183 1184 if (_bSessionStateProxyCallInProgress)1185 {1186 throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.AnotherSessionStateProxyInProgress);1187 }1188 1189 Pipeline runningPipeline = GetCurrentlyRunningPipeline();1190 if (runningPipeline != null)1191 {1192 // Detect if we're running an engine pulse, or we're running a nested pipeline1193 // from an engine pulse1194 if (runningPipeline == PulsePipeline ||1195 (runningPipeline.IsNested && PulsePipeline != null))1196 {1197 // If so, wait and try again1198 // Release the lock before we wait for the pulse pipelines1199 Monitor.Exit(SyncRoot);1200 