PowerFederatedDirectory.psm1
function Join-UriQuery { <# .SYNOPSIS Provides ability to join two Url paths together including advanced querying .DESCRIPTION Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url (optional) .PARAMETER QueryParameter Parameters and their values in form of hashtable .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-UrlQuery')] [CmdletBinding()] param ([parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri, [Parameter()][System.Collections.IDictionary] $QueryParameter) if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri } if ($QueryParameter) { $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) } } $uriRequest = [System.UriBuilder] $Url if ($Collection) { $uriRequest.Query = $Collection.ToString() } return $uriRequest.Uri.AbsoluteUri } function Remove-EmptyValue { [alias('Remove-EmptyValues')] [CmdletBinding()] param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun, [switch] $DoNotRemoveNull, [switch] $DoNotRemoveEmpty, [switch] $DoNotRemoveEmptyArray, [switch] $DoNotRemoveEmptyDictionary) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } function Join-Uri { <# .SYNOPSIS Provides ability to join two Url paths together .DESCRIPTION Provides ability to join two Url paths together .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url .EXAMPLE Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-Url')] [cmdletBinding()] param([parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri) return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/')) } function Convert-FederatedUser { [cmdletBinding()] param( [Array] $Users ) foreach ($FederatedUser in $Users) { $WorkEmail = $null $HomeEmail = $null $WorkPhone = $null $MobilePhone = $null $HomePhone = $null $Addresses = $FederatedUser.'addresses' foreach ($Address in $Addresses) { if ($Address.'type' -eq 'work') { $streetAddress = $Address.streetAddress $postalCode = $Address.PostalCode $city = $Address.Locality $region = $Address.region $country = $Address.country } elseif ($Address.'type' -eq 'home') { $HomeStreetAddress = $Address.streetAddress $HomePostalCode = $Address.PostalCode $HomeCity = $Address.Locality $HomeRegion = $Address.region $HomeCountry = $Address.country } } $Emails = $FederatedUser.'emails' foreach ($Email in $Emails) { if ($Email.Type -eq 'work') { $WorkEmail = $Email.Value } elseif ($Email.Type -eq 'home') { $HomeEmail = $Email.Value } } $PhoneNumbers = $FederatedUser.'phoneNumbers' foreach ($Phone in $PhoneNumbers) { if ($Phone.Type -eq 'work') { $WorkPhone = $Phone.Value } elseif ($Phone.Type -eq 'mobile') { $MobilePhone = $Phone.Value } elseif ($Phone.Type -eq 'home') { $HomePhone = $Phone.Value } } $CompanyLogoUrl = $null $CompanyThumbnailUrl = $null $CompanyLogos = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User:companyLogos' foreach ($C in $CompanyLogos) { if ($Type -eq 'logo') { $CompanyLogoUrl = $C.Value } elseif ($Type -eq 'thumbnail') { $CompanyThumbnailUrl = $C.Value } } $PhotoUrl = $null $ThumbnailUrl = $null $Photos = $FederatedUser.'photos' foreach ($C in $Photos) { if ($Type -eq 'photo') { $PhotoUrl = $C.Value } elseif ($Type -eq 'thumbnail') { $ThumbnailUrl = $C.Value } } [PSCustomObject] @{ Id = $FederatedUser.'id' ExternalId = $FederatedUser.'externalId' UserName = $FederatedUser.'userName' GivenName = $FederatedUser.'name'.'givenName' FamilyName = $FederatedUser.'name'.'familyName' DisplayName = $FederatedUser.'displayName' NickName = $FederatedUser.'nickName' ProfileUrl = $FederatedUser.'profileUrl' Title = $FederatedUser.'title' UserType = $FederatedUser.'userType' EmailAddress = $WorkEmail EmailAddressHome = $HomeEmail PhoneNumberWork = $WorkPhone PhoneNumberMobile = $MobilePhone PhoneNumberHome = $HomePhone StreetAddress = $streetAddress City = $City Region = $region PostalCode = $postalCode Country = $country StreetAddressHome = $HomeStreetAddress CityHome = $HomeCity RegionHome = $HomeRegion PostalCodeHome = $HomePostalCode CountryHome = $HomeCountry PreferredLanguage = $FederatedUser.'preferredLanguage' Locale = $FederatedUser.'locale' TimeZone = $FederatedUser.'timezone' Active = $FederatedUser.'active' Groups = $FederatedUser.'groups' Roles = $FederatedUser.'roles'.value Organization = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'organization' EmployeeNumber = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'employeeNumber' CostCenter = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'costCenter' Division = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'division' Department = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'department' Manager = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0User'.'manager' Description = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User'.'description' DirectoryId = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User'.'directoryId' CompanyId = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User'.'companyId' CompanyLogoUrl = $CompanyLogoUrl CompanyThumbnailUrl = $CompanyThumbnailUrl PhotoUrl = $PhotoUrl ThumbnailUrl = $ThumbnailUrl Password = $FederatedUser.Password ManagerID = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'manager'.'value' #ManagerUserName = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager'.'displayName' #ManagerReference = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'manager'.'$ref' ManagerDisplayName = $FederatedUser.'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.'manager'.'displayName' Role = $FederatedUser.'roles'.value Custom01 = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User'.'custom01' Custom02 = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User'.'custom02' Custom03 = $FederatedUser.'urn:ietf:params:scim:schemas:extension:fd:2.0:User'.'custom03' ResourceType = $FederatedUser.Meta.ResourceType Created = $FederatedUser.Meta.Created LastModified = $FederatedUser.Meta.LastModified Location = $FederatedUser.Meta.location } } } function Add-FederatedDirectoryUser { [alias('Add-FDUser')] [CmdletBinding(SupportsShouldProcess)] param( [System.Collections.IDictionary] $Authorization, [string] $ExternalId, [parameter()][string] $DirectoryID, [parameter(Mandatory)][string] $UserName, [Alias('FirstName')] $FamilyName, [string] $GivenName, [parameter(Mandatory)][string] $DisplayName, [string] $NickName, [string] $ProfileUrl, [string] $EmailAddress, [string] $EmailAddressHome, [string] $StreetAddress, [string] $City, [string] $Region, [string] $PostalCode, [string] $Country, [string] $StreetAddressHome, [string] $PostalCodeHome, [string] $CityHome, [string] $RegionHome, [string] $CountryHome, [string] $PhoneNumberWork, [string] $PhoneNumberHome, [string] $PhoneNumberMobile, [string] $PhotoUrl, [string] $ThumbnailUrl, [string] $CompanyID, # [string] $CompanyLogoUrl, # [string] $CompanyThumbnailUrl, [string] $PreferredLanguage, [string] $Locale, [string] $TimeZone, [string] $Title, [string] $UserType, [string] $Password, [string] $ManagerID, [string] $ManagerUserName, [string] $ManagerDisplayName, [switch] $Active, [string] $Department, [string] $EmployeeNumber, [string] $CostCenter, [string] $Division, [string] $Description, [ValidateSet('admin', 'user')][string] $Role = 'user', [alias('CustomAttribute01')][string] $Custom01, [alias('CustomAttribute02')][string] $Custom02, [alias('CustomAttribute03')][string] $Custom03, [switch] $Suppress, [switch] $BulkProcessing ) if (-not $Authorization) { if ($Script:AuthorizationCacheFD) { $Authorization = $Script:AuthorizationCacheFD[0] } if (-not $Authorization) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "No authorization found. Please run 'Connect-FederatedDirectory' first." } else { Write-Warning -Message "Add-FederatedDirectoryUser - No authorization found. Please run 'Connect-FederatedDirectory' first." return } } } if ($Authorization) { if ($ManagerUserName) { $ManagerID = (Get-FederatedDirectoryUser -Authorization $Authorization -UserName $ManagerUserName).Id } $Body = [ordered] @{ schemas = @( "urn:ietf:params:scim:schemas:core:2.0:User" "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" "urn:ietf:params:scim:schemas:extension:fd:2.0:User" ) "externalId" = $ExternalId "userName" = $UserName "name" = [ordered] @{ "familyName" = $FamilyName "givenName" = $GivenName } "displayName" = $DisplayName "nickName" = $NickName "profileUrl" = $ProfileUrl "emails" = @( if ($EmailAddress) { @{ "value" = $EmailAddress "type" = "work" "primary" = $true } } if ($EmailAddressHome) { @{ "value" = $EmailAddressHome "type" = "home" } } ) "addresses" = @( if ($StreetAddress -or $City -or $Region -or $PostalCode -or $Country) { @{ "streetAddress" = $StreetAddress "locality" = $City "region" = $Region "postalCode" = $PostalCode "country" = $Country "type" = "work" "primary" = $true } } if ($StreetAddressHome -or $CityHome -or $RegionHome -or $PostalCodeHome -or $CountryHome) { @{ "streetAddress" = $StreetAddressHome "locality" = $CityHome "region" = $RegionHome "postalCode" = $PostalCodeHome "country" = $CountryHome "type" = "home" } } ) "phoneNumbers" = @( if ($PhoneNumberWork) { @{ "value" = $PhoneNumberWork "type" = "work" "primary" = $true } } if ($PhoneNumberHome) { @{ "value" = $PhoneNumberHome "type" = "home" } } if ($PhoneNumberMobile) { @{ "value" = $PhoneNumberMobile "type" = "mobile" } } ) "photos" = @( if ($PhotoUrl) { @{ "value" = $PhotoUrl "type" = "photo" } } if ($ThumbnailUrl) { @{ "value" = $ThumbnailUrl "type" = "thumbnail" } } ) "password" = $Password "preferredLanguage" = $PreferredLanguage "locale" = $Locale "timeZone" = $TimeZone "userType" = $UserType "title" = $Title "active" = if ($PSBoundParameters.Keys -contains ('Active')) { $Active.IsPresent } else { $Null } "roles" = @( @{ "value" = $Role "display" = $Role } ) "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" = [ordered] @{ #"organization" = $Organization # read only... "department" = $Department "employeeNumber" = $EmployeeNumber "costCenter" = $CostCenter "division" = $Division "manager" = @{ "displayName" = $ManagerDisplayName "value" = $ManagerID #"`$ref" = $ManagerReference # readonly } } "urn:ietf:params:scim:schemas:extension:fd:2.0:User" = [ordered] @{ "description" = $Description "companyId" = $CompanyId # "companyLogos" = @( # if ($CompanyLogoUrl) { # @{ # "value" = $CompanyLogoUrl # "type" = "logo" # } # } # if ($CompanyThumbnailUrl) { # @{ # "value" = $CompanyThumbnailUrl # "type" = "thumbnail" # } # } # ) #'directoryId' = $DirectoryID 'custom01' = $Custom01 'custom02' = $Custom02 'custom03' = $Custom03 } } Try { Remove-EmptyValue -Hashtable $Body -Recursive -Rerun 2 if ($BulkProcessing) { # Return body is used for using Invoke-FederatedDirectory to add/set/remove users in bulk return [ordered] @{ data = $Body method = 'POST' bulkid = $Body.userName } } $invokeRestMethodSplat = [ordered] @{ Method = 'POST' Uri = 'https://api.federated.directory/v2/Users' Headers = [ordered] @{ 'Content-Type' = 'application/json; charset=utf-8' 'Authorization' = $Authorization.Authorization 'Cache-Control' = 'no-cache' } Body = $Body | ConvertTo-Json -Depth 10 ErrorAction = 'Stop' } if ($DirectoryID) { $invokeRestMethodSplat['Headers']['directoryId'] = $DirectoryID } # for troubleshooting if ($VerbosePreference -eq 'Continue') { $Body | ConvertTo-Json -Depth 10 | Write-Verbose } if ($PSCmdlet.ShouldProcess("username $UserName, displayname $DisplayName", "Adding user")) { $ReturnData = Invoke-RestMethod @invokeRestMethodSplat -Verbose:$false # don't return data as we trust it's been created if (-not $Suppress) { $ReturnData } } # # for troubleshooting # if ($VerbosePreference -eq 'Continue') { # $invokeRestMethodSplat.Remove('body') # $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose # } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue if ($ErrorDetails.Detail -like '*already exists*directory*') { Write-Warning -Message "Add-FederatedDirectoryUser - $($ErrorDetails.Detail) [UserName: $UserName / DisplayName: $DisplayName]" } else { Write-Warning -Message "Add-FederatedDirectoryUser - Error $($_.Exception.Message), $($ErrorDetails.Detail)" } } } } else { Write-Warning -Message 'Add-FederatedDirectoryUser - No authorization found. Please make sure to use Connect-FederatedDirectory first.' } } function Connect-FederatedDirectory { <# .SYNOPSIS Connects to a federated directory. .DESCRIPTION Connects to a federated directory. .PARAMETER Token The token to use for authentication to the federated directory from New-JWT command. This is the default. .PARAMETER TokenEncrypted The encrypted token to use for authentication to the federated directory from New-JWT command. .PARAMETER ExpiresTimeout The number of seconds before the token expires. .PARAMETER ForceRefresh Forces a refresh of the authentication .PARAMETER Suppress Suppresses the output of the command. By default the command will output the connection information. .EXAMPLE $Token = 'TokenInformation' Connect-FederatedDirectory -Token $Token -Suppress .NOTES General notes #> [alias('Connect-FD')] [cmdletbinding(DefaultParameterSetName = 'ClearText')] param( [Parameter(Mandatory, ParameterSetName = 'ClearText')] [alias('ApplicationSecret', 'ApplicationKey')] [string] $Token, [Parameter(Mandatory, ParameterSetName = 'Encrypted')] [alias('ApplicationSecretEncrypted', 'ApplicationKeyEncrypted')] [string] $TokenEncrypted, [int] $ExpiresTimeout = 30, [switch] $ForceRefresh, [switch] $Suppress ) if (-not $Script:AuthorizationCacheFD) { $Script:AuthorizationCacheFD = [ordered] @{} } if ($TokenEncrypted) { try { $ApplicationKeyTemp = $ClientSecretEncrypted | ConvertTo-SecureString -ErrorAction Stop } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-ILM - Error: $ErrorMessage" return } } $ApplicationKey = [System.Net.NetworkCredential]::new([string]::Empty, $ApplicationKeyTemp).Password } else { $ApplicationKey = $Token } $ShortKey = $ApplicationKey.Trim(15) $RestSplat = @{ ErrorAction = 'Stop' Method = 'POST' Body = @{ "grant_type" = "urn:ietf:params:oauth:grant-type:jwt-bearer" "assertion" = $ApplicationKey } Uri = 'https://api.federated.directory/v2/Login/Oauth2/Token' } if ($Script:AuthorizationCacheFD[$ShortKey] -and -not $ForceRefesh) { if ($Script:AuthorizationCacheFD[$ShortKey].ExpiresOn -gt [datetime]::UtcNow) { Write-Verbose "Connect-FederatedDirectory - Using cache for $ShortKey..." if (-not $Suppress) { return $Script:AuthorizationCacheFD[$ShortKey] } } } try { $Authorization = Invoke-RestMethod @RestSplat $Key = [ordered] @{ 'Authorization' = "$($Authorization.token_type) $($Authorization.access_token)" 'Extended' = $Authorization 'Error' = '' 'ExpiresOn' = ([datetime]::UtcNow).AddSeconds($Authorization.expires_in - $ExpiresTimeout) 'Splat' = [ordered] @{ Token = $RestSplat['Body']['assertion'] } 'Platform' = $Platform } $Script:AuthorizationCacheFD[$ShortKey] = $Key } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-ILM - Error: $ErrorMessage" $Key = [ordered] @{ 'Authorization' = $Null 'Extended' = $Null 'Error' = $ErrorMessage 'ExpiresOn' = $null 'Splat' = [ordered] @{ Token = $RestSplat['Body']['assertion'] } 'Platform' = $Platform } } } if (-not $Suppress) { $Key } } function Get-FederatedDirectorySchema { <# .SYNOPSIS Get the schema of a federated directory. .DESCRIPTION Get the schema of a federated directory. .EXAMPLE $Schema = Get-FederatedDirectorySchema $Schema | Where-Object { $_.Name -eq 'User' } | Select-Object -ExpandProperty Attributes | Format-Table $Schema | Where-Object { $_.Name -eq 'EnterpriseUser' } | Select-Object -ExpandProperty Attributes | Format-Table $Schema | Where-Object { $_.Name -eq 'FederatedDirectoryUser' } | Select-Object -ExpandProperty Attributes | Format-Table .NOTES General notes #> [alias('Get-FDSchema')] [cmdletbinding()] param() $BaseUri = "https://api.federated.directory/v2/Schemas" Write-Verbose -Message "Get-FederatedDirectorySchema - Using query: $BaseUri" $Headers = @{ 'Content-Type' = 'application/json' } Try { $BatchObjects = Invoke-RestMethod -Method Get -Uri $BaseUri -Headers $Headers -ErrorAction Stop $BatchObjects.Resources } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue Write-Warning -Message "Get-FederatedDirectorySchema - Error $($_.Exception.Message), $($ErrorDetails.Detail)" } } } function Get-FederatedDirectoryUser { [alias('Get-FDUser')] [cmdletbinding()] param( [System.Collections.IDictionary] $Authorization, [string] $Id, [Alias('SearchUserName')][string] $UserName, [string] $DirectoryID, [int] $MaxResults, [int] $StartIndex = 1, [int] $Count = 50, [string] $Filter, [ValidateSet( 'id', 'externalId', 'userName', 'givenName', 'familyName', 'displayName', 'nickName', 'profileUrl', 'title', 'userType', 'emails', 'phoneNumbers', 'addresses', 'preferredLanguage', 'locale', 'timezone', 'active', 'groups', 'roles', 'meta', 'organization', 'employeeNumber', 'costCenter', 'division', 'department', 'manager', 'description', 'directoryId', 'companyId', 'companyLogos', 'custom01', 'custom02', 'custom03' )] [string] $SortBy, [ValidateSet('ascending', 'descending')][string] $SortOrder, [Alias('Property')] [ValidateSet( 'id', 'externalId', 'userName', 'givenName', 'familyName', 'displayName', 'nickName', 'profileUrl', 'title', 'userType', 'emails', 'phoneNumbers', 'addresses', 'preferredLanguage', 'locale', 'timezone', 'active', 'groups', 'roles', 'meta', 'organization', 'employeeNumber', 'costCenter', 'division', 'department', 'manager', 'description', 'directoryId', 'companyId', 'companyLogos', 'custom01', 'custom02', 'custom03' )] [string[]] $Attributes, [switch] $Native ) $ConvertAttributes = @{ 'id' = 'id' 'externalId' = 'externalId' 'userName' = 'userName' 'givenName' = 'name.givenName' 'familyName' = 'name.familyName' 'displayName' = 'displayName' 'nickName' = 'nickName' 'profileUrl' = 'profileUrl' 'title' = 'title' 'userType' = 'userType' 'emails' = 'emails' 'phoneNumbers' = 'phoneNumbers' 'addresses' = 'addresses' 'preferredLanguage' = 'preferredLanguage' 'locale' = 'locale' 'timezone' = 'timezone' 'active' = 'active' 'groups' = 'groups' 'roles' = 'roles' 'meta' = 'meta' 'organization' = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization' 'employeeNumber' = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber' 'costCenter' = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:costCenter' 'division' = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:division' 'department' = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department' 'manager' = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager' 'description' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:description' 'directoryId' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:directoryId' 'companyId' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:companyId' 'companyLogos' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:companyLogos' 'custom01' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:custom01' 'custom02' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:custom02' 'custom03' = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:custom03' } $AttributesConverted = foreach ($Attribute in $Attributes) { if ($ConvertAttributes[$Attribute]) { $ConvertAttributes[$Attribute] } } if ($SortBy) { $SortByConverted = $ConvertAttributes[$SortBy] } if (-not $Authorization) { if ($Script:AuthorizationCacheFD) { $Authorization = $Script:AuthorizationCacheFD[0] } if (-not $Authorization) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "No authorization found. Please run 'Connect-FederatedDirectory' first." } else { Write-Warning -Message "Get-FederatedDirectory - No authorization found. Please run 'Connect-FederatedDirectory' first." return } } } if ($Authorization) { if ($ID) { $BaseUri = "https://api.federated.directory/v2/Users/$ID" } else { $BaseUri = "https://api.federated.directory/v2/Users" } # Lets build up query $QueryParameter = [ordered] @{ count = if ($Count) { $Count } else { $null } startIndex = if ($StartIndex) { $StartIndex } else { $null } filter = if ($UserName) { # keep in mind regardless of used operator it will always revert back to co as per API (weird) "userName co $UserName" } else { $Filter } sortBy = $SortByConverted sortOrder = $SortOrder attributes = $AttributesConverted -join "," } # lets remove empty values to remove whatever user hasn't requested Remove-EmptyValue -Hashtable $QueryParameter # Lets build our url $Uri = Join-UriQuery -BaseUri $BaseUri -QueryParameter $QueryParameter Write-Verbose -Message "Get-FederatedDirectoryUser - Using query: $Uri" $Headers = @{ 'Content-Type' = 'application/json; charset=utf-8' 'Authorization' = $Authorization.Authorization 'directoryID' = $DirectoryID } Remove-EmptyValue -Hashtable $Headers Try { $BatchObjects = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -ErrorAction Stop #{id}?attributes={attributes}' } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue if ($ErrorDetails.Detail -like '*already exists*directory*') { Write-Warning -Message "Get-FederatedDirectoryUser - $($ErrorDetails.Detail) [UserName: $UserName / ID: $ID]" return } else { Write-Warning -Message "Get-FederatedDirectoryUser - Error $($_.Exception.Message), $($ErrorDetails.Detail)" return } } } if ($BatchObjects.Resources) { Write-Verbose -Message "Get-FederatedDirectoryUser - Got $($BatchObjects.Resources.Count) users (StartIndex: $StartIndex, Count: $Count). Starting to process them." if ($MaxResults -gt 0 -and $BatchObjects.Resources.Count -ge $MaxResults) { # return users if amount of users available is more than we wanted if ($Native) { $BatchObjects.Resources | Select-Object -First $MaxResults } else { Convert-FederatedUser -Users ($BatchObjects.Resources | Select-Object -First $MaxResults) } $LimitReached = $true } else { # return all users that were given in a batch if ($Native) { $BatchObjects.Resources } else { Convert-FederatedUser -Users $BatchObjects.Resources } } } elseif ($BatchObjects.Schemas -and $BatchObjects.id) { if ($Native) { $BatchObjects } else { Convert-FederatedUser -Users $BatchObjects } } else { Write-Verbose "Get-FederatedDirectoryUser - No users found" return } if (-not $Count -and -not $StartIndex) { # paging is disabled, we don't do anything } elseif (-not $LimitReached -and $BatchObjects.TotalResults -gt $BatchObjects.StartIndex + $Count) { # lets get more users because there's more to get and user wanted more $MaxResults = $MaxResults - $BatchObjects.Resources.Count Write-Verbose "Get-FederatedDirectoryUser - Processing more pages (StartIndex: $StartIndex, Count: $Count)." $getFederatedDirectoryUserSplat = @{ Authorization = $Authorization StartIndex = $($BatchObjects.StartIndex + $Count) Count = $Count MaxResults = $MaxResults Filter = $Filter SortBy = $SortBy SortOrder = $SortOrder Attributes = $Attributes DirectoryID = $DirectoryID } Remove-EmptyValue -Hashtable $getFederatedDirectoryUserSplat Get-FederatedDirectoryUser @getFederatedDirectoryUserSplat } } else { Write-Warning -Message 'Get-FederatedDirectoryUser - No authorization found. Please make sure to use Connect-FederatedDirectory first.' } } # $Script:AttributesList = @( # 'id', # 'externalId', # 'userName', # 'givenName', # 'familyName', # 'displayName', # 'nickName', # 'profileUrl', # 'title', # 'userType', # 'emails', # 'phoneNumbers', # 'addresses', # 'preferredLanguage', # 'locale', # 'timezone', # 'active', # 'groups', # 'roles', # 'meta', # 'organization', # 'employeeNumber', # 'costCenter', # 'division', # 'department', # 'manager', # 'description', # 'directoryId', # 'companyId', # 'companyLogos' # 'custom01', # 'custom02', # 'custom03' # ) # $Script:ScriptBlockAttributes = { # param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) # $Script:AttributesList | Where-Object { $_ -like "*$wordToComplete*" } # } # Register-ArgumentCompleter -CommandName Get-FederatedDirectoryUser -ParameterName Attributes -ScriptBlock $Script:ScriptBlockAttributes # Register-ArgumentCompleter -CommandName Get-FederatedDirectoryUser -ParameterName SortBy -ScriptBlock $Script:ScriptBlockAttributes function Invoke-FederatedDirectory { [cmdletbinding(SupportsShouldProcess)] param( [System.Collections.IDictionary] $Authorization, [Array] $Operations ) if (-not $Authorization) { if ($Script:AuthorizationCacheFD) { $Authorization = $Script:AuthorizationCacheFD[0] } if (-not $Authorization) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "No authorization found. Please run 'Connect-FederatedDirectory' first." } else { Write-Warning -Message "Invoke-FederatedDirectory - No authorization found. Please run 'Connect-FederatedDirectory' first." return } } } if ($Authorization) { $Body = [ordered] @{ schemas = @('urn:ietf:params:scim:api:messages:2.0:BulkRequest') Operations = $Operations } Remove-EmptyValue -Hashtable $Body -Recursive -Rerun 2 Try { $invokeRestMethodSplat = [ordered] @{ Method = 'POST' Uri = 'https://api.federated.directory/v2/Bulk' Headers = [ordered] @{ 'Content-Type' = 'application/json; charset=utf-8' 'Authorization' = $Authorization.Authorization 'Cache-Control' = 'no-cache' } Body = $Body | ConvertTo-Json -Depth 10 ErrorAction = 'Stop' } if ($DirectoryID) { $invokeRestMethodSplat['Headers']['directoryId'] = $DirectoryID } # for troubleshooting if ($VerbosePreference -eq 'Continue') { $Body | ConvertTo-Json -Depth 10 | Write-Verbose } if ($PSCmdlet.ShouldProcess("Federated Directory", "Bulk sending $($Operations.Count) operations")) { $ReturnData = Invoke-RestMethod @invokeRestMethodSplat -Verbose:$false # don't return data as we trust it's been created if (-not $Suppress) { $ReturnData.Operations.Response } } # # for troubleshooting # if ($VerbosePreference -eq 'Continue') { # $invokeRestMethodSplat.Remove('body') # $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose # } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue Write-Warning -Message "Invoke-FederatedDirectory - Error $($_.Exception.Message), $($ErrorDetails.Detail)" } } } else { Write-Warning -Message 'Invoke-FederatedDirectory - No authorization found. Please make sure to use Connect-FederatedDirectory first.' } } function Remove-FederatedDirectoryUser { <# .SYNOPSIS Remove a user from a federated directory. .DESCRIPTION Remove a user from a federated directory. .PARAMETER Authorization The authorization identity to use for the request from Connect-FederatedDirectory. If not specified, the default authorization identity will be used. .PARAMETER User The user to remove from the federated directory. .PARAMETER Id The id of the user to remove from the federated directory. .PARAMETER SearchUserName The user name of the user to remove from the federated directory. .PARAMETER DirectoryID The id of the directory to remove the user from. If not specified, the default directory will be used. .EXAMPLE # remove specific user id Remove-FederatedDirectoryUser -Id '171a8cd0-2382-11ed-9dd1-b13400d703b6' -Verbose .EXAMPLE # get all ther users that contain name test user and delete them Remove-FederatedDirectoryUser -UserName 'testuser' -Verbose .EXAMPLE # get all ther users that contain name test user and delete them Get-FederatedDirectoryUser -UserName 'testuser' | Remove-FederatedDirectoryUser -Verbose .NOTES General notes #> [alias('Remove-FDUser')] [CmdletBinding(DefaultParameterSetName = 'Id', SupportsShouldProcess)] param( [System.Collections.IDictionary] $Authorization, [parameter(Position = 0, ValueFromPipeline, Mandatory, ParameterSetName = 'User')][PSCustomObject[]] $User, [parameter(Mandatory, ParameterSetName = 'Id')][string[]] $Id, [parameter(Mandatory, ParameterSetName = 'UserName')][string[]] $SearchUserName, [parameter()][string] $DirectoryID, [switch] $BulkProcessing ) Begin { if (-not $Authorization) { if ($Script:AuthorizationCacheFD) { $Authorization = $Script:AuthorizationCacheFD[0] } if (-not $Authorization) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "No authorization found. Please run 'Connect-FederatedDirectory' first." } else { Write-Warning -Message "Get-FederatedDirectory - No authorization found. Please run 'Connect-FederatedDirectory' first." return } } } } Process { if ($Authorization) { if ($Id) { $RemoveID = $Id } elseif ($User) { $RemoveID = $User.Id } elseif ($SearchUserName) { $RemoveID = Foreach ($U in $SearchUserName) { (Get-FederatedDirectoryUser -Authorization $Authorization -UserName $U).Id } } else { return } foreach ($I in $RemoveID) { Try { if ($BulkProcessing) { # Return body is used for using Invoke-FederatedDirectory to add/set/remove users in bulk return [ordered] @{ data = @{ schemas = @("urn:ietf:params:scim:schemas:core:2.0:User") id = $I } method = 'DELETE' bulkid = $I } } $invokeRestMethodSplat = [ordered] @{ Method = 'DELETE' Uri = "https://api.federated.directory/v2/Users/$I" Headers = [ordered] @{ 'Content-Type' = 'application/json' 'Authorization' = $Authorization.Authorization 'Cache-Control' = 'no-cache' 'directoryId' = $DirectoryID } ErrorAction = 'Stop' } Remove-EmptyValue -Hashtable $invokeRestMethodSplat -Recursive if ($VerbosePreference -eq 'Continue') { $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose } if ($PSCmdlet.ShouldProcess($I, "Removing user")) { $ReturnData = Invoke-RestMethod @invokeRestMethodSplat $ReturnData } } catch { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue if ($ErrorDetails.Detail -like "*not found*") { Write-Warning -Message "Remove-FederatedDirectoryUser - $($ErrorDetails.Detail)." } else { Write-Warning -Message "Remove-FederatedDirectoryUser - Error $($_.Exception.Message), $($ErrorDetails.Detail)" } } } } else { Write-Warning -Message 'Remove-FederatedDirectoryUser - No authorization found. Please make sure to use Connect-FederatedDirectory first.' } } } function Set-FederatedDirectoryUser { [alias('Set-FDUser')] [CmdletBinding(SupportsShouldProcess)] param( [System.Collections.IDictionary] $Authorization, [string] $SearchUserName, [string] $Id, [string] $ExternalId, [parameter()][string] $DirectoryID, [parameter()][string] $UserName, [Alias('FirstName')] $FamilyName, [string] $GivenName, [parameter()][string] $DisplayName, [string] $NickName, [string] $ProfileUrl, [string] $EmailAddress, [string] $EmailAddressHome, [string] $StreetAddress, [string] $City, [string] $Region, [string] $PostalCode, [string] $Country, [string] $StreetAddressHome, [string] $PostalCodeHome, [string] $CityHome, [string] $RegionHome, [string] $CountryHome, [string] $PhoneNumberWork, [string] $PhoneNumberHome, [string] $PhoneNumberMobile, [string] $PhotoUrl, [string] $ThumbnailUrl, [string] $CompanyID, #[string] $CompanyLogoUrl, #[string] $CompanyThumbnailUrl, [string] $PreferredLanguage, [string] $Locale, [string] $TimeZone, [string] $Title, [string] $UserType, [string] $Password, [string] $ManagerID, [string] $ManagerUserName, [string] $ManagerReference, [string] $ManagerDisplayName, [bool] $Active, #[string] $Organization, [string] $Department, [string] $EmployeeNumber, [string] $CostCenter, [string] $Division, [string] $Description, [ValidateSet('admin', 'user')][string] $Role, [alias('CustomAttribute01')][string] $Custom01, [alias('CustomAttribute02')][string] $Custom02, [alias('CustomAttribute03')][string] $Custom03, [switch] $Suppress, [ValidateSet('Overwrite', 'Update')][string] $Action = 'Update', [System.Collections.IDictionary] $ActionPerProperty = @{}, [switch] $BulkProcessing ) if (-not $Authorization) { if ($Script:AuthorizationCacheFD) { $Authorization = $Script:AuthorizationCacheFD[0] } if (-not $Authorization) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "No authorization found. Please run 'Connect-FederatedDirectory' first." } else { Write-Warning -Message "Set-FederatedDirectoryUser - No authorization found. Please run 'Connect-FederatedDirectory' first." return } } } if ($Authorization) { if ($Id) { $SetID = $Id } elseif ($User) { $SetID = $User.Id } elseif ($SearchUserName) { $SetID = Foreach ($U in $SearchUserName) { (Get-FederatedDirectoryUser -Authorization $Authorization -UserName $U).Id } } else { return } if ($Action -eq 'Update') { $TranslatePath = @{ UserName = "userName" ExternalId = "externalId" FamilyName = "name.familyName" Password = "password" # not used yet # not used yet Role = "roles.value" # "admin" or "user" GivenName = 'name.givenName' DisplayName = 'displayName' NickName = 'nickName' ProfileUrl = 'profileUrl' EmailAddress = 'emails[type eq "work"].value' EmailAddressHome = 'emails[type eq "home"].value' StreetAddress = 'addresses[type eq "work"].streetAddress' City = 'addresses[type eq "work"].locality' Region = 'addresses[type eq "work"].region' PostalCode = 'addresses[type eq "work"].postalCode' Country = 'addresses[type eq "work"].country' StreetAddressHome = 'addresses[type eq "home"].streetAddress' PostalCodeHome = 'addresses[type eq "home"].postalCode' CityHome = 'addresses[type eq "home"].locality' RegionHome = 'addresses[type eq "home"].region' CountryHome = 'addresses[type eq "home"].country' PhoneNumberWork = 'phoneNumbers[type eq "work"].value' PhoneNumberHome = 'phoneNumbers[type eq "home"].value' PhoneNumberMobile = 'phoneNumbers[type eq "mobile"].value' PhotoUrl = 'photos[type eq "photo"].value' ThumbnailUrl = 'photos[type eq "thumbnail"].value' Title = 'title' UserType = 'userType' Active = 'active' TimeZone = 'timezone' Locale = 'locale' PreferredLanguage = 'preferredLanguage' EmployeeNumber = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber' CostCenter = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:costCenter' Division = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:division' Department = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department' Organization = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization' ManagerID = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager.value' ManagerUserName = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager.value' ManagerDisplayName = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager.displayName' Description = "urn:ietf:params:scim:schemas:extension:fd:2.0:User:description" Custom01 = "urn:ietf:params:scim:schemas:extension:fd:2.0:User:custom01" Custom02 = "urn:ietf:params:scim:schemas:extension:fd:2.0:User:custom02" Custom03 = "urn:ietf:params:scim:schemas:extension:fd:2.0:User:custom03" CompanyID = "urn:ietf:params:scim:schemas:extension:fd:2.0:User:companyId" #CompanyLogoUrl = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:companyLogos[type eq "logo"].value' #CompanyThumbnailUrl = 'urn:ietf:params:scim:schemas:extension:fd:2.0:User:companyLogos[type eq "thumbnail"].value' } if ($ManagerUserName) { $ManagerID = (Get-FederatedDirectoryUser -Authorization $Authorization -UserName $ManagerUserName).Id } $Body = [ordered] @{ schemas = @( "urn:ietf:params:scim:api:messages:2.0:PatchOp" ) Operations = @( foreach ($Key in $PSBoundParameters.Keys) { if ($Key -in $TranslatePath.Keys) { if ($TranslatePath[$Key]) { $Path = $TranslatePath[$Key] } else { $Path = $Key } if ($PSBoundParameters[$Key]) { $Value = $PSBoundParameters[$Key] } else { $Value = $null } if ($ActionPerProperty) { if ($ActionPerProperty[$Key]) { $ActionProperty = $ActionPerProperty[$Key] } else { $ActionProperty = 'replace' } } else { $ActionProperty = 'replace' } [ordered] @{ op = $ActionProperty path = $Path value = $Value } } } ) } } else { $Body = [ordered] @{ schemas = @( "urn:ietf:params:scim:schemas:core:2.0:User" "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" "urn:ietf:params:scim:schemas:extension:fd:2.0:User" ) # Mandatory "id" = $Id "externalId" = $ExternalId "userName" = $UserName "name" = [ordered] @{ "familyName" = $FamilyName "givenName" = $GivenName } "displayName" = $DisplayName "nickName" = $NickName "profileUrl" = $ProfileUrl "emails" = @( if ($EmailAddress) { @{ "value" = $EmailAddress "type" = "work" "primary" = $true } } if ($EmailAddressHome) { @{ "value" = $EmailAddressHome "type" = "home" } } ) "addresses" = @( if ($StreetAddress -or $Locality -or $Region -or $PostalCode -or $Country) { @{ "streetAddress" = $StreetAddress "locality" = $Locality "region" = $Region "postalCode" = $PostalCode "country" = $Country "type" = "work" "primary" = $true } } if ($StreetAddressHome -or $LocalityHome -or $RegionHome -or $PostalCodeHome -or $CountryHome) { @{ "streetAddress" = $StreetAddressHome "locality" = $LocalityHome "region" = $RegionHome "postalCode" = $PostalCodeHome "country" = $CountryHome "type" = "home" } } ) "phoneNumbers" = @( if ($PhoneNumberWork) { @{ "value" = $PhoneNumberWork "type" = "work" "primary" = $true } } if ($PhoneNumberHome) { @{ "value" = $PhoneNumberHome "type" = "home" } } if ($PhoneNumberMobile) { @{ "value" = $PhoneNumberMobile "type" = "mobile" } } ) "photos" = @( if ($PhotoUrl) { @{ "value" = $PhotoUrl "type" = "photo" } } if ($ThumbnailUrl) { @{ "value" = $ThumbnailUrl "type" = "thumbnail" } } ) "password" = $Password "preferredLanguage" = $PreferredLanguage "locale" = $Locale "timeZone" = $TimeZone "userType" = $UserType "title" = $Title "active" = if ($PSBoundParameters.Keys -contains ('Active')) { $Active.IsPresent } else { $Null } "roles" = @( @{ "value" = $Role "display" = $Role } ) "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" = [ordered] @{ #"organization" = $Organization # read only... "department" = $Department "employeeNumber" = $EmployeeNumber "costCenter" = $CostCenter "division" = $Division "manager" = @{ "displayName" = $ManagerDisplayName "value" = $ManagerID } } "urn:ietf:params:scim:schemas:extension:fd:2.0:User" = [ordered] @{ "description" = $Description "companyId" = $CompanyId # "companyLogos" = @( # if ($CompanyLogoUrl) { # @{ # "value" = $CompanyLogoUrl # "type" = "logo" # } # } # if ($CompanyThumbnailUrl) { # @{ # "value" = $CompanyThumbnailUrl # "type" = "thumbnail" # } # } # ) #'directoryId' = $DirectoryID 'custom01' = $Custom01 'custom02' = $Custom02 'custom03' = $Custom03 } } } Try { Remove-EmptyValue -Hashtable $Body -Recursive -Rerun 2 $MethodChosen = if ($Action -eq 'Update') { 'PATCH' } else { 'PUT' } if ($BulkProcessing) { # Return body is used for using Invoke-FederatedDirectory to add/set/remove users in bulk return [ordered] @{ data = $Body method = $MethodChosen bulkid = $Body.id } } $invokeRestMethodSplat = [ordered] @{ Method = $MethodChosen Uri = "https://api.federated.directory/v2/Users/$SetID" Headers = [ordered] @{ 'Content-Type' = 'application/json' 'Authorization' = $Authorization.Authorization 'Cache-Control' = 'no-cache' } Body = $Body | ConvertTo-Json -Depth 10 ErrorAction = 'Stop' } if ($DirectoryID) { $invokeRestMethodSplat['Headers']['directoryId'] = $DirectoryID } # for troubleshooting if ($VerbosePreference -eq 'Continue') { $Body | ConvertTo-Json -Depth 10 | Write-Verbose } if ($PSCmdlet.ShouldProcess($SetID, "Updating user using $Action method")) { $ReturnData = Invoke-RestMethod @invokeRestMethodSplat # don't return data as we trust it's been updated if (-not $Suppress) { $ReturnData } } # # for troubleshooting # if ($VerbosePreference -eq 'Continue') { # $invokeRestMethodSplat.Remove('body') # $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose # } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue if ($ErrorDetails.Detail -like '*userName is mandatory*') { Write-Warning -Message "Set-FederatedDirectoryUser - $($ErrorDetails.Detail) [Id: $SetID]" } else { Write-Warning -Message "Set-FederatedDirectoryUser - Error $($_.Exception.Message), $($ErrorDetails.Detail) [Id: $SetID]" } } } } else { Write-Warning -Message 'Set-FederatedDirectoryUser - No authorization found. Please make sure to use Connect-FederatedDirectory first.' } } # Export functions and aliases as required Export-ModuleMember -Function @('Add-FederatedDirectoryUser', 'Connect-FederatedDirectory', 'Get-FederatedDirectorySchema', 'Get-FederatedDirectoryUser', 'Invoke-FederatedDirectory', 'Remove-FederatedDirectoryUser', 'Set-FederatedDirectoryUser') -Alias @('Add-FDUser', 'Connect-FD', 'Get-FDSchema', 'Get-FDUser', 'Remove-FDUser', 'Set-FDUser') # SIG # Begin signature block # MIInPgYJKoZIhvcNAQcCoIInLzCCJysCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCuZAuZdaoBE2HZ # 7sa0Qr3fWIuRk1eyeN+Jj5Oh/CPkyqCCITcwggO3MIICn6ADAgECAhAM5+DlF9hG # /o/lYPwb8DA5MA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBa # Fw0zMTExMTAwMDAwMDBaMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lD # ZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC # AQoCggEBAK0OFc7kQ4BcsYfzt2D5cRKlrtwmlIiq9M71IDkoWGAM+IDaqRWVMmE8 # tbEohIqK3J8KDIMXeo+QrIrneVNcMYQq9g+YMjZ2zN7dPKii72r7IfJSYd+fINcf # 4rHZ/hhk0hJbX/lYGDW8R82hNvlrf9SwOD7BG8OMM9nYLxj+KA+zp4PWw25EwGE1 # lhb+WZyLdm3X8aJLDSv/C3LanmDQjpA1xnhVhyChz+VtCshJfDGYM2wi6YfQMlqi # uhOCEe05F52ZOnKh5vqk2dUXMXWuhX0irj8BRob2KHnIsdrkVxfEfhwOsLSSplaz # vbKX7aqn8LfFqD+VFtD/oZbrCF8Yd08CAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEXroq/0ksuCMS1Ri6enIZ3zbcgP # MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA # A4IBAQCiDrzf4u3w43JzemSUv/dyZtgy5EJ1Yq6H6/LV2d5Ws5/MzhQouQ2XYFwS # TFjk0z2DSUVYlzVpGqhH6lbGeasS2GeBhN9/CTyU5rgmLCC9PbMoifdf/yLil4Qf # 6WXvh+DfwWdJs13rsgkq6ybteL59PyvztyY1bV+JAbZJW58BBZurPSXBzLZ/wvFv # hsb6ZGjrgS2U60K3+owe3WLxvlBnt2y98/Efaww2BxZ/N3ypW2168RJGYIPXJwS+ # S86XvsNnKmgR34DnDDNmvxMNFG7zfx9jEB76jRslbWyPpbdhAbHSoyahEHGdreLD # +cOZUbcrBwjOLuZQsqf6CkUvovDyMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1 # b5VQCDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln # aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtE # aWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgx # MDIyMTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j # MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT # SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEF # AAOCAQ8AMIIBCgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLX # cep2nQUut4/6kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSR # I5aQd4L5oYQjZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXi # TWAYvqrEsq5wMWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5 # Ng2Q7+S1TqSp6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8 # vYWxYoNzQYIH5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYD # VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB # BQUHAwMweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k # aWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4 # oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv # b3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dEFzc3VyZWRJRFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCow # KAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZI # AYb9bAMwHQYDVR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaA # FEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPz # ItEVyCx8JSl2qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRu # pY5a4l4kgU4QpO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKN # JK4kxscnKqEpKBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmif # z0DLQESlE/DmZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN # 3fYBIM6ZMWM9CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKy # ZqHnGKSaZFHvMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkqhkiG # 9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw # FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEy # IEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoXDTIz # MDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tpZTER # MA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlzIEVW # T1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqDBqln # r3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfFjVye # 3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub+3ti # i0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf3tZZ # zO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6Ea41 # zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQABo4IB # xTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYE # FBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK # BggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2Vy # dC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQu # ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3 # BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu # Y29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25p # bmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1sz4ls # LARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsRXPHU # F/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHINrTC # vPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8Rj9y # G4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6o6ES # Jre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNpezQu # g9ufqExx6lHYDjCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZI # hvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ # MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNz # dXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVow # YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290 # IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjww # IjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J5 # 8soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMH # hOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6 # Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQ # ecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4b # A3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9 # WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCU # tNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvo # ZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/J # vNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCP # orF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMB # Af8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXr # oq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRt # MGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEF # BQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl # ZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgw # BgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cH # vZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8 # UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTn # f+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxU # jG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8j # LfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDCCBq4w # ggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4X # DTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAV # BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVk # IEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5M # om2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE # 2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWN # lCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFo # bjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhN # ef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3Vu # JyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtz # Q87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4O # uGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5 # sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm # 4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIz # tM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6 # FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qY # rhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYB # BQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w # QQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwz # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZ # MBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmO # wJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H # 6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/ # R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzv # qLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/ae # sXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdm # kfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3 # EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh # 3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA # 3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8 # BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsf # gPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbAMIIEqKADAgECAhADyzT9 # Pf8SETOf8HxLIVfHMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYD # VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH # NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjIwODMwMDAwMDAw # WhcNMjMwODI5MjM1OTU5WjBGMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNl # cnQxJDAiBgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIyIC0gMjCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBAM/spSY6xqnya7uNwQ2a26HoFIV0Mxom # rNAcVR4eNm28klUMYfSdCXc9FZYIL2tkpP0GgxbXkZI4HDEClvtysZc6Va8z7GGK # 6aYo25BjXL2JU+A6LYyHQq4mpOS7eHi5ehbhVsbAumRTuyoW51BIu4hpDIjG8b7g # L307scpTjUCDHufLckkoHkyAHoVW54Xt8mG8qjoHffarbuVm3eJc9S/tjdRNlYRo # 44DLannR0hCRRinrPibytIzNTLlmyLuqUDgN5YyUXRlav/V7QG5vFqianJVHhoV5 # PgxeZowaCiS+nKrSnLb3T254xCg/oxwPUAY3ugjZNaa1Htp4WB056PhMkRCWfk3h # 3cKtpX74LRsf7CtGGKMZ9jn39cFPcS6JAxGiS7uYv/pP5Hs27wZE5FX/NurlfDHn # 88JSxOYWe1p+pSVz28BqmSEtY+VZ9U0vkB8nt9KrFOU4ZodRCGv7U0M50GT6Vs/g # 9ArmFG1keLuY/ZTDcyHzL8IuINeBrNPxB9ThvdldS24xlCmL5kGkZZTAWOXlLimQ # prdhZPrZIGwYUWC6poEPCSVT8b876asHDmoHOWIZydaFfxPZjXnPYsXs4Xu5zGcT # B5rBeO3GiMiwbjJ5xwtZg43G7vUsfHuOy2SJ8bHEuOdTXl9V0n0ZKVkDTvpd6kVz # HIR+187i1Dp3AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/ # BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEE # AjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8w # HQYDVR0OBBYEFGKK3tBh/I8xFO2XC809KpQU31KcMFoGA1UdHwRTMFEwT6BNoEuG # SWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQw # OTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKG # TGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJT # QTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIB # AC0UyaEGSS3dimxaHgXjrMnYnjeKsKYhIj9EyjE9ywwM33xT5ZRqdiX3Isk7nEIE # lPWCRN5u4oTo7k5EGGktx3ZsrHpzf0siEEmEdDfygtNBlXYxLvlZab8HVrslWfex # M+66XRCFK19PgSnudu0gC3XaxWbC6eAeWmgBTLRktDRpqbY9fj1d6REtuXxf4RNr # N0MDT+kVDdt1BVTHDTlfGDbA6HAXR1Vc+khF8cv4RMJ8vvP3p6z05qFttPe3RMWP # CC+d8hKtJI+2C3hBwdKChzJizkfq60Vrqqj+dEeBnrUYhUcYIIz6WeVYk72r/31a # 9SowYPuTzNCktU59LF6Y2/bMPIpHeHhsBAvg2RMxDzH4TfzgKkGM8F8VDpTAKUXe # 8vlzzsNjJ4m+oeGi72Kj6if/M07iiT4kMEQV5Fg8BotKdIqx7a1Cf+aqpZq5+DAc # FhPwo4uoKtSLAWY0aIACxRKSFqIHngiuc2t9n+vB/oM/rtlQNnnlt8E2hvC3yQl5 # +M/7sqzX4vI3BBv6ASmOsDaYOGrb90BA77kpxccgavKscb/UdmJ+yGZjMyuuUzjP # pKpGxMG95S9ATieDVuDFi68taSY81PJVmxBD/MrBbfTZ9JBLS5F1s0ecKEr6OOY1 # PvLIry+8TrgnFUP5KT019GjiRV2GVCOBx9aBB9M+oTliMYIFXTCCBVkCAQEwgYYw # cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk # IElEIENvZGUgU2lnbmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzANBglghkgBZQME # AgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM # BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqG # SIb3DQEJBDEiBCAFA34JHpNGStjKQoVK6NO9jT3R5T9YOCnUIeWpopJ7ZjANBgkq # hkiG9w0BAQEFAASCAQBbFRkKOmEK775bPcZ40VEurroQRyRuxnmdw8C/kmgP1Sfb # 46AsACcQ+TQ8HrdVFhcjjMBXRiFP4DocXdA+dd0y/n4yAczfohDbvRGFE1Qrp7an # +W1DWdESJxd/iH5DiEQHmaDGWBkdjdX13BWPRIKgflluSFJajyn6EJU6uTly6XgL # bQabYKVDmO4gkfsqBLiXyXtR4q7AI6qcqm4khfcEhrKgM60hRjJiVKjt+JH6by1i # J70MibKW97CNEl9C1TNXY8ZB/mYmnG746WZ6rSfPWKksvj6rzao8qKDRwDwS6AzK # RJPed5op3b/SuiHxAkRii6BEG5YjjUU6JsZqW4+XoYIDIDCCAxwGCSqGSIb3DQEJ # BjGCAw0wggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0 # LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hB # MjU2IFRpbWVTdGFtcGluZyBDQQIQA8s0/T3/EhEzn/B8SyFXxzANBglghkgBZQME # AgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X # DTIyMDkxNjE4MTczOVowLwYJKoZIhvcNAQkEMSIEIFg2/DUqAoFqSE/xbPHncW21 # z1hslSbiHmxmghvujqBQMA0GCSqGSIb3DQEBAQUABIICAHjeh6XD8L9I0Hd9SLDq # ndWDJ5y6bWHIpnL421p9LkLrOJT8arTitylNx8/Ax6Utzzx2zcWfZUGXSvqOL4/Y # QDAaCgHu1yVyVZ9WyQlK0E2Oz5XkojG45VRGF1EbYoETMnLB38WJRL173jvt/FcC # X3ursxTkeG0D6haM1x9wH7a5RWpfJI6riiOLgeogmG37Rom8QD9dJlZW8BphjiV+ # sYpIy8TV9kn17+lJ2CrJ77Q74t5oSo+CUO0I2rDT7Eb8XdS3lZMK4gq3GqF0ahPW # eHB7CB3EXwRmOhDwybtgUcn7kZ5ud5dBGjBHtizjHU/i4+IvaovzZ0TFDR5tisP/ # QNpRbvVxL70uP0PS0idRD7JZSk8Nn7cc3IIzlZqDvsOOHaqM5k2NsNqZR4j5e2bW # Rxliy7CxITQPi6flbBz8VrAbIK9jFxv/9CyXOfWCmdHpzDbCUm6jnPu3KGC/6EuR # kYukJPemVhG8kLnXVp8pQw4v1QLX5H5Mumix8sm38EYxsbdB9YYrnWYhUsrm+bkh # PcMU7t8SGVd/LdAkIG0FIA0KuRbY3YB2POaZiI4YPVVZ+Tlpb2rHXPGTwppz9mJ4 # 5hocMNCycYLYEU++XYMxgoGOSmkJ7qmPxizvB39Wppqx6eLrEyC4u2rRV03YT3Vx # ASef1l1h2Ox4ezwlxFEXf0vZ # SIG # End signature block |