Graphimo.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 Add-GraphGroupMember { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [parameter(Mandatory)][alias('GroupID')][string] $ID, [parameter(Mandatory)][string] $MemberID ) $URI = "/groups/$ID/members/`$ref" $Body = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$MemberID" } Invoke-Graphimo -Uri $URI -Method POST -Headers $Headers -Body $Body } function Add-GraphUser { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [Parameter(Mandatory)][string] $UserPrincipalName, [string] $Name, [parameter(Mandatory)][alias('AccountEnabled')][bool] $Enabled, [alias('FirstName')][string] $GivenName, [alias('LastName')][string] $Surname, [alias('Title')][string] $JobTitle, [string] $EmployeeId, [string] $City, [Parameter(Mandatory)][string] $MailNickname, [alias('EmailAddress')][string] $Mail, [string] $Country, [string] $Department, [string] $PostalCode, [alias('Fax')][string] $FaxNumber, [string] $State, [string] $StreetAddress, [alias('OfficePhone')][string] $BusinessPhones, [alias('Mobile')][string] $MobilePhone, [string] $OfficeLocation, [string] $CompanyName, [Parameter(Mandatory)][string] $DisplayName, [switch] $ShowInAddressList, [switch] $DoNotForceChangePasswordNextSignIn, [Parameter(Mandatory)][string] $Password, [alias('HireDate')][DateTime] $StartDate, [alias('CustomProperty')][System.Collections.IDictionary] $CustomProperties ) $URI = "/users" $Body = [ordered]@{} # https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0 if ($PSBoundParameters.ContainsKey('StartDate')) { # requires fixing # The date and time when the user was hired or will start work in case of a future hire. $Body['employeeHireDate'] = $StartDate } if ($PSBoundParameters.ContainsKey('UserPrincipalName')) { $Body['userPrincipalName'] = $UserPrincipalName } if ($PSBoundParameters.ContainsKey('JobTitle')) { $Body['jobTitle'] = $JobTitle } if ($PSBoundParameters.ContainsKey('EmployeeId')) { $Body['employeeId'] = $EmployeeId } if ($PSBoundParameters.ContainsKey('MailNickname')) { $Body['mailNickname'] = $MailNickname } if ($PSBoundParameters.ContainsKey('Mail')) { $Body['mail'] = $Mail } if ($PSBoundParameters.ContainsKey('FaxNumber')) { $Body['faxNumber'] = $FaxNumber } if ($PSBoundParameters.ContainsKey('givenName')) { $Body['givenName'] = $givenName } if ($PSBoundParameters.ContainsKey('Surname')) { $Body['surname'] = $Surname } if ($PSBoundParameters.ContainsKey('City')) { $Body['city'] = $City } if ($PSBoundParameters.ContainsKey('Country')) { $Body['country'] = $Country } if ($PSBoundParameters.ContainsKey('Department')) { $Body['department'] = $Department } if ($PSBoundParameters.ContainsKey('PostalCode')) { $Body['postalCode'] = $PostalCode } if ($PSBoundParameters.ContainsKey('State')) { $Body['state'] = $State } if ($PSBoundParameters.ContainsKey('StreetAddress')) { $Body['streetAddress'] = $StreetAddress } if ($PSBoundParameters.ContainsKey('businessPhones')) { $Body['businessPhones'] = @($businessPhones) } if ($PSBoundParameters.ContainsKey('mobilePhone')) { $Body['mobilePhone'] = $mobilePhone } if ($PSBoundParameters.ContainsKey('OfficeLocation')) { $Body['officeLocation'] = $OfficeLocation } if ($PSBoundParameters.ContainsKey('CompanyName')) { $Body['companyName'] = $CompanyName } if ($PSBoundParameters.ContainsKey('DisplayName')) { $Body['displayName'] = $DisplayName } if ($PSBoundParameters.ContainsKey('ShowInAddressList')) { $Body['showInAddressList'] = $ShowInAddressList.IsPresent } if ($PSBoundParameters.ContainsKey('Enabled')) { $Body['accountEnabled'] = $Enabled } $Body['passwordProfile'] = @{ forceChangePasswordNextSignIn = -not $DoNotForceChangePasswordNextSignIn.IsPresent password = $Password } foreach ($Property in $CustomProperties.Keys) { $Body[$Property] = $CustomProperties[$Property] } #Remove-EmptyValue -Hashtable $Body if ($Body.Count -gt 0) { Invoke-Graphimo -Uri $URI -Method POST -Headers $Headers -Body $Body } } function Connect-Graphimo { [cmdletBinding(DefaultParameterSetName = 'ClearText')] param( [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(Mandatory, ParameterSetName = 'ClearText')][string][alias('ClientID')] $ApplicationID, [parameter(Mandatory, ParameterSetName = 'ClearText')][string][alias('ClientSecret')] $ApplicationKey, [parameter(Mandatory, ParameterSetName = 'Encrypted')][string][alias('ClientSecretEncrypted')] $ApplicationKeyEncrypted, [parameter(Mandatory, ParameterSetName = 'Credential')][PSCredential] $Credential, [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(Mandatory, ParameterSetName = 'ClearText')] [parameter(Mandatory, ParameterSetName = 'Credential')] [string] $TenantDomain, [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(ParameterSetName = 'ClearText')] [parameter(ParameterSetName = 'Credential')] [ValidateSet("https://manage.office.com", "https://graph.microsoft.com", "https://graph.microsoft.com/beta", 'https://graph.microsoft.com/.default')] $Resource = 'https://graph.microsoft.com/.default', [int] $ExpiresTimeout = 30, [switch] $ForceRefesh ) # Comparison V1/V2 https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/ if (-not $Script:AuthorizationCache) { $Script:AuthorizationCache = [ordered] @{} } if ($Credential) { $RestSplat = @{ ErrorAction = 'Stop' Method = 'POST' Body = @{ grant_type = "client_credentials" client_id = $Credential.UserName client_secret = $Credential.GetNetworkCredential().Password } } } elseif ($ApplicationKey -or $ApplicationKeyEncrypted) { if ($ApplicationKeyEncrypted) { try { $ApplicationKeyTemp = $ApplicationKeyEncrypted | ConvertTo-SecureString -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-Graphimo - Error: $ErrorMessage" return } $ApplicationKey = [System.Net.NetworkCredential]::new([string]::Empty, $ApplicationKeyTemp).Password } $RestSplat = @{ ErrorAction = 'Stop' Method = 'POST' Body = @{ grant_type = "client_credentials" client_id = $ApplicationID client_secret = $ApplicationKey } } } if ($Script:AuthorizationCache[$ApplicationID] -and -not $ForceRefesh) { if ($Script:AuthorizationCache[$ApplicationID].ExpiresOn -gt [datetime]::UtcNow) { Write-Verbose "Connect-Graphimo - Using cache for $ApplicationID" return $Script:AuthorizationCache[$ApplicationID] } } if ($Resource -in 'https://graph.microsoft.com/.default', "https://graph.microsoft.com/beta") { # V2 Endpoint $RestSplat['Body']['scope'] = $Resource $RestSplat['Uri'] = "https://login.microsoftonline.com/$($TenantDomain)/oauth2/v2.0/token" } else { # V1 Endpoint $RestSplat['Body']['resource'] = $Resource $RestSplat['Uri'] = "https://login.microsoftonline.com/$($TenantDomain)/oauth2/token" } Write-Verbose "Connect-Graphimo - EndPoint $($RestSplat['Uri'])" 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] @{ ApplicationID = $RestSplat['Body']['client_id'] ApplicationKey = $RestSplat['Body']['client_secret'] TenantDomain = $TenantDomain Resource = $Resource } } $Script:AuthorizationCache[$ApplicationID] = $Key } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-Graphimo - Error: $ErrorMessage" $Key = [ordered] @{ 'Authorization' = $Null 'Extended' = $Null 'Error' = $ErrorMessage 'ExpiresOn' = $null 'Splat' = [ordered] @{ ApplicationID = $RestSplat['Body']['client_id'] ApplicationKey = $RestSplat['Body']['client_secret'] TenantDomain = $TenantDomain Resource = $Resource } } } $Key } function Get-GraphApplication { [cmdletBinding()] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $ID, [string] $DisplayName, [string[]] $Property ) if ($ID) { # Query a single group $RelativeURI = "/applications/$ID" $QueryParameter = @{ '$Select' = $Property -join ',' } } elseif ($DisplayName) { $RelativeURI = '/applications' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = "displayName eq '$DisplayName'" '$orderby' = $OrderBy } } else { # Query multiple groups $RelativeURI = '/applications' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = $Filter '$orderby' = $OrderBy } } Remove-EmptyValue -Hashtable $QueryParameter Invoke-Graphimo -Uri $RelativeURI -Method GET -Headers $Headers -QueryParameter $QueryParameter } function Get-GraphContact { [cmdletBinding()] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $Id, [string[]] $Property, [string] $Filter, [string] $OrderBy ) if ($ID) { # Query a single group $RelativeURI = "/contacts/$ID" $QueryParameter = @{ '$Select' = $Property -join ',' } } else { # Query multiple groups $RelativeURI = '/contacts' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = $Filter '$orderby' = $OrderBy } } Remove-EmptyValue -Hashtable $QueryParameter Invoke-Graphimo -Uri $RelativeURI -Method GET -Headers $Headers -QueryParameter $QueryParameter } function Get-GraphGroup { [cmdletBinding()] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $Id, [string[]] $Property, [string] $Filter, [string] $OrderBy ) if ($ID) { # Query a single group $RelativeURI = "/groups/$ID" $QueryParameter = @{ '$Select' = $Property -join ',' } } else { # Query multiple groups $RelativeURI = '/groups' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = $Filter '$orderby' = $OrderBy } } Remove-EmptyValue -Hashtable $QueryParameter Invoke-Graphimo -Uri $RelativeURI -Method GET -Headers $Headers -QueryParameter $QueryParameter } function Get-GraphGroupMember { [cmdletBinding()] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [parameter(Mandatory)][string] $Id, [string] $Search, [string[]] $Property ) if ($ID) { # Query a single group $RelativeURI = "/groups/$ID/members" $QueryParameter = @{ '$Select' = $Property -join ',' '$Search' = $Search } if ($QueryParameter.'$Search') { # This is required for search to work # https://developer.microsoft.com/en-us/identity/blogs/build-advanced-queries-with-count-filter-search-and-orderby/ $Headers['ConsistencyLevel'] = 'eventual' } } Remove-EmptyValue -Hashtable $QueryParameter Invoke-Graphimo -Uri $RelativeURI -Method GET -Headers $Headers -QueryParameter $QueryParameter } function Get-GraphUser { [alias('Get-GraphUsers')] [cmdletBinding(DefaultParameterSetName = 'Default')] param( [parameter(ParameterSetName = 'Default', Mandatory)] [parameter(ParameterSetName = 'EmailAddress', Mandatory)] [parameter(ParameterSetName = 'UserPrincipalName', Mandatory)] [parameter(ParameterSetName = 'Filter', Mandatory)] [parameter(ParameterSetName = 'Id', Mandatory)] [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [parameter(ParameterSetName = 'Id')][string] $Id, [parameter(ParameterSetName = 'UserPrincipalName')][string] $UserPrincipalName, [alias('Mail')][parameter(ParameterSetName = 'EmailAddress')][string] $EmailAddress, [string[]] $Property, [parameter(ParameterSetName = 'Filter')][string] $Filter, [string] $OrderBy, [switch] $IncludeManager ) if ($UserPrincipalName) { $RelativeURI = '/users' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = "userPrincipalName eq '$UserPrincipalName'" } } elseif ($EmailAddress) { $RelativeURI = '/users' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = "mail eq '$EmailAddress'" } } elseif ($ID) { # Query a single user # doing it standard way doesn't seem to work so lets user filter instead #$RelativeURI = "/users/$ID" $RelativeURI = "/users" $QueryParameter = @{ '$filter' = "id eq '$ID'" '$Select' = $Property -join ',' } } else { # Query multiple groups $RelativeURI = '/users' $QueryParameter = @{ '$Select' = $Property -join ',' '$filter' = $Filter '$orderby' = $OrderBy } } if ($IncludeManager) { $QueryParameter['$expand'] = 'manager' } Remove-EmptyValue -Hashtable $QueryParameter Invoke-Graphimo -Uri $RelativeURI -Method GET -Headers $Headers -QueryParameter $QueryParameter } function Import-GraphGuest { [cmdletBinding()] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $Name, [Parameter(Mandatory)][string] $EmailAddress, [switch] $SendInvitationMessage, [string] $InviteRedirectUrl = "https://portal.office.com" ) $URI = '/invitations' $body = [ordered]@{ 'invitedUserDisplayName' = $Name 'invitedUserEmailAddress' = $EmailAddress 'inviteRedirectUrl' = $InviteRedirectUrl 'sendInvitationMessage' = $SendInvitationMessage.IsPresent } Invoke-Graphimo -Uri $URI -Method POST -Headers $Headers -Body $Body } function Invoke-Graphimo { [cmdletBinding(SupportsShouldProcess)] param( [alias('PrimaryUri')][uri] $BaseUri = 'https://graph.microsoft.com/v1.0', [uri] $Uri, [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [validateset('GET', 'DELETE', 'POST', 'PATCH', 'PUT')][string] $Method = 'GET', [string] $ContentType = "application/json; charset=UTF-8", [System.Collections.IDictionary] $Body, [System.Collections.IDictionary] $QueryParameter, [switch] $FullUri ) if ($Headers.MsalToken) { } else { # This forces a reconnect of session in case it's about to time out. If it's not timeouting a cache value is used if ($Headers.Splat) { $Splat = $Headers.Splat $Headers = Connect-Graphimo @Splat } } if ($Headers.Error) { Write-Warning "Invoke-Graphimo - Authorization error. Skipping." return } if ($Headers.MsalToken) { $RestSplat = @{ Headers = @{ Authorization = $Headers.MsalToken.TokenType + ' ' + $Headers.MsalToken.AccessToken } Method = $Method ContentType = $ContentType } } else { $RestSplat = @{ Headers = $Headers Method = $Method ContentType = $ContentType } } if ($Body) { $RestSplat['Body'] = $Body | ConvertTo-Json -Depth 5 } if ($FullUri) { $RestSplat.Uri = $Uri } else { $RestSplat.Uri = Join-UriQuery -BaseUri $BaseUri -RelativeOrAbsoluteUri $Uri -QueryParameter $QueryParameter } if ($RestSplat['Body']) { $WhatIfInformation = "Invoking [$Method] " + [System.Environment]::NewLine + $RestSplat['Body'] + [System.Environment]::NewLine } else { $WhatIfInformation = "Invoking [$Method] " } try { if ($Method -eq 'GET') { Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)over URI $($RestSplat.Uri)" $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false if ($OutputQuery.value) { $OutputQuery.value } if ($OutputQuery.'@odata.nextLink') { $RestSplat.Uri = $OutputQuery.'@odata.nextLink' $MoreData = Invoke-Graphimo @RestSplat -FullUri if ($MoreData) { $MoreData } } } else { Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)over URI $($RestSplat.Uri)" if ($PSCmdlet.ShouldProcess($($RestSplat.Uri), $WhatIfInformation)) { $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false if ($Method -in 'POST') { $OutputQuery } else { return $true } } } } catch { $RestError = $_.ErrorDetails.Message if ($RestError) { try { $ErrorMessage = ConvertFrom-Json -InputObject $RestError # Write-Warning -Message "Invoke-Graphimo - [$($ErrorMessage.error.code)] $($ErrorMessage.error.message), exception: $($_.Exception.Message)" Write-Warning -Message "Invoke-Graphimo - Error: $($_.Exception.Message) $($ErrorMessage.error.message)" } catch { Write-Warning -Message "Invoke-Graphimo - Error: $($_.Exception.Message)" } } else { Write-Warning -Message "Invoke-Graphimo - Error: $($_.Exception.Message)" } if ($Method -notin 'GET', 'POST') { return $false } } } function Remove-GraphGroupMember { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory, ParameterSetName = 'All')] [parameter(Mandatory, ParameterSetName = 'PerID')] [parameter(Mandatory, ParameterSetName = 'BySearch')] [alias('Authorization')][System.Collections.IDictionary] $Headers, [parameter(Mandatory, ParameterSetName = 'All')] [parameter(Mandatory, ParameterSetName = 'PerID')] [parameter(Mandatory, ParameterSetName = 'BySearch')] [alias('GroupID')][string] $ID, [parameter(Mandatory, ParameterSetName = 'PerID')][string] $MemberID, [parameter(Mandatory, ParameterSetName = 'BySearch')][string] $Search, [parameter(ParameterSetName = 'All')][switch] $All ) if ($All) { # Lets remove all, but to do that we need to know who to remove $Users = Get-GraphGroupMember -Id $ID -Headers $Headers -Verbose -Property id, displayName foreach ($User in $Users) { $URI = "/groups/$ID/members/$($User.id)/`$ref" $Status = Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers $Status } } elseif ($Search) { $Users = Get-GraphGroupMember -Id $ID -Headers $Headers -Verbose -Property id, displayName -Search $Search foreach ($User in $Users) { $URI = "/groups/$ID/members/$($User.id)/`$ref" $Status = Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers $Status } } else { # Lets delete just one record $URI = "/groups/$ID/members/$MemberID/`$ref" Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers } } function Remove-GraphManager { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [alias('UserID')][string] $ID, [string] $UserPrincipalName ) if ($ID) { $URI = "/users/$ID/manager/`$ref" } else { $URI = "/users/$UserPrincipalName/manager/`$ref" } Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers } function Remove-GraphUser { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $UserPrincipalName, [alias('UserID')][string] $ID ) if ($ID) { $URI = "/users/$ID" } else { $URI = "/users/$UserPrincipalName" } Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers } function Set-GraphManager { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [alias('UserID')][string] $ID, [string] $UserPrincipalName, [string] $Name, [string] $ManagerID, [string] $ManagerDisplayName ) if ($ID) { $URI = "/users/$ID/manager/`$ref" } else { $URI = "/users/$UserPrincipalName/manager/`$ref" } $Body = [ordered]@{ "@odata.id" = "https://graph.microsoft.com/v1.0/users/$ManagerID" } Invoke-Graphimo -Uri $URI -Method PUT -Headers $Headers -Body $Body } function Set-GraphUser { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [alias('UserID')][string] $ID, [string] $UserPrincipalName, [string] $Name, [alias('AccountEnabled')][nullable[bool]] $Enabled, [alias('FirstName')][string] $GivenName, [alias('LastName')][string] $Surname, [alias('Title')][string] $JobTitle, [string] $EmployeeId, [string] $City, [string] $MailNickname, [alias('EmailAddress')][string] $Mail, [string] $Country, [string] $Department, [string] $PostalCode, [alias('Fax')][string] $FaxNumber, [string] $State, [string] $StreetAddress, [alias('OfficePhone')][string] $BusinessPhones, [alias('Mobile')][string] $MobilePhone, [string] $OfficeLocation, [string] $CompanyName, [string] $DisplayName, [switch] $ShowInAddressList, [alias('HireDate')][DateTime] $StartDate, [alias('CustomProperty')][System.Collections.IDictionary] $CustomProperties ) if ($ID) { $URI = "/users/$ID" } else { $URI = "/users/$UserPrincipalName" } $Body = [ordered]@{} # https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0 if ($PSBoundParameters.ContainsKey('StartDate')) { # requires fixing # The date and time when the user was hired or will start work in case of a future hire. $Body['employeeHireDate'] = $StartDate } if ($PSBoundParameters.ContainsKey('JobTitle')) { $Body['jobTitle'] = $JobTitle } if ($PSBoundParameters.ContainsKey('EmployeeId')) { $Body['employeeId'] = $EmployeeId } if ($PSBoundParameters.ContainsKey('MailNickname')) { $Body['mailNickname'] = $MailNickname } if ($PSBoundParameters.ContainsKey('Mail')) { $Body['mail'] = $Mail } if ($PSBoundParameters.ContainsKey('FaxNumber')) { $Body['faxNumber'] = $FaxNumber } if ($PSBoundParameters.ContainsKey('givenName')) { $Body['givenName'] = $givenName } if ($PSBoundParameters.ContainsKey('Surname')) { $Body['surname'] = $Surname } if ($PSBoundParameters.ContainsKey('City')) { $Body['city'] = $City } if ($PSBoundParameters.ContainsKey('Country')) { $Body['country'] = $Country } if ($PSBoundParameters.ContainsKey('Department')) { $Body['department'] = $Department } if ($PSBoundParameters.ContainsKey('PostalCode')) { $Body['postalCode'] = $PostalCode } if ($PSBoundParameters.ContainsKey('State')) { $Body['state'] = $State } if ($PSBoundParameters.ContainsKey('StreetAddress')) { $Body['streetAddress'] = $StreetAddress } if ($PSBoundParameters.ContainsKey('businessPhones')) { $Body['businessPhones'] = @($businessPhones) } if ($PSBoundParameters.ContainsKey('mobilePhone')) { $Body['mobilePhone'] = $mobilePhone } if ($PSBoundParameters.ContainsKey('OfficeLocation')) { $Body['officeLocation'] = $OfficeLocation } if ($PSBoundParameters.ContainsKey('CompanyName')) { $Body['companyName'] = $CompanyName } if ($PSBoundParameters.ContainsKey('DisplayName')) { $Body['displayName'] = $DisplayName } if ($PSBoundParameters.ContainsKey('ShowInAddressList')) { $Body['showInAddressList'] = $ShowInAddressList.IsPresent } if ($PSBoundParameters.ContainsKey('Enabled')) { $Body['accountEnabled'] = $Enabled } foreach ($Property in $CustomProperties.Keys) { $Body[$Property] = $CustomProperties[$Property] } if ($Body.Count -gt 0) { Invoke-Graphimo -Uri $URI -Method PATCH -Headers $Headers -Body $Body } else { Write-Warning -Message "Set-GraphUser - No changes were made to the user, as no field to change." } } # Export functions and aliases as required Export-ModuleMember -Function @('Add-GraphGroupMember', 'Add-GraphUser', 'Connect-Graphimo', 'Get-GraphApplication', 'Get-GraphContact', 'Get-GraphGroup', 'Get-GraphGroupMember', 'Get-GraphUser', 'Import-GraphGuest', 'Invoke-Graphimo', 'Remove-GraphGroupMember', 'Remove-GraphManager', 'Remove-GraphUser', 'Set-GraphManager', 'Set-GraphUser') -Alias @('Get-GraphUsers') # SIG # Begin signature block # MIInaAYJKoZIhvcNAQcCoIInWTCCJ1UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCADhlgfyHFlvpB0 # vgp+YIqAwPNnPTNz9JzOluWHm/ZmnqCCIWEwggO3MIICn6ADAgECAhAM5+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 # g9ufqExx6lHYDjCCBbEwggSZoAMCAQICEAEkCvseOAuKFvFLcZ3008AwDQYJKoZI # hvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ # MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNz # dXJlZCBJRCBSb290IENBMB4XDTIyMDYwOTAwMDAwMFoXDTMxMTEwOTIzNTk1OVow # 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+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCAV4wggFaMA8GA1UdEwEB/wQFMAMB # Af8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXr # oq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggr # BgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw # LmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqg # OKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS # b290Q0EuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkq # hkiG9w0BAQwFAAOCAQEAmhYCpQHvgfsNtFiyeK2oIxnZczfaYJ5R18v4L0C5ox98 # QE4zPpA854kBdYXoYnsdVuBxut5exje8eVxiAE34SXpRTQYy88XSAConIOqJLhU5 # 4Cw++HV8LIJBYTUPI9DtNZXSiJUpQ8vgplgQfFOOn0XJIDcUwO0Zun53OdJUlsem # Ed80M/Z1UkJLHJ2NltWVbEcSFCRfJkH6Gka93rDlkUcDrBgIy8vbZol/K5xlv743 # Tr4t851Kw8zMR17IlZWt0cu7KgYg+T9y6jbrRXKSeil7FAM8+03WSHF6EBGKCHTN # bBsEXNKKlQN2UVBT1i73SkbDrhAscUywh7YnN0RgRDCCBq4wggSWoAMCAQICEAc2 # N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTAT # BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEh # MB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAw # MFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD # ZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYg # U0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC # AgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFE # FUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoi # GN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YA # e9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O # 9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI # 1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7m # O1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPK # qpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8F # nGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMD # iP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4Jduyr # XUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFd # MIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91 # jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8B # Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKG # NWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290 # RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQC # MAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW # 2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H # +oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4os # equFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p # /yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnf # xI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36T # U6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0 # cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf # +yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa6 # 3VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1d # wvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9E # FUrnEw4d2zc4GqEr9u3WfPwwggbGMIIErqADAgECAhAKekqInsmZQpAGYzhNhped # MA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy # dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNI # QTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjIwMzI5MDAwMDAwWhcNMzMwMzE0MjM1 # OTU5WjBMMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJDAi # BgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIyIC0gMjCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBALkqliOmXLxf1knwFYIY9DPuzFxs4+AlLtIx5DxA # rvurxON4XX5cNur1JY1Do4HrOGP5PIhp3jzSMFENMQe6Rm7po0tI6IlBfw2y1vmE # 8Zg+C78KhBJxbKFiJgHTzsNs/aw7ftwqHKm9MMYW2Nq867Lxg9GfzQnFuUFqRUIj # QVr4YNNlLD5+Xr2Wp/D8sfT0KM9CeR87x5MHaGjlRDRSXw9Q3tRZLER0wDJHGVvi # mC6P0Mo//8ZnzzyTlU6E6XYYmJkRFMUrDKAz200kheiClOEvA+5/hQLJhuHVGBS3 # BEXz4Di9or16cZjsFef9LuzSmwCKrB2NO4Bo/tBZmCbO4O2ufyguwp7gC0vICNEy # u4P6IzzZ/9KMu/dDI9/nw1oFYn5wLOUrsj1j6siugSBrQ4nIfl+wGt0ZvZ90QQqv # uY4J03ShL7BUdsGQT5TshmH/2xEvkgMwzjC3iw9dRLNDHSNQzZHXL537/M2xwafE # DsTvQD4ZOgLUMalpoEn5deGb6GjkagyP6+SxIXuGZ1h+fx/oK+QUshbWgaHK2jCQ # a+5vdcCwNiayCDv/vb5/bBMY38ZtpHlJrYt/YYcFaPfUcONCleieu5tLsuK2QT3n # r6caKMmtYbCgQRgZTu1Hm2GV7T4LYVrqPnqYklHNP8lE54CLKUJy93my3YTqJ+7+ # fXprAgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW # BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg # hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O # BBYEFI1kt4kh/lZYRIRhp+pvHDaP3a8NMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 # Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy # NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT # SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAA0tI3Sm # 0fX46kuZPwHk9gzkrxad2bOMl4IpnENvAS2rOLVwEb+EGYs/XeWGT76TOt4qOVo5 # TtiEWaW8G5iq6Gzv0UhpGThbz4k5HXBw2U7fIyJs1d/2WcuhwupMdsqh3KErlrib # Vakaa33R9QIJT4LWpXOIxJiA3+5JlbezzMWn7g7h7x44ip/vEckxSli23zh8y/pc # 9+RTv24KfH7X3pjVKWWJD6KcwGX0ASJlx+pedKZbNZJQfPQXpodkTz5GiRZjIGvL # 8nvQNeNKcEiptucdYL0EIhUlcAZyqUQ7aUcR0+7px6A+TxC5MDbk86ppCaiLfmSi # ZZQR+24y8fW7OK3NwJMR1TJ4Sks3KkzzXNy2hcC7cDBVeNaY/lRtf3GpSBp43UZ3 # Lht6wDOK+EoojBKoc88t+dMj8p4Z4A2UKKDr2xpRoJWCjihrpM6ddt6pc6pIallD # rl/q+A8GQp3fBmiW/iqgdFtjZt5rLLh4qk1wbfAs8QcVfjW05rUMopml1xVrNQ6F # 1uAszOAMJLh8UgsemXzvyMjFjFhpr6s94c/MfRWuFL+Kcd/Kl7HYR+ocheBFThIc # FClYzG/Tf8u+wQ5KbyCcrtlzMlkI5y2SoRoR/jKYpl0rl+CL05zMbbUNrkdjOEcX # W28T2moQbh9Jt0RbtAgKh1pZBHYRoad3AhMcMYIFXTCCBVkCAQEwgYYwcjELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENv # ZGUgU2lnbmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzANBglghkgBZQMEAgEFAKCB # hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ # BDEiBCB5YFxHXtmA76yZZtPZEipz1k+axZ+KhjwZ8fSqIP9NGTANBgkqhkiG9w0B # AQEFAASCAQCJvLXwkVtZ8PX5vhNWo5CqDODPERJZjRU8kZXj96ozZOPC6OCcCQuB # VYlMYX0mu2VogRVwyfBBhnG83wQE7Sp97qSyH5FlGSqRbZzOoiLZumcLMWaKStFi # QTsaqA6q/47qkpzK+oSTmHZC4Qkw5nPfXFdEmhwFXCw2SOqPZeiVForlhUUo4uFh # 5HpvWhW4tgg+GbuqesM8OFubGMqgbWmfEq9vGLg2Pj8ZLLvqGF6jyi2R/FM2rhuU # EhF3Wknq7kxKDw7MgK5NBsL8r8uG3B4LVBBz6P7T6clHbi0szzcnTMLlHcp5yuOo # ceuFYZNKMCYndo0f6l7TA6gWCmjUjS04oYIDIDCCAxwGCSqGSIb3DQEJBjGCAw0w # ggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRp # bWVTdGFtcGluZyBDQQIQCnpKiJ7JmUKQBmM4TYaXnTANBglghkgBZQMEAgEFAKBp # MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIyMDcy # NzEwNTgwM1owLwYJKoZIhvcNAQkEMSIEIIw4P4GllEgyZyRDps8F/mKYWVMHFoju # wSucY4mXr4xIMA0GCSqGSIb3DQEBAQUABIICAIu05U9drXzQL0zqhMHi77TiueLA # R8+yghO/CbVO99UR1Jq+gh/TBHz0nznfhVa7NVs83GN5X4NnynZm3VngSJEEhsqx # X8upn0LaJqZ1CVf1Cla4Sdc51OtkRIg8cMY+SfxKcCTneokzX8QHsIPDLiByxeaB # sAocTuGEwdgW/2TFkfzQAQVPtnSBQj7tsM5mDFqr1Y1yk3UF5fl3dtPtEZq6jAhJ # 8NwgaP4XO8kl6m4RyrNzHuyzOZG0qs/JFDxlHwiEhu+enqbmVimMtJxkScYDDnRj # aRLc4ZSKRm+Wkf4i+5SuYc9VyONqkpunsklVaVEZVC5cMSBApPr4l98EwEo6JZb7 # 3SmOvJo2KCmRMCZoLHoxUPsdMm6QyGllZXqQH7aVsTzy8xdgYHyMBVNMx35/cDOj # 8seF1eHpEL2+qXHrmafOsbNWQzligPBiiDCgLQAgxwB5d1M2fjGnyWugoXI3QEji # 0ke2WA+wzr/D1C/BECCLVuBiMD11ivIszyaPMqPbcWuZI/uP+Nln7w43PRXsVcUT # 8hRvRktuLhAuLQcPJLVv9GMEbPCgIhtJHtfRjcb+03OjqcVwqnZRhuF2NSukgshK # cqdWvuh+Cf1nGDJHz1TEnppGUjuxMdvZ15VpBlfZRjT4ISwt9ZMtD+jwZfCsfJa4 # 0gmx6eEq9r5qqWbH # SIG # End signature block |