Plugin Architecture Version 2

The following C# class is taken from our demo project https://github.com/ikarstein/kenaflow.demoplugin.v2

using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;

namespace kenaflow.demoplugin.v2
{
    public class DemoPluginV2 : KenaflowPluginV2
    {
        private readonly KenaflowPluginConfigurationV2 MyConfig = new KenaflowPluginConfigurationV2
        {
			Id = Guid.Parse("{BC3E435B-ADD6-4764-B415-3D887403222C}"),
            Enabled = true,
            Name = "Demo Plugin v2",
            Version = new Version("1.0.0.0"),
            MinKenaflowVersion = new Version("4.0.0.0"),
            PSModule = true,
            CustomWorkflowType = "DEMO2::SUB1",
            LogDebug = true,
			SupportedWorkflowTypes = new List<string>{"DEMO2::SUB1"}
        };

        private string SomePrivateData { get; set; }

        /* BEGIN Accessible from PowerShell */
        public KenaflowPluginConfigurationV2 GetPluginConfig()
        {
            return MyConfig;
        }

        public WFConfig GetCurrentWorkflowConfig()
        {
            return WorkflowConfig;
        }
        
        /* END Accessible from PowerShell */

        protected override KenaflowPluginConfigurationV2 GetPluginConfig(Options options)
        {
            //Return the Plugin Configuriation to kenaflow.
            LogNormal("Loading config of DemoPlugin v1");
            return MyConfig;
        }

        protected override void Loaded()
        {
            //Can be used to initialize the plugin after it was loaded and successfully registered.
        }

        protected override string CheckConfig(WFConfig config)
        {
            //Can be used to check the loaded workflow config (_wfconfig.ps1)
            //To return an error just return a string. On success return NULL.
            return null;
        }

        protected override void Init(WFConfig config)
        {
            //Can be used to initialize the current workflow run.
        }

        protected override object Connect()
        {
            //Connect to your system. Return a handle to the connection. Whatever you want.
            return null;
        }

        protected override string GetLastProcessedItemId(object li)
        {
            //In case of an aborted workflow this is called to get the ID of the last processed item. The value is later passed to 'GetItems'
            return li as string;
        }

        protected override void PreProcessEventData(ref Hashtable hashtableEventData)
        {
            //In case of remote events this is method can be used to modify the remote data BEFORE initiating the workflow.
        }

        protected override void PreProcessWorkflowConfigValues1(ref Dictionary<string, object> currentWorkflowConfigValues)
        {
            //after config values are read from _wfconfig.ps1
        }

        protected override void PreProcessWorkflowConfigValues2(ref Dictionary<string, object> currentWorkflowConfigValues)
        {
            //after config values are modified by engine BEFORE execution
        }

        protected override bool ProcessLastRun(DateTime lastRun, ref string errorMessage, ref string errorCode)
        {
            return true; // TRUE => do not execute workflow.
        }

        protected override bool ProcessTBEandCRON(ref DateTime dt)
        {
            return true; // TRUE => do not execute workflow. Next runtime can be set in 'dt';
        }

        protected override List<object> GetItems(Hashtable eventData, string lastProcessedItem)
        {
            //Get list of items for processing by workflow script.
            return new List<object> { 1, 2, 3, 4, 5 };
        }

        protected override void PreItemProcessing(ref object li)
        {
            SerializeStorageData(Guid.Parse(WorkflowConfig.Id), $"key_{li}", new { a = "a", b = "b" }, false, "");
        }

        protected override void PostItemProcessing(ref object li)
        {
            var d = DeserializeStorageData(Guid.Parse(WorkflowConfig.Id), $"key_{li}", "");
            RemoveStorageData(Guid.Parse(WorkflowConfig.Id), $"key_{li}");
        }

        protected override void CleanupAfterItem()
        {
            //Called after the workflow has processed all items. (Also in case of failure.)
        }

        protected override void CleanupAfterRun()
        {
            //Called after the workflow has processed all items.
        }

        protected override void ItemRemoval(object li, int removalState)
        {
            //Called at the end of the item processing. 
            // 0 = no removal requested
            // 1 = removal requested
            // 2 = move to recycle bin requested
            base.ItemRemoval(li, removalState);
        }

        protected override void Disconnect(object connection)
        {
            //Disconnect from your system. You get to handle that was returned by 'Connect'.
            //Only called at the end of the workflow run if "Connect" returns a value != null.
        }

        /**** TOOLS ****/

        protected override object GetData(string key, object item)
        {
            //can be used to read persisted data. 'item' can be null, e.g. if using Set-KFData
            return null;
        }

        protected override void SetData(string key, object item, object data)
        {
            //can be used to persist data. 'item' can be null, e.g. if using Set-KFData
        }

