c748652c34
commit 8cec8f150da7583b7af5efbe2853efee0179750c
6573 lines
283 KiB
C#
6573 lines
283 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Activities;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Management.Automation.Remoting;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Activities.Hosting;
|
|
using System.Activities.Statements;
|
|
using System.ComponentModel;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Language;
|
|
using System.Management.Automation.Runspaces;
|
|
using Microsoft.Management.Infrastructure;
|
|
using Microsoft.Management.Infrastructure.Options;
|
|
using System.Management.Automation.Tracing;
|
|
using Dbg=System.Diagnostics.Debug;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Management.Automation.Host;
|
|
using System.Management;
|
|
using Microsoft.PowerShell.Workflow;
|
|
|
|
namespace Microsoft.PowerShell.Activities
|
|
{
|
|
internal class ActivityParameters
|
|
{
|
|
internal uint? ConnectionRetryCount
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal uint? ConnectionRetryInterval
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal uint? ActionRetryCount
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal uint? ActionRetryInterval
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal string[] PSRequiredModules
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal ActivityParameters(uint? connectionRetryCount, uint? connectionRetryInterval, uint? actionRetryCount, uint? actionRetryInterval, string[] requiredModule)
|
|
{
|
|
this.ConnectionRetryCount = connectionRetryCount;
|
|
this.ConnectionRetryInterval = connectionRetryInterval;
|
|
this.ActionRetryCount = actionRetryCount;
|
|
this.ActionRetryInterval = actionRetryInterval;
|
|
this.PSRequiredModules = requiredModule;
|
|
}
|
|
}
|
|
|
|
internal delegate void PrepareSessionDelegate(ActivityImplementationContext implementationContext);
|
|
|
|
internal class RunCommandsArguments
|
|
{
|
|
internal ActivityParameters ActivityParameters { get; private set; }
|
|
internal PSDataCollection<PSObject> Output { get; private set; }
|
|
internal PSDataCollection<PSObject> Input { get; private set; }
|
|
internal PSActivityContext PSActivityContext { get; private set; }
|
|
internal Dictionary<string, object> ParameterDefaults { get; private set; }
|
|
internal Type ActivityType { get; private set; }
|
|
internal PrepareSessionDelegate Delegate { get; private set; }
|
|
internal object ActivityObject { get; private set; }
|
|
internal PSWorkflowHost WorkflowHost { get; private set; }
|
|
internal ActivityImplementationContext ImplementationContext { get; private set; }
|
|
internal int CommandExecutionType { get; private set; }
|
|
internal System.Management.Automation.PowerShell HelperCommand { get; set; }
|
|
internal PSDataCollection<object> HelperCommandInput { get; set; }
|
|
internal int CleanupTimeout { get; set; }
|
|
|
|
internal RunCommandsArguments(ActivityParameters activityParameters, PSDataCollection<PSObject> output, PSDataCollection<PSObject> input,
|
|
PSActivityContext psActivityContext, PSWorkflowHost workflowHost,
|
|
bool runInProc, Dictionary<string, object> parameterDefaults, Type activityType,
|
|
PrepareSessionDelegate prepareSession, object activityObject, ActivityImplementationContext implementationContext)
|
|
{
|
|
ActivityParameters = activityParameters;
|
|
Output = output;
|
|
Input = input;
|
|
PSActivityContext = psActivityContext;
|
|
ParameterDefaults = parameterDefaults;
|
|
ActivityType = activityType;
|
|
Delegate = prepareSession;
|
|
ActivityObject = activityObject;
|
|
WorkflowHost = workflowHost;
|
|
ImplementationContext = implementationContext;
|
|
|
|
CommandExecutionType =
|
|
DetermineCommandExecutionType(
|
|
implementationContext.ConnectionInfo, runInProc,
|
|
activityType, psActivityContext);
|
|
}
|
|
|
|
internal static int DetermineCommandExecutionType(WSManConnectionInfo connectionInfo, bool runInProc, Type activityType, PSActivityContext psActivityContext)
|
|
{
|
|
// check for cleanup activity first
|
|
if (typeof(PSCleanupActivity).IsAssignableFrom(activityType))
|
|
{
|
|
return PSActivity.CleanupActivity;
|
|
}
|
|
|
|
if (connectionInfo != null)
|
|
{
|
|
// check if this is an activity with custom remoting
|
|
// then we need to run it in proc
|
|
if (psActivityContext != null && psActivityContext.RunWithCustomRemoting)
|
|
return PSActivity.CommandRunInProc;
|
|
|
|
return PSActivity.CommandRunRemotely;
|
|
}
|
|
|
|
// if connectionInfo is null, then the command needs to be executed
|
|
// on this machine. #1 to #3 from above is possible
|
|
if (!runInProc)
|
|
{
|
|
return PSActivity.CommandRunOutOfProc;
|
|
}
|
|
|
|
// at this point the command is being run in-proc
|
|
|
|
// Check for WMI activity
|
|
if (typeof(WmiActivity).IsAssignableFrom(activityType) || typeof(GenericCimCmdletActivity).IsAssignableFrom(activityType))
|
|
{
|
|
return PSActivity.RunInProcNoRunspace;
|
|
}
|
|
|
|
if (typeof(PSGeneratedCIMActivity).IsAssignableFrom(activityType))
|
|
{
|
|
return PSActivity.CimCommandRunInProc;
|
|
}
|
|
|
|
return PSActivity.CommandRunInProc;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementing this interface indicates that the activity supports connection retry.
|
|
/// </summary>
|
|
public interface IImplementsConnectionRetry
|
|
{
|
|
/// <summary>
|
|
/// Defines the number of retries that the activity will make to connect to a remote
|
|
/// machine when it encounters an error. The default is to not retry.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
InArgument<uint?> PSConnectionRetryCount
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the delay, in seconds, between connection retry attempts.
|
|
/// The default is one second.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
InArgument<uint?> PSConnectionRetryIntervalSec
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Special variables that can be defined in a workflow to
|
|
/// control the behaviour of PowerShell activities.
|
|
/// </summary>
|
|
public static class WorkflowPreferenceVariables
|
|
{
|
|
/// <summary>
|
|
/// The parent activity ID to be used for all progress records
|
|
/// that are written in the enclosing scope
|
|
/// </summary>
|
|
public const string PSParentActivityId = "PSParentActivityID";
|
|
|
|
/// <summary>
|
|
/// Workflow variable that controls when activities are run
|
|
/// in process. If true, all activities in the enclosing scope
|
|
/// will be run in process
|
|
/// </summary>
|
|
public const string PSRunInProcessPreference = "PSRunInProcessPreference";
|
|
|
|
/// <summary>
|
|
/// Workflow variable that is used to determine if a PowerShell activity should
|
|
/// persist when it's done. if true, then all PSActivities in that scope
|
|
/// will not persist at the end of their execution.
|
|
/// </summary>
|
|
public const string PSPersistPreference = "PSPersistPreference";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parameter default, contains all the information which needs to be passed to Workflow context.
|
|
/// </summary>
|
|
public sealed class HostParameterDefaults : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Default constructor.
|
|
/// </summary>
|
|
public HostParameterDefaults()
|
|
{
|
|
Parameters = new Dictionary<string, object>();
|
|
HostCommandMetadata = new HostSettingCommandMetadata();
|
|
Runtime = null;
|
|
HostPersistenceDelegate = null;
|
|
ActivateDelegate = null;
|
|
AsyncExecutionCollection = null;
|
|
RemoteActivityState = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// All the activity level default parameter values are passed here.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
|
public Dictionary<string, object> Parameters
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metadata / symbolic information about the currently-running workflow.
|
|
/// </summary>
|
|
public HostSettingCommandMetadata HostCommandMetadata
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Job instance id.
|
|
/// </summary>
|
|
public Guid JobInstanceId
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The workflow runtime.
|
|
/// </summary>
|
|
public PSWorkflowHost Runtime
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The host persistence delegate.
|
|
/// </summary>
|
|
public Func<bool> HostPersistenceDelegate
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Workflow activation delegate.
|
|
/// </summary>
|
|
public Action<object> ActivateDelegate
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The asynchronous execution collection.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
|
public Dictionary<string, PSActivityContext> AsyncExecutionCollection
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Currently executing remote activities state with runspace id or completion state.
|
|
/// </summary>
|
|
public PSWorkflowRemoteActivityState RemoteActivityState
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose method
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dipose of managed resources
|
|
/// </summary>
|
|
/// <param name="disposing">true if disposing</param>
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (!disposing) return;
|
|
|
|
Parameters = null;
|
|
ActivateDelegate = null;
|
|
AsyncExecutionCollection = null;
|
|
RemoteActivityState = null;
|
|
HostPersistenceDelegate = null;
|
|
Runtime = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runtime metadata that represents the currently-running command.
|
|
/// </summary>
|
|
public class HostSettingCommandMetadata
|
|
{
|
|
/// <summary>
|
|
/// The command name that generated this workflow.
|
|
/// </summary>
|
|
public string CommandName
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The start line of the command name that generated this section of
|
|
/// the workflow.
|
|
/// </summary>
|
|
public int StartLineNumber
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The start column of the command name that generated this section of
|
|
/// the workflow.
|
|
/// </summary>
|
|
public int StartColumnNumber
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The end line of the command name that generated this section of
|
|
/// the workflow.
|
|
/// </summary>
|
|
public int EndLineNumber
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The end column of the command name that generated this section of
|
|
/// the workflow.
|
|
/// </summary>
|
|
public int EndColumnNumber
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The activity context.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PSActivityContext : IDisposable
|
|
{
|
|
// Holds our list of commands to run, mapped to a RetryCount class of:
|
|
// Connection Attempts, Action Attempts
|
|
|
|
[NonSerialized]
|
|
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
|
|
|
|
[NonSerialized]
|
|
internal Dictionary<System.Management.Automation.PowerShell, RetryCount> runningCommands;
|
|
|
|
[NonSerialized]
|
|
internal ConcurrentQueue<ActivityImplementationContext> commandQueue;
|
|
|
|
/// <summary>
|
|
/// IsCanceled.
|
|
/// </summary>
|
|
public bool IsCanceled { get; set; }
|
|
internal Dictionary<string, object> UserVariables = new Dictionary<string, object>();
|
|
internal List<Exception> exceptions = new List<Exception>();
|
|
internal PSDataCollection<ErrorRecord> errors = new PSDataCollection<ErrorRecord>();
|
|
internal bool Failed;
|
|
internal bool SuspendOnError;
|
|
|
|
[NonSerialized]
|
|
internal readonly ConcurrentQueue<IAsyncResult> AsyncResults = new ConcurrentQueue<IAsyncResult>();
|
|
|
|
[NonSerialized]
|
|
internal EventHandler<RunspaceStateEventArgs> HandleRunspaceStateChanged;
|
|
|
|
[NonSerialized]
|
|
internal PSDataCollection<ProgressRecord> progress;
|
|
|
|
[NonSerialized] internal object SyncRoot = new object();
|
|
[NonSerialized] internal bool AllCommandsStarted;
|
|
[NonSerialized] internal int CommandsRunningCount = 0;
|
|
|
|
[NonSerialized] internal WaitCallback Callback;
|
|
[NonSerialized] internal object AsyncState;
|
|
[NonSerialized] internal Guid JobInstanceId;
|
|
|
|
[NonSerialized]
|
|
internal ActivityParameters ActivityParams;
|
|
|
|
[NonSerialized]
|
|
internal PSDataCollection<PSObject> Input;
|
|
|
|
[NonSerialized]
|
|
internal PSDataCollection<PSObject> Output;
|
|
|
|
[NonSerialized]
|
|
internal PSWorkflowHost WorkflowHost;
|
|
|
|
[NonSerialized]
|
|
internal HostParameterDefaults HostExtension;
|
|
|
|
[NonSerialized]
|
|
internal bool RunInProc;
|
|
|
|
[NonSerialized]
|
|
internal bool MergeErrorToOutput;
|
|
|
|
[NonSerialized]
|
|
internal Dictionary<string, object> ParameterDefaults;
|
|
|
|
[NonSerialized]
|
|
internal Type ActivityType;
|
|
|
|
[NonSerialized]
|
|
internal PrepareSessionDelegate PrepareSession;
|
|
|
|
[NonSerialized]
|
|
internal object ActivityObject;
|
|
|
|
/// <summary>
|
|
/// The .NET type implementing the cmdlet to call, used for
|
|
/// direct-call cmdlets.
|
|
/// </summary>
|
|
internal Type TypeImplementingCmdlet { get; set; }
|
|
|
|
internal bool RunWithCustomRemoting { get; set; }
|
|
|
|
/// <summary>
|
|
/// Cancelling the Async execution.
|
|
/// </summary>
|
|
public void Cancel()
|
|
{
|
|
if (WorkflowHost == null)
|
|
{
|
|
throw new InvalidOperationException("WorkflowHost");
|
|
}
|
|
|
|
// First, clear pending items
|
|
if (this.commandQueue != null)
|
|
{
|
|
while (!this.commandQueue.IsEmpty)
|
|
{
|
|
ActivityImplementationContext implementationContext;
|
|
bool gotCommand = this.commandQueue.TryDequeue(out implementationContext);
|
|
if (gotCommand)
|
|
{
|
|
System.Management.Automation.PowerShell command = implementationContext.PowerShellInstance;
|
|
_tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity: Cancelling pending command {0}.", command));
|
|
|
|
command.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
// cancel all pending invocations in activityhostmanager
|
|
PSResumableActivityHostController resumablecontroller = WorkflowHost.PSActivityHostController as PSResumableActivityHostController;
|
|
if (resumablecontroller != null)
|
|
{
|
|
resumablecontroller.StopAllResumablePSCommands(this.JobInstanceId);
|
|
}
|
|
else
|
|
{
|
|
PSOutOfProcessActivityController delegateController = WorkflowHost.PSActivityHostController as PSOutOfProcessActivityController;
|
|
if (delegateController != null)
|
|
{
|
|
while (this.AsyncResults.Count > 0)
|
|
{
|
|
IAsyncResult result;
|
|
this.AsyncResults.TryDequeue(out result);
|
|
delegateController.CancelInvokePowerShell(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (this.runningCommands.Count > 0)
|
|
{
|
|
System.Management.Automation.PowerShell currentCommand = null;
|
|
|
|
lock (this.runningCommands)
|
|
{
|
|
foreach (System.Management.Automation.PowerShell command in this.runningCommands.Keys)
|
|
{
|
|
currentCommand = command;
|
|
break;
|
|
}
|
|
|
|
if (currentCommand == null)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentCommand.InvocationStateInfo.State == PSInvocationState.Running)
|
|
{
|
|
_tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity: Stopping command {0}.", currentCommand));
|
|
|
|
try
|
|
{
|
|
currentCommand.Stop();
|
|
}
|
|
catch (NullReferenceException)
|
|
{
|
|
// The PowerShell API has a bug where it sometimes throws NullReferenceException
|
|
// when being stopped. Ignore in this case.
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// It's possible that the pipeline stopped just before stopping it.
|
|
// (In-process case)
|
|
}
|
|
}
|
|
|
|
|
|
// If anything fails, then set the overall failure state for the activity...
|
|
if (currentCommand.InvocationStateInfo.State != PSInvocationState.Completed || currentCommand.HadErrors)
|
|
{
|
|
this.Failed = true;
|
|
}
|
|
|
|
int commandType =
|
|
RunCommandsArguments.DetermineCommandExecutionType(
|
|
currentCommand.Runspace.ConnectionInfo as WSManConnectionInfo, RunInProc, ActivityType, this);
|
|
if (commandType != PSActivity.RunInProcNoRunspace)
|
|
{
|
|
PSActivity.CloseRunspaceAndDisposeCommand(currentCommand, WorkflowHost, this, commandType);
|
|
}
|
|
|
|
this.runningCommands.Remove(currentCommand);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks>This function is designed to be lightweight and to
|
|
/// run on the Workflow thread when Execute() is called. When
|
|
/// any changes are made to this function the contract needs to
|
|
/// be honored</remarks>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
|
"Microsoft.Reliability",
|
|
"CA2000:Dispose objects before losing scope",
|
|
Justification = "Disposed in EndExecute.")]
|
|
public bool Execute()
|
|
{
|
|
// Create the template of a thread that just pulls from the commandQueue
|
|
// and processes the SMA.PowerShell given to it.
|
|
/*
|
|
|
|
A note about cancellation
|
|
-------------------------
|
|
|
|
While a worker thread is running, there's
|
|
the possibility of the user cancelling the activity as a whole.
|
|
That means, at any line, there's the potential of us wanting
|
|
to stop what the thread is doing.
|
|
In the worst case of a race condition, the user attempts to cancel
|
|
the activity, but we ignore it.
|
|
The natural way to prevent this kind of race condition is through
|
|
locking so that the 'if(canceled)' condition doesn't change during
|
|
individual lines of the thread's invocation.
|
|
However, adding this lock would mean that the running thread would
|
|
_have_ the lock, which would would prevent the cancellation thread
|
|
from _entering_ the lock, preventing the cancellation thread from
|
|
signalling its condition. This is, in fact, the very worst case of
|
|
the race condition: the user wants to cancel the activity, but we
|
|
ignore it.
|
|
Verification of cancellation, then, is on a "best-effort" basis.
|
|
*/
|
|
|
|
if (commandQueue == null)
|
|
{
|
|
throw new InvalidOperationException("commandQueue");
|
|
}
|
|
|
|
ActivityImplementationContext implementationContext;
|
|
bool result = commandQueue.TryDequeue(out implementationContext);
|
|
|
|
lock (SyncRoot)
|
|
{
|
|
while (result)
|
|
{
|
|
RunCommandsArguments args = new RunCommandsArguments(ActivityParams, Output, Input,
|
|
this, WorkflowHost, RunInProc,
|
|
ParameterDefaults, ActivityType, PrepareSession,
|
|
ActivityObject, implementationContext);
|
|
// increment count of running commands
|
|
Interlocked.Increment(ref CommandsRunningCount);
|
|
PSActivity.BeginRunOneCommand(args);
|
|
|
|
result = commandQueue.TryDequeue(out implementationContext);
|
|
}
|
|
|
|
// at this point all commands in the activity context should have been
|
|
// started
|
|
AllCommandsStarted = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose
|
|
/// </summary>
|
|
/// <param name="disposing"></param>
|
|
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", Justification = "errors is disposed at the time workflow is removed.")]
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposing)
|
|
return;
|
|
|
|
_tracer.Dispose();
|
|
|
|
Callback = null;
|
|
PrepareSession = null;
|
|
HandleRunspaceStateChanged = null;
|
|
ActivityObject = null;
|
|
ParameterDefaults = null;
|
|
Input = null;
|
|
Output = null;
|
|
errors = null;
|
|
progress = null;
|
|
WorkflowHost = null;
|
|
ActivityParams = null;
|
|
exceptions = null;
|
|
runningCommands = null;
|
|
commandQueue = null;
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the activity on-resume action.
|
|
/// </summary>
|
|
public enum ActivityOnResumeAction
|
|
{
|
|
/// <summary>
|
|
/// Indicates the resumption is normal.
|
|
/// </summary>
|
|
Resume = 0,
|
|
|
|
/// <summary>
|
|
/// Indicates that the activity needs to be restarted.
|
|
/// </summary>
|
|
Restart = 1,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Activities derived from this class can be used in the Pipeline activity
|
|
/// </summary>
|
|
public abstract class PipelineEnabledActivity : NativeActivity
|
|
{
|
|
|
|
/// <summary>
|
|
/// The Input stream for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<PSDataCollection<PSObject>> Input
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to connect the input stream for this activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(false)]
|
|
public bool UseDefaultInput
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The output stream from the activity
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<PSObject>> Result
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to append output to Result.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public bool? AppendOutput
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
|
|
internal class BookmarkContext
|
|
{
|
|
internal PSWorkflowInstanceExtension BookmarkResumingExtension { get; set; }
|
|
internal Bookmark CurrentBookmark { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base class for PowerShell-based workflow activities
|
|
/// </summary>
|
|
public abstract class PSActivity : PipelineEnabledActivity
|
|
{
|
|
/// <summary>
|
|
/// The bookmark prefix for Powershell activities.
|
|
/// </summary>
|
|
public static readonly string PSBookmarkPrefix = "Microsoft_PowerShell_Workflow_Bookmark_";
|
|
|
|
/// <summary>
|
|
/// The bookmark prefix for Powershell suspend activities.
|
|
/// </summary>
|
|
public static readonly string PSSuspendBookmarkPrefix = "Microsoft_PowerShell_Workflow_Bookmark_Suspend_";
|
|
|
|
/// <summary>
|
|
/// The bookmark prefix for Powershell persist activities.
|
|
/// </summary>
|
|
public static readonly string PSPersistBookmarkPrefix = "Microsoft_PowerShell_Workflow_Bookmark_PSPersist_";
|
|
|
|
/// <summary>
|
|
/// Constructor for the PSActivity class.
|
|
/// </summary>
|
|
protected PSActivity()
|
|
{
|
|
cancelTimer = new Delay
|
|
{
|
|
Duration = new InArgument<TimeSpan>((context) =>
|
|
new TimeSpan(0, 0, (int)context.GetValue(this.PSActionRunningTimeoutSec)))
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the fully qualified name of the command invoked by this activity.
|
|
/// </summary>
|
|
public virtual string PSCommandName
|
|
{
|
|
get
|
|
{
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the module defining the command called by this activity.
|
|
/// It may be null.
|
|
/// </summary>
|
|
protected virtual string PSDefiningModule
|
|
{
|
|
get
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tracer initialization.
|
|
/// </summary>
|
|
protected PowerShellTraceSource Tracer
|
|
{
|
|
get
|
|
{
|
|
return _tracer;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// In addition to the display name PSProgress Message will provide
|
|
/// the way to append the additional information into the activity progress message
|
|
/// like branch name or iteration number in case of parallel foreach.
|
|
/// </summary>
|
|
[DefaultValue(null)]
|
|
public InArgument<string> PSProgressMessage
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Error stream / collection for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<ErrorRecord>> PSError
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Progress stream / collection for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<ProgressRecord>> PSProgress
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Verbose stream / collection for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<VerboseRecord>> PSVerbose
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Debug stream / collection for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<DebugRecord>> PSDebug
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Warning stream / collection for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<WarningRecord>> PSWarning
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Information stream / collection for the activity.
|
|
/// </summary>
|
|
[InputAndOutputCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InOutArgument<PSDataCollection<InformationRecord>> PSInformation
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forces the activity to return non-serialized objects. Resulting objects
|
|
/// have functional methods and properties (as opposed to serialized versions
|
|
/// of them), but will not survive persistence when the Workflow crashes or is
|
|
/// persisted.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<bool?> PSDisableSerialization
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forces the activity to not call the persist functionality, which will be responsible for
|
|
/// persisting the workflow state onto the disk.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<bool?> PSPersist
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to merge error data to the output stream
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<bool?> MergeErrorToOutput
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the maximum amount of time, in seconds, that this activity may run.
|
|
/// The default is unlimited.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<uint?> PSActionRunningTimeoutSec
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This the list of module names (or paths) that are required to run this Activity successfully.
|
|
/// The default is null.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<string[]> PSRequiredModules
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the number of retries that the activity will make when it encounters
|
|
/// an error during execution of its action. The default is to not retry.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<uint?> PSActionRetryCount
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the delay, in seconds, between action retry attempts.
|
|
/// The default is one second.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<uint?> PSActionRetryIntervalSec
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to emit verbose output of the activity.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<bool?> Verbose
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to emit debug output of the activity.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<bool?> Debug
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines how errors should be handled by the activity.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<ActionPreference?> ErrorAction
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines how warnings should be handled by the activity.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<ActionPreference?> WarningAction
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines how information records should be handled by the activity.
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public InArgument<ActionPreference?> InformationAction
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides access to the parameter defaults dictionary
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design","CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
protected Variable<Dictionary<string, object>> ParameterDefaults
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private Variable<PSActivityContext> psActivityContextImplementationVariable = new Variable<PSActivityContext>("psActivityContextImplementationVariable");
|
|
private Variable<ActivityInstance> psRunningTimeoutDelayActivityInstanceVar = new Variable<ActivityInstance>("psRunningTimeoutDelayActivityInstanceVar");
|
|
private Delay cancelTimer;
|
|
|
|
// Instance variables. The following are OK to be an instance variables, as they are
|
|
// not modified during runtime. If any variable holds activity-specific state,
|
|
// it must be stored in PSActivityContext.
|
|
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
|
|
private static readonly Tracer _structuredTracer = new Tracer();
|
|
|
|
private Variable<NoPersistHandle> noPersistHandle = new Variable<NoPersistHandle>();
|
|
private Variable<bool> bookmarking = new Variable<bool>();
|
|
private TerminateWorkflow terminateActivity = new TerminateWorkflow() { Reason = Resources.RunningTimeExceeded };
|
|
private SuspendOnError suspendActivity = new SuspendOnError();
|
|
|
|
/// <summary>
|
|
/// Records a non-terminating error. If the runtime has associated an error stream, this
|
|
/// error will be written to that stream. Otherwise, this will be thrown as an exception.
|
|
/// </summary>
|
|
/// <param name="exception">The exception associated with the error.</param>
|
|
/// <param name="errorId">The error ID associated with the error. This should be a non-localized string.</param>
|
|
/// <param name="errorCategory">The error category associated with the error.</param>
|
|
/// <param name="originalTarget">The object that was being processed while encountering this error.</param>
|
|
/// <param name="psActivityContext">The powershell activity context.</param>
|
|
private static void WriteError(Exception exception, string errorId, ErrorCategory errorCategory, object originalTarget, PSActivityContext psActivityContext)
|
|
{
|
|
if (psActivityContext.errors != null)
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(exception, errorId, errorCategory, originalTarget);
|
|
lock (psActivityContext.errors)
|
|
{
|
|
psActivityContext.errors.Add(errorRecord);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lock (psActivityContext.exceptions)
|
|
{
|
|
psActivityContext.exceptions.Add(exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// In order for an activity to go idle, 'CanInduceIdle' should be true.
|
|
/// </summary>
|
|
protected override bool CanInduceIdle
|
|
{
|
|
get
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begins the execution of the activity.
|
|
/// </summary>
|
|
/// <param name="context">The NativeActivityContext provided by the workflow.</param>
|
|
/// <returns></returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
|
"Microsoft.Reliability",
|
|
"CA2000:Dispose objects before losing scope",
|
|
Justification = "Disposed in EndExecute.")]
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Beginning execution.", context.ActivityInstanceId));
|
|
|
|
string displayName = this.DisplayName;
|
|
|
|
if (string.IsNullOrEmpty(displayName))
|
|
displayName = this.GetType().Name;
|
|
|
|
if (_structuredTracer.IsEnabled)
|
|
{
|
|
_structuredTracer.ActivityExecutionStarted(displayName, this.GetType().FullName);
|
|
}
|
|
|
|
// bookmarking will only be enabled when the PSPersist variable is set to 'true' by the author.
|
|
// so we are retriveing the information before host overrides.
|
|
// if the value is false or not provided then we will not go into the unloaded mode.
|
|
|
|
bool intBookmarking = InternalBookmarkingRequired(context);
|
|
|
|
if (intBookmarking == false)
|
|
{
|
|
NoPersistHandle handle = this.noPersistHandle.Get(context);
|
|
handle.Enter(context);
|
|
}
|
|
this.bookmarking.Set(context, intBookmarking);
|
|
|
|
Dictionary<string, object> parameterDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
HostParameterDefaults hostExtension = null;
|
|
|
|
// Retrieve our host overrides
|
|
hostExtension = context.GetExtension<HostParameterDefaults>();
|
|
if (hostExtension != null)
|
|
{
|
|
Dictionary<string, object> incomingArguments = hostExtension.Parameters;
|
|
foreach (KeyValuePair<string, object> parameterDefault in incomingArguments)
|
|
{
|
|
parameterDefaults[parameterDefault.Key] = parameterDefault.Value;
|
|
}
|
|
|
|
if (parameterDefaults.ContainsKey("PSComputerName"))
|
|
{
|
|
if (parameterDefaults["PSComputerName"] is String)
|
|
{
|
|
// If we are given a single string as a parameter default, change it to our required array
|
|
parameterDefaults["PSComputerName"] = new object[] { (string) parameterDefaults["PSComputerName"] };
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// If they haven't specified 'UseDefaultInput', ignore that host override
|
|
if( (! this.UseDefaultInput) &&
|
|
parameterDefaults.ContainsKey("Input"))
|
|
{
|
|
parameterDefaults.Remove("Input");
|
|
}
|
|
|
|
// Store the values in context
|
|
context.SetValue<Dictionary<string, object>>(this.ParameterDefaults, parameterDefaults);
|
|
|
|
// Prepare our state
|
|
PSActivityContext psActivityContextInstance = new PSActivityContext();
|
|
psActivityContextInstance.runningCommands = new Dictionary<System.Management.Automation.PowerShell, RetryCount>();
|
|
psActivityContextInstance.commandQueue = new ConcurrentQueue<ActivityImplementationContext>();
|
|
psActivityContextInstance.IsCanceled = false;
|
|
psActivityContextInstance.HostExtension = hostExtension;
|
|
|
|
// If this is a GenericCimCmdletActivity, then copy the type of the cmdlet int0
|
|
// the context so we'll know which type to instantiate at runtime.
|
|
GenericCimCmdletActivity genericCimActivity = this as GenericCimCmdletActivity;
|
|
if (genericCimActivity != null)
|
|
{
|
|
psActivityContextInstance.TypeImplementingCmdlet = genericCimActivity.TypeImplementingCmdlet;
|
|
}
|
|
|
|
foreach (PSActivityArgumentInfo currentArgument in GetActivityArguments())
|
|
{
|
|
Argument argumentInstance = currentArgument.Value;
|
|
PopulateParameterFromDefault(argumentInstance, context, currentArgument.Name, parameterDefaults);
|
|
|
|
// Log the bound parameter
|
|
if (argumentInstance.Get(context) != null)
|
|
{
|
|
Tracer.WriteMessage(
|
|
String.Format(
|
|
CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Using parameter {1}, with value '{2}'.",
|
|
context.ActivityInstanceId,
|
|
currentArgument.Name,
|
|
argumentInstance.Get(context)));
|
|
}
|
|
}
|
|
|
|
// Commands that get hooked up to the input stream when that
|
|
// input stream is empty should not run:
|
|
// PS > function foo { $input | Write-Output "Hello" }
|
|
// PS > foo
|
|
// PS >
|
|
PSDataCollection<PSObject> input = Input.Get(context);
|
|
if ((input != null) && (input.Count == 0))
|
|
{
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: Execution skipped due to supplied (but empty) pipeline input.", context.ActivityInstanceId));
|
|
return;
|
|
}
|
|
|
|
// Get the output. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
bool needToCreateOutput = false;
|
|
PSDataCollection<PSObject> output = Result.Get(context);
|
|
|
|
// Set serialization properties on the stream if there is one.
|
|
// If there isn't one, the properties will be set when we
|
|
// create it.
|
|
if (output != null)
|
|
{
|
|
// Set the collection to serialize by default if that option is specified
|
|
if (!GetDisableSerialization(context))
|
|
{
|
|
output.SerializeInput = true;
|
|
}
|
|
else
|
|
{
|
|
output.SerializeInput = false;
|
|
}
|
|
}
|
|
|
|
if (((output == null) || (! output.IsOpen)) && (this.Result.Expression != null))
|
|
{
|
|
if (output == null)
|
|
{
|
|
output = CreateOutputStream(context);
|
|
}
|
|
else
|
|
{
|
|
needToCreateOutput = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// See if it's a host default stream. If so, the expectation is that this stream will
|
|
// remain and hold all results at the end. Otherwise, clear it every invocation.
|
|
if ((ParameterDefaults != null) &&
|
|
parameterDefaults.ContainsKey("Result") &&
|
|
(parameterDefaults["Result"] == this.Result.Get(context)))
|
|
{
|
|
// Do nothing
|
|
}
|
|
else
|
|
{
|
|
// This is a user-supplied output stream, and should be cleared the same
|
|
// way that variables are overwritten when assigned multiple times in a
|
|
// C# method.
|
|
if (output != null)
|
|
{
|
|
bool appendOutput = false;
|
|
if ((this.AppendOutput != null) && (this.AppendOutput.Value))
|
|
{
|
|
appendOutput = true;
|
|
}
|
|
|
|
if (!appendOutput)
|
|
{
|
|
needToCreateOutput = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the error stream. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
psActivityContextInstance.errors = PSError.Get(context);
|
|
if (this.PSError.Expression != null)
|
|
{
|
|
if ((psActivityContextInstance.errors == null) || psActivityContextInstance.errors.IsAutoGenerated)
|
|
{
|
|
psActivityContextInstance.errors = new PSDataCollection<ErrorRecord>();
|
|
psActivityContextInstance.errors.IsAutoGenerated = true;
|
|
|
|
this.PSError.Set(context, psActivityContextInstance.errors);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: No ErrorStream was passed in; creating a new stream.",
|
|
context.ActivityInstanceId));
|
|
}
|
|
}
|
|
|
|
// Merge error stream to the output stream
|
|
if (this.MergeErrorToOutput.Get(context) != null &&
|
|
this.MergeErrorToOutput.Get(context).GetValueOrDefault(false) &&
|
|
output != null && psActivityContextInstance.errors != null)
|
|
{
|
|
// See if it's a host default stream. If so, we need to create our own instance
|
|
// to hold the error, so that the host won't consume the event first
|
|
if (ParameterDefaults != null &&
|
|
parameterDefaults.ContainsKey("PSError") &&
|
|
(parameterDefaults["PSError"] == PSError.Get(context)))
|
|
{
|
|
psActivityContextInstance.errors = new PSDataCollection<ErrorRecord>();
|
|
psActivityContextInstance.errors.IsAutoGenerated = true;
|
|
|
|
this.PSError.Set(context, psActivityContextInstance.errors);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: Merge error to the output stream and current error stream is the host default; creating a new stream.",
|
|
context.ActivityInstanceId));
|
|
}
|
|
|
|
psActivityContextInstance.MergeErrorToOutput = true;
|
|
}
|
|
|
|
// Get the progress stream. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
PSDataCollection<ProgressRecord> progress = PSProgress.Get(context);
|
|
if (this.PSProgress.Expression != null)
|
|
{
|
|
if ((progress == null) || progress.IsAutoGenerated)
|
|
{
|
|
progress = new PSDataCollection<ProgressRecord>();
|
|
progress.IsAutoGenerated = true;
|
|
|
|
this.PSProgress.Set(context, progress);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: No ProgressStream was passed in; creating a new stream.", context.ActivityInstanceId));
|
|
}
|
|
}
|
|
|
|
psActivityContextInstance.progress = progress;
|
|
|
|
// Write the Activity Starting progress record...
|
|
WriteProgressRecord(context, progress, Resources.RunningString, ProgressRecordType.Processing);
|
|
|
|
// Get the verbose stream. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
PSDataCollection<VerboseRecord> verbose = PSVerbose.Get(context);
|
|
if (this.PSVerbose.Expression != null)
|
|
{
|
|
if ((verbose == null) || verbose.IsAutoGenerated)
|
|
{
|
|
verbose = new PSDataCollection<VerboseRecord>();
|
|
verbose.IsAutoGenerated = true;
|
|
|
|
this.PSVerbose.Set(context, verbose);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: No VerboseStream was passed in; creating a new stream.", context.ActivityInstanceId));
|
|
}
|
|
}
|
|
|
|
// Get the debug stream. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
PSDataCollection<DebugRecord> debug = PSDebug.Get(context);
|
|
if (this.PSDebug.Expression != null)
|
|
{
|
|
if ((debug == null) || debug.IsAutoGenerated)
|
|
{
|
|
debug = new PSDataCollection<DebugRecord>();
|
|
debug.IsAutoGenerated = true;
|
|
|
|
this.PSDebug.Set(context, debug);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: No DebugStream was passed in; creating a new stream.", context.ActivityInstanceId));
|
|
}
|
|
}
|
|
|
|
// Get the warning stream. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
PSDataCollection<WarningRecord> warning = PSWarning.Get(context);
|
|
if (this.PSWarning.Expression != null)
|
|
{
|
|
if ((warning == null) || warning.IsAutoGenerated)
|
|
{
|
|
warning = new PSDataCollection<WarningRecord>();
|
|
warning.IsAutoGenerated = true;
|
|
|
|
this.PSWarning.Set(context, warning);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: No WarningStream was passed in; creating a new stream.", context.ActivityInstanceId));
|
|
}
|
|
}
|
|
|
|
// Get the information stream. If it's null (but has been assigned by the user),
|
|
// then populate it.
|
|
PSDataCollection<InformationRecord> information = PSInformation.Get(context);
|
|
if (this.PSInformation.Expression != null)
|
|
{
|
|
if ((information == null) || information.IsAutoGenerated)
|
|
{
|
|
information = new PSDataCollection<InformationRecord>();
|
|
information.IsAutoGenerated = true;
|
|
|
|
this.PSInformation.Set(context, information);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: No InformationStream was passed in; creating a new stream.", context.ActivityInstanceId));
|
|
}
|
|
}
|
|
|
|
// Clear the input stream so that it is consumed, but only if streamed from
|
|
// the host. We don't want to clear user-supplied streams.
|
|
input = Input.Get(context);
|
|
if (input != null)
|
|
{
|
|
bool usedDefaultOutput = false;
|
|
|
|
if ((ParameterDefaults != null) &&
|
|
parameterDefaults.ContainsKey("Input") &&
|
|
(parameterDefaults["Input"] == this.Input.Get(context)))
|
|
{
|
|
usedDefaultOutput = true;
|
|
}
|
|
|
|
if (usedDefaultOutput)
|
|
{
|
|
PSDataCollection<PSObject> newInput = new PSDataCollection<PSObject>(input);
|
|
newInput.IsAutoGenerated = true;
|
|
|
|
this.Input.Set(context, newInput);
|
|
input.Clear();
|
|
|
|
input = Input.Get(context);
|
|
}
|
|
}
|
|
|
|
// Auto reconnect feature is enabled only for the remoting activity with persistence
|
|
bool enableAutoConnectToManagedNode = this is PSRemotingActivity && intBookmarking;
|
|
var hostParamValues = context.GetExtension<HostParameterDefaults>();
|
|
bool remoteActivityResumeOperation = false;
|
|
PSWorkflowRemoteActivityState remoteActivityState = null;
|
|
if (enableAutoConnectToManagedNode && hostParamValues != null && hostParamValues.RemoteActivityState != null)
|
|
{
|
|
remoteActivityResumeOperation = hostParamValues.RemoteActivityState.RemoteActivityResumeRequired(this, false);
|
|
remoteActivityState = hostParamValues.RemoteActivityState;
|
|
}
|
|
|
|
// Prepare the command(s)
|
|
List<ActivityImplementationContext> commandsToRun = GetTasks(context);
|
|
int taskId = 1;
|
|
foreach (ActivityImplementationContext commandToRun in commandsToRun)
|
|
{
|
|
if (remoteActivityState != null)
|
|
{
|
|
if (remoteActivityResumeOperation == true)
|
|
{
|
|
// task state is maintained in remoteActivityState as <activity id, task id, state>
|
|
// state value can be NotStarted, RunspaceId (running), Completed
|
|
|
|
object taskEntry = null;
|
|
taskEntry = remoteActivityState.GetRemoteActivityRunspaceEntry(this.Id, taskId);
|
|
|
|
if (taskEntry != null)
|
|
{
|
|
// re-execution of completed tasks is not required during the resume operation
|
|
string taskCompletion = taskEntry as string;
|
|
if ((taskCompletion != null) && (String.Compare(taskCompletion, "completed", StringComparison.OrdinalIgnoreCase) == 0))
|
|
{
|
|
taskId++;
|
|
continue;
|
|
}
|
|
|
|
if (taskEntry is Guid)
|
|
commandToRun.DisconnectedRunspaceInstanceId = (Guid)taskEntry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// After crash/shutdown, this entry is required for checking all activity level fanout tasks are completed
|
|
remoteActivityState.SetRemoteActivityRunspaceEntry(this.Id, taskId, "notstarted",
|
|
commandToRun.ConnectionInfo != null ? commandToRun.ConnectionInfo.ComputerName: null);
|
|
}
|
|
}
|
|
|
|
|
|
// If the activity contains a ScriptBlock, then we set the user variables from the workflow.
|
|
bool hasScriptBlock = false;
|
|
|
|
foreach (PropertyInfo field in this.GetType().GetProperties())
|
|
{
|
|
// See if it's an argument
|
|
if (typeof(Argument).IsAssignableFrom(field.PropertyType))
|
|
{
|
|
// Get the argument
|
|
Argument currentArgument = (Argument)field.GetValue(this, null);
|
|
|
|
if (currentArgument != null &&
|
|
(currentArgument.ArgumentType.IsAssignableFrom(typeof(ScriptBlock)) ||
|
|
currentArgument.ArgumentType.IsAssignableFrom(typeof(ScriptBlock[]))))
|
|
{
|
|
hasScriptBlock = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Populate the user variables for locally running activities or activities with scriptblock
|
|
if (hasScriptBlock || !(this is PSRemotingActivity))
|
|
{
|
|
PopulateRunspaceFromContext(commandToRun, psActivityContextInstance, context);
|
|
}
|
|
|
|
commandToRun.Id = taskId++;
|
|
commandToRun.EnableRemotingActivityAutoResume = enableAutoConnectToManagedNode;
|
|
|
|
// If user execute activity with credential but no computername/connectionUri, credential will be bypassed.
|
|
// We throw a warning here.
|
|
if ((commandToRun.PSCredential != null) &&
|
|
!(((commandToRun.PSComputerName != null) && !commandToRun.PSComputerName.All(name => string.IsNullOrEmpty(name))) ||
|
|
((commandToRun.PSConnectionUri != null) && (commandToRun.PSConnectionUri.Length > 0))))
|
|
|
|
{
|
|
commandToRun.PSWarning.Add(new WarningRecord(Resources.CredentialParameterAssignedWithNoComputerName));
|
|
}
|
|
psActivityContextInstance.commandQueue.Enqueue(commandToRun);
|
|
}
|
|
|
|
// Launch our delegate to actually run the given command,
|
|
// possibly across many machines.
|
|
//Func<ActivityParameters, PSDataCollection<PSObject>, PSDataCollection<PSObject>, PSActivityContext, string,
|
|
// PSWorkflowHost, bool, Dictionary<string, object>, Type, PrepareSessionDelegate, object, bool> RunCommandsDelegate = Execute;
|
|
|
|
// Set up the max running time trigger
|
|
uint? maxRunningTime = PSActionRunningTimeoutSec.Get(context);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Max running time: {1}.", context.ActivityInstanceId, maxRunningTime));
|
|
|
|
if (maxRunningTime.HasValue)
|
|
{
|
|
psRunningTimeoutDelayActivityInstanceVar.Set(context, context.ScheduleActivity(cancelTimer, MaxRunTimeElapsed));
|
|
}
|
|
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Invoking command.", context.ActivityInstanceId));
|
|
|
|
OnActivityCreated(this, new ActivityCreatedEventArgs(null));
|
|
|
|
uint? connectionRetryCount = null;
|
|
uint? connectionRetryInterval = null;
|
|
|
|
IImplementsConnectionRetry implementsRetry = this as IImplementsConnectionRetry;
|
|
if (implementsRetry != null)
|
|
{
|
|
connectionRetryCount = implementsRetry.PSConnectionRetryCount.Get(context);
|
|
connectionRetryInterval = implementsRetry.PSConnectionRetryIntervalSec.Get(context);
|
|
}
|
|
|
|
List<string> modulesToLoad = new List<string>();
|
|
if (!string.IsNullOrEmpty(PSDefiningModule))
|
|
{
|
|
modulesToLoad.Add(PSDefiningModule);
|
|
}
|
|
string[] requiredModules = PSRequiredModules.Get(context);
|
|
if (requiredModules != null)
|
|
{
|
|
modulesToLoad.AddRange(requiredModules);
|
|
}
|
|
|
|
Action<object> activateDelegate = null;
|
|
if ((hostExtension != null) && (hostExtension.ActivateDelegate != null))
|
|
{
|
|
activateDelegate = hostExtension.ActivateDelegate;
|
|
}
|
|
|
|
Guid jobInstanceId = Guid.Empty;
|
|
object asyncState = null;
|
|
WaitCallback callback = null;
|
|
Bookmark bookmark = context.CreateBookmark(PSBookmarkPrefix + Guid.NewGuid().ToString(), this.OnResumeBookmark);
|
|
|
|
if (activateDelegate != null)
|
|
{
|
|
asyncState = bookmark;
|
|
callback = new WaitCallback(activateDelegate);
|
|
jobInstanceId = hostExtension.JobInstanceId;
|
|
|
|
if ((hostExtension != null) && (hostExtension.AsyncExecutionCollection != null))
|
|
{
|
|
Dictionary<string, PSActivityContext> asyncExecutionCollection = null;
|
|
asyncExecutionCollection = hostExtension.AsyncExecutionCollection;
|
|
if (asyncExecutionCollection != null)
|
|
{
|
|
if (asyncExecutionCollection.ContainsKey(context.ActivityInstanceId))
|
|
{
|
|
asyncExecutionCollection.Remove(context.ActivityInstanceId);
|
|
}
|
|
|
|
asyncExecutionCollection.Add(context.ActivityInstanceId, psActivityContextInstance);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PSWorkflowInstanceExtension extension = context.GetExtension<PSWorkflowInstanceExtension>();
|
|
BookmarkContext resumeContext = new BookmarkContext
|
|
{
|
|
CurrentBookmark = bookmark,
|
|
BookmarkResumingExtension = extension
|
|
};
|
|
|
|
callback = OnComplete;
|
|
asyncState = resumeContext;
|
|
}
|
|
|
|
psActivityContextImplementationVariable.Set(context, psActivityContextInstance);
|
|
|
|
psActivityContextInstance.Callback = callback;
|
|
psActivityContextInstance.AsyncState = asyncState;
|
|
psActivityContextInstance.JobInstanceId = jobInstanceId;
|
|
|
|
psActivityContextInstance.ActivityParams = new ActivityParameters(connectionRetryCount, connectionRetryInterval,
|
|
PSActionRetryCount.Get(context), PSActionRetryIntervalSec.Get(context),
|
|
modulesToLoad.ToArray());
|
|
psActivityContextInstance.Input = input;
|
|
|
|
if (needToCreateOutput)
|
|
{
|
|
output = CreateOutputStream(context);
|
|
}
|
|
|
|
psActivityContextInstance.Output = output;
|
|
psActivityContextInstance.WorkflowHost = GetWorkflowHost(hostExtension);
|
|
psActivityContextInstance.RunInProc = GetRunInProc(context);
|
|
psActivityContextInstance.ParameterDefaults = parameterDefaults;
|
|
psActivityContextInstance.ActivityType = GetType();
|
|
psActivityContextInstance.PrepareSession = PrepareSession;
|
|
psActivityContextInstance.ActivityObject = this;
|
|
|
|
if (IsActivityInlineScript(this) && RunWithCustomRemoting(context))
|
|
{
|
|
psActivityContextInstance.RunWithCustomRemoting = true;
|
|
}
|
|
|
|
// One more time writing the parameter defaults into the context
|
|
context.SetValue<Dictionary<string, object>>(this.ParameterDefaults, parameterDefaults);
|
|
|
|
// Execution
|
|
psActivityContextInstance.Execute();
|
|
|
|
if (_structuredTracer.IsEnabled)
|
|
{
|
|
_structuredTracer.ActivityExecutionFinished(displayName);
|
|
}
|
|
|
|
}
|
|
|
|
private bool GetDisableSerialization(NativeActivityContext context)
|
|
{
|
|
// First check parameter override
|
|
bool valueByParameter = PSDisableSerialization.Get(context).GetValueOrDefault(false);
|
|
if (valueByParameter) { return valueByParameter; }
|
|
|
|
// Next check preference variable
|
|
foreach (System.ComponentModel.PropertyDescriptor property in context.DataContext.GetProperties())
|
|
{
|
|
if (string.Equals(property.DisplayName, "PSDisableSerializationPreference", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
object serializationPreference = property.GetValue(context.DataContext);
|
|
if (serializationPreference != null)
|
|
{
|
|
bool variableValue;
|
|
if (LanguagePrimitives.TryConvertTo<bool>(serializationPreference, CultureInfo.InvariantCulture, out variableValue))
|
|
{
|
|
return variableValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally, check URI setting.
|
|
// Always say "disable serialization" for the Server Manager endpoint.
|
|
// This should NOT be removed at the same time as any RDS changes and should be
|
|
// evaluated separately.
|
|
if (PSSessionConfigurationData.IsServerManager)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool InternalBookmarkingRequired(NativeActivityContext context)
|
|
{
|
|
bool? activityPersistFlag = GetActivityPersistFlag(context);
|
|
return activityPersistFlag.HasValue ? (bool)activityPersistFlag : false;
|
|
}
|
|
|
|
private void MaxRunTimeElapsed(NativeActivityContext context, ActivityInstance instance)
|
|
{
|
|
if (instance.State != ActivityInstanceState.Canceled)
|
|
{
|
|
string message = String.Format(System.Globalization.CultureInfo.CurrentCulture,
|
|
Resources.RunningTimeExceeded, PSActionRunningTimeoutSec.Get(context));
|
|
throw new TimeoutException(message);
|
|
}
|
|
}
|
|
|
|
private PSDataCollection<PSObject> CreateOutputStream(NativeActivityContext context)
|
|
{
|
|
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
|
|
output.IsAutoGenerated = true;
|
|
output.EnumeratorNeverBlocks = true;
|
|
|
|
// Set the collection to serialize by default if that option is specified
|
|
if (!GetDisableSerialization(context))
|
|
{
|
|
output.SerializeInput = true;
|
|
}
|
|
else
|
|
{
|
|
output.SerializeInput = false;
|
|
}
|
|
|
|
this.Result.Set(context, output);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: No OutputStream was passed in; creating a new stream.", context.ActivityInstanceId));
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a progress record fo the current activity
|
|
/// </summary>
|
|
/// <param name="context">Workflow engine context</param>
|
|
/// <param name="progress">The progress stream to write to</param>
|
|
/// <param name="statusDescription">The status string to display</param>
|
|
/// <param name="type">the Progress record type</param>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
|
|
protected void WriteProgressRecord(NativeActivityContext context, PSDataCollection<ProgressRecord> progress, string statusDescription, ProgressRecordType type)
|
|
{
|
|
if (progress == null)
|
|
{
|
|
// While it seems like we should throw and exception, since we want to run in a non-M3P
|
|
// environment, we need to silently ignore this....
|
|
return;
|
|
}
|
|
|
|
string activityProgMsg = null;
|
|
|
|
if ((context != null) && (this.PSProgressMessage != null))
|
|
{
|
|
activityProgMsg = this.PSProgressMessage.Get(context);
|
|
|
|
// there is no need to write the progress message since the value of psprogressmessage is explicityly provided with null
|
|
if (this.PSProgressMessage.Expression != null && string.IsNullOrEmpty(activityProgMsg))
|
|
return;
|
|
}
|
|
|
|
|
|
string progressActivity;
|
|
|
|
if (activityProgMsg == null)
|
|
{
|
|
progressActivity = this.DisplayName;
|
|
|
|
if (string.IsNullOrEmpty(progressActivity))
|
|
progressActivity = this.GetType().Name;
|
|
}
|
|
else
|
|
{
|
|
progressActivity = this.DisplayName + ": " + activityProgMsg;
|
|
}
|
|
|
|
ProgressRecord pr = new ProgressRecord(0, progressActivity, statusDescription);
|
|
pr.RecordType = type;
|
|
|
|
string currentOperation = this.Id + ":";
|
|
|
|
// Add in position information from the host override
|
|
if (context != null)
|
|
{
|
|
HostParameterDefaults hostExtension = context.GetExtension<HostParameterDefaults>();
|
|
if (hostExtension != null)
|
|
{
|
|
HostSettingCommandMetadata commandMetadata = hostExtension.HostCommandMetadata;
|
|
if (commandMetadata != null)
|
|
{
|
|
currentOperation += String.Format(CultureInfo.CurrentCulture,
|
|
Resources.ProgressPositionMessage,
|
|
commandMetadata.CommandName, commandMetadata.StartLineNumber, commandMetadata.StartColumnNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
pr.CurrentOperation = currentOperation;
|
|
|
|
// Look to see if the parent id has been set for this scope...
|
|
if (context != null)
|
|
{
|
|
foreach (System.ComponentModel.PropertyDescriptor property in context.DataContext.GetProperties())
|
|
{
|
|
if (string.Equals(property.DisplayName, WorkflowPreferenceVariables.PSParentActivityId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
object parentId = property.GetValue(context.DataContext);
|
|
if (parentId != null)
|
|
{
|
|
int idToUse;
|
|
if (LanguagePrimitives.TryConvertTo<int>(parentId, CultureInfo.InvariantCulture, out idToUse))
|
|
{
|
|
pr.ParentActivityId = idToUse;
|
|
}
|
|
}
|
|
}
|
|
else if (string.Equals(property.DisplayName, "ProgressPreference", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
string progressPreference = property.GetValue(context.DataContext) as string;
|
|
if (!string.IsNullOrEmpty(progressPreference) &&
|
|
(string.Equals(progressPreference, "SilentlyContinue", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(progressPreference, "Ignore", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
// See if we should skip writing out the progress record...
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
progress.Add(pr);
|
|
}
|
|
|
|
|
|
private static void OnComplete(object state)
|
|
{
|
|
_structuredTracer.Correlate();
|
|
|
|
//BookmarkContext bookmarkContext = (BookmarkContext)result.AsyncState;
|
|
Dbg.Assert(state != null, "State not passed correctly to OnComplete");
|
|
BookmarkContext bookmarkContext = state as BookmarkContext;
|
|
Dbg.Assert(bookmarkContext != null, "BookmarkContext not passed correctly to OnComplete");
|
|
PSWorkflowInstanceExtension extension = bookmarkContext.BookmarkResumingExtension;
|
|
Bookmark bookmark = bookmarkContext.CurrentBookmark;
|
|
|
|
// Resuming the bookmark
|
|
ThreadPool.QueueUserWorkItem(o => extension.BeginResumeBookmark(bookmark, null, ar => extension.EndResumeBookmark(ar), null));
|
|
|
|
}
|
|
|
|
private void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object value)
|
|
{
|
|
_structuredTracer.Correlate();
|
|
|
|
if (this.bookmarking.Get(context) == false)
|
|
{
|
|
NoPersistHandle handle = this.noPersistHandle.Get(context);
|
|
handle.Exit(context);
|
|
}
|
|
|
|
//context.RemoveBookmark(bookmark);
|
|
|
|
// Check for the activity if it is restarting
|
|
// this is possible when the activity in bookmarked stated get crashed/terminated
|
|
// in this case we restart this activity
|
|
ActivityOnResumeAction action = ActivityOnResumeAction.Resume;
|
|
if (value != null && value.GetType() == typeof(ActivityOnResumeAction))
|
|
action = (ActivityOnResumeAction) value;
|
|
|
|
// All activity level fanout tasks/commands might have finished before the process crash for the remoting activity
|
|
// Check activity restart is not required.
|
|
var hostParamValues = context.GetExtension<HostParameterDefaults>();
|
|
if (this is PSRemotingActivity && hostParamValues != null && hostParamValues.RemoteActivityState != null)
|
|
{
|
|
// Auto reconnect is enabled for remoting activity with PSPersist value true
|
|
if (InternalBookmarkingRequired(context))
|
|
{
|
|
bool remoteActivityCompleted = !(hostParamValues.RemoteActivityState.RemoteActivityResumeRequired(this, true));
|
|
|
|
if (remoteActivityCompleted == true)
|
|
action = ActivityOnResumeAction.Resume;
|
|
}
|
|
}
|
|
|
|
if (action == ActivityOnResumeAction.Restart)
|
|
{
|
|
this.Execute(context);
|
|
return;
|
|
}
|
|
|
|
// this is expected when there would be a disconnected execution
|
|
// here we expects the PSActivityHostArguments to be passed
|
|
// agrument contains the result from the execution
|
|
PSResumableActivityContext arguments = null;
|
|
if (value != null && value.GetType() == typeof(PSResumableActivityContext))
|
|
{
|
|
arguments = (PSResumableActivityContext)value;
|
|
}
|
|
if (arguments != null)
|
|
{
|
|
HostParameterDefaults hostValues = context.GetExtension<HostParameterDefaults>();
|
|
if (hostValues != null)
|
|
{
|
|
if (arguments.SupportDisconnectedStreams && arguments.Streams != null)
|
|
{
|
|
PopulateSteamsData(arguments, context, hostValues);
|
|
}
|
|
|
|
PSDataCollection<ProgressRecord> psprogress = null;
|
|
if (this.PSProgress.Expression != null)
|
|
{
|
|
psprogress = PSProgress.Get(context);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSProgress"] != null && hostValues.Parameters["PSProgress"].GetType() == typeof(PSDataCollection<ProgressRecord>))
|
|
{
|
|
psprogress = hostValues.Parameters["PSProgress"] as PSDataCollection<ProgressRecord>;
|
|
}
|
|
}
|
|
|
|
// If we got an exception, throw it here.
|
|
if (arguments.Error != null)
|
|
{
|
|
WriteProgressRecord(context, psprogress, Resources.FailedString, ProgressRecordType.Completed);
|
|
|
|
Tracer.WriteMessage("PSActivity", "OnResumeBookmark", context.WorkflowInstanceId,
|
|
@"We are about to rethrow the exception in order to preserve the stack trace writing it into the logs.");
|
|
Tracer.TraceException(arguments.Error);
|
|
Dbg.Assert(String.IsNullOrWhiteSpace(arguments.Error.Message) == false, "Exception to be thrown doesnt have proper error message, throwing this will results in Suspended job state instead of Failed state !");
|
|
throw arguments.Error;
|
|
}
|
|
|
|
// Perform persistence
|
|
this.ActivityEndPersistence(context);
|
|
|
|
|
|
// Write the activity completed progress record
|
|
if (arguments.Failed)
|
|
{
|
|
WriteProgressRecord(context, psprogress, Resources.FailedString, ProgressRecordType.Completed);
|
|
}
|
|
else
|
|
{
|
|
WriteProgressRecord(context, psprogress,Resources.CompletedString, ProgressRecordType.Completed);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// In the most of the cases where the state is null
|
|
PSActivityContext psActivityContextInstance = null;
|
|
PSDataCollection<ProgressRecord> progress = null;
|
|
|
|
try
|
|
{
|
|
if (this.bookmarking.Get(context) == false)
|
|
{
|
|
progress = PSProgress.Get(context);
|
|
psActivityContextInstance = psActivityContextImplementationVariable.Get(context);
|
|
|
|
//TODO: add below block in finally for whole function
|
|
psActivityContextImplementationVariable.Set(context, null);
|
|
|
|
HostParameterDefaults hostValues = context.GetExtension<HostParameterDefaults>();
|
|
if (hostValues != null)
|
|
{
|
|
|
|
if ((hostValues != null) && (hostValues.AsyncExecutionCollection != null))
|
|
{
|
|
Dictionary<string, PSActivityContext> asyncExecutionCollection = null;
|
|
asyncExecutionCollection = hostValues.AsyncExecutionCollection;
|
|
if (asyncExecutionCollection != null)
|
|
{
|
|
if (asyncExecutionCollection.ContainsKey(context.ActivityInstanceId))
|
|
{
|
|
asyncExecutionCollection.Remove(context.ActivityInstanceId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
HostParameterDefaults hostValues = context.GetExtension<HostParameterDefaults>();
|
|
if (hostValues != null)
|
|
{
|
|
|
|
if ((hostValues != null) && (hostValues.AsyncExecutionCollection != null))
|
|
{
|
|
Dictionary<string, PSActivityContext> asyncExecutionCollection = null;
|
|
asyncExecutionCollection = hostValues.AsyncExecutionCollection;
|
|
if (asyncExecutionCollection != null)
|
|
{
|
|
if (asyncExecutionCollection.ContainsKey(context.ActivityInstanceId))
|
|
{
|
|
psActivityContextInstance = asyncExecutionCollection[context.ActivityInstanceId];
|
|
asyncExecutionCollection.Remove(context.ActivityInstanceId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (psActivityContextInstance != null)
|
|
{
|
|
progress = psActivityContextInstance.progress;
|
|
|
|
if (this.Result.Expression != null)
|
|
{
|
|
this.Result.Set(context, psActivityContextInstance.Output);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Kill the MaxRunningTime timer
|
|
var runningCancelTimerActivityInstance = psRunningTimeoutDelayActivityInstanceVar.Get(context);
|
|
if (runningCancelTimerActivityInstance != null)
|
|
{
|
|
psRunningTimeoutDelayActivityInstanceVar.Set(context, null);
|
|
context.CancelChild(runningCancelTimerActivityInstance);
|
|
}
|
|
|
|
if (psActivityContextInstance != null)
|
|
{
|
|
// If we got an exception, throw it here.
|
|
if (psActivityContextInstance.exceptions.Count > 0)
|
|
{
|
|
WriteProgressRecord(context, progress, Resources.FailedString, ProgressRecordType.Completed);
|
|
|
|
Tracer.WriteMessage("PSActivity", "OnResumeBookmark", context.WorkflowInstanceId,
|
|
@"We are about to rethrow the exception in order to preserve the stack trace writing it into the logs.");
|
|
Tracer.TraceException(psActivityContextInstance.exceptions[0]);
|
|
|
|
Dbg.Assert(String.IsNullOrWhiteSpace(psActivityContextInstance.exceptions[0].Message) == false, "Exception to be thrown doesnt have proper error message, throwing this will results in Suspended job state instead of Failed state !");
|
|
throw psActivityContextInstance.exceptions[0];
|
|
}
|
|
}
|
|
|
|
// Perform persistence
|
|
this.ActivityEndPersistence(context);
|
|
|
|
|
|
// Write the activity completed progress record
|
|
if (psActivityContextInstance != null && psActivityContextInstance.Failed)
|
|
{
|
|
WriteProgressRecord(context, progress, Resources.FailedString, ProgressRecordType.Completed);
|
|
}
|
|
else
|
|
{
|
|
WriteProgressRecord(context, progress, Resources.CompletedString, ProgressRecordType.Completed);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (psActivityContextInstance != null)
|
|
{
|
|
// If we had an error to suspend on, schedule the suspend activity
|
|
if (psActivityContextInstance.SuspendOnError)
|
|
{
|
|
context.ScheduleActivity(this.suspendActivity);
|
|
}
|
|
|
|
psActivityContextInstance.Dispose();
|
|
psActivityContextInstance = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The method is override-able by the drived classes in case they would like to implement different logic at the end of persistence.
|
|
/// The default behavior would be to schedule the 'Persist' activity if the PSPersist flag is true or Host is asking for it.
|
|
/// </summary>
|
|
/// <param name="context">The native activity context of execution engine.</param>
|
|
protected virtual void ActivityEndPersistence(NativeActivityContext context)
|
|
{
|
|
bool? activityPersist = GetActivityPersistFlag(context);
|
|
|
|
bool? HostPSPersistVariable = null;
|
|
if (this.PSPersist.Expression == null && this.PSPersist.Get(context).HasValue)
|
|
{
|
|
HostPSPersistVariable = this.PSPersist.Get(context).Value;
|
|
}
|
|
|
|
bool hostPersist = false;
|
|
hostPersist = this.GetHostPersistFlag(context);
|
|
|
|
// Schedule Persist activity if the preference variable was true, the activity explicitly requests it
|
|
// or the host has requested persistence.
|
|
if (((activityPersist == null) && (HostPSPersistVariable == true)) || hostPersist == true || activityPersist == true)
|
|
{
|
|
string bookmarkname = PSActivity.PSPersistBookmarkPrefix + Guid.NewGuid().ToString().Replace("-", "_");
|
|
context.CreateBookmark(bookmarkname, BookmarkResumed);
|
|
}
|
|
}
|
|
private void BookmarkResumed(NativeActivityContext context, Bookmark bookmark, object value)
|
|
{
|
|
}
|
|
|
|
|
|
private bool GetHostPersistFlag(NativeActivityContext context)
|
|
{
|
|
Func<bool> hostDelegate = null;
|
|
|
|
HostParameterDefaults hostValues = context.GetExtension<HostParameterDefaults>();
|
|
|
|
if (hostValues != null)
|
|
{
|
|
if ((hostValues != null) && (hostValues.HostPersistenceDelegate != null))
|
|
{
|
|
hostDelegate = hostValues.HostPersistenceDelegate;
|
|
}
|
|
}
|
|
|
|
if (hostDelegate == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool value = hostDelegate();
|
|
|
|
return value;
|
|
}
|
|
private bool? GetActivityPersistFlag(NativeActivityContext context)
|
|
{
|
|
bool? activityPersist = null;
|
|
if (this.PSPersist.Expression != null && this.PSPersist.Get(context).HasValue)
|
|
{
|
|
activityPersist = this.PSPersist.Get(context).Value;
|
|
}
|
|
else
|
|
{
|
|
// Look to see if there is a PSPersistPreference variable in scope...
|
|
foreach (System.ComponentModel.PropertyDescriptor property in context.DataContext.GetProperties())
|
|
{
|
|
if (string.Equals(property.DisplayName, WorkflowPreferenceVariables.PSPersistPreference, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
object variableValue = property.GetValue(context.DataContext);
|
|
if (variableValue != null)
|
|
{
|
|
bool persist;
|
|
if (LanguagePrimitives.TryConvertTo<bool>(variableValue, CultureInfo.InvariantCulture, out persist))
|
|
{
|
|
activityPersist = persist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if activity level PSPersist value OR $PSPersistPreference are not found try to get the PSPersist value specified at workflow level
|
|
if (activityPersist == null)
|
|
{
|
|
HostParameterDefaults hostExtension = context.GetExtension<HostParameterDefaults>();
|
|
if (hostExtension != null)
|
|
{
|
|
Dictionary<string, object> incomingArguments = hostExtension.Parameters;
|
|
if (incomingArguments.ContainsKey(Constants.Persist))
|
|
{
|
|
activityPersist = (bool)incomingArguments[Constants.Persist];
|
|
}
|
|
}
|
|
}
|
|
return activityPersist;
|
|
}
|
|
|
|
private void PopulateSteamsData(PSResumableActivityContext arguments, NativeActivityContext context, HostParameterDefaults hostValues)
|
|
{
|
|
// setting the output from arguments
|
|
if (arguments.Streams.OutputStream != null)
|
|
{
|
|
if (this.Result.Expression != null)
|
|
{
|
|
this.Result.Set(context, arguments.Streams.OutputStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["Result"] != null && hostValues.Parameters["Result"].GetType() == typeof(PSDataCollection<PSObject>))
|
|
{
|
|
PSDataCollection<PSObject> output = hostValues.Parameters["Result"] as PSDataCollection<PSObject>;
|
|
if (output != arguments.Streams.OutputStream && output != null && output.IsOpen)
|
|
{
|
|
foreach (PSObject obj in arguments.Streams.OutputStream)
|
|
{
|
|
output.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// setting the input from the arguments
|
|
if (arguments.Streams.InputStream != null)
|
|
{
|
|
if (this.Input.Expression != null)
|
|
{
|
|
this.Input.Set(context, arguments.Streams.InputStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["Input"] != null && hostValues.Parameters["Input"].GetType() == typeof(PSDataCollection<PSObject>))
|
|
{
|
|
hostValues.Parameters["Input"] = arguments.Streams.InputStream;
|
|
}
|
|
}
|
|
}
|
|
|
|
// setting the error from arguments
|
|
if (arguments.Streams.ErrorStream != null)
|
|
{
|
|
if (this.PSError.Expression != null)
|
|
{
|
|
this.PSError.Set(context, arguments.Streams.ErrorStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSError"] != null && hostValues.Parameters["PSError"].GetType() == typeof(PSDataCollection<ErrorRecord>))
|
|
{
|
|
PSDataCollection<ErrorRecord> error = hostValues.Parameters["PSError"] as PSDataCollection<ErrorRecord>;
|
|
if (error != arguments.Streams.ErrorStream && error != null && error.IsOpen)
|
|
{
|
|
foreach (ErrorRecord obj in arguments.Streams.ErrorStream)
|
|
{
|
|
error.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// setting the warning from arguments
|
|
if (arguments.Streams.WarningStream != null)
|
|
{
|
|
if (this.PSWarning.Expression != null)
|
|
{
|
|
this.PSWarning.Set(context, arguments.Streams.WarningStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSWarning"] != null && hostValues.Parameters["PSWarning"].GetType() == typeof(PSDataCollection<WarningRecord>))
|
|
{
|
|
PSDataCollection<WarningRecord> warning = hostValues.Parameters["PSWarning"] as PSDataCollection<WarningRecord>;
|
|
if (warning != arguments.Streams.WarningStream && warning != null && warning.IsOpen)
|
|
{
|
|
foreach (WarningRecord obj in arguments.Streams.WarningStream)
|
|
{
|
|
warning.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// setting the progress from arguments
|
|
if (arguments.Streams.ProgressStream != null)
|
|
{
|
|
if (this.PSProgress.Expression != null)
|
|
{
|
|
this.PSProgress.Set(context, arguments.Streams.ProgressStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSProgress"] != null && hostValues.Parameters["PSProgress"].GetType() == typeof(PSDataCollection<ProgressRecord>))
|
|
{
|
|
PSDataCollection<ProgressRecord> tmpProgress = hostValues.Parameters["PSProgress"] as PSDataCollection<ProgressRecord>;
|
|
if (tmpProgress != arguments.Streams.ProgressStream && tmpProgress != null && tmpProgress.IsOpen)
|
|
{
|
|
foreach (ProgressRecord obj in arguments.Streams.ProgressStream)
|
|
{
|
|
tmpProgress.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// setting the verbose from arguments
|
|
if (arguments.Streams.VerboseStream != null)
|
|
{
|
|
if (this.PSVerbose.Expression != null)
|
|
{
|
|
this.PSVerbose.Set(context, arguments.Streams.VerboseStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSVerbose"] != null && hostValues.Parameters["PSVerbose"].GetType() == typeof(PSDataCollection<VerboseRecord>))
|
|
{
|
|
PSDataCollection<VerboseRecord> verbose = hostValues.Parameters["PSVerbose"] as PSDataCollection<VerboseRecord>;
|
|
if (verbose != arguments.Streams.VerboseStream && verbose != null && verbose.IsOpen)
|
|
{
|
|
foreach (VerboseRecord obj in arguments.Streams.VerboseStream)
|
|
{
|
|
verbose.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// setting the debug from arguments
|
|
if (arguments.Streams.DebugStream != null)
|
|
{
|
|
if (this.PSDebug.Expression != null)
|
|
{
|
|
this.PSDebug.Set(context, arguments.Streams.DebugStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSDebug"] != null && hostValues.Parameters["PSDebug"].GetType() == typeof(PSDataCollection<DebugRecord>))
|
|
{
|
|
PSDataCollection<DebugRecord> debug = hostValues.Parameters["PSDebug"] as PSDataCollection<DebugRecord>;
|
|
if (debug != arguments.Streams.DebugStream && debug != null && debug.IsOpen)
|
|
{
|
|
foreach (DebugRecord obj in arguments.Streams.DebugStream)
|
|
{
|
|
debug.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// setting the information stream from arguments
|
|
if (arguments.Streams.InformationStream != null)
|
|
{
|
|
if (this.PSInformation.Expression != null)
|
|
{
|
|
this.PSInformation.Set(context, arguments.Streams.InformationStream);
|
|
}
|
|
else
|
|
{
|
|
if (hostValues.Parameters["PSInformation"] != null && hostValues.Parameters["PSInformation"].GetType() == typeof(PSDataCollection<InformationRecord>))
|
|
{
|
|
PSDataCollection<InformationRecord> information = hostValues.Parameters["PSInformation"] as PSDataCollection<InformationRecord>;
|
|
if (information != arguments.Streams.InformationStream && information != null && information.IsOpen)
|
|
{
|
|
foreach (InformationRecord obj in arguments.Streams.InformationStream)
|
|
{
|
|
information.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the runspace with user variables from the current context
|
|
/// </summary>
|
|
/// <param name="context"></param>
|
|
/// <param name="activityContext"></param>
|
|
/// <param name="implementationContext"></param>
|
|
private void PopulateRunspaceFromContext(ActivityImplementationContext implementationContext, PSActivityContext activityContext, NativeActivityContext context)
|
|
{
|
|
System.Diagnostics.Debug.Assert(implementationContext != null, "Implementation context cannot be null");
|
|
|
|
if (implementationContext.PowerShellInstance != null)
|
|
{
|
|
// Then, set the variables from the workflow
|
|
SetActivityVariables(context, activityContext.UserVariables);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the required variables to set in runspace
|
|
/// </summary>
|
|
/// <param name="context"></param>
|
|
/// <param name="activityVariables"></param>
|
|
internal void SetActivityVariables(NativeActivityContext context, Dictionary<string, object> activityVariables)
|
|
{
|
|
Dictionary<string, object> defaults = this.ParameterDefaults.Get(context);
|
|
|
|
string[] streams =
|
|
{
|
|
"Result", "PSError", "PSWarning", "PSVerbose", "PSDebug", "PSProgress", "PSInformation"
|
|
};
|
|
|
|
// First, set the variables from the user's variables
|
|
foreach (System.ComponentModel.PropertyDescriptor property in context.DataContext.GetProperties())
|
|
{
|
|
if (String.Equals(property.Name, "ParameterDefaults", StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
Object value = property.GetValue(context.DataContext);
|
|
if (value != null)
|
|
{
|
|
object tempValue = value;
|
|
|
|
PSDataCollection<PSObject> collectionObject = value as PSDataCollection<PSObject>;
|
|
|
|
if (collectionObject != null && collectionObject.Count == 1)
|
|
{
|
|
tempValue = collectionObject[0];
|
|
}
|
|
|
|
activityVariables[property.Name] = tempValue;
|
|
}
|
|
}
|
|
|
|
// Then, set anything we received from parameters
|
|
foreach (PSActivityArgumentInfo currentArgument in GetActivityArguments())
|
|
{
|
|
string @default = currentArgument.Name;
|
|
if (streams.Any(item => string.Equals(item, @default, StringComparison.OrdinalIgnoreCase)))
|
|
continue;
|
|
|
|
object argumentValue = currentArgument.Value.Get(context);
|
|
if (argumentValue != null && !activityVariables.ContainsKey(currentArgument.Name))
|
|
{
|
|
activityVariables[currentArgument.Name] = argumentValue;
|
|
}
|
|
}
|
|
|
|
// Then, set the variables from the host defaults
|
|
if (defaults != null)
|
|
{
|
|
foreach (string hostDefault in defaults.Keys)
|
|
{
|
|
string @default = hostDefault;
|
|
if (streams.Any(item => string.Equals(item, @default, StringComparison.OrdinalIgnoreCase)))
|
|
continue;
|
|
|
|
object propertyValue = defaults[hostDefault];
|
|
if (propertyValue != null && !activityVariables.ContainsKey(hostDefault))
|
|
{
|
|
activityVariables[hostDefault] = propertyValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a parameter from the defaults supplied by the workflow host
|
|
/// </summary>
|
|
/// <param name="argument">The argument to modify (i.e.: Input, Result, ComputerName, etc)</param>
|
|
/// <param name="context">The activity context to use</param>
|
|
/// <param name="argumentName">The name of the argument</param>
|
|
/// <param name="parameterDefaults">The parameter defaults</param>
|
|
private void PopulateParameterFromDefault(Argument argument, NativeActivityContext context, string argumentName, Dictionary<string, object> parameterDefaults)
|
|
{
|
|
// See if they haven't specified a value
|
|
if ((argument != null) &&
|
|
(argument.Expression == null) &&
|
|
(argument.Direction != ArgumentDirection.Out))
|
|
{
|
|
// See if we have a parameter defaults collection
|
|
if (ParameterDefaults != null)
|
|
{
|
|
// See if it has this parameter
|
|
if (parameterDefaults.ContainsKey(argumentName))
|
|
{
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Using default {1} value.", context.ActivityInstanceId, argumentName));
|
|
Object parameterDefault = parameterDefaults[argumentName];
|
|
|
|
// Map any switch parameters to booleans if required
|
|
if ((argument.ArgumentType == typeof(Boolean)) ||
|
|
(argument.ArgumentType == typeof(Nullable<Boolean>)))
|
|
{
|
|
if (parameterDefault is SwitchParameter)
|
|
{
|
|
parameterDefault = ((SwitchParameter) parameterDefault).ToBool();
|
|
}
|
|
}
|
|
|
|
// If the argument type is nullable, but the provided default
|
|
// is a non-nullable version, create a nullable version of it
|
|
// to set.
|
|
if ((argument.ArgumentType.IsGenericType) &&
|
|
(argument.ArgumentType.GetGenericTypeDefinition() == typeof(Nullable<>)) &&
|
|
(!(parameterDefault is Nullable)))
|
|
{
|
|
parameterDefault = LanguagePrimitives.ConvertTo(parameterDefault,
|
|
argument.ArgumentType,
|
|
CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
if (argument.ArgumentType.IsAssignableFrom(typeof(PSCredential)) &&
|
|
parameterDefault.GetType().IsAssignableFrom(typeof(PSObject)))
|
|
{
|
|
parameterDefault = LanguagePrimitives.ConvertTo(parameterDefault,
|
|
typeof(PSCredential), CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
argument.Set(context, parameterDefault);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Populates the activity task variables from the current PSActivity instance
|
|
private void PopulateActivityImplementationContext(ActivityImplementationContext implementationContext, NativeActivityContext context, int index)
|
|
{
|
|
// Go through all of the public PSActivity arguments, and populate their value
|
|
// into the ActivityImplementationContext.
|
|
foreach (PSActivityArgumentInfo field in GetActivityArguments())
|
|
{
|
|
// Get the field of the same name from the 'ActivityImplementationContext' class
|
|
PropertyInfo implementationContextField = implementationContext.GetType().GetProperty(field.Name);
|
|
if (implementationContextField == null)
|
|
{
|
|
// If you have added a new argument then please added it to 'ActivityImplementationContext' classs.
|
|
throw new Exception("Could not find corresponding task context field for activity argument: " + field.Name);
|
|
}
|
|
|
|
if (string.Equals(field.Name, PSComputerName, StringComparison.OrdinalIgnoreCase) && index != -1)
|
|
{
|
|
// set only the corresponding entry for computername
|
|
PopulatePSComputerName(implementationContext, context, field, index);
|
|
}
|
|
else
|
|
{
|
|
// And set it in the task context
|
|
implementationContextField.SetValue(implementationContext, field.Value.Get(context), null);
|
|
}
|
|
}
|
|
}
|
|
|
|
private const string PSComputerName = "PSComputerName";
|
|
private static void PopulatePSComputerName(ActivityImplementationContext implementationContext, NativeActivityContext context, PSActivityArgumentInfo field, int index)
|
|
{
|
|
Dbg.Assert(string.Equals(field.Name, PSComputerName, StringComparison.OrdinalIgnoreCase), "PSActivityArgumentInfo passed should be for PSComputerName");
|
|
|
|
PropertyInfo computerNameField = implementationContext.GetType().GetProperty(field.Name);
|
|
string[] computerNames = (string[]) field.Value.Get(context);
|
|
|
|
computerNameField.SetValue(implementationContext, new string[]{computerNames[index]}, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overload this method to implement any command-type specific preparations.
|
|
/// If this command needs any workflow-specific information during its PrepareSession call,
|
|
/// it should be stored in ActivityImplementationContext.WorkflowContext.
|
|
/// </summary>
|
|
/// <param name="context">The activity context to use</param>
|
|
protected virtual List<ActivityImplementationContext> GetImplementation(NativeActivityContext context)
|
|
{
|
|
ActivityImplementationContext implementationContext = GetPowerShell(context);
|
|
UpdateImplementationContextForLocalExecution(implementationContext, context);
|
|
|
|
return new List<ActivityImplementationContext>() { implementationContext };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the ImplementationContext returned from GetPowerShell() to support local execution
|
|
/// against the host's runspace pool.
|
|
/// </summary>
|
|
/// <param name="implementationContext">The implementation context returned by GetPowerShell()</param>
|
|
/// <param name="context">The activity context to use</param>
|
|
protected internal void UpdateImplementationContextForLocalExecution(ActivityImplementationContext implementationContext, ActivityContext context)
|
|
{
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The method for derived activities to return a configured instance of System.Management.Automation.PowerShell.
|
|
/// The implementor should have added all of the commands and parameters required to launch their command through
|
|
/// the standard AddCommand() and AddParameter() methods. Derived activites should not manage the Runspace property
|
|
/// directly, as the PSActivity class configures the runspace afterward to enable remote connections.
|
|
/// </summary>
|
|
/// <param name="context">The NativeActivityContext for the currently running activity.</param>
|
|
/// <returns>A populated instance of Sytem.Management.Automation.PowerShell</returns>
|
|
/// <remarks>The infrastructure takes responsibility for closing and disposing the PowerShell instance returned.</remarks>
|
|
protected abstract ActivityImplementationContext GetPowerShell(NativeActivityContext context);
|
|
|
|
|
|
/// <summary>
|
|
/// The method for derived activities to customize the runspace of a System.Management.Automation.PowerShell instance
|
|
/// that the runtime has prepared for them.
|
|
/// If the command needs any workflow-specific information during this PrepareSession call,
|
|
/// it should be stored in ActivityImplementationContext.WorkflowContext during the GetCommand preparation phase.
|
|
/// </summary>
|
|
/// <param name="implementationContext">The ActivityImplementationContext returned by the call to GetCommand.</param>
|
|
protected virtual void PrepareSession(ActivityImplementationContext implementationContext)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// If an activity needs to load a module before it can execute, override this member
|
|
/// to return the name of that module.
|
|
/// </summary>
|
|
protected string DefiningModule
|
|
{
|
|
get
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrievs all of the default arguments from the type and its parents.
|
|
/// </summary>
|
|
/// <returns>All of the default arguments from the type and its parents</returns>
|
|
protected IEnumerable<PSActivityArgumentInfo> GetActivityArguments()
|
|
{
|
|
// Walk up the type hierarchy, looking for types that we should pull in
|
|
// parameter defaults.
|
|
Type currentType = this.GetType();
|
|
|
|
while (currentType != null)
|
|
{
|
|
// We don't want to support parameter defaults for arguments on
|
|
// concrete types (as they almost guaranteed to collide with other types),
|
|
// but base classes make sense.
|
|
if (currentType.IsAbstract)
|
|
{
|
|
// Populate any parameter defaults. We only look at fields that are defined on this
|
|
// specific type (as opposed to derived types) so that we don't make assumptions about
|
|
// other activities and their defaults.
|
|
foreach (PropertyInfo field in currentType.GetProperties())
|
|
{
|
|
// See if it's an argument
|
|
if (typeof(Argument).IsAssignableFrom(field.PropertyType))
|
|
{
|
|
// Get the argument
|
|
Argument currentArgument = (Argument)field.GetValue(this, null);
|
|
yield return new PSActivityArgumentInfo { Name = field.Name, Value = currentArgument };
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go to our base type, but stop when we go above PSActivity
|
|
currentType = currentType.BaseType;
|
|
if (!typeof(PSActivity).IsAssignableFrom(currentType))
|
|
currentType = null;
|
|
}
|
|
}
|
|
|
|
// Get the tasks from the derived activity, and save the state we require.
|
|
private List<ActivityImplementationContext> GetTasks(NativeActivityContext context)
|
|
{
|
|
List<ActivityImplementationContext> tasks = GetImplementation(context);
|
|
|
|
if (IsActivityInlineScript(this))
|
|
{
|
|
// in case of inlinescript activity supporting custom remoting we need
|
|
// to ensure that PSComputerName is populated only for the specified
|
|
// index
|
|
if (RunWithCustomRemoting(context))
|
|
{
|
|
for(int i=0; i<tasks.Count;i++)
|
|
{
|
|
PopulateActivityImplementationContext(tasks[i], context, i);
|
|
}
|
|
return tasks;
|
|
}
|
|
}
|
|
|
|
foreach (ActivityImplementationContext task in tasks)
|
|
{
|
|
PopulateActivityImplementationContext(task, context, -1);
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates if preference variables need to be updated
|
|
/// </summary>
|
|
protected virtual bool UpdatePreferenceVariable
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
// Common configuration of a SMA.PowerShell instance.
|
|
/// <summary>
|
|
/// Add the other streams and enqueue the command to be run
|
|
/// </summary>
|
|
/// <param name="implementationContext">The activity context to use</param>
|
|
/// <param name="psActivityContext">The powershell activity context.</param>
|
|
/// <param name="ActivityType">The activity type.</param>
|
|
/// <param name="PrepareSession">The prepare session delgate.</param>
|
|
/// <param name="activityObject">This object representing activity.</param>
|
|
private static void UpdatePowerShell(ActivityImplementationContext implementationContext, PSActivityContext psActivityContext, Type ActivityType, PrepareSessionDelegate PrepareSession, object activityObject)
|
|
{
|
|
try
|
|
{
|
|
PrepareSession(implementationContext);
|
|
System.Management.Automation.PowerShell invoker = implementationContext.PowerShellInstance;
|
|
|
|
// Prepare the streams
|
|
if (implementationContext.PSError != null)
|
|
{
|
|
invoker.Streams.Error = implementationContext.PSError;
|
|
}
|
|
|
|
if (implementationContext.PSProgress != null)
|
|
{
|
|
invoker.Streams.Progress = implementationContext.PSProgress;
|
|
}
|
|
|
|
if (implementationContext.PSVerbose != null)
|
|
{
|
|
invoker.Streams.Verbose = implementationContext.PSVerbose;
|
|
}
|
|
|
|
if (implementationContext.PSDebug != null)
|
|
{
|
|
invoker.Streams.Debug = implementationContext.PSDebug;
|
|
}
|
|
|
|
if (implementationContext.PSWarning != null)
|
|
{
|
|
invoker.Streams.Warning = implementationContext.PSWarning;
|
|
}
|
|
|
|
if (implementationContext.PSInformation != null)
|
|
{
|
|
invoker.Streams.Information = implementationContext.PSInformation;
|
|
}
|
|
|
|
// InlineScript activity needs to handle these in its own way
|
|
PSActivity activityBase = activityObject as PSActivity;
|
|
Dbg.Assert(activityBase != null, "Only activities derived from PSActivityBase are supported");
|
|
if (activityBase.UpdatePreferenceVariable)
|
|
{
|
|
UpdatePreferenceVariables(implementationContext);
|
|
}
|
|
|
|
//OnActivityCreated(activityObject, new ActivityCreatedEventArgs(invoker));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Catch all exceptions and add them to the exception list.
|
|
// This way, they will be reported on EndExecute(), rather than
|
|
// killing the process if an exception happens on the background thread.
|
|
lock (psActivityContext.exceptions)
|
|
{
|
|
psActivityContext.exceptions.Add(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void UpdatePreferenceVariables(ActivityImplementationContext implementationContext)
|
|
{
|
|
// Update the PowerShell ubiquitous parameters
|
|
Command activityCommand = implementationContext.PowerShellInstance.Commands.Commands[0];
|
|
|
|
if (implementationContext.Verbose != null)
|
|
{
|
|
activityCommand.Parameters.Add("Verbose", implementationContext.Verbose);
|
|
}
|
|
|
|
if (implementationContext.Debug != null)
|
|
{
|
|
activityCommand.Parameters.Add("Debug", implementationContext.Debug);
|
|
}
|
|
|
|
if (implementationContext.WhatIf != null)
|
|
{
|
|
activityCommand.Parameters.Add("WhatIf", implementationContext.WhatIf);
|
|
}
|
|
|
|
if (implementationContext.ErrorAction != null)
|
|
{
|
|
activityCommand.Parameters.Add("ErrorAction", implementationContext.ErrorAction);
|
|
}
|
|
|
|
if (implementationContext.WarningAction != null)
|
|
{
|
|
activityCommand.Parameters.Add("WarningAction", implementationContext.WarningAction);
|
|
}
|
|
|
|
if (implementationContext.InformationAction != null)
|
|
{
|
|
activityCommand.Parameters.Add("InformationAction", implementationContext.InformationAction);
|
|
}
|
|
}
|
|
|
|
internal const int CommandRunInProc = 0;
|
|
internal const int RunInProcNoRunspace = 1;
|
|
internal const int CommandRunOutOfProc = 2;
|
|
internal const int CommandRunRemotely = 3;
|
|
internal const int CimCommandRunInProc = 4;
|
|
internal const int CleanupActivity = 5;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
/// <remarks>THREADING CONTRACT: This function is designed to be
|
|
/// lightweight and to run on the Workflow thread when Execute()
|
|
/// is called. When any changes are made to this function the contract
|
|
/// needs to be honored</remarks>
|
|
internal static void BeginRunOneCommand(RunCommandsArguments args)
|
|
{
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
actionTracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"Begining action to run command {0}.", commandToRun));
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
// initialize for command execution based on type
|
|
InitializeOneCommand(args);
|
|
|
|
// run the command based on type
|
|
if (args.CommandExecutionType != CommandRunRemotely &&
|
|
args.CommandExecutionType != CommandRunInProc &&
|
|
args.CommandExecutionType != CimCommandRunInProc)
|
|
{
|
|
// when the command is run on a remote runspace it is
|
|
// executed on the callback thread from connection
|
|
// manager
|
|
BeginExecuteOneCommand(args);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize a single command for execution.
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
/// <remarks>THREADING CONTRACT: This function is designed to be
|
|
/// lightweight and to run on the Workflow thread when Execute()
|
|
/// is called. When any changes are made to this function the
|
|
/// contract needs to be honored</remarks>
|
|
private static void InitializeOneCommand(RunCommandsArguments args)
|
|
{
|
|
ActivityParameters activityParameters = args.ActivityParameters;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
Dictionary<string, object> parameterDefaults = args.ParameterDefaults;
|
|
Type activityType = args.ActivityType;
|
|
PrepareSessionDelegate prepareSession = args.Delegate;
|
|
object activityObject = args.ActivityObject;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
int commandExecutionType = args.CommandExecutionType;
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
actionTracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"Beginning initialization for command '{0}'.", commandToRun));
|
|
//
|
|
// Common Initialization
|
|
//
|
|
|
|
// Store which command we're running. We need to lock this one
|
|
// because Dictionary<T,T> is not thread-safe (while ConcurrentQueue is,
|
|
// by definition.)
|
|
lock (psActivityContext.runningCommands)
|
|
{
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
if (!psActivityContext.runningCommands.ContainsKey(commandToRun))
|
|
psActivityContext.runningCommands[commandToRun] = new RetryCount();
|
|
}
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
// Record the invocation attempt
|
|
psActivityContext.runningCommands[commandToRun].ActionAttempts++;
|
|
|
|
// NOTE: previously UpdatePowerShell was called after the runspace
|
|
// for the command was open and available. An assumption is made
|
|
// that this is not required and hence the code is moved before
|
|
// the runspace is available. If there are any issues found flip
|
|
// the order back
|
|
|
|
// Let the activity prepare the PowerShell instance.
|
|
if (commandExecutionType != CleanupActivity)
|
|
UpdatePowerShell(implementationContext, psActivityContext, activityType, prepareSession,
|
|
activityObject);
|
|
|
|
//
|
|
// Initialization based on type
|
|
//
|
|
switch (commandExecutionType)
|
|
{
|
|
case CommandRunInProc:
|
|
{
|
|
// good to run on current thread
|
|
InitializeActivityEnvironmentAndAddRequiredModules(implementationContext, activityParameters);
|
|
workflowHost.LocalRunspaceProvider.BeginGetRunspace(null, 0, 0,
|
|
LocalRunspaceProviderCallback, args);
|
|
}
|
|
break;
|
|
|
|
case RunInProcNoRunspace:
|
|
{
|
|
|
|
// These commands don't have a runspace
|
|
// so we don't need to do anything here
|
|
}
|
|
break;
|
|
|
|
case CimCommandRunInProc:
|
|
{
|
|
workflowHost.LocalRunspaceProvider.BeginGetRunspace(null, 0, 0,
|
|
LocalRunspaceProviderCallback, args);
|
|
}
|
|
break;
|
|
|
|
case CommandRunOutOfProc:
|
|
{
|
|
// out of proc commands do not require the runspace in the
|
|
// powershell. However since this runspace was created by
|
|
// the local runspace provider it need to be released and
|
|
// not disposed We need to release the connection
|
|
// before calling into activity host manager so as to not
|
|
// risk closing an out of process runspace if
|
|
// things happen too quickly
|
|
|
|
InitializeActivityEnvironmentAndAddRequiredModules(implementationContext, activityParameters);
|
|
|
|
}
|
|
break;
|
|
case CommandRunRemotely:
|
|
{
|
|
// when a command is run remotely, the connection manager
|
|
// assigns a remote runspace. Dispose the existing one
|
|
// We should not set the runspace to null as it required in
|
|
// the callback to retrieve connection info in case there
|
|
// is a connection failure. We need to close the connection
|
|
// before calling into connection manager so as to not
|
|
// risk closing a connection manager assigned runspace if
|
|
// things happen too quickly
|
|
DisposeRunspaceInPowerShell(commandToRun, false);
|
|
|
|
InitializeActivityEnvironmentAndAddRequiredModules(implementationContext, activityParameters);
|
|
|
|
// can block - should be run on a different thread
|
|
WSManConnectionInfo connectionInfo =
|
|
commandToRun.Runspace.ConnectionInfo as WSManConnectionInfo;
|
|
workflowHost.RemoteRunspaceProvider.BeginGetRunspace(connectionInfo,
|
|
activityParameters.ConnectionRetryCount.
|
|
GetValueOrDefault(0),
|
|
activityParameters.ConnectionRetryInterval.
|
|
GetValueOrDefault(0),
|
|
ConnectionManagerCallback,
|
|
args);
|
|
|
|
}
|
|
break;
|
|
|
|
case CleanupActivity:
|
|
{
|
|
// no initialization required
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InitializeActivityEnvironmentAndAddRequiredModules(ActivityImplementationContext implementationContext,
|
|
ActivityParameters activityParameters)
|
|
{
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
if (implementationContext.PSActivityEnvironment == null)
|
|
implementationContext.PSActivityEnvironment = new PSActivityEnvironment();
|
|
|
|
PSActivityEnvironment policy = implementationContext.PSActivityEnvironment;
|
|
|
|
foreach (string module in activityParameters.PSRequiredModules ?? new string[0])
|
|
{
|
|
actionTracer.WriteMessage("Adding dependent module to policy: " + module);
|
|
policy.Modules.Add(module);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DisposeRunspaceInPowerShell(System.Management.Automation.PowerShell commandToRun, bool setToNull=true)
|
|
{
|
|
Dbg.Assert(commandToRun.Runspace != null, "Method cannot be called when runspace is null");
|
|
commandToRun.Runspace.Dispose();
|
|
commandToRun.Runspace.Close();
|
|
if (setToNull)
|
|
commandToRun.Runspace = null;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This function runs on the workflow thread when Execute()
|
|
/// is called for all cases except the remote runspace case
|
|
/// where it runs on a WinRM thread or the connection manager
|
|
/// servicing thread
|
|
/// Therefore this function is designed to be lightweight in
|
|
/// all cases
|
|
/// When any changes are made to this function the contract needs to
|
|
/// be honored</remarks>
|
|
private static void BeginExecuteOneCommand(RunCommandsArguments args)
|
|
{
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
PSDataCollection<PSObject> input = args.Input;
|
|
PSDataCollection<PSObject> output = args.Output;
|
|
int commandExecutionType = args.CommandExecutionType;
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
actionTracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"BEGIN BeginExecuteOneCommand {0}.", commandToRun));
|
|
|
|
switch (commandExecutionType)
|
|
{
|
|
case RunInProcNoRunspace:
|
|
{
|
|
// execute on threadpool thread
|
|
ThreadPool.QueueUserWorkItem(ExecuteOneRunspaceFreeCommandWorker, args);
|
|
}
|
|
break;
|
|
|
|
case CimCommandRunInProc:
|
|
{
|
|
// execute on threadpool thread
|
|
ThreadPool.QueueUserWorkItem(InitializeRunspaceAndExecuteCommandWorker, args);
|
|
}
|
|
break;
|
|
|
|
case CommandRunOutOfProc:
|
|
{
|
|
Dbg.Assert(implementationContext.PSActivityEnvironment != null,
|
|
"Policy should have been initialized correctly by the initialization method");
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
PSResumableActivityHostController resumableController = workflowHost.PSActivityHostController as PSResumableActivityHostController;
|
|
if (resumableController != null)
|
|
{
|
|
PowerShellStreams<PSObject, PSObject> streams = new PowerShellStreams<PSObject, PSObject>();
|
|
|
|
if (resumableController.SupportDisconnectedPSStreams)
|
|
{
|
|
streams.InputStream = input;
|
|
streams.OutputStream = new PSDataCollection<PSObject>();
|
|
streams.ErrorStream = new PSDataCollection<ErrorRecord>();
|
|
streams.DebugStream = new PSDataCollection<DebugRecord>();
|
|
streams.ProgressStream = new PSDataCollection<ProgressRecord>();
|
|
streams.VerboseStream = new PSDataCollection<VerboseRecord>();
|
|
streams.WarningStream = new PSDataCollection<WarningRecord>();
|
|
streams.InformationStream = new PSDataCollection<InformationRecord>();
|
|
}
|
|
else
|
|
{
|
|
streams.InputStream = input;
|
|
streams.OutputStream = output;
|
|
streams.DebugStream = commandToRun.Streams.Debug;
|
|
streams.ErrorStream = commandToRun.Streams.Error;
|
|
streams.ProgressStream = commandToRun.Streams.Progress;
|
|
streams.VerboseStream = commandToRun.Streams.Verbose;
|
|
streams.WarningStream = commandToRun.Streams.Warning;
|
|
streams.InformationStream = commandToRun.Streams.Information;
|
|
}
|
|
|
|
resumableController.StartResumablePSCommand(args.PSActivityContext.JobInstanceId,
|
|
(Bookmark)args.PSActivityContext.AsyncState,
|
|
commandToRun,
|
|
streams,
|
|
implementationContext.PSActivityEnvironment,
|
|
(PSActivity)psActivityContext.ActivityObject);
|
|
}
|
|
else
|
|
{
|
|
PSOutOfProcessActivityController delgateController = workflowHost.PSActivityHostController as PSOutOfProcessActivityController;
|
|
if (delgateController != null)
|
|
{
|
|
AddHandlersToStreams(commandToRun, args);
|
|
|
|
IAsyncResult asyncResult =
|
|
delgateController.BeginInvokePowerShell(commandToRun, input,
|
|
output,
|
|
implementationContext.PSActivityEnvironment,
|
|
ActivityHostManagerCallback,
|
|
args);
|
|
psActivityContext.AsyncResults.Enqueue(asyncResult);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CommandRunRemotely:
|
|
{
|
|
ArgsTableForRunspaces.TryAdd(commandToRun.Runspace.InstanceId, args);
|
|
InitializeRunspaceAndExecuteCommandWorker(args);
|
|
}
|
|
break;
|
|
case CommandRunInProc:
|
|
{
|
|
commandToRun.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread;
|
|
ThreadPool.QueueUserWorkItem(InitializeRunspaceAndExecuteCommandWorker, args);
|
|
}
|
|
break;
|
|
case CleanupActivity:
|
|
{
|
|
ExecuteCleanupActivity(args);
|
|
}
|
|
break;
|
|
}
|
|
actionTracer.WriteMessage("END BeginExecuteOneCommand");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls the DoCleanup method of the cleanup activity
|
|
/// </summary>
|
|
/// <param name="args">RunCommandsArguments</param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This function runs on the workflow thread when Execute()
|
|
/// is called
|
|
/// Therefore this function is designed to be lightweight in
|
|
/// all cases
|
|
/// When any changes are made to this function the contract needs to
|
|
/// be honored</remarks>
|
|
private static void ExecuteCleanupActivity(RunCommandsArguments args)
|
|
{
|
|
PSCleanupActivity cleanupActivity = args.ActivityObject as PSCleanupActivity;
|
|
if (cleanupActivity == null)
|
|
{
|
|
throw new ArgumentNullException("args");
|
|
}
|
|
cleanupActivity.DoCleanup(args, CleanupActivityCallback);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Callback when all connections to the specified computer
|
|
/// are closed
|
|
/// </summary>
|
|
/// <param name="state">RunCommandsArguments that the activity
|
|
/// passed int</param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// The callback will happen on a WinRM thread. Hence the
|
|
/// function needs to be lightweight to release the thread
|
|
/// back to WinRM
|
|
/// </remarks>
|
|
private static void CleanupActivityCallback(object state)
|
|
{
|
|
RunCommandsArguments args = state as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "Clean up activity should pass back RunCommandsArguments");
|
|
DecrementRunningCountAndCheckForEnd(args.PSActivityContext);
|
|
}
|
|
|
|
private static void ExecuteOneRunspaceFreeCommandWorker(object state)
|
|
{
|
|
Dbg.Assert(state != null, "State needs to be passed to ExecuteOneWmiCommandWorker");
|
|
RunCommandsArguments args = state as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "RunCommandsArguments not passed correctly to ExecuteOneWmiCommandWorker");
|
|
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
PSDataCollection<PSObject> input = args.Input;
|
|
PSDataCollection<PSObject> output = args.Output;
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
bool attemptRetry = false;
|
|
try
|
|
{
|
|
actionTracer.WriteMessage("Running WMI/CIM generic activity on ThreadPool thread");
|
|
RunDirectExecutionActivity(implementationContext.PowerShellInstance, input, output, psActivityContext, implementationContext);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource();
|
|
tracer.TraceException(e);
|
|
|
|
attemptRetry = HandleRunOneCommandException(args, e);
|
|
if (attemptRetry)
|
|
BeginActionRetry(args);
|
|
}
|
|
finally
|
|
{
|
|
implementationContext.CleanUp();
|
|
|
|
RunOneCommandFinally(args, attemptRetry);
|
|
actionTracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity: Finished running command."));
|
|
// decrement count of running commands
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private static void BeginActionRetry(RunCommandsArguments args)
|
|
{
|
|
Dbg.Assert(args != null, "Arguments not passed correctly to BeginActionRetry");
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
Interlocked.Increment(ref psActivityContext.CommandsRunningCount);
|
|
BeginRunOneCommand(args);
|
|
}
|
|
|
|
private static bool HandleRunOneCommandException(RunCommandsArguments args, Exception e)
|
|
{
|
|
bool attemptRetry = false;
|
|
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
ActivityParameters activityParameters = args.ActivityParameters;
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
actionTracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"Exception handling for command {0}.", commandToRun));
|
|
actionTracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"Got exception running command: {0}.", e.Message));
|
|
|
|
#if(DEBUG)
|
|
// Synchronization code to help tests verify that canceling during error handling doesn't
|
|
// cause unhandled exceptions.
|
|
const string timingTest = "Test.Cancellation.During.Error.Handling";
|
|
if (Environment.GetEnvironmentVariable(timingTest) != null)
|
|
{
|
|
while (!String.Equals(Environment.GetEnvironmentVariable(timingTest),
|
|
"TestReady", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Thread.Sleep(50);
|
|
}
|
|
Environment.SetEnvironmentVariable(timingTest, null);
|
|
}
|
|
Thread.Sleep(100);
|
|
#endif
|
|
|
|
// If we've used more attempts than retries,
|
|
// this is a fatal error. We initialize this to MaxValue in case the activity was canceled,
|
|
// in which case we want no more retries.
|
|
int attempts = Int32.MaxValue;
|
|
|
|
// No need add the exceptions in the list if the activity is already canceled.
|
|
if (!psActivityContext.IsCanceled)
|
|
{
|
|
// if there is a connection failure then Connection manager
|
|
// callback will handle the same. However if we got a connection
|
|
// and it broke while we are preparing we need to attempt a retry
|
|
if (psActivityContext.runningCommands.ContainsKey(commandToRun))
|
|
attempts = psActivityContext.runningCommands[commandToRun].ActionAttempts;
|
|
|
|
attemptRetry = HandleFailure(attempts, activityParameters.ActionRetryCount,
|
|
activityParameters.ActionRetryInterval,
|
|
implementationContext, "ActivityActionFailed", e,
|
|
psActivityContext);
|
|
}
|
|
}
|
|
|
|
return attemptRetry;
|
|
}
|
|
|
|
private static void RunOneCommandFinally(RunCommandsArguments args, bool attemptRetry)
|
|
{
|
|
if (attemptRetry) return;
|
|
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
|
|
// Once we're done, remove this command from the list
|
|
// of running commands.
|
|
lock (psActivityContext.runningCommands)
|
|
{
|
|
psActivityContext.runningCommands.Remove(commandToRun);
|
|
}
|
|
|
|
// Discard the runspace - don't need to do this for activities that don't use a runspace
|
|
if (!psActivityContext.IsCanceled && args.CommandExecutionType != RunInProcNoRunspace)
|
|
{
|
|
CloseRunspaceAndDisposeCommand(commandToRun, workflowHost, psActivityContext, args.CommandExecutionType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Callback from connection manager
|
|
/// </summary>
|
|
/// <param name="asyncResult"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// The callback happens either in a WinRM thread or in the
|
|
/// connection manager servicing thread. Therefore any
|
|
/// operations that this thread initiates is supposed to
|
|
/// be very small. Make sure that this contract is maintained
|
|
/// when any changes are made to the function</remarks>
|
|
private static void ConnectionManagerCallback(IAsyncResult asyncResult)
|
|
{
|
|
object asyncState = asyncResult.AsyncState;
|
|
Dbg.Assert(asyncState != null, "AsyncState not returned correctly by connection manager");
|
|
RunCommandsArguments args = asyncState as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "AsyncState casting to RunCommandsArguments failed");
|
|
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
WSManConnectionInfo connectionInfo = commandToRun.Runspace.ConnectionInfo as WSManConnectionInfo;
|
|
Dbg.Assert(connectionInfo != null, "ConnectionInfo cannot be null");
|
|
string[] psComputerName = implementationContext.PSComputerName;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback for GetRunspace for computer ",
|
|
commandToRun.Runspace.ConnectionInfo.ComputerName);
|
|
Runspace runspace = null;
|
|
try
|
|
{
|
|
runspace = workflowHost.RemoteRunspaceProvider.EndGetRunspace(asyncResult);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
// there is an error in connecting to the specified computer
|
|
// handle the same
|
|
tracer.WriteMessage("Error in connecting to remote computer ",
|
|
commandToRun.Runspace.ConnectionInfo.ComputerName);
|
|
// If this was a multi-machine activity, this is an error, not an exception.)
|
|
if ((psComputerName != null) && (psComputerName.Length > 1))
|
|
{
|
|
WriteError(exception, "ConnectionAttemptFailed",
|
|
ErrorCategory.InvalidResult,
|
|
psComputerName, psActivityContext);
|
|
}
|
|
else
|
|
{
|
|
var failureErrorRecord = new ErrorRecord(exception, "ConnectionAttemptFailed", ErrorCategory.OpenError,
|
|
psComputerName);
|
|
lock (psActivityContext.exceptions)
|
|
{
|
|
psActivityContext.exceptions.Add(new RuntimeException(exception.Message, exception,
|
|
failureErrorRecord));
|
|
}
|
|
}
|
|
|
|
// when runspace connection cannot be obtained we will not retry
|
|
RunOneCommandFinally(args, false);
|
|
// when runspace cannot be obtained we cannot execute the command
|
|
// simply decrement count and return
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
return;
|
|
}
|
|
|
|
// runspace should be in an opened state when connection manager
|
|
// assigns the same
|
|
tracer.WriteMessage("Runspace successfully obtained with guid ", runspace.InstanceId.ToString());
|
|
|
|
// Before the connection was assigned, the activity could have been cancelled, therefore
|
|
// we need to check and release before return.
|
|
if (psActivityContext.IsCanceled)
|
|
{
|
|
CloseRunspace(runspace, CommandRunRemotely, workflowHost, psActivityContext);
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
}
|
|
|
|
// In case of auto reconnect after crash/shutdown, the disconnected remote runspace is already assigned to the disconnected powershell instance
|
|
// Assign the runspace only if instance ids are different
|
|
if (!commandToRun.Runspace.InstanceId.Equals(runspace.InstanceId))
|
|
commandToRun.Runspace = runspace;
|
|
|
|
// the handler is saved in the context so that the registration
|
|
// can be unregistered later
|
|
psActivityContext.HandleRunspaceStateChanged = HandleRunspaceStateChanged;
|
|
commandToRun.Runspace.StateChanged += psActivityContext.HandleRunspaceStateChanged;
|
|
|
|
// Invocation state can be in running or final state in reconnect scenario
|
|
// Command should be invoked only when invocation state is not strted,
|
|
// during the normal scenario, invocation state is always NotStarted as runspace is just assigned
|
|
if (commandToRun.InvocationStateInfo.State == PSInvocationState.NotStarted)
|
|
{
|
|
BeginExecuteOneCommand(args);
|
|
}
|
|
|
|
tracer.WriteMessage("Returning from callback for GetRunspace for computer ",
|
|
commandToRun.Runspace.ConnectionInfo.ComputerName);
|
|
}
|
|
}
|
|
|
|
private static void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs eventArgs)
|
|
{
|
|
if (!( (eventArgs.RunspaceStateInfo.State == RunspaceState.Opened) ||
|
|
(eventArgs.RunspaceStateInfo.State == RunspaceState.Disconnected)))
|
|
return;
|
|
|
|
Runspace borrowedRunspace = sender as Runspace;
|
|
RunCommandsArguments args;
|
|
|
|
Dbg.Assert(borrowedRunspace != null, "Sender needs to be Runspace object");
|
|
ArgsTableForRunspaces.TryGetValue(borrowedRunspace.InstanceId, out args);
|
|
|
|
if (args == null)
|
|
return;
|
|
|
|
System.Management.Automation.PowerShell commandToRun = args.ImplementationContext.PowerShellInstance;
|
|
PSWorkflowHost workflowHost = args.PSActivityContext.WorkflowHost;
|
|
|
|
if (eventArgs.RunspaceStateInfo.State == RunspaceState.Opened &&
|
|
commandToRun.InvocationStateInfo.State == PSInvocationState.Disconnected)
|
|
{
|
|
commandToRun.ConnectAsync();
|
|
return;
|
|
}
|
|
|
|
if (eventArgs.RunspaceStateInfo.State == RunspaceState.Disconnected)
|
|
{
|
|
// the connection manager can disconnect a runspace on purpose, if that
|
|
// is not the case then it is a case of network disconnect. In this case
|
|
// we force a failure
|
|
if (!workflowHost.RemoteRunspaceProvider.IsDisconnectedByRunspaceProvider(borrowedRunspace))
|
|
{
|
|
ArgsTableForRunspaces.TryRemove(borrowedRunspace.InstanceId, out args);
|
|
RuntimeException exception =
|
|
new RuntimeException(
|
|
String.Format(CultureInfo.CurrentCulture,
|
|
Resources.ActivityFailedDueToRunspaceDisconnect,
|
|
borrowedRunspace.ConnectionInfo.ComputerName),
|
|
eventArgs.RunspaceStateInfo.Reason);
|
|
RunspaceDisconnectedCallback(args, exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the $pwd variable in the current runspace
|
|
/// </summary>
|
|
/// <param name="psActivityContext"></param>
|
|
/// <param name="runspace"></param>
|
|
private static void SetCurrentDirectory(PSActivityContext psActivityContext, Runspace runspace)
|
|
{
|
|
if (psActivityContext.ParameterDefaults != null)
|
|
{
|
|
if (psActivityContext.ParameterDefaults.ContainsKey(Constants.PSCurrentDirectory))
|
|
{
|
|
string path = psActivityContext.ParameterDefaults[Constants.PSCurrentDirectory] as string;
|
|
|
|
if (path != null)
|
|
{
|
|
runspace.SessionStateProxy.Path.SetLocation(path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Callback from local runspace provider
|
|
/// </summary>
|
|
/// <param name="asyncResult">This callback happens in the workflow
|
|
/// thread or a threadpool callback servicing thread.
|
|
/// However there is only one thread for servicing all callbacks and
|
|
/// so all operations have to be small</param>
|
|
private static void LocalRunspaceProviderCallback(IAsyncResult asyncResult)
|
|
{
|
|
object asyncState = asyncResult.AsyncState;
|
|
Dbg.Assert(asyncState != null, "AsyncState not returned correctly by LocalRunspaceProvider");
|
|
RunCommandsArguments args = asyncState as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "AsyncState casting to RunCommandsArguments failed");
|
|
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback for LocalRunspaceProvider");
|
|
Runspace runspace = null;
|
|
try
|
|
{
|
|
runspace = workflowHost.LocalRunspaceProvider.EndGetRunspace(asyncResult);
|
|
|
|
if (runspace.ConnectionInfo == null)
|
|
{
|
|
if (psActivityContext.UserVariables.Count != 0)
|
|
{
|
|
foreach (KeyValuePair<string, object> entry in psActivityContext.UserVariables)
|
|
{
|
|
runspace.SessionStateProxy.SetVariable(entry.Key, entry.Value);
|
|
}
|
|
}
|
|
|
|
SetCurrentDirectory(psActivityContext, runspace);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
|
|
lock (psActivityContext.exceptions)
|
|
{
|
|
psActivityContext.exceptions.Add(exception);
|
|
}
|
|
|
|
// when runspace connection cannot be obtained we will not retry
|
|
RunOneCommandFinally(args, false);
|
|
// when runspace cannot be obtained we cannot execute the command
|
|
// simply decrement count and return
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
return;
|
|
}
|
|
|
|
// runspace should be in an opened state when connection manager
|
|
// assigns the same
|
|
tracer.WriteMessage("Local Runspace successfully obtained with guid ", runspace.InstanceId.ToString());
|
|
|
|
// Before the runspace is assigned, the activity could have been cancelled, therefore
|
|
// we need to check and release before return.
|
|
if (psActivityContext.IsCanceled)
|
|
{
|
|
CloseRunspace(runspace, CommandRunInProc, workflowHost, psActivityContext);
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
}
|
|
|
|
commandToRun.Runspace = runspace;
|
|
|
|
// only after the runspace is set to commandToRun is the
|
|
// activity completely created. Raise the ActivityCreated
|
|
// event now
|
|
OnActivityCreated(args.ActivityObject, new ActivityCreatedEventArgs(commandToRun));
|
|
|
|
if (args.CommandExecutionType == CimCommandRunInProc)
|
|
{
|
|
CimActivityImplementationContext cimImplContext =
|
|
implementationContext as CimActivityImplementationContext;
|
|
Dbg.Assert(cimImplContext != null,
|
|
"If CommandExecutionType is CimCommand, then implementationContext must be of type CimActivityImplementationContext");
|
|
|
|
// Configure the runspace by executing the module definition scriptblock...
|
|
Dbg.Assert(cimImplContext.ModuleScriptBlock != null, "A Generated CIM activity should never have a null module scriptblock");
|
|
runspace.SessionStateProxy.InvokeCommand.InvokeScript(false, cimImplContext.ModuleScriptBlock, null);
|
|
|
|
// Get the CIM session if needed...
|
|
if (cimImplContext.Session == null && !string.IsNullOrEmpty(cimImplContext.ComputerName) &&
|
|
!string.Equals(cimImplContext.ComputerName, "localhost",
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
bool useSsl = false;
|
|
if (cimImplContext.PSUseSsl.HasValue)
|
|
{
|
|
useSsl = cimImplContext.PSUseSsl.Value;
|
|
}
|
|
|
|
uint port = 0;
|
|
if (cimImplContext.PSPort.HasValue)
|
|
{
|
|
port = cimImplContext.PSPort.Value;
|
|
}
|
|
|
|
|
|
AuthenticationMechanism authenticationMechanism = AuthenticationMechanism.Default;
|
|
if (cimImplContext.PSAuthentication.HasValue)
|
|
{
|
|
authenticationMechanism = cimImplContext.PSAuthentication.Value;
|
|
}
|
|
|
|
cimImplContext.Session =
|
|
CimConnectionManager.GetGlobalCimConnectionManager().GetSession(cimImplContext.ComputerName,
|
|
cimImplContext.PSCredential,
|
|
cimImplContext.PSCertificateThumbprint,
|
|
authenticationMechanism,
|
|
cimImplContext.SessionOptions,
|
|
useSsl,
|
|
port,
|
|
cimImplContext.PSSessionOption);
|
|
|
|
if (cimImplContext.Session == null)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
commandToRun.AddParameter("CimSession", cimImplContext.Session);
|
|
}
|
|
}
|
|
|
|
BeginExecuteOneCommand(args);
|
|
|
|
tracer.WriteMessage("Returning from callback for GetRunspace for LocalRunspaceProvider");
|
|
}
|
|
}
|
|
|
|
private static void ActivityHostManagerCallback(IAsyncResult asyncResult)
|
|
{
|
|
object asyncState = asyncResult.AsyncState;
|
|
Dbg.Assert(asyncState != null, "AsyncState not returned correctly by activity host manager");
|
|
RunCommandsArguments args = asyncState as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "AsyncState casting to RunCommandsArguments failed");
|
|
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback for Executing command out of proc");
|
|
bool attemptRetry = false;
|
|
try
|
|
{
|
|
((PSOutOfProcessActivityController)workflowHost.PSActivityHostController).EndInvokePowerShell(asyncResult);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
attemptRetry = HandleRunOneCommandException(args, e);
|
|
if (attemptRetry)
|
|
BeginActionRetry(args);
|
|
}
|
|
finally
|
|
{
|
|
RemoveHandlersFromStreams(args.ImplementationContext.PowerShellInstance, args);
|
|
RunOneCommandFinally(args, attemptRetry);
|
|
tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity: Finished running command."));
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DecrementRunningCountAndCheckForEnd(PSActivityContext psActivityContext)
|
|
{
|
|
Interlocked.Decrement(ref psActivityContext.CommandsRunningCount);
|
|
|
|
if (psActivityContext.CommandsRunningCount != 0) return;
|
|
|
|
RaiseTerminalCallback(psActivityContext);
|
|
}
|
|
|
|
private static void RaiseTerminalCallback(PSActivityContext psActivityContext)
|
|
{
|
|
lock (psActivityContext.SyncRoot)
|
|
{
|
|
if (psActivityContext.AllCommandsStarted || psActivityContext.commandQueue.Count == 0)
|
|
{
|
|
// This signals all commands are done
|
|
// time to start finish routines
|
|
Dbg.Assert(psActivityContext.Callback != null, "A callback should have been assigned in the context");
|
|
ThreadPool.QueueUserWorkItem(psActivityContext.Callback, psActivityContext.AsyncState);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool CheckForCancel(PSActivityContext psActivityContext)
|
|
{
|
|
bool canceled = psActivityContext.IsCanceled;
|
|
|
|
if (canceled)
|
|
RaiseTerminalCallback(psActivityContext);
|
|
|
|
return canceled;
|
|
}
|
|
|
|
private static void RunDirectExecutionActivity(System.Management.Automation.PowerShell commandToRun, PSDataCollection<PSObject> input,
|
|
PSDataCollection<PSObject> output, PSActivityContext psActivityContext, ActivityImplementationContext implementationContext)
|
|
{
|
|
Command command = commandToRun.Commands.Commands[0];
|
|
var cmdName = command.CommandText;
|
|
|
|
Cmdlet cmdlet = null;
|
|
bool takesInput = false;
|
|
if (string.Equals(cmdName, "Get-WMIObject", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
cmdlet = new Microsoft.PowerShell.Commands.GetWmiObjectCommand();
|
|
}
|
|
else if (string.Equals(cmdName, "Invoke-WMIMethod", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
cmdlet = new Microsoft.PowerShell.Commands.InvokeWmiMethod();
|
|
takesInput = true;
|
|
}
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
// Set up the command runtime instance...
|
|
DirectExecutionActivitiesCommandRuntime cmdRuntime = new DirectExecutionActivitiesCommandRuntime(output != null ? output : new PSDataCollection<PSObject>(),
|
|
implementationContext, cmdlet != null ? cmdlet.GetType() : psActivityContext.TypeImplementingCmdlet);
|
|
cmdRuntime.Error = commandToRun.Streams.Error;
|
|
cmdRuntime.Warning = commandToRun.Streams.Warning;
|
|
cmdRuntime.Progress = commandToRun.Streams.Progress;
|
|
cmdRuntime.Verbose = commandToRun.Streams.Verbose;
|
|
cmdRuntime.Debug = commandToRun.Streams.Debug;
|
|
cmdRuntime.Information = commandToRun.Streams.Information;
|
|
|
|
// If the cmdlet takes input and there is or may be some input, then
|
|
// iterate processing the input
|
|
|
|
if (cmdlet != null)
|
|
{
|
|
cmdlet.CommandRuntime = cmdRuntime;
|
|
|
|
// Copy the parameters from the PowerShell object to the cmdlet object
|
|
PSObject wrappedCmdlet = PSObject.AsPSObject(cmdlet);
|
|
InitializeCmdletInstanceParameters(command, wrappedCmdlet, false, psActivityContext, null, implementationContext);
|
|
|
|
if (takesInput && input != null && (input.Count > 0 || input.IsOpen))
|
|
{
|
|
Microsoft.PowerShell.Commands.InvokeWmiMethod iwm = cmdlet as Microsoft.PowerShell.Commands.InvokeWmiMethod;
|
|
foreach (PSObject inputObject in input)
|
|
{
|
|
try
|
|
{
|
|
var managementObject = LanguagePrimitives.ConvertTo<System.Management.ManagementObject>(inputObject);
|
|
|
|
iwm.InputObject = managementObject;
|
|
iwm.Invoke().GetEnumerator().MoveNext();
|
|
}
|
|
catch (PSInvalidCastException psice)
|
|
{
|
|
if (psice.ErrorRecord != null)
|
|
{
|
|
cmdRuntime.Error.Add(psice.ErrorRecord);
|
|
}
|
|
}
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmdlet.Invoke().GetEnumerator().MoveNext();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// See if any session options were passed..
|
|
CimActivityImplementationContext cimActivityImplementationContext = implementationContext as CimActivityImplementationContext;
|
|
CimSessionOptions cimSessionOptionsToUse = cimActivityImplementationContext != null ? cimActivityImplementationContext.SessionOptions : null;
|
|
|
|
if (psActivityContext.TypeImplementingCmdlet == null)
|
|
{
|
|
throw new InvalidOperationException(cmdName);
|
|
}
|
|
|
|
|
|
if (input != null && (input.Count > 0 || input.IsOpen))
|
|
{
|
|
// Only deals with InputObject property for input
|
|
if (psActivityContext.TypeImplementingCmdlet.GetProperty("InputObject") == null)
|
|
{
|
|
// Throw if the cmdlet does not implement a InputObject property to bind to....
|
|
throw new NotImplementedException(String.Format(CultureInfo.CurrentCulture,
|
|
Resources.CmdletDoesNotImplementInputObjectProperty,
|
|
cmdName));
|
|
}
|
|
|
|
foreach (PSObject inputObject in input)
|
|
{
|
|
try
|
|
{
|
|
using (var cimCmdlet = (Microsoft.Management.Infrastructure.CimCmdlets.CimBaseCommand)Activator.CreateInstance(psActivityContext.TypeImplementingCmdlet))
|
|
{
|
|
cimCmdlet.CommandRuntime = cmdRuntime;
|
|
var cimInstance = LanguagePrimitives.ConvertTo<CimInstance>(inputObject);
|
|
PSObject wrapper = PSObject.AsPSObject(cimCmdlet);
|
|
InitializeCmdletInstanceParameters(command, wrapper, true, psActivityContext, cimSessionOptionsToUse, implementationContext);
|
|
var prop = wrapper.Properties["InputObject"];
|
|
prop.Value = cimInstance;
|
|
cimCmdlet.Invoke().GetEnumerator().MoveNext();
|
|
}
|
|
}
|
|
catch (PSInvalidCastException psice)
|
|
{
|
|
if (psice.ErrorRecord != null)
|
|
{
|
|
cmdRuntime.Error.Add(psice.ErrorRecord);
|
|
}
|
|
}
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using (var cimCmdlet = (Microsoft.Management.Infrastructure.CimCmdlets.CimBaseCommand)Activator.CreateInstance(psActivityContext.TypeImplementingCmdlet))
|
|
{
|
|
cimCmdlet.CommandRuntime = cmdRuntime;
|
|
PSObject wrapper = PSObject.AsPSObject(cimCmdlet);
|
|
InitializeCmdletInstanceParameters(command, wrapper, true, psActivityContext, cimSessionOptionsToUse, implementationContext);
|
|
cimCmdlet.Invoke().GetEnumerator().MoveNext();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InitializeCmdletInstanceParameters(Command command, PSObject wrappedCmdlet, bool isGenericCim,
|
|
PSActivityContext psActivityContext, CimSessionOptions cimSessionOptions, ActivityImplementationContext implementationContext)
|
|
{
|
|
|
|
bool sessionSet = false;
|
|
|
|
foreach (CommandParameter p in command.Parameters)
|
|
{
|
|
// Note - skipping null common parameters avoids a null pointer exception.
|
|
// Also need to replicate parameter set validation for the WMI activities at this point
|
|
// since we aren't going through the parameter binder
|
|
if (Cmdlet.CommonParameters.Contains(p.Name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If a session was explicitly passed, use it instead of an ambient session for the current computer.
|
|
if (p.Name.Equals("CimSession"))
|
|
{
|
|
sessionSet = true;
|
|
}
|
|
|
|
if (wrappedCmdlet.Properties[p.Name] != null)
|
|
{
|
|
wrappedCmdlet.Properties[p.Name].Value = p.Value;
|
|
}
|
|
else
|
|
{
|
|
wrappedCmdlet.Properties.Add(new PSNoteProperty(p.Name, p.Value));
|
|
}
|
|
}
|
|
|
|
string[] computerNameArray = null;
|
|
CimActivityImplementationContext cimActivityImplementationContext =
|
|
implementationContext as CimActivityImplementationContext;
|
|
|
|
// Set the target computer for this command...
|
|
if (cimActivityImplementationContext != null && !String.IsNullOrEmpty(cimActivityImplementationContext.ComputerName))
|
|
{
|
|
computerNameArray = new string[] { cimActivityImplementationContext.ComputerName };
|
|
}
|
|
else if(psActivityContext.ParameterDefaults.ContainsKey("PSComputerName"))
|
|
{
|
|
computerNameArray = psActivityContext.ParameterDefaults["PSComputerName"] as string[];
|
|
}
|
|
|
|
if (computerNameArray != null && computerNameArray.Length > 0)
|
|
{
|
|
// Not all of the generic CIM cmdlets take a session (e.g. New-CimSession)
|
|
if (isGenericCim && wrappedCmdlet.Properties["CimSession"] != null)
|
|
{
|
|
if (!sessionSet)
|
|
{
|
|
if (cimActivityImplementationContext == null)
|
|
throw new ArgumentException(Resources.InvalidImplementationContext);
|
|
|
|
bool useSsl = false;
|
|
if (cimActivityImplementationContext.PSUseSsl.HasValue)
|
|
{
|
|
useSsl = cimActivityImplementationContext.PSUseSsl.Value;
|
|
}
|
|
|
|
uint port = 0;
|
|
if (cimActivityImplementationContext.PSPort.HasValue)
|
|
{
|
|
port = cimActivityImplementationContext.PSPort.Value;
|
|
}
|
|
|
|
AuthenticationMechanism authenticationMechanism = AuthenticationMechanism.Default;
|
|
if (cimActivityImplementationContext.PSAuthentication.HasValue)
|
|
{
|
|
authenticationMechanism = cimActivityImplementationContext.PSAuthentication.Value;
|
|
}
|
|
|
|
// Convert the computer names to session objects.
|
|
List<CimSession> cimSessions = computerNameArray
|
|
.ToList()
|
|
.ConvertAll<CimSession>(
|
|
(string computer) => CimConnectionManager.GetGlobalCimConnectionManager().GetSession(computer,
|
|
cimActivityImplementationContext.PSCredential,
|
|
cimActivityImplementationContext.PSCertificateThumbprint,
|
|
authenticationMechanism,
|
|
cimSessionOptions,
|
|
useSsl,
|
|
port,
|
|
cimActivityImplementationContext.PSSessionOption));
|
|
wrappedCmdlet.Properties["CimSession"].Value = cimSessions.ToArray<CimSession>();
|
|
|
|
if (computerNameArray.Length > 1)
|
|
{
|
|
Dbg.Assert(false, "Something in this fix is not right, have a look");
|
|
}
|
|
cimActivityImplementationContext.Session = cimSessions[0];
|
|
}
|
|
}
|
|
else if (wrappedCmdlet.Properties["ComputerName"] == null)
|
|
{
|
|
// If the cmdlet takes ComputerName and it wasn't explicitly set already,
|
|
// then set it to the ambient PSComputerName we got from the context...
|
|
wrappedCmdlet.Properties.Add(new PSNoteProperty("ComputerName", computerNameArray));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Script to initialize the runspace variables...
|
|
private const string RunspaceInitScript = @"
|
|
Get-Variable -Exclude input | Remove-Variable -ErrorAction Ignore; $input | Foreach-Object {$nvp=$_}; foreach($k in $nvp.keys){set-variable -name $k -value $nvp[$k]}
|
|
";
|
|
internal static readonly InitialSessionState Iss = InitialSessionState.CreateDefault();
|
|
|
|
private static void SetVariablesInRunspaceUsingProxy(PSActivityEnvironment activityEnvironment, Runspace runspace)
|
|
{
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
actionTracer.WriteMessage("BEGIN SetVariablesInRunspaceUsingProxy");
|
|
Dictionary<string, object> nameValuePairs = GetVariablesToSetInRunspace(activityEnvironment);
|
|
// Copy in the workflow variables
|
|
foreach (string name in nameValuePairs.Keys)
|
|
{
|
|
object value = nameValuePairs[name];
|
|
if (value != null)
|
|
{
|
|
try
|
|
{
|
|
runspace.SessionStateProxy.PSVariable.Set(name, value);
|
|
}
|
|
catch (PSNotSupportedException)
|
|
{
|
|
actionTracer.WriteMessage("SetVariablesInRunspaceUsingProxy: Copying the workflow variables to a RemoteSessionStateProxy is not supported.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
actionTracer.WriteMessage("END SetVariablesInRunspaceUsingProxy");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This function runs in either a WinRM thread or in the
|
|
/// connection manager servicing thread. Therefore any
|
|
/// operations that this thread initiates is supposed to
|
|
/// be very small. Make sure that this contract is maintained
|
|
/// when any changes are made to the function
|
|
/// </remarks>
|
|
private static void BeginSetVariablesInRemoteRunspace(RunCommandsArguments args)
|
|
{
|
|
Runspace runspace = args.ImplementationContext.PowerShellInstance.Runspace;
|
|
PSActivityEnvironment activityEnvironment = args.ImplementationContext.PSActivityEnvironment;
|
|
|
|
Dbg.Assert(runspace.ConnectionInfo != null,
|
|
"BeginSetVariablesInRemoteRunspace can only be called for remote runspaces");
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
actionTracer.WriteMessage("BEGIN BeginSetVariablesInRemoteRunspace");
|
|
|
|
System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create();
|
|
ps.Runspace = runspace;
|
|
ps.AddScript(RunspaceInitScript);
|
|
|
|
Dictionary<string, object> nameValuePairs = GetVariablesToSetInRunspace(activityEnvironment);
|
|
|
|
PSDataCollection<object> vars = new PSDataCollection<object> {nameValuePairs};
|
|
vars.Complete();
|
|
args.HelperCommand = ps;
|
|
args.HelperCommandInput = vars;
|
|
|
|
BeginInvokeOnPowershellCommand(ps, vars, null, SetVariablesCallback, args);
|
|
|
|
actionTracer.WriteMessage("END BeginSetVariablesInRemoteRunspace");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This function either runs on a thread pool thread, or in the
|
|
/// remote case runs on a WinRM/CM servicing thread. Therefore operations
|
|
/// need to be light so that the thread can be released back quickly
|
|
/// When changes are made this assumption need to be validated</remarks>
|
|
private static void BeginRunspaceInitializeSetup(RunCommandsArguments args)
|
|
{
|
|
PSActivityEnvironment activityEnvironment= args.ImplementationContext.PSActivityEnvironment;
|
|
Runspace runspace = args.ImplementationContext.PowerShellInstance.Runspace;
|
|
string[] requiredModules = args.ActivityParameters.PSRequiredModules;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
using (PowerShellTraceSource actionTracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
actionTracer.WriteMessage("BEGIN BeginRunspaceInitializeSetup");
|
|
if (args.CommandExecutionType != CimCommandRunInProc)
|
|
{
|
|
if (runspace.ConnectionInfo != null)
|
|
{
|
|
// for a remote runspace do an async invocation
|
|
BeginSetVariablesInRemoteRunspace(args);
|
|
return;
|
|
}
|
|
|
|
// for a local runspace set the variables using proxy
|
|
// on the same thread
|
|
try
|
|
{
|
|
SetVariablesInRunspaceUsingProxy(activityEnvironment, runspace);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
bool attemptRetry = HandleRunOneCommandException(args, e);
|
|
|
|
if (attemptRetry)
|
|
{
|
|
actionTracer.WriteMessage("Setting variables for command failed, attempting retry");
|
|
// before attempting a retry we will have to ditch this runspace
|
|
// and use a new one
|
|
CloseRunspace(args.ImplementationContext.PowerShellInstance.Runspace,
|
|
args.CommandExecutionType,
|
|
args.WorkflowHost, psActivityContext);
|
|
BeginActionRetry(args);
|
|
}
|
|
else
|
|
{
|
|
// if we are not attempting a retry we just need to
|
|
// return
|
|
actionTracer.WriteMessage("Setting variables for command failed, returning");
|
|
RunOneCommandFinally(args, false);
|
|
}
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (requiredModules.Length > 0)
|
|
{
|
|
BeginImportRequiredModules(args);
|
|
}
|
|
else
|
|
{
|
|
// else we are good to call command invocation this point
|
|
// since this function is guaranteed to run on a threadpool
|
|
// thread BeginPowerShellInvocation can be directly called here
|
|
BeginPowerShellInvocation(args);
|
|
}
|
|
|
|
actionTracer.WriteMessage("END BeginRunspaceInitializeSetup");
|
|
}
|
|
}
|
|
|
|
private static void BeginImportRequiredModules(RunCommandsArguments args)
|
|
{
|
|
Runspace runspace = args.ImplementationContext.PowerShellInstance.Runspace;
|
|
PSActivityEnvironment activityEnvironment = args.ImplementationContext.PSActivityEnvironment;
|
|
System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create();
|
|
if (activityEnvironment != null)
|
|
{
|
|
Dbg.Assert(activityEnvironment.Modules.Count > 0,
|
|
"When PSActivityEnvironment is specified and modules are imported, PSActivityEnvironment.Modules need to be populated");
|
|
// Setting erroaction to stop for import-module since they are required modules. If not present, stop the execution
|
|
ps.AddCommand("Import-Module").AddParameter("Name", activityEnvironment.Modules).AddParameter(
|
|
"ErrorAction", ActionPreference.Stop);
|
|
}
|
|
else
|
|
{
|
|
// Setting erroaction to stop for import-module since they are required modules. If not present, stop the execution
|
|
ps.AddCommand("Import-Module").AddParameter("Name", args.ActivityParameters.PSRequiredModules).AddParameter(
|
|
"ErrorAction", ActionPreference.Stop);
|
|
}
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
// we need to import required modules only if any are specified
|
|
tracer.WriteMessage("Importing modules in runspace ", runspace.InstanceId);
|
|
|
|
ps.Runspace = runspace;
|
|
args.HelperCommand = ps;
|
|
|
|
BeginInvokeOnPowershellCommand(ps, null, null, ImportRequiredModulesCallback, args);
|
|
}
|
|
}
|
|
|
|
private static void BeginInvokeOnPowershellCommand(System.Management.Automation.PowerShell ps, PSDataCollection<object> varsInput, PSInvocationSettings settings, AsyncCallback callback, RunCommandsArguments args)
|
|
{
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
try
|
|
{
|
|
ps.BeginInvoke(varsInput, settings, callback, args);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
bool attemptRetry = ProcessException(args, e);
|
|
|
|
if (attemptRetry)
|
|
{
|
|
BeginActionRetry(args);
|
|
}
|
|
else
|
|
{
|
|
ReleaseResourcesAndCheckForEnd(ps, args, false, false);
|
|
}
|
|
|
|
tracer.TraceException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ReleaseResourcesAndCheckForEnd(System.Management.Automation.PowerShell ps, RunCommandsArguments args, bool removeHandlersFromStreams, bool attemptRetry)
|
|
{
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
if (removeHandlersFromStreams)
|
|
{
|
|
RemoveHandlersFromStreams(ps, args);
|
|
}
|
|
|
|
RunOneCommandFinally(args, attemptRetry);
|
|
|
|
tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity: Finished running command."));
|
|
|
|
var remotingActivity = args.ActivityObject as PSRemotingActivity;
|
|
if ((remotingActivity != null) &&
|
|
(args.PSActivityContext.HostExtension != null) &&
|
|
(args.PSActivityContext.HostExtension.RemoteActivityState != null) &&
|
|
(args.ImplementationContext != null))
|
|
{
|
|
// remote activity task execution has finished
|
|
// change the task's state to Completed in RemoteActivityState
|
|
args.PSActivityContext.HostExtension.RemoteActivityState.SetRemoteActivityRunspaceEntry(
|
|
remotingActivity.Id,
|
|
args.ImplementationContext.Id,
|
|
"completed", null);
|
|
}
|
|
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
}
|
|
}
|
|
|
|
private static bool ProcessException(RunCommandsArguments args, Exception e)
|
|
{
|
|
bool attemptRetry = false;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
|
|
if ((args.ActivityParameters.ConnectionRetryCount.HasValue ||
|
|
args.ActivityParameters.ConnectionRetryInterval.HasValue) && e.InnerException != null &&
|
|
e.InnerException is IContainsErrorRecord)
|
|
{
|
|
IContainsErrorRecord er = e as IContainsErrorRecord;
|
|
|
|
// CIM specific case
|
|
if (er.ErrorRecord.FullyQualifiedErrorId.StartsWith("CimJob_BrokenCimSession", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
int attempts = Int32.MaxValue;
|
|
|
|
if (!psActivityContext.IsCanceled)
|
|
{
|
|
if (psActivityContext.runningCommands.ContainsKey(commandToRun))
|
|
attempts = psActivityContext.runningCommands[commandToRun].ActionAttempts;
|
|
|
|
attemptRetry = HandleFailure(attempts, args.ActivityParameters.ConnectionRetryCount,
|
|
args.ActivityParameters.ConnectionRetryInterval, implementationContext, "ActivityActionFailed",
|
|
null, psActivityContext);
|
|
}
|
|
|
|
if(!attemptRetry)
|
|
{
|
|
ErrorRecord errorRecord = er.ErrorRecord;
|
|
String computerName;
|
|
Guid jobInstanceId;
|
|
if (GetComputerNameAndJobIdForCommand(commandToRun.InstanceId, out computerName, out jobInstanceId))
|
|
{
|
|
AddIdentifierInfoToErrorRecord(errorRecord, computerName, jobInstanceId);
|
|
}
|
|
|
|
// If this was a multi-machine activity, this is an error, not an exception.
|
|
if ((implementationContext.PSComputerName != null) && (implementationContext.PSComputerName.Length > 1))
|
|
{
|
|
WriteError(e, "ActivityActionFailed", ErrorCategory.InvalidResult,
|
|
implementationContext.PowerShellInstance.Runspace.ConnectionInfo, psActivityContext);
|
|
}
|
|
else
|
|
{
|
|
lock (psActivityContext.exceptions)
|
|
{
|
|
psActivityContext.exceptions.Add(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
return attemptRetry;
|
|
}
|
|
}
|
|
|
|
IContainsErrorRecord containsErrorRecord = e as IContainsErrorRecord;
|
|
if (containsErrorRecord != null)
|
|
{
|
|
ErrorRecord errorRecord = containsErrorRecord.ErrorRecord;
|
|
String computerName;
|
|
Guid jobInstanceId;
|
|
if (GetComputerNameAndJobIdForCommand(commandToRun.InstanceId, out computerName, out jobInstanceId))
|
|
{
|
|
AddIdentifierInfoToErrorRecord(errorRecord, computerName, jobInstanceId);
|
|
}
|
|
}
|
|
attemptRetry = HandleRunOneCommandException(args, e);
|
|
|
|
return attemptRetry;
|
|
}
|
|
|
|
private static Dictionary<string, object> GetVariablesToSetInRunspace(PSActivityEnvironment activityEnvironment)
|
|
{
|
|
// first set the predefined variables to reset the runspace before
|
|
// setting the activity specified ones
|
|
Dictionary<string, object> nameValuePairs = Iss.Variables.ToDictionary(entry => entry.Name, entry => entry.Value);
|
|
|
|
if (activityEnvironment != null && activityEnvironment.Variables != null)
|
|
{
|
|
foreach (string name in activityEnvironment.Variables.Keys)
|
|
{
|
|
object value = activityEnvironment.Variables[name];
|
|
if (value == null) continue;
|
|
if (nameValuePairs.ContainsKey(name))
|
|
{
|
|
nameValuePairs[name] = value;
|
|
}
|
|
else
|
|
{
|
|
nameValuePairs.Add(name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// workaround: PSPR does not support rehydration of System.Text.ASCIIEncoding
|
|
// therefore we need to remove this otherwise FS RI is blocked - per their dev mgr
|
|
// once the DCR is implemented on Engines side, we should remove
|
|
if (nameValuePairs.ContainsKey("OutputEncoding"))
|
|
{
|
|
nameValuePairs.Remove("OutputEncoding");
|
|
}
|
|
|
|
return nameValuePairs;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="asyncResult"></param>
|
|
/// THREADING CONTRACT:
|
|
/// This callback runs in a WinRM thread - in the normal
|
|
/// course of operation i.e unless PowerShell.EndInvoke() throws
|
|
/// an exception, the operations performed on this thread
|
|
/// should be minimal
|
|
private static void SetVariablesCallback(IAsyncResult asyncResult)
|
|
{
|
|
object asyncState = asyncResult.AsyncState;
|
|
Dbg.Assert(asyncState != null, "AsyncState was not set correctly by SetVariablesInActivityHost() method");
|
|
RunCommandsArguments args = asyncState as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "AsyncState casting to RunCommandsArguments failed");
|
|
System.Management.Automation.PowerShell powerShell = args.HelperCommand;
|
|
Dbg.Assert(powerShell != null, "Caller did not pass PowerShell instance correctly");
|
|
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
PSActivityEnvironment activityEnvironment = args.ImplementationContext.PSActivityEnvironment;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback for setting variables in remote runspace");
|
|
try
|
|
{
|
|
powerShell.EndInvoke(asyncResult);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Exception setting variables, try using proxy
|
|
tracer.WriteMessage("Setting varibles in remote runspace failed using script, trying with proxy");
|
|
|
|
try
|
|
{
|
|
SetVariablesInRunspaceUsingProxy(activityEnvironment, powerShell.Runspace);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
bool attemptRetry = HandleRunOneCommandException(args, ex);
|
|
if (attemptRetry)
|
|
{
|
|
tracer.WriteMessage("Runspace initialization failed, attempting retry");
|
|
// Since the error is in runspace initialization, we need to discard this
|
|
// runspace and try with a new one
|
|
CloseRunspace(args.ImplementationContext.PowerShellInstance.Runspace,
|
|
args.CommandExecutionType,
|
|
args.WorkflowHost, psActivityContext);
|
|
BeginActionRetry(args);
|
|
}
|
|
else
|
|
{
|
|
RunOneCommandFinally(args, false);
|
|
}
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
return;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
powerShell.Dispose();
|
|
args.HelperCommand = null;
|
|
args.HelperCommandInput.Dispose();
|
|
args.HelperCommandInput = null;
|
|
}
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
if ((activityEnvironment != null && activityEnvironment.Modules != null && activityEnvironment.Modules.Count > 0) ||
|
|
(args.ActivityParameters != null && args.ActivityParameters.PSRequiredModules != null && args.ActivityParameters.PSRequiredModules.Length > 0))
|
|
{
|
|
// if there are modules to import, begin async invocation
|
|
// for importing modules
|
|
BeginImportRequiredModules(args);
|
|
}
|
|
else
|
|
{
|
|
// else we are good to call command invocation this point
|
|
// since this function is guaranteed to run on a threadpool
|
|
// thread BeginPowerShellInvocation can be directly called here
|
|
BeginPowerShellInvocation(args);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ImportRequiredModulesCallback(IAsyncResult asyncResult)
|
|
{
|
|
object asyncState = asyncResult.AsyncState;
|
|
Dbg.Assert(asyncState != null, "AsyncState not returned correctly by activity host manager");
|
|
RunCommandsArguments args = asyncState as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "AsyncState casting to RunCommandsArguments failed");
|
|
System.Management.Automation.PowerShell powerShell = args.HelperCommand;
|
|
Dbg.Assert(powerShell != null, "Caller did not pass PowerShell instance correctly");
|
|
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityParameters activityParameters = args.ActivityParameters;
|
|
Type activityType = args.ActivityType;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback for importing required modules");
|
|
try
|
|
{
|
|
powerShell.EndInvoke(asyncResult);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
string psRequiredModuleNames = "";
|
|
foreach (string moduleName in activityParameters.PSRequiredModules)
|
|
{
|
|
psRequiredModuleNames += moduleName + ", ";
|
|
}
|
|
psRequiredModuleNames = psRequiredModuleNames.TrimEnd(',',' ');
|
|
|
|
string msg = string.Format(CultureInfo.InvariantCulture,
|
|
Resources.DependModuleImportFailed,
|
|
psRequiredModuleNames,
|
|
activityType.Name);
|
|
Exception ex = new Exception(msg, e);
|
|
tracer.TraceException(ex);
|
|
|
|
bool attemptRetry = HandleRunOneCommandException(args, ex);
|
|
if (attemptRetry)
|
|
{
|
|
tracer.WriteMessage("Runspace initialization failed, attempting retry");
|
|
// Since the error is in runspace initialization, we need to discard this
|
|
// runspace and try with a new one
|
|
CloseRunspace(args.ImplementationContext.PowerShellInstance.Runspace, args.CommandExecutionType,
|
|
args.WorkflowHost, psActivityContext);
|
|
BeginActionRetry(args);
|
|
}
|
|
else
|
|
{
|
|
RunOneCommandFinally(args, false);
|
|
}
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
powerShell.Dispose();
|
|
args.HelperCommand = null;
|
|
}
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
// at this point the initialization is done and we need to execute
|
|
// the command
|
|
BeginPowerShellInvocation(args);
|
|
}
|
|
}
|
|
|
|
private static void InitializeRunspaceAndExecuteCommandWorker(object state)
|
|
{
|
|
Dbg.Assert(state != null, "State not passed correctly to worker");
|
|
RunCommandsArguments args = state as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "RunCommandsArguments not passed correctly");
|
|
|
|
System.Management.Automation.PowerShell commandToRun = args.ImplementationContext.PowerShellInstance;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
// Invoke the command. If we want to return the output, then call the overload
|
|
// that streams output as well.
|
|
try
|
|
{
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
// we need to set the variables in the runspace before
|
|
// invoking the command
|
|
BeginRunspaceInitializeSetup(args);
|
|
|
|
}
|
|
catch (PipelineStoppedException)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="state"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This function is designed to be lightweight. It always runs
|
|
/// on a callback thread - which is a threadpool thread</remarks>
|
|
private static void BeginPowerShellInvocation(object state)
|
|
{
|
|
Dbg.Assert(state != null, "State not passed correctly to BeginPowerShellInvocation");
|
|
RunCommandsArguments args = state as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "Args not passed correctly to BeginPowerShellInvocation");
|
|
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
PSDataCollection<PSObject> output = args.Output;
|
|
PSDataCollection<PSObject> input = args.Input;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
PSWorkflowHost workflowHost = args.WorkflowHost;
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
PSDataCollection<PSObject> outputForInvoke = output ??
|
|
new PSDataCollection<PSObject>();
|
|
bool exceptionOccurred = false;
|
|
|
|
try
|
|
{
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
AddHandlersToStreams(commandToRun, args);
|
|
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
commandToRun.BeginInvoke(input, outputForInvoke, null,
|
|
PowerShellInvocationCallback,
|
|
args);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
exceptionOccurred = true;
|
|
|
|
bool attemptRetry = ProcessException(args, e);
|
|
|
|
if (attemptRetry)
|
|
{
|
|
BeginActionRetry(args);
|
|
}
|
|
else
|
|
{
|
|
ReleaseResourcesAndCheckForEnd(commandToRun, args, true, false);
|
|
}
|
|
|
|
tracer.TraceException(e);
|
|
}
|
|
|
|
tracer.WriteMessage("Completed BeginInvoke call on PowerShell");
|
|
|
|
if (exceptionOccurred == false &&
|
|
args.CommandExecutionType == CommandRunRemotely)
|
|
{
|
|
if ((args.ImplementationContext != null) && (args.ImplementationContext.EnableRemotingActivityAutoResume))
|
|
{
|
|
var remotingActivity = args.ActivityObject as PSRemotingActivity;
|
|
if ((remotingActivity != null) &&
|
|
(args.PSActivityContext.HostExtension != null) &&
|
|
(args.PSActivityContext.HostExtension.RemoteActivityState != null))
|
|
{
|
|
// Command invocation has started, save the tasks's runspace instance id to RemoteActivityState
|
|
args.PSActivityContext.HostExtension.RemoteActivityState.SetRemoteActivityRunspaceEntry(
|
|
remotingActivity.Id,
|
|
args.ImplementationContext.Id,
|
|
commandToRun.Runspace.InstanceId, null);
|
|
}
|
|
}
|
|
|
|
// if the runspace was obtained from the connection manager
|
|
// then it need to be signaled as ready for disconnect/reconnect
|
|
workflowHost.RemoteRunspaceProvider.ReadyForDisconnect(commandToRun.Runspace);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ConcurrentDictionary<Guid, RunCommandsArguments> ArgsTableForRunspaces
|
|
= new ConcurrentDictionary<Guid, RunCommandsArguments>();
|
|
|
|
#region Object Decoration
|
|
|
|
internal static void AddHandlersToStreams(System.Management.Automation.PowerShell commandToRun, RunCommandsArguments args)
|
|
{
|
|
if (commandToRun == null || args == null)
|
|
return;
|
|
|
|
bool hasErrorMerged = args.PSActivityContext.MergeErrorToOutput;
|
|
|
|
if (hasErrorMerged)
|
|
{
|
|
commandToRun.Streams.Error.DataAdded += HandleErrorDataAdded;
|
|
}
|
|
|
|
if (args.PSActivityContext.Output != null)
|
|
args.PSActivityContext.Output.DataAdding += HandleOutputDataAdding;
|
|
commandToRun.Streams.Error.DataAdding += HandleErrorDataAdding;
|
|
commandToRun.Streams.Progress.DataAdding += HandleProgressDataAdding;
|
|
commandToRun.Streams.Verbose.DataAdding += HandleInformationalRecordDataAdding;
|
|
commandToRun.Streams.Warning.DataAdding += HandleInformationalRecordDataAdding;
|
|
commandToRun.Streams.Debug.DataAdding += HandleInformationalRecordDataAdding;
|
|
commandToRun.Streams.Information.DataAdding += HandleInformationDataAdding;
|
|
|
|
ArgsTable.TryAdd(commandToRun.InstanceId, args);
|
|
}
|
|
|
|
private static void HandleInformationalRecordDataAdding(object sender, DataAddingEventArgs e)
|
|
{
|
|
InformationalRecord informationalRecord = (InformationalRecord) e.ItemAdded;
|
|
if (informationalRecord == null) return;
|
|
|
|
string computerName;
|
|
Guid jobInstanceId;
|
|
if (GetComputerNameAndJobIdForCommand(e.PowerShellInstanceId, out computerName, out jobInstanceId))
|
|
{
|
|
informationalRecord.Message =
|
|
AddIdentifierInfoToString(jobInstanceId, computerName, informationalRecord.Message);
|
|
}
|
|
}
|
|
|
|
private static void HandleProgressDataAdding(object sender, DataAddingEventArgs e)
|
|
{
|
|
ProgressRecord progressRecord = (ProgressRecord)e.ItemAdded;
|
|
if (progressRecord == null) return;
|
|
|
|
string computerName;
|
|
Guid jobInstanceId;
|
|
|
|
if (GetComputerNameAndJobIdForCommand(e.PowerShellInstanceId, out computerName, out jobInstanceId))
|
|
{
|
|
progressRecord.CurrentOperation = AddIdentifierInfoToString(jobInstanceId, computerName,
|
|
progressRecord.CurrentOperation);
|
|
}
|
|
}
|
|
|
|
private static void HandleInformationDataAdding(object sender, DataAddingEventArgs e)
|
|
{
|
|
InformationRecord informationRecord = (InformationRecord)e.ItemAdded;
|
|
if (informationRecord == null) return;
|
|
|
|
string computerName;
|
|
Guid jobInstanceId;
|
|
|
|
if (GetComputerNameAndJobIdForCommand(e.PowerShellInstanceId, out computerName, out jobInstanceId))
|
|
{
|
|
informationRecord.Source = AddIdentifierInfoToString(jobInstanceId, computerName,
|
|
informationRecord.Source);
|
|
}
|
|
}
|
|
|
|
internal static void AddIdentifierInfoToOutput(PSObject psObject, Guid jobInstanceId, string computerName)
|
|
{
|
|
Dbg.Assert(psObject != null, "PSObject not passed correctly");
|
|
|
|
if (psObject.Properties["PSComputerName"] != null)
|
|
{
|
|
// if it is a NoteProperty then try changing the same
|
|
PSNoteProperty noteProperty = psObject.Properties["PSComputerName"] as PSNoteProperty;
|
|
if (noteProperty != null)
|
|
{
|
|
try
|
|
{
|
|
noteProperty.Value = computerName;
|
|
}
|
|
catch(SetValueException)
|
|
{
|
|
// this is a best attempt so fine to
|
|
// eat exception if PSComputerName is
|
|
// defined in type data
|
|
}
|
|
}
|
|
}
|
|
else
|
|
psObject.Properties.Add(new PSNoteProperty("PSComputerName", computerName));
|
|
|
|
if (psObject.Properties["PSShowComputerName"] != null)
|
|
psObject.Properties.Remove("PSShowComputerName");
|
|
psObject.Properties.Add(new PSNoteProperty("PSShowComputerName", true));
|
|
|
|
if (psObject.Properties["PSSourceJobInstanceId"] != null)
|
|
psObject.Properties.Remove("PSSourceJobInstanceId");
|
|
psObject.Properties.Add(new PSNoteProperty("PSSourceJobInstanceId", jobInstanceId));
|
|
}
|
|
|
|
private static void HandleOutputDataAdding(object sender, DataAddingEventArgs e)
|
|
{
|
|
PSObject psObject = (PSObject)e.ItemAdded;
|
|
if (psObject == null) return;
|
|
|
|
string computerName;
|
|
Guid jobInstanceId;
|
|
|
|
RunCommandsArguments args;
|
|
args = GetArgsForCommand(e.PowerShellInstanceId, out computerName, out jobInstanceId);
|
|
|
|
if (args == null)
|
|
return;
|
|
|
|
AddIdentifierInfoToOutput(psObject, jobInstanceId, computerName);
|
|
}
|
|
|
|
private static void HandleErrorDataAdding(object sender, DataAddingEventArgs e)
|
|
{
|
|
ErrorRecord errorRecord = (ErrorRecord)e.ItemAdded;
|
|
if (errorRecord == null) return;
|
|
|
|
string computerName;
|
|
Guid jobInstanceId;
|
|
RunCommandsArguments args = GetArgsForCommand(e.PowerShellInstanceId, out computerName, out jobInstanceId);
|
|
|
|
if (args == null) return;
|
|
|
|
AddIdentifierInfoToErrorRecord(errorRecord, computerName, args.PSActivityContext.JobInstanceId);
|
|
|
|
bool hasHostExtension = args.PSActivityContext.HostExtension != null;
|
|
bool hasErrorMerged = args.PSActivityContext.MergeErrorToOutput;
|
|
|
|
if (!hasErrorMerged && !hasHostExtension) return;
|
|
|
|
HostSettingCommandMetadata sourceCommandMetadata = hasHostExtension
|
|
? args.PSActivityContext.HostExtension.
|
|
HostCommandMetadata
|
|
: null;
|
|
PSDataCollection<PSObject> outputStream = hasErrorMerged ? args.PSActivityContext.Output : null;
|
|
|
|
PowerShellInvocation_ErrorAdding(sender, e, sourceCommandMetadata, outputStream);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="powerShellId"></param>
|
|
/// <param name="computerName"></param>
|
|
/// <param name="jobInstanceId"></param>
|
|
/// <returns>false if default values were substituted</returns>
|
|
private static bool GetComputerNameAndJobIdForCommand(Guid powerShellId, out string computerName, out Guid jobInstanceId)
|
|
{
|
|
RunCommandsArguments args = GetArgsForCommand(powerShellId, out computerName, out jobInstanceId);
|
|
return args != null;
|
|
}
|
|
|
|
private static RunCommandsArguments GetArgsForCommand(Guid powerShellId, out string computerName, out Guid jobInstanceId)
|
|
{
|
|
RunCommandsArguments args;
|
|
ArgsTable.TryGetValue(powerShellId, out args);
|
|
|
|
computerName = LocalHost;
|
|
|
|
jobInstanceId = args == null
|
|
? Guid.Empty
|
|
: args.PSActivityContext.JobInstanceId;
|
|
|
|
if (args != null )
|
|
{
|
|
if (args.PSActivityContext.HostExtension != null &&
|
|
args.PSActivityContext.HostExtension.Parameters != null &&
|
|
args.PSActivityContext.HostExtension.Parameters.ContainsKey("PSComputerName"))
|
|
{
|
|
string[] computerNames =
|
|
(string[]) args.PSActivityContext.HostExtension.Parameters["PSComputerName"];
|
|
|
|
if (computerNames.Length == 1)
|
|
{
|
|
// this is the workflow fan-out scenario. Use the intended computer
|
|
// name in this case
|
|
computerName = computerNames[0];
|
|
return args;
|
|
}
|
|
}
|
|
|
|
// looks like an activity level fan-out. If this is a command
|
|
// run with a runspace, try to extract the computer name from
|
|
// the runspace
|
|
switch (args.CommandExecutionType)
|
|
{
|
|
case CommandRunOutOfProc:
|
|
case CommandRunInProc:
|
|
case CommandRunRemotely:
|
|
computerName = GetComputerNameFromCommand(args.ImplementationContext.PowerShellInstance);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (args != null)
|
|
{
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
|
|
if (implementationContext != null)
|
|
{
|
|
if (implementationContext.PSRemotingBehavior == RemotingBehavior.Custom)
|
|
{
|
|
if (implementationContext.PSComputerName != null &&
|
|
implementationContext.PSComputerName.Length != 0)
|
|
{
|
|
computerName = implementationContext.PSComputerName[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return args;
|
|
}
|
|
|
|
internal static void AddIdentifierInfoToErrorRecord(ErrorRecord errorRecord, string computerName, Guid jobInstanceId)
|
|
{
|
|
RemotingErrorRecord remoteErrorRecord = errorRecord as RemotingErrorRecord;
|
|
|
|
if (remoteErrorRecord != null) return;
|
|
|
|
if (errorRecord.ErrorDetails == null)
|
|
{
|
|
errorRecord.ErrorDetails = new ErrorDetails(String.Empty);
|
|
errorRecord.ErrorDetails.RecommendedAction = AddIdentifierInfoToString(jobInstanceId, computerName,
|
|
errorRecord.ErrorDetails.
|
|
RecommendedAction);
|
|
}
|
|
else
|
|
{
|
|
errorRecord.ErrorDetails.RecommendedAction =
|
|
AddIdentifierInfoToString(jobInstanceId, computerName, errorRecord.ErrorDetails.RecommendedAction);
|
|
}
|
|
}
|
|
|
|
internal static string AddIdentifierInfoToString(Guid instanceId, string computerName, string message)
|
|
{
|
|
Guid jobInstanceId;
|
|
string originalComputerName;
|
|
string originalMessage;
|
|
string messageToAppend = StringContainsIdentifierInfo(message, out jobInstanceId, out originalComputerName,
|
|
out originalMessage)
|
|
? originalMessage
|
|
: message;
|
|
|
|
var newMessage = new StringBuilder(instanceId.ToString());
|
|
newMessage.Append(":[");
|
|
newMessage.Append(computerName);
|
|
newMessage.Append("]:");
|
|
newMessage.Append(messageToAppend);
|
|
return newMessage.ToString();
|
|
}
|
|
|
|
private const string MessagePattern = @"^([\d\w]{8}\-[\d\w]{4}\-[\d\w]{4}\-[\d\w]{4}\-[\d\w]{12}:\[.*\]:).*";
|
|
private static readonly char[] Delimiter = new[]{':'};
|
|
|
|
private static bool StringContainsIdentifierInfo(string message, out Guid jobInstanceId, out string computerName, out string originalString)
|
|
{
|
|
jobInstanceId = Guid.Empty;
|
|
computerName = string.Empty;
|
|
originalString = string.Empty;
|
|
|
|
if (string.IsNullOrEmpty(message)) return false;
|
|
|
|
|
|
if (!Regex.IsMatch(message, MessagePattern))
|
|
return false;
|
|
|
|
String[] parts = message.Split(Delimiter, 3);
|
|
if (parts.Length != 3) return false;
|
|
|
|
if (!Guid.TryParse(parts[0], out jobInstanceId))
|
|
jobInstanceId = Guid.Empty;
|
|
|
|
computerName = parts[1];
|
|
originalString = parts[2].Trim();
|
|
return true;
|
|
}
|
|
|
|
private const string LocalHost = "localhost";
|
|
private static String GetComputerNameFromCommand(System.Management.Automation.PowerShell commandToRun)
|
|
{
|
|
Runspace runspace = commandToRun.Runspace;
|
|
|
|
return runspace.ConnectionInfo == null ? LocalHost : runspace.ConnectionInfo.ComputerName;
|
|
}
|
|
|
|
private readonly static ConcurrentDictionary<Guid, RunCommandsArguments> ArgsTable =
|
|
new ConcurrentDictionary<Guid, RunCommandsArguments>();
|
|
|
|
private static void HandleErrorDataAdded(object sender, DataAddedEventArgs e)
|
|
{
|
|
RunCommandsArguments args;
|
|
ArgsTable.TryGetValue(e.PowerShellInstanceId, out args);
|
|
if (args == null) return;
|
|
|
|
bool hasErrorMerged = args.PSActivityContext.MergeErrorToOutput;
|
|
|
|
if (hasErrorMerged)
|
|
{
|
|
MergeError_DataAdded(sender, e, args.PSActivityContext.errors);
|
|
}
|
|
}
|
|
|
|
internal static void RemoveHandlersFromStreams(System.Management.Automation.PowerShell commandToRun, RunCommandsArguments args)
|
|
{
|
|
if (commandToRun == null || args == null)
|
|
return;
|
|
|
|
bool hasErrorMerged = args.PSActivityContext.MergeErrorToOutput;
|
|
|
|
if (hasErrorMerged)
|
|
{
|
|
commandToRun.Streams.Error.DataAdded -= HandleErrorDataAdded;
|
|
}
|
|
|
|
if (args.PSActivityContext.Output != null)
|
|
args.PSActivityContext.Output.DataAdding -= HandleOutputDataAdding;
|
|
commandToRun.Streams.Error.DataAdding -= HandleErrorDataAdding;
|
|
commandToRun.Streams.Progress.DataAdding -= HandleProgressDataAdding;
|
|
commandToRun.Streams.Verbose.DataAdding -= HandleInformationalRecordDataAdding;
|
|
commandToRun.Streams.Warning.DataAdding -= HandleInformationalRecordDataAdding;
|
|
commandToRun.Streams.Debug.DataAdding -= HandleInformationalRecordDataAdding;
|
|
commandToRun.Streams.Information.DataAdding -= HandleInformationDataAdding;
|
|
|
|
RunCommandsArguments arguments;
|
|
ArgsTable.TryRemove(commandToRun.InstanceId, out arguments);
|
|
}
|
|
|
|
private static void MergeError_DataAdded(object sender, DataAddedEventArgs e, PSDataCollection<ErrorRecord> errors)
|
|
{
|
|
if (errors != null)
|
|
{
|
|
// Don't use the index from "e" because that may cause race condition.
|
|
// It's safe to always remove the first item.
|
|
errors.RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
private static void PowerShellInvocation_ErrorAdding(object sender, DataAddingEventArgs e, HostSettingCommandMetadata commandMetadata, PSDataCollection<PSObject> output)
|
|
{
|
|
ErrorRecord errorRecord = e.ItemAdded as ErrorRecord;
|
|
|
|
if (errorRecord != null)
|
|
{
|
|
if (commandMetadata != null)
|
|
{
|
|
ScriptPosition scriptStart = new ScriptPosition(
|
|
commandMetadata.CommandName,
|
|
commandMetadata.StartLineNumber,
|
|
commandMetadata.StartColumnNumber,
|
|
null);
|
|
ScriptPosition scriptEnd = new ScriptPosition(
|
|
commandMetadata.CommandName,
|
|
commandMetadata.EndLineNumber,
|
|
commandMetadata.EndColumnNumber,
|
|
null);
|
|
ScriptExtent extent = new ScriptExtent(scriptStart, scriptEnd);
|
|
|
|
if (errorRecord.InvocationInfo != null)
|
|
{
|
|
errorRecord.InvocationInfo.DisplayScriptPosition = extent;
|
|
}
|
|
}
|
|
|
|
if (output != null)
|
|
{
|
|
output.Add(PSObject.AsPSObject(errorRecord));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Object Decoration
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="asyncResult"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This methods executes on a WinRM thread
|
|
/// </remarks>
|
|
internal static void PowerShellInvocationCallback(IAsyncResult asyncResult)
|
|
{
|
|
object asyncState = asyncResult.AsyncState;
|
|
Dbg.Assert(asyncState != null, "AsyncState not returned correctly by PowerShell");
|
|
RunCommandsArguments args = asyncState as RunCommandsArguments;
|
|
Dbg.Assert(args != null, "AsyncState casting to RunCommandsArguments failed");
|
|
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback for Executing command using PowerShell - either inproc or remote");
|
|
bool attemptRetry = false;
|
|
try
|
|
{
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
commandToRun.EndInvoke(asyncResult);
|
|
|
|
// Do any required cleanup here...
|
|
implementationContext.CleanUp();
|
|
|
|
if (commandToRun.HadErrors)
|
|
{
|
|
tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"Errors occurred executing the command."));
|
|
psActivityContext.Failed = true;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
attemptRetry = ProcessException(args, e);
|
|
}
|
|
finally
|
|
{
|
|
if (attemptRetry)
|
|
{
|
|
Interlocked.Decrement(ref psActivityContext.CommandsRunningCount);
|
|
BeginActionRetry(args);
|
|
}
|
|
else
|
|
{
|
|
ReleaseResourcesAndCheckForEnd(commandToRun, args, true, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
/// <param name="runspaceDisconnectedException"></param>
|
|
/// <remarks>
|
|
/// THREADING CONTRACT:
|
|
/// This methods executes on a WinRM thread
|
|
/// </remarks>
|
|
private static void RunspaceDisconnectedCallback(RunCommandsArguments args, Exception runspaceDisconnectedException)
|
|
{
|
|
PSActivityContext psActivityContext = args.PSActivityContext;
|
|
ActivityImplementationContext implementationContext = args.ImplementationContext;
|
|
System.Management.Automation.PowerShell commandToRun = implementationContext.PowerShellInstance;
|
|
Dbg.Assert(runspaceDisconnectedException != null, "Exception needs to be passed to RunspaceDisconnectedCallback");
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
tracer.WriteMessage("Executing callback when remote runspace got disconnected");
|
|
bool attemptRetry = false;
|
|
try
|
|
{
|
|
if (CheckForCancel(psActivityContext)) return;
|
|
|
|
// Do any required cleanup here...
|
|
implementationContext.CleanUp();
|
|
|
|
// if there are any helper commands they need to be disposed
|
|
if (args.HelperCommand != null)
|
|
{
|
|
args.HelperCommand.Dispose();
|
|
args.HelperCommand = null;
|
|
}
|
|
|
|
tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"Runspace disconnected is treated as an errors in executing the command."));
|
|
psActivityContext.Failed = true;
|
|
|
|
throw runspaceDisconnectedException;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
attemptRetry = HandleRunOneCommandException(args, e);
|
|
if (attemptRetry)
|
|
BeginActionRetry(args);
|
|
}
|
|
finally
|
|
{
|
|
RemoveHandlersFromStreams(commandToRun, args);
|
|
RunOneCommandFinally(args, attemptRetry);
|
|
tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity: Finished running command."));
|
|
DecrementRunningCountAndCheckForEnd(psActivityContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle a failure. Check if the attempt has gone over the number of allowed attempts,
|
|
// and if not, sleep for the retry delay.
|
|
static private bool HandleFailure(int attempts, uint? retryCount, uint? retryInterval,
|
|
ActivityImplementationContext implementationContext, string errorId, Exception e, PSActivityContext psActivityContext)
|
|
{
|
|
bool attemptRetry = false;
|
|
|
|
// See if the attempt has gone over the quota
|
|
if (attempts > retryCount.GetValueOrDefault(0))
|
|
{
|
|
// If this was a multi-machine activity, this is an error, not an exception.
|
|
if ((implementationContext.PSComputerName != null) && (implementationContext.PSComputerName.Length > 1))
|
|
{
|
|
if (e != null)
|
|
{
|
|
WriteError(e, errorId, ErrorCategory.InvalidResult, implementationContext.PowerShellInstance.Runspace.ConnectionInfo, psActivityContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (e != null)
|
|
{
|
|
// If they wanted to suspend on error, do so.
|
|
if (psActivityContext.ParameterDefaults.ContainsKey(Constants.PSSuspendOnError) &&
|
|
(((bool) psActivityContext.ParameterDefaults[Constants.PSSuspendOnError]) == true))
|
|
{
|
|
psActivityContext.SuspendOnError = true;
|
|
WriteError(e, errorId, ErrorCategory.InvalidResult, implementationContext.PowerShellInstance.Runspace.ConnectionInfo, psActivityContext);
|
|
}
|
|
else
|
|
{
|
|
lock (psActivityContext.exceptions)
|
|
{
|
|
psActivityContext.exceptions.Add(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// We should do a retry
|
|
else
|
|
{
|
|
// Write / Log that an activity retry was required.
|
|
if (psActivityContext.progress != null)
|
|
{
|
|
string progressActivity = ((Activity) psActivityContext.ActivityObject).DisplayName;
|
|
|
|
if (string.IsNullOrEmpty(progressActivity))
|
|
progressActivity = psActivityContext.ActivityType.Name;
|
|
|
|
string retryMessage = String.Format(CultureInfo.CurrentCulture, Resources.RetryingAction, attempts);
|
|
|
|
ProgressRecord progressRecord = new ProgressRecord(0, progressActivity, retryMessage);
|
|
lock (psActivityContext.progress)
|
|
{
|
|
psActivityContext.progress.Add(progressRecord);
|
|
}
|
|
}
|
|
|
|
if (e != null)
|
|
{
|
|
WriteError(e, errorId, ErrorCategory.InvalidResult, implementationContext.PowerShellInstance.Runspace.ConnectionInfo, psActivityContext);
|
|
}
|
|
|
|
// Reschedule the command for another attempt.
|
|
if (!psActivityContext.IsCanceled)
|
|
attemptRetry = true;
|
|
|
|
// Wait for the retry delay
|
|
for (int currentRetry = 0; currentRetry < retryInterval.GetValueOrDefault(1); currentRetry++)
|
|
{
|
|
if (CheckForCancel(psActivityContext))
|
|
break;
|
|
Thread.Sleep(1000);
|
|
}
|
|
}
|
|
|
|
return attemptRetry;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel the running activity
|
|
/// </summary>
|
|
/// <param name="context">The NativeActivityContext provided by the workflow.</param>
|
|
protected override void Cancel(NativeActivityContext context)
|
|
{
|
|
try
|
|
{
|
|
if (this.bookmarking.Get(context) == true)
|
|
{
|
|
NoPersistHandle handle = this.noPersistHandle.Get(context);
|
|
handle.Enter(context);
|
|
}
|
|
|
|
PSActivityContext psActivityContextInstance = null;
|
|
|
|
HostParameterDefaults hostValues = context.GetExtension<HostParameterDefaults>();
|
|
|
|
if ((hostValues != null) && (hostValues.AsyncExecutionCollection != null))
|
|
{
|
|
Dictionary<string, PSActivityContext> asyncExecutionCollection = hostValues.AsyncExecutionCollection;
|
|
|
|
if (asyncExecutionCollection.ContainsKey(context.ActivityInstanceId))
|
|
{
|
|
psActivityContextInstance = asyncExecutionCollection[context.ActivityInstanceId];
|
|
asyncExecutionCollection.Remove(context.ActivityInstanceId);
|
|
}
|
|
|
|
}
|
|
|
|
if (psActivityContextInstance == null)
|
|
{
|
|
psActivityContextInstance = psActivityContextImplementationVariable.Get(context);
|
|
}
|
|
|
|
psActivityContextInstance.IsCanceled = true;
|
|
psActivityContextImplementationVariable.Set(context, psActivityContextInstance);
|
|
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity: Executing cancel request."));
|
|
|
|
if (psActivityContextInstance.commandQueue != null && !psActivityContextInstance.commandQueue.IsEmpty)
|
|
{
|
|
foreach(ActivityImplementationContext activityImplementationContext in psActivityContextInstance.commandQueue.ToArray() )
|
|
{
|
|
RunCommandsArguments args = null;
|
|
ArgsTable.TryGetValue(activityImplementationContext.PowerShellInstance.InstanceId, out args);
|
|
|
|
if (args != null)
|
|
{
|
|
RemoveHandlersFromStreams(activityImplementationContext.PowerShellInstance, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (psActivityContextInstance.runningCommands != null && psActivityContextInstance.runningCommands.Count > 0)
|
|
{
|
|
lock (psActivityContextInstance.runningCommands)
|
|
{
|
|
foreach (System.Management.Automation.PowerShell command in psActivityContextInstance.runningCommands.Keys)
|
|
{
|
|
RunCommandsArguments args = null;
|
|
ArgsTable.TryGetValue(command.InstanceId, out args);
|
|
|
|
if (args != null)
|
|
{
|
|
RemoveHandlersFromStreams(command, args);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
psActivityContextInstance.Cancel();
|
|
|
|
}
|
|
finally
|
|
{
|
|
// Marking the workflow state as cancelled.
|
|
context.MarkCanceled();
|
|
}
|
|
}
|
|
|
|
private static void UnregisterAndReleaseRunspace(Runspace runspace, PSWorkflowHost workflowHost, PSActivityContext psActivityContext)
|
|
{
|
|
Dbg.Assert(runspace.ConnectionInfo != null, "Only remote runspaces can be passed to UnregisterAndReleaseRunspace");
|
|
Dbg.Assert(workflowHost != null, "For remote types PSWorkflowHost should be passed");
|
|
Dbg.Assert(psActivityContext != null, "For remote types, activity context must be passed");
|
|
RunCommandsArguments args;
|
|
ArgsTableForRunspaces.TryRemove(runspace.InstanceId, out args);
|
|
args = null;
|
|
if (psActivityContext.HandleRunspaceStateChanged != null)
|
|
runspace.StateChanged -= psActivityContext.HandleRunspaceStateChanged;
|
|
workflowHost.RemoteRunspaceProvider.ReleaseRunspace(runspace);
|
|
}
|
|
|
|
private static void CloseRunspace(Runspace runspace, int commandType = CommandRunInProc, PSWorkflowHost workflowHost = null, PSActivityContext psActivityContext = null)
|
|
{
|
|
switch (commandType)
|
|
{
|
|
case CommandRunInProc:
|
|
Dbg.Assert(workflowHost != null, "For commands to run in proc PSWorkflowHost should be passed");
|
|
workflowHost.LocalRunspaceProvider.ReleaseRunspace(runspace);
|
|
break;
|
|
|
|
case CommandRunRemotely:
|
|
UnregisterAndReleaseRunspace(runspace, workflowHost, psActivityContext);
|
|
break;
|
|
|
|
case CimCommandRunInProc:
|
|
Dbg.Assert(workflowHost != null, "For CIM commands to run in proc, PSWorkflowHost should be passed");
|
|
workflowHost.LocalRunspaceProvider.ReleaseRunspace(runspace);
|
|
break;
|
|
|
|
case CommandRunOutOfProc:
|
|
case RunInProcNoRunspace:
|
|
{
|
|
// do nothing
|
|
}
|
|
break;
|
|
default:
|
|
Dbg.Assert(false, "Attempting to close runspace for a command type that is not supported");
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal static void CloseRunspaceAndDisposeCommand(System.Management.Automation.PowerShell currentCommand, PSWorkflowHost WorkflowHost, PSActivityContext psActivityContext, int commandType)
|
|
{
|
|
Dbg.Assert(commandType != RunInProcNoRunspace, "Disposing runspaces should not occur for WMI");
|
|
if (!currentCommand.IsRunspaceOwner && (currentCommand.Runspace.RunspaceStateInfo.State == RunspaceState.Opened || currentCommand.Runspace.RunspaceStateInfo.State == RunspaceState.Disconnected))
|
|
CloseRunspace(currentCommand.Runspace, commandType, WorkflowHost, psActivityContext);
|
|
currentCommand.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the stream and ubiquitous parameter information from the hosting application.
|
|
/// These must be passed in as "Streams" and "UbiquitousParameters", respectively.
|
|
/// </summary>
|
|
/// <param name="metadata">The metadata provided by the hosting application.</param>
|
|
protected override void CacheMetadata(NativeActivityMetadata metadata)
|
|
{
|
|
base.CacheMetadata(metadata);
|
|
|
|
metadata.AddImplementationVariable(this.bookmarking);
|
|
metadata.AddImplementationVariable(this.noPersistHandle);
|
|
metadata.AddImplementationChild(this.cancelTimer);
|
|
metadata.AddImplementationVariable(psRunningTimeoutDelayActivityInstanceVar);
|
|
metadata.AddImplementationChild(this.terminateActivity);
|
|
metadata.AddImplementationChild(this.suspendActivity);
|
|
metadata.AddDefaultExtensionProvider(() => new PSWorkflowInstanceExtension());
|
|
|
|
this.ParameterDefaults = new Variable<Dictionary<string, object>>();
|
|
metadata.AddImplementationVariable(this.ParameterDefaults);
|
|
|
|
Tracer.WriteMessage(this.GetType().Name, "CacheMetadata", Guid.Empty,
|
|
"Adding PowerShell specific extensions to metadata, CommonParameters are {0} available.",
|
|
"not");
|
|
|
|
metadata.AddImplementationVariable(psActivityContextImplementationVariable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The event fired when the PSActivity-derived activity has initialized its its instance
|
|
/// of System.Management.Automation.PowerShell, but has not yet invoked its action. This event
|
|
/// is for diagnostic, tracing, and testing purposes.
|
|
/// </summary>
|
|
internal static event ActivityCreatedEventHandler ActivityCreated;
|
|
|
|
private static void OnActivityCreated(Object sender, ActivityCreatedEventArgs e)
|
|
{
|
|
if (ActivityCreated != null)
|
|
{
|
|
ActivityCreated(sender, e);
|
|
}
|
|
}
|
|
|
|
internal static bool IsActivityInlineScript(Activity activity)
|
|
{
|
|
return String.Equals(activity.GetType().FullName, "Microsoft.PowerShell.Activities.InlineScript", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
internal bool RunWithCustomRemoting(ActivityContext context)
|
|
{
|
|
if (typeof(PSRemotingActivity).IsAssignableFrom(GetType()))
|
|
{
|
|
PSRemotingActivity remotingActivity = (PSRemotingActivity) this;
|
|
if (remotingActivity.PSRemotingBehavior.Get(context) == RemotingBehavior.Custom)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#region Check In-Proc vs Out-of-Proc
|
|
|
|
/// <summary>
|
|
/// Determine if this activity should be run in or out of process
|
|
/// when run locally/
|
|
/// </summary>
|
|
/// <param name="context">The native activity context for this workflow instance</param>
|
|
/// <returns>True if it should be run in process with the workflow engine.</returns>
|
|
protected bool GetRunInProc(ActivityContext context)
|
|
{
|
|
HostParameterDefaults defaults = context.GetExtension<HostParameterDefaults>();
|
|
if (this is PSGeneratedCIMActivity)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Look to see if there is a PSRunInProc variable in scope...
|
|
foreach (System.ComponentModel.PropertyDescriptor property in context.DataContext.GetProperties())
|
|
{
|
|
if (string.Equals(property.DisplayName, WorkflowPreferenceVariables.PSRunInProcessPreference, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
object parentId = property.GetValue(context.DataContext);
|
|
if (parentId != null)
|
|
{
|
|
bool variableValue;
|
|
if (LanguagePrimitives.TryConvertTo<bool>(parentId, CultureInfo.InvariantCulture, out variableValue))
|
|
{
|
|
return variableValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PSActivityContext activityContext = psActivityContextImplementationVariable.Get(context);
|
|
PSWorkflowHost workflowHost = GetWorkflowHost(defaults);
|
|
|
|
return workflowHost.PSActivityHostController.RunInActivityController(this);
|
|
}
|
|
|
|
internal static PSWorkflowHost GetWorkflowHost(HostParameterDefaults defaults)
|
|
{
|
|
PSWorkflowHost _psWorkflowHost = null;
|
|
|
|
if ((defaults != null) && (defaults.Runtime != null))
|
|
{
|
|
PSWorkflowHost workflowHost = defaults.Runtime;
|
|
Interlocked.CompareExchange(ref _psWorkflowHost, workflowHost, null);
|
|
|
|
if (_psWorkflowHost != workflowHost)
|
|
{
|
|
System.Diagnostics.Debug.Assert(false, "Workflow host has been set before the incoming value was processed");
|
|
}
|
|
}
|
|
if (_psWorkflowHost == null)
|
|
{
|
|
_psWorkflowHost = DefaultWorkflowHost.Instance;
|
|
}
|
|
|
|
return _psWorkflowHost;
|
|
}
|
|
|
|
#endregion Check In-Proc vs Out-of-Proc
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The delegate invoked when an activity is created.
|
|
/// </summary>
|
|
/// <param name="sender">The PSActivity instance being invoked.</param>
|
|
/// <param name="e">The ActivityCreatedEventArgs associated with this invocation.</param>
|
|
internal delegate void ActivityCreatedEventHandler(object sender, ActivityCreatedEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Holds the event arguments when a new PSActivity instance is created.
|
|
/// </summary>
|
|
internal class ActivityCreatedEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Creates a new ActivityCreatedEventArgs instance.
|
|
/// </summary>
|
|
/// <param name="instance">The instance of System.Management.Automation.PowerShell the activity has prepared.</param>
|
|
internal ActivityCreatedEventArgs(System.Management.Automation.PowerShell instance)
|
|
{
|
|
PowerShellInstance = instance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The instance of System.Management.Automation.PowerShell the activity has prepared.
|
|
/// </summary>
|
|
public System.Management.Automation.PowerShell PowerShellInstance
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holds an instance of System.Management.Automation.PowerShell, and the context it needs to run.
|
|
/// </summary>
|
|
public class ActivityImplementationContext
|
|
{
|
|
/// <summary>
|
|
/// The instance of System.Management.Automation.PowerShell the activity has prepared.
|
|
/// </summary>
|
|
public System.Management.Automation.PowerShell PowerShellInstance
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Any context required by the command.
|
|
/// </summary>
|
|
public Object WorkflowContext
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// context id.
|
|
/// </summary>
|
|
internal int Id
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// DisconnectedRunspaceInstanceId
|
|
/// </summary>
|
|
internal Guid DisconnectedRunspaceInstanceId
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// DisconnectedRunspaceInstanceId
|
|
/// </summary>
|
|
internal bool EnableRemotingActivityAutoResume
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Input stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<PSObject> Input
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The collection to hold the results of the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<PSObject> Result
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Error stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<ErrorRecord> PSError
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Progress stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<ProgressRecord> PSProgress
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Verbose stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<VerboseRecord> PSVerbose
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Debug stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<DebugRecord> PSDebug
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Warning stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<WarningRecord> PSWarning
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Information stream / collection for the activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
|
|
"CA2227:CollectionPropertiesShouldBeReadOnly",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public PSDataCollection<InformationRecord> PSInformation
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The computer name to invoke this activity on.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
|
|
"CA1819:PropertiesShouldNotReturnArrays",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public string[] PSComputerName
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the credential to use in the remote connection.
|
|
/// </summary>
|
|
public PSCredential PSCredential
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the remoting behavior to use when invoking this activity.
|
|
/// </summary>
|
|
public RemotingBehavior PSRemotingBehavior { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines the number of retries that the activity will make to connect to a remote
|
|
/// machine when it encounters an error. The default is to not retry.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public uint? PSConnectionRetryCount { get; set; }
|
|
|
|
/// <summary>
|
|
/// The port to use in a remote connection attempt. The default is:
|
|
/// HTTP: 5985, HTTPS: 5986.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public uint? PSPort { get; set; }
|
|
|
|
/// <summary>
|
|
/// Determines whether to use SSL in the connection attempt. The default is false.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public bool? PSUseSsl { get; set; }
|
|
|
|
/// <summary>
|
|
/// Determines whether to allow redirection by the remote computer. The default is false.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public bool? PSAllowRedirection { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines the remote application name to connect to. The default is "wsman".
|
|
/// </summary>
|
|
public string PSApplicationName { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines the remote configuration name to connect to. The default is "Microsoft.PowerShell".
|
|
/// </summary>
|
|
public string PSConfigurationName { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines the fully-qualified remote URI to connect to. When specified, the PSComputerName,
|
|
/// PSApplicationName, PSConfigurationName, and PSPort are not used.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
|
|
"CA1819:PropertiesShouldNotReturnArrays",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public string[] PSConnectionUri { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines the authentication type to be used in the remote connection.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
|
|
"CA1006:DoNotNestGenericTypesInMemberSignatures",
|
|
Justification = "This is forced by the interaction of PowerShell and Workflow.")]
|
|
public AuthenticationMechanism? PSAuthentication { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines the certificate thumbprint to be used in the remote connection.
|
|
/// </summary>
|
|
public string PSCertificateThumbprint { get; set; }
|
|
|
|
/// <summary>
|
|
/// Defines any session options to be used in the remote connection.
|
|
/// </summary>
|
|
public System.Management.Automation.Remoting.PSSessionOption PSSessionOption { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Forces the activity to return non-serialized objects. Resulting objects
|
|
/// have functional methods and properties (as opposed to serialized versions
|
|
/// of them), but will not survive persistence when the Workflow crashes or is
|
|
/// persisted.
|
|
/// </summary>
|
|
public bool? PSDisableSerialization
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forces the activity to not call the persist functionality, which will be responsible for
|
|
/// persisting the workflow state onto the disk.
|
|
/// </summary>
|
|
public bool? PSPersist
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to append output to Result.
|
|
/// </summary>
|
|
public bool? AppendOutput
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to merge the error data to the output stream
|
|
/// </summary>
|
|
public bool? MergeErrorToOutput
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the maximum amount of time, in seconds, that this activity may run.
|
|
/// The default is unlimited.
|
|
/// </summary>
|
|
public uint? PSActionRunningTimeoutSec
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the delay, in seconds, between connection retry attempts.
|
|
/// The default is one second.
|
|
/// </summary>
|
|
public uint? PSConnectionRetryIntervalSec
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the number of retries that the activity will make when it encounters
|
|
/// an error during execution of its action. The default is to not retry.
|
|
/// </summary>
|
|
public uint? PSActionRetryCount
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the delay, in seconds, between action retry attempts.
|
|
/// The default is one second.
|
|
/// </summary>
|
|
public uint? PSActionRetryIntervalSec
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the PSProgressMessage.
|
|
/// </summary>
|
|
public string PSProgressMessage
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The connection info to use for this command (may be null)
|
|
/// </summary>
|
|
public WSManConnectionInfo ConnectionInfo
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This the list of module names (or paths) that are required to run this Activity successfully.
|
|
/// The default is null.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
|
|
"CA1819:PropertiesShouldNotReturnArrays",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public string[] PSRequiredModules
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The paththat the workflow was imported from.
|
|
/// </summary>
|
|
public string PSWorkflowPath
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to emit verbose output of the activity.
|
|
/// </summary>
|
|
public bool? Verbose
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to emit debug output of the activity.
|
|
/// </summary>
|
|
public bool? Debug
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether to emit whatif output of the activity.
|
|
/// </summary>
|
|
public bool? WhatIf
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines how errors should be handled by the activity.
|
|
/// </summary>
|
|
public ActionPreference? ErrorAction
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines how warnings should be handled by the activity.
|
|
/// </summary>
|
|
public ActionPreference? WarningAction
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines how information messages should be handled by the activity.
|
|
/// </summary>
|
|
public ActionPreference? InformationAction
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
/// <summary>
|
|
/// Policy for activity host that will execute this activity
|
|
/// </summary>
|
|
public PSActivityEnvironment PSActivityEnvironment
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the authentication level to be used with the WMI connection. Valid values are:
|
|
/// -1: Unchanged
|
|
/// 0: Default
|
|
/// 1: None (No authentication in performed.)
|
|
/// 2: Connect (Authentication is performed only when the client establishes a relationship with the application.)
|
|
/// 3: Call (Authentication is performed only at the beginning of each call when the application receives the request.)
|
|
/// 4: Packet (Authentication is performed on all the data that is received from the client.)
|
|
/// 5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.)
|
|
/// 6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.)
|
|
/// </summary>
|
|
public AuthenticationLevel PSAuthenticationLevel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Specifies the impersonation level to use. Valid values are:
|
|
/// 0: Default (reads the local registry for the default impersonation level , which is usually set to "3: Impersonate".)
|
|
/// 1: Anonymous (Hides the credentials of the caller.)
|
|
/// 2: Identify (Allows objects to query the credentials of the caller.)
|
|
/// 3: Impersonate (Allows objects to use the credentials of the caller.)
|
|
/// 4: Delegate (Allows objects to permit other objects to use the credentials of the caller.)
|
|
/// </summary>
|
|
public ImpersonationLevel Impersonation { get; set; }
|
|
|
|
/*
|
|
* Enables all the privileges of the current user before the command makes the WMI call.
|
|
*/
|
|
/// <summary>
|
|
/// Enables all the privileges of the current user before the command makes the WMI call.
|
|
/// </summary>
|
|
public bool EnableAllPrivileges { get; set; }
|
|
|
|
/// <summary>
|
|
/// Specifies the authority to use to authenticate the WMI connection. You can specify
|
|
/// standard NTLM or Kerberos authentication. To use NTLM, set the authority setting
|
|
/// to ntlmdomain:"DomainName", where "DomainName" identifies a valid NTLM domain name.
|
|
/// To use Kerberos, specify kerberos:"DomainName>\ServerName". You cannot include
|
|
/// the authority setting when you connect to the local computer.
|
|
/// </summary>
|
|
public string Authority { get; set; }
|
|
|
|
/// <summary>
|
|
/// When used with the Class parameter, this parameter specifies the WMI repository namespace
|
|
/// where the referenced WMI class is located. When used with the List parameter, it specifies
|
|
/// the namespace from which to gather WMI class information.
|
|
/// </summary>summary>
|
|
public string Namespace { get; set; }
|
|
|
|
/// <summary>
|
|
/// Specifies the preferred locale for WMI objects. Specify the value of the Locale
|
|
/// parameter as an array in the MS_"LCID" format in the preferred order .
|
|
/// </summary>
|
|
public string Locale { get; set; }
|
|
|
|
/// <summary>
|
|
/// CIM Sessions to use for this activity.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
|
|
"CA1819:PropertiesShouldNotReturnArrays",
|
|
Justification = "This is needs to mimic the properties of the PSActivity class.")]
|
|
public CimSession[] CimSession { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Perform any cleanup activities needed by this activity implementation
|
|
/// </summary>
|
|
public virtual void CleanUp()
|
|
{
|
|
}
|
|
}
|
|
|
|
class RetryCount
|
|
{
|
|
//internal int ConnectionAttempts { get; set; }
|
|
internal int ActionAttempts
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defining resuming extension.
|
|
/// </summary>
|
|
public class PSWorkflowInstanceExtension : IWorkflowInstanceExtension
|
|
{
|
|
private WorkflowInstanceProxy instance;
|
|
|
|
/// <summary>
|
|
/// Get all additional extensions.
|
|
/// </summary>
|
|
/// <returns>Retruns no extensions.</returns>
|
|
public IEnumerable<object> GetAdditionalExtensions()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the instance of the worfkow.
|
|
/// </summary>
|
|
/// <param name="instance">The workflow instance proxy.</param>
|
|
public void SetInstance(WorkflowInstanceProxy instance)
|
|
{
|
|
this.instance = instance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin resuming book mark.
|
|
/// </summary>
|
|
/// <param name="bookmark">The bookmark where it will be resumed.</param>
|
|
/// <param name="value">The value which need to be passed to the bookmark.</param>
|
|
/// <param name="callback">The call back function when resuming the bookmark.</param>
|
|
/// <param name="state">The state of the aysn call.</param>
|
|
/// <returns>Returns the result of async call.</returns>
|
|
public IAsyncResult BeginResumeBookmark(Bookmark bookmark, object value, AsyncCallback callback, object state)
|
|
{
|
|
return instance.BeginResumeBookmark(bookmark, value, callback, state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// End resuming bookmark.
|
|
/// </summary>
|
|
/// <param name="asyncResult">The result of asyc all.</param>
|
|
/// <returns>Returns the bookmark resumption result.</returns>
|
|
public BookmarkResumptionResult EndResumeBookmark(IAsyncResult asyncResult)
|
|
{
|
|
return instance.EndResumeBookmark(asyncResult);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores information about an activity argument
|
|
/// </summary>
|
|
public sealed class PSActivityArgumentInfo
|
|
{
|
|
/// <summary>
|
|
/// The name of the argument.
|
|
/// </summary>
|
|
public string Name { get; set; }
|
|
|
|
/// <summary>
|
|
/// The actual argument.
|
|
/// </summary>
|
|
public Argument Value { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Abstract base contining the common members and invocation code for the WMI cmdlets.
|
|
/// </summary>
|
|
public abstract class WmiActivity : PSActivity
|
|
{
|
|
/// <summary>
|
|
/// The computer name to invoke this activity on.
|
|
/// </summary>
|
|
[ConnectivityCategory]
|
|
[DefaultValue(null)]
|
|
public InArgument<string[]> PSComputerName
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the credential to use in the remote connection.
|
|
/// </summary>
|
|
[ConnectivityCategory]
|
|
[DefaultValue(null)]
|
|
public InArgument<PSCredential> PSCredential
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the authentication level to be used with the WMI connection. Valid values are:
|
|
/// -1: Unchanged
|
|
/// 0: Default
|
|
/// 1: None (No authentication in performed.)
|
|
/// 2: Connect (Authentication is performed only when the client establishes a relationship with the application.)
|
|
/// 3: Call (Authentication is performed only at the beginning of each call when the application receives the request.)
|
|
/// 4: Packet (Authentication is performed on all the data that is received from the client.)
|
|
/// 5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.)
|
|
/// 6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.)
|
|
/// </summary>
|
|
[ConnectivityCategory]
|
|
[DefaultValue(null)]
|
|
public AuthenticationLevel PSAuthenticationLevel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Specifies the impersonation level to use. Valid values are:
|
|
/// 0: Default (reads the local registry for the default impersonation level , which is usually set to "3: Impersonate".)
|
|
/// 1: Anonymous (Hides the credentials of the caller.)
|
|
/// 2: Identify (Allows objects to query the credentials of the caller.)
|
|
/// 3: Impersonate (Allows objects to use the credentials of the caller.)
|
|
/// 4: Delegate (Allows objects to permit other objects to use the credentials of the caller.)
|
|
/// </summary>
|
|
[ConnectivityCategory]
|
|
[DefaultValue(null)]
|
|
public ImpersonationLevel Impersonation { get; set; }
|
|
|
|
/*
|
|
* Enables all the privileges of the current user before the command makes the WMI call.
|
|
*/
|
|
/// <summary>
|
|
/// Enables all the privileges of the current user before the command makes the WMI call.
|
|
/// </summary>
|
|
[ConnectivityCategory]
|
|
[DefaultValue(null)]
|
|
public bool EnableAllPrivileges { get; set; }
|
|
|
|
/// <summary>
|
|
/// Specifies the authority to use to authenticate the WMI connection. You can specify
|
|
/// standard NTLM or Kerberos authentication. To use NTLM, set the authority setting
|
|
/// to ntlmdomain:"DomainName", where "DomainName" identifies a valid NTLM domain name.
|
|
/// To use Kerberos, specify kerberos:"DomainName>\ServerName". You cannot include
|
|
/// the authority setting when you connect to the local computer.
|
|
/// </summary>
|
|
[ConnectivityCategory]
|
|
[DefaultValue(null)]
|
|
public string Authority { get; set; }
|
|
|
|
/// <summary>
|
|
/// When used with the Class parameter, this parameter specifies the WMI repository namespace
|
|
/// where the referenced WMI class is located. When used with the List parameter, it specifies
|
|
/// the namespace from which to gather WMI class information.
|
|
/// </summary>summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
public InArgument<string> Namespace { get; set; }
|
|
|
|
/// <summary>
|
|
/// Specifies the preferred locale for WMI objects. Specify the value of the Locale
|
|
/// parameter as an array in the MS_"LCID" format in the preferred order .
|
|
/// </summary>
|
|
[BehaviorCategory]
|
|
[DefaultValue(null)]
|
|
public string Locale { get; set; }
|
|
|
|
/// <summary>
|
|
/// Generic version of the function to handle value types
|
|
/// </summary>
|
|
/// <typeparam name="T">THe type of the intende argument</typeparam>
|
|
/// <param name="parameterName"></param>
|
|
/// <param name="parameterDefaults"></param>
|
|
/// <returns></returns>
|
|
protected T GetUbiquitousParameter<T>(string parameterName, Dictionary<string, object> parameterDefaults)
|
|
{
|
|
if (ParameterDefaults != null && parameterDefaults.ContainsKey(parameterName))
|
|
return (T)parameterDefaults[parameterName];
|
|
else
|
|
return default(T);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets to execute the command that was passed in.
|
|
/// </summary>
|
|
/// <param name="context"></param>
|
|
/// <param name="name"></param>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
|
protected System.Management.Automation.PowerShell GetWmiCommandCore(NativeActivityContext context, string name)
|
|
{
|
|
System.Management.Automation.PowerShell command;
|
|
command = System.Management.Automation.PowerShell.Create().AddCommand(name);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: WMI Command '{1}'.",
|
|
context.ActivityInstanceId, name));
|
|
|
|
|
|
if (Impersonation != ImpersonationLevel.Default)
|
|
{
|
|
command.AddParameter("Impersonation", Impersonation);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Setting parameter {1} to {2}.",
|
|
context.ActivityInstanceId, "Impersonation", Impersonation));
|
|
|
|
}
|
|
|
|
Dictionary<string, object> parameterDefaults = context.GetValue<Dictionary<string, object>>(this.ParameterDefaults);
|
|
|
|
if (PSAuthenticationLevel != AuthenticationLevel.Default)
|
|
{
|
|
command.AddParameter("Authentication", PSAuthenticationLevel);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Setting parameter {1} to {2}.",
|
|
context.ActivityInstanceId, "Authentication", PSAuthenticationLevel));
|
|
}
|
|
else if (GetUbiquitousParameter<AuthenticationLevel>("PSAuthenticationLevel", parameterDefaults) != AuthenticationLevel.Default)
|
|
{
|
|
var authLevel = GetUbiquitousParameter<AuthenticationLevel>("PSAuthenticationLevel", parameterDefaults);
|
|
command.AddParameter("Authentication", authLevel);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: Setting parameter {1} to {2} from ubiquitious parameters.",
|
|
context.ActivityInstanceId, "AuthenticationLevel", authLevel));
|
|
}
|
|
|
|
if (Locale != null)
|
|
{
|
|
command.AddParameter("Locale", Locale);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: Setting parameter {1} to {2}.",
|
|
context.ActivityInstanceId, "Locale", Locale));
|
|
|
|
}
|
|
|
|
if (EnableAllPrivileges)
|
|
{
|
|
command.AddParameter("EnableAllPrivileges", EnableAllPrivileges);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: Setting parameter {1} to {2}.",
|
|
context.ActivityInstanceId, "EnableAllPrivileges", EnableAllPrivileges));
|
|
}
|
|
|
|
if (Authority != null)
|
|
{
|
|
command.AddParameter("Authority", Authority);
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture,
|
|
"PowerShell activity ID={0}: Setting parameter {1} to {2}.", context.ActivityInstanceId, "Authority", Authority));
|
|
}
|
|
|
|
if (Namespace.Get(context) != null)
|
|
{
|
|
command.AddParameter("Namespace", Namespace.Get(context));
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Setting parameter {1} to {2}.",
|
|
context.ActivityInstanceId, "Namespace", Namespace.Get(context)));
|
|
}
|
|
|
|
// WMI does it's own remoting so we need to handle the PSCredential/Credential parameter
|
|
// explicitly ourselves.
|
|
if (PSCredential.Get(context) != null)
|
|
{
|
|
command.AddParameter("Credential", PSCredential.Get(context));
|
|
Tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "PowerShell activity ID={0}: Setting parameter {1} to {2}.",
|
|
context.ActivityInstanceId, "Credential", PSCredential.Get(context)));
|
|
}
|
|
|
|
return command;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform necessary steps to prepare the WMI commands
|
|
/// </summary>
|
|
/// <param name="context">The activity context to use</param>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
|
protected override List<ActivityImplementationContext> GetImplementation(NativeActivityContext context)
|
|
{
|
|
List<ActivityImplementationContext> commands = new List<ActivityImplementationContext>();
|
|
string[] computernames = PSComputerName.Get(context);
|
|
|
|
// Configure the remote connectivity options
|
|
if (computernames == null || computernames.Length == 0)
|
|
{
|
|
computernames = new string[] { "localhost" };
|
|
}
|
|
|
|
foreach (string computername in computernames)
|
|
{
|
|
// Create the PowerShell instance, and add the command to it.
|
|
ActivityImplementationContext implementationContext = GetPowerShell(context);
|
|
System.Management.Automation.PowerShell invoker = implementationContext.PowerShellInstance;
|
|
|
|
// Don't add the conputer if it's empty or localhost...
|
|
if (!String.IsNullOrEmpty(computername) && !String.Equals(computername, "localhost", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
invoker.AddParameter("ComputerName", computername);
|
|
}
|
|
|
|
commands.Add(
|
|
new ActivityImplementationContext() { PowerShellInstance = invoker }
|
|
);
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of ICommandRuntime for running the WMI cmdlets in
|
|
/// workflow without PowerShell.
|
|
/// </summary>
|
|
internal class DirectExecutionActivitiesCommandRuntime : ICommandRuntime
|
|
{
|
|
|
|
/// <summary>
|
|
/// Constructs an instance of the default ICommandRuntime object
|
|
/// that will write objects into the arraylist that was passed.
|
|
/// </summary>
|
|
public DirectExecutionActivitiesCommandRuntime(PSDataCollection<PSObject> output, ActivityImplementationContext implementationContext, Type cmdletType)
|
|
{
|
|
if (output == null) throw new ArgumentNullException("output");
|
|
if (implementationContext == null) throw new ArgumentNullException("implementationContext");
|
|
if (cmdletType == null) throw new ArgumentNullException("cmdletType");
|
|
|
|
_output = output;
|
|
_implementationContext = implementationContext;
|
|
_cmdletType = cmdletType;
|
|
}
|
|
|
|
PSDataCollection<PSObject> _output;
|
|
ActivityImplementationContext _implementationContext;
|
|
Type _cmdletType;
|
|
|
|
/// <summary>
|
|
/// THe error record stream
|
|
/// </summary>
|
|
public PSDataCollection<ErrorRecord> Error { get; set; }
|
|
|
|
/// <summary>
|
|
/// The progress record stream
|
|
/// </summary>
|
|
public PSDataCollection<ProgressRecord> Progress { get; set; }
|
|
|
|
/// <summary>
|
|
/// The verbose record stream
|
|
/// </summary>
|
|
public PSDataCollection<VerboseRecord> Verbose { get; set; }
|
|
|
|
/// <summary>
|
|
/// The warning record stream
|
|
/// </summary>
|
|
public PSDataCollection<WarningRecord> Warning { get; set; }
|
|
|
|
/// <summary>
|
|
/// The debug output stream
|
|
/// </summary>
|
|
public PSDataCollection<DebugRecord> Debug { get; set; }
|
|
|
|
/// <summary>
|
|
/// The information record stream
|
|
/// </summary>
|
|
public PSDataCollection<InformationRecord> Information { get; set; }
|
|
|
|
/// <summary>
|
|
/// Return the instance of PSHost - null by default.
|
|
/// </summary>
|
|
public PSHost Host { get { return null; } }
|
|
|
|
#region Write
|
|
/// <summary>
|
|
/// Implementation of WriteDebug - just discards the input.
|
|
/// </summary>
|
|
/// <param name="text">Text to write</param>
|
|
public void WriteDebug(string text)
|
|
{
|
|
if (Debug == null)
|
|
return;
|
|
|
|
if (text != null)
|
|
{
|
|
Debug.Add(new DebugRecord(text));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default implementation of WriteError - if the error record contains
|
|
/// an exceptin then that exception will be thrown. If not, then an
|
|
/// InvalidOperationException will be constructed and thrown.
|
|
/// </summary>
|
|
/// <param name="errorRecord">Error record instance to process</param>
|
|
public void WriteError(ErrorRecord errorRecord)
|
|
{
|
|
if (Error == null)
|
|
return;
|
|
|
|
ErrorRecord updatedErrorRecord = new ErrorRecord(errorRecord.Exception, errorRecord.FullyQualifiedErrorId + ',' + _cmdletType.FullName, errorRecord.CategoryInfo.Category, errorRecord.TargetObject);
|
|
|
|
ActionPreference preference = (_implementationContext.ErrorAction == null) ?
|
|
ActionPreference.Continue : _implementationContext.ErrorAction.Value;
|
|
|
|
switch (preference)
|
|
{
|
|
case ActionPreference.SilentlyContinue:
|
|
case ActionPreference.Ignore:
|
|
break;
|
|
case ActionPreference.Inquire:
|
|
case ActionPreference.Continue:
|
|
if (errorRecord != null)
|
|
{
|
|
Error.Add(updatedErrorRecord);
|
|
}
|
|
break;
|
|
case ActionPreference.Stop:
|
|
ThrowTerminatingError(updatedErrorRecord);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default implementation of WriteObject - adds the object to the arraylist
|
|
/// passed to the objects constructor.
|
|
/// </summary>
|
|
/// <param name="sendToPipeline">Object to write</param>
|
|
public void WriteObject(object sendToPipeline)
|
|
{
|
|
_output.Add(PSObject.AsPSObject(sendToPipeline));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write objects to the output collection
|
|
/// </summary>
|
|
/// <param name="sendToPipeline">Object to write</param>
|
|
/// <param name="enumerateCollection">If true, the collection is enumerated, otherwise
|
|
/// it's written as a scalar.
|
|
/// </param>
|
|
public void WriteObject(object sendToPipeline, bool enumerateCollection)
|
|
{
|
|
if (enumerateCollection)
|
|
{
|
|
IEnumerator e = LanguagePrimitives.GetEnumerator(sendToPipeline);
|
|
if (e == null)
|
|
{
|
|
WriteObject(sendToPipeline);
|
|
}
|
|
else
|
|
{
|
|
while (e.MoveNext())
|
|
{
|
|
WriteObject(e.Current);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WriteObject(sendToPipeline);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a progress record
|
|
/// </summary>
|
|
/// <param name="progressRecord">progress record to write.</param>
|
|
public void WriteProgress(ProgressRecord progressRecord)
|
|
{
|
|
WriteProgress(1, progressRecord);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a progress record, ignore the id field
|
|
/// </summary>
|
|
/// <param name="sourceId">Source ID to write for</param>
|
|
/// <param name="progressRecord">record to write.</param>
|
|
public void WriteProgress(Int64 sourceId, ProgressRecord progressRecord)
|
|
{
|
|
if (Progress == null)
|
|
return;
|
|
|
|
if (progressRecord != null)
|
|
{
|
|
Progress.Add(progressRecord);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a verbose record
|
|
/// </summary>
|
|
/// <param name="text">Text to write.</param>
|
|
public void WriteVerbose(string text)
|
|
{
|
|
if (_implementationContext.Verbose != true)
|
|
return;
|
|
|
|
if (Verbose == null)
|
|
return;
|
|
|
|
if (text != null)
|
|
{
|
|
Verbose.Add(new VerboseRecord(text));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a warning record
|
|
/// </summary>
|
|
/// <param name="text">Text to write.</param>
|
|
public void WriteWarning(string text)
|
|
{
|
|
if (_implementationContext.WarningAction != ActionPreference.Continue)
|
|
return;
|
|
|
|
if (Warning == null)
|
|
return;
|
|
|
|
if (text != null)
|
|
{
|
|
Warning.Add(new WarningRecord(text));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a information record
|
|
/// </summary>
|
|
/// <param name="record">Record to write.</param>
|
|
public void WriteInformation(InformationRecord record)
|
|
{
|
|
if (_implementationContext.InformationAction != ActionPreference.Continue)
|
|
return;
|
|
|
|
if (Information == null)
|
|
return;
|
|
|
|
if (record != null)
|
|
{
|
|
Information.Add(record);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write command detail info to the eventlog.
|
|
/// </summary>
|
|
/// <param name="text">Text to write.</param>
|
|
public void WriteCommandDetail(string text)
|
|
{
|
|
PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource();
|
|
tracer.WriteMessage(text);
|
|
}
|
|
|
|
#endregion Write
|
|
|
|
#region Should
|
|
/// <summary>
|
|
/// Default implementation - always returns true.
|
|
/// </summary>
|
|
/// <param name="target">ignored</param>
|
|
/// <returns>true</returns>
|
|
public bool ShouldProcess(string target) { return true; }
|
|
/// <summary>
|
|
/// Default implementation - always returns true.
|
|
/// </summary>
|
|
/// <param name="target">ignored</param>
|
|
/// <param name="action">ignored</param>
|
|
/// <returns>true</returns>
|
|
public bool ShouldProcess(string target, string action) { return true; }
|
|
/// <summary>
|
|
/// Default implementation - always returns true.
|
|
/// </summary>
|
|
/// <param name="verboseDescription">ignored</param>
|
|
/// <param name="verboseWarning">ignored</param>
|
|
/// <param name="caption">ignored</param>
|
|
/// <returns>true</returns>
|
|
public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption) { return true; }
|
|
/// <summary>
|
|
/// Default implementation - always returns true.
|
|
/// </summary>
|
|
/// <param name="verboseDescription">ignored</param>
|
|
/// <param name="verboseWarning">ignored</param>
|
|
/// <param name="caption">ignored</param>
|
|
/// <param name="shouldProcessReason">ignored</param>
|
|
/// <returns>true</returns>
|
|
public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason) { shouldProcessReason = ShouldProcessReason.None; return true; }
|
|
/// <summary>
|
|
/// Default implementation - always returns true.
|
|
/// </summary>
|
|
/// <param name="query">ignored</param>
|
|
/// <param name="caption">ignored</param>
|
|
/// <returns>true</returns>
|
|
public bool ShouldContinue(string query, string caption) { return true; }
|
|
/// <summary>
|
|
/// Default implementation - always returns true.
|
|
/// </summary>
|
|
/// <param name="query">ignored</param>
|
|
/// <param name="caption">ignored</param>
|
|
/// <param name="yesToAll">ignored</param>
|
|
/// <param name="noToAll">ignored</param>
|
|
/// <returns>true</returns>
|
|
public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll) { return true; }
|
|
#endregion Should
|
|
|
|
#region Transaction Support
|
|
/// <summary>
|
|
/// Returns true if a transaction is available and active.
|
|
/// </summary>
|
|
public bool TransactionAvailable() { return false; }
|
|
|
|
/// <summary>
|
|
/// Gets an object that surfaces the current PowerShell transaction.
|
|
/// When this object is disposed, PowerShell resets the active transaction
|
|
/// </summary>
|
|
public PSTransactionContext CurrentPSTransaction
|
|
{
|
|
get
|
|
{
|
|
// We want to throw in this situation, and want to use a
|
|
// property because it mimics the C# using(TransactionScope ...) syntax
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
#endregion Transaction Support
|
|
|
|
#region Misc
|
|
/// <summary>
|
|
/// Implementation of the dummy default ThrowTerminatingError API - it just
|
|
/// does what the base implementation does anyway - rethrow the exception
|
|
/// if it exists, otherwise throw an invalid operation exception.
|
|
/// </summary>
|
|
/// <param name="errorRecord">The error record to throw</param>
|
|
public void ThrowTerminatingError(ErrorRecord errorRecord)
|
|
{
|
|
if (errorRecord.Exception != null)
|
|
{
|
|
throw errorRecord.Exception;
|
|
}
|
|
else
|
|
{
|
|
throw new System.InvalidOperationException(errorRecord.ToString());
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Suspends the current workflow.
|
|
/// </summary>
|
|
internal class SuspendOnError : NativeActivity
|
|
{
|
|
/// <summary>
|
|
/// Returns true if the activity can induce an idle.
|
|
/// </summary>
|
|
protected override bool CanInduceIdle { get { return true; } }
|
|
|
|
/// <summary>
|
|
/// Invokes the activity
|
|
/// </summary>
|
|
/// <param name="context">The activity context.</param>
|
|
/// <returns>True if the given argument is set.</returns>
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
string bookmarkname = PSActivity.PSSuspendBookmarkPrefix;
|
|
bookmarkname += Guid.NewGuid().ToString().Replace("-", "_");
|
|
|
|
context.CreateBookmark(bookmarkname, BookmarkResumed);
|
|
}
|
|
|
|
private void BookmarkResumed(NativeActivityContext context, Bookmark bookmark, object value)
|
|
{
|
|
}
|
|
}
|
|
}
|