How to Use PowerShell to Recover From Large Scale Workflow Failures

FIM ScriptBox Item

Summary

Fabrikam has a policy that ensures all groups have unique aliases.
A database in an external system keeps track of all the aliases that have been assigned to groups in Fabrikam.
Whenever a new group is created, or has its alias modified, the group’s alias is verified against the external system for uniqueness.
After the group is created, the new alias for the group is added to the external system.

In the middle of one typical weekday, the FIM Services servicing group management requests lose connectivity to the external system tracking the group aliases.
This connectivity issue results in several outcomes:

  1. All authorization workflows attempting to validate a group alias encounter an unhandled exception and terminate as a result.
    This results in all group creation, and alias modification, requests currently in the authorizing state being marked as “denied”.
  2. All action workflows attempting to reserve a new alias in the external system encounter an unhandled exception and terminate.
    This results in all group creation, and alias modification requests currently in the “Post Processing” state being marked as “PostProcessingError”.

Ichiro, the administrator for the FIM Service, does not immediately become aware of the connectivity issue.
By the time the connectivity issue is resolved, Ichiro realizes there may be a large number of requests affected by the issue.
Ichiro first identifies the requests that were denied because of the connectivity issue.

  1. He submits a query, in the FIM Portal, to identify all users whose requests that were denied because of a termination in the alias validation authorization workflow.
  2.  Ichiro’s query looks as follows: /Request[CreatedTime >= ‘X’ and AuthorizationWorkflowInstance = /WorkflowInstance[WorkflowDefinition = ‘Y’ and WorkflowStatus = ‘Terminated’]]/Creator
    1.  ‘X’ is the approximate DateTime when the connectivity issue first appeared.
    2.  ‘Y’ is the ObjectID of the workflow definition corresponding to the alias validation authorization workflow.

From his previous step, Ichiro discovers that a large number of users were affected by the connectivity issue.
Since there is no way for Ichiro to restart these failed authorization workflows, these users need to resubmit their requests.
Ichiro decides that he wants to notify the users that they may need to resubmit their requests, due to the temporary connectivity issue.
In order to notify the users, Ichiro needs the ability to either extract the list of users from the FIM Portal and paste them as the recipients of an email message in Outlook, or he needs to create a new set with the users as members so that he can create a new retroactive policy to send an email notification to these users.

  1. There is no way for Ichiro to create a dynamic set with the target users as members, because the xpath query needed to identify the users is not supported by sets in FIM.
  2. Ichiro must get a list of the ObjectIDs of the target users and add them in bulk as explicit members to a set.
    The FIM Portal does not support this ability, so Ichiro must rely on the PowerShell cmdlets for FIM to accomplish this.
    Once Ichiro has created the set containing all the affected users he wants to notify, he creates the email notification workflow he wants to apply to them, and a new MPR that run the workflow.

Ichiro’s next step is to identify the requests whose alias reservation action workflow failed because of the connectivity issue.

  1. He submits a query, in the FIM Portal, to identify groups whose aliases were not reserved because of a termination in the alias reservation action workflow.
  2. Ichiro’s query looks as follows: /Request[CreatedTime >= ‘X’ and ActionWorkflowInstance = /WorkflowInstance[WorkflowDefinition = ‘Y’ and WorkflowStatus = ‘Terminated’]]/Target
    •  ‘X’ is the approximate DateTime when the connectivity issue first appeared.
    •  ‘Y’ is the ObjectID of the workflow definition corresponding to the alias reservation action workflow.

From his previous step, Ichiro discovers that a large number of groups have not had their alias reserved.
The alias reservation workflow does not need any information from the Request that triggered it, since it reads the alias to reserve from the group itself.
Ichiro uses the “run on policy update” feature to retroactively apply a policy that reserves the alias for all the groups identified in his previous step.

  1.  Ichiro creates a new static set with all the affected groups as members.
    •  There is no way for Ichiro to create a dynamic set with the target users as members, because the xpath query needed to identify the users is not supported by sets in FIM.
    •  Ichiro must get a list of the ObjectIDs of the target groups and add them in bulk as explicit members to a set. The FIM Portal does not support this ability, so Ichiro must rely on the PowerShell cmdlets for FIM to accomplish this.
  2.  Ichiro creates a new MPR that applies the alias reservation action workflow to all members of the set he created.

