Skip to content

Run PowerShell Script Activity

Nilesh Ghodekar edited this page Feb 26, 2018 · 29 revisions

Overview

The Run PowerShell Script activity allows you to run a PowerShell 2.0 script and optionally return the script output to the executing workflow for consumption in the same or subsequent activities.

Activity UI

Run PowerShell Script - Activity UI

Activity Display Name

Optional. Name of the activity to be displayed on the MIM / FIM workflow designer.

Advanced Features

Activity Execution Condition

Optional. The condition which must be satisfied for execution of this activity's core task (i.e. run the PowerShell script). This can be any WAL function expression resolving to a boolean value. See Activity Execution Condition wiki for more information.

PowerShell Script User

Optional.The username of the user account to use while constructing a PowerShell Credential object to pass the script. The credential object is passed as a $Credential session variable to the script. The username must be in the Domain\UserName format if "Impersonate PowerShell User" setting is selected.

PowerShell Script User Password

Required when "PowerShell Script User" is specified. The password of the user account to use while constructing a PowerShell Credential objects to pass the script. The password value specified is an encrypted value using a certificate, DPAPI or RSA Machine Keys. Using a self-signed certificate is recommended due to ease of deployment. Please see EncryptedData.ps1 in the "SolutionOutput" folder that demonstrates how the password can be encrypted in the RunPowerShellScript Activity.

Impersonate PowerShell User

Optional. If this setting is selected, the PowerShell script is executed in the context of the specified RunAs user account. This option should be avoided, if the cmdlets used in the script support a Credential parameter.

By default the script runs under the context of FIMService service account. Impersonate PowerShell User option should be used as a last resort. If impersonation cannot be avoided, to configure the permissions of the impersonated user, follow the section Additional Configuration for Impersonation of "PowerShell Connector for FIM 2010 R2" Technical Reference.

This feature should not be mistaken as the ability to impersonate requestor in during Workflow execution. It is simply not possible to impersonate requestor during workflow execution to access external systems. Only the specified PowerShell Script User could be impersonated.

Load user profile

Optional. If this setting is selected, the user profile of the RunAs user is loaded during impersonation.

Log on type

Optional. Log on type to use during impersonation. For more information, refer to the dwLogonType documentation. The default value is "Log on as a batch job".

Script Location

Required. Specifies how the activity will locate the script for execution. The available options are:

  • Include in the Workflow Definition
  • Read from File
  • Resolve from Lookup
Script

Required when "Include in the Workflow Definition" option is selected for Script Location. In this case, the PowerShell script is stored as part of the workflow XOML.

Script Path

Required when "Read from File" option is selected for Script Location. In this case, the full path of the script located on a network share accessible by all FIMService instances is specified.

Script Lookup

Required when "Resolve from Lookup" option is selected for Script Location. In this case, a WAL Lookup expression which represents the script to be executed by the activity is specified.

Fail When Script is Missing

Required when "Resolve from Lookup" option is selected for Script Location. Specifies whether or not the activity should generate an error when the script lookup resolves to a null value.

Input Type

Optional. Specifies how the input should be provided to the PowerShell script. The available options are:

  • None
  • Named Parameters
  • Arguments
Named Parameters

Required when "Named Parameters" option is selected for script Input Type. When this option is selected, you can then define a list of parameter names expected by the script and corresponding WAL value expressions to be supplied to the script as the values of those parameters.

Arguments

Required when "Arguments" option is selected for script Input Type. When this option is selected, you can define a list of WAL value expressions to be passed as unnamed arguments to the script. The arguments are passed in the order they are listed.

Script Return Type

Optional. Specifies how the script output should be processed. The available options are:

  • None
  • Single Value
  • Table of Values

The activity can capture a single value or a list of values of same type returned from the PowerShell script and publish it to a lookup. This is specified when "Single Value" is selected for "Script Return Type". Alternatively, the PowerShell script may return a Hashtable of key-value pairs which can be published to the workflow data dictionary WorkflowData using corresponding key names. This is specified when "Table of Values" is selected for "Script Return Type".

It should be noted that, in contrast to a "return statement" in a C# function, the return value from a PowerShell script / function is not necessarily what is returned by a return statement. The script / function output consists of everything that isn't captured. Hence output from any command or function that is not the intended return value from the script should be ignored (voided) or thrown away (Out-Nulled) so that it does not get piped to the output stream that is what is "returned" by the script.

Return Value Lookup

