2016-03-30 14:20:52 -07:00
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Diagnostics.CodeAnalysis ;
using System.Management.Automation.Language ;
using System.Management.Automation.Remoting.Internal ;
using System.Management.Automation.Tracing ;
using System.Management.Automation.Remoting ;
using System.Management.Automation.Runspaces ;
using System.Management.Automation.Internal ;
using System.Management.Automation.Host ;
using System.Runtime.Serialization ;
using System.Threading ;
using System.Text ;
using Microsoft.PowerShell.Commands ;
using Dbg = System . Management . Automation . Diagnostics ;
#if CORECLR
// Use stubs for SerializableAttribute, SystemException, SerializationInfo and ISerializable related types.
using Microsoft.PowerShell.CoreClr.Stubs ;
#endif
// Stops compiler from warning about unknown warnings
#pragma warning disable 1634 , 1691
namespace System.Management.Automation
{
/// <summary>
/// Enumeration for job status values. Indicates the status
/// of the result object
/// </summary>
public enum JobState
{
/// <summary>
/// Execution of command in job not started
/// </summary>
NotStarted = 0 ,
/// <summary>
/// execution of command in progress
/// </summary>
Running = 1 ,
/// <summary>
/// execution of command completed in all
/// computernames/runspaces
/// </summary>
Completed = 2 ,
/// <summary>
/// An error was encountered when trying to executed
/// command in one or more computernames/runspaces
/// </summary>
Failed = 3 ,
/// <summary>
/// Command execution is cancelled (stopped) in one or more
/// computernames/runspaces.
/// </summary>
Stopped = 4 ,
/// <summary>
/// Command execution is blocked (on user input host calls etc)
/// </summary>
Blocked = 5 ,
/// <summary>
/// The job has been suspended
/// </summary>
Suspended = 6 ,
/// <summary>
/// The job is a remote job and has been disconnected from the server.
/// </summary>
Disconnected = 7 ,
/// <summary>
/// Suspend is in progress
/// </summary>
Suspending = 8 ,
/// <summary>
/// Stop is in progress
/// </summary>
Stopping = 9 ,
/// <summary>
/// Script execution is halted in a debugger stop.
/// </summary>
AtBreakpoint = 10
}
/// <summary>
/// Defines exception which is thrown when state of the PSJob is different
/// from the expected state.
/// </summary>
[Serializable]
public class InvalidJobStateException : SystemException
{
/// <summary>
/// Creates a new instance of InvalidPSJobStateException class.
/// </summary>
public InvalidJobStateException ()
: base
(
PSRemotingErrorInvariants . FormatResourceString
(
RemotingErrorIdStrings . InvalidJobStateGeneral
)
)
{
}
/// <summary>
/// Creates a new instance of InvalidPSJobStateException class.
/// </summary>
/// <param name="message">
/// The error message that explains the reason for the exception.
/// </param>
public InvalidJobStateException ( string message )
: base ( message )
{
}
/// <summary>
/// Creates a new instance of InvalidPSJobStateException 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 InvalidJobStateException ( string message , Exception innerException )
: base ( message , innerException )
{
}
/// <summary>
/// Creates a new instance of InvalidJobStateException class.
/// </summary>
/// <param name="currentState">
/// The Job State at the time of the error.
/// </param>
/// <param name="actionMessage">
/// An additional message that gives more information about the error. Used
/// for context after a generalized error message.
/// </param>
public InvalidJobStateException ( JobState currentState , string actionMessage )
: base
(
PSRemotingErrorInvariants . FormatResourceString
(
RemotingErrorIdStrings . InvalidJobStateSpecific , currentState , actionMessage
)
)
{
2016-07-29 13:02:49 -07:00
_currState = currentState ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Initializes a new instance of the InvalidPSJobStateException and defines value of
/// CurrentState.
/// </summary>
/// <param name="currentState">Current state of powershell</param>
internal InvalidJobStateException ( JobState currentState )
: base
(
PSRemotingErrorInvariants . FormatResourceString
(
RemotingErrorIdStrings . InvalidJobStateGeneral
)
)
{
2016-07-29 13:02:49 -07:00
_currState = currentState ;
2016-03-30 14:20:52 -07:00
}
#region ISerializable Members
// No need to implement GetObjectData
// if all fields are static or [NonSerialized]
/// <summary>
/// Initializes a new instance of the InvalidPSJobStateException
/// 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>
protected
InvalidJobStateException ( SerializationInfo info , StreamingContext context )
: base ( info , context )
{
}
#endregion
/// <summary>
/// Gets CurrentState of the Job
/// </summary>
public JobState CurrentState
{
get
{
2016-07-29 13:02:49 -07:00
return _currState ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// State of job when exception was thrown.
/// </summary>
[NonSerialized]
2016-07-29 13:02:49 -07:00
private JobState _currState = 0 ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Type which has information about JobState and Exception
/// ,if any, associated with JobState
/// </summary>
public sealed class JobStateInfo
{
#region constructors
/// <summary>
/// Constructor for state changes not resulting from an error.
/// </summary>
/// <param name="state">Execution state</param>
public JobStateInfo ( JobState state )
: this ( state , null )
{
}
/// <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>
public JobStateInfo ( JobState state , Exception reason )
{
2016-08-02 13:29:09 -07:00
State = state ;
Reason = reason ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Copy constructor to support cloning
/// </summary>
/// <param name="jobStateInfo">source information</param>
/// <throws>
/// ArgumentNullException when <paramref name="jobStateInfo"/> is null.
/// </throws>
internal JobStateInfo ( JobStateInfo jobStateInfo )
{
2016-08-02 13:29:09 -07:00
State = jobStateInfo . State ;
Reason = jobStateInfo . Reason ;
2016-03-30 14:20:52 -07:00
}
#endregion constructors
#region public_properties
/// <summary>
/// The state of the job.
/// </summary>
/// <remarks>
/// This value indicates the state of the job .
/// </remarks>
2016-08-02 13:29:09 -07:00
public JobState State { get ; }
2016-03-30 14:20:52 -07:00
/// <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>
2016-08-02 13:29:09 -07:00
public Exception Reason { get ; }
2016-03-30 14:20:52 -07:00
#endregion public_properties
/// <summary>
/// override for ToString()
/// </summary>
/// <returns></returns>
public override string ToString ()
{
2016-08-02 13:29:09 -07:00
return State . ToString ();
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Clones this object
/// </summary>
/// <returns>Cloned object</returns>
internal JobStateInfo Clone ()
{
return new JobStateInfo ( this );
}
#region private_fields
#endregion private_fields
}
/// <summary>
/// Event arguments passed to JobStateEvent handlers
/// <see cref="Job.StateChanged"/> event.
/// </summary>
public sealed class JobStateEventArgs : EventArgs
{
#region constructors
/// <summary>
/// Constructor of JobStateEventArgs
/// </summary>
/// <param name="jobStateInfo">The current state of the job.</param>
public JobStateEventArgs ( JobStateInfo jobStateInfo )
: this ( jobStateInfo , null )
{
}
/// <summary>
/// Constructor of JobStateEventArgs
/// </summary>
/// <param name="jobStateInfo">The current state of the job.</param>
/// <param name="previousJobStateInfo">The previous state of the job.</param>
public JobStateEventArgs ( JobStateInfo jobStateInfo , JobStateInfo previousJobStateInfo )
{
if ( jobStateInfo == null )
{
throw PSTraceSource . NewArgumentNullException ( "jobStateInfo" );
}
2016-08-02 13:29:09 -07:00
JobStateInfo = jobStateInfo ;
PreviousJobStateInfo = previousJobStateInfo ;
2016-03-30 14:20:52 -07:00
}
#endregion constructors
#region public_properties
/// <summary>
/// Info about the current state of the job.
/// </summary>
2016-08-02 13:29:09 -07:00
public JobStateInfo JobStateInfo { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Info about the previous state of the job.
/// </summary>
2016-08-02 13:29:09 -07:00
public JobStateInfo PreviousJobStateInfo { get ; }
2016-03-30 14:20:52 -07:00
#endregion public_properties
}
/// <summary>
/// Object that must be created by PowerShell to allow reuse of an ID for a job.
/// Also allows setting of the Instance Id so that jobs may be recreated.
/// </summary>
public sealed class JobIdentifier
{
internal JobIdentifier ( int id , Guid instanceId )
{
if ( id <= 0 )
PSTraceSource . NewArgumentException ( "id" , RemotingErrorIdStrings . JobSessionIdLessThanOne , id );
Id = id ;
InstanceId = instanceId ;
}
internal int Id { get ; private set ; }
internal Guid InstanceId { get ; private set ; }
}
/// <summary>
/// Interface to expose a job debugger.
/// </summary>
public interface IJobDebugger
{
/// <summary>
/// Job Debugger
/// </summary>
Debugger Debugger
{
get ;
}
/// <summary>
/// True if job is running asynchronously.
/// </summary>
bool IsAsync
{
get ;
set ;
}
}
/// <summary>
/// Represents a command running in background. A job object can internally
/// contain many child job objects.
/// </summary>
public abstract class Job : IDisposable
{
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
2016-07-29 13:02:49 -07:00
protected Job ()
2016-03-30 14:20:52 -07:00
{
2016-08-02 13:29:09 -07:00
Id = System . Threading . Interlocked . Increment ( ref s_jobIdSeed );
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Creates an instance of this class
/// </summary>
/// <param name="command">Command invoked by this job object</param>
protected Job ( string command )
2016-07-29 13:02:49 -07:00
: this ()
2016-03-30 14:20:52 -07:00
{
2016-08-02 13:29:09 -07:00
Command = command ;
2016-07-29 13:02:49 -07:00
_name = AutoGenerateJobName ();
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Creates an instance of this class
/// </summary>
/// <param name="command">Command invoked by this job object</param>
/// <param name="name">Friendly name for the job object</param>
protected Job ( string command , string name )
2016-07-29 13:02:49 -07:00
: this ( command )
2016-03-30 14:20:52 -07:00
{
if (! String . IsNullOrEmpty ( name ))
{
2016-07-29 13:02:49 -07:00
_name = name ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// Creates an instance of this class
/// </summary>
/// <param name="command">Command invoked by this job object</param>
/// <param name="name">Friendly name for the job object</param>
/// <param name="childJobs">Child jobs of this job object</param>
protected Job ( string command , string name , IList < Job > childJobs )
2016-07-29 13:02:49 -07:00
: this ( command , name )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_childJobs = childJobs ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Creates an instance of this class
/// </summary>
/// <param name="command">Command invoked by this job object</param>
/// <param name="name">Friendly name for the job object</param>
/// <param name="token">Id and InstanceId pair to be used for this job object</param>
/// <remarks>The JobIdentifier is a token that must be issued by PowerShell to allow
/// reuse of the Id. This is the only way to set either Id or instance Id.</remarks>
protected Job ( string command , string name , JobIdentifier token )
{
if ( token == null )
throw PSTraceSource . NewArgumentNullException ( "token" , RemotingErrorIdStrings . JobIdentifierNull );
2016-07-29 13:02:49 -07:00
if ( token . Id > s_jobIdSeed )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "token" , RemotingErrorIdStrings . JobIdNotYetAssigned , token . Id );
}
2016-08-02 13:29:09 -07:00
Command = command ;
2016-03-30 14:20:52 -07:00
2016-08-02 13:29:09 -07:00
Id = token . Id ;
InstanceId = token . InstanceId ;
2016-03-30 14:20:52 -07:00
if (! String . IsNullOrEmpty ( name ))
{
2016-07-29 13:02:49 -07:00
_name = name ;
2016-03-30 14:20:52 -07:00
}
else
{
2016-07-29 13:02:49 -07:00
_name = AutoGenerateJobName ();
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// Creates an instance of this class
/// </summary>
/// <param name="command">Command invoked by this job object</param>
/// <param name="name">Friendly name for the job object</param>
/// <param name="instanceId">InstanceId to be used for this job object</param>
/// <remarks>The InstanceId may need to be set to maintain job identity across
/// instances of the process.</remarks>
protected Job ( string command , string name , Guid instanceId )
2016-07-29 13:02:49 -07:00
: this ( command , name )
2016-03-30 14:20:52 -07:00
{
2016-08-02 13:29:09 -07:00
InstanceId = instanceId ;
2016-03-30 14:20:52 -07:00
}
static internal string GetCommandTextFromInvocationInfo ( InvocationInfo invocationInfo )
{
if ( invocationInfo == null )
{
return null ;
}
IScriptExtent scriptExtent = invocationInfo . ScriptPosition ;
if (( scriptExtent != null ) && ( scriptExtent . StartScriptPosition != null ) && ! string . IsNullOrWhiteSpace ( scriptExtent . StartScriptPosition . Line ))
{
2016-07-29 13:02:49 -07:00
Dbg . Assert ( scriptExtent . StartScriptPosition . ColumnNumber > 0 , "Column numbers start at 1" );
Dbg . Assert ( scriptExtent . StartScriptPosition . ColumnNumber <= scriptExtent . StartScriptPosition . Line . Length , "Column numbers are not greater than the length of a line" );
2016-03-30 14:20:52 -07:00
return scriptExtent . StartScriptPosition . Line . Substring ( scriptExtent . StartScriptPosition . ColumnNumber - 1 ). Trim ();
}
return invocationInfo . InvocationName ;
}
#endregion Constructor
#region Private Members
2016-07-29 13:02:49 -07:00
private ManualResetEvent _finished = new ManualResetEvent ( false );
private string _name ;
private IList < Job > _childJobs ;
2016-03-30 14:20:52 -07:00
internal readonly object syncObject = new object (); // object used for synchronization
//ISSUE: Should Result be public property
2016-07-29 13:02:49 -07:00
private PSDataCollection < PSStreamObject > _results = new PSDataCollection < PSStreamObject >();
private bool _resultsOwner = true ;
private PSDataCollection < ErrorRecord > _error = new PSDataCollection < ErrorRecord >();
private bool _errorOwner = true ;
private PSDataCollection < ProgressRecord > _progress = new PSDataCollection < ProgressRecord >();
private bool _progressOwner = true ;
private PSDataCollection < VerboseRecord > _verbose = new PSDataCollection < VerboseRecord >();
private bool _verboseOwner = true ;
private PSDataCollection < WarningRecord > _warning = new PSDataCollection < WarningRecord >();
private bool _warningOwner = true ;
private PSDataCollection < DebugRecord > _debug = new PSDataCollection < DebugRecord >();
private bool _debugOwner = true ;
private PSDataCollection < InformationRecord > _information = new PSDataCollection < InformationRecord >();
private bool _informationOwner = true ;
private PSDataCollection < PSObject > _output = new PSDataCollection < PSObject >();
private bool _outputOwner = true ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Static variable which is incremented to generate id
/// </summary>
2016-07-29 13:02:49 -07:00
private static int s_jobIdSeed = 0 ;
2016-03-30 14:20:52 -07:00
private string _jobTypeName = String . Empty ;
#endregion Private Members
#region Job Properties
/// <summary>
/// Command Invoked by this Job
/// </summary>
2016-08-02 13:29:09 -07:00
public string Command { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Status of the command execution.
/// </summary>
2016-08-02 13:29:09 -07:00
public JobStateInfo JobStateInfo { get ; private set ; } = new JobStateInfo ( JobState . NotStarted );
2016-03-30 14:20:52 -07:00
/// <summary>
/// Wait Handle which is signaled when job is finished.
/// This is set when state of the job is set to Completed,
/// Stopped or Failied.
/// </summary>
public WaitHandle Finished
{
get
{
lock ( this . syncObject )
{
2016-07-29 13:02:49 -07:00
if ( _finished != null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
return _finished ;
2016-03-30 14:20:52 -07:00
}
else
{
// Damage control mode:
// Somebody is trying to get Finished handle for an already disposed Job instance.
// Return an already triggered handle (disposed job is finished by definition).
// The newly created handle will not be disposed in a deterministic manner
// and in some circumstances can be mistaken for a handle leak.
return new ManualResetEvent ( true );
}
}
}
}
/// <summary>
/// unique identifier for this job
/// </summary>
2016-08-02 13:29:09 -07:00
public Guid InstanceId { get ; } = Guid . NewGuid ();
2016-03-30 14:20:52 -07:00
/// <summary>
/// Short identifier for this result which will be
/// recycled and used within a process
/// </summary>
2016-08-02 13:29:09 -07:00
public Int32 Id { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Name for identifying this job object
/// </summary>
public String Name
{
get
{
2016-07-29 13:02:49 -07:00
return _name ;
2016-03-30 14:20:52 -07:00
}
set
{
AssertNotDisposed ();
2016-07-29 13:02:49 -07:00
_name = value ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// List of child jobs contained within this job
/// </summary>
public IList < Job > ChildJobs
{
get
{
2016-07-29 13:02:49 -07:00
if ( _childJobs == null )
2016-03-30 14:20:52 -07:00
{
lock ( syncObject )
{
2016-07-29 13:02:49 -07:00
if ( _childJobs == null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_childJobs = new List < Job >();
2016-03-30 14:20:52 -07:00
}
}
}
2016-07-29 13:02:49 -07:00
return _childJobs ;
2016-03-30 14:20:52 -07:00
}
}
///<summary>
/// Success status of the command execution.
/// </summary>
public abstract String StatusMessage { get ; }
/// <summary>
/// Indicates that more data is available in this
/// result object for reading.
/// </summary>
2016-07-29 13:02:49 -07:00
public abstract bool HasMoreData { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Time job was started.
/// </summary>
2016-08-02 13:29:09 -07:00
public DateTime ? PSBeginTime { get ; protected set ; } = null ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Time job stopped.
/// </summary>
2016-08-02 13:29:09 -07:00
public DateTime ? PSEndTime { get ; protected set ; } = null ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Job type name.
/// </summary>
public string PSJobTypeName
{
get { return _jobTypeName ; }
protected internal set
{
_jobTypeName = ( value != null ) ? value : this . GetType (). ToString ();
}
}
#region results
/// <summary>
/// Result objects from this job. If this object is not a
/// leaf node (with no children), then this will
/// aggregate the results from all child jobs
/// </summary>
internal PSDataCollection < PSStreamObject > Results
{
get
{
2016-07-29 13:02:49 -07:00
return _results ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Results" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_resultsOwner = false ;
_results = value ;
2016-03-30 14:20:52 -07:00
}
}
}
/// <summary>
/// Indicates if a particular Job type uses the
/// internal results collection
/// </summary>
internal bool UsesResultsCollection { get ; set ; }
/// <summary>
/// Suppresses forwarding of job output into a cmdlet (like Receive-Job).
/// This flag modifies functionality of <see cref="WriteObject"/> method, so that it doesnt add output-processing to <see cref="Results"/> collection.
/// </summary>
2016-08-02 13:29:09 -07:00
internal bool SuppressOutputForwarding { get ; set ; }
2016-03-30 14:20:52 -07:00
internal virtual void WriteObject ( object outputObject )
{
PSObject pso = ( outputObject == null ) ? null : PSObject . AsPSObject ( outputObject );
this . Output . Add ( pso );
2016-08-02 13:29:09 -07:00
if (! SuppressOutputForwarding )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
this . Results . Add ( new PSStreamObject ( PSStreamObjectType . Output , pso ));
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// Allows propagating of terminating exceptions from remote "throw" statement
/// (normally / by default all remote errors are transformed into non-terminating errors
/// </summary>
2016-08-02 13:29:09 -07:00
internal bool PropagateThrows { get ; set ; }
2016-03-30 14:20:52 -07:00
private void WriteError ( Cmdlet cmdlet , ErrorRecord errorRecord )
{
if ( this . PropagateThrows )
{
Exception e = GetExceptionFromErrorRecord ( errorRecord );
if ( e != null )
throw e ;
}
errorRecord . PreserveInvocationInfoOnce = true ;
cmdlet . WriteError ( errorRecord );
}
private static Exception GetExceptionFromErrorRecord ( ErrorRecord errorRecord )
{
RuntimeException runtimeException = errorRecord . Exception as RuntimeException ;
if ( runtimeException == null )
2016-07-29 13:02:49 -07:00
return null ;
2016-03-30 14:20:52 -07:00
RemoteException remoteException = runtimeException as RemoteException ;
if ( remoteException == null )
return null ;
PSPropertyInfo wasThrownFromThrow =
remoteException . SerializedRemoteException . Properties [ "WasThrownFromThrowStatement" ];
2016-07-29 13:02:49 -07:00
if ( wasThrownFromThrow == null || !(( bool ) wasThrownFromThrow . Value ))
2016-03-30 14:20:52 -07:00
return null ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
runtimeException . WasThrownFromThrowStatement = true ;
return runtimeException ;
}
internal virtual void WriteError ( ErrorRecord errorRecord )
{
Error . Add ( errorRecord );
if ( PropagateThrows )
{
Exception exception = GetExceptionFromErrorRecord ( errorRecord );
if ( exception != null )
{
Results . Add ( new PSStreamObject ( PSStreamObjectType . Exception , exception ));
return ;
}
}
Results . Add ( new PSStreamObject ( PSStreamObjectType . Error , errorRecord ));
}
internal void WriteError ( ErrorRecord errorRecord , out Exception exceptionThrownOnCmdletThread )
{
this . Error . Add ( errorRecord );
this . InvokeCmdletMethodAndWaitForResults < object >(
delegate ( Cmdlet cmdlet )
{
this . WriteError ( cmdlet , errorRecord );
return null ;
},
out exceptionThrownOnCmdletThread );
}
internal virtual void WriteWarning ( string message )
{
this . Warning . Add ( new WarningRecord ( message ));
this . Results . Add ( new PSStreamObject ( PSStreamObjectType . Warning , message ));
}
internal virtual void WriteVerbose ( string message )
{
this . Verbose . Add ( new VerboseRecord ( message ));
2016-07-29 13:02:49 -07:00
this . Results . Add ( new PSStreamObject ( PSStreamObjectType . Verbose , message ));
2016-03-30 14:20:52 -07:00
}
internal virtual void WriteDebug ( string message )
{
this . Debug . Add ( new DebugRecord ( message ));
this . Results . Add ( new PSStreamObject ( PSStreamObjectType . Debug , message ));
}
internal virtual void WriteProgress ( ProgressRecord progressRecord )
{
2016-07-29 13:02:49 -07:00
if (( progressRecord . ParentActivityId == (- 1 )) && ( _parentActivityId != null ))
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
progressRecord = new ProgressRecord ( progressRecord ) { ParentActivityId = _parentActivityId . Value };
2016-03-30 14:20:52 -07:00
}
Progress . Add ( progressRecord );
Results . Add ( new PSStreamObject ( PSStreamObjectType . Progress , progressRecord ));
}
internal virtual void WriteInformation ( InformationRecord informationRecord )
{
Information . Add ( informationRecord );
Results . Add ( new PSStreamObject ( PSStreamObjectType . Information , informationRecord ));
}
2016-07-29 13:02:49 -07:00
private Lazy < int > _parentActivityId ;
2016-03-30 14:20:52 -07:00
internal void SetParentActivityIdGetter ( Func < int > parentActivityIdGetter )
{
Dbg . Assert ( parentActivityIdGetter != null , "Caller should verify parentActivityIdGetter != null" );
2016-07-29 13:02:49 -07:00
_parentActivityId = new Lazy < int >( parentActivityIdGetter );
2016-03-30 14:20:52 -07:00
}
internal bool ShouldContinue ( string query , string caption )
{
Exception exceptionThrownOnCmdletThread ;
return this . ShouldContinue ( query , caption , out exceptionThrownOnCmdletThread );
}
internal bool ShouldContinue ( string query , string caption , out Exception exceptionThrownOnCmdletThread )
{
bool methodResult = InvokeCmdletMethodAndWaitForResults (
cmdlet => cmdlet . ShouldContinue ( query , caption ),
out exceptionThrownOnCmdletThread );
return methodResult ;
}
internal virtual void NonblockingShouldProcess (
string verboseDescription ,
string verboseWarning ,
string caption )
{
InvokeCmdletMethodAndIgnoreResults (
2016-07-29 13:02:49 -07:00
delegate ( Cmdlet cmdlet )
2016-03-30 14:20:52 -07:00
{
ShouldProcessReason throwAwayProcessReason ;
cmdlet . ShouldProcess (
verboseDescription ,
verboseWarning ,
caption ,
out throwAwayProcessReason );
});
}
internal virtual bool ShouldProcess (
string verboseDescription ,
string verboseWarning ,
string caption ,
out ShouldProcessReason shouldProcessReason ,
out Exception exceptionThrownOnCmdletThread )
{
ShouldProcessReason closureSafeShouldProcessReason = ShouldProcessReason . None ;
bool methodResult = InvokeCmdletMethodAndWaitForResults (
cmdlet => cmdlet . ShouldProcess (
verboseDescription ,
verboseWarning ,
caption ,
out closureSafeShouldProcessReason ),
out exceptionThrownOnCmdletThread );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
shouldProcessReason = closureSafeShouldProcessReason ;
return methodResult ;
}
private void InvokeCmdletMethodAndIgnoreResults ( Action < Cmdlet > invokeCmdletMethod )
{
object resultsLock = new object ();
CmdletMethodInvoker < object > methodInvoker = new CmdletMethodInvoker < object >
2016-07-29 13:02:49 -07:00
{
Action = delegate ( Cmdlet cmdlet ) { invokeCmdletMethod ( cmdlet ); return null ; },
Finished = null ,
SyncObject = resultsLock
};
2016-03-30 14:20:52 -07:00
Results . Add ( new PSStreamObject ( PSStreamObjectType . BlockingError , methodInvoker ));
}
private T InvokeCmdletMethodAndWaitForResults < T >( Func < Cmdlet , T > invokeCmdletMethodAndReturnResult , out Exception exceptionThrownOnCmdletThread )
{
Dbg . Assert ( invokeCmdletMethodAndReturnResult != null , "Caller should verify invokeCmdletMethodAndReturnResult != null" );
T methodResult = default ( T );
Exception closureSafeExceptionThrownOnCmdletThread = null ;
object resultsLock = new object ();
using ( var gotResultEvent = new ManualResetEventSlim ( false ))
{
EventHandler < JobStateEventArgs > stateChangedEventHandler =
2016-07-29 13:02:49 -07:00
delegate ( object sender , JobStateEventArgs eventArgs )
2016-03-30 14:20:52 -07:00
{
if ( IsFinishedState ( eventArgs . JobStateInfo . State ) || eventArgs . JobStateInfo . State == JobState . Stopping )
{
lock ( resultsLock )
{
closureSafeExceptionThrownOnCmdletThread = new OperationCanceledException ();
}
gotResultEvent . Set ();
}
};
this . StateChanged += stateChangedEventHandler ;
Interlocked . MemoryBarrier ();
try
{
stateChangedEventHandler ( null , new JobStateEventArgs ( this . JobStateInfo ));
if (! gotResultEvent . IsSet )
{
this . SetJobState ( JobState . Blocked );
// addition to results column happens here
CmdletMethodInvoker < T > methodInvoker = new CmdletMethodInvoker < T >
2016-07-29 13:02:49 -07:00
{
Action = invokeCmdletMethodAndReturnResult ,
Finished = gotResultEvent ,
SyncObject = resultsLock
};
2016-03-30 14:20:52 -07:00
PSStreamObjectType objectType = PSStreamObjectType . ShouldMethod ;
if ( typeof ( T ) == typeof ( object ))
objectType = PSStreamObjectType . BlockingError ;
Results . Add ( new PSStreamObject ( objectType , methodInvoker ));
gotResultEvent . Wait ();
this . SetJobState ( JobState . Running );
lock ( resultsLock )
{
if ( closureSafeExceptionThrownOnCmdletThread == null ) // stateChangedEventHandler didn't set the results? = ok to clobber results?
{
closureSafeExceptionThrownOnCmdletThread = methodInvoker . ExceptionThrownOnCmdletThread ;
methodResult = methodInvoker . MethodResult ;
}
}
}
}
finally
{
this . StateChanged -= stateChangedEventHandler ;
}
}
lock ( resultsLock )
{
exceptionThrownOnCmdletThread = closureSafeExceptionThrownOnCmdletThread ;
return methodResult ;
}
}
internal virtual void ForwardAvailableResultsToCmdlet ( Cmdlet cmdlet )
{
2016-07-29 13:02:49 -07:00
foreach ( PSStreamObject obj in Results . ReadAll ())
2016-03-30 14:20:52 -07:00
{
obj . WriteStreamObject ( cmdlet );
}
}
internal virtual void ForwardAllResultsToCmdlet ( Cmdlet cmdlet )
{
foreach ( PSStreamObject obj in this . Results )
{
obj . WriteStreamObject ( cmdlet );
}
}
/// <summary>
/// This method is introduce for delaying the loading of streams
/// for a particular job.
/// </summary>
protected virtual void DoLoadJobStreams ()
{
}
/// <summary>
/// Unloads job streams information. Enables jobs to
/// clear stream information from memory
/// </summary>
protected virtual void DoUnloadJobStreams ()
{
}
/// <summary>
/// Load the required job streams
/// </summary>
public void LoadJobStreams ()
{
if ( _jobStreamsLoaded ) return ;
lock ( syncObject )
{
if ( _jobStreamsLoaded ) return ;
_jobStreamsLoaded = true ;
}
try
{
DoLoadJobStreams ();
}
2016-07-29 13:02:49 -07:00
catch ( Exception e )
{
2016-03-30 14:20:52 -07:00
// third party call-out for platform API
// Therefore it is fine to eat the exception
// here
using ( PowerShellTraceSource tracer = PowerShellTraceSourceFactory . GetTraceSource ())
{
tracer . TraceException ( e );
}
}
}
private bool _jobStreamsLoaded ;
/// <summary>
/// Unload the required job streams
/// </summary>
public void UnloadJobStreams ()
{
if (! _jobStreamsLoaded ) return ;
2016-07-29 13:02:49 -07:00
lock ( syncObject )
2016-03-30 14:20:52 -07:00
{
if (! _jobStreamsLoaded ) return ;
_jobStreamsLoaded = false ;
}
try
{
DoUnloadJobStreams ();
}
2016-07-29 13:02:49 -07:00
catch ( Exception e )
2016-03-30 14:20:52 -07:00
{
// third party call-out for platform API
// Therefore it is fine to eat the exception
// here
using ( PowerShellTraceSource tracer = PowerShellTraceSourceFactory . GetTraceSource ())
{
tracer . TraceException ( e );
}
}
}
/// <summary>
/// Gets or sets the output buffer. Output of job is written
/// into this buffer.
/// </summary>
/// <exception cref="ArgumentNullException">
/// Cannot set to a null value.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < PSObject > Output
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _output ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Output" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_outputOwner = false ;
_output = value ;
2016-03-30 14:20:52 -07:00
_jobStreamsLoaded = true ;
}
}
}
/// <summary>
/// Gets or sets the error buffer. Errors of job are written
/// into this buffer.
/// </summary>
/// <exception cref="ArgumentNullException">
/// Cannot set to a null value.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < ErrorRecord > Error
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _error ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Error" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_errorOwner = false ;
_error = value ;
2016-03-30 14:20:52 -07:00
_jobStreamsLoaded = true ;
}
}
}
/// <summary>
/// Gets or sets the progress buffer. Progress of job is written
/// into this buffer.
/// </summary>
/// <exception cref="ArgumentNullException">
/// Cannot set to a null value.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < ProgressRecord > Progress
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _progress ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Progress" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_progressOwner = false ;
_progress = value ;
2016-03-30 14:20:52 -07:00
_jobStreamsLoaded = true ;
}
}
}
/// <summary>
/// Gets or sets the verbose buffer. Verbose output of job is written to
/// this stream.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < VerboseRecord > Verbose
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _verbose ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Verbose" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_verboseOwner = false ;
_verbose = value ;
2016-03-30 14:20:52 -07:00
_jobStreamsLoaded = true ;
}
}
}
/// <summary>
/// Gets or sets the debug buffer. Debug output of Job is written
/// to this buffer.
/// <exception cref="ArgumentNullException">
/// Cannot set to a null value.
/// </exception>
/// </summary>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < DebugRecord > Debug
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _debug ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Debug" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_debugOwner = false ;
_debug = value ;
2016-03-30 14:20:52 -07:00
}
}
}
/// <summary>
/// Gets or sets the warning buffer. Warnings of job are written to
/// this buffer
/// </summary>
/// <exception cref="ArgumentNullException">
/// Cannot set to a null value.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < WarningRecord > Warning
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _warning ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Warning" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_warningOwner = false ;
_warning = value ;
2016-03-30 14:20:52 -07:00
_jobStreamsLoaded = true ;
}
}
}
/// <summary>
/// Gets or sets the information buffer. Information records of job are written to
/// this buffer
/// </summary>
/// <exception cref="ArgumentNullException">
/// Cannot set to a null value.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public PSDataCollection < InformationRecord > Information
{
get
{
LoadJobStreams (); // for delayed loading
2016-07-29 13:02:49 -07:00
return _information ;
2016-03-30 14:20:52 -07:00
}
set
{
if ( null == value )
{
throw PSTraceSource . NewArgumentNullException ( "Information" );
}
lock ( syncObject )
{
AssertChangesAreAccepted ();
2016-07-29 13:02:49 -07:00
_informationOwner = false ;
_information = value ;
2016-03-30 14:20:52 -07:00
_jobStreamsLoaded = true ;
}
}
}
/// <summary>
/// Indicates a location where this job is running
/// </summary>
2016-07-29 13:02:49 -07:00
public abstract String Location { get ; }
2016-03-30 14:20:52 -07:00
#endregion results
#region Connect / Disconnect
/// <summary>
/// Returns boolean indicating whether the underlying
/// transport for the job (or child jobs) supports
/// connect/disconnect semantics.
/// </summary>
internal virtual bool CanDisconnect
{
get { return false ; }
}
/// <summary>
/// Returns runspaces associated with the Job, including
/// child jobs.
/// </summary>
/// <returns>IEnumerable of RemoteRunspaces</returns>
internal virtual IEnumerable < RemoteRunspace > GetRunspaces ()
{
return null ;
}
#endregion
#endregion Job Properties
#region Job State and State Change Event
/// <summary>
/// Event raised when state of the job changes
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public event EventHandler < JobStateEventArgs > StateChanged ;
/// <summary>
/// Sets Job State.
/// </summary>
/// <param name="state">
/// New State of Job
/// </param>
protected void SetJobState ( JobState state )
{
AssertNotDisposed ();
SetJobState ( state , null );
}
/// <summary>
/// Sets Job State.
/// </summary>
/// <param name="state">
/// New State of Job
/// </param>
/// <param name="reason">
/// Reason associated with the state.
/// </param>
internal void SetJobState ( JobState state , Exception reason )
{
using ( PowerShellTraceSource tracer = PowerShellTraceSourceFactory . GetTraceSource ())
{
AssertNotDisposed ();
bool alldone = false ;
2016-08-02 13:29:09 -07:00
JobStateInfo previousState = JobStateInfo ;
2016-03-30 14:20:52 -07:00
lock ( syncObject )
{
2016-08-02 13:29:09 -07:00
JobStateInfo = new JobStateInfo ( state , reason );
2016-03-30 14:20:52 -07:00
if ( state == JobState . Running )
{
// BeginTime is set only once.
if ( PSBeginTime == null )
{
PSBeginTime = DateTime . Now ;
}
}
else if ( IsFinishedState ( state ))
{
alldone = true ;
// EndTime is set only once.
if ( PSEndTime == null )
{
PSEndTime = DateTime . Now ;
}
}
}
if ( alldone )
{
CloseAllStreams ();
if ( _processingOutput )
{
try
{
// Still marked as processing output. Send final state changed.
HandleOutputProcessingStateChanged ( this , new OutputProcessingStateEventArgs ( false ));
}
catch ( Exception e )
{
CommandProcessorBase . CheckForSevereException ( e );
}
}
}
2016-07-29 13:02:49 -07:00
#pragma warning disable 56500
2016-03-30 14:20:52 -07:00
//Exception rasied in the eventhandler are not error in job.
//silently ignore them.
try
{
tracer . WriteMessage ( "Job" , "SetJobState" , Guid . Empty , this , "Invoking StateChanged event" , null );
2016-08-02 13:29:09 -07:00
StateChanged . SafeInvoke ( this , new JobStateEventArgs ( JobStateInfo . Clone (), previousState ));
2016-03-30 14:20:52 -07:00
}
catch ( Exception exception ) // ignore non-severe exceptions
{
tracer . WriteMessage ( "Job" , "SetJobState" , Guid . Empty , this ,
"Some Job StateChange event handler threw an unhandled exception." , null );
tracer . TraceException ( exception );
CommandProcessorBase . CheckForSevereException ( exception );
}
// finished needs to be set after StateChanged event
// has been raised
if ( alldone )
{
lock ( syncObject )
{
2016-07-29 13:02:49 -07:00
if ( _finished != null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_finished . Set ();
2016-03-30 14:20:52 -07:00
}
}
}
#pragma warning restore 56500
}
}
#endregion Job State and State Change Event
#region Job Public Methods
/// <summary>
/// Stop this job object. If job contains child job, this should
/// stop child job objects also.
/// </summary>
public abstract void StopJob ();
#endregion Job Public Methods
#region Private / Internal Methods
/// <summary>
/// Returns the items in results collection
/// after clearing up all the internal
/// structures
/// </summary>
/// <returns>collection of stream objects</returns>
internal Collection < PSStreamObject > ReadAll ()
{
Output . Clear ();
Error . Clear ();
Debug . Clear ();
Warning . Clear ();
Verbose . Clear ();
Progress . Clear ();
return Results . ReadAll ();
}
/// <summary>
/// Helper function to check if job is finished
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
internal bool IsFinishedState ( JobState state )
{
lock ( syncObject )
{
return ( state == JobState . Completed || state == JobState . Failed || state == JobState . Stopped );
}
}
internal bool IsPersistentState ( JobState state )
{
lock ( syncObject )
{
return ( IsFinishedState ( state ) || state == JobState . Disconnected || state == JobState . Suspended );
}
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Checks if the current instance can accept changes like
/// changing one of the properties like Output, Error etc.
/// If changes are not allowed, throws an exception.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Powershell instance cannot be changed in its
/// current state.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
private void AssertChangesAreAccepted ()
{
AssertNotDisposed ();
lock ( syncObject )
{
if ( JobStateInfo . State == JobState . Running )
{
throw new InvalidJobStateException ( JobState . Running );
}
}
}
/// <summary>
/// Automatically generate a job name if the user
/// does not supply one
/// </summary>
/// <returns>auto generated job name</returns>
/// <remarks>Since the user can script/program against the
/// job name, the auto generated name will not be
/// localizable</remarks>
protected String AutoGenerateJobName ()
{
2016-08-02 13:29:09 -07:00
return "Job" + Id . ToString ( System . Globalization . NumberFormatInfo . InvariantInfo );
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Checks if the current powershell instance is disposed.
/// If disposed, throws ObjectDisposedException.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// Object is disposed.
/// </exception>
internal void AssertNotDisposed ()
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewObjectDisposedException ( "PSJob" );
}
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// A helper method to close all the streams.
/// </summary>
internal void CloseAllStreams ()
{
2016-07-29 13:02:49 -07:00
if ( _resultsOwner ) _results . Complete ();
if ( _outputOwner ) _output . Complete ();
if ( _errorOwner ) _error . Complete ();
if ( _progressOwner ) _progress . Complete ();
if ( _verboseOwner ) _verbose . Complete ();
if ( _warningOwner ) _warning . Complete ();
if ( _debugOwner ) _debug . Complete ();
if ( _informationOwner ) _information . Complete ();
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Gets the job for the specified location
/// </summary>
/// <param name="location">location to filter on</param>
/// <returns>collection of jobs</returns>
internal List < Job > GetJobsForLocation ( String location )
{
List < Job > returnJobList = new List < Job >();
foreach ( Job job in ChildJobs )
{
if ( String . Equals ( job . Location , location , StringComparison . OrdinalIgnoreCase ))
{
returnJobList . Add ( job );
}
}
return returnJobList ;
} // GetJobsForLocation
#endregion Private / Internal Methods
#region IDisposable Members
/// <summary>
/// Dispose all managed resources. This will suppress finalizer on the object from getting called by
/// calling System.GC.SuppressFinalize(this).
/// </summary>
public void Dispose ()
{
Dispose ( true );
// To prevent derived types with finalizers from having to re-implement System.IDisposable to call it,
// unsealed types without finalizers should still call SuppressFinalize.
System . GC . SuppressFinalize ( this );
}
/// <summary>
/// Release all the resources.
/// </summary>
/// <param name="disposing">
/// if true, release all the managed objects.
/// </param>
protected virtual void Dispose ( bool disposing )
{
if ( disposing )
{
2016-07-29 13:02:49 -07:00
if (! _isDisposed )
2016-03-30 14:20:52 -07:00
{
CloseAllStreams ();
// release the WaitHandle
lock ( syncObject )
{
2016-07-29 13:02:49 -07:00
if ( null != _finished )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_finished . Dispose ();
_finished = null ;
2016-03-30 14:20:52 -07:00
}
}
// Only dispose the collections if we've created them...
2016-07-29 13:02:49 -07:00
if ( _resultsOwner ) _results . Dispose ();
if ( _outputOwner ) _output . Dispose ();
if ( _errorOwner ) _error . Dispose ();
if ( _debugOwner ) _debug . Dispose ();
if ( _informationOwner ) _information . Dispose ();
if ( _verboseOwner ) _verbose . Dispose ();
if ( _warningOwner ) _warning . Dispose ();
if ( _progressOwner ) _progress . Dispose ();
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
_isDisposed = true ;
2016-03-30 14:20:52 -07:00
}
}
}
2016-07-29 13:02:49 -07:00
private bool _isDisposed ;
2016-03-30 14:20:52 -07:00
#endregion IDisposable Members
#region MonitorOutputProcessing
internal event EventHandler < OutputProcessingStateEventArgs > OutputProcessingStateChanged ;
private bool _processingOutput ;
/// <summary>
/// MonitorOutputProcessing
/// </summary>
internal bool MonitorOutputProcessing
{
get ;
set ;
}
internal void SetMonitorOutputProcessing ( IOutputProcessingState outputProcessingState )
{
if ( outputProcessingState != null )
{
outputProcessingState . OutputProcessingStateChanged += HandleOutputProcessingStateChanged ;
}
}
internal void RemoveMonitorOutputProcessing ( IOutputProcessingState outputProcessingState )
{
if ( outputProcessingState != null )
{
outputProcessingState . OutputProcessingStateChanged -= HandleOutputProcessingStateChanged ;
}
}
private void HandleOutputProcessingStateChanged ( object sender , OutputProcessingStateEventArgs e )
{
_processingOutput = e . ProcessingOutput ;
OutputProcessingStateChanged . SafeInvoke < OutputProcessingStateEventArgs >( this , e );
}
#endregion
} //Job
/// <summary>
/// Top level job object for remoting. This contains multiple child job
/// objects. Each child job object invokes command on one remote machine.
/// </summary>
/// <remarks>
/// Not removing the prefix "PS" as this signifies powershell specific remoting job
/// </remarks>
internal class PSRemotingJob : Job
{
#region Internal Constructors
/// <summary>
/// Internal constructor for initializing PSRemotingJob using
/// computer names
/// </summary>
/// <param name="computerNames">names of computers for
/// which the job object is being created</param>
/// <param name="computerNameHelpers">list of helper objects
/// corresponding to the computer names
/// </param>
/// <param name="remoteCommand">remote command corresponding to this
/// job object</param>
/// <param name="name"> a friendly name for the job object
/// </param>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal PSRemotingJob ( String [] computerNames ,
List < IThrottleOperation > computerNameHelpers , String remoteCommand , string name )
:
2016-07-29 13:02:49 -07:00
this ( computerNames , computerNameHelpers , remoteCommand , 0 , name )
{ }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Internal constructor for initializing job using
/// PSSession objects
/// </summary>
/// <param name="remoteRunspaceInfos">array of runspace info
/// objects on which the remote command is executed</param>
/// <param name="runspaceHelpers">List of helper objects for the
/// runspaces</param>
/// <param name="remoteCommand"> remote command corresponding to this
/// job object</param>
/// <param name="name">a friendly name for the job object
/// </param>
internal PSRemotingJob ( PSSession [] remoteRunspaceInfos ,
List < IThrottleOperation > runspaceHelpers , String remoteCommand , string name )
:
2016-07-29 13:02:49 -07:00
this ( remoteRunspaceInfos , runspaceHelpers , remoteCommand , 0 , name )
{ }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Internal constructor for initializing PSRemotingJob using
/// computer names
/// </summary>
/// <param name="computerNames">names of computers for
/// which the result object is being created</param>
/// <param name="computerNameHelpers">list of helper objects
/// corresponding to the computer names
/// </param>
/// <param name="remoteCommand">remote command corresponding to this
/// result object</param>
/// <param name="throttleLimit">Throttle limit to use</param>
/// <param name="name">a friendly name for the job object</param>
internal PSRemotingJob ( String [] computerNames ,
List < IThrottleOperation > computerNameHelpers , String remoteCommand ,
int throttleLimit , string name )
2016-07-29 13:02:49 -07:00
: base ( remoteCommand , name )
2016-03-30 14:20:52 -07:00
{
// Create child jobs for each object in the list
foreach ( ExecutionCmdletHelperComputerName helper in computerNameHelpers )
{
//Create Child Job and Register for its StateChanged Event
2016-07-29 13:02:49 -07:00
PSRemotingChildJob childJob = new PSRemotingChildJob ( remoteCommand ,
helper , _throttleManager );
2016-03-30 14:20:52 -07:00
childJob . StateChanged += new EventHandler < JobStateEventArgs >( HandleChildJobStateChanged );
childJob . JobUnblocked += new EventHandler ( HandleJobUnblocked );
//Add this job to list of childjobs
ChildJobs . Add ( childJob );
}
CommonInit ( throttleLimit , computerNameHelpers );
}
/// <summary>
/// Internal constructor for initializing job using
/// PSSession objects
/// </summary>
/// <param name="remoteRunspaceInfos">array of runspace info
/// objects on which the remote command is executed</param>
/// <param name="runspaceHelpers">List of helper objects for the
/// runspaces</param>
/// <param name="remoteCommand"> remote command corresponding to this
/// result object</param>
/// <param name="throttleLimit">throttle limit to use</param>
/// <param name="name"></param>
internal PSRemotingJob ( PSSession [] remoteRunspaceInfos ,
List < IThrottleOperation > runspaceHelpers , String remoteCommand ,
int throttleLimit , string name )
2016-07-29 13:02:49 -07:00
: base ( remoteCommand , name )
2016-03-30 14:20:52 -07:00
{
// Create chiild jobs for each object in the list
for ( int i = 0 ; i < remoteRunspaceInfos . Length ; i ++)
{
ExecutionCmdletHelperRunspace helper = ( ExecutionCmdletHelperRunspace ) runspaceHelpers [ i ];
//Create Child Job object and Register for its state changed event
2016-07-29 13:02:49 -07:00
PSRemotingChildJob job = new PSRemotingChildJob ( remoteCommand ,
helper , _throttleManager );
2016-03-30 14:20:52 -07:00
job . StateChanged += new EventHandler < JobStateEventArgs >( HandleChildJobStateChanged );
job . JobUnblocked += new EventHandler ( HandleJobUnblocked );
//Add the child job to list of child jobs
ChildJobs . Add ( job );
}
CommonInit ( throttleLimit , runspaceHelpers );
}
/// <summary>
/// Creates a job object and child jobs for each disconnected pipeline/runspace
/// provided in the list of ExecutionCmdletHelperRunspace items. The runspace
/// object must have a remote running command that can be connected to.
/// Use Connect() method to transition to the connected state.
/// </summary>
/// <param name="helpers">List of DisconnectedJobOperation objects with disconnected pipelines.</param>
/// <param name="throttleLimit">Throttle limit value.</param>
/// <param name="name">Job name.</param>
/// <param name="aggregateResults">Aggregate results.</param>
2016-07-29 13:02:49 -07:00
internal PSRemotingJob ( List < IThrottleOperation > helpers ,
2016-03-30 14:20:52 -07:00
int throttleLimit , string name , bool aggregateResults )
: base ( string . Empty , name )
{
// All pipeline objects must be in "disconnected" state and associated to running
// remote commands. Once the jobs are connected they can be stopped using the
// ExecutionCmdletHelperRunspace object and ThrottleManager.
foreach ( ExecutionCmdletHelper helper in helpers )
{
2016-07-29 13:02:49 -07:00
PSRemotingChildJob job = new PSRemotingChildJob ( helper , _throttleManager , aggregateResults );
2016-03-30 14:20:52 -07:00
job . StateChanged += new EventHandler < JobStateEventArgs >( HandleChildJobStateChanged );
job . JobUnblocked += new EventHandler ( HandleJobUnblocked );
ChildJobs . Add ( job );
}
// Since no results are produced by any streams. We should
// close all the streams.
base . CloseAllStreams ();
// Set status to "disconnected".
SetJobState ( JobState . Disconnected );
// Submit the disconnected operation helpers to the throttle manager
2016-07-29 13:02:49 -07:00
_throttleManager . ThrottleLimit = throttleLimit ;
_throttleManager . SubmitOperations ( helpers );
_throttleManager . EndSubmitOperations ();
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Default constructor
/// </summary>
protected PSRemotingJob () { }
/// <summary>
/// Initialization common to both constructors
/// </summary>
2016-07-29 13:02:49 -07:00
private void CommonInit ( int throttleLimit , List < IThrottleOperation > helpers )
2016-03-30 14:20:52 -07:00
{
//Since no results are produced by any streams. We should
//close all the streams
base . CloseAllStreams ();
// set status to "in progress"
SetJobState ( JobState . Running );
// submit operations to the throttle manager
2016-07-29 13:02:49 -07:00
_throttleManager . ThrottleLimit = throttleLimit ;
_throttleManager . SubmitOperations ( helpers );
_throttleManager . EndSubmitOperations ();
2016-03-30 14:20:52 -07:00
}
#endregion Internal Constructors
#region internal methods
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Get entity result for the specified computer
/// </summary>
/// <param name="computerName">computername for which entity
/// result is required</param>
/// <returns>entity result</returns>
internal List < Job > GetJobsForComputer ( String computerName )
{
List < Job > returnJobList = new List < Job >();
foreach ( Job j in ChildJobs )
{
PSRemotingChildJob child = j as PSRemotingChildJob ;
if ( j == null ) continue ;
2016-07-29 13:02:49 -07:00
if ( String . Equals ( child . Runspace . ConnectionInfo . ComputerName , computerName ,
2016-03-30 14:20:52 -07:00
StringComparison . OrdinalIgnoreCase ))
{
returnJobList . Add ( child );
}
}
return returnJobList ;
} // GetResultForComputer
/// <summary>
/// Get entity result for the specified runspace
/// </summary>
/// <param name="runspace">runspace for which entity
/// result is required</param>
/// <returns>entity result</returns>
internal List < Job > GetJobsForRunspace ( PSSession runspace )
{
List < Job > returnJobList = new List < Job >();
foreach ( Job j in ChildJobs )
{
PSRemotingChildJob child = j as PSRemotingChildJob ;
if ( j == null ) continue ;
if ( child . Runspace . InstanceId . Equals ( runspace . InstanceId ))
{
returnJobList . Add ( child );
}
}
return returnJobList ;
} // GetResultForRunspace
/// <summary>
/// Get entity result for the specified helper object
/// </summary>
/// <param name="operation">helper for which entity
/// result is required</param>
/// <returns>entity result</returns>
internal List < Job > GetJobsForOperation ( IThrottleOperation operation )
{
List < Job > returnJobList = new List < Job >();
ExecutionCmdletHelper helper = operation as ExecutionCmdletHelper ;
foreach ( Job j in ChildJobs )
{
PSRemotingChildJob child = j as PSRemotingChildJob ;
if ( j == null ) continue ;
if ( child . Helper . Equals ( helper ))
{
returnJobList . Add ( child );
}
}
return returnJobList ;
} // GetResultForHelper
#endregion internal methods
#region Connection Support
/// <summary>
/// Connect all child jobs if they are in a disconnected state.
/// </summary>
internal void ConnectJobs ()
{
// Create connect operation objects for each child job object to connect.
List < IThrottleOperation > connectJobOperations = new List < IThrottleOperation >();
foreach ( PSRemotingChildJob childJob in ChildJobs )
{
if ( childJob . JobStateInfo . State == JobState . Disconnected )
{
connectJobOperations . Add ( new ConnectJobOperation ( childJob ));
}
}
if ( connectJobOperations . Count == 0 )
{
return ;
}
// Submit the connect job operation.
// Return only after the connect operation completes.
SubmitAndWaitForConnect ( connectJobOperations );
}
/// <summary>
/// Connect a single child job associated with the provided runspace.
/// </summary>
/// <param name="runspaceInstanceId">Runspace instance id for child job.</param>
internal void ConnectJob ( Guid runspaceInstanceId )
{
List < IThrottleOperation > connectJobOperations = new List < IThrottleOperation >();
PSRemotingChildJob childJob = FindDisconnectedChildJob ( runspaceInstanceId );
if ( childJob != null )
{
connectJobOperations . Add ( new ConnectJobOperation ( childJob ));
}
if ( connectJobOperations . Count == 0 )
{
return ;
}
// Submit the connect job operation.
// Return only after the connect operation completes.
SubmitAndWaitForConnect ( connectJobOperations );
}
private void SubmitAndWaitForConnect ( List < IThrottleOperation > connectJobOperations )
{
using ( ThrottleManager connectThrottleManager = new ThrottleManager ())
{
using ( ManualResetEvent connectResult = new ManualResetEvent ( false ))
{
EventHandler < EventArgs > throttleCompleteEventHandler =
2016-07-29 13:02:49 -07:00
delegate ( object sender , EventArgs eventArgs )
2016-03-30 14:20:52 -07:00
{
connectResult . Set ();
};
connectThrottleManager . ThrottleComplete += throttleCompleteEventHandler ;
try
{
connectThrottleManager . ThrottleLimit = 0 ;
connectThrottleManager . SubmitOperations ( connectJobOperations );
connectThrottleManager . EndSubmitOperations ();
connectResult . WaitOne ();
}
finally
{
connectThrottleManager . ThrottleComplete -= throttleCompleteEventHandler ;
}
}
}
}
/// <summary>
/// Simple throttle operation class for connecting jobs.
/// </summary>
private class ConnectJobOperation : IThrottleOperation
{
2016-07-29 13:02:49 -07:00
private PSRemotingChildJob _psRemoteChildJob ;
2016-03-30 14:20:52 -07:00
internal ConnectJobOperation ( PSRemotingChildJob job )
{
2016-07-29 13:02:49 -07:00
_psRemoteChildJob = job ;
_psRemoteChildJob . StateChanged += ChildJobStateChangedHandler ;
2016-03-30 14:20:52 -07:00
}
internal override void StartOperation ()
{
bool startedSuccessfully = true ;
try
{
2016-07-29 13:02:49 -07:00
_psRemoteChildJob . ConnectAsync ();
2016-03-30 14:20:52 -07:00
}
catch ( InvalidJobStateException e )
{
startedSuccessfully = false ;
2016-07-29 13:02:49 -07:00
string msg = StringUtil . Format ( RemotingErrorIdStrings . JobConnectFailed , _psRemoteChildJob . Name );
2016-03-30 14:20:52 -07:00
Exception reason = new RuntimeException ( msg , e );
2016-07-29 13:02:49 -07:00
ErrorRecord errorRecord = new ErrorRecord ( reason , "PSJobConnectFailed" , ErrorCategory . InvalidOperation , _psRemoteChildJob );
_psRemoteChildJob . WriteError ( errorRecord );
2016-03-30 14:20:52 -07:00
}
if (! startedSuccessfully )
{
RemoveEventCallback ();
SendStartComplete ();
}
}
internal override void StopOperation ()
{
RemoveEventCallback ();
// Cannot stop a connect attempt.
OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs ();
operationStateEventArgs . OperationState = OperationState . StopComplete ;
OperationComplete . SafeInvoke ( this , operationStateEventArgs );
}
internal override event EventHandler < OperationStateEventArgs > OperationComplete ;
private void ChildJobStateChangedHandler ( object sender , JobStateEventArgs eArgs )
{
if ( eArgs . JobStateInfo . State == JobState . Disconnected )
{
return ;
}
RemoveEventCallback ();
SendStartComplete ();
}
private void SendStartComplete ()
{
OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs ();
operationStateEventArgs . OperationState = OperationState . StartComplete ;
OperationComplete . SafeInvoke ( this , operationStateEventArgs );
}
private void RemoveEventCallback ()
{
2016-07-29 13:02:49 -07:00
_psRemoteChildJob . StateChanged -= ChildJobStateChangedHandler ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// Finds the disconnected child job associated with this runspace and returns
/// the PowerShell object that is remotely executing the command.
/// </summary>
/// <param name="runspaceInstanceId">Runspace instance Id.</param>
/// <returns>Associated PowerShell object.</returns>
internal PowerShell GetAssociatedPowerShellObject ( Guid runspaceInstanceId )
{
PowerShell ps = null ;
PSRemotingChildJob childJob = FindDisconnectedChildJob ( runspaceInstanceId );
if ( childJob != null )
{
ps = childJob . GetPowerShell ();
}
return ps ;
}
/// <summary>
/// Helper method to find a disconnected child job associated with
/// a given runspace.
/// </summary>
/// <param name="runspaceInstanceId">Runspace Id.</param>
/// <returns>PSRemotingChildJob object.</returns>
private PSRemotingChildJob FindDisconnectedChildJob ( Guid runspaceInstanceId )
{
PSRemotingChildJob rtnJob = null ;
foreach ( PSRemotingChildJob childJob in this . ChildJobs )
{
if (( childJob . Runspace . InstanceId . Equals ( runspaceInstanceId )) &&
( childJob . JobStateInfo . State == JobState . Disconnected ))
{
rtnJob = childJob ;
break ;
}
}
return rtnJob ;
}
/// <summary>
/// Internal method to stop a job without first connecting it if it is in
/// a disconnected state. This supports Receive-PSSession where it abandons
/// a currently running/disconnected job when user selects -OutTarget Host.
/// </summary>
internal void InternalStopJob ()
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed || _stopIsCalled || IsFinishedState ( JobStateInfo . State ))
2016-03-30 14:20:52 -07:00
{
return ;
}
lock ( _syncObject )
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed || _stopIsCalled || IsFinishedState ( JobStateInfo . State ))
2016-03-30 14:20:52 -07:00
{
return ;
}
_stopIsCalled = true ;
}
2016-07-29 13:02:49 -07:00
_throttleManager . StopAllOperations ();
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
Finished . WaitOne ();
2016-03-30 14:20:52 -07:00
}
#endregion
2016-07-29 13:02:49 -07:00
private bool _moreData = true ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// indicates if more data is available
/// </summary>
/// <remarks>
/// This has more data if any of the child jobs have more data.
/// </remarks>
public override bool HasMoreData
{
get
{
// moreData is initially set to true, and it
// will remain so until the async result
// object has completed execution.
2016-07-29 13:02:49 -07:00
if ( _moreData && IsFinishedState ( JobStateInfo . State ))
2016-03-30 14:20:52 -07:00
{
bool atleastOneChildHasMoreData = false ;
for ( int i = 0 ; i < ChildJobs . Count ; i ++)
{
if ( ChildJobs [ i ]. HasMoreData )
{
atleastOneChildHasMoreData = true ;
break ;
}
}
2016-07-29 13:02:49 -07:00
_moreData = atleastOneChildHasMoreData ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
return _moreData ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
}
2016-03-30 14:20:52 -07:00
private bool _stopIsCalled = false ;
/// <summary>
/// Stop Job
/// </summary>
public override void StopJob ()
{
// If the job is in a disconnected state then try to connect it
// so that it can be stopped on the server.
if ( JobStateInfo . State == JobState . Disconnected )
{
bool ConnectSuccessful ;
try
{
ConnectJobs ();
ConnectSuccessful = true ;
}
catch ( InvalidRunspaceStateException )
{
ConnectSuccessful = false ;
}
catch ( PSRemotingTransportException )
{
ConnectSuccessful = false ;
}
catch ( PSInvalidOperationException )
{
ConnectSuccessful = false ;
}
if (! ConnectSuccessful && this . Error . IsOpen )
{
string msg = StringUtil . Format ( RemotingErrorIdStrings . StopJobNotConnected , this . Name );
Exception reason = new RuntimeException ( msg );
ErrorRecord errorRecord = new ErrorRecord ( reason , "StopJobCannotConnectToServer" ,
ErrorCategory . InvalidOperation , this );
WriteError ( errorRecord );
return ;
}
}
InternalStopJob ();
}
2016-07-29 13:02:49 -07:00
private string _statusMessage ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Message indicating status of the job
/// </summary>
public override string StatusMessage
{
get
{
2016-07-29 13:02:49 -07:00
return _statusMessage ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// used by Invoke-Command cmdlet to show/hide computername property value.
/// Format and Output has capability to understand RemoteObjects and this property lets
/// Format and Output decide whether to show/hide computername.
/// Default is true.
/// </summary>
internal bool HideComputerName
{
2016-07-29 13:02:49 -07:00
get { return _hideComputerName ; }
2016-03-30 14:20:52 -07:00
set
{
2016-07-29 13:02:49 -07:00
_hideComputerName = value ;
2016-03-30 14:20:52 -07:00
foreach ( Job job in this . ChildJobs )
{
PSRemotingChildJob rJob = job as PSRemotingChildJob ;
if ( null != rJob )
{
rJob . HideComputerName = value ;
}
}
}
}
2016-07-29 13:02:49 -07:00
private bool _hideComputerName = true ;
2016-03-30 14:20:52 -07:00
//ISSUE: Implement StatusMessage
/// <summary>
/// Checks the status of remote command execution
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void SetStatusMessage ()
{
2016-07-29 13:02:49 -07:00
_statusMessage = "test" ;
2016-03-30 14:20:52 -07:00
// bool localErrors = false; // if local errors are present
// bool setFinished = false; // if finished needs to be set
// statusMessage = "OK";
// lock (syncObject)
// {
// if (finishedCount == ChildJobs.Count)
// {
// //ISSUE: Change this code to look in to child jobs for exception
// if (errors.Count > 0)
// {
// statusMessage = "LocalErrors";
// localErrors = true;
// }
// // check for status of remote command
// for (int i = 0; i < ChildJobs.Count; i++)
// {
// PSRemotingChildJob childJob = ChildJobs[i] as PSRemotingChildJob;
// if (childJob == null) continue;
// if (childJob.ContainsErrors)
// {
// if (localErrors)
// {
// statusMessage = "LocalAndRemoteErrors";
// }
// else
// {
// statusMessage = "RemoteErrors";
// }
// break;
// }
// } // for (int ...
// setFinished = true;
// } // if (finishedCount ...
// } // lock...
} // SetStatusMessage
#region finish logic
//This variable is set to true if atleast one child job failed.
2016-07-29 13:02:49 -07:00
private bool _atleastOneChildJobFailed = false ;
2016-03-30 14:20:52 -07:00
// count of number of child jobs which have finished
2016-07-29 13:02:49 -07:00
private int _finishedChildJobsCount = 0 ;
2016-03-30 14:20:52 -07:00
// count of number of child jobs which are blocked
2016-07-29 13:02:49 -07:00
private int _blockedChildJobsCount = 0 ;
2016-03-30 14:20:52 -07:00
// count of child jobs that are in disconnected state.
2016-07-29 13:02:49 -07:00
private int _disconnectedChildJobsCount = 0 ;
2016-03-30 14:20:52 -07:00
// count of child jobs that are in Debug halted state.
2016-07-29 13:02:49 -07:00
private int _debugChildJobsCount = 0 ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Handles the StateChanged event from each of the child job objects
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleChildJobStateChanged ( object sender , JobStateEventArgs e )
{
// Update object state to reflect disconnect state related changes in child jobs.
CheckDisconnectedAndUpdateState ( e . JobStateInfo . State , e . PreviousJobStateInfo . State );
if ( e . JobStateInfo . State == JobState . Blocked )
{
// increment count of blocked child jobs
lock ( _syncObject )
{
2016-07-29 13:02:49 -07:00
_blockedChildJobsCount ++;
2016-03-30 14:20:52 -07:00
}
// if any of the child job is blocked, we set state to blocked
SetJobState ( JobState . Blocked , null );
return ;
}
// Handle transition of child job to Debug halt state.
if ( e . JobStateInfo . State == JobState . AtBreakpoint )
{
2016-07-29 13:02:49 -07:00
lock ( _syncObject ) { _debugChildJobsCount ++; }
2016-03-30 14:20:52 -07:00
// If any child jobs are Debug halted, we set state to Debug.
SetJobState ( JobState . AtBreakpoint );
return ;
}
// Handle transition of child job back to running state.
if (( e . JobStateInfo . State == JobState . Running ) &&
( e . PreviousJobStateInfo . State == JobState . AtBreakpoint ))
{
int totalDebugCount ;
2016-07-29 13:02:49 -07:00
lock ( _syncObject ) { totalDebugCount = -- _debugChildJobsCount ; }
2016-03-30 14:20:52 -07:00
if ( totalDebugCount == 0 )
{
SetJobState ( JobState . Running );
return ;
}
}
//Ignore state changes which are not resulting in state change to finished.
if (! IsFinishedState ( e . JobStateInfo . State ))
{
return ;
}
if ( e . JobStateInfo . State == JobState . Failed )
{
//If any of the child job failed, we set status to failed
2016-07-29 13:02:49 -07:00
_atleastOneChildJobFailed = true ;
2016-03-30 14:20:52 -07:00
}
bool allChildJobsFinished = false ;
lock ( _syncObject )
{
2016-07-29 13:02:49 -07:00
_finishedChildJobsCount ++;
2016-03-30 14:20:52 -07:00
//We are done
2016-07-29 13:02:49 -07:00
if ( _finishedChildJobsCount + _disconnectedChildJobsCount
2016-03-30 14:20:52 -07:00
== ChildJobs . Count )
{
allChildJobsFinished = true ;
}
}
if ( allChildJobsFinished )
{
//if any child job failed, set status to failed
//If stop was called set, status to stopped
//else completed
2016-07-29 13:02:49 -07:00
if ( _disconnectedChildJobsCount > 0 )
2016-03-30 14:20:52 -07:00
{
SetJobState ( JobState . Disconnected );
}
2016-07-29 13:02:49 -07:00
else if ( _atleastOneChildJobFailed )
2016-03-30 14:20:52 -07:00
{
SetJobState ( JobState . Failed );
}
else if ( _stopIsCalled == true )
{
SetJobState ( JobState . Stopped );
}
else
{
SetJobState ( JobState . Completed );
}
}
}
/// <summary>
/// Updates the parent job state based on state of all child jobs.
/// </summary>
/// <param name="newState">New child job state.</param>
/// <param name="prevState">Previous child job state.</param>
private void CheckDisconnectedAndUpdateState ( JobState newState , JobState prevState )
{
if ( IsFinishedState ( JobStateInfo . State ))
{
return ;
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
// Do all logic inside a lock to ensure it is atomic against
// multiple job thread state changes.
lock ( _syncObject )
{
if ( newState == JobState . Disconnected )
{
2016-07-29 13:02:49 -07:00
++( _disconnectedChildJobsCount );
2016-03-30 14:20:52 -07:00
// If previous state was Blocked then we need to decrement the count
// since it is now Disconnected.
if ( prevState == JobState . Blocked )
{
2016-07-29 13:02:49 -07:00
-- _blockedChildJobsCount ;
2016-03-30 14:20:52 -07:00
}
// If all unfinished and unblocked child jobs are disconnected then this
// parent job becomes disconnected.
2016-07-29 13:02:49 -07:00
if (( _disconnectedChildJobsCount +
_finishedChildJobsCount +
_blockedChildJobsCount ) == ChildJobs . Count )
2016-03-30 14:20:52 -07:00
{
SetJobState ( JobState . Disconnected , null );
}
}
else
{
if ( prevState == JobState . Disconnected )
{
2016-07-29 13:02:49 -07:00
--( _disconnectedChildJobsCount );
2016-03-30 14:20:52 -07:00
}
if (( newState == JobState . Running ) && ( JobStateInfo . State == JobState . Disconnected ))
{
// Note that SetJobState() takes a lock so it is unnecessary to do
// this under a lock here.
SetJobState ( JobState . Running , null );
}
}
}
}
#endregion finish logic
/// <summary>
/// Release all the resources.
/// </summary>
/// <param name="disposing">
/// if true, release all the managed objects.
/// </param>
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed )
2016-03-30 14:20:52 -07:00
{
return ;
}
lock ( _syncObject )
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed )
2016-03-30 14:20:52 -07:00
{
return ;
}
2016-07-29 13:02:49 -07:00
_isDisposed = true ;
2016-03-30 14:20:52 -07:00
}
try
{
if (! IsFinishedState ( JobStateInfo . State ))
{
StopJob ();
}
foreach ( Job job in ChildJobs )
{
job . Dispose ();
}
2016-07-29 13:02:49 -07:00
_throttleManager . Dispose ();
2016-03-30 14:20:52 -07:00
}
finally
{
base . Dispose ( disposing );
}
}
}
2016-07-29 13:02:49 -07:00
private bool _isDisposed = false ;
2016-03-30 14:20:52 -07:00
private String ConstructLocation ()
{
StringBuilder location = new StringBuilder ();
if ( ChildJobs . Count > 0 )
{
foreach ( PSRemotingChildJob job in ChildJobs )
{
location . Append ( job . Location );
location . Append ( "," );
}
location . Remove ( location . Length - 1 , 1 );
}
return location . ToString ();
}
/// <summary>
/// Computers on which this job is running
/// </summary>
public override string Location
{
2016-07-29 13:02:49 -07:00
get
2016-03-30 14:20:52 -07:00
{
return ConstructLocation ();
}
}
/// <summary>
/// Returns boolean indicating whether the underlying
/// transport for the job (or child jobs) supports
/// connect/disconnect semantics.
/// </summary>
internal override bool CanDisconnect
{
get
{
// If one child job can disconnect then all of them can since
// all child jobs use the same remote runspace transport.
return ( ChildJobs . Count > 0 ) && ChildJobs [ 0 ]. CanDisconnect ;
}
}
/// <summary>
/// Returns runspaces associated with the Job, including
/// child jobs.
/// </summary>
/// <returns>IEnumerable of RemoteRunspaces</returns>
internal override IEnumerable < RemoteRunspace > GetRunspaces ()
{
List < RemoteRunspace > runspaces = new List < RemoteRunspace >();
foreach ( PSRemotingChildJob job in ChildJobs )
{
runspaces . Add ( job . Runspace as RemoteRunspace );
}
return runspaces ;
}
/// <summary>
/// Handles JobUnblocked event from a child job and decrements
/// count of blocked child jobs. When count reaches 0, sets the
/// state of the parent job to running
/// </summary>
/// <param name="sender">sender of this event, unused</param>
/// <param name="eventArgs">event arguments, should be empty in this
/// case</param>
private void HandleJobUnblocked ( object sender , EventArgs eventArgs )
{
bool unblockjob = false ;
lock ( _syncObject )
{
2016-07-29 13:02:49 -07:00
_blockedChildJobsCount --;
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
if ( _blockedChildJobsCount == 0 )
2016-03-30 14:20:52 -07:00
{
unblockjob = true ;
}
}
if ( unblockjob )
{
SetJobState ( JobState . Running , null );
}
}
#region Private Members
2016-07-29 13:02:49 -07:00
private ThrottleManager _throttleManager = new ThrottleManager ();
2016-03-30 14:20:52 -07:00
private readonly object _syncObject = new object (); // sync object
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
#endregion Private Members
}
#region DisconnectedJobOperation class
/// <summary>
/// Simple throttle operation class for PSRemoting jobs created in the
/// disconnected state.
/// </summary>
internal class DisconnectedJobOperation : ExecutionCmdletHelper
{
internal DisconnectedJobOperation ( Pipeline pipeline )
{
this . pipeline = pipeline ;
this . pipeline . StateChanged += new EventHandler < PipelineStateEventArgs >( HandlePipelineStateChanged );
}
internal override void StartOperation ()
{
// This is a no-op since disconnected jobs (pipelines) have
// already been started.
}
internal override void StopOperation ()
{
if ( pipeline . PipelineStateInfo . State == PipelineState . Running ||
pipeline . PipelineStateInfo . State == PipelineState . Disconnected ||
pipeline . PipelineStateInfo . State == PipelineState . NotStarted )
{
// If the pipeline state has reached Complete/Failed/Stopped
// by the time control reaches here, then this operation
// becomes a no-op. However, an OperationComplete would have
// already been raised from the handler.
pipeline . StopAsync ();
}
else
{
// Will have to raise OpertionComplete from here,
// else ThrottleManager will have
SendStopComplete ();
}
}
internal override event EventHandler < OperationStateEventArgs > OperationComplete ;
private void HandlePipelineStateChanged ( object sender , PipelineStateEventArgs stateEventArgs )
{
PipelineStateInfo stateInfo = stateEventArgs . PipelineStateInfo ;
switch ( stateInfo . State )
{
case PipelineState . Running :
case PipelineState . NotStarted :
case PipelineState . Stopping :
case PipelineState . Disconnected :
return ;
}
SendStopComplete ( stateEventArgs );
}
private void SendStopComplete ( EventArgs eventArgs = null )
{
OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs ();
operationStateEventArgs . BaseEvent = eventArgs ;
operationStateEventArgs . OperationState = OperationState . StopComplete ;
OperationComplete . SafeInvoke ( this , operationStateEventArgs );
}
}
#endregion
/// <summary>
/// Class for RemotingChildJob object. This job object invokes command
/// on one remote machine.
/// </summary>
/// <remarks>
/// TODO: I am not sure whether to change this internal to just RemotingChildJob.
/// </remarks>
/// <remarks>
/// Not removing the prefix "PS" as this signifies powershell specific remoting job
/// </remarks>
internal class PSRemotingChildJob : Job , IJobDebugger
{
#region Internal Constructor
/// <summary>
/// Creates an instance of PSRemotingChildJob
/// </summary>
/// <param name="remoteCommand">command invoked by this job object</param>
///
/// <param name="helper"></param>
/// <param name="throttleManager"></param>
internal PSRemotingChildJob ( string remoteCommand , ExecutionCmdletHelper helper , ThrottleManager throttleManager )
: base ( remoteCommand )
{
UsesResultsCollection = true ;
Dbg . Assert ( helper . Pipeline is RemotePipeline , "Pipeline passed should be a remote pipeline" );
2016-08-02 13:29:09 -07:00
Helper = helper ;
Runspace = helper . Pipeline . Runspace ;
2016-07-29 13:02:49 -07:00
_remotePipeline = helper . Pipeline as RemotePipeline ;
_throttleManager = throttleManager ;
2016-03-30 14:20:52 -07:00
2016-08-02 13:29:09 -07:00
RemoteRunspace remoteRS = Runspace as RemoteRunspace ;
2016-03-30 14:20:52 -07:00
if (( null != remoteRS ) && ( remoteRS . RunspaceStateInfo . State == RunspaceState . BeforeOpen ))
{
remoteRS . URIRedirectionReported += HandleURIDirectionReported ;
}
AggregateResultsFromHelper ( helper );
2016-08-02 13:29:09 -07:00
Runspace . AvailabilityChanged += HandleRunspaceAvailabilityChanged ;
2016-03-30 14:20:52 -07:00
RegisterThrottleComplete ( throttleManager );
}
/// <summary>
/// Constructs a disconnected child job that is able to connect to a remote
/// runspace/command on a server. The ExecutionCmdletHelperRunspace must
/// contain a remote pipeline object in a disconnected state. In addition
/// the pipeline runspace must be associated with a valid running remote
/// command that can be connected to.
/// </summary>
/// <param name="helper">ExecutionCmdletHelper object containing runspace and pipeline objects.</param>
/// <param name="throttleManager">ThrottleManger object.</param>
/// <param name="aggregateResults">Aggregate results.</param>
internal PSRemotingChildJob ( ExecutionCmdletHelper helper , ThrottleManager throttleManager , bool aggregateResults = false )
{
UsesResultsCollection = true ;
Dbg . Assert (( helper . Pipeline is RemotePipeline ), "Helper pipeline object should be a remote pipeline" );
Dbg . Assert (( helper . Pipeline . PipelineStateInfo . State == PipelineState . Disconnected ), "Remote pipeline object must be in Disconnected state." );
2016-08-02 13:29:09 -07:00
Helper = helper ;
2016-07-29 13:02:49 -07:00
_remotePipeline = helper . Pipeline as RemotePipeline ;
2016-08-02 13:29:09 -07:00
Runspace = helper . Pipeline . Runspace ;
2016-07-29 13:02:49 -07:00
_throttleManager = throttleManager ;
2016-03-30 14:20:52 -07:00
if ( aggregateResults )
{
AggregateResultsFromHelper ( helper );
}
else
{
2016-07-29 13:02:49 -07:00
_remotePipeline . StateChanged += new EventHandler < PipelineStateEventArgs >( HandlePipelineStateChanged );
_remotePipeline . Output . DataReady += new EventHandler ( HandleOutputReady );
_remotePipeline . Error . DataReady += new EventHandler ( HandleErrorReady );
2016-03-30 14:20:52 -07:00
}
2016-08-02 13:29:09 -07:00
Runspace . AvailabilityChanged += HandleRunspaceAvailabilityChanged ;
2016-03-30 14:20:52 -07:00
IThrottleOperation operation = helper as IThrottleOperation ;
operation . OperationComplete += new EventHandler < OperationStateEventArgs >( HandleOperationComplete );
SetJobState ( JobState . Disconnected , null );
}
/// <summary>
/// Default constructor
/// </summary>
protected PSRemotingChildJob ()
{
}
#endregion Internal Constructor
#region Internal Methods
/// <summary>
/// Connects the remote pipeline that this job represents.
/// </summary>
internal void ConnectAsync ()
{
if ( JobStateInfo . State != JobState . Disconnected )
{
throw new InvalidJobStateException ( JobStateInfo . State );
}
2016-07-29 13:02:49 -07:00
_remotePipeline . ConnectAsync ();
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Removes job data aggregation callbacks. Used for jobs
/// stopped in debugger so that debugger can access data.
/// </summary>
internal void RemoveJobAggregation ()
{
2016-08-02 13:29:09 -07:00
RemoveAggreateCallbacksFromHelper ( Helper );
2016-03-30 14:20:52 -07:00
}
#endregion
#region stop
//bool isStopCalled = false;
/// <summary>
/// Stops the job
/// </summary>
public override void StopJob ()
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed || _stopIsCalled || IsFinishedState ( JobStateInfo . State ))
2016-03-30 14:20:52 -07:00
{
return ;
}
lock ( SyncObject )
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed || _stopIsCalled || IsFinishedState ( JobStateInfo . State ))
2016-03-30 14:20:52 -07:00
{
return ;
}
2016-07-29 13:02:49 -07:00
_stopIsCalled = true ;
2016-03-30 14:20:52 -07:00
}
2016-08-02 13:29:09 -07:00
_throttleManager . StopOperation ( Helper );
2016-03-30 14:20:52 -07:00
// if IgnoreStop is set, then StopOperation will
// return immediately, but StopJob should only
// return when job is complete. Waiting on the
// wait handle will ensure that its blocked
// until the job reaches a terminal state
Finished . WaitOne ();
}
#endregion stop
#region Properties
/// <summary>
/// Status Message associated with the Job
/// </summary>
public override string StatusMessage
{
get
{
//ISSUE implement this.
return "" ;
}
}
/// <summary>
/// Indicates if there is more data available in
/// this Job
/// </summary>
public override bool HasMoreData
{
get
{
return ( Results . IsOpen || Results . Count > 0 );
}
}
/// <summary>
/// Returns the computer on which this command is
/// running
/// </summary>
public override string Location
{
get
{
2016-08-02 13:29:09 -07:00
return ( Runspace != null ) ? Runspace . ConnectionInfo . ComputerName : string . Empty ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
///
/// </summary>
2016-08-02 13:29:09 -07:00
public Runspace Runspace { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// helper associated with this entity
/// </summary>
2016-08-02 13:29:09 -07:00
internal ExecutionCmdletHelper Helper { get ; } = null ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// used by Invoke-Command cmdlet to show/hide computername property value.
/// Format and Output has capability to understand RemoteObjects and this property lets
/// Format and Output decide whether to show/hide computername.
/// Default is true.
/// </summary>
internal bool HideComputerName
{
2016-07-29 13:02:49 -07:00
get { return _hideComputerName ; }
2016-03-30 14:20:52 -07:00
set
{
2016-07-29 13:02:49 -07:00
_hideComputerName = value ;
2016-03-30 14:20:52 -07:00
foreach ( Job job in this . ChildJobs )
{
PSRemotingChildJob rJob = job as PSRemotingChildJob ;
if ( null != rJob )
{
rJob . HideComputerName = value ;
}
}
}
}
2016-07-29 13:02:49 -07:00
private bool _hideComputerName = true ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Property that indicates this disconnected child job was
/// previously in the Blocked state.
/// </summary>
2016-08-02 13:29:09 -07:00
internal bool DisconnectedAndBlocked { get ; private set ; } = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Returns boolean indicating whether the underlying
/// transport for the job (or child jobs) supports
/// connect/disconnect semantics.
/// </summary>
internal override bool CanDisconnect
{
get
{
2016-08-02 13:29:09 -07:00
RemoteRunspace remoteRS = Runspace as RemoteRunspace ;
2016-03-30 14:20:52 -07:00
return ( remoteRS != null ) ? remoteRS . CanDisconnect : false ;
}
}
#endregion Properties
#region IJobDebugger
/// <summary>
/// Job Debugger
/// </summary>
public Debugger Debugger
{
get
{
if ( _jobDebugger == null )
{
lock ( this . SyncObject )
{
if (( _jobDebugger == null ) &&
2016-08-02 13:29:09 -07:00
( Runspace . Debugger != null ))
2016-03-30 14:20:52 -07:00
{
2016-08-02 13:29:09 -07:00
_jobDebugger = new RemotingJobDebugger ( Runspace . Debugger , Runspace , this . Name );
2016-03-30 14:20:52 -07:00
}
}
}
return _jobDebugger ;
}
}
/// <summary>
/// True if job is synchronous and can be debugged.
/// </summary>
public bool IsAsync
{
get { return _isAsync ; }
set { _isAsync = true ; }
}
#endregion
#region Private Methods
/// <summary>
/// Handler which will handle output ready events of the
/// pipeline. The output objects are queued on to the
/// internal stream
/// </summary>
/// <param name="sender">the pipeline reader which raised
/// this event</param>
/// <param name="eventArgs">information describing the ready event</param>
private void HandleOutputReady ( object sender , EventArgs eventArgs )
{
PSDataCollectionPipelineReader < PSObject , PSObject > reader =
sender as PSDataCollectionPipelineReader < PSObject , PSObject >;
Collection < PSObject > output = reader . NonBlockingRead ();
foreach ( PSObject dataObject in output )
{
// attach origin information only if it doesn't exist
// in case of a second-hop scenario, the origin information
// will already be added at the second hop machine
if ( dataObject != null )
{
// if the server has already added some properties, which we do not
// want to trust, we simply replace them with the server's
// identity we know of
if ( dataObject . Properties [ RemotingConstants . ComputerNameNoteProperty ] != null )
{
dataObject . Properties . Remove ( RemotingConstants . ComputerNameNoteProperty );
}
if ( dataObject . Properties [ RemotingConstants . RunspaceIdNoteProperty ] != null )
{
dataObject . Properties . Remove ( RemotingConstants . RunspaceIdNoteProperty );
}
dataObject . Properties . Add ( new PSNoteProperty ( RemotingConstants . ComputerNameNoteProperty , reader . ComputerName ));
dataObject . Properties . Add ( new PSNoteProperty ( RemotingConstants . RunspaceIdNoteProperty , reader . RunspaceId ));
// PSShowComputerName is present for all the objects (from remoting)..this is to allow PSComputerName to be selected.
// Ex: Invoke-Command localhost,blah { gps } | select PSComputerName should work.
if ( dataObject . Properties [ RemotingConstants . ShowComputerNameNoteProperty ] == null )
{
2016-07-29 13:02:49 -07:00
PSNoteProperty showComputerNameNP = new PSNoteProperty ( RemotingConstants . ShowComputerNameNoteProperty , ! _hideComputerName );
2016-03-30 14:20:52 -07:00
dataObject . Properties . Add ( showComputerNameNP );
}
}
this . WriteObject ( dataObject );
}
} //HandleOutputReady
/// <summary>
/// Handler which will handle error ready events of the
/// pipeline. The error records are queued on to the
/// internal stream
/// </summary>
/// <param name="sender">the pipeline reader which raised
/// this event</param>
/// <param name="eventArgs">information describing the ready event</param>
private void HandleErrorReady ( object sender , EventArgs eventArgs )
{
PSDataCollectionPipelineReader < ErrorRecord , object > reader =
sender as PSDataCollectionPipelineReader < ErrorRecord , object >;
Collection < object > error = reader . NonBlockingRead ();
foreach ( object errorData in error )
{
ErrorRecord er = errorData as ErrorRecord ;
if ( er != null )
{
OriginInfo originInfo = new OriginInfo ( reader . ComputerName , reader . RunspaceId );
RemotingErrorRecord errorRecord =
new RemotingErrorRecord ( er , originInfo );
errorRecord . PreserveInvocationInfoOnce = true ;
//ISSUE: Add an Assert for ErrorRecord.
//Add to the PSRemotingChild jobs streams
this . WriteError ( errorRecord );
}
}
} //HandleErrorReady
/// <summary>
/// When the client remote session reports a URI redirection, this method will report the
/// message to the user as a Warning using Host method calls.
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
protected void HandleURIDirectionReported ( object sender , RemoteDataEventArgs < Uri > eventArgs )
{
string message = StringUtil . Format ( RemotingErrorIdStrings . URIRedirectWarningToHost , eventArgs . Data . OriginalString );
this . WriteWarning ( message );
}
/// <summary>
/// Used to detect an Invoke-Command running command breakpoint hit.
/// In this case disconnect the runspace so that a debugger can be
/// attached later by the user.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void HandleRunspaceAvailabilityChangedForInvoke ( object sender , RunspaceAvailabilityEventArgs e )
{
RemoteRunspace remoteRunspace = sender as RemoteRunspace ;
if ( remoteRunspace != null &&
e . RunspaceAvailability == RunspaceAvailability . RemoteDebug )
{
remoteRunspace . AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke ;
try
{
remoteRunspace . DisconnectAsync ();
}
catch ( PSNotImplementedException ) { }
catch ( InvalidRunspacePoolStateException ) { }
catch ( InvalidRunspaceStateException ) { }
catch ( PSInvalidOperationException ) { }
}
}
/// <summary>
/// Handle method executor stream events.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="eventArgs">The event args.</param>
private void HandleHostCalls ( object sender , EventArgs eventArgs )
{
ObjectStream hostCallsStream = sender as ObjectStream ;
if ( hostCallsStream != null )
{
Collection < object > hostCallMethodExecutors =
hostCallsStream . NonBlockingRead ( hostCallsStream . Count );
lock ( SyncObject )
{
foreach ( ClientMethodExecutor hostCallMethodExecutor in hostCallMethodExecutors )
{
Results . Add ( new PSStreamObject ( PSStreamObjectType . MethodExecutor , hostCallMethodExecutor ));
// if the call id of the underlying remote host call is not ServerDispatchTable.VoidCallId
// then the call is waiting on user input. Change state to Blocked
if ( hostCallMethodExecutor . RemoteHostCall . CallId != ServerDispatchTable . VoidCallId )
{
SetJobState ( JobState . Blocked , null );
}
}
}
} // if (hostCallsStream...
} // HandleHostCalls
/// <summary>
/// Handle changes in pipeline states
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void HandlePipelineStateChanged ( object sender , PipelineStateEventArgs e )
{
2016-08-02 13:29:09 -07:00
if (( null != Runspace ) && ( e . PipelineStateInfo . State != PipelineState . Running ))
2016-03-30 14:20:52 -07:00
{
// since we got state changed event..we dont need to listen on
// URI redirections anymore
2016-08-02 13:29:09 -07:00
(( RemoteRunspace ) Runspace ). URIRedirectionReported -= HandleURIDirectionReported ;
2016-03-30 14:20:52 -07:00
// We monitor runspace RemoteDebug availability only while
// this pipeline is running.
2016-08-02 13:29:09 -07:00
(( RemoteRunspace ) Runspace ). AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke ;
2016-03-30 14:20:52 -07:00
}
PipelineState state = e . PipelineStateInfo . State ;
switch ( state )
{
case PipelineState . Running :
2016-08-02 13:29:09 -07:00
if ( DisconnectedAndBlocked )
2016-03-30 14:20:52 -07:00
{
2016-08-02 13:29:09 -07:00
DisconnectedAndBlocked = false ;
2016-03-30 14:20:52 -07:00
SetJobState ( JobState . Blocked );
}
else
{
SetJobState ( JobState . Running );
}
break ;
case PipelineState . Disconnected :
2016-08-02 13:29:09 -07:00
DisconnectedAndBlocked = ( JobStateInfo . State == JobState . Blocked );
2016-03-30 14:20:52 -07:00
SetJobState ( JobState . Disconnected );
break ;
}
// Question: Why is the DoFinish() call on terminal pipeline state deleted
// Answer: Because in the runspace case, when pipeline reaches a terminal state
// OperationComplete will be raised and DoFinish() is called on OperationComplete
// In the computer name case, once pipeline reaches a terminal state, runspace is
// closed which will result in an OperationComplete event
}
/// <summary>
/// Handle a throttle complete event
/// </summary>
/// <param name="sender">sender of this event</param>
/// <param name="eventArgs">not used in this method</param>
private void HandleThrottleComplete ( object sender , EventArgs eventArgs )
{
//Question: Why do we register for HandleThrottleComplete when we have already
//registered for PipelineStateChangedEvent?
//Answer: Because ThrottleManager at a given time can have some pipelines which are
//still not started. If TM.Stop() is called, then it simply discards those pipelines and
//PipelineStateChangedEvent is not called for them. For such jobs, we depend on
//HandleThrottleComplete to mark the finish of job.
//Question: So it is possible in some cases DoFinish can be called twice.
//Answer: Yes: One from PipelineStateChangedEvent and Another here. But
//DoFinish has logic to check if it has been already called and second call
//becomes noOp.
DoFinish ();
} // HandleThrottleComplete
/// <summary>
/// Handle the operation complete event
/// </summary>
/// <param name="sender">sender of this event</param>
/// <param name="stateEventArgs">operation complete event args</param>
protected virtual void HandleOperationComplete ( object sender , OperationStateEventArgs stateEventArgs )
{
//Question:Why are we registering for OperationComplete if we already
//registering for StateChangedEvent and ThrottleComplete event
//Answer:Because in case of computer, if Runspace.Open it self fails,
//no pipeline is created and no pipeline state changed event is raised.
//We can wait for throttle complete, but it is raised only when all the
//operations are completed and this means that status of job is not updated
//untill Operation Complete.
ExecutionCmdletHelper helper = sender as ExecutionCmdletHelper ;
Dbg . Assert ( helper != null , "Sender of OperationComplete has to be ExecutionCmdletHelper" );
DeterminedAndSetJobState ( helper );
}
2016-07-29 13:02:49 -07:00
private bool _doFinishCalled = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// This method marks the completion state for Job. Also if job failed, it processes the
/// reason of failure.
/// </summary>
protected virtual void DoFinish ()
{
2016-07-29 13:02:49 -07:00
if ( _doFinishCalled == true )
2016-03-30 14:20:52 -07:00
return ;
lock ( SyncObject )
{
2016-07-29 13:02:49 -07:00
if ( _doFinishCalled == true )
2016-03-30 14:20:52 -07:00
return ;
2016-07-29 13:02:49 -07:00
_doFinishCalled = true ;
2016-03-30 14:20:52 -07:00
}
2016-08-02 13:29:09 -07:00
DeterminedAndSetJobState ( Helper );
2016-03-30 14:20:52 -07:00
DoCleanupOnFinished ();
}
/// <summary>
/// This is the pretty formated error record associated with the reason of failure.
/// </summary>
2016-07-29 13:02:49 -07:00
private ErrorRecord _failureErrorRecord ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// This is the pretty formated error record associated with the reason of failure.
/// This is set if Job state is Failed and Reason has a exception.
/// </summary>
internal ErrorRecord FailureErrorRecord
{
get
{
2016-07-29 13:02:49 -07:00
return _failureErrorRecord ;
2016-03-30 14:20:52 -07:00
}
}
/// <summary>
/// Process the exceptions to decide reason for job failure
/// </summary>
/// <param name="helper"></param>
/// <param name="failureException"></param>
/// <param name="failureErrorRecord"></param>
protected void ProcessJobFailure ( ExecutionCmdletHelper helper , out Exception failureException ,
out ErrorRecord failureErrorRecord )
{
// There are three errors possible
// 1. The remote runspace is in (or went into) a
// broken state. This information is available
// in the runspace state information
// 2. The remote pipeline failed because of an
// exception. This information is available
// in the pipeline state information
// 3. Runspace.OpenAsync or Pipeline.InvokeAsync threw exception
// They are in Helper.InternalException
Dbg . Assert ( helper != null , "helper is null" );
RemotePipeline pipeline = helper . Pipeline as RemotePipeline ;
Dbg . Assert ( pipeline != null , "pipeline is null" );
RemoteRunspace runspace = pipeline . GetRunspace () as RemoteRunspace ;
Dbg . Assert ( runspace != null , "runspace is null" );
failureException = null ;
failureErrorRecord = null ;
if ( helper . InternalException != null )
{
string errorId = "RemotePipelineExecutionFailed" ;
failureException = helper . InternalException ;
if (( failureException is InvalidRunspaceStateException ) || ( failureException is InvalidRunspacePoolStateException ))
{
errorId = "InvalidSessionState" ;
if (! string . IsNullOrEmpty ( failureException . Source ))
{
errorId = string . Format ( System . Globalization . CultureInfo . InvariantCulture ,
"{0},{1}" , errorId , failureException . Source );
}
}
failureErrorRecord = new ErrorRecord ( helper . InternalException ,
errorId , ErrorCategory . OperationStopped ,
helper );
}
// there is a failure reason available in the runspace
else if ( runspace . RunspaceStateInfo . State == RunspaceState . Broken )
{
failureException = runspace . RunspaceStateInfo . Reason ;
object targetObject = runspace . ConnectionInfo . ComputerName ;
String errorDetails = null ;
// set the transport message in the error detail so that
// the user can directly get to see the message without
// having to mine through the error record details
PSRemotingTransportException transException =
failureException as PSRemotingTransportException ;
2016-07-29 13:02:49 -07:00
string fullyQualifiedErrorId =
2016-03-30 14:20:52 -07:00
System . Management . Automation . Remoting . Client . WSManTransportManagerUtils . GetFQEIDFromTransportError (
( transException != null ) ? transException . ErrorCode : 0 ,
"PSSessionStateBroken" );
if ( transException != null )
{
errorDetails = "[" + runspace . ConnectionInfo . ComputerName + "] " ;
if ( transException . ErrorCode ==
Remoting . Client . WSManNativeApi . ERROR_WSMAN_REDIRECT_REQUESTED )
{
// Handling a special case for redirection..we should talk about
// AllowRedirection parameter and WSManMaxRedirectionCount preference
// variables
string message = PSRemotingErrorInvariants . FormatResourceString (
RemotingErrorIdStrings . URIRedirectionReported ,
transException . Message ,
"MaximumConnectionRedirectionCount" ,
Microsoft . PowerShell . Commands . PSRemotingBaseCmdlet . DEFAULT_SESSION_OPTION ,
"AllowRedirection" );
errorDetails += message ;
}
else if (! String . IsNullOrEmpty ( transException . Message ))
{
errorDetails += transException . Message ;
}
else if (! String . IsNullOrEmpty ( transException . TransportMessage ))
{
errorDetails += transException . TransportMessage ;
}
}
if ( failureException == null )
{
failureException = new RuntimeException (
PSRemotingErrorInvariants . FormatResourceString (
2016-07-29 13:02:49 -07:00
RemotingErrorIdStrings . RemoteRunspaceOpenUnknownState ,
2016-03-30 14:20:52 -07:00
runspace . RunspaceStateInfo . State ));
}
failureErrorRecord = new ErrorRecord ( failureException , targetObject ,
fullyQualifiedErrorId , ErrorCategory . OpenError ,
null , null , null , null , null , errorDetails , null );
} // if (runspace...
else if ( pipeline . PipelineStateInfo . State == PipelineState . Failed )
{
object targetObject = runspace . ConnectionInfo . ComputerName ;
failureException = pipeline . PipelineStateInfo . Reason ;
if ( failureException != null )
{
RemoteException rException = failureException as RemoteException ;
ErrorRecord errorRecord = null ;
if ( rException != null )
{
errorRecord = rException . ErrorRecord ;
}
else
{
// at this point, there may be no failure reason available in
// the runspace because the remoting protocol
// layer may not have yet assigned it to the runspace
// in such a case, the remoting protocol layer would have
// assigned an exception in the client end to the pipeline
// create an error record from it and write it out
errorRecord = new ErrorRecord ( pipeline . PipelineStateInfo . Reason ,
"JobFailure" , ErrorCategory . OperationStopped ,
targetObject );
}
String computerName = (( RemoteRunspace ) pipeline . GetRunspace ()). ConnectionInfo . ComputerName ;
Guid runspaceId = pipeline . GetRunspace (). InstanceId ;
OriginInfo originInfo = new OriginInfo ( computerName , runspaceId );
failureErrorRecord = new RemotingErrorRecord ( errorRecord , originInfo );
} // if (exception != null...
} // if (pipeline...
}
/// <summary>
/// Release all the resources.
/// </summary>
/// <param name="disposing">
/// if true, release all the managed objects.
/// </param>
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed )
2016-03-30 14:20:52 -07:00
{
return ;
}
lock ( SyncObject )
{
2016-07-29 13:02:49 -07:00
if ( _isDisposed )
2016-03-30 14:20:52 -07:00
{
return ;
}
2016-07-29 13:02:49 -07:00
_isDisposed = true ;
2016-03-30 14:20:52 -07:00
}
try
{
DoCleanupOnFinished ();
}
finally
{
base . Dispose ( disposing );
}
}
}
2016-07-29 13:02:49 -07:00
private bool _isDisposed = false ;
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
private bool _cleanupDone = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Cleanup after state changes to finished
/// </summary>
protected virtual void DoCleanupOnFinished ()
{
bool doCleanup = false ;
2016-07-29 13:02:49 -07:00
if ( _cleanupDone == false )
2016-03-30 14:20:52 -07:00
{
lock ( SyncObject )
{
2016-07-29 13:02:49 -07:00
if ( _cleanupDone == false )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_cleanupDone = true ;
2016-03-30 14:20:52 -07:00
doCleanup = true ;
}
}
}
if (! doCleanup ) return ;
2016-08-02 13:29:09 -07:00
StopAggregateResultsFromHelper ( Helper );
Runspace . AvailabilityChanged -= HandleRunspaceAvailabilityChanged ;
IThrottleOperation operation = Helper as IThrottleOperation ;
2016-03-30 14:20:52 -07:00
operation . OperationComplete -= new EventHandler < OperationStateEventArgs >( HandleOperationComplete );
2016-07-29 13:02:49 -07:00
UnregisterThrottleComplete ( _throttleManager );
_throttleManager = null ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Aggregates results from the pipeline associated
/// with the specified helper
/// </summary>
/// <param name="helper">helper whose pipeline results
/// need to be aggregated</param>
protected void AggregateResultsFromHelper ( ExecutionCmdletHelper helper )
{
//Get the pipeline associated with this helper and register for appropriate events
Pipeline pipeline = helper . Pipeline ;
pipeline . Output . DataReady += new EventHandler ( HandleOutputReady );
pipeline . Error . DataReady += new EventHandler ( HandleErrorReady );
pipeline . StateChanged += new EventHandler < PipelineStateEventArgs >( HandlePipelineStateChanged );
// Register handler for method executor object stream.
Dbg . Assert ( pipeline is RemotePipeline , "pipeline is RemotePipeline" );
2016-07-29 13:02:49 -07:00
RemotePipeline remotePipeline = pipeline as RemotePipeline ;
2016-03-30 14:20:52 -07:00
remotePipeline . MethodExecutorStream . DataReady += new EventHandler ( HandleHostCalls );
2016-07-29 13:02:49 -07:00
remotePipeline . PowerShell . Streams . Progress . DataAdded +=
2016-03-30 14:20:52 -07:00
new EventHandler < DataAddedEventArgs >( HandleProgressAdded );
2016-07-29 13:02:49 -07:00
remotePipeline . PowerShell . Streams . Warning . DataAdded +=
2016-03-30 14:20:52 -07:00
new EventHandler < DataAddedEventArgs >( HandleWarningAdded );
2016-07-29 13:02:49 -07:00
remotePipeline . PowerShell . Streams . Verbose . DataAdded +=
2016-03-30 14:20:52 -07:00
new EventHandler < DataAddedEventArgs >( HandleVerboseAdded );
2016-07-29 13:02:49 -07:00
remotePipeline . PowerShell . Streams . Debug . DataAdded +=
2016-03-30 14:20:52 -07:00
new EventHandler < DataAddedEventArgs >( HandleDebugAdded );
remotePipeline . PowerShell . Streams . Information . DataAdded +=
new EventHandler < DataAddedEventArgs >( HandleInformationAdded );
// Enable method executor stream so that host methods are queued up
// on it instead of being executed asynchronously when they arrive.
remotePipeline . IsMethodExecutorStreamEnabled = true ;
IThrottleOperation operation = helper as IThrottleOperation ;
operation . OperationComplete += new EventHandler < OperationStateEventArgs >( HandleOperationComplete );
}
/// <summary>
/// If the pipeline is not null, returns the pipeline's PowerShell
/// If it is null, then returns the PowerShell with the specified
/// instance Id.
/// </summary>
/// <param name="pipeline">remote pipeline</param>
/// <param name="instanceId">instance as described in event args</param>
/// <returns>PowerShell instance</returns>
private PowerShell GetPipelinePowerShell ( RemotePipeline pipeline , Guid instanceId )
{
if ( pipeline != null )
{
return pipeline . PowerShell ;
}
return GetPowerShell ( instanceId );
}
/// <summary>
/// When a debug message is raised in the underlying PowerShell
/// add it to the jobs debug stream
/// </summary>
/// <param name="sender">unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleDebugAdded ( object sender , DataAddedEventArgs eventArgs )
{
int index = eventArgs . Index ;
2016-07-29 13:02:49 -07:00
PowerShell powershell = GetPipelinePowerShell ( _remotePipeline , eventArgs . PowerShellInstanceId );
2016-03-30 14:20:52 -07:00
if ( powershell != null )
{
this . Debug . Add ( powershell . Streams . Debug [ index ]);
}
}
/// <summary>
/// When a verbose message is raised in the underlying PowerShell
/// add it to the jobs verbose stream
/// </summary>
/// <param name="sender">unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleVerboseAdded ( object sender , DataAddedEventArgs eventArgs )
{
int index = eventArgs . Index ;
2016-07-29 13:02:49 -07:00
PowerShell powershell = GetPipelinePowerShell ( _remotePipeline , eventArgs . PowerShellInstanceId );
2016-03-30 14:20:52 -07:00
if ( powershell != null )
{
this . Verbose . Add ( powershell . Streams . Verbose [ index ]);
}
}
/// <summary>
/// When a warning message is raised in the underlying PowerShell
/// add it to the jobs warning stream
/// </summary>
/// <param name="sender">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
PowerShell powershell = GetPipelinePowerShell ( _remotePipeline , eventArgs . PowerShellInstanceId );
2016-03-30 14:20:52 -07:00
if ( powershell != null )
{
WarningRecord warningRecord = powershell . Streams . Warning [ index ];
this . Warning . Add ( warningRecord );
this . Results . Add ( new PSStreamObject ( PSStreamObjectType . WarningRecord , warningRecord ));
}
}
/// <summary>
/// When a progress message is raised in the underlying PowerShell
/// add it to the jobs progress tream
/// </summary>
/// <param name="sender">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
PowerShell powershell = GetPipelinePowerShell ( _remotePipeline , eventArgs . PowerShellInstanceId );
2016-03-30 14:20:52 -07:00
if ( powershell != null )
{
this . Progress . Add ( powershell . Streams . Progress [ index ]);
}
}
/// <summary>
/// When a Information message is raised in the underlying PowerShell
/// add it to the jobs Information stream
/// </summary>
/// <param name="sender">unused</param>
/// <param name="eventArgs">arguments describing this event</param>
private void HandleInformationAdded ( object sender , DataAddedEventArgs eventArgs )
{
int index = eventArgs . Index ;
2016-07-29 13:02:49 -07:00
PowerShell powershell = GetPipelinePowerShell ( _remotePipeline , eventArgs . PowerShellInstanceId );
2016-03-30 14:20:52 -07:00
if ( powershell != null )
{
InformationRecord informationRecord = powershell . Streams . Information [ index ];
this . Information . Add ( informationRecord );
// Host output is handled by the hosting APIs directly, so we need to add a tag that it was
// forwarded so that it is not written twice.
// For all other Information records, forward them.
if ( informationRecord . Tags . Contains ( "PSHOST" ))
{
informationRecord . Tags . Add ( "FORWARDED" );
}
this . Results . Add ( new PSStreamObject ( PSStreamObjectType . Information , informationRecord ));
}
}
/// <summary>
/// Stops collecting results from the pipeline associated with
/// the specified helper
/// </summary>
/// <param name="helper">helper class whose pipeline results
/// aggregation has to be stopped</param>
protected void StopAggregateResultsFromHelper ( ExecutionCmdletHelper helper )
{
// Ensure the Runspace availability handler is removed on command completion.
if ( helper . PipelineRunspace != null )
{
helper . PipelineRunspace . AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke ;
}
// Get the pipeline associated with this helper and register for appropriate events
RemoveAggreateCallbacksFromHelper ( helper );
Pipeline pipeline = helper . Pipeline ;
pipeline . Dispose ();
pipeline = null ;
}
/// <summary>
/// Removes aggregate callbacks from pipeline so that a new job object can
/// be created and can add its own callbacks.
/// This is to support Invoke-Command auto-disconnect where a new PSRemoting
/// job must be created to pass back to user for connection.
/// </summary>
/// <param name="helper">helper class</param>
protected void RemoveAggreateCallbacksFromHelper ( ExecutionCmdletHelper helper )
{
// Remove old data output callbacks from pipeline so new callbacks can be added.
Pipeline pipeline = helper . Pipeline ;
pipeline . Output . DataReady -= HandleOutputReady ;
pipeline . Error . DataReady -= HandleErrorReady ;
pipeline . StateChanged -= HandlePipelineStateChanged ;
// Remove old data aggregation and host calls.
Dbg . Assert ( pipeline is RemotePipeline , "pipeline is RemotePipeline" );
RemotePipeline remotePipeline = pipeline as RemotePipeline ;
remotePipeline . MethodExecutorStream . DataReady -= new EventHandler ( HandleHostCalls );
if ( remotePipeline . PowerShell != null )
{
remotePipeline . PowerShell . Streams . Progress . DataAdded -=
new EventHandler < DataAddedEventArgs >( HandleProgressAdded );
remotePipeline . PowerShell . Streams . Warning . DataAdded -=
new EventHandler < DataAddedEventArgs >( HandleWarningAdded );
remotePipeline . PowerShell . Streams . Verbose . DataAdded -=
new EventHandler < DataAddedEventArgs >( HandleVerboseAdded );
remotePipeline . PowerShell . Streams . Debug . DataAdded -=
new EventHandler < DataAddedEventArgs >( HandleDebugAdded );
remotePipeline . PowerShell . Streams . Information . DataAdded -=
new EventHandler < DataAddedEventArgs >( HandleInformationAdded );
remotePipeline . IsMethodExecutorStreamEnabled = false ;
}
}
/// <summary>
/// register for throttle complete from the specified
/// throttlemanager
/// </summary>
/// <param name="throttleManager"></param>
protected void RegisterThrottleComplete ( ThrottleManager throttleManager )
{
throttleManager . ThrottleComplete += new EventHandler < EventArgs >( HandleThrottleComplete );
}
/// <summary>
/// unregister for throttle complete from the specified
/// throttle manager
/// </summary>
/// <param name="throttleManager"></param>
protected void UnregisterThrottleComplete ( ThrottleManager throttleManager )
{
throttleManager . ThrottleComplete -= new EventHandler < EventArgs >( HandleThrottleComplete );
}
/// <summary>
/// Determine the current state of the job based on the underlying
/// pipeline state and set the state accordingly
/// </summary>
/// <param name="helper"></param>
protected void DeterminedAndSetJobState ( ExecutionCmdletHelper helper )
{
Exception failureException ;
//Process the reason in case of failure.
2016-07-29 13:02:49 -07:00
ProcessJobFailure ( helper , out failureException , out _failureErrorRecord );
2016-03-30 14:20:52 -07:00
if ( failureException != null )
{
SetJobState ( JobState . Failed , failureException );
}
else
{
//Get the state of the pipeline
PipelineState state = helper . Pipeline . PipelineStateInfo . State ;
if ( state == PipelineState . NotStarted )
{
//This is a case in which pipeline was not started and TM.Stop was
//called. See comment in HandleThrottleComplete
SetJobState ( JobState . Stopped );
}
else if ( state == PipelineState . Completed )
{
SetJobState ( JobState . Completed );
}
else
{
SetJobState ( JobState . Stopped );
}
}
}
/// <summary>
/// Set the state of the current job from blocked to
/// running and raise an event indicating to this
/// parent job that this job is unblocked
/// </summary>
internal void UnblockJob ()
{
Dbg . Assert ( JobStateInfo . State == JobState . Blocked ,
"Current state of job must be blocked before it can be unblocked" );
SetJobState ( JobState . Running , null );
Dbg . Assert ( JobUnblocked != null , "Parent job must register for JobUnblocked event from all child jobs" );
JobUnblocked . SafeInvoke ( this , EventArgs . Empty );
}
/// <summary>
/// Returns the PowerShell for the specified instance id
/// </summary>
/// <param name="instanceId">instance id of powershell</param>
/// <returns>powershell instance</returns>
internal virtual PowerShell GetPowerShell ( Guid instanceId )
{
// this should be called only in the derived implementation
throw PSTraceSource . NewInvalidOperationException ();
}
/// <summary>
/// Returns the PowerShell object associated with this remote child job.
/// </summary>
/// <returns>PowerShell object.</returns>
internal PowerShell GetPowerShell ()
{
PowerShell ps = null ;
2016-07-29 13:02:49 -07:00
if ( _remotePipeline != null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
ps = _remotePipeline . PowerShell ;
2016-03-30 14:20:52 -07:00
}
return ps ;
}
/// <summary>
/// Monitor runspace availability and if it goes to RemoteDebug then set
/// job state to Debug. Set back to Running when availability goes back to
/// Busy (indicating the script/command is running again).
/// </summary>
/// <param name="sender">Runspace</param>
/// <param name="e">RunspaceAvailabilityEventArgs</param>
private void HandleRunspaceAvailabilityChanged ( object sender , RunspaceAvailabilityEventArgs e )
{
RunspaceAvailability prevAvailability = _prevRunspaceAvailability ;
_prevRunspaceAvailability = e . RunspaceAvailability ;
if ( e . RunspaceAvailability == RunspaceAvailability . RemoteDebug )
{
SetJobState ( JobState . AtBreakpoint );
}
else if (( prevAvailability == RunspaceAvailability . RemoteDebug ) &&
( e . RunspaceAvailability == RunspaceAvailability . Busy ))
{
SetJobState ( JobState . Running );
}
}
/// <summary>
/// Event raised by this job to indicate to its parent that
/// its now unblocked by the user
/// </summary>
internal event EventHandler JobUnblocked ;
#endregion Private Methods
#region Private Members
//helper associated with this job object
2016-07-29 13:02:49 -07:00
private RemotePipeline _remotePipeline = null ;
2016-03-30 14:20:52 -07:00
//object used for synchronization
protected object SyncObject = new object ();
2016-07-29 13:02:49 -07:00
private ThrottleManager _throttleManager ;
private bool _stopIsCalled = false ;
2016-03-30 14:20:52 -07:00
private volatile Debugger _jobDebugger ;
private bool _isAsync = true ;
private RunspaceAvailability _prevRunspaceAvailability = RunspaceAvailability . None ;
#endregion Private Members
}
/// <summary>
/// This is a debugger wrapper class used to allow debugging of
/// remoting jobs that implement the IJobDebugger interface.
/// </summary>
internal sealed class RemotingJobDebugger : Debugger
{
#region Members
private Debugger _wrappedDebugger ;
private Runspace _runspace ;
private string _jobName ;
#endregion
#region Constructor
private RemotingJobDebugger () { }
/// <summary>
/// Constructor
/// </summary>
/// <param name="debugger">Debugger to wrap.</param>
/// <param name="runspace">Remote runspace.</param>
/// <param name="jobName">Name of associated job.</param>
public RemotingJobDebugger (
Debugger debugger ,
Runspace runspace ,
string jobName )
{
if ( debugger == null )
{
throw new PSArgumentNullException ( "debugger" );
}
if ( runspace == null )
{
throw new PSArgumentNullException ( "runspace" );
}
_wrappedDebugger = debugger ;
_runspace = runspace ;
_jobName = jobName ?? string . Empty ;
// Create handlers for wrapped debugger events.
_wrappedDebugger . BreakpointUpdated += HandleBreakpointUpdated ;
_wrappedDebugger . DebuggerStop += HandleDebuggerStop ;
}
#endregion
#region Debugger overrides
/// <summary>
/// Evaluates provided command either as a debugger specific command
/// or a PowerShell command.
/// </summary>
/// <param name="command">PowerShell command</param>
/// <param name="output">Output</param>
/// <returns>DebuggerCommandResults</returns>
public override DebuggerCommandResults ProcessCommand ( PSCommand command , PSDataCollection < PSObject > output )
{
// Special handling for the prompt command.
if ( command . Commands [ 0 ]. CommandText . Trim (). Equals ( "prompt" , StringComparison . OrdinalIgnoreCase ))
{
return HandlePromptCommand ( output );
}
return _wrappedDebugger . ProcessCommand ( command , output );
}
/// <summary>
/// Sets the debugger resume action.
/// </summary>
/// <param name="resumeAction">DebuggerResumeAction</param>
public override void SetDebuggerAction ( DebuggerResumeAction resumeAction )
{
_wrappedDebugger . SetDebuggerAction ( resumeAction );
}
/// <summary>
/// Stops a running command.
/// </summary>
public override void StopProcessCommand ()
{
_wrappedDebugger . StopProcessCommand ();
}
/// <summary>
/// Returns current debugger stop event arguments if debugger is in
/// debug stop state. Otherwise returns null.
/// </summary>
/// <returns>DebuggerStopEventArgs</returns>
public override DebuggerStopEventArgs GetDebuggerStopArgs ()
{
return _wrappedDebugger . GetDebuggerStopArgs ();
}
/// <summary>
/// Sets the parent debugger, breakpoints, function source and other
/// debugging context information.
/// </summary>
/// <param name="parent">Parent debugger</param>
/// <param name="breakPoints">List of breakpoints</param>
/// <param name="startAction">Debugger mode</param>
/// <param name="host">PowerShell host</param>
/// <param name="path">Current path</param>
/// <param name="functionSourceMap">Function to source map</param>
public override void SetParent (
Debugger parent ,
IEnumerable < Breakpoint > breakPoints ,
DebuggerResumeAction ? startAction ,
PSHost host ,
PathInfo path ,
Dictionary < string , DebugSource > functionSourceMap )
{
// For now always enable step mode debugging.
SetDebuggerStepMode ( true );
}
/// <summary>
/// Sets the parent debugger and breakpoints.
/// </summary>
/// <param name="parent">Parent debugger</param>
/// <param name="breakPoints">List of breakpoints</param>
/// <param name="startAction">Debugger mode</param>
/// <param name="host">host</param>
/// <param name="path">Current path</param>
public override void SetParent (
2016-07-29 13:02:49 -07:00
Debugger parent ,
IEnumerable < Breakpoint > breakPoints ,
DebuggerResumeAction ? startAction ,
PSHost host ,
2016-03-30 14:20:52 -07:00
PathInfo path )
{
// For now always enable step mode debugging.
SetDebuggerStepMode ( true );
}
/// <summary>
/// Sets the debugger mode.
/// </summary>
public override void SetDebugMode ( DebugModes mode )
{
_wrappedDebugger . SetDebugMode ( mode );
base . SetDebugMode ( mode );
}
/// <summary>
/// Returns IEnumerable of CallStackFrame objects.
/// </summary>
/// <returns></returns>
public override IEnumerable < CallStackFrame > GetCallStack ()
{
return _wrappedDebugger . GetCallStack ();
}
/// <summary>
/// Sets debugger stepping mode.
/// </summary>
/// <param name="enabled">True if stepping is to be enabled</param>
public override void SetDebuggerStepMode ( bool enabled )
{
_wrappedDebugger . SetDebuggerStepMode ( enabled );
}
/// <summary>
/// CheckStateAndRaiseStopEvent
/// </summary>
internal void CheckStateAndRaiseStopEvent ()
{
RemoteDebugger remoteDebugger = _wrappedDebugger as RemoteDebugger ;
if ( remoteDebugger != null )
{
remoteDebugger . CheckStateAndRaiseStopEvent ();
}
}
/// <summary>
/// True when debugger is stopped at a breakpoint.
/// </summary>
public override bool InBreakpoint
{
get { return _wrappedDebugger . InBreakpoint ; }
}
#endregion
#region Private methods
2016-07-29 13:02:49 -07:00
private void HandleDebuggerStop ( object sender , DebuggerStopEventArgs e )
2016-03-30 14:20:52 -07:00
{
Pipeline remoteRunningCmd = null ;
try
{
// For remote debugging drain/block output channel.
remoteRunningCmd = DrainAndBlockRemoteOutput ();
this . RaiseDebuggerStopEvent ( e );
}
finally
{
RestoreRemoteOutput ( remoteRunningCmd );
}
}
private Pipeline DrainAndBlockRemoteOutput ()
{
// We only do this for remote runspaces.
if (!( _runspace is RemoteRunspace )) { return null ; }
Pipeline runningCmd = _runspace . GetCurrentlyRunningPipeline ();
if ( runningCmd != null )
{
runningCmd . DrainIncomingData ();
runningCmd . SuspendIncomingData ();
return runningCmd ;
}
return null ;
}
private void RestoreRemoteOutput ( Pipeline runningCmd )
{
if ( runningCmd != null )
{
runningCmd . ResumeIncomingData ();
}
}
private void HandleBreakpointUpdated ( object sender , BreakpointUpdatedEventArgs e )
{
this . RaiseBreakpointUpdatedEvent ( e );
}
private DebuggerCommandResults HandlePromptCommand ( PSDataCollection < PSObject > output )
{
// Nested debugged runspace prompt should look like:
// [DBG]: [JobName]: PS C:\>>
string promptScript = "'[DBG]: '" + " + " + "'[" + CodeGeneration . EscapeSingleQuotedStringContent ( _jobName ) + "]: '" + " + " + @"""PS $($executionContext.SessionState.Path.CurrentLocation)>> """ ;
PSCommand promptCommand = new PSCommand ();
promptCommand . AddScript ( promptScript );
_wrappedDebugger . ProcessCommand ( promptCommand , output );
return new DebuggerCommandResults ( null , true );
}
#endregion
}
/// <summary>
/// This job is used for running as a job the results from multiple
/// pipelines. This is used in synchronouse Invoke-Expression execution
/// </summary>
/// <remarks>
/// TODO: I am not sure whether to change this internal to just InvokeExpressionSyncJob.
/// </remarks>
/// <remarks>
/// Not removing the prefix "PS" as this signifies powershell specific remoting job
/// </remarks>
internal class PSInvokeExpressionSyncJob : PSRemotingChildJob
{
#region Private Members
2016-07-29 13:02:49 -07:00
private List < ExecutionCmdletHelper > _helpers = new List < ExecutionCmdletHelper >();
private ThrottleManager _throttleManager ;
private Dictionary < Guid , PowerShell > _powershells = new Dictionary < Guid , PowerShell >();
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
private int _pipelineFinishedCount ;
private int _pipelineDisconnectedCount ;
2016-03-30 14:20:52 -07:00
#endregion Private Members
#region Constructors
/// <summary>
/// Construct an invoke-expression sync job
/// </summary>
/// <param name="operations">list of operations to use</param>
/// <param name="throttleManager">throttle manager to use for
/// this job</param>
internal PSInvokeExpressionSyncJob ( List < IThrottleOperation > operations , ThrottleManager throttleManager )
{
UsesResultsCollection = true ;
Results . AddRef ();
2016-07-29 13:02:49 -07:00
_throttleManager = throttleManager ;
RegisterThrottleComplete ( _throttleManager );
2016-03-30 14:20:52 -07:00
foreach ( IThrottleOperation operation in operations )
{
ExecutionCmdletHelper helper = operation as ExecutionCmdletHelper ;
RemoteRunspace remoteRS = helper . Pipeline . Runspace as RemoteRunspace ;
if ( null != remoteRS )
{
remoteRS . AvailabilityChanged += HandleRunspaceAvailabilityChangedForInvoke ;
remoteRS . StateChanged += HandleRunspaceStateChanged ;
if ( remoteRS . RunspaceStateInfo . State == RunspaceState . BeforeOpen )
{
remoteRS . URIRedirectionReported += HandleURIDirectionReported ;
}
}
2016-07-29 13:02:49 -07:00
_helpers . Add ( helper );
2016-03-30 14:20:52 -07:00
AggregateResultsFromHelper ( helper );
Dbg . Assert ( helper . Pipeline is RemotePipeline , "Only remote pipeline can be used in InvokeExpressionSyncJob" );
RemotePipeline pipeline = helper . Pipeline as RemotePipeline ;
2016-07-29 13:02:49 -07:00
_powershells . Add ( pipeline . PowerShell . InstanceId , pipeline . PowerShell );
2016-03-30 14:20:52 -07:00
}
}
#endregion Constructors
#region Protected Methods
2016-07-29 13:02:49 -07:00
private bool _cleanupDone = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Clean up once job is finished
/// </summary>
protected override void DoCleanupOnFinished ()
{
bool doCleanup = false ;
2016-07-29 13:02:49 -07:00
if ( _cleanupDone == false )
2016-03-30 14:20:52 -07:00
{
lock ( SyncObject )
{
2016-07-29 13:02:49 -07:00
if ( _cleanupDone == false )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_cleanupDone = true ;
2016-03-30 14:20:52 -07:00
doCleanup = true ;
}
}
}
if (! doCleanup ) return ;
2016-07-29 13:02:49 -07:00
foreach ( ExecutionCmdletHelper helper in _helpers )
2016-03-30 14:20:52 -07:00
{
// cleanup remote runspace related handlers
RemoteRunspace remoteRS = helper . PipelineRunspace as RemoteRunspace ;
if ( null != remoteRS )
{
remoteRS . StateChanged -= HandleRunspaceStateChanged ;
remoteRS . URIRedirectionReported -= HandleURIDirectionReported ;
}
StopAggregateResultsFromHelper ( helper );
}
2016-07-29 13:02:49 -07:00
UnregisterThrottleComplete ( _throttleManager );
2016-03-30 14:20:52 -07:00
//throttleManager = null;
Results . DecrementRef ();
}
/// <summary>
/// release all resources
/// </summary>
/// <param name="disposing">true if called by Dispose()</param>
protected override void Dispose ( bool disposing )
{
base . Dispose ( disposing );
}
/// <summary>
/// Handles operation complete from the operations. Adds an error record
/// to results whenever an error is encountered
/// </summary>
/// <param name="sender">sender of this event</param>
/// <param name="stateEventArgs">arguments describing this event, unused</param>
protected override void HandleOperationComplete ( object sender , OperationStateEventArgs stateEventArgs )
{
ExecutionCmdletHelper helper = sender as ExecutionCmdletHelper ;
Dbg . Assert ( helper != null , "Sender of OperationComplete has to be ExecutionCmdletHelper" );
Exception failureException ;
//Process the reason in case of failure.
ErrorRecord failureErrorRecord ;
ProcessJobFailure ( helper , out failureException , out failureErrorRecord );
if ( failureErrorRecord != null )
{
this . WriteError ( failureErrorRecord );
}
}
/// <summary>
/// Handle changes in pipeline states
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected override void HandlePipelineStateChanged ( object sender , PipelineStateEventArgs e )
{
PipelineState state = e . PipelineStateInfo . State ;
switch ( state )
{
case PipelineState . Running :
SetJobState ( JobState . Running );
break ;
case PipelineState . Completed :
case PipelineState . Failed :
case PipelineState . Stopped :
case PipelineState . Disconnected :
CheckForAndSetDisconnectedState ( state );
break ;
}
}
/// <summary>
/// Checks for a condition where all pipelines are either finished
/// or disconnected and at least one pipeline is disconnected.
/// In this case the Job state is set to Disconnected.
/// </summary>
private void CheckForAndSetDisconnectedState ( PipelineState pipelineState )
{
bool setJobStateToDisconnected ;
lock ( SyncObject )
{
if ( IsTerminalState ())
{
return ;
}
switch ( pipelineState )
{
case PipelineState . Completed :
case PipelineState . Failed :
case PipelineState . Stopped :
2016-07-29 13:02:49 -07:00
_pipelineFinishedCount += 1 ;
2016-03-30 14:20:52 -07:00
break ;
case PipelineState . Disconnected :
2016-07-29 13:02:49 -07:00
_pipelineDisconnectedCount += 1 ;
2016-03-30 14:20:52 -07:00
break ;
}
2016-07-29 13:02:49 -07:00
setJobStateToDisconnected = (( _pipelineFinishedCount + _pipelineDisconnectedCount ) == _helpers . Count &&
_pipelineDisconnectedCount > 0 );
2016-03-30 14:20:52 -07:00
}
if ( setJobStateToDisconnected )
{
// Job cannot finish with pipelines in disconnected state.
// Set Job state to Disconnected.
SetJobState ( JobState . Disconnected );
}
}
/// <summary>
/// Used to stop all operations
/// </summary>
public override void StopJob ()
{
2016-07-29 13:02:49 -07:00
_throttleManager . StopAllOperations ();
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
private bool _doFinishCalled = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// This method marks the completion state for Job. Also if job failed, it processes the
/// reason of failure.
/// </summary>
protected override void DoFinish ()
{
2016-07-29 13:02:49 -07:00
if ( _doFinishCalled == true )
2016-03-30 14:20:52 -07:00
return ;
lock ( SyncObject )
{
2016-07-29 13:02:49 -07:00
if ( _doFinishCalled == true )
2016-03-30 14:20:52 -07:00
return ;
2016-07-29 13:02:49 -07:00
_doFinishCalled = true ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
foreach ( ExecutionCmdletHelper helper in _helpers )
2016-03-30 14:20:52 -07:00
{
DeterminedAndSetJobState ( helper );
}
2016-07-29 13:02:49 -07:00
if ( _helpers . Count == 0 && this . JobStateInfo . State == JobState . NotStarted )
2016-03-30 14:20:52 -07:00
{
SetJobState ( JobState . Completed );
}
DoCleanupOnFinished ();
}
/// <summary>
/// Returns the PowerShell instance for the specified id
/// </summary>
/// <param name="instanceId">instance id of PowerShell</param>
/// <returns>PowerShell instance</returns>
internal override PowerShell GetPowerShell ( Guid instanceId )
{
PowerShell powershell = null ;
2016-07-29 13:02:49 -07:00
_powershells . TryGetValue ( instanceId , out powershell );
2016-03-30 14:20:52 -07:00
return powershell ;
}
#endregion Protected Methods
#region Event Handlers
/// <summary>
/// Used to unregister URI Redirection handler.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleRunspaceStateChanged ( object sender , RunspaceStateEventArgs e )
{
RemoteRunspace remoteRS = sender as RemoteRunspace ;
// remote runspace must be connected (or connection failed)
// we dont need URI redirection any more..so clear it
if ( null != remoteRS )
{
if ( e . RunspaceStateInfo . State != RunspaceState . Opening )
{
remoteRS . URIRedirectionReported -= HandleURIDirectionReported ;
if ( e . RunspaceStateInfo . State != RunspaceState . Opened )
{
remoteRS . StateChanged -= HandleRunspaceStateChanged ;
remoteRS . AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke ;
}
}
}
}
#endregion
#region Internal Methods
/// <summary>
/// Submits the operations created in the constructor for invocation.
/// </summary>
internal void StartOperations ( List < IThrottleOperation > operations )
{
// submit operations to the throttle manager
2016-07-29 13:02:49 -07:00
_throttleManager . SubmitOperations ( operations );
_throttleManager . EndSubmitOperations ();
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Determines if the job is in a terminal state
/// </summary>
/// <returns>true, if job in terminal state
/// false otherwise</returns>
internal bool IsTerminalState ()
{
return ( IsFinishedState ( this . JobStateInfo . State ) ||
this . JobStateInfo . State == JobState . Disconnected );
}
/// <summary>
/// Returns a collection of all powershells for this job.
/// </summary>
/// <returns>Collection of PowerShell objects.</returns>
internal Collection < PowerShell > GetPowerShells ()
{
Collection < PowerShell > powershellsToReturn = new Collection < PowerShell >();
2016-07-29 13:02:49 -07:00
foreach ( PowerShell ps in _powershells . Values )
2016-03-30 14:20:52 -07:00
{
powershellsToReturn . Add ( ps );
}
return powershellsToReturn ;
}
/// <summary>
/// Returns a disconnected remoting job object that contains all
/// remote pipelines/runspaces that are in the Disconnected state.
/// </summary>
/// <returns></returns>
internal PSRemotingJob CreateDisconnectedRemotingJob ()
{
List < IThrottleOperation > disconnectedJobHelpers = new List < IThrottleOperation >();
2016-07-29 13:02:49 -07:00
foreach ( var helper in _helpers )
2016-03-30 14:20:52 -07:00
{
if ( helper . Pipeline . PipelineStateInfo . State == PipelineState . Disconnected )
{
// Remove data callbacks from the old helper.
RemoveAggreateCallbacksFromHelper ( helper );
// Create new helper used to create the new Disconnected PSRemoting job.
disconnectedJobHelpers . Add ( new DisconnectedJobOperation ( helper . Pipeline ));
}
}
if ( disconnectedJobHelpers . Count == 0 )
{
return null ;
}
return new PSRemotingJob ( disconnectedJobHelpers , 0 , Name , true );
}
#endregion Internal Methods
}
#region OutputProcessingState class
internal class OutputProcessingStateEventArgs : EventArgs
{
internal bool ProcessingOutput
{
get ;
private set ;
}
internal OutputProcessingStateEventArgs ( bool processingOutput )
{
ProcessingOutput = processingOutput ;
}
}
internal interface IOutputProcessingState
{
event EventHandler < OutputProcessingStateEventArgs > OutputProcessingStateChanged ;
}
#endregion
}