Wexflow: Open source workflow engine in C#

0
189

This open source project is sponsored by JetBrains.

Table of contents

  1. Introduction
  2. Is Wexflow an ETL system?
  3. Why not Workflow Foundation?
  4. Prerequisites
  5. How to install Wexflow?
    1. Windows
    2. macOS
    3. Linux
    4. Android
  6. How to uninstall Wexflow?
    1. Windows
    2. macOS
    3. Linux
    4. Android
  7. How to use Wexflow?
    1. General
    2. Wexflow Manager
    3. Wexflow Web Manager
    4. Wexflow Android Manager
  8. Workflow samples
    1. Sequential workflows
    2. Execution graph
    3. Flowchart workflows
    4. Workflow events
  9. How to create a custom task?
  10. How to debug Wexflow?
  11. Bugs and features
  12. Using the code
    1. Wexflow Windows Service
    2. Wexflow Engine
    3. Wexflow Manager
  13. Libraries used by Wexflow
  14. History


Workflows Made Easy

Introduction

Wexflow is an open source extensible and multithreaded workflow engine written in C# with a cross platform manager app that works on Windows, macOS, Linux and Android. Wexflow aims to make automations, workflow processes, long-running processes and interactions between systems, applications and folks easy, straitforward and clean.

A workflow is a series of distinct steps or phases. Each step is modeled in Wexflow as a Task. Tasks can be assembled visually into workflows using XML.

Wexflow provides the following features:

  • Sequential workflows: A sequential workflow executes a set of tasks in order, one by one. Tasks are executed in a sequential manner until the last task finishes. The order of the execution of the tasks can be altered by modifying the execution graph of the workflow.
  • Flowchart workflows: A flowchart workflow is a workflow that contains at least one flowchart node (If/While/Switch) in its execution graph. A flow chart node takes as input a flowchart task (A task that returns either true or false after performing its job) and a set of tasks to execute in order, one by one. The order of the execution of the tasks can be altered by modifying the execution graph of the flowchart node.
  • Workflow events: After a workflow finishes its job, its final result is either success, or warning or error. If its final result is success, the OnSuccess event is triggered. If its final result is warning, the OnWarning event is triggered. If its final result is error, the OnError event is triggered. An event contains a set of tasks and/or flowchart nodes to execute in order, one by one. The order of the execution of the tasks and/or flowchart nodes can be altered by modifying the execution graph of the event.
  • Wexflow Manager: Wexflow provides a GUI for managing workflows that works on Windows, macOS, Linux and Android.
  • Wexflow Web Manager: Wexflow can be hosted on any website through its lightweight JavaScript API (~6Kb).
  • Automation: Workflows can either be launched when Wexflow engine starts or triggered manually or launched periodically.
  • Monitoring: Everything that happens in Wexflow is traced in the log files.
  • Built-in tasks: Wexflow provides 32 built-in tasks for different kind of jobs.
  • Extensible: The user can create his own custom tasks in C# or VB.

Wexflow provides the following built-in tasks:

  • File system tasks: These tasks allow to create, copy, move, rename or delete files and directories on a file system. These tasks allow also to check whether a collection of remote or local files and/or directories exists.
  • Sync task: This task allows to synchronise the content of a local or a remote source directory to a local or a remote destination directory. This task makes use of Microsoft Sync Framework 2.1.
  • Compression tasks: These tasks allow to create a zip, a tar or a tar.gz from a collection of files.
  • MD5 task: This task allows to generate MD5 sums of a collection of files.
  • FTP task: This task allows to list, upload, download or delete files over FTP, FTPS (explicit/implicit) or SFTP. This task makes use of open source libraries written in C#.
  • HTTP task: This task allows to downoad files over HTTP or HTTPS.
  • XML tasks: These tasks allow to work with XML data. XSLT can be used along with XPath to generate XML documents. XSLT 1.0 and XSLT 2.0 are supported.
  • CSV tasks: These tasks allow to work with CSV data. XML can be used along with XSLT to validate, compare and merge CSV data. The results of this can then be stored in CSV or XML format.
  • SQL task: This task allows to execute SQL scripts. This task supports Microsoft Sql Server, Microsoft Access, Oracle, MySql, SQLite, PostGreSql and Teradata. This task can be used for bulk insert, for database updates, for database cleanup, for rebuilding indexes, for reorganizing indexes, for shrinking databases, for updating statistics, for transfering database data and so on.
  • WMI task: This task allows to execute WMI queries. The results can be stored in XML format.
  • Image task: This task allows to convert images to the following formats: Bmp, Emf, Exif, Gif, Icon, Jpeg, Png, Tiff and Wmf.
  • Audio and video tasks: These tasks allow to convert, cut or edit audio and video files through FFMEG or VLC. These tasks can also be used to perform custom operations such as generating images and thumbnails from video files.
  • Email task: This task allows to send a collection of emails.
  • Twitter task: This task allows to send a collection of tweets.
  • Process task: This task allows to launch any process on the computer.
  • Wait task: This task allows to wait for a specified duration of time.
  • Script tasks: These tasks allows execute custom tasks written in C# or VB.

In this article you will learn how to install Wexflow, how to uninstall it, how to use it, how to create your custom tasks and finally you will see how it was coded.

In the section Using the code, the source code of the version 1.0.1 is explained in details. The source code has changed since this release but that section will give you a clear idea on how Wexflow works.

Is Wexflow an ETL system?

You may think that Wexflow is rather an ETL system than a workflow engine. Well, the answer is that you can do ETL with Wexflow and even more. The spirit of Wexflow is to offer generic functionalities in a way that you can do pretty much whatever you want. With Wexflow, you can do ETL and even more through custom tasks and sequential/flowchart workflows.

Why not Workflow Foundation?

WF (Windows Workflow Foundation) is a Microsoft technology for defining, executing and managing workflows.

WF is a proprietary solution and comes with a limited number of built-in activities.

Here are the strengths of Wexflow vs WF:

  • Open source.
  • Comes with 32 built-in tasks.
  • Workflow events (OnSuccess, OnWarning and OnError).
  • An If flowchart node can be inside an If, a While and a Switch flowchart nodes.
  • A While flowchart node can be inside an If, a While and a Switch flowchart nodes.
  • A Switch flowchart node can be inside an If, a While and a Switch flowchart nodes.
  • Workflows can either be launched when Wexflow engine starts or triggered manually or launched periodically.
  • Provides a GUI for managing workflows that works on Windows, macOS and Linux.
  • Provides an Android application for managing workflows.
  • Provides a lightweight JavaScript API that allows to manage workflows in any website.
  • Everything that happens in Wexflow is traced in the log files.

Prerequisites

To use Wexflow, you’ll need basic skills in:

  • XML
  • XPath
  • XSL

To create a custom task, you’ll need basic skills in:

  • XML
  • XPath
  • XSL if necessary
  • C# or VB

At this time, Wexflow only supports creating and editing workflows in XML. However, creating and editing workflows in design mode are in the todo list and are comming soon. Wexflow Designer aims to allow folks who are not familiar with XML to work with Wexflow so they can create and edit their workflows easily.

How to install Wexflow?

Windows

Wexflow can be installed on Windows XP, Windows server 2003 and higher. Wexflow supports .NET Framework 4.0 and higher.

To install Wexflow, proceed as follows:

1. Install Microsoft .NET Framework 4.0 or higher.

2. Install Microsoft Sync Framework 2.1 Synchronization Redistributables (Synchronization-v2.1-x86-ENU.msi available in Wexflow_setup_windows.zip).

3. Install Microsoft Sync Framework 2.1 Provider Services Redistributables (ProviderServices-v2.1-x86-ENU.msi available in Wexflow_setup_windows.zip).

4. Install WexflowSetup.exe (available in Wexflow_setup_windows.zip):

5. You can choose to create a desktop shortcut:

6. Click on install to perform the installation:

7. Finally, click on finish to finish the installation:

The following menus are added in the start menu:

After Wexflow is installed a Windows Service named Wexflow is installed and starts automatically. To start Wexflow Manager, this Windows Service must be running. However, If you want to stop it you can do it from Windows Services console:

The “Manager” menu opens Wexflow Manager GUI. The “Web Manager” menu opens Wexflow Web Manager. The “Documentation” menu opens the documentation folder of Wexflow. The “Configuration” menu opens the configuration folder of Wexflow. The “Logs” menu opens the log file of the day.

macOS

Wexflow provides a GUI for managing workflows that can be installed on a macOS system.

To run Wexflow on Linux, Wexflow Windows Service must be installed on a Windows machine. Wexflow Windows Service provides a self hosted web service that allows to query Wexflow Engine.

After Wexflow Windows Service is installed on a Windows Machine, proceed as follows to install Wexflow Manager on macOS:

1. Install Mono.

2. Open Wexflow.dmg:

3. Drag and drop Wexflow in Applications folder to install.

4. Configure Wexflow Web Service URI by modifying the setting option WexflowWebServiceUri in /Applications/Wexflow.app/Contents/MonoBundle/Wexflow.Clients.Eto.Manager.exe.config

If the installation succeeds, the following window will appear when launching the application:

Linux

Wexflow provides a GUI for managing workflows that can be installed on a Linux system.

