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 .PARAMETER EscapeUriString If set, will escape the url string .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, [alias('EscapeUrlString')][switch] $EscapeUriString ) begin { Add-Type -AssemblyName System.Web } process { # Join primary url with additional path if needed if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri } # Create a http name value collection from an empty string if ($QueryParameter) { $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) } } # Build the uri $uriRequest = [System.UriBuilder] $Url if ($Collection) { $uriRequest.Query = $Collection.ToString() } if (-not $EscapeUriString) { $uriRequest.Uri.AbsoluteUri } else { [System.Uri]::EscapeUriString($uriRequest.Uri.AbsoluteUri) } } } function Remove-EmptyValue { <# .SYNOPSIS Removes empty values from a hashtable recursively. .DESCRIPTION This function removes empty values from a given hashtable. It can be used to clean up a hashtable by removing keys with null, empty string, empty array, or empty dictionary values. The function supports recursive removal of empty values. .PARAMETER Hashtable The hashtable from which empty values will be removed. .PARAMETER ExcludeParameter An array of keys to exclude from the removal process. .PARAMETER Recursive Indicates whether to recursively remove empty values from nested hashtables. .PARAMETER Rerun Specifies the number of times to rerun the removal process recursively. .PARAMETER DoNotRemoveNull If specified, null values will not be removed. .PARAMETER DoNotRemoveEmpty If specified, empty string values will not be removed. .PARAMETER DoNotRemoveEmptyArray If specified, empty array values will not be removed. .PARAMETER DoNotRemoveEmptyDictionary If specified, empty dictionary values will not be removed. .EXAMPLE $hashtable = @{ 'Key1' = ''; 'Key2' = $null; 'Key3' = @(); 'Key4' = @{} } Remove-EmptyValue -Hashtable $hashtable -Recursive Description ----------- This example removes empty values from the $hashtable recursively. .EXAMPLE $SplatDictionary = [ordered] @{ Test = $NotExistingParameter Test1 = 'Existing Entry' Test2 = $null Test3 = '' Test5 = 0 Test6 = 6 Test7 = @{} } Remove-EmptyValue -Splat $SplatDictionary -Recursive -ExcludeParameter 'Test7' $SplatDictionary #> [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 -ExcludeParameter $ExcludeParameter -DoNotRemoveNull:$DoNotRemoveNull -DoNotRemoveEmpty:$DoNotRemoveEmpty -DoNotRemoveEmptyArray:$DoNotRemoveEmptyArray -DoNotRemoveEmptyDictionary:$DoNotRemoveEmptyDictionary } } 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 -ExcludeParameter $ExcludeParameter -DoNotRemoveNull:$DoNotRemoveNull -DoNotRemoveEmpty:$DoNotRemoveEmpty -DoNotRemoveEmptyArray:$DoNotRemoveEmptyArray -DoNotRemoveEmptyDictionary:$DoNotRemoveEmptyDictionary } } } 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('/')) #return [Uri]::new([Uri]::new($BaseUri), $RelativeOrAbsoluteUri).ToString() #return [Uri]::new($BaseUri.OriginalString, $RelativeOrAbsoluteUri).ToString() } function Connect-MsalToken { [cmdletBinding()] param( [System.Collections.IDictionary] $MsalTokenSplat, [System.Collections.IDictionary] $Authorization, [int] $ExpiresTimeout = 30, [switch] $ForceRefesh ) if (-not $Script:AuthorizationCache) { $Script:AuthorizationCache = [ordered] @{} } if ($Authorization.Splat) { $ApplicationID = $Authorization.Splat.ClientId if ($Script:AuthorizationCache[$ApplicationID] -and -not $ForceRefesh) { if ($Script:AuthorizationCache[$ApplicationID].MsalToken.ExpiresOn.UtcDateTime -gt ([datetime]::UtcNow).AddSeconds($ExpiresTimeout)) { Write-Verbose "Connect-MsalToken - Using cache for $ApplicationID" return $Script:AuthorizationCache[$ApplicationID] } } $Splat = $Authorization.Splat try { $MsalToken = Get-MsalToken @Splat -ErrorAction Stop } catch { Write-Warning -Message "Connect-MsalToken - Couldn't execute Get-MsalToken. Error: $($_.Exception.Message)" return } $Script:AuthorizationCache[$ApplicationID] = [ordered] @{ 'MsalToken' = $MsalToken 'Splat' = $Authorization.Splat } $Script:AuthorizationCache[$ApplicationID] } else { Write-Warning -Message "Connect-MsalToken - Using old authorization format without Splatting. No refresh of tokens will be available." } } function Convert-GraphInternalUser { <# .SYNOPSIS Converts user returned by graph with onPremisesExtensionAttributes to new simplified object .DESCRIPTION Converts user returned by graph with onPremisesExtensionAttributes to new simplified object .PARAMETER InputObject The object to convert .EXAMPLE An example .NOTES General notes #> [CmdletBinding()] param( [parameter(Mandatory, ValueFromPipeline)][PSCustomObject[]] $InputObject ) process { $InputObject | ForEach-Object { $NewObject = [ordered] @{} $Object = $_ foreach ($Property in $Object.PSObject.Properties.Name) { if ($Property -eq 'onPremisesExtensionAttributes') { $OnPrem = $Object.onPremisesExtensionAttributes if ($null -ne $OnPrem) { if ($OnPrem -is [System.Collections.IDictionary]) { foreach ($Key in $OnPrem.Keys) { $NewObject[$Key] = $OnPrem[$Key] } } else { foreach ($ExtensionAttribute in $OnPrem.PSObject.Properties.Name) { $NewObject[$ExtensionAttribute] = $OnPrem.$ExtensionAttribute } } } # keep the raw object for debugging/inspection $NewObject['onPremisesExtensionAttributes'] = $OnPrem } else { $NewObject[$Property] = $Object.$Property } } [PSCustomObject] $NewObject } } } function Invoke-InternalGraphimo { [CmdletBinding()] param( [Array] $OutputQuery, [int] $First, [int] $CurrentCount, [string] $CountVariable, [switch] $MgGraph ) if ($OutputQuery.value) { $FoundUsers = $OutputQuery.value } if ($CountVariable) { Set-Variable -Name $CountVariable -Value $OutputQuery.'@odata.count' -Scope Global } if ($First) { if ($CurrentCount) { $First = $First - $CurrentCount } if ($FoundUsers.Count -eq $First) { } elseif ($FoundUsers.Count -gt $First) { $FoundUsers = $FoundUsers | Select-Object -First $First } else { $First = $First - $FoundUsers.Count } } if ($MgGraph -or $Script:MgGraphAuthenticated -eq $true) { foreach ($Object in $FoundUsers) { [PSCustomObject] $Object } } else { $FoundUsers } } function Add-GraphGroup { [CmdletBinding()] param( [alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $DisplayName, [string] $Name, [string] $Description, [string] $MailNickname, [switch] $SecurityEnabled, [switch] $MailEnabled, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } $URI = "/groups" $BaseUri = 'https://graph.microsoft.com/v1.0' $Body = [ordered]@{ groupTypes = @() } if ($PSBoundParameters.ContainsKey('DisplayName')) { $Body['displayName'] = $DisplayName } if ($PSBoundParameters.ContainsKey('Name')) { $Body['name'] = $Name } if ($PSBoundParameters.ContainsKey('Description')) { $Body['description'] = $Description } if ($PSBoundParameters.ContainsKey('MailNickname')) { $Body['mailNickname'] = $MailNickname } if ($PSBoundParameters.ContainsKey('SecurityEnabled')) { $Body['securityEnabled'] = $SecurityEnabled.IsPresent } if ($PSBoundParameters.ContainsKey('MailEnabled')) { $Body['mailEnabled'] = $MailEnabled.IsPresent } if ($Body.Count -gt 0) { Invoke-Graphimo -Uri $URI -Method POST -Headers $Headers -Body $Body -BaseUri $BaseUri -MgGraph:$MgGraph.IsPresent } } function Add-GraphGroupMember { [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Headers')] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [parameter(Mandatory)][alias('GroupID')][string] $ID, [parameter(Mandatory)][string] $MemberID, [parameter()][switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } $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 -MgGraph:$MgGraph.IsPresent } function Add-GraphUser { [cmdletBinding(SupportsShouldProcess)] param( [parameter()][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, [string] $EmployeeType, [Parameter(Mandatory)][string] $Password, [alias('HireDate')][DateTime] $StartDate, [alias('CustomProperty')][System.Collections.IDictionary] $CustomProperties, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } $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 } if ($PSBoundParameters.ContainsKey('EmployeeType')) { $Body['employeeType'] = $EmployeeType $BaseUri = 'https://graph.microsoft.com/beta' } else { $BaseUri = 'https://graph.microsoft.com/v1.0' } $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 -BaseUri $BaseUri -MgGraph:$MgGraph.IsPresent } } 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(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, [parameter(Mandatory, ParameterSetName = 'MsalToken')][System.Collections.IDictionary] $MsalToken, [parameter(Mandatory, ParameterSetName = 'MsGraphConfiguration')] [System.Collections.IDictionary] $MsGraphConfiguration, [parameter(Mandatory, ParameterSetName = 'MsGraphConfigurationSwitch')] [switch] $MgGraph ) # Comparison V1/V2 https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/ if (-not $Script:AuthorizationCache) { $Script:AuthorizationCache = [ordered] @{} } if ($null -ne $MsGraphConfiguration -or $MgGraph.IsPresent) { if ($MgGraph.IsPresent) { # We use Microsoft.Graph module instead of Invoke-RestMethod # We assume the connection was done before this function was called $Script:MgGraphAuthenticated = $true return } else { if (-not $MsGraphConfiguration.ErrorAction) { $MsGraphConfiguration.ErrorAction = 'Stop' } try { Connect-MgGraph @MsGraphConfiguration $Script:MgGraphAuthenticated = $true return } catch { if ($ErrorActionPreference -eq 'Stop') { throw } $Script:MgGraphAuthenticated = $null $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-Graphimo - Error: $ErrorMessage" return $false } } } 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 } } } elseif ($MsalToken) { $Authorization = @{ Splat = $MsalToken } return Connect-MsalToken -Authorization $Authorization -ExpiresTimeout $ExpiresTimeout -ForceRefesh:$ForceRefesh } 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 -Verbose:$false $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()][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $ID, [string] $DisplayName, [string[]] $Property, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } 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 -MgGraph:$MgGraph.IsPresent } function Get-GraphContact { [cmdletBinding()] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $Id, [string[]] $Property, [string] $Filter, [string] $OrderBy, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } 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 -MgGraph:$MgGraph.IsPresent } function Get-GraphGroup { [cmdletBinding()] param( [alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $Id, [string[]] $Property, [string] $Filter, [string] $OrderBy, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } 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 -MgGraph:$MgGraph.IsPresent } function Get-GraphGroupMember { [cmdletBinding()] param( [alias('Authorization')][System.Collections.IDictionary] $Headers, [parameter(Mandatory)][string] $Id, [string] $Search, [string[]] $Property, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } if ($ID) { # Query a single group $RelativeURI = "/groups/$ID/members" $QueryParameter = @{ '$Select' = $Property -join ',' '$Search' = $Search } } Remove-EmptyValue -Hashtable $QueryParameter $invokeGraphimoSplat = @{ Uri = $RelativeURI Method = 'GET' Headers = $Headers QueryParameter = $QueryParameter MgGraph = $MgGraph.IsPresent } 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/ $invokeGraphimoSplat['ConsistencyLevel'] = 'eventual' } Invoke-Graphimo @invokeGraphimoSplat } function Get-GraphUser { [alias('Get-GraphUsers')] [cmdletBinding(DefaultParameterSetName = 'Default')] param( [parameter(ParameterSetName = 'Default')] [parameter(ParameterSetName = 'EmailAddress')] [parameter(ParameterSetName = 'UserPrincipalName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'Id')] [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, [int] $First, [string] $CountVariable, [string] $ConsistencyLevel, [switch] $DebugRequest, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } if ($Property -contains 'EmployeeType' -or $Property -contains 'onPremisesExtensionAttributes') { $BaseURI = 'https://graph.microsoft.com/beta' } else { $BaseURI = 'https://graph.microsoft.com/v1.0' } $NewProperties = foreach ($P in $Property) { if ($P -like "extensionAttribute*") { 'onPremisesExtensionAttributes' } else { $P } } $Property = $NewProperties | Select-Object -Unique if ($UserPrincipalName) { $RelativeURI = '/users' $QueryParameter = [ordered]@{ '$Select' = $Property -join ',' '$filter' = "userPrincipalName eq '$UserPrincipalName'" } } elseif ($EmailAddress) { $RelativeURI = '/users' $QueryParameter = [ordered]@{ '$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 = [ordered]@{ '$filter' = "id eq '$ID'" '$Select' = $Property -join ',' } } else { # Query multiple groups $RelativeURI = '/users' $QueryParameter = [ordered]@{ '$Select' = $Property -join ',' '$filter' = $Filter '$orderby' = $OrderBy } } if ($IncludeManager) { $QueryParameter['$expand'] = 'manager' } if ($CountVariable) { $QueryParameter['$count'] = 'true' } # if ($ConsistencyLevel) { # $Headers['consistencyLevel'] = $ConsistencyLevel # } Remove-EmptyValue -Hashtable $QueryParameter if ($DebugRequest) { Write-Verbose ("Get-GraphUser request: BaseUri={0} Uri={1} Select={2} Filter={3} Expand={4} OrderBy={5} Count={6} ConsistencyLevel={7}" -f $BaseURI, $RelativeURI, $QueryParameter['$Select'], $QueryParameter['$filter'], $QueryParameter['$expand'], $QueryParameter['$orderby'], $QueryParameter['$count'], $ConsistencyLevel) } $invokeGraphimoSplat = @{ Uri = $RelativeURI Method = 'GET' QueryParameter = $QueryParameter BaseUri = $BaseURI First = $First CountVariable = $CountVariable ConsistencyLevel = $ConsistencyLevel MgGraph = $MgGraph.IsPresent } if ($Headers) { $invokeGraphimoSplat['Headers'] = $Headers } if ($Property -contains 'onPremisesExtensionAttributes') { $OutputData = Invoke-Graphimo @invokeGraphimoSplat if ($OutputData) { $OutputData | Convert-GraphInternalUser } } else { Invoke-Graphimo @invokeGraphimoSplat } } function Import-GraphGuest { [cmdletBinding()] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $Name, [Parameter(Mandatory)][string] $EmailAddress, [switch] $SendInvitationMessage, [string] $InviteRedirectUrl = "https://portal.office.com", [switch] $ResetRedemption, [string] $InvitedUserID, [ValidateSet('Member', 'Guest')][string] $UserType, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } $URI = '/invitations' $body = [ordered]@{ 'invitedUserDisplayName' = $Name 'invitedUserEmailAddress' = $EmailAddress 'inviteRedirectUrl' = $InviteRedirectUrl 'sendInvitationMessage' = $SendInvitationMessage.IsPresent 'resetRedemption' = $ResetRedemption.IsPresent } if ($UserType) { $Body['invitedUserType'] = $UserType } if ($InvitedUserID) { $Body['invitedUser'] = @{ 'id' = $InvitedUserID } } Invoke-Graphimo -Uri $URI -Method POST -Headers $Headers -Body $Body -MgGraph:$MgGraph.IsPresent } function Invoke-Graphimo { [cmdletBinding(SupportsShouldProcess)] param( [alias('PrimaryUri')][uri] $BaseUri = 'https://graph.microsoft.com/v1.0', [uri] $Uri, [parameter()][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, [string] $CountVariable, [int] $First, [string] $ConsistencyLevel, [switch] $MgGraph ) if ($MgGraph -or $Script:MgGraphAuthenticated -eq $true) { # we use the Microsoft.Graph module instead of Invoke-RestMethod } elseif ($Headers.MsalToken) { if ($Headers.Splat) { $Splat = $Headers.Splat $Headers = Connect-MsalToken -Authorization $Headers } } else { if (-not $Headers) { Write-Warning "No headers provided. Skipping." return } # 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] / [ConsistencyLevel: $($ConsistencyLevel)] " } if ($ConsistencyLevel) { if (-not $RestSplat.Headers) { $RestSplat.Headers = [ordered]@{} } $RestSplat.Headers['ConsistencyLevel'] = $ConsistencyLevel } Remove-EmptyValue -Hashtable $RestSplat try { if ($Method -eq 'GET') { Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)over URI $($RestSplat.Uri)" if ($MgGraph -or $Script:MgGraphAuthenticated -eq $true) { $OutputQuery = Invoke-MgGraphRequest @RestSplat -Verbose:$false } else { $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false } $Count = 1 [Array] $FoundUsers = Invoke-InternalGraphimo -OutputQuery $OutputQuery -First $First -CountVariable $CountVariable -MgGraph:$MgGraph.IsPresent $CurrentCount = $FoundUsers.Count if ($First -gt 0 -and $CurrentCount -ge $First) { return $FoundUsers } else { $FoundUsers } if ($OutputQuery.'@odata.nextLink') { do { $RestSplat.Uri = $OutputQuery.'@odata.nextLink' Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)NextLink (Page $Count/Current Count: $($CurrentCount))) over URI $($RestSplat.Uri)" if ($MgGraph -or $Script:MgGraphAuthenticated -eq $true) { $OutputQuery = Invoke-MgGraphRequest @RestSplat -Verbose:$false } else { $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false } [Array] $FoundUsers = Invoke-InternalGraphimo -OutputQuery $OutputQuery -First $First -CurrentCount $CurrentCount -CountVariable $CountVariable -MgGraph:$MgGraph.IsPresent $FoundUsers $Count++ $CurrentCount = $CurrentCount + $FoundUsers.Count } until (($First -gt 0 -and $CurrentCount -ge $First) -or $null -eq $OutputQuery.'@odata.nextLink') } } else { Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)over URI $($RestSplat.Uri)" if ($PSCmdlet.ShouldProcess($($RestSplat.Uri), $WhatIfInformation)) { if ($MgGraph -or $Script:MgGraphAuthenticated -eq $true) { $OutputQuery = Invoke-MgGraphRequest @RestSplat -Verbose:$false } else { $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 - 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(ParameterSetName = 'All')] [parameter(ParameterSetName = 'PerID')] [parameter(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, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } 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 -MgGraph:$MgGraph.IsPresent foreach ($User in $Users) { $URI = "/groups/$ID/members/$($User.id)/`$ref" Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers } } elseif ($Search) { $Users = Get-GraphGroupMember -Id $ID -Headers $Headers -Verbose -Property id, displayName -Search $Search -MgGraph:$MgGraph.IsPresent foreach ($User in $Users) { $URI = "/groups/$ID/members/$($User.id)/`$ref" Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers -MgGraph:$MgGraph.IsPresent } } else { # Lets delete just one record $URI = "/groups/$ID/members/$MemberID/`$ref" Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers -MgGraph:$MgGraph.IsPresent } } function Remove-GraphManager { [cmdletBinding(SupportsShouldProcess)] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [alias('UserID')][string] $ID, [string] $UserPrincipalName, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } if ($ID) { $URI = "/users/$ID/manager/`$ref" } else { $URI = "/users/$UserPrincipalName/manager/`$ref" } Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers -MgGraph:$MgGraph.IsPresent } function Remove-GraphUser { [cmdletBinding(SupportsShouldProcess)] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [string] $UserPrincipalName, [alias('UserID')][string] $ID, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } if ($ID) { $URI = "/users/$ID" } else { $URI = "/users/$UserPrincipalName" } Invoke-Graphimo -Uri $URI -Method DELETE -Headers $Headers -MgGraph:$MgGraph.IsPresent } function Set-GraphManager { [cmdletBinding(SupportsShouldProcess)] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [alias('UserID')][string] $ID, [string] $UserPrincipalName, [string] $Name, [string] $ManagerID, [string] $ManagerDisplayName, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } 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 -MgGraph:$MgGraph.IsPresent } function Set-GraphUser { [cmdletBinding(SupportsShouldProcess)] param( [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers, [alias('UserID')][string] $ID, [string] $SearchUserPrincipalName, [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, [string] $EmployeeType, [switch] $ShowInAddressList, [alias('HireDate')][DateTime] $StartDate, [alias('CustomProperty')][System.Collections.IDictionary] $CustomProperties, [string] $ExtensionAttribute1, [string] $ExtensionAttribute2, [string] $ExtensionAttribute3, [string] $ExtensionAttribute4, [string] $ExtensionAttribute5, [string] $ExtensionAttribute6, [string] $ExtensionAttribute7, [string] $ExtensionAttribute8, [string] $ExtensionAttribute9, [string] $ExtensionAttribute10, [string] $ExtensionAttribute11, [string] $ExtensionAttribute12, [string] $ExtensionAttribute13, [string] $ExtensionAttribute14, [string] $ExtensionAttribute15, [System.Collections.IDictionary] $OnPremisesExtensionAttributes, [string] $UserType, [switch] $MgGraph ) if (-not $MgGraph -and -not $Headers -and $Script:MgGraphAuthenticated -ne $true) { Write-Warning -Message "No headers or MgGraph switch provided. Skipping." return } $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('UserPrincipalName')) { $Body['userPrincipalName'] = $UserPrincipalName } 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 } if ($PSBoundParameters.ContainsKey('EmployeeType')) { $Body['employeeType'] = $EmployeeType $BaseUri = 'https://graph.microsoft.com/beta' } else { $BaseUri = 'https://graph.microsoft.com/v1.0' } foreach ($Property in $CustomProperties.Keys) { $Body[$Property] = $CustomProperties[$Property] } $Body['onPremisesExtensionAttributes'] = [ordered] @{} if ($PSBoundParameters.ContainsKey('ExtensionAttribute1')) { $Body['onPremisesExtensionAttributes']['extensionAttribute1'] = $ExtensionAttribute1 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute2')) { $Body['onPremisesExtensionAttributes']['extensionAttribute2'] = $ExtensionAttribute2 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute3')) { $Body['onPremisesExtensionAttributes']['extensionAttribute3'] = $ExtensionAttribute3 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute4')) { $Body['onPremisesExtensionAttributes']['extensionAttribute4'] = $ExtensionAttribute4 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute5')) { $Body['onPremisesExtensionAttributes']['extensionAttribute5'] = $ExtensionAttribute5 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute6')) { $Body['onPremisesExtensionAttributes']['extensionAttribute6'] = $ExtensionAttribute6 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute7')) { $Body['onPremisesExtensionAttributes']['extensionAttribute7'] = $ExtensionAttribute7 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute8')) { $Body['onPremisesExtensionAttributes']['extensionAttribute8'] = $ExtensionAttribute8 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute9')) { $Body['onPremisesExtensionAttributes']['extensionAttribute9'] = $ExtensionAttribute9 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute10')) { $Body['onPremisesExtensionAttributes']['extensionAttribute10'] = $ExtensionAttribute10 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute11')) { $Body['onPremisesExtensionAttributes']['extensionAttribute11'] = $ExtensionAttribute11 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute12')) { $Body['onPremisesExtensionAttributes']['extensionAttribute12'] = $ExtensionAttribute12 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute13')) { $Body['onPremisesExtensionAttributes']['extensionAttribute13'] = $ExtensionAttribute13 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute14')) { $Body['onPremisesExtensionAttributes']['extensionAttribute14'] = $ExtensionAttribute14 } if ($PSBoundParameters.ContainsKey('ExtensionAttribute15')) { $Body['onPremisesExtensionAttributes']['extensionAttribute15'] = $ExtensionAttribute15 } if ($PSBoundParameters.ContainsKey("UserType")) { $Body['userType'] = $UserType } if ($ID) { $URI = "/users/$ID" } else { $URI = "/users/$SearchUserPrincipalName" } if ($Body['onPremisesExtensionAttributes'].Count -eq 0) { $Body.Remove('onPremisesExtensionAttributes') } if ($Body.Count -gt 0) { $UriEncoded = [System.Web.HttpUtility]::UrlEncode($Uri) Invoke-Graphimo -Uri $UriEncoded -Method PATCH -Headers $Headers -Body $Body -BaseUri $BaseUri -MgGraph:$MgGraph.IsPresent } else { Write-Warning -Message "Set-GraphUser - No changes were made to the user, as no field to change." } } $FunctionsToExport = @('Add-GraphGroup', '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') $CmdletsToExport = @() $AliasesToExport = @('Get-GraphUsers') Export-ModuleMember -Function $FunctionsToExport -Alias $AliasesToExport -Cmdlet $CmdletsToExport # SIG # Begin signature block # MIIt6AYJKoZIhvcNAQcCoIIt2TCCLdUCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCsvKBRZmOw5ryy # zSzbyeKk/9z+/xRLtos/oopKw8Y7kKCCJuUwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqGSIb3DQEBDAUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1 # c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C0CiteLdd1TlZG7GIQvUz # jOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS1WcaUk8OoVf8iJnB # kcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZMswEgJfMQ04uy+wjw # iuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBCMo/7xuoc82VokaJN # TIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3/hoGlMJ8yOobMubK # wvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7OhD26jq22YBoMbt2pnLdK9 # RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5OBDm6s6R9b7T+2+T # YTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo7AqnsAMrkbI2eb73 # rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05huzUtw1T0PhH5nUwjeww # k3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNYmtwmKwH0iU1Z23jPgUo+ # QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NPTLuqDQhTQXxYPUez # +rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAd # BgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0jBBgwFoAU7NfjgtJx # XWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF # BwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln # aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJo # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy # bDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkqhkiG9w0BAQwFAAOC # AgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95RysQDKr2wwJxMSnpBEn0v9 # nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvdaqpDP9ZNq4+sg0dVQ # eYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+lP92OT2e1JnPSt0o # 618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnhOE7abrs2nfvlIVNaw8rp # avGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIhdXNSy0bYxDQcoqVLjc1v # djcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV9zeKiwmhywvpMRr/Lhlc # OXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/jwVYbKyP/j7XqiHtwa+ag # uv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYHKi8QxAwIZDwzj64ojDzL # j4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmCXBVmzGwOysWGw/YmMwwH # S6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCnHwZ05/LWUpD9r4VI # IflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZWeE4wgga0MIIEnKADAgEC # AhANx6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcw # MDAwMDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E # aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1l # U3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZ # loMsVO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM # 2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj # 7GH8BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQ # Sku2Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZ # lDwFt+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+ # 8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRx # ykvq6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yG # OP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqI # MRvUBDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm # 1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBj # UwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729T # SunkBnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4c # D08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUF # BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG # CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5k # aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAX # MAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaA # HP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQ # M2qEJPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt # 6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7 # bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmS # Nq1UH410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69 # M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnF # RsjsYg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmM # Thi0vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oa # Qf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx # 9yHbxtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3 # /BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN # 8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg # VGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAw # MDAwMFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp # Z2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBU # aW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx # +wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvN # Zh6wW2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlL # nh00Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmn # cOOMA3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjPKHW5KqCvpSduSwhw # UmotuQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL # 4Q1OpbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7wJNdoRORVbPR1VVnD # uSeHVZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCy # FG1roSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOgrY7rlRyTlaCCfw7a # SUROwnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K096V1hE0yZIXe+gi # AwW00aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGj # ggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zyMe39/dfzkXFjGVBD # z2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8E # BAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGF # MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUH # MAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH # NFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBW # MFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk # RzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkw # FzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3x # HCcEua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh # 8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ML9lFfim8/9yJmZS # e2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu+WUqW4daIqToXFE/ # JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4obEMnxYOX8VBRKe1u # NnzQVTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq # 8/gVutDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasnM9AWcIQfVjnzrvwi # CZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol/DJgddJ35XTxfUlQ # +8Hggt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1 # R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3ocCVccAvlKV9jEnstr # niLvUxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcBZU8atufk+EMF/cWu # iC7POGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCB18wggVHoAMCAQICEAfCUnQo # FKLWq/4k6hfl3S4wDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMTAeFw0yMzA0MTYw # MDAwMDBaFw0yNjA3MDYyMzU5NTlaMGcxCzAJBgNVBAYTAlBMMRIwEAYDVQQHDAlN # aWtvxYLDs3cxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlzIEVWT1RFQzEhMB8G # A1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAlJoHlzELSGimkpCr2wLfBhWSdcsDh/EsMZU7rODHMq1p # lTq0QVUUAPAKRfRWnqG8JpGcb5MUExSxypvvJJ8KJhFLJXGvAqkjiNGMBC7+RME1 # RIdAvw2nob8aOrZJjTxff0j9Sgt3NJdbzvjO73TVRikCEK4cauxBtInswWTgIrpD # XRlV0WDi5+O1d6i+T8Bv6LtmpSf74nyA2nfNahW/kJFIdNiaNuEjI1nSg8rXazF4 # tNt+QjeEa1vvII30Sfnyio4DCJm7nHgrIvSL9Wuum1HPWpwHpjm0+JheVP8kAYAL # gKN/o1QfMIlHfO5FEDtMyQhfL6tmK1Ts/DiZjF/IICLBBFGdwmSg9IVXN3Zu3Fkg # MPPxTcxjT5QGiMc11/ang9BIGgi0ZCLQN7d3kFviAF8kv/WZ56RVKA70BmyvkOP2 # z9Im/fFy30KcVRkbtHAldDYO+wyJERfiMkdT3MFQKvjs1VN7ynqNub/657Ylwpgs # YluKB2DtvHkkP3iAHJ4ovt7igzWayNeT+1cQ65FCHOhbYkrzocHNwM2PrxH4r1JB # SkasL0kq+Hwq65JO89kHu9mcJcNhA0VR8stH1FRjvUDLoehN0cJyS/eoqdGpXJoS # gARqCKkltOZ13QlG5F5oTwk0+Z2kA7mdVJAF22T0oSo2z8M3Vz9m/CPZ0PPVUoEC # AwEAAaOCAgMwggH/MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0G # A1UdDgQWBBR68WolWbgyccRJNeWy6DLhSOdt9zAOBgNVHQ8BAf8EBAMCB4AwEwYD # VR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0 # MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIw # MjFDQTEuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEWG2h0 # dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBlAYIKwYBBQUHAQEEgYcwgYQwJAYI # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcwAoZQ # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29k # ZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcnQwCQYDVR0TBAIwADANBgkq # hkiG9w0BAQsFAAOCAgEAtxHh11D4aXt9Stgy+Nx34eqpLwR8kdUZQ/ZVSJJXEQke # dGR86FrOhAZUxcqIb5KXJVQrkXUFt97Uur7SjzrnKQw7+MLAPus5CWCPHx6Lluk6 # mtVuO2Eq3OQDkoSHCffjaTWyjRood3aEpXIqNplCgl+SP2a8yQZEKSdJGIWv6VEk # 9gmxNya6CX9r0FhlIiPidy3YjzR5oTtZfs2kJEsb9HFQxEzH0BmSikVREmehYOtW # 9HY70EseddDHW8bSjI70t2bQMrap0B5NYqT/kYPjOZRR60pFJZ6Rmvn957kIcQ2+ # zfRPIVFXr8QC06xYn4PM4bJVUR+fw3/wsZTClwu6Kd9PwMkLDkMR1tbjcd7RtQzI # Is6cAWrK8YesGu4mgPi6dO6tSPdni4a2G7cN8QtrzSBnTHTe53e+sjCI3WJwJ+69 # /MMLWidymA9EE5e+xAfLv+XArN0oWXQ3coOCuzaCZfIhB626raKABzjC4iaYi9ov # WJ/JEDAev0OkTDtyFDy7snAfaOgzYsEB3+ibeaFuz9PZOTccQRJpLMcDW5mbzUOu # WZ93sVACqhvsd9RIM+SGeFP4z80WpRJRCKUtK4K1YPEfKRDoXfeZhM6eVhEShcl4 # Xupwem0mB7/HJSwFdIjJLt9PK6X4zIkJKktyy831CeTh6rSikDTC8c/c9fOVArMx # ggZZMIIGVQIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg # SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg # UlNBNDA5NiBTSEEzODQgMjAyMSBDQTECEAfCUnQoFKLWq/4k6hfl3S4wDQYJYIZI # AWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0B # CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAv # BgkqhkiG9w0BCQQxIgQgFOFEkvMCyBuvwPok7RcI3F7ORNkS0oLyXIyfthKlOsAw # DQYJKoZIhvcNAQEBBQAEggIAdfmYvLjMY4/MylmFDiznnTrrt3sPKg4GR5jQ7Kcz # iSyMvi7DFC8Uk4s+nLfXhMT+sFfMYkA/gtdEyZLrnGJLcwniPqftUPhgrlae/ZCa # ULiBOL4LZgC0m9eGygFxDrQUHuiklOUFqQ36U+Vw4JiYdc4fPH1tMFX5QPV7eORz # 9RrFlPLXnxzWwp6zQQAMQJ+pteatqY9dp23PTScs5DArxmvYywXV1ShLjuUH/8nY # W8Rr4KsAOM9j+GwTqxYNwN/8HfVmwBQ2EWcv5Z3Kpay1Au6z6UUWKnHeMDt+Vc3r # ZIgM7eK/AwDIDb42qxD7NGgj/miBiTUJD+7ngb3ZKECfsEI2ZDGpxOFq9V5ltlrb # K55O/N2PIaL1YWKPHE4gNTqGANVqKlXwpyRmzxm/5clmSCT9QHIFARERSci91hX0 # NdF8ApAJ917RVqJ1vtEBvz9Lu+xl69ucftRBetKNfmFJ51hKx12yLSfeCSCNw08P # hJQ4eRwNytJgpVxJ/daAKQfUUqnSffLof+lY2t6Isy0gXxRgAoVCmseC7uuSazHK # FQV1gJa8ELdB9sfM/3/tpIldO6slO2qxomPOWH0Fk9BZ5jFGeC48eEhBFqfBw2yN # oYQ5JGy8DZbfZL85zYLdnuv+oYXrDIPTQBUJH9Ri0aEz7/oxtGTxGCyy5Jy1HA/j # etmhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYDVQQGEwJV # UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRy # dXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExAhAK # gO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsG # CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNTI4MTQwMzE0WjAvBgkqhkiG # 9w0BCQQxIgQgre5cvuDlgX0fOMtIZFUmqmBAjap42VSu2x8G7EPUn34wDQYJKoZI # hvcNAQEBBQAEggIAU+eEmQGViUifaMsgPUV22hpQ/Pqx7faJz70B3R3N6WI7pCVJ # 31GPRwsWLfDu0nyw8HDF2ztTbqFm1k4qjiABvtT/C/14Kd8i7GNo+8kEipWT2YhM # nf80Qq4NEiFPivuCfrCVpUoIdE96Lz7TboMlnhoCZieEzVe7pUw8tSsNVN7qG4T8 # feEF2JClmSIO8Tgp0wdI0KsfaCfXol7i5aj62JmadBiNXaBb9edydaD+5SFJ+5px # OnLj48BIDiJfue5REEITal5rM9PFua/R4YHJIF6OaQ1gDusnZjYQ5dV7lTk9Mcf/ # eTyR7YDa3iU66NJ/Zt+cCqdZF7YZsQ/8L5OCYdH79UH00ZxKH8L+MZ1SmsCOlDTJ # BZFt0fiHt1gfkHZ/oNOS3ob0dxWjPLBbLWb+1QxzqLA/OhIzyWaIAvUORISsy01a # wyNXRYgw5P8FP/1x7x/bFimsQaplae375d0snfUdp2mo2IkvhVatbl1dMJtMgdYI # c/8HdetpKwr3e9H3MU2cxfNwq32BIpEwngcLutxMGMi360z3oETHi16Itv70sN56 # EUxGzpCpxVHWEAGF+paSB4ogHjWkegv6cpSx8HeLj5kPARSuMI0HfQdE1lg1cUat # OIoZBFFq2J8gfTC6PSqromiwFTn6kHOz/xHSumGGVbvHxFTk8B9SNAPYOCs= # SIG # End signature block |