/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
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
///
/// 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
///
public sealed class PSJobProxy : Job2
{
#region Constructors
///
/// Internal constructor
///
/// the command to execute
internal PSJobProxy(string command)
: base(command)
{
_tracer.WriteMessage(ClassNameTrace, "ctor", _remoteJobInstanceId, this,
"Constructing proxy job", null);
StateChanged += HandleMyStateChange;
}
#endregion Constructors
#region Overrides of Job
///
/// Success status of the command execution.
///
public override string StatusMessage
{
get { return _remoteJobStatusMessage; }
}
///
/// Indicates that more data is available in this
/// result object for reading.
///
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;
}
}
///
/// This is the location string from the remote job.
///
public override string Location
{
get { return _remoteJobLocation; }
}
#endregion
#region Overrides of Job2
///
/// start a job. The job will be started with the parameters
/// specified in StartParameters
///
/// Thrown if the job
/// is already running, if there is no runspace or runspace pool
/// assigned.
/// Thrown if the job is
/// otherwise started, finished, or suspended.
/// Thrown if job is
/// disposed.
public override void StartJob()
{
StartJob(null, null, null);
}
///
/// Start a job asynchronously
///
/// When a job is started all the data in the
/// job streams from a previous invocation will be cleared
public override void StartJobAsync()
{
StartJobAsync(null, null, null);
}
///
/// 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
///
/// Thrown if job is blocked.
/// Thrown if job is disposed.
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;
}
}
///
/// Stop a job asynchronously
///
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
}
///
/// StopJob
///
///
///
public override void StopJob(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported);
}
///
/// StopJobAsync
///
///
///
public override void StopJobAsync(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported);
}
///
/// Suspend a job
///
/// Throws if the job is not in
/// a running or suspended state.
/// Thrown if job is
/// disposed.
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;
}
}
///
/// Asynchronously suspend a job
///
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
}
///
/// SuspendJob
///
///
///
public override void SuspendJob(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported);
}
///
/// SuspendJobAsync
///
///
///
public override void SuspendJobAsync(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported);
}
///
/// Resume a suspended job
///
/// Throws if the job
/// is not in a suspended or running state.
/// Thrown if job is
/// disposed.
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;
}
}
///
/// Resume a suspended job asynchronously.
///
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
}
///
/// Unblock a blocked job
///
/// Unblock job is not supported on PSJobProxy.
public override void UnblockJob()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyUnblockJobNotSupported);
}
///
/// Unblock a blocked job asynchronously
///
public override void UnblockJobAsync()
{
OnUnblockJobCompleted(new AsyncCompletedEventArgs(PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyUnblockJobNotSupported), false, null));
}
#endregion
#region Additional State Management Methods
///
/// Start execution of the workflow with the
/// specified input. This input will serve as
/// input to the underlying pipeline
///
/// collection of input
/// objects
public void StartJobAsync(PSDataCollection input)
{
StartJobAsync(null, null, input);
}
///
/// Start execution of the job with the
/// specified input. This input will serve as
/// input to the underlying pipeline
///
///
/// Not sure if this method is needed. This has
/// been added just to be in sync with the PowerShell
/// APIs
public void StartJob(PSDataCollection input)
{
StartJob(null, null, input);
}
///
/// 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.
///
/// delegate used to subscribe to data added events on the child jobs
/// delegate used to subscribe to state changed events on the child jobs
/// collection of input
/// objects
public void StartJob(EventHandler dataAdded, EventHandler stateChanged, PSDataCollection 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);
_jobInitialiedWaitHandle.WaitOne();
}
catch (Exception error)
{
CommandProcessorBase.CheckForSevereException(error);
_tracer.WriteMessage(ClassNameTrace, "StartJob", _remoteJobInstanceId, this, "Error", null);
_tracer.TraceException(error);
throw;
}
}
///
/// 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.
///
/// delegate used to subscribe to data added events on the child jobs
/// delegate used to subscribe to state changed events on the child jobs
/// collection of input
/// objects
public void StartJobAsync(EventHandler dataAdded, EventHandler stateChanged, PSDataCollection 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
}
///
/// Removes the job.
/// If remoteJob is true, the job output that has been transferred to this
/// client object will be preserved.
///
/// Indicates whether the remove operation should
/// be applied to the remote or local job.
/// Force will stop the job on the server before
/// attempting removal. Default value is false.
/// Thrown if the job is not in
/// a completed state.
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)
{
Diagnostics.Assert(false, "remove called is false after calling remove. No exception was thrown.");
return;
}
}
RemoveComplete.WaitOne();
}
catch (Exception error)
{
CommandProcessorBase.CheckForSevereException(error);
_tracer.WriteMessage(ClassNameTrace, "RemoveJob", _remoteJobInstanceId, this, "Error", null);
_tracer.TraceException(error);
throw;
}
}
///
/// Removes the job.
/// If remoteJob is true, the job output that has been transferred to this
/// client object will be preserved.
///
/// Indicates whether the remove operation should
/// be applied to the remote or local job.
/// Thrown if the job is not in
/// a completed state.
public void RemoveJob(bool removeRemoteJob)
{
RemoveJob(removeRemoteJob, false);
}
///
/// Removes the job on the remote server.
/// The job output that has been transferred to this client object will be
/// preserved.
///
/// Indicates whether the remove operation should
/// be applied to the remote or local job.
/// Force will stop the job on the server before
/// attempting removal.
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);
}
///
/// Removes the job on the remote server.
/// The job output that has been transferred to this client object will be
/// preserved.
///
/// Indicates whether the remove operation should
/// be applied to the remote or local job.
public void RemoveJobAsync(bool removeRemoteJob)
{
RemoveJobAsync(removeRemoteJob, false);
}
///
/// This event should be raised whenever the asynchronous removal of
/// a server side job is completed.
///
public event EventHandler RemoveJobCompleted;
///
/// Method to raise the event when removing a
/// server side job is completed
///
/// argument describing
/// an exception that is associated with the event
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 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
///
/// 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.
///
public bool RemoveRemoteJobOnCompletion
{
get
{
return _removeRemoteJobOnCompletion;
}
set
{
AssertChangesCanBeAccepted();
_removeRemoteJobOnCompletion = value;
}
}
private bool _removeRemoteJobOnCompletion;
///
/// The instance ID of the remote job that this proxy interacts with.
///
public Guid RemoteJobInstanceId
{
get { return _remoteJobInstanceId; }
}
///
/// Runspace in which this job will be executed
///
/// At any point of time only a runspace or a
/// runspacepool may be specified
public Runspace Runspace
{
get
{
return _runspace;
}
set
{
if (value == null)
{
throw PSTraceSource.NewArgumentNullException("value");
}
lock (SyncRoot)
{
AssertChangesCanBeAccepted();
_runspacePool = null;
_runspace = value;
}
}
}
///
/// RunspacePool in which this job will be executed
///
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
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// Runspace containing the jobs to base the proxy on.
/// Hashtable to use for the Get-Job -filter command.
/// Handler to subscribe to any child job data added events.
/// Handler to subscribe to any child job state changed events.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(Runspace runspace, Hashtable filter,
EventHandler dataAdded, EventHandler stateChanged)
{
return Create(runspace, filter, dataAdded, stateChanged, true);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// Runspace containing the jobs to base the proxy on.
/// Hashtable to use for the Get-Job -filter command.
/// If true, the data streaming will start immediately. If false,
/// the user must call "ReceiveJob()" to start data streaming.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(Runspace runspace, Hashtable filter, bool receiveImmediately)
{
return Create(runspace, filter, null, null, receiveImmediately);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// Runspace containing the jobs to base the proxy on.
/// Hashtable to use for the Get-Job -filter command.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(Runspace runspace, Hashtable filter)
{
return Create(runspace, filter, null, null, true);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// Runspace containing the jobs to base the proxy on.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(Runspace runspace)
{
return Create(runspace, null, null, null, true);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// RunspacePool containing the jobs to base the proxy on.
/// Hashtable to use for the Get-Job -filter command.
/// Handler to subscribe to any child job data added events.
/// Handler to subscribe to any child job state changed events.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(RunspacePool runspacePool, Hashtable filter,
EventHandler dataAdded, EventHandler stateChanged)
{
return Create(runspacePool, filter, dataAdded, stateChanged, true);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// RunspacePool containing the jobs to base the proxy on.
/// Hashtable to use for the Get-Job -filter command.
/// If true, the data streaming will start immediately. If false,
/// the user must call "ReceiveJob()" to start data streaming.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(RunspacePool runspacePool, Hashtable filter, bool receiveImmediately)
{
return Create(runspacePool, filter, null, null, receiveImmediately);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// RunspacePool containing the jobs to base the proxy on.
/// Hashtable to use for the Get-Job -filter command.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(RunspacePool runspacePool, Hashtable filter)
{
return Create(runspacePool, filter, null, null, true);
}
///
/// Queries the runspace for jobs and constructs a collection of job proxies to interact with them.
///
/// RunspacePool containing the jobs to base the proxy on.
/// A collection of job proxies that represent the jobs collected based on the filter.
public static ICollection Create(RunspacePool runspacePool)
{
return Create(runspacePool, null, null, null, true);
}
private static ICollection Create(RunspacePool runspacePool, Hashtable filter,
EventHandler dataAdded, EventHandler stateChanged, bool connectImmediately)
{
if (runspacePool == null)
throw new PSArgumentNullException("runspacePool");
return Create(null, runspacePool, filter, dataAdded, stateChanged, connectImmediately);
}
private static ICollection Create(Runspace runspace, Hashtable filter,
EventHandler dataAdded, EventHandler stateChanged, bool connectImmediately)
{
if (runspace == null)
throw new PSArgumentNullException("runspace");
return Create(runspace, null, filter, dataAdded, stateChanged, connectImmediately);
}
private static ICollection Create(Runspace runspace, RunspacePool runspacePool, Hashtable filter,
EventHandler dataAdded, EventHandler stateChanged, bool connectImmediately)
{
// run command
Collection 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 jobs = new Collection();
foreach (var deserializedJob in jobResults)
{
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
///
/// Will begin streaming data for a job object created by the "create" method that is in a not started state.
///
public void ReceiveJob()
{
ReceiveJob(null, null);
}
///
/// Will begin streaming data for a job object created by the "create" method that is in a not started state.
///
/// delegate used to subscribe to data added events on the child jobs
/// delegate used to subscribe to state changed events on the child jobs
public void ReceiveJob(EventHandler dataAdded, EventHandler 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)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 { 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 psParamCollection = new List();
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;
}
///
/// Helper to do error checking for getting a property of type T from a PSobject.
///
///
///
///
///
///
internal static bool TryGetJobPropertyValue(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)
{
// Trasfer exception via event arguments.
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;
}
}
}
///
/// Checks if there is more data in the specified collection
///
/// Type of the collection
/// collection to check
/// true if the collection has more data
private static bool CollectionHasMoreData(PSDataCollection collection)
{
return (collection.IsOpen || collection.Count > 0);
}
///
/// Worker method which starts the job
///
private void DoStartAsync(EventHandler dataAdded, EventHandler stateChanged, PSDataCollection 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);
s_structuredTracer.BeginProxyJobExecution(InstanceId);
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);
}
///
/// Worker method which stops the job
///
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)null, _receivePowerShellOutput, null, CleanupReceivePowerShell, null);
}
///
/// Worker method which suspends the job
///
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)null, _receivePowerShellOutput, null, CleanupReceivePowerShell, null);
}
///
/// Worker method to resume the job
///
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)null, _receivePowerShellOutput, null, CleanupReceivePowerShell, null);
}
///
/// Worker method to remove the remote job object
///
/// state information indicates the "force" parameter
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.
_jobInitialiedWaitHandle.WaitOne();
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
}
///
/// Assigns either a runspace or runspacepool to the specified powershell
/// instance
///
/// powershell instance to which the set has to
/// happen
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));
_jobInitialiedWaitHandle.Set();
OnStartJobCompleted(new AsyncCompletedEventArgs(e.JobStateInfo.Reason, false, null));
OnStopJobCompleted(new AsyncCompletedEventArgs(e.JobStateInfo.Reason, false, null));
}
}
break;
}
ProcessQueue();
}
///
/// Event handler for InvocationStateChanged on the powershell
/// object running receive-job
///
/// sender of this event
/// argument describing this event
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;
}
}
///
/// Assigns job properties and creates child job tree.
///
/// Deserialized job object representing the remote job for this proxy.
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);
s_structuredTracer.ProxyJobRemoteJobAssociation(InstanceId, _remoteJobInstanceId);
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().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 listComParCol = new List();
// check childjobstartparameters--was a List
foreach (PSObject paramCollection in childJobStartParameters.Cast().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().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,
ref _suspendedChildJobsCount,
ref _suspendingChildJobsCount,
ref _finishedChildJobsCount,
ref _failedChildJobsCount,
ref _stoppedChildJobsCount,
ChildJobs.Count,
out computedJobState))
return;
if (computedJobState == JobState.Suspending) return; // Ignor for proxy job
_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;
}
///
/// Check if changes to the jobs properties can be accepted
///
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);
}
}
///
/// Assert if the object is not yet disposed and if so
/// throw an ObjectDisposedException
///
/// Thrown if
/// the object has already been disposed
/// Method is not thread-safe. Caller has to
/// ensure thread safety
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());
s_structuredTracer.EndProxyJobExecution(InstanceId);
s_structuredTracer.BeginProxyJobEventHandler(InstanceId);
SetJobState(state, reason);
s_structuredTracer.EndProxyJobEventHandler(InstanceId);
_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(object sender)
{
lock (SyncRoot)
{
var collection = sender as PSDataCollection;
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(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);
_jobInitialiedWaitHandle.Set();
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.
_jobInitialiedWaitHandle.Set();
_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());
((PSChildJobProxy)_childJobsMapping[sourceJobId]).DoSetJobState(
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(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(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(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(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(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(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
///
/// Dispose all managed resources
///
/// true when being disposed
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();
_jobInitialiedWaitHandle.Dispose();
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 _pendingOperations = new ConcurrentQueue();
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;
}
}
private readonly ManualResetEvent _jobInitialiedWaitHandle = new ManualResetEvent(false);
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 _dataAddedHandler;
private EventHandler _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 _receivePowerShellOutput = new PSDataCollection();
private bool _moreData = true;
private JobState _previousState = JobState.NotStarted;
private JobState _computedJobState;
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
private static Tracer s_structuredTracer = new Tracer();
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
}
///
/// Job class used for children of PSJobProxy jobs.
///
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);
}
///
/// Method to raise the event when this job has data added.
///
/// argument describing
/// an exception that is associated with the event
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));
}
private static Tracer s_structuredTracer = new Tracer();
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());
s_structuredTracer.BeginProxyChildJobEventHandler(InstanceId);
SetJobState(state, reason);
s_structuredTracer.EndProxyJobEventHandler(InstanceId);
_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.
}
}
///
/// Dispose
///
///
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
///
/// Not supported
///
public override void StartJob()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void StartJobAsync()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// StopJob
///
///
///
public override void StopJob(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void StopJobAsync()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// StopJobAsync
///
///
///
public override void StopJobAsync(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void SuspendJob()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void SuspendJobAsync()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// SuspendJob
///
///
///
public override void SuspendJob(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// SuspendJobAsync
///
///
///
public override void SuspendJobAsync(bool force, string reason)
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void ResumeJob()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void ResumeJobAsync()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void UnblockJob()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void UnblockJobAsync()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
///
/// Not supported
///
public override void StopJob()
{
throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported);
}
#endregion Control method overrides
#region Public properties
///
/// 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.
///
public event EventHandler JobDataAdded;
///
/// Status message
///
public override string StatusMessage
{
get { return _statusMessage; }
}
///
/// Indicates the job has or can have more data on one or more data collection
///
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
);
}
}
///
/// The location of the job
///
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
}
///
/// Event arguments that indicate data has been added to a child job.
///
public sealed class JobDataAddedEventArgs : EventArgs
{
///
/// Constructor
///
/// The job that contains the data that is added.
/// The type of data that this event is raised for.
///
/// Index at which the data is added.
///
internal JobDataAddedEventArgs(Job job, PowerShellStreamType dataType, int index)
{
SourceJob = job;
DataType = dataType;
Index = index;
}
///
/// The job that contains the PSDataCollection which is the sender.
///
public Job SourceJob { get; }
///
/// 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.
///
public PowerShellStreamType DataType { get; }
///
/// Index at which the data is added.
///
public int Index { get; }
}
///
/// Job data is added to one of these streams. Each
/// type of data implies a different type of object.
///
public enum PowerShellStreamType
{
///
/// PSObject
///
Input = 0,
///
/// PSObject
///
Output = 1,
///
/// ErrorRecord
///
Error = 2,
///
/// WarningRecord
///
Warning = 3,
///
/// VerboseRecord
///
Verbose = 4,
///
/// DebugRecord
///
Debug = 5,
///
/// ProgressRecord
///
Progress = 6,
///
/// InformationRecord
///
Information = 7
}
#endregion Workflow Hosting API
}