To run Wexflow on Linux, Wexflow Windows Service must be installed on a Windows machine. Wexflow Windows Service provides a self hosted web service that allows to query Wexflow Engine.

After Wexflow Windows Service is installed on a Windows Machine, proceed as follows to install Wexflow Manager on Linux:

1. Download wexflow.tar.gz (available in Wexflow_setup_linux.zip)

2. Install mono-complete:

sudo apt install mono-complete

3. Install Wexflow Manager:

sudo mv wexflow.tar.gz /opt/ cd /opt/ sudo tar -zxvf wexflow.tar.gz sudo chmod +x /opt/wexflow/wexflow.sh sudo ln -s /opt/wexflow/wexflow.sh /usr/local/bin/wexflow

5. Configure Wexflow web service uri by modifying the settings option WexflowWebServiceUri in Wexflow Manager configuration file:

sudo vim /opt/wexflow/Wexflow.Clients.Eto.Manager.exe.config 

6. Run Wexflow Manager:

wexflow

The following window will appear:

Android

Wexflow provides a GUI for managing workflows that can be installed on an Android device.

To run Wexflow on Andoird, Wexflow Windows Service must be installed on a Windows machine. Wexflow Windows Service provides a self hosted web service that allows to query Wexflow Engine.

After Wexflow Windows Service is installed on a Windows Machine, proceed as follows to install Wexflow Manager on an Android device:

1. Download wexflow.apk (available in Wexflow_setup_android.zip)

2. Copy wexflow.apk into the Android device

3. Install wexflow.apk

4. Launch Wexflow application and open the application settings through the settings menu:

5. Configure Wexflow Web Service Uri: 

That’s it. Wexflow application is ready for work: 

How to uninstall Wexflow?

Windows

To uninstall Wexflow, simply click on “Uninstall” menu from “Windows Start menu > Wexflow”.

Or go to “Configuration Panel > Add/remove programs” then select “Wexflow version 1.0.1” and click on uninstall:

After Wexflow is uninstalled, the folders C:\Wexflow\ and C:\WexflowTesting\ are not deleted to prevent user defined workflows and testing scenarios from being deleted. However, If you do not need them you can delete them manually.

The log file C:\Program Files\Wexflow\Wexflow.log is also not deleted to keep track of the last operations done by Wexflow. However, If you do not need the logs you can delete the log files.

macOS

To uninstall Wexflow Manager from a macOS machine, simply delete Wexflow application from Applications folder.

Linux

To uninstall Wexflow Manager from a Linux machine, proceed as follows:

sudo rm /usr/local/bin/wexflow sudo rm -rf /opt/wexflow

Android

To uninstall Wexflow from an Android device, simply open Settings>Applications>Wexflow then uninstall it.

How to use Wexflow?

General

After installing Wexflow, the folders C:\Wexflow\ and C:\WexflowTesting\ are created. The folder C:\Wexflow\ contains the following elements:

  • Wexflow.xml which is the main configuration file of Wexflow engine. Its path can be configured from C:\Program Files\Wexflow\Wexflow.Clients.WindowsService.exe.config
  • Workflows/ which contains the workflows in XML format.
  • Temp/ which is the temporary foler of Wexflow.

The folder C:\WexflowTesting\ contains data of testing scenarios.

The logs are written in C:\Program Files\Wexflow\Wexflow.log. There is one log file per day. The old log files are saved in this format C:\Program Files\Wexflow\Wexflow.logyyyyMMdd.

Below the configuration file of a workflow:

="1.0"="utf-8"  <Workflow xmlns="urn:wexflow-schema" id="$int" name="$string" description="$string">   <Settings>     <Setting name="launchType" value="startup|trigger|periodic" />     <Setting name="period" value="dd.hh:mm:ss" />     <Setting name="enabled" value="true|false" />   </Settings>   <Tasks>     <Task id="$int" name="$string" description="$string" enabled="true|false">       <Setting name="$string" value="$string" />       <Setting name="$string" value="$string" />            </Task>     <Task id="$int" name="$string" description="$string" enabled="true|false">       <Setting name="$string" value="$string" />       <Setting name="$string" value="$string" />     </Task>        </Tasks>      <ExecutionGraph /> </Workflow>

The name option of a Task must be one of the followings:

  • CsvToXml : This task transforms a CSV file to an XML file. The format of the output XML file is described in the documentation of the task.
  • FilesCopier : This task copies a collection of files to a destination folder. 
  • FilesLoader : This task loads a collection of files located in folders or through the file option. 
  • FilesMover : This task moves a collection of files to a destination folder.
  • FilesRemover : This task deletes a collection of files.
  • Ftp : This task allows to list, upload, download or delete files over FTP, FTPS (explicit/implicit) or SFTP. This task makes use of open source libraries written in C#.
  • ListEntities : This task lists all the entities loaded by the workflow tasks in the logs. This task is useful for resolving issues.
  • ListFiles : This task lists all the files loaded by the workflow tasks in the logs. This task is useful for resolving issues.
  • MailsSender : This task sends a collection of emails from XML files. The format of the input XML files is described in the documentation of the task.
  • Md5 : This task generates MD5 sums of a collection of files and writes the results in an XML file. The format of the output XML file is described in the documentation of the task.
  • Mkdir : This task creates a collection of folders.
  • ProcessLauncher : This task launches a process. If the process generates a file as output It is possible to pass a collection of files to the task so that for each file an output file will be generated through the process. Read the documentation of the task for further informations.
  • Rmdir : This task deletes a collection of folders.
  • Touch : This task creates a collection of empty files.
  • Twitter : This task sends a collection of tweets from XML files. The format of input XML files is described in the documentation of the task.
  • XmlToCsv : This task transforms an XML file to a CSV file. The format of the input XML file is described in the documentation of the task.
  • Xslt : This task transforms an XML file. It is possible to use XSLT 1.0 processor or XSLT 2.0 processor.
  • Zip : This task creates a zip archive from a collection of files.
  • Tar : This task creates a tar archive from a collection of files.
  • Tgz : This task creates a tar.gz archive from a collection of files.
  • Sql: This task executes a colletion of SQL script files or a simple SQL script through sql settings option. It supports Microsoft Sql Server, Microsoft Access, Oracle, MySql, SQLite, PostGreSql and Teradata.
  • Wmi: This task executes a WMI query and outputs the results in an XML file. The format of the output XML file is described in the documentation of the task.
  • ImagesTransformer: This task transforms a collection of image files to a specified format. The output format can be one of the followings: Bmp, Emf, Exif, Gif, Icon, Jpeg, Png, Tiff or Wmf.
  • Http: This task allows to downoad files over HTTP or HTTPS.
  • Sync: This task allows to synchronise the content of a local or remote source directory to a local or remote destination directory. This task makes use of Microsoft Sync Framework 2.1.
  • FilesRenamer: This task allows to rename a collection of files on a file system. The Xslt task can be used along with the ListFiles task to create new file names. Check out the documentation of these tasks and the workflow sample Workflow_FilesRenamer.xml to see how this can be done.
  • Wait: This task waits for a specified duration of time.
  • FilesExist: This task checks whether a collection of files and/or directories exists.
  • FileExists: This is a flowchart task that checks whether a given file exists on a file system or not.
  • Movedir: This task moves a folder and allows to overwrite the destination folder.
  • Now: This is a flowchart task that retrieves the current date in the specified format. This task is designed to be used in a Switch flowchart node.
  • Workflow: This task allows to start, suspend, resume or stop a list of workflows.

To learn how to make your own workflows, you can check out the workflow samples availabe in C:\Wexflow\Workflows\ and read the tasks documentations available in the documentation folder C:\Program Files\Wexflow\Documentation.

If a new workflow is created in C:\Wexflow\Workflows\ or if an existing workflow is deleted or modified, you have to restart Wexflow Windows Service so that these modifications take effect.

To disable a workflow, you can set the enabled settings option of the workflow to false. If you want to make a workflow disappears from the list of the workflows loaded by Wexflow engine, you can create a directory named disabled within C:\Wexflow\Workflows\ and move that workflow to that directory then restart Wexflow Windows service.

Wexflow Manager

Wexflow Manager is a simple application that allows the user to do the following things:

  • See all the workflows loaded by Wexflow Engine.
  • See the status of the selected workflow (running, suspended or disabled).
  • Start a workflow.
  • Stop a workflow.
  • Suspend a workflow.
  • Resume a workflow.

To see what’s going on in Wexflow, open the log file C:\Program Files\Wexflow\Wexflow.log in a text editor like Notepad++. Notepad ++ will update the log file as it fills up.

Wexflow Web Manager

Wexflow provides a lightweight JavaScript API (~6Kb) that allows Wexflow Manager to be hosted on any website.

Wexflow Web Manager allows the user to do the following things:

  • See all the workflows loaded by Wexflow Engine.
  • See the status of the selected workflow (running, suspended or disabled).
  • Start a workflow.
  • Stop a workflow.
  • Suspend a workflow.
  • Resume a workflow.

To host Wexflow Web Manager in a website, simply proceed as follows:

  1. Reference wexflow.min.css and wexflow.min.js:
    These files are located in C:\Program Files\Wexfow\Web Manager\
  2. Create an instance of Wexflow.

The HTML source code should look like as follows:

