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 { 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() } 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('/')) } # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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." } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 function Format-GraphimoRestError { [cmdletBinding()] param( [parameter(Mandatory)][System.Management.Automation.ErrorRecord] $ErrorRecord, [parameter(Mandatory)][System.Collections.IDictionary] $RestSplat ) $ExceptionMessage = $ErrorRecord.Exception.Message -replace "`r", ' ' -replace "`n", ' ' $RestError = $ErrorRecord.ErrorDetails.Message $RequestSummary = "[$($RestSplat.Method)] $($RestSplat.Uri)" $StatusSummary = $null $Response = $ErrorRecord.Exception.Response if ($Response) { $StatusCode = $Response.StatusCode $ReasonPhrase = $Response.ReasonPhrase if (-not $ReasonPhrase) { $ReasonPhrase = $Response.StatusDescription } if ($StatusCode) { $StatusSummary = " Status: $([int] $StatusCode) $ReasonPhrase." } } if (-not $RestError) { return "Invoke-Graphimo - Error: $ExceptionMessage Request: $RequestSummary.$StatusSummary" } try { $ParsedError = ConvertFrom-Json -InputObject $RestError -ErrorAction Stop $GraphMessage = $ParsedError.error.message if (-not $GraphMessage) { $GraphMessage = $ParsedError.error_description } if (-not $GraphMessage) { $GraphMessage = $ParsedError.message } if ($GraphMessage) { $GraphMessage = $GraphMessage -replace "`r", ' ' -replace "`n", ' ' return "Invoke-Graphimo - Error: $ExceptionMessage Request: $RequestSummary.$StatusSummary Graph error: $GraphMessage" } } catch { $ResponsePreview = $RestError -replace "`r", ' ' -replace "`n", ' ' if ($ResponsePreview.Length -gt 500) { $ResponsePreview = $ResponsePreview.Substring(0, 500) + '...' } return "Invoke-Graphimo - Error: $ExceptionMessage Request: $RequestSummary.$StatusSummary Non-JSON response preview: $ResponsePreview" } $ResponsePreview = $RestError -replace "`r", ' ' -replace "`n", ' ' if ($ResponsePreview.Length -gt 500) { $ResponsePreview = $ResponsePreview.Substring(0, 500) + '...' } "Invoke-Graphimo - Error: $ExceptionMessage Request: $RequestSummary.$StatusSummary Response preview: $ResponsePreview" } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 { Write-Warning -Message (Format-GraphimoRestError -ErrorRecord $_ -RestSplat $RestSplat) if ($Method -notin 'GET', 'POST') { return $false } } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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 } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 # PowerForge merged source begin 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 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) { $UserIdentifier = [System.Uri]::EscapeDataString($ID) } else { $UserIdentifier = [System.Uri]::EscapeDataString($SearchUserPrincipalName) } $URI = "/users/$UserIdentifier" if ($Body['onPremisesExtensionAttributes'].Count -eq 0) { $Body.Remove('onPremisesExtensionAttributes') } if ($Body.Count -gt 0) { Invoke-Graphimo -Uri $URI -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." } } # PowerForge merged source end 46f9d62272ed584a2d89b5bed3e940f8d756194e3253db7193c33a7baefaaa96 $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 # MIIt7AYJKoZIhvcNAQcCoIIt3TCCLdkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBCVcslmfXkJSo7 # WUTT4Pauttyr3CWUf2+T5dijGDeFqKCCJukwggWNMIIEdaADAgECAhAOmxiO+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 # /BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWgAwIBAgIQCE/cM09+ # RU7bww+P+ZIYNTANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg # VGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI2MDgwNTAw # MDAwMFoXDTM3MTEwNDIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp # Z2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBU # aW1lc3RhbXAgUmVzcG9uZGVyIDIwMjYgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBALZ7pvLJ/s1K+NSbTGWz/TjGMPh8CQ6RucZCLv5anHzWJjF/NWJr # FIhy24fcpKXlgRiky4WAawDfU3YP0BMxt9l3Dm5oCG5Z69AqEN1kgHg2epx+l+lZ # BcmJCcN0ASURML5uFIS80sZsDwO3BSkUxDjLJhBI+qiZP3aixAC/qEGLjsBNlLol # 9VZ7pfGEXiMlneJIC5/YKuizVzNFKZZEeoy/0B8Zm+nzKBgSWG52lCO1w+nCg6Xp # CtklTJXeIg283hw7TmmsZXR+SMbjbrEOvZ3fP2VxIgeR28Y90ZStd3F9VuA5RVyn # b/whITPAo9b75Zr4Ta6Mj3URm26QZYMn/FnbuTegcoRcFEZ9FOqM5T6MTdtr/n74 # lIT/ug0eeOzmZ6QTFg33otX+bFRsIolvykE1jive4PuESaT8zzVeFWDAMDtozNgL # ctkGD1ZjkEyZtJrLl5ya0m5doH/ScpaZCZVl6pNUOCybMc/kxC6EAmSJY24L0yYK # D1Nkddsnb/ItVKi/2nXpQNMu1PT5prW83vV8d67WowuUs0HdY4H8AMLGvdL/WHEj # 3ZnqMqAQQP9u3Ai9t+5eQ02GDwy0ODjdzi0xlp70W+ow63/0++YDEX1M0iwgUHwb # rJvfpklkZQvw3+kv3vUPItdwroczk9icflf55W1zOEKAcJVAIXpcMCU9AgMBAAGj # ggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQUyWOKMC7USvtulPPm40B+ # 9ezN4jAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8E # BAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGF # MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUH # MAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH # NFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBW # MFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk # RzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkw # FzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQCNxTph # Hp1SCt+ZrAmAfn0oQLFr0mLywSLaDXQIENoyKqxrFbJblzCVP/pkXmwXOdrOpWyg # LzlT12os5ipDCy35RBCg2UMeApEtrfGhz45F4Wt4WGdNdIbRWt3YTYJmpR+b7lr4 # d7Uwn+H600u4D7RnOGf8Wj4UNgAdZkfHhHv1mx9EVh71SJelcEN/oORSjXzdjfw1 # iZH9d8Nh/thn6hH23d+VsPAr6GAYyzSA02nXD1nYLI7Ijmiv+xLCiYC41DSFYL3G # hTiy0PxpawPtGRyaBVGzq+UiTfM8pD7KVyF5aQyWP4KhVGUUTnmm/RlYJoW3TiXA # /+t0YcT2oRVBm3JETjajHug2AL+v5jhtKVnd3D0rbHXEu27o+Q8p4sEWPMqKDB+q # bceb6T/6WcwTwXmQ9lOCLLYcsQeSWmvKqzpAec9etE14jOQAzLKWdE3w/TCaKtLR # aRT7LCkRYVnhA2D73FLje1O5b3HR5eHs0NzU/+xX7NbEdcofy0W3Wdwd1XOqtlpg # /JgwtKfZM5dqO94lbUveOiJBI+xZEbGRsMNbXmMREUTgu+Oca7Y73MPWcslIx2Vh # kSKSXjDbD6rgg39H5Mh7QfieAIjWagkJNt68Yfim6cjEzVSiLSeZfdkr5dtFPTW6 # jATlWJdYeeDRGCyatf8R1hSjzSvdN8yWQPT9gzCCB2MwggVLoAMCAQICEAhTOt+G # wz3QWpc3Z3ZgARUwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMTAeFw0yNTEyMjYw # MDAwMDBaFw0yODEyMjcyMzU5NTlaMGsxCzAJBgNVBAYTAlBMMRIwEAYDVQQHDAlN # aWtvxYLDs3cxIzAhBgNVBAoTGkV2b3RlYyBTZXJ2aWNlcyBzcC4geiBvLm8uMSMw # IQYDVQQDExpFdm90ZWMgU2VydmljZXMgc3AuIHogby5vLjCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBANxbP/C0CFW8Z1gxi5+MxwMPmc5G8c3fIiV9S9FY # Lic5MEFf2hrQJ2PIx/Xf7bsfkuk0cSeUTnTL/sZYI9U9Hjah1EAxSvryILnhjoyZ # eedLTjOJkyWjoIs8cquJ8QAJgGYW8xhYp4e52MQGv8nHFkytFLebmrNJ88czDh4F # 5+UvvYovOkxJ68kvAXU0MMdiF3SOLii+gWQRcmRFQNghMJo4jS0jDfp7EzKN5GHY # iNbT5/xc1H16olQHZ37kIzGQlCel9+hTnPxf94eVouHo+HOQWlC55nujZAGFkID1 # h9EuRFCpPfapQVKmKSH8kxcJ1Ml11VQOE2HvFuMnOzE0DTq4qUSY7OZa5ddPXz5e # usNIwiIVEIXfKZD+UHRZkmdoSEGoCNVJHLRDTEKxnNJeaGKihE/K+DgeCYVipITP # 5MOwgG5nvOxILPs7CmxqaEGH9iT/xuJ4wiiBtLmvJiyJlHUOsVj0GtTdxMP5jjg2 # tIWVRAMkLRsfqSz31c9DbENEh7n8u6qqWAW3U2pWktY1i0PZt9vwfJnboZQnP91h # +Lee9/3YGnVyXq6J/x0TkBD9tg3CD5cTBDtA3MyDYQ4AYkYoToj6QP8fJnecOHx7 # 8UuU+f3N//gAfAu1UR3cM044N3Btm6IuyaXaRrFCro8L0nAV0WFMGz/W1FWnqTfT # SQ/XAgMBAAGjggIDMIIB/zAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfRO # QjAdBgNVHQ4EFgQUehhCwq/shewf/KCCM+q+nhvZG/wwPgYDVR0gBDcwNTAzBgZn # gQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BT # MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGt # MIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZN # aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNp # Z25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGE # MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUH # MAKGUGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH # NENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAw # DQYJKoZIhvcNAQELBQADggIBAD/YXkCSn1GzVQkE3O/LO2XlSbJYUNUFpAN4F4JZ # qAjQN1KCIdzWBPCOA844XDWo3fZox3sO/KF0tXVF2QeD7scK5bpd2gVE0tTqsWO0 # 9wR5VvzBHWZI3vWk6JfBT8pZK3++7iqpOYtZPQFYhHCk0pKqwL8K5ajrSRNoXcKK # VK8+ITMgVuwKqRtYpCs3VjX/kEPZtVTXAOLWB875hcuaqWhHye199ZCtuV0xgp4k # v1Euk42sN4s5nuFgS0M3ZTAAujJLSFsKnbGv086tbBiOjj/ntPwbKd2jwZu1MDSK # rZjvKRvUvU2sK4xVu185n5Lfz4CU6j7XRVFAYYYDxvYfMJ9ABKiZgl5o++PPx66d # gLxDKNC30YIAnQXgExaFEp1u5OCEQNJ/VmYjwl8SRvMy4A20sb9bczlFww8tpRb6 # 2WCRdhZck7rPLsPBjQgvkreachnOQAO8hMY5R4i9t88vBVq+cxj3tg0PMgqv7MWO # mrgzBeTow7twTnSU9w1SgVoUi39M7t8pwX1E6xJQiJL+SFlBH+AJK0iByN7YZjW7 # 65xxum54/kolZb9hgWiGQABNQMBHhEGYG8LLuLdBotIFs4sL+aP7IJv5N0mierbK # N5ROlavUwY4YIVqQSj+c1BU+f+uljQkZ5YFFt1y8oiYvTHkUkLYjL8cHVLvxTzsT # Nd1ZMYIGWTCCBlUCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl # cnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWdu # aW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAIUzrfhsM90FqXN2d2YAEVMA0G # CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwLwYJKoZIhvcNAQkEMSIEIKqMdxVrPZNzTX+vaNRg0bebhHYIBFQ5xuu4sY3H # QDiSMA0GCSqGSIb3DQEBAQUABIICAEGrxiw0TQIVPcz/kbR49M0R0GaHz9qk4PjG # 5JM1C/SIVmpRaSpKeg/NYBr2SG7QC+6faM+pITV651Rc74hnNHGzVIe3sr8nuPup # M4v4qzGlEd++obq0MwCfY5QGDtSa3BJMKfkVAIl+SLqU/TDkSqJwGsDYTaeswtE2 # UUtE0DqLTkbtVO8ez4fPxMzZWZhjL1D36iSLrJKuyDIhxbYVBnVvVhV6MmLdOtk8 # GV9fQKLTQTijMSDtrLNJexEiApkVgQZR4O14jQkldE1VI3NB4wc5vyImR50eoSSl # iAOJ/Nywk4aS06rDJujACtQrgJYZdCtAV7ZZ68he27pctm4wjniv/wNVai35w4a3 # F+V00Q76/YWkjk911yDhXj2PgT4DwfO3k+NKQmK3AD/i4Gpai69KaMaI1GSOw1BR # Emjgc6eCtAwdttlPWN+QbK0u+NOpihwXJy3lHJRxZ0EQ1SOk2OIMwQke1OQHV0DM # HOaf3zUHO9ZnrJkplBoGd7AO5i3xkq9QoSCDHdTnE+SjxyUxoOiaYyGXZ1ugFpxa # gk+KOL5YDbH6TanHZfhXNb3ufxKPv9GCClzEn+t8V+8oa23oPLoety2aj13KgqQm # 5F+gSr4miYoYyUyQ9Y4EN9uwGcSdTeoW9FwFlBvZAp6dkTjDXTxvt/sMfvG7MiDp # 72zHRg1IoYIDJjCCAyIGCSqGSIb3DQEJBjGCAxMwggMPAgEBMH0waTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENB # MQIQCE/cM09+RU7bww+P+ZIYNTANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJ # AzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI2MDkwODA3NTA0NFowLwYJ # KoZIhvcNAQkEMSIEIC8KgV+Gu0zBT6CUUm8gh5G7Zv09Nv+fHaDFbFB8UaWRMA0G # CSqGSIb3DQEBAQUABIICAIjH3HUhUGcoapWPnKytsxe0RRH+qOhyV60s8H0yXe3S # h3zc0E91J6qOvHjVE26fbn8ADbwrR6hzAC3xDRwJ92SNvrPOCmCuFl2zBscQrPTo # xaHo/FnaqrXQpP7V7yyAW+c0FJd7RPEiLDNCX5yDMTmVjHFZpRZjpItDZnNaPXtK # yPna9wE2CWUy5YC19pvdnpqwEVU1xui+D0C3mhUCEl3LQ9ZqpkHUSBxFqEQyahQM # SZy0eHdJDbVHOvHA0ZH2uKLSTnQicHIKzBwjiqWJM5J1J6fPOy4adGw/4FB1MTnU # cDQXpq1Mxxk7qqcLM6OWo3q8cKfGuDpNIRytwBLexzYlAVkZklhIGoJ7fgpY7qpP # Xhjf9Pa4aYyZh9shFZ/ZTwfpNINKbnCzDSn8TdYA42glcnIO8D+69vyNS7K4LbOL # S8OoD2mFf6twg+9AVc0kikcAJribt0whzZYFHI3IchKJXPoZ3A7zClGCFZ8EF8ko # Ed2Lz40nSiNfJNtIwh27db+HpsMp7OSiXfueVn8e33kfWXMXyHBkdXA4tdOptZdv # 5ysFXf60zFLbw5YcU3oMBcRTu7j5ORYlZ/BVCF3IFsVYrIcneEq3fmay1dmCnllu # pgV+/41fIDd1eH3YD8yoe5jXCxR7YkYSmX+96/8y/qSF6K74Yk0oo8YAWfJ/jwn9 # SIG # End signature block |