**
Script Code**

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#--------------------------------------------------------------------------------------------------------------------
 Set-Variable -Name URI -Value "http://localhost:5725/resourcemanagementservice" -Option Constant 
#--------------------------------------------------------------------------------------------------------------------
 Function SubmitNewResource
 {
    Param($Resource)
    End 
    {
       $Resource | Import-FIMConfig -uri $URI `
                                    -ErrorVariable Err `
                                    -ErrorAction SilentlyContinue 
       If($Err){Throw $Err}
    }   
 }
#--------------------------------------------------------------------------------------------------------------------
Function GetResources
{
    Param($Filter)
    End 
    {
       $ExportResource = export-fimconfig -uri $URI `
                                          –onlyBaseResources `
                                          -customconfig ("$Filter")`
                                          -ErrorVariable Err `
                                          -ErrorAction SilentlyContinue
       If($Err){Throw $Err}
       Return $ExportResource                                                       
    }
}
#--------------------------------------------------------------------------------------------------------------------
Function AddChangeToResource
{
    Param($Resource, $AttributeName, $AttributeValue, $Operation)
    End
    {
       $ImportChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
       $ImportChange.Operation = $Operation
       $ImportChange.AttributeName = $AttributeName
       $ImportChange.AttributeValue = $AttributeValue
       $ImportChange.FullyResolved = 1
       $ImportChange.Locale = "Invariant"
         
       If ($Resource.Changes -eq $null)
       {
           $Resource.Changes = (,$ImportChange)
       }
       Else
       {
          $Resource.Changes += $ImportChange
       }
    }
}
#--------------------------------------------------------------------------------------------------------------------
Function CreateResource
{
    Param($ObjectType, $DisplayName)
    End
    {
       $NewResource = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject
       $NewResource.ObjectType = $ObjectType
       $NewResource.SourceObjectIdentifier = [System.Guid]::NewGuid().ToString()
         
       AddChangeToResource -Resource      $NewResource `
                           -AttributeName "DisplayName" `
                           -AttributeValue $DisplayName `
                           -Operation     "1"
                                       
       Return $NewResource
    }
}
#--------------------------------------------------------------------------------------------------------------------
 Function AddMemberToSet
 {
    Param($SetResource, $MemberId)
    End
    {
       AddChangeToResource -Resource      $SetResource `
                           -AttributeName "ExplicitMember" `
                           -AttributeValue $MemberId `
                           -Operation     "0"
    }   
 }
