UMN-Grouper.psm1
#region License # Copyright 2017 University of Minnesota, Office of Information Technology # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with Foobar. If not, see <http://www.gnu.org/licenses/>. #endregion #region New-GrouperHeader function New-GrouperHeader { <# .SYNOPSIS Create Header to be consumed by all other functions .DESCRIPTION Create Header to be consumed by all other functions .PARAMETER psCreds PScredential composed of your username/password to Server .NOTES Author: Travis Sobeck LASTEDIT: 6/20/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [System.Management.Automation.PSCredential]$psCreds ) $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($psCreds.UserName+':'+$psCreds.GetNetworkCredential().Password)) return (@{"Authorization" = "Basic $auth"}) } #endregion #region Get-GrouperGroup function Get-GrouperGroup { <# .SYNOPSIS Get Grouper Group(s) .DESCRIPTION Get Grouper Group(s) .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName Use this if you know the exact name .PARAMETER stemName Use this to get a list of groups in a specific stem. Use Get-GrouperStem to find stem .PARAMETER subjectId Set this to a username to search as that user if you have access to .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='groupName')] [string]$groupName, [Parameter(ParameterSetName='groupName')] [switch]$search, [Parameter(Mandatory,ParameterSetName='stemName')] [string]$stemName, [string]$subjectId ) Begin { $uri = "$uri/groups" $body = @{} } Process { if ($groupName) { if ($search){$body['WsRestFindGroupsRequest'] = @{wsQueryFilter = @{groupName = $groupName;queryFilterType = 'FIND_BY_GROUP_NAME_APPROXIMATE'}}} else{$body['WsRestFindGroupsRequest'] = @{wsQueryFilter = @{groupName = $groupName;queryFilterType = 'FIND_BY_GROUP_NAME_EXACT'}}} } else { $body['WsRestFindGroupsRequest'] = @{wsQueryFilter = @{stemName = $stemName;queryFilterType = 'FIND_BY_STEM_NAME'}} } if ($subjectId) { $body['WsRestFindGroupsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 Write-Verbose -Message $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsFindGroupsResults.groupResults } End{} } #endregion #region Get-GrouperGroupMembers function Get-GrouperGroupMembers { <# .SYNOPSIS Get List of Members in a Group .DESCRIPTION Get List of Members in a Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName This represents the identifier for the group, it should look like 'stemname:group' Example: stem1:substem:supergroup .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$groupName, [string]$subjectId ) Begin{} Process { $uri = "$uri/groups" $body = @{ WsRestGetMembersRequest = @{ subjectAttributeNames = @("description") wsGroupLookups = @(@{groupName = $groupName}) } } if ($subjectId) { #$body['WsRestGetMembersRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; $body['WsRestGetMembersRequest']['actAsSubjectLookup'] = @{subjectIdentifier = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 Write-Verbose -Message $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsGetMembersResults.results.wsSubjects } End{} } #endregion #region Get-GrouperGroupsForMember function Get-GrouperGroupsForMember { <# .SYNOPSIS Get List of Members in a Group .DESCRIPTION Get List of Members in a Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER memberName This represents the member for which you want to retrieve the list of groups by .PARAMETER subjectSourceId Source location of subjectId, ie ldap .PARAMETER memberFilter Can base membership list based on memberfilter (e.g. All, Immediate, Effective) Immediate = Direct membership, Effective = Inherited .PARAMETER stemName Limit search to stem .NOTES Author: Travis Sobeck LASTEDIT: 6/30/2019 .EXAMPLE Get-GrouperGroupsForMember -uri $uri -header $header -memberName 'ldap_Identifier' -subjectSourceId 'umnldap' -stemName 'umn:itac' #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='subjectId')] [string]$subjectId, [Parameter(Mandatory,ParameterSetName='subjectIdentifier')] [string]$subjectIdentifier, [string]$actAsSubjectId, [Parameter(Mandatory)] [string]$subjectSourceId, [string]$stemName, [ValidateSet("All", "Immediate", "Effective")] [string]$memberFilter ) Begin{} Process { $uri = "$uri/memberships" $body = @{ WsRestGetMembershipsRequest = @{ fieldName = 'members' wsSubjectLookups = @(@{subjectId = $memberName;subjectSourceId = $subjectSourceId}) } } if ($subjectIdentifier){$body['WsRestGetMembershipsRequest']['wsSubjectLookups'] = @(@{subjectIdentifier = $subjectIdentifier;subjectSourceId = $subjectSourceId})} else{$body['WsRestGetMembershipsRequest']['wsSubjectLookups'] = @(@{subjectId = $subjectId;subjectSourceId = $subjectSourceId})} if ($actAsSubjectId) { $body['WsRestGetMembershipsRequest']['actAsSubjectLookup'] = @{subjectId = $actAsSubjectId}; } if($memberFilter) { $body['WsRestGetMembershipsRequest']['memberFilter'] = $memberFilter; } if($stemName) { $body['WsRestGetMembershipsRequest']['wsStemLookup'] = @{stemName = $stemName} $body['WsRestGetMembershipsRequest']['stemScope'] = 'ALL_IN_SUBTREE' } $body = $body | ConvertTo-Json -Depth 5 Write-Verbose -Message $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsGetMembershipsResults.wsGroups } End{} } #endregion #region Get-GrouperPrivileges function Get-GrouperPrivileges { <# .SYNOPSIS Get Grouper Privileges .DESCRIPTION Get Grouper Privileges .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName stemName .PARAMETER subjectId Filter result for a specific user .PARAMETER actAsSubjectId User security context to restrict search to. ie search as this user .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='stem')] [string]$stemName, [Parameter(Mandatory,ParameterSetName='group')] [string]$groupName, [string]$actAsSubjectId, [string]$subjectId ) Begin{} Process { $uri = "$uri/grouperPrivileges" $body = @{ WsRestGetGrouperPrivilegesLiteRequest = @{} } if ($subjectId) { $body['WsRestGetGrouperPrivilegesLiteRequest']['subjectId'] = $subjectId } if ($actAsSubjectId) { $body['WsRestGetGrouperPrivilegesLiteRequest']['actAsSubjectId'] = $actAsSubjectId } if ($groupName) { $body['WsRestGetGrouperPrivilegesLiteRequest']['groupName'] = $groupName } if ($stemName) { $body['WsRestGetGrouperPrivilegesLiteRequest']['stemName'] = $stemName } $body = $body | ConvertTo-Json -Depth 5 Write-Verbose $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType Write-Verbose ($response.Content | ConvertFrom-Json).WsGetGrouperPrivilegesLiteResult return ($response.Content | ConvertFrom-Json).WsGetGrouperPrivilegesLiteResult.privilegeResults } End{} } #endregion #region Get-GrouperStem function Get-GrouperStem { <# .SYNOPSIS Get Grouper Stem(s) .DESCRIPTION Get a Grouper Stem or use the -search switch to get all Grouper Stem(s) that match stem pattern From API docs -- find by approx name, pass the name in. stem name is required .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName stemName .PARAMETER search Switch to do a search. Use with the caution, results from grouper API are not very reliable .PARAMETER subjectId Set this to a username to search as that user if you have access to .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$stemName, [switch]$search, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{stemName = $stemName} } } if ($search){$body['WsRestFindStemsRequest']['wsStemQueryFilter']['stemQueryFilterType'] = 'FIND_BY_STEM_NAME_APPROXIMATE'} else{$body['WsRestFindStemsRequest']['wsStemQueryFilter']['stemQueryFilterType'] = 'FIND_BY_STEM_NAME'} if ($subjectId) { $body['WsRestFindStemsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Verbose "NO results found" } } End{} } #endregion #region Get-GrouperStemByParent function Get-GrouperStemByParent { <# .SYNOPSIS Get Grouper child Stem(s) of a parent stem .DESCRIPTION Get Grouper child Stem(s) of a parent stem .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER parentStemName stemName of Parent .PARAMETER noRecursion By default the function will recursivly search for all sub-stems, use this switch to only get stems one level below the parent stem .PARAMETER subjectId Set this to a username to search as that user if you have access to .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$parentStemName, [switch]$noRecursion, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{parentStemName = $parentStemName;stemQueryFilterType = 'FIND_BY_PARENT_STEM_NAME'} } } if($noRecursion){$body['WsRestFindStemsRequest']['wsStemQueryFilter']["parentStemNameScope"] = 'ONE_LEVEL'} else{$body['WsRestFindStemsRequest']['wsStemQueryFilter']["parentStemNameScope"] = 'ALL_IN_SUBTREE'} if ($subjectId) { $body['WsRestFindStemsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 Write-Verbose -Message $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Verbose "NO results found" } } End{} } #endregion #region Get-GrouperStemByUUID function Get-GrouperStemByUUID { <# .SYNOPSIS Get a Grouper Stem by its UUID .DESCRIPTION Get a Grouper Stem by its UUID .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER uuid UUID of the stem to retrieve .PARAMETER subjectId Set this to a username to search as that user if you have access to .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$uuid, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{stemUuid = $uuid;stemQueryFilterType = 'FIND_BY_STEM_UUID'} } } if ($subjectId) { $body['WsRestFindStemsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Verbose "NO results found" } } End{} } #endregion #region New-GrouperGroup function New-GrouperGroup { <# .SYNOPSIS Create new Group in Grouper .DESCRIPTION Create new Group in Grouper .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName This represents the identifier for the group, it should look like 'stemname:group' Example: stem1:substem:supergroup .PARAMETER description The description represents the the Name in the form users in the UI will see the group .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$groupName, [Parameter(Mandatory)] [string]$description ) Begin{} Process { $uri = "$uri/groups" $body = @{ WsRestGroupSaveRequest = @{ wsGroupToSaves = @(@{wsGroup = @{description = $description;displayExtension = $description;extension = $description;name = $groupName};wsGroupLookup = @{groupName = $groupName}}) } } | ConvertTo-Json -Depth 5 ($response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType) return ($response.Content | ConvertFrom-Json).WsGroupSaveResults.results.wsGroup } End{} } #endregion #region New-GrouperGroupMember function New-GrouperGroupMember { <# .SYNOPSIS Add a user to a Group .DESCRIPTION Add a user to a Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName This represents the identifier for the group, it should look like 'stemname:group' Example: stem1:substem:supergroup .PARAMETER subjectId Each implemetation of Grouper will determine what this value represents .PARAMETER subjectIdentifier Alternative way to identify user to be added .PARAMETER subjectSourceId Source location of subjectId, ie ldap .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$groupName, [Parameter(Mandatory,ParameterSetName='subjectId')] [string]$subjectId, [Parameter(Mandatory,ParameterSetName='subjectIdentifier')] [string]$subjectIdentifier, [string]$subjectSourceId ) Begin{} Process { $uri = "$uri/groups" if ($subjectIdentifier){$subjectLookups = @(@{subjectIdentifier = $subjectIdentifier})} else{$subjectLookups = @(@{subjectId = $subjectId})} if ($subjectSourceId){$subjectLookups[0]['subjectSourceId'] = $subjectSourceId} $body = @{ WsRestAddMemberRequest = @{ subjectLookups = $subjectLookups wsGroupLookup = @{groupName = $groupName} } } | ConvertTo-Json -Depth 5 Write-Verbose $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return @(($response.Content | ConvertFrom-Json).WsAddMemberResults.results.wsSubject,($response.Content | ConvertFrom-Json).WsAddMemberResults.wsGroupAssigned) } End{} } #endregion #region Set-GrouperPrivileges/New-GrouperPrivileges function Set-GrouperPrivileges { <# .SYNOPSIS Set Grouper Privileges, either Add or Remove based on 'allowed' paramter .DESCRIPTION Set Grouper Privileges, either Add or Remove based on 'allowed' paramter .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName stemName .PARAMETER subjectId User to apply Privilege to .PARAMETER actAsSubjectId User security context to use to apply change .PARAMETER privilegeName Name of privilege to apply, see Get-GrouperPrivileges for examples .PARAMETER allowed Setting this to 'T' (true) will ADD the priviledge, while setting it to 'F' (false) will remove the Privilege .PARAMETER subjectIdIsAGroup Use this switch (set to true) if the subjectID is actually a GroupName. The default assumption is that the subjectID is a users ID .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] [Alias('New-GrouperPrivileges')] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='stem')] [string]$stemName, [Parameter(Mandatory,ParameterSetName='group')] [string]$groupName, [string]$actAsSubjectId, [Parameter(Mandatory)] [string]$subjectId, [switch]$subjectIdIsAGroup = $false, [Parameter(Mandatory)] [string]$privilegeName, [ValidateSet("T","F")] [string]$allowed = 'T' ) Begin{} Process { $uri = "$uri/grouperPrivileges" $body = @{ WsRestAssignGrouperPrivilegesLiteRequest = @{ allowed = $allowed privilegeName = $privilegeName } } if ($subjectIdIsAGroup) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['subjectIdentifier'] = $subjectId $body['WsRestAssignGrouperPrivilegesLiteRequest']['subjectSourceId'] = "g:gsa" } else {$body['WsRestAssignGrouperPrivilegesLiteRequest']['subjectId'] = $subjectId} if ($actAsSubjectId) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['actAsSubjectId'] = $actAsSubjectId } if ($groupName) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['groupName'] = $groupName $body['WsRestAssignGrouperPrivilegesLiteRequest']['privilegeType'] = 'access' } if ($stemName) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['stemName'] = $stemName $body['WsRestAssignGrouperPrivilegesLiteRequest']['privilegeType'] = 'naming' } $body = $body | ConvertTo-Json -Depth 5 #Write-Debug $body $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsGetGrouperPrivilegesLiteResult.privilegeResults if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Verbose "NO results found" } } End{} } #endregion #region New-GrouperStem function New-GrouperStem { <# .SYNOPSIS Create new Stem in Grouper .DESCRIPTION Create new Stem in Grouper .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName This represents the identifier for the stem, it should look like 'stemParentA:stemParentB:stemname' Example: stem1:substem:newstem .PARAMETER description The description represents the the Name in the form users in the UI will see the group .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$stemName, [Parameter(Mandatory)] [string]$description ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestStemSaveRequest = @{ wsStemToSaves = @(@{wsStem = @{description = $description;displayExtension = $description;name = $stemName};wsStemLookup = @{stemName = $stemName}}) } } | ConvertTo-Json -Depth 5 ($response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType) return ($response.Content | ConvertFrom-Json).WsStemSaveResults.results.wsStem } End{} } #endregion #region Remove-GrouperGroup function Remove-GrouperGroup { <# .SYNOPSIS Remove a Grouper Group .DESCRIPTION Remove a Grouper Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName The groupName, use Get-GrouperGroup to the get the "name" field .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string[]]$groupName ) Begin{} Process { $uri = "$uri/groups" <# This didn't seem to work :() foreach ($gn in $groupName) { $gnArray = $gnArray + @{groupName = $gn} } $body = @{ WsRestGroupDeleteRequest = @{ wsGroupLookups = $gnArray } } | ConvertTo-Json -Depth 5 #> foreach ($gn in $groupName) { $body = @{ WsRestGroupDeleteRequest = @{ wsGroupLookups = @(@{groupName = $gn}) } } | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType $deletedGroups = ($response.Content | ConvertFrom-Json).WsGroupDeleteResults.results.wsGroup $deletedGroups } #return ($response.Content | ConvertFrom-Json).WsGroupDeleteResults.results.resultMetadata.resultCode } End{} } #endregion #region Remove-GrouperStem function Remove-GrouperStem { <# .SYNOPSIS Remove a Grouper Stem .DESCRIPTION Remove a Grouper Stem .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName Use Get-GrouperStem to find name .PARAMETER removeGroups Grouper will not remove a Stem with other Stems or Groups in it. Set this to remove all the groups first .PARAMETER recursive Recursively remove all child stems .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$stemName, [switch]$removeGroups, [switch]$recursive ) Begin{} Process { if ($recursive) { $stemNames = (Get-GrouperStemByParent -uri $uri -header $header -parentStemName $stemName -noRecursion).Name Write-Verbose "Child Stems: $stemNames" foreach ($stem in $stemNames) { (Remove-GrouperStem -uri $uri -header $header -stemName $stem -removeGroups:$removeGroups -recursive:$recursive).name } } if ($removeGroups) { # Get all the groups $groupNames = (Get-GrouperGroup -uri $uri -header $header -stemName $stemName).name # Remove the groups Write-Verbose "Child groups: $groupNames" foreach ($groupName in $groupNames) { $null = Remove-GrouperGroup -uri $uri -header $header -groupName $groupName } Start-Sleep -Seconds 1 } $uri = "$uri/stems" $body = @{ WsRestStemDeleteRequest = @{ wsStemLookups = @(@{stemName = $stemName}) } } | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType $removedStems = ($response.Content | ConvertFrom-Json).WsStemDeleteResults.results.wsStem return $removedStems } End{} } #endregion # SIG # Begin signature block # MIIfBwYJKoZIhvcNAQcCoIIe+DCCHvQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU7pD8PEjpQEUb8HpD1JpZnNIO # ggWgghoTMIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B # AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV # BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU # cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw # NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs # dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G # A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF # UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q # gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x # 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ # w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH # d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh # 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT # bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE # JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P # AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG # A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz # dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG # GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C # L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9 # 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf # ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt # pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd # ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY # Ju6ay0SnRVqBlRUa9VEwggTmMIIDzqADAgECAhBiXE2QjNVC+6supXM/8VQZMA0G # CSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNV # BAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv # cmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMU # VVROLVVTRVJGaXJzdC1PYmplY3QwHhcNMTEwNDI3MDAwMDAwWhcNMjAwNTMwMTA0 # ODM4WjB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVy # MRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEg # MB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB # AQUAA4IBDwAwggEKAoIBAQCqgvGEqVvYcbXSXSvt9BMgDPmb6dGPdF5u7uspSNjI # vizrCmFgzL2SjXzddLsKnmhOqnUkcyeuN/MagqVtuMgJRkx+oYPp4gNgpCEQJ0Ca # WeFtrz6CryFpWW1jzM6x9haaeYOXOh0Mr8l90U7Yw0ahpZiqYM5V1BIR8zsLbMaI # upUu76BGRTl8rOnjrehXl1/++8IJjf6OmqU/WUb8xy1dhIfwb1gmw/BC/FXeZb5n # OGOzEbGhJe2pm75I30x3wKoZC7b9So8seVWx/llaWm1VixxD9rFVcimJTUA/vn9J # AV08m1wI+8ridRUFk50IYv+6Dduq+LW/EDLKcuoIJs0ZAgMBAAGjggFKMIIBRjAf # BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUZCKGtkqJ # yQQP0ARYkiuzbj0eJ2wwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C # AQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEIGA1Ud # HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZp # cnN0LU9iamVjdC5jcmwwdAYIKwYBBQUHAQEEaDBmMD0GCCsGAQUFBzAChjFodHRw # Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RPYmplY3RfQ0EuY3J0MCUG # CCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEB # BQUAA4IBAQARyT3hBeg7ZazJdDEDt9qDOMaSuv3N+Ntjm30ekKSYyNlYaDS18Ash # U55ZRv1jhd/+R6pw5D9eCJUoXxTx/SKucOS38bC2Vp+xZ7hog16oYNuYOfbcSV4T # p5BnS+Nu5+vwQ8fQL33/llqnA9abVKAj06XCoI75T9GyBiH+IV0njKCv2bBS7vzI # 7bec8ckmONalMu1Il5RePeA9NbSwyVivx1j/YnQWkmRB2sqo64sDvcFOrh+RMrjh # JDt77RRoCYaWKMk7yWwowiVp9UphreAn+FOndRWwUTGw8UH/PlomHmB+4uNqOZrE # 6u4/5rITP1UDBE0LkHLU6/u8h5BRsjgZMIIE/jCCA+agAwIBAgIQK3PbdGMRTFpb # MkryMFdySTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMS # R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD # T01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcg # Q0EwHhcNMTkwNTAyMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgzELMAkGA1UEBhMC # R0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9y # ZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDDCJTZWN0aWdvIFNI # QS0xIFRpbWUgU3RhbXBpbmcgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAv1I2gjrcdDcNeNV/FlAZZu26GpnRYziaDGayQNungFC/aS42Lwpn # P0ChSopjNZvQGcx0qhcZkSu1VSAZ+8AaOm3KOZuC8rqVoRrYNMe4iXtwiHBRZmns # d/7GlHJ6zyWB7TSCmt8IFTcxtG2uHL8Y1Q3P/rXhxPuxR3Hp+u5jkezx7M5ZBBF8 # rgtgU+oq874vAg/QTF0xEy8eaQ+Fm0WWwo0Si2euH69pqwaWgQDfkXyVHOaeGWTf # dshgRC9J449/YGpFORNEIaW6+5H6QUDtTQK0S3/f4uA9uKrzGthBg49/M+1BBuJ9 # nj9ThI0o2t12xr33jh44zcDLYCQD3npMqwIDAQABo4IBdDCCAXAwHwYDVR0jBBgw # FoAUZCKGtkqJyQQP0ARYkiuzbj0eJ2wwHQYDVR0OBBYEFK7u2WC6XvUsARL9jo2y # VXI1Rm/xMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMEAGA1UdIAQ5MDcwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB # BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMEIGA1UdHwQ7MDkwN6A1oDOG # MWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0FfMi5j # cmwwcgYIKwYBBQUHAQEEZjBkMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnNlY3Rp # Z28uY29tL0NPTU9ET1RpbWVTdGFtcGluZ0NBXzIuY3J0MCMGCCsGAQUFBzABhhdo # dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAen+pStKw # pBwdDZ0tXMauWt2PRR3wnlyQ9l6scP7T2c3kGaQKQ3VgaoOkw5mEIDG61v5MzxP4 # EPdUCX7q3NIuedcHTFS3tcmdsvDyHiQU0JzHyGeqC2K3tPEG5OfkIUsZMpk0uRlh # dwozkGdswIhKkvWhQwHzrqJvyZW9ljj3g/etfCgf8zjfjiHIcWhTLcuuquIwF4Mi # KRi14YyJ6274fji7kE+5Xwc0EmuX1eY7kb4AFyFu4m38UnnvgSW6zxPQ+90rzYG2 # V4lO8N3zC0o0yoX/CLmWX+sRE+DhxQOtVxzhXZIGvhvIPD+lIJ9p0GnBxcLJPufF # cvfqG5bilK+GLjCCBawwggSUoAMCAQICEHJNXiAT1cKRQFXzfFSJVHEwDQYJKoZI # hvcNAQELBQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQHEwlB # bm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21tb24x # JTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcxMjE0 # MDAwMDAwWhcNMjAxMjEzMjM1OTU5WjCByzELMAkGA1UEBhMCVVMxDjAMBgNVBBEM # BTU1NDU1MRIwEAYDVQQIDAlNaW5uZXNvdGExFDASBgNVBAcMC01pbm5lYXBvbGlz # MRgwFgYDVQQJDA8xMDAgVW5pb24gU3QgU0UxIDAeBgNVBAoMF1VuaXZlcnNpdHkg # b2YgTWlubmVzb3RhMSQwIgYDVQQLDBtDb21wdXRlciBhbmQgRGV2aWNlIFN1cHBv # cnQxIDAeBgNVBAMMF1VuaXZlcnNpdHkgb2YgTWlubmVzb3RhMIIBIjANBgkqhkiG # 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwk6kLE9u+tWv0JUkIJSn5pWfa09g6cqFLucC # XomNj9NYj8t+JfPna3gC6LHv3OQAUDHOoC5H+8N3ea7qVGYIiwPRHzXOGqG/tVai # U5s5hG3vBhfRX8W1/2g4/hpgeXUzrxYn/2c5SOGGy0MU1ZJyUSFEdsjXHEV7HXK4 # qmFGV9RJxtiLZH1qUldCglxcj7zw0QnUdG6oAxpwTCeVp057/WXbnIR8a0gPse+y # /new5+CBUGTAvrw6K2BrJQVsdIIVn/q+BbcZxh9PpeZfTtsi6lgkvy0bUWtl5sSp # d75+hvw4Sl3HAaWZtoWN7LPmbDbbVRO2Arv4doh4Chod4wJ5xQIDAQABo4IB2DCC # AdQwHwYDVR0jBBgwFoAUrjUjF///Bj2cUOCMJGUzHnAQiKIwHQYDVR0OBBYEFF4L # EhElVUvT8n5txOJSNAczooSAMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA # MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBmBgNVHSAE # XzBdMFsGDCsGAQQBriMBBAMCATBLMEkGCCsGAQUFBwIBFj1odHRwczovL3d3dy5p # bmNvbW1vbi5vcmcvY2VydC9yZXBvc2l0b3J5L2Nwc19jb2RlX3NpZ25pbmcucGRm # MEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuaW5jb21tb24tcnNhLm9yZy9J # bkNvbW1vblJTQUNvZGVTaWduaW5nQ0EuY3JsMH4GCCsGAQUFBwEBBHIwcDBEBggr # BgEFBQcwAoY4aHR0cDovL2NydC5pbmNvbW1vbi1yc2Eub3JnL0luQ29tbW9uUlNB # Q29kZVNpZ25pbmdDQS5jcnQwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmluY29t # bW9uLXJzYS5vcmcwGQYDVR0RBBIwEIEOb2l0bXB0QHVtbi5lZHUwDQYJKoZIhvcN # AQELBQADggEBAENRlesMKmBaZ0g68lttYEMtaPiz+DaNpOlXBs1gH66aghB1aP6i # iRJcFVasGLUVFncdG1xbw503LTrBUc5PECMVDVF7KKCfHA1OeFV9vOWyvdVgbe3p # aDy1sj4CADO2D0gnxcGiZoFhEZiBkTvSsj4S3GXZEvoFHJxJLw2kvdLnzy0gH/b/ # b/yblwA1fKXw4locUpDM6qTwM7SiKgkQ5W7/280EYu8BI6c8rpiJmqM1tZLcpswu # avB00T52Y+ZZmz3tMMVgFHn9pFFltYr3s3bEek7I6pU8unISbiyQzxqhIUKaBi8h # y8LgoY5UnGjX5jHsIvINzms+JX5Ity02sL0wggXrMIID06ADAgECAhBl4eLj1d5Q # RYXzJiSABeLUMA0GCSqGSIb3DQEBDQUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRo # ZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0 # aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNDA5MTkwMDAwMDBaFw0yNDA5MTgyMzU5 # NTlaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFy # Ym9yMRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYD # VQQDExxJbkNvbW1vbiBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEAwKAvix56u2p1rPg+3KO6OSLK86N25L99MCfmutOY # MlYjXAaGlw2A6O2igTXrC/Zefqk+aHP9ndRnec6q6mi3GdscdjpZh11emcehsrip # hHMMzKuHRhxqx+85Jb6n3dosNXA2HSIuIDvd4xwOPzSf5X3+VYBbBnyCV4RV8zj7 # 8gw2qblessWBRyN9EoGgwAEoPgP5OJejrQLyAmj91QGr9dVRTVDTFyJG5XMY4Drk # N3dRyJ59UopPgNwmucBMyvxR+hAJEXpXKnPE4CEqbMJUvRw+g/hbqSzx+tt4z9mJ # mm2j/w2nP35MViPWCb7hpR2LB8W/499Yqu+kr4LLBfgKCQIDAQABo4IBWjCCAVYw # HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFK41Ixf/ # /wY9nFDgjCRlMx5wEIiiMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/ # AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNV # HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0 # UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8G # CCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNB # QWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVz # dC5jb20wDQYJKoZIhvcNAQENBQADggIBAEYstn9qTiVmvZxqpqrQnr0Prk41/PA4 # J8HHnQTJgjTbhuET98GWjTBEE9I17Xn3V1yTphJXbat5l8EmZN/JXMvDNqJtkyOh # 26owAmvquMCF1pKiQWyuDDllxR9MECp6xF4wnH1Mcs4WeLOrQPy+C5kWE5gg/7K6 # c9G1VNwLkl/po9ORPljxKKeFhPg9+Ti3JzHIxW7LdyljffccWiuNFR51/BJHAZIq # UDw3LsrdYWzgg4x06tgMvOEf0nITelpFTxqVvMtJhnOfZbpdXZQ5o1TspxfTEVOQ # Asp05HUNCXyhznlVLr0JaNkM7edgk59zmdTbSGdMq8Ztuu6VyrivOlMSPWmay5Mj # vwTzuNorbwBv0DL+7cyZBp7NYZou+DoGd1lFZN0jU5IsQKgm3+00pnnJ67crdFwf # z/8bq3MhTiKOWEb04FT3OZVp+jzvaChHWLQ8gbCORgClaZq1H3aqI7JeRkWEEEp6 # Tv4WAVsr/i7LoXU72gOb8CAzPFqwI4Excdrxp0I4OXbECHlDqU4sTInqwlMwofmx # eO4u94196qIqJQl+8Sykl06VktqMux84Iw3ZQLH08J8LaJ+WDUycc4OjY61I7FGx # CDkbSQf3npXeRFm0IBn8GiW+TRDk6J2XJFLWEtVZmhboFlBLoUlqHUCKu0QOhU/+ # AEOqnY98j2zRMYIEXjCCBFoCAQEwgZAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgT # Ak1JMRIwEAYDVQQHEwlBbm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8G # A1UECxMISW5Db21tb24xJTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25p # bmcgQ0ECEHJNXiAT1cKRQFXzfFSJVHEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcC # AQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYB # BAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFDHbray6Kvbb # ActQ8cXDwAM0SIF0MA0GCSqGSIb3DQEBAQUABIIBAALjtKw1RcvjP0iyZnwc9Opo # 6DLbc6qQv8B8uJmyWsrgikDe+RSYBmhMzegw6FZgct0KZWwvT5JFYj0o9xx2aQEB # W+V2c6XY2y6VWAWTtt+J3weB3TxFnxTxfDKdSIrypZLmlsl/bygLuxm0Dgt1PIhj # S4wMcchVlz2QYssWheMqXSbV5f/3bIb0clw/PI90msb9esHjwK3xqpE1TBX7Q2pT # FAEITvMCcB9IXzxwf+azekRk+RBT1ZvOqeSNU4Kdx+dbHqU/WvtdB2ZgMiGMF4Iz # FwddR6OkhOO5x8Um4UbbALRmwULld5DQn2fLLBp6kg/AOeybhui2cahw2Lg/oGeh # ggIoMIICJAYJKoZIhvcNAQkGMYICFTCCAhECAQEwgY4wejELMAkGA1UEBhMCR0Ix # GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEa # MBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxIDAeBgNVBAMTF0NPTU9ETyBUaW1l # IFN0YW1waW5nIENBAhArc9t0YxFMWlsySvIwV3JJMAkGBSsOAwIaBQCgXTAYBgkq # hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDA0MDIyMjQ0 # NTFaMCMGCSqGSIb3DQEJBDEWBBSryD2Yoqv+4P7W1lkJdatqYB/iyDANBgkqhkiG # 9w0BAQEFAASCAQCT7cUOM4Wj0qvn+d48lOxQVKRqtLwsYFdOUDjRJfcE71uNl76G # k7M5L2fgECy+aC2oZLJu16X9tEL++BlV8K4lYLltQ6ijHJjMx7oQLZlOlXB8baM0 # Lb+oOgmrlYsToYtHIq8AUM+CowEQEPhDijvPl6D4+f1/p6FrUd/sryIU+vob5L0c # B2zasP0oL9rIEMzgvwJsb0pcCvc5yh4IlO8ObNEJPiktKFri77PErGoM1yBQd4wK # dQNfQDTUymOYUKAdLx97uiYHyt/3Z8okDFejVRNF2BFW31U46Aj2Z6Vgu2ROoTtz # k4K2qhfvdeqBo65gRXIbNURMtno9q3E798uJ # SIG # End signature block |