diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/AssemblyInfo.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/AssemblyInfo.cs
new file mode 100644
index 000000000..2d9b3d98b
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/AssemblyInfo.cs
@@ -0,0 +1,25 @@
+using System.Reflection;
+using System.Security.Permissions;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyInformationalVersionAttribute (@"10.0.10011.16384")]
+[assembly: ReliabilityContractAttribute(Consistency.MayCorruptAppDomain, Cer.MayFail)]
+[assembly: AssemblyTitle("Microsoft.PowerShell.Workflow.ServiceCore")]
+[assembly: AssemblyDescription("Microsoft.PowerShell.Workflow.ServiceCore")]
+
+[module: SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant")]
+[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope="resource", Target="Resources.resources", MessageId="InlineScript")]
+[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope="resource", Target="Resources.resources", MessageId="foreach")]
+[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope="resource", Target="Resources.resources", MessageId="suspendable")]
+
+[assembly:InternalsVisibleTo("M3PTestAutomation,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly:InternalsVisibleTo("M3PTestCommon,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly:InternalsVisibleTo("VSTS.M3PUnitTest,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly:InternalsVisibleTo("VSTS.ActivityTests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly:InternalsVisibleTo("ActivityTests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100eb57e8072e5fa11a7783bde0003e38bec8272f81d965c161b33b2f45404c9c9177a682ee7049400723fc456ea69b638514943375f7c594027477f9d17e8c3535e6491f382308fbf1c7795413456ac88c0c9881ef49ab9789919f5eeabc9149ccb800bbef8f04103ea24b9be1fb923006b0a272c0d8d928bfdbe947a0f3df4a99")]
+[assembly:InternalsVisibleTo("WorkflowCoreTest,PublicKey=0024000004800000940000000602000000240000525341310004000001000100eb57e8072e5fa11a7783bde0003e38bec8272f81d965c161b33b2f45404c9c9177a682ee7049400723fc456ea69b638514943375f7c594027477f9d17e8c3535e6491f382308fbf1c7795413456ac88c0c9881ef49ab9789919f5eeabc9149ccb800bbef8f04103ea24b9be1fb923006b0a272c0d8d928bfdbe947a0f3df4a99")]
+[assembly:InternalsVisibleTo("WorkflowJobsTest,PublicKey=0024000004800000940000000602000000240000525341310004000001000100eb57e8072e5fa11a7783bde0003e38bec8272f81d965c161b33b2f45404c9c9177a682ee7049400723fc456ea69b638514943375f7c594027477f9d17e8c3535e6491f382308fbf1c7795413456ac88c0c9881ef49ab9789919f5eeabc9149ccb800bbef8f04103ea24b9be1fb923006b0a272c0d8d928bfdbe947a0f3df4a99")]
+
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityCategories.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityCategories.cs
new file mode 100644
index 000000000..dde7af650
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityCategories.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Activities;
+using System.ComponentModel;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Collections.ObjectModel;
+using System.ComponentModel.Design;
+using Microsoft.PowerShell.Workflow;
+
+namespace Microsoft.PowerShell.Activities
+{
+ ///
+ /// Category attribute for "Activity-Specific Parameters"
+ ///
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
+ public sealed class ParameterSpecificCategoryAttribute : CategoryAttribute
+ {
+ ///
+ /// Creates the attribute.
+ ///
+ public ParameterSpecificCategoryAttribute() : base("") { }
+
+ ///
+ /// Gets a localized version of the attribute description.
+ ///
+ /// Not used.
+ /// A localized version of the attribute description
+ protected override string GetLocalizedString(string value)
+ {
+ return Resources.ActivityParameterGroup;
+ }
+ }
+
+ ///
+ /// Category attribute for "Input and Output"
+ ///
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
+ public sealed class InputAndOutputCategoryAttribute : CategoryAttribute
+ {
+ ///
+ /// Creates the attribute.
+ ///
+ public InputAndOutputCategoryAttribute() : base("") { }
+
+ ///
+ /// Gets a localized version of the attribute description.
+ ///
+ /// Not used.
+ /// A localized version of the attribute description
+ protected override string GetLocalizedString(string value)
+ {
+ return Resources.InputAndOutputGroup;
+ }
+ }
+
+ ///
+ /// Category attribute for "Behavior"
+ ///
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
+ public sealed class BehaviorCategoryAttribute : CategoryAttribute
+ {
+ ///
+ /// Creates the attribute.
+ ///
+ public BehaviorCategoryAttribute() : base("") { }
+
+ ///
+ /// Gets a localized version of the attribute description.
+ ///
+ /// Not used.
+ /// A localized version of the attribute description
+ protected override string GetLocalizedString(string value)
+ {
+ return CategoryAttribute.Behavior.Category;
+ }
+ }
+
+ ///
+ /// Category attribute for "Connectivity"
+ ///
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
+ public sealed class ConnectivityCategoryAttribute : CategoryAttribute
+ {
+ ///
+ /// Creates the attribute.
+ ///
+ public ConnectivityCategoryAttribute() : base("") { }
+
+ ///
+ /// Gets a localized version of the attribute description.
+ ///
+ /// Not used.
+ /// A localized version of the attribute description
+ protected override string GetLocalizedString(string value)
+ {
+ return Resources.ConnectivityGroup;
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityHostManager.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityHostManager.cs
new file mode 100644
index 000000000..cf5f1f3f0
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityHostManager.cs
@@ -0,0 +1,673 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Management.Automation.Runspaces;
+using System.Threading;
+using System.Management.Automation;
+using Microsoft.PowerShell.Activities;
+using System.Management.Automation.Tracing;
+using System.Activities;
+using System.Management.Automation.PerformanceData;
+using System.Management.Automation.Remoting;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ internal class ConnectionAsyncResult : IAsyncResult
+ {
+ #region Private Members
+
+ private readonly object _state;
+ private readonly AsyncCallback _callback;
+ private bool _isCompleted;
+ private ManualResetEvent _completedWaitHandle;
+ private readonly object _syncObject = new object();
+ private Exception _exception;
+ private readonly Guid _ownerId;
+ #endregion Private Members
+
+ #region Internal
+
+ internal ConnectionAsyncResult(object state, AsyncCallback callback, Guid ownerId)
+ {
+ _state = state;
+ _ownerId = ownerId;
+ _callback = callback;
+ }
+
+ internal Object State
+ {
+ get { return _state; }
+ }
+
+ internal AsyncCallback Callback
+ {
+ get { return _callback; }
+ }
+
+ internal ActivityInvoker Invoker { get; set; }
+
+ ///
+ /// this method is not thread safe
+ ///
+ internal void SetAsCompleted(Exception exception)
+ {
+ if (_isCompleted) return;
+
+ lock (_syncObject)
+ {
+ if (_isCompleted) return;
+
+ _isCompleted = true;
+ _exception = exception;
+ if (_completedWaitHandle != null) _completedWaitHandle.Set();
+ }
+
+ // invoke callback if available
+ if (null != _callback)
+ _callback(this);
+ }
+
+ internal Guid OwnerId
+ {
+ get { return _ownerId; }
+ }
+
+ internal void EndInvoke()
+ {
+ AsyncWaitHandle.WaitOne();
+ AsyncWaitHandle.Close();
+ _completedWaitHandle = null; // allow early GC
+
+ if (null != _exception) throw _exception;
+ }
+
+ #endregion Internal
+
+ #region Overrides
+
+ ///
+ /// Whether the operation reprensented by this method is
+ /// completed
+ ///
+ public bool IsCompleted
+ {
+ get { return _isCompleted; }
+ }
+
+ ///
+ /// Wait Handle for the operation
+ ///
+ public WaitHandle AsyncWaitHandle
+ {
+ get
+ {
+ if(null == _completedWaitHandle)
+ {
+ lock(_syncObject)
+ {
+ if (null == _completedWaitHandle)
+ {
+ _completedWaitHandle = new ManualResetEvent(_isCompleted);
+ }
+ }
+ }
+
+ return _completedWaitHandle;
+ }
+ }
+
+ ///
+ /// Optional user specified state
+ ///
+ public object AsyncState
+ {
+ get { return _state; }
+ }
+
+ ///
+ /// Whether this operation completed synchronously
+ ///
+ public bool CompletedSynchronously
+ {
+ get { return _completedSynchronously; }
+ set { _completedSynchronously = value; }
+ }
+
+ private bool _completedSynchronously;
+
+ #endregion Overrides
+ }
+
+ ///
+ /// Activity Host Manager which will spawn a set of activity
+ /// host processes until all of them are used after which
+ /// it will start queueing requests
+ ///
+ /// Whether this class needs to remain public should be
+ /// evaluated
+ internal sealed class PSOutOfProcessActivityController : PSActivityHostController
+ {
+ #region Private Members
+
+ ///
+ /// Stack of available host processes
+ ///
+ private readonly Collection _hostProcesses = new Collection();
+
+ ///
+ /// Queue of requests
+ ///
+ private readonly ConcurrentQueue _requests =
+ new ConcurrentQueue();
+ private int _isServicing;
+ private const int Servicing = 1;
+ private const int NotServicing = 0;
+ private const int MinActivityHosts = 1;
+ private int _busyHosts;
+ private readonly Tracer _structuredTracer = new Tracer();
+ private readonly PSWorkflowConfigurationProvider _configuration;
+ private static readonly PSPerfCountersMgr PerfCountersMgr = PSPerfCountersMgr.Instance;
+
+
+ ///
+ /// List of requests that need to be invoked again due to process crash
+ ///
+ private readonly ConcurrentQueue _failedRequests = new ConcurrentQueue();
+
+ #endregion Private Members
+
+ #region Internal Methods
+
+ internal void RunPowerShellInActivityHost(System.Management.Automation.PowerShell powershell, PSDataCollection input,
+ PSDataCollection output, PSActivityEnvironment policy, ConnectionAsyncResult asyncResult)
+ {
+ ActivityInvoker invoker =
+ new ActivityInvoker
+ {
+ Input = input,
+ Output = output,
+ Policy = policy,
+ PowerShell = powershell,
+ AsyncResult = asyncResult
+ };
+
+ _requests.Enqueue(invoker);
+ CheckAndStartServicingThread();
+ }
+
+ ///
+ /// This is a helper method which should only be called
+ /// from test code to reset stuff
+ ///
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is called from the tests")]
+ internal void Reset()
+ {
+ foreach (ActivityHostProcess process in _hostProcesses)
+ {
+ process.Dispose();
+ }
+ _hostProcesses.Clear();
+
+ InitializeActivityHostProcesses();
+ }
+
+ #endregion Internal Methods
+
+ #region Constructors
+
+ internal PSOutOfProcessActivityController(PSWorkflowRuntime runtime)
+ : base(runtime)
+ {
+ if (runtime == null)
+ throw new ArgumentNullException("runtime");
+
+ Debug.Assert(runtime.Configuration != null, "For now only expecting PSWorkflowConfigurationProvider");
+
+ this._configuration = runtime.Configuration;
+ InitializeActivityHostProcesses();
+ }
+
+ #endregion Constructors
+
+ #region Private Methods
+
+ private void InitializeActivityHostProcesses()
+ {
+ // create the minimum number of hosts
+ for (int i = 0; i < MinActivityHosts; i++)
+ {
+ ActivityHostProcess process = CreateNewActivityHostProcess();
+ _hostProcesses.Add(process);
+ }
+ }
+
+ private ActivityHostProcess CreateNewActivityHostProcess()
+ {
+ ActivityHostProcess process = new ActivityHostProcess(_configuration.ActivityProcessIdleTimeoutSec, _configuration.LanguageMode);
+ process.ProcessCrashed += ProcessCrashed;
+ process.Finished += ProcessFinished;
+ process.OnProcessIdle += ProcessIdle;
+
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrProcessesPoolSize);
+ return process;
+ }
+
+ private void ProcessCrashed(object sender, ActivityHostCrashedEventArgs e)
+ {
+ // the process crashed, we need to not use it anymore
+ ActivityHostProcess process = sender as ActivityHostProcess;
+ Debug.Assert(process != null, "ActivityHostProcess did not raise event correctly");
+ Debug.Assert(process.Busy, "When ProcessCrashed is raised busy should not be reset");
+ process.MarkForRemoval = true;
+
+ if (e.FailureOnSetup)
+ {
+ // the request needs to be processed again
+ _failedRequests.Enqueue(e.Invoker);
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrFailedRequestsPerSec);
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrFailedRequestsQueueLength);
+ }
+
+ // Below call is added to fix the race condition:
+ // When OOP host process is crashed, ProcessCrashed() method sets the process as MarkForRemoval, context switch happened at this time
+ // and another process has finished and started the servicing thread, which checks the above process as MarkForRemoval and disposes it.
+ // ProcessFinished event handler is unregistered from the process and ActivityHostProcess.HandleTransportError will not be able to raise
+ // process finished event, resulting inconsistent _busyHost count.
+ //
+ DecrementHostCountAndStartThreads();
+ }
+
+ private void ProcessFinished(object sender, EventArgs e)
+ {
+ DecrementHostCountAndStartThreads();
+ }
+
+ private void DecrementHostCountAndStartThreads()
+ {
+ Interlocked.Decrement(ref _busyHosts);
+
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrBusyProcessesCount,
+ -1);
+
+ CheckAndStartServicingThread();
+ }
+
+ private void ProcessIdle(object sender, EventArgs e)
+ {
+ // Only service thread should access the hostProcess collection
+ // Start servicing thread if it is not running already
+ //
+ CheckAndStartServicingThread();
+ }
+
+ ///
+ /// Handler method which runs a specific command in the specified process
+ ///
+ ///
+ private static void RunPowerShellInActivityHostWorker(object state)
+ {
+ // Tuple object with HostProcess and ActivityInvoker is added to ThreadPool.QueueUserWorkItem as part of RunInProcess()
+ // Back reference on ActivityHostProcess in ActivityInvoker is removed
+ //
+ Tuple tupleProcessAndInvoker = state as Tuple;
+ tupleProcessAndInvoker.Item1.PrepareAndRun(tupleProcessAndInvoker.Item2);
+ }
+
+ ///
+ /// Checks to see if a thread is servicing requests. If not
+ /// starts one
+ ///
+ private void CheckAndStartServicingThread()
+ {
+ if (Interlocked.CompareExchange(ref _isServicing, Servicing, NotServicing) == NotServicing)
+ {
+ ThreadPool.QueueUserWorkItem(ServiceRequests);
+ }
+ }
+
+ ///
+ /// Method which performs the actual servicing of requests
+ ///
+ ///
+ private void ServiceRequests(object state)
+ {
+ bool isFailedRequest = false;
+ while (Interlocked.CompareExchange(ref _busyHosts, _configuration.MaxActivityProcesses, _configuration.MaxActivityProcesses) < _configuration.MaxActivityProcesses)
+ {
+ // if there are any processes marked for removal
+ // remove them
+ List toRemove = _hostProcesses.Where(process => process.MarkForRemoval).ToList();
+
+ foreach(var process in toRemove)
+ {
+ SafelyDisposeProcess(process);
+ }
+
+ ActivityInvoker invoker;
+ // first service previously failed request
+ // and then a queued request
+ if (_failedRequests.Count > 0)
+ {
+ _failedRequests.TryDequeue(out invoker);
+ isFailedRequest = true;
+ }
+ else
+ {
+ _requests.TryDequeue(out invoker);
+ isFailedRequest = false;
+ }
+
+ if (invoker == null) break;
+
+ if (invoker.IsCancelled) continue;
+
+ if (isFailedRequest)
+ {
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrFailedRequestsQueueLength,
+ -1);
+ }
+ else
+ {
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrPendingRequestsQueueLength,
+ -1);
+ }
+
+ bool processed = false;
+ foreach (ActivityHostProcess process in _hostProcesses.Where(process => !process.Busy))
+ {
+ // we have found the first free process available use it
+ processed = true;
+ RunInProcess(invoker, process);
+ break;
+ }
+
+ // if there weren't enough processes, then we create one more
+ if (processed) continue;
+
+ ActivityHostProcess hostProcess = CreateNewActivityHostProcess();
+ _hostProcesses.Add(hostProcess);
+ RunInProcess(invoker, hostProcess);
+ }
+
+ // we are all done, set servicing to false
+ Interlocked.CompareExchange(ref _isServicing, NotServicing, Servicing);
+
+ // Try to start the servicing thread again if there are any pending requests and activity host processes are available.
+ // This is required to fix the race condition between CheckAndStartServicingThread() and ServiceRequests() methods.
+ //
+ if((_failedRequests.Count > 0 || _requests.Count > 0) &&
+ Interlocked.CompareExchange(ref _busyHosts, _configuration.MaxActivityProcesses, _configuration.MaxActivityProcesses) < _configuration.MaxActivityProcesses)
+ {
+ CheckAndStartServicingThread();
+ }
+ }
+
+
+ ///
+ /// Unregisters all wait handles and disposes a process
+ ///
+ ///
+ private void SafelyDisposeProcess(ActivityHostProcess process)
+ {
+ process.Finished -= ProcessFinished;
+ process.ProcessCrashed -= ProcessCrashed;
+ process.OnProcessIdle -= ProcessIdle;
+
+ process.Dispose();
+
+ _hostProcesses.Remove(process);
+
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrProcessesPoolSize,
+ -1);
+ }
+
+ ///
+ /// Method called by servicing thread. This method will run the command in the
+ /// specified process on a separate thread
+ ///
+ ///
+ ///
+ private void RunInProcess(ActivityInvoker invoker, ActivityHostProcess process)
+ {
+ if (invoker.IsCancelled)
+ {
+ return;
+ }
+
+ process.Busy = true;
+ Interlocked.Increment(ref _busyHosts);
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrBusyProcessesCount);
+
+ // Adding Tuple object with HostProcess and ActivityInvoker to ThreadPool.QueueUserWorkItem
+ // Back reference on ActivityHostProcess in ActivityInvoker is removed
+ //
+ Tuple tupleProcessAndInvoker = new Tuple(process, invoker);
+ ThreadPool.QueueUserWorkItem(RunPowerShellInActivityHostWorker, tupleProcessAndInvoker);
+ }
+
+ #endregion Private Methods
+
+ #region PSActivityHostManager Overrides
+
+ ///
+ /// Begin invocation of command specified in activity
+ ///
+ /// pipeline of command to execute
+ /// input collection
+ /// output collection
+ /// policy to use for the activity
+ /// optional callback
+ /// optional caller specified state
+ /// IAsyncResult
+ internal IAsyncResult BeginInvokePowerShell(System.Management.Automation.PowerShell command,
+ PSDataCollection input, PSDataCollection output, PSActivityEnvironment policy,
+ AsyncCallback callback, object state)
+ {
+ if (command == null)
+ {
+ throw new ArgumentNullException("command");
+ }
+
+ ConnectionAsyncResult result = new ConnectionAsyncResult(state, callback, command.InstanceId);
+
+ _structuredTracer.OutOfProcessRunspaceStarted(command.ToString());
+
+ ActivityInvoker invoker =
+ new ActivityInvoker
+ {
+ Input = input,
+ Output = output,
+ Policy = policy,
+ PowerShell = command,
+ AsyncResult = result
+ };
+
+ result.Invoker = invoker;
+
+ _requests.Enqueue(invoker);
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrIncomingRequestsPerSec);
+ PerfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrPendingRequestsQueueLength);
+ CheckAndStartServicingThread();
+ return result;
+ }
+
+ ///
+ /// Block until operation is complete on the current thread
+ ///
+ /// IAsyncResult to block on
+ internal void EndInvokePowerShell(IAsyncResult asyncResult)
+ {
+ ConnectionAsyncResult result = asyncResult as ConnectionAsyncResult;
+
+ if (result == null)
+ {
+ throw new PSInvalidOperationException(Resources.AsyncResultNotValid);
+ }
+
+ result.EndInvoke();
+ }
+
+ ///
+ /// Cancels an already started execution
+ ///
+ /// async result to cancel
+ internal void CancelInvokePowerShell(IAsyncResult asyncResult)
+ {
+ ConnectionAsyncResult result = asyncResult as ConnectionAsyncResult;
+ if (result != null)
+ {
+ result.Invoker.StopPowerShell();
+ }
+ }
+
+ #endregion PSActivityHostManager Overrides
+ }
+
+ #region ActivityInvoker
+
+ internal class ActivityInvoker
+ {
+ internal PSDataCollection Input { get; set; }
+ internal PSDataCollection Output { get; set; }
+ internal System.Management.Automation.PowerShell PowerShell { get; set; }
+ internal PSActivityEnvironment Policy { get; set; }
+ internal ConnectionAsyncResult AsyncResult { get; set; }
+ private readonly object _syncObject = new object();
+ private bool _invoked;
+ private bool _cancelled;
+ private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
+
+ internal void InvokePowerShell(Runspace runspace)
+ {
+ IAsyncResult result = null;
+
+ if (_cancelled)
+ return;
+
+ Exception invokeException = null;
+
+ try
+ {
+ // Acquiring sync object to fix a race condition between BeginInvoke() and Stop() operations on powershell object.
+ // Invoked flag is set only when begin invoke is succeeded, so that if this activity is cancelled, powershell needs to be stopped in StopPowerShell().
+ //
+ lock (_syncObject)
+ {
+ if (_cancelled)
+ return;
+
+ _tracer.WriteMessage("State of runspace passed to invoker ", runspace.RunspaceStateInfo.State.ToString());
+
+ // at this point we assume we have a clean runspace to
+ // run the command
+ // if the runspace is broken then the invocation will
+ // result in an error either ways
+ PowerShell.Runspace = runspace;
+
+ // Temporary fix:
+ // If we are about to run this out of proc, we need to complete any open
+ // input. Otherwise, the out-of-proc PowerShell will hang expecting input.
+ // However, this has a bug that it will break a third command trying to
+ // use the same input collection. Nana to resolve with final fix.
+ if ((Input != null) && Input.EnumeratorNeverBlocks && Input.IsOpen)
+ {
+ Input.Complete();
+ }
+
+ _tracer.WriteMessage("BEGIN invocation of command out of proc");
+ result = Output == null
+ ? PowerShell.BeginInvoke(Input)
+ : PowerShell.BeginInvoke(Input, Output);
+
+ // _invoked should bet set here as powershell object can be disposed before runspace assignment due to cancelling an activity
+ //
+ _invoked = true;
+ } // end of lock
+
+ // wait until PowerShell Execution Completes
+ PowerShell.EndInvoke(result);
+ _tracer.WriteMessage("END invocation of command out of proc");
+ }
+ catch (Exception exception)
+ {
+ // Since the callback is internally handled there should
+ // not be any exceptions. Fix the same
+ // ignore any exceptions caused in the callback invocation
+ _tracer.WriteMessage("Running powershell in activity host threw exception");
+ _tracer.TraceException(exception);
+ invokeException = exception;
+
+ // transport errors are centrally handled for queue and process management
+ if (exception is PSRemotingTransportException) throw;
+ }
+ finally
+ {
+ AsyncResult.SetAsCompleted(invokeException);
+ }
+ }
+
+ internal void StopPowerShell()
+ {
+ bool needToStop = false;
+
+ lock (_syncObject)
+ {
+ _cancelled = true;
+
+ if (_invoked)
+ {
+ needToStop = true;
+ }
+ }
+
+ if (needToStop)
+ {
+ PowerShell.Stop();
+ }
+
+ AsyncResult.SetAsCompleted(null);
+ }
+
+ internal bool IsCancelled
+ {
+ get
+ {
+ lock (_syncObject)
+ {
+ return _cancelled;
+ }
+ }
+ }
+ }
+
+ #endregion ActivityInvoker
+
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityHostProcess.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityHostProcess.cs
new file mode 100644
index 000000000..a37263f5d
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityHostProcess.cs
@@ -0,0 +1,564 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Management.Automation.Remoting;
+using System.Management.Automation.Tracing;
+using System.Management.Automation.PerformanceData;
+using System.Timers;
+using Timer = System.Timers.Timer;
+using System.Globalization;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ internal class ActivityHostCrashedEventArgs : EventArgs
+ {
+ internal bool FailureOnSetup { get; set; }
+ internal ActivityInvoker Invoker { get; set; }
+ }
+
+ ///
+ /// Encapsulates an out of process activity host
+ ///
+ /// This class is not thread safe. Caller has to
+ /// ensure thread safety of accessing internal properties
+ internal class ActivityHostProcess : IDisposable
+ {
+ #region Private Members
+ private static PSPerfCountersMgr _perfCountersMgr = PSPerfCountersMgr.Instance;
+ private Runspace _runspace;
+ private static readonly WSManConnectionInfo ActivityHostConnectionInfo;
+ private const string ActivityHostShellUri = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell.Workflow.ActivityHost";
+ private static readonly string[] ActivitiesTypesFiles = new[] {
+ @"%windir%\system32\windowspowershell\v1.0\modules\psworkflow\PSWorkflow.types.ps1xml"
+ };
+ private static readonly TypeTable ActivitiesTypeTable;
+ private const int WSManLocalPort = 47001;
+ private bool _busy;
+ private readonly object _syncObject = new object();
+ private readonly PowerShellProcessInstance _processInstance;
+ private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
+ private ActivityInvoker _currentInvoker;
+
+ ///
+ /// This the period of time for which the process will
+ /// remain inactive. Afterwards it will be killed
+ ///
+ private const int TimeOut = 5*60*1000;
+ private readonly Timer _timer;
+
+ ///
+ /// Use this flag to flip between IPC process and
+ /// WSMan process in localhost
+ ///
+ private readonly bool _useJobIPCProcess;
+
+ private readonly PSLanguageMode? _languageMode;
+
+ ///
+ /// Creating a _PSSetVariable to set multiple variables in a single call --> PERF
+ /// Naming it _PSSetVariable to avoid name collision with Set-Variable cmdlet
+ ///
+ private const string SetVariableFunction = @"function _PSSetVariable
+ {
+ [CmdletBinding()]
+ param(
+
+ [Parameter(Position=0)]
+ [string[]]
+ $Name,
+
+ [Parameter(Position=1)]
+ [object[]]
+ $Value
+ )
+
+ for($i=0; $i -lt $Name.Count; $i++)
+ {
+ microsoft.powershell.utility\set-variable -name $Name[$i] -value $Value[$i] -scope global
+ }
+
+ Set-StrictMode -Off
+ }";
+
+ private bool _isDisposed;
+ #endregion Private Members
+
+ #region Constructors
+
+ ///
+ /// Initialize a connection info object in the static constructor
+ /// It can be reused for all connections
+ ///
+ static ActivityHostProcess()
+ {
+ ActivityHostConnectionInfo = new WSManConnectionInfo { Port = WSManLocalPort, ShellUri = ActivityHostShellUri };
+
+ List typefiles = TypeTable.GetDefaultTypeFiles();
+ typefiles.AddRange(ActivitiesTypesFiles.Select(Environment.ExpandEnvironmentVariables));
+ ActivitiesTypeTable = new TypeTable(typefiles);
+
+ }
+
+ internal ActivityHostProcess(int activityHostTimeoutSec, PSLanguageMode? languageMode)
+ {
+ _languageMode = languageMode;
+ _useJobIPCProcess = true;
+ _tracer.WriteMessage("BEGIN Creating new PowerShell process instance");
+ _processInstance = new PowerShellProcessInstance();
+ _tracer.WriteMessage("END Creating new PowerShell process instance ");
+ _runspace = CreateRunspace();
+ _tracer.WriteMessage("New runspace created ", _runspace.InstanceId.ToString());
+ _timer = new Timer {AutoReset = false, Interval = TimeOut};
+ _timer.Elapsed += TimerElapsed;
+ _timer.Interval = activityHostTimeoutSec > 0 ? activityHostTimeoutSec*1000 : TimeOut;
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrCreatedProcessesCount);
+ }
+
+ #endregion Constructors
+
+ #region Private Methods
+
+ ///
+ /// Get the runspace corresponding to this process
+ ///
+ /// indicates if a new runspace
+ /// needs to be created
+ /// runspace object
+ private Runspace GetRunspace(bool createNew)
+ {
+ if (_runspace.RunspaceStateInfo.State == RunspaceState.BeforeOpen)
+ {
+ // if the runspace is not opened, open it and return
+ OpenRunspace(_runspace);
+ }
+ else
+ {
+ if (_useJobIPCProcess)
+ {
+ // dispose the existing runspace and create a new one
+ CloseAndDisposeRunspace();
+
+ _runspace = CreateRunspace();
+ _tracer.WriteMessage("New runspace created ", _runspace.InstanceId.ToString());
+ OpenRunspace(_runspace);
+ }
+ }
+ return _runspace;
+ }
+
+ private void CloseAndDisposeRunspace()
+ {
+ try
+ {
+ // Close and dispose the existing runspace
+ //
+ _runspace.Close();
+ _runspace.Dispose();
+ }
+ catch (Exception e)
+ {
+ // RemoteRunspace.Close can throw exceptions when Server process has exited or runspace is invalid.
+ // Ignoring all exceptions as this runpspace was used for previous OOP activity execution.
+ //
+ _tracer.TraceException(e);
+
+ // do nothing
+ }
+ }
+
+ ///
+ /// Depending on the option return an Out-of-proc
+ /// or remoting runspace on localhost
+ ///
+ /// runspace object for use
+ private Runspace CreateRunspace()
+ {
+ return _useJobIPCProcess ? RunspaceFactory.CreateOutOfProcessRunspace(ActivitiesTypeTable, _processInstance) : RunspaceFactory.CreateRunspace(ActivityHostConnectionInfo, null, ActivitiesTypeTable);
+ }
+
+ ///
+ /// Opens the specified runspace. If there are any errors in
+ /// opening the runspace, the method just eats them so that
+ /// an unhandled exception in the background thread in which this
+ /// method is invoked does not lead to a process crash
+ ///
+ /// runspace to open
+ private void OpenRunspace(Runspace runspace)
+ {
+ // a runspace open can fail for a variety of reasons
+ // eat the exceptions
+ try
+ {
+ _tracer.WriteMessage("Opening runspace " , _runspace.InstanceId.ToString());
+ runspace.Open();
+ _tracer.WriteMessage("Runspace opened successfully ", _runspace.InstanceId.ToString());
+
+ if (_languageMode != null && _languageMode.HasValue)
+ {
+ using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
+ {
+ ps.Runspace = runspace;
+ string langSript = "$ExecutionContext.SessionState.LanguageMode = '" + _languageMode.Value.ToString() + "'";
+ ps.AddScript(langSript);
+ ps.Invoke();
+ }
+ }
+ }
+ catch (PSRemotingTransportRedirectException)
+ {
+ // we should not be getting this exception
+ // in the normal case
+ _tracer.WriteMessage("Opening runspace threw PSRemotingTransportRedirectException", _runspace.InstanceId.ToString());
+ Debug.Assert(false, "We should not get a redirect exception under normal circumstances");
+ }
+ catch (PSRemotingTransportException transportException)
+ {
+ _tracer.WriteMessage("Opening runspace threw PSRemotingTransportException", _runspace.InstanceId.ToString());
+ _tracer.TraceException(transportException);
+ // throwing PSRemotingTransportException exception as it will be handled at single place in PrepareAndRun() method.
+ throw;
+ }
+ catch(PSRemotingDataStructureException)
+ {
+ // just eat the exception
+ _tracer.WriteMessage("Opening runspace threw PSRemotingDataStructureException", _runspace.InstanceId.ToString());
+ Debug.Assert(false, "We should not get a protocol exception under normal circumstances");
+ }
+ }
+
+ ///
+ /// Import all the specified modules in the specified runspace
+ ///
+ /// runspace in which to import the modules
+ /// collection of modules to import
+ /// true if setting up of runspace from policy succeeded
+ private void ImportModulesFromPolicy(Runspace runspace, ICollection modules)
+ {
+ // If any modules are specified, load them into the runspace.
+ // In general, autoloading should be preferred to explicitly loading a module
+ // at startup time...
+ if (modules.Count <= 0)
+ return;
+
+ using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
+ {
+ ps.Runspace = runspace;
+ // Setting erroaction to stop for import-module since they are required modules. If not present, stop the execution
+ ps.AddCommand("Import-Module").AddArgument(modules).AddParameter("ErrorAction",ActionPreference.Stop).AddParameter("Force");
+ ps.Invoke();
+ }
+ }
+
+ ///
+ /// Set the specified variables in the runspace
+ ///
+ /// runspace in which the variables need
+ /// to be set
+ /// collection of variables that need to be set
+ private void SetVariablesFromPolicy(Runspace runspace, IDictionary variables)
+ {
+ using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
+ {
+ ps.Runspace = runspace;
+ ps.AddScript(SetVariableFunction);
+ ps.Invoke();
+
+ ps.Commands.Clear();
+ ps.AddCommand("_PSSetVariable").AddParameter("Name", variables.Keys).AddParameter("Value", variables.Values);
+ ps.Invoke();
+
+ // Remove the temporary function _PSSetVariable after its use is done.
+ ps.Commands.Clear();
+ ps.AddCommand("Remove-Item").AddParameter("Path", "function:\\_PSSetVariable").AddParameter("Force");
+ ps.Invoke();
+
+ }
+ }
+
+ ///
+ /// Set the busy flag since the process is being used.
+ /// Stop the idle timer
+ ///
+ private void SetBusy()
+ {
+ lock(_syncObject)
+ {
+ _busy = true;
+
+ // stop the idle timer
+ _timer.Stop();
+ }
+ }
+
+ ///
+ /// Reset the busy flag and start the idle timer
+ ///
+ private void ResetBusy()
+ {
+ lock(_syncObject)
+ {
+ // Process state should not be set to false if it is already marked for removal
+ // Process is marked for removal in two cases:
+ // 1) When PSRemotingTransportException is received in PrepareAndRun, in this case Server process is already crashed/exited.
+ // If busy state is set to false, this process can be assigned to another activity by the servicing thread.
+ // Instead of adding separate boolean variable in PrepareAndRun to call ResetBusy we are checking for not marked for removal here.
+ // 2) When idle time of 5 min elapsed, process is marked for removal so that server process can be killed. After this, ResetBusy is never called.
+ //
+ if (_busy && !_markForRemoval)
+ {
+ _busy = false;
+
+ // enable the idle timer
+ _timer.Enabled = true;
+
+ this.RaiseProcessFinishedEvent();
+ }
+ }
+ }
+
+ ///
+ /// Idle timeout has occured. If the runspace is not being used
+ /// just close it
+ ///
+ /// sender of this event
+ /// unused
+ private void TimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ lock (_syncObject)
+ {
+ if (_busy) return;
+
+ // Mark this process for removal and set it as busy so that this process will not be assigned to any new activity.
+ // Marking for removal will ensure that servicing thread will remove this object from host processs collection.
+ //
+ _busy = true;
+ _markForRemoval = true;
+ }
+
+ if (OnProcessIdle != null)
+ OnProcessIdle(this, new EventArgs());
+ }
+
+ ///
+ /// Handles a transport error since it is most likely that
+ /// the process crash
+ ///
+ /// the transport exception
+ /// that was raised
+ /// true indicates that the crash was
+ /// encountered when setting up the process and not when the
+ /// command was run
+ private void HandleTransportError(PSRemotingTransportException transportException, bool onSetup)
+ {
+ _tracer.TraceException(transportException);
+
+ if (ProcessCrashed != null)
+ {
+ ActivityHostCrashedEventArgs eventArgs = new ActivityHostCrashedEventArgs
+ {FailureOnSetup = onSetup,
+ Invoker = _currentInvoker};
+ ProcessCrashed(this, eventArgs);
+ }
+ }
+
+ internal void RaiseProcessFinishedEvent()
+ {
+ if (Finished != null)
+ Finished(this, new EventArgs());
+ }
+
+ #endregion Private Methods
+
+ #region Internal Methods
+
+ internal static String ActivityHostConfiguration
+ {
+ get { return ActivityHostShellUri; }
+ }
+ internal event EventHandler Finished;
+ internal event EventHandler OnProcessIdle;
+
+ internal event EventHandler ProcessCrashed;
+
+ ///
+ /// Prepare the environment based on the policy and run the powershell
+ ///
+ ///
+ /// It is assumed that the caller of this method will spawn appropriate
+ /// thread and so it is fine for us to call the callback on the same thread
+ internal void PrepareAndRun(ActivityInvoker invoker)
+ {
+ bool setupSucceeded = false;
+ try
+ {
+ // Transport (PSRemotingTransportException) error can happen during setup/prepare phase, so it is getting assigned before setup phase.
+ //
+ _currentInvoker = invoker;
+ Runspace runspace = null;
+ // Retry for 10 times if runspace is in Broken state
+ //
+ // Runspace can be broken, when a remote runspace is getting closed during the next activity execution,
+ // the close ack is getting timedout and that close ack is received by the newly created remote runspace for
+ // the next activity since stdoutput stream of server process is not cleared/discarded,
+ // that is why during new runspace is in broken state while it is being opened.
+ //
+ for (int i = 1; (i <= 10) && !invoker.IsCancelled; i++)
+ {
+ // prepare the runspace with the necessary
+ // modules and variables here
+ runspace = GetRunspace(true);
+
+ if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
+ break;
+
+ System.Threading.Thread.Sleep(i*200);
+ }
+
+ if (invoker.IsCancelled)
+ {
+ return;
+ }
+
+ Debug.Assert((runspace.RunspaceStateInfo.State == RunspaceState.Opened), "Runspace is not in Opened state for running an OOP activity");
+
+ if (invoker.Policy.Variables.Count > 0)
+ {
+ // set the variables in the specified runspace
+ _tracer.WriteMessage("BEGIN Setting up variables in runspace ", _runspace.InstanceId.ToString());
+ SetVariablesFromPolicy(runspace, invoker.Policy.Variables);
+ _tracer.WriteMessage("END Setting up variables in runspace ", _runspace.InstanceId.ToString());
+ }
+
+ if (invoker.Policy.Modules.Count > 0)
+ {
+ // attempt loading the modules in the specified runspace
+ _tracer.WriteMessage("BEGIN Setting up runspace from policy ", _runspace.InstanceId.ToString());
+ ImportModulesFromPolicy(runspace, invoker.Policy.Modules);
+ _tracer.WriteMessage("END Setting up runspace from policy ", _runspace.InstanceId.ToString());
+ }
+
+ // Prepare phase is completed without any issues.
+ // setupSucceeded flag is used in HandleTranportError method to enqueue the current activity for retry.
+ // If there is any PSRemotingTransportException during InvokePowershell current activity will not be enqueued to setup failed requests in ActivityHostManager.
+ //
+ setupSucceeded = true;
+
+ // at this point we assume we have a clean runspace to
+ // run the command
+ // if the runspace is broken then the invocation will
+ // result in an error either ways
+ invoker.InvokePowerShell(runspace);
+ }
+ catch (PSRemotingTransportException transportException)
+ {
+ HandleTransportError(transportException, !setupSucceeded);
+ }
+ catch (Exception e)
+ {
+ // at this point there is an exception other than a
+ // transport exception that is caused by trying to
+ // setup the runspace. Release the asyncresult
+ // accordingly
+ _tracer.TraceException(e);
+ invoker.AsyncResult.SetAsCompleted(e);
+ }
+ finally
+ {
+ _currentInvoker = null;
+ ResetBusy();
+ }
+ }
+
+ ///
+ /// If the process represented by this object is busy
+ ///
+ internal bool Busy
+ {
+ get
+ {
+ lock (_syncObject)
+ {
+ return _busy;
+ }
+ }
+ set
+ {
+ if (value) SetBusy();
+ else ResetBusy();
+ }
+ }
+
+ ///
+ /// Indicates that the associated process crashed and this object
+ /// needs to be removed by ActivityHostManager
+ ///
+ internal bool MarkForRemoval {
+ get
+ {
+ // _processInstance.HasExited can not be used to tell as marked for removal
+ // as it will cause inconsistent busy host count
+ //
+ return _markForRemoval;
+ }
+ set
+ {
+ _markForRemoval = value;
+ }
+ }
+ private bool _markForRemoval = false;
+
+ #endregion Internal Methods
+
+ #region IDisposable
+
+ ///
+ /// Dipose
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// dispose of managed resources
+ ///
+ /// true if being disposed
+ protected void Dispose(bool disposing)
+ {
+ if (!disposing) return;
+
+ if (_isDisposed) return;
+ lock (_syncObject)
+ {
+ if (_isDisposed) return;
+ _isDisposed = true;
+ }
+
+ Debug.Assert(_runspace != null, "Runspace is already null");
+
+ CloseAndDisposeRunspace();
+
+ _processInstance.Dispose();
+
+ _currentInvoker = null;
+ _tracer.Dispose();
+
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.ActivityHostMgrDisposedProcessesCount);
+ }
+
+ #endregion IDisposable
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityUtils.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityUtils.cs
new file mode 100644
index 000000000..ccc5af334
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ActivityUtils.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Management.Automation.Remoting;
+using System.Management.Automation.Runspaces;
+using Microsoft.PowerShell.Workflow;
+
+namespace Microsoft.PowerShell.Activities
+{
+ static class ActivityUtils
+ {
+ // Extension method to add IsNullOrEmpty to arrays.
+ internal static bool IsNullOrEmpty(this System.Collections.ICollection c)
+ {
+ return (c == null || c.Count == 0);
+ }
+
+ private static int DefaultMaximumConnectionRedirectionCount = 5;
+
+ internal static List GetConnectionInfo(string[] PSComputerName, string[] PSConnectionUri,
+ string PSCertificateThumbprint, string PSConfigurationName,
+ bool? PSUseSsl, uint? PSPort, string PSApplicationName,
+ PSCredential PSCredential, AuthenticationMechanism PSAuthentication,
+ bool PSAllowRedirection, System.Management.Automation.Remoting.PSSessionOption options)
+ {
+ List connections = new List();
+
+ string[] machineList = null;
+ bool connectByComputerName = false;
+
+ // Connect by computername
+ if ((! PSComputerName.IsNullOrEmpty()) && (PSConnectionUri.IsNullOrEmpty()))
+ {
+ machineList = PSComputerName;
+ connectByComputerName = true;
+ }
+ else if ((PSComputerName.IsNullOrEmpty()) && (! PSConnectionUri.IsNullOrEmpty()))
+ {
+ machineList = PSConnectionUri;
+ }
+ else
+ {
+ throw new ArgumentException(Resources.CannotSupplyUriAndComputername);
+ }
+
+ // Go through each machine in the list an update its properties
+ foreach (string machine in machineList)
+ {
+ if (!string.IsNullOrEmpty(machine))
+ {
+ WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
+
+ if (PSPort.HasValue)
+ {
+ connectionInfo.Port = (int)PSPort.Value;
+ }
+
+ if (PSUseSsl.HasValue && (PSUseSsl.Value))
+ {
+ connectionInfo.Scheme = WSManConnectionInfo.HttpsScheme;
+ }
+
+ if (!String.IsNullOrEmpty(PSConfigurationName))
+ {
+ connectionInfo.ShellUri = PSConfigurationName;
+ }
+
+ if (!String.IsNullOrEmpty(PSApplicationName))
+ {
+ connectionInfo.AppName = PSApplicationName;
+ }
+
+ if (connectByComputerName)
+ {
+ connectionInfo.ComputerName = machine;
+ }
+ else
+ {
+ connectionInfo.ConnectionUri = (Uri)LanguagePrimitives.ConvertTo(machine, typeof(Uri), System.Globalization.CultureInfo.InvariantCulture);
+ }
+
+ if (PSCredential != null)
+ {
+ connectionInfo.Credential = PSCredential;
+ }
+
+ if (!String.IsNullOrEmpty(PSCertificateThumbprint))
+ {
+ connectionInfo.CertificateThumbprint = PSCertificateThumbprint;
+ }
+
+ if (PSAuthentication != AuthenticationMechanism.Default)
+ {
+ connectionInfo.AuthenticationMechanism = PSAuthentication;
+ }
+
+ connectionInfo.MaximumConnectionRedirectionCount = PSAllowRedirection ? DefaultMaximumConnectionRedirectionCount : 0;
+
+ if (options != null)
+ {
+ connectionInfo.SetSessionOptions(options);
+ }
+
+ connections.Add(connectionInfo);
+ }
+ else
+ {
+ // add a null connection to account for "" or $null in PSComputerName parameter
+ connections.Add(null);
+ }
+ }
+
+ return connections;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ConnectionManager.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ConnectionManager.cs
new file mode 100644
index 000000000..00a6879cf
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/ConnectionManager.cs
@@ -0,0 +1,2372 @@
+/*
+ * Copyright (c) 2010 Microsoft Corporation. All rights reserved
+ */
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Threading;
+using System.Timers;
+using Timer = System.Timers.Timer;
+using Microsoft.PowerShell.Activities;
+using System.Management.Automation.Tracing;
+using System.Management.Automation.PerformanceData;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ #region Connection
+
+ ///
+ /// class the contains a remote connection (runspace) and associated
+ /// data for managing the same
+ ///
+ /// the only reason this class has an internal scope is for
+ /// test purposes. Else this can be a private class inside
+ /// connection manager
+ internal class Connection
+ {
+ private Runspace _runspace;
+ private bool _busy;
+ private bool _idle;
+ private readonly object _syncObject = new object();
+ private static readonly EventArgs EventArgs = new EventArgs();
+ private readonly Guid _instanceId = Guid.NewGuid();
+ private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
+ private readonly Tracer _structuredTracer = new Tracer();
+ private static readonly PSPerfCountersMgr _perfCountersMgr = PSPerfCountersMgr.Instance;
+ private bool _readyForDisconnect = false;
+ private bool _readyForReconnect = false;
+ private ConnectionManager _manager;
+
+ ///
+ /// retry interval cannot be 0, so initial value is 1
+ ///
+ private uint _retryInterval = 1;
+
+ internal uint RetryAttempt { get; set; }
+ internal uint RetryCount { get; set; }
+ internal GetRunspaceAsyncResult AsyncResult { get; set; }
+ internal WSManConnectionInfo ConnectionInfo { get; set; }
+
+ internal event EventHandler CloseCompleted;
+ internal event EventHandler OpenCompleted;
+ internal event EventHandler DisconnectCompleted;
+ internal event EventHandler ReconnectCompleted;
+
+ private const int Open = 1;
+ private const int Close = 2;
+ private const int Disconnect = 3;
+ private const int Reconnect = 4;
+
+ internal Connection(ConnectionManager manager)
+ {
+ _manager = manager;
+ _tracer.WriteMessage("PSW Conn: Creating new connection");
+ }
+
+ internal bool DisconnectedIntentionally { get; set; }
+
+ internal Runspace Runspace
+ {
+ get { return _runspace; }
+ }
+
+ internal Guid InstanceId
+ {
+ get { return _instanceId; }
+ }
+
+ internal bool Idle
+ {
+ get
+ {
+ lock (_syncObject)
+ {
+ return _idle;
+ }
+ }
+ set
+ {
+ lock (_syncObject)
+ {
+ _idle = value;
+ }
+ }
+ }
+ internal bool ReadyForDisconnect
+ {
+ get
+ {
+ lock (_syncObject)
+ {
+ return _readyForDisconnect;
+ }
+ }
+
+ set
+ {
+ lock (_syncObject)
+ {
+ _readyForDisconnect = value;
+
+ if (_readyForDisconnect)
+ _readyForReconnect = false;
+ }
+ }
+ }
+
+ internal bool ReadyForReconnect
+ {
+ get
+ {
+ lock (_syncObject)
+ {
+ return _readyForReconnect;
+ }
+ }
+ set
+ {
+ lock (_syncObject)
+ {
+ _readyForReconnect = value;
+
+ if (_readyForReconnect)
+ _readyForDisconnect = false;
+ }
+ }
+ }
+
+ internal bool Busy
+ {
+ get
+ {
+ lock (_syncObject)
+ {
+ return _busy;
+ }
+ }
+ set
+ {
+ lock (_syncObject)
+ {
+ _busy = value;
+
+ if (_busy)
+ {
+ _idle = false;
+ }
+
+ // connection is set to busy by the connection manager
+ // when it is assigning the connection to a new activity
+ // then the connection should be marked as not ready for
+ // disconnect since the activity has to signal the same
+ // when it is not busy we do not want to disconnect idle
+ // connections, hence it needs to be marked as not ready
+ // for disconnect
+ _readyForDisconnect = false;
+ _readyForReconnect = false;
+ }
+ }
+ }
+
+ internal uint RetryInterval
+ {
+ get { return _retryInterval; }
+ set
+ {
+ _retryInterval = value;
+ if (_retryInterval == 0) _retryInterval = 1;
+ }
+ }
+
+ private bool CheckAndReconnectAfterCrashOrShutdown(WSManConnectionInfo connectionInfo)
+ {
+ bool connectAsyncSucceeded = false;
+
+ RunCommandsArguments args = this.AsyncResult.AsyncState as RunCommandsArguments;
+ if (args != null)
+ {
+ Guid runspaceId = args.ImplementationContext.DisconnectedRunspaceInstanceId;
+
+ ActivityImplementationContext implementationContext = args.ImplementationContext;
+ System.Management.Automation.PowerShell currentPowerShellInstance = implementationContext.PowerShellInstance;
+ PSActivityContext psActivityContext = args.PSActivityContext;
+
+ // Non-empty guid represtents that args.ImplementationContext.EnableRemotingActivityAutoResume is true
+ if (!runspaceId.Equals(Guid.Empty))
+ {
+ Runspace[] runspaces = null;
+
+ try
+ {
+ // Queries the server for disconnected runspaces.
+ // Each runspace object in the returned array is in the Disconnected state and can be
+ // connected to the server by calling the Connect() or ConnectAsync() methods on the runspace.
+ runspaces = Runspace.GetRunspaces(connectionInfo);
+ }
+ catch (System.Management.Automation.RuntimeException)
+ {
+ _tracer.WriteMessage("PSW Conn: Disconnected Remote Runspace is not avialable. Runspace Instance Id: " + runspaceId.ToString());
+ }
+
+ if (runspaces != null)
+ {
+ // In local machine case, remote runspace with the specific id will be not be available after machine shutdown.
+ // Remoterunspace on local machine may be available if only current process is crashed or suspended forcefully.
+ // OpenAsync() will takes care of create new remote runspace if disconnected runspace is not available.
+
+ Runspace foundRunspace = runspaces.FirstOrDefault(currentRunspace => currentRunspace.InstanceId.Equals(runspaceId));
+
+ if (foundRunspace != null && foundRunspace.RunspaceStateInfo.State == RunspaceState.Disconnected)
+ {
+ System.Management.Automation.PowerShell disconnectedPowerShell = null;
+
+ try
+ {
+ // When disconnectedPowerShell is not available PSInvalidOperationException will be thrown and finally block takes care of releasing the runspace.
+ // And OpenAsync() will create a new remote runspace if disconnected runspace is not available.
+ disconnectedPowerShell = foundRunspace.CreateDisconnectedPowerShell();
+
+ // Add stream handler and connect to disconnected remote runspace
+ PSActivity.AddHandlersToStreams(disconnectedPowerShell, args);
+ disconnectedPowerShell.ConnectAsync(args.Output, PSActivity.PowerShellInvocationCallback, args);
+
+
+ // Assign the disconnected powershell to ImplementationContext and dispose the existing powershell instance.
+ implementationContext.PowerShellInstance = disconnectedPowerShell;
+
+ // Since we are disposing the current command, remove this command from the list
+ // of running commands.
+ lock (psActivityContext.runningCommands)
+ {
+ RetryCount retryCount = psActivityContext.runningCommands[currentPowerShellInstance];
+ psActivityContext.runningCommands.Remove(currentPowerShellInstance);
+ psActivityContext.runningCommands[disconnectedPowerShell] = retryCount;
+ }
+ currentPowerShellInstance.Dispose();
+
+ _runspace = foundRunspace;
+ _runspace.StateChanged += RunspaceStateChanged;
+
+ connectAsyncSucceeded = true;
+ }
+ catch (PSInvalidOperationException)
+ {
+ _tracer.WriteMessage("PSW Conn: Disconnected PowerShell is not avialable. Runspace Instance Id: " + foundRunspace.InstanceId.ToString());
+ }
+ finally
+ {
+ // release the runspace if reconnect did not happen.
+ if (connectAsyncSucceeded == false && foundRunspace != null)
+ {
+ try
+ {
+ // Remove stream handlers on disconnectedPowerShell
+ // Non null disconnectedPowerShell represents that handlers were added on disconnectedPowerShell and an exception happened during ConnectAsync.
+ if (disconnectedPowerShell != null)
+ PSActivity.RemoveHandlersFromStreams(disconnectedPowerShell, args);
+
+ foundRunspace.Close();
+ foundRunspace.Dispose();
+ }
+ catch (Exception)
+ {
+ // Runspace.Close can throw exceptions when Server process has exited or runspace is invalid.
+ // Ignoring all exceptions.
+ //
+ _tracer.WriteMessage("PSW Conn: Disconnected PowerShell is not avialable. Runspace Instance Id: " + foundRunspace.InstanceId.ToString());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return connectAsyncSucceeded;
+ }
+
+ internal void OpenAsync()
+ {
+ // Always create runspace with WSManConnectionInfo.EnableNetworkAccess set to true.
+ WSManConnectionInfo copyConnectionInfo = ConnectionInfo.Copy();
+ copyConnectionInfo.EnableNetworkAccess = true;
+
+ // If the remote runspace is not available after crash/shutdown,
+ // activity task resume will fall back to reexecuting in a new runspace is assigned for the connection
+ if (!CheckAndReconnectAfterCrashOrShutdown(copyConnectionInfo))
+ {
+ _runspace = RunspaceFactory.CreateRunspace(copyConnectionInfo, null, LocalRunspaceProvider.SharedTypeTable);
+ _runspace.StateChanged += RunspaceStateChanged;
+ _tracer.WriteMessage("PSW Conn: Calling OpenAsync on runspace");
+ _runspace.OpenAsync();
+ }
+ }
+
+ internal void CloseAsync()
+ {
+ _tracer.WriteMessage("PSW Conn: Calling CloseAsync on runspace");
+
+ // at this point the runspace can be in a closed or broken state
+ // if so then close has to simply raise the event
+ // if it was not in a terminal state when checking but happens
+ // later but before CloseAsync() is called then the other
+ // call which caused the state change will raise an event
+ if (_runspace.RunspaceStateInfo.State == RunspaceState.Broken ||
+ _runspace.RunspaceStateInfo.State == RunspaceState.Closed)
+ {
+ RaiseEvents(Close);
+ }
+ else
+ _runspace.CloseAsync();
+ }
+
+ internal void DisconnectAsync()
+ {
+ bool disconnect = false;
+
+ if (_readyForDisconnect)
+ {
+ lock (_syncObject)
+ {
+ if (_readyForDisconnect)
+ {
+ _readyForDisconnect=false;
+ _readyForReconnect = false;
+ disconnect = true;
+ }
+ }
+ if (disconnect)
+ {
+ _tracer.WriteMessage("PSW Conn: Calling Disconnect Async");
+ _manager.DisconnectCalled();
+ _runspace.DisconnectAsync();
+ return;
+ }
+ }
+ RaiseEvents(Disconnect);
+ }
+
+ internal void ReconnectAsync()
+ {
+ bool reconnect = false;
+
+ if (_readyForReconnect)
+ {
+ lock (_syncObject)
+ {
+ if (_readyForReconnect)
+ {
+ _readyForReconnect = false;
+ _readyForDisconnect = false;
+ reconnect = true;
+ }
+ }
+
+ if (reconnect)
+ {
+ _tracer.WriteMessage("PSW Conn: Calling reconnect async");
+ _manager.ReconnectCalled();
+ _runspace.ConnectAsync();
+ return;
+ }
+ }
+ RaiseEvents(Reconnect);
+ }
+
+ private void RunspaceStateChanged(object sender, RunspaceStateEventArgs e)
+ {
+ _structuredTracer.RunspaceStateChanged(this._instanceId.ToString(), e.RunspaceStateInfo.State.ToString(), string.Empty);
+ _tracer.WriteMessage("PSW Conn: runspace state" + e.RunspaceStateInfo.State.ToString());
+ switch (e.RunspaceStateInfo.State)
+ {
+ case RunspaceState.Opening:
+ return;
+
+ case RunspaceState.Closing:
+ return;
+
+ case RunspaceState.Disconnecting:
+ {
+ ReadyForDisconnect = false;
+ }
+ return;
+
+ case RunspaceState.Opened:
+ {
+ // runspace opened successfully, assign it to the asyncresult
+ // reset retry counter
+ RetryAttempt = 0;
+ ReadyForReconnect = false;
+ AsyncResult.Connection = this;
+ // this SetAsCompleted will result in the callback to the
+ // activity happening in the WinRM thread
+ AsyncResult.SetAsCompleted(null);
+ RaiseEvents(Open);
+ RaiseEvents(Reconnect);
+ }
+ break;
+
+ case RunspaceState.Broken:
+ {
+ // Dispose the broken runspace before retry
+ DisposeRunspace();
+
+ // connection attempt failed, retry
+ if (RetryCount > 0 && RetryAttempt < RetryCount)
+ {
+ RetryAttempt++;
+
+ LogRetryAttempt(RetryAttempt, RetryCount);
+
+ Timer timer = new Timer { AutoReset = false, Enabled = false, Interval = _retryInterval * 1000 };
+ timer.Elapsed += RetryTimerElapsed;
+ timer.Start();
+ }
+ else
+ {
+ // all retries have failed, end the asyncresult with an exception
+ // message
+ Busy = false;
+
+ lock (_syncObject)
+ {
+ if (AsyncResult != null)
+ {
+ AsyncResult.Connection = null;
+ AsyncResult.SetAsCompleted(e.RunspaceStateInfo.Reason);
+ }
+ }
+
+ _tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "Disposing broken connection to {0}", _runspace.ConnectionInfo.ComputerName));
+
+ RaiseEvents(Open);
+ RaiseEvents(Disconnect);
+ RaiseEvents(Close);
+ RaiseEvents(Reconnect);
+ }
+ }
+ break;
+
+ case RunspaceState.Disconnected:
+ {
+ ReadyForReconnect = true;
+ RaiseEvents(Disconnect);
+ }
+ break;
+
+ case RunspaceState.Closed:
+ {
+ DisposeRunspace();
+ RaiseEvents(Close);
+ }
+ break;
+ }
+ }
+
+ private void LogRetryAttempt(uint retryAttempt, uint retryTotal)
+ {
+ RunCommandsArguments args = this.AsyncResult.AsyncState as RunCommandsArguments;
+ if (args != null)
+ {
+ PSActivityContext psActivityContext = args.PSActivityContext;
+
+ // Write / Log that an activity retry was required.
+ if (psActivityContext.progress != null)
+ {
+ string progressActivity = ((System.Activities.Activity)psActivityContext.ActivityObject).DisplayName;
+
+ if (string.IsNullOrEmpty(progressActivity))
+ progressActivity = psActivityContext.ActivityType.Name;
+
+ string retryMessage = String.Format(CultureInfo.CurrentCulture, Resources.RetryingConnection, retryAttempt, retryTotal);
+
+ ProgressRecord progressRecord = new ProgressRecord(0, progressActivity, retryMessage);
+ lock (psActivityContext.progress)
+ {
+ psActivityContext.progress.Add(progressRecord);
+ }
+ }
+ }
+ }
+
+ private void RaiseEvents(int eventType)
+ {
+ switch (eventType)
+ {
+ case Open:
+ {
+ if (OpenCompleted != null) OpenCompleted(this, EventArgs);
+ }
+ break;
+ case Close:
+ {
+ if (CloseCompleted != null) CloseCompleted(this, EventArgs);
+ }
+ break;
+ case Disconnect:
+ {
+ if (DisconnectCompleted != null) DisconnectCompleted(this, EventArgs);
+ }
+ break;
+ case Reconnect:
+ {
+ if (ReconnectCompleted != null) ReconnectCompleted(this, EventArgs);
+ }
+ break;
+ }
+ }
+
+ private void DisposeRunspace()
+ {
+ _runspace.StateChanged -= RunspaceStateChanged;
+ _tracer.WriteMessage("PSW Conn: disposing runspace");
+ _runspace.Dispose();
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingConnectionsDisposedCount);
+ }
+
+ private void RetryTimerElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
+ {
+ // specified retry interval timeout has elapsed, try again
+ // dispose the timer
+ Timer timer = sender as Timer;
+ if (timer != null) timer.Dispose();
+
+ // retry the connection again
+ OpenAsync();
+ }
+ }
+
+ #endregion Connection
+
+ #region GetRunspaceAsyncResult
+
+ ///
+ /// AsyncResult object returned by ConnectionManager
+ ///
+ /// the only reason this class has an internal scope is for
+ /// test purposes. Else this can be a private class inside
+ /// connection manager
+ internal class GetRunspaceAsyncResult : ConnectionAsyncResult
+ {
+ internal GetRunspaceAsyncResult(object state, AsyncCallback callback, Guid ownerId) :
+ base(state, callback, ownerId)
+ {
+ }
+
+ internal Connection Connection { get; set; }
+ }
+
+ #endregion GetRunspaceAsyncResult
+
+ #region ThrottleOperation
+
+ internal abstract class ThrottleOperation
+ {
+ internal event EventHandler OperationComplete;
+ private static readonly EventArgs EventArgs = new EventArgs();
+
+ internal void RaiseOperationComplete()
+ {
+ if (OperationComplete != null) OperationComplete(this, EventArgs);
+ }
+
+ internal virtual void DoOperation()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ internal class OpenOperation : ThrottleOperation
+ {
+ private readonly Connection _connection;
+
+ internal OpenOperation(Connection connection)
+ {
+ _connection = connection;
+ _connection.OpenCompleted += HandleOpenCompleted;
+ }
+
+ private void HandleOpenCompleted(object sender, EventArgs eventArgs)
+ {
+ _connection.OpenCompleted -= HandleOpenCompleted;
+ RaiseOperationComplete();
+ }
+
+ internal override void DoOperation()
+ {
+ _connection.OpenAsync();
+ }
+ }
+
+ internal class CloseOperation : ThrottleOperation
+ {
+ private readonly Connection _connection;
+ internal Connection Connection
+ {
+ get { return _connection; }
+ }
+
+ internal CloseOperation(Connection connection)
+ {
+ _connection = connection;
+ _connection.CloseCompleted += HandleCloseCompleted;
+ }
+
+ private void HandleCloseCompleted(object sender, EventArgs eventArgs)
+ {
+ _connection.CloseCompleted -= HandleCloseCompleted;
+ RaiseOperationComplete();
+ }
+
+ internal override void DoOperation()
+ {
+ _connection.CloseAsync();
+ }
+ }
+
+ internal class CloseOneAndOpenAnotherOperation : ThrottleOperation
+ {
+ private readonly Connection _connectionToClose;
+ private readonly Connection _connectionToOpen;
+
+ internal CloseOneAndOpenAnotherOperation(Connection toClose, Connection toOpen)
+ {
+ _connectionToClose = toClose;
+ _connectionToOpen = toOpen;
+
+ _connectionToClose.CloseCompleted += HandleCloseCompleted;
+ _connectionToOpen.OpenCompleted += HandleOpenCompleted;
+ }
+
+ private void HandleOpenCompleted(object sender, EventArgs eventArgs)
+ {
+ _connectionToOpen.OpenCompleted -= HandleOpenCompleted;
+ RaiseOperationComplete();
+ }
+
+ private void HandleCloseCompleted(object sender, EventArgs eventArgs)
+ {
+ _connectionToClose.CloseCompleted -= HandleCloseCompleted;
+ _connectionToOpen.OpenAsync();
+ }
+
+ internal override void DoOperation()
+ {
+ _connectionToClose.CloseAsync();
+ }
+ }
+
+ internal class DisconnectOperation : ThrottleOperation
+ {
+ private readonly Connection _connection;
+
+ internal DisconnectOperation(Connection connection)
+ {
+ _connection = connection;
+ _connection.DisconnectCompleted += HandleDisconnectCompleted;
+ }
+
+ private void HandleDisconnectCompleted(object sender, EventArgs eventArgs)
+ {
+ _connection.DisconnectCompleted -= HandleDisconnectCompleted;
+ RaiseOperationComplete();
+ }
+
+ internal override void DoOperation()
+ {
+ _connection.DisconnectAsync();
+ }
+ }
+
+ internal class ReconnectOperation : ThrottleOperation
+ {
+ private readonly Connection _connection;
+
+ internal ReconnectOperation(Connection connection)
+ {
+ _connection = connection;
+ _connection.ReconnectCompleted += HandleReconnectCompleted;
+ }
+
+ private void HandleReconnectCompleted(object sender, EventArgs eventArgs)
+ {
+ _connection.ReconnectCompleted -= HandleReconnectCompleted;
+ RaiseOperationComplete();
+ }
+
+ internal override void DoOperation()
+ {
+ if (_connection.Runspace.RunspaceStateInfo.State != RunspaceState.Disconnected)
+ {
+ EventHandler HandleRunspaceStateChanged = null;
+ HandleRunspaceStateChanged =
+ delegate(object sender, RunspaceStateEventArgs e)
+ {
+ if (e.RunspaceStateInfo.State == RunspaceState.Disconnected)
+ {
+ _connection.ReconnectAsync();
+ _connection.Runspace.StateChanged -= HandleRunspaceStateChanged;
+ }
+ };
+
+ _connection.Runspace.StateChanged += HandleRunspaceStateChanged;
+
+ if (_connection.Runspace.RunspaceStateInfo.State == RunspaceState.Disconnected)
+ {
+ _connection.ReconnectAsync();
+ _connection.Runspace.StateChanged -= HandleRunspaceStateChanged;
+ }
+
+ return;
+ }
+
+ _connection.ReconnectAsync();
+ }
+ }
+
+ #endregion ThrottleOperation
+
+ internal class ConnectionManager : RunspaceProvider, IDisposable
+ {
+ #region RequestInfo
+
+ private class RequestInfo
+ {
+ private WSManConnectionInfo _connectionInfo;
+ private uint _retryCount;
+ private GetRunspaceAsyncResult _asyncResult;
+ private uint _retryInterval;
+
+ internal WSManConnectionInfo ConnectionInfo
+ {
+ get { return _connectionInfo; }
+ set { _connectionInfo = value; }
+ }
+
+ internal uint RetryCount
+ {
+ get { return _retryCount; }
+ set { _retryCount = value; }
+ }
+
+ internal GetRunspaceAsyncResult AsyncResult
+ {
+ get { return _asyncResult; }
+ set { _asyncResult = value; }
+ }
+
+ internal uint RetryInterval
+ {
+ get { return _retryInterval; }
+ set { _retryInterval = value; }
+ }
+ }
+
+ #endregion RequestInfo
+
+ #region Private members
+
+ // this is the connection pool indexed based on a given machine
+ // and a given session configuration
+ private readonly ConcurrentDictionary>> _connectionPool =
+ new ConcurrentDictionary>>();
+
+ // this is the list of computers to which a cleanup is requested
+ private readonly ConcurrentDictionary>> _cleanupComputers =
+ new ConcurrentDictionary>>();
+
+ private readonly int _idleTimeout;
+ private readonly int _maxOutBoundConnections;
+ private readonly int _maxConnectedSessions;
+ private readonly int _maxDisconnectedSessions;
+
+ private int _isServicing;
+ private int _isServicingCallbacks;
+ private int _isServicingCleanups;
+ private const int Servicing = 1;
+ private const int NotServicing = 0;
+ private readonly Timer _timer;
+ private readonly ConcurrentQueue _inComingRequests = new ConcurrentQueue();
+
+ private readonly ConcurrentQueue _callbacks =
+ new ConcurrentQueue();
+
+ private int _timerFired;
+ private const int TimerFired = 1;
+ private const int TimerReset = 0;
+ private bool _isDisposed;
+ private bool _isDisposing;
+ private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
+ private readonly object _syncObject = new object();
+ private static readonly PSPerfCountersMgr _perfCountersMgr = PSPerfCountersMgr.Instance;
+
+
+ ///
+ /// The servicing thread will release control of processing and the timer
+ /// thread will take control and process based on this wait handle
+ ///
+ private readonly ManualResetEvent _servicingThreadRelease = new ManualResetEvent(false);
+
+ ///
+ /// The timer thread will release control of processing and the servicing thread
+ /// will take control and process based on this wait handle
+ ///
+ private readonly ManualResetEvent _timerThreadRelease = new ManualResetEvent(true);
+
+ ///
+ /// This list is assumed to accessed by only the servicing thread and
+ /// hence is not designed to be thread safe
+ ///
+ private readonly List _pendingRequests = new List();
+
+ ///
+ /// number of sessions in the connected state
+ ///
+ /// this is made static in the interest of time
+ /// should not be static
+ //internal static int ConnectedSessionCount = 0;
+ private int _connectedSessionCount = 0;
+
+ ///
+ /// number of sessions in the disconnected state
+ ///
+ /// this is made static in the interest of time
+ /// should not be static
+ //internal static int _disconnectedSessionCount = 0;
+ private int _disconnectedSessionCount = 0;
+
+ private int _createdConnections = 0;
+
+ ///
+ /// if we need to check whether runspaces need to be
+ /// disconnected
+ ///
+ private int _checkForDisconnect = 0;
+ private const int CheckForDisconnect = 1;
+ private const int DoNotCheckForDisconnect = 0;
+
+ ///
+ /// Map of timers for each machine
+ ///
+ private readonly ConcurrentDictionary _timerMap = new ConcurrentDictionary();
+
+ #endregion Private Members
+
+ #region Constructors
+
+ internal ConnectionManager(int idleTimeout, int maxOutBoundConnections, int throttleLimit, int maxConnectedSessions, int maxDisconnectedSessions)
+ {
+ _idleTimeout = idleTimeout;
+ _maxOutBoundConnections = maxOutBoundConnections;
+ _throttleLimit = throttleLimit;
+ _maxConnectedSessions = maxConnectedSessions;
+ _maxDisconnectedSessions = maxDisconnectedSessions;
+
+ _timer = new Timer {AutoReset = true, Interval = _idleTimeout, Enabled=false};
+ _timer.Elapsed += HandleTimerElapsed;
+ _timer.Start();
+ }
+
+ #endregion Constructors
+
+ #region Interface Methods
+
+ ///
+ /// Get runspace for the specified connection info to be
+ /// used for running commands
+ ///
+ /// connection info to use
+ /// retry count
+ /// retry interval in ms
+ /// remote runspace to use
+ public override Runspace GetRunspace(WSManConnectionInfo connectionInfo, uint retryCount, uint retryInterval)
+ {
+ IAsyncResult asyncResult = BeginGetRunspace(connectionInfo, retryCount, retryInterval, null, null);
+
+ return EndGetRunspace(asyncResult);
+ }
+
+ ///
+ /// Begin for obtaining a runspace for the specified ConnectionInfo
+ ///
+ /// connection info to be used for remote connections
+ /// number of times to retry
+ /// optional user defined callback
+ /// optional user specified state
+ /// time in milliseconds before the next retry has to be attempted
+ /// async result
+ public override IAsyncResult BeginGetRunspace(WSManConnectionInfo connectionInfo, uint retryCount, uint retryInterval, AsyncCallback callback, object state)
+ {
+ GetRunspaceAsyncResult result = new GetRunspaceAsyncResult(state, callback, Guid.Empty);
+
+ // Create a Request Object and queue the same
+ RequestInfo requestInfo = new RequestInfo
+ {
+ ConnectionInfo = connectionInfo,
+ RetryCount = retryCount,
+ AsyncResult = result,
+ RetryInterval = retryInterval
+ };
+
+ _tracer.WriteMessage("PSW ConnMgr: New incoming request for runspace queued");
+ _inComingRequests.Enqueue(requestInfo);
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingPendingRequestsQueueLength);
+
+ // start servicing thread if required
+ CheckAndStartRequiredThreads();
+
+ return result;
+ }
+
+ ///
+ /// End for obtaining a runspace for the specified connection info
+ ///
+ /// async result to end on
+ /// remote runspace to invoke commands on
+ public override Runspace EndGetRunspace(IAsyncResult asyncResult)
+ {
+ if (asyncResult == null)
+ throw new ArgumentNullException("asyncResult");
+
+ GetRunspaceAsyncResult result = asyncResult as GetRunspaceAsyncResult;
+
+ if (result == null)
+ throw new ArgumentException(Resources.InvalidAsyncResultSpecified, "asyncResult");
+
+ AssertNotDisposed();
+
+ // this will throw an exeption when a runspace is not successfully
+ // available
+ result.EndInvoke();
+
+ Debug.Assert(result.Connection != null, "EndInvoke() should throw an exception if connection is null");
+
+ _tracer.WriteMessage("PSW ConnMgr: Request serviced and runspace returned");
+ Runspace runspace = result.Connection != null ? result.Connection.Runspace : null;
+
+ return runspace;
+ }
+
+ ///
+ /// Release the runspace once the activity is done using the same
+ ///
+ /// runspace to release
+ public override void ReleaseRunspace(Runspace runspace)
+ {
+ AssertNotDisposed();
+
+ Connection connection = GetConnectionForRunspace(runspace);
+ _tracer.WriteMessage("PSW ConnMgr: Runspace released");
+
+ // at this point connection will be not null
+ // since GetConnectionForRunspace() should have
+ // handled all cases and raised an exception
+ Debug.Assert(connection != null, "GetConnectionForRunspace has not handled all cases and raised an exception");
+
+ connection.Busy = false;
+ connection.AsyncResult = null;
+
+ CheckAndStartRequiredThreads();
+ }
+
+ ///
+ /// Request a cleanup to the destination specified in the
+ /// connection info. This means no runspaces will be held
+ /// to the specified connection info.
+ ///
+ /// connection info to which
+ /// cleanup is desired
+ ///callback to invoke
+ /// caller specified state
+ public override void RequestCleanup(WSManConnectionInfo connectionInfo, WaitCallback callback, object state)
+ {
+ if (_isDisposed || _isDisposing)
+ {
+ if (callback != null)
+ {
+ callback(state);
+ }
+
+ return;
+ }
+
+ // add this computer to the list of cleanup computers
+ string computerName = connectionInfo.ComputerName;
+
+ ConcurrentQueue> callbacks =
+ _cleanupComputers.GetOrAdd(computerName, new ConcurrentQueue>());
+
+ var tuple = new Tuple(callback, state);
+ callbacks.Enqueue(tuple);
+
+ CheckAndStartCleanupThread();
+ }
+
+ ///
+ /// Checks to see if the provider intentionally disconnected a runspace
+ /// or it went into disconnected state due to network issues
+ ///
+ /// runspace that needs to be checked
+ /// true - when intentionally disconnected
+ /// false - disconnected due to network issues
+ public override bool IsDisconnectedByRunspaceProvider(Runspace runspace)
+ {
+ Connection connection = GetConnectionForRunspace(runspace);
+ return connection.DisconnectedIntentionally;
+ }
+
+ internal void DisconnectCalled()
+ {
+ lock (_syncObject)
+ {
+ _disconnectedSessionCount++;
+ _connectedSessionCount--;
+ }
+ }
+ internal void ReconnectCalled()
+ {
+ lock (_syncObject)
+ {
+ _connectedSessionCount++;
+ _disconnectedSessionCount--;
+ }
+ }
+
+ #endregion Interface Methods
+
+ #region Private Methods
+
+ private static void ThrowInvalidRunspaceException(Runspace runspace)
+ {
+ throw new ArgumentException(Resources.InvalidRunspaceSpecified, "runspace");
+ }
+
+ ///
+ /// Handle the idle timer event
+ ///
+ /// unused
+ /// unused
+ private void HandleTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ // ensure that the servicing thread is done before proceeding
+ if (Interlocked.CompareExchange(ref _timerFired, TimerFired, TimerReset) == TimerFired)
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Another timer thread is already servicing return");
+ return;
+ }
+
+ if (_isDisposed || _isDisposing)
+ {
+ return;
+ }
+
+ try
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Timer fired");
+
+ _servicingThreadRelease.WaitOne();
+ _timerThreadRelease.Reset();
+
+ _tracer.WriteMessage("PSW ConnMgr: Timer servicing started");
+
+ // when the timer elapses mark all unused connections to be closed
+ Collection toRemoveComputer = new Collection();
+
+ foreach (string computername in _connectionPool.Keys)
+ {
+ ConcurrentDictionary> table = _connectionPool[computername];
+
+ Collection toRemoveConfig = new Collection();
+ foreach (string configName in table.Keys)
+ {
+ ConcurrentDictionary connections = table[configName];
+ Collection toRemoveConnections = new Collection();
+
+ lock (_syncObject)
+ {
+ foreach (Connection connection in connections.Values)
+ {
+ // if connection has been marked idle the last
+ // time remove resources
+ if (connection.Idle)
+ {
+ toRemoveConnections.Add(connection);
+ }
+ else if (!connection.Busy)
+ {
+ connection.Idle = true;
+ }
+ }
+ }
+
+ // remove all connections that need to be removed
+ foreach (Connection connection in toRemoveConnections)
+ {
+ Connection removeConnection;
+ _createdConnections--;
+ // remove connection from the table before attempting to
+ // close it. This will ensure that this connection is not
+ // assigned by mistake
+ connections.TryRemove(connection.InstanceId, out removeConnection);
+ _tracer.WriteMessage(String.Format(CultureInfo.InvariantCulture, "Closing idle connection to {0}", connection.Runspace.ConnectionInfo.ComputerName));
+ SubmitOperation(new CloseOperation(connection));
+ }
+
+ // if there are no more entries in connections it needs
+ // to be removed from the table
+ if (connections.Count == 0)
+ {
+ toRemoveConfig.Add(configName);
+ }
+
+ } // go through all configurations for a specified computer
+
+ foreach (string configName in toRemoveConfig)
+ {
+ ConcurrentDictionary removedConnections;
+ table.TryRemove(configName, out removedConnections);
+ }
+
+ // if there are no more entries in table it needs to be removed
+ // from the connection pool
+ if (table.Keys.Count == 0)
+ {
+ toRemoveComputer.Add(computername);
+ }
+
+ } // go through all computernames
+
+ // remove unwanted computernames from the connectionpool
+ foreach (string computerName in toRemoveComputer)
+ {
+ ConcurrentDictionary> removedTable;
+ _connectionPool.TryRemove(computerName, out removedTable);
+ }
+
+ _tracer.WriteMessage("PSW ConnMgr: Timer servicing completed");
+
+ Interlocked.CompareExchange(ref _timerFired, TimerReset, TimerFired);
+ _timerThreadRelease.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Ignoring this exception
+ }
+ finally
+ {
+ CheckAndStartRequiredThreads();
+ }
+ }
+
+ private void TraceThreadPoolInfo(string message)
+ {
+#if DEBUG
+ int maxWorkerThreads, maxCompletionPortThreads, availableWorkerThreads, availableCompletionPortThreads,
+ minWorkerThreads, minCompletionPortThreads;
+
+ ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);
+ ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads);
+ ThreadPool.GetAvailableThreads(out availableWorkerThreads, out availableCompletionPortThreads);
+
+ _tracer.WriteMessage(string.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "PSW ConnMgr: {0} minwt:{1} mincpt:{2} wt:{3} ct:{4} awt:{5} act:{6}",
+ message, minWorkerThreads, minCompletionPortThreads, maxWorkerThreads,
+ maxCompletionPortThreads, availableWorkerThreads,
+ availableCompletionPortThreads));
+#else
+ _tracer.WriteMessage(string.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "PSW ConnMgr: {0}",
+ message));
+#endif
+ }
+
+ ///
+ /// Checks to see if a thread is already servicing and if not starts one
+ ///
+ private void CheckAndStartConnectionServicingThread()
+ {
+ if (Interlocked.CompareExchange(ref _isServicing, Servicing, NotServicing) != NotServicing) return;
+
+ TraceThreadPoolInfo("QueueUserWorkItem Connection Servicing thread");
+ ThreadPool.QueueUserWorkItem(ServiceRequests);
+ }
+
+ ///
+ /// Checks to see if a thread is already servicing callbacks
+ /// and if not starts one
+ ///
+ private void CheckAndStartCallbackServicingThread()
+ {
+ if (Interlocked.CompareExchange(ref _isServicingCallbacks, Servicing, NotServicing) != NotServicing) return;
+
+ TraceThreadPoolInfo("Callback thread");
+ ThreadPool.QueueUserWorkItem(ServiceCallbacks);
+ }
+
+ ///
+ /// Method that services the callbacks for all
+ /// successfully assigned runspaces
+ ///
+ /// unused
+ private void ServiceCallbacks(object state)
+ {
+ if (_isDisposed || _isDisposing)
+ {
+ return;
+ }
+
+ GetRunspaceAsyncResult result;
+ while (_callbacks.TryDequeue(out result))
+ {
+ result.SetAsCompleted(null);
+ }
+
+ // release servicing callbacks
+ Interlocked.CompareExchange(ref _isServicingCallbacks, NotServicing, Servicing);
+ }
+
+ internal void AddToPendingCallback(GetRunspaceAsyncResult asyncResult)
+ {
+ _callbacks.Enqueue(asyncResult);
+ CheckAndStartCallbackServicingThread();
+ }
+
+ private void CheckAndStartRequiredThreads()
+ {
+ if (_isDisposed || _isDisposing)
+ {
+ return;
+ }
+
+ CheckAndStartConnectionServicingThread();
+ CheckAndStartDisconnectReconnectThread();
+ CheckAndStartThrottleManagerThread();
+ CheckAndStartCleanupThread();
+ }
+
+ ///
+ /// Method that contains the core servicing logic
+ ///
+ /// not used
+ private void ServiceRequests(object state)
+ {
+ try
+ {
+ TraceThreadPoolInfo("Starting servicing thread");
+
+ // ensure that idle timer thread has completed execution
+ // before proceeding
+ _timerThreadRelease.WaitOne();
+ _servicingThreadRelease.Reset();
+
+ if (NeedToReturnFromServicing())
+ {
+ return;
+ }
+
+ Collection toRemove = new Collection();
+
+ // service all pending requests
+ foreach (RequestInfo info in _pendingRequests)
+ {
+ if (ServiceOneRequest(info)) toRemove.Add(info);
+
+ if (NeedToReturnFromServicing())
+ {
+ return;
+ }
+ }
+
+ // remove serviced requests from pending queues
+ foreach (RequestInfo info in toRemove)
+ {
+ _pendingRequests.Remove(info);
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingForcedToWaitRequestsQueueLength,
+ -1);
+
+ }
+
+ // if timer has fired, return
+ if (NeedToReturnFromServicing())
+ {
+ return;
+ }
+
+ // start servicing the incoming requests
+ RequestInfo requestInfo;
+ while (_inComingRequests.TryDequeue(out requestInfo))
+ {
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingPendingRequestsQueueLength,
+ -1);
+ if (!ServiceOneRequest(requestInfo))
+ {
+ // the request could not be serviced now
+ // add it to the pending queue
+ _pendingRequests.Add(requestInfo);
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingForcedToWaitRequestsQueueLength);
+ }
+
+ if (NeedToReturnFromServicing())
+ {
+ return;
+ }
+
+ } // service incoming requests
+ }
+ catch (ObjectDisposedException)
+ {
+ //
+ }
+ finally
+ {
+ // set servicing as complete
+ SafelyReturnFromServicing();
+
+ // Try to start the servicing thread again if there are any _inComingRequests.
+ // This is required to fix the race condition between CheckAndStartRequiredThreads() and ServiceRequests() methods.
+ //
+ if (_inComingRequests.Count > 0)
+ {
+ CheckAndStartConnectionServicingThread();
+ }
+ }
+ }
+
+ ///
+ /// Services one single request
+ ///
+ /// request to service
+ /// true, if successfully serviced, false otherwise
+ private bool ServiceOneRequest(RequestInfo requestInfo)
+ {
+ string computerName = requestInfo.ConnectionInfo.ComputerName;
+ string configName = requestInfo.ConnectionInfo.ShellUri;
+
+ // if a cleanup is requested for a specified computer
+ // do not service the request until it is good to do so
+ if (_cleanupComputers.ContainsKey(computerName))
+ return false;
+
+ int existingConnections = 0;
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingRequestsBeingServicedCount);
+
+ // check if there is a table for this computername
+ ConcurrentDictionary> table =
+ _connectionPool.GetOrAdd(computerName,
+ new ConcurrentDictionary>());
+
+ // count the number of existing connections to this computer
+ existingConnections = table.Keys.Sum(key => table[key].Count);
+
+ // check if there is a connection collection for the specified configuration
+ ConcurrentDictionary connections = table.GetOrAdd(configName,
+ new ConcurrentDictionary
+ ());
+
+ bool activityResumption = false;
+ RunCommandsArguments args = requestInfo.AsyncResult.AsyncState as RunCommandsArguments;
+
+ if (args != null && args.ImplementationContext != null)
+ activityResumption = !args.ImplementationContext.DisconnectedRunspaceInstanceId.Equals(Guid.Empty);
+
+ // Existing connections should not be used when remote activity is resuming as they have a valid remote runspace
+ // Remote activity resume operation has to reconnect to the disconnected runspace
+ if (!activityResumption)
+ {
+ // table is available, check if there are any free runspaces
+ // if the collections was newly created, then this loop will
+ // be skipped)
+ foreach (Connection connection in
+ connections.Values.Where(connection => !connection.Busy).Where(connection => ValidateConnection(requestInfo, connection)))
+ {
+ // the assumption is connected session count and
+ // disconnected session count apply only to running
+ // pipelines. So a connection that is not busy will
+ // not be in the disconnected state
+ //Debug.Assert(connection.Runspace.RunspaceStateInfo.State != RunspaceState.Disconnected,
+ // "A not Busy connection should not be in the disconnected state");
+ _tracer.WriteMessage("PSW ConnMgr: Assigning existing connection to request");
+ AssignConnection(requestInfo, connection);
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingRequestsBeingServicedCount,
+ -1);
+ return true;
+ }
+ }
+
+ if (existingConnections < _maxOutBoundConnections)
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Creating new connection to service request");
+ // the number of connections hasn't maxed out for this
+ // computer - a new connection can be created
+ Connection connection = CreateConnection(requestInfo, connections);
+
+ // when the open succeeds within the specified retry attempts
+ // the connection will be assigned.
+ // submit the connection to the pending operations queue instead
+ // of opening it directly
+ SubmitOperation(new OpenOperation(connection));
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingRequestsBeingServicedCount,
+ -1);
+ return true;
+ }
+
+ // when the existing connections are maxed out, there
+ // are one of two choices
+ // 1. all connections are busy, in which case
+ // the request cannot be serviced
+ // 2. there are some idle connections, which
+ // can be closed and a new one created
+
+ Connection potentialConnection = null;
+
+ // find the first available idle connection
+ ConcurrentDictionary removeConnections=null;
+
+ foreach (var key in table.Keys)
+ {
+ removeConnections = table[key];
+
+ potentialConnection = removeConnections.Values.Where(connection => !connection.Busy).FirstOrDefault();
+
+ if (potentialConnection != null) break;
+ }
+
+ if (potentialConnection == null)
+ {
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingRequestsBeingServicedCount,
+ -1);
+ return false; /* case 1 */
+ }
+
+ // it is possible that the potential connection in fact
+ // is a good match, if so return the same
+ // Existing connections should not be used when remote activity is resuming as they have a valid remote runspace
+ // Remote activity resume operation has to reconnect to the disconnected runspace
+ if (!activityResumption && ValidateConnection(requestInfo, potentialConnection))
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Assigning potential connection to service request");
+ AssignConnection(requestInfo, potentialConnection);
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingRequestsBeingServicedCount,
+ -1);
+ return true;
+ }
+
+ // there is a potential connection which can be closed,
+ // close the same and create a new one
+ removeConnections.TryRemove(potentialConnection.InstanceId, out potentialConnection);
+
+ Debug.Assert(potentialConnection != null, "Trying to remove an element not in the dictionary");
+ _tracer.WriteMessage("PSW ConnMgr: Closing potential connection and creating a new one to service request");
+
+ // Create the connection object which will be returned after
+ // the potential connection is closed
+ _createdConnections--;
+ Connection newConnection = CreateConnection(requestInfo, connections);
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingConnectionsClosedReopendCount);
+ SubmitOperation(new CloseOneAndOpenAnotherOperation(potentialConnection, newConnection));
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingRequestsBeingServicedCount,
+ -1);
+ return true;
+ }
+
+ private Connection CreateConnection(RequestInfo requestInfo, ConcurrentDictionary connections)
+ {
+ Connection connection = new Connection(this)
+ {
+ ConnectionInfo = requestInfo.ConnectionInfo,
+ RetryCount = requestInfo.RetryCount,
+ RetryInterval = requestInfo.RetryInterval,
+ RetryAttempt = 0,
+ AsyncResult = requestInfo.AsyncResult,
+ Busy = true
+ };
+
+ // the busy status should be set before adding to the
+ // collection so that this connection is accounted in
+ // the total and is not assigned to another request
+ connections.TryAdd(connection.InstanceId, connection);
+
+ _createdConnections++;
+ _perfCountersMgr.UpdateCounterByValue(
+ PSWorkflowPerformanceCounterSetInfo.CounterSetId,
+ PSWorkflowPerformanceCounterIds.PSRemotingConnectionsCreatedCount);
+ return connection;
+ }
+
+ ///
+ /// Do everything required so that servicing thread can return
+ ///
+ private void SafelyReturnFromServicing()
+ {
+ if (_isDisposed || _isDisposing)
+ {
+ return;
+ }
+
+ CheckAndStartRequiredThreads();
+
+ try
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Safely returning from servicing");
+ Interlocked.CompareExchange(ref _isServicing, NotServicing, Servicing);
+ _servicingThreadRelease.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Ignoring ObjectDisposedException
+ }
+ }
+
+ ///
+ /// Check if the servicing thread need to stop processing requests
+ ///
+ /// true if processing needs to stop
+ private bool NeedToReturnFromServicing()
+ {
+ if(_isDisposed || _isDisposing)
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Returning from servicing as ConnMgr is disposed or being disposed");
+ return true;
+ }
+
+ // servicing thread has to return if the timer
+ // has been fired
+ if (_timerFired == TimerFired)
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Returning from servicing since timer fired");
+ return true;
+ }
+
+ if (_createdConnections >= _maxConnectedSessions)
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Setting check for runspaces disconnect flag");
+ Interlocked.CompareExchange(ref _checkForDisconnect, CheckForDisconnect, DoNotCheckForDisconnect);
+ }
+
+ return false;
+ }
+
+ private static bool ValidateConnection(RequestInfo requestInfo, Connection connection)
+ {
+ if (connection.Runspace.RunspaceStateInfo.State != RunspaceState.Opened ) return false;
+
+ WSManConnectionInfo connectionInfo = requestInfo.ConnectionInfo;
+
+ // when validation is called the Connection will have a runspace populated
+ WSManConnectionInfo connectionInfo2 = connection.Runspace.OriginalConnectionInfo as WSManConnectionInfo;
+
+ // Runspace.OriginalConnectionInfo is null for disconnected runspace after process crash
+ if(connectionInfo2 == null)
+ connectionInfo2 = connection.Runspace.ConnectionInfo as WSManConnectionInfo;
+
+ if (connectionInfo2 == null) return false;
+
+ // check URI related stuff
+ if (!WorkflowUtils.CompareConnectionUri(connectionInfo, connectionInfo2))
+ {
+ return false;
+ }
+
+ // compare shell URI
+ if (!WorkflowUtils.CompareShellUri(connectionInfo.ShellUri, connectionInfo2.ShellUri))
+ {
+ return false;
+ }
+
+ // check authentication
+ if (!WorkflowUtils.CompareAuthentication(connectionInfo.AuthenticationMechanism, connectionInfo2.AuthenticationMechanism))
+ {
+ return false;
+ }
+
+ // check credentials if present
+ if(!WorkflowUtils.CompareCredential(connectionInfo.Credential, connectionInfo2.Credential))
+ {
+ return false;
+ }
+
+ //check certificate thumbprint
+ if (!WorkflowUtils.CompareCertificateThumbprint(connectionInfo.CertificateThumbprint, connectionInfo2.CertificateThumbprint))
+ {
+ return false;
+ }
+
+ //check proxy settings
+ if (!WorkflowUtils.CompareProxySettings(connectionInfo, connectionInfo2))
+ {
+ return false;
+ }
+
+ //check rest of wsman settings
+ if (!WorkflowUtils.CompareOtherWSManSettings(connectionInfo, connectionInfo2))
+ {
+ return false;
+ }
+
+ // check open timeout
+ if (connectionInfo2.IdleTimeout < connectionInfo.IdleTimeout)
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Service the request using the available connection
+ ///
+ /// request to service
+ /// connection to use for servicing
+ private void AssignConnection(RequestInfo requestInfo, Connection connection)
+ {
+ IAsyncResult asyncResult = requestInfo.AsyncResult;
+ GetRunspaceAsyncResult result = asyncResult as GetRunspaceAsyncResult;
+ Debug.Assert(result != null, "IAsyncResult should be GetRunspaceAsyncResult");
+
+ connection.Busy = true;
+
+ connection.AsyncResult = result;
+ result.Connection = connection;
+
+ AddToPendingCallback(result);
+ }
+
+ ///
+ /// Find the connection object given a runspace
+ ///
+ /// runspace whose connection
+ /// needs to be found
+ /// Connection if found, null otherwise
+ private Connection GetConnectionForRunspace(Runspace runspace)
+ {
+ if (runspace == null)
+ throw new ArgumentNullException("runspace");
+
+ // OriginalConnectionInfo will be null for reconnected remoterunspace after crash or shutdown
+ WSManConnectionInfo connectionInfo = runspace.ConnectionInfo as WSManConnectionInfo;
+
+ if (connectionInfo == null)
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Incoming connectioninfo is null");
+ // throw an exception here
+ ThrowInvalidRunspaceException(runspace);
+ }
+
+ string computername = connectionInfo.ComputerName;
+ string configname = connectionInfo.ShellUri;
+
+ ConcurrentDictionary> table;
+ ConcurrentDictionary connections;
+
+ if (!_connectionPool.TryGetValue(computername, out table))
+ {
+ // invalid runspace specified, throw an exception here
+ _tracer.WriteMessage("PSW ConnMgr: Cannot find table for computername " + computername);
+ ThrowInvalidRunspaceException(runspace);
+ }
+
+ if (!table.TryGetValue(configname, out connections))
+ {
+ // invalid runspace specified, throw an exception here
+ _tracer.WriteMessage("PSW ConnMgr: Cannot find list for config " + configname);
+ ThrowInvalidRunspaceException(runspace);
+ }
+
+ foreach (Connection connection in
+ connections.Values.Where(connection => connection.Runspace != null).Where(connection => connection.Runspace.InstanceId == runspace.InstanceId))
+ {
+ return connection;
+ }
+
+ // if this point is reached, then there is no match
+ // invalid runspace specified, throw an exception
+ _tracer.WriteMessage("PSW ConnMgr: Cannot find the actual connection object");
+ ThrowInvalidRunspaceException(runspace);
+
+ return null;
+ }
+
+ ///
+ /// Check and start a method which will do cleanups to
+ /// the specified computer
+ ///
+ private void CheckAndStartCleanupThread()
+ {
+ if (Interlocked.CompareExchange(ref _isServicingCleanups, Servicing, NotServicing) != NotServicing)
+ return;
+
+ TraceThreadPoolInfo("Cleanup thread");
+
+ ThreadPool.QueueUserWorkItem(ServiceCleanupRequests);
+ }
+
+ ///
+ /// Worker method for servicing cleanups to requested computers
+ ///
+ ///
+ private void ServiceCleanupRequests(object state)
+ {
+
+ foreach (string computerName in _cleanupComputers.Keys)
+ {
+ ConcurrentDictionary> table;
+ _connectionPool.TryGetValue(computerName, out table);
+
+ if (table == null)
+ {
+ RaiseCallbacksAfterCleanup(computerName);
+ continue;
+ }
+
+ foreach (string configName in table.Keys)
+ {
+ ConcurrentDictionary connections;
+ table.TryGetValue(configName, out connections);
+
+ if (connections == null) continue;
+ lock (_syncObject)
+ {
+ foreach (CloseOperation closeOperation in
+ connections.Values.Where(connection => !connection.Busy).Select(connection => new CloseOperation(connection)))
+ {
+ closeOperation.OperationComplete += HandleCloseOperationComplete;
+ SubmitOperation(closeOperation);
+ }
+ }
+ }
+ }
+
+ Interlocked.CompareExchange(ref _isServicingCleanups, NotServicing, Servicing);
+ }
+
+ ///
+ /// Handles OperationComplete of close operations i.e
+ /// when connections to a specified computer is closed
+ ///
+ /// the CloseOperation object
+ /// that initiated this event
+ /// event parameters
+ private void HandleCloseOperationComplete(object sender, EventArgs e)
+ {
+ // the specified connection was successfully closed
+ CloseOperation closeOperation = sender as CloseOperation;
+ Debug.Assert(closeOperation != null, "CloseOperation object need to be returned in OperationComplete event");
+ closeOperation.OperationComplete -= HandleCloseOperationComplete;
+
+ Connection connection = closeOperation.Connection;
+ WSManConnectionInfo connectionInfo = connection.Runspace.ConnectionInfo as WSManConnectionInfo;
+ if (connectionInfo == null) return;
+
+ string computerName = connectionInfo.ComputerName;
+ string configName = connectionInfo.ShellUri;
+
+ ConcurrentDictionary> table;
+ _connectionPool.TryGetValue(computerName, out table);
+ if (table == null) return;
+
+ ConcurrentDictionary connections;
+ table.TryGetValue(configName, out connections);
+ if (connections == null) return;
+
+ // remove the connection from the table for this configName
+ Connection removedConnection;
+ connections.TryRemove(connection.InstanceId, out removedConnection);
+ if (connections.Count != 0) return;
+
+ // if there are no more connections to this configName
+ // remove the table for the configName
+ ConcurrentDictionary removedConnections;
+ table.TryRemove(configName, out removedConnections);
+ if (table.Count != 0) return;
+
+ // if there are no more tables for any specified configNames
+ // for this computerName, remove the table for the computerName
+ ConcurrentDictionary> removedTable;
+ _connectionPool.TryRemove(computerName, out removedTable);
+
+ if (removedTable != null)
+ {
+ // if this thread is the one that removed the table, then
+ // make all the callbacks in this thread
+ RaiseCallbacksAfterCleanup(computerName);
+ }
+ }
+
+ ///
+ /// Raise all the callbacks to the specified computer
+ /// after the requested cleanup
+ ///
+ /// computer to which the
+ /// callback needs to be raised
+ private void RaiseCallbacksAfterCleanup(string computerName)
+ {
+ bool alreadyAdded = _timerMap.Values.Contains(computerName, StringComparer.OrdinalIgnoreCase);
+
+ // before making the callbacks fire the timer
+ Timer timer = new Timer();
+ _timerMap.TryAdd(timer, computerName);
+
+ timer.Elapsed += HandleCleanupWaitTimerElapsed;
+
+ ConcurrentQueue> waitCallbacks;
+ if (!_cleanupComputers.TryGetValue(computerName, out waitCallbacks))
+ {
+ _tracer.WriteMessage("PSW ConnMgr: Cannot find specified computer in _waitCallbacks dictionary: " + computerName);
+ return;
+ }
+
+ Tuple tuple;
+ Collection> callbacks = new Collection>();
+
+ int highest = 0;
+ bool calledFromActivity = false;
+
+ while (waitCallbacks.TryDequeue(out tuple))
+ {
+ if (tuple.Item2 != null)
+ {
+ RunCommandsArguments args = tuple.Item2 as RunCommandsArguments;
+
+ if (args != null)
+ {
+ if (highest < args.CleanupTimeout)
+ {
+ highest = args.CleanupTimeout;
+ }
+
+ calledFromActivity = true;
+ }
+ }
+
+ callbacks.Add(tuple);
+ }
+
+ if (highest != 0)
+ {
+ timer.Interval = highest * 1000;
+ timer.AutoReset = false;
+ timer.Enabled = true;
+
+ foreach (Tuple t in callbacks)
+ {
+ if (t.Item1 != null)
+ t.Item1(t.Item2);
+ }
+ }
+ else
+ {
+ if (!alreadyAdded)
+ {
+ HandleCleanupWaitTimerElapsed(timer, null);
+ }
+ else
+ {
+ string unused;
+ _timerMap.TryRemove(timer, out unused);
+ }
+ }
+
+ if (!calledFromActivity)
+ {
+ foreach(var callbackTuple in callbacks)
+ {
+ if (callbackTuple.Item1 != null)
+ callbackTuple.Item1(callbackTuple.Item2);
+ }
+ }
+ }
+
+ ///
+ /// Timer elapsed handler for the specified computer. Once
+ /// the timer elapses, new connections to the specified
+ /// computer will be allowed
+ ///
+ /// timer that generated the event
+ /// event arguments
+ private void HandleCleanupWaitTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ Timer timer = sender as Timer;
+ Debug.Assert(timer != null, "Sender cannot be null");
+
+ string computerName;
+ _timerMap.TryGetValue(timer, out computerName);
+ if (!string.IsNullOrEmpty(computerName))
+ {
+ ConcurrentQueue> removedCallbacks;
+ _cleanupComputers.TryRemove(computerName, out removedCallbacks);
+ }
+
+ // remove entry from the timer map and do cleanup
+ _timerMap.TryRemove(timer, out computerName);
+
+ timer.Elapsed -= HandleCleanupWaitTimerElapsed;
+ timer.Dispose();
+
+ CheckAndStartRequiredThreads();
+ }
+
+ #endregion Private Methods
+
+ #region Test Helper Methods
+
+ internal bool IsConnectionPoolEmpty()
+ {
+ return _connectionPool.Keys.Count > 0;
+ }
+
+ internal IEnumerable GetConnectionEnumerator()
+ {
+ return new ConnectionEnumerator(_connectionPool);
+ }
+ #endregion Test Helper Methods
+
+ #region Dispose
+
+ ///
+ /// Dispose the connection manager
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Dipose the connection manager
+ ///
+ ///
+ protected void Dispose(bool isDisposing)
+ {
+ if (_isDisposed || !isDisposing)
+ return;
+
+ lock (_syncObject)
+ {
+ if (_isDisposed)
+ return;
+
+ _isDisposing = true;
+
+ // set these values to servicing so that a thread is
+ // not started
+ Interlocked.CompareExchange(ref _isServicing, Servicing, NotServicing);
+ Interlocked.CompareExchange(ref _isServicingCallbacks, Servicing, NotServicing);
+ Interlocked.CompareExchange(ref _isServicingCleanups, Servicing, NotServicing);
+ Interlocked.CompareExchange(ref _timerFired, TimerFired, TimerReset);
+
+ // close and clear all connections in connection pool
+ ClearAll();
+
+ // This should be done after closing all connections.
+ // Throttling thread executes the close operations
+ Interlocked.CompareExchange(ref _isOperationsServiced, Servicing, NotServicing);
+
+ _timer.Elapsed -= HandleTimerElapsed;
+ _timer.Dispose();
+
+ var objectDisposedException = new ObjectDisposedException("ConnectionManager");
+ GetRunspaceAsyncResult item;
+ while (_callbacks.TryDequeue(out item))
+ {
+ item.SetAsCompleted(objectDisposedException);
+ }
+ _cleanupComputers.Clear();
+
+ RequestInfo info;
+ while (_inComingRequests.Count > 0)
+ {
+ _inComingRequests.TryDequeue(out info);
+ info.AsyncResult.SetAsCompleted(objectDisposedException);
+ }
+
+ foreach(var reqInfo in _pendingRequests)
+ {
+ reqInfo.AsyncResult.SetAsCompleted(objectDisposedException);
+ }
+ _pendingRequests.Clear();
+
+ ThrottleOperation operation;
+ while (_pendingQueue.Count > 0)
+ {
+ _pendingQueue.TryDequeue(out operation);
+ }
+
+ _timerMap.Clear();
+ _servicingThreadRelease.Close();
+ _timerThreadRelease.Close();
+ _testHelperCloseDone.Close();
+ _tracer.Dispose();
+
+ _isDisposed = true;
+ }
+ }
+
+ private void AssertNotDisposed()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException("ConnectionManager");
+ }
+ }
+ #endregion Dispose
+
+ #region Throttling
+
+ ///
+ /// queue of operations to be used for throttling
+ ///
+ private readonly ConcurrentQueue _pendingQueue = new ConcurrentQueue();
+
+ ///
+ /// Count of operations in progress
+ ///
+ private int _inProgressCount = 0;
+
+ ///
+ /// throttle limit - includes open/close/connect/disconnect operations
+ ///
+ private readonly int _throttleLimit;
+
+ ///
+ /// is the queue of operations being serviced
+ ///
+ private int _isOperationsServiced;
+
+ ///
+ /// submit an operation to the queue
+ ///
+ /// operation to submit
+ private void SubmitOperation(ThrottleOperation operation)
+ {
+ _pendingQueue.Enqueue(operation);
+ CheckAndStartThrottleManagerThread();
+ }
+
+ private void CheckAndStartThrottleManagerThread()
+ {
+ // if not already set, set flag that pending operations queue is being serviced
+ // else another thread is already servicing so return
+ if (Interlocked.CompareExchange(ref _isOperationsServiced, Servicing, NotServicing) != NotServicing) return;
+
+ TraceThreadPoolInfo("Queuing user workitem Running operations in throttle queue");
+
+ ThreadPool.QueueUserWorkItem(StartOperationsFromQueue);
+ }
+
+ ///
+ /// Start operations upto throttle limit from the queue
+ ///
+ /// this method is thread safe. It starts all pending
+ /// operations in the first calling thread. The assumption here
+ /// is that starting a few operations isn't expensive and so
+ /// the calling thread is not blocked for a long period of time
+ private void StartOperationsFromQueue(object state)
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ TraceThreadPoolInfo("Running operations in throttle queue");
+
+ // at this point, there will be only one thread which will be
+ // servicing operations from the pending operations queue
+ ThrottleOperation operation;
+
+ while (_inProgressCount < _throttleLimit && _pendingQueue.TryDequeue(out operation))
+ {
+ operation.OperationComplete += HandleOperationComplete;
+ Interlocked.Increment(ref _inProgressCount);
+ operation.DoOperation();
+ }
+
+ _tracer.WriteMessage("PSW ConnMgr: Done throttling");
+ // set flag that pending operations queue is not being serviced
+ Interlocked.CompareExchange(ref _isOperationsServiced, NotServicing, Servicing);
+ }
+
+ private void HandleOperationComplete (Object sender, EventArgs e)
+ {
+ ThrottleOperation operation = sender as ThrottleOperation;
+ Debug.Assert(operation != null, "OperationComplete event does not pass ThrottleOperation as sender");
+ operation.OperationComplete -= HandleOperationComplete;
+ Interlocked.Decrement(ref _inProgressCount);
+ CheckAndStartRequiredThreads();
+ }
+
+ #endregion Throttling
+
+ #region Connection Enumerator
+
+ private class ConnectionEnumerator : IEnumerator, IEnumerable
+ {
+ private Connection _currentConnection;
+ private ConcurrentDictionary _currentConnections;
+ private ConcurrentDictionary> _currentTable;
+ private readonly IEnumerator _tableEnumerator;
+ private IEnumerator _configEnumerator;
+ private IEnumerator _connectionEnumerator;
+
+ private readonly ConcurrentDictionary>>
+ _connectionPool;
+
+ internal ConnectionEnumerator(ConcurrentDictionary>> connectionPool)
+ {
+ Debug.Assert(connectionPool != null, "ConnectionPool cannot be null");
+ _connectionPool = connectionPool;
+ _tableEnumerator = _connectionPool.Keys.GetEnumerator();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public bool MoveNext()
+ {
+ while (true)
+ {
+ if (_connectionEnumerator != null && _connectionEnumerator.MoveNext())
+ {
+ Guid key = (Guid) _connectionEnumerator.Current;
+ _currentConnection = null;
+ _currentConnections.TryGetValue(key, out _currentConnection);
+ if (_currentConnection != null)
+ return true;
+ else
+ continue;
+ }
+
+ if (_configEnumerator != null && _configEnumerator.MoveNext())
+ {
+ string configName = (string)_configEnumerator.Current;
+ _currentTable.TryGetValue(configName, out _currentConnections);
+ _connectionEnumerator = _currentConnections.Keys.GetEnumerator();
+
+ Debug.Assert(_connectionEnumerator != null, "Enumerator should not be null");
+
+ continue;
+ }
+
+ if (_tableEnumerator.MoveNext())
+ {
+ string tableName = (string)_tableEnumerator.Current;
+ _connectionPool.TryGetValue(tableName, out _currentTable);
+ _configEnumerator = _currentTable.Keys.GetEnumerator();
+ Debug.Assert(_configEnumerator != null, "Enumerator should not be null");
+ continue;
+ }
+
+ break;
+ }
+
+ return false;
+ }
+
+ ///
+ ///
+ ///
+ public void Reset()
+ {
+ _tableEnumerator.Reset();
+ _currentTable = (ConcurrentDictionary>)_tableEnumerator.Current;
+ _configEnumerator = _currentTable.Keys.GetEnumerator();
+ _currentConnections = (ConcurrentDictionary)_configEnumerator.Current;
+ _connectionEnumerator = _currentConnections.Keys.GetEnumerator();
+ Guid key = (Guid)_connectionEnumerator.Current;
+ _currentConnections.TryGetValue(key, out _currentConnection);
+ }
+
+ ///
+ ///
+ ///
+ public object Current
+ {
+ get { return _currentConnection; }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return this;
+ }
+ }
+
+ #endregion Connection Enumerator
+
+ #region Disconnect/Reconnect
+
+ private int _isReconnectServicing;
+ private const long NotMarked = 0;
+ private const long Marked = 1;
+ private long _newConnectionMarked = NotMarked;
+
+ private void CheckAndStartDisconnectReconnectThread()
+ {
+ if (_checkForDisconnect != CheckForDisconnect) return;
+
+ if (Interlocked.CompareExchange(ref _isReconnectServicing, Servicing, NotServicing) != NotServicing) return;
+
+ TraceThreadPoolInfo("Queuing user workitem disconnect/reconnect worker");
+ ThreadPool.QueueUserWorkItem(DisconnectReconnectWorker);
+ }
+
+ ///
+ /// Disconnect and reconnect to different runspaces as necessary
+ /// so as to help the connection manager scale and run a large
+ /// number of commands on a large number of remote nodes
+ ///
+ /// unused
+ private void DisconnectReconnectWorker(object state)
+ {
+ if (_isDisposed || _isDisposing)
+ {
+ return;
+ }
+
+ TraceThreadPoolInfo("Running disconnect/reconnect worker");
+
+ Interlocked.CompareExchange(ref _newConnectionMarked, NotMarked, Marked);
+
+ while (Interlocked.CompareExchange(ref _newConnectionMarked, Marked, NotMarked) == Marked)
+ {
+ foreach (Connection connection in
+ (new ConnectionEnumerator(_connectionPool)))
+ {
+ if (_disconnectedSessionCount > _maxDisconnectedSessions) break;
+
+ if (!connection.Busy) continue;
+ if (!connection.ReadyForDisconnect) continue;
+
+ connection.DisconnectedIntentionally = true;
+ // add a disconnect operation to the queue of operations
+ SubmitOperation(new DisconnectOperation(connection));
+ }
+
+ // at this point all relevant connections have been disconnected
+ // now connect to every remote connection for a specified period of time
+
+ // only connections which were initially marked for disconnected
+ // would have been disconnected
+ foreach (Connection connection in
+ (new ConnectionEnumerator(_connectionPool)))
+ {
+ if (_connectedSessionCount > _maxConnectedSessions) break;
+
+ if (!connection.ReadyForReconnect) continue;
+
+ connection.DisconnectedIntentionally = false;
+ SubmitOperation(new ReconnectOperation(connection));
+ }
+ }
+
+ _tracer.WriteMessage("PSW ConnMgr: Exiting disconnect reconnect worker");
+ // reset flag that disconnect/reconnect thread is running
+ Interlocked.CompareExchange(ref _isReconnectServicing, NotServicing, Servicing);
+ }
+
+ ///
+ /// Callback to indicate that this runspace been initiated with
+ /// a pipeline and can be disconnected
+ ///
+ /// runspace that needs to be marked as
+ /// ready for disconnect
+ public override void ReadyForDisconnect(Runspace runspace)
+ {
+ Connection connection = GetConnectionForRunspace(runspace);
+ _tracer.WriteMessage("PSW ConnMgr: Runspace marked as ready for disconnect");
+
+ // at this point connection will be not null
+ // since GetConnectionForRunspace() should have
+ // handled all cases and raised an exception
+ Debug.Assert(connection != null, "GetConnectionForRunspace has not handled all cases and raised an exception");
+
+ if (connection.Busy)
+ {
+ // mark the connection as ready for disconnect
+ // the thread which is servicing disconnect and reconnect
+ // will take care of disconnecting and reconnecting the same
+ lock (_syncObject)
+ {
+ if (!connection.Busy)
+ return;
+
+ connection.ReadyForDisconnect = true;
+ Interlocked.CompareExchange(ref _newConnectionMarked, NotMarked, Marked);
+ }
+
+ CheckAndStartRequiredThreads();
+ }
+ }
+
+ #endregion Disconnect/Reconnect
+
+ #region Test Helpers
+
+ private readonly ManualResetEvent _testHelperCloseDone = new ManualResetEvent(false);
+ internal void ClearAll()
+ {
+ if (_connectionPool.Count > 0)
+ {
+ _testHelperCloseDone.Reset();
+
+ foreach (CloseOperation operation in
+ from Connection connection in new ConnectionEnumerator(_connectionPool)
+ select new CloseOperation(connection))
+ {
+ operation.OperationComplete += OperationComplete;
+ SubmitOperation(operation);
+ }
+
+ _testHelperCloseDone.WaitOne();
+
+ _connectionPool.Clear();
+ }
+ }
+
+ private void OperationComplete(object sender, EventArgs e)
+ {
+ CloseOperation closeOperation = sender as CloseOperation;
+ Debug.Assert(closeOperation != null, "CloseOperation not returned as sender");
+ closeOperation.OperationComplete -= OperationComplete;
+
+ if (_pendingQueue.Count == 0)
+ {
+ _testHelperCloseDone.Set();
+ }
+ }
+
+ #endregion Test Helpers
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/Constants.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/Constants.cs
new file mode 100644
index 000000000..2d178f83b
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/Constants.cs
@@ -0,0 +1,91 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ internal static class Constants
+ {
+
+ internal const string AsJob = "AsJob";
+
+ // Remoting-related constants
+ internal const string ComputerName = "PSComputerName";
+ internal const string Credential = "PSCredential";
+ internal const string Port = "PSPort";
+ internal const string UseSSL = "PSUseSSL";
+ internal const string ConfigurationName = "PSConfigurationName";
+ internal const string ApplicationName = "PSApplicationName";
+ internal const string ConnectionURI = "PSConnectionURI";
+ internal const string AllowRedirection = "PSAllowRedirection";
+ internal const string SessionOption = "PSSessionOption";
+ internal const string Authentication = "PSAuthentication";
+ internal const string AuthenticationLevel = "PSAuthenticationLevel";
+ internal const string CertificateThumbprint = "PSCertificateThumbprint";
+ internal const string PSParameterCollection = "PSParameterCollection";
+ internal const string PSInputCollection = "PSInputCollection";
+ internal const string InputObject = "InputObject";
+ internal const string PSSenderInfo = "PSSenderInfo";
+ internal const string PSCurrentDirectory = "PSCurrentDirectory";
+ internal const string PSSuspendOnError = "PSSuspendOnError";
+
+ // PowerShell-common constants
+ internal const string Verbose = "Verbose";
+ internal const string Debug = "Debug";
+ internal const string ErrorAction = "ErrorAction";
+ internal const string WarningAction = "WarningAction";
+ internal const string InformationAction = "InformationAction";
+ internal const string ErrorVariable = "ErrorVariable";
+ internal const string WarningVariable = "WarningVariable";
+ internal const string InformationVariable = "InformationVariable";
+ internal const string OutVariable = "OutVariable";
+ internal const string OutBuffer = "OutBuffer";
+ internal const string PipelineVariable = "PipelineVariable";
+
+ // Retry policy constants
+ internal const string ConnectionRetryCount = "PSConnectionRetryCount";
+ internal const string ActionRetryCount = "PSActionRetryCount";
+ internal const string ConnectionRetryIntervalSec = "PSConnectionRetryIntervalSec";
+ internal const string ActionRetryIntervalSec = "PSActionRetryIntervalSec";
+
+ internal const string PrivateMetadata = "PSPrivateMetadata";
+ internal const string WorkflowTakesPrivateMetadata = "WorkflowTakesPrivateMetadata";
+ internal const string WorkflowJobCreationContext = "WorkflowJobCreationContext";
+
+ // Timers
+ internal const string PSRunningTime = "PSRunningTimeoutSec";
+ internal const string PSElapsedTime = "PSElapsedTimeoutSec";
+ internal const string Int32MaxValueDivideByThousand = "2147483";
+
+ internal const string ModulePath = "PSWorkflowRoot";
+ internal const string JobName = "JobName";
+ internal const string DefaultComputerName = "localhost";
+
+ // Job Metadata constants
+ internal const string JobMetadataInstanceId = "InstanceId";
+ internal const string JobMetadataSessionId = "Id";
+ internal const string JobMetadataName = "Name";
+ internal const string JobMetadataCommand = "Command";
+ internal const string JobMetadataStateReason = "Reason";
+ internal const string JobMetadataParentName = "ParentName";
+ internal const string JobMetadataParentCommand = "ParentCommand";
+ internal const string JobMetadataParentInstanceId = "ParentInstanceId";
+ internal const string JobMetadataParentSessionId = "ParentSessionId";
+ internal const string JobMetadataLocation = "Location";
+ internal const string JobMetadataStatusMessage = "StatusMessage";
+ internal const string JobMetadataUserName = "UserName";
+ internal const string JobMetadataPid = "ProcessId";
+ internal const string JobMetadataFilterState = "State";
+
+ internal const string Persist = "PSPersist";
+ internal const string PSRequiredModules = "PSRequiredModules";
+
+ internal const string PSWorkflowErrorAction = "PSWorkflowErrorAction";
+ internal const int MaxAllowedPersistencePathLength = 120;
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/DefaultWorkflowSessionConfiguration.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/DefaultWorkflowSessionConfiguration.cs
new file mode 100644
index 000000000..7eafc6031
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/DefaultWorkflowSessionConfiguration.cs
@@ -0,0 +1,126 @@
+/********************************************************************++
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * --********************************************************************/
+
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Management.Automation.Remoting;
+using System;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Threading;
+using System.Management.Automation.Tracing;
+using System.Globalization;
+using Microsoft.PowerShell.Commands;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ ///
+ /// Configuration for the M3P endpoint
+ ///
+ public class PSWorkflowSessionConfiguration : PSSessionConfiguration
+ {
+ ///
+ /// IsWorkflowTypeEndpoint
+ ///
+ internal static bool IsWorkflowTypeEndpoint;
+
+ #region Overrides of PSSessionConfiguration
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderInfo)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override InitialSessionState GetInitialSessionState(PSSessionConfigurationData sessionConfigurationData, PSSenderInfo senderInfo, string configProviderId)
+ {
+ Tracer structuredTracer = new Tracer();
+
+ structuredTracer.Correlate();
+
+ if (sessionConfigurationData == null)
+ throw new ArgumentNullException("sessionConfigurationData");
+
+ if (senderInfo == null)
+ throw new ArgumentNullException("senderInfo");
+
+ if (string.IsNullOrEmpty(configProviderId))
+ throw new ArgumentNullException("configProviderId");
+
+ if (Interlocked.CompareExchange(ref _modulesLoaded, ModulesLoaded, ModulesNotLoaded) == ModulesNotLoaded)
+ {
+ // it is sufficient if Populate() is called the first time and
+ // modules are loaded once
+
+ try
+ {
+ IsWorkflowTypeEndpoint = true;
+
+ PSWorkflowConfigurationProvider workflowConfiguration = WorkflowJobSourceAdapter.GetInstance().GetPSWorkflowRuntime().Configuration;
+ if (workflowConfiguration == null)
+ throw new InvalidOperationException("PSWorkflowConfigurationProvider is null");
+
+ workflowConfiguration.Populate(sessionConfigurationData.PrivateData, configProviderId, senderInfo);
+
+ // now get all the modules in the specified path and import the same
+ if (sessionConfigurationData.ModulesToImport != null)
+ {
+ foreach (var module in sessionConfigurationData.ModulesToImport)
+ {
+ ModuleSpecification moduleSpec = null;
+ if (ModuleSpecification.TryParse(module, out moduleSpec))
+ {
+ var modulesToImport = new Collection { moduleSpec };
+ InitialSessionState.ImportPSModule(modulesToImport);
+ }
+ else
+ {
+ InitialSessionState.ImportPSModule(new[] { Environment.ExpandEnvironmentVariables(module) });
+ }
+ }
+ }
+
+ // Start the workflow job manager, if not started, to add an event handler for zero active sessions changed events
+ // This is required to auto shudown the workflow type shared process when no workflow jobs have scheduled/inprogress and when no active sessions
+ WorkflowJobSourceAdapter.GetInstance().GetJobManager();
+ }
+ catch(Exception)
+ {
+ // if there is an exception in either Populate() or Importing modules
+ // we consider that it is not loaded
+ Interlocked.CompareExchange(ref _modulesLoaded, ModulesNotLoaded, ModulesLoaded);
+ throw;
+ }
+ }
+
+ if (configProviderId.ToLower(CultureInfo.InvariantCulture).Equals("http://schemas.microsoft.com/powershell/microsoft.windows.servermanagerworkflows"))
+ {
+ PSSessionConfigurationData.IsServerManager = true;
+ }
+
+ return InitialSessionState;
+ }
+
+ private static readonly InitialSessionState InitialSessionState =
+ InitialSessionState.CreateRestricted(SessionCapabilities.WorkflowServer | SessionCapabilities.RemoteServer | SessionCapabilities.Language);
+
+ private static int _modulesLoaded = ModulesNotLoaded;
+ private const int ModulesNotLoaded = 0;
+ private const int ModulesLoaded = 1;
+
+ #endregion
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/DefinitionCache.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/DefinitionCache.cs
new file mode 100644
index 000000000..4bdcbc67c
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/DefinitionCache.cs
@@ -0,0 +1,608 @@
+//
+// Copyright (C) Microsoft. All rights reserved.
+//
+using System;
+using System.Activities;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Tracing;
+using Microsoft.PowerShell.Commands;
+using System.Reflection;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ internal sealed class DefinitionCache
+ {
+
+ #region Privates
+
+ internal struct WorkflowDetails
+ {
+ internal Activity ActivityTree;
+ internal string CompiledAssemblyPath;
+ internal string CompiledAssemblyName;
+ internal bool IsWindowsActivity;
+ };
+
+ private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
+
+ ///
+ /// Cache of workflow details. This is unbounded
+ ///
+ private readonly Dictionary _workflowDetailsCache =
+ new Dictionary(new CompareBasedOnInstanceId());
+
+ ///
+ /// this is the cache of compiled activities which will be bounded
+ ///
+ /// this is separate since Opalis will not use our cache
+ private readonly ConcurrentDictionary _cachedActivities =
+ new ConcurrentDictionary(new CompareBasedOnInstanceId());
+
+ private System.Timers.Timer _cleanupTimer;
+ private int activitiesCleanupIntervalMSec;
+
+ private static readonly DefinitionCache _instance = new DefinitionCache();
+
+ private DefinitionCache()
+ {
+ activitiesCleanupIntervalMSec = WorkflowJobSourceAdapter.GetInstance().GetPSWorkflowRuntime().Configuration.ActivitiesCacheCleanupIntervalMSec;
+
+ _cleanupTimer = new System.Timers.Timer(activitiesCleanupIntervalMSec);
+ _cleanupTimer.Elapsed += HandleCleanupTimerElapsed;
+ _cleanupTimer.AutoReset = false;
+ _cleanupTimer.Start();
+ }
+
+ private void HandleCleanupTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
+ {
+ List activitiesToRemove = new List();
+ Activity activity;
+
+ foreach (var workflowJobDefinition in _cachedActivities.Keys)
+ {
+ if ((GetRuntimeAssemblyPath(workflowJobDefinition) == null) &&
+ (DateTime.Now - workflowJobDefinition.LastUsedDateTime > TimeSpan.FromMilliseconds(activitiesCleanupIntervalMSec)))
+ {
+ activitiesToRemove.Add(workflowJobDefinition);
+ }
+ }
+
+ foreach (var wfJobDefinition in activitiesToRemove)
+ {
+ _cachedActivities.TryRemove(wfJobDefinition, out activity);
+ }
+
+ // Schedule the next cleanup
+ _cleanupTimer.Start();
+ }
+
+ ///
+ /// Cache size
+ ///
+ private const int _cacheSize = 1000;
+
+ ///
+ /// Path to Windows folder
+ ///
+ private const string WindowsPath = "%windir%\\system32";
+
+ #endregion Privates
+
+ #region Internal
+
+ internal int CacheSize
+ {
+ get { return _cacheSize; }
+ }
+
+ ///
+ /// Return the singleton instance.
+ ///
+ internal static DefinitionCache Instance
+ {
+ get
+ {
+ return _instance;
+ }
+ }
+
+ internal JobDefinition GetDefinition(Guid instanceId)
+ {
+ return _workflowDetailsCache.Keys.FirstOrDefault(def => def.InstanceId == instanceId);
+ }
+
+ internal string GetWorkflowXaml(WorkflowJobDefinition definition)
+ {
+ return definition.Xaml;
+ }
+
+ internal string GetRuntimeAssemblyPath(WorkflowJobDefinition definition)
+ {
+ WorkflowDetails workflowDetail;
+
+ return _workflowDetailsCache.TryGetValue(definition, out workflowDetail) ? workflowDetail.CompiledAssemblyPath : null;
+ }
+
+ internal string GetRuntimeAssemblyName(WorkflowJobDefinition definition)
+ {
+ WorkflowDetails workflowDetail;
+
+ return _workflowDetailsCache.TryGetValue(definition, out workflowDetail) ? workflowDetail.CompiledAssemblyName : null;
+ }
+
+ internal bool AllowExternalActivity;
+
+ ///
+ /// Compiles the activity and adds it to the cache before returning it.
+ ///
+ /// WorkflowJobDefinition defined to represent a compiled activity.
+ /// Activity Tree used for external activities
+ ///
+ /// indicates if the specified xaml is a Windows workflow
+ /// Optional, once assigned, only root Workflow will be compiled
+ /// Activity compiled from xaml given, or retrieved from cache.
+ /// Null if not found.
+ internal Activity CompileActivityAndSaveInCache(WorkflowJobDefinition definition, Activity activityTree, Dictionary requiredAssemblies, out bool windowsWorkflow, string rootWorkflowName = null)
+ {
+ WorkflowDetails workflowDetail = new WorkflowDetails();
+ Activity activity = null;
+
+ // initialize windows workflow to false
+ windowsWorkflow = false;
+
+ string resultingCompiledAssemblyPath = definition.DependentAssemblyPath;
+ string modulePath = definition.ModulePath;
+ string[] dependentWorkflows = definition.DependentWorkflows.ToArray();
+ Assembly resultingCompiledAssembly = null;
+ string resultingCompiledAssemblyName = null;
+ string xaml = definition.Xaml;
+
+ if (activityTree != null)
+ {
+ _tracer.WriteMessage(string.Format(CultureInfo.InvariantCulture,
+ "DefinitionCache: Caching activity for definition with instance ID: {0}. The activity Tree is passed.",
+ definition.InstanceId));
+ workflowDetail.ActivityTree = activityTree;
+ activity = activityTree;
+ }
+ else if (!String.IsNullOrEmpty(xaml))
+ {
+ // we need to read the xaml from the specified path and compile the same
+ _tracer.WriteMessage(string.Format(CultureInfo.InvariantCulture,
+ "DefinitionCache: Caching activity for definition with instance ID: {0}. Xaml is passed",
+ definition.InstanceId));
+
+ workflowDetail.ActivityTree = null;
+ // check if specified workflow is a windows workflow
+ if (!string.IsNullOrEmpty(modulePath))
+ {
+ string resolvedPath = Environment.ExpandEnvironmentVariables(modulePath);
+ string resolvedWindowsPath = Environment.ExpandEnvironmentVariables(WindowsPath);
+ if (resolvedPath.IndexOf(resolvedWindowsPath, StringComparison.CurrentCultureIgnoreCase) != -1)
+ {
+ windowsWorkflow = true;
+ }
+ }
+
+ if (definition.DependentAssemblyPath == null && dependentWorkflows.Length == 0)
+ {
+ activity = ImportWorkflowCommand.ConvertXamlToActivity(xaml);
+ }
+ else
+ {
+ if (rootWorkflowName == null || (definition.Command == rootWorkflowName))
+ {
+ activity = ImportWorkflowCommand.ConvertXamlToActivity(xaml, dependentWorkflows,
+ requiredAssemblies,
+ ref resultingCompiledAssemblyPath,
+ ref resultingCompiledAssembly,
+ ref resultingCompiledAssemblyName);
+ }
+ else
+ {
+ activity = ImportWorkflowCommand.ConvertXamlToActivity(xaml);
+ }
+ }
+ }
+
+ if (activity != null)
+ {
+ workflowDetail.IsWindowsActivity = windowsWorkflow;
+ workflowDetail.CompiledAssemblyPath = resultingCompiledAssemblyPath;
+ workflowDetail.CompiledAssemblyName = resultingCompiledAssemblyName;
+
+ lock (_syncObject)
+ {
+ WorkflowJobDefinition definitionToRemove =
+ _workflowDetailsCache.Keys.FirstOrDefault(item => item.InstanceId == definition.InstanceId);
+ if (definitionToRemove != null)
+ _workflowDetailsCache.Remove(definition);
+
+ _workflowDetailsCache.Add(definition, workflowDetail);
+ }
+
+ // If cached activity count reaches _cacheSize,
+ // Removing the cached activity at index 0 and adding the new activity to the activity cache,
+ // Old logic was to clear 1000 cached activities and recompiling them again when needed.
+ //
+ if (_cachedActivities.Count == _cacheSize)
+ {
+ Activity removedActivity;
+ _cachedActivities.TryRemove(_cachedActivities.Keys.ElementAt(0), out removedActivity);
+ }
+
+ _cachedActivities.TryAdd(definition, activity);
+
+ return activity;
+ }
+
+ // we should never hit this point under normal course of operations
+ return null;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal Activity GetActivityFromCache(WorkflowJobDefinition definition, out bool windowsWorkflow)
+ {
+ Activity activity = null;
+ WorkflowDetails workflowDetail;
+
+ // initialize windowsWorkflow to false
+ windowsWorkflow = false;
+ if (_workflowDetailsCache.TryGetValue(definition, out workflowDetail))
+ {
+ // if there is a workflow activity we would have already computed whether it is a
+ // Windows workflow, set the same
+ windowsWorkflow = workflowDetail.IsWindowsActivity;
+
+ if (workflowDetail.ActivityTree != null)
+ {
+ if (!AllowExternalActivity)
+ {
+ Debug.Assert(false, "Product code should not contain passing an activity to definition cache");
+ }
+
+ activity = workflowDetail.ActivityTree;
+ }
+
+ // if a cached value is available return the same
+ _cachedActivities.TryGetValue(definition, out activity);
+ if (activity == null)
+ {
+ // if activity is not available in cache recompile using info in
+ // definition cache
+ activity = CompileActivityAndSaveInCache(definition, null, null, out windowsWorkflow);
+ }
+
+ definition.LastUsedDateTime = DateTime.Now;
+ }
+
+ return activity;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal Activity GetActivityFromCache(string xaml, out WorkflowJobDefinition workflowJobDefinition)
+ {
+ workflowJobDefinition = null;
+
+ foreach (var definition in
+ _workflowDetailsCache.Keys.Where(definition => string.Equals(definition.Xaml, xaml, StringComparison.OrdinalIgnoreCase)))
+ {
+ bool windowsWorkflow;
+ workflowJobDefinition = definition;
+ return GetActivityFromCache(definition, out windowsWorkflow);
+ }
+
+ return null;
+ }
+
+ private readonly object _syncObject = new object();
+
+ #endregion Internal
+
+ #region Test Helpers
+
+ internal Dictionary WorkflowDetailsCache
+ {
+ get { return _workflowDetailsCache; }
+ }
+
+ internal ConcurrentDictionary ActivityCache
+ {
+ get { return _cachedActivities; }
+ }
+
+ internal void ClearAll()
+ {
+ _workflowDetailsCache.Clear();
+ _cachedActivities.Clear();
+ }
+
+ // only called from test
+ internal Activity GetActivity(Guid instanceId)
+ {
+ WorkflowJobDefinition workflowJobDefinition = new WorkflowJobDefinition(typeof(WorkflowJobSourceAdapter),
+ string.Empty, string.Empty,
+ string.Empty,
+ WorkflowJobDefinition.EmptyEnumerable,
+ string.Empty, null, string.Empty) { InstanceId = instanceId };
+
+ bool windowsWorkflow;
+ Activity activity = GetActivityFromCache(workflowJobDefinition, out windowsWorkflow) ??
+ CompileActivityAndSaveInCache(workflowJobDefinition, null, null, out windowsWorkflow);
+
+ return activity;
+ }
+
+ //only called from test
+ internal Activity GetActivity(JobDefinition definition, string xaml)
+ {
+ bool windowsWorkflow;
+ WorkflowJobDefinition workflowJobDefinition = new WorkflowJobDefinition(definition, string.Empty,
+ WorkflowJobDefinition.EmptyEnumerable,
+ string.Empty,
+ xaml);
+ Activity activity = GetActivityFromCache(workflowJobDefinition, out windowsWorkflow) ??
+ CompileActivityAndSaveInCache(workflowJobDefinition, null, null, out windowsWorkflow);
+
+ return activity;
+ }
+
+ //only called from test
+ internal Activity GetActivity(JobDefinition definition, string xaml, string[] dependentWorkflows)
+ {
+ bool windowsWorkflow;
+ WorkflowJobDefinition workflowJobDefinition = new WorkflowJobDefinition(definition, string.Empty,
+ dependentWorkflows ?? WorkflowJobDefinition.EmptyEnumerable,
+ string.Empty,
+ xaml);
+
+ Activity activity = GetActivityFromCache(workflowJobDefinition, out windowsWorkflow) ??
+ CompileActivityAndSaveInCache(workflowJobDefinition, null, null, out windowsWorkflow);
+
+ return activity;
+ }
+
+ internal Activity GetActivity(JobDefinition definition, out bool windowsWorkflow)
+ {
+ WorkflowJobDefinition workflowJobDefinition = new WorkflowJobDefinition(definition);
+
+ Activity activity = GetActivityFromCache(workflowJobDefinition, out windowsWorkflow) ??
+ CompileActivityAndSaveInCache(workflowJobDefinition, null, null, out windowsWorkflow);
+
+ return activity;
+ }
+
+ ///
+ /// Remove a cached definition. Needed when functions are removed to release resources.
+ ///
+ /// Xaml definition to remove.
+ /// True if succeeded.
+ internal bool RemoveCachedActivity(JobDefinition definition)
+ {
+ Debug.Assert(definition != null);
+
+ WorkflowJobDefinition workflowJobDefinition = new WorkflowJobDefinition(definition);
+ Activity activity;
+ _cachedActivities.TryRemove(workflowJobDefinition, out activity);
+ lock (_syncObject)
+ {
+ if (_workflowDetailsCache.ContainsKey(workflowJobDefinition))
+ {
+ return _workflowDetailsCache.Remove(workflowJobDefinition);
+ }
+ }
+ return false;
+ }
+
+ internal bool RemoveCachedActivity(Guid instanceId)
+ {
+ var definition = new JobDefinition(null, null, null);
+ definition.InstanceId = instanceId;
+ return RemoveCachedActivity(definition);
+ }
+
+ #endregion Test Helpers
+ }
+
+ internal class CompareBasedOnInstanceId : IEqualityComparer
+ {
+ #region IEqualityComparer Members
+
+ public bool Equals(WorkflowJobDefinition x, WorkflowJobDefinition y)
+ {
+ return x.InstanceId == y.InstanceId;
+ }
+
+ public int GetHashCode(WorkflowJobDefinition obj)
+ {
+ return obj.InstanceId.GetHashCode();
+ }
+
+ #endregion
+ }
+
+ internal class CompareBasedOnCommand : IEqualityComparer
+ {
+ #region IEqualityComparer Members
+
+ public bool Equals(WorkflowJobDefinition x, WorkflowJobDefinition y)
+ {
+ bool returnValue = false;
+
+ if (String.Equals(y.ModulePath, x.ModulePath, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.Equals(y.Command, x.Command, StringComparison.OrdinalIgnoreCase))
+ {
+ returnValue = true;
+ }
+ }
+
+ return returnValue;
+ }
+
+ public int GetHashCode(WorkflowJobDefinition obj)
+ {
+ int hashCode = 0;
+ string moduleQualifiedCommand = string.Empty;
+
+ if (!string.IsNullOrEmpty(obj.ModulePath))
+ moduleQualifiedCommand += obj.ModulePath;
+
+ if (!string.IsNullOrEmpty(obj.Command))
+ moduleQualifiedCommand += obj.Command;
+
+ hashCode = moduleQualifiedCommand.GetHashCode();
+
+ return hashCode;
+ }
+
+ private static readonly CompareBasedOnCommand Comparer = new CompareBasedOnCommand();
+ internal static bool Compare(WorkflowJobDefinition x, WorkflowJobDefinition y)
+ {
+ return Comparer.Equals(x, y);
+ }
+
+ #endregion
+ }
+
+ internal class WorkflowJobDefinition : JobDefinition
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public WorkflowJobDefinition(Type jobSourceAdapterType, string command, string name) : base(jobSourceAdapterType, command, name)
+ {
+ IsScriptWorkflow = false;
+ }
+
+ internal WorkflowJobDefinition(Type jobSourceAdapterType, string command, string name, string modulePath,
+ IEnumerable dependentWorkflows, string dependentAssemblyPath, Dictionary requiredAssemblies, string xaml)
+ : this(jobSourceAdapterType, command, name)
+ {
+ _modulePath = modulePath;
+ _dependentAssemblyPath = dependentAssemblyPath;
+ _dependentWorkflows.AddRange(dependentWorkflows);
+ _requiredAssemblies = requiredAssemblies;
+ _xaml = xaml;
+ _lastUsedDateTime = DateTime.Now;
+ }
+
+ internal WorkflowJobDefinition(JobDefinition jobDefinition, string modulePath,
+ IEnumerable dependentWorkflows, string dependentAssemblyPath, string xaml)
+ : this(jobDefinition.JobSourceAdapterType, jobDefinition.Command, jobDefinition.Name, modulePath, dependentWorkflows, dependentAssemblyPath, null, xaml)
+ {
+ InstanceId = jobDefinition.InstanceId;
+ }
+
+ internal WorkflowJobDefinition(JobDefinition jobDefinition)
+ :this(jobDefinition, string.Empty, EmptyEnumerable, string.Empty, string.Empty)
+ {
+
+ }
+
+ private readonly string _modulePath;
+ internal string ModulePath
+ {
+ get { return _modulePath; }
+ }
+
+ private readonly List _dependentWorkflows = new List();
+ internal List DependentWorkflows
+ {
+ get { return _dependentWorkflows; }
+ }
+
+ private readonly string _dependentAssemblyPath;
+ internal string DependentAssemblyPath
+ {
+ get { return _dependentAssemblyPath; }
+ }
+
+ private readonly Dictionary _requiredAssemblies;
+ internal Dictionary RequiredAssemblies
+ {
+ get { return _requiredAssemblies; }
+ }
+
+ private readonly string _xaml;
+ internal string Xaml
+ {
+ get { return _xaml; }
+ }
+
+ internal bool IsScriptWorkflow { get; set; }
+
+ internal static IEnumerable EmptyEnumerable = new Collection();
+
+ private DateTime _lastUsedDateTime;
+ internal DateTime LastUsedDateTime
+ {
+ get { return _lastUsedDateTime; }
+ set { _lastUsedDateTime = value; }
+ }
+
+ ///
+ /// Returns the same object is the specified job definition is a
+ /// WorkflowJobDefinition. If not creates a new one and assigns
+ /// the same
+ ///
+ /// job definition to check
+ /// WorkflowJobDefinition equivalent
+ internal static WorkflowJobDefinition AsWorkflowJobDefinition(JobDefinition definition)
+ {
+ return definition as WorkflowJobDefinition ??
+ (DefinitionCache.Instance.GetDefinition(definition.InstanceId) as WorkflowJobDefinition ??
+ new WorkflowJobDefinition(definition));
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Workflow script file name or null if no associated file.
+ ///
+ internal string WorkflowScriptFile
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Full workflow script source.
+ ///
+ internal string WorkflowFullScript
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/AsyncResult.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/AsyncResult.cs
new file mode 100644
index 000000000..8935ea98f
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/AsyncResult.cs
@@ -0,0 +1,174 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.Diagnostics;
+ using System.Threading;
+
+ ///
+ /// A generic base class for IAsyncResult implementations
+ /// that wraps a ManualResetEvent.
+ ///
+ abstract class FileStoreAsyncResult : IAsyncResult
+ {
+ AsyncCallback callback;
+ object state;
+ bool completedSynchronously;
+ bool endCalled;
+ Exception exception;
+ bool isCompleted;
+ ManualResetEvent manualResetEvent;
+ object thisLock;
+
+ protected FileStoreAsyncResult(AsyncCallback callback, object state)
+ {
+ this.callback = callback;
+ this.state = state;
+ this.thisLock = new object();
+ }
+
+ public object AsyncState
+ {
+ get
+ {
+ return state;
+ }
+ }
+
+ public WaitHandle AsyncWaitHandle
+ {
+ get
+ {
+ if (manualResetEvent != null)
+ {
+ return manualResetEvent;
+ }
+
+ lock (ThisLock)
+ {
+ if (manualResetEvent == null)
+ {
+ manualResetEvent = new ManualResetEvent(isCompleted);
+ }
+ }
+
+ return manualResetEvent;
+ }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get
+ {
+ return completedSynchronously;
+ }
+ }
+
+ public bool IsCompleted
+ {
+ get
+ {
+ return isCompleted;
+ }
+ }
+
+ object ThisLock
+ {
+ get
+ {
+ return this.thisLock;
+ }
+ }
+
+ // Call this version of complete when your asynchronous operation is complete. This will update the state
+ // of the operation and notify the callback.
+ protected void Complete(bool completedSynchronously)
+ {
+ if (isCompleted)
+ {
+ // It is a bug to call Complete twice.
+ throw new InvalidOperationException(Resources.AsyncResultAlreadyCompleted);
+ }
+
+ this.completedSynchronously = completedSynchronously;
+
+ if (completedSynchronously)
+ {
+ // If we completedSynchronously, then there is no chance that the manualResetEvent was created so
+ // we do not need to worry about a race condition.
+ Debug.Assert(this.manualResetEvent == null, "No ManualResetEvent should be created for a synchronous AsyncResult.");
+ this.isCompleted = true;
+ }
+ else
+ {
+ lock (ThisLock)
+ {
+ this.isCompleted = true;
+ if (this.manualResetEvent != null)
+ {
+ this.manualResetEvent.Set();
+ }
+ }
+ }
+
+ // If the callback throws, there is a bug in the callback implementation
+ if (callback != null)
+ {
+ callback(this);
+ }
+ }
+
+ // Call this version of complete if you raise an exception during processing. In addition to notifying
+ // the callback, it will capture the exception and store it to be thrown during AsyncResult.End.
+ protected void Complete(bool completedSynchronously, Exception exception)
+ {
+ this.exception = exception;
+ Complete(completedSynchronously);
+ }
+
+ // End should be called when the End function for the asynchronous operation is complete. It
+ // ensures the asynchronous operation is complete, and does some common validation.
+ protected static TAsyncResult End(IAsyncResult result)
+ where TAsyncResult : FileStoreAsyncResult
+ {
+ if (result == null)
+ {
+ throw new ArgumentNullException("result");
+ }
+
+ TAsyncResult asyncResult = result as TAsyncResult;
+
+ if (asyncResult == null)
+ {
+ throw new ArgumentException(Resources.InvalidAsyncResult);
+ }
+
+ if (asyncResult.endCalled)
+ {
+ throw new InvalidOperationException(Resources.AsyncResultAlreadyEnded);
+ }
+
+ asyncResult.endCalled = true;
+
+ if (!asyncResult.isCompleted)
+ {
+ asyncResult.AsyncWaitHandle.WaitOne();
+ }
+
+ if (asyncResult.manualResetEvent != null)
+ {
+ asyncResult.manualResetEvent.Close();
+ }
+
+ if (asyncResult.exception != null)
+ {
+ throw asyncResult.exception;
+ }
+
+ return asyncResult;
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/FileInstanceStore.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/FileInstanceStore.cs
new file mode 100644
index 000000000..0f95d8ec8
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/FileInstanceStore.cs
@@ -0,0 +1,225 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.Runtime.DurableInstancing;
+ using System.Activities.DurableInstancing;
+ using System.Collections.Generic;
+ using System.Xml.Linq;
+ using System.IO;
+ using System.Xml;
+ using System.Management.Automation.Tracing;
+ using System.Activities.Persistence;
+
+ internal class FileInstanceStore : InstanceStore
+ {
+ private static readonly Tracer StructuredTracer = new Tracer();
+
+ private readonly PSWorkflowFileInstanceStore _stores;
+ internal FileInstanceStore(PSWorkflowFileInstanceStore stores)
+ {
+ _stores = stores;
+ }
+
+ protected override IAsyncResult BeginTryCommand(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
+ {
+ StructuredTracer.Correlate();
+
+ try
+ {
+ if (command is SaveWorkflowCommand)
+ {
+ return new TypedCompletedAsyncResult(SaveWorkflow(context, (SaveWorkflowCommand)command), callback, state);
+ }
+ else if (command is LoadWorkflowCommand)
+ {
+ return new TypedCompletedAsyncResult(LoadWorkflow(context, (LoadWorkflowCommand)command), callback, state);
+ }
+ else if (command is CreateWorkflowOwnerCommand)
+ {
+ return new TypedCompletedAsyncResult(CreateWorkflowOwner(context, (CreateWorkflowOwnerCommand)command), callback, state);
+ }
+ else if (command is DeleteWorkflowOwnerCommand)
+ {
+ return new TypedCompletedAsyncResult(DeleteWorkflowOwner(context, (DeleteWorkflowOwnerCommand)command), callback, state);
+ }
+ return new TypedCompletedAsyncResult(false, callback, state);
+ }
+ catch (Exception e)
+ {
+ return new TypedCompletedAsyncResult(e, callback, state);
+ }
+ }
+
+ protected override bool EndTryCommand(IAsyncResult result)
+ {
+ StructuredTracer.Correlate();
+
+ TypedCompletedAsyncResult exceptionResult = result as TypedCompletedAsyncResult;
+ if (exceptionResult != null)
+ {
+ throw exceptionResult.Data;
+ }
+ return TypedCompletedAsyncResult.End(result);
+ }
+
+ private bool SaveWorkflow(InstancePersistenceContext context, SaveWorkflowCommand command)
+ {
+ if (context.InstanceVersion == -1)
+ {
+ context.BindAcquiredLock(0);
+ }
+
+ if (command.CompleteInstance)
+ {
+ context.CompletedInstance();
+ }
+ else
+ {
+ string instanceType = "";
+
+ const string InstanceTypeXName = "{urn:schemas-microsoft-com:System.Runtime.DurableInstancing/4.0/metadata}InstanceType";
+ InstanceValue instanceTypeInstanceValue;
+ if (command.InstanceMetadataChanges.TryGetValue(InstanceTypeXName, out instanceTypeInstanceValue))
+ {
+ instanceType = instanceTypeInstanceValue.Value.ToString();
+ }
+
+ Dictionary fullInstanceData = new Dictionary();
+ fullInstanceData.Add("instanceId", context.InstanceView.InstanceId);
+ fullInstanceData.Add("instanceOwnerId", context.InstanceView.InstanceOwner.InstanceOwnerId);
+ fullInstanceData.Add("instanceData", SerializeablePropertyBag(command.InstanceData));
+ fullInstanceData.Add("instanceMetadata", SerializeInstanceMetadata(context, command));
+
+ foreach (KeyValuePair property in command.InstanceMetadataChanges)
+ {
+ context.WroteInstanceMetadataValue(property.Key, property.Value);
+ }
+
+ context.PersistedInstance(command.InstanceData);
+
+ _stores.Save(WorkflowStoreComponents.Definition
+ | WorkflowStoreComponents.Metadata
+ | WorkflowStoreComponents.Streams
+ | WorkflowStoreComponents.TerminatingError
+ | WorkflowStoreComponents.Timer
+ | WorkflowStoreComponents.ActivityState
+ | WorkflowStoreComponents.JobState,
+ fullInstanceData);
+
+ }
+
+ return true;
+ }
+
+ private bool LoadWorkflow(InstancePersistenceContext context, LoadWorkflowCommand command)
+ {
+ if (command.AcceptUninitializedInstance)
+ {
+ return false;
+ }
+
+ if (context.InstanceVersion == -1)
+ {
+ context.BindAcquiredLock(0);
+ }
+
+ Guid instanceId = context.InstanceView.InstanceId;
+ Guid instanceOwnerId = context.InstanceView.InstanceOwner.InstanceOwnerId;
+
+ IDictionary instanceData = null;
+ IDictionary instanceMetadata = null;
+
+ Dictionary fullInstanceData = _stores.LoadWorkflowContext();
+
+ instanceData = this.DeserializePropertyBag((Dictionary)fullInstanceData["instanceData"]);
+ instanceMetadata = this.DeserializePropertyBag((Dictionary)fullInstanceData["instanceMetadata"]);
+
+ context.LoadedInstance(InstanceState.Initialized, instanceData, instanceMetadata, null, null);
+
+ return true;
+ }
+
+ private bool CreateWorkflowOwner(InstancePersistenceContext context, CreateWorkflowOwnerCommand command)
+ {
+ Guid instanceOwnerId = Guid.NewGuid();
+ context.BindInstanceOwner(instanceOwnerId, instanceOwnerId);
+ context.BindEvent(HasRunnableWorkflowEvent.Value);
+ return true;
+ }
+
+ private bool DeleteWorkflowOwner(InstancePersistenceContext context, DeleteWorkflowOwnerCommand command)
+ {
+ return true;
+ }
+
+ private Dictionary SerializeablePropertyBag(IDictionary source)
+ {
+ Dictionary scratch = new Dictionary();
+ foreach (KeyValuePair property in source)
+ {
+ bool writeOnly = (property.Value.Options & InstanceValueOptions.WriteOnly) != 0;
+
+ if (!writeOnly && !property.Value.IsDeletedValue)
+ {
+ scratch.Add(property.Key, property.Value.Value);
+ }
+ }
+
+ return scratch;
+ }
+ private Dictionary SerializeInstanceMetadata(InstancePersistenceContext context, SaveWorkflowCommand command)
+ {
+ Dictionary metadata = null;
+
+ foreach (var property in command.InstanceMetadataChanges)
+ {
+ if (!property.Value.Options.HasFlag(InstanceValueOptions.WriteOnly))
+ {
+ if (metadata == null)
+ {
+ metadata = new Dictionary();
+ // copy current metadata. note that we must rid of InstanceValue as it is not properly serializeable
+ foreach (var m in context.InstanceView.InstanceMetadata)
+ {
+ metadata.Add(m.Key, m.Value.Value);
+ }
+ }
+
+ if (metadata.ContainsKey(property.Key))
+ {
+ if (property.Value.IsDeletedValue) metadata.Remove(property.Key);
+ else metadata[property.Key] = property.Value.Value;
+ }
+ else
+ {
+ if (!property.Value.IsDeletedValue) metadata.Add(property.Key, property.Value.Value);
+ }
+ }
+ }
+
+ if (metadata == null)
+ metadata = new Dictionary();
+
+ return metadata;
+ }
+
+ private IDictionary DeserializePropertyBag(Dictionary source)
+ {
+ Dictionary destination = new Dictionary();
+
+ foreach (KeyValuePair property in source)
+ {
+ destination.Add(property.Key, new InstanceValue(property.Value));
+ }
+
+ return destination;
+ }
+
+
+ }
+}
+
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/InstanceStoreCryptography.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/InstanceStoreCryptography.cs
new file mode 100644
index 000000000..6801cb071
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/InstanceStoreCryptography.cs
@@ -0,0 +1,68 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.IO;
+ using System.Security.Cryptography;
+ using System.Management.Automation.Tracing;
+
+ ///
+ /// This class implements the encrypt and decrypt functionality.
+ ///
+ internal class InstanceStoreCryptography
+ {
+ ///
+ /// Tracer initialization.
+ ///
+ private static readonly PowerShellTraceSource Tracer = PowerShellTraceSourceFactory.GetTraceSource();
+
+ ///
+ /// The additional entroy for security 'POWERSHELLWORKFLOW'
+ ///
+ private static byte[] s_aditionalEntropy = { (byte)'P', (byte)'O', (byte)'W', (byte)'E', (byte)'R', (byte)'S', (byte)'H', (byte)'E', (byte)'L', (byte)'L', (byte)'W', (byte)'O', (byte)'R', (byte)'K', (byte)'F', (byte)'L', (byte)'O', (byte)'W' };
+
+ ///
+ /// Protect the data.
+ ///
+ /// The input data for encryption.
+ /// Returns encrypted data.
+ internal static byte[] Protect(byte[] data)
+ {
+ try
+ {
+ // Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted
+ // only by the same current user.
+ return ProtectedData.Protect(data, s_aditionalEntropy, DataProtectionScope.CurrentUser);
+ }
+ catch (CryptographicException e)
+ {
+ Tracer.TraceException(e);
+
+ throw e;
+ }
+ }
+
+ ///
+ /// Unprotect data the encrypted data.
+ ///
+ /// Encrypted data.
+ /// Returns decrypted data.
+ internal static byte[] Unprotect(byte[] data)
+ {
+ try
+ {
+ return ProtectedData.Unprotect(data, s_aditionalEntropy, DataProtectionScope.CurrentUser);
+ }
+ catch (CryptographicException e)
+ {
+ Tracer.TraceException(e);
+
+ throw e;
+ }
+
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/InstanceStorePermission.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/InstanceStorePermission.cs
new file mode 100644
index 000000000..2317389f2
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/InstanceStorePermission.cs
@@ -0,0 +1,44 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.IO;
+ using System.Security.Cryptography;
+ using System.Management.Automation.Tracing;
+ using System.Security.AccessControl;
+ using System.Security.Principal;
+
+ ///
+ /// This class implements the encrypt and decrypt functionality.
+ ///
+ internal class InstanceStorePermission
+ {
+ internal static void SetDirectoryPermissions(string folderName)
+ {
+ string account = WindowsIdentity.GetCurrent().Name;
+ RemoveInheritablePermissons(folderName);
+ AddDirectorySecurity(folderName, account, FileSystemRights.Modify, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow);
+ }
+
+ private static void AddDirectorySecurity(string folderName, string account, FileSystemRights rights, InheritanceFlags inheritance, PropagationFlags propogation, AccessControlType controlType)
+ {
+ DirectoryInfo info = new DirectoryInfo(folderName);
+ DirectorySecurity dSecurity = info.GetAccessControl();
+ dSecurity.AddAccessRule(new FileSystemAccessRule(account, rights, inheritance, propogation, controlType));
+ info.SetAccessControl(dSecurity);
+ }
+
+ private static void RemoveInheritablePermissons(string folderName)
+ {
+ DirectoryInfo info = new DirectoryInfo(folderName);
+ DirectorySecurity dSecurity = info.GetAccessControl();
+ const bool IsProtected = true;
+ const bool PreserveInheritance = false;
+ dSecurity.SetAccessRuleProtection(IsProtected, PreserveInheritance);
+ info.SetAccessControl(dSecurity);
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/TypedAsyncResult.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/TypedAsyncResult.cs
new file mode 100644
index 000000000..dd3a3d2e1
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/TypedAsyncResult.cs
@@ -0,0 +1,41 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.Diagnostics;
+ using System.Threading;
+
+ ///
+ /// A strongly typed AsyncResult.
+ ///
+ ///
+ abstract class TypedAsyncResult : FileStoreAsyncResult
+ {
+ T data;
+
+ protected TypedAsyncResult(AsyncCallback callback, object state)
+ : base(callback, state)
+ {
+ }
+
+ public T Data
+ {
+ get { return data; }
+ }
+
+ protected void Complete(T data, bool completedSynchronously)
+ {
+ this.data = data;
+ Complete(completedSynchronously);
+ }
+
+ public static T End(IAsyncResult result)
+ {
+ TypedAsyncResult typedResult = FileStoreAsyncResult.End>(result);
+ return typedResult.Data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/TypedCompletedAsyncResult.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/TypedCompletedAsyncResult.cs
new file mode 100644
index 000000000..92ce3cde6
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/TypedCompletedAsyncResult.cs
@@ -0,0 +1,35 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.Diagnostics;
+ using System.Threading;
+
+ ///
+ /// A strongly typed AsyncResult that completes as soon as it is instantiated.
+ ///
+ ///
+ class TypedCompletedAsyncResult : TypedAsyncResult
+ {
+ public TypedCompletedAsyncResult(T data, AsyncCallback callback, object state)
+ : base(callback, state)
+ {
+ Complete(data, true);
+ }
+
+ public new static T End(IAsyncResult result)
+ {
+ TypedCompletedAsyncResult completedResult = result as TypedCompletedAsyncResult;
+
+ if (completedResult == null)
+ {
+ throw new ArgumentException(Resources.InvalidAsyncResult);
+ }
+
+ return TypedAsyncResult.End(completedResult);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/WorkflowAdditionalStores.cs b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/WorkflowAdditionalStores.cs
new file mode 100644
index 000000000..066a39486
--- /dev/null
+++ b/src/Microsoft.PowerShell.Workflow.ServiceCore/ServiceCore/WorkflowCore/FileInstanceStore/WorkflowAdditionalStores.cs
@@ -0,0 +1,2039 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+using System.Diagnostics;
+using System.IO.Compression;
+using System.Management.Automation.Remoting;
+
+namespace Microsoft.PowerShell.Workflow
+{
+ using System;
+ using System.IO;
+ using System.Management.Automation;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Runtime.Serialization;
+ using System.Xml.Linq;
+ using System.Runtime.DurableInstancing;
+ using System.Globalization;
+ using System.Management.Automation.Tracing;
+ using System.Activities.DurableInstancing;
+ using System.Activities.Persistence;
+ using System.Reflection;
+ using Microsoft.PowerShell.Commands;
+ using System.Text;
+
+ internal class PersistenceVersion
+ {
+ private readonly PowerShellTraceSource Tracer = PowerShellTraceSourceFactory.GetTraceSource();
+ private bool saved = false;
+ private object syncLock = new object();
+
+ internal Version StoreVersion { get; set; }
+ internal Version CLRVersion { get; set; }
+ internal bool EnableEncryption { get; set; }
+ internal bool EnableCompression { get; set; }
+
+ internal PersistenceVersion(bool enableEncryption, bool enableCompression)
+ {
+ StoreVersion = new Version(1, 0);
+ CLRVersion = Environment.Version;
+ EnableEncryption = enableEncryption;
+ EnableCompression = enableCompression;
+ }
+
+ internal void save(string versionFileName)
+ {
+ if (saved) return;
+
+ lock (syncLock)
+ {
+
+ if (saved) return;
+ saved = true;
+
+ if (File.Exists(versionFileName))
+ return;
+
+ XElement versionXml = new XElement("PersistenceVersion",
+ new XElement("StoreVersion", StoreVersion),
+ new XElement("CLRVersion", CLRVersion),
+ new XElement("EnableEncryption", EnableEncryption),
+ new XElement("EnableCompression", EnableCompression));
+
+ versionXml.Save(versionFileName);
+ }
+ }
+
+ internal void load(string versionFileName)
+ {
+ try
+ {
+ if (!File.Exists(versionFileName))
+ return; // use the value provided in the constructor
+
+ XElement versionXml = XElement.Load(versionFileName);
+
+ if (versionXml.Name.LocalName.Equals("PersistenceVersion", StringComparison.OrdinalIgnoreCase))
+ {
+ foreach (XElement x in versionXml.Elements())
+ {
+ if (x.Name.LocalName.Equals("StoreVersion", StringComparison.OrdinalIgnoreCase))
+ {
+ this.StoreVersion = new Version(x.Value);
+ }
+ else if (x.Name.LocalName.Equals("CLRVersion", StringComparison.OrdinalIgnoreCase))
+ {
+ this.CLRVersion = new Version(x.Value);
+ }
+ else if (x.Name.LocalName.Equals("EnableEncryption", StringComparison.OrdinalIgnoreCase))
+ {
+ this.EnableEncryption = Convert.ToBoolean(x.Value, CultureInfo.InvariantCulture);
+ }
+ else if (x.Name.LocalName.Equals("EnableCompression", StringComparison.OrdinalIgnoreCase))
+ {
+ this.EnableCompression = Convert.ToBoolean(x.Value, CultureInfo.InvariantCulture);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ //default values will be used
+ Tracer.WriteMessage(string.Format(CultureInfo.InvariantCulture, "Exception while reading or parsing the version file: {0}", versionFileName));
+ Tracer.TraceException(e);
+ }
+ }
+ }
+
+
+
+ internal enum InternalStoreComponents
+ {
+ Streams = 0,
+ Metadata = 1,
+ Definition = 2,
+ Timer = 3,
+ JobState = 4,
+ TerminatingError = 5,
+ Context = 6,
+ ActivityState = 7,
+ }
+
+ ///
+ /// This class implements the functionality for storing the streams data on to the disk.
+ ///
+ public class PSWorkflowFileInstanceStore : PSWorkflowInstanceStore
+ {
+ # region private variables
+
+ private readonly PowerShellTraceSource Tracer = PowerShellTraceSourceFactory.GetTraceSource();
+ private static readonly Tracer etwTracer = new Tracer();
+
+ private readonly string Streams = "Str";
+ private readonly string Error = "Err";
+ private readonly string Metadatas = "Meta";
+ private readonly string Definition = "Def";
+ private readonly string WorkflowState = "Stat";
+
+ private readonly string Version_xml = "V.xml";
+
+ private readonly string InputStream_xml = "IS.xml";
+ private readonly string OutputStream_xml = "OS.xml";
+ private readonly string ErrorStream_xml = "ES.xml";
+ private readonly string WarningStream_xml = "WS.xml";
+ private readonly string VerboseStream_xml = "VS.xml";
+ private readonly string ProgressStream_xml = "PS.xml";
+ private readonly string DebugStream_xml = "DS.xml";
+ private readonly string InformationStream_xml = "INFS.xml";
+
+ private readonly string ErrorException_xml = "EE.xml";
+
+ private readonly string Input_xml = "I.xml";
+ private readonly string PSWorkflowCommonParameters_xml = "UI.xml";
+ private readonly string JobMetadata_xml = "JM.xml";
+ private readonly string PrivateMetadata_xml = "PM.xml";
+ private readonly string Timer_xml = "TI.xml";
+
+ private readonly string WorkflowInstanceState_xml = "WS.xml";
+
+ private readonly string WorkflowDefinition_xaml = "WD.xaml";
+ private readonly string RuntimeAssembly_dll = "RA.dll";
+ private readonly string RequiredAssemblies_xml = "RA.xml";
+
+ private readonly string State_xml = "S.xml";
+
+ private readonly string ActivityState_xml = "AS.xml";
+
+ private readonly object _syncLock = new object();
+
+ private bool firstTimeStoringDefinition;
+
+ private PSWorkflowConfigurationProvider _configuration;
+
+ private Dictionary SavedComponentLengths;
+
+ private long writtenTotalBytes = 0;
+
+ # endregion
+
+ // For testing purpose ONLY
+ internal static bool TestMode = false;
+ // For testing purpose ONLY
+ internal static long ObjectCounter = 0;
+
+ ///
+ /// Returns all Workflow guids.
+ ///
+ ///
+ public static IEnumerable GetAllWorkflowInstanceIds()
+ {
+ PSWorkflowConfigurationProvider _configuration = (PSWorkflowConfigurationProvider) PSWorkflowRuntime.Instance.Configuration;
+ DirectoryInfo dir = new DirectoryInfo(_configuration.InstanceStorePath);
+ if (dir.Exists)
+ {
+ foreach (DirectoryInfo childDir in dir.GetDirectories())
+ {
+ Guid id;
+ if (Guid.TryParse(childDir.Name, out id))
+ yield return new PSWorkflowId(id);
+ }
+ }
+ }
+
+
+ ///
+ /// PSWorkflowFileInstanceStore ctor
+ ///
+ ///
+ ///
+ public PSWorkflowFileInstanceStore(PSWorkflowConfigurationProvider configuration, PSWorkflowInstance instance)
+ : base(instance)
+ {
+ if (configuration == null)
+ throw new ArgumentNullException("configuration");
+
+ if (TestMode)
+ {
+ System.Threading.Interlocked.Increment(ref ObjectCounter);
+ }
+
+ _configuration = configuration;
+ firstTimeStoringDefinition = true;
+ SavedComponentLengths = new Dictionary();
+
+ bool enableCompression = true;
+ _disablePersistenceLimits = true;
+ if (PSSessionConfigurationData.IsServerManager)
+ {
+ enableCompression = false;
+ _disablePersistenceLimits = false;
+ }
+
+ _version = new PersistenceVersion(_configuration.PersistWithEncryption, enableCompression);
+ _version.load(Path.Combine(_configuration.InstanceStorePath, PSWorkflowInstance.Id.ToString(), Version_xml));
+ }
+
+ private void SaveVersionFile()
+ {
+ string storePath = Path.Combine(_configuration.InstanceStorePath, PSWorkflowInstance.Id.ToString());
+ EnsureInstancePath(storePath);
+
+ _version.save(Path.Combine(storePath, Version_xml));
+ }
+
+ private PersistenceVersion _version;
+
+ ///
+ /// CreateInstanceStore
+ ///
+ ///
+ public override InstanceStore CreateInstanceStore()
+ {
+ return new FileInstanceStore(this);
+ }
+
+ ///
+ /// CreatePersistenceIOParticipant
+ ///
+ ///
+ public override PersistenceIOParticipant CreatePersistenceIOParticipant()
+ {
+ return null;
+ }
+
+ # region Serialization
+
+ ///
+ /// To be called from test code only.
+ ///
+ ///
+ internal void CallDoSave(IEnumerable