Files
PowerShell/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs
T

890 lines
39 KiB
C#
Raw Normal View History

/********************************************************************++
* Copyright (c) Microsoft Corporation. All rights reserved.
* --********************************************************************/
using System.Security.Principal;
using System.Management.Automation.Runspaces;
using Dbg = System.Management.Automation.Diagnostics;
using System.Management.Automation.Remoting;
using System.Management.Automation.Internal;
using System.Threading;
namespace System.Management.Automation
{
/// <summary>
/// This class wraps a PowerShell object. It is used to function
/// as a server side powershell
/// </summary>
internal class ServerPowerShellDriver
{
#region Private Members
2016-07-29 13:02:49 -07:00
private bool _extraPowerShellAlreadyScheduled;
private PowerShell _extraPowerShell; // extra PowerShell at the server to be run after localPowerShell
private PSDataCollection<PSObject> _localPowerShellOutput; // output buffer for the local PowerShell
2016-08-02 13:29:09 -07:00
// that is associated with this
2016-07-29 13:02:49 -07:00
// powershell driver
2016-08-02 13:29:09 -07:00
// associated with this powershell
2016-07-29 13:02:49 -07:00
// data structure handler object to handle all
// communications with the client
private bool[] _datasent = new bool[2]; // if the remaining data has been sent
// to the client before sending state
// information
private object _syncObject = new object(); // sync object for synchronizing sending
// data to client
private bool _noInput; // there is no input when this driver
// was created
private bool _addToHistory;
private ServerRemoteHost _remoteHost; // the server remote host instance
// associated with this powershell
#if !CORECLR // No ApartmentState In CoreCLR
private ApartmentState apartmentState; // apartment state for this powershell
#endif
2016-07-29 13:02:49 -07:00
private IRSPDriverInvoke _psDriverInvoker; // Handles nested invocation of PS drivers.
#endregion Private Members
#region Constructors
#if !CORECLR
/// <summary>
/// Default constructor for creating ServerPowerShellDrivers
/// </summary>
/// <param name="powershell">decoded powershell object</param>
/// <param name="extraPowerShell">extra pipeline to be run after <paramref name="powershell"/> completes</param>
/// <param name="noInput">whether there is input for this powershell</param>
/// <param name="clientPowerShellId">the client powershell id</param>
/// <param name="clientRunspacePoolId">the client runspacepool id</param>
/// <param name="runspacePoolDriver">runspace pool driver
/// which is creating this powershell driver</param>
/// <param name="apartmentState">apartment state for this powershell</param>
/// <param name="hostInfo">host info using which the host for
/// this powershell will be constructed</param>
/// <param name="streamOptions">serialization options for the streams in this powershell</param>
/// <param name="addToHistory">
/// true if the command is to be added to history list of the runspace. false, otherwise.
/// </param>
/// <param name="rsToUse">
/// If not null, this Runspace will be used to invoke Powershell.
/// If null, the RunspacePool pointed by <paramref name="runspacePoolDriver"/> will be used.
/// </param>
internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShell, bool noInput, Guid clientPowerShellId,
Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver,
ApartmentState apartmentState, HostInfo hostInfo, RemoteStreamOptions streamOptions,
bool addToHistory, Runspace rsToUse)
: this(powershell, extraPowerShell, noInput, clientPowerShellId, clientRunspacePoolId, runspacePoolDriver,
apartmentState, hostInfo, streamOptions, addToHistory, rsToUse, null)
{
}
#else
/// <summary>
/// Default constructor for creating ServerPowerShellDrivers
/// </summary>
/// <param name="powershell">decoded powershell object</param>
/// <param name="extraPowerShell">extra pipeline to be run after <paramref name="powershell"/> completes</param>
/// <param name="noInput">whether there is input for this powershell</param>
/// <param name="clientPowerShellId">the client powershell id</param>
/// <param name="clientRunspacePoolId">the client runspacepool id</param>
/// <param name="runspacePoolDriver">runspace pool driver
/// which is creating this powershell driver</param>
/// <param name="hostInfo">host info using which the host for
/// this powershell will be constructed</param>
/// <param name="streamOptions">serialization options for the streams in this powershell</param>
/// <param name="addToHistory">
/// true if the command is to be added to history list of the runspace. false, otherwise.
/// </param>
/// <param name="rsToUse">
/// If not null, this Runspace will be used to invoke Powershell.
/// If null, the RunspacePool pointed by <paramref name="runspacePoolDriver"/> will be used.
/// </param>
internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShell, bool noInput, Guid clientPowerShellId,
Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver,
HostInfo hostInfo, RemoteStreamOptions streamOptions,
bool addToHistory, Runspace rsToUse)
: this(powershell, extraPowerShell, noInput, clientPowerShellId, clientRunspacePoolId, runspacePoolDriver,
hostInfo, streamOptions, addToHistory, rsToUse, null)
{
}
#endif
#if CORECLR
/// <summary>
/// Default constructor for creating ServerPowerShellDrivers
/// </summary>
/// <param name="powershell">decoded powershell object</param>
/// <param name="extraPowerShell">extra pipeline to be run after <paramref name="powershell"/> completes</param>
/// <param name="noInput">whether there is input for this powershell</param>
/// <param name="clientPowerShellId">the client powershell id</param>
/// <param name="clientRunspacePoolId">the client runspacepool id</param>
/// <param name="runspacePoolDriver">runspace pool driver
/// which is creating this powershell driver</param>
/// <param name="hostInfo">host info using which the host for
/// this powershell will be constructed</param>
/// <param name="streamOptions">serialization options for the streams in this powershell</param>
/// <param name="addToHistory">
/// true if the command is to be added to history list of the runspace. false, otherwise.
/// </param>
/// <param name="rsToUse">
/// If not null, this Runspace will be used to invoke Powershell.
/// If null, the RunspacePool pointed by <paramref name="runspacePoolDriver"/> will be used.
/// </param>
/// <param name="output">
/// If not null, this is used as another source of output sent to the client.
/// </param>
2016-07-29 13:02:49 -07:00
internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShell, bool noInput, Guid clientPowerShellId,
Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver,
HostInfo hostInfo, RemoteStreamOptions streamOptions,
bool addToHistory, Runspace rsToUse, PSDataCollection<PSObject> output)
#else
/// <summary>
/// Default constructor for creating ServerPowerShellDrivers
/// </summary>
/// <param name="powershell">decoded powershell object</param>
/// <param name="extraPowerShell">extra pipeline to be run after <paramref name="powershell"/> completes</param>
/// <param name="noInput">whether there is input for this powershell</param>
/// <param name="clientPowerShellId">the client powershell id</param>
/// <param name="clientRunspacePoolId">the client runspacepool id</param>
/// <param name="runspacePoolDriver">runspace pool driver
/// which is creating this powershell driver</param>
/// <param name="apartmentState">apartment state for this powershell</param>
/// <param name="hostInfo">host info using which the host for
/// this powershell will be constructed</param>
/// <param name="streamOptions">serialization options for the streams in this powershell</param>
/// <param name="addToHistory">
/// true if the command is to be added to history list of the runspace. false, otherwise.
/// </param>
/// <param name="rsToUse">
/// If not null, this Runspace will be used to invoke Powershell.
/// If null, the RunspacePool pointed by <paramref name="runspacePoolDriver"/> will be used.
/// </param>
/// <param name="output">
/// If not null, this is used as another source of output sent to the client.
/// </param>
2016-07-29 13:02:49 -07:00
internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShell, bool noInput, Guid clientPowerShellId,
Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver,
ApartmentState apartmentState, HostInfo hostInfo, RemoteStreamOptions streamOptions,
bool addToHistory, Runspace rsToUse, PSDataCollection<PSObject> output)
#endif
{
2016-08-02 13:29:09 -07:00
InstanceId = clientPowerShellId;
RunspacePoolId = clientRunspacePoolId;
RemoteStreamOptions = streamOptions;
#if !CORECLR // No ApartmentState In CoreCLR
this.apartmentState = apartmentState;
#endif
2016-08-02 13:29:09 -07:00
LocalPowerShell = powershell;
2016-07-29 13:02:49 -07:00
_extraPowerShell = extraPowerShell;
_localPowerShellOutput = new PSDataCollection<PSObject>();
_noInput = noInput;
_addToHistory = addToHistory;
_psDriverInvoker = runspacePoolDriver;
2016-08-02 13:29:09 -07:00
DataStructureHandler = runspacePoolDriver.DataStructureHandler.CreatePowerShellDataStructureHandler(clientPowerShellId, clientRunspacePoolId, RemoteStreamOptions, LocalPowerShell);
_remoteHost = DataStructureHandler.GetHostAssociatedWithPowerShell(hostInfo, runspacePoolDriver.ServerRemoteHost);
if (!noInput)
{
2016-08-02 13:29:09 -07:00
InputCollection = new PSDataCollection<object>();
InputCollection.ReleaseOnEnumeration = true;
InputCollection.IdleEvent += new EventHandler<EventArgs>(HandleIdleEvent);
}
2016-07-29 13:02:49 -07:00
RegisterPipelineOutputEventHandlers(_localPowerShellOutput);
2016-08-02 13:29:09 -07:00
if (LocalPowerShell != null)
{
2016-08-02 13:29:09 -07:00
RegisterPowerShellEventHandlers(LocalPowerShell);
2016-07-29 13:02:49 -07:00
_datasent[0] = false;
}
if (extraPowerShell != null)
{
RegisterPowerShellEventHandlers(extraPowerShell);
2016-07-29 13:02:49 -07:00
_datasent[1] = false;
}
2016-08-02 13:29:09 -07:00
RegisterDataStructureHandlerEventHandlers(DataStructureHandler);
// set the runspace pool and invoke this powershell
if (null != rsToUse)
{
2016-08-02 13:29:09 -07:00
LocalPowerShell.Runspace = rsToUse;
if (extraPowerShell != null)
{
extraPowerShell.Runspace = rsToUse;
}
}
else
{
2016-08-02 13:29:09 -07:00
LocalPowerShell.RunspacePool = runspacePoolDriver.RunspacePool;
if (extraPowerShell != null)
{
extraPowerShell.RunspacePool = runspacePoolDriver.RunspacePool;
}
}
if (output != null)
{
output.DataAdded += (sender, args) =>
{
2016-07-29 13:02:49 -07:00
if (_localPowerShellOutput.IsOpen)
{
var items = output.ReadAll();
foreach (var item in items)
{
2016-07-29 13:02:49 -07:00
_localPowerShellOutput.Add(item);
}
}
};
}
}
#endregion Constructors
#region Internal Methods
/// <summary>
/// Input collection sync object
/// </summary>
2016-08-02 13:29:09 -07:00
internal PSDataCollection<object> InputCollection { get; }
/// <summary>
/// Local PowerShell instance
/// </summary>
2016-08-02 13:29:09 -07:00
internal PowerShell LocalPowerShell { get; }
/// <summary>
/// Instance id by which this powershell driver is
/// identified. This is the same as the id of the
/// powershell on the client side
/// </summary>
2016-08-02 13:29:09 -07:00
internal Guid InstanceId { get; }
/// <summary>
/// Serialization options for the streams in this powershell
/// </summary>
2016-08-02 13:29:09 -07:00
internal RemoteStreamOptions RemoteStreamOptions { get; }
/// <summary>
/// Id of the runspace pool driver which created
/// this object. This is the same as the id of
/// the runspace pool at the client side which
/// is associated with the powershell on the
/// client side
/// </summary>
2016-08-02 13:29:09 -07:00
internal Guid RunspacePoolId { get; }
/// <summary>
/// ServerPowerShellDataStructureHandler associated with this
/// powershell driver
/// </summary>
2016-08-02 13:29:09 -07:00
internal ServerPowerShellDataStructureHandler DataStructureHandler { get; }
private PSInvocationSettings PrepInvoke(bool startMainPowerShell)
{
if (startMainPowerShell)
{
// prepare transport manager for sending and receiving data.
2016-08-02 13:29:09 -07:00
DataStructureHandler.Prepare();
}
PSInvocationSettings settings = new PSInvocationSettings();
#if !CORECLR // No ApartmentState In CoreCLR
settings.ApartmentState = apartmentState;
#endif
2016-07-29 13:02:49 -07:00
settings.Host = _remoteHost;
// Flow the impersonation policy to pipeline execution thread
// only if the current thread is impersonated (Delegation is
// also a kind of impersonation).
2016-04-08 14:54:41 -07:00
if (Platform.IsWindows)
{
2016-07-29 13:02:49 -07:00
WindowsIdentity currentThreadIdentity = WindowsIdentity.GetCurrent();
switch (currentThreadIdentity.ImpersonationLevel)
{
case TokenImpersonationLevel.Impersonation:
case TokenImpersonationLevel.Delegation:
settings.FlowImpersonationPolicy = true;
break;
default:
settings.FlowImpersonationPolicy = false;
break;
}
2016-04-06 12:01:07 -07:00
}
else
{
2016-07-29 13:02:49 -07:00
settings.FlowImpersonationPolicy = false;
}
2016-07-29 13:02:49 -07:00
settings.AddToHistory = _addToHistory;
return settings;
}
private IAsyncResult Start(bool startMainPowerShell)
{
PSInvocationSettings settings = PrepInvoke(startMainPowerShell);
if (startMainPowerShell)
{
2016-08-02 13:29:09 -07:00
return LocalPowerShell.BeginInvoke<object, PSObject>(InputCollection, _localPowerShellOutput, settings, null, null);
}
else
{
2016-08-02 13:29:09 -07:00
return _extraPowerShell.BeginInvoke<object, PSObject>(InputCollection, _localPowerShellOutput, settings, null, null);
}
}
/// <summary>
/// invokes the powershell asynchronously
/// </summary>
internal IAsyncResult Start()
{
return Start(true);
}
/// <summary>
/// Runs no command but allows the PowerShell object on the client
/// to complete. This is used for running "virtual" remote debug
/// commands that sets debugger state but doesn't run any command
/// on the server runspace.
/// </summary>
internal void RunNoOpCommand()
{
2016-08-02 13:29:09 -07:00
if (LocalPowerShell != null)
{
System.Threading.ThreadPool.QueueUserWorkItem(
2016-07-29 13:02:49 -07:00
(state) =>
{
2016-08-02 13:29:09 -07:00
LocalPowerShell.SetStateChanged(
new PSInvocationStateInfo(
PSInvocationState.Running, null));
2016-08-02 13:29:09 -07:00
LocalPowerShell.SetStateChanged(
new PSInvocationStateInfo(
PSInvocationState.Completed, null));
});
}
}
/// <summary>
/// Invokes the Main PowerShell object synchronously.
/// </summary>
internal void InvokeMain()
{
PSInvocationSettings settings = PrepInvoke(true);
Exception ex = null;
try
{
2016-08-02 13:29:09 -07:00
LocalPowerShell.InvokeWithDebugger(InputCollection, _localPowerShellOutput, settings, true);
}
catch (Exception e)
{
CommandProcessor.CheckForSevereException(e);
ex = e;
}
if (ex != null)
{
// Since this is being invoked asynchronously on a single pipeline thread
// any invoke failures (such as possible debugger failures) need to be
// passed back to client or the original client invoke request will hang.
2016-08-02 13:29:09 -07:00
string failedCommand = LocalPowerShell.Commands.Commands[0].CommandText;
LocalPowerShell.Commands.Clear();
string msg = StringUtil.Format(
RemotingErrorIdStrings.ServerSideNestedCommandInvokeFailed,
failedCommand ?? string.Empty,
ex.Message ?? string.Empty);
2016-08-02 13:29:09 -07:00
LocalPowerShell.AddCommand("Write-Error").AddArgument(msg);
LocalPowerShell.Invoke();
}
}
#endregion Internal Methods
#region Private Methods
private void RegisterPowerShellEventHandlers(PowerShell powerShell)
{
powerShell.InvocationStateChanged += HandlePowerShellInvocationStateChanged;
powerShell.Streams.Error.DataAdded += HandleErrorDataAdded;
powerShell.Streams.Debug.DataAdded += HandleDebugAdded;
powerShell.Streams.Verbose.DataAdded += HandleVerboseAdded;
powerShell.Streams.Warning.DataAdded += HandleWarningAdded;
powerShell.Streams.Progress.DataAdded += HandleProgressAdded;
powerShell.Streams.Information.DataAdded += HandleInformationAdded;
}
private void UnregisterPowerShellEventHandlers(PowerShell powerShell)
{
powerShell.InvocationStateChanged -= HandlePowerShellInvocationStateChanged;
powerShell.Streams.Error.DataAdded -= HandleErrorDataAdded;
powerShell.Streams.Debug.DataAdded -= HandleDebugAdded;
powerShell.Streams.Verbose.DataAdded -= HandleVerboseAdded;
powerShell.Streams.Warning.DataAdded -= HandleWarningAdded;
powerShell.Streams.Progress.DataAdded -= HandleProgressAdded;
powerShell.Streams.Information.DataAdded -= HandleInformationAdded;
}
private void RegisterDataStructureHandlerEventHandlers(ServerPowerShellDataStructureHandler dsHandler)
{
dsHandler.InputEndReceived += HandleInputEndReceived;
dsHandler.InputReceived += HandleInputReceived;
dsHandler.StopPowerShellReceived += HandleStopReceived;
dsHandler.HostResponseReceived += HandleHostResponseReceived;
dsHandler.OnSessionConnected += HandleSessionConnected;
}
private void UnregisterDataStructureHandlerEventHandlers(ServerPowerShellDataStructureHandler dsHandler)
{
dsHandler.InputEndReceived -= HandleInputEndReceived;
dsHandler.InputReceived -= HandleInputReceived;
dsHandler.StopPowerShellReceived -= HandleStopReceived;
dsHandler.HostResponseReceived -= HandleHostResponseReceived;
dsHandler.OnSessionConnected -= HandleSessionConnected;
}
private void RegisterPipelineOutputEventHandlers(PSDataCollection<PSObject> pipelineOutput)
{
pipelineOutput.DataAdded += HandleOutputDataAdded;
}
private void UnregisterPipelineOutputEventHandlers(PSDataCollection<PSObject> pipelineOutput)
{
pipelineOutput.DataAdded -= HandleOutputDataAdded;
}
/// <summary>
/// Handle state changed information from PowerShell
/// and send it to the client
/// </summary>
/// <param name="sender">sender of this event</param>
/// <param name="eventArgs">arguments describing state changed
/// information for this powershell</param>
private void HandlePowerShellInvocationStateChanged(object sender,
PSInvocationStateChangedEventArgs eventArgs)
{
PSInvocationState state = eventArgs.InvocationStateInfo.State;
switch (state)
{
case PSInvocationState.Completed:
case PSInvocationState.Failed:
case PSInvocationState.Stopped:
{
2016-08-02 13:29:09 -07:00
if (LocalPowerShell.RunningExtraCommands)
{
// If completed successfully then allow extra commands to run.
if (state == PSInvocationState.Completed) { return; }
2016-07-29 13:02:49 -07:00
// For failed or stopped state, extra commands cannot run and
// we allow this command invocation to finish.
}
// send the remaining data before sending in
// state information. This is required because
// the client side runspace pool will remove
// the association with the client side powershell
// once the powershell reaches a terminal state.
// If the association is removed, then any data
// sent to the powershell will be discarded by
// the runspace pool data structure handler on the client side
SendRemainingData();
2016-07-29 13:02:49 -07:00
if (state == PSInvocationState.Completed &&
(_extraPowerShell != null) &&
!_extraPowerShellAlreadyScheduled)
{
2016-07-29 13:02:49 -07:00
_extraPowerShellAlreadyScheduled = true;
Start(false);
}
else
{
2016-08-02 13:29:09 -07:00
DataStructureHandler.RaiseRemoveAssociationEvent();
// send the state change notification to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendStateChangedInformationToClient(
eventArgs.InvocationStateInfo);
2016-08-02 13:29:09 -07:00
UnregisterPowerShellEventHandlers(LocalPowerShell);
2016-07-29 13:02:49 -07:00
if (_extraPowerShell != null)
{
2016-07-29 13:02:49 -07:00
UnregisterPowerShellEventHandlers(_extraPowerShell);
}
2016-08-02 13:29:09 -07:00
UnregisterDataStructureHandlerEventHandlers(DataStructureHandler);
2016-07-29 13:02:49 -07:00
UnregisterPipelineOutputEventHandlers(_localPowerShellOutput);
// BUGBUG: currently the local powershell cannot
// be disposed as raising the events is
// not done towards the end. Need to fix
// powershell in order to get this enabled
//localPowerShell.Dispose();
}
}
break;
case PSInvocationState.Stopping:
{
// abort all pending host calls
2016-07-29 13:02:49 -07:00
_remoteHost.ServerMethodExecutor.AbortAllCalls();
}
break;
}
}
/// <summary>
/// Handles DataAdded event from the Output of the powershell
/// </summary>
/// <param name="sender">sender of this information</param>
/// <param name="e">arguments describing this event</param>
private void HandleOutputDataAdded(object sender, DataAddedEventArgs e)
{
int index = e.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if (!_datasent[indexIntoDataSent])
{
2016-07-29 13:02:49 -07:00
PSObject data = _localPowerShellOutput[index];
// once send the output is removed so that the same
// is not sent again by SendRemainingData() method
2016-07-29 13:02:49 -07:00
_localPowerShellOutput.RemoveAt(index);
// send the output data to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendOutputDataToClient(data);
}
} // lock ..
}
/// <summary>
/// Handles DataAdded event from Error of the PowerShell
/// </summary>
/// <param name="sender">sender of this event</param>
/// <param name="e">arguments describing this event</param>
private void HandleErrorDataAdded(object sender, DataAddedEventArgs e)
{
int index = e.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if ((indexIntoDataSent == 0) && (!_datasent[indexIntoDataSent]))
{
2016-08-02 13:29:09 -07:00
ErrorRecord errorRecord = LocalPowerShell.Streams.Error[index];
// once send the error record is removed so that the same
// is not sent again by SendRemainingData() method
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Error.RemoveAt(index);
// send the error record to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendErrorRecordToClient(errorRecord);
}
} // lock ...
}
/// <summary>
/// Handles DataAdded event from Progress of PowerShell
/// </summary>
/// <param name="sender">sender of this information, unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleProgressAdded(object sender, DataAddedEventArgs eventArgs)
{
int index = eventArgs.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if ((indexIntoDataSent == 0) && (!_datasent[indexIntoDataSent]))
{
2016-08-02 13:29:09 -07:00
ProgressRecord data = LocalPowerShell.Streams.Progress[index];
// once the debug message is sent, it is removed so that
// the same is not sent again by SendRemainingData() method
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Progress.RemoveAt(index);
// send the output data to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendProgressRecordToClient(data);
}
} // lock ..
}
/// <summary>
/// Handles DataAdded event from Warning of PowerShell
/// </summary>
/// <param name="sender">sender of this information, unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleWarningAdded(object sender, DataAddedEventArgs eventArgs)
{
int index = eventArgs.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if ((indexIntoDataSent == 0) && (!_datasent[indexIntoDataSent]))
{
2016-08-02 13:29:09 -07:00
WarningRecord data = LocalPowerShell.Streams.Warning[index];
// once the debug message is sent, it is removed so that
// the same is not sent again by SendRemainingData() method
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Warning.RemoveAt(index);
2016-07-29 13:02:49 -07:00
// send the output data to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendWarningRecordToClient(data);
}
} // lock ..
}
/// <summary>
/// Handles DataAdded from Verbose of PowerShell
/// </summary>
/// <param name="sender">sender of this information, unused</param>
/// <param name="eventArgs">sender of this information</param>
private void HandleVerboseAdded(object sender, DataAddedEventArgs eventArgs)
{
int index = eventArgs.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if ((indexIntoDataSent == 0) && (!_datasent[indexIntoDataSent]))
{
2016-08-02 13:29:09 -07:00
VerboseRecord data = LocalPowerShell.Streams.Verbose[index];
// once the debug message is sent, it is removed so that
// the same is not sent again by SendRemainingData() method
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Verbose.RemoveAt(index);
// send the output data to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendVerboseRecordToClient(data);
}
} // lock ..
}
/// <summary>
/// Handles DataAdded from Debug of PowerShell
/// </summary>
/// <param name="sender">sender of this information, unused</param>
/// <param name="eventArgs">sender of this information</param>
private void HandleDebugAdded(object sender, DataAddedEventArgs eventArgs)
{
int index = eventArgs.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if ((indexIntoDataSent == 0) && (!_datasent[indexIntoDataSent]))
{
2016-08-02 13:29:09 -07:00
DebugRecord data = LocalPowerShell.Streams.Debug[index];
// once the debug message is sent, it is removed so that
// the same is not sent again by SendRemainingData() method
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Debug.RemoveAt(index);
// send the output data to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendDebugRecordToClient(data);
}
} // lock ..
}
/// <summary>
/// Handles DataAdded from Information of PowerShell
/// </summary>
/// <param name="sender">sender of this information, unused</param>
/// <param name="eventArgs">sender of this information</param>
private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs)
{
int index = eventArgs.Index;
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
if ((indexIntoDataSent == 0) && (!_datasent[indexIntoDataSent]))
{
2016-08-02 13:29:09 -07:00
InformationRecord data = LocalPowerShell.Streams.Information[index];
// once the Information message is sent, it is removed so that
// the same is not sent again by SendRemainingData() method
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Information.RemoveAt(index);
// send the output data to the client
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendInformationRecordToClient(data);
}
} // lock ..
}
/// <summary>
/// Send the remaining output and error information to
/// client
/// </summary>
/// <remarks>This method should be called before
/// sending the state information. The client will
/// remove the association between a powershell and
/// runspace pool if it receives any of the terminal
/// states. Hence all the remaining data should be
/// sent before this happens. Else the data will be
/// discarded</remarks>
private void SendRemainingData()
{
2016-07-29 13:02:49 -07:00
int indexIntoDataSent = (!_extraPowerShellAlreadyScheduled) ? 0 : 1;
lock (_syncObject)
{
2016-07-29 13:02:49 -07:00
_datasent[indexIntoDataSent] = true;
}
try
{
// BUGBUG: change this code to use enumerator
// blocked on bug #108824, to be fixed by Kriscv
2016-07-29 13:02:49 -07:00
for (int i = 0; i < _localPowerShellOutput.Count; i++)
{
2016-07-29 13:02:49 -07:00
PSObject data = _localPowerShellOutput[i];
2016-08-02 13:29:09 -07:00
DataStructureHandler.SendOutputDataToClient(data);
}
2016-07-29 13:02:49 -07:00
_localPowerShellOutput.Clear();
//foreach (ErrorRecord errorRecord in localPowerShell.Error)
2016-08-02 13:29:09 -07:00
for (int i = 0; i < LocalPowerShell.Streams.Error.Count; i++)
{
2016-08-02 13:29:09 -07:00
ErrorRecord errorRecord = LocalPowerShell.Streams.Error[i];
DataStructureHandler.SendErrorRecordToClient(errorRecord);
}
2016-08-02 13:29:09 -07:00
LocalPowerShell.Streams.Error.Clear();
}
finally
{
2016-07-29 13:02:49 -07:00
lock (_syncObject)
{
// reset to original state so other pipelines can stream.
2016-07-29 13:02:49 -07:00
_datasent[indexIntoDataSent] = true;
}
}
}
/// <summary>
/// Stop the local powershell
/// </summary>
/// <param name="sender">sender of this event, unused</param>
/// <param name="eventArgs">unused</param>
private void HandleStopReceived(object sender, EventArgs eventArgs)
{
do // false loop
{
2016-08-02 13:29:09 -07:00
if (LocalPowerShell.InvocationStateInfo.State == PSInvocationState.Stopped ||
LocalPowerShell.InvocationStateInfo.State == PSInvocationState.Completed ||
LocalPowerShell.InvocationStateInfo.State == PSInvocationState.Failed ||
LocalPowerShell.InvocationStateInfo.State == PSInvocationState.Stopping)
{
break;
}
else
{
// Ensure that the local PowerShell command is not stopped in debug mode.
bool handledByDebugger = false;
2016-08-02 13:29:09 -07:00
if (!LocalPowerShell.IsNested &&
2016-07-29 13:02:49 -07:00
_psDriverInvoker != null)
{
2016-07-29 13:02:49 -07:00
handledByDebugger = _psDriverInvoker.HandleStopSignal();
}
2016-07-29 13:02:49 -07:00
if (!handledByDebugger)
{
2016-08-02 13:29:09 -07:00
LocalPowerShell.Stop();
}
}
} while (false);
2016-07-29 13:02:49 -07:00
if (_extraPowerShell != null)
{
do // false loop
{
2016-07-29 13:02:49 -07:00
if (_extraPowerShell.InvocationStateInfo.State == PSInvocationState.Stopped ||
_extraPowerShell.InvocationStateInfo.State == PSInvocationState.Completed ||
_extraPowerShell.InvocationStateInfo.State == PSInvocationState.Failed ||
_extraPowerShell.InvocationStateInfo.State == PSInvocationState.Stopping)
{
break;
}
else
{
2016-07-29 13:02:49 -07:00
_extraPowerShell.Stop();
}
} while (false);
}
}
/// <summary>
/// Add input to the local powershell's input collection
/// </summary>
/// <param name="sender">sender of this event, unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleInputReceived(object sender, RemoteDataEventArgs<object> eventArgs)
{
// This can be called in pushed runspace scenarios for error reporting (pipeline stopped).
// Ignore for noInput.
2016-08-02 13:29:09 -07:00
if (!_noInput && (InputCollection != null))
{
2016-08-02 13:29:09 -07:00
InputCollection.Add(eventArgs.Data);
}
}
/// <summary>
/// Close the input collection of the local powershell
/// </summary>
/// <param name="sender">sender of this event, unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleInputEndReceived(object sender, EventArgs eventArgs)
{
// This can be called in pushed runspace scenarios for error reporting (pipeline stopped).
// Ignore for noInput.
2016-08-02 13:29:09 -07:00
if (!_noInput && (InputCollection != null))
{
2016-08-02 13:29:09 -07:00
InputCollection.Complete();
}
}
private void HandleSessionConnected(object sender, EventArgs eventArgs)
{
//Close input if its active. no need to synchronize as input stream would have already been processed
// when connect call came into PS plugin
2016-08-02 13:29:09 -07:00
if (InputCollection != null)
{
//TODO: Post an ETW event
2016-08-02 13:29:09 -07:00
InputCollection.Complete();
}
}
/// <summary>
/// Handle a host message response received
/// </summary>
/// <param name="sender">sender of this event, unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleHostResponseReceived(object sender, RemoteDataEventArgs<RemoteHostResponse> eventArgs)
{
2016-07-29 13:02:49 -07:00
_remoteHost.ServerMethodExecutor.HandleRemoteHostResponseFromClient(eventArgs.Data);
}
/// <summary>
/// Handles the PSDataCollection idle event
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void HandleIdleEvent(object sender, EventArgs args)
{
2016-08-02 13:29:09 -07:00
Runspace rs = DataStructureHandler.RunspaceUsedToInvokePowerShell;
if (rs != null)
{
PSLocalEventManager events = (object)rs.Events as PSLocalEventManager;
if (events != null)
{
foreach (PSEventSubscriber subscriber in events.Subscribers)
{
// Use the synchronous version
events.DrainPendingActions(subscriber);
}
}
}
}
#endregion Private Methods
}
}