//
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
//
// DISCLAIMER OF WARRANTY: The software is licensed “as-is.” You
// bear the risk of using it. Microsoft gives no express warranties,
// guarantees or conditions. You may have additional consumer rights
// under your local laws which this agreement cannot change. To the extent
// permitted under your local laws, Microsoft excludes the implied warranties
// of merchantability, fitness for a particular purpose and non-infringement.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.PowerShell.Activities;
using Microsoft.PowerShell.Workflow;
using System.Activities;
using System.Management.Automation;
using System.Collections.Concurrent;
using System.Threading;
using System.Management.Automation.Runspaces;
namespace ActivityControllerExtensibilitySample
{
// This activity controller sample does not handle multiple concurrent requests from the same job, but it could be extended to do so.
class SampleActivityController : PSResumableActivityHostController
{
// Pass the PSWorkflowRuntime as the parameter.
// This resumes execution of the job.
public SampleActivityController(PSWorkflowRuntime runtime)
: base(runtime)
{
_runtime = runtime;
}
private PSWorkflowRuntime _runtime;
// This parameter notifies the workflow runtime about the usage of the streams.
// If there is no need to maintain the live streams from the job then, this method should return true.
// If the disconnected streams are supported then the activit can be performed on a different computer.
// Once the job is completed the data is sent to the live streams.
public override bool SupportDisconnectedPSStreams
{
get
{
return true;
}
}
// This function executes the activity.
// It is highly recommended to not to block the execution of this function while executing the activity.
// All the information should be logged into the queue and the function should be returned.
// In a separate thread or on a separate machine, read the data from the queue and execute the activity.
// Once the activity action is completed the bookmark should be resumed.
// Design the activity controller to hanlde multiple activities from one workflowflow.
public override void StartResumablePSCommand(Guid jobInstanceId, Bookmark bookmark, PowerShell command, PowerShellStreams streams, PSActivityEnvironment environment, PSActivity activityInstance)
{
ActivityActionData data = new ActivityActionData();
data.jobInstanceId = jobInstanceId;
data.bookmark = bookmark;
data.command = command;
data.streams = streams;
data.environment = environment;
// Add the request to the queue.
ActivityActionsQueue.TryAdd(jobInstanceId, data);
// Return the fucntion and allow the workfow do other work in parallel.
// There should be a servicing thead which gets the data from the queue and perform the action.
// To keep this sample simple, the worker thread calls the servicing function.
ThreadPool.QueueUserWorkItem(ServiceRequests, jobInstanceId);
}
// This function is called to stop the execution of the job.
public override void StopAllResumablePSCommands(Guid jobInstanceId)
{
this.StopExecution(jobInstanceId);
}
#region Private Variables and Function
// Dictionary inside dictionary can be used for handling multiple parallel jobs.
private ConcurrentDictionary ActivityActionsQueue = new ConcurrentDictionary();
// this class will hold all the information about the activity action
// there is no need to save the PSActivity instance, just get the relevant info and let go the PSActivity object.
private class ActivityActionData
{
internal Guid jobInstanceId;
internal Bookmark bookmark;
internal PowerShell command;
internal PowerShellStreams streams;
internal PSActivityEnvironment environment;
}
// the servicing thread will execute this function to perform the activity action.
private void ServiceRequests(object state)
{
Guid jobInstanceId = (Guid)state;
ActivityActionData actiondata = null;
ActivityActionsQueue.TryRemove(jobInstanceId, out actiondata);
if (actiondata != null)
{
// the work is getting perform here
PerformWork(actiondata);
}
}
// In this function the activity action is performed
private void PerformWork(ActivityActionData data)
{
bool failed = false;
Exception exception = null;
try
{
// setting up the streams
data.command.Streams.Debug = data.streams.DebugStream;
data.command.Streams.Error = data.streams.ErrorStream;
data.command.Streams.Progress = data.streams.ProgressStream;
data.command.Streams.Verbose = data.streams.VerboseStream;
data.command.Streams.Warning = data.streams.WarningStream;
// Custom WinRM Workflow Endpoint details
// run CustomWorkflowEndpointSetup.ps1 in Powershell console as administrator, if you have not done.
//
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.ShellUri = "http://schemas.microsoft.com/powershell/CustomeWorkflowEndpoint";
// Create runspace pool on custom workflow endpoint where command will be invoked
using (RunspacePool r = RunspaceFactory.CreateRunspacePool(1, 1, connectionInfo))
{
try
{
r.Open();
data.command.RunspacePool = r;
// now executing the powershell command.
data.command.Invoke(data.streams.InputStream, data.streams.OutputStream, new PSInvocationSettings());
}
finally
{
r.Close();
r.Dispose();
}
}
}
catch (Exception e)
{
// since work is getting performed on background thread so there should not be any exception.
failed = true;
exception = e;
}
// Now since activity action has already been performed so now we need to resume the execution of the
// workflow. This will be done by
PSWorkflowJob job = _runtime.JobManager.GetJob(data.jobInstanceId);
// Now resuming the job
if (failed)
{
job.ResumeBookmark(data.bookmark, this.SupportDisconnectedPSStreams, data.streams, exception);
}
else
{
job.ResumeBookmark(data.bookmark, this.SupportDisconnectedPSStreams, data.streams);
}
}
// stopping the execution of commands
private void StopExecution(Guid jobInstanceId)
{
// can be impletement by holding the reference to
// actiondata and the calling stop() on command object
}
#endregion Private Variables and Function
}
}