2016-03-30 14:20:52 -07:00
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
using System.Collections ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.ComponentModel ;
using System.Linq ;
using System.Management.Automation.Internal ;
using System.Management.Automation.Remoting ;
using System.Management.Automation.Runspaces ;
using System.Management.Automation.Tracing ;
using System.Threading ;
using Dbg = System . Management . Automation . Diagnostics ;
// Stops compiler from warning about unknown warnings
#pragma warning disable 1634 , 1691
namespace System.Management.Automation
{
#region Workflow Hosting API
/// <summary>
/// Class that will serve as the API for hosting and executing
/// workflows in PowerShell. This class will have a behavior
/// similar to how the Runspace and PowerShell APIs behave in
/// the remoting scenario. The objects on the client side act
/// as proxies to the real objects on the server
/// </summary>
public sealed class PSJobProxy : Job2
{
#region Constructors
/// <summary>
/// Internal constructor
/// </summary>
/// <param name="command">the command to execute</param>
internal PSJobProxy ( string command )
: base ( command )
{
_tracer . WriteMessage ( ClassNameTrace , "ctor" , _remoteJobInstanceId , this ,
"Constructing proxy job" , null );
StateChanged += HandleMyStateChange ;
}
#endregion Constructors
#region Overrides of Job
///<summary>
/// Success status of the command execution.
/// </summary>
public override string StatusMessage
{
get { return _remoteJobStatusMessage ; }
}
/// <summary>
/// Indicates that more data is available in this
/// result object for reading.
/// </summary>
public override bool HasMoreData
{
get
{
// moreData is initially set to true, and it
// will remain so until the async result
// object has completed execution.
if ( _moreData && IsFinishedState ( JobStateInfo . State ))
{
bool atleastOneChildHasMoreData = ChildJobs . Any ( t => t . HasMoreData );
bool parentHasMoreData = ( CollectionHasMoreData ( Output )
|| CollectionHasMoreData ( Error )
|| CollectionHasMoreData ( Verbose )
|| CollectionHasMoreData ( Debug )
|| CollectionHasMoreData ( Warning )
|| CollectionHasMoreData ( Progress ));
_moreData = parentHasMoreData || atleastOneChildHasMoreData ;
}
return _moreData ;
}
}
/// <summary>
/// This is the location string from the remote job.
/// </summary>
public override string Location
{
get { return _remoteJobLocation ; }
}
#endregion
#region Overrides of Job2
/// <summary>
/// start a job. The job will be started with the parameters
/// specified in StartParameters
/// </summary>
/// <exception cref="InvalidJobStateException">Thrown if the job
/// is already running, if there is no runspace or runspace pool
/// assigned.</exception>
/// <exception cref="NotSupportedException">Thrown if the job is
/// otherwise started, finished, or suspended.</exception>
/// <exception cref="ObjectDisposedException">Thrown if job is
/// disposed.</exception>
public override void StartJob ()
{
StartJob ( null , null , null );
}
/// <summary>
/// Start a job asynchronously
/// </summary>
/// <remarks>When a job is started all the data in the
/// job streams from a previous invocation will be cleared</remarks>
public override void StartJobAsync ()
{
StartJobAsync ( null , null , null );
}
/// <summary>
/// Stop a job synchronously. In order to be consistent, this method
/// should be used in place of StopJob which was introduced in the
/// v2 Job API
/// </summary>
/// <exception cref="InvalidJobStateException">Thrown if job is blocked.</exception>
/// <exception cref="ObjectDisposedException">Thrown if job is disposed.</exception>
public override void StopJob ()
{
try
{
// The call is asynchronous because Receive-Job is called
// in the same invocation. Return once the job state changed
// event is received, and the job state is updated to a
// Finished state.
if ( ShouldQueueOperation ())
_pendingOperations . Enqueue ( QueueOperation . Stop );
else
DoStopAsync ();
Finished . WaitOne ();
}
catch ( Exception error )
{
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "StopJob" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
throw ;
}
}
/// <summary>
/// Stop a job asynchronously
/// </summary>
public override void StopJobAsync ()
{
#pragma warning disable 56500
try
{
if ( ShouldQueueOperation ())
_pendingOperations . Enqueue ( QueueOperation . Stop );
else
DoStopAsync ();
}
catch ( Exception error )
{
// Exception transferred using event arguments.
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "StopJobAsync" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
OnStopJobCompleted ( new AsyncCompletedEventArgs ( error , false , null ));
}
#pragma warning restore 56500
}
/// <summary>
/// StopJob
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
2016-07-29 13:02:49 -07:00
public override void StopJob ( bool force , string reason )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyJobControlNotSupported );
}
/// <summary>
/// StopJobAsync
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
2016-07-29 13:02:49 -07:00
public override void StopJobAsync ( bool force , string reason )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyJobControlNotSupported );
}
/// <summary>
/// Suspend a job
/// </summary>
/// <exception cref="InvalidJobStateException">Throws if the job is not in
/// a running or suspended state.</exception>
/// <exception cref="ObjectDisposedException">Thrown if job is
/// disposed.</exception>
public override void SuspendJob ()
{
try
{
// The call is asynchronous because Receive-Job is called
// in the same invocation. Return once the job state changed
// event is received, and the job state is updated to a
// Finished or Suspended state.
if ( ShouldQueueOperation ())
_pendingOperations . Enqueue ( QueueOperation . Suspend );
else
DoSuspendAsync ();
JobSuspendedOrFinished . WaitOne ();
}
catch ( Exception error )
{
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "SuspendJob" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
throw ;
}
}
/// <summary>
/// Asynchronously suspend a job
/// </summary>
public override void SuspendJobAsync ()
{
#pragma warning disable 56500
try
{
if ( ShouldQueueOperation ())
_pendingOperations . Enqueue ( QueueOperation . Suspend );
else
DoSuspendAsync ();
}
catch ( Exception error )
{
// Exception transferred using event arguments.
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "SuspendJobAsync" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
OnSuspendJobCompleted ( new AsyncCompletedEventArgs ( error , false , null ));
}
#pragma warning restore 56500
}
/// <summary>
/// SuspendJob
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
2016-07-29 13:02:49 -07:00
public override void SuspendJob ( bool force , string reason )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyJobControlNotSupported );
}
/// <summary>
/// SuspendJobAsync
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
2016-07-29 13:02:49 -07:00
public override void SuspendJobAsync ( bool force , string reason )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyJobControlNotSupported );
}
/// <summary>
/// Resume a suspended job
/// </summary>
/// <exception cref="InvalidJobStateException">Throws if the job
/// is not in a suspended or running state.</exception>
/// <exception cref="ObjectDisposedException">Thrown if job is
/// disposed.</exception>
public override void ResumeJob ()
{
try
{
// The call is asynchronous because Receive-Job is called
// in the same invocation. Return once the job state changed
// event is received, and the job state is updated to a
// Finished or Running state.
if ( ShouldQueueOperation ())
_pendingOperations . Enqueue ( QueueOperation . Resume );
else
DoResumeAsync ();
JobRunningOrFinished . WaitOne ();
}
catch ( Exception error )
{
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "ResumeJob" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
throw ;
}
}
/// <summary>
/// Resume a suspended job asynchronously.
/// </summary>
public override void ResumeJobAsync ()
{
#pragma warning disable 56500
try
{
if ( ShouldQueueOperation ())
_pendingOperations . Enqueue ( QueueOperation . Resume );
else
DoResumeAsync ();
}
catch ( Exception error )
{
// Exception transferred using event arguments.
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "ResumeJobAsync" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
OnResumeJobCompleted ( new AsyncCompletedEventArgs ( error , false , null ));
}
#pragma warning restore 56500
}
/// <summary>
/// Unblock a blocked job
/// </summary>
/// <exception cref="NotSupportedException">Unblock job is not supported on PSJobProxy.</exception>
public override void UnblockJob ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyUnblockJobNotSupported );
}
/// <summary>
/// Unblock a blocked job asynchronously
/// </summary>
public override void UnblockJobAsync ()
{
OnUnblockJobCompleted ( new AsyncCompletedEventArgs ( PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyUnblockJobNotSupported ), false , null ));
}
#endregion
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
#region Additional State Management Methods
/// <summary>
/// Start execution of the workflow with the
/// specified input. This input will serve as
/// input to the underlying pipeline
/// </summary>
/// <param name="input">collection of input
/// objects</param>
public void StartJobAsync ( PSDataCollection < object > input )
{
StartJobAsync ( null , null , input );
}
/// <summary>
/// Start execution of the job with the
/// specified input. This input will serve as
/// input to the underlying pipeline
/// </summary>
/// <param name="input"></param>
/// <remarks>Not sure if this method is needed. This has
/// been added just to be in sync with the PowerShell
/// APIs</remarks>
public void StartJob ( PSDataCollection < object > input )
{
StartJob ( null , null , input );
}
/// <summary>
/// Start execution of the workflow with the
/// specified input. This input will serve as
/// input to the underlying pipeline.
/// Because the number of child jobs is unknown before starting
/// the job, delegates may be indicated to ensure that no events will be missed
/// after the child job is created if data begins streaming back immediately.
/// </summary>
/// <param name="dataAdded">delegate used to subscribe to data added events on the child jobs</param>
/// <param name="stateChanged">delegate used to subscribe to state changed events on the child jobs</param>
/// <param name="input">collection of input
/// objects</param>
public void StartJob ( EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged , PSDataCollection < object > input )
{
try
{
// The call is asynchronous because Receive-Job is called
// in the same invocation. Return once the job object is returned and
// used to initialize the proxy.
DoStartAsync ( dataAdded , stateChanged , input );
2016-09-02 16:21:20 +00:00
_jobInitializedWaitHandle . WaitOne ();
2016-03-30 14:20:52 -07:00
}
catch ( Exception error )
{
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "StartJob" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
throw ;
}
}
/// <summary>
/// Start asynchronous execution of the workflow with the
/// specified input. This input will serve as
/// input to the underlying pipeline.
/// Because the number of child jobs is unknown before starting
/// the job, delegates may be indicated to ensure that no events will be missed
/// after the child job is created if data begins streaming back immediately.
/// </summary>
/// <param name="dataAdded">delegate used to subscribe to data added events on the child jobs</param>
/// <param name="stateChanged">delegate used to subscribe to state changed events on the child jobs</param>
/// <param name="input">collection of input
/// objects</param>
public void StartJobAsync ( EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged , PSDataCollection < object > input )
{
#pragma warning disable 56500
try
{
DoStartAsync ( dataAdded , stateChanged , input );
}
catch ( Exception error )
{
// Exception transferred using event arguments.
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "StartJobAsync" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
OnStartJobCompleted ( new AsyncCompletedEventArgs ( error , false , null ));
}
#pragma warning restore 56500
}
/// <summary>
/// Removes the job.
/// If remoteJob is true, the job output that has been transferred to this
/// client object will be preserved.
/// </summary>
/// <param name="removeRemoteJob">Indicates whether the remove operation should
/// be applied to the remote or local job.</param>
/// <param name="force">Force will stop the job on the server before
/// attempting removal. Default value is false.</param>
/// <exception cref="InvalidOperationException">Thrown if the job is not in
/// a completed state.</exception>
public void RemoveJob ( bool removeRemoteJob , bool force )
{
if (! removeRemoteJob )
{
Dispose ();
return ;
}
lock ( SyncRoot )
{
AssertNotDisposed ();
}
try
{
DoRemove ( force );
lock ( SyncRoot )
{
// If _removeCalled has not been set to true, do not wait.
// There should be an exception during DoRemove in most cases when
// this is true.
if (! _removeCalled )
{
2016-07-29 13:02:49 -07:00
Diagnostics . Assert ( false , "remove called is false after calling remove. No exception was thrown." );
2016-03-30 14:20:52 -07:00
return ;
}
}
RemoveComplete . WaitOne ();
}
catch ( Exception error )
{
CommandProcessorBase . CheckForSevereException ( error );
_tracer . WriteMessage ( ClassNameTrace , "RemoveJob" , _remoteJobInstanceId , this , "Error" , null );
_tracer . TraceException ( error );
throw ;
}
}
/// <summary>
/// Removes the job.
/// If remoteJob is true, the job output that has been transferred to this
/// client object will be preserved.
/// </summary>
/// <param name="removeRemoteJob">Indicates whether the remove operation should
/// be applied to the remote or local job.</param>
/// <exception cref="InvalidOperationException">Thrown if the job is not in
/// a completed state.</exception>
public void RemoveJob ( bool removeRemoteJob )
{
RemoveJob ( removeRemoteJob , false );
}
/// <summary>
/// Removes the job on the remote server.
/// The job output that has been transferred to this client object will be
/// preserved.
/// </summary>
/// <param name="removeRemoteJob">Indicates whether the remove operation should
/// be applied to the remote or local job.</param>
/// <param name="force">Force will stop the job on the server before
/// attempting removal.</param>
public void RemoveJobAsync ( bool removeRemoteJob , bool force )
{
if (! removeRemoteJob )
{
Dispose ();
OnRemoveJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
return ;
}
var asyncOp = AsyncOperationManager . CreateOperation ( force );
var workerDelegate = new JobActionWorkerDelegate ( JobActionWorker );
workerDelegate . BeginInvoke ( asyncOp , ActionType . Remove , null , null );
}
/// <summary>
/// Removes the job on the remote server.
/// The job output that has been transferred to this client object will be
/// preserved.
/// </summary>
/// <param name="removeRemoteJob">Indicates whether the remove operation should
/// be applied to the remote or local job.</param>
public void RemoveJobAsync ( bool removeRemoteJob )
{
RemoveJobAsync ( removeRemoteJob , false );
}
/// <summary>
/// This event should be raised whenever the asynchronous removal of
/// a server side job is completed.
/// </summary>
public event EventHandler < AsyncCompletedEventArgs > RemoveJobCompleted ;
/// <summary>
/// Method to raise the event when removing a
/// server side job is completed
/// </summary>
/// <param name="eventArgs">argument describing
/// an exception that is associated with the event</param>
private void OnRemoveJobCompleted ( AsyncCompletedEventArgs eventArgs )
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler < AsyncCompletedEventArgs > handler = RemoveJobCompleted ;
#pragma warning disable 56500
try
{
if ( handler != null )
{
handler ( this , eventArgs );
}
}
catch ( Exception exception )
{
// errors in the handlers are not errors in the operation
// silently ignore them
CommandProcessorBase . CheckForSevereException ( exception );
_tracer . TraceException ( exception );
}
#pragma warning restore 56500
}
#endregion Additional State Management Methods
#region Additional Properties
/// <summary>
/// If set, the remote job will be removed when it has been completed and the data has been received.
/// This can only be set prior to a job being started.
/// </summary>
public bool RemoveRemoteJobOnCompletion
{
get
{
return _removeRemoteJobOnCompletion ;
}
set
{
AssertChangesCanBeAccepted ();
_removeRemoteJobOnCompletion = value ;
}
}
private bool _removeRemoteJobOnCompletion ;
/// <summary>
/// The instance ID of the remote job that this proxy interacts with.
/// </summary>
public Guid RemoteJobInstanceId
{
get { return _remoteJobInstanceId ; }
}
/// <summary>
/// Runspace in which this job will be executed
/// </summary>
/// <remarks>At any point of time only a runspace or a
/// runspacepool may be specified</remarks>
public Runspace Runspace
{
get
{
return _runspace ;
}
set
{
if ( value == null )
{
throw PSTraceSource . NewArgumentNullException ( "value" );
}
lock ( SyncRoot )
{
AssertChangesCanBeAccepted ();
_runspacePool = null ;
_runspace = value ;
}
}
}
/// <summary>
/// RunspacePool in which this job will be executed
/// </summary>
public RunspacePool RunspacePool
{
get
{
return _runspacePool ;
}
set
{
if ( value == null )
{
throw PSTraceSource . NewArgumentNullException ( "value" );
}
lock ( SyncRoot )
{
AssertChangesCanBeAccepted ();
_runspace = null ;
_runspacePool = value ;
}
}
}
#endregion Additional Properties
#region Proxy Factory Static Methods
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspace">Runspace containing the jobs to base the proxy on.</param>
/// <param name="filter">Hashtable to use for the Get-Job -filter command.</param>
/// <param name="dataAdded">Handler to subscribe to any child job data added events.</param>
/// <param name="stateChanged">Handler to subscribe to any child job state changed events.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( Runspace runspace , Hashtable filter ,
EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged )
{
return Create ( runspace , filter , dataAdded , stateChanged , true );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspace">Runspace containing the jobs to base the proxy on.</param>
/// <param name="filter">Hashtable to use for the Get-Job -filter command.</param>
/// <param name="receiveImmediately">If true, the data streaming will start immediately. If false,
/// the user must call "ReceiveJob()" to start data streaming.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( Runspace runspace , Hashtable filter , bool receiveImmediately )
{
return Create ( runspace , filter , null , null , receiveImmediately );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspace">Runspace containing the jobs to base the proxy on.</param>
/// <param name="filter">Hashtable to use for the Get-Job -filter command.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( Runspace runspace , Hashtable filter )
{
return Create ( runspace , filter , null , null , true );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspace">Runspace containing the jobs to base the proxy on.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( Runspace runspace )
{
return Create ( runspace , null , null , null , true );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspacePool">RunspacePool containing the jobs to base the proxy on.</param>
/// <param name="filter">Hashtable to use for the Get-Job -filter command.</param>
/// <param name="dataAdded">Handler to subscribe to any child job data added events.</param>
/// <param name="stateChanged">Handler to subscribe to any child job state changed events.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( RunspacePool runspacePool , Hashtable filter ,
EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged )
{
return Create ( runspacePool , filter , dataAdded , stateChanged , true );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspacePool">RunspacePool containing the jobs to base the proxy on.</param>
/// <param name="filter">Hashtable to use for the Get-Job -filter command.</param>
/// <param name="receiveImmediately">If true, the data streaming will start immediately. If false,
/// the user must call "ReceiveJob()" to start data streaming.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( RunspacePool runspacePool , Hashtable filter , bool receiveImmediately )
{
return Create ( runspacePool , filter , null , null , receiveImmediately );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspacePool">RunspacePool containing the jobs to base the proxy on.</param>
/// <param name="filter">Hashtable to use for the Get-Job -filter command.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( RunspacePool runspacePool , Hashtable filter )
{
return Create ( runspacePool , filter , null , null , true );
}
/// <summary>
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
/// </summary>
/// <param name="runspacePool">RunspacePool containing the jobs to base the proxy on.</param>
/// <returns>A collection of job proxies that represent the jobs collected based on the filter.</returns>
public static ICollection < PSJobProxy > Create ( RunspacePool runspacePool )
{
return Create ( runspacePool , null , null , null , true );
}
private static ICollection < PSJobProxy > Create ( RunspacePool runspacePool , Hashtable filter ,
EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged , bool connectImmediately )
{
if ( runspacePool == null )
throw new PSArgumentNullException ( "runspacePool" );
return Create ( null , runspacePool , filter , dataAdded , stateChanged , connectImmediately );
}
private static ICollection < PSJobProxy > Create ( Runspace runspace , Hashtable filter ,
EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged , bool connectImmediately )
{
if ( runspace == null )
throw new PSArgumentNullException ( "runspace" );
return Create ( runspace , null , filter , dataAdded , stateChanged , connectImmediately );
}
private static ICollection < PSJobProxy > Create ( Runspace runspace , RunspacePool runspacePool , Hashtable filter ,
EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged , bool connectImmediately )
{
// run command
Collection < PSObject > jobResults ;
using ( PowerShell ps = PowerShell . Create ())
{
Dbg . Assert ( runspacePool == null && runspace != null ||
runspace == null && runspacePool != null , "Either a runspace or a runspacepool should be used, not both." );
if ( runspacePool == null )
{
ps . Runspace = runspace ;
}
else
{
ps . RunspacePool = runspacePool ;
}
ps . AddCommand ( "Get-Job" );
if ( filter != null ) ps . AddParameter ( "Filter" , filter );
jobResults = ps . Invoke ();
}
Collection < PSJobProxy > jobs = new Collection < PSJobProxy >();
foreach ( var deserializedJob in jobResults )
2016-07-29 13:02:49 -07:00
{
2016-03-30 14:20:52 -07:00
if (! Deserializer . IsDeserializedInstanceOfType ( deserializedJob , typeof ( Job )))
{
// Do not create proxies if jobs are live.
continue ;
}
string command = String . Empty ;
if (! TryGetJobPropertyValue ( deserializedJob , "Command" , out command ))
{
Dbg . Assert ( false , "Job object did not contain command when creating proxy." );
}
PSJobProxy job = new PSJobProxy ( command );
job . InitializeExistingJobProxy ( deserializedJob , runspace , runspacePool );
// Events will be registered and states will be set by ReceiveJob when it is called.
// This may be later, if connectImmediately is set to false.
job . _receiveIsValidCall = true ;
if ( connectImmediately )
{
// This will make receive invalid in the future, sets the state to running.
job . ReceiveJob ( dataAdded , stateChanged );
}
else
{
Dbg . Assert ( dataAdded == null , "DataAdded cannot be specified if not connecting immediately" );
Dbg . Assert ( stateChanged == null , "StateChanged cannot be specified if not connecting immediately" );
}
jobs . Add ( job );
}
return jobs ;
}
#endregion Proxy Factory Static Methods
/// <summary>
/// Will begin streaming data for a job object created by the "create" method that is in a not started state.
/// </summary>
public void ReceiveJob ()
{
ReceiveJob ( null , null );
}
/// <summary>
/// Will begin streaming data for a job object created by the "create" method that is in a not started state.
/// </summary>
/// <param name="dataAdded">delegate used to subscribe to data added events on the child jobs</param>
/// <param name="stateChanged">delegate used to subscribe to state changed events on the child jobs</param>
public void ReceiveJob ( EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged )
{
lock ( SyncRoot )
{
if (! _receiveIsValidCall )
{
// Receive may only be called once, and is only a valid call after a proxy job has been created in
// a non-streaming state.
throw PSTraceSource . NewInvalidOperationException ( PowerShellStrings . JobProxyReceiveInvalid );
}
Dbg . Assert ( JobStateInfo . State == JobState . NotStarted , "ReceiveJob is only valid on a not started PSJobProxy" );
Dbg . Assert ( _receivePowerShell . Commands . Commands . Count == 0 , "ReceiveJob is only valid if internal pipeline is unused." );
Dbg . Assert ( _receivePowerShell . InvocationStateInfo . State == PSInvocationState . NotStarted , "ReceiveJob is only valid if internal pipeline has not been started" );
Dbg . Assert ( _dataAddedHandler == null , "DataAdded cannot be specified before calling ReceiveJob" );
Dbg . Assert ( _stateChangedHandler == null , "StateChanged cannot be specified before calling ReceiveJob" );
_receiveIsValidCall = false ;
_dataAddedHandler = dataAdded ;
_stateChangedHandler = stateChanged ;
}
RegisterChildEvents ();
// Set job states after events have been registered.
// All jobs begin in the running state. States will be updated when state changed
// event information is gotten by Receive-Job. See DataAddedToOutput.
ValidateAndDoSetJobState ( JobState . Running );
foreach ( PSChildJobProxy child in ChildJobs )
{
Dbg . Assert ( child != null , "Child should always be PSChildJobProxy" );
child . DoSetJobState ( JobState . Running );
}
AssignRunspaceOrRunspacePool ( _receivePowerShell );
AddReceiveJobCommandToPowerShell ( _receivePowerShell , false );
_receivePowerShell . AddParameter ( "InstanceId" , _remoteJobInstanceId );
_receivePowerShell . BeginInvoke (( PSDataCollection < PSObject >) null , _receivePowerShellOutput , null ,
CleanupReceivePowerShell , null );
}
#region Internal Methods
internal void InitializeJobProxy ( PSCommand command , Runspace runspace , RunspacePool runspacePool )
{
Dbg . Assert ( command != null , "Command cannot be null" );
_tracer . WriteMessage ( ClassNameTrace , "InitializeJobProxy" , _remoteJobInstanceId , this ,
"Initializing Job Proxy." , null );
_pscommand = command . Clone ();
var paramCol = new CommandParameterCollection ();
foreach ( var parameter in _pscommand . Commands [ 0 ]. Parameters )
{
paramCol . Add ( parameter );
}
// The StartParameters may be edited or overwritten by the user.
// The parameters will need to be added back to the command object before
// execution.
StartParameters = new List < CommandParameterCollection > { paramCol };
_pscommand . Commands [ 0 ]. Parameters . Clear ();
CommonInit ( runspace , runspacePool );
}
internal void InitializeExistingJobProxy ( PSObject o , Runspace runspace , RunspacePool runspacePool )
{
Dbg . Assert ( o != null , "deserialized job cannot be null" );
_tracer . WriteMessage ( ClassNameTrace , "InitializeExistingJobProxy" , _remoteJobInstanceId , this ,
"Initializing job proxy for existing job." , null );
_pscommand = null ;
_startCalled = true ;
_jobInitialized = true ;
CommonInit ( runspace , runspacePool );
PopulateJobProperties ( o );
// Do not set child job states, or subscribe to child events. This will
// be done in ReceiveJob whenever it is called.
// We need to wait until then because event handlers may not be specified until then.
// Construct the parent job's start parameters specific for SM+
List < Hashtable > psParamCollection = new List < Hashtable >();
object psPrivateMetadata = null ;
foreach ( PSChildJobProxy job in ChildJobs )
{
// Each child job should have StartParameters.Count = 1.
if ( job . StartParameters . Count == 0 ) continue ;
Hashtable childJobCol = new Hashtable ();
foreach ( CommandParameter p in job . StartParameters [ 0 ])
{
if ( psPrivateMetadata == null && p . Name . Equals ( "PSPrivateMetadata" , StringComparison . OrdinalIgnoreCase ))
{
psPrivateMetadata = p . Value ;
}
childJobCol . Add ( p . Name , p . Value );
}
psParamCollection . Add ( childJobCol );
}
CommandParameterCollection newStartParameters = new CommandParameterCollection ();
newStartParameters . Add ( new CommandParameter ( "PSParameterCollection" , psParamCollection ));
if ( psPrivateMetadata != null )
{
newStartParameters . Add ( new CommandParameter ( "PSPrivateMetadata" , psPrivateMetadata ));
}
StartParameters . Add ( newStartParameters );
}
private void CommonInit ( Runspace runspace , RunspacePool runspacePool )
{
_runspacePool = runspacePool ;
_runspace = runspace ;
_receivePowerShell . InvocationStateChanged += ReceivePowerShellInvocationStateChanged ;
// Output buffer other than the PSJobProxy output is needed.
// If the proxy's output buffer is used, data added there will raise events
// to the consumer before we have re-distributed the output.
_receivePowerShellOutput . DataAdded += DataAddedToOutput ;
_receivePowerShell . Streams . Error . DataAdded += DataAddedToError ;
_receivePowerShell . Streams . Debug . DataAdded += DataAddedToDebug ;
_receivePowerShell . Streams . Verbose . DataAdded += DataAddedToVerbose ;
_receivePowerShell . Streams . Warning . DataAdded += DataAddedToWarning ;
_receivePowerShell . Streams . Progress . DataAdded += DataAddedToProgress ;
_receivePowerShell . Streams . Information . DataAdded += DataAddedToInformation ;
}
/// <summary>
/// Helper to do error checking for getting a property of type T from a PSobject.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
/// <returns></returns>
internal static bool TryGetJobPropertyValue < T >( PSObject o , string propertyName , out T propertyValue )
{
propertyValue = default ( T );
PSPropertyInfo propertyInfo = o . Properties [ propertyName ];
if ( propertyInfo == null || !( propertyInfo . Value is T )) return false ;
propertyValue = ( T ) propertyInfo . Value ;
return true ;
}
#endregion Internal Methods
#region Async helpers
private enum ActionType
{
Remove
}
private class AsyncCompleteContainer
{
internal AsyncCompletedEventArgs EventArgs ;
internal ActionType Action ;
}
private delegate void JobActionWorkerDelegate ( AsyncOperation asyncOp , ActionType action );
private void JobActionWorker ( AsyncOperation asyncOp , ActionType action )
{
Exception exception = null ;
#pragma warning disable 56500
try
{
switch ( action )
{
case ActionType . Remove :
DoRemove ( asyncOp . UserSuppliedState );
break ;
}
}
catch ( Exception e )
{
// Called on a background thread, need to include any exception in
// event arguments.
exception = e ;
}
#pragma warning restore 56500
var eventArgs = new AsyncCompletedEventArgs ( exception , false , asyncOp . UserSuppliedState );
var container = new AsyncCompleteContainer { EventArgs = eventArgs , Action = action };
// End the task. The asyncOp object is responsible
// for marshaling the call.
asyncOp . PostOperationCompleted ( JobActionAsyncCompleted , container );
}
private void JobActionAsyncCompleted ( object operationState )
{
var container = operationState as AsyncCompleteContainer ;
Dbg . Assert ( container != null , "AsyncComplete Container is null; not passed properly." );
_tracer . WriteMessage ( ClassNameTrace , "JobActionAsyncCompleted" , _remoteJobInstanceId , this ,
"Async operation {0} completed" , container . Action . ToString ());
switch ( container . Action )
{
case ActionType . Remove :
if ( container . EventArgs . Error == null ) RemoveComplete . WaitOne ();
OnRemoveJobCompleted ( container . EventArgs );
break ;
}
}
#endregion Async helpers
#region Private Methods
private bool ShouldQueueOperation ()
{
bool queueThisOperation ;
lock ( _inProgressSyncObject )
{
if (! _inProgress )
{
_inProgress = true ;
queueThisOperation = false ;
}
else
{
queueThisOperation = true ;
}
}
return queueThisOperation ;
}
private void ProcessQueue ()
{
bool queueWorkItem = false ;
lock ( _inProgressSyncObject )
{
if (! _pendingOperations . IsEmpty && ! _workerCreated )
{
queueWorkItem = true ;
_workerCreated = true ;
_inProgress = true ;
}
else
_inProgress = false ;
}
if ( queueWorkItem )
{
ThreadPool . QueueUserWorkItem ( ProcessQueueWorker );
}
}
private void ProcessQueueWorker ( object state )
{
// If this operation has been queued to the thread pool, processing rights
// already belong here. Release only when finished.
while ( true )
{
QueueOperation nextOperation ;
lock ( _inProgressSyncObject )
{
if (! _pendingOperations . TryDequeue ( out nextOperation ))
{
_inProgress = false ;
_workerCreated = false ;
break ;
}
}
switch ( nextOperation )
{
case QueueOperation . Stop :
#pragma warning disable 56500
try
{
DoStopAsync ();
Finished . WaitOne ();
}
catch ( Exception e )
{
2016-08-28 05:25:01 +00:00
// Transfer exception via event arguments.
2016-03-30 14:20:52 -07:00
OnStopJobCompleted ( new AsyncCompletedEventArgs ( e , false , null ));
}
break ;
case QueueOperation . Suspend :
try
{
DoSuspendAsync ();
JobSuspendedOrFinished . WaitOne ();
}
catch ( Exception e )
{
// Transfer exception via event arguments.
OnSuspendJobCompleted ( new AsyncCompletedEventArgs ( e , false , null ));
}
break ;
case QueueOperation . Resume :
try
{
DoResumeAsync ();
JobRunningOrFinished . WaitOne ();
}
catch ( Exception e )
{
// Transfer exception via event arguments.
OnResumeJobCompleted ( new AsyncCompletedEventArgs ( e , false , null ));
}
#pragma warning restore 56500
break ;
}
}
}
/// <summary>
/// Checks if there is more data in the specified collection
/// </summary>
/// <typeparam name="T">Type of the collection</typeparam>
/// <param name="collection">collection to check</param>
/// <returns>true if the collection has more data</returns>
private static bool CollectionHasMoreData < T >( PSDataCollection < T > collection )
{
return ( collection . IsOpen || collection . Count > 0 );
}
/// <summary>
/// Worker method which starts the job
/// </summary>
private void DoStartAsync ( EventHandler < JobDataAddedEventArgs > dataAdded , EventHandler < JobStateEventArgs > stateChanged , PSDataCollection < object > input )
{
// Checks disposed, and whether the start operation is valid.
AssertJobCanBeStartedAndSetStateToRunning ();
Dbg . Assert ( _inProgress == false , "Start should always be able to obtain processing rights if it is a valid call." );
lock ( _inProgressSyncObject ) { _inProgress = true ; }
// Set input and handlers. Should only be done after we've confirmed that this
// call to StartJob should proceed, which isn't until now.
lock ( SyncRoot )
{
Dbg . Assert ( _dataAddedHandler == null , "DataAdded cannot be specified before StartJob is called" );
Dbg . Assert ( _stateChangedHandler == null , "StateChanged cannot be specified before StartJob is called" );
_dataAddedHandler = dataAdded ;
_stateChangedHandler = stateChanged ;
}
_tracer . WriteMessage ( ClassNameTrace , "DoStartAsync" , _remoteJobInstanceId , this ,
"Starting command invocation." , null );
2016-07-29 13:02:49 -07:00
s_structuredTracer . BeginProxyJobExecution ( InstanceId );
2016-03-30 14:20:52 -07:00
DoStartPrepare ();
_receivePowerShell . BeginInvoke ( input , _receivePowerShellOutput , null , CleanupReceivePowerShell , null );
}
private void DoStartPrepare ()
{
// Either a runspace or a runspacepool has to be set
// before the job can be started
if ( _runspacePool == null && _runspace == null )
{
throw PSTraceSource . NewInvalidOperationException ( PowerShellStrings . RunspaceAndRunspacePoolNull );
}
AssignRunspaceOrRunspacePool ( _receivePowerShell );
// set the parameters. the user may have changed them.
bool found = false ;
if ( StartParameters != null && StartParameters . Count > 0 )
{
foreach ( var parameter in StartParameters [ 0 ])
{
_pscommand . Commands [ 0 ]. Parameters . Add ( parameter );
if ( string . Compare ( parameter . Name , "AsJob" , StringComparison . OrdinalIgnoreCase ) == 0 )
{
if (!( parameter . Value is bool ) || !( bool ) parameter . Value )
{
// If the AsJob Parameter has been passed and explicitly set to false, we should
// not proceed with the operation. This is an error.
throw PSTraceSource . NewInvalidOperationException ( PowerShellStrings . JobProxyAsJobMustBeTrue );
}
found = true ;
}
}
}
// If the user has overwritten the parameter collection, we should add AsJob back again.
if (! found )
{
_pscommand . Commands [ 0 ]. Parameters . Add ( "AsJob" , true );
}
// set the commands for the powershell
_receivePowerShell . Commands = _pscommand ;
// Setup receive-job data streaming.
// Pipe the invocation with as job to Receive-Job, because we do not yet have the job's
// instance ID.
// Use the WriteJob parameter to cause Receive-Job to output the job object.
AddReceiveJobCommandToPowerShell ( _receivePowerShell , true );
}
/// <summary>
/// Worker method which stops the job
/// </summary>
private void DoStopAsync ()
{
if (! AssertStopJobIsValidAndSetToStopping ())
{
OnStopJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
return ;
}
_receivePowerShell . Stop ();
_receivePowerShell . Commands . Clear ();
_receivePowerShell . GenerateNewInstanceId ();
_receivePowerShell . AddCommand ( "Stop-Job" ). AddParameter ( "InstanceId" , _remoteJobInstanceId ). AddParameter ( "PassThru" );
AddReceiveJobCommandToPowerShell ( _receivePowerShell , false );
_receivePowerShell . BeginInvoke (( PSDataCollection < PSObject >) null , _receivePowerShellOutput , null , CleanupReceivePowerShell , null );
}
/// <summary>
/// Worker method which suspends the job
/// </summary>
private void DoSuspendAsync ()
{
if (! AssertSuspendJobIsValidAndSetToSuspending ())
{
OnSuspendJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
return ;
}
_receivePowerShell . Stop ();
_receivePowerShell . Commands . Clear ();
_receivePowerShell . GenerateNewInstanceId ();
_receivePowerShell . AddCommand ( "Suspend-Job" ). AddParameter ( "InstanceId" , _remoteJobInstanceId ). AddParameter ( "Wait" );
AddReceiveJobCommandToPowerShell ( _receivePowerShell , false );
_receivePowerShell . BeginInvoke (( PSDataCollection < PSObject >) null , _receivePowerShellOutput , null , CleanupReceivePowerShell , null );
}
/// <summary>
/// Worker method to resume the job
/// </summary>
private void DoResumeAsync ()
{
AssertResumeJobIsValidAndSetToRunning ();
// If the job state was not set to running, the job should not be resumed. (True if job is in finished state.)
if ( JobStateInfo . State != JobState . Running )
{
OnResumeJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
return ;
}
_receivePowerShell . Stop ();
_receivePowerShell . Commands . Clear ();
_receivePowerShell . GenerateNewInstanceId ();
_receivePowerShell . AddCommand ( "Resume-Job" ). AddParameter ( "InstanceId" , _remoteJobInstanceId ). AddParameter ( "Wait" );
AddReceiveJobCommandToPowerShell ( _receivePowerShell , false );
_receivePowerShell . BeginInvoke (( PSDataCollection < PSObject >) null , _receivePowerShellOutput , null , CleanupReceivePowerShell , null );
}
/// <summary>
/// Worker method to remove the remote job object
/// </summary>
/// <param name="state">state information indicates the "force" parameter</param>
private void DoRemove ( object state )
{
AssertNotDisposed ();
_tracer . WriteMessage ( ClassNameTrace , "DoRemove" , _remoteJobInstanceId , this , "Start" , null );
Dbg . Assert ( state is bool , "State should be boolean indicating 'force'" );
// Do not check RemoveCalled outside of the lock. We do not want to wait on RemoveComplete unless this lock
// has been released by the thread that continues.
if ( _isDisposed || _remoteJobRemoved ) return ;
lock ( SyncRoot )
{
if ( _isDisposed || _remoteJobRemoved || _removeCalled ) return ;
// Only return here if the job has not been started.
if ( _remoteJobInstanceId == Guid . Empty && ! _startCalled ) return ;
AssertRemoveJobIsValid ();
Dbg . Assert (! _removeCalled , "removecalled should only be modified within base locks" );
_removeCalled = true ;
RemoveComplete . Reset ();
}
// _receivePowerShell will never be null and so
// there is no need to do a null check
Dbg . Assert ( _receivePowerShell != null , "ReceivePowerShell should not be null" );
try
{
// Ensure that the job has been initialized, if it has been started. If
// the instance ID is Guid.Empty after this, there was an error, and the
// job should not be on the server. If it is, we cannot reliably connect to
// remove it.
// Do this within the Try/Finally block so that the wait handle will be set
// on exit.
2016-09-02 16:21:20 +00:00
_jobInitializedWaitHandle . WaitOne ();
2016-03-30 14:20:52 -07:00
if ( _remoteJobInstanceId == Guid . Empty ) return ;
// Stop the receive-job command if it's in progress.
_receivePowerShell . Stop ();
// Remove the job with Remove-Job.
using ( PowerShell powershell = PowerShell . Create ())
{
// Either the runspace or runspace pool will be
// not null at this point. This is because of
// the following:
// 1. The job state is running
// 2. The job was started using either a runspace
// or a runspace pool
// 3. Once a job has been started, the values cannot
// be modified until the job reaches a terminal
// state
// Therefore it is not required to validate the above
// here
Dbg . Assert (( _runspace != null && _runspacePool == null ) ||
( _runspace == null && _runspacePool != null ),
"Either the runspace or a runspacepool should not be null" );
AssignRunspaceOrRunspacePool ( powershell );
// set the commands for the powershell
powershell . Commands . AddCommand ( "Remove-Job" ). AddParameter ( "InstanceId" , _remoteJobInstanceId );
if (( bool ) state ) powershell . AddParameter ( "Force" , true ). AddParameter ( "ErrorAction" , ActionPreference . SilentlyContinue );
try
{
_tracer . WriteMessage ( ClassNameTrace , "DoRemove" , _remoteJobInstanceId , this ,
"Invoking Remove-Job" , null );
powershell . Invoke ();
}
catch ( Exception e )
{
CommandProcessorBase . CheckForSevereException ( e );
// since this is third party code call out - it is ok
// to catch Exception. In all other cases the specific
// exception must be caught
_tracer . WriteMessage ( ClassNameTrace , "DoRemove" , _remoteJobInstanceId , this ,
"Setting job state to failed since invoking Remove-Job failed." , null );
DoSetJobState ( JobState . Failed , e );
throw ;
}
if ( powershell . Streams . Error != null && powershell . Streams . Error . Count > 0 )
{
throw powershell . Streams . Error [ 0 ]. Exception ;
}
}
_tracer . WriteMessage ( ClassNameTrace , "DoRemove" , _remoteJobInstanceId , this ,
"Completed Invoking Remove-Job" , null );
lock ( SyncRoot )
{
_remoteJobRemoved = true ;
}
if (! IsFinishedState ( JobStateInfo . State ))
{
DoSetJobState ( JobState . Stopped );
}
}
catch ( Exception )
{
lock ( SyncRoot )
{
Dbg . Assert (! _remoteJobRemoved ,
"Should never allow DoRemove to be called again if proxy job has removed its server job." );
_removeCalled = false ;
}
throw ;
}
finally
{
RemoveComplete . Set ();
}
}
private void AddReceiveJobCommandToPowerShell ( PowerShell powershell , bool writeJob )
{
powershell . AddCommand ( "Receive-Job" ). AddParameter ( "Wait" ). AddParameter ( "WriteEvents" ). AddParameter ( "Verbose" ). AddParameter ( "Debug" );
if ( writeJob )
powershell . AddParameter ( "WriteJobInResults" );
if ( RemoveRemoteJobOnCompletion )
powershell . AddParameter ( "AutoRemoveJob" );
}
private void CleanupReceivePowerShell ( IAsyncResult asyncResult )
{
#pragma warning disable 56500
try
{
_receivePowerShell . EndInvoke ( asyncResult );
// set the state based on the previous computed state
_tracer . WriteMessage ( ClassNameTrace , "CleanupReceivePowerShell" , Guid . Empty , this ,
"Setting job state to {0} from computed stated" , _computedJobState . ToString ());
ValidateAndDoSetJobState ( _computedJobState );
}
catch ( PipelineStoppedException e )
{
// Raised if the pipeline was stopped.
// Pipeline stopped indicates that the command was interrupted, and it should
// not be used to indicate that a job failed.
_tracer . TraceException ( e );
}
catch ( PSRemotingDataStructureException e )
{
// Raised if the pipeline was stopped. (remote case)
// Pipeline stopped indicates that the command was interrupted, and it should
// not be used to indicate that a job failed.
_tracer . TraceException ( e );
}
catch ( RemoteException e )
{
if ( Deserializer . IsInstanceOfType ( e . SerializedRemoteException , typeof ( PipelineStoppedException )))
{
// Raised if the pipeline was stopped. (remote case)
// Pipeline stopped indicates that the command was interrupted, and it should
// not be used to indicate that a job failed.
_tracer . TraceException ( e );
}
else
{
_tracer . TraceException ( e );
DoSetJobState ( JobState . Failed , e );
}
}
catch ( Exception e )
{
// EndInvoke may throw any exception that the command execution could throw
// Since this is third party code, we will catch and trace the exception.
// Exception raised to the user using JobStateInfo.Reason.
_tracer . WriteMessage ( ClassNameTrace , "CleanupReceivePowerShell" , _remoteJobInstanceId , this ,
"Exception calling receivePowerShell.EndInvoke" , null );
_tracer . TraceException ( e );
DoSetJobState ( JobState . Failed , e );
// This throw would cause the client application to crash if an exception is thrown in the command invocation.
}
#pragma warning restore 56500
}
/// <summary>
/// Assigns either a runspace or runspacepool to the specified powershell
/// instance
/// </summary>
/// <param name="powershell">powershell instance to which the set has to
/// happen</param>
private void AssignRunspaceOrRunspacePool ( PowerShell powershell )
{
Dbg . Assert ( _runspacePool == null && _runspace != null ||
_runspace == null && _runspacePool != null ,
"Either a runspace or a runspacepool should be assigned to the proxy job" );
if ( _runspacePool == null )
{
powershell . Runspace = _runspace ;
}
else
{
powershell . RunspacePool = _runspacePool ;
}
}
private void HandleMyStateChange ( object sender , JobStateEventArgs e )
{
switch ( e . JobStateInfo . State )
{
case JobState . Running :
{
lock ( SyncRoot )
{
if ( e . PreviousJobStateInfo . State == JobState . NotStarted )
{
PSBeginTime = DateTime . Now ;
}
JobRunningOrFinished . Set ();
JobSuspendedOrFinished . Reset ();
OnResumeJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
}
}
break ;
case JobState . Suspended :
{
lock ( SyncRoot )
{
PSEndTime = DateTime . Now ;
JobSuspendedOrFinished . Set ();
JobRunningOrFinished . Reset ();
OnSuspendJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
}
}
break ;
case JobState . Failed :
case JobState . Completed :
case JobState . Stopped :
{
lock ( SyncRoot )
{
// In a finished state, further state transitions will not occur.
PSEndTime = DateTime . Now ;
// Release any thread waiting for Start, Suspend, Resume
JobRunningOrFinished . Set ();
OnResumeJobCompleted ( new AsyncCompletedEventArgs ( e . JobStateInfo . Reason , false , null ));
JobSuspendedOrFinished . Set ();
OnSuspendJobCompleted ( new AsyncCompletedEventArgs ( e . JobStateInfo . Reason , false , null ));
2016-09-02 16:21:20 +00:00
_jobInitializedWaitHandle . Set ();
2016-03-30 14:20:52 -07:00
OnStartJobCompleted ( new AsyncCompletedEventArgs ( e . JobStateInfo . Reason , false , null ));
OnStopJobCompleted ( new AsyncCompletedEventArgs ( e . JobStateInfo . Reason , false , null ));
}
}
break ;
}
ProcessQueue ();
}
/// <summary>
/// Event handler for InvocationStateChanged on the powershell
/// object running receive-job
/// </summary>
/// <param name="sender">sender of this event</param>
/// <param name="e">argument describing this event</param>
private void ReceivePowerShellInvocationStateChanged ( object sender , PSInvocationStateChangedEventArgs e )
{
_tracer . WriteMessage ( ClassNameTrace , "ReceivePowerShellInvocationStateChanged" , _remoteJobInstanceId , this ,
"receivePowerShell state changed to {0}" , e . InvocationStateInfo . State . ToString ());
switch ( e . InvocationStateInfo . State )
{
case PSInvocationState . Running :
{
return ;
}
case PSInvocationState . Completed :
// Job State in this case is set in CleanupReceivePowerShell.
break ;
case PSInvocationState . Failed :
{
var newState = JobState . Failed ;
var reason = e . InvocationStateInfo . Reason == null
? String . Empty
: e . InvocationStateInfo . Reason . ToString ();
_tracer . WriteMessage ( ClassNameTrace , "ReceivePowerShellInvocationStateChanged" , _remoteJobInstanceId , this ,
"Setting job state to {0} old state was {1} and reason is {2}." , newState . ToString (), JobStateInfo . State . ToString (), reason );
DoSetJobState ( newState , e . InvocationStateInfo . Reason );
}
break ;
case PSInvocationState . Stopped :
break ;
}
}
/// <summary>
/// Assigns job properties and creates child job tree.
/// </summary>
/// <param name="o">Deserialized job object representing the remote job for this proxy.</param>
private void PopulateJobProperties ( PSObject o )
{
Dbg . Assert ( Deserializer . IsDeserializedInstanceOfType ( o , typeof ( Job )),
"Cannot populate job members unless o is a deserialized job." );
TryGetJobPropertyValue ( o , "InstanceId" , out _remoteJobInstanceId );
TryGetJobPropertyValue ( o , "StatusMessage" , out _remoteJobStatusMessage );
TryGetJobPropertyValue ( o , "Location" , out _remoteJobLocation );
2016-07-29 13:02:49 -07:00
s_structuredTracer . ProxyJobRemoteJobAssociation ( InstanceId , _remoteJobInstanceId );
2016-03-30 14:20:52 -07:00
string name ;
TryGetJobPropertyValue ( o , "Name" , out name );
Name = name ;
// Create proxy jobs for each child job.
// The remote child jobs will be started and managed by their parent. They are provided as
// children on this job for access to the state, properties and for direct management.
PSObject remoteChildrenObject ;
if (! TryGetJobPropertyValue ( o , "ChildJobs" , out remoteChildrenObject )) return ; // No ChildJobs property
var remoteChildren = remoteChildrenObject . BaseObject as ArrayList ;
Dbg . Assert ( remoteChildren != null , "ChildJobs property had unexpected type." );
foreach ( PSObject job in remoteChildren . Cast < PSObject >(). Where ( job => !( job . BaseObject is string )))
{
Guid childJobInstanceId ;
if (! TryGetJobPropertyValue ( job , "InstanceId" , out childJobInstanceId ))
{
Dbg . Assert ( false , "ChildJobs should be serialized to include InstanceID, cannot interact with them otherwise." );
continue ;
}
var childProxyJob = new PSChildJobProxy ( Command , job ); // All have the same workflow name.
_childJobsMapping . Add ( childJobInstanceId , childProxyJob );
// Set DataAddedCount for each child to match parent.
// This may otherwise not be available to set until data arrives.
childProxyJob . Output . DataAddedCount = Output . DataAddedCount ;
childProxyJob . Error . DataAddedCount = Error . DataAddedCount ;
childProxyJob . Progress . DataAddedCount = Progress . DataAddedCount ;
childProxyJob . Warning . DataAddedCount = Warning . DataAddedCount ;
childProxyJob . Verbose . DataAddedCount = Verbose . DataAddedCount ;
childProxyJob . Debug . DataAddedCount = Debug . DataAddedCount ;
childProxyJob . Information . DataAddedCount = Information . DataAddedCount ;
// Now see if there are start parameters we can populate.
PSObject childJobStartParametersObject ;
if ( TryGetJobPropertyValue ( job , "StartParameters" , out childJobStartParametersObject ))
{
PopulateStartParametersOnChild ( childJobStartParametersObject , childProxyJob );
}
ChildJobs . Add ( childProxyJob );
}
}
private void PopulateStartParametersOnChild ( PSObject childJobStartParametersObject , PSChildJobProxy childProxyJob )
{
ArrayList childJobStartParameters = childJobStartParametersObject . BaseObject as ArrayList ;
if ( childJobStartParameters != null )
{
List < CommandParameterCollection > listComParCol = new List < CommandParameterCollection >();
// check childjobstartparameters--was a List<CommandParameterCollection>
foreach ( PSObject paramCollection in childJobStartParameters . Cast < PSObject >(). Where ( paramCollection => !( paramCollection . BaseObject is string )))
{
ArrayList parameterCollection = paramCollection . BaseObject as ArrayList ;
if ( parameterCollection != null )
{
CommandParameterCollection newComParCol = new CommandParameterCollection ();
foreach ( PSObject deserializedCommandParameter in parameterCollection . Cast < PSObject >(). Where ( deserializedCommandParameter => !( deserializedCommandParameter . BaseObject is string )))
{
string parameterName ;
object parameterValue ;
if ( TryGetJobPropertyValue ( deserializedCommandParameter , "Name" , out parameterName ) && TryGetJobPropertyValue ( deserializedCommandParameter , "Value" , out parameterValue ))
{
CommandParameter cp = new CommandParameter ( parameterName , parameterValue );
newComParCol . Add ( cp );
}
}
listComParCol . Add ( newComParCol );
}
}
childProxyJob . StartParameters = listComParCol ;
}
}
private void RegisterChildEvents ()
{
if ( _childEventsRegistered ) return ;
lock ( SyncRoot )
{
if ( _childEventsRegistered ) return ;
_childEventsRegistered = true ;
// if the data added handler was provided, use it to subscribe to child events.
if ( _dataAddedHandler != null )
{
foreach ( PSChildJobProxy job in ChildJobs )
{
Dbg . Assert ( job != null , "ChildJob that is not child job proxy exists" );
job . JobDataAdded += _dataAddedHandler ;
}
}
// we need to register for our handler to compute parent proxy's state
// after the passed in handlers are registered. This will ensure that
// our handler is invoked last
foreach ( var job in ChildJobs )
{
job . StateChanged += HandleChildProxyJobStateChanged ;
}
// likewise for state changed
if ( _stateChangedHandler != null )
{
foreach ( PSChildJobProxy job in ChildJobs )
{
Dbg . Assert ( job != null , "ChildJob that is not child job proxy exists" );
job . StateChanged += _stateChangedHandler ;
}
}
}
}
private void UnregisterChildEvents ()
{
lock ( SyncRoot )
{
// Check inside the lock only, if the lock is held by the register method
// the childEventsRegistered may be altered before the lock is released.
if (! _childEventsRegistered ) return ;
// if the data added handler was provided, use it to unsubscribe from child events.
if ( _dataAddedHandler != null )
{
foreach ( PSChildJobProxy job in ChildJobs )
{
Dbg . Assert ( job != null , "ChildJob that is not child job proxy exists" );
job . JobDataAdded -= _dataAddedHandler ;
}
}
// likewise for state changed
if ( _stateChangedHandler != null )
{
foreach ( PSChildJobProxy job in ChildJobs )
{
Dbg . Assert ( job != null , "ChildJob that is not child job proxy exists" );
job . StateChanged -= _stateChangedHandler ;
}
}
foreach ( var job in ChildJobs )
{
job . StateChanged -= HandleChildProxyJobStateChanged ;
}
_childEventsRegistered = false ;
}
}
private void HandleChildProxyJobStateChanged ( object sender , JobStateEventArgs e )
{
JobState computedJobState ;
if (! ContainerParentJob . ComputeJobStateFromChildJobStates ( ClassNameTrace , e , ref _blockedChildJobsCount ,
2016-07-29 13:02:49 -07:00
ref _suspendedChildJobsCount ,
2016-03-30 14:20:52 -07:00
ref _suspendingChildJobsCount ,
2016-07-29 13:02:49 -07:00
ref _finishedChildJobsCount ,
ref _failedChildJobsCount ,
ref _stoppedChildJobsCount ,
2016-03-30 14:20:52 -07:00
ChildJobs . Count ,
2016-07-29 13:02:49 -07:00
out computedJobState ))
return ;
2016-08-28 05:25:01 +00:00
if ( computedJobState == JobState . Suspending ) return ; // Ignore for proxy job
2016-03-30 14:20:52 -07:00
_tracer . WriteMessage ( ClassNameTrace , "HandleChildProxyJobStateChanged" , Guid . Empty , this ,
"storing job state to {0}" , computedJobState . ToString ());
// we need to store the state and set it only when invocation
// state changed for _receivePowerShell is received. This is to
// enable consumers from disposing the proxy job on its state
// changed handler
_computedJobState = computedJobState ;
}
/// <summary>
/// Check if changes to the jobs properties can be accepted
/// </summary>
private void AssertChangesCanBeAccepted ()
{
lock ( SyncRoot )
{
AssertNotDisposed ();
if ( JobStateInfo . State != JobState . NotStarted )
{
throw new InvalidJobStateException ( JobStateInfo . State );
}
}
}
private void AssertJobCanBeStartedAndSetStateToRunning ()
{
lock ( SyncRoot )
{
AssertNotDisposed ();
// only a job which is not started or which is in a terminal
// state can be restarted
if ( JobStateInfo . State != JobState . NotStarted && ! IsFinishedState ( JobStateInfo . State ))
{
throw new InvalidJobStateException ( JobStateInfo . State , StringUtil . Format ( PowerShellStrings . JobCannotBeStartedWhenRunning ));
}
if ( _startCalled )
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . JobCanBeStartedOnce );
}
_startCalled = true ;
}
_tracer . WriteMessage ( ClassNameTrace , "AssertJobCanBeStartedAndSetStateToRunning" , _remoteJobInstanceId , this ,
"Setting job state to running" , null );
ValidateAndDoSetJobState ( JobState . Running );
}
private bool AssertStopJobIsValidAndSetToStopping ()
{
lock ( SyncRoot )
{
AssertNotDisposed ();
if ( JobStateInfo . State == JobState . NotStarted )
{
throw new InvalidJobStateException ( JobState . NotStarted );
}
if ( IsFinishedState ( JobStateInfo . State ))
return false ;
}
DoSetJobState ( JobState . Stopping );
return true ;
}
private bool AssertSuspendJobIsValidAndSetToSuspending ()
{
lock ( SyncRoot )
{
AssertNotDisposed ();
// Valid states: Running, Suspended, Suspending
if ( JobStateInfo . State != JobState . Suspended
&& JobStateInfo . State != JobState . Suspending
&& JobStateInfo . State != JobState . Running )
{
throw new InvalidJobStateException ( JobStateInfo . State );
}
}
if ( JobStateInfo . State != JobState . Running ) return false ;
DoSetJobState ( JobState . Suspending );
return true ;
}
private void AssertResumeJobIsValidAndSetToRunning ()
{
lock ( SyncRoot )
{
AssertNotDisposed ();
// Valid states: Running, Suspended, Suspending
if ( JobStateInfo . State != JobState . Suspended
&& JobStateInfo . State != JobState . Suspending
&& JobStateInfo . State != JobState . Running )
{
throw new InvalidJobStateException ( JobStateInfo . State );
}
}
if ( JobStateInfo . State != JobState . Running ) ValidateAndDoSetJobState ( JobState . Running );
foreach ( PSChildJobProxy job in ChildJobs )
{
Dbg . Assert ( job != null , "child job is not PSChildJobProxy" );
if (! IsFinishedState ( job . JobStateInfo . State ))
job . DoSetJobState ( JobState . Running );
}
}
private void AssertRemoveJobIsValid ()
{
if ( JobStateInfo . State == JobState . NotStarted || _remoteJobInstanceId == Guid . Empty )
{
throw new InvalidJobStateException ( JobStateInfo . State );
}
}
/// <summary>
/// Assert if the object is not yet disposed and if so
/// throw an ObjectDisposedException
/// </summary>
/// <exception cref="ObjectDisposedException">Thrown if
/// the object has already been disposed</exception>
/// <remarks>Method is not thread-safe. Caller has to
/// ensure thread safety</remarks>
private new void AssertNotDisposed ()
{
if ( _isDisposed )
{
throw PSTraceSource . NewObjectDisposedException ( "PSJobProxy" );
}
}
private void ValidateAndDoSetJobState ( JobState state , Exception reason = null )
{
// Do not set the state to running if the job is in the process of suspending or stopping.
// This can happen if Stop or Suspend is called during job initialization.
if (( _previousState == JobState . Stopping || _previousState == JobState . Suspending ) && state == JobState . Running )
return ;
DoSetJobState ( state , reason );
}
private void DoSetJobState ( JobState state , Exception reason = null )
{
// If the object is disposed, the user does not care about state.
if ( _isDisposed ) return ;
lock ( SyncRoot )
{
if ( _previousState == state ) return ; // Do not set the state if the desired state is accurate.
_previousState = state ;
}
#if DEBUG
switch ( JobStateInfo . State )
{
case JobState . Running :
Dbg . Assert ( state != JobState . NotStarted && state != JobState . Blocked ,
"JobProxy invalid state transition from Running." );
break ;
case JobState . Stopping :
Dbg . Assert (
state == JobState . Stopped || state == JobState . Failed || state == JobState . Completed ,
"JobProxy invalid state transition from Stopping." );
break ;
case JobState . Stopped :
Dbg . Assert ( false , "JobProxy should never transition after Stopped state." );
break ;
case JobState . Suspending :
Dbg . Assert (
state == JobState . Suspended || state == JobState . Completed || state == JobState . Failed ||
state == JobState . Stopped || state == JobState . Stopping ,
"JobProxy invalid state transition from Suspending." );
break ;
case JobState . Suspended :
Dbg . Assert (
state == JobState . Running || state == JobState . Stopping || state == JobState . Stopped ,
"JobProxy invalid state transition from Suspended." );
break ;
case JobState . Failed :
Dbg . Assert ( false , "JobProxy should never transition after Failed state." );
break ;
case JobState . Completed :
Dbg . Assert ( false , "JobProxy should never transition after Completed state." );
break ;
case JobState . Disconnected :
Dbg . Assert ( state != JobState . NotStarted && state != JobState . Blocked ,
"JobProxy invalid state transition from Disconnected." );
break ;
case JobState . Blocked :
Dbg . Assert ( false , "JobProxy should never be in a blocked state" );
break ;
default :
break ;
}
#endif
try
{
_tracer . WriteMessage ( ClassNameTrace , "DoSetJobState" , _remoteJobInstanceId , this ,
"BEGIN Set job state to {0} and call event handlers" , state . ToString ());
2016-07-29 13:02:49 -07:00
s_structuredTracer . EndProxyJobExecution ( InstanceId );
s_structuredTracer . BeginProxyJobEventHandler ( InstanceId );
2016-03-30 14:20:52 -07:00
SetJobState ( state , reason );
2016-07-29 13:02:49 -07:00
s_structuredTracer . EndProxyJobEventHandler ( InstanceId );
2016-03-30 14:20:52 -07:00
_tracer . WriteMessage ( ClassNameTrace , "DoSetJobState" , _remoteJobInstanceId , this ,
"END Set job state to {0} and call event handlers" , state . ToString ());
}
catch ( ObjectDisposedException )
{
_tracer . WriteMessage ( ClassNameTrace , "DoSetJobState" , _remoteJobInstanceId , this ,
"Caught object disposed exception" , null );
}
}
#endregion Private Methods
#region Private methods for Output management
private T GetRecord < T >( object sender )
{
lock ( SyncRoot )
{
var collection = sender as PSDataCollection < T >;
Dbg . Assert ( collection != null , "Sender cannot be null" );
T data = collection . ReadAndRemoveAt0 ();
Dbg . Assert ( data != null , "DataAdded should be raised for each object added" );
return data ;
}
}
private void DataAddedToOutput ( object sender , DataAddedEventArgs e )
{
var newObject = GetRecord < PSObject >( sender );
if (! _jobInitialized )
{
// If the job is not initialized, then this should be the first object received after
// invoking a command. This condition should be met when StartJob is called.
// Note: These events will always come in sequence, So this is not thread safe.
_jobInitialized = true ;
// Because we have added the AsJob parameter we expect
// the command to write out a job, if it does not
// the command fails.)
// If the command writes an error before any output, the proxy will use this
// as an indication of a failed command execution.
if (! Deserializer . IsDeserializedInstanceOfType ( newObject , typeof ( Job )))
{
_tracer . WriteMessage ( ClassNameTrace , "DataAddedToOutput" , _remoteJobInstanceId , this ,
"Setting job state to failed. Command did not return a job object." ,
null );
Exception reason = ( _receivePowerShell . Streams . Error . Count == 0 ||
_receivePowerShell . Streams . Error [ 0 ]. Exception == null )
? PSTraceSource . NewNotSupportedException ( PowerShellStrings . CommandDoesNotWriteJob )
: _receivePowerShell . Streams . Error [ 0 ]. Exception ;
DoSetJobState ( JobState . Failed , reason );
2016-09-02 16:21:20 +00:00
_jobInitializedWaitHandle . Set ();
2016-03-30 14:20:52 -07:00
OnStartJobCompleted ( new AsyncCompletedEventArgs ( reason , false , null ));
return ;
}
PopulateJobProperties ( newObject );
RegisterChildEvents ();
// Child Job States must be set after events are subscribed.
// All jobs start in the running state. Receive-Job will return
// a persistent job state event for each child job sometime after
// the job object is returned. This will update the job state of
// the child in a subsequent call to DataAddedToOutput.
foreach ( PSChildJobProxy child in ChildJobs )
{
Dbg . Assert ( child != null , "Child should always be PSChildJobProxy" );
child . DoSetJobState ( JobState . Running );
}
// Release any thread waiting start.
2016-09-02 16:21:20 +00:00
_jobInitializedWaitHandle . Set ();
2016-03-30 14:20:52 -07:00
_tracer . WriteMessage ( ClassNameTrace , "DataAddedToOutput" , Guid . Empty , this ,
"BEGIN Invoke StartJobCompleted event" , null );
OnStartJobCompleted ( new AsyncCompletedEventArgs ( null , false , null ));
_tracer . WriteMessage ( ClassNameTrace , "DataAddedToOutput" , Guid . Empty , this ,
"END Invoke StartJobCompleted event" , null );
ProcessQueue ();
return ;
}
if ( newObject . Properties [ RemotingConstants . EventObject ] != null )
{
PSPropertyInfo guidProperty = newObject . Properties [ RemotingConstants . SourceJobInstanceId ];
Guid sourceJobId = guidProperty != null ? ( Guid ) guidProperty . Value : Guid . Empty ;
if ( guidProperty == null || sourceJobId == Guid . Empty )
{
Diagnostics . Assert ( false , "We should not get guidProperty as null or an empty source job id in non interop scenarios" );
return ;
}
if (! _childJobsMapping . ContainsKey ( sourceJobId ))
{
if ( sourceJobId != _remoteJobInstanceId )
{
Diagnostics . Assert ( false ,
"We should not get an unidentified source job id in non interop scenarios" );
}
return ;
}
// If the event args are job state, the event is a Job's StateChanged event.
var jobStateEventArgs = newObject . BaseObject as JobStateEventArgs ??
Microsoft . PowerShell . DeserializingTypeConverter . RehydrateJobStateEventArgs ( newObject );
if ( jobStateEventArgs != null )
{
// If the child job sent the event, then change the proxy child's state.
// If the current job raised the event, the state change will come when the
// receive call returns.
_tracer . WriteMessage ( ClassNameTrace , "DataAddedToOutput" , Guid . Empty , this ,
"Updating child job {0} state to {1} " , sourceJobId . ToString (),
jobStateEventArgs . JobStateInfo . State . ToString ());
2016-07-29 13:02:49 -07:00
(( PSChildJobProxy ) _childJobsMapping [ sourceJobId ]). DoSetJobState (
2016-03-30 14:20:52 -07:00
jobStateEventArgs . JobStateInfo . State , jobStateEventArgs . JobStateInfo . Reason );
_tracer . WriteMessage ( ClassNameTrace , "DataAddedToOutput" , Guid . Empty , this ,
"Finished updating child job {0} state to {1} " , sourceJobId . ToString (),
jobStateEventArgs . JobStateInfo . State . ToString ());
}
return ;
}
SortOutputObject ( newObject );
}
private void SortOutputObject ( PSObject newObject )
{
PSPropertyInfo guidProperty = newObject . Properties [ RemotingConstants . SourceJobInstanceId ];
Guid sourceJobId = guidProperty != null ? ( Guid ) guidProperty . Value : Guid . Empty ;
if ( guidProperty == null || sourceJobId == Guid . Empty || ! _childJobsMapping . ContainsKey ( sourceJobId ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
Output . Add ( newObject );
return ;
}
// make sure the tag reflects client side job.
newObject . Properties . Remove ( RemotingConstants . SourceJobInstanceId );
newObject . Properties . Add ( new PSNoteProperty ( RemotingConstants . SourceJobInstanceId ,
(( PSChildJobProxy ) _childJobsMapping [ sourceJobId ]). InstanceId ));
(( PSChildJobProxy ) _childJobsMapping [ sourceJobId ]). Output . Add ( newObject );
}
private void DataAddedToError ( object sender , DataAddedEventArgs e )
{
var newError = GetRecord < ErrorRecord >( sender );
Dbg . Assert ( newError != null , "DataAdded should be raised once for each piece of data." );
SortError ( newError );
}
private void SortError ( ErrorRecord record )
{
// var newJobError = record as RemotingErrorRecord;
// if (newJobError == null || !_childJobsMapping.ContainsKey(newJobError.OriginInfo.InstanceID))
Guid id = Guid . Empty ;
string computerName = string . Empty ;
if ( record . ErrorDetails != null )
{
record . ErrorDetails . RecommendedAction = RemoveIdentifierInformation ( record . ErrorDetails . RecommendedAction , out id , out computerName );
}
if ( id == Guid . Empty || ! _childJobsMapping . ContainsKey ( id ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
WriteError ( record );
return ;
}
// make sure the tag reflects client side job.
// newJobError.OriginInfo.InstanceID = ((PSChildJobProxy)_childJobsMapping[newJobError.OriginInfo.InstanceID]).InstanceId;
var oi = new OriginInfo ( null , Guid . Empty , (( PSChildJobProxy ) _childJobsMapping [ id ]). InstanceId );
(( PSChildJobProxy ) _childJobsMapping [ id ]). WriteError ( new RemotingErrorRecord ( record , oi ));
}
private void DataAddedToProgress ( object sender , DataAddedEventArgs e )
{
var newRecord = GetRecord < ProgressRecord >( sender );
Dbg . Assert ( newRecord != null , "DataAdded should be raised once for each piece of data." );
SortProgress ( newRecord );
}
private void SortProgress ( ProgressRecord newRecord )
{
// var newJobRecord = newRecord as RemotingProgressRecord;
// if (newJobRecord == null || !_childJobsMapping.ContainsKey(newJobRecord.OriginInfo.InstanceID))
Guid id ;
string computerName ;
newRecord . CurrentOperation = RemoveIdentifierInformation ( newRecord . CurrentOperation , out id , out computerName );
if ( id == Guid . Empty || ! _childJobsMapping . ContainsKey ( id ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
WriteProgress ( newRecord );
return ;
}
// make sure the tag reflects client side job.
// newJobRecord.OriginInfo.InstanceID = ((PSChildJobProxy)_childJobsMapping[newJobRecord.OriginInfo.InstanceID]).InstanceId;
var oi = new OriginInfo ( computerName , Guid . Empty , (( PSChildJobProxy ) _childJobsMapping [ id ]). InstanceId );
(( PSChildJobProxy ) _childJobsMapping [ id ]). WriteProgress ( new RemotingProgressRecord ( newRecord , oi ));
}
private void DataAddedToDebug ( object sender , DataAddedEventArgs e )
{
var record = GetRecord < DebugRecord >( sender );
Dbg . Assert ( record != null , "DataAdded should be raised once for each piece of data." );
SortDebug ( record );
}
private void SortDebug ( DebugRecord record )
{
Guid remoteJobInstanceId ;
string computerName ;
string message = RemoveIdentifierInformation ( record . Message , out remoteJobInstanceId , out computerName );
if ( remoteJobInstanceId == Guid . Empty || ! _childJobsMapping . ContainsKey ( remoteJobInstanceId ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
WriteDebug ( message );
return ;
}
// make sure the tag reflects client side job.
var originInfo = new OriginInfo ( computerName , Guid . Empty , (( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). InstanceId );
(( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). Debug . Add ( new RemotingDebugRecord ( message , originInfo ));
}
private void DataAddedToWarning ( object sender , DataAddedEventArgs e )
{
var record = GetRecord < WarningRecord >( sender );
Dbg . Assert ( record != null , "DataAdded should be raised once for each piece of data." );
SortWarning ( record );
}
private void SortWarning ( WarningRecord record )
{
Guid remoteJobInstanceId ;
string computerName ;
string message = RemoveIdentifierInformation ( record . Message , out remoteJobInstanceId , out computerName );
if ( remoteJobInstanceId == Guid . Empty || ! _childJobsMapping . ContainsKey ( remoteJobInstanceId ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
WriteWarning ( message );
return ;
}
// make sure the tag reflects client side job.
var originInfo = new OriginInfo ( computerName , Guid . Empty , (( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). InstanceId );
(( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). Warning . Add ( new RemotingWarningRecord ( message , originInfo ));
}
private void DataAddedToVerbose ( object sender , DataAddedEventArgs e )
{
var record = GetRecord < VerboseRecord >( sender );
Dbg . Assert ( record != null , "DataAdded should be raised once for each piece of data." );
SortVerbose ( record );
}
private void SortVerbose ( VerboseRecord record )
{
Guid remoteJobInstanceId ;
string computerName ;
string message = RemoveIdentifierInformation ( record . Message , out remoteJobInstanceId , out computerName );
if ( remoteJobInstanceId == Guid . Empty || ! _childJobsMapping . ContainsKey ( remoteJobInstanceId ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
WriteVerbose ( message );
return ;
}
// make sure the tag reflects client side job.
var originInfo = new OriginInfo ( computerName , Guid . Empty , (( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). InstanceId );
(( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). Verbose . Add ( new RemotingVerboseRecord ( message , originInfo ));
}
private void DataAddedToInformation ( object sender , DataAddedEventArgs e )
{
var record = GetRecord < InformationRecord >( sender );
Dbg . Assert ( record != null , "DataAdded should be raised once for each piece of data." );
SortInformation ( record );
}
private void SortInformation ( InformationRecord record )
{
Guid remoteJobInstanceId ;
string computerName ;
record . Source = RemoveIdentifierInformation ( record . Source , out remoteJobInstanceId , out computerName );
if ( remoteJobInstanceId == Guid . Empty || ! _childJobsMapping . ContainsKey ( remoteJobInstanceId ))
{
// If there is no child job matching the source, add it to the parent's collection so that it is not lost.
WriteInformation ( record );
return ;
}
// make sure the tag reflects client side job.
var originInfo = new OriginInfo ( computerName , Guid . Empty , (( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). InstanceId );
(( PSChildJobProxy ) _childJobsMapping [ remoteJobInstanceId ]). Information . Add ( new RemotingInformationRecord ( record , originInfo ));
}
private static string RemoveIdentifierInformation ( string message , out Guid jobInstanceId , out string computerName )
{
jobInstanceId = Guid . Empty ;
computerName = string . Empty ;
if (! string . IsNullOrEmpty ( message ))
{
string [] parts = message . Split ( Utils . Separators . Colon , 3 );
if ( parts . Length == 3 )
{
if (! Guid . TryParse ( parts [ 0 ], out jobInstanceId ))
jobInstanceId = Guid . Empty ;
computerName = parts [ 1 ];
return parts [ 2 ];
}
}
return message ;
}
#endregion Output management
#region IDisposable Overrides
/// <summary>
/// Dispose all managed resources
/// </summary>
/// <param name="disposing">true when being disposed</param>
protected override void Dispose ( bool disposing )
{
if (! disposing ) return ;
if ( _isDisposed ) return ;
lock ( SyncRoot )
{
if ( _isDisposed ) return ;
_isDisposed = true ;
}
if ( _receivePowerShell != null )
{
_receivePowerShell . Stop ();
_receivePowerShell . InvocationStateChanged -= ReceivePowerShellInvocationStateChanged ;
_receivePowerShell . Streams . Error . DataAdded -= DataAddedToError ;
_receivePowerShell . Streams . Warning . DataAdded -= DataAddedToWarning ;
_receivePowerShell . Streams . Verbose . DataAdded -= DataAddedToVerbose ;
_receivePowerShell . Streams . Progress . DataAdded -= DataAddedToProgress ;
_receivePowerShell . Streams . Debug . DataAdded -= DataAddedToDebug ;
_receivePowerShell . Streams . Information . DataAdded -= DataAddedToInformation ;
_receivePowerShell . Dispose ();
}
UnregisterChildEvents ();
StateChanged -= HandleMyStateChange ;
_receivePowerShellOutput . DataAdded -= DataAddedToOutput ;
if ( _receivePowerShellOutput != null ) _receivePowerShellOutput . Dispose ();
if ( _removeComplete != null ) _removeComplete . Dispose ();
if ( _jobRunningOrFinished != null ) _jobRunningOrFinished . Dispose ();
2016-09-02 16:21:20 +00:00
_jobInitializedWaitHandle . Dispose ();
2016-03-30 14:20:52 -07:00
if ( _jobSuspendedOrFinished != null ) _jobSuspendedOrFinished . Dispose ();
if ( ChildJobs != null && ChildJobs . Count > 0 )
{
foreach ( var job in ChildJobs )
{
// Child Job events are unregistered by UnregisterChildEvents above.
job . Dispose ();
}
}
_tracer . Dispose ();
}
#endregion IDisposable Overrides
#region Private Members
private enum QueueOperation
{ Stop , Suspend , Resume }
private ConcurrentQueue < QueueOperation > _pendingOperations = new ConcurrentQueue < QueueOperation >();
private ManualResetEvent _removeComplete ;
private ManualResetEvent RemoveComplete
{
get
{
if ( _removeComplete == null )
{
lock ( SyncRoot )
{
if ( _removeComplete == null )
{
// this assert is required so that a wait handle
// is not created after the object is disposed
// which will result in a leak
AssertNotDisposed ();
_removeComplete = new ManualResetEvent ( false );
}
}
}
return _removeComplete ;
}
}
private ManualResetEvent _jobRunningOrFinished ;
private ManualResetEvent JobRunningOrFinished
{
get
{
if ( _jobRunningOrFinished == null )
{
lock ( SyncRoot )
{
if ( _jobRunningOrFinished == null )
{
// this assert is required so that a wait handle
// is not created after the object is disposed
// which will result in a leak
AssertNotDisposed ();
_jobRunningOrFinished = new ManualResetEvent ( false );
}
}
}
return _jobRunningOrFinished ;
}
}
2016-09-02 16:21:20 +00:00
private readonly ManualResetEvent _jobInitializedWaitHandle = new ManualResetEvent ( false );
2016-03-30 14:20:52 -07:00
private ManualResetEvent _jobSuspendedOrFinished ;
private ManualResetEvent JobSuspendedOrFinished
{
get
{
if ( _jobSuspendedOrFinished == null )
{
lock ( SyncRoot )
{
if ( _jobSuspendedOrFinished == null )
{
// this assert is required so that a wait handle
// is not created after the object is disposed
// which will result in a leak
AssertNotDisposed ();
_jobSuspendedOrFinished = new ManualResetEvent ( false );
}
}
}
return _jobSuspendedOrFinished ;
}
}
private PSCommand _pscommand ;
private Runspace _runspace ;
private RunspacePool _runspacePool ;
private EventHandler < JobDataAddedEventArgs > _dataAddedHandler ;
private EventHandler < JobStateEventArgs > _stateChangedHandler ;
private const String ResBaseName = "PowerShellStrings" ;
private Guid _remoteJobInstanceId = Guid . Empty ;
private string _remoteJobStatusMessage = String . Empty ;
private string _remoteJobLocation = String . Empty ;
private readonly Hashtable _childJobsMapping = new Hashtable ();
private readonly PowerShell _receivePowerShell = PowerShell . Create ();
private readonly PSDataCollection < PSObject > _receivePowerShellOutput = new PSDataCollection < PSObject >();
private bool _moreData = true ;
private JobState _previousState = JobState . NotStarted ;
private JobState _computedJobState ;
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory . GetTraceSource ();
2016-07-29 13:02:49 -07:00
private static Tracer s_structuredTracer = new Tracer ();
2016-03-30 14:20:52 -07:00
private bool _jobInitialized ;
private bool _removeCalled ;
private bool _startCalled ;
private bool _receiveIsValidCall ;
private bool _isDisposed ;
private bool _remoteJobRemoved ;
private bool _childEventsRegistered ;
private object _inProgressSyncObject = new object ();
private bool _inProgress = false ;
private bool _workerCreated = false ;
private const string ClassNameTrace = "PSJobProxy" ;
// count of number of child jobs which have finished
private int _finishedChildJobsCount = 0 ;
// count of number of child jobs which are blocked
private int _blockedChildJobsCount = 0 ;
// count of number of child jobs which are suspended
private int _suspendedChildJobsCount = 0 ;
// count of number of child jobs which are suspending
private int _suspendingChildJobsCount = 0 ;
// count of number of child jobs which failed
private int _failedChildJobsCount = 0 ;
// count of number of child jobs which stopped
private int _stoppedChildJobsCount = 0 ;
#endregion Private Members
}
/// <summary>
/// Job class used for children of PSJobProxy jobs.
/// </summary>
public sealed class PSChildJobProxy : Job2
{
internal PSChildJobProxy ( string command , PSObject o )
: base ( command )
{
PSJobProxy . TryGetJobPropertyValue ( o , "StatusMessage" , out _statusMessage );
PSJobProxy . TryGetJobPropertyValue ( o , "Location" , out _location );
string name ;
PSJobProxy . TryGetJobPropertyValue ( o , "Name" , out name );
Name = name ;
Output . DataAdded += OutputAdded ;
Error . DataAdded += ErrorAdded ;
Warning . DataAdded += WarningAdded ;
Verbose . DataAdded += VerboseAdded ;
Progress . DataAdded += ProgressAdded ;
Debug . DataAdded += DebugAdded ;
Information . DataAdded += InformationAdded ;
}
internal void AssignDisconnectedState ()
{
DoSetJobState ( JobState . Disconnected );
}
/// <summary>
/// Method to raise the event when this job has data added.
/// </summary>
/// <param name="eventArgs">argument describing
/// an exception that is associated with the event</param>
private void OnJobDataAdded ( JobDataAddedEventArgs eventArgs )
{
#pragma warning disable 56500
try
{
_tracer . WriteMessage ( ClassNameTrace , "OnJobDataAdded" , Guid . Empty , this , "BEGIN call event handlers" );
JobDataAdded . SafeInvoke ( this , eventArgs );
_tracer . WriteMessage ( ClassNameTrace , "OnJobDataAdded" , Guid . Empty , this , "END call event handlers" );
}
catch ( Exception exception )
{
// errors in the handlers are not errors in the operation
// silently ignore them
CommandProcessorBase . CheckForSevereException ( exception );
_tracer . WriteMessage ( ClassNameTrace , "OnJobDataAdded" , Guid . Empty , this , "END Exception thrown in JobDataAdded handler" );
_tracer . TraceException ( exception );
}
#pragma warning restore 56500
}
private void OutputAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Output , e . Index ));
}
private void ErrorAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Error , e . Index ));
}
private void WarningAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Warning , e . Index ));
}
private void VerboseAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Verbose , e . Index ));
}
private void ProgressAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Progress , e . Index ));
}
private void DebugAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Debug , e . Index ));
}
private void InformationAdded ( object sender , DataAddedEventArgs e )
{
OnJobDataAdded ( new JobDataAddedEventArgs ( this , PowerShellStreamType . Information , e . Index ));
}
2016-07-29 13:02:49 -07:00
private static Tracer s_structuredTracer = new Tracer ();
2016-03-30 14:20:52 -07:00
internal void DoSetJobState ( JobState state , Exception reason = null )
{
if ( _disposed ) return ;
#if DEBUG
switch ( JobStateInfo . State )
{
case JobState . Running :
Dbg . Assert ( state != JobState . NotStarted && state != JobState . Blocked ,
"ChildJobProxy invalid state transition from Running." );
break ;
case JobState . Stopping :
Dbg . Assert (
state == JobState . Stopped || state == JobState . Failed || state == JobState . Completed ,
"ChildJobProxy invalid state transition from Stopping." );
break ;
case JobState . Stopped :
Dbg . Assert ( false , "ChildJobProxy should never transition after Stopped state." );
break ;
case JobState . Suspending :
Dbg . Assert (
state == JobState . Suspended || state == JobState . Completed || state == JobState . Failed ||
state == JobState . Stopped || state == JobState . Stopping ,
"ChildJobProxy invalid state transition from Suspending." );
break ;
case JobState . Suspended :
Dbg . Assert (
state == JobState . Running || state == JobState . Stopping || state == JobState . Stopped ,
"ChildJobProxy invalid state transition from Suspended." );
break ;
case JobState . Failed :
Dbg . Assert ( false , "ChildJobProxy should never transition after Failed state." );
break ;
case JobState . Completed :
Dbg . Assert ( false , "ChildJobProxy should never transition after Completed state." );
break ;
case JobState . Disconnected :
Dbg . Assert ( state != JobState . NotStarted && state != JobState . Blocked ,
"ChildJobProxy invalid state transition from Disconnected." );
break ;
case JobState . Blocked :
Dbg . Assert ( false , "ChildJobProxy should never be in a blocked state" );
break ;
default :
break ;
}
#endif
try
{
Dbg . Assert ( state != JobStateInfo . State , "Setting job state should always change the state." );
_tracer . WriteMessage ( "PSChildJobProxy" , "DoSetJobState" , Guid . Empty , this ,
"BEGIN Set job state to {0} and call event handlers" , state . ToString ());
2016-07-29 13:02:49 -07:00
s_structuredTracer . BeginProxyChildJobEventHandler ( InstanceId );
2016-03-30 14:20:52 -07:00
SetJobState ( state , reason );
2016-07-29 13:02:49 -07:00
s_structuredTracer . EndProxyJobEventHandler ( InstanceId );
2016-03-30 14:20:52 -07:00
_tracer . WriteMessage ( "PSChildJobProxy" , "DoSetJobState" , Guid . Empty , this ,
"END Set job state to {0} and call event handlers" , state . ToString ());
}
catch ( ObjectDisposedException )
{
// If the object is disposed, the user will not need the state.
}
}
/// <summary>
/// Dispose
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose ( bool disposing )
{
if ( _disposed ) return ;
lock ( _syncObject )
{
if ( _disposed ) return ;
_disposed = true ;
Output . DataAdded -= OutputAdded ;
Error . DataAdded -= ErrorAdded ;
Warning . DataAdded -= WarningAdded ;
Verbose . DataAdded -= VerboseAdded ;
Progress . DataAdded -= ProgressAdded ;
Debug . DataAdded -= DebugAdded ;
Information . DataAdded -= InformationAdded ;
}
base . Dispose ( disposing );
}
#region Control method overrides
/// <summary>
/// Not supported
/// </summary>
public override void StartJob ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void StartJobAsync ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// StopJob
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
2016-07-29 13:02:49 -07:00
public override void StopJob ( bool force , string reason )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void StopJobAsync ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// StopJobAsync
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
public override void StopJobAsync ( bool force , string reason )
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void SuspendJob ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void SuspendJobAsync ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// SuspendJob
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
2016-07-29 13:02:49 -07:00
public override void SuspendJob ( bool force , string reason )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// SuspendJobAsync
/// </summary>
/// <param name="force"></param>
/// <param name="reason"></param>
public override void SuspendJobAsync ( bool force , string reason )
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void ResumeJob ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void ResumeJobAsync ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void UnblockJob ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void UnblockJobAsync ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
/// <summary>
/// Not supported
/// </summary>
public override void StopJob ()
{
throw PSTraceSource . NewNotSupportedException ( PowerShellStrings . ProxyChildJobControlNotSupported );
}
#endregion Control method overrides
#region Public properties
/// <summary>
/// This event will be raised whenever data has been added to one of the job object's
/// 6 collections. The event arguments include the job itself, the data type, indicating
/// which collection has data added, and the index.
/// </summary>
public event EventHandler < JobDataAddedEventArgs > JobDataAdded ;
/// <summary>
/// Status message
/// </summary>
public override string StatusMessage
{
get { return _statusMessage ; }
}
/// <summary>
/// Indicates the job has or can have more data on one or more data collection
/// </summary>
public override bool HasMoreData
{
get
{
return ( Output . IsOpen || Output . Count > 0
|| Error . IsOpen || Error . Count > 0
|| Verbose . IsOpen || Verbose . Count > 0
|| Debug . IsOpen || Debug . Count > 0
|| Warning . IsOpen || Warning . Count > 0
|| Progress . IsOpen || Progress . Count > 0
|| Information . IsOpen || Information . Count > 0
);
}
}
/// <summary>
/// The location of the job
/// </summary>
public override string Location
{
get { return _location ; }
}
#endregion Public properties
#region Private members
private string _statusMessage ;
private string _location ;
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory . GetTraceSource ();
private readonly object _syncObject = new object ();
private bool _disposed = false ;
private const string ClassNameTrace = "PSChildJobProxy" ;
#endregion
}
/// <summary>
/// Event arguments that indicate data has been added to a child job.
/// </summary>
public sealed class JobDataAddedEventArgs : EventArgs
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="job">The job that contains the data that is added.</param>
/// <param name="dataType">The type of data that this event is raised for.</param>
/// <param name="index">
/// Index at which the data is added.
/// </param>
internal JobDataAddedEventArgs ( Job job , PowerShellStreamType dataType , int index )
{
2016-08-02 13:29:09 -07:00
SourceJob = job ;
DataType = dataType ;
Index = index ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// The job that contains the PSDataCollection which is the sender.
/// </summary>
2016-08-02 13:29:09 -07:00
public Job SourceJob { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Identifies the type of the sending collection as one of the six collections
/// associated with a job.
/// If data type = output, sender is PSDataCollection of PSObject, Error is of ErrorRecord, etc.
/// </summary>
2016-08-02 13:29:09 -07:00
public PowerShellStreamType DataType { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Index at which the data is added.
/// </summary>
2016-08-02 13:29:09 -07:00
public int Index { get ; }
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Job data is added to one of these streams. Each
/// type of data implies a different type of object.
/// </summary>
public enum PowerShellStreamType
{
/// <summary>
/// PSObject
/// </summary>
Input = 0 ,
/// <summary>
/// PSObject
/// </summary>
Output = 1 ,
/// <summary>
/// ErrorRecord
/// </summary>
Error = 2 ,
/// <summary>
/// WarningRecord
/// </summary>
Warning = 3 ,
/// <summary>
/// VerboseRecord
/// </summary>
Verbose = 4 ,
/// <summary>
/// DebugRecord
/// </summary>
Debug = 5 ,
/// <summary>
/// ProgressRecord
/// </summary>
Progress = 6 ,
/// <summary>
/// InformationRecord
/// </summary>
Information = 7
}
#endregion Workflow Hosting API
}