<!DOCTYPE html> <html> <head>     <title>Wexflow</title>     <link rel="stylesheet" type="text/css" href="css/wexflow.min.css" />     <script type="text/javascript" src="js/wexflow.min.js"></script>     <script type="text/javascript">         window.onload = function () {             new Wexflow("wexflow", "http://localhost:8000/wexflow/");         };     </script> </head> <body>     <div id="wexflow" style="position: absolute; top:0; right:0; bottom:0; left:0;"></div> </body> </html>

Wexflow Android Manager

Wexflow provides an Android application for managing workflows.

Wexflow Android Manager allows the user to do the following things:

  • See all the workflows loaded by Wexflow Engine.
  • See the status of the selected workflow (running, suspended or disabled).
  • Start a workflow.
  • Stop a workflow.
  • Suspend a workflow.
  • Resume a workflow.

Workflow samples

In this section, few workflow samples will be presented in order to make the end user familiar with Wexflow workflows synthax.

Sequential workflows

Workflow 1

This workflow uploads invoices to an SFTP server, then waits for 2 days and then notifies the customers.

<Workflow xmlns="urn:wexflow-schema" id="1" name="Workflow_Invoices" description="Workflow_Invoices">     <Settings>         <Setting name="launchType" value="trigger" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading invioces" enabled="true">             <Setting name="folder" value="C:\WexflowTesting\Invoices\" />         </Task>         <Task id="2" name="Ftp" description="Uploading invoices" enabled="true">             <Setting name="protocol" value="sftp" />              <Setting name="command" value="upload" />              <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="1" />         </Task>         <Task id="3" name="Wait" description="Waiting for 2 days" enabled="true">             <Setting name="duration" value="2.00:00:00" />         </Task>      <Task id="4" name="FilesLoader" description="Loading emails" enabled="true">             <Setting name="file" value="C:\WexflowTesting\Emails\Invoices.xml" />         </Task>     <Task id="5" name="MailsSender" description="Notifying customers" enabled="true">             <Setting name="selectFiles" value="4" />          <Setting name="host" value="127.0.0.1" />          <Setting name="port" value="587" />          <Setting name="enableSsl" value="true" />          <Setting name="user" value="user" />          <Setting name="password" value="password" />         </Task>         <Task id="6" name="FilesMover" description="Moving invoices" enabled="true">             <Setting name="selectFiles" value="1" />             <Setting name="destFolder" value="C:\WexflowTesting\Invoices_sent\" />         </Task>     </Tasks> </Workflow>

First of all, the FilesLoader task loads all the invoices located in the folder C:\WexflowTesting\Invoices\, then the Ftp task uploads them to the SFTP server, then the Wait task waits for 2 days, then the FilesLoader task loads the emails in XML format and then the MailsSender task sends the emails. Finally, the FilesMover task moves the invoices to the folder C:\WexflowTesting\Invoices_sent\.

Workflow 2

This workflow waits for files to arrive in C:\WexflowTesting\Watchfolder1\ and C:\WexflowTesting\Watchfolder2\ then uploads them to an FTP server then moves them to C:\WexflowTesting\Sent\ folder. This workflow starts every 2 minutes.

<Workflow xmlns="urn:wexflow-schema" id="2" name="Workflow_FilesSender" description="Workflow_FilesSender">     <Settings>         <Setting name="launchType" value="periodic" />         <Setting name="period" value="00.00:02:00.00" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading files" enabled="true">             <Setting name="folder" value="C:\WexflowTesting\Watchfolder1\" />             <Setting name="folder" value="C:\WexflowTesting\Watchfolder2\" />         </Task>         <Task id="2" name="Ftp" description="Uploading files" enabled="true">             <Setting name="protocol" value="ftp" />              <Setting name="command" value="upload" />              <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="1" />         </Task>         <Task id="3" name="FilesMover" description="Moving files to Sent folder" enabled="true">             <Setting name="selectFiles" value="1" />             <Setting name="destFolder" value="C:\WexflowTesting\Sent\" />         </Task>     </Tasks> </Workflow>

First of all, the FilesLoader task loads all the files located in the folders C:\WexflowTesting\Watchfolder1\ and C:\WexflowTesting\Watchfolder2\ then the Ftp task loads the files and uploads them to the FTP server. Finally, the FilesMover task moves the files to the folder C:\WexflowTesting\Sent\.

Workflow 3

This workflow transcodes the WAV files located in C:\WexflowTesting\WAV\ to MP3 format through FFMPEG and moves the transcoded files to C:\WexflowTesting\MP3\.

<Workflow xmlns="urn:wexflow-schema" id="3" name="Workflow_ffmpeg" description="Workflow_ffmpeg">     <Settings>         <Setting name="launchType" value="trigger" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading WAV files" enabled="true">             <Setting name="folder" value="C:\WexflowTesting\WAV\" />         </Task>         <Task id="2" name="ProcessLauncher" description="WAV to MP3" enabled="true">             <Setting name="selectFiles" value="1" />              <Setting name="processPath" value="C:\Program Files\ffmpeg\bin\ffmpeg.exe" />                          <Setting name="processCmd" value="-i {$filePath} -codec:a libmp3lame -qscale:a 2 {$output:$fileNameWithoutExtension.mp3}" />              <Setting name="hideGui" value="true" />             <Setting name="generatesFiles" value="true" />          </Task>         <Task id="3" name="FilesMover" description="Moving MP3 files from temp folder" enabled="true">             <Setting name="selectFiles" value="2" />             <Setting name="destFolder" value="C:\WexflowTesting\MP3\" />         </Task>     </Tasks> </Workflow>

First of all, the FilesLoader task loads all the files located in the folder C:\WexflowTesting\WAV\ then the ProcessLauncher task launches FFMPEG process on every file by specifying the right command in order to create the MP3 file. Finally, the FilesMover task moves the MP3 files to the folder C:\WexflowTesting\MP3\.

Workflow 4

This workflow waits for WAV files to arrive in C:\WexflowTesting\WAV\ then transcodes them to MP3 files through VLC then uploads the MP3 files to an FTP server then moves the WAV files to C:\WexflowTesting\WAV_processed\. This workflow starts every 2 minutes.

<Workflow xmlns="urn:wexflow-schema" id="4" name="Workflow_vlc" description="Workflow_vlc">     <Settings>         <Setting name="launchType" value="periodic" />         <Setting name="period" value="00.00:02:00.00" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading WAV files" enabled="true">             <Setting name="folder" value="C:\WexflowTesting\WAV\" />         </Task>         <Task id="2" name="ProcessLauncher" description="WAV to MP3" enabled="true">             <Setting name="selectFiles" value="1" />                          <Setting name="processPath" value="C:\Program Files\VideoLAN\VLC\vlc.exe" />                          <Setting name="processCmd" value="-I dummy {$filePath} :sout=#transcode{acodec=mpga}:std{dst={$output:$fileNameWithoutExtension.mp3},access=file} vlc://quit" />             <Setting name="hideGui" value="true" />             <Setting name="generatesFiles" value="true" />         </Task>         <Task id="3" name="Ftp" description="Uploading MP3 files" enabled="true">             <Setting name="protocol" value="ftp" />             <Setting name="command" value="upload" />             <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="2" />         </Task>         <Task id="4" name="FilesMover" description="Moving WAV files" enabled="true">             <Setting name="selectFiles" value="1" />             <Setting name="destFolder" value="C:\WexflowTesting\WAV_processed\" />         </Task>     </Tasks> </Workflow>

First of all, the FilesLoader task loads all the files located in the folder C:\WexflowTesting\WAV\ then the ProcessLauncher task launches VLC process on every file by specifying the right command in order to create the MP3 file. Then, the Ftp task loads the MP3 files generated by the ProcessLauncher task and then uploads them to the FTP server. Finally, the FilesMover task moves the processed WAV files to the folder C:\WexflowTesting\WAV_processed\.

Workflow 5

This workflow downloads specific files from an FTP server. This workflow starts by listing all the files located at the root folder of the server, then the specific files that will be downloaded are tagged through an XSLT (LisFiles.xslt), then the files are downloaded by the Ftp task through todo="toDownload" and from="app4" tags, then the downloaded files are moved to the folder C:\WexflowTesting\Ftp_download\.

<Workflow xmlns="urn:wexflow-schema" id="5" name="Workflow_Ftp_download_tag" description="Workflow_Ftp_download_tag">     <Settings>         <Setting name="launchType" value="trigger" />          <Setting name="enabled" value="true" />      </Settings>     <Tasks>         <Task id="1" name="Ftp" description="Listing files (FTP)" enabled="true">             <Setting name="command" value="list" />             <Setting name="protocol" value="ftp" />              <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />         </Task>         <Task id="2" name="ListFiles" description="Listing files" enabled="true">         </Task>         <Task id="3" name="Xslt" description="Renaming and tagging files" enabled="true">             <Setting name="selectFiles" value="2" />             <Setting name="xsltPath" value="C:\Wexflow\Xslt\ListFiles.xslt" />             <Setting name="version" value="2.0" />              <Setting name="removeWexflowProcessingNodes" value="false" />         </Task>         <Task id="4" name="Ftp" description="Downloading files" enabled="true">             <Setting name="command" value="download" />             <Setting name="protocol" value="ftp" />              <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" todo="toDownload" from="app4" />         </Task>         <Task id="5" name="FilesMover" description="Moving files to Ftp_download" enabled="true">             <Setting name="selectFiles" value="4" />             <Setting name="destFolder" value="C:\WexflowTesting\Ftp_download\" />             <Setting name="overwrite" value="true" />         </Task>     </Tasks> </Workflow>