#--------------------------------------------------------------------------------------------------------------------
 Function CreateMpr
 {
    Param($MPRName, $TRSetId, $WorkflowId)
      End
      {
         $newMpr = CreateResource -ObjectType "ManagementPolicyRule" -DisplayName $MPRName
         
         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "ActionParameter" `
                                       -AttributeValue "*" `
                                       -Operation      "0"

         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "ActionType" `
                                       -AttributeValue "TransitionIn" `
                                       -Operation      "0"

         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "ActionWorkflowDefinition" `
                                       -AttributeValue $WorkflowId `
                                       -Operation      "0"

         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "Disabled" `
                                       -AttributeValue "False" `
                                       -Operation      "1"

         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "GrantRight" `
                                       -AttributeValue "False" `
                                       -Operation      "1"
         
         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "ManagementPolicyRuleType" `
                                       -AttributeValue "SetTransition" `
                                       -Operation      "1"
                                       
         AddChangeToResource -Resource       $newMpr `
                             -AttributeName  "ResourceFinalSet" `
                                       -AttributeValue $TRSetId `
                                       -Operation      "0"
                                 
         SubmitNewResource -Resource $newMpr
      }
 }
#--------------------------------------------------------------------------------------------------------------------
 If(@(Get-PSSnapin | Where-Object {$_.Name -eq "FIMAutomation"} ).count -eq 0) {Add-PSSnapin FIMAutomation}
 Clear-Host
 If($args.count -ne 4)
 {
    Write-Host "To run this script, you need to provide four parameters:"
    Write-Host "1) Workflow name"
    Write-Host "2) Set name"
    Write-Host "3) MPR name"
    Write-Host "4) Object filter"
    Throw "Parameter missing"
 }

 $WorkflowName = $args[0]
 $SetName      = $args[1]
 $MprName      = $args[2]
 $ObjectFilter = $args[3]

 If($WorkflowName.Length -eq 0) {Throw("Parameter Workflow name must have a value")}
 If($SetName.Length      -eq 0) {Throw("Parameter Set name must have a value")}
 If($MPRName.Length      -eq 0) {Throw("Paramater MPR name must have a value")}
 If($ObjectFilter.Length -eq 0) {Throw("Parameter object filter must have a value")}

 
 Write-Host "Current configuration"
 Write-Host "====================="
 Write-Host "Workflow: $WorkflowName"
 Write-Host "Set : $SetName"
 Write-Host "MPR : $MPRName"
 Write-Host "Filter : $ObjectFilter"

 $response = Read-Host "`nDo you want to proceed with this configuration (y/n)"
 If($response.ToLower() -ne "y") {Exit 0} 
#--------------------------------------------------------------------------------------------------------------------
 $WorkflowObject = GetResources -Filter "/WorkflowDefinition[DisplayName='$WorkflowName']"
 If($WorkflowObject -eq $null) {Throw "L:Workflow not found!"} 
 If((@($WorkflowObject)).Count -ne 1) {Throw "L:Several workflows with this display name found: $WorkflowName"} 
 
 $bMatch = ($WorkflowObject.ResourceManagementObject.ResourceManagementAttributes | `
            Where-Object {$_.AttributeName -eq "RequestPhase"}).Value.CompareTo("Action")
 If($bMatch -ne 0){Throw "L:Workflow is not an Action Workflow"}

 $bMatch = ($WorkflowObject.ResourceManagementObject.ResourceManagementAttributes | `
            Where-Object {$_.AttributeName -eq "RunOnPolicyUpdate"}).Value.CompareTo("True")
 If($bMatch -ne 0){Throw "L:Run on policy update is not enabled in Workflow"}
#--------------------------------------------------------------------------------------------------------------------
 $SetObject = GetResources -Filter "/Set[DisplayName='$SetName']"
 If($SetObject -ne $null) {Throw "L:A Set with this display name already exists: $SetName"} 
 
 $MprObject = GetResources -Filter "/ManagementPolicyRule[DisplayName='$MprName']"
 If($MprObject -ne $null) {Throw "L:A Management Policy Rule with this display name already exists: $MprName"} 
  
 $SourceObjects = GetResources -Filter $ObjectFilter
 If($SourceObjects -eq $null) {Throw "L:Source objects not found!"} 
#--------------------------------------------------------------------------------------------------------------------
 $NewSet = CreateResource -ObjectType "Set" -DisplayName "$SetName"
 $SourceObjects | ForEach{
    AddMemberToSet -SetResource $NewSet -MemberId (($_.ResourceManagementObject.ObjectIdentifier).split(":"))[2]  
 }
 SubmitNewResource -Resource $NewSet
 $SetObject = GetResources -Filter "/Set[DisplayName='$SetName']"
#--------------------------------------------------------------------------------------------------------------------
 CreateMpr -MPRName    $MprName `
           -TRSetId    ((@($SetObject)[0].ResourceManagementObject.ObjectIdentifier).split(":"))[2] `
           -WorkflowId ((@($WorkflowObject)[0].ResourceManagementObject.ObjectIdentifier).split(":"))[2]

 Write-Host "Command completed successfully"
#--------------------------------------------------------------------------------------------------------------------
 Trap
 { 
    $exMessage = $_.Exception.Message
    If($exMessage.StartsWith("L:"))
    {write-host "`n" $exMessage.substring(2) "`n" -foregroundcolor white -backgroundcolor darkblue}
    Else
    {
       write-host "`nError: " $exMessage "`n"     -foregroundcolor white -backgroundcolor darkred
       write-host $_.Exception.GetType().FullName -foregroundcolor white -backgroundcolor darkred
       Write-Host "`n"
    }
    Exit 1
 }
#--------------------------------------------------------------------------------------------------------------------

 

Note

To provide feedback about this article, create a post on the FIM TechNet Forum.
For more FIM related Windows PowerShell scripts, see the  FIM ScriptBox

 


See Also