Functions/Public/MSAzure.ps1
|
# Microsoft Azure and Graph API functions Function Get-MSAzureUser { <# .SYNOPSIS Return a list of users from Azure AD. .DESCRIPTION Return a list of users from Azure AD. .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties .PARAMETER UPN Return user details for the given UserPrincipalName .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER HideProgressBar Don't show the progress bar. Cleans up logs when running on Docker. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TotalCount Only return a total count of objects .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSAzureUser Returns all MS Azure user accounts .EXAMPLE Get-MSAzureUser -Properties 'id,userPrincipalName' -AuthToken $AuthToken Returns a list of Azure users' ID and userPrincipalNames using a previously-obtained authtoken .EXAMPLE Get-MSAzureUser -Filter "startswith(displayName,'Meeting Room')" Returns a list of Azure users whose display names start with 'Meeting Room' .NOTES Version 1.3 #> Param ( [Parameter(Mandatory=$False)] [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(Mandatory=$False)] [string]$UPN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 500000, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1 ) Process { $Body = @{'$count' = 'true'} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token. $($_.Exception.Message)" If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ } } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" ConsistencyLevel = 'eventual' } If ($Properties) { $Body.Add('$select',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) } # If UPN is entered, just look for that user and exit. If ($UPN) { $URI = "https://graph.microsoft.com/v1.0/users/$UPN" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers Return $JSON } If ($TotalCount) { $URI = 'https://graph.microsoft.com/v1.0/users/$count' $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $UserCount = New-Object PsObject $UserCount | Add-Member -NotePropertyName 'UserCount' -NotePropertyValue $JSON If ($TenantName) { $UserCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $UserCount } Else { $URI = "https://graph.microsoft.com/v1.0/users" $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $TotalUsers = $JSON.'@odata.count' $JSON.value $PageSize = $JSON.value.count $Message = "Getting $TotalUsers Azure AD users for tenant $TenantName." If ($HideProgressBar) { Write-Host $Message } $UserCount = 0 $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) { $Percentage = ($PageSize / $TotalUsers) * 100 If ($HideProgressBar) { $Percentage = [math]::Round($Percentage,1) Write-Host "Retrieving $Percentage`% of $($TotalUsers) users on tenant $TenantName..." } Else { Write-Progress -Activity $Message -PercentComplete $Percentage -Status 'Retrieving...' } $Stopwatch.Reset() $Stopwatch.Start() } } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get user data. $($_.Exception.Message)" Clear-Variable -Name AuthToken } } } } Function Get-MSAzureUserGroupMembership { <# .SYNOPSIS Return the groups that a user is a member of from Azure AD. .DESCRIPTION Return the groups that a user is a member of from Azure AD. .PARAMETER ID The Azure AD GUID of the user .PARAMETER Transitive Show groups where the user is a member of a group that is a member of another group .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TotalCount Only return a total count of objects .PARAMETER ResultSize The number of results to return. Defaults to 10000. .EXAMPLE Get-MSAzureUserGroupMembership -ID abcdefab-1234-1234-1234-abcdabcdabcd Returns the groups that the selected user is a member of .EXAMPLE Get-MSAzureUser -Properties 'id,userPrincipalName' -AuthToken $AuthToken Returns a list of Azure users' ID and userPrincipalNames using a previously-obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ID, [Parameter(Mandatory=$False)] [switch]$Transitive, [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 10000, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1 ) Begin { If ($Transitive) { $MemberOfScope = 'transitiveMemberOf' } Else { $MemberOfScope = 'MemberOf' } } Process { $Body = @{} $Params = @{} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token. $($_.Exception.Message)" If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ } } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($Properties) { $Body.Add('$select',$Properties) $Params.Add('Properties',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) $Params.Add('Filter',$Filter) } If ($TotalCount) { $Body.Add('ConsistencyLevel','eventual') $URI = "https://graph.microsoft.com/v1.0/users/$ID/$MemberOfScope/`$count" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $GroupCount = New-Object PsObject $GroupCount | Add-Member -NotePropertyName 'UserCount' -NotePropertyValue $JSON If ($TenantName) { $GroupCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $GroupCount } Else { $URI = "https://graph.microsoft.com/v1.0/users/$ID/$MemberOfScope" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get group membership data. $($_.Exception.Message)" Clear-Variable -Name AuthToken } } } } Function Get-MSAzureGroup { <# .SYNOPSIS Return a list of groups from Azure AD. .DESCRIPTION Return a list of groups from Azure AD. .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER HideProgressBar Don't show the progress bar. Cleans up logs when running on Docker. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TotalCount Only return a total count of objects .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSAzureGroup Returns all MS Azure groups .EXAMPLE Get-MSAzureGroup -Filter "startsWith(displayName,'Global-')" Returns all MS Azure groups whose display names start with Global- .EXAMPLE Get-MSAzureGroup -Properties 'id,DisplayName' -AuthToken $AuthToken Returns a list of Azure group ID and display names using a previously-obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1 ) Process { $Body = @{} $Params = @{} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token. $($_.Exception.Message)" If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ } } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($Properties) { $Body.Add('$select',$Properties) $Params.Add('Properties',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) $Params.Add('Filter',$Filter) } If ($TotalCount) { $Body.Add('ConsistencyLevel','eventual') $URI = 'https://graph.microsoft.com/v1.0/groups/$count' $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $GroupCount = New-Object PsObject $GroupCount | Add-Member -NotePropertyName 'GroupCount' -NotePropertyValue $JSON If ($TenantName) { $GroupCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $GroupCount } Else { $URI = "https://graph.microsoft.com/v1.0/groups" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $TotalGroups = Get-MSAzureGroup @Params -TotalCount $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count $Message = "Getting Azure AD groups for tenant $TenantName." If ($HideProgressBar) { Write-Host $Message } $GroupCount = 0 $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) { $Percentage = ($PageSize / $TotalGroups.GroupCount) * 100 If ($HideProgressBar) { $Percentage = [math]::Round($Percentage,1) Write-Host "Retrieving $Percentage`% of $($TotalGroups.GroupCount) users on tenant $TenantName..." } Else { Write-Progress -Activity $Message -PercentComplete $Percentage -Status 'Retrieving...' } $Stopwatch.Reset() $Stopwatch.Start() } } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get group data. $($_.Exception.Message)" Clear-Variable -Name AuthToken } } } } Function Get-MSAzureGroupMembers { <# .SYNOPSIS Return a list of group members from Azure AD. .DESCRIPTION Return a list of group members from Azure AD. .PARAMETER ID The GUID of the group to return membership info .PARAMETER Transitive Show members where the members are members of a group that is a member of another group .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 10000. .EXAMPLE Get-MSAzureGroup -Filter "startsWith(displayName,'Global-Sales')" | Get-MSAzureGroupMembers -TotalOnly Returns all MS Azure groups whose display names start with Global-Sales .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$ID, [Parameter(Mandatory=$False)] [switch]$Transitive, [Parameter(Mandatory=$False)] [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 10000 ) Begin { If ($Transitive) { $MemberScope = 'transitiveMembers' } Else { $MemberScope = 'members' } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token. $($_.Exception.Message)" If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ } } } Process { $Body = @{} $Params = @{} If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($Properties) { $Body.Add('$select',$Properties) $Params.Add('Properties',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) $Params.Add('Filter',$Filter) } If ($TotalCount) { $Body.Add('ConsistencyLevel','eventual') $URI = "https://graph.microsoft.com/v1.0/groups/$ID/$MemberScope/`$count" $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $MemberCount = New-Object PsObject $MemberCount | Add-Member -NotePropertyName 'MemberCount' -NotePropertyValue $JSON If ($TenantName) { $MemberCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $MemberCount } Else { $URI = "https://graph.microsoft.com/v1.0/groups/$ID/$MemberScope" $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get group member data. $($_.Exception.Message)" Clear-Variable -Name AuthToken } } } } # SIG # Begin signature block # MIIfjQYJKoZIhvcNAQcCoIIffjCCH3oCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAuhKQ7wx6R3STR # 5mf/3i6uJ8J35tNDI37O3cZWN39MHKCCDRIwggZyMIIEWqADAgECAghkM1HTxzif # CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx # EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G # A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe # Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w # DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv # cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD # QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG # bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU # Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX # 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC # GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB # wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk # jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X # gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg # Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge # VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR # 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV # t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF # MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH # AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE # CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g # LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G # A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ # KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH # 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x # xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT # V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P # Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc # +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ # LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV # HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3 # NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9 # XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz # iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od # MIIGmDCCBICgAwIBAgIQIc+kRkn+AQupTThE+j58IjANBgkqhkiG9w0BAQsFADB4 # MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x # ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu # ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI1MDgwODIwMTMzNFoXDTI2MDgw # ODIwMTMzNFowfzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRAwDgYD # VQQHDAdKZXJpY2hvMR4wHAYDVQQKDBVOZWN0YXIgU2VydmljZXMgQ29ycC4xCzAJ # BgNVBAsMAklUMR4wHAYDVQQDDBVOZWN0YXIgU2VydmljZXMgQ29ycC4wggGiMA0G # CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCJXN3SkHk8zvqqnHkfyImA6vDVtGrO # zTVO6nlzNe85eCoGRBk5ToZ+/uUwcFyjcxSV+jbR29y4O6azNBmwvEw96ukUVKUh # J+0NIQoH2DJqtkp4v3EppsevtxxwarqC1fvMXMhz2NVBZS6moStIiFyeGTBgyR7P # gdU+JRqiG1muq5QiSZWjCyBLN6DTDimz5YdX1nhc/64V/oT0g79tYmZm7UEw1rN8 # HZgk46Gezt3IIQGX22ng1nh/3vy1Q46T/8mCT3UANrd3l/jS0XUmYgW8Z91nYlrl # iNH3nHbONGNFFN8WLPAakt3ITeGmqhZkHyyXmlxKlkLHiR8XewRHn7PpD/DT03x7 # ngKS5Ie0fUM1ZAdXDoghvQ6uNuQ5Q3TAjL2ukJs9u5VmvWyFL1l9ujuKCiNGfy1D # cS7u1WlcCIXdrX4Hpe2lt/M7fZFkSMeS1TD2gM2+a/7xK5MWwmbV6qK27nKlRpbG # Q6Yj0VmqJmcgekSrCKPFudNAsDyD6rUYxlUCAwEAAaOCAZUwggGRMAwGA1UdEwEB # /wQCMAAwHwYDVR0jBBgwFoAUVML+EJUAk81q9efA19myS7iPDOMwegYIKwYBBQUH # AQEEbjBsMEgGCCsGAQUFBzAChjxodHRwOi8vY2VydC5zc2wuY29tL1NTTGNvbS1T # dWJDQS1Db2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jZXIwIAYIKwYBBQUHMAGGFGh0 # dHA6Ly9vY3Nwcy5zc2wuY29tMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwGDCsGAQQB # gqkwAQMDATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29tL3JlcG9z # aXRvcnkwEwYDVR0lBAwwCgYIKwYBBQUHAwMwTQYDVR0fBEYwRDBCoECgPoY8aHR0 # cDovL2NybHMuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQw # OTYtUjEuY3JsMB0GA1UdDgQWBBQoc8kxtmxEx1bpEE+bhb8JNyGrHTAOBgNVHQ8B # Af8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAD1Wlyoz/oOVnzXFQKuv29SQ9J6G # lqSNMvPzF6204eydcEDLwKh5IpZ7iGo6Km5AoVGNzYwJWyWaTwZpaxOoh+M+NK+E # /QIOH+mItAaHhKogn96pHL7ZG9oD23s+/EP9jqNpenOZ0HSBWNHc4PO0e7Ys5zyQ # 0a63PF83kU6hqkny0hca7Mr4gZoFyt6ynEJvv/TIkNCE5tqo/cBW94EpjQBfXTd4 # ycDoQ5Be1gbLboBoEjyrnPl0n+5dQgXSCg25hgf37iTfg+sCXzw3SKeiFauf4s8o # h3wyXvNGzW4q0ZoE+ZNInk/puRVGnGmGZUr6iKuplqey1vneOOAv5we2hryR8PaA # eqo6OqxyEw6a+nmRwf3lsp9k6Aml6ary1bdGjRY+ysScAxbeCvC9yS/EzwtaU3Z8 # R+2qLqh7cX8gztflHxrsV7Ql0nE3MrM0ry2bAWYzthbdwYtIuoeGHibng/qxuHOI # uRLSTzddpXDdLSmw+G116kaxxHCBM+wf6K0nvehm08pXNyzAolNIJrVjsll9YHDT # wws15kpojCglZsMIDYAlrFzF9MI0dRdiaj7/ttGSdblKZJoDOdfPrWMuBlSmjamk # 5VxgKKHPQ9mHTP1Q5baEuotRYqJLgTWV1ZzCF+MYH2vmmuyjm4sSEjVfetLaYwIk # RYAx7I+e9wcfhciSMYIR0TCCEc0CAQEwgYwweDELMAkGA1UEBhMCVVMxDjAMBgNV # BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0 # MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRlIENBIFJT # QSBSMQIQIc+kRkn+AQupTThE+j58IjANBglghkgBZQMEAgEFAKB8MBAGCisGAQQB # gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC # AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAMQLytDwr8kRccCkgE # kaMOQ7DCPdjrM8U3+GXJpQpU2jANBgkqhkiG9w0BAQEFAASCAYCIcagMKoGS5xFg # S4BgfXqdwCovEQsEVNXEMBi50RNMXq1UTt5OXkHy5g4LuIRdOXVdvj0YJnPmYLBi # 3h/uDt+y4RMkJ6jIjlFI/sHVhhTMiRPpRayydz19CbDbbZ5KdEyRujNo0NApAYo5 # d52XDJk+7Z+l+2712QxGHbAgFRxTuTd3/igUIKILki44BwP2Hy0QRbdQLdkTOIHN # EqLmNYpw9TtmA0RH34b6jD/EEmmcqcAmXxhK2LxSd9xEx+fG3nreTNgwsESIKw13 # AdHNhmP+8mYOLLsEtJEClNcq31OimidHxeaxljhiNjEdUVpHDPsM+YLJkGwKjQ74 # r+uZu7cbozqVfwBMPtXcHKHJo/3L4Z/05yRAXY92fWwpaCaJhdCLiG0zMRxTKwbg # nuee2O5xc0G4TDuuCYEOFeSPJbVObxDYWhFi60Xvz1XHekYX2DMSe8DqK6EjOTDg # XNNfW9t99GOA3FDe4cubNzyLkLdBeVMhuw87cC8llcxSmPazfiihgg8XMIIPEwYK # KwYBBAGCNwMDATGCDwMwgg7/BgkqhkiG9w0BBwKggg7wMIIO7AIBAzENMAsGCWCG # SAFlAwQCATB3BgsqhkiG9w0BCRABBKBoBGYwZAIBAQYMKwYBBAGCqTABAwYBMDEw # DQYJYIZIAWUDBAIBBQAEIDObmC1oOwuPmy9nSvuBs3V3NQ3EDBBEHAgwryfYPp/o # Aghv764pKMkTMRgPMjAyNjA0MDYxOTQzNTlaMAMCAQGgggwAMIIE/DCCAuSgAwIB # AgIQH2sWYtIuG2xd8cDBoGAOODANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJV # UzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNT # TCBDb3JwMS8wLQYDVQQDDCZTU0wuY29tIFRpbWVzdGFtcGluZyBJc3N1aW5nIFJT # QSBDQSBSMTAeFw0yNTAyMTgxNjMyMDJaFw0zNDExMTIxODUwMDVaMG4xCzAJBgNV # BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UE # CgwIU1NMIENvcnAxKjAoBgNVBAMMIVNTTC5jb20gVGltZXN0YW1waW5nIFVuaXQg # MjAyNSBFMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBv7UVHHb+ZVluxPXlfE # 3M6tg0Xnq8dic+O3vPOCRpalUM1vO9A+GRzSVVjyygHhYrBw62XLFh1kv7e+yRd/ # aTajggFaMIIBVjAfBgNVHSMEGDAWgBQMnRAljpqnG5mHQ88IfuG9gZD0zzBRBggr # BgEFBQcBAQRFMEMwQQYIKwYBBQUHMAKGNWh0dHA6Ly9jZXJ0LnNzbC5jb20vU1NM # LmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY2VyMFEGA1UdIARKMEgwPAYMKwYB # BAGCqTABAwYBMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8vd3d3LnNzbC5jb20vcmVw # b3NpdG9yeTAIBgZngQwBBAIwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwRgYDVR0f # BD8wPTA7oDmgN4Y1aHR0cDovL2NybHMuc3NsLmNvbS9TU0wuY29tLXRpbWVTdGFt # cGluZy1JLVJTQS1SMS5jcmwwHQYDVR0OBBYEFM582cAEgMUkEGoJ6hyrJT0R/ajS # MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAgHN1JIW9ZlNip07F # fY9r1LJwLKTl1h/y9r1BjDgHMEz7U7J7mAy32JxA1KLyjCCJqNkuRfhDHGFEfZtj # TOru7AQMDSDiUDvCCc3jaz5py9jGvwIe5npkLbXqXhA+SqCzG719USigwhHkYr7v # 8NiXoaY0Xu03p0BiZZreZlpngmGB9+86N94FqIFx2I5Z5iuu7d4j7aAOZBNxfU9j # FDIIGDtWfWeCpF0Q8rLE/3DHDlzJKs5Il3i/VoOl7rTU938oSrxMwww6GDBlKX8r # GHxvkjgIb9MXkVglJSDHsNAZDeCS/inaxKHA+ChuB8K4OxIxa6k1e+eHBypakAY3 # wuN7w7PlPmhN6k5IdqZn3HZHs2VjoR841Z9wmVPFdGDdkBZ7XOmb5OZUaKufsvtE # R0VbY2DkzaqAaItt1kc+I+FUz7PiVz2PUzpegkftRRxvrPyIL08blJTnbMQ0XrqD # N0rEfuTLv4XhhnKyJaXhxCUdjO/cRumMzZ35QTUMItBWy1xGp0iyoFVnAya2pPsV # MuAI+sy1zxlOS9l5iSxvKJ8gpnpOgjqYTa7u1eyZo+4JlcGoiiR17LthMTrF1q62 # tO5xOpI8txUZ6gtKGtkJV6wdj+vZLcwKZ2M93GqVpOo6UBlGjx1sKsVZ7AIUzJZl # i4NJ0M2KYcrfNnlebkYKpDnSqWwwggb8MIIE5KADAgECAhBtUhhwh+gjTYVgANCA # j5NWMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh # czEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEw # LwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB # MB4XDTE5MTExMzE4NTAwNVoXDTM0MTExMjE4NTAwNVowczELMAkGA1UEBhMCVVMx # DjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wg # Q29ycDEvMC0GA1UEAwwmU1NMLmNvbSBUaW1lc3RhbXBpbmcgSXNzdWluZyBSU0Eg # Q0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCuURAT0vk8IKAg # hd7JUBxkyeH9xek0/wp/MUjoclrFXqhh/fGH91Fc+7fm0MHCE7A+wmOiqBj9ODrJ # AYGq3rm33jCnHSsCBNWAQYyoauLq8IjqsS1JlXL29qDNMMdwZ8UNzQS7vWZMDJ40 # JSGNphMGTIA2qn2bohGtgRc4p1395ESypUOaGvJ3t0FNL3BuKmb6YctMcQUF2sqo # oMzd89h0E6ujdvBDo6ZwNnWoxj7YmfWjSXg33A5GuY9ym4QZM5OEVgo8ebz/B+gy # hyCLNNhh4Mb/4xvCTCMVmNYrBviGgdPZYrym8Zb84TQCmSuX0JlLLa6WK1aO6qlw # ISbb9bVGh866ekKblC/XRP20gAu1CjvcYciUgNTrGFg8f8AJgQPOCc1/CCdaJSYw # hJpSdheKOnQgESgNmYZPhFOC6IKaMAUXk5U1tjTcFCgFvvArXtK4azAWUOO1Y3fd # ldIBL6LjkzLUCYJNkFXqhsBVcPMuB0nUDWvLJfPimstjJ8lF4S6ECxWnlWi7OElV # wTnt1GtRqeY9ydvvGLntU+FecK7DbqHDUd366UreMkSBtzevAc9aqoZPnjVMjvFq # V1pYOjzmTiVHZtAc80bAfFe5LLfJzPI6DntNyqobpwTevQpHqPDN9qqNO83r3kaw # 8A9j+HZiSw2AX5cGdQP0kG0vhzfgBwIDAQABo4IBgTCCAX0wEgYDVR0TAQH/BAgw # BgEB/wIBADAfBgNVHSMEGDAWgBTdBAkHovV6fVJTEpKV7jiAJQ2mWTCBgwYIKwYB # BQUHAQEEdzB1MFEGCCsGAQUFBzAChkVodHRwOi8vd3d3LnNzbC5jb20vcmVwb3Np # dG9yeS9TU0xjb21Sb290Q2VydGlmaWNhdGlvbkF1dGhvcml0eVJTQS5jcnQwIAYI # KwYBBQUHMAGGFGh0dHA6Ly9vY3Nwcy5zc2wuY29tMD8GA1UdIAQ4MDYwNAYEVR0g # ADAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkw # EwYDVR0lBAwwCgYIKwYBBQUHAwgwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2Ny # bHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0GA1UdDgQWBBQMnRAl # jpqnG5mHQ88IfuG9gZD0zzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD # ggIBAJIZdQ2mWkLPGQfZ8vyU+sCb8BXpRJZaL3Ez3VDlE3uZk3cPxPtybVfLuqac # i0W6SB22JTMttCiQMnIVOsXWnIuAbD/aFTcUkTLBI3xys+wEajzXaXJYWACDS47B # RjDtYlDW14gLJxf8W6DQoH3jHDGGy8kGJFOlDKG7/YrK7UGfHtBAEDVe6lyZ+FtC # srk7dD/IiL/+Q3Q6SFASJLQ2XI89ihFugdYL77CiDNXrI2MFspQGswXEAGpHuaQD # THUp/LdR3TyrIsLlnzoLskUGswF/KF8+kpWUiKJNC4rPWtNrxlbXYRGgdEdx8SMj # UTDClldcrknlFxbqHsVmr9xkT2QtFmG+dEq1v5fsIK0vHaHrWjMMmaJ9i+4qGJSD # 0stYfQ6v0PddT7EpGxGd867Ada6FZyHwbuQSadMb0K0P0OC2r7rwqBUe0BaMqTa6 # LWzWItgBjGcObXeMxmbQqlEz2YtAcErkZvh0WABDDE4U8GyV/32FdaAvJgTfe9Mi # L2nSBioYe/g5mHUSWAay/Ip1RQmQCvmF9sNfqlhJwkjy/1U1ibUkTIUBX3HgymyQ # vqQTZLLys6pL2tCdWcjI9YuLw30rgZm8+K387L7ycUvqrmQ3ZJlujHl3r1hgV76s # 3WwMPgKk1bAEFMj+rRXimSC+Ev30hXZdqyMdl/il5Ksd0vhGMYICWTCCAlUCAQEw # gYcwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3Vz # dG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBUaW1lc3Rh # bXBpbmcgSXNzdWluZyBSU0EgQ0EgUjECEB9rFmLSLhtsXfHAwaBgDjgwCwYJYIZI # AWUDBAIBoIIBYTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcN # AQkFMQ8XDTI2MDQwNjE5NDM1OVowKAYJKoZIhvcNAQk0MRswGTALBglghkgBZQME # AgGhCgYIKoZIzj0EAwIwLwYJKoZIhvcNAQkEMSIEICwXDMs3W6XQRCMO3+Pi3n5K # FiR7xNnsTMPmZwHwi/WbMIHJBgsqhkiG9w0BCRACLzGBuTCBtjCBszCBsAQgVCr5 # oWqNci5mEUl4iumUwYqaruWmXLNEolSa+Wx5x4swgYswd6R1MHMxCzAJBgNVBAYT # AlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwI # U1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3Vpbmcg # UlNBIENBIFIxAhAfaxZi0i4bbF3xwMGgYA44MAoGCCqGSM49BAMCBEgwRgIhAI6E # X3EF/PaIX7URRajAUOMTG6bRdj4c60CoimZtvCcLAiEAl7IGs2L1yA/jcQbrAyDy # GNwD4s93Yi9YCQ0dZQVjdag= # SIG # End signature block |