Roughly speaking, the Ftp task loads the list of files located at the root folder of the FTP server in the running instance of the workflow, then the ListFiles task outputs and XML file that contains all the files loaded then the Xslt task takes as input this XML and generates an XML wich contains a system node called wich contains the list of files to be tagged and/or renamed.

To understand how tagging and renaming files work, refer to the documentation of the ListFiles and Xslt tasks.

Below is the XSLT ListFiles.xslt used for tagging files:

="1.0"="utf-8" <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="xml" indent="yes"/>   <xsl:template match="/">     <root>       <WexflowProcessing>         <xsl:for-each select="//WexflowProcessing/Workflow/Files//File">           <xsl:choose>             <xsl:when test="@name = 'file1.txt'">               <File taskId="{@taskId}" name="{@name}" renameTo="file1_renamed.txt" 

                    todo="toRename" 

                    from="app1" />             </xsl:when>             <xsl:when test="@name = 'file2.txt'">               <File taskId="{@taskId}" name="{@name}" renameTo="file2_renamed.txt" 

                    todo="toSend" 

                    from="app2" />             </xsl:when>             <xsl:when test="@name = 'file3.txt'">               <File taskId="{@taskId}" name="{@name}" renameTo="file3_renamed.txt" 

                    todo="toDownload" 

                    from="app3" />             </xsl:when>             <xsl:when test="@name = 'file4.txt'">               <File taskId="{@taskId}" name="{@name}" renameTo="file4_renamed.txt"

                    todo="toDownload" 

                    from="app4" />             </xsl:when>           </xsl:choose>         </xsl:for-each>       </WexflowProcessing>     </root>   </xsl:template> </xsl:stylesheet>

Execution graph

This workflow loads the file C:\WexflowTesting\file1.txt then uploads it to an FTP server then moves it to C:\WexflowTesting\Sent\ folder.

<Workflow xmlns="urn:wexflow-schema" id="6" name="Workflow_Ftp_upload" description="Workflow_Ftp_upload">     <Settings>         <Setting name="launchType" value="trigger" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading files" enabled="true">             <Setting name="file" value="C:\WexflowTesting\file1.txt" />         </Task>         <Task id="2" name="Ftp" description="Uploading files" enabled="true">             <Setting name="protocol" value="ftp" />             <Setting name="command" value="upload" />             <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="1" />         </Task>         <Task id="3" name="FilesMover" description="Moving files to Sent folder" enabled="true">             <Setting name="selectFiles" value="1" />             <Setting name="destFolder" value="C:\WexflowTesting\Sent\" />         </Task>     </Tasks>     <ExecutionGraph>      <Task id="1"><Parent id="-1" /></Task>      <Task id="2"><Parent id="1"  /></Task>      <Task id="3"><Parent id="2"  /></Task>     </ExecutionGraph> </Workflow>

First of all, the FilesLoader task loads the file C:\WexflowTesting\file1.txt then the Ftp task loads that file and uploads it to the FTP server. Finally, the FilesMover task moves that file to the folder C:\WexflowTesting\Sent\.

By convention, the parent task id of first task to be executed must always be -1. The execution graph of this workflow will execute the tasks in the following order:

However, if the execution graph is modified as follows:

<ExecutionGraph>   <Task id="1"><Parent id="-1" /></Task>   <Task id="3"><Parent id="1"  /></Task>   <Task id="2"><Parent id="3"  /></Task> </ExecutionGraph>

The tasks will be executed as follows:

If the execution graph is modified as follows:

<ExecutionGraph>   <Task id="3"><Parent id="-1" /></Task>   <Task id="2"><Parent id="3"  /></Task>   <Task id="1"><Parent id="2"  /></Task> </ExecutionGraph>

The tasks will be executed as follows:

Two things are forbidden in the execution graph:

  1. Infinite loops.
  2. Parallel tasks.

Here is an example of an infinite loop:

<ExecutionGraph>   <Task id="1"><Parent id="-1" /></Task>   <Task id="2"><Parent id="1"  /></Task>   <Task id="1"><Parent id="2"  /></Task> </ExecutionGraph>

Here is an example of parallel tasks:

<ExecutionGraph>   <Task id="1"><Parent id="-1" /></Task>   <Task id="2"><Parent id="1"  /></Task>   <Task id="3"><Parent id="1"  /></Task> </ExecutionGraph>

Flowchart workflows

If

The following workflow is a flowchart workflow that is triggered by the file file.trigger. If the file file.trigger is found on the file system then this workflow will upload the file file1.txt to an FTP server then it will notify customers that the upload was successful. Otherwise, if the trigger file.trigger is not found on the file system then the workflow will notify customers that the upload failed.

<Workflow xmlns="urn:wexflow-schema" id="7" name="Workflow_If" description="Workflow_If">     <Settings>         <Setting name="launchType" value="trigger" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading files" enabled="true">             <Setting name="file" value="C:\WexflowTesting\file1.txt" />         </Task>         <Task id="2" name="Ftp" description="Uploading files" enabled="true">             <Setting name="protocol" value="ftp" />             <Setting name="command" value="upload" />             <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="1" />         </Task>         <Task id="3" name="FilesLoader" description="Loading emails (OK)" enabled="true">             <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />         </Task>        <Task id="4" name="MailsSender" description="Notifying customers (OK)" enabled="true">             <Setting name="selectFiles" value="3" />             <Setting name="host" value="127.0.0.1" />             <Setting name="port" value="587" />             <Setting name="enableSsl" value="true" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />         </Task>         <Task id="5" name="FilesLoader" description="Loading emails (KO)" enabled="true">             <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />         </Task>        <Task id="6" name="MailsSender" description="Notifying customers (KO)" enabled="true">             <Setting name="selectFiles" value="5" />             <Setting name="host" value="127.0.0.1" />             <Setting name="port" value="587" />             <Setting name="enableSsl" value="true" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />         </Task>         <Task id="99" name="FileExists" description="Checking trigger file" enabled="true">             <Setting name="file" value="C:\WexflowTesting\file.trigger" />         </Task>     </Tasks>     <ExecutionGraph>       <If id="100" parent="-1" if="99">          <Do>             <Task id="1"><Parent id="-1" /></Task>             <Task id="2"><Parent id="1"  /></Task>             <Task id="3"><Parent id="2"  /></Task>             <Task id="4"><Parent id="3"  /></Task>          </Do>          <Else>             <Task id="5"><Parent id="-1" /></Task>             <Task id="6"><Parent id="5"  /></Task>          </Else>       </If>     </ExecutionGraph> </Workflow>

By convention, the parent task id of the first task to execute in and nodes must always be -1.

You can add If flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.

An If can be inside an If, a While and a Switch.

While

This workflow is triggered by the file file.trigger. While the file file.trigger exists, this workflow will upload the file file1.txt to an FTP server then it will notify customers then it will wait for 2 days then it will start again.

<Workflow xmlns="urn:wexflow-schema" id="8" name="Workflow_While" description="Workflow_While">     <Settings>         <Setting name="launchType" value="trigger" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading files" enabled="true">             <Setting name="file" value="C:\WexflowTesting\file1.txt" />         </Task>         <Task id="2" name="Ftp" description="Uploading files" enabled="true">             <Setting name="protocol" value="ftp" />             <Setting name="command" value="upload" />             <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="1" />         </Task>         <Task id="3" name="FilesLoader" description="Loading emails" enabled="true">             <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />         </Task>        <Task id="4" name="MailsSender" description="Notifying customers" enabled="true">             <Setting name="selectFiles" value="3" />             <Setting name="host" value="127.0.0.1" />             <Setting name="port" value="587" />             <Setting name="enableSsl" value="true" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />         </Task>         <Task id="5" name="Wait" description="Waiting for 2 days..." enabled="true">             <Setting name="duration" value="02.00:00:00" />         </Task>         <Task id="99" name="FileExists" description="Checking trigger file" enabled="true">             <Setting name="file" value="C:\WexflowTesting\file.trigger" />         </Task>     </Tasks>     <ExecutionGraph>       <While id="100" parent="-1" while="99">         <Task id="1"><Parent id="-1" /></Task>         <Task id="2"><Parent id="1"  /></Task>         <Task id="3"><Parent id="2"  /></Task>         <Task id="4"><Parent id="3"  /></Task>         <Task id="5"><Parent id="4"  /></Task>       </While>     </ExecutionGraph> </Workflow>

By convention, the parent task id of the first task to be executed in the  node must always be -1.

You can add While flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.

A While can be inside a While, an If and a Switch.

Switch

This workflow starts every 24 hours. On Monday, it uploads files to an FTP server and on Wednesday it notifies customers.

