Microsoft.DotNet.Dsc.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Set-StrictMode -Version Latest #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 This class is used to install and uninstall .NET SDK tools globally or use the tool path directory. #> [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 # MIIoPAYJKoZIhvcNAQcCoIIoLTCCKCkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBbIHdIp5YpzD8Y # AokWfxoa9rRjeQE2dRpM3HZmwyR+mqCCDYUwggYDMIID66ADAgECAhMzAAAEA73V # lV0POxitAAAAAAQDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTEzWhcNMjUwOTExMjAxMTEzWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCfdGddwIOnbRYUyg03O3iz19XXZPmuhEmW/5uyEN+8mgxl+HJGeLGBR8YButGV # LVK38RxcVcPYyFGQXcKcxgih4w4y4zJi3GvawLYHlsNExQwz+v0jgY/aejBS2EJY # oUhLVE+UzRihV8ooxoftsmKLb2xb7BoFS6UAo3Zz4afnOdqI7FGoi7g4vx/0MIdi # kwTn5N56TdIv3mwfkZCFmrsKpN0zR8HD8WYsvH3xKkG7u/xdqmhPPqMmnI2jOFw/ # /n2aL8W7i1Pasja8PnRXH/QaVH0M1nanL+LI9TsMb/enWfXOW65Gne5cqMN9Uofv # ENtdwwEmJ3bZrcI9u4LZAkujAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU6m4qAkpz4641iK2irF8eWsSBcBkw # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMjkyNjAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AFFo/6E4LX51IqFuoKvUsi80QytGI5ASQ9zsPpBa0z78hutiJd6w154JkcIx/f7r # EBK4NhD4DIFNfRiVdI7EacEs7OAS6QHF7Nt+eFRNOTtgHb9PExRy4EI/jnMwzQJV # NokTxu2WgHr/fBsWs6G9AcIgvHjWNN3qRSrhsgEdqHc0bRDUf8UILAdEZOMBvKLC # rmf+kJPEvPldgK7hFO/L9kmcVe67BnKejDKO73Sa56AJOhM7CkeATrJFxO9GLXos # oKvrwBvynxAg18W+pagTAkJefzneuWSmniTurPCUE2JnvW7DalvONDOtG01sIVAB # +ahO2wcUPa2Zm9AiDVBWTMz9XUoKMcvngi2oqbsDLhbK+pYrRUgRpNt0y1sxZsXO # raGRF8lM2cWvtEkV5UL+TQM1ppv5unDHkW8JS+QnfPbB8dZVRyRmMQ4aY/tx5x5+ # sX6semJ//FbiclSMxSI+zINu1jYerdUwuCi+P6p7SmQmClhDM+6Q+btE2FtpsU0W # +r6RdYFf/P+nK6j2otl9Nvr3tWLu+WXmz8MGM+18ynJ+lYbSmFWcAj7SYziAfT0s # IwlQRFkyC71tsIZUhBHtxPliGUu362lIO0Lpe0DOrg8lspnEWOkHnCT5JEnWCbzu # iVt8RX1IV07uIveNZuOBWLVCzWJjEGa+HhaEtavjy6i7MIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAQDvdWVXQ87GK0AAAAA # BAMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIO1q # Bg4AgJ3LU8RNNC9LXjZ30ohwfDZI7xmcSxeqECNTMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAYTvbgq4Y8YZdkpJiA2ECVd/pS9Vdelp6m0R+ # k09LJUOjoIuZ3peF6HXr2Qr15hUfqpBDrET8LlET0v2DED/BQzMvUzcIGNI8ogC0 # oJdc6EgeAxImuxsPpLWvEkr3HFlkIF1stEpQqg9uXq63K48zXhtKzyfIGoX83Ycm # MU6f/keuldDl033mV0a/SHJjIv97G4z4qxM0g+PUc/3gdqxKvHgu1QccLnBc1b1S # 2wxOKL9ePLGEvAse6pl6VE12TS+hfKkhj1MM5I0kzTCw8cJAzQCGxrx697NWFvp+ # oizlyI3OU+uX3IWcaljqxVgX8pvCVU+AxsMGARkBZ/ADsg7kvKGCF5cwgheTBgor # BgEEAYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCD7Enra1ScS1Ffq+liXjZ2HD8W54uD080HW # kXoO9jBe0QIGZwfzLC8yGBMyMDI0MTAyMjEzMjgxOC40NjdaMASAAgH0oIHRpIHO # MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL # ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk # IFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l # LVN0YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAe3hX8vV96VdcwAB # AAAB7TANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDAeFw0yMzEyMDYxODQ1NDFaFw0yNTAzMDUxODQ1NDFaMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCoMMJskrrqapycLxPC1H7z # D7g88NpbEaQ6SjcTIRbzCVyYQNsz8TaL1pqFTEAPL1X7ojL4/EaEW+UjNqZs/ayM # yW4YIpFPZP2x4FBMVCddseF2i+aMMjDHi0LcTQZxM2s3mFMrCZAWSfLYXYDIimFB # z8j0oLWGy3VgLmBTKM4xLqv7DZUz8B2SoAmbEtp62ngSl0hOoN73SFwE+Y24SvGQ # MWhykpG+vXDwcpWvwDe+TgnrLR7ATRFXN5JS26dm2yy6SYFMRYnME3dMHCQ/UQIQ # QNC8nLmIvdKkAoWEMXtJsGEo3QrM2S2SBv4PpHRzRukzTtP+UAceGxM9JyrwUQP5 # OCEmW6YchEyRDSwP4hU9f7B0Ayh14Pw9vJo7jewNjeMPIkmneyLSi0ruv2ox/xRG # tcJ9yBNC5BaRktjz7stPaojR+PDA2fuBtCo8xKlkt53mUb7AY+CZHHqhLm76pdMF # 6BHv2TvwlVBeQRN22XjaVVRwCgjgJnNewt7PejcrpUn0qHLgLq+1BN1DzYukWkTr # 7wT0zl0iXr+NtqUkWSOnWRfe8N21tB6uv3VkW8nFdChtbbZZz24peLtJEZuNrN8X # f9PTPMzZXDJBI1EciR/91QcGoZFmVbFVb2rUIAs01+ZkewvbhmGVDefX9oZG4/K4 # gGUsTvTW+r1JZMxUT2MwqQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFM4b8Oz33hAq # BEfKlAZf0NKh4CIZMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G # A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs # BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH # AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCd1gK2Rd+eGL0e # Hi+iE6/qDY8sbbsO4emancp6KPN+xq5ZAatiBR4jmRRhm+9Vik0Fo0DLWi/N28bF # I7dXYw09p3vCipbjy4Eoifm0Nud7/4U30i9+7RvW7XOQ3rx37+U7vq9lk6yYpGCN # p0jlJ188/CuRPgqJnfq5EdeafH2AoG46hKWTeB7DuXasGt6spJOenGedSre34MWZ # qeTIQ0raOItZnFuGDy4+xoD1qRz2QW+u2gCHaG8AQjhYUM4uTi9t6kttj6c7Xamr # 2zrWuceDhz7sKLttLTJ7ws5YrA2I8cTlbMAf2KW0GVjKbYGd+LZGduEK7/7fs4GU # kMqc51FsNdG1n+zgc7zHu2oGGeCBg4s8ZR0ZFyx7jsgm9sSFCKQ5CsbAvlr/60Nd # k5TeMR8Js2kNUicu2CqZ03833TsvTgk7iD1KLgfS16HEvjN6m4VKJKgjJ7OJJzab # tS4JQgUnJrIZfyosk4D18rZni9pUwN03WgTmd10WTwiZOu4g8Un6iKcPMY/iFqTu # 4ntkzFUxBBpbFG6k1CINZmoirEWmCtG3lyZ2IddmjtIefTkIvGWb4Jxzz7l2m/E2 # kGOixDJHsahZVmwsoNvhy5ku/inU++dXHzw+hlvqTSFT89rIFVhcmsWPDJPNRSSp # MhoJ33V2Za/lkKcbkUM0SbQgS9qsdzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb # SZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj # YXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy # NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXI # yjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjo # YH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1y # aa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v # 3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pG # ve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viS # kR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYr # bqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM # jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSL # W6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AF # emzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIu # rQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIE # FgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWn # G1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEW # M2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5 # Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi # AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV # 9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js # Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx # MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2 # LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv # 6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZn # OlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1 # bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4 # rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU # 6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDF # NLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/ # HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdU # CbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi # excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTm # dHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZq # ELQdVTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp # Y2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVF # MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMK # AQEwBwYFKw4DAhoDFQDuHayKTCaYsYxJh+oWTx6uVPFw+aCBgzCBgKR+MHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6sGZ5TAi # GA8yMDI0MTAyMjAzMjYyOVoYDzIwMjQxMDIzMDMyNjI5WjB3MD0GCisGAQQBhFkK # BAExLzAtMAoCBQDqwZnlAgEAMAoCAQACAjOXAgH/MAcCAQACAhLmMAoCBQDqwutl # AgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSCh # CjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBABveEfWZaVwZ+OlFZ9kQtpqo # Ffz92o3jQxZGqJotswekcNytSoTUCe2H4C2ik0DWqxkPmBoUSlA1YLpyiWu7bnoO # GzMIteq79YoMg4cJEffK29YSRBKJc2bumA8YrFpNfOeDXHs2cebM4/kXmBegVP3K # HYDKz9Tz1OCwpERHiHXPy7kY5miXbOOFxv4ksewAggcYFfZzr0bclY3OYgHQ6CXG # LKUD2vMFoH1p5EudsxQCoegDQ5b6cnJ+LPr33YCWQnNQuZDAVt7K/gm+N3CUppms # jCpJarOXz2QL1NdmckLx5gLeeHaCL0D7KdQaYNwhr62Vd4BKrLox1n8Sr33t6ZMx # ggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA # Ae3hX8vV96VdcwABAAAB7TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCBBqZJUhmdRxuEVGdLpFW4z # L0Osyjt7ElpBhru5fabthzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EII0u # DWg0CFseKxK3A16l1wrIwrsSDrXZ6xSf0F4xbMo5MIGYMIGApH4wfDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHt4V/L1felXXMAAQAAAe0wIgQgPzUO # mS933Rxkby0kSWrJlGP4Qaiob+zZLFDJ9fArZ8QwDQYJKoZIhvcNAQELBQAEggIA # P3lptC//Woc5BP6m3SzezmNrSYZ/AnrJVSu49SUjJ6sQ1RuvHTmeleBAVM4MwMDn # MLyXyrvcnN4hQOxIVlyijHRneXSY6wQe3z31c8UPhRBgpV+bu6LjQJTaZGx8WEFH # 8PPELcJyxmnxwvEsKUX8V++Sh4Lf2ME5xxfNA9A3POl0meUQQhfj2YgARq925m1P # VXxx4cxyBSKOO8kEkiKO2NyP9F8kKBX+tvpXwUJ5BEBseOdh00uer3SIXZcZNDUE # ymIJ4uZZazznRL3/ROpQ5WxIGmK4jbMClOtdKWdR22+dl5KsNp9g1gS6AbAQJyL0 # FZrVW4s7ylf7DF7Jx9aeeupe0Tm5c66sQydJ0qUYLsigRXKAKJZ1BC17AhTYMUxu # ypL3XOQp0c3N4SzIA7vldD5607tH8anrEAe9yydEu8+u1yhphTD/3uO5moJBrL50 # 4FZy4pMjxSFEn98LgOUhcaZymj7R0ymxVJivbwCQent2kqyMvvwxbrFGyDvSnjPP # WQ9ePZy9kMyJHhpiztREmdObLRwNqo0wAvVcAzYa5IabCnegnhSxv76nv13YMvqN # p4nUeSMWxhhoBJMpjBsmejFI2ZA5uc59G5rob+OGJZM2BObpsuOYZoDElocOC1su # KVI9xlvlGbhdpin+rx3qanS+kwyBTp3QDVN5As3z6RE= # SIG # End signature block |