        protected override void RemoveData(string key, object item)
        {
            //can be used to remove persisted data. 'item' can be null, e.g. if using Set-KFData
        }

        protected override bool HandleException(Exception exception)
        {
            return true; // => throw exception in engine. FALSE => don't throw
        }

        protected override void RegisterReceiver(string url)
        {
            //Can be used to register web hooks.
        }

        protected override void UnregisterReceiver(string url)
        {
            //Can be used to un-register web hooks.
        }
    }

    [Cmdlet("Get", "PluginTestV2")]
    public class Test : PSCmdlet
    {
        protected override void ProcessRecord()
        {
            WriteObject($"Test: {DateTime.Now}");
        }
    }
}

The plugin class is derrived from KenaflowPluginV2.

This base class offers some tools that are described in this article: Helper Methods.

It has a configuration object that is stored in a private property called MyConfig.

During initialization kenaflow requests this configuration object by calling GetPluginConfig. The parameter options will contain used the command line options for kenaflow.exe.

Id is the unique ID of the plugin.

Enabled tells kenaflow whether the plugin is available. If set to false the plugin is going to be ignored. But if kenaflow.exe was started with command line switch --plugin <assemlby> the plugin is executed even if it's Enabled-setting is `false.

Name is the display name of the plugin. It must be unique in kenaflow. No other plugin can have the same name!

Version is the version of the plugin. It's the developers choice what to set here.

PSModule tells kenaflow whether the plugin contains custom cmdlets or not.

CustomWorkflowType tells kenaflow that the plugin contains a custom workflow type and what the name of the workflow type is. The custom workflow type can have a sub type. Type and sub type are seperated by ::.

MinKenaflowVersion is the minimum kenaflow version that is supported by the plugin.

LogDebug tells kenaflow to write some debugging messages to the log reporting plugin method calls.

SupportedWorkflowTypes can be used to restrict the provided Cmdlets to certain workflow types. Valid values are:

To support all workflow types please specify:

SupportedWorkflowTypes = new List{"*"}

