/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/hostifaces/PowerShell.cs
6 191 строка
241 KB
xtqqczze
Fix `IDisposable` implementation in sealed classes (#26215)
18 мар 2026, 21:46
Не верифицирован
18 мар 2026, 21:46
28a2e35
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Telemetry; using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings namespace System.Management.Automation { #region Exceptions /// <summary> /// Defines exception which is thrown when state of the PowerShell is different /// from the expected state. /// </summary> public class InvalidPowerShellStateException : SystemException { /// <summary> /// Creates a new instance of InvalidPowershellStateException class. /// </summary> public InvalidPowerShellStateException() : base (StringUtil.Format(PowerShellStrings.InvalidPowerShellStateGeneral)) { } /// <summary> /// Creates a new instance of InvalidPowershellStateException class. /// </summary> /// <param name="message"> /// The error message that explains the reason for the exception. /// </param> public InvalidPowerShellStateException(string message) : base(message) { } /// <summary> /// Creates a new instance of InvalidPowershellStateException class. /// </summary> /// <param name="message"> /// The error message that explains the reason for the exception. /// </param> /// <param name="innerException"> /// The exception that is the cause of the current exception. /// </param> public InvalidPowerShellStateException(string message, Exception innerException) : base(message, innerException) { } /// <summary> /// Initializes a new instance of the InvalidPowerShellStateException and defines value of /// CurrentState. /// </summary> /// <param name="currentState">Current state of powershell.</param> internal InvalidPowerShellStateException(PSInvocationState currentState) : base (StringUtil.Format(PowerShellStrings.InvalidPowerShellStateGeneral)) { _currState = currentState; } #region ISerializable Members // No need to implement GetObjectData // if all fields are static or [NonSerialized] /// <summary> /// Initializes a new instance of the InvalidPowerShellStateException /// class with serialized data. /// </summary> /// <param name="info"> /// The <see cref="SerializationInfo"/> that holds the serialized object /// data about the exception being thrown. /// </param> /// <param name="context"> /// The <see cref="StreamingContext"/> that contains contextual information /// about the source or destination. /// </param> [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidPowerShellStateException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } #endregion /// <summary> /// Gets CurrentState of the powershell. /// </summary> public PSInvocationState CurrentState { get { return _currState; } } /// <summary> /// State of powershell when exception was thrown. /// </summary> [NonSerialized] private readonly PSInvocationState _currState = 0; } #endregion #region PSInvocationState, PSInvocationStateInfo, PSInvocationStateChangedEventArgs /// <summary> /// Enumerated type defining the state of the PowerShell. /// </summary> public enum PSInvocationState { /// <summary> /// PowerShell has not been started. /// </summary> NotStarted = 0, /// <summary> /// PowerShell is executing. /// </summary> Running = 1, /// <summary> /// PowerShell is stoping execution. /// </summary> Stopping = 2, /// <summary> /// PowerShell is completed due to a stop request. /// </summary> Stopped = 3, /// <summary> /// PowerShell has completed executing a command. /// </summary> Completed = 4, /// <summary> /// PowerShell completed abnormally due to an error. /// </summary> Failed = 5, /// <summary> /// PowerShell is in disconnected state. /// </summary> Disconnected = 6 } /// <summary> /// Enumerated type defining runspace modes for nested pipeline. /// </summary> public enum RunspaceMode { /// <summary> /// Use current runspace from the current thread of execution. /// </summary> CurrentRunspace = 0, /// <summary> /// Create new runspace. /// </summary> NewRunspace = 1 } /// <summary> /// Type which has information about InvocationState and Exception /// associated with InvocationState. /// </summary> public sealed class PSInvocationStateInfo { #region Constructors /// <summary> /// Constructor for state changes with an optional error. /// </summary> /// <param name="state">The new state.</param> /// <param name="reason">A non-null exception if the state change was /// caused by an error,otherwise; null. /// </param> internal PSInvocationStateInfo(PSInvocationState state, Exception reason) { _executionState = state; _exceptionReason = reason; } /// <summary> /// Construct from PipelineStateInfo. /// </summary> /// <param name="pipelineStateInfo"></param> internal PSInvocationStateInfo(PipelineStateInfo pipelineStateInfo) { _executionState = (PSInvocationState)((int)pipelineStateInfo.State); _exceptionReason = pipelineStateInfo.Reason; } #endregion #region Public Properties /// <summary> /// The state of the PowerShell instance. /// </summary> /// <remarks> /// </remarks> public PSInvocationState State { get { return _executionState; } } /// <summary> /// The reason for the state change, if caused by an error. /// </summary> /// <remarks> /// The value of this property is non-null if the state /// changed due to an error. Otherwise, the value of this /// property is null. /// </remarks> public Exception Reason { get { return _exceptionReason; } } #endregion /// <summary> /// Clone the current instance. /// </summary> /// <returns> /// A copy of the current instance. /// </returns> internal PSInvocationStateInfo Clone() { return new PSInvocationStateInfo( _executionState, _exceptionReason ); } #region Private data /// <summary> /// The current execution state. /// </summary> private readonly PSInvocationState _executionState; /// <summary> /// Non-null exception if the execution state change was due to an error. /// </summary> private readonly Exception _exceptionReason; #endregion } /// <summary> /// Event arguments passed to PowerShell state change handlers /// <see cref="PowerShell.InvocationStateChanged"/> event. /// </summary> public sealed class PSInvocationStateChangedEventArgs : EventArgs { #region Constructors /// <summary> /// Constructs PSInvocationStateChangedEventArgs from PSInvocationStateInfo. /// </summary> /// <param name="psStateInfo"> /// state to raise the event with. /// </param> internal PSInvocationStateChangedEventArgs(PSInvocationStateInfo psStateInfo) { Dbg.Assert(psStateInfo != null, "caller should validate the parameter"); InvocationStateInfo = psStateInfo; } #endregion #region Public Properties /// <summary> /// Information about current state of a PowerShell Instance. /// </summary> public PSInvocationStateInfo InvocationStateInfo { get; } #endregion } #endregion /// <summary> /// Settings to control command invocation. /// </summary> public sealed class PSInvocationSettings { #region Private Fields private PSHost _host; // the following are used to flow the identity to pipeline execution thread // Invokes a remote command and immediately disconnects, if transport layer // supports this operation. #endregion #region Constructors /// <summary> /// Default Constructor. /// </summary> public PSInvocationSettings() { this.ApartmentState = ApartmentState.Unknown; _host = null; RemoteStreamOptions = 0; AddToHistory = false; ErrorActionPreference = null; } #endregion /// <summary> /// ApartmentState of the thread in which the command /// is executed. /// </summary> public ApartmentState ApartmentState { get; set; } /// <summary> /// Host to use with the Runspace when the command is /// executed. /// </summary> public PSHost Host { get { return _host; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Host"); } _host = value; } } /// <summary> /// Options for the Error, Warning, Verbose and Debug streams during remote calls. /// </summary> public RemoteStreamOptions RemoteStreamOptions { get; set; } /// <summary> /// Boolean which tells if the command is added to the history of the /// Runspace the command is executing in. By default this is false. /// </summary> public bool AddToHistory { get; set; } /// <summary> /// Determines how errors should be handled during batch command execution. /// </summary> public ActionPreference? ErrorActionPreference { get; set; } /// <summary> /// Used by Powershell remoting infrastructure to flow identity from calling thread to /// Pipeline Execution Thread. /// </summary> /// <remarks> /// Scenario: In the IIS hosting model, the calling thread is impersonated with a different /// identity than the process identity. However Pipeline Execution Thread always inherits /// process's identity and this will create problems related to security. In the IIS hosting /// model, we should honor calling threads identity. /// </remarks> public bool FlowImpersonationPolicy { get; set; } internal System.Security.Principal.WindowsIdentity WindowsIdentityToImpersonate { get; set; } /// <summary> /// When true, allows an unhandled flow control exceptions to /// propagate to a caller invoking the PowerShell object. /// </summary> public bool ExposeFlowControlExceptions { get; set; } /// <summary> /// Invokes a remote command and immediately disconnects, if the transport /// layer supports this operation. /// </summary> internal bool InvokeAndDisconnect { get; set; } } /// <summary> /// Batch execution context. /// </summary> internal class BatchInvocationContext { private readonly AutoResetEvent _completionEvent; /// <summary> /// Class constructor. /// </summary> /// <param name="command"></param> /// <param name="output"></param> internal BatchInvocationContext(PSCommand command, PSDataCollection<PSObject> output) { Command = command; Output = output; _completionEvent = new AutoResetEvent(false); } /// <summary> /// Invocation output. /// </summary> internal PSDataCollection<PSObject> Output { get; } /// <summary> /// Command to invoke. /// </summary> internal PSCommand Command { get; } /// <summary> /// Waits for the completion event. /// </summary> internal void Wait() { _completionEvent.WaitOne(); } /// <summary> /// Signals the completion event. /// </summary> internal void Signal() { _completionEvent.Set(); } } /// <summary> /// These flags control whether InvocationInfo is added to items in the Error, Warning, Verbose and Debug /// streams during remote calls. /// </summary> [Flags] public enum RemoteStreamOptions { /// <summary> /// If this flag is set, ErrorRecord will include an instance of InvocationInfo on remote calls. /// </summary> AddInvocationInfoToErrorRecord = 0x01, /// <summary> /// If this flag is set, WarningRecord will include an instance of InvocationInfo on remote calls. /// </summary> AddInvocationInfoToWarningRecord = 0x02, /// <summary> /// If this flag is set, DebugRecord will include an instance of InvocationInfo on remote calls. /// </summary> AddInvocationInfoToDebugRecord = 0x04, /// <summary> /// If this flag is set, VerboseRecord will include an instance of InvocationInfo on remote calls. /// </summary> AddInvocationInfoToVerboseRecord = 0x08, /// <summary> /// If this flag is set, ErrorRecord, WarningRecord, DebugRecord, and VerboseRecord will include an instance of InvocationInfo on remote calls. /// </summary> AddInvocationInfo = AddInvocationInfoToErrorRecord | AddInvocationInfoToWarningRecord | AddInvocationInfoToDebugRecord | AddInvocationInfoToVerboseRecord } #region PowerShell AsyncResult /// <summary> /// Internal Async result type used by BeginInvoke() and BeginStop() overloads. /// </summary> internal sealed class PowerShellAsyncResult : AsyncResult { #region Private Data / Properties // used to track if this AsyncResult is created by a BeginInvoke operation or // a BeginStop operation. /// <summary> /// True if AsyncResult monitors Async BeginInvoke(). /// false otherwise. /// </summary> internal bool IsAssociatedWithAsyncInvoke { get; } /// <summary> /// The output buffer for the asynchronous invoke. /// </summary> internal PSDataCollection<PSObject> Output { get; } #endregion #region Constructor /// <summary> /// Constructor. /// </summary> /// <param name="ownerId"> /// Instance Id of the Powershell object creating this instance /// </param> /// <param name="callback"> /// Callback to call when the async operation completes. /// </param> /// <param name="state"> /// A user supplied state to call the "callback" with. /// </param> /// <param name="output"> /// The output buffer to return from EndInvoke. /// </param> /// <param name="isCalledFromBeginInvoke"> /// true if AsyncResult monitors BeginInvoke. /// false otherwise /// </param> internal PowerShellAsyncResult(Guid ownerId, AsyncCallback callback, object state, PSDataCollection<PSObject> output, bool isCalledFromBeginInvoke) : base(ownerId, callback, state) { IsAssociatedWithAsyncInvoke = isCalledFromBeginInvoke; Output = output; } #endregion } #endregion /// <summary> /// Represents a PowerShell command or script to execute against a /// Runspace(Pool) if provided, otherwise execute using a default /// Runspace. Provides access to different result buffers /// like output, error, debug, verbose, progress, warning, and information. /// /// Provides a simple interface to execute a powershell command: /// <code> /// Powershell.Create().AddScript("get-process").Invoke(); /// </code> /// The above statement creates a local runspace using default /// configuration, executes the command and then closes the runspace. /// /// Using RunspacePool property, the caller can provide the runspace /// where the command / script is executed. /// </summary> [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "PowerShell is a valid type in SMAR namespace.")] public sealed class PowerShell : IDisposable { #region Private Fields private PSCommand _psCommand; // worker object which does the invoke private Worker _worker; private PowerShellAsyncResult _invokeAsyncResult; private PowerShellAsyncResult _stopAsyncResult; private PowerShellAsyncResult _batchAsyncResult; private PSInvocationSettings _batchInvocationSettings; private PSCommand _backupPSCommand; private object _rsConnection; private PSDataCollection<ErrorRecord> _errorBuffer; private bool _isDisposed; private readonly object _syncObject = new object(); // client remote powershell if the powershell // is executed with a remote runspace pool private ConnectCommandInfo _connectCmdInfo; private bool _commandInvokedSynchronously = false; private bool _isBatching = false; private bool _stopBatchExecution = false; // Delegates for asynchronous invocation/termination of PowerShell commands private readonly Func<IAsyncResult, PSDataCollection<PSObject>> _endInvokeMethod; private readonly Action<IAsyncResult> _endStopMethod; #endregion #region Internal Constructors /// <summary> /// Constructs PowerShell. /// </summary> /// <param name="command"> /// A PSCommand. /// </param> /// <param name="extraCommands"> /// A list of extra commands to run /// </param> /// <param name="rsConnection"> /// A Runspace or RunspacePool to refer while invoking the command. /// This can be null in which case a new runspace is created /// whenever Invoke* method is called. /// </param> private PowerShell(PSCommand command, Collection<PSCommand> extraCommands, object rsConnection) { Dbg.Assert(command != null, "command must not be null"); ExtraCommands = extraCommands ?? new Collection<PSCommand>(); RunningExtraCommands = false; _psCommand = command; _psCommand.Owner = this; RemoteRunspace remoteRunspace = rsConnection as RemoteRunspace; _rsConnection = remoteRunspace != null ? remoteRunspace.RunspacePool : rsConnection; InstanceId = Guid.NewGuid(); InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.NotStarted, null); OutputBuffer = null; OutputBufferOwner = true; _errorBuffer = new PSDataCollection<ErrorRecord>(); ErrorBufferOwner = true; InformationalBuffers = new PSInformationalBuffers(InstanceId); Streams = new PSDataStreams(this); _endInvokeMethod = EndInvoke; _endStopMethod = EndStop; ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.PowerShellCreate, "create"); } /// <summary> /// Constructs a PowerShell instance in the disconnected start state with /// the provided remote command connect information and runspace(pool) objects. /// </summary> /// <param name="connectCmdInfo">Remote command connect information.</param> /// <param name="rsConnection">Remote Runspace or RunspacePool object.</param> internal PowerShell(ConnectCommandInfo connectCmdInfo, object rsConnection) : this(new PSCommand(), null, rsConnection) { ExtraCommands = new Collection<PSCommand>(); RunningExtraCommands = false; AddCommand(connectCmdInfo.Command); _connectCmdInfo = connectCmdInfo; // The command ID is passed to the PSRP layer through the PowerShell instanceID. InstanceId = _connectCmdInfo.CommandId; InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Disconnected, null); if (rsConnection is RemoteRunspace) { _runspace = rsConnection as Runspace; _runspacePool = ((RemoteRunspace)rsConnection).RunspacePool; } else if (rsConnection is RunspacePool) { _runspacePool = (RunspacePool)rsConnection; } Dbg.Assert(_runspacePool != null, "Invalid rsConnection parameter>"); RemotePowerShell = new ClientRemotePowerShell(this, _runspacePool.RemoteRunspacePoolInternal); } /// <summary> /// </summary> /// <param name="inputstream"></param> /// <param name="outputstream"></param> /// <param name="errorstream"></param> /// <param name="runspacePool"></param> internal PowerShell(ObjectStreamBase inputstream, ObjectStreamBase outputstream, ObjectStreamBase errorstream, RunspacePool runspacePool) { ExtraCommands = new Collection<PSCommand>(); RunningExtraCommands = false; _rsConnection = runspacePool; InstanceId = Guid.NewGuid(); InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.NotStarted, null); InformationalBuffers = new PSInformationalBuffers(InstanceId); Streams = new PSDataStreams(this); PSDataCollectionStream<PSObject> outputdatastream = (PSDataCollectionStream<PSObject>)outputstream; OutputBuffer = outputdatastream.ObjectStore; PSDataCollectionStream<ErrorRecord> errordatastream = (PSDataCollectionStream<ErrorRecord>)errorstream; _errorBuffer = errordatastream.ObjectStore; if (runspacePool != null && runspacePool.RemoteRunspacePoolInternal != null) { RemotePowerShell = new ClientRemotePowerShell(this, runspacePool.RemoteRunspacePoolInternal); } _endInvokeMethod = EndInvoke; _endStopMethod = EndStop; } /// <summary> /// Creates a PowerShell object in the disconnected start state and with a ConnectCommandInfo object /// parameter that specifies what remote command to associate with this PowerShell when it is connected. /// </summary> /// <param name="connectCmdInfo"></param> /// <param name="inputstream"></param> /// <param name="outputstream"></param> /// <param name="errorstream"></param> /// <param name="runspacePool"></param> internal PowerShell(ConnectCommandInfo connectCmdInfo, ObjectStreamBase inputstream, ObjectStreamBase outputstream, ObjectStreamBase errorstream, RunspacePool runspacePool) : this(inputstream, outputstream, errorstream, runspacePool) { ExtraCommands = new Collection<PSCommand>(); RunningExtraCommands = false; _psCommand = new PSCommand(); _psCommand.Owner = this; _runspacePool = runspacePool; AddCommand(connectCmdInfo.Command); _connectCmdInfo = connectCmdInfo; // The command ID is passed to the PSRP layer through the PowerShell instanceID. InstanceId = _connectCmdInfo.CommandId; InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Disconnected, null); RemotePowerShell = new ClientRemotePowerShell(this, runspacePool.RemoteRunspacePoolInternal); } /// <summary> /// Sets the command collection in this powershell. /// </summary> /// <remarks>This method will be called by RemotePipeline /// before it begins execution. This method is used to set /// the command collection of the remote pipeline as the /// command collection of the underlying powershell</remarks> internal void InitForRemotePipeline(CommandCollection command, ObjectStreamBase inputstream, ObjectStreamBase outputstream, ObjectStreamBase errorstream, PSInvocationSettings settings, bool redirectShellErrorOutputPipe) { Dbg.Assert(command != null, "A command collection need to be specified"); _psCommand = new PSCommand(command[0]); _psCommand.Owner = this; for (int i = 1; i < command.Count; i++) { AddCommand(command[i]); } RedirectShellErrorOutputPipe = redirectShellErrorOutputPipe; // create the client remote powershell for remoting // communications RemotePowerShell ??= new ClientRemotePowerShell(this, ((RunspacePool)_rsConnection).RemoteRunspacePoolInternal); // If we get here, we don't call 'Invoke' or any of it's friends on 'this', instead we serialize 'this' in PowerShell.ToPSObjectForRemoting. // Without the following two steps, we'll be missing the 'ExtraCommands' on the serialized instance of 'this'. // This is the last possible chance to call set up for batching as we will indirectly call ToPSObjectForRemoting // in the call to ClientRemotePowerShell.Initialize (which happens just below.) DetermineIsBatching(); if (_isBatching) { SetupAsyncBatchExecution(); } RemotePowerShell.Initialize(inputstream, outputstream, errorstream, InformationalBuffers, settings); } /// <summary> /// Initialize PowerShell object for connection to remote command. /// </summary> /// <param name="inputstream">Input stream.</param> /// <param name="outputstream">Output stream.</param> /// <param name="errorstream">Error stream.</param> /// <param name="settings">Settings information.</param> /// <param name="redirectShellErrorOutputPipe">Redirect error output.</param> internal void InitForRemotePipelineConnect(ObjectStreamBase inputstream, ObjectStreamBase outputstream, ObjectStreamBase errorstream, PSInvocationSettings settings, bool redirectShellErrorOutputPipe) { // The remotePowerShell and DSHandler cannot be initialized with a disconnected runspace. // Make sure the associated runspace is valid and connected. CheckRunspacePoolAndConnect(); if (InvocationStateInfo.State != PSInvocationState.Disconnected) { throw new InvalidPowerShellStateException(InvocationStateInfo.State); } RedirectShellErrorOutputPipe = redirectShellErrorOutputPipe; RemotePowerShell ??= new ClientRemotePowerShell(this, ((RunspacePool)_rsConnection).RemoteRunspacePoolInternal); if (!RemotePowerShell.Initialized) { RemotePowerShell.Initialize(inputstream, outputstream, errorstream, InformationalBuffers, settings); } } #endregion #region Construction Factory /// <summary> /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance. /// </summary> /// <returns> /// An instance of PowerShell. /// </returns> public static PowerShell Create() { return new PowerShell(new PSCommand(), null, null); } /// <summary> /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance. /// </summary> /// <param name="runspace">Runspace mode.</param> /// <returns>An instance of PowerShell.</returns> public static PowerShell Create(RunspaceMode runspace) { PowerShell result = null; switch (runspace) { case RunspaceMode.CurrentRunspace: if (Runspace.DefaultRunspace == null) { throw new InvalidOperationException(PowerShellStrings.NoDefaultRunspaceForPSCreate); } result = new PowerShell(new PSCommand(), null, Runspace.DefaultRunspace); result.IsChild = true; result.IsNested = true; result.IsRunspaceOwner = false; result._runspace = Runspace.DefaultRunspace; break; case RunspaceMode.NewRunspace: result = new PowerShell(new PSCommand(), null, null); break; } return result; } /// <summary> /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance. /// </summary> /// <param name="initialSessionState">InitialSessionState with which to create the runspace.</param> /// <returns>An instance of PowerShell.</returns> public static PowerShell Create(InitialSessionState initialSessionState) { PowerShell result = Create(); result.Runspace = RunspaceFactory.CreateRunspace(initialSessionState); result.Runspace.Open(); return result; } /// <summary> /// Constructs an empty PowerShell instance and associates it with the provided /// Runspace; a script or command must be added before invoking this instance. /// </summary> /// <param name="runspace">Runspace in which to invoke commands.</param> /// <returns>An instance of PowerShell.</returns> /// <remarks> /// The required Runspace argument is accepted no matter what state it is in. /// Leaving Runspace state management to the caller allows them to open their /// runspace in whatever manner is most appropriate for their application /// (in another thread while this instance of the PowerShell class is being /// instantiated, for example). /// </remarks> public static PowerShell Create(Runspace runspace) { if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); } PowerShell result = Create(); result.Runspace = runspace; return result; } /// <summary> /// Creates a nested powershell within the current instance. /// Nested PowerShell is used to do simple operations like checking state /// of a variable while another command is using the runspace. /// /// Nested PowerShell should be invoked from the same thread as the parent /// PowerShell invocation thread. So effectively the parent Powershell /// invocation thread is blocked until nested invoke() operation is /// complete. /// /// Implement PSHost.EnterNestedPrompt to perform invoke() operation on the /// nested powershell. /// </summary> /// <exception cref="InvalidOperationException"> /// 1. State of powershell instance is not valid to create a nested powershell instance. /// Nested PowerShell should be created only for a running powershell instance. /// </exception> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ps", Justification = "ps represents PowerShell and is used at many places.")] public PowerShell CreateNestedPowerShell() { if ((_worker != null) && (_worker.CurrentlyRunningPipeline != null)) { PowerShell result = new PowerShell(new PSCommand(), null, _worker.CurrentlyRunningPipeline.Runspace); result.IsNested = true; return result; } throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.InvalidStateCreateNested); } /// <summary> /// Method needed when deserializing PowerShell object coming from a RemoteDataObject. /// </summary> /// <param name="isNested">Indicates if PowerShell object is nested.</param> /// <param name="psCommand">Commands that the PowerShell pipeline is built of.</param> /// <param name="extraCommands">Extra commands to run.</param> private static PowerShell Create(bool isNested, PSCommand psCommand, Collection<PSCommand> extraCommands) { PowerShell powerShell = new PowerShell(psCommand, extraCommands, null); powerShell.IsNested = isNested; return powerShell; } #endregion #region Command / Parameter Construction /// <summary> /// Add a cmdlet to construct a command pipeline. /// For example, to construct a command string "Get-Process | Sort-Object", /// <code> /// PowerShell shell = PowerShell.Create() /// .AddCommand("Get-Process") /// .AddCommand("Sort-Object"); /// </code> /// </summary> /// <param name="cmdlet"> /// A string representing cmdlet. /// </param> /// <returns> /// A PowerShell instance with <paramref name="cmdlet"/> added. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> /// <exception cref="ArgumentNullException"> /// cmdlet is null. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddCommand(string cmdlet) { lock (_syncObject) { AssertChangesAreAccepted(); _psCommand.AddCommand(cmdlet); return this; } } /// <summary> /// Add a cmdlet to construct a command pipeline. /// For example, to construct a command string "Get-Process | Sort-Object", /// <code> /// PowerShell shell = PowerShell.Create() /// .AddCommand("Get-Process", true) /// .AddCommand("Sort-Object", true); /// </code> /// </summary> /// <param name="cmdlet"> /// A string representing cmdlet. /// </param> /// <param name="useLocalScope"> /// if true local scope is used to run the script command. /// </param> /// <returns> /// A PowerShell instance with <paramref name="cmdlet"/> added. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> /// <exception cref="ArgumentNullException"> /// cmdlet is null. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddCommand(string cmdlet, bool useLocalScope) { lock (_syncObject) { AssertChangesAreAccepted(); _psCommand.AddCommand(cmdlet, useLocalScope); return this; } } /// <summary> /// Add a piece of script to construct a command pipeline. /// For example, to construct a command string "Get-Process | ForEach-Object { $_.Name }" /// <code> /// PowerShell shell = PowerShell.Create() /// .AddScript("Get-Process | ForEach-Object { $_.Name }"); /// </code> /// </summary> /// <param name="script"> /// A string representing a script. /// </param> /// <returns> /// A PowerShell instance with <paramref name="command"/> added. /// </returns> /// <remarks> /// This method is not thread-safe. /// </remarks> /// <exception cref="ArgumentNullException"> /// command is null. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddScript(string script) { lock (_syncObject) { AssertChangesAreAccepted(); _psCommand.AddScript(script); return this; } } /// <summary> /// Add a piece of script to construct a command pipeline. /// For example, to construct a command string "Get-Process | ForEach-Object { $_.Name }" /// <code> /// PowerShell shell = PowerShell.Create() /// .AddScript("Get-Process | ForEach-Object { $_.Name }", true); /// </code> /// </summary> /// <param name="script"> /// A string representing a script. /// </param> /// <param name="useLocalScope"> /// if true local scope is used to run the script command. /// </param> /// <returns> /// A PowerShell instance with <paramref name="command"/> added. /// </returns> /// <remarks> /// This method is not thread-safe. /// </remarks> /// <exception cref="ArgumentNullException"> /// command is null. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddScript(string script, bool useLocalScope) { lock (_syncObject) { AssertChangesAreAccepted(); _psCommand.AddScript(script, useLocalScope); return this; } } /// <summary> /// Add a <see cref="Command"/> element to the current command /// pipeline. /// </summary> /// <param name="command"> /// Command to add. /// </param> /// <returns> /// A PSCommand instance with <paramref name="command"/> added. /// </returns> /// <remarks> /// This method is not thread-safe. /// </remarks> /// <exception cref="ArgumentNullException"> /// command is null. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> internal PowerShell AddCommand(Command command) { lock (_syncObject) { AssertChangesAreAccepted(); _psCommand.AddCommand(command); return this; } } /// <summary> /// CommandInfo object for the command to add. /// </summary> /// <param name="commandInfo">The CommandInfo object for the command to add.</param> /// <returns> /// A PSCommand instance with the command added. /// </returns> /// <remarks> /// This method is not thread-safe. /// </remarks> /// <exception cref="ArgumentNullException"> /// command is null. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddCommand(CommandInfo commandInfo) { if (commandInfo == null) { throw PSTraceSource.NewArgumentNullException(nameof(commandInfo)); } Command cmd = new Command(commandInfo); _psCommand.AddCommand(cmd); return this; } /// <summary> /// Add a parameter to the last added command. /// For example, to construct a command string "Get-Process | Select-Object -Property Name" /// <code> /// PowerShell shell = PowerShell.Create() /// .AddCommand("Get-Process") /// .AddCommand("Select-Object").AddParameter("Property", "Name"); /// </code> /// </summary> /// <param name="parameterName"> /// Name of the parameter. /// </param> /// <param name="value"> /// Value for the parameter. /// </param> /// <returns> /// A PowerShell instance with <paramref name="parameterName"/> added /// to the parameter list of the last command. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> /// <exception cref="ArgumentException"> /// Name is non null and name length is zero after trimming whitespace. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddParameter(string parameterName, object value) { lock (_syncObject) { if (_psCommand.Commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); } AssertChangesAreAccepted(); _psCommand.AddParameter(parameterName, value); return this; } } /// <summary> /// Adds a switch parameter to the last added command. /// For example, to construct a command string "get-process | sort-object -descending" /// <code> /// PSCommand command = new PSCommand("get-process"). /// AddCommand("sort-object").AddParameter("descending"); /// </code> /// </summary> /// <param name="parameterName"> /// Name of the parameter. /// </param> /// <returns> /// A PowerShell instance with <paramref name="parameterName"/> added /// to the parameter list of the last command. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> /// <exception cref="ArgumentException"> /// Name is non null and name length is zero after trimming whitespace. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PowerShell AddParameter(string parameterName) { lock (_syncObject) { if (_psCommand.Commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); } AssertChangesAreAccepted(); _psCommand.AddParameter(parameterName); return this; } } /// <summary> /// Adds a <see cref="CommandParameter"/> instance to the last added command. /// </summary> internal PowerShell AddParameter(CommandParameter parameter) { lock (_syncObject) { if (_psCommand.Commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); } AssertChangesAreAccepted(); _psCommand.AddParameter(parameter); return this; } } /// <summary> /// Adds a set of parameters to the last added command. /// </summary> /// <param name="parameters"> /// List of parameters. /// </param> /// <returns> /// A PowerShell instance with the items in <paramref name="parameters"/> added /// to the parameter list of the last command. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> /// <exception cref="PSArgumentNullException"> /// The function was given a null argument. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> public PowerShell AddParameters(IList parameters) { lock (_syncObject) { if (parameters == null) { throw PSTraceSource.NewArgumentNullException(nameof(parameters)); } if (_psCommand.Commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); } AssertChangesAreAccepted(); foreach (object p in parameters) { _psCommand.AddParameter(null, p); } return this; } } /// <summary> /// Adds a set of parameters to the last added command. /// </summary> /// <param name="parameters"> /// Dictionary of parameters. Each key-value pair corresponds to a parameter name and its value. Keys must strings. /// </param> /// <returns> /// A PowerShell instance with the items in <paramref name="parameters"/> added /// to the parameter list of the last command. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> /// <exception cref="PSArgumentNullException"> /// The function was given a null argument. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="PSArgumentException"> /// One of the dictionary keys is not a string. /// </exception> public PowerShell AddParameters(IDictionary parameters) { lock (_syncObject) { if (parameters == null) { throw PSTraceSource.NewArgumentNullException(nameof(parameters)); } if (_psCommand.Commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); } AssertChangesAreAccepted(); foreach (DictionaryEntry entry in parameters) { if (entry.Key is not string parameterName) { throw PSTraceSource.NewArgumentException(nameof(parameters), PowerShellStrings.KeyMustBeString); } _psCommand.AddParameter(parameterName, entry.Value); } return this; } } /// <summary> /// Adds an argument to the last added command. /// For example, to construct a command string "Get-Process | Select-Object Name" /// <code> /// PowerShell shell = PowerShell.Create() /// .AddCommand("Get-Process") /// .AddCommand("Select-Object").AddArgument("Name"); /// </code> /// This will add the value "name" to the positional parameter list of "select-object" /// cmdlet. When the command is invoked, this value will get bound to positional parameter 0 /// of the "select-object" cmdlet which is "Property". /// </summary> /// <param name="value"> /// Value for the parameter. /// </param> /// <returns> /// A PSCommand instance parameter value <paramref name="value"/> added /// to the parameter list of the last command. /// </returns> /// <remarks> /// This method is not thread safe. /// </remarks> public PowerShell AddArgument(object value) { lock (_syncObject) { if (_psCommand.Commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); } AssertChangesAreAccepted(); _psCommand.AddArgument(value); return this; } } /// <summary> /// Adds an additional statement for execution /// /// For example, /// <code> /// Runspace rs = RunspaceFactory.CreateRunspace(); /// PowerShell ps = PowerShell.Create(); /// /// ps.Runspace = rs; /// ps.AddCommand("Get-Process").AddArgument("idle"); /// ps.AddStatement().AddCommand("Get-Service").AddArgument("audiosrv"); /// ps.Invoke(); /// </code> /// </summary> /// <returns> /// A PowerShell instance with the items in <paramref name="parameters"/> added /// to the parameter list of the last command. /// </returns> public PowerShell AddStatement() { lock (_syncObject) { // for PowerShell.Create().AddStatement().AddCommand("Get-Process"); // we reduce it to PowerShell.Create().AddCommand("Get-Process"); if (_psCommand.Commands.Count == 0) { return this; } AssertChangesAreAccepted(); _psCommand.Commands[_psCommand.Commands.Count - 1].IsEndOfStatement = true; return this; } } #endregion #region Public Properties /// <summary> /// Gets or sets current powershell command line. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public PSCommand Commands { get { return _psCommand; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Command"); } lock (_syncObject) { AssertChangesAreAccepted(); _psCommand = value.Clone(); _psCommand.Owner = this; } } } /// <summary> /// Streams generated by PowerShell invocations. /// </summary> public PSDataStreams Streams { get; } /// <summary> /// Gets or sets the error buffer. Powershell invocation writes /// the error data into this buffer. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] internal PSDataCollection<ErrorRecord> ErrorBuffer { get { return _errorBuffer; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Error"); } lock (_syncObject) { AssertChangesAreAccepted(); _errorBuffer = value; ErrorBufferOwner = false; } } } /// <summary> /// Gets or sets the progress buffer. Powershell invocation writes /// the progress data into this buffer. Can be null. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] internal PSDataCollection<ProgressRecord> ProgressBuffer { get { return InformationalBuffers.Progress; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Progress"); } lock (_syncObject) { AssertChangesAreAccepted(); InformationalBuffers.Progress = value; } } } /// <summary> /// Gets or sets the verbose buffer. Powershell invocation writes /// the verbose data into this buffer. Can be null. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] internal PSDataCollection<VerboseRecord> VerboseBuffer { get { return InformationalBuffers.Verbose; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Verbose"); } lock (_syncObject) { AssertChangesAreAccepted(); InformationalBuffers.Verbose = value; } } } /// <summary> /// Gets or sets the debug buffer. Powershell invocation writes /// the debug data into this buffer. Can be null. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] internal PSDataCollection<DebugRecord> DebugBuffer { get { return InformationalBuffers.Debug; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Debug"); } lock (_syncObject) { AssertChangesAreAccepted(); InformationalBuffers.Debug = value; } } } /// <summary> /// Gets or sets the warning buffer. Powershell invocation writes /// the warning data into this buffer. Can be null. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] internal PSDataCollection<WarningRecord> WarningBuffer { get { return InformationalBuffers.Warning; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Warning"); } lock (_syncObject) { AssertChangesAreAccepted(); InformationalBuffers.Warning = value; } } } /// <summary> /// Gets or sets the information buffer. Powershell invocation writes /// the information data into this buffer. Can be null. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] internal PSDataCollection<InformationRecord> InformationBuffer { get { return InformationalBuffers.Information; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Information"); } lock (_syncObject) { AssertChangesAreAccepted(); InformationalBuffers.Information = value; } } } /// <summary> /// Gets the informational buffers. /// </summary> internal PSInformationalBuffers InformationalBuffers { get; } /// <summary> /// If this flag is true, the commands in this Pipeline will redirect /// the global error output pipe to the command's error output pipe. /// /// (see the comment in Pipeline.RedirectShellErrorOutputPipe for an /// explanation of why this flag is needed) /// </summary> internal bool RedirectShellErrorOutputPipe { get; set; } = true; /// <summary> /// Get unique id for this instance of runspace pool. It is primarily used /// for logging purposes. /// </summary> public Guid InstanceId { get; private set; } /// <summary> /// Gets the execution state of the current PowerShell instance. /// </summary> public PSInvocationStateInfo InvocationStateInfo { get; private set; } /// <summary> /// Gets the property which indicates if this PowerShell instance /// is nested. /// </summary> public bool IsNested { get; private set; } /// <summary> /// Gets the property which indicates if this PowerShell instance /// is a child instance. /// /// IsChild flag makes it possible for the pipeline to differentiate between /// a true v1 nested pipeline and the cmdlets calling cmdlets case. See bug /// 211462. /// </summary> internal bool IsChild { get; private set; } = false; /// <summary> /// If an error occurred while executing the pipeline, this will be set to true. /// </summary> public bool HadErrors { get; private set; } internal void SetHadErrors(bool status) { HadErrors = status; } /// <summary> /// Access to the EndInvoke AsyncResult object. Used by remote /// debugging to invoke debugger commands on command thread. /// </summary> internal AsyncResult EndInvokeAsyncResult { get; private set; } /// <summary> /// Event raised when PowerShell Execution State Changes. /// </summary> public event EventHandler<PSInvocationStateChangedEventArgs> InvocationStateChanged; /// <summary> /// This event gets fired when a Runspace from the RunspacePool is assigned to this PowerShell /// instance to invoke the commands. /// </summary> internal event EventHandler<PSEventArgs<Runspace>> RunspaceAssigned; #endregion #region Public Methods /// <summary> /// Sets an associated Runspace for this PowerShell instance. /// This can be null in which case a new runspace is created /// whenever Invoke* method is called. /// </summary> /// <remarks> /// This property and RunspacePool are mutually exclusive; setting one of them resets the other to null /// </remarks> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace", Justification = "Runspace is a well-known term in PowerShell.")] public Runspace Runspace { get { if (_runspace == null && _runspacePool == null) // create a runspace only if neither a runspace nor a runspace pool have been set { lock (_syncObject) { if (_runspace == null && _runspacePool == null) { AssertChangesAreAccepted(); SetRunspace(RunspaceFactory.CreateRunspace(), true); this.Runspace.Open(); } } } return _runspace; } set { lock (_syncObject) { AssertChangesAreAccepted(); if (_runspace != null && IsRunspaceOwner) { _runspace.Dispose(); _runspace = null; IsRunspaceOwner = false; } SetRunspace(value, false); } } } /// <summary> /// Internal method to set the Runspace property. /// </summary> private void SetRunspace(Runspace runspace, bool owner) { RemoteRunspace remoteRunspace = runspace as RemoteRunspace; if (remoteRunspace == null) { _rsConnection = runspace; } else { _rsConnection = remoteRunspace.RunspacePool; if (RemotePowerShell != null) { RemotePowerShell.Clear(); RemotePowerShell.Dispose(); } RemotePowerShell = new ClientRemotePowerShell(this, remoteRunspace.RunspacePool.RemoteRunspacePoolInternal); } _runspace = runspace; IsRunspaceOwner = owner; _runspacePool = null; } private Runspace _runspace = null; /// <summary> /// Sets an associated RunspacePool for this PowerShell instance. /// A Runspace from this pool is used whenever Invoke* method /// is called. /// /// This can be null in which case a new runspace is created /// whenever Invoke* method is called. /// </summary> /// <remarks> /// This property and Runspace are mutually exclusive; setting one of them resets the other to null /// </remarks> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace", Justification = "Runspace is a well-known term in PowerShell.")] public RunspacePool RunspacePool { get { return _runspacePool; } set { if (value != null) { lock (_syncObject) { AssertChangesAreAccepted(); if (_runspace != null && IsRunspaceOwner) { _runspace.Dispose(); _runspace = null; IsRunspaceOwner = false; } _rsConnection = value; _runspacePool = value; if (_runspacePool.IsRemote) { if (RemotePowerShell != null) { RemotePowerShell.Clear(); RemotePowerShell.Dispose(); } RemotePowerShell = new ClientRemotePowerShell(this, _runspacePool.RemoteRunspacePoolInternal); } _runspace = null; } } } } private RunspacePool _runspacePool = null; /// <summary> /// Gets the associated Runspace or RunspacePool for this PowerShell /// instance. If this is null, PowerShell instance is not associated /// with any runspace. /// </summary> internal object GetRunspaceConnection() { return _rsConnection; } #endregion #region Connect Support /// <summary> /// Synchronously connects to a running command on a remote server. /// </summary> /// <returns>Command output as a PSDataCollection.</returns> public Collection<PSObject> Connect() { // Regardless of how the command was originally invoked, set this member // variable to indicate the command is now running synchronously. _commandInvokedSynchronously = true; IAsyncResult asyncResult = ConnectAsync(); PowerShellAsyncResult psAsyncResult = asyncResult as PowerShellAsyncResult; // Wait for command to complete. If an exception was thrown during command // execution (such as disconnect occurring during this synchronous execution) // then the exception will be thrown here. EndInvokeAsyncResult = psAsyncResult; psAsyncResult.EndInvoke(); EndInvokeAsyncResult = null; Collection<PSObject> results = null; if (psAsyncResult.Output != null) { results = psAsyncResult.Output.ReadAll(); } else { // Return empty collection. results = new Collection<PSObject>(); } return results; } /// <summary> /// Asynchronously connects to a running command on a remote server. /// The returned IAsyncResult object can be used with EndInvoke() method /// to wait on command and/or get command returned data. /// </summary> /// <returns>IAsyncResult.</returns> public IAsyncResult ConnectAsync() { return ConnectAsync(null, null, null); } /// <summary> /// Asynchronously connects to a running command on a remote server. /// The returned IAsyncResult object can be used with EndInvoke() method /// to wait on command and/or get command returned data. /// </summary> /// <param name="output">The output buffer to return from EndInvoke.</param> /// <param name="invocationCallback">An AsyncCallback to be called once the previous invocation has completed.</param> /// <param name="state">A user supplied state to call the <paramref name="invocationCallback"/> with.</param> /// <returns>IAsyncResult.</returns> public IAsyncResult ConnectAsync( PSDataCollection<PSObject> output, AsyncCallback invocationCallback, object state) { if (InvocationStateInfo.State != PSInvocationState.Disconnected) { throw new InvalidPowerShellStateException(InvocationStateInfo.State); } // Ensure this is a command invoked on a remote runspace(pool) and connect the // runspace if it is currently disconnected. CheckRunspacePoolAndConnect(); if (_connectCmdInfo != null) { // // This is a reconstruct/connect scenario and we create new state. // PSDataCollection<PSObject> streamToUse = OutputBuffer; // The remotePowerShell may have been initialized by InitForRemotePipelineConnect() if (!RemotePowerShell.Initialized) { // Empty input stream. ObjectStreamBase inputStream = new ObjectStream(); inputStream.Close(); // Output stream. if (output != null) { // Use the supplied output buffer. OutputBuffer = output; OutputBufferOwner = false; } else if (OutputBuffer == null) { OutputBuffer = new PSDataCollection<PSObject>(); OutputBufferOwner = true; } streamToUse = OutputBuffer; ObjectStreamBase outputStream = new PSDataCollectionStream<PSObject>(InstanceId, streamToUse); RemotePowerShell.Initialize(inputStream, outputStream, new PSDataCollectionStream<ErrorRecord>(InstanceId, _errorBuffer), InformationalBuffers, null); } Dbg.Assert((_invokeAsyncResult == null), "Async result should be null in the reconstruct scenario."); _invokeAsyncResult = new PowerShellAsyncResult(InstanceId, invocationCallback, state, streamToUse, true); } else { // If this is not a reconstruct scenario then this must be a PowerShell object that was // previously disconnected, and all state should be valid. Dbg.Assert((_invokeAsyncResult != null && RemotePowerShell.Initialized), "AsyncResult and RemotePowerShell objects must be valid here."); if (output != null || invocationCallback != null || _invokeAsyncResult.IsCompleted) { // A new async object is needed. PSDataCollection<PSObject> streamToUse; if (output != null) { streamToUse = output; OutputBuffer = output; OutputBufferOwner = false; } else if (_invokeAsyncResult.Output == null || !_invokeAsyncResult.Output.IsOpen) { OutputBuffer = new PSDataCollection<PSObject>(); OutputBufferOwner = true; streamToUse = OutputBuffer; } else { streamToUse = _invokeAsyncResult.Output; OutputBuffer = streamToUse; OutputBufferOwner = false; } _invokeAsyncResult = new PowerShellAsyncResult( InstanceId, invocationCallback ?? _invokeAsyncResult.Callback, (invocationCallback != null) ? state : _invokeAsyncResult.AsyncState, streamToUse, true); } } try { // Perform the connect operation to the remote server through the PSRP layer. // If this.connectCmdInfo is null then a connection will be attempted using current state. // If this.connectCmdInfo is non-null then a connection will be attempted with this info. RemotePowerShell.ConnectAsync(_connectCmdInfo); } catch (Exception exception) { // allow GC collection _invokeAsyncResult = null; SetStateChanged(new PSInvocationStateInfo(PSInvocationState.Failed, exception)); // re-throw the exception InvalidRunspacePoolStateException poolException = exception as InvalidRunspacePoolStateException; if (poolException != null && _runspace != null) // the pool exception was actually thrown by a runspace { throw poolException.ToInvalidRunspaceStateException(); } throw; } return _invokeAsyncResult; } /// <summary> /// Checks that the current runspace associated with this PowerShell is a remote runspace, /// and if it is in Disconnected state then to connect it. /// </summary> private void CheckRunspacePoolAndConnect() { RemoteRunspacePoolInternal remoteRunspacePoolInternal = null; if (_rsConnection is RemoteRunspace) { remoteRunspacePoolInternal = (_rsConnection as RemoteRunspace).RunspacePool.RemoteRunspacePoolInternal; } else if (_rsConnection is RunspacePool) { remoteRunspacePoolInternal = (_rsConnection as RunspacePool).RemoteRunspacePoolInternal; } if (remoteRunspacePoolInternal == null) { throw new InvalidOperationException(PowerShellStrings.CannotConnect); } // Connect runspace if needed. if (remoteRunspacePoolInternal.RunspacePoolStateInfo.State == RunspacePoolState.Disconnected) { remoteRunspacePoolInternal.Connect(); } // Make sure runspace is in valid state for connection. if (remoteRunspacePoolInternal.RunspacePoolStateInfo.State != RunspacePoolState.Opened) { throw new InvalidRunspacePoolStateException(RunspacePoolStrings.InvalidRunspacePoolState, remoteRunspacePoolInternal.RunspacePoolStateInfo.State, RunspacePoolState.Opened); } } #endregion #region Script Debugger Support /// <summary> /// This method allows the script debugger first crack at evaluating the /// command in case it is a debugger command, otherwise the command is /// evaluated by PowerShell. /// If the debugger evaluated a command then DebuggerCommand.ResumeAction /// value will be set appropriately. /// </summary> /// <param name="input">Input.</param> /// <param name="output">Output collection.</param> /// <param name="settings">PS invocation settings.</param> /// <param name="invokeMustRun">True if PowerShell Invoke must run regardless /// of whether debugger handles the command. /// </param> /// <returns>DebuggerCommandResults.</returns> internal void InvokeWithDebugger( IEnumerable<object> input, IList<PSObject> output, PSInvocationSettings settings, bool invokeMustRun) { Debugger debugger = _runspace.Debugger; bool addToHistory = true; if (debugger != null && Commands.Commands.Count > 0) { Command cmd = this.Commands.Commands[0]; DebuggerCommand dbgCommandResult = debugger.InternalProcessCommand( cmd.CommandText, output); if (dbgCommandResult.ResumeAction != null || dbgCommandResult.ExecutedByDebugger) { output.Add(new PSObject(dbgCommandResult)); Commands.Commands.Clear(); addToHistory = false; } else if (!dbgCommandResult.Command.Equals(cmd.CommandText, StringComparison.OrdinalIgnoreCase)) { // Script debugger will replace commands, e.g., "k" -> "Get-PSCallStack". Commands.Commands[0] = new Command(dbgCommandResult.Command, false, true, true); // Report that these replaced commands are executed by the debugger. DebuggerCommand dbgCommand = new DebuggerCommand(dbgCommandResult.Command, null, false, true); output.Add(new PSObject(dbgCommand)); addToHistory = false; } } if (addToHistory && (Commands.Commands.Count > 0)) { addToHistory = DebuggerUtils.ShouldAddCommandToHistory( Commands.Commands[0].CommandText); } // Remote PowerShell Invoke must always run Invoke so that the // command can complete. if (Commands.Commands.Count == 0 && invokeMustRun) { Commands.Commands.AddScript(string.Empty); } if (Commands.Commands.Count > 0) { if (addToHistory) { settings ??= new PSInvocationSettings(); settings.AddToHistory = true; } Invoke<PSObject>(input, output, settings); } } #endregion #region Invoke Overloads /// <summary> /// Invoke the <see cref="Command"/> synchronously and return /// the output PSObject collection. /// </summary> /// <returns> /// collection of PSObjects. /// </returns> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public Collection<PSObject> Invoke() { return Invoke(null, null); } /// <summary> /// Invoke the <see cref="Command"/> synchronously and return /// the output PSObject collection. /// </summary> /// <param name="input"> /// Input to the command /// </param> /// <returns> /// Collection of PSObjects representing output. /// </returns> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public Collection<PSObject> Invoke(IEnumerable input) { return Invoke(input, null); } /// <summary> /// Invoke the <see cref="Command"/> synchronously and return /// the output PSObject collection. /// </summary> /// <param name="input"> /// Input to the command /// </param> /// <param name="settings"> /// Invocation Settings /// </param> /// <returns> /// Collection of PSObjects representing output. /// </returns> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public Collection<PSObject> Invoke(IEnumerable input, PSInvocationSettings settings) { Collection<PSObject> result = new Collection<PSObject>(); PSDataCollection<PSObject> listToWriteTo = new PSDataCollection<PSObject>(result); CoreInvoke<PSObject>(input, listToWriteTo, settings); return result; } /// <summary> /// Invoke the <see cref="Command"/> synchronously and return /// the output. /// </summary> /// <typeparam name="T"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public Collection<T> Invoke<T>() { // We should bind all the results to this instance except // for output. Collection<T> result = new Collection<T>(); Invoke<T>(null, result, null); return result; } /// <summary> /// Invoke the <see cref="Command"/> synchronously and return /// the output. /// </summary> /// <typeparam name="T"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command /// </param> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public Collection<T> Invoke<T>(IEnumerable input) { Collection<T> result = new Collection<T>(); Invoke<T>(input, result, null); return result; } /// <summary> /// Invoke the <see cref="Command"/> synchronously and return /// the output. /// </summary> /// <typeparam name="T"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command /// </param> /// <param name="settings"> /// Invocation Settings /// </param> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public Collection<T> Invoke<T>(IEnumerable input, PSInvocationSettings settings) { Collection<T> result = new Collection<T>(); Invoke<T>(input, result, settings); return result; } /// <summary> /// Invoke the <see cref="Command"/> synchronously and collect /// output data into the buffer <paramref name="output"/> /// </summary> /// <typeparam name="T"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command /// </param> /// <param name="output"> /// A collection supplied by the user where output is collected. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="output"/> cannot be null. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is /// already started.Stop the command and try the operation again. /// (or) /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public void Invoke<T>(IEnumerable input, IList<T> output) { Invoke<T>(input, output, null); } /// <summary> /// Invoke the <see cref="Command"/> synchronously and collect /// output data into the buffer <paramref name="output"/> /// </summary> /// <typeparam name="T"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command /// </param> /// <param name="output"> /// A collection supplied by the user where output is collected. /// </param> /// <param name="settings"> /// Invocation Settings to use. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="output"/> cannot be null. /// </exception> /// <exception cref="InvalidOperationException"> /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public void Invoke<T>(IEnumerable input, IList<T> output, PSInvocationSettings settings) { if (output == null) { throw PSTraceSource.NewArgumentNullException(nameof(output)); } // use the above collection as the data store. PSDataCollection<T> listToWriteTo = new PSDataCollection<T>(output); CoreInvoke<T>(input, listToWriteTo, settings); } /// <summary> /// Invoke the <see cref="Command"/> synchronously and stream /// output data into the buffer <paramref name="output"/> /// </summary> /// <typeparam name="TInput"> /// Type of input object(s) expected from the command invocation. /// </typeparam> /// <typeparam name="TOutput"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command /// </param> /// <param name="output"> /// Output of the command. /// </param> /// <param name="settings"> /// Invocation Settings to use. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="output"/> cannot be null. /// </exception> /// <exception cref="InvalidOperationException"> /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> public void Invoke<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings) { if (output == null) { throw PSTraceSource.NewArgumentNullException(nameof(output)); } CoreInvoke<TInput, TOutput>(input, output, settings); } /// <summary> /// Invoke the <see cref="Command"/> asynchronously. /// Use EndInvoke() to obtain the output of the command. /// </summary> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public IAsyncResult BeginInvoke() { return BeginInvoke<object>(null, null, null, null); } /// <summary> /// Invoke the <see cref="Command"/> asynchronously. /// Use EndInvoke() to obtain the output of the command. /// </summary> /// <remarks> /// When invoked using BeginInvoke, invocation doesn't /// finish until Input is closed. Caller of BeginInvoke must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling BeginInvoke(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </remarks> /// <typeparam name="T"> /// Type of the input buffer /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public IAsyncResult BeginInvoke<T>(PSDataCollection<T> input) { return BeginInvoke<T>(input, null, null, null); } /// <summary> /// Invoke the <see cref="Command"/> asynchronously. /// Use EndInvoke() to obtain the output of the command. /// </summary> /// <remarks> /// When invoked using BeginInvoke, invocation doesn't /// finish until Input is closed. Caller of BeginInvoke must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling BeginInvoke(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </remarks> /// <typeparam name="T"> /// Type of the input buffer /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="settings"> /// Invocation Settings. /// </param> /// <param name="callback"> /// An AsyncCallback to call once the BeginInvoke completes. /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. /// The callback could be invoked from a thread without a default Runspace and a delegate cast from /// a script block would fail in that case. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public IAsyncResult BeginInvoke<T>(PSDataCollection<T> input, PSInvocationSettings settings, AsyncCallback callback, object state) { DetermineIsBatching(); if (OutputBuffer != null) { if (_isBatching || ExtraCommands.Count != 0) { return BeginBatchInvoke<T, PSObject>(input, OutputBuffer, settings, callback, state); } return CoreInvokeAsync<T, PSObject>(input, OutputBuffer, settings, callback, state, null); } else { OutputBuffer = new PSDataCollection<PSObject>(); OutputBufferOwner = true; if (_isBatching || ExtraCommands.Count != 0) { return BeginBatchInvoke<T, PSObject>(input, OutputBuffer, settings, callback, state); } return CoreInvokeAsync<T, PSObject>(input, OutputBuffer, settings, callback, state, OutputBuffer); } } /// <summary> /// Invoke the <see cref="Command"/> asynchronously. /// When this method is used EndInvoke() returns a null buffer. /// </summary> /// <remarks> /// When invoked using BeginInvoke, invocation doesn't /// finish until Input is closed. Caller of BeginInvoke must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling BeginInvoke(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </remarks> /// <typeparam name="TInput"> /// Type of input object(s) for the command invocation. /// </typeparam> /// <typeparam name="TOutput"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="output"> /// A buffer supplied by the user where output is collected. /// </param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public IAsyncResult BeginInvoke<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output) { return BeginInvoke<TInput, TOutput>(input, output, null, null, null); } /// <summary> /// Invoke the <see cref="Command"/> asynchronously and collect /// output data into the buffer <paramref name="output"/>. /// When this method is used EndInvoke() returns a null buffer. /// </summary> /// <remarks> /// When invoked using BeginInvoke, invocation doesn't /// finish until Input is closed. Caller of BeginInvoke must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling BeginInvoke(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </remarks> /// <typeparam name="TInput"> /// Type of input object(s) for the command invocation. /// </typeparam> /// <typeparam name="TOutput"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="output"> /// A buffer supplied by the user where output is collected. /// </param> /// <param name="settings"> /// Invocation Settings. /// </param> /// <param name="callback"> /// An AsyncCallback to call once the BeginInvoke completes. /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. /// The callback could be invoked from a thread without a default Runspace and a delegate cast from /// a script block would fail in that case. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public IAsyncResult BeginInvoke<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, AsyncCallback callback, object state) { if (output == null) { throw PSTraceSource.NewArgumentNullException(nameof(output)); } DetermineIsBatching(); if (_isBatching || ExtraCommands.Count != 0) { return BeginBatchInvoke<TInput, TOutput>(input, output, settings, callback, state); } return CoreInvokeAsync<TInput, TOutput>(input, output, settings, callback, state, null); } /// <summary> /// Invoke a PowerShell command asynchronously. /// Use await to wait for the command to complete and obtain the output of the command. /// </summary> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke. /// </returns> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="System.Management.Automation.PipelineStoppedException"> /// The running PowerShell pipeline was stopped. /// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called. /// </exception> public Task<PSDataCollection<PSObject>> InvokeAsync() => Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke(), _endInvokeMethod); /// <summary> /// Invoke a PowerShell command asynchronously. /// Use await to wait for the command to complete and obtain the output of the command. /// </summary> /// <remarks> /// <para> /// When invoked using InvokeAsync, invocation doesn't /// finish until Input is closed. Caller of InvokeAsync must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// </para><para> /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling InvokeAsync(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </para> /// </remarks> /// <typeparam name="T"> /// Type of the input buffer. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke. /// </returns> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="System.Management.Automation.PipelineStoppedException"> /// The running PowerShell pipeline was stopped. /// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called. /// </exception> public Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input) => Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<T>(input), _endInvokeMethod); /// <summary> /// Invoke a PowerShell command asynchronously. /// Use await to wait for the command to complete and obtain the output of the command. /// </summary> /// <remarks> /// <para> /// When invoked using InvokeAsync, invocation doesn't /// finish until Input is closed. Caller of InvokeAsync must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// </para><para> /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling InvokeAsync(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </para> /// </remarks> /// <typeparam name="T"> /// Type of the input buffer. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="settings"> /// Invocation Settings. /// </param> /// <param name="callback"> /// An AsyncCallback to call once the command is invoked. /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. /// The callback could be invoked from a thread without a default Runspace and a delegate cast from /// a script block would fail in that case. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke. /// </returns> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="System.Management.Automation.PipelineStoppedException"> /// The running PowerShell pipeline was stopped. /// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called. /// </exception> public Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input, PSInvocationSettings settings, AsyncCallback callback, object state) => Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<T>(input, settings, callback, state), _endInvokeMethod); /// <summary> /// Invoke a PowerShell command asynchronously. /// Use await to wait for the command to complete and obtain the output of the command. /// </summary> /// <remarks> /// <para> /// When invoked using InvokeAsync, invocation doesn't /// finish until Input is closed. Caller of InvokeAsync must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// </para><para> /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling InvokeAsync(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </para> /// </remarks> /// <typeparam name="TInput"> /// Type of input object(s) for the command invocation. /// </typeparam> /// <typeparam name="TOutput"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="output"> /// A buffer supplied by the user where output is collected. /// </param> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke, /// or null if the caller provided their own buffer. /// </returns> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="System.Management.Automation.PipelineStoppedException"> /// The running PowerShell pipeline was stopped. /// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called. /// To collect partial output in this scenario, /// supply a <see cref="System.Management.Automation.PSDataCollection{T}" /> for the <paramref name="output"/> parameter, /// and either add a handler for the <see cref="System.Management.Automation.PSDataCollection{T}.DataAdding"/> event /// or catch the exception and enumerate the object supplied for <paramref name="output"/>. /// </exception> public Task<PSDataCollection<PSObject>> InvokeAsync<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output) => Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<TInput, TOutput>(input, output), _endInvokeMethod); /// <summary> /// Invoke a PowerShell command asynchronously and collect /// output data into the buffer <paramref name="output"/>. /// Use await to wait for the command to complete and obtain the output of the command. /// </summary> /// <remarks> /// <para> /// When invoked using InvokeAsync, invocation doesn't /// finish until Input is closed. Caller of InvokeAsync must /// close the input buffer after all input has been written to /// input buffer. Input buffer is closed by calling /// Close() method. /// </para><para> /// If you want this command to execute as a standalone cmdlet /// (that is, using command-line parameters only), /// be sure to call Close() before calling InvokeAsync(). Otherwise, /// the command will be executed as though it had external input. /// If you observe that the command isn't doing anything, /// this may be the reason. /// </para> /// </remarks> /// <typeparam name="TInput"> /// Type of input object(s) for the command invocation. /// </typeparam> /// <typeparam name="TOutput"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="output"> /// A buffer supplied by the user where output is collected. /// </param> /// <param name="settings"> /// Invocation Settings. /// </param> /// <param name="callback"> /// An AsyncCallback to call once the command is invoked. /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. /// The callback could be invoked from a thread without a default Runspace and a delegate cast from /// a script block would fail in that case. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke, /// or null if the caller provided their own buffer. /// </returns> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <exception cref="System.Management.Automation.PipelineStoppedException"> /// The running PowerShell pipeline was stopped. /// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called. /// To collect partial output in this scenario, /// supply a <see cref="System.Management.Automation.PSDataCollection{T}" /> for the <paramref name="output"/> parameter, /// and either add a handler for the <see cref="System.Management.Automation.PSDataCollection{T}.DataAdding"/> event /// or catch the exception and use object supplied for <paramref name="output"/>. /// </exception> public Task<PSDataCollection<PSObject>> InvokeAsync<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, AsyncCallback callback, object state) => Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<TInput, TOutput>(input, output, settings, callback, state), _endInvokeMethod); /// <summary> /// Begins a batch execution. /// </summary> /// <typeparam name="TInput"> /// Type of input object(s) for the command invocation. /// </typeparam> /// <typeparam name="TOutput"> /// Type of output object(s) expected from the command invocation. /// </typeparam> /// <param name="input"> /// Input to the command. See remarks for more details. /// </param> /// <param name="output"> /// A buffer supplied by the user where output is collected. /// </param> /// <param name="settings"> /// Invocation Settings. /// </param> /// <param name="callback"> /// An AsyncCallback to call once the BeginInvoke completes. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> private IAsyncResult BeginBatchInvoke<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, AsyncCallback callback, object state) { if ((object)output is not PSDataCollection<PSObject> asyncOutput) { throw PSTraceSource.NewInvalidOperationException(); } if (_isBatching) { SetupAsyncBatchExecution(); } RunspacePool pool = _rsConnection as RunspacePool; if ((pool != null) && (pool.IsRemote)) { // Server supports batch invocation, in this case, we just send everything to the server and return immediately if (ServerSupportsBatchInvocation()) { try { return CoreInvokeAsync<TInput, TOutput>(input, output, settings, callback, state, asyncOutput); } finally { if (_isBatching) { EndAsyncBatchExecution(); } } } } // Non-remoting case or server does not support batching // In this case we execute the cmdlets one by one RunningExtraCommands = true; _batchInvocationSettings = settings; _batchAsyncResult = new PowerShellAsyncResult(InstanceId, callback, state, asyncOutput, true); CoreInvokeAsync<TInput, TOutput>(input, output, settings, new AsyncCallback(BatchInvocationCallback), state, asyncOutput); return _batchAsyncResult; } /// <summary> /// Batch invocation callback. /// </summary> /// <param name="state"></param> private void BatchInvocationWorkItem(object state) { Debug.Assert(ExtraCommands.Count != 0, "This callback is for batch invocation only"); BatchInvocationContext context = state as BatchInvocationContext; Debug.Assert(context != null, "Context should never be null"); PSCommand backupCommand = _psCommand; try { _psCommand = context.Command; // Last element if (_psCommand == ExtraCommands[ExtraCommands.Count - 1]) { RunningExtraCommands = false; } try { IAsyncResult cmdResult = CoreInvokeAsync<object, PSObject>(null, context.Output, _batchInvocationSettings, null, _batchAsyncResult.AsyncState, context.Output); EndInvoke(cmdResult); } catch (ActionPreferenceStopException e) { // We need to honor the current error action preference here _stopBatchExecution = true; _batchAsyncResult.SetAsCompleted(e); return; } catch (Exception e) { SetHadErrors(true); // Stop if necessarily if ((_batchInvocationSettings != null) && _batchInvocationSettings.ErrorActionPreference == ActionPreference.Stop) { _stopBatchExecution = true; AppendExceptionToErrorStream(e); _batchAsyncResult.SetAsCompleted(null); return; } // If we get here, then ErrorActionPreference is either Continue, // SilentlyContinue, or Inquire (Continue), so we just continue.... if (_batchInvocationSettings == null) { ActionPreference preference = (ActionPreference)Runspace.SessionStateProxy.GetVariable("ErrorActionPreference"); switch (preference) { case ActionPreference.SilentlyContinue: case ActionPreference.Continue: AppendExceptionToErrorStream(e); break; case ActionPreference.Stop: _batchAsyncResult.SetAsCompleted(e); return; case ActionPreference.Inquire: case ActionPreference.Ignore: break; } } else if (_batchInvocationSettings.ErrorActionPreference != ActionPreference.Ignore) { AppendExceptionToErrorStream(e); } // Let it continue } if (_psCommand == ExtraCommands[ExtraCommands.Count - 1]) { _batchAsyncResult.SetAsCompleted(null); } } finally { _psCommand = backupCommand; context.Signal(); } } /// <summary> /// Batch invocation callback. /// </summary> /// <param name="result"></param> private void BatchInvocationCallback(IAsyncResult result) { Debug.Assert(ExtraCommands.Count != 0, "This callback is for batch invocation only"); PSDataCollection<PSObject> objs = null; try { objs = EndInvoke(result) ?? _batchAsyncResult.Output; DoRemainingBatchCommands(objs); } catch (PipelineStoppedException e) { // PowerShell throws the pipeline stopped exception. _batchAsyncResult.SetAsCompleted(e); return; } catch (ActionPreferenceStopException e) { // We need to honor the current error action preference here _batchAsyncResult.SetAsCompleted(e); return; } catch (Exception e) { RunningExtraCommands = false; SetHadErrors(true); ActionPreference preference; if (_batchInvocationSettings != null) { preference = _batchInvocationSettings.ErrorActionPreference ?? ActionPreference.Continue; } else { preference = (Runspace != null) ? (ActionPreference)Runspace.SessionStateProxy.GetVariable("ErrorActionPreference") : ActionPreference.Continue; } switch (preference) { case ActionPreference.SilentlyContinue: case ActionPreference.Continue: AppendExceptionToErrorStream(e); break; case ActionPreference.Stop: _batchAsyncResult.SetAsCompleted(e); return; case ActionPreference.Inquire: case ActionPreference.Ignore: break; } objs ??= _batchAsyncResult.Output; DoRemainingBatchCommands(objs); } finally { if (_isBatching) { EndAsyncBatchExecution(); } } } /// <summary> /// Executes remaining batch commands. /// </summary> private void DoRemainingBatchCommands(PSDataCollection<PSObject> objs) { if (ExtraCommands.Count > 1) { for (int i = 1; i < ExtraCommands.Count; i++) { if (_stopBatchExecution) { break; } BatchInvocationContext context = new BatchInvocationContext(ExtraCommands[i], objs); // Queue a batch work item here. // Calling CoreInvokeAsync / CoreInvoke here directly doesn't work and causes the thread to not respond. ThreadPool.QueueUserWorkItem(new WaitCallback(BatchInvocationWorkItem), context); context.Wait(); } } } /// <summary> /// </summary> private void DetermineIsBatching() { foreach (Command command in _psCommand.Commands) { if (command.IsEndOfStatement) { _isBatching = true; return; } } _isBatching = false; } /// <summary> /// Prepare for async batch execution. /// </summary> private void SetupAsyncBatchExecution() { Debug.Assert(_isBatching); _backupPSCommand = _psCommand.Clone(); ExtraCommands.Clear(); PSCommand currentPipe = new PSCommand(); currentPipe.Owner = this; foreach (Command command in _psCommand.Commands) { if (command.IsEndOfStatement) { currentPipe.Commands.Add(command); ExtraCommands.Add(currentPipe); currentPipe = new PSCommand(); currentPipe.Owner = this; } else { currentPipe.Commands.Add(command); } } if (currentPipe.Commands.Count != 0) { ExtraCommands.Add(currentPipe); } _psCommand = ExtraCommands[0]; } /// <summary> /// Ends an async batch execution. /// </summary> private void EndAsyncBatchExecution() { Debug.Assert(_isBatching); _psCommand = _backupPSCommand; } /// <summary> /// Appends an exception to the error stream. /// </summary> /// <param name="e"></param> private void AppendExceptionToErrorStream(Exception e) { IContainsErrorRecord er = e as IContainsErrorRecord; if (er != null && er.ErrorRecord != null) { this.Streams.Error.Add(er.ErrorRecord); } else { this.Streams.Error.Add(new ErrorRecord(e, "InvalidOperation", ErrorCategory.InvalidOperation, null)); } } /// <summary> /// Waits for the pending asynchronous BeginInvoke to complete. /// </summary> /// <param name="asyncResult"> /// Instance of IAsyncResult returned by BeginInvoke. /// </param> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke, or null if the caller provided their own buffer. /// </returns> /// <exception cref="ArgumentNullException"> /// asyncResult is a null reference. /// </exception> /// <exception cref="ArgumentException"> /// asyncResult object was not created by calling BeginInvoke /// on this PowerShell instance. /// </exception> /// <exception cref="System.Management.Automation.PipelineStoppedException"> /// The running PowerShell pipeline was stopped. /// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called. /// To collect partial output in this scenario, /// supply a <see cref="System.Management.Automation.PSDataCollection{T}" /> to <see cref="PowerShell.BeginInvoke"/> for the <paramref name="output"/> parameter /// and either add a handler for the <see cref="System.Management.Automation.PSDataCollection{T}.DataAdding"/> event /// or catch the exception and enumerate the object supplied. /// </exception> public PSDataCollection<PSObject> EndInvoke(IAsyncResult asyncResult) { try { _commandInvokedSynchronously = true; if (asyncResult == null) { throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } PowerShellAsyncResult psAsyncResult = asyncResult as PowerShellAsyncResult; if ((psAsyncResult == null) || (psAsyncResult.OwnerId != InstanceId) || (!psAsyncResult.IsAssociatedWithAsyncInvoke)) { throw PSTraceSource.NewArgumentException(nameof(asyncResult), PowerShellStrings.AsyncResultNotOwned, "IAsyncResult", "BeginInvoke"); } EndInvokeAsyncResult = psAsyncResult; psAsyncResult.EndInvoke(); EndInvokeAsyncResult = null; // PowerShell no longer owns the output buffer when it is passed back to the caller. ResetOutputBufferAsNeeded(); return psAsyncResult.Output; } catch (InvalidRunspacePoolStateException exception) { SetHadErrors(true); if (_runspace != null) // the pool exception was actually thrown by a runspace { throw exception.ToInvalidRunspaceStateException(); } throw; } } #endregion #region Stop Overloads /// <summary> /// Stop the currently running command synchronously. /// </summary> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <remarks> /// When used with <see cref="PowerShell.Invoke()"/>, that call will return a partial result. /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. /// </remarks> public void Stop() { try { IAsyncResult asyncResult = CoreStop(true, null, null); // This is a sync call..Wait for the stop operation to complete. asyncResult.AsyncWaitHandle.WaitOne(); // PowerShell no longer owns the output buffer when the pipeline is stopped by caller. ResetOutputBufferAsNeeded(); } catch (ObjectDisposedException) { // If it's already disposed, then the client doesn't need to know. } } /// <summary> /// Stop the currently running command asynchronously. If the command is not started, /// the state of PowerShell instance is changed to Stopped and corresponding events /// will be raised. /// /// The returned IAsyncResult object can be used to wait for the stop operation /// to complete. /// </summary> /// <param name="callback"> /// A AsyncCallback to call once the BeginStop completes. /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. /// The callback could be invoked from a thread without a default Runspace and a delegate cast from /// a script block would fail in that case. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> public IAsyncResult BeginStop(AsyncCallback callback, object state) { return CoreStop(false, callback, state); } /// <summary> /// Waits for the pending asynchronous BeginStop to complete. /// </summary> /// <param name="asyncResult"> /// Instance of IAsyncResult returned by BeginStop. /// </param> /// <exception cref="ArgumentNullException"> /// asyncResult is a null reference. /// </exception> /// <exception cref="ArgumentException"> /// asyncResult object was not created by calling BeginStop /// on this PowerShell instance. /// </exception> /// <remarks> /// When used with <see cref="PowerShell.Invoke()"/>, that call will return a partial result. /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. /// </remarks> public void EndStop(IAsyncResult asyncResult) { if (asyncResult == null) { throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } PowerShellAsyncResult psAsyncResult = asyncResult as PowerShellAsyncResult; if ((psAsyncResult == null) || (psAsyncResult.OwnerId != InstanceId) || (psAsyncResult.IsAssociatedWithAsyncInvoke)) { throw PSTraceSource.NewArgumentException(nameof(asyncResult), PowerShellStrings.AsyncResultNotOwned, "IAsyncResult", "BeginStop"); } psAsyncResult.EndInvoke(); // PowerShell no longer owns the output buffer when the pipeline is stopped by caller. ResetOutputBufferAsNeeded(); } /// <summary> /// Stop a PowerShell command asynchronously. /// Use await to wait for the command to stop. /// </summary> /// <remarks> /// <para> /// If the command is not started, the state of the PowerShell instance /// is changed to Stopped and corresponding events will be raised. /// </para> /// </remarks> /// <param name="callback"> /// An AsyncCallback to call once the command is invoked. /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. /// The callback could be invoked from a thread without a default Runspace and a delegate cast from /// a script block would fail in that case. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <returns> /// The output buffer created to hold the results of the asynchronous invoke, /// or null if the caller provided their own buffer. /// </returns> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> /// <remarks> /// When used with <see cref="PowerShell.Invoke()"/>, that call will return a partial result. /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. /// </remarks> public Task StopAsync(AsyncCallback callback, object state) => Task.Factory.FromAsync(BeginStop(callback, state), _endStopMethod); #endregion #region Event Handlers /// <summary> /// Handler for state changed events for the currently running pipeline. /// </summary> /// <param name="source"> /// Source of the event. /// </param> /// <param name="stateEventArgs"> /// Pipeline State. /// </param> private void PipelineStateChanged(object source, PipelineStateEventArgs stateEventArgs) { // we need to process the pipeline event. PSInvocationStateInfo targetStateInfo = new PSInvocationStateInfo(stateEventArgs.PipelineStateInfo); SetStateChanged(targetStateInfo); } #endregion #region IDisposable Overrides /// <summary> /// Release all resources. /// </summary> public void Dispose() { lock (_syncObject) { // if already disposed return if (_isDisposed) { return; } } // Stop the currently running command outside of the lock if (InvocationStateInfo.State == PSInvocationState.Running || InvocationStateInfo.State == PSInvocationState.Stopping) { Stop(); } lock (_syncObject) { _isDisposed = true; } if (OutputBuffer != null && OutputBufferOwner) { OutputBuffer.Dispose(); } if (_errorBuffer != null && ErrorBufferOwner) { _errorBuffer.Dispose(); } if (IsRunspaceOwner) { _runspace.Dispose(); } RemotePowerShell?.Dispose(); _invokeAsyncResult = null; _stopAsyncResult = null; } #endregion #region Internal / Private Methods / Properties /// <summary> /// Indicates if this PowerShell object is the owner of the /// runspace or RunspacePool assigned to this object. /// </summary> public bool IsRunspaceOwner { get; internal set; } = false; internal bool ErrorBufferOwner { get; set; } = true; internal bool OutputBufferOwner { get; set; } = true; /// <summary> /// OutputBuffer. /// </summary> internal PSDataCollection<PSObject> OutputBuffer { get; private set; } /// <summary> /// Reset the output buffer to null if it's owned by the current powershell instance. /// </summary> private void ResetOutputBufferAsNeeded() { if (OutputBufferOwner) { OutputBufferOwner = false; OutputBuffer = null; } } /// <summary> /// Get a steppable pipeline object. /// </summary> /// <returns>A steppable pipeline object.</returns> /// <exception cref="InvalidOperationException">An attempt was made to use the scriptblock outside of the engine.</exception> public SteppablePipeline GetSteppablePipeline() { ExecutionContext context = GetContextFromTLS(); SteppablePipeline spl = GetSteppablePipeline(context, CommandOrigin.Internal); return spl; } /// <summary> /// Returns the current execution context from TLS, or raises an exception if it is null. /// </summary> /// <exception cref="InvalidOperationException">An attempt was made to use the scriptblock outside of the engine.</exception> internal ExecutionContext GetContextFromTLS() { ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); // If ExecutionContext from TLS is null then we are not in powershell engine thread. if (context == null) { string scriptText = this.Commands.Commands.Count > 0 ? this.Commands.Commands[0].CommandText : null; PSInvalidOperationException e = null; if (scriptText != null) { scriptText = ErrorCategoryInfo.Ellipsize(System.Globalization.CultureInfo.CurrentUICulture, scriptText); e = PSTraceSource.NewInvalidOperationException( PowerShellStrings.CommandInvokedFromWrongThreadWithCommand, scriptText); } else { e = PSTraceSource.NewInvalidOperationException( PowerShellStrings.CommandInvokedFromWrongThreadWithoutCommand); } e.SetErrorId("CommandInvokedFromWrongThread"); throw e; } return context; } /// <summary> /// Gets the steppable pipeline from the powershell object. /// </summary> /// <param name="context">Engine execution context.</param> /// <param name="commandOrigin">Command origin.</param> /// <returns>Steppable pipeline object.</returns> private SteppablePipeline GetSteppablePipeline(ExecutionContext context, CommandOrigin commandOrigin) { // Check for an empty pipeline if (Commands.Commands.Count == 0) { return null; } PipelineProcessor pipelineProcessor = new PipelineProcessor(); bool failed = false; try { foreach (Command cmd in Commands.Commands) { CommandProcessorBase commandProcessorBase = cmd.CreateCommandProcessor ( Runspace.DefaultRunspace.ExecutionContext, false, IsNested ? CommandOrigin.Internal : CommandOrigin.Runspace ); commandProcessorBase.RedirectShellErrorOutputPipe = RedirectShellErrorOutputPipe; pipelineProcessor.Add(commandProcessorBase); } } catch (RuntimeException) { failed = true; throw; } catch (Exception e) { failed = true; throw new RuntimeException(PipelineStrings.CannotCreatePipeline, e); } finally { if (failed) { pipelineProcessor.Dispose(); } } return new SteppablePipeline(context, pipelineProcessor); } internal bool IsGetCommandMetadataSpecialPipeline { get; set; } /// <summary> /// Checks if the command is running. /// </summary> /// <returns></returns> private bool IsCommandRunning() { if (InvocationStateInfo.State == PSInvocationState.Running) { return true; } return false; } /// <summary> /// Checks if the current state is Disconnected. /// </summary> /// <returns></returns> private bool IsDisconnected() { return (InvocationStateInfo.State == PSInvocationState.Disconnected); } /// <summary> /// Checks if the command is already running. /// If the command is already running, throws an /// exception. /// </summary> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> private void AssertExecutionNotStarted() { AssertNotDisposed(); if (IsCommandRunning()) { string message = StringUtil.Format(PowerShellStrings.ExecutionAlreadyStarted); throw new InvalidOperationException(message); } if (IsDisconnected()) { string message = StringUtil.Format(PowerShellStrings.ExecutionDisconnected); throw new InvalidOperationException(message); } if (InvocationStateInfo.State == PSInvocationState.Stopping) { string message = StringUtil.Format(PowerShellStrings.ExecutionStopping); throw new InvalidOperationException(message); } } /// <summary> /// Checks if the current powershell instance can accept changes like /// changing one of the properties like Output, Command etc. /// If changes are not allowed, throws an exception. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> internal void AssertChangesAreAccepted() { lock (_syncObject) { AssertNotDisposed(); if (IsCommandRunning() || IsDisconnected()) { throw new InvalidPowerShellStateException(InvocationStateInfo.State); } } } /// <summary> /// Checks if the current powershell instance is disposed. /// If disposed, throws ObjectDisposedException. /// </summary> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> private void AssertNotDisposed() { if (_isDisposed) { throw PSTraceSource.NewObjectDisposedException("PowerShell"); } } /// <summary> /// Clear the internal elements. /// </summary> private void InternalClearSuppressExceptions() { lock (_syncObject) { _worker?.InternalClearSuppressExceptions(); } } /// <summary> /// Raise the execution state change event handlers. /// </summary> /// <param name="stateInfo"> /// State Information /// </param> private void RaiseStateChangeEvent(PSInvocationStateInfo stateInfo) { // First update the runspace availability. // The Pipeline class takes care of updating local runspaces. // Don't update for RemoteRunspace and nested PowerShell since this is used // only internally by the remote debugger. RemoteRunspace remoteRunspace = _runspace as RemoteRunspace; if (remoteRunspace != null && !this.IsNested) { _runspace.UpdateRunspaceAvailability(InvocationStateInfo.State, true, InstanceId); } if (stateInfo.State == PSInvocationState.Running) { AddToRemoteRunspaceRunningList(); } else if (stateInfo.State == PSInvocationState.Completed || stateInfo.State == PSInvocationState.Stopped || stateInfo.State == PSInvocationState.Failed) { RemoveFromRemoteRunspaceRunningList(); } InvocationStateChanged.SafeInvoke(this, new PSInvocationStateChangedEventArgs(stateInfo)); } /// <summary> /// Sets the state of this powershell instance. /// </summary> /// <param name="stateInfo">The state info to set.</param> internal void SetStateChanged(PSInvocationStateInfo stateInfo) { PSInvocationStateInfo copyStateInfo = stateInfo; PSInvocationState previousState; // copy pipeline HasdErrors property to PowerShell instance... if (_worker != null && _worker.CurrentlyRunningPipeline != null) { SetHadErrors(_worker.CurrentlyRunningPipeline.HadErrors); } // win281312: Usig temporary variables to avoid thread // synchronization issues between Dispose and transition // to Terminal States (Completed/Failed/Stopped) PowerShellAsyncResult tempInvokeAsyncResult; PowerShellAsyncResult tempStopAsyncResult; lock (_syncObject) { previousState = InvocationStateInfo.State; // Check the current state and see if we need to process this pipeline event. switch (InvocationStateInfo.State) { case PSInvocationState.Completed: case PSInvocationState.Failed: case PSInvocationState.Stopped: // if the current state is already completed..then no need to process state // change requests. This will happen if another thread calls BeginStop return; case PSInvocationState.Running: if (stateInfo.State == PSInvocationState.Running) { return; } break; case PSInvocationState.Stopping: // We are in stopping state and we should not honor Running state // here. if (stateInfo.State == PSInvocationState.Running || stateInfo.State == PSInvocationState.Stopping) { return; } else if (stateInfo.State == PSInvocationState.Completed || stateInfo.State == PSInvocationState.Failed) { copyStateInfo = new PSInvocationStateInfo(PSInvocationState.Stopped, stateInfo.Reason); } break; default: break; } tempInvokeAsyncResult = _invokeAsyncResult; tempStopAsyncResult = _stopAsyncResult; InvocationStateInfo = copyStateInfo; } bool isExceptionOccured = false; switch (InvocationStateInfo.State) { case PSInvocationState.Running: CloseInputBufferOnReconnection(previousState); RaiseStateChangeEvent(InvocationStateInfo.Clone()); break; case PSInvocationState.Stopping: RaiseStateChangeEvent(InvocationStateInfo.Clone()); break; case PSInvocationState.Completed: case PSInvocationState.Failed: case PSInvocationState.Stopped: // Clear Internal data InternalClearSuppressExceptions(); // Ensure remote receive queue is not blocked. if (RemotePowerShell != null) { ResumeIncomingData(); } try { if (RunningExtraCommands) { tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); RaiseStateChangeEvent(InvocationStateInfo.Clone()); } else { RaiseStateChangeEvent(InvocationStateInfo.Clone()); tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); } tempStopAsyncResult?.SetAsCompleted(null); } catch (Exception) { // need to release asyncresults if there is an // exception from the eventhandlers. isExceptionOccured = true; SetHadErrors(true); throw; } finally { // takes care exception occurred with invokeAsyncResult if (isExceptionOccured && (tempStopAsyncResult != null)) { tempStopAsyncResult.Release(); } } break; case PSInvocationState.Disconnected: try { // Ensure remote receive queue is not blocked. if (RemotePowerShell != null) { ResumeIncomingData(); } // If this command was disconnected and was also invoked synchronously then // we throw an exception on the calling thread. if (_commandInvokedSynchronously && (tempInvokeAsyncResult != null)) { tempInvokeAsyncResult.SetAsCompleted(new RuntimeException(PowerShellStrings.DiscOnSyncCommand)); } // This object can be disconnected even if "BeginStop" was called if it is a remote object // and robust connections is retrying a failed network connection. // In this case release the stop wait handle to prevent not responding. tempStopAsyncResult?.SetAsCompleted(null); // Only raise the Disconnected state changed event if the PowerShell state // actually transitions to Disconnected from some other state. This condition // can happen when the corresponding runspace disconnects/connects multiple // times with the command remaining in Disconnected state. if (previousState != PSInvocationState.Disconnected) { RaiseStateChangeEvent(InvocationStateInfo.Clone()); } } catch (Exception) { // need to release asyncresults if there is an // exception from the eventhandlers. isExceptionOccured = true; SetHadErrors(true); throw; } finally { // takes care exception occurred with invokeAsyncResult if (isExceptionOccured && (tempStopAsyncResult != null)) { tempStopAsyncResult.Release(); } } // Make sure the connect command information is null when going to Disconnected state. // This parameter is used to determine reconnect/reconstruct scenarios. Setting to null // means we have a reconnect scenario. _connectCmdInfo = null; break; default: return; } } /// <summary> /// Helper function to close the input buffer after command is reconnected. /// </summary> /// <param name="previousState">Previous state.</param> private void CloseInputBufferOnReconnection(PSInvocationState previousState) { // If the previous state was disconnected and we are now running (reconnected), // and we reconnected synchronously with pending input, then we need to close // the input buffer to allow the remote command to complete. Otherwise the // synchronous Connect() method will wait indefinitely for the command to complete. if (previousState == PSInvocationState.Disconnected && _commandInvokedSynchronously && RemotePowerShell.InputStream != null && RemotePowerShell.InputStream.IsOpen && RemotePowerShell.InputStream.Count > 0) { RemotePowerShell.InputStream.Close(); } } /// <summary> /// Clear the internal reference to remote powershell. /// </summary> internal void ClearRemotePowerShell() { lock (_syncObject) { RemotePowerShell?.Clear(); } } /// <summary> /// Sets if the pipeline is nested, typically used by the remoting infrastructure. /// </summary> /// <param name="isNested"></param> internal void SetIsNested(bool isNested) { AssertChangesAreAccepted(); IsNested = isNested; } /// <summary> /// Performs the actual synchronous command invocation. The caller /// should check if it safe to call this method. /// </summary> /// <typeparam name="TOutput"> /// Type of objects to return. /// </typeparam> /// <param name="input"> /// Input to the command. /// </param> /// <param name="output"> /// output from the command /// </param> /// <param name="settings"> /// Invocation settings. /// </param> /// <exception cref="InvalidOperationException"> /// No commands are specified. /// </exception> /// <exception cref="ScriptCallDepthException"> /// The script recursed too deeply into script functions. /// There is a fixed limit on the depth of recursion. /// </exception> /// <exception cref="System.Security.SecurityException"> /// A CLR security violation occurred. Typically, this happens /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// </exception> /// <exception cref="RuntimeException"> /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions /// are listed below. /// </exception> /// <exception cref="ParameterBindingException"> /// One of more parameters or parameter values specified for /// a cmdlet are not valid, or mandatory parameters for a cmdlet /// were not specified. /// </exception> /// <exception cref="CmdletInvocationException"> /// A cmdlet generated a terminating error. /// </exception> /// <exception cref="CmdletProviderInvocationException"> /// A provider generated a terminating error. /// </exception> /// <exception cref="ActionPreferenceStopException"> /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. /// </exception> /// <exception cref="PipelineStoppedException"> /// The pipeline was terminated asynchronously. /// </exception> /// <exception cref="MetadataException"> /// If there is an error generating the metadata for dynamic parameters. /// </exception> private void CoreInvoke<TOutput>(IEnumerable input, PSDataCollection<TOutput> output, PSInvocationSettings settings) { PSDataCollection<object> inputBuffer = null; if (input != null) { inputBuffer = new PSDataCollection<object>(); foreach (object o in input) { inputBuffer.Add(o); } inputBuffer.Complete(); } CoreInvoke(inputBuffer, output, settings); } /// <summary> /// Core invocation helper method. /// </summary> /// <typeparam name="TInput">input type</typeparam> /// <typeparam name="TOutput">output type</typeparam> /// <param name="input">Input objects.</param> /// <param name="output">Output object.</param> /// <param name="settings">Invocation settings.</param> private void CoreInvokeHelper<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings) { RunspacePool pool = _rsConnection as RunspacePool; // Prepare the environment...non-remoting case. Prepare<TInput, TOutput>(input, output, settings, true); try { // Invoke in the same thread as the calling thread. Runspace rsToUse = null; if (!IsNested) { if (pool != null) { #if !UNIX VerifyThreadSettings(settings, pool.ApartmentState, pool.ThreadOptions, false); #endif // getting the runspace asynchronously so that Stop can be supported from a different // thread. _worker.GetRunspaceAsyncResult = pool.BeginGetRunspace(null, null); _worker.GetRunspaceAsyncResult.AsyncWaitHandle.WaitOne(); rsToUse = pool.EndGetRunspace(_worker.GetRunspaceAsyncResult); } else { rsToUse = _rsConnection as Runspace; if (rsToUse != null) { #if !UNIX VerifyThreadSettings(settings, rsToUse.ApartmentState, rsToUse.ThreadOptions, false); #endif if (rsToUse.RunspaceStateInfo.State != RunspaceState.Opened) { string message = StringUtil.Format(PowerShellStrings.InvalidRunspaceState, RunspaceState.Opened, rsToUse.RunspaceStateInfo.State); InvalidRunspaceStateException e = new InvalidRunspaceStateException(message, rsToUse.RunspaceStateInfo.State, RunspaceState.Opened ); throw e; } } } // perform the work in the current thread _worker.CreateRunspaceIfNeededAndDoWork(rsToUse, true); } else { rsToUse = _rsConnection as Runspace; Dbg.Assert(rsToUse != null, "Nested PowerShell can only work on a Runspace"); // Perform work on the current thread. Nested Pipeline // should be invoked from the same thread that the parent // pipeline is executing in. _worker.ConstructPipelineAndDoWork(rsToUse, true); } } catch (Exception exception) { SetStateChanged(new PSInvocationStateInfo(PSInvocationState.Failed, exception)); // re-throw the exception InvalidRunspacePoolStateException poolException = exception as InvalidRunspacePoolStateException; if (poolException != null && _runspace != null) // the pool exception was actually thrown by a runspace { throw poolException.ToInvalidRunspaceStateException(); } throw; } } /// <summary> /// Core invocation helper method for remoting. /// </summary> /// <typeparam name="TInput">input type</typeparam> /// <typeparam name="TOutput">output type</typeparam> /// <param name="input">Input objects.</param> /// <param name="output">Output object.</param> /// <param name="settings">Invocation settings.</param> private void CoreInvokeRemoteHelper<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings) { RunspacePool pool = _rsConnection as RunspacePool; // For remote calls..use the infrastructure built in CoreInvokeAsync.. IAsyncResult asyncResult = CoreInvokeAsync<TInput, TOutput>(input, output, settings, null, null, null); _commandInvokedSynchronously = true; PowerShellAsyncResult psAsyncResult = asyncResult as PowerShellAsyncResult; // Wait for command to complete. If an exception was thrown during command // execution (such as disconnect occurring during this synchronous execution) // then the exception will be thrown here. EndInvokeAsyncResult = psAsyncResult; psAsyncResult.EndInvoke(); EndInvokeAsyncResult = null; if ((InvocationStateInfo.State == PSInvocationState.Failed) && (InvocationStateInfo.Reason != null)) { throw InvocationStateInfo.Reason; } return; } /// <summary> /// Core invocation method. /// </summary> /// <typeparam name="TInput">input type</typeparam> /// <typeparam name="TOutput">output type</typeparam> /// <param name="input">Input objects.</param> /// <param name="output">Output object.</param> /// <param name="settings">Invocation settings.</param> private void CoreInvoke<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings) { bool isRemote = false; DetermineIsBatching(); if (_isBatching) { SetupAsyncBatchExecution(); } SetHadErrors(false); RunspacePool pool = _rsConnection as RunspacePool; if ((pool != null) && (pool.IsRemote)) { if (ServerSupportsBatchInvocation()) { try { CoreInvokeRemoteHelper(input, output, settings); } finally { if (_isBatching) { EndAsyncBatchExecution(); } } return; } isRemote = true; } if (_isBatching) { try { foreach (PSCommand command in ExtraCommands) { // Last element if (_psCommand != ExtraCommands[ExtraCommands.Count - 1]) { RunningExtraCommands = true; } else { RunningExtraCommands = false; } try { _psCommand = command; if (isRemote) { CoreInvokeRemoteHelper(input, output, settings); } else { CoreInvokeHelper(input, output, settings); } } catch (ActionPreferenceStopException) { // We need to honor the current error action preference here throw; } catch (Exception e) { SetHadErrors(true); // Stop if necessarily if ((settings != null) && settings.ErrorActionPreference == ActionPreference.Stop) { throw; } // Ignore the exception if necessary. if ((settings != null) && settings.ErrorActionPreference == ActionPreference.Ignore) { continue; } // If we get here, then ErrorActionPreference is either Continue, // SilentlyContinue, or Inquire (Continue), so we just continue.... IContainsErrorRecord er = e as IContainsErrorRecord; if (er != null && er.ErrorRecord != null) { this.Streams.Error.Add(er.ErrorRecord); } else { this.Streams.Error.Add(new ErrorRecord(e, "InvalidOperation", ErrorCategory.InvalidOperation, null)); } continue; } } } finally { RunningExtraCommands = false; EndAsyncBatchExecution(); } } else { RunningExtraCommands = false; if (isRemote) { CoreInvokeRemoteHelper(input, output, settings); } else { CoreInvokeHelper(input, output, settings); } } } /// <summary> /// Performs the actual asynchronous command invocation. /// </summary> /// <typeparam name="TInput">Type of the input buffer</typeparam> /// <typeparam name="TOutput">Type of the output buffer</typeparam> /// <param name="input"> /// input can be null /// </param> /// <param name="output"></param> /// <param name="settings"></param> /// <param name="callback"> /// A AsyncCallback to call once the BeginInvoke completes. /// </param> /// <param name="state"> /// A user supplied state to call the <paramref name="callback"/> /// with. /// </param> /// <param name="asyncResultOutput"> /// The output buffer to attach to the IAsyncResult returned by this method /// </param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// (or) /// No command is added. /// (or) /// BeginInvoke is called on nested powershell. Nested /// Powershell cannot be executed Asynchronously. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] private IAsyncResult CoreInvokeAsync<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, AsyncCallback callback, object state, PSDataCollection<PSObject> asyncResultOutput) { RunspacePool pool = _rsConnection as RunspacePool; // We dont need to create worker if pool is remote Prepare<TInput, TOutput>(input, output, settings, (pool == null || !pool.IsRemote)); _invokeAsyncResult = new PowerShellAsyncResult(InstanceId, callback, state, asyncResultOutput, true); try { // IsNested is true for the icm | % { icm } scenario if (!IsNested || (pool != null && pool.IsRemote)) { if (pool != null) { #if !UNIX VerifyThreadSettings(settings, pool.ApartmentState, pool.ThreadOptions, pool.IsRemote); #endif pool.AssertPoolIsOpen(); // for executing in a remote runspace pool case if (pool.IsRemote) { _worker = null; lock (_syncObject) { // for remoting case, when the state is set to // Running, the message should have been sent // to the server. In order to ensure the same // all of the following are placed inside the // lock // 1. set the state to Running // 2. create remotePowerShell // 3. Send message to server // set the execution state to running.. so changes // to the current instance of powershell // are blocked. AssertExecutionNotStarted(); InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Running, null); ObjectStreamBase inputStream = null; if (input != null) { inputStream = new PSDataCollectionStream<TInput>(InstanceId, input); } if (!RemotePowerShell.Initialized) { if (inputStream == null) { inputStream = new ObjectStream(); inputStream.Close(); } RemotePowerShell.Initialize( inputStream, new PSDataCollectionStream<TOutput>(InstanceId, output), new PSDataCollectionStream<ErrorRecord>(InstanceId, _errorBuffer), InformationalBuffers, settings); } else { if (inputStream != null) { RemotePowerShell.InputStream = inputStream; } if (output != null) { RemotePowerShell.OutputStream = new PSDataCollectionStream<TOutput>(InstanceId, output); } } pool.RemoteRunspacePoolInternal.CreatePowerShellOnServerAndInvoke(RemotePowerShell); } RaiseStateChangeEvent(InvocationStateInfo.Clone()); } else { _worker.GetRunspaceAsyncResult = pool.BeginGetRunspace( new AsyncCallback(_worker.RunspaceAvailableCallback), null); } } else { LocalRunspace rs = _rsConnection as LocalRunspace; if (rs != null) { #if !UNIX VerifyThreadSettings(settings, rs.ApartmentState, rs.ThreadOptions, false); #endif if (rs.RunspaceStateInfo.State != RunspaceState.Opened) { string message = StringUtil.Format(PowerShellStrings.InvalidRunspaceState, RunspaceState.Opened, rs.RunspaceStateInfo.State); InvalidRunspaceStateException e = new InvalidRunspaceStateException(message, rs.RunspaceStateInfo.State, RunspaceState.Opened ); throw e; } _worker.CreateRunspaceIfNeededAndDoWork(rs, false); } else { // create a new runspace and perform invoke.. ThreadPool.QueueUserWorkItem( new WaitCallback(_worker.CreateRunspaceIfNeededAndDoWork), _rsConnection); } } } else { // Nested PowerShell throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.NestedPowerShellInvokeAsync); } } catch (Exception exception) { // allow GC collection _invokeAsyncResult = null; SetStateChanged(new PSInvocationStateInfo(PSInvocationState.Failed, exception)); // re-throw the exception InvalidRunspacePoolStateException poolException = exception as InvalidRunspacePoolStateException; if (poolException != null && _runspace != null) // the pool exception was actually thrown by a runspace { throw poolException.ToInvalidRunspaceStateException(); } throw; } return _invokeAsyncResult; } // Apartment thread state does not apply to non-Windows platforms. #if !UNIX /// <summary> /// Verifies the settings for ThreadOptions and ApartmentState. /// </summary> private static void VerifyThreadSettings(PSInvocationSettings settings, ApartmentState runspaceApartmentState, PSThreadOptions runspaceThreadOptions, bool isRemote) { ApartmentState apartmentState; if (settings != null && settings.ApartmentState != ApartmentState.Unknown) { apartmentState = settings.ApartmentState; } else { apartmentState = runspaceApartmentState; } if (runspaceThreadOptions == PSThreadOptions.ReuseThread) { if (apartmentState != runspaceApartmentState) { throw new InvalidOperationException(PowerShellStrings.ApartmentStateMismatch); } } else if (runspaceThreadOptions == PSThreadOptions.UseCurrentThread) { if (!isRemote) // on remote calls this check needs to be done by the server { if (apartmentState != ApartmentState.Unknown && apartmentState != Thread.CurrentThread.GetApartmentState()) { throw new InvalidOperationException(PowerShellStrings.ApartmentStateMismatchCurrentThread); } } } } #endif /// <summary> /// </summary> /// <typeparam name="TInput">Type for the input collection</typeparam> /// <typeparam name="TOutput">Type for the output collection</typeparam> /// <param name="input"></param> /// <param name="output"></param> /// <param name="settings"></param> /// <param name="shouldCreateWorker"></param> /// <exception cref="InvalidOperationException"> /// Cannot perform the operation because the command is already started. /// Stop the command and try the operation again. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> private void Prepare<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, bool shouldCreateWorker) { Dbg.Assert(output != null, "Output cannot be null"); lock (_syncObject) { if ((_psCommand == null) || (_psCommand.Commands == null) || (_psCommand.Commands.Count == 0)) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.NoCommandToInvoke); } // If execution has already started this will throw AssertExecutionNotStarted(); if (shouldCreateWorker) { // set the execution state to running.. so changes // to the current instance of powershell // are blocked. InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Running, null); // update settings for impersonation policy if ((settings != null) && (settings.FlowImpersonationPolicy)) { // get the identity of the thread. // false behavior: If the thread is impersonating the WindowsIdentity for the // thread is returned. If the thread is not impersonating, the WindowsIdentity of // the process is returned. settings.WindowsIdentityToImpersonate = System.Security.Principal.WindowsIdentity.GetCurrent(false); } // Create the streams and handoff these to the pipeline // this way pipeline will not waste resources creating // the same. ObjectStreamBase inputStream; if (input != null) { inputStream = new PSDataCollectionStream<TInput>(InstanceId, input); } else { inputStream = new ObjectStream(); inputStream.Close(); } ObjectStreamBase outputStream = new PSDataCollectionStream<TOutput>(InstanceId, output); _worker = new Worker(inputStream, outputStream, settings, this); } } // Only one thread will be running after this point // so no need to lock. if (shouldCreateWorker) { // Raise the state change events outside of the lock // send a cloned copy..this way the handler will // not see changes happening to the instance's execution state. RaiseStateChangeEvent(InvocationStateInfo.Clone()); } } /// <summary> /// Called by both Sync Stop and Async Stop. /// If isSyncCall is false, then an IAsyncResult object is returned which /// can be passed back to the user. /// </summary> /// <param name="isSyncCall"> /// true if pipeline to be stopped synchronously, /// false otherewise. /// </param> /// <param name="callback"> /// Valid for asynchronous stop /// </param> /// <param name="state"> /// Valid for asynchronous stop /// </param> private IAsyncResult CoreStop(bool isSyncCall, AsyncCallback callback, object state) { bool isRunning = false; bool isDisconnected = false; Queue<PSInvocationStateInfo> events = new Queue<PSInvocationStateInfo>(); // Acquire lock as we are going to change state here.. lock (_syncObject) { // BUGBUG: remote powershell appears to handle state change's differently // Need to speak with remoting dev and resolve this. switch (InvocationStateInfo.State) { case PSInvocationState.NotStarted: // Stopped is called before operation started..we need to change // state to stopping and then to stopped... so that future stops // dont affect the state. InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Stopping, null); events.Enqueue(new PSInvocationStateInfo(PSInvocationState.Stopped, null)); break; case PSInvocationState.Completed: case PSInvocationState.Failed: case PSInvocationState.Stopped: _stopAsyncResult = new PowerShellAsyncResult(InstanceId, callback, state, null, false); _stopAsyncResult.SetAsCompleted(null); return _stopAsyncResult; case PSInvocationState.Stopping: // Create new stop sync object if none exists. Otherwise return existing. if (_stopAsyncResult == null) { _stopAsyncResult = new PowerShellAsyncResult(InstanceId, callback, state, null, false); _stopAsyncResult.SetAsCompleted(null); } return _stopAsyncResult; case PSInvocationState.Running: InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Stopping, null); isRunning = true; break; case PSInvocationState.Disconnected: // Stopping a disconnected command results in a failed state. InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Failed, null); isDisconnected = true; break; } _stopAsyncResult = new PowerShellAsyncResult(InstanceId, callback, state, null, false); } // If in the Disconnected state then stopping simply cuts loose the PowerShell object // so that a new one can be connected. The state is set to Failed since the command // cannot complete with this object. if (isDisconnected) { // Since object is stopped, allow result wait to end. _invokeAsyncResult?.SetAsCompleted(null); _stopAsyncResult.SetAsCompleted(null); // Raise event for failed state change. RaiseStateChangeEvent(InvocationStateInfo.Clone()); return _stopAsyncResult; } // Ensure the runspace is not blocking in a debug stop. ReleaseDebugger(); RaiseStateChangeEvent(InvocationStateInfo.Clone()); bool shouldRunStopHelper = false; RunspacePool pool = _rsConnection as RunspacePool; if (pool != null && pool.IsRemote) { if ((RemotePowerShell != null) && RemotePowerShell.Initialized) { RemotePowerShell.StopAsync(); if (isSyncCall) { _stopAsyncResult.AsyncWaitHandle.WaitOne(); } } else { shouldRunStopHelper = true; } } else if (isRunning) { _worker.Stop(isSyncCall); } else { shouldRunStopHelper = true; } if (shouldRunStopHelper) { if (isSyncCall) { StopHelper(events); } else { ThreadPool.QueueUserWorkItem(new WaitCallback(StopThreadProc), events); } } return _stopAsyncResult; } private void ReleaseDebugger() { LocalRunspace localRunspace = _runspace as LocalRunspace; localRunspace?.ReleaseDebugger(); } /// <summary> /// If there is no worker assigned yet, we need to emulate stop here. /// In Asynchronous stop case, we need to send event change notifications /// from a different thread. /// </summary> /// <param name="state"></param> private void StopHelper(object state) { Queue<PSInvocationStateInfo> events = state as Queue<PSInvocationStateInfo>; Dbg.Assert(events != null, "StopImplementation expects a Queue<PSInvocationStateInfo> as parameter"); // Raise the events outside of the lock..this way 3rd party callback // cannot hold our lock. while (events.Count > 0) { PSInvocationStateInfo targetStateInfo = events.Dequeue(); SetStateChanged(targetStateInfo); } // Clear internal resources InternalClearSuppressExceptions(); } private void StopThreadProc(object state) { // variable to keep track of exceptions. Exception exception = null; try { StopHelper(state); } catch (Exception e) { // report non-severe exceptions to the user via the // asyncresult object exception = e; throw; } } /// <summary> /// The client remote powershell associated with this /// powershell object. /// </summary> internal ClientRemotePowerShell RemotePowerShell { get; private set; } /// <summary> /// The history string to be used for displaying /// the history. /// </summary> public string HistoryString { get; set; } /// <summary> /// Extra commands to run in a single invocation. /// </summary> internal Collection<PSCommand> ExtraCommands { get; } /// <summary> /// Currently running extra commands. /// </summary> internal bool RunningExtraCommands { get; private set; } private bool ServerSupportsBatchInvocation() { if (_runspace != null) { return _runspace.RunspaceStateInfo.State != RunspaceState.BeforeOpen && _runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersion_2_2; } RemoteRunspacePoolInternal remoteRunspacePoolInternal = null; if (_rsConnection is RemoteRunspace) { remoteRunspacePoolInternal = (_rsConnection as RemoteRunspace).RunspacePool.RemoteRunspacePoolInternal; } else if (_rsConnection is RunspacePool) { remoteRunspacePoolInternal = (_rsConnection as RunspacePool).RemoteRunspacePoolInternal; } return remoteRunspacePoolInternal != null && remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersion_2_2; } /// <summary> /// Helper method to add running remote PowerShell to the remote runspace list. /// </summary> private void AddToRemoteRunspaceRunningList() { if (_runspace != null) { _runspace.PushRunningPowerShell(this); } else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); remoteRunspacePoolInternal?.PushRunningPowerShell(this); } } /// <summary> /// Helper method to remove running remote PowerShell from the remote runspacelist. /// </summary> private void RemoveFromRemoteRunspaceRunningList() { if (_runspace != null) { _runspace.PopRunningPowerShell(); } else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); remoteRunspacePoolInternal?.PopRunningPowerShell(); } } private RemoteRunspacePoolInternal GetRemoteRunspacePoolInternal() { RunspacePool runspacePool = _rsConnection as RunspacePool; return runspacePool?.RemoteRunspacePoolInternal; } #endregion #region Worker /// <summary> /// AsyncResult object used to monitor pipeline creation and invocation. /// This is needed as a Runspace may not be available in the RunspacePool. /// </summary> private sealed class Worker { private readonly ObjectStreamBase _inputStream; private readonly ObjectStreamBase _outputStream; private readonly ObjectStreamBase _errorStream; private readonly PSInvocationSettings _settings; private bool _isNotActive; private readonly PowerShell _shell; private readonly object _syncObject = new object(); /// <summary> /// </summary> /// <param name="inputStream"></param> /// <param name="outputStream"></param> /// <param name="settings"></param> /// <param name="shell"></param> internal Worker(ObjectStreamBase inputStream, ObjectStreamBase outputStream, PSInvocationSettings settings, PowerShell shell) { _inputStream = inputStream; _outputStream = outputStream; _errorStream = new PSDataCollectionStream<ErrorRecord>(shell.InstanceId, shell._errorBuffer); _settings = settings; _shell = shell; } /// <summary> /// Sets the async result object that monitors a /// BeginGetRunspace async operation on the /// RunspacePool. /// </summary> internal IAsyncResult GetRunspaceAsyncResult { get; set; } /// <summary> /// Gets the currently running pipeline. /// </summary> internal Pipeline CurrentlyRunningPipeline { get; private set; } /// <summary> /// This method gets invoked from a ThreadPool thread. /// </summary> /// <param name="state"></param> internal void CreateRunspaceIfNeededAndDoWork(object state) { Runspace rsToUse = state as Runspace; CreateRunspaceIfNeededAndDoWork(rsToUse, false); } /// <summary> /// This method gets invoked when PowerShell is not associated /// with a RunspacePool. /// </summary> /// <param name="rsToUse"> /// User supplied Runspace if any. /// </param> /// <param name="isSync"> /// true if Invoke() should be used to invoke pipeline /// false if InvokeAsync() should be used. /// </param> /// <remarks> /// All exceptions are caught and reported via a /// PipelineStateChanged event. /// </remarks> internal void CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, bool isSync) { #pragma warning disable 56500 try { // Set the host for this local runspace if user specified one. LocalRunspace rs = rsToUse as LocalRunspace; if (rs == null) { lock (_shell._syncObject) { if (_shell._runspace != null) { rsToUse = _shell._runspace; } else { Runspace runspace = null; if ((_settings != null) && (_settings.Host != null)) { runspace = RunspaceFactory.CreateRunspace(_settings.Host); } else { runspace = RunspaceFactory.CreateRunspace(); } _shell.SetRunspace(runspace, true); rsToUse = (LocalRunspace)runspace; rsToUse.Open(); } } } ConstructPipelineAndDoWork(rsToUse, isSync); } catch (Exception e) { // PipelineStateChangedEvent is not raised // if there is an exception calling BeginInvoke // So raise the event here and notify the caller. lock (_syncObject) { if (_isNotActive) return; _isNotActive = true; } _shell.PipelineStateChanged(this, new PipelineStateEventArgs( new PipelineStateInfo(PipelineState.Failed, e))); if (isSync) { throw; } } #pragma warning restore 56500 } /// <summary> /// This method gets called from a ThreadPool thread. /// This method gets called from a RunspacePool thread when a /// Runspace is available. /// </summary> /// <param name="asyncResult"> /// AsyncResult object which monitors the asyncOperation. /// </param> /// <remarks> /// All exceptions are caught and reported via a /// PipelineStateChanged event. /// </remarks> internal void RunspaceAvailableCallback(IAsyncResult asyncResult) { #pragma warning disable 56500 try { RunspacePool pool = _shell._rsConnection as RunspacePool; Dbg.Assert(pool != null, "RunspaceConnection must be a runspace pool"); // get the runspace..this will throw if there is an exception // occurred while getting the runspace. Runspace pooledRunspace = pool.EndGetRunspace(asyncResult); bool isPipelineCreated = ConstructPipelineAndDoWork(pooledRunspace, false); if (!isPipelineCreated) { pool.ReleaseRunspace(pooledRunspace); } } catch (Exception e) { // PipelineStateChangedEvent is not raised // if there is an exception calling BeginInvoke // So raise the event here and notify the caller. lock (_syncObject) { if (_isNotActive) return; _isNotActive = true; } _shell.PipelineStateChanged(this, new PipelineStateEventArgs( new PipelineStateInfo(PipelineState.Failed, e))); } #pragma warning restore 56500 } /// <summary> /// Constructs a pipeline from the supplied runspace and invokes /// pipeline either synchronously or asynchronously identified by /// <paramref name="performSyncInvoke"/>. /// </summary> /// <param name="rs"> /// Runspace to create pipeline. Cannot be null. /// </param> /// <param name="performSyncInvoke"> /// if true, Invoke() is called /// BeginInvoke() otherwise. /// </param> /// <exception cref="InvalidOperationException"> /// 1.BeginInvoke is called on nested powershell. Nested /// Powershell cannot be executed Asynchronously. /// </exception> /// <returns> /// true if the pipeline is created/invoked successfully. /// false otherwise. /// </returns> internal bool ConstructPipelineAndDoWork(Runspace rs, bool performSyncInvoke) { Dbg.Assert(rs != null, "Runspace cannot be null in ConstructPipelineAndDoWork"); _shell.RunspaceAssigned.SafeInvoke(this, new PSEventArgs<Runspace>(rs)); // lock is needed until a pipeline is created to // make stop() cleanly release resources. LocalRunspace lrs = rs as LocalRunspace; lock (_syncObject) { if (_isNotActive) { return false; } if (lrs != null) { LocalPipeline localPipeline = new LocalPipeline( lrs, _shell.Commands.Commands, (_settings != null && _settings.AddToHistory), _shell.IsNested, _inputStream, _outputStream, _errorStream, _shell.InformationalBuffers); localPipeline.IsChild = _shell.IsChild; if (!string.IsNullOrEmpty(_shell.HistoryString)) { localPipeline.SetHistoryString(_shell.HistoryString); } localPipeline.RedirectShellErrorOutputPipe = _shell.RedirectShellErrorOutputPipe; CurrentlyRunningPipeline = localPipeline; // register for pipeline state changed events within a lock...so that if // stop is called before invoke, we can listen to state transition and // take appropriate action. CurrentlyRunningPipeline.StateChanged += _shell.PipelineStateChanged; } else { throw PSTraceSource.NewNotImplementedException(); } } // Set pipeline specific settings CurrentlyRunningPipeline.InvocationSettings = _settings; Dbg.Assert(lrs != null, "LocalRunspace cannot be null here"); if (performSyncInvoke) { CurrentlyRunningPipeline.Invoke(); } else { CurrentlyRunningPipeline.InvokeAsync(); } return true; } /// <summary> /// Stops the async operation. /// </summary> /// <param name="isSyncCall"></param> internal void Stop(bool isSyncCall) { lock (_syncObject) { if (_isNotActive) { return; } _isNotActive = true; if (CurrentlyRunningPipeline != null) { if (isSyncCall) { CurrentlyRunningPipeline.Stop(); } else { CurrentlyRunningPipeline.StopAsync(); } return; } if (GetRunspaceAsyncResult != null) { RunspacePool pool = _shell._rsConnection as RunspacePool; Dbg.Assert(pool != null, "RunspaceConnection must be a runspace pool"); pool.CancelGetRunspace(GetRunspaceAsyncResult); } } // Pipeline is not yet associated with PowerShell..so emulate stop // locally Queue<PSInvocationStateInfo> events = new Queue<PSInvocationStateInfo>(); events.Enqueue(new PSInvocationStateInfo(PSInvocationState.Stopped, null)); if (isSyncCall) { _shell.StopHelper(events); } else { ThreadPool.QueueUserWorkItem(new WaitCallback(_shell.StopThreadProc), events); } } /// <summary> /// Internal clear is called when the invoke operation /// is completed or failed or stopped. /// </summary> internal void InternalClearSuppressExceptions() { try { if ((_settings != null) && (_settings.WindowsIdentityToImpersonate != null)) { _settings.WindowsIdentityToImpersonate.Dispose(); _settings.WindowsIdentityToImpersonate = null; } _inputStream.Close(); _outputStream.Close(); _errorStream.Close(); if (CurrentlyRunningPipeline == null) { return; } // Detach state changed handler so that runspace.close // and pipeline.dispose will not change powershell instances state CurrentlyRunningPipeline.StateChanged -= _shell.PipelineStateChanged; if ((GetRunspaceAsyncResult == null) && (_shell._rsConnection == null)) { // user did not supply a runspace..Invoke* method created // a new runspace..so close it. CurrentlyRunningPipeline.Runspace.Close(); } else { RunspacePool pool = _shell._rsConnection as RunspacePool; pool?.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); } CurrentlyRunningPipeline.Dispose(); } catch (ArgumentException) { } catch (InvalidOperationException) { } catch (InvalidRunspaceStateException) { } catch (InvalidRunspacePoolStateException) { } CurrentlyRunningPipeline = null; } #if !CORECLR // PSMI Not Supported On CSS internal void GetSettings(out bool addToHistory, out bool noInput, out uint apartmentState) { addToHistory = _settings.AddToHistory; noInput = false; apartmentState = (uint)_settings.ApartmentState; } #endif } #endregion #region Serialization / deserialization for remoting /// <summary> /// Creates a PowerShell object from a PSObject property bag. /// PSObject has to be in the format returned by ToPSObjectForRemoting method. /// </summary> /// <param name="powerShellAsPSObject">PSObject to rehydrate.</param> /// <returns> /// PowerShell rehydrated from a PSObject property bag /// </returns> /// <exception cref="ArgumentNullException"> /// Thrown if the PSObject is null. /// </exception> /// <exception cref="System.Management.Automation.Remoting.PSRemotingDataStructureException"> /// Thrown when the PSObject is not in the expected format /// </exception> internal static PowerShell FromPSObjectForRemoting(PSObject powerShellAsPSObject) { if (powerShellAsPSObject == null) { throw PSTraceSource.NewArgumentNullException(nameof(powerShellAsPSObject)); } Collection<PSCommand> extraCommands = null; ReadOnlyPSMemberInfoCollection<PSPropertyInfo> properties = powerShellAsPSObject.Properties.Match(RemoteDataNameStrings.ExtraCommands); if (properties.Count > 0) { extraCommands = new Collection<PSCommand>(); foreach (PSObject extraCommandsAsPSObject in RemotingDecoder.EnumerateListProperty<PSObject>(powerShellAsPSObject, RemoteDataNameStrings.ExtraCommands)) { PSCommand cmd = null; foreach (PSObject extraCommand in RemotingDecoder.EnumerateListProperty<PSObject>(extraCommandsAsPSObject, RemoteDataNameStrings.Commands)) { System.Management.Automation.Runspaces.Command command = System.Management.Automation.Runspaces.Command.FromPSObjectForRemoting(extraCommand); if (cmd == null) { cmd = new PSCommand(command); } else { cmd.AddCommand(command); } } extraCommands.Add(cmd); } } PSCommand psCommand = null; foreach (PSObject commandAsPSObject in RemotingDecoder.EnumerateListProperty<PSObject>(powerShellAsPSObject, RemoteDataNameStrings.Commands)) { System.Management.Automation.Runspaces.Command command = System.Management.Automation.Runspaces.Command.FromPSObjectForRemoting(commandAsPSObject); if (psCommand == null) { psCommand = new PSCommand(command); } else { psCommand.AddCommand(command); } } bool isNested = RemotingDecoder.GetPropertyValue<bool>(powerShellAsPSObject, RemoteDataNameStrings.IsNested); PowerShell shell = PowerShell.Create(isNested, psCommand, extraCommands); shell.HistoryString = RemotingDecoder.GetPropertyValue<string>(powerShellAsPSObject, RemoteDataNameStrings.HistoryString); shell.RedirectShellErrorOutputPipe = RemotingDecoder.GetPropertyValue<bool>(powerShellAsPSObject, RemoteDataNameStrings.RedirectShellErrorOutputPipe); return shell; } /// <summary> /// Returns this object as a PSObject property bag /// that can be used in a remoting protocol data object. /// </summary> /// <returns>This object as a PSObject property bag.</returns> internal PSObject ToPSObjectForRemoting() { PSObject powerShellAsPSObject = RemotingEncoder.CreateEmptyPSObject(); Version psRPVersion = RemotingEncoder.GetPSRemotingProtocolVersion(_rsConnection as RunspacePool); // Check if the server supports batch invocation if (ServerSupportsBatchInvocation()) { if (ExtraCommands.Count > 0) { List<PSObject> extraCommandsAsListOfPSObjects = new List<PSObject>(ExtraCommands.Count); foreach (PSCommand extraCommand in ExtraCommands) { PSObject obj = RemotingEncoder.CreateEmptyPSObject(); obj.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.Commands, CommandsAsListOfPSObjects(extraCommand.Commands, psRPVersion))); extraCommandsAsListOfPSObjects.Add(obj); } powerShellAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ExtraCommands, extraCommandsAsListOfPSObjects)); } } List<PSObject> commandsAsListOfPSObjects = CommandsAsListOfPSObjects(Commands.Commands, psRPVersion); powerShellAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.Commands, commandsAsListOfPSObjects)); powerShellAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.IsNested, this.IsNested)); powerShellAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.HistoryString, HistoryString)); powerShellAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.RedirectShellErrorOutputPipe, this.RedirectShellErrorOutputPipe)); return powerShellAsPSObject; } private static List<PSObject> CommandsAsListOfPSObjects(CommandCollection commands, Version psRPVersion) { List<PSObject> commandsAsListOfPSObjects = new List<PSObject>(commands.Count); foreach (Command command in commands) { commandsAsListOfPSObjects.Add(command.ToPSObjectForRemoting(psRPVersion)); } return commandsAsListOfPSObjects; } #endregion #region Remote data drain/block methods /// <summary> /// Suspends data arriving from remote session. /// </summary> internal void SuspendIncomingData() { if (RemotePowerShell == null) { throw new PSNotSupportedException(); } RemotePowerShell.DataStructureHandler?.TransportManager.SuspendQueue(true); } /// <summary> /// Resumes data arriving from remote session. /// </summary> internal void ResumeIncomingData() { if (RemotePowerShell == null) { throw new PSNotSupportedException(); } RemotePowerShell.DataStructureHandler?.TransportManager.ResumeQueue(); } /// <summary> /// Blocking call that waits until the *current remote* data /// queue at the transport manager is empty. This affects only /// the current queue until it is empty. /// </summary> internal void WaitForServicingComplete() { if (RemotePowerShell == null) { throw new PSNotSupportedException(); } if (RemotePowerShell.DataStructureHandler != null) { int count = 0; while (++count < 2 && RemotePowerShell.DataStructureHandler.TransportManager.IsServicing) { // Try waiting for 50 ms, then continue. Threading.Thread.Sleep(50); } } } #endregion #if !CORECLR // PSMI Not Supported On CSS #region Win Blue Extensions internal CimInstance AsPSPowerShellPipeline() { CimInstance c = InternalMISerializer.CreateCimInstance("PS_PowerShellPipeline"); CimProperty instanceIdProperty = InternalMISerializer.CreateCimProperty("InstanceId", this.InstanceId.ToString(), Microsoft.Management.Infrastructure.CimType.String); c.CimInstanceProperties.Add(instanceIdProperty); CimProperty isNestedProperty = InternalMISerializer.CreateCimProperty("IsNested", this.IsNested, Microsoft.Management.Infrastructure.CimType.Boolean); c.CimInstanceProperties.Add(isNestedProperty); bool addToHistoryValue = false, noInputValue = false; uint apartmentStateValue = 0; if (_worker != null) { _worker.GetSettings(out addToHistoryValue, out noInputValue, out apartmentStateValue); } CimProperty addToHistoryProperty = InternalMISerializer.CreateCimProperty("AddToHistory", addToHistoryValue, Microsoft.Management.Infrastructure.CimType.Boolean); c.CimInstanceProperties.Add(addToHistoryProperty); CimProperty noInputProperty = InternalMISerializer.CreateCimProperty("NoInput", noInputValue, Microsoft.Management.Infrastructure.CimType.Boolean); c.CimInstanceProperties.Add(noInputProperty); CimProperty apartmentStateProperty = InternalMISerializer.CreateCimProperty("ApartmentState", apartmentStateValue, Microsoft.Management.Infrastructure.CimType.UInt32); c.CimInstanceProperties.Add(apartmentStateProperty); if (this.Commands.Commands.Count > 0) { List<CimInstance> commandInstances = new List<CimInstance>(); foreach (var command in this.Commands.Commands) { commandInstances.Add(command.ToCimInstance()); } CimProperty commandsProperty = InternalMISerializer.CreateCimProperty("Commands", commandInstances.ToArray(), Microsoft.Management.Infrastructure.CimType.ReferenceArray); c.CimInstanceProperties.Add(commandsProperty); } return c; } #endregion Win Blue Extensions #endif } /// <summary> /// Streams generated by PowerShell invocations. /// </summary> public sealed class PSDataStreams { /// <summary> /// PSDataStreams is the public interface to access the *Buffer properties in the PowerShell class. /// </summary> internal PSDataStreams(PowerShell powershell) { _powershell = powershell; } /// <summary> /// Gets or sets the error buffer. Powershell invocation writes /// the error data into this buffer. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] public PSDataCollection<ErrorRecord> Error { get { return _powershell.ErrorBuffer; } set { _powershell.ErrorBuffer = value; } } /// <summary> /// Gets or sets the progress buffer. Powershell invocation writes /// the progress data into this buffer. Can be null. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] public PSDataCollection<ProgressRecord> Progress { get { return _powershell.ProgressBuffer; } set { _powershell.ProgressBuffer = value; } } /// <summary> /// Gets or sets the verbose buffer. Powershell invocation writes /// the verbose data into this buffer. Can be null. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] public PSDataCollection<VerboseRecord> Verbose { get { return _powershell.VerboseBuffer; } set { _powershell.VerboseBuffer = value; } } /// <summary> /// Gets or sets the debug buffer. Powershell invocation writes /// the debug data into this buffer. Can be null. /// </summary> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] public PSDataCollection<DebugRecord> Debug { get { return _powershell.DebugBuffer; } set { _powershell.DebugBuffer = value; } } /// <summary> /// Gets or sets the warning buffer. Powershell invocation writes /// the warning data into this buffer. Can be null. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] public PSDataCollection<WarningRecord> Warning { get { return _powershell.WarningBuffer; } set { _powershell.WarningBuffer = value; } } /// <summary> /// Gets or sets the information buffer. Powershell invocation writes /// the warning data into this buffer. Can be null. /// </summary> /// <exception cref="ArgumentNullException"> /// Cannot set to a null value. /// </exception> /// <exception cref="InvalidPowerShellStateException"> /// Powershell instance cannot be changed in its /// current state. /// </exception> /// <exception cref="ObjectDisposedException"> /// Object is disposed. /// </exception> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow callers to change the backing store.")] public PSDataCollection<InformationRecord> Information { get { return _powershell.InformationBuffer; } set { _powershell.InformationBuffer = value; } } /// <summary> /// Removes all items from all the data streams. /// </summary> public void ClearStreams() { this.Error.Clear(); this.Progress.Clear(); this.Verbose.Clear(); this.Information.Clear(); this.Debug.Clear(); this.Warning.Clear(); } private readonly PowerShell _powershell; } /// <summary> /// Helper class for making sure Ctrl-C stops an active powershell invocation. /// </summary> /// <example> /// powerShell = PowerShell.Create(); /// powerShell.AddCommand("Start-Sleep"); /// powerShell.AddParameter("Seconds", 10); /// powerShell.Runspace = remoteRunspace; /// Collection<PSObject> result; /// using (new PowerShellStopper(context, powerShell)) /// { /// result = powerShell.Invoke(); /// } /// </example> internal class PowerShellStopper : IDisposable { private readonly PipelineBase _pipeline; private readonly PowerShell _powerShell; private EventHandler<PipelineStateEventArgs> _eventHandler; internal PowerShellStopper(ExecutionContext context, PowerShell powerShell) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(powerShell); _powerShell = powerShell; if ((context.CurrentCommandProcessor != null) && (context.CurrentCommandProcessor.CommandRuntime != null) && (context.CurrentCommandProcessor.CommandRuntime.PipelineProcessor != null) && (context.CurrentCommandProcessor.CommandRuntime.PipelineProcessor.LocalPipeline != null)) { _eventHandler = new EventHandler<PipelineStateEventArgs>(LocalPipeline_StateChanged); _pipeline = context.CurrentCommandProcessor.CommandRuntime.PipelineProcessor.LocalPipeline; _pipeline.StateChanged += _eventHandler; } } private void LocalPipeline_StateChanged(object sender, PipelineStateEventArgs e) { if ((e.PipelineStateInfo.State == PipelineState.Stopping) && (_powerShell.InvocationStateInfo.State == PSInvocationState.Running)) { _powerShell.Stop(); } } private bool _isDisposed; public void Dispose() { if (!_isDisposed) { if (_eventHandler != null) { _pipeline.StateChanged -= _eventHandler; _eventHandler = null; } GC.SuppressFinalize(this); _isDisposed = true; } } } }