Windows PowerShell: Workflow activities in depth

Each month this year, Don Jones will present an installment in a 12-part tutorial on Windows PowerShell Workflow. We encourage you to read through the series in order, beginning with the January 2013 column.

Don Jones

When writing a workflow in Windows PowerShell, you need to remember that every command has to be translated into a Windows Workflow Foundation (WF) activity or an InlineScript activity.

First of all, what’s an activity? Windows PowerShell workflows are ultimately translated into something that WF understands. What WF understands is activities. In WF, an activity is a single action that takes place within a workflow.

Many of the core Windows PowerShell commands have equivalent activities. Those activities are completely separate from the commands. In other words, the team at Microsoft had to sit down and write both the Windows PowerShell command—such as Where-Object—and a corresponding WF activity that did the same thing.

So they’re really two parallel structures. When you use a command in a workflow, Windows PowerShell looks to see if there has been an equivalent activity written and installed on the computer. If there is one, then that’s what the workflow will use.

This is an important consideration. It’s entirely possible that other Microsoft teams, and even third parties, will author activities in the future. Before you can use them, though, they must be installed and registered on whichever computer will run the workflows you write. How you go about doing that will differ from product to product.

Absence of activity

If you use a command that doesn’t have a WF activity equivalent, Windows PowerShell will wrap the command in an implicit InlineScript block. InlineScript is an activity natively recognized by WF. Basically, it tells WF to launch a copy of Windows PowerShell, run the command and then close that copy of Windows PowerShell.

Because the InlineScript wrapping can happen implicitly—meaning you don’t see it happening—you may be completely unaware that it’s happening. In Windows PowerShell 3.0, in fact, it’s difficult to figure out which commands have WF equivalents and which ones don’t. You might not ever be able to tell what’s being run as a native activity and what’s being wrapped in an InlineScript. For the most part, you don’t need to know. It does, however, create certain impact.

If you were to try this, for example, it would fail:

Workflow My-Workflow { Import-Module DHCPServer Get-DHCPServerv4Scope }

The workflow fails because there’s no WF activity for Import-Module. Each activity needs to happen in its own process (more or less). Remember you can interrupt and later resume a workflow, meaning every command needs to do its own setup and teardown. Nothing can rely on anything else having run in the same memory or process. Windows PowerShell won’t wrap Import-Module in an InlineScript because that would open Windows PowerShell, run Import-Module and then close Windows PowerShell. This would effectively unload the module and make the entire exercise pointless.

The following, however, will work:

Workflow My-Workflow { InlineScript { Import-Module DHCPServer Get-DHCPServerv4Scope } }

This adds an explicit InlineScript block. This means the two commands will run in the same Windows PowerShell session.

InlineScript blocks

You can probably imagine how many of your workflows could be broken into one or more InlineScript blocks. After all, if you need to run more than one command in a process and have those commands share results and output, then an InlineScript is the way to go.

Keep in mind that each InlineScript is its own self-contained Windows PowerShell process. For example, this won’t work:

Workflow My-Workflow { InlineScript { Import-Module DHCPServer $scopes = Get-DHCPServerv4Scope } InlineScript { ForEach ($scope in $scopes) { Write $scope.IPAddress } } }

The $scopes variable created in the first InlineScript won’t exist in the second one. The code will fail or just not do anything. Now you may be thinking, “Why don’t I just put everything into a single InlineScript block?” You could:

Workflow My-Workflow { InlineScript { Import-Module DHCPServer $scopes = Get-DHCPServerv4Scope ForEach ($scope in $scopes) { Write $scope.IPAddress } } }

The problem you run into now is that your workflow consists of a single activity. WF only supports inter-activity checkpoints and resumption. Your single-activity workflow can’t be paused, resumed or interrupted.

In many ways, you’ve removed the advantages of doing a workflow in the first place. You still get built-in support for remote execution. If this was a long-running, multi-step process, though, you couldn’t survive a failure (like a network interruption or power outage) and pick up where you left off.

Workflow therefore becomes an interesting architectural puzzle. You want to keep each task as discrete and self-contained as possible. By doing so, you can get maximum support for interruption and resuming. You’re limited, however, by the process boundary for sharing information between commands.

In fact, what would make Windows PowerShell Workflow a lot more useful is the ability for one activity to persist information in an external store (like SQL Server). That way another activity could pick up and use that information. Such an external store would become a kind of persistent, external variable store.

Unfortunately, Windows PowerShell doesn’t natively offer such a thing. There’s no reason, however, that you couldn’t build something like that on your own.

Don Jones

Don Jones* is a Windows PowerShell MVP Award recipient, and a contributing editor to TechNet Magazine. He has coauthored four books about Windows PowerShell version 3, including free ones on Windows PowerShell remoting and creating HTML reports in Windows PowerShell. Find them all at PowerShellBooks.com, or ask Jones questions in the discussion forums at PowerShell.org.*