<Workflow xmlns="urn:wexflow-schema" id="43" name="Workflow_Switch" description="Workflow_Switch"> <Settings> <Setting name="launchType" value="periodic" /> <Setting name="period" value="24.00:00:00" /> <Setting name="enabled" value="true" /> </Settings> <Tasks> <Task id="1" name="Now" description="Getting current day" enabled="true"> <Setting name="culture" value="en-US" /> <Setting name="format" value="dddd" /> </Task> <Task id="2" name="FilesLoader" description="Loading files" enabled="true"> <Setting name="file" value="C:\WexflowTesting\file1.txt" /> </Task> <Task id="3" name="Ftp" description="Uploading files" enabled="true"> <Setting name="protocol" value="ftp" /> <Setting name="command" value="upload" /> <Setting name="server" value="127.0.1" /> <Setting name="port" value="21" /> <Setting name="user" value="user" /> <Setting name="password" value="password" /> <Setting name="path" value="/" /> <Setting name="selectFiles" value="1" /> </Task> <Task id="4" name="FilesLoader" description="Loading emails" enabled="true"> <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" /> </Task> <Task id="5" name="MailsSender" description="Notifying customers" enabled="true"> <Setting name="selectFiles" value="3" /> <Setting name="host" value="127.0.0.1" /> <Setting name="port" value="587" /> <Setting name="enableSsl" value="true" /> <Setting name="user" value="user" /> <Setting name="password" value="password" /> </Task> </Tasks> <ExecutionGraph> <Switch id="100" parent="-1" switch="1"> <Case value="Monday"> <Task id="2"><Parent id="-1" /></Task> <Task id="3"><Parent id="2" /></Task> </Case> <Case value="Wednesday"> <Task id="4"><Parent id="-1" /></Task> <Task id="5"><Parent id="4" /></Task> </Case> <Default /> </Switch> </ExecutionGraph> </Workflow>

By convention, the parent task id of the first task to be executed in the Case/Default nodes must always be -1.

You can add Switch flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.

A Switch can be inside a While, an If and a Switch.

Workflow events

This workflow uploads the file1.txt to an FTP server then notifies customers in case of success.

<Workflow xmlns="urn:wexflow-schema" id="9" name="Workflow_Events" description="Workflow_Events">     <Settings>         <Setting name="launchType" value="trigger" />         <Setting name="enabled" value="true" />     </Settings>     <Tasks>         <Task id="1" name="FilesLoader" description="Loading files" enabled="true">             <Setting name="file" value="C:\WexflowTesting\file1.txt" />         </Task>         <Task id="2" name="Ftp" description="Uploading files" enabled="true">             <Setting name="protocol" value="ftp" />             <Setting name="command" value="upload" />             <Setting name="server" value="127.0.1" />             <Setting name="port" value="21" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />             <Setting name="path" value="/" />             <Setting name="selectFiles" value="1" />         </Task>        <Task id="3" name="FilesLoader" description="Loading emails" enabled="true">             <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />         </Task>        <Task id="4" name="MailsSender" description="Notifying customers" enabled="true">             <Setting name="selectFiles" value="3" />             <Setting name="host" value="127.0.0.1" />             <Setting name="port" value="587" />             <Setting name="enableSsl" value="true" />             <Setting name="user" value="user" />             <Setting name="password" value="password" />         </Task>     </Tasks>     <ExecutionGraph>       <Task id="1"><Parent id="-1" /></Task>       <Task id="2"><Parent id="1"  /></Task>       <OnSuccess>         <Task id="3"><Parent id="-1" /></Task>         <Task id="4"><Parent id="3"  /></Task>       </OnSuccess>     </ExecutionGraph> </Workflow>

The flowchart event nodes and can be used in the same way. You can put If, While and Switch flowchart nodes in event nodes.

These are simple and basic workflows to give an idea on how to make your own workflows. However, if you have multiple systems, applications and automations involved in a workflow, the workflow could be very interesting.

How to create a custom task?

Custom tasks are a must in a workflow engine and allows systems and applications to interact.

To create a custom task MyTask for example you will need to proceed as follows:

  1. Create a class library project in Visual Studio and name it Wexflow.Tasks.MyTask.
  2. Reference the assemblies Wexflow.Core.dll and log4net.dll.These assemblies are located in the installation folder of Wexflow C:\Program Files\Wexflow\.
  3. Create a public class MyTask that implements the abstract class Wexflow.Core.Task.

Wexflow.Tasks.MyTask code should look like as follows:

public class MyTask : Wexflow.Core.Task {     public MyTask(XElement xe, Workflow wf) : base(xe, wf)     {             }     public override void Run()     {         try         {               return new TaskStatus(Status.Success);         }         catch (ThreadAbortException)         {             throw;         }     } }

Each task returns a TaskStatus object when it finishes performing its job. TaskStatus is composed of the following elements:

public Status Status { get; set; } public bool Condition { get; set; } public string SwitchValue { get; set; }

The Status can be one of the followings:

public enum Status {   Success,   Warning,   Error }

For example, if a task performs an opetation on a collection of files and if this operation succeeds for all the files then its Status should be Success. Otherwise if this operation succeeds for some files and fails for others then its Status should be Warning. Otherwise if this operation fails for all the files then its Status should be Error.

The Condition property is designed for flowchart tasks. In addition to the Status of the task, a flowchart task returns either true or false after performing its operation.

The Condition property should always be set to false for sequential tasks.

The SwitchValue is designed to be used by Switch flowchart nodes. If you set a value in the SwitchValue property and use this task in a Switch flowchart node, the case corresponding to the value will be executed. Otherwise, if the Default case is set, it will be executed.

You can use the TaskStatus constructor that suits your needs.

To retrieve settings, you can use the following methods:

string settingValue = this.GetSetting("settingName"); string settingValue = this.GetSetting("settingName", defaultValue); string[] settingValues = this.GetSettings("settingName");

To select the files loaded by the running instance of a workflow through the selectFiles settings option, you can do it as follows:

FileInf[] files = this.SelectFiles();

To select entities loaded by the running instance of a workflow through the selectEntities settings option, you can do it as follows:

Entity[] entities = this.SelectEntities();

The Entity class could be very useful when working with custom tasks that manipulate objects from a database or Web Services for example.

To load a file within a task, you can do it as follows:

this.Files.Add(new FileInf(path, this.Id));

To load an entity within a task, you can do it as follows:

this.Entities.Add(myEntity);

Finally if you finished coding your custom task, compile the class library project and copy the assembly Wexflow.Tasks.MyTask.dll in C:\Program Files\Wexflow\. Your custom task is then ready to be used as follows:

<Task id="$int" name="MyTask" description="My task description" enabled="true">     <Setting name="settingName" value="settingValue" /> </Task>

That’s it. That’s all the things you need to know to start coding your own custom tasks.

How to debug Wexflow?

To debug Wexflow, proceed as follows:

  • Install Microsoft .NET Framework 4.0 or higher.
  • Install Microsoft Sync Framework 2.1 SDK. You can download it from here.
  • Install Visual Studio 2010 or higher.
  • Copy the folders “Wexflow” and “WexflowTesting” in C:\. You can download them from here.

Wexflow Windows Service

To debug Wexflow Windows Service (Wexflow.Clients.WindowsService project), add “debug” command line argument in “Propject settings > Debug > Startup options”:

Wexflow Manager

To debug Wexflow Manager (Wexflow.Clients.Manager project), add “debug” command line argument in “Propject settings > Debug > Startup options”:

Bugs and features

If you found any issues with Wexflow, please submit a bug report at the Issue Tracker. Please include the following:

  • The version of Wexflow you are using.
  • How to reproduce the issue (a step-by-step description).
  • Expected result.

If you’d like to add a feature request please add some details how it is supposed to work.

Using the code

In this section, the source code of the version 1.0.1 will be explained in details. The source code has changed since this release but this section will give you a clear idea on how Wexflow works.

Wexflow source code is very simple to understand. The source code projects are organized as follows:

The Wexflow.Clients solution folder contains Wexflow WIndows Service and Wexflow Manager. The Wexflow.Core solution folder contains the core assembly of Wexflow Wexflow.Core, the WCF service contracts in Wexflow.Core.Service.Contracts and the WCF proxy in Wexflow.Core.Service.Client. The Wexflow.Tasks contains Wexflow tasks.

Wexflow Windows Service

Wexflow uses a Windows Service that hosts a WCF Web service. When Wexflow Windows Service starts a new instance of WexflowEngine is created then runned through the method Run(). The WCF Web service allows to do the following things:

  • Get the list of workflows loaded by WexflowEngine.
  • Get a WorkflowInfo object from a workflow id.
  • Start a workflow from a workflow id.
  • Stop a workflow from a workflow id.
  • Suspend a workflow from a workflow id.
  • Resume a workflow from a workflow id.

Below the source code of Wexflow Windows Service.

public partial class WexflowWindowsService : ServiceBase {     public static string SETTINGS_FILE = ConfigurationManager.AppSettings["WexflowSettingsFile"];     public static WexflowEngine WEXFLOW_ENGINE = new WexflowEngine(SETTINGS_FILE);     private ServiceHost _serviceHost = null;          public WexflowWindowsService()     {         InitializeComponent();         this.ServiceName = "Wexflow";         WEXFLOW_ENGINE.Run();     }     public void OnDebug()     {         this.OnStart(null);     }     protected override void OnStart(string[] args)     {         if (this._serviceHost != null)         {             this._serviceHost.Close();         }                           this._serviceHost = new ServiceHost(typeof(WexflowService));                                        this._serviceHost.Open();     }     protected override void OnStop()     {         if (this._serviceHost != null)         {             this._serviceHost.Close();             this._serviceHost = null;         }     } }