Required when "Single Value" option is selected for Script Return Type. When this option is selected, the script return value is published to the specified WAL Lookup expression.

Implementation Guidance

The RunPowerShellScript activity is a "catch_all" activity in WAL as anything can be scripted and executed when other native WAL activities fall short. But remember, with great power comes great responsibility :). Due to the obvious performance implications when a large / remote PowerShell module is loaded and the possibility of failures due to session throttling implemented by the remote systems such as Exchange, Lync/Skype as well as memory leaks that may be present in the PowerShell scripts and get accumulated over each run of the workflow, use of this activity to invoke external / remote systems should be avoided in production environments and particularly so in large scale, high-transaction implementations. Instead you should look at exploiting other native WAL activities or take a Management Agent based approach as much as possible. If you think you can avoid the use of RunPowerShellScript activity if a feature or function were added to WAL, please submit the feedback using New Issue button. In particular, do report any use case that needs use of FIMAutomation cmdlets because native WAL activities cannot fulfil the need.

Workflow Thread pool is a precious system resource. The default configuration for maxSimultaneousAuthorizationAndActionWorkflows is ~4 (yes, four!) workflow threads per CPU core. So optimise your scripts to the fullest possible extent so that they complete as quickly as possible. Load only those commands that you need from a remote module. Never use any Sleep statements in your script. Instead use Add Delay activity if there is such a need. Every second saved will count in a production environment!

Note: PowerShell scripts that do not load large external / remote modules and only use local .NET libraries will typically not have any noticeable impact on the system performance when they are done carefully. In such cases, using a custom .NET activity may not provide much value for money. When in doubt, "test" your theory!!

Prior to build 2.15.305.0, the RunPowerShellScript activity treated any error in the script as fatal errors and aborted the workflow. As of build 2.15.305.0, the RunPowerShellScript activity aborts the workflow execution only when an unhandled or explicit exception is thrown from the PowerShell script. All other errors are simply logged in the event log. However, depending on the logic encoded in the PowerShell script, a non-terminating error may be equally detrimental to your business objective. It is now your responsibility to check for any errors (the $Error variable) and throw an exception for errors that are non-terminating but logically fatal to abort the workflow.

The RunPowerShellScript activity passes the activity execution context as the session variables named AECWorkflowInstanceId, AECRequestId, AECActorId, AECTargetId and AECWorkflowDefinitionId. It also sets ProgressPreference, DebugPreference and VerbosePreference as per trace levels in the app.config file of FIMService. It is recommended that you instrument your scripts using this information in conjunction with standard Write-* cmdlets such as a Write-Debug Cmdlet so that the workflow can be traced end-to-end. This is demonstrated in the sample script RunPSLoggingSample.ps1 that gets copied to the "SolutionOutput" folder.

Without the use of AEC* variables, the events will still get logged with standard Write-* statements at appropriate trace levels, but they'll not have the request / workflow details and hence will be hard to correlate in a busy production environment.

All MIM/FIM workflows run in a .NET Framework 3.5 runtime. This is a product limitation. This .NET runtime environment cannot execute scripts and cmdlets that need PowerShell 3.0 or above runtime. If there is a need to execute a script containing PowerShell 3.0+ cmdlets (e.g. ActiveDirectory module on Windows Server 2012), they can be made to run in a separate process to avoid the product limitation. e.g. using PowerShell Remoting or launching a new "powershell.exe" session using Start-Process cmdlet. Examples of these two techniques are given below:

<#
This script uses PowerShell.exe to execute ActiveDirectory PowerShell 3.0 cmdlets
#>

param
(
	[parameter(mandatory = $true)] $AccountName
)