> To support **no** workflow types please specify:
> ```cs
SupportedWorkflowTypes = null

or

SupportedWorkflowTypes = new List<string()



#### Method `KenaflowPluginConfigurationV2 GetPluginConfig(Options options)`

The method is called to request the plugin configuration.

#### Method `void Loaded()`

The method is called immediately after the plugin was registered successfully.

### Tool Methods

The following methods are used by _kenaflow_ to initialize the plugin or request special actions.

#### Method `string CheckConfig(WFConfig config)`

This method can be used to check the loaded workflow config (`_wfconfig.ps1`). 

To return an error just return a `string`. 

On success return `NULL`.

#### Method `string GetLastProcessedItemId(object li)`

A workflow has a maximum execution time. BEFORE each item processing the elapsed time of the current workflow run is checked. If the time exceeded the limit the workflow execution is stopped. Therefore a hint to the next item is stored in the `_wfdata` folder. 

The get the hint this method is called. The hint is returned as string. On the next workflow run its passed to the method `GetItems`.

#### Method `object GetData(string key, object item)`

This method is called if the workflow uses [`Get-KFData`](/scriptreference/cmdlets/get-kfdata) or [`Get-KFItemData`](/scriptreference/cmdlets/get-kfitemdata).

The plugin developer decides that to do with the data.

`key` and `item` are both used as key to the data. `item` represents the currently processed item - if any.

#### Method `void SetData(string key, object item, object data)`
This method is called if the workflow uses [`Set-KFData`](/scriptreference/cmdlets/set-kfdata) or [`Set-KFItemData`](/scriptreference/cmdlets/set-kfitemdata).

The plugin developer decides that to do with the data.

`key` and `item` are both used as key to the data. `item` represents the currently processed item - if any.

The object in `data` should be serialized.

#### Method `void RemoveData(string key, object item)`

This method is called if the workflow uses [`Remove-KFData`](/scriptreference/cmdlets/remove-kfdata) or [`Remove-KFItemData`](/scriptreference/cmdlets/remove-kfitemdata).

`key` and `item` are both used as key to the data. `item` represents the currently processed item - if any.

`void RegisterReceiver(string url)`
      {
          //Can be used to register web hooks.
      }

`void UnregisterReceiver(string url)`
      {
          //Can be used to un-register web hooks.
      }


### Flow Methods

The following methods are used by _kenaflow_ to interact with the plugin and enable the plugin to execute. 

The methods are executed in a fixed order during each workflow run. Not all are executed every time.

This is the order of the methods:

1. `Init`
1. `PreProcessEventData`
1. `CheckLastRun`
1. `CheckTBEandCRON`
1. `PreProcessWorkflowConfigValues1`
1. `Connect`
1. `RegisterReceiver` or `UnregisterReceiver`
1. `GetItems` 
> If the result is not `null` not an empty list of objects (`System.Collections.Generic.List<object>`) the next step is executed. 
>
> Otherwise the **workflow script** is executed next and afterwards `CleanupAfterRun`.
1. `HandleException` ... __*if*__ an **unhandled exception** occurs between the next step and `PostItemProcessing` the method `CleanupAfterItem`. 
1. `PreItemProcessing`
1. Now the workflow script is executed!
1. `PostItemProcessing`
1. `ItemRemoval`
1. `CleanupAfterItem` ... loop back to `PreItemProcessing`
1. `CleanupAfterRun`
1. `Disconnect`


#### Method `void Init(WFConfig config)`

`Init` is called BEFORE the execution of a workflow. It's called for every loaded and enabled workflow! This could be used to manipulate the workflow configuration `config`.

AFTER the workflow processing was started but BEFORE executing the workflow script the `Connect` method is called. This can be used to connect to a third party system. The method schould return `null` if no connection is needed OR `Disconnect` is not required. 

#### Method `void PreProcessEventData(ref Hashtable hashtableEventData)`

In case of remote events this is method can be used to modify the remote data BEFORE initiating the workflow.

#### Method `bool CheckLastRun(ref DateTime lastRun, ref string errorMessage, ref string errorCode)`

Before _kenaflow_ executes a workflow it checks the last run of the workflow.

First _kenaflow_ loads the last execution state file from disk (from folder `_wfdata`). There can be an error reading and interpreting the file. If an error occurs the method `CheckLastRun` is called. You get the read last run timestamp (if any) and the error message/code. All this information can be modified.

If the method returns `false` the workflow is **NOT** executed!

#### Method `bool CheckTBEandCRON(ref DateTime dt, bool checkResult)`

After successfully loading the last run timestamp it is checked against workflow settings `TBE` ("time between execution") and `CRON`.

The check result can force the workflow run to stop.

With method `CheckTBEandCRON` the check result can be modified. 

`dt` is the last run timestamp.

`checkResult` is the result of the `TBE` and `CRON` check. 
+ if it is `false` the workflow should not run based on `TBE` and `CRON`. The proposed next run time is given in `dt`.
+ if it is `true` the workflow should run.

You can modify the next run time in `dt` and you can return `true` (run workflow) or `false` (stop workflow).

#### Method `void PreProcessWorkflowConfigValues(ref Dictionary<string, object> currentWorkflowConfigValues)`

This method is called after loading the workflow configuration values from `_wfconfig.ps1`.

The plugin can modify the workflow configuration values.


#### Method `List<object> GetItems(Hashtable eventData, string lastProcessedItem)`

This methods is called the determine the items for processing in the current workflow run.

The items are returned as list of object (`System.Collections.Generic.List<object>`).

The parameter `eventData` contains data if the workflow was started by receiving a remote event. The data can be modified.

The parameter `lastProcessedItem` contains the hint from the last workflow run if this was stopped before processing all given items. Otherwise this string is null.

If the method returns `null` or an empty list of objects the **next step is the execution of the workflow script**.

#### Method `bool HandleException(Exception exception)`

If an unhandled exception occurs during execution of the workflow script this method is called. 

If the workflow has an error handling script ([Error Handling](/basics/errorhandling)) this is called first. If this script handles the exception successfully (return "OKAY") the method `HandleException` is not executed.

If it returns `false` the exception is **NOT** throws by the engine. 

if it returns `true` the exception is thrown by the engine.


#### Method `void PreItemProcessing(ref object li)`

This method is called before execting workflow script for the current item.

#### Method `void PostItemProcessing(ref object li)`

This method is called after the execution of the workflow script for the current item.

#### Method `void ItemRemoval(object li, int removalState)`

After the item execution this method can be used to remove the item if required.

The workflow developer handles the removal. The method just indicates the requested removal state:

+ `0` = no removal requested
+ `1` = removal requested
+ `2` = move to recycle bin requested

#### Method `void CleanupAfterItem()`

This method is called after the processing of the current item is finished. It is executed also in case of failure.
      }

#### Method `void CleanupAfterRun()`

The method is executed after the workflow has processed all items (if `GetItems` did not return `null` or an empty list of objects) or after the workflow script was executed (if `GetItems` returned `null` or an empty list of objects).

#### Method `void Disconnect(object connection)`

This can be used to disconnect from the third-party system. 

It is only called if `Connect` returned an object ( = not `null`). This object is passed to `Disconnect`.

### Cmdlet Definition

The class `Test` in the exmaple above defines the cmdlet `Get-PluginTestV2`.


Discussion