WexflowSettingsFile defaults to C:\Wexflow\Wexflow.xml.

ServiceHost is used to host WexflowService WCF Web service in Wexflow Windows service.

Below the source code of WexflowService.

[ServiceContract(Namespace = "http://WexflowService/")] public interface IWexflowService {     [OperationContract]     string Hello();     [OperationContract]     WorkflowInfo[] GetWorkflows();     [OperationContract]     void StartWorkflow(int workflowId);     [OperationContract]     void StopWorkflow(int workflowId);     [OperationContract]     void SuspendWorkflow(int workflowId);     [OperationContract]     void ResumeWorkflow(int workflowId);     [OperationContract]     WorkflowInfo GetWorkflow(int workflowId); } [ServiceBehavior(IncludeExceptionDetailInFaults=true)] public class WexflowService:IWexflowService {     public string Hello()     {         return "Hello!";     }     public WorkflowInfo[] GetWorkflows()     {         List wfis = new List();         foreach (Workflow wf in WexflowWindowsService.WEXFLOW_ENGINE.Workflows)         {             wfis.Add(new WorkflowInfo(wf.Id, wf.Name, wf.LaunchType, wf.IsEnabled, wf.Description, wf.IsRunning, wf.IsPaused));         }         return wfis.ToArray();     }     public void StartWorkflow(int workflowId)     {         WexflowWindowsService.WEXFLOW_ENGINE.StartWorkflow(workflowId);     }     public void StopWorkflow(int workflowId)     {         WexflowWindowsService.WEXFLOW_ENGINE.StopWorkflow(workflowId);     }     public void SuspendWorkflow(int workflowId)     {         WexflowWindowsService.WEXFLOW_ENGINE.PauseWorkflow(workflowId);     }     public void ResumeWorkflow(int workflowId)     {         WexflowWindowsService.WEXFLOW_ENGINE.ResumeWorkflow(workflowId);     }     public WorkflowInfo GetWorkflow(int workflowId)     {         Workflow wf = WexflowWindowsService.WEXFLOW_ENGINE.GetWorkflow(workflowId);         return new WorkflowInfo(wf.Id, wf.Name, wf.LaunchType, wf.IsEnabled, wf.Description, wf.IsRunning, wf.IsPaused);     } }

Below the system.serviceModel configuration of Wexflow Windows Service.

<system.serviceModel>     <services>       <service behaviorConfiguration="WexflowServiceBehavior" name="Wexflow.Clients.WindowsService.WexflowService">         <endpoint address="" binding="wsHttpBinding" contract="Wexflow.Clients.WindowsService.IWexflowService" />         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />         <host>           <baseAddresses>             <add baseAddress="http://localhost:8000/WexflowService/" />           </baseAddresses>         </host>       </service>     </services>     <behaviors>       <serviceBehaviors>         <behavior name="WexflowServiceBehavior">           <serviceMetadata httpGetEnabled="true"/>           <serviceDebug includeExceptionDetailInFaults="False"/>         </behavior>       </serviceBehaviors>     </behaviors>   </system.serviceModel>

SvcUtil.exe was used to generate the WCF proxy as follows:

SvcUtil.exe http://localhost:8000/WexflowService/ /out:WexflowServiceClient.cs /config:App.config

To debug Wexflow Windows service, the following trick has been used:

namespace Wexflow.Clients.WindowsService {     static class Program     {         static void Main(string[] args)         {             if (args.Length > 0 && args[0].Equals("debug"))             {                 WexflowWindowsService service = new WexflowWindowsService();                 service.OnDebug();                 Thread.Sleep(Timeout.Infinite);             }             else             {                 ServiceBase[] servicesToRun = new ServiceBase[] { new WexflowWindowsService() };                 ServiceBase.Run(servicesToRun);             }         }     } }

Wexflow Engine

The class diagram below describes the general architecture of Wexflow Engine.

WexflowEngine class is composed of a collection of workflows and other properties. A workflow is composed of a collection of tasks and other properties. Task is an abstract class inherited by Wexflow tasks.

Roughly speaking, WexflowEngine class parses all the XML files of the workflows into Workflow objects through LINQ To XML. Then, when the method Run() of this class is called, the workflows having startup as launchType are launched automatically, then the workflows having periodic as launchType are launched through a timer.

When a new instance of WexflowEngine is created the following things happen:

public WexflowEngine(string settingsFile)  {     this.SettingsFile = settingsFile;     LoadSettings();     LoadWorkflows(); }

The workflow engine settings are loaded then the workflows are loaded from their XML files.

The settings are loaded as follows:

private void LoadSettings() {     XDocument xdoc = XDocument.Load(this.SettingsFile);     this.WorkflowsFolder = GetWexflowSetting(xdoc, "workflowsFolder");     this.TempFolder = GetWexflowSetting(xdoc, "tempFolder"); } private string GetWexflowSetting(XDocument xdoc, string name) {     return xdoc.XPathSelectElement(string.Format("/Wexflow/Setting[@name='{0}']", name)).Attribute("value").Value; }

The workflows are loaded as follows:

private void LoadWorkflows() {      List workflows = new List();     foreach (string file in Directory.GetFiles(this.WorkflowsFolder))     {         try         {             Workflow workflow = new Workflow(file, this.TempFolder);             workflows.Add(workflow);             Logger.InfoFormat("Workflow loaded: {0}", workflow);         }         catch (Exception e)         {             Logger.ErrorFormat("An error occured while loading the workflow : {0} Please check the workflow configuration.", file);         }     }     this.Workflows = workflows.ToArray(); }

When a new instance of a workflow is created the following things happen:

public Workflow(string path, string wexflowTempFolder) {     this.JobId = 1;     this._thread = null;     this.WorkflowFilePath = path;     this.WexflowTempFolder = wexflowTempFolder;     this.FilesPerTask = new Dictionary<int, List>();     this.EntitiesPerTask = new Dictionary<int, List>();     Load(); } private void Load() {     XDocument xdoc = XDocument.Load(this.WorkflowFilePath);     this.Id = int.Parse(GetWorkflowAttribute(xdoc, "id"));     this.Name = GetWorkflowAttribute(xdoc, "name");     this.Description = GetWorkflowAttribute(xdoc, "description");     this.LaunchType = (LaunchType)Enum.Parse(typeof(LaunchType), GetWorkflowSetting(xdoc, "launchType"), true);     if(this.LaunchType == Core.LaunchType.Periodic) this.Period = TimeSpan.Parse(GetWorkflowSetting(xdoc, "period"));     this.IsEnabled = bool.Parse(GetWorkflowSetting(xdoc, "enabled"));     List tasks = new List();     foreach (XElement xTask in xdoc.XPathSelectElements("/Workflow/Tasks/Task"))     {         string name = xTask.Attribute("name").Value;         string assemblyName = "Wexflow.Tasks." + name;         string typeName = "Wexflow.Tasks." + name + "." + name + ", " + assemblyName;         Task task = (Task)Activator.CreateInstance(Type.GetType(typeName), xTask, this);         tasks.Add(task);     }     this.Taks = tasks.ToArray(); } private string GetWorkflowAttribute(XDocument xdoc, string attr) {     return xdoc.XPathSelectElement("/Workflow").Attribute(attr).Value; } private string GetWorkflowSetting(XDocument xdoc, string name) {     return xdoc.XPathSelectElement(string.Format("/Workflow[@id='{0}']/Settings/Setting[@name='{1}']", this.Id, name)).Attribute("value").Value; }

First of all, the workflow settings are retrieved then the tasks are built by reflection.

When Wexflow engine starts through the method Run() the following things happen:

public void Run() {     foreach (Workflow workflow in this.Workflows)     {         if (workflow.IsEnabled)         {             if (workflow.LaunchType == LaunchType.Startup)             {                 workflow.Start();             }             else if (workflow.LaunchType == LaunchType.Periodic)             {                 Action callback = o =>                 {                     Workflow wf = (Workflow)o;                     if (!wf.IsRunning) wf.Start();                 };                                  WexflowTimer timer = new WexflowTimer(new TimerCallback(callback), workflow, workflow.Period);                 timer.Start();             }         }     } }

For each workflow, if the workflow is enabled, Wexflow will launch it if its launchType is startup otherwise if its launchType is periodic Wexflow will create a WexflowTimer that will launch the workflow every period.

The WexflowTimer class is very simple and looks like as follows:

public class WexflowTimer {     public TimerCallback TimerCallback { get; private set; }     public object State { get; private set; }     public TimeSpan Period { get; private set; }     public WexflowTimer(TimerCallback timerCallback, object state, TimeSpan period)     {         this.TimerCallback = timerCallback;         this.State = state;         this.Period = period;     }     public void Start()     {         Thread thread = new Thread(new ThreadStart(() =>         {             Stopwatch stopwatch = new Stopwatch();             stopwatch.Start();             for(;;)             {                 if (stopwatch.ElapsedMilliseconds >= this.Period.TotalMilliseconds)                 {                     stopwatch.Reset();                     stopwatch.Start();                     this.TimerCallback.Invoke(this.State);                 }                 Thread.Sleep(100);             }         }));         thread.Start();     } }

