VConnectMultiStampManagement.psm1
Invoke-InstallOrUpdateModule 'SqlServer' function Get-VConnectAzsStampsHealth { [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "VConnect API connection name; Please use Add-ApiConnection to create new connecition")] [string] $endpointName, [Switch] $exportCsv, [Switch] $gridView, [Switch] $printToConsole, [Switch] $extendedOutput, [Switch] $showLogs ) try { $result = Invoke-RestApiRequest -endpointName $endpointName -relativeUrl 'admin/AzureStackAdminConnections/Get' -method 'GET' if (($result.Length -eq 2) -and ($null -ne $result[1])) { $azsConnectionsList = $result[1] | Select-Object -Property ConnectionId, ConnectionName, Location, ArmEndpointUrl, TenantArmEndpointUrl, DefaultProviderSubscriptionId, TenantId, ClientId, LoginEndpoint, UseCertificateThumbprintInsteadAppSecret, EnvironmentCategoryTag, EnvironmentSubCategoryTag, IsAdfs, IsDisconnectedEnvironment, IsAllowManagement, CreatedTime, LastUpdatedTime foreach ($azsConnection in $azsConnectionsList) { try { $adminArmEndpoint = $azsConnection.ArmEndpointUrl $requestUrl = "$($adminArmEndpoint)/metadata/endpoints?api-version=2015-01-11" if ($showLogs) { Write-Host "$($azsConnection.ConnectionName): Validating ARM endpoint..." Invoke-WebRequest $requestUrl } else { Invoke-WebRequest $requestUrl | Out-Null } Add-Member -InputObject $azsConnection -MemberType NoteProperty -Name Reachable -Value $true } catch { $exception = $_ if ($showLogs) { Write-Host "$($azsConnection.ConnectionName) Validating ARM endpoint failed." Write-Host $exception } Add-Member -InputObject $azsConnection -MemberType NoteProperty -Name Reachable -Value $false } } if ($gridView) { $azsConnectionsList | Out-GridView return } if ($exportCsv) { $dateTimeTicks = (Get-Date).Ticks $outFileName = "Get-MamStamps-$($endpointName)-$($dateTimeTicks).csv" Write-Host "Writing to File: $($outFileName)" $azsConnectionsList | Export-Csv -Path $outFileName -NoTypeInformation return } if ($printToConsole) { if (-not $extendedOutput) { $azsConnectionsList = $azsConnectionsList | Select-Object -Property ConnectionId, ConnectionName, Location, IsAdfs, EnvironmentCategoryTag, ArmEndpointUrl,TenantArmEndpointUrl,Reachable } Write-Host "" Write-Host "Azure Stack Hub Stamp Connectivity Status" -BackgroundColor White -ForegroundColor DarkBlue Write-Host "" foreach ($azsConnection in $azsConnectionsList) { Write-Host "------------------------------------------------------------------------------------------------" if ($azsConnection.Reachable) { Write-Host "$($azsConnection.ConnectionName) is Reachable" -BackgroundColor Green -ForegroundColor Black } else { Write-Host "$($azsConnection.ConnectionName) is not Reachable" -BackgroundColor Red -ForegroundColor Black } Write-Host "------------------------------------------------------------------------------------------------" Write-Output -InputObject $azsConnection Write-Host "" } return } return $azsConnectionsList } Write-Error "Command returns null result." return $null } catch { $exception = $_ Write-Host "Azure Stack Hub Stamp ARM endpoint validation: Failed to get stamp details." Write-Error $exception } } function Get-VConnectDiscoveredResourcesStatus { param( [Parameter(Mandatory = $true, Position = 0)] $name, [Parameter(Mandatory = $true, Position = 1)] $dbName, [Parameter(Mandatory = $false, Position = 2)] [int] $connectionId = 0, [Switch] $printToConsole ) $query = "select drs.PlatformConnectionId as ConnectionId, drs.Cluster as StampName, drs.PlatformServiceName as Service, drs.ResourceSubType as ResourceType, Max(drs.DiscoveredDate) as UpdatesDate, Max(dj.Id) as DiscoverJobId, Max(ds.DeploymentId) as DeploymentId, Max(ops.ResultMessage) as LastMessage from DiscoveredResources drs with (nolock) inner join DiscoveryJobs dj with (nolock) on drs.DiscoveryJobId = dj.Id inner join Deployments ds with (nolock) on ds.DeploymentId = dj.DiscoveryJobDeploymentId inner join Operations ops with (nolock) on ops.DeploymentId = ds.DeploymentId Where ($connectionId <= 0 or drs.PlatformConnectionId = $connectionId) and drs.IsDeleted = 0 and drs.PlatformConnectionType = 1050 and ds.StatusString = 'Completed' and ops.OperationName = 'DiscoverAdminPlatformServiceOp' Group by PlatformConnectionId, Cluster, PlatformServiceName, ResourceSubType order by Cluster, PlatformServiceName, ResourceSubType" Write-Host "Azure Stack Hub Stamp Resource Update Status..." if ($printToConsole) { Invoke-SqlDbQuery $name $query $dbName -printToConsole return } return Invoke-SqlDbQuery $name $query $dbName } function Test-SharedFolderAccess { param( [Parameter(Mandatory = $true, Position = 0)] $MarketplaceOfflineRepoPath, [Parameter(Mandatory = $true, Position = 1)] $MarketplaceOfflineRepoUsername, [Parameter(Mandatory = $true, Position = 2)] $MarketplaceOfflineRepoPassword ) try { $MarketplaceOfflineRepoPath = $MarketplaceOfflineRepoPath.TrimEnd('\') Write-Verbose "Connecting to '$MarketplaceOfflineRepoPath'..." -Verbose net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no if (-not (Test-Path $("filesystem::$MarketplaceOfflineRepoPath"))) { Write-Error "Unable to access Offline repo directory '$MarketplaceOfflineRepoPath'" throw } $tempDownloadDir = "$MarketplaceOfflineRepoPath\testaccess.txt" New-Item $tempDownloadDir -ItemType File if (Test-Path $tempDownloadDir) { Remove-Item -Path $tempDownloadDir -Force } Write-Verbose "Read Write validation completed." -Verbose Write-Host "Validating SharedFolderAccess is success. " -BackgroundColor Black -ForegroundColor Green } catch { Write-Error "Test Offline Marketplace Repository access failed" throw } } function Test-AzureSyndicationCredential { param( [Parameter(Mandatory = $true, Position = 0)] $SyndicationSubscriptionId, [Parameter(Mandatory = $true, Position = 1)] $SyndicationAdminTenantId, [Parameter(Mandatory = $false, Position = 2)] $SyndicationAdminApplicationId, [Parameter(Mandatory = $false, Position = 3)] $SyndicationAdminApplicationCredential ) try { Import-Module -Name Az.Accounts -ErrorAction Stop Import-Module -Name Azs.Syndication.Admin -ErrorAction Stop } catch { Write-Error "Import Az.Accounts and Azs.Syndication.Admin failed." throw } # Login into Azure Stack environment try { Write-Verbose "Trying to login in to Azure..." -Verbose [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 Clear-AzContext -Scope Process -Force -ErrorAction Stop if ($SyndicationAdminApplicationId -ne $null) { $syndicationAdminCred = Get-Credential -UserName $SyndicationAdminApplicationId -Message 'Enter Azure Syndication Application Secret...' } else { $syndicationAdminCred = $SyndicationAdminApplicationCredential } if ($syndicationAdminCred -eq $null) { Write-Error "Please provide input for 'SyndicationAdminApplicationId' or 'syndicationAdminCred'..." } Connect-AzAccount -EnvironmentName AzureCloud -ServicePrincipal -Credential $syndicationAdminCred -TenantId $SyndicationAdminTenantId -Scope Process -Force -ErrorAction Stop Get-AzSubscription -SubscriptionID $SyndicationSubscriptionId | Select-AzSubscription -Scope Process } catch { Write-Error "Login into Azure to access $SyndicationSubscriptionId Failed." throw } try { Write-Verbose "Trying to list available Azure Marketplace Products..." -Verbose $productsSelected = Select-AzsMarketplaceItem $productsSelected Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue Write-Host "Validating AzureSyndicationCredential is success. " -BackgroundColor Black -ForegroundColor Green } catch { Write-Error "Test AzureSyndicationCredential is failed." throw } } function Export-AzsMarketplaceItemToOfflineRepo { param( [Parameter(Mandatory = $true, Position = 0)] $SyndicationSubscriptionId, [Parameter(Mandatory = $true, Position = 1)] $SyndicationAdminTenantId, [Parameter(Mandatory = $false, Position = 2)] $SyndicationAdminApplicationId, [Parameter(Mandatory = $false, Position = 3)] $SyndicationAdminApplicationCredential, [Parameter(Mandatory = $true, Position = 4)] $MarketplaceOfflineRepoPath, [Parameter(Mandatory = $true, Position = 5)] $MarketplaceOfflineRepoUsername, [Parameter(Mandatory = $true, Position = 6)] $MarketplaceOfflineRepoPassword ) try { Import-Module -Name Az.Accounts -ErrorAction Stop Import-Module -Name Azs.Syndication.Admin -ErrorAction Stop } catch { Write-Error "Import Az.Accounts and Azs.Syndication.Admin failed." throw } try { Write-Verbose "Connecting to '$MarketplaceOfflineRepoPath'..." -Verbose $MarketplaceOfflineRepoPath = $MarketplaceOfflineRepoPath.TrimEnd('\') $tempDownloadDir = "$MarketplaceOfflineRepoPath\.downloading" net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no if (-not (Test-Path $("filesystem::$MarketplaceOfflineRepoPath"))) { return Get-ScriptResult $false 1 "Unable to access Offline repo directory '$MarketplaceOfflineRepoPath'" } if (-not (Test-Path $tempDownloadDir)) { New-Item $tempDownloadDir -ItemType Directory -Force -ErrorAction SilentlyContinue } } catch { Write-Error "Test Offline Marketplace Repository access failed" throw } # Login into Azure Stack environment try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 Clear-AzContext -Scope Process -Force -ErrorAction Stop if ($SyndicationAdminApplicationId -ne $null) { $syndicationAdminCred = Get-Credential -UserName $SyndicationAdminApplicationId -Message 'Enter Azure Syndication Application Secret...' } else { $syndicationAdminCred = $SyndicationAdminApplicationCredential } if ($syndicationAdminCred -eq $null) { Write-Error "Please provide input for 'SyndicationAdminApplicationId' or 'SyndicationAdminApplicationCredential'..." } Connect-AzAccount -EnvironmentName AzureCloud -ServicePrincipal -Credential $syndicationAdminCred -TenantId $SyndicationAdminTenantId -Scope Process -Force -ErrorAction Stop Get-AzSubscription -SubscriptionID $SyndicationSubscriptionId | Select-AzSubscription -Scope Process } catch { Write-Error "Login into Azure to access $SyndicationSubscriptionId Failed." throw } try { Write-Verbose "Please select only one product to export." -Verbose $productToDownload = Select-AzsMarketplaceItem if ($productToDownload.Length -eq 0) { Write-Error "No Marketplace prodcuct is selected. Please try again." return } if ($productToDownload -is [array]) { Write-Warning "Multiple Products are selected to export. Please try again." -Verbose return } $MarketplaceItemId = $productToDownload.Split('/')[-1] Write-Verbose "Start exporting $MarketplaceItemId" -Verbose $products = @($productToDownload) $products | Export-AzsMarketplaceItem -RepositoryDir $tempDownloadDir -IgnoreDependencies -AcceptLegalTerms -ErrorAction Stop Write-Verbose "Exporting $MarketplaceItemId is completed successfully." -Verbose } catch { Write-Error "Export Marketplace products into Offline Repository failed." throw } Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue try { # Update status of downloaded Marketplace product folder Write-Verbose "Moving download product from temp folder to Repository." -Verbose net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no Move-Item "$tempDownloadDir\$MarketplaceItemId" $MarketplaceOfflineRepoPath -Force -ErrorAction Stop } catch { Write-Error "Configuring Offline Marketplace Repository is failed." throw } } function Import-AzsMarketplaceItemFromOfflineRepo { param( [Parameter(Mandatory = $true, Position = 0)] $MarketplaceProductId, [Parameter(Mandatory = $true, Position = 1)] $AdminArmEndpoint, [Parameter(Mandatory = $true, Position = 2)] $DefaultProviderSubscriptionId, [Parameter(Mandatory = $true, Position = 3)] $AzsAdminTenantId, [Parameter(Mandatory = $false, Position = 4)] $AdminApplicationId, [Parameter(Mandatory = $false, Position = 5)] $AdminApplicationCredential, [Parameter(Mandatory = $true, Position = 6)] $MarketplaceOfflineRepoPath, [Parameter(Mandatory = $true, Position = 7)] $MarketplaceOfflineRepoUsername, [Parameter(Mandatory = $true, Position = 8)] $MarketplaceOfflineRepoPassword ) try { Import-Module -Name Az.Accounts -ErrorAction Stop Import-Module -Name Az.Storage -ErrorAction Stop Import-Module -Name Azs.Syndication.Admin -ErrorAction Stop } catch { Write-Error "Import Az.Accounts, Az.Storage and Azs.Syndication.Admin failed." throw } try { Write-Verbose "Connecting to '$MarketplaceOfflineRepoPath'..." -Verbose # Validate access to shared folder / offline repo net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no if (-not (Test-Path $("filesystem::$MarketplaceOfflineRepoPath"))) { throw "Unable to find/access Offline repo directory $MarketplaceOfflineRepoPath" } $marketplaceItemPath = "$MarketplaceOfflineRepoPath\$MarketplaceProductId" if (-not (Test-Path $marketplaceItemPath)) { Write-Error "'$MarketplaceProductId' is not found the in offline repo. Please download the Marketplace Item." } } catch { Write-Error "Test Offline Marketplace Repository access failed" throw } try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 # Login into Azure Stack environment Clear-AzContext -Scope Process -Force -ErrorAction Stop $azsEnvironmentName = "AzsHub_ImportAzsMarketplaceItem" Add-AzEnvironment -Name $azsEnvironmentName -ArmEndpoint $AdminArmEndpoint -Scope Process -ErrorAction Stop if ($AdminApplicationId -ne $null) { $azSAdminCred = Get-Credential -UserName $AdminApplicationId -Message 'Enter Azure Stack Hub App Registration Secret...' } else { $azSAdminCred = $AdminApplicationCredential } if ($azSAdminCred -eq $null) { Write-Error "Please provide input for 'AdminApplicationId' or 'AdminApplicationCredential'..." } Add-AzAccount -EnvironmentName $azsEnvironmentName -ServicePrincipal -Credential $azSAdminCred -TenantId $AzsAdminTenantId -SubscriptionId $DefaultProviderSubscriptionId -Scope Process -ErrorAction Stop Get-AzSubscription -SubscriptionID $DefaultProviderSubscriptionId | Select-AzSubscription -Scope Process -ErrorAction Stop } catch { Write-Error "Login into Azure Stack Hub to access $DefaultProviderSubscriptionId Failed." throw } try { try { $JournalDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [guid]::NewGuid().ToString()) # Invoke Syndication Module $productName = $($MarketplaceProductId) # Product Name List to import Import-AzsMarketplaceItem -RepositoryDir $MarketplaceOfflineRepoPath -ProductName $productName -JournalDir $JournalDir -UpTo Import -ErrorAction Stop Write-Verbose "Importing $MarketplaceProductId is completed successfully." -Verbose } catch { Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue Write-Error "Import Marketplace products from Offline Repository failed." throw } # Removing Syndication tool journal directory if (Test-Path $JournalDir) { Remove-Item -Path $JournalDir -Recurse -Force -ErrorAction SilentlyContinue } try { # Can be found in 'Azs.Syndication.Admin' PS Module Path => Import.psm1 $syndicationTempRgName = "syndicationtemp-rg" $syndicationTempSaName = "syndicationtempsa" # Cleaning Syndication Temp Storage Account $tempSa = Get-AzStorageAccount -ResourceGroupName $syndicationTempRgName -Name $syndicationTempSaName -ErrorAction SilentlyContinue if ($null -eq $tempSa) { Write-Warning "Successfully imported Marketplace Item '$MarketplaceProductId'. Storage Account $syndicationTempSaName is not found. Cleaning imported Marketplace Item artifacts skiped." return } $saContext = New-AzStorageContext -ConnectionString $tempSa.Context.ConnectionString -ErrorAction Stop $artifactsToClean = Get-AzStorageBlob -Container temp -Context $saContext -Prefix "$MarketplaceProductId/" -ErrorAction Stop if ($true) { $artifactsToClean = $artifactsToClean | Where-Object { -not $_.Name.StartsWith("$MarketplaceProductId/icons/") } } $artifactsToClean | Remove-AzStorageBlob -Context $saContext -ErrorAction Stop -Verbose Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue } catch { Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue Write-Error "Import Marketplace Product completed successfully, but clean-up failed from storage account $syndicationTempSaName." throw } Write-Verbose "Successfully imported Marketplace Item '$MarketplaceProductId'." -Verbose } catch { Clear-AzContext -Scope Process -Force -ErrorAction Stop try { net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no if (Test-Path $($JournalDir)) { Remove-Item -Path $JournalDir -Recurse -Force -ErrorAction Stop } } catch { # Can be ignored } $_ } } # SIG # Begin signature block # MIIQQAYJKoZIhvcNAQcCoIIQMTCCEC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUQBAyniHslSBZ4A1nM88Myy3R # liCgggz8MIIGcjCCBFqgAwIBAgIIZDNR08c4nwgwDQYJKoZIhvcNAQELBQAwfDEL # MAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgw # FgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBD # ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwNjI0MjA0NDMwWhcNMzEw # NjI0MjA0NDMwWjB4MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV # BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29t # IENvZGUgU2lnbmluZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAn4MTc6qwxm0hy9uLeod00HHcjpdymuS7iDS0 # 3YADxi9FpHSavx4PUOqebXjzn/pRJqk9ndGylFc++zmJG5ErVu9ny+YL4w45jMY1 # 9Iw93SXpAawXQn1YFkDc+dUoRB2VZDBhOmTyl9dzTH17IwJt83XrVT1vqi3Er750 # rF3+arb86lx56Q9DnLVSBQ/vPrGxj9BJrabjQhlUP/MvDqHLfP4T+SM52iUcuD4A # SjpvMjA3ZB7HrnUH2FXSGMkOiryjXPB8CqeFgcIOr4+ZXNNgJbyDWmkcJRPNcvXr # nICb3CxnxN3JCZjVc+vEIaPlMo4+L1KYxmA3ZIyyb0pUchjMJ4f6zXWiYyFMtT1k # /Summ1WvJkxgtLlc/qtDva3QE2ZQHwvSiab/14AG8cMRAjMzYRf3Vh+OLzto5xXx # d1ZKKZ4D2sIrJmEyW6BW5UkpjTan9cdSolYDIC84eIC99gauQTTLlEW9m8eJGB8L # uv+prmpAmRPd71DfAbryBNbQMd80OF5XW8g4HlbUrEim7f/5uME77cIkvkRgp3fN # 1T2YWbRD6qpgfc3C5S/x6/XUINWXNG5dBGsFEdLTkowJJ0TtTzUxRn50GQVi7Inj # 6iNwmOTRL9SKExhGk2XlWHPTTD0neiI/w/ijVbf55oeC7EUexW46fLFOuato95tj # 1ZFBvKkCAwEAAaOB+zCB+DAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0E # CQei9Xp9UlMSkpXuOIAlDaZZMDAGCCsGAQUFBwEBBCQwIjAgBggrBgEFBQcwAYYU # aHR0cDovL29jc3BzLnNzbC5jb20wEQYDVR0gBAowCDAGBgRVHSAAMBMGA1UdJQQM # MAoGCCsGAQUFBwMDMDsGA1UdHwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5j # b20vc3NsLmNvbS1yc2EtUm9vdENBLmNybDAdBgNVHQ4EFgQUVML+EJUAk81q9efA # 19myS7iPDOMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQD1DyaH # cK+Zosr11snwjWY9OYLTiCPYgr+PVIQnttODB9eeJ4lNhI5U0SDuYEPbV0I8x7CV # 9r7M6qM9jk8GxitZhn/rcxvK5UAm4D1vzPa9ccbNfQ4gQDnWBdKvlAi/f8JRtyu1 # e4Mh8GPa5ZzhaS51HU7LYR71pTPfAp0V2e1pk1e6RkUugLxlvucSPt5H/5CcEK32 # VrKk1PrW/C68lyGzdoPSkfoGUNGxgCiA/tutD2ft+H3c2XBberpotbNKZheP5/Dn # V91p/rxe4dWMnxO7lZoV+3krhdVtPmdHbhsHXPtURQ8WES4Rw7C8tW4cM1eUHv5C # NEaOMVBO2zNXlfo45OYS26tYLkW32SLK9FpHSSwo6E+MQjxkaOnmQ6wZkanHE4Jf # /HEKN7edUHs8XfeiUoI15LXn0wpva/6N+aTX1R1L531iCPjZ16yZSdu1hEEULvYu # YJdTS5r+8Yh6dLqedeng2qfJzCw7e0wKeM+U9zZgtoM8ilTLTg1oKpQRdSYU6iA3 # zOt5F3ZVeHFt4kk4Mzfb5GxZxyNi5rzOLlRL/V4DKsjdHktxRNB1PjFiZYsppu0k # 4XodhDR/pBd8tKx9PzVYy8O/Gt2fVFZtReVT84iKKzGjyj5Q0QA07CcIw2fGXOho # v88uFmW4PGb/O7KVq5qNncyU8O14UH/sZEejnTCCBoIwggRqoAMCAQICEA0SjRWQ # uYT7eM+eDgHqTTMwDQYJKoZIhvcNAQELBQAweDELMAkGA1UEBhMCVVMxDjAMBgNV # BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0 # MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRlIENBIFJT # QSBSMTAeFw0yMTEwMjUyMDQ1NTNaFw0yMzEwMjUyMDQ1NTNaMHcxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdSZWRtb25kMRkwFwYD # VQQKDBBDbG91ZCBBc3NlcnQgTExDMQswCQYDVQQLDAJVUzEZMBcGA1UEAwwQQ2xv # dWQgQXNzZXJ0IExMQzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOr+ # k6LFTYXntOaV4dGI/mIyACrEE3JCr4RP5Aur5QgWuraKhL2JSh63eADxOOuk5P4E # KXhNG8F1XW67gDLNfUlQ9ZagD3+xts/Vc8hZOqmwGw57K0/EUy5RoVVVWntMQ7DX # q0VNp2SgMVHBuRWLvB7MX7OGTJ96+IWgEzMITBxx+bToBl+iefkJhOVZi2lCG9oN # M3i5Yrq2T7cV1uCQwl6JNBrsaCJ64vs6pz8LzR0XmhXtg5rLYehFCqcWYCcH4Njm # ZUVharTmBozLOTPdL6y3UReZRM5J1SxvrfRvalFQGWX4hK6OirBey1yPnhzqNHAt # iwCLxn5l+pnTh89LLmtc1Bp8OI2nN7yaiXK13441EQFpIYnBSQJ6e8n0dDpwwoux # OSfxtgX8iila0DBoy9vLCyGTnyXdO1zZYGoll9v8aSbvWOZu4n4gvQPVIhgROU74 # wkfGXI61Ab9ZtltF5W5WQesJoDiRIYgHUxYWU5fsTPzsoQFIXzHyaTqeJKXOtwID # AQABo4IBhzCCAYMwHwYDVR0jBBgwFoAUVML+EJUAk81q9efA19myS7iPDOMwegYI # KwYBBQUHAQEEbjBsMEgGCCsGAQUFBzAChjxodHRwOi8vY2VydC5zc2wuY29tL1NT # TGNvbS1TdWJDQS1Db2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jZXIwIAYIKwYBBQUH # MAGGFGh0dHA6Ly9vY3Nwcy5zc2wuY29tMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwG # DCsGAQQBgqkwAQMDATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29t # L3JlcG9zaXRvcnkwEwYDVR0lBAwwCgYIKwYBBQUHAwMwTQYDVR0fBEYwRDBCoECg # PoY8aHR0cDovL2NybHMuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmct # UlNBLTQwOTYtUjEuY3JsMB0GA1UdDgQWBBT5QOeOXNcPYtBWMGBY9lkdLp4AczAO # BgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAGL909UmLyhbmLPe0AyH # mItkDXXIonmIsCrNrquwtFB5ZFhV2eQEEcFi8N+R1Pw2CGWNQe8EGN83nr1ItDNc # JqweHvadc6i5FF1DVRPKEVHzORKKsKGZ97KyYQkT+YxJLfVCLdFCemCd2QYQuFJQ # 4LdKcR9QZE0LvoiE9qVZ0fv2oO/4Yg/jgFTS4m1znT1IXIfCgnxfK9dr5QwQt/wX # 3ayq554Ptbl7f6g9AGnD3U7cEaDvaPqRX16AGgxWbJU4W740UeNZnsFvdNcBHY/7 # wWxCzR03dzTGivW1aozokn05KeOyF0ZU7vhhXSeKyoaLzJXEr96r7pBUfBlVL9p9 # 6IVsHxsnPGFVZiaaZ0YQFsBWJZLEpVOIXCl2Jb2KX/NshRJGeijK0a6msVYIHKPv # mhLnDruJkadj4RIgk8AQ2wsttUWtjWRKjD072OnAVZatRsCPIPQJsk+8gSKqfDZR # o3DZhnrCd6TfjuoU9aULSXrwJljrOqLNOZHFoBuT7y3dZHPoo596yCmwUs+7dYCR # nBU+hQ0Fca9aWpaYw4lKdxxhXn66EIR00TbaE3HYdHhlOc8koA9VUI/eiWdd1rKL # j67luXYkCEJ37fE6SlyL1Jkhu3dd79+GSYlTINRnH415fH4DwiOMckj8kRbdyRV1 # tT1R5QeVMAdZHzQ80j8shEydMYICrjCCAqoCAQEwgYwweDELMAkGA1UEBhMCVVMx # DjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wg # Q29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRl # IENBIFJTQSBSMQIQDRKNFZC5hPt4z54OAepNMzAJBgUrDgMCGgUAoHgwGAYKKwYB # BAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAc # BgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQU4apG # UqrskZkk+23w8VVV5yJbcgwwDQYJKoZIhvcNAQEBBQAEggGAdqg8j3eKBy9wf5l6 # 8XWJINDnTTDVN6sVaTPG8S3vQf/E96UyQe1n0ptQmr3f0VRTwGOpZvT110E/4AWH # Y0ZtXF3TlRXKRv2jMPcPdrvTOy9ccEKXl/hU9dXdIG0TMCXIVIpqgAcAn3OSWwsr # X1dCxgzkrcfXNjct9wlKXEwrMLFKJMc+5wgO86mdiOO3FRc3sgKg+zkF/8PoUED9 # /oh/LuikUV6IBQFIl7aYZtJZk80K8lsi7yrjvdXReW633q9mDJfWaZNPaAJUXY7k # 1+Wm7IC86Q5jBIY1Mv+MAx4mwLH6iGgWj4AfQMLPEm3a0j1BxCxvNdY2NRbuEKw1 # C1+JwopqTfQhtrf80UtVDKxBrlUL0rJQ168kHA3c28Z2c5jSFRb34qlkkMjUO18A # feKoS6izz3FsPwja4VGQE+yo7ryC9yeaLHeeyc5xIexvWwTXjYfS8vuHhFAa399H # Wf1CpC2P9Z3q4FvfNYPdDl6FyHW+s40HXCueCiSxvMvIGyoY # SIG # End signature block |