-
Notifications
You must be signed in to change notification settings - Fork 36
Run PowerShell Script Activity
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.
Optional. Name of the activity to be displayed on the MIM / FIM workflow designer.
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.
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 or UPN format if "Impersonate PowerShell User" setting is selected. As of build 2.20.0523.0, use of a WAL expression is supported.
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. As of build 2.20.0523.0, use of a WAL expression is supported.
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.
Optional. If this setting is selected, the user profile of the RunAs user is loaded during impersonation.
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".
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
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.
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.
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.
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.
Optional. Specifies how the input should be provided to the PowerShell script. The available options are:
- None
- Named Parameters
- Arguments
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.
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.
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 (void
ed) or thrown away (Out-Null
ed) so that it does not get piped to the output stream that is what is "returned" by the script.
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.
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.
By default the script runs under the context of FIMService service account.
Hence the script will fail if the service account does not have appropriate rights in AD.
!!NOTE!! For brevity, this script is not instrumented. For a production quality script, please
instrument the code as demonstrated in the sample script RunPSLoggingSample.ps1
that is part of the MIMWAL source code.
#>
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
}
}
"@
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))
{
# Communicate error back the parent workflow to abort the workflow
throw $((Get-Content $stdOutFile) -join "`r`n")
}
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.
By default, on Windows Server 2012, Remote PowerShellis is enabled.
It also needs FIMService service account permissions to execute remote commands.
These two pre-reqs can be configured using:
1. Enable-PSRemoting -Force
2. Set-PSSessionConfiguration -Name Microsoft.PowerShell -ShowSecurityDescriptorUI
By default the script runs under the context of FIMService service account.
Hence the script will fail if the service account does not have appropriate rights in AD.
!!NOTE!! For brevity, this script is not instrumented. For a production quality script, please
instrument the code as demonstrated in the sample script RunPSLoggingSample.ps1
that is part of the MIMWAL source code.
#>
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 the interactively in PowerShell ISE.
Invoke-Command -ScriptBlock {
param($AccountName)
if (!(Get-Module -Name "ActiveDirectory"))
{
Import-Module ActiveDirectory
}
Set-ADUser -Identity $AccountName -Enabled:$false
} -ArgumentList $AccountName -ComputerName localhost
if ($Error)
{
# Communicate error back the parent workflow to abort the workflow
throw $Error
}
}
InvokeImmediateTermination
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)
!!NOTE!! For brevity, this script is not instrumented. For a production quality script, please instrument
the code as demonstrated in the sample script RunPSLoggingSample.ps1 that is part of the MIMWAL source code.
#>
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] |
- MIMWAL Site - http://aka.ms/MIMWAL
- MIMWAL Releases - http://aka.ms/MIMWAL/Releases
- MIMWAL Documentation Wiki - http://aka.ms/MIMWAL/Wiki
- MIMWAL FAQ - http://aka.ms/mimwal/faq
- MIMWAL GitHub Code Repo - http://aka.ms/MIMWAL/Repo
- MIMWAL TechNet Q&A Forum (now read-only) - http://aka.ms/MIMWAL/Forum