Microsoft.DotNet.Dsc.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true Set-StrictMode -Version Latest # Set environment variable always to true to skip first run experience $env:DOTNET_NOLOGO = $true #region Functions function Get-DotNetPath { if ($IsWindows) { $dotNetPath = "$env:ProgramFiles\dotnet\dotnet.exe" if (-not (Test-Path $dotNetPath)) { $dotNetPath = "${env:ProgramFiles(x86)}\dotnet\dotnet.exe" if (-not (Test-Path $dotNetPath)) { throw 'dotnet.exe not found in Program Files or Program Files (x86)' } } } elseif ($IsMacOS) { $dotNetPath = '/usr/local/share/dotnet/dotnet' if (-not (Test-Path $dotNetPath)) { $dotNetPath = '/usr/local/bin/dotnet' if (-not (Test-Path $dotNetPath)) { throw 'dotnet not found in /usr/local/share/dotnet or /usr/local/bin' } } } elseif ($IsLinux) { $dotNetPath = '/usr/share/dotnet/dotnet' if (-not (Test-Path $dotNetPath)) { $dotNetPath = '/usr/bin/dotnet' if (-not (Test-Path $dotNetPath)) { throw 'dotnet not found in /usr/share/dotnet or /usr/bin' } } } else { throw 'Unsupported operating system' } Write-Verbose -Message "'dotnet' found at $dotNetPath" return $dotNetPath } function Get-DotNetToolArguments { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $PackageId, [Parameter(Mandatory = $false)] [string] $Version, [Parameter(Mandatory = $false)] [bool] $PreRelease, [Parameter(Mandatory = $false)] [string] $ToolPathDirectory, [bool] $Exist, [switch] $Downgrade ) $arguments = @($PackageId) if (-not ($PSBoundParameters.ContainsKey('ToolPathDirectory'))) { $arguments += '--global' } if ($PSBoundParameters.ContainsKey('Prerelease') -and $PSBoundParameters.ContainsKey('Version')) { # do it with version instead of pre $null = $PSBoundParameters.Remove('Prerelease') } # mapping table of command line arguments $mappingTable = @{ Version = '--version {0}' PreRelease = '--prerelease' ToolPathDirectory = '--tool-path {0}' Downgrade = '--allow-downgrade' } $PSBoundParameters.GetEnumerator() | ForEach-Object { if ($mappingTable.ContainsKey($_.Key)) { if ($_.Value -ne $false -and -not (([string]::IsNullOrEmpty($_.Value)))) { $arguments += ($mappingTable[$_.Key] -f $_.Value) } } } return ($arguments -join ' ') } # TODO: when https://github.com/dotnet/sdk/pull/37394 is documented and version is released with option simple use --format=JSON function Convert-DotNetToolOutput { [CmdletBinding()] [OutputType([PSCustomObject[]])] param ( [string[]] $Output ) process { # Split the output into lines $lines = $Output | Select-Object -Skip 2 # Initialize an array to hold the custom objects $inputObject = @() # Skip the header lines and process each line foreach ($line in $lines) { # Split the line into columns $columns = $line -split '\s{2,}' # Create a custom object for each line $customObject = [PSCustomObject]@{ PackageId = $columns[0] Version = $columns[1] Commands = $columns[2] } # Add the custom object to the array $inputObject += $customObject } return $inputObject } } function Get-InstalledDotNetToolPackages { [CmdletBinding()] param ( [string] $PackageId, [string] $Version, [bool] $PreRelease, [Parameter(Mandatory = $false)] [ValidateScript({ if (-Not ($_ | Test-Path -PathType Container) ) { throw 'Directory does not exist' } return $true })] [string] $ToolPathDirectory, [bool] $Exist ) $resultSet = [System.Collections.Generic.List[DotNetToolPackage]]::new() $listCommand = 'tool list --global' $installDir = Join-Path -Path $env:USERPROFILE '.dotnet' 'tools' if ($PSBoundParameters.ContainsKey('ToolPathDirectory')) { $listCommand = "tool list --tool-path $ToolPathDirectory" $installDir = $ToolPathDirectory } $result = Invoke-DotNet -Command $listCommand $packages = Convert-DotNetToolOutput -Output $result if ($null -eq $packages) { Write-Debug -Message 'No packages found.' return } if (-not [string]::IsNullOrEmpty($PackageId)) { $packages = $packages | Where-Object { $_.PackageId -eq $PackageId } } foreach ($package in $packages) { # flags to determine the existence of the package $isPrerelease = $false $preReleasePackage = $package.Version -Split '-' if ($preReleasePackage.Count -gt 1) { # set the pre-release flag to true to build the object $isPrerelease = $true } $resultSet.Add([DotNetToolPackage]::new( $package.PackageId, $package.Version, $package.Commands, $isPrerelease, $installDir, $true )) } return $resultSet } function Get-SemVer($version) { $version -match '^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?(\-(?<pre>[0-9A-Za-z\-\.]+))?(\+(?<build>[0-9A-Za-z\-\.]+))?$' | Out-Null $major = [int]$matches['major'] $minor = [int]$matches['minor'] $patch = [int]$matches['patch'] if ($null -eq $matches['pre']) { $pre = @() } else { $pre = $matches['pre'].Split('.') } $revision = 0 if ($pre.Length -gt 1) { $revision = Get-HighestRevision -InputArray $pre } return [version]$version = "$major.$minor.$patch.$revision" } function Get-HighestRevision { param ( [Parameter(Mandatory = $true)] [array]$InputArray ) # Filter the array to keep only integers $integers = $InputArray | ForEach-Object { $_ -as [int] } # Return the highest integer if ($integers.Count -gt 0) { return ($integers | Measure-Object -Maximum).Maximum } else { return $null } } function Install-DotNetToolPackage { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $PackageId, [string] $Version, [bool] $PreRelease, [string] $ToolPathDirectory, [bool] $Exist ) $installArgument = Get-DotNetToolArguments @PSBoundParameters $arguments = "tool install $installArgument --ignore-failed-sources" Write-Verbose -Message "Installing dotnet tool package with arguments: $arguments" Invoke-DotNet -Command $arguments } function Update-DotNetToolPackage { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $PackageId, [string] $Version, [bool] $PreRelease, [string] $ToolPathDirectory, [bool] $Exist ) $installArgument = Get-DotNetToolArguments @PSBoundParameters $arguments = "tool update $installArgument --ignore-failed-sources" Write-Verbose -Message "update dotnet tool package with arguments: $arguments" Invoke-DotNet -Command $arguments } function Assert-DotNetToolDowngrade { [version]$version = Invoke-DotNet -Command '--version' if ($version.Build -lt 200) { return $false } return $true } function Uninstall-DotNetToolPackage { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $PackageId, [string] $ToolPathDirectory ) $installArgument = Get-DotNetToolArguments @PSBoundParameters $arguments = "tool uninstall $installArgument" Write-Verbose -Message "Uninstalling dotnet tool package with arguments: $arguments" Invoke-DotNet -Command $arguments } function Invoke-DotNet { param ( [Parameter(Mandatory = $true)] [string] $Command ) try { Invoke-Expression "& `"$DotNetCliPath`" $Command" } catch { throw "Executing dotnet.exe with {$Command} failed." } } # Keeps the path of the code.exe CLI path. $DotNetCliPath = Get-DotNetPath #endregion Functions #region Classes <# .SYNOPSIS The `DotNetToolPackage` DSC Resource allows you to install, update, and uninstall .NET tool packages using the dotnet CLI. .PARAMETER PackageId The ID of the .NET tool package to manage. This is a required parameter. For a list of available .NET tool packages, see the following link: https://www.nuget.org/packages?q=&includeComputedFrameworks=true&packagetype=dotnettool&prerel=true&sortby=relevance .PARAMETER Version The version of the .NET tool package to install. If not specified, the latest version will be installed. .PARAMETER Commands An array of commands provided by the .NET tool package. This parameter is optional. .PARAMETER Prerelease Indicates whether to include prerelease versions of the .NET tool package. The default value is $false. .PARAMETER ToolPathDirectory The directory where the .NET tool package will be installed. If not specified, the package will be installed globally. .PARAMETER Exist Indicates whether the package should exist. Defaults to $true. .EXAMPLE PS C:\> Invoke-DscResource -ModuleName Microsoft.DotNet.Dsc -Name DotNetToolPackage -Method Get -Property @{ PackageId = 'GitVersion.Tool' } This example gets the current state of the .NET tool package 'GitVersion.Tool' in the default directory. .EXAMPLE PS C:\> Invoke-DscResource -ModuleName Microsoft.DotNet.Dsc -Name DotNetToolPackage -Method Set -Property @{ PackageId = 'GitVersion.Tool'; Version = '5.6.8'; } This example installs the .NET tool package 'GitVersion.Tool' version 5.6.8 in the default directory. .EXAMPLE PS C:\> Invoke-DscResource -ModuleName Microsoft.DotNet.Dsc -Name DotNetToolPackage -Method Set -Property @{ PackageId = 'PowerShell'; Prerelease = $true; ToolPathDirectory = 'C:\tools'; } This example installs the prerelease version of the .NET tool package 'PowerShell' in the 'C:\tools' directory. NOTE: When the version in the feed is for example v7.4.5-preview1 and the highest is v7.4.6, the highest will be installed. #> [DSCResource()] class DotNetToolPackage { [DscProperty(Key)] [string] $PackageId [DscProperty()] [string] $Version [DscProperty()] [string[]] $Commands [DscProperty()] [bool] $Prerelease = $false [DscProperty()] [string] $ToolPathDirectory [DscProperty()] [bool] $Exist = $true static [hashtable] $InstalledPackages DotNetToolPackage() { [DotNetToolPackage]::GetInstalledPackages() } DotNetToolPackage([string] $PackageId, [string] $Version, [string[]] $Commands, [bool] $PreRelease, [string] $ToolPathDirectory, [bool] $Exist) { $this.PackageId = $PackageId $this.Version = $Version $this.Commands = $Commands $this.PreRelease = $PreRelease $this.ToolPathDirectory = $ToolPathDirectory $this.Exist = $Exist } [DotNetToolPackage] Get() { # get the properties of the object currently set $properties = $this.ToHashTable() # refresh installed packages [DotNetToolPackage]::GetInstalledPackages($properties) # current state $currentState = [DotNetToolPackage]::InstalledPackages[$this.PackageId] if ($null -ne $currentState) { if ($this.Version -and ($this.Version -ne $currentState.Version)) { # See treatment: https://learn.microsoft.com/en-us/nuget/concepts/package-versioning?tabs=semver20sort#normalized-version-numbers # in this case, we misuse revision if beta,alpha, rc are present and grab the highest revision $installedVersion = Get-SemVer -version $currentState.Version $currentVersion = Get-SemVer -version $this.Version if ($currentVersion -ne $installedVersion) { $currentState.Exist = $false } } return $currentState } return [DotNetToolPackage]@{ PackageId = $this.PackageId Version = $this.Version Commands = $this.Commands PreRelease = $this.PreRelease ToolPathDirectory = $this.ToolPathDirectory Exist = $false } } Set() { if ($this.Test()) { return } $currentPackage = [DotNetToolPackage]::InstalledPackages[$this.PackageId] if ($currentPackage -and $this.Exist) { if ($this.Version -lt $currentPackage.Version) { $this.ReInstall($false) } else { $this.Upgrade($false) } } elseif ($this.Exist) { $this.Install($false) } else { $this.Uninstall($false) } } [bool] Test() { $currentState = $this.Get() if ($currentState.Exist -ne $this.Exist) { return $false } if ($null -ne $this.Version -or $this.Version -ne $currentState.Version -and $this.PreRelease -ne $currentState.PreRelease) { return $false } return $true } static [DotNetToolPackage[]] Export() { return [DotNetToolPackage]::Export(@{}) } static [DotNetToolPackage[]] Export([hashtable] $filterProperties) { $packages = Get-InstalledDotNetToolPackages @filterProperties return $packages } #region DotNetToolPackage helper functions static [void] GetInstalledPackages() { [DotNetToolPackage]::InstalledPackages = @{} foreach ($extension in [DotNetToolPackage]::Export()) { [DotNetToolPackage]::InstalledPackages[$extension.PackageId] = $extension } } static [void] GetInstalledPackages([hashtable] $filterProperties) { [DotNetToolPackage]::InstalledPackages = @{} foreach ($extension in [DotNetToolPackage]::Export($filterProperties)) { [DotNetToolPackage]::InstalledPackages[$extension.PackageId] = $extension } } [void] Upgrade([bool] $preTest) { if ($preTest -and $this.Test()) { return } $params = $this.ToHashTable() Update-DotNetToolPackage @params [DotNetToolPackage]::GetInstalledPackages() } [void] ReInstall([bool] $preTest) { if ($preTest -and $this.Test()) { return } $this.Uninstall($false) $this.Install($false) [DotNetToolPackage]::GetInstalledPackages() } [void] Install([bool] $preTest) { if ($preTest -and $this.Test()) { return } $params = $this.ToHashTable() Install-DotNetToolPackage @params [DotNetToolPackage]::GetInstalledPackages() } [void] Install() { $this.Install($true) } [void] Uninstall([bool] $preTest) { $params = $this.ToHashTable() $uninstallParams = @{ PackageId = $this.PackageId } if ($params.ContainsKey('ToolPathDirectory')) { $uninstallParams.Add('ToolPathDirectory', $params['ToolPathDirectory']) } Uninstall-DotNetToolPackage @uninstallParams [DotNetToolPackage]::GetInstalledPackages() } [void] Uninstall() { $this.Uninstall($true) } [hashtable] ToHashTable() { $parameters = @{} foreach ($property in $this.PSObject.Properties) { if (-not ([string]::IsNullOrEmpty($property.Value))) { $parameters[$property.Name] = $property.Value } } return $parameters } #endregion DotNetToolPackage helper functions } #endregion Classes # SIG # Begin signature block # MIIoQwYJKoZIhvcNAQcCoIIoNDCCKDACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAriScikg2lq8qi # UFk0Yg9MY7yKh5L29+vCpO2ErX+dy6CCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGiMwghofAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEILp0dWZCG7QcP5VEIU+Si5BD # BSgfkqrgO9Yu+qxmESxVMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAqaN4hgSgawYg4W5lY4RcBWCyH8LcAhZcHxOKddt4gnh5Rf8JkkBUCofo # +9KsdYEYVWwUGQE0wJCwChzgkIX6+7iDvJszZD5AQKquTH6Nab6OEsK/QHLCikKR # ACTNkCRnV9uhuS38NZVx3PRiUMry5zp7mJ8hGYUZ9ji81cjAmaACgQeOZMUyNdXp # zhyWExWbMV12K60WkM0+w+RmNpbFmdTdeEyKYcVx4SQD5u8YDMhyMvKEeCTdCbPW # B0IaQjDSOy04xPEvsLq19jFlyeSfVyMZCEYwrJpQk0XiIezLxVZkTYy7UZyg9YI2 # Ow6loIFSsKaflAQ/DRjNQrkHokKYE6GCF60wghepBgorBgEEAYI3AwMBMYIXmTCC # F5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq # hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCw0uFQPGbzToA2wsPhcSBO+BTpXt06l/O37K8dU279PAIGZ5qokznb # GBMyMDI1MDIwNzIzMTE1NC4zNjNaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # Tjo0MDFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAAB/tCowns0IQsBAAEAAAH+MA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0 # MDcyNTE4MzExOFoXDTI1MTAyMjE4MzExOFowgdMxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w # ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjQwMUEt # MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvLwhFxWlqA43olsE4PCe # gZ4mSfsH2YTSKEYv8Gn3362Bmaycdf5T3tQxpP3NWm62YHUieIQXw+0u4qlay4AN # 3IonI+47Npi9fo52xdAXMX0pGrc0eqW8RWN3bfzXPKv07O18i2HjDyLuywYyKA9F # mWbePjahf9Mwd8QgygkPtwDrVQGLyOkyM3VTiHKqhGu9BCGVRdHW9lmPMrrUlPWi # YV9LVCB5VYd+AEUtdfqAdqlzVxA53EgxSqhp6JbfEKnTdcfP6T8Mir0HrwTTtV2h # 2yDBtjXbQIaqycKOb633GfRkn216LODBg37P/xwhodXT81ZC2aHN7exEDmmbiWss # jGvFJkli2g6dt01eShOiGmhbonr0qXXcBeqNb6QoF8jX/uDVtY9pvL4j8aEWS49h # KUH0mzsCucIrwUS+x8MuT0uf7VXCFNFbiCUNRTofxJ3B454eGJhL0fwUTRbgyCbp # LgKMKDiCRub65DhaeDvUAAJT93KSCoeFCoklPavbgQyahGZDL/vWAVjX5b8Jzhly # 9gGCdK/qi6i+cxZ0S8x6B2yjPbZfdBVfH/NBp/1Ln7xbeOETAOn7OT9D3UGt0q+K # iWgY42HnLjyhl1bAu5HfgryAO3DCaIdV2tjvkJay2qOnF7Dgj8a60KQT9QgfJfwX # nr3ZKibYMjaUbCNIDnxz2ykCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBRvznuJ9SU2 # g5l/5/b+5CBibbHF3TAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww # bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF # BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAiT4NUvO2lw+0 # dDMtsBuxmX2o3lVQqnQkuITAGIGCgI+sl7ZqZOTDd8LqxsH4GWCPTztc3tr8AgBv # sYIzWjFwioCjCQODq1oBMWNzEsKzckHxAzYo5Sze7OPkMA3DAxVq4SSR8y+TRC2G # cOd0JReZ1lPlhlPl9XI+z8OgtOPmQnLLiP9qzpTHwFze+sbqSn8cekduMZdLyHJk # 3Niw3AnglU/WTzGsQAdch9SVV4LHifUnmwTf0i07iKtTlNkq3bx1iyWg7N7jGZAB # RWT2mX+YAVHlK27t9n+WtYbn6cOJNX6LsH8xPVBRYAIRVkWsMyEAdoP9dqfaZzwX # GmjuVQ931NhzHjjG+Efw118DXjk3Vq3qUI1re34zMMTRzZZEw82FupF3viXNR3DV # OlS9JH4x5emfINa1uuSac6F4CeJCD1GakfS7D5ayNsaZ2e+sBUh62KVTlhEsQRHZ # RwCTxbix1Y4iJw+PDNLc0Hf19qX2XiX0u2SM9CWTTjsz9SvCjIKSxCZFCNv/zpKI # lsHx7hQNQHSMbKh0/wwn86uiIALEjazUszE0+X6rcObDfU4h/O/0vmbF3BMR+45r # AZMAETJsRDPxHJCo/5XGhWdg/LoJ5XWBrODL44YNrN7FRnHEAAr06sflqZ8eeV3F # uDKdP5h19WUnGWwO1H/ZjUzOoVGiV3gwggdxMIIFWaADAgECAhMzAAAAFcXna54C # m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp # Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy # MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51 # yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY # 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9 # cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN # 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua # Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74 # kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2 # K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5 # TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk # i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q # BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri # Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC # BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw # MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp # b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm # ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM # 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW # OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4 # FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw # xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX # fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX # VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC # onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU # 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG # ahC0HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # Tjo0MDFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAhGNHD/a7Q0bQLWVG9JuGxgLRXseggYMw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF # AAIFAOtRA9YwIhgPMjAyNTAyMDcyMjEyMzhaGA8yMDI1MDIwODIyMTIzOFowdDA6 # BgorBgEEAYRZCgQBMSwwKjAKAgUA61ED1gIBADAHAgEAAgIO8zAHAgEAAgIShjAK # AgUA61JVVgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQC2Vb8No/r6GiuK # NFh3n8j5Hl1ORG+7AsqCxdl8nLn/6YE4ndlDcNF4jnMIEIe1UfzPg8xNVjzC+kxl # YsW65d6iL51CetWMLwa0bZpdXTNANMQPb/ngi46WFH/t9N1BmSb0yInEyqzHMDyl # mDWBcN6Zzv9qRvwrlUkRGPxHfTni6UP0a/c+7GZ8ofWkSwMy4QImOgCAOTN8zdgb # gKgIz6kH9eqw3JXbfUVdqankopdzvGycEGR3isvHZpoIMHMRF51Z3bAhVuRlaTTF # XaGITsWpjYCB5FXCW5M7CEDVf5noxvuCsrBhu4JfeBw6sZH3aJ/VkvtNgmQugEOq # fIFIEVyoMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAH+0KjCezQhCwEAAQAAAf4wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqG # SIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgO0wMOZLLOlHf # zgxWrhVwVQaWqONWQXaq/bQSBTiCTeMwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHk # MIG9BCARhczd/FPInxjR92m2hPWqc+vGOG1+/I0WtkCstyh0eTCBmDCBgKR+MHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB/tCowns0IQsBAAEAAAH+ # MCIEIPLPq96+QtExaQRA0ktmE+YHwaCuCygNAQAQ1iOMmJAMMA0GCSqGSIb3DQEB # CwUABIICACC+ONHr+L+XTboBrErqsr0aR+MViBqchJReOOHqhjTQpE0muZAeyfZP # YmzatOSf/y6PUr0a39K7MpGLxIQMpvQn/Th5U0WrARauyq2OqZHe9+D3yz/2fJhY # V0hIP9ro5eSErWSIiiUJf2DJ3bL6DRqn0M/DEjEBvJJCqKrpw5qrSnKu+tt34Osn # ClOnsNJF2rRURBbSjNeNvdL/1sabwMfsfnboWSgkxJIkzyQR7pBQ0tKUD2QpTeoH # 1BknPgLR+PD63GJBuudcACq+rf7F1ugTMYmagmtk9otZNtR1aVT8AOE4gp01Gis0 # gFwgyFNva80u/HbmsB1vT9LfyWo7/XD8NSB1NM6OdljQwgkiLMdHiGPF63l0KV08 # M12rCOWC76H4vWPcuG3use6aYBVNzvgZTRgZmyeHbR1Z7vye6tapHt3K2qWtUIbL # XDpHJFyqLSO7l+b7q071ztQEKUHTuXRdW5pRDN/s04Pd7P2gt5htpy78LdcKVtEf # nyiCNx8rbFYqb/G3s9SCpVdh27NAwFI8og4DoOUWkz+l9AI7gGn2gB7gX4dmbOOg # d7W05a9Vp7xm9EFOOt7kYHSZfTk5fwBY3/G+RcfrDTQlZGCHCn0dQHU4AENQYazx # kc9dgO2D/qAyJH7dcGqE9YGmZiudvd+n62yGPerwwnOx+8nQ1ZR/ # SIG # End signature block |