Summary: Microsoft Windows PowerShell MVP Richard Siddaway talks about restrictions on Windows PowerShell workflows.
Microsoft Scripting Guy, Ed Wilson, is here. Today, we have the second in a series of guest blog posts written by Windows PowerShell MVP Richard Siddaway dealing with Windows PowerShell workflow.
Note The first post PowerShell Workflows: The Basicsintroduced the basic concepts of Windows PowerShell workflow. You should read that post prior to reading today’s article.
Richard has written a number of guest Hey, Scripting Guy! Blog posts, and has also written two books on Windows PowerShell. His most recent book, PowerShell in Depth, is co-written with fellow MVPs Don Jones and Jeffrey Hicks.
Now, take it away Richard …
In the last post, I introduced the basics of Windows PowerShell workflows. I mentioned a number of restrictions around workflows. It’s time to look at those restrictions and discover how they can be overcome.
Workflow restrictions tend to fall into the following groups:
- Unsupported Windows PowerShell activities
- Scope of variables
- Objects are de-serialized
- Cmdlets that haven’t had workflow activities created
Unsupported Windows PowerShell activities
A number of Windows PowerShell keywords and techniques aren’t supported in workflows. The following table provides a summary.
Unsupported PowerShell techniques | ||
Begin,Process,End | Break,Continue | Subexpressions |
Multiple assignment | Modify loop variable | Dynamic parameters |
Set properties | Dot sourcing | Advanced parameter validation |
Positional parameters | Switch statement | Trap statement |
Inline help | Setting drive qualified variables | Method invocation on objects |
Single #requires |
|
|
One other issue I’ve see regards using custom Windows PowerShell drives—workflows may fail if they are present.
Some of these restrictions—such using the trap statement—aren’t too much of an issue. In this case, you should be using a try-catch block. Other restrictions will be more awkward such as not being able to use a switch statement. If you try what looks like a perfectly valid switch statement (try replacing “workflow” with “function“ and see what happens).
workflow testswitch {
param (
[string]$os
)
switch ($os) {
"XP" {"Time to upgrade"}
"Windows7" {"OK - but not the lastest"}
"Windows 8" {"Latest and greatest"}
}
}
You will get a sea of red error messages:
At line:5 char:2
+ switch ($os) {
+ ~~~~~~~~~~~~~~
Case-insensitive switch statements are not supported in a Windows PowerShell workflow. Supply the -CaseSensitive flag, and ensure that case clauses are written appropriately. To write a case-insensitive case statement, first convert the input to either uppercase or lowercase, and update the case clauses to match.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
ception
+ FullyQualifiedErrorId : SwitchCaseSensitive
So you might think you can do this:
workflow testswitchi {
param (
[string]$os
)
switch -CaseSensitive ($os.ToUpper()) {
"XP" {"Time to upgrade"}
"WINDOWS7" {"OK - but not the lastest"}
"WINDOWS 8" {"Latest and greatest"}
}
}
Nope. More red.
At line:5 char:25
+ switch -CaseSensitive ($os.ToUpper()) {
+ ~~~~~~~~~~~~~
Method invocation is not supported in a Windows PowerShell Workflow. To use .NET Framework scripting, place your commands in an inline script:
InlineScript {
<commands> }.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
ception
+ FullyQualifiedErrorId : MethodInvocationNotSupported
So time to trot out the workflow “get out of jail card” – InlineScript
workflow testswitch {
param (
[string]$os
)
InlineScript {
switch ($using:os) {
"XP" {"Time to upgrade"}
"Windows7" {"OK - but not the lastest"}
"Windows 8" {"Latest and greatest"}
}
}
}
This works as you would expect.
Other restrictions such as the BEGIN, PROCESS, and END blocks that you can use in functions impact features that don’t really fit with the workflow concept. The other restriction that can have a big impact is that you can’t use inline comment-based Help. You have to use XML-based Help files. There are a number of ways to generate these files—one of the easiest is through InfoPath as described by James O’Neill.
Not having parameter validation available could be a big problem if your workflows are heavily parameterized. This restriction doesn’t appear to have a workaround unless you create the validation code inside the workflow.
You will have noticed the $using:computer I used last time and $using:os in the code demonstrating the switch statement. This syntax is part of the solution to overcoming the second restriction that is the scope of variables.
Scope of variables
In workflows, the following restrictions on the use of variables occur:
- Variables defined in a higher scope are visible to lower workflow scopes but not Inlinescript scopes
- CANNOT have a variable in a lower scope with same name as a variable in higher scope
- if define or redefine variable can use it in that scope without problems
- there is no $global scope
- use “$using” in InlineScript blocks to access variables defined in a higher scope
- modification of a variable from a higher scope in an InlineScript requires the use of a temporary variable
- use “$workflow” to modify variable defined in a higher scope
- CANNOT use subexpressions
That probably looks very confusing but this demonstration should help clear things up. I recommend that you run this and work through the output.
workflow demo-scope {
# This is a workflow top-level variable
$a = 22
"Initial value of A is: $a"
# Access $a from Inlinescript (bringing a workflow variable to the Windows PowerShell session) using $using
inlinescript {"PowerShell variable A is: $a"}
inlinescript {"Workflow variable A is: $using:a"}
## changing a variable value
$a = InlineScript {$b = $Using:a+5; $b}
"Workflow variable A after InlineScript change is: $a"
parallel {
sequence {
# Reading a top-level variable (no $workflow: needed)
"Value of A inside parallel is: $a"
# Updating a top-level variable with $workflow:<variable name>
$workflow:a = 3
}
}
"Updated value of A is: $a"
}
demo-scope
You should see something like this as your output:
Initial value of A is: 22
PowerShell variable A is:
Workflow variable A is: 22
Workflow variable A after InlineScript change is: 27
Value of A inside parallel is: 27
Updated value of A is: 3
The workflow starts by defining a variable, $a = 22, and then displaying its value. In an InlineScript, if you try to access a variable defined in a higher scope, you get nothing as shown in the second line of the output. You have to use $using:a to access the variable. If you want to change that variable you have to use a second variable and return it to the original variable:
$a = InlineScript {$b = $Using:a+5; $b}
The output shows the variable now has a value of 27.
Moving into the parallel block you can read the variable without any scope issues. If you need to change the variable’s value you access via the $workflow scope.
Bottom line with variables: keep it simple and be careful.
Objects are de-serialized
The third restriction is that objects in workflows are de-serialized. This is similar to the position in Windows PowerShell remoting (which is not surprising as workflows use the same transport mechanism). As an aside object de-serialization is becoming much more prevalent in Windows PowerShell. It appears in:
- Windows PowerShell remoting
- Remote access via CIM sessions
- WSMAN cmdlets
- Workflows
Perhaps it’s time for the normal approach to become one of working in this manner.
A de-serialized object gives you the properties of the object BUT not the methods. In other words, it is inert. Lots of Windows PowerShell code does something like these examples:
$string = "abcde"
$string.ToUpper()
$os = Get-WmiObject -Class Win32_OperatingSystem
$os.ConvertToDateTime($os.LastBootUpTime)
This approach isn’t going to work.
workflow test2 {
$string = "abcde"
$string.ToUpper()
$os = Get-WmiObject -Class Win32_OperatingSystem
$os.ConvertToDateTime($os.LastBootUpTime)
}
Will throw an error about method invocation not being supported. You can however do this:
workflow test2 {
inlinescript {
$string = "abcde"
$string.ToUpper()
$os = Get-WmiObject -Class Win32_OperatingSystem
$os.ConvertToDateTime($os.LastBootUpTime)
}
}
Don’t try and access variables from a higher scope that represent objects and then invoke methods—it won’t work.
Bottom line on de-serialized objects—if you need the methods of an object, access it through an inlinescript. Remember that you can nest InlineScript blocks in parallel blocks!
Cmdlets that haven’t had workflow activities created
The final restriction is around the cmdlets that haven’t been turned into workflows. Remember last time there was a problem with using the Format* cmdlets in workflows. The way around it is to use the InlineScript keyword like this:
workflow foreachpitest {
param([string[]]$computers)
foreach –parallel ($computer in $computers){
InlineScript {
Get-WmiObject –Class Win32_OperatingSystem –ComputerName $using:computer |
Format-List
}
}
}
Other cmdlets that haven’t been turned into workflow activities are shown in the following table. You can use them in InlineScript blocks.
Unsupported cmdlet (group) | Reason |
*Alias, *FormatData, *History, *Location, *PSDrive, *Transcript, *TypeDate, *Variable, Connect/Disconnect-Wsman | Only change Windows PowerShell session so not needed in workflow |
Show-Command, Show-ControlPanelItem, Get-Credential, Show-EventLog, Out-Gridview, Read-Host, Debug-Process | Workflows don’t support interactive cmdlets |
*BreakPoint, Get-PSCallStack, Set-PSDebug | Workflows don’t support script debugging |
*Transaction | Workflows don’t support transactions |
Format* | No formatting support |
*PSsession, *PSsessionoption | Remoting controlled by workflow |
Export-Console,Get-ControlPanelItem, Out-Default, Out-Null, Write-Host, Export-ModuleMember, Add-PSSnapin, Get-PSSnapin, Remove-PSSnapin, Trace-Command |
|
There are a number of cmdlets that are local execution only in workflows.
Add-Member | Compare-Object | ConvertFrom-Csv | ConvertFrom-Json |
ConvertFrom-StringData | Convert-Path | ConvertTo-Csv | ConvertTo-Html |
ConvertTo-Json | ConvertTo-Xml | ForEach-Object | Get-Host |
Get-Member | Get-Random | Get-Unique | Group-Object |
Measure-Command | Measure-Object | New-PSSessionOption | New-PSTransportOption |
New-TimeSpan | Out-Default | Out-Host | Out-Null |
Out-String | Select-Object | Sort-Object | Update-List |
Where-Object | Write-Debug | Write-Error | Write-Host |
Write-Output | Write-Progress | Write-Verbose |
|
If you want to use them remotely—you’ve guessed it—use an InlineScript.
You can find out more about workflow restrictions in the Help files:
about_ActivityCommonParameters
about_Checkpoint-Workflow
about_Foreach-Parallel
about_InlineScript
about_Parallel
about_Sequence
about_Suspend-Workflow
about_WorkflowCommonParameters
about_Workflows
You may not see them when you first load Windows PowerShell. Import the PSWorkflow module and they will become visible.
I’ve tried to cover the restrictions you will face when using workflows. As I said in the first article in the series, PowerShell Workflows: The Basics, if you don’t understand the restrictions they will trip you up.
Next time, we’ll look at nesting workflows and using Windows PowerShell functions with workflows.
~Richard
Thank you, Richard, for another great article. Join me tomorrow for more cool Windows PowerShell stuff.
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