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:
- 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”. - 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.
- 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.
- Ichiro’s query looks as follows: /Request[CreatedTime >= ‘X’ and AuthorizationWorkflowInstance = /WorkflowInstance[WorkflowDefinition = ‘Y’ and WorkflowStatus = ‘Terminated’]]/Creator
- ‘X’ is the approximate DateTime when the connectivity issue first appeared.
- ‘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.
- 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 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.
- 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.
- 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.
- 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.
- 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