- Install Ansible on Windows - Thu, Jul 20 2023
- Use Azure Bastion as a jump host for RDP and SSH - Tue, Apr 18 2023
- Azure Virtual Desktop: Getting started - Fri, Apr 14 2023
Windows PowerShell Workflow
What a workflow is
From the broadest possible perspective, a workflow is (according to Google) a sequence of industrial or administrative processes through which a piece of work passes from initiation to completion.
Microsoft built a workflow engine named, appropriately enough, Windows Workflow Foundation (WF), which allows .NET applications to offload workflow processing. The offloading piece is important because some long-running tasks can consume many CPU cycles.
Windows PowerShell v3 and later connects to WF via its own internal PSWorkflow core module. Specifically, you use the Workflow data structure to define one or more activities that have the following attributes:
- Can affect one or more machines either in sequence or in parallel
- Can survive power interruptions and system restarts
- Can execute tasks in a specified order
- Can be paused and resumed at the administrator’s discretion
When you run a PowerShell workflow, your activities are translated behind the scenes into Extensible Application Markup Language (XAML, pronounced ZAM-uhl) and handed off to WF for processing.
Here’s the requisite “Hello World” example of a PowerShell Workflow script:
Workflow Get-Hello { Write-Output –InputObject 'Hello world!' }
In a nutshell, here is PowerShell Workflow’s primary use case: create workflows for long-running, potentially complex jobs that require persistence and possible administrator intervention.
From what I’ve seen and experienced in the industry, PowerShell Workflow is particularly suited to larger-scale provisioning and deprovisioning tasks. For instance, you can use Workflow to deploy a VM pod for a newly hired developer in your company.
Workflow scripts vs. PowerShell functions
At first blush, PowerShell Workflow syntax appears to mirror that of PowerShell functions. Consider the following simple example:
function Get-Hello2 { Clear-Host Write-Host 'Hello world!' -BackgroundColor Yellow -ForegroundColor Black }
You call the workflow and function the same way:
Get-Hello Get-Hello2
However, if you try to make the Get-Hello2 function a workflow, PowerShell will throw errors. Why? Because the workflow isn’t native PowerShell but a “wrapper” around the Windows Workflow Foundation.
In fact, PowerShell Workflow has its own set of dedicated keywords:
That said, you can include an InlineScript expression to run “pure” PowerShell code as-is:
Workflow Get-Hello { InlineScript { Write-Host 'Hello world!' -BackgroundColor Yellow - ForegroundColor Black } }
You should read the workflow-related PowerShell conceptual help to learn more about this:
Get-Help -Name about_*workflow* | Select-Object -Property Name, Synopsis Name Synopsis ---- -------- about_Checkpoint-Workflow Describes the Checkpoint-Workflow... about_Suspend-Workflow Describes the Suspend-Workflow... about_WorkflowCommonParameters This topic describes the parameters... about_Workflows Provides a brief introduction...
A sample workflow
Perhaps the best way to “sink our teeth” into PowerShell Workflow is to create a representative example. Imagine we have three Windows Server 2012 R2 computers:
- dc: administrative server from which we’ll run the workflow
- adfs1: member server
- mem2: member server
It’s not insignificant to note that we create, run, and manage the workflow from a separate computer. You can even use your Windows 8.1 administrative workstation. By combining Windows PowerShell WS-Man remoting and Windows Workflow Foundation, you have a lot of automation and power at your disposal.
We’ll write a workflow named Get-SysInfo that performs the following (admittedly arbitrary) actions:
- Ping one or more target hosts to determine their availability and write to an output file.
- Retrieve WMI details on the target hosts and write to an output file.
- Checkpoint the workflow.
- Force a restart of each target host.
- Gather BIOS details on the target hosts post-restart.
My workflow activities are so “all over the place” because I want to show you as much functionality as I can in a limited space. Without further ado, let me show you the code and then explain line by line what’s happening:
Workflow Get-Sysinfo { param( [string[]]$h) Parallel { Test-Connection -ComputerName $h -Count 1 | Out-File -FilePath 'C:\tmp\ping.txt' -Append Get-CimInstance -PSComputerName $h -ClassName Win32_OperatingSystem | Out-File -FilePath 'C:\tmp\os.txt' -Append } Checkpoint-Workflow foreach -parallel ($h1 in $h) { #Restart-Computer -Wait -Force -PSComputerName $h1 Get-CimInstance -PSComputerName $h -ClassName Win32_BIOS | Out-File -FilePath 'C:\tmp\bios.txt' -Append } } Get-Sysinfo -h mic8
1: We name our workflows according to PowerShell best practice, using the ApprovedVerb-SingularNoun format.
2: We define a string array variable so we can pass one or more computer names to the workflow.
4: Any activities we place in a Parallel block are executed at the same time.
5-6: You’ll have to experiment to determine which commands are or are not acceptable in a workflow. Generally, any command that requires manual intervention isn’t allowed.
9: This is how we can place checkpoints in a workflow. We can suspend and resume workflows at checkpoints; this makes your workflows highly resilient.
11: Another way to do parallel execution is the foreach -Parallel construction. Here, we iterate through a collection (in this case, computers) synchronously.
12: The -Wait switch parameter is important here so we can keep the workflow script active while the target machines complete the reboot and automatically resume the workflow.
17: Note that we call a script workflow in exactly the same way that we run PowerShell simple and advanced functions.
For grins, you can view the underlying workflow XAML code by running Get-Command:
Get-Command –Name Get-SysInfo | Format-List *
More on checkpoints
The idea behind workflow checkpoints is that we may want to prevent re-running an entire workflow if our script bombs out partway through. In addition to placing manual checkpoints in your workflow by using Checkpoint-Workflow, you can also instruct PowerShell to “automagically” take a checkpoint after each activity by invoking the PSPersist workflow common parameter:
Get-SysInfo –PSPersist $True
A checkpointed workflow persists on the hard drive (specifically, in the home folder of the user who is currently logged on) of the computer that’s orchestrating the workflow.
The other “gotcha” with checkpointed workflows is that they need to be started as a job; that’s what allows you to resume a suspended workflow job later:
Get-SysInfo –PSPersist $True –AsJob
We then call Get-Job, Receive-Job, and Resume-Job as we do normally.
More on parallel vs. serial execution
We saw that we can instruct Windows Workflow Foundation to run our workflow activities in parallel either by using the Parallel block or the foreach -Parallel construction.
Curiously, to run activities in a specified sequence, we need to place a sequence block within a Parallel block:
workflow start-testworkflow { Parallel { Sequence { Install-WindowsFeature –Name Web-Server –IncludeManagementTools Get-Service –Name W3SVC } } }
More on workflow syntax
Recall that a PowerShell workflow is simply an IT administrator-friendly way to access Windows Workflow Foundation. If we were .NET developers, we’d create our XAML by using Visual Studio.
Therefore, we’re limited in the kinds of PowerShell code we can add to a workflow. Here are some general guidelines:
- Spell out all your cmdlet calls (no aliases).
- Use named parameters (no positional parameters).
- Avoid any commands that reference user interaction (workflows are meant to run independently).
You can hack around the workflow syntax restriction by using an InlineScript block. Here, the workflow passes your PowerShell script commands as a workflow activity on its own and with no further comment:
workflow Start-TestWorkflow2 { InlineScript { \\mem2\scripts\Install-VMs.ps1 -All } }
Workflow is not an easy topic to master, that’s for sure. Nevertheless, I hope that I was able to get you a bit clearer on the subject. Thanks for reading!