04 Feb 2014

SMA Workflows and Remoting, how to deal with Variable Scoping

At the moment I’m working a lot with SMA (Service Management Automation) in Windows Azure Pack. You you’re unfamiliar with this topic, I’d recommend to read my white paper about SMA.

So what’s the deal with Powershell Workflows in SMA and Remoting? Well first you have to be aware that inside Powershell Workflows, you can’t run any command available to native Powershell Scripts. There are enhancements to workflows, but also limitations.

Let’s take the following non-working example.

Workflow Get-VMReplicaStatus
{
    param(
    [Parameter(mandatory=$true)]
    $VMHost,
    [Parameter(mandatory=$true)]
    $VMName
    )
    Invoke-Command -ComputerName $VMHost -ScriptBlock {Get-VMReplication -VMName $USING:VMName}
}

Ooops..

At line:10 char:5
+ Invoke-Command -ComputerName $VMHost -ScriptBlock {Get-VMReplication -VMName …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cannot call the ‘Invoke-Command’ command. Other commands from this module have been packaged as workflow activities, but this command was specifically excluded

Calling this workflow will throw an error, telling us that Invoke-Command is not supported inside a workflow as a native action. But hey.. how can I do Powershell Remoting then? The answer is called “InlineScripts”. Inline scripts are executed in a separated thread, we could also call it a sandbox. What happens in the sandbox, stays in the sandbox. But sometimes, when a CMDLET is not available natively in a workflow, we can use it as an alternative.

Ok, next try…

Workflow Get-VMReplicaStatus
{
    param(
    [Paramater(mandatory=$true)]
    $VMHost,
    [Paramater(mandatory=$true)]
    $VMName
    )
    InlineScript
    {
       Invoke-Command -ComputerName $USING:VMHost -ScriptBlock {Get-VMReplication -VMName $USING:VMName}
    }
}

Oops again..

Cannot validate argument on parameter ‘VMName’. The argument is null or empty.
Provide an argument that is not null or empty, and then try the command again.

So, one step closer but still an error. The workflow claims that the Argument VMName is empty. But why? Because if we use Inline Scripts, $USING is reserved to pass variables of a higher scope (meaning the workflow body) to an Inline Script. So the variable $VMName never gets passed to the remote command on our VM Host. But hey.. of course we have a solution here as well. Those who dealed with Powershell 2.0 Remoting and passing variables into a remote session will be very well aware of this.

Next try…

Workflow Get-VMReplicaStatus
{
    param(
    [Paramater(mandatory=$true)]
    $VMHost,
    [Paramater(mandatory=$true)]
    $VMName
    )
    InlineScript
    {
        $VMName = $USING:VMName
        Invoke-Command -ComputerName $USING:VMHost -ScriptBlock {param($VMName) Get-VMReplication -VMName $VMName} -ArgumentList $VMName
    }
}

Et voilà! This works like a charm, but why?

First we re-scope the variable VMName, so it is available inside our inline script. The variable then gets passed as an argument to the remote command as it was necessary before Powershell 3.0

How to do it better..

Workflow Get-VMReplicaStatus
{
    param(
    [Paramater(mandatory=$true)]
    $VMHost,
    [Paramater(mandatory=$true)]
    $VMName
    )
    InlineScript
    {
        Get-VMReplication $USING:VMName
    } -PSComputerName $VMHost
}

The last example is a best practice for SMA workflows. Instead of using remote commands inside InlineScripts you should use the -PSComputerName as a parameter for the InlineScript itself.

Hope this helps with the transition to SMA and Powershell Workflows.