I’ve used a custom timer because I’ve faced a lot of issues whith System.Threading.Timer while testing complex periodic workflows.

When a workflow is started the following things happen:

public void Start() {     Thread thread = new Thread(new ThreadStart(() =>         {             try             {                 this.IsRunning = true;                 Logger.InfoFormat("{0} Workflow started.", this.LogTag);                                  CreateTempFolder();                                  foreach (Task task in this.Taks)                 {                     if (task.IsEnabled)                     {                         task.Run();                     }                 }             }             catch (ThreadAbortException)             {             }             catch (Exception e)             {                 Logger.ErrorFormat("An error occured while running the workflow : {0}", e, this);             }             finally             {                                  foreach (List files in this.FilesPerTask.Values) files.Clear();                 foreach (List entities in this.EntitiesPerTask.Values) entities.Clear();                 this._thread = null;                 this.IsRunning = false;                 GC.Collect();                 Logger.InfoFormat("{0} Workflow finished.", this.LogTag);                 this.JobId++;             }         }));     this._thread = thread;     thread.Start(); } private void CreateTempFolder() {           string wfTempFolder = Path.Combine(this.WexflowTempFolder, this.Id.ToString());     if (!Directory.Exists(wfTempFolder)) Directory.CreateDirectory(wfTempFolder);          string wfDayTempFolder = Path.Combine(wfTempFolder, string.Format("{0:yyyy-MM-dd}", DateTime.Now));     if (!Directory.Exists(wfDayTempFolder)) Directory.CreateDirectory(wfDayTempFolder);          string wfJobTempFolder = Path.Combine(wfDayTempFolder, string.Format("{0:HH-mm-ss-fff}", DateTime.Now));     if (!Directory.Exists(wfJobTempFolder)) Directory.CreateDirectory(wfJobTempFolder);     this.WorkflowTempFolder = wfJobTempFolder; }

A new thread is created and launched. In this thread, a temporary folder is created then the tasks are launched one by one. At the end of the workflow the files and entities loaded by the tasks are cleared.

Below the FileInf class:

public class FileInf {     public string Path { get; private set; }     public string FileName { get; private set; }     public int TaskId { get; private set; }     public FileInf(string path, int taskId)     {         this.Path = path;         this.FileName = System.IO.Path.GetFileName(this.Path);         this.TaskId = taskId;     } }

Below the Entity class:

public abstract class Entity {     public int TaskId { get; private set; } }

The workflows having trigger as launchType are launched from the Wexflow Manager by calling Workflow.Start() method.

A workflow can be stopped, suspended and resumed from the Wexflow Manager.

Finally, each task implements the abstract class Wexflow.Core.Task:

public abstract class Task {     public int Id { get; private set; }     public string Name { get; private set; }     public string Description { get; private set; }     public bool IsEnabled { get; private set; }     public Workflow Workflow { get; private set; }     public List Files   {          get         {             return this.Workflow.FilesPerTask[this.Id];         }     }     public List Entities     {         get         {             return this.Workflow.EntitiesPerTask[this.Id];         }     }     private XElement _xElement;     public Task(XElement xe, Workflow wf)      {         this._xElement = xe;         this.Id = int.Parse(xe.Attribute("id").Value);         this.Name = xe.Attribute("name").Value;         this.Description = xe.Attribute("description").Value;         this.IsEnabled = bool.Parse(xe.Attribute("enabled").Value);         this.Workflow = wf;         this.Workflow.FilesPerTask.Add(this.Id, new List());         this.Workflow.EntitiesPerTask.Add(this.Id, new List());     }     public abstract void Run();     public string GetSetting(string name)     {         return this._xElement.XPathSelectElement(string.Format("Setting[@name='{0}']", name)).Attribute("value").Value;     }     public string GetSetting(string name, string defaultValue)     {         XElement xe = this._xElement.XPathSelectElement(string.Format("Setting[@name='{0}']", name));         if (xe == null) return defaultValue;         return xe.Attribute("value").Value;     }     public string[] GetSettings(string name)     {         List settings = new List();         foreach (XElement xe in this._xElement.XPathSelectElements(string.Format("Setting[@name='{0}']", name)))         {             settings.Add(xe.Attribute("value").Value);         }         return settings.ToArray();     }     public FileInf[] SelectFiles()      {         List files = new List();         foreach (string id in this.GetSettings("selectFiles"))         {             int taskId = int.Parse(id);             files.AddRange(this.Workflow.FilesPerTask[taskId]);         }         return files.ToArray();     }     public Entity[] SelectEntities()     {         List entities = new List();         foreach (string id in this.GetSettings("selectEntities"))         {             int taskId = int.Parse(id);             entities.AddRange(this.Workflow.EntitiesPerTask[taskId]);         }         return entities.ToArray();     } }

Each task implements a custom logic in the Run() method. Each task can load a collection of files through the Files property and a collection of entities through the Entities property. The source code of each task will not be detailed in this article but you can have a look at the source code of each task so you can see how to build custom tasks.

Wexflow manager

Wexflow Manager is a really simple windows forms application that shows all the workflows loaded by Wexflow and allows the end user to start, stop, suspend or resume a workflow.

The source code of Wexflow manager looks like as follows:

