Skip to main content

PowerShell 3.0 Workflow Secret Sauce

I was poking around in workflows and stumbled across this when I looked at a workflows definition and saw a large PowerShell scriptblock. I'm sure some of you will find this as interesting as I did. Actually, this explains a lot about workflows and how and why they behave the way they do. Enjoy!

Note: fu was the actual workflow name I used just to look at an empty defined workflow, e.g. workflow fu {}; then use this to see the properties of the workflow: gci function:fu | fl *

Below is what is in the ScriptBlock property.
  1. [CmdletBinding()]  
  2.                 param (  
  3.                       
  4.                     [hashtable[]] $PSParameterCollection,  
  5.                     [string[]] $PSComputerName,  
  6.                     [ValidateNotNullOrEmpty()] $PSCredential,  
  7.                     [uint32] $PSConnectionRetryCount,  
  8.                     [uint32] $PSConnectionRetryIntervalSec,  
  9.                     [ValidateRange(1, 2147483)][uint32] $PSRunningTimeoutSec,  
  10.                     [ValidateRange(1, 2147483)][uint32] $PSElapsedTimeoutSec,  
  11.                     [bool] $PSPersist,  
  12.                     [ValidateNotNullOrEmpty()] [System.Management.Automation.Runspaces.AuthenticationMechanism] $PSAuthentication,  
  13.                     [ValidateNotNullOrEmpty()][System.Management.AuthenticationLevel] $PSAuthenticationLevel,  
  14.                     [ValidateNotNullOrEmpty()] [string] $PSApplicationName,  
  15.                     [uint32] $PSPort,  
  16.                     [switch] $PSUseSSL,  
  17.                     [ValidateNotNullOrEmpty()] [string] $PSConfigurationName,  
  18.                     [ValidateNotNullOrEmpty()][string[]] $PSConnectionURI,  
  19.                     [switch] $PSAllowRedirection,  
  20.                     [ValidateNotNullOrEmpty()][System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption,  
  21.                     [ValidateNotNullOrEmpty()] [string] $PSCertificateThumbprint,  
  22.                     [hashtable] $PSPrivateMetadata,  
  23.                     [switch] $AsJob,  
  24.                     [string] $JobName,  
  25.                     [Parameter(ValueFromPipeline=$true)]$InputObject  
  26.                     )  
  27.         begin {  
  28.                 function fu  {  
  29.   
  30.                 [CmdletBinding()]  
  31.                 param (  
  32.                     $PSInputCollection,  
  33.                     [string[]] $PSComputerName,  
  34.                     [ValidateNotNullOrEmpty()] $PSCredential,  
  35.                     [uint32] $PSConnectionRetryCount,  
  36.                     [uint32] $PSConnectionRetryIntervalSec,  
  37.                     [ValidateRange(1, 2147483)][uint32] $PSRunningTimeoutSec,  
  38.                     [ValidateRange(1, 2147483)][uint32] $PSElapsedTimeoutSec,  
  39.                     [bool] $PSPersist,  
  40.                     [ValidateNotNullOrEmpty()] [System.Management.Automation.Runspaces.AuthenticationMechanism] $PSAuthentication,  
  41.                     [ValidateNotNullOrEmpty()][System.Management.AuthenticationLevel] $PSAuthenticationLevel,  
  42.                     [ValidateNotNullOrEmpty()] [string] $PSApplicationName,  
  43.                     [uint32] $PSPort,  
  44.                     [switch] $PSUseSSL,  
  45.                     [ValidateNotNullOrEmpty()] [string] $PSConfigurationName,  
  46.                     [ValidateNotNullOrEmpty()][string[]] $PSConnectionURI,  
  47.                     [switch] $PSAllowRedirection,  
  48.                     [ValidateNotNullOrEmpty()][System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption,  
  49.                     [ValidateNotNullOrEmpty()] [string] $PSCertificateThumbprint,  
  50.                     [hashtable] $PSPrivateMetadata,  
  51.                     [switch] $AsJob,  
  52.                     [string] $JobName,  
  53.                     [Parameter(ValueFromPipeline=$true)]$InputObject  
  54.                     )           $PSBoundParameters  
  55.               }  
  56.   
  57.   
  58.                 $PSInputCollection = New-Object 'System.Collections.Generic.List[PSObject]'  
  59.             }  
  60.   
  61.             process {  
  62.                  if ($PSBoundParameters.ContainsKey('InputObject'))  
  63.                  {  
  64.                      $PSInputCollection.Add($InputObject)  
  65.                  }  
  66.             }  
  67.               
  68.             end {  
  69.   
  70.                   
  71.   
  72.                     $parametersWithDefaults = @()  
  73.                     trap { break }  
  74.                     $parameterCollectionProcessed = $false  
  75.                     $PSParameterCollectionDefaultsMember = $null  
  76.                     if ($PSBoundParameters.ContainsKey('PSParameterCollection'))  
  77.                     {  
  78.   
  79.                         foreach ($pa in $PSBoundParameters.Keys)  
  80.                         {  
  81.                             if ($pa -eq 'JobName' -or $pa -eq 'AsJob' -or $pa -eq 'InputObject' -or $pa -eq 'PSParameterCollection')  
  82.                             {  
  83.                                 continue  
  84.                             }  
  85.                             $msg = [Microsoft.PowerShell.Commands.ImportWorkflowCommand]::InvalidPSParameterCollectionAdditionalErrorMessage;  
  86.                             throw (New-Object System.Management.Automation.ErrorRecord $msg, StartWorkflow.InvalidArgument, InvalidArgument, $PSParameterCollection)  
  87.                         }  
  88.                         $parameterCollectionProcessed = $true  
  89.   
  90.   
  91.                         foreach ($collection in $PSParameterCollection)  
  92.                         {  
  93.                             if ($collection['PSComputerName'] -eq '*' )  
  94.                             {  
  95.                                 if ($PSParameterCollectionDefaultsMember -ne $null)  
  96.                                 {  
  97.                                     $msg = [Microsoft.PowerShell.Commands.ImportWorkflowCommand]::ParameterErrorMessage;  
  98.                                     throw ( New-Object System.Management.Automation.ErrorRecord $msg, StartWorkflow.InvalidArgument, InvalidArgument, $PSParameterCollection)  
  99.                                 }  
  100.                                 $PSParameterCollectionDefaultsMember = $collection;  
  101.                                 foreach($parameter in $parametersWithDefaults)  
  102.                                 {  
  103.                                     if(! $collection.ContainsKey($parameter))  
  104.                                     {  
  105.                                         $collection[$parameter] = (Get-Variable $parameter).Value  
  106.                                     }  
  107.                                 }  
  108.                             }  
  109.                         }  
  110.   
  111.                         $PSParameterCollection = [Microsoft.PowerShell.Commands.ImportWorkflowCommand]::MergeParameterCollection(  
  112.                                         $PSParameterCollection, $PSParameterCollectionDefaultsMember)  
  113.   
  114.   
  115.                         $PSParameterCollection = foreach ( $c in $PSParameterCollection) {  
  116.                             if($c.containskey('AsJob') -or $c.containsKey('JobName') -or $c.containsKey('PSParameterCollection') -or $c.containsKey('InputObject'))  
  117.                             {  
  118.                                     $msg = [Microsoft.PowerShell.Commands.ImportWorkflowCommand]::InvalidPSParameterCollectionEntryErrorMessage;  
  119.                                     throw ( New-Object System.Management.Automation.ErrorRecord $msg, StartWorkflow.InvalidArgument, InvalidArgument, $PSParameterCollection)  
  120.                             }  
  121.                             & "fu" @c  
  122.                         }  
  123.   
  124.   
  125.   
  126.                         if (-not $PSParameterCollectionDefaultsMember)  
  127.                         {  
  128.                             foreach ($collection in $PSParameterCollection)  
  129.                             {  
  130.                                 foreach($parameter in $parametersWithDefaults)  
  131.                                 {  
  132.                                     if(! $collection.ContainsKey($parameter))  
  133.                                     {  
  134.                                         $collection[$parameter] = (Get-Variable $parameter).Value  
  135.                                     }  
  136.                                 }  
  137.                             }  
  138.                         }  
  139.                     }  
  140.                     else  
  141.                     {  
  142.   
  143.                         foreach($parameter in $parametersWithDefaults)  
  144.                         {  
  145.                             if(! $PSBoundParameters.ContainsKey($parameter))  
  146.                             {  
  147.                                 $PSBoundParameters[$parameter] = (Get-Variable $parameter).Value  
  148.                             }  
  149.                         }  
  150.   
  151.                         $PSBoundParameters = & "fu" @PSBoundParameters  
  152.                     }  
  153.                       
  154.                 if ($PSBoundParameters['PSCredential'])  
  155.                 {  
  156.                     $CredentialTransform = New-Object System.Management.Automation.CredentialAttribute  
  157.                     $LocalCredential = $CredentialTransform.Transform($ExecutionContext, $PSCredential)  
  158.                     $PSBoundParameters['PSCredential'] = [system.management.automation.pscredential]$LocalCredential  
  159.   
  160.                     if (!$PSBoundParameters['PSComputerName'] -and !$PSBoundParameters['PSConnectionURI'])  
  161.                     {  
  162.                         $PSBoundParameters['PSComputerName'] =  New-Object string @(,'localhost')  
  163.                     }  
  164.                 }  
  165.   
  166.   
  167.                 $jobName = ''  
  168.                 if ($PSBoundParameters['JobName'])  
  169.                 {  
  170.                     $jobName = $PSBoundParameters['JobName']  
  171.                     [void] $PSBoundParameters.Remove('JobName');  
  172.                 }  
  173.   
  174.   
  175.                 [hashtable[]] $jobSpecifications = @()  
  176.                 $parametersCollection = $null;  
  177.                 if ($PSBoundParameters['PSParameterCollection'])  
  178.                 {  
  179.                     $parameterSCollection = $PSBoundParameters['PSParameterCollection']  
  180.                     [void] $PSBoundParameters.Remove('PSParameterCollection');  
  181.                 }  
  182.   
  183.   
  184.                 if ($PSBoundParameters['InputObject'])  
  185.                 {  
  186.                     [void] $PSBoundParameters.Remove('InputObject');  
  187.                 }  
  188.   
  189.   
  190.                 $null = $PSBoundParameters.Remove('AsJob')  
  191.                 $null = $psBoundParameters.Remove('WarningVariable')  
  192.                 $null = $psBoundParameters.Remove('ErrorVariable')  
  193.                 $null = $psBoundParameters.Remove('OutVariable')  
  194.                 $null = $psBoundParameters.Remove('OutBuffer')  
  195.                   
  196.   
  197.   
  198.                 $psBoundParameters['PSWorkflowRoot'] = ''  
  199.   
  200.                 try  
  201.                 {  
  202.                      $psBoundParameters['PSSenderInfo'] = $PSSenderInfo  
  203.                 }  
  204.                 catch  
  205.                 {  
  206.   
  207.                 }  
  208.   
  209.                 $psBoundParameters['PSCurrentDirectory'] = $pwd.Path  
  210.   
  211.   
  212.   
  213.                 $myCommand = $MyInvocation.MyCommand  
  214.                 $myModule = $myCommand.Module  
  215.                 if ($myModule)  
  216.                 {  
  217.   
  218.   
  219.                     [Hashtable] $privateData = $myModule.PrivateData -as [Hashtable]  
  220.                           
  221.                     if ($privateData)  
  222.                     {  
  223.   
  224.   
  225.                         [hashtable] $authorMetadata = $privateData[$myCommand.Name]  
  226.                         if ($authorMetadata)  
  227.                         {  
  228.   
  229.   
  230.                             $authorMetadata = @{} + $authorMetadata   
  231.                             if ($psBoundParameters['PSPrivateMetadata'])  
  232.                             {  
  233.   
  234.                                 foreach ($pair in $psPrivateMetadata.GetEnumerator())  
  235.                                 {  
  236.                                     $authorMetadata[$pair.Key] = $pair.Value  
  237.                                 }  
  238.                             }  
  239.   
  240.                             $psBoundParameters['PSPrivateMetadata'] = $authorMetadata  
  241.                         }  
  242.                     }  
  243.                 }  
  244.   
  245.   
  246.   
  247.                 if (! $PSBoundParameters['PSInputCollection'])  
  248.                 {  
  249.                     $PSBoundParameters['PSInputCollection'] = $PSInputCollection  
  250.                 }  
  251.   
  252.                 $errorAction = "Continue"  
  253.                 if ($PSBoundParameters['ErrorAction'] -eq "SilentlyContinue")  
  254.                 {  
  255.                     $errorAction = "SilentlyContinue"  
  256.                 }  
  257.   
  258.                 if($PSBoundParameters['ErrorAction'] -eq "Ignore")  
  259.                 {  
  260.                     $PSBoundParameters['ErrorAction'] = "SilentlyContinue"  
  261.                     $errorAction = "SilentlyContinue"  
  262.                 }  
  263.   
  264.                 if ($PSBoundParameters['ErrorAction'] -eq "Inquire")  
  265.                 {  
  266.                     $PSBoundParameters['ErrorAction'] = "Continue"  
  267.                     $errorAction = "Continue"  
  268.                 }  
  269.   
  270.                 $warningAction = "Continue"  
  271.                 if ($PSBoundParameters['WarningAction'] -eq "SilentlyContinue" -or $PSBoundParameters['WarningAction'] -eq "Ignore")  
  272.                 {  
  273.                     $warningAction = "SilentlyContinue"  
  274.                 }  
  275.   
  276.                 if ($PSBoundParameters['WarningAction'] -eq "Inquire")  
  277.                 {  
  278.                     $PSBoundParameters['WarningAction'] = "Continue"  
  279.                     $warningAction = "Continue"  
  280.                 }  
  281.   
  282.   
  283.                 $finalParameterCollection = $null  
  284.                 if ($PSParameterCollection -ne $null)  
  285.                 {  
  286.                     $finalParameterCollection = $PSParameterCollection   
  287.                 }  
  288.                 else  
  289.                 {  
  290.                     $finalParameterCollection = $PSBoundParameters  
  291.                 }  
  292.   
  293.                 try  
  294.                 {  
  295.   
  296.                     $job = [Microsoft.PowerShell.Commands.ImportWorkflowCommand]::StartWorkflowApplication(  
  297.                                         $PSCmdlet,  
  298.                                         $jobName,  
  299.                                         'ab0c05c8-c28f-48c7-97ea-915f34f4c0b8',  
  300.                                         $AsJob,  
  301.                                         $parameterCollectionProcessed,  
  302.                                         $finalParameterCollection)  
  303.                 }  
  304.                 catch  
  305.                 {  
  306.   
  307.                     $e = $_.Exception  
  308.   
  309.   
  310.                     if ($e -is [System.Management.Automation.MethodException] -and $e.InnerException)  
  311.                     {  
  312.                         $e = $e.InnerException  
  313.                     }  
  314.   
  315.                     $msg = [Microsoft.PowerShell.Commands.ImportWorkflowCommand]::UnableToStartWorkflowMessageMessage -f `  
  316.                         $MyInvocation.MyCommand.Name, $e.Message  
  317.   
  318.                     $newException = New-Object System.Management.Automation.RuntimeException $msg, $e  
  319.   
  320.                     throw (New-Object System.Management.Automation.ErrorRecord $newException, StartWorkflow.InvalidArgument, InvalidArgument, $finalParameterCollection)  
  321.                 }  
  322.   
  323.                 if (-not $AsJob -and $job -ne $null)  
  324.                 {  
  325.                     try  
  326.                     {  
  327.                         Receive-Job -Job $job -Wait -Verbose -Debug -ErrorAction $errorAction -WarningAction $warningAction  
  328.   
  329.                         $PSCmdlet.InvokeCommand.HasErrors = $job.State -eq 'failed'  
  330.                     }  
  331.                     finally  
  332.                     {  
  333.                         if($job.State -ne "Suspended" -and $job.State -ne "Stopped")  
  334.                         {  
  335.                             Remove-Job -Job $job -Force  
  336.                         }  
  337.                         else  
  338.                         {  
  339.                             $job  
  340.                         }  
  341.                     }  
  342.                 }  
  343.                 else  
  344.                 {  
  345.                     $job  
  346.                 }  
  347.             }  

Comments

  1. Great find! This makes understanding all the crazy restrictions so much easier to understand.

    ReplyDelete

Post a Comment

Popular posts from this blog

PowerShell SupportsShouldProcess Worst & Best Practices

This has been a very big discussion within the Scripting Games 2013 community and I want to add my two cents in an official blog post.

I've left several people comments on how they might be misunderstanding how SupportsShouldProcess works, but I also realize, everyone of these individuals has given me more insight into its use and perhaps, how it should best be utilized.

For those of you that don't know, SupportsShouldProcess is a parameter on the CmdletBinding attribute you can place on your cmdlets that automatically adds the -WhatIf and -Confirm parameters. These will naturally flow into other cmdlets you use that also SupportsShouldProcess, e.g. New-Item, Move-Item.

The major discussion has been around, should you just let the other cmdlets handle the $PSCmdlet.ShouldProcess feature, and if not how should you implement it. ShouldProcess has the following definitions.
OverloadDefinitions�����������������������������������������������������������������������������������������…

FIM 2010 R2 Self-Service Password Reset Auto-Registration

I have been working on our FIM 2010 R2 SP1 lab environment looking for ways to simplify some of the overly complicated scenarios we had to implement to workaround the limitations in FIM 2010. One of those workarounds was the auto-registration of SSPR for new employees. When we onboard a new employee, we want to create a simple SSPR for them to get their first-time password reset.

Prior to the R2 release we were using the http://fim2010client.codeplex.com/ client and PowerShell to complete the registration process on a daily schedule. I was working on converting this to use the R2 registration cmdlets and combined it with PowerShell 3.0 Workflows to get to a solution similar to this.
[CmdletBinding()]  param()  begin {      workflow Invoke-RegisterSSPR {          InlineScript {              Import-Module FIM          }  $workflow = InlineScript { Get-FIMResource -Filter '/WorkflowDefinition[DisplayName = "Password Reset AuthN Workflow for New Employees"]' }  $members  …

PowerShell Error Handling Behavior Debunked

Note: I am using simple error messages as an example, please reference the best practices and guidelines I outlined on when to use custom error messages.

I have been churning in my mind for the last few days all the entries in the 2013 Scripting Games and how they handle errors, or lack thereof.

I am coming to the conclusion through some testing that the simple fact of seeing a try..catch or throw statements does not mean there is proper error handling.

I've been testing several variations and forms of error handling, so lets start with the basics.
function Test-WriteError {      [CmdletBinding()] param()  "Test-WriteError::ErrorActionPreference = $ErrorActionPreference"Move-Item -Path 'C:\Does\Not\Exists.log' -Destination 'C:\No\Where'"Test-WriteError::End"}   Test-WriteError::ErrorActionPreference = Continue
Move-Item : Cannot find path 'C:\Does\Not\Exists.log' because it does not exist.
At line:6 char:5
+     Move-Item -Path 'C:\Does\N…