Azs.Deployment.Admin.psm1
#----------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. #----------------------------------------------------------------------- # This module contains PowerShell commands providing an access to Deployment Provider functions. class CustomResponse { [string]$StatusCode [string]$AsyncOperationStatusUri [string]$LocationUri [string]$Content } class WaitResult { [bool]$IsSuccess [string]$ErrorCode [String]$ErrorMessage } <# .SYNOPSIS Retrieves Resource Manager access token. #> function Get-AzsResourceManagerAccessToken { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [object] $context ) $profile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile $profileClient = [Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient]::new($profile) $token = $profileClient.AcquireAccessToken($context.Subscription.TenantId) return $token.AccessToken } <# .SYNOPSIS Send a request to Azure Stack Resource Manager. #> function Invoke-AzsResourceManager { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE')] [string] $Method, [Parameter(Mandatory = $true)] [ValidateNotNull()] [Uri] $Uri, [Parameter(Mandatory = $false)] [object] $Body = $null, [Parameter(Mandatory = $false)] [string] $AccessToken = "", [Parameter(Mandatory = $false)] [switch] $ThrowOnError, [Parameter(Mandatory = $false)] [switch] $RetryOnError ) function Resolve-RequestUri { param ( [string] $resourceManagerUrl, [Uri] $uri ) if ($uri.IsAbsoluteUri) { return $uri } return [uri]::new([uri]::new($resourceManagerUrl), $Uri) } function Resolve-RequestContent { param ( [object] $body ) if ($null -eq $body) { return [NullString]::Value } if ($body -is [string]) { return $Body.ToString() } return ($body | ConvertTo-Json -Depth 99 -Compress) } function Resolve-AccessToken { param( [object] $context, [string] $accessToken ) if (-not [string]::IsNullOrEmpty($accessToken)) { return $accessToken } return Get-AzsResourceManagerAccessToken -Context $context } function Get-HeaderValue { param ( [System.Net.Http.Headers.HttpHeaders] $headers, [string] $name ) [System.Collections.Generic.IEnumerable[string]] $values = $null if (-not $headers.TryGetValues($name, [ref] $values)) { return [NullString]::Value } return [System.Linq.Enumerable]::FirstOrDefault($values) } function Trace-HttpRequestMessage { param ( [System.Net.Http.HttpRequestMessage] $request, [string] $content ) Write-Verbose "$($request.Method) $($request.RequestUri) with $($content.Length)-char payload" -Verbose $sb = [System.Text.StringBuilder]::new() $sb.AppendLine("$($request.Method) $($request.RequestUri) HTTP/$($request.Version)") | Out-Null DumpHttpMessageHeaders $sb $request.Headers if (-not [string]::IsNullOrEmpty($content)) { $sb.AppendLine() | Out-Null $sb.Append($content) | Out-Null } Write-Debug $sb.ToString() } function Trace-HttpResponseMessage { param ( [System.Net.Http.HttpResponseMessage] $response, [string] $content ) Write-Verbose "Received $($content.Length)-char response, StatusCode = $($response.StatusCode)" -Verbose $sb = [System.Text.StringBuilder]::new() $sb.AppendLine("HTTP/$($response.Version) $([int]$response.StatusCode) $($response.ReasonPhrase)") | Out-Null DumpHttpMessageHeaders -Sb $sb -Headers $response.Headers if (-not [string]::IsNullOrEmpty($content)) { $sb.AppendLine() | Out-Null $sb.Append($content) | Out-Null } Write-Debug $sb.ToString() } function DumpHttpMessageHeaders { param ( [System.Text.StringBuilder] $sb, [System.Net.Http.Headers.HttpHeaders] $headers ) if ($null -ne $headers) { foreach ($header in $headers) { $sb.Append($header.Key) | Out-Null $sb.Append(": ") | Out-Null if ($header.Key -eq 'Authorization') { $sb.AppendLine('HIDDEN') | Out-Null } else { $sb.AppendLine($header.Value -join " ") | Out-Null } } } } #----------------------------------------------------------------------- $ctx = Get-AzContext if ($null -eq $ctx.Environment) { throw 'AzContext is not set.' } $Uri = Resolve-RequestUri -ResourceManagerUrl $ctx.Environment.ResourceManagerUrl -Uri $Uri [string] $requestContent = Resolve-RequestContent -Body $Body $AccessToken = Resolve-AccessToken -Context $ctx -AccessToken $AccessToken [System.Net.Http.HttpRequestMessage] $request = $null [System.Net.Http.HttpResponseMessage] $response = $null $retryable = $RetryOnError; $attemptCount = 1; $maxAttemptCount = 3; try { do { $request = [System.Net.Http.HttpRequestMessage]::new() $request.Method = [System.Net.Http.HttpMethod]::new($Method) $request.RequestUri = $Uri $request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $AccessToken) if ($null -ne $requestContent) { $request.Content = [System.Net.Http.StringContent]::new($requestContent, [System.Text.Encoding]::UTF8, 'application/json') } Trace-HttpRequestMessage -Request $request -Content $requestContent $task = $HttpClient.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseContentRead) $response = $task.Result $task = $response.Content.ReadAsStringAsync() [string] $responseContent = $task.Result if ([string]::IsNullOrEmpty($responseContent)) { $responseContent = [NullString]::Value } Trace-HttpResponseMessage -Response $response -Content $responseContent $result = [CustomResponse]::new() $result.StatusCode = $response.StatusCode if ($result.StatusCode -eq ""){ $result.StatusCode = "RequestTimeout" } else { $result.AsyncOperationStatusUri = Get-HeaderValue -Headers $response.Headers -Name 'Azure-AsyncOperation' $result.LocationUri = Get-HeaderValue -Headers $response.Headers -Name 'Location' $result.Content = $responseContent } $retriableError = IsRetryableError -StatusCode $result.StatusCode if ($retryable -and $retriableError) { [string] $statusCode = $result.StatusCode Write-Verbose "Retryable error occured: ${statusCode}, retrying with attempt count number ${attemptCount}." -Verbose # Progresive backoff in case of a retryable error. $waitTime = 5 * $attemptCount; Start-Sleep -Seconds $waitTime # Should the next attempt be retryable or not? After the $attemptCount++; $retryable = $attemptCount -le $maxAttemptCount Write-Verbose "retryable: ${retryable}" -Verbose } else { if ($ThrowOnError) { EnsureSuccessStatusCode -Response $result } return $result } # loop until the retry attempts are exhausted } until ($false); } catch [System.AggregateException] { throw $_.Exception.InnerException.Message } finally { if ($null -ne $request) { $request.Dispose() } if ($null -ne $response) { $response.Dispose() } } } <# .SYNOPSIS Waits for Azure Stack Resource Manager asynchronous operation to complete (Azure-AsyncOperation header style). .NOTES Track asynchronous Azure operations https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-async-operations #> function Wait-AzsAsyncOperation { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $OperationName, [Parameter(Mandatory = $true)] [ValidateNotNull()] [Uri] $AsyncOperationStatusUri, [Parameter(Mandatory = $false)] [string] $AccessToken = "" ) Write-Verbose "${OperationName}: Wait for asynchronous operation to complete." -Verbose $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() while ($true) { $response = Invoke-AzsResourceManager -Method GET -Uri $AsyncOperationStatusUri -AccessToken $AccessToken -Verbose -RetryOnError EnsureSuccessStatusCode -Response $response $operationResult = $response.Content | ConvertFrom-Json if (IsOperationResultTerminalState $operationResult.status) { $result = [WaitResult]::new() if ($operationResult.status -eq 'Succeeded') { $result.IsSuccess = $true return $result } $result.IsSuccess = $false $result.ErrorCode = $operationResult.error.code $result.ErrorMessage = $operationResult.error.message return $result } Write-Verbose "${OperationName}: Sleeping for 5 seconds, waiting time: $($stopwatch.Elapsed)" Start-Sleep -Seconds 5 } } function EnsureSuccessStatusCode { param( [Parameter(Mandatory = $true)] [psobject] $Response ) if (-not (IsSuccessStatusCode -StatusCode $Response.StatusCode)) { Write-Verbose "HTTP error: $($Response.StatusCode)" -Verbose Write-Verbose $Response.Content -Verbose throw "HTTP error: $($Response.StatusCode)" } } function IsOperationResultTerminalState { param ( [Parameter(Mandatory = $true)] [string] $Value ) return $Value -in @('Canceled', 'Failed', 'Succeeded') } function IsSuccessStatusCode { param( [Parameter(Mandatory = $true)] [System.Net.HttpStatusCode] $StatusCode ) return [int]$StatusCode -ge 200 -and [int]$StatusCode -le 299 } <# .SYNOPSIS Check if the status code is a retryable error .NOTES List of retryable status code: 408 // RequestTimeout 429 // TooManyRequests (RFC 6585) 500 // InternalServerError 502 // BadGateway 503 // ServiceUnavailable 504 // GatewayTimeout 506..599 #> function IsRetryableError { param( [Parameter(Mandatory = $true)] [System.Net.HttpStatusCode] $StatusCode ) switch([int]$statusCode) { 408 {return $True} 429 {return $True} 500 {return $True} 502 {return $True} 503 {return $True} 504 {return $True} {$_-ge 506 -and $_-le 599} {return $True} default {return $False} } } function ThrowOnError { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [WaitResult] $WaitResult, [Parameter(Mandatory = $true)] [string] $ProblemDescription ) if (-not ($WaitResult.IsSuccess)) { throw "$ProblemDescription, errorCode: '$($WaitResult.ErrorCode)', errorMessage: '$($WaitResult.ErrorMessage)'" } } #----------------------------------------------------------------------- <# .SYNOPSIS Lists file containers or gets a file container properties. .DESCRIPTION Lists file containers or gets a file container properties. .PARAMETER FileContainerId Container ID to fetch the properties for. .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:\> Get-AzsFileContainer Lists the available file containers in the subscription. .EXAMPLE PS C:\> Get-AzsFileContainer -FileContainerId <ContainerID> Get the file container with id <ContainerID>. #> function Get-AzsFileContainer { [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string] $FileContainerId = $null, [Parameter()] [ValidateSet('2019-01-01', '2018-07-01')] [string] $ApiVersion = '2019-01-01', [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ([string]::IsNullOrEmpty($FileContainerId)) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/fileContainers?api-version=$ApiVersion" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/fileContainers/$($FileContainerId)?api-version=$ApiVersion" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -Verbose -RetryOnError if ($response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { return $null } EnsureSuccessStatusCode -Response $response if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } <# .SYNOPSIS Creates a new file container. .DESCRIPTION Creates a new file container from a soucre Uri. .PARAMETER FileContainerId Container ID to be given to the new container. .PARAMETER SourceUri The remote file location URI for the container. .PARAMETER PostCopyAction The file post copy action. .EXAMPLE PS C:\> New-AzsFileContainer -FileContainerId $ContainerId -SourceUri $packageUri -PostCopyAction Unzip Creates a new file container from the specified values. #> function New-AzsFileContainer { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $FileContainerId, [Parameter(Mandatory = $true)] [Uri] $SourceUri, [Parameter()] [ValidateSet('None', 'Unzip')] [string] $PostCopyAction = 'None', [Parameter()] [ValidateSet('2019-01-01', '2018-07-01')] [string] $ApiVersion = '2019-01-01' ) Write-Verbose "Create a new file container, fileContainerId = '$FileContainerId', sourceUri = '$SourceUri', postCopyAction = '$PostCopyAction'." -Verbose $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/fileContainers/$($FileContainerId)?api-version=$ApiVersion" $body = @{ properties = @{ sourceUri = $SourceUri postCopyAction = $PostCopyAction } } $response = Invoke-AzsResourceManager -Method PUT -Uri $requestUri -Body $body -ThrowOnError -Verbose if (-not [string]::IsNullOrEmpty($response.AsyncOperationStatusUri)) { $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'New-AzsFileContainer' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to create file container' } } <# .SYNOPSIS Removes an existing file container. .DESCRIPTION Removes an existing file container. .PARAMETER FileContainerId Container ID of the container to be removed. .EXAMPLE PS C:\> Remove-AzsFileContainer -FileContainerId $ContainerId Removes an existing file container. #> function Remove-AzsFileContainer { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $FileContainerId, [Parameter()] [ValidateSet('2019-01-01', '2018-07-01')] [string] $ApiVersion = '2019-01-01' ) Write-Verbose "Remove the file container, fileContainerId = '$FileContainerId'." -Verbose $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/fileContainers/$($FileContainerId)?api-version=$ApiVersion" Invoke-AzsResourceManager -Method DELETE -Uri $requestUri -ThrowOnError -Verbose | Out-Null } # Product Packages <# .SYNOPSIS Lists product packages or gets a product package properties. .DESCRIPTION Lists product packages or gets a product package properties. .PARAMETER PackageId Product package Id to get the properties for. .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:\> Get-AzsProductPackage Lists all the product packages in the subscription. .EXAMPLE PS C:\> Get-AzsProductPackage -PackageId $PackageId Gets the product package properties of the product with Id. #> function Get-AzsProductPackage { [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string] $PackageId = $null, [Parameter()] [ValidateSet('2019-01-01', '2018-07-01')] [string] $ApiVersion = '2019-01-01', [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ([string]::IsNullOrEmpty($PackageId)) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages?api-version=$ApiVersion" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages/$($PackageId)?api-version=$ApiVersion" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -Verbose -RetryOnError if ($response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { return $null } EnsureSuccessStatusCode -Response $response if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } <# .SYNOPSIS Create a new product package. .DESCRIPTION Create a new product package. .PARAMETER PackageId ID of the product package to be created. .PARAMETER FileContainerId File container resource identifier. .EXAMPLE PS C:\> New-AzsProductPackage -PackageId $PackageId -FileContainerId $ContainerId Creates a product package with the specified values. #> function New-AzsProductPackage { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PackageId, [Parameter(Mandatory = $true)] [string] $FileContainerId, [Parameter()] [ValidateSet('2019-01-01', '2018-07-01')] [string] $ApiVersion = '2019-01-01' ) Write-Verbose "Create a new product package, packageId = '$PackageId', fileContainerId = '$FileContainerId'." -Verbose $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages/$($PackageId)?api-version=$ApiVersion" if ($ApiVersion -eq '2019-01-01') { $body = @{ properties = @{ fileContainerId = $FileContainerId } } } else { $body = @{ properties = @{ productManifestId = $FileContainerId } } } $response = Invoke-AzsResourceManager -Method PUT -Uri $requestUri -Body $body -ThrowOnError -Verbose if (-not [string]::IsNullOrEmpty($response.AsyncOperationStatusUri)) { $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'New-AzsProductPackage' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to create product package' } } <# .SYNOPSIS Removes an existing product package. .DESCRIPTION Removes an existing product package. .PARAMETER PackageId ID of the product package to be removed. .EXAMPLE PS C:\> Remove-AzsProductPackage -PackageId $PackageId Removes a product package with Id $PackageId. #> function Remove-AzsProductPackage { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PackageId, [Parameter()] [ValidateSet('2019-01-01', '2018-07-01')] [string] $ApiVersion = '2019-01-01' ) Write-Verbose "Remove the product package, packageId = '$PackageId'." -Verbose $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages/$($PackageId)?api-version=$ApiVersion" Invoke-AzsResourceManager -Method DELETE -Uri $requestUri -ThrowOnError -Verbose | Out-Null } #----------------------------------------------------------------------- <# .SYNOPSIS Lists product deployments or gets a product deployment properties. .DESCRIPTION Lists product deployments or gets a product deployment properties. .PARAMETER ProductId Product package Id to get the product deployment properties for. .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:\> Get-AzsProductDeployment Lists all the product package deployments in the subscription. .EXAMPLE PS C:\> Get-AzsProductDeployment -ProductId $ProductId Gets the product package deployment with the specified product Id. #> function Get-AzsProductDeployment { [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string] $ProductId = $null, [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ([string]::IsNullOrEmpty($ProductId)) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments?api-version=2019-01-01" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments/$($ProductId)?api-version=2019-01-01" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -Verbose -RetryOnError if ($response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { return $null } EnsureSuccessStatusCode -Response $response if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } <# .SYNOPSIS Invokes 'bootstrap product' action. .DESCRIPTION Invokes 'bootstrap product' action. .PARAMETER ProductId Product package Id to start the bootstrap action for. .PARAMETER Version Product version .EXAMPLE PS C:\> Invoke-AzsProductBootstrapAction -ProductId $ProductId -Version $ProductVersion Starts the bootstrap action for the specified product. #> function Invoke-AzsProductBootstrapAction { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $ProductId, [Parameter(Mandatory = $true)] [string] $Version ) $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments/$ProductId/bootstrap?api-version=2019-01-01" $body = @{ version = $Version } $response = Invoke-AzsResourceManager -Method POST -Uri $requestUri -Body $body -ThrowOnError -Verbose $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'Invoke-AzsProductBootstrapAction' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to complete bootstrap operation' } <# .SYNOPSIS Invokes 'deploy product' action. .DESCRIPTION Invokes 'deploy product' action. .PARAMETER ProductId Product package Id to start the deploy action for. .PARAMETER Version Product Version. .PARAMETER Parameters Deployment parameters, value in JToken .EXAMPLE PS C:\> Invoke-AzsProductDeployAction -ProductId $ProductId -Version $ProductVersion -Parameters $Parameters Starts the product deploy action for the specified product. #> function Invoke-AzsProductDeployAction { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $ProductId, [Parameter(Mandatory = $true)] [string] $Version, [Parameter(Mandatory = $true)] [psobject] $Parameters ) $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments/$ProductId/deploy?api-version=2019-01-01" $body = @{ version = $Version parameters = $Parameters } $response = Invoke-AzsResourceManager -Method POST -Uri $requestUri -Body $body -ThrowOnError -Verbose $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'Invoke-AzsProductDeployAction' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to complete deploy operation' } <# .SYNOPSIS Invokes 'execute runner' action. .DESCRIPTION Invokes 'execute runner' action. .PARAMETER ProductId Product package Id to start the execute runner action for. .PARAMETER Parameters Deployment parameters, value in JToken .EXAMPLE PS C:\> Invoke-AzsProductExecuteRunnerAction -ProductId $ProductId -Parameters $Parameters Starts the product execute runner action for the specified product. #> function Invoke-AzsProductExecuteRunnerAction { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $ProductId, [Parameter(Mandatory = $true)] [psobject] $Parameters ) $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments/$ProductId/executeRunner?api-version=2019-01-01" $body = $parameters $response = Invoke-AzsResourceManager -Method POST -Uri $requestUri -Body $body -ThrowOnError -Verbose if (-not [string]::IsNullOrEmpty($response.AsyncOperationStatusUri)) { $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'Invoke-AzsProductExecuteRunnerAction' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to complete execute runner operation' } } <# .SYNOPSIS Invokes 'remove product' action. .DESCRIPTION Invokes 'remove product' action. .PARAMETER ProductId Product package Id to start the remove product action for. .EXAMPLE PS C:\> Invoke-AzsProductRemoveAction -ProductId $ProductId Starts the product remove action for the specified product. #> function Invoke-AzsProductRemoveAction { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $ProductId ) $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments/$ProductId/remove?api-version=2019-01-01" $response = Invoke-AzsResourceManager -Method POST -Uri $requestUri -ThrowOnError -Verbose if (-not [string]::IsNullOrEmpty($response.AsyncOperationStatusUri)) { $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'Invoke-AzsProductRemoveAction' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to complete remove operation' } } <# .SYNOPSIS Invokes 'rotate secrets' action. .DESCRIPTION Invokes 'rotate secrets' action. .PARAMETER ProductId Product package Id to start the product rotate secrets action for. .EXAMPLE PS C:\> Invoke-AzsProductRotateSecretsAction -ProductId $ProductId Starts the product rotate secrets action for the specified product. #> function Invoke-AzsProductRotateSecretsAction { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $ProductId ) $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productDeployments/$ProductId/rotateSecrets?api-version=2019-01-01" $response = Invoke-AzsResourceManager -Method POST -Uri $requestUri -ThrowOnError -Verbose if (-not [string]::IsNullOrEmpty($response.AsyncOperationStatusUri)) { $waitAsyncOperation = Wait-AzsAsyncOperation -OperationName 'Invoke-AzsProductRotateSecretsAction' -AsyncOperationStatusUri $response.AsyncOperationStatusUri -Verbose ThrowOnError -WaitResult $waitAsyncOperation -ProblemDescription 'Unable to complete rotate secrets operation' } } #----------------------------------------------------------------------- <# .SYNOPSIS Lists product secrets or gets a product secret properties. .DESCRIPTION Lists product secrets or gets a product secret properties. .PARAMETER PackageId Product package Id to get the product secret properties for. .PARAMETER SecretName Name of the secret to be retrieved. .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:/> Get-AzsProductSecret -PackageId $PackageId -AsJson Lists all external secrets from package with Id $PackageId. Outputs in Json format. .EXAMPLE PS C:/> Get-AzsProductSecret -PackageId $PackageId -SecretName AdHoc Gets the product secret called 'AdHoc' #> function Get-AzsProductSecret { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PackageId, [Parameter()] [ValidateNotNullOrEmpty()] [string] $SecretName = $null, [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ([string]::IsNullOrEmpty($SecretName)) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages/$($PackageId)/secrets?api-version=2019-01-01" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages/$($PackageId)/secrets/$($SecretName)?api-version=2019-01-01" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -Verbose -RetryOnError if ($response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { return $null } EnsureSuccessStatusCode -Response $response if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } <# .SYNOPSIS Sets product secret value. .DESCRIPTION Sets product secret value. .PARAMETER PackageId Product package Id to set the product secret for. .PARAMETER SecretName Name of the secret. .PARAMETER Value Value of the secret. .PARAMETER PfxFileName Location of the pfx file. .PARAMETER PfxPassword PFX file password. .PARAMETER Password Password Value. .PARAMETER Key The symmetric key. .PARAMETER Force Do not ask for confirmation. .EXAMPLE PS C:/> Set-AzsProductSecret -PackageId $PackageId -SecretName AdHoc -Value $value Sets the product secret value to the given value. .EXAMPLE PS C:/> Set-AzsProductSecret -PackageId $PackageId -SecretName TlsCertificate -PfxFileName .\temp\ExternalCertificate\cert.pfx -PfxPassword $pfxPassword -Force Sets the product secret value to the given value. .EXAMPLE PS C:/> Set-AzsProductSecret -PackageId $PackageId -SecretName ExternalSymmetricKey -Key $key -Force Sets the product secret value to the given value. #> function Set-AzsProductSecret { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PackageId, [Parameter(Mandatory = $true)] [string] $SecretName, [Parameter(Mandatory = $true, ParameterSetName = 'AdHoc')] [securestring] $Value, [Parameter(Mandatory = $true, ParameterSetName = 'Certificate')] [string] $PfxFileName, [Parameter(Mandatory = $true, ParameterSetName = 'Certificate')] [securestring] $PfxPassword, [Parameter(Mandatory = $true, ParameterSetName = 'Password')] [securestring] $Password, [Parameter(Mandatory = $true, ParameterSetName = 'SymmetricKey')] [securestring] $Key, [Parameter()] [switch] $Force ) function ConvertFrom-SecureString { param( [Parameter(Mandatory = $true)] [securestring] $Value ) return [System.Net.NetworkCredential]::new('', $Value).Password } if ($PSCmdlet.ParameterSetName -eq 'AdHoc') { $body = @{ value = (ConvertFrom-SecureString -Value $Value) } } elseif ($PSCmdlet.ParameterSetName -eq 'Certificate') { $body = @{ data = [System.Convert]::ToBase64String((Get-Content $PfxFileName -Encoding Byte)) password = (ConvertFrom-SecureString -Value $PfxPassword) } } elseif ($PSCmdlet.ParameterSetName -eq 'Password') { $body = @{ password = (ConvertFrom-SecureString -Value $Password) } } elseif ($PSCmdlet.ParameterSetName -eq 'SymmetricKey') { $body = @{ key = (ConvertFrom-SecureString -Value $Key) } } if ($Force.ToBool()) { Write-Verbose 'Importing secret...' -Verbose $action = 'import' } else { Write-Verbose 'Validating secret...' -Verbose $action = 'validate' } $subscriptionId = (Get-AzContext).Subscription.Id $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/productPackages/$PackageId/secrets/$SecretName/$($action)?api-version=2019-01-01" Invoke-AzsResourceManager -Method POST -Uri $requestUri -Body $body -ThrowOnError -Verbose | Out-Null } #----------------------------------------------------------------------- <# .SYNOPSIS Gets or lists the action plans. .DESCRIPTION Gets or lists the action plans. .PARAMETER PlanId Action Plan Id to retrieve the properties for. .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:/> Get-AzsActionPlan Lists all the action plan under the subscription. .EXAMPLE PS C:/> Get-AzsActionPlan -PlanId $planId -AsJson Gets the action plan properties for plan with Id $planId. #> function Get-AzsActionPlan { [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string] $PlanId = $null, [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ([string]::IsNullOrEmpty($PlanId)) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/actionplans?api-version=2019-01-01" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/actionplans/$($PlanId)?api-version=2019-01-01" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -ThrowOnError -Verbose -RetryOnError if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } <# .SYNOPSIS Gets or lists action plan operations. .DESCRIPTION Gets or lists action plan operations. .PARAMETER PlanId Action Plan Identifier. .PARAMETER OperationId Operation Id to retrieve the properties for. .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:/> Get-AzsActionPlanOperation -PlanId $planId -AsJson Gets the action plan operations for plan with id $planId. #> function Get-AzsActionPlanOperation { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PlanId, [Parameter()] [ValidateNotNullOrEmpty()] [string] $OperationId = $null, [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ([string]::IsNullOrEmpty($OperationId)) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/actionplans/$PlanId/operations?api-version=2019-01-01" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/actionplans/$PlanId/operations/$($OperationId)?api-version=2019-01-01" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -ThrowOnError -Verbose -RetryOnError if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } <# .SYNOPSIS Gets or lists the action plan attempt .DESCRIPTION Gets or lists the action plan attempts .PARAMETER PlanId Plan Id of the action plan .PARAMETER OperationId Operation Id of the action plan attempt .PARAMETER AttemptNo Action plan attempt number .PARAMETER AsJson Outputs the result in Json format. .EXAMPLE PS C:/> Get-AzsActionPlanAttempt -PlanId $planId -OperationId $operationId -AsJson Gets or lists the action plan attempt properties for plan with id $planId and operation Id $operationId. #> function Get-AzsActionPlanAttempt { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PlanId, [Parameter(Mandatory = $true)] [string] $OperationId, [Parameter()] [int] $AttemptNo, [Parameter()] [switch] $AsJson ) $subscriptionId = (Get-AzContext).Subscription.Id if ($AttemptNo -eq 0) { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/actionplans/$PlanId/operations/$OperationId/attempts?api-version=2019-01-01" } else { $requestUri = "/subscriptions/$subscriptionId/providers/Microsoft.Deployment.Admin/locations/global/actionplans/$PlanId/operations/$OperationId/attempts/$($AttemptNo)?api-version=2019-01-01" } $response = Invoke-AzsResourceManager -Method GET -Uri $requestUri -ThrowOnError -Verbose -RetryOnError if ($AsJson) { return $response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 99 } return $response.Content | ConvertFrom-Json } #----------------------------------------------------------------------- $ErrorActionPreference = 'Stop' [System.Reflection.Assembly]::LoadWithPartialName('System.Net.Http') | Out-Null [System.Net.Http.HttpClient] $HttpClient = [System.Net.Http.HttpClient]::new() $functions = @( 'Get-AzsFileContainer' 'New-AzsFileContainer' 'Remove-AzsFileContainer' 'Get-AzsProductPackage' 'New-AzsProductPackage' 'Remove-AzsProductPackage' 'Get-AzsProductDeployment' 'Invoke-AzsProductBootstrapAction' 'Invoke-AzsProductDeployAction' 'Invoke-AzsProductExecuteRunnerAction' 'Invoke-AzsProductRemoveAction' 'Invoke-AzsProductRotateSecretsAction' 'Get-AzsProductSecret' 'Set-AzsProductSecret' 'Get-AzsActionPlan' 'Get-AzsActionPlanOperation' 'Get-AzsActionPlanAttempt' ) Export-ModuleMember -Function $functions # SIG # Begin signature block # MIIjhQYJKoZIhvcNAQcCoIIjdjCCI3ICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBLSWKVIBw/QURr # kingqsrQNx1uRdK7A+pD2b9uq88nX6CCDYEwggX/MIID56ADAgECAhMzAAACUosz # qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I # sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O # L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA # v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o # RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 # q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 # uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp # kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 # l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u # TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 # o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti # yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z # 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf # 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK # WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW # esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F # 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWjCCFVYCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgeE1rmnIZ # O2UsLyZ9DOw6rxNvPi08uhiQgJraABNPOsswQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQB1TM6h6abilzwyIAzWkYHRDlu5YgrNFZjyvYY08meF # bkW5T5etW6fOvEvssbZTUHZGA2n8uTaaHGteXJcwZIMB4922XAu+67FnwRP54L/v # TFGlEUIeme4GX9L7rSwSZLdXc65MmWzscYKCT/NH95VhQJJHB/OL4jx9CU89aooi # i3ZKO9xqAiVwyh8EOA1oSJoqXA61fiTTAjGKOkj7vvoS3dg7y4l2kIEESEsIwrR+ # R+v4RPVM8vo/puulpDB6ESUZ0s50fPLOLjcpIWp16eefoTn26CrlvpqP25c//hjI # XygWZcEcWHc/CXfDoiRH1SRlXA8uM6UexFBhSWGTJ2IyoYIS5DCCEuAGCisGAQQB # gjcDAwExghLQMIISzAYJKoZIhvcNAQcCoIISvTCCErkCAQMxDzANBglghkgBZQME # AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIER89xudJI7uAYpnKNysdX7CrcW8WRVU+ZN4JY88 # FKhzAgZhkGmImRcYEzIwMjExMjExMDMzMDM0Ljk4NlowBIACAfSggdCkgc0wgcox # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p # Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg # RVNOOkFFMkMtRTMyQi0xQUZDMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIOOzCCBPEwggPZoAMCAQICEzMAAAFIoohFVrwvgL8AAAAAAUgw # DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN # MjAxMTEyMTgyNTU2WhcNMjIwMjExMTgyNTU2WjCByjELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046QUUyQy1FMzJCLTFB # RkMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD3/3ivFYSK0dGtcXaZ8pNLEARbraJe # wryi/JgbaKlq7hhFIU1EkY0HMiFRm2/Wsukt62k25zvDxW16fphg5876+l1wYnCl # ge/rFlrR2Uu1WwtFmc1xGpy4+uxobCEMeIFDGhL5DNTbbOisLrBUYbyXr7fPzxbV # kEwJDP5FG2n0ro1qOjegIkLIjXU6qahduQxTfsPOEp8jgqMKn++fpH6fvXKlewWz # dsfvhiZ4H4Iq1CTOn+fkxqcDwTHYkYZYgqm+1X1x7458rp69qjFeVP3GbAvJbY3b # Flq5uyxriPcZxDZrB6f1wALXrO2/IdfVEdwTWqJIDZBJjTycQhhxS3i1AgMBAAGj # ggEbMIIBFzAdBgNVHQ4EFgQUhzLwaZ8OBLRJH0s9E63pIcWJokcwHwYDVR0jBBgw # FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov # L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx # MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN # BgkqhkiG9w0BAQsFAAOCAQEAZhKWwbMnC9Qywcrlgs0qX9bhxiZGve+8JED27hOi # yGa8R9nqzHg4+q6NKfYXfS62uMUJp2u+J7tINUTf/1ugL+K4RwsPVehDasSJJj+7 # boIxZP8AU/xQdVY7qgmQGmd4F+c5hkJJtl6NReYE908Q698qj1mDpr0Mx+4LhP/t # TqL6HpZEURlhFOddnyLStVCFdfNI1yGHP9n0yN1KfhGEV3s7MBzpFJXwOflwgyE9 # cwQ8jjOTVpNRdCqL/P5ViCAo2dciHjd1u1i1Q4QZ6xb0+B1HdZFRELOiFwf0sh3Z # 1xOeSFcHg0rLE+rseHz4QhvoEj7h9bD8VN7/HnCDwWpBJTCCBnEwggRZoAMCAQIC # CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp # ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx # NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF # ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD # DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx # z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1 # rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc # sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB # 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF # bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud # EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD # VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv # cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB # BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j # ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB # kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe # MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA # LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx # vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS # inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1 # L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO # M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4 # pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45 # V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x # 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe # gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn # QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp # 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT # X4/edIhJEqGCAs0wggI2AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP # cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpBRTJDLUUzMkItMUFG # QzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG # BSsOAwIaAxUAhyuClrocWf4SIcRafAEX1Rhs6zmggYMwgYCkfjB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOVef8MwIhgPMjAy # MTEyMTEwOTM5MTVaGA8yMDIxMTIxMjA5MzkxNVowdjA8BgorBgEEAYRZCgQBMS4w # LDAKAgUA5V5/wwIBADAJAgEAAgFeAgH/MAcCAQACAhHXMAoCBQDlX9FDAgEAMDYG # CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEA # AgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAGXo7QWqSS+V78isIqspldM7evX+snlAX # KLB+ZJmdsZVQpaApJjYNIQnsAtJM+eqGPuiW5ing5fD/4lkf98lJvZCj41XtEpM/ # sP0IPLMKVmIfpxLZ9DMX3LJ6pC+fOFvkPLXAlFMfSkG5GW5Qi8/4XL09bLQUFXLs # Kzq6yOJSAMExggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg # MjAxMAITMwAAAUiiiEVWvC+AvwAAAAABSDANBglghkgBZQMEAgEFAKCCAUowGgYJ # KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCAYSgm7ZfwN # k59kKdvLIKAlvJRJL4ZP+Q1iTHjXMiHCajCB+gYLKoZIhvcNAQkQAi8xgeowgecw # geQwgb0EIKmQGuqMeaG/Jh/m1NxO8Pljhr5Xv1PBVXpPVoDB22jYMIGYMIGApH4w # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFIoohFVrwvgL8AAAAA # AUgwIgQgpNp+Llt7q3VC2jcb1lNFhjn/Usfo0Zg+BbARu1tkuPowDQYJKoZIhvcN # AQELBQAEggEAs4mgF6uQ0A0VKJotU7O4ds084mckPHmZvt6oqd3vMw1bGFY+GYZL # dGJi/+N2VZ5iEW/ZPMEIQXAU7oB7N7IbkktXR297IoZq8tsQvyhBx6MwGpFIrf3+ # 80hefz/JNThHDYwYAwn4ptRgQ+WK6WRd6T5YQ0ijFZflCNc6BCuRZ0PZZY/LV/Cs # F2gH9NvfrdYEZqNvRu5SNWgt4FHJ6XKh6rFouyuj6/BztwNjwEnql3TnCdcTzHE8 # 3sPS288JvY8zDUWPlXsQAwFUDdoRFqBk3RxrJC2ijIpuuziwWplKRFECpSbWCMuy # pJuWHLExuh5E5Z3yPsi0IEUZSvvZcPfQuA== # SIG # End signature block |