private const string COLUMN_ID = "Id"; private const string COLUMN_ENABLED = "Enabled"; private const int TIMER_INTERVAL = 100; private WexflowServiceClient _wexflowServiceClient; private WorkflowInfo[] _workflows; private Dictionary _timers; private Dictionary _previousIsRunning; private Dictionary _previousIsPaused; private bool _windowsServiceWasStopped; public Form1() {     InitializeComponent();     this.textBoxInfo.Text = "Loading workflows...";     this._timers = new Dictionary();     this._previousIsRunning = new Dictionary();     this._previousIsPaused = new Dictionary();     this.backgroundWorker1.RunWorkerAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {     if (Program.DEBUG_MODE || Program.IsWexflowWindowsServiceRunning())     {         this._wexflowServiceClient = new WexflowServiceClient();         this._workflows = _wexflowServiceClient.GetWorkflows();     }     else      {         this._workflows = new WorkflowInfo[] { };         this.textBoxInfo.Text = "";     } } private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {     BindDataGridView();    } private void BindDataGridView() {     SortableBindingList workflows = new SortableBindingList();     foreach (WorkflowInfo workflow in this._workflows)     {         workflows.Add(new WorkflowDataInfo(workflow.Id, workflow.Name, workflow.LaunchType, workflow.IsEnabled, workflow.Description));     }     this.dataGridViewWorkflows.DataSource = workflows;     this.dataGridViewWorkflows.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;     this.dataGridViewWorkflows.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;     this.dataGridViewWorkflows.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;     this.dataGridViewWorkflows.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;     this.dataGridViewWorkflows.Columns[3].Name = COLUMN_ENABLED;     this.dataGridViewWorkflows.Columns[3].HeaderText = COLUMN_ENABLED;     this.dataGridViewWorkflows.Columns[4].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;          this.dataGridViewWorkflows.Sort(this.dataGridViewWorkflows.Columns[0], ListSortDirection.Ascending); }

When a new instance of Form1 is created a BackgroundWorker is started asynchronously. This BackgroundWorker retrieves the workflows loaded by Wexflow engine and binds the DataGridview in OnCompleted event. 

Below the WorkflowDataInfo class:

public class WorkflowDataInfo:IComparable {     public int Id { get; private set; }     public string Name { get; private set; }     public LaunchType LaunchType { get; private set; }     public bool IsEnabled { get; private set; }     public string Description { get; private set; }     public WorkflowDataInfo(int id, string name, LaunchType launchType, bool isEnabled, string desc)     {         this.Id = id;         this.Name = name;         this.LaunchType = launchType;         this.IsEnabled = isEnabled;         this.Description = desc;     }     public int CompareTo(object obj)     {         WorkflowDataInfo wf = (WorkflowDataInfo)obj;         return wf.Id.CompareTo(this.Id);     } }

Below the implementation of the Click events of each button:

private void buttonStart_Click(object sender, EventArgs e) {     int wfId = GetSlectedWorkflowId();     if (wfId > -1)     {         this._wexflowServiceClient.StartWorkflow(wfId);     } } private void buttonPause_Click(object sender, EventArgs e) {     int wfId = GetSlectedWorkflowId();     if (wfId > -1)     {         this._wexflowServiceClient.SuspendWorkflow(wfId);         this.UpdateButtons(wfId, true);     } } private void buttonResume_Click(object sender, EventArgs e) {     int wfId = GetSlectedWorkflowId();     if (wfId > -1)     {         this._wexflowServiceClient.ResumeWorkflow(wfId);     } } private void buttonStop_Click(object sender, EventArgs e) {     int wfId = GetSlectedWorkflowId();     if (wfId > -1)     {         this._wexflowServiceClient.StopWorkflow(wfId);         this.UpdateButtons(wfId, true);     } } private int GetSlectedWorkflowId() {     int wfId = -1;     if (dataGridViewWorkflows.SelectedRows.Count > 0)     {         if(Program.DEBUG_MODE || Program.IsWexflowWindowsServiceRunning())         {             wfId = int.Parse(dataGridViewWorkflows.SelectedRows[0].Cells[COLUMN_ID].Value.ToString());         }         else         {             HandleNonRunningWindowsService();         }     }     return wfId; } private WorkflowInfo GetWorkflow(int id) {     if (Program.DEBUG_MODE || Program.IsWexflowWindowsServiceRunning())     {         if (this._windowsServiceWasStopped)         {             this._wexflowServiceClient = new WexflowServiceClient();             this._windowsServiceWasStopped = false;             this.backgroundWorker1.RunWorkerAsync();         }         return this._wexflowServiceClient.GetWorkflow(id);     }     else     {         this._windowsServiceWasStopped = true;         HandleNonRunningWindowsService();     }     return null; } private void HandleNonRunningWindowsService() {     this.buttonStart.Enabled = this.buttonPause.Enabled = this.buttonResume.Enabled = this.buttonStop.Enabled = false;     this.textBoxInfo.Text = "Wexflow Windows Service is not running."; } private void UpdateButtons(int wfId, bool force) {     if (wfId > -1)     {         Workflow workflow = _wexflowEngine.GetWorkflow(wfId);         if (workflow != null)         {             if (!workflow.IsEnabled)             {                 this.textBoxInfo.Text = "This workflow is disabled.";                 this.buttonStart.Enabled = this.buttonPause.Enabled = this.buttonResume.Enabled                     = this.buttonStop.Enabled = false;             }             else             {                 if (!force && !WorkflowStatusChanged(workflow)) return;                 buttonStart.Enabled = !workflow.IsRunning;                 buttonStop.Enabled = workflow.IsRunning && !workflow.IsPaused;                 buttonPause.Enabled = workflow.IsRunning && !workflow.IsPaused;                 buttonResume.Enabled = workflow.IsPaused;                 if (workflow.IsRunning && !workflow.IsPaused)                 {                     this.textBoxInfo.Text = "This workflow is running...";                 }                 else if (workflow.IsPaused)                 {                     this.textBoxInfo.Text = "This workflow is paused.";                 }                 else                 {                     this.textBoxInfo.Text = "";                 }             }         }     } } private bool WorkflowStatusChanged(Workflow workflow) {     bool changed = false;     if (!this._previousIsRunning.ContainsKey(workflow.Id))     {         this._previousIsRunning.Add(workflow.Id, workflow.IsRunning);         changed = true;     }     if (!this._previousIsPaused.ContainsKey(workflow.Id))     {         this._previousIsPaused.Add(workflow.Id, workflow.IsPaused);         changed = true;     }     if (changed)     {         return true;     }     else     {         if (this._previousIsRunning[workflow.Id] != workflow.IsRunning)         {             changed = true;         }         if (this._previousIsPaused[workflow.Id] != workflow.IsPaused)         {             changed = true;         }         this._previousIsRunning[workflow.Id] = workflow.IsRunning;         this._previousIsPaused[workflow.Id] = workflow.IsPaused;         return changed;     } }

Below the implementation of SelectionChanged event of the DataGridView:

private void dataGridViewWorkflows_SelectionChanged(object sender, EventArgs e) {     int wfId = GetSlectedWorkflowId();     if (wfId > -1)     {         Workflow workflow = _wexflowEngine.GetWorkflow(wfId);         foreach (Timer timer in this._timers.Values) timer.Stop();         if (workflow.IsEnabled)         {             if (!this._timers.ContainsKey(wfId))             {                 Timer timer = new Timer();                 timer.Interval = TIMER_INTERVAL;                 timer.Tick += new EventHandler((o, ea) =>                     {                         this.UpdateButtons(wfId, false);                     });                 this._timers.Add(wfId, timer);             }             this.UpdateButtons(wfId, true);             this._timers[wfId].Start();         }         else         {             this.UpdateButtons(wfId, true);         }     } }

When the selection of the DataGridView changes, a timer is created for the selected workflow if it does not exist then this timer updates the Enabled option of the buttons and the textBoxInfo text box if the status of the selected workflow changes through the time.

If a workflow is double clicked in Wexflow Manager, it starts or it resumes if it was suspended as follows:

private void dataGridViewWorkflows_CellDoubleClick(object sender, DataGridViewCellEventArgs e) {     int wfId = this.GetSlectedWorkflowId();     if (wfId > -1)     {          Workflow workflow = _wexflowEngine.GetWorkflow(wfId);         if (workflow != null && workflow.IsEnabled)         {             if (!workflow.IsRunning && !workflow.IsPaused)             {                 buttonStart_Click(this, null);             }             else if(workflow.IsPaused)             {                 buttonResume_Click(this, null);             }         }     } }

That’s it. I hope you enjoyed reading this article. If you have any thoughts to improve Wexflow or if you face any issues or if you want to contribute to this project please let me know in the comments.

Libraries used by Wexflow

Here is the list of the libraries used by Wexflow:

  • FluentFTP: An FTP client supporting FTP and FTPS(exmplicit/implicit) written in C# and under MIT license.
  • SSH.NET: An SSH library for .NET written in C# and under MIT license.
  • SharpZipLib: A Zip, GZip, Tar and BZip2 library written in C# and under MIT license.
  • Saxon-HE: An XSLT and XQuery Processor that provides implementations of XSLT (2.0), XQuery (1.0, 3.0, and 3.1), and XPath (2.0, 3.0, and 3.1) at the basic level of conformance defined by W3C. It’s an open source library available under the Mozilla Public License version 1.0.
  • log4net: A port of the famous Apache log4j framework to the Microsoft .NET runtime. It’s under the Apache license version 2.0.
  • TweetSharp: A fast and clean wrapper around the Twitter AP written in C#.
  • Microsoft Sync Framework 2.1: A data synchronization platform that allows to synchronize data across multiple data stores.
  • Json.NET: A high-performance JSON framework for .NET written in C# and under MIT license.
  • Hammock: an HTTP library that simplifies consuming and wrapping RESTful services.
  • Mono.Security: A library that provides the missing pieces to .NET security.
  • Oracle Data Access Components (ODAC): Oracle database client for .NET.
  • MySQL Connector/Net: A fully-managed ADO.NET driver for MySQL.
  • System.Data.SQLite: An ADO.NET provider for SQLite.
  • Npgsql: An open source ADO.NET Data Provider for PostgreSQL written in C# and under the PostgreSQL License, a liberal OSI-approved open source license.
  • .NET Data Provider for Teradata: An ADO.NET provider for Teradata.
  • Eto.Forms: A cross platform GUI framework for desktop and mobile applications in .NET.

History

  • 5 Jan 2017:
  • 9 Jan 2017: 
    • Released version 1.0.1.
    • Created Wexflow Windows Service.
    • Created Tar, Tgz and Sql tasks.
    • Updated Wexflow Manager.
    • Fixed some bugs.
  • 16 Jan 2017:
  • 23 Jan 2017:
    • Released version 1.0.3.
    • Created HttpSyncFilesRenamerFilesExist and Wait tasks.
    • Created file tags functionality.
    • Added list, download and delete commands to Ftp task (FTP/FTPS(explicit/implicit)/SFTP).
    • Added retryCount and retryTimeout setting options to Ftp task.
    • Updated Wexflow manager.
    • Updated Wexflow engine.
    • Fixed some bugs.
    • Updated setup file.
    • Updated article content.
  • 26 Jan 2017:
    • Released version 1.0.4.
    • Created XSD validation of worklfow files before loading them.
    • Created tasks execution graph.
    • Created flowchart workflows (DoIf and DoWhile).
    • Created workflow events (OnSuccess, OnWarning and OnError).
    • Created FileExists flowchart task.
    • Updated setup.
    • Updated article content.
  • 30 Jan 2017:
    • Released version 1.0.5.
    • Created Wexflow Web Manager: a lightweight JavaScript API (~6Kb) for managing workflows.
    • Created Wexflow Manager GUI for Linux.
    • Updated Wexflow Manager for Windows.
    • Updated setup for Windows.
    • Created a setup for Linux.
  • 06 Feb 2017:
    • Released version 1.0.6.
    • Created Wexflow Android Manager: an Android application for managing workflows.
    • Updated Wexflow Web Manager.
    • Updated Wexflow Manager (Windows and Linux).
    • Updated Wexflow Engine.
  • 17 Feb 2017:
    • Released version 1.0.7.
    • Created Movedir task.
    • Updated Wexflow Web Manager.
    • Updated Wexflow Manager (Windows, Linux and Android).
    • Updated Wexflow Engine.
    • Fixed some bugs.
  • 06 Mar 2017:
    • Released version 1.0.8.
    • Created Swictch/Case flowchart node.
    • Created Now task.
    • Created Wexflow Manager for macOS.
    • Updated If and While flowchart nodes syntax.
    • Updated setup projects.
    • Now, an If flowchart node can be inside an If, a While and a Switch flowchart nodes.
    • Now, a While flowchart node can be inside an If, a While and a Switch flowchart nodes.
    • Now, a Switch flowchart node can be inside an If, a While and a Switch flowchart nodes.
    • Code refactoring.
    • Fixed some bugs.
  • 07 Apr 2017:
    • Released version 1.0.9.
    • Fixed Switch flowchart node children execution.
    • Fixed WCF service configuration issue.
  • 22 May 2017:
    • Released version 1.1.
    • Created Workflow task. This task allows to start, suspend, resume or stop a list of workflows.
    • Performance optimization.
    • Code refactoring.

LEAVE A REPLY