function InvokeImmediateTermination
{
    $stdOutFile = Join-Path $env:TEMP -ChildPath "StdOut_$AECRequestId.log"
    $stdErrFile = Join-Path $env:TEMP -ChildPath "StdErr_$AECRequestId.log"

    $command = @"
    & {
        if (!(Get-Module -Name "ActiveDirectory"))
        {
            Import-Module ActiveDirectory
        }

        Set-ADUser -Identity '$AccountName' -Enabled:`$false

        if (`$Error.Count -eq 0)
        {
            "!!Success!!"
        }
        else
        {
			# Can't easily use stdErrFile as it comes as CLIXML and includes Warning/Verbose/Debug streams as well. So we'll simply use stdOutFile
            "!!Error!!"
            `$Error
        }
    }
"@

    Write-Debug $command

    $commandBytes = [System.Text.Encoding]::Unicode.GetBytes($command)
    $encodedCommand = [Convert]::ToBase64String($commandBytes)

    Start-Process 'PowerShell.exe' `
        -ArgumentList "-Version 3.0 -NonInteractive -OutputFormat Text -EncodedCommand $encodedCommand" `
        -RedirectStandardOutput $stdOutFile `
        -RedirectStandardError $stdErrFile `
        -Wait

    $statusLine = Get-Content $stdOutFile| Select-Object -Last 2 | ? {
        $_.Contains("!!Success!!") 
    }
        
    if ([string]::IsNullOrEmpty($statusLine))
    {
        throw $((Get-Content $stdOutFile) -join "`r`n") # TODO: Only get the lines after !!Error!! marker
    }
    else
    {
        Write-Debug "Script executed successfully."
    }

    Remove-Item $stdOutFile -Force
    Remove-Item $stdErrFile -Force
}

InvokeImmediateTermination
<#
This script uses Remote PowerShell to execute ActiveDirectory PowerShell 3.0 cmdlets
This script needs Remote PowerShell enabled on each FIMService server.
It also needs FIMService service account permissions to execute remote commands.
This can be configured using
	1. Enable-PSRemoting -Force
	2. Set-PSSessionConfiguration -Name Microsoft.PowerShell -ShowSecurityDescriptorUI
#>

param
(
	[parameter(mandatory = $true)] $AccountName
)

function InvokeImmediateTermination
{
    # Any errors during execution of the script or the script block are bubbled up automatically.
    # Comment out -ComputerName parameter when running interactively
	Invoke-Command -ScriptBlock {
            param($AccountName)

            if (!(Get-Module -Name "ActiveDirectory"))
            {
                Import-Module ActiveDirectory
            }

            Set-ADUser -Identity $AccountName -Enabled:$false
        } -ArgumentList $AccountName -ComputerName localhost
}

InvokeImmediateTermination

Examples

The following Run PowerShell Script activity invokes InvokeImmediateTermination script described earlier that used PowerShell 3.0 cmdlets from ActiveDirectory module installed on the server:

Activity Display Name Execute Immediate Termination
Script Location Include in Workflow Definition
Script {Copy & Paste of one of the previous examples of InvokeImmediateTermination script after adding Extended Logging functions from RunPSLoggingSample.ps1 script}
Input Type Named Parameters
Parameter Value Expression
AccountName [//Target/AccountName]

The following PowerShell script and corresponding Run PowerShell Script activities demonstrate how to return the output from the script to the activity:

<#
The purpose of this script is to demontrate how to return a value
from the script to the PSH activity
 
$DemoIndex = 1? return a Single Value (Activity Script Return Type = Single Value)
$DemoIndex = 2? return an Array (Activity Script Return Type = Single Value)
$DemoIndex = 3? return a Hashtable (Activity Script Return Type = Table of Values)
#>

param
(
	[parameter(mandatory = $true)] $FirstName
	,[parameter(mandatory = $true)] $LastName
	,[parameter(mandatory = $true)] $DemoIndex
)


if ($DemoIndex -eq 1)
{
    return $FirstName + " - " + $LastName
}

if ($DemoIndex -eq 2)
{
    return @($FirstName, $LastName)
}

if ($DemoIndex -eq 3)
{
    return @{ "FirstName" = $FirstName; "LastName" = $LastName }
}

# Still here?

throw "Demo '$DemoIndex' is not implemented."
Activity Display Name Demo #1
Script Location Include in Workflow Definition
Script {Copy & Paste of previous demo script}
Input Type Named Parameters
Parameter Value Expression
FirstName [//Target/FirstName]
LastName [//Target/LastName]
DemoIndex 1
Script Return Type Single Value
Return Value Lookup [//WorkflowData/Demo1]
Activity Display Name Demo #2
Script Location Include in Workflow Definition
Script {Copy & Paste of previous demo script}
Input Type Named Parameters
Parameter Value Expression
FirstName [//Target/FirstName]
LastName [//Target/LastName]
DemoIndex 2
Script Return Type Single Value
Return Value Lookup [//WorkflowData/Demo2]
Activity Display Name Demo #3
Script Location Include in Workflow Definition
Script {Copy & Paste of previous demo script}
Input Type Named Parameters
Parameter Value Expression
FirstName [//Target/FirstName]
LastName [//Target/LastName]
DemoIndex 3
Script Return Type Table of Values
[//WorkflowData/FirstName]
[//WorkflowData/LastName]
Clone this wiki locally