Summary: Guest blogger Jeff Wouters talks about the basics of Windows PowerShell 3.0 workflows: when to use it and why.
Microsoft Scripting Guy, Ed Wilson, is here. Today, The Scripting Wife and I are traveling on our way from Oslo, Norway, to Eindhoven, The Netherlands, where I will speak at the Dutch PowerShell User Group event tomorrow. Tonight, we will spend the night in a sleeper car on the train for the last part of the journey between Copenhagen, Denmark, and Utrecht in The Netherlands. I have ridden in a sleeper car before, but this will be Teresa’s first time and she is all excited. Once we arrive in Utrecht, Microsoft PFE Stefan Stranger will pick us up to take us the rest of the way to the User Group Event.
This leads to today’s guest blogger and the host for the User group event Jeff Wouters.
Jeff Wouters (B ICT, MCITP, MCSA, MCSE) is a freelance Technical Consultant from The Netherlands with a main focus on high availability and automation using Microsoft and Citrix products by using technologies such as virtualization, redundancy, clustering, and replication. He also has a great passion for Windows PowerShell and is a founding member of the Dutch PowerShell User Group in 2012.
Jeff has been a speaker on IT events such as E2E Virtualization Conference (formerly known as PubForum), BriForum Chicago, and NGN (Dutch IT community). You can find Jeff on social media (Twitter @JeffWouters | Facebook @JeffWouters | LinkedIn @JeffWouters) and at his blog. He speaks and blogs mainly about Windows PowerShell and virtualization, but every now and then something else slips in that piques his interest.
Jeff is also a contributing author for a book project where 30+ authors from all over the world work together to create a Windows PowerShell deep-dive book that will be published in 2013.
Take it away, Jeff.
Back when Windows PowerShell 2.0 was introduced, it brought us remoting. Soon after, you could see lots of products starting to use this technology since it is very, very powerful.
Windows PowerShell 3.0 has brought us workflow. I dare to predict that this will have an even bigger impact than remoting had, and therefore, I had the idea to write a little blog post about it from my usual practical point of view… and put the subject on my “to write” list.
And then Ed’s mail came along asking if I would be interested in writing a guest post for the Hey, Scripting Guy! Blog. As soon as I read the mail I thought about my plan to write a Workflow post and thought that this would be a great subject for Ed’s blog.
And, so here I am … Jeff Wouters, PowerShell enthusiast, from The Netherlands and blogger at http://www.jeffwouters.nl.
What is workflow?
A workflow is basically a bunch of tasks executed in sequence that require some coordination because they may be executed across different devices. Or, at least that’s what the Help file says. I’ve found a few more practical applications for it that I’ll cover in this post.
By design, workflows are repeatable, interruptible, restartable, stoppable, parallelizable, and frequent—more about that in “Why use workflow?” later in this post.
Although workflow is very powerful, please understand that it is also very difficult. It is very easy to overcomplicate things and, as with all larger scripts, it’s smart to think about what you want your script to do, including its structure, before actually writing the script.
PowerShell has a core module named PSWorkflow, which simply makes use of the Windows Workflow Foundation. Workflow is not something native to Windows PowerShell—it simply makes very smart use of a technology that’s already there in the Windows operating system.
The power of workflow
To truly understand the power of workflow, I can only tell you to just start using it.
But, of course, that’s not something useful for this post because you want an example, right? Instead of giving you a bunch of code, I’ll simply give you an example that just about any Windows administrator knows: Server Manager.
In Windows Server 2012, they’ve totally rewritten the thing, and it has become a lot faster because it is now basically a GUI around (A LOT!) of Windows PowerShell and WMI code. It also allows you to manage your environment from that single console because it also uses workflow!
When you deploy a scenario-based solution from Server Manager, it will know, based on that scenario, which task to execute before the other. It will generate a workflow to accomplish the actual work and will let it loose on the target servers. So, WMI combined with Windows PowerShell and workflow and remoting brings some serious horse power to the table.
Why use workflow?
Don Jones, Windows PowerShell MVP, has already covered that in one of his posts, so I’ll not be re-doing his work: http://powershell.org/wp/2012/08/30/powershell-workflow-when-should-you-use-it.
In my opinion, only situations for a deployment script and some basic maintenance/configuration scripts will allow you to use workflow in an easy and understandable way. You’ll see that in the scripts I’m providing in this post.
That’s why, as with all larger scripts, I advise you to chop your larger workflows up into smaller pieces.
With code, developers call this modular programming. When you do it in your mind, I call it modular thinking. So, when I start scripting, I think: Tasks > Workflows > Functions > Modules… in that order.
For me, personally, this has made my scripting life a whole lot easier.
Don’t use workflow unless there is no other way … ?
I’ve seen someone write—don’t know for sure who it was—that you should only apply workflow when there is truly no other option available that fits your specific needs. I don’t agree with that up to a certain point and let me tell you why.
- When you can read functions, you can read workflows—it’s readable.
- Even at its basic level, workflow is very powerful.
- A lot of times using workflow will require much less code compared to not using it while it remains readable.
- It can seriously reduce execution times for your scripts.
- When you keep at the basics, while it still suits your needs, it isn’t that difficult… really!
Next to these things, the only true limitation to Windows PowerShell is your imagination. So, when you overcomplicate your scripts, it’s not the fault of Windows PowerShell. Saying that you should only apply workflow when there is no other option available is like saying only to use Windows PowerShell when there is no other option… that’s crazy, right?
Practical and simple workflow applications
When I need to deploy 50 virtual machines with the same configuration and named VM<number> from 1 to 50, there are a couple of ways you can do this.
The first one could be to write 150 lines of code—not something I would like doing.
The second is to write a small loop:
1..50 | ForEach-Object { New-VM -Name VM$_ -MemoryStartupBytes 512MB –NewVHDPath “D:\Hyper-V\Virtual Hard Disks\VM$_.vhdx” -NewVHDSizeBytes 15GB }
With a loop, tasks are executed in sequence. This is where workflow can help you to seriously speed things up in an easy way. With workflow, you are able to use the -Parallel parameter with the ForEach cmdlet that allows for 5 parallel threads at a time. So, if your hardware can handle the load, then creating the virtual machines can become about 5 times faster!
The third way of doing this by using workflow:
workflow New-VirtualMachines
{
$VMs = 1..50
foreach -parallel ($VM in $VMs)
{
New-VM -Name VM$VM -MemoryStartupBytes 512MB –NewVHDPath “D:\Hyper-V\Virtual Hard Disks\VM$VM.vhdx” -NewVHDSizeBytes 15GB
}
}
Execution time of the first script was about 9 minutes in my environment; execution time of the workflow script was about 2 minutes! But, for all of you that are thinking this is rather static, workflows accept parameters just like functions do. The following workflow accepts both the virtual macine name, number of to create virtual machines, and the VHD size as input by parameter:
workflow New-VirtualMachines
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,Position=0)]$VMName,
[Parameter(Mandatory=$true,Position=1)]$VMCount,
[Parameter(Mandatory=$true,Position=2)]$VHDSize
)
$VMs = 1..$VMCount
foreach -parallel ($VM in $VMs)
{
New-VM -Name $VMName$VM -MemoryStartupBytes 512MB -NewVHDPath “D:\Hyper-V\Virtual Hard Disks\$VMName$VM.vhdx” -NewVHDSizeBytes $VHDSize
}
}
You can simply call this workflow the same way you would a function:
New-VirtualMachines -VMName VM -VMCount 50 -VHDSize 15GB
Even at a basic level, it’s easy, fast, readable, and reusable!
And then there is the second most common use case I see for workflow: Executing tasks in sequence. Some time ago, I wrote a maintenance script for one of my customers and all it had to do was accomplish a few tasks:
Number |
Wait for number |
Target |
Task |
1 |
|
WEB01 |
Stop MyApp service |
2 |
1 |
WEB01 |
Stop MyAppQueue service |
3 |
1,2 |
FS01 |
Remove MyAppQueue.xml |
4 |
1,2 |
FS02 |
Remove MyAppQueue.xml |
5 |
4,5 |
WEB01 |
Start MyAppQueue service |
6 |
5 |
WEB01 |
Start MyApp Service |
Back then I didn’t have workflow, so there was quite a lot of code. Now, with workflow, it looks like this:
Workflow Refresh-MyApp
{
sequence
{
Invoke-Command -ComputerName WEB1 {Stop-Service -Name MyApp -Force}
Invoke-Command -ComputerName WEB1 {Stop-Service -name MyAppQueue
-Force}
parallel
{
Invoke-Command -ComputerName FS1
{
Remove-Item -Path “D:\MyApp\Files\MyAppQueue.xml” –Force
}
Invoke-Command -ComputerName FS2
{
Remove-Item -Path “D:\MyApp\Files\MyAppQueue.xml” –Force
}
}
Invoke-Command -ComputerName WEB1 {Start-Service -Name MyAppQueue}
Invoke-Command -ComputerName WEB1 {Start-Service -Name MyApp}
}
}
Note I’m using Invoke-Command here since I find it to be easy, readable, and it fits my needs in that specific situation. I prefer to do it this way above the way workflow offers natively, but I won’t cover the native way in this post.
As you can see, some of the tasks are executed in sequence and will wait for each other. But since the deletion of the files are both depended on the same previous steps and are not depended on one another you can execute these in parallel.
Function vs. workflow
What’s the difference? No, wait…that’s the wrong question because they are two separate things. What’s the same? Yes, that’s the one!
When you know how a function is written, you already know mostly how a workflow is written. It uses the same syntax and accepts parameters the same way as a function does. The Windows PowerShell product group is nothing if not consistent.
Compare the virtual machine deployment workflow a few lines earlier with this VM Deployment function:
function New-VirtualMachines
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,Position=0)]$VMName,
[Parameter(Mandatory=$true,Position=1)]$VMCount,
[Parameter(Mandatory=$true,Position=2)]$VHDSize
)
$VMs = 1..$VMCount
foreach ($VM in $VMs)
{
New-VM -Name $VMName$VM -MemoryStartupBytes 512MB -NewVHDPath “D:\Hyper-V\Virtual Hard Disks\$VMName$VM.vhdx” -NewVHDSizeBytes $VHDSize
}
}
As you may have noticed, there are only two:
- Replace “workflow” with “function” in the 1st line.
- Remove -Parallel from the 10th line. Functions don’t know -Parallel, only workflow does.
Go crazy with the basics
A function can call a workflow, and a workflow can call a function. But as a function can call a function, a workflow can also call a workflow—still following? This is where it can suddenly become overcomplicated and where workflow allows for check pointing.
Also, remember your resources. When you create 5 VHDX files at the same time by using ForEach -Parallel, then suddenly your environment can become sloooooow. So, think before you script. And, if you truly want to overcomplicate things, here’s something you’re gonna love. Within a sequence block, you can have a parallel block. And, within that parallel block you can have a sequence block, and so on and so forth.
Just to give you an idea, I’ve taken the Refresh-MyApp workflow I used earlier in this script and added a few things to it—just to give you an idea of how easy it is to overcomplicate things:
Workflow Refresh-MyApp
{
sequence {
Invoke-Command -ComputerName WEB1 {Stop-Process -Name MyApp -Force}
Invoke-Command -ComputerName WEB1 {Stop-Process -Name MyAppQueue -Force}
parallel {
Invoke-Command -ComputerName FS1 {Remove-Item -Path “D:\MyApp\Files\MyAppQueue.xml” –Force}
Invoke-Command -ComputerName FS2 {Remove-Item –Path “D:\MyApp\Files\MyAppQueue.xml” –Force}
sequence {
parallel {
Invoke-Command -ComputerName FS1 {Stop-Process -Name MyAppFSQueue}
Invoke-Command -ComputerName FS2 {Stop-Process -Name MyAppFSQueue}
}
Invoke-Command -ComputerName FS1 {Remove-Item –Path “D:\MyApp\Log\Queue.log” –Force}
Invoke-Command -ComputerName FS2 {Remove-Item –Path “D:\MyApp\Log\Queue.log” –Force}
parallel {
Invoke-Command -ComputerName SQL1 {Stop-Process -Name mssqlserver -Force}
Invoke-Command -ComputerName SQL2 {Stop-Process -Name mssqlserver -Force}
Invoke-Command -ComputerName SQL3 {Stop-Process -Name mssqlserver -Force}
}
parallel {
Invoke-Command -ComputerName SQL1 {Start-Process -Name mssqlserver -Force}
Invoke-Command -ComputerName SQL2 {Start-Process -Name mssqlserver -Force}
Invoke-Command -ComputerName SQL3 {Start-Process -Name mssqlserver -Force}
}
sequence {
Invoke-Command -ComputerName FS1 {Start-Process -Name MyAppFSQueue}
Invoke-Command -ComputerName FS2 {Start-Process -Name MyAppFSQueue}
}
}
}
Invoke-Command -ComputerName WEB1 {Start-Process -Name MyAppQueue}
Invoke-Command -ComputerName WEB1 {Start-Process -Name MyApp}
}
Send-MailMessage -From “MyApp@domain.com” -To “servicedesk@domain.com” -Subject “MyApp maintenance completed” -Body “The MyApp maintenance script has run.”
}
Here’s a table that explains the inner relationships of the script.
Number |
Wait for number |
Target |
Task |
1 |
|
WEB1 |
Stop process MyApp |
2 |
|
WEB1 |
Stop process MyAppQueue |
3 |
1-2 |
FS1 |
Remove MyAppQueue.xml |
4 |
1-2 |
FS2 |
Remove MyAppQueue.xml |
5 |
1-4 |
FS1 |
Stop process MyAppFSQueue |
6 |
1-4 |
FS2 |
Stop process MyAppFSQueue |
7 |
1-6 |
FS1 |
Remove Queue.log |
8 |
1-7 |
FS2 |
Remove Queue.log |
9 |
1-8 |
SQL1 |
Stop process mssqlserver |
10 |
1-8 |
SQL2 |
Stop process mssqlserver |
11 |
1-8 |
SQL3 |
Stop process mssqlserver |
12 |
1-11 |
SQL1 |
Start process mssqlserver |
13 |
1-11 |
SQL2 |
Start process mssqlserver |
14 |
1-11 |
SQL3 |
Start process mssqlserver |
15 |
1-14 |
FS1 |
Start process MyAppFSQueue |
16 |
1-15 |
FS2 |
Start process MyAppFSQueue |
17 |
1-16 |
WEB1 |
Start process MyAppQueue |
18 |
1-17 |
WEB2 |
Start process MyApp |
19 |
1-18 |
– |
Send email |
The goal of this post
My only goal with this post is to show you just how easy workflow can be. I hope that you can see the power of workflow—even at a basic level. The similarities with functions make it easy to learn and use and its power for executing tasks in parallel. It is, well, unparalleled. Maybe it can help you with your scripting challenges endeavors—I think it can. If you’re new to workflow, start with the basics as shown in this post and go from there.
Happy scripting!
~Jeff
Thank you, Jeff—excellent job.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
foreach -parallel uses processes, not threads.