Microsoft.WinGet.DSC.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. using namespace System.Collections.Generic try { # Load all non-test .ps1 files in the script's directory. Get-ChildItem -Path $PSScriptRoot\* -Filter *.ps1 -Exclude *.Tests.ps1 -Recurse | ForEach-Object { Import-Module $_.FullName } } catch { $e = $_.Exception while ($e.InnerException) { $e = $e.InnerException } if (-not [string]::IsNullOrWhiteSpace($e.Message)) { Write-Host $e.Message -ForegroundColor Red -BackgroundColor Black } } #region enums enum WinGetAction { Partial Full } enum Ensure { Absent Present } enum MatchOption { Equals EqualsCaseInsensitive StartsWithCaseInsensitive ContainsCaseInsensitive } enum InstallMode { Default Silent Interactive } #endregion enums #region DscResources # Author here all DSC Resources. # DSC Powershell doesn't support binary DSC resources without the MOF schema. # DSC Powershell classes aren't discoverable if placed outside of the psm1. # This resource is in charge of managing the settings.json file of winget. [DSCResource()] class WinGetUserSettings { # We need a key. Do not set. [DscProperty(Key)] [string]$SID # A hash table with the desired settings. [DscProperty(Mandatory)] [Hashtable]$Settings [DscProperty()] [WinGetAction]$Action = [WinGetAction]::Full # Gets the current UserSettings by looking at the settings.json file for the current user. [WinGetUserSettings] Get() { Assert-WinGetCommand "Get-WinGetUserSettings" $userSettings = Get-WinGetUserSettings $result = @{ SID = '' Settings = $userSettings } return $result } # Tests if desired properties match. [bool] Test() { Assert-WinGetCommand "Test-WinGetUserSettings" $hashArgs = @{ UserSettings = $this.Settings } if ($this.Action -eq [WinGetAction]::Partial) { $hashArgs.Add('IgnoreNotSet', $true) } return Test-WinGetUserSettings @hashArgs } # Sets the desired properties. [void] Set() { Assert-WinGetCommand "Set-WinGetUserSettings" $hashArgs = @{ UserSettings = $this.Settings } if ($this.Action -eq [WinGetAction]::Partial) { $hashArgs.Add('Merge', $true) } Set-WinGetUserSettings @hashArgs } } # Handles configuration of administrator settings. [DSCResource()] class WinGetAdminSettings { # We need a key. Do not set. [DscProperty(Key)] [string]$SID # A hash table with the desired admin settings. [DscProperty(Mandatory)] [Hashtable]$Settings # Gets the administrator settings. [WinGetAdminSettings] Get() { Assert-WinGetCommand "Get-WinGetSettings" $settingsJson = Get-WinGetSettings | ConvertFrom-Json -AsHashtable # Get admin setting values. $result = @{ SID = '' Settings = $settingsJson.adminSettings } return $result } # Tests if administrator settings given are set as expected. # This doesn't do a full comparison to allow users to don't have to update # their resource every time a new admin setting is added on winget. [bool] Test() { $adminSettings = $this.Get().Settings foreach ($adminSetting in $adminSettings.GetEnumerator()) { if ($this.Settings.ContainsKey($adminSetting.Name)) { if ($this.Settings[$adminSetting.Name] -ne $adminSetting.Value) { return $false } } } return $true } # Sets the desired properties. [void] Set() { Assert-IsAdministrator Assert-WinGetCommand "Enable-WinGetSetting" Assert-WinGetCommand "Disable-WinGetSetting" # It might be better to implement an internal Test with one value, or # create a new instances with only one setting than calling Enable/Disable # for all of them even if only one is different. if (-not $this.Test()) { foreach ($adminSetting in $this.Settings.GetEnumerator()) { if ($adminSetting.Value) { Enable-WinGetSetting -Name $adminSetting.Name } else { Disable-WinGetSetting -Name $adminSetting.Name } } } } } [DSCResource()] class WinGetSources { # We need a key. Do not set. [DscProperty(Key)] [string]$SID # An array of Hashtable with the key value properties that follows the source's group policy schema. [DscProperty(Mandatory)] [Hashtable[]]$Sources [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [DscProperty()] [bool]$Reset = $false [DscProperty()] [WinGetAction]$Action = [WinGetAction]::Full # Gets the current sources on winget. [WinGetSources] Get() { Assert-WinGetCommand "Get-WinGetSource" $packageCatalogReferences = Get-WinGetSource $wingetSources = [List[Hashtable]]::new() foreach ($packageCatalogReference in $packageCatalogReferences) { $source = @{ Arg = $packageCatalogReference.Info.Argument Identifier = $packageCatalogReference.Info.Id Name = $packageCatalogReference.Info.Name Type = $packageCatalogReference.Info.Type } $wingetSources.Add($source) } $result = @{ SID = '' Sources = $wingetSources } return $result } # Tests if desired properties match. [bool] Test() { $currentSources = $this.Get().Sources # If this is a full match and the counts are different give up. if (($this.Action -eq [WinGetAction]::Full) -and ($this.Sources.Count -ne $currentSources.Count)) { return $false } # There's no need to differentiate between Partial and Full anymore. foreach ($source in $this.Sources) { # Require Name and Arg. if ((-not $source.ContainsKey("Name")) -or [string]::IsNullOrWhiteSpace($source.Name)) { # TODO: Localize. throw "Invalid source input. Name is required." } if ((-not $source.ContainsKey("Arg")) -or [string]::IsNullOrWhiteSpace($source.Arg)) { # TODO: Localize. throw "Invalid source input. Arg is required." } # Type has a default value. $sourceType = "Microsoft.PreIndexed.Package" if ($source.ContainsKey("Type") -and (-not([string]::IsNullOrWhiteSpace($source.Type)))) { $sourceType = $source.Type } $result = $currentSources | Where-Object { $_.Name -eq $source.Name -and $_.Arg -eq $source.Arg -and $_.Type -eq $sourceType } # Source not found. if ($null -eq $result) { return $false } } return $true } # Sets the desired properties. [void] Set() { Assert-IsAdministrator Assert-WinGetCommand "Add-WinGetSource" Assert-WinGetCommand "Reset-WinGetSource" Assert-WinGetCommand "Remove-WinGetSource" foreach ($source in $this.Sources) { $sourceType = "Microsoft.PreIndexed.Package" # Require Name and Arg. if ((-not $source.ContainsKey("Name")) -or [string]::IsNullOrWhiteSpace($source.Name)) { # TODO: Localize. throw "Invalid source input. Name is required." } if ((-not $source.ContainsKey("Arg")) -or [string]::IsNullOrWhiteSpace($source.Arg)) { # TODO: Localize. throw "Invalid source input. Arg is required." } if ($source.ContainsKey("Type") -and (-not([string]::IsNullOrWhiteSpace($source.Type)))) { $sourceType = $source.Type } if ($this.Ensure -eq [Ensure]::Present) { Add-WinGetSource -Name $source.Name -Argument $source.Argument -Type $sourceType if ($this.Reset) { Reset-WinGetSource -Name $source.Name } } else { Remove-WinGetSource -Name $source.Name } } } } # TODO: It would be nice if these resource has a non configurable property that has extra information that comes from # GitHub. We could implement it here or add more cmdlets in Microsoft.WinGet.Client. [DSCResource()] class WinGetPackageManager { # We need a key. Do not set. [DscProperty(Key)] [string]$SID [DscProperty()] [string]$Version = "" [DscProperty()] [bool]$UseLatest [DscProperty()] [bool]$UseLatestPreRelease # If winget is not installed the version will be empty. [WinGetPackageManager] Get() { $integrityResource = [WinGetPackageManager]::new() if ($integrityResource.Test()) { $integrityResource.Version = Get-WinGetVersion } return $integrityResource } # Tests winget is installed. [bool] Test() { Assert-WinGetCommand "Assert-WinGetPackageManager" Assert-WinGetCommand "Get-WinGetVersion" try { $hashArgs = @{} if ($this.UseLatest) { $hashArgs.Add("Latest", $true) } elseif ($this.UseLatestPreRelease) { $hashArgs.Add("Latest", $true) $hashArgs.Add("IncludePreRelease", $true) } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) { $hashArgs.Add("Version", $this.Version) } Assert-WinGetPackageManager @hashArgs } catch { return $false } return $true } # Repairs Winget. [void] Set() { Assert-WinGetCommand "Repair-WinGetPackageManager" if (-not $this.Test()) { $result = -1 $hashArgs = @{} if ($this.UseLatest) { $hashArgs.Add("Latest", $true) } elseif ($this.UseLatestPreRelease) { $hashArgs.Add("Latest", $true) $hashArgs.Add("IncludePreRelease", $true) } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) { $hashArgs.Add("Version", $this.Version) } $result = Repair-WinGetPackageManager @hashArgs if ($result -ne 0) { # TODO: Localize. throw "Failed to repair winget. Result $result" } } } } [DSCResource()] class WinGetPackage { [DscProperty(Key, Mandatory)] [string]$Id [DscProperty()] [string]$Version [DscProperty()] [string]$Source [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [DscProperty()] [MatchOption]$MatchOption = [MatchOption]::EqualsCaseInsensitive [DscProperty()] [bool]$UseLatest = $false [DSCProperty()] [InstallMode]$InstallMode = [InstallMode]::Silent [DscProperty(NotConfigurable)] [string]$InstalledVersion [DscProperty(NotConfigurable)] [bool]$IsInstalled = $false [DscProperty(NotConfigurable)] [bool]$IsUpdateAvailable = $false [PSObject] hidden $CatalogPackage = $null hidden Initialize() { # DSC only validates keys and mandatories in a Set call. if ([string]::IsNullOrWhiteSpace($this.Id)) { # TODO: Localize. throw "WinGetPackage: Id is required" } if (($this.UseLatest -eq $true) -and (-not[string]::IsNullOrWhiteSpace($this.Version))) { # TODO: Localize. throw "WinGetPackage: Version and UseLatest cannot be set at the same time" } # This has to use MatchOption equals. Otherwise, it might find other package where the # id starts with. $this.CatalogPackage = Get-WinGetPackage -Id $this.Id -MatchOption $this.MatchOption if ($null -ne $this.CatalogPackage) { $this.InstalledVersion = $this.CatalogPackage.InstalledVersion $this.IsInstalled = $true $this.IsUpdateAvailable = $this.CatalogPackage.IsUpdateAvailable } } # Get. [WinGetPackage] Get() { Assert-WinGetCommand "Get-WinGetPackage" $this.Initialize() return $this } # Test. [bool] Test() { $this.Initialize() $ensureInstalled = $this.Ensure -eq [Ensure]::Present # Not installed, doesn't have to. if (-not($this.IsInstalled -or $ensureInstalled)) { return $true } # Not install, need to ensure installed. # Installed, need to ensure not installed. if ($this.IsInstalled -ne $ensureInstalled) { return $false } # At this point we know is installed. # If asked for latests, but there are updates available. if ($this.UseLatest -and $this.CatalogPackage.IsUpdateAvailable) { return $false } # If there is an specific version, compare with the current installed version. if (-not ([string]::IsNullOrWhiteSpace($this.Version))) { $compareResult = $this.CatalogPackage.CompareToVersion($this.Version) if ($compareResult -ne 'Equal') { return $false } } # For now this is all. return $true } # Set. [void] Set() { Assert-WinGetCommand "Install-WinGetPackage" Assert-WinGetCommand "Uninstall-WinGetPackage" if (-not $this.Test()) { $hashArgs = @{ Id = $this.Id MatchOption = $this.MatchOption Mode = $this.InstallMode } if ($this.Ensure -eq [Ensure]::Present) { if (-not([string]::IsNullOrWhiteSpace($this.Source))) { $hashArgs.Add("Source", $this.Source) } if ($this.IsInstalled) { if ($this.UseLatest) { $this.TryUpdate($hashArgs) } elseif (-not([string]::IsNullOrWhiteSpace($this.Version))) { $hashArgs.Add("Version", $this.Version) $compareResult = $this.CatalogPackage.CompareToVersion($this.Version) switch ($compareResult) { 'Lesser' { $this.TryUpdate($hashArgs) break } {'Greater' -or 'Unknown'} { # The installed package has a greater version or unknown. Uninstall and install. $this.Uninstall() $this.Install($hashArgs) break } } } } else { if (-not([string]::IsNullOrWhiteSpace($this.Version))) { $hashArgs.Add("Version", $this.Version) } $this.Install($hashArgs) } } else { $this.Uninstall() } } } hidden Install([Hashtable]$hashArgs) { $installResult = Install-WinGetPackage @hashArgs if (-not $installResult.Succeeded()) { # TODO: Localize. throw "WinGetPackage Failed installing $($this.Id). $($installResult.ErrorMessage())" } } hidden Uninstall() { $uninstallResult = Uninstall-WinGetPackage -PSCatalogPackage $this.CatalogPackage if (-not $uninstallResult.Succeeded()) { # TODO: Localize. throw "WinGetPackage Failed uninstalling $($this.Id). $($uninstallResult.ErrorMessage())" } } hidden Update([Hashtable]$hashArgs) { $updateResult = Update-WinGetPackage @hashArgs if (-not $updateResult.Succeeded()) { # TODO: Localize. throw "WinGetPackage Failed updating $($this.Id). $($updateResult.ErrorMessage())" } } # Tries to update, if not, uninstall and install. hidden TryUpdate([Hashtable]$hashArgs) { try { $this.Update($hashArgs) } catch { $this.Uninstall() $this.Install($hashArgs) } } } #endregion DscResources # SIG # Begin signature block # MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDM1WbuYzqrC0h9 # E+PET+GspiwKKWYPXNm6zHCriNOsRqCCDYUwggYDMIID66ADAgECAhMzAAADTU6R # phoosHiPAAAAAANNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI4WhcNMjQwMzE0MTg0MzI4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDUKPcKGVa6cboGQU03ONbUKyl4WpH6Q2Xo9cP3RhXTOa6C6THltd2RfnjlUQG+ # Mwoy93iGmGKEMF/jyO2XdiwMP427j90C/PMY/d5vY31sx+udtbif7GCJ7jJ1vLzd # j28zV4r0FGG6yEv+tUNelTIsFmmSb0FUiJtU4r5sfCThvg8dI/F9Hh6xMZoVti+k # bVla+hlG8bf4s00VTw4uAZhjGTFCYFRytKJ3/mteg2qnwvHDOgV7QSdV5dWdd0+x # zcuG0qgd3oCCAjH8ZmjmowkHUe4dUmbcZfXsgWlOfc6DG7JS+DeJak1DvabamYqH # g1AUeZ0+skpkwrKwXTFwBRltAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUId2Img2Sp05U6XI04jli2KohL+8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMDUxNzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACMET8WuzLrDwexuTUZe9v2xrW8WGUPRQVmyJ1b/BzKYBZ5aU4Qvh5LzZe9jOExD # YUlKb/Y73lqIIfUcEO/6W3b+7t1P9m9M1xPrZv5cfnSCguooPDq4rQe/iCdNDwHT # 6XYW6yetxTJMOo4tUDbSS0YiZr7Mab2wkjgNFa0jRFheS9daTS1oJ/z5bNlGinxq # 2v8azSP/GcH/t8eTrHQfcax3WbPELoGHIbryrSUaOCphsnCNUqUN5FbEMlat5MuY # 94rGMJnq1IEd6S8ngK6C8E9SWpGEO3NDa0NlAViorpGfI0NYIbdynyOB846aWAjN # fgThIcdzdWFvAl/6ktWXLETn8u/lYQyWGmul3yz+w06puIPD9p4KPiWBkCesKDHv # XLrT3BbLZ8dKqSOV8DtzLFAfc9qAsNiG8EoathluJBsbyFbpebadKlErFidAX8KE # usk8htHqiSkNxydamL/tKfx3V/vDAoQE59ysv4r3pE+zdyfMairvkFNNw7cPn1kH # Gcww9dFSY2QwAxhMzmoM0G+M+YvBnBu5wjfxNrMRilRbxM6Cj9hKFh0YTwba6M7z # ntHHpX3d+nabjFm/TnMRROOgIXJzYbzKKaO2g1kWeyG2QtvIR147zlrbQD4X10Ab # rRg9CpwW7xYxywezj+iNAc+QmFzR94dzJkEPUSCJPsTFMIIHejCCBWKgAwIBAgIK # 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/Xmfwb1tbWrJUnMTDXpQzTGCGXIwghluAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA # A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGfB # 5QKoVKPOAHLItxSQLu2EwHZQYa9Ly5EttLgMwjrDMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAEZXWqfCQPdA3tHL4Dt08FVhUZfBVPY0Pj1+z # D2x0XmBdRCkiNcy7gIHscZlI71SwxqcVS8Y2Rqx1CdFZgBlDojc4HNsv+//E3Q6i # s6bs7luPAF5eMBC/ll4ihc3u8Iu6lznLdQaF+DnbUYfjmfYDLY6459TFCCUbk8W8 # Jx9lXBuj9TsDnB7jY370YEpjDXnWJM4y7p5hzsrCl649VLMHprYlCnu2QuvAzzag # cb3QNjfnahk3pFmn4k3NJNYGHMmmim56AOaPzg+xtLPpzpRON7hQnWVNXTSdJz38 # pg8m8Lmw2MKsmodlaKaQcyXaQHkNPQAF1n6UR0fdmomvReQ1XqGCFvwwghb4Bgor # BgEEAYI3AwMBMYIW6DCCFuQGCSqGSIb3DQEHAqCCFtUwghbRAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFQBgsqhkiG9w0BCRABBKCCAT8EggE7MIIBNwIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCDJYcO8nROWHBGPj6UUCxZKWPEe2N1sWA7+ # P2fMxL3cbwIGZDfqLDLvGBIyMDIzMDUxMTA0MjExMi40NVowBIACAfSggdCkgc0w # gcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsT # HE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBU # U1MgRVNOOkU1QTYtRTI3Qy01OTJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T # dGFtcCBTZXJ2aWNloIIRVDCCBwwwggT0oAMCAQICEzMAAAG+9CCi7pbWINYAAQAA # Ab4wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw # HhcNMjIxMTA0MTkwMTIyWhcNMjQwMjAyMTkwMTIyWjCByjELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp # Y2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RTVBNi1FMjdD # LTU5MkUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQClX/LbPsNrucy7S3KQtjyiWHtn # TcSoU3PeIWUyn2A59WZkAGaF4JzztG491DY/44dQmKoJABY241Kgj9DWLETD0ADr # nuV0Pxnf8SS2mbEocdq86HBBIU9ylMYVVcjEoLCg7zbiCLIc8bzh1+F2LpZTt/sP # 7zkto8HR06w8coowaUL2nrou/3JDO8CFkYWYWGW6wLL96CvPolf84c5P2oLC6CGs # vQg9/jtQt7WlBIQSKHLjfwnBL6tlTgBXK9BzOUwLbpexO4M+ARAqXPH2u7sS81X3 # 2X8oJT1tsV/lKeQ3WahSApSrT01aUrHMsYS+GR7ZA0yimfzomHV+X89V683/GtlK # lXbesziUHuWHtdKwI94WyVNiiMo3aKg4LqncHLuQSa9kKHqsCw8qwBEkhJ3MpAIy # r6aoO6I/qav8u+5YqKc/7ZkaYr8LX+yS+VOO0h6G7nTKhc0OWHUI30HdAuCVBj5Q # IESomiD8HECfelZ1HTWj/rpchpyBcj93TAbb/HQ61uMQYCRpx9CWbDRsNzTZ2FAW # SL/VD1VvCHiQLtWACIkDxsLnMQhhYc1TsL4d7r0Hj/Z1mlGOB3mkSkdsX05iIB/u # zkydgScc3/mj9sY7RqMBvtUjh/1q/rawLrG+EpMHlHiWHEQxYXTPi/sFDkIfIw2Q # v6hOfMkuqctV1ee4zQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFOsqIBahhEGg8a1v # C9uGFfprb6KqMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud # HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr # BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ # KoZIhvcNAQELBQADggIBAMeV+71zQiaF0GKzXKPnpsG85LIakL+tJK3L7UXj1N/p # +YUR6rGHBNMdUc54hE13yBwMtLPR3v2ZcKqrzKerqAmDLa7gvLICvYubDMVW67Gg # ZVHxi5SdG2+wMfkn66fJ7+cyTAeIL4bzaHe5Dx9waP7YfSco+ZSlS19Cu4xRe/Mu # NXk3JGMOIIvlz9/l5ybPTV2emcK8TqQjP8VOmS855UmTbYjZqQVmE/PbgPo5PoqR # O3AFGlIQcNioJDhxn7tJfHuPPN3tv7Sn28NuioLLtLBaAqkZAb7BVsqtObiEqRkP # Nx0ASBip6FfPvwbTSZgguINPJSKTBCmhntqb2kDoF1M9j6jW/oJHNyd4g6clhqcd # bPRH4oRH9lEW0sLIEy8vNIcSfSxHT7SQuSWdwqMZ0DVgDjbM5vrXVR4gbK1n1WE3 # CfjCzkYnqfo8mYw877I8SQ7LZ/w4GK6FqqWKmJaHMa23lSwLSB4bSxb2rBrhABbW # xBYiuFKXbgw45XA2X8Cb39mq8tFavXHie6l5Hwbv4M3KfgxODbzIVlFTWS1K/IEx # RK83Yr30E7qnWBLH/C9KxHjl0bfc8Mbl8qoc6APFy2MFTltfj14mqM0vtL9Sd0sX # tLQ5Yv2Z2T+M9Uc/Yjpe03QrhWN1HC8iCveM2JvcZnIYmc5Gn9kxtjYO/WYpzHt1 # MIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsF # ADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UE # AxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcN # MjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzn # tHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3 # lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFE # yHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+ # jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4x # yDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBc # TyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9 # pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ # 8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pn # ol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYG # NRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cI # FRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEE # AYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E # 7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwr # BgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUF # BwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNV # HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYG # A1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3Js # L3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcB # AQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kv # Y2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUA # A4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2 # P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J # 6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfak # Vqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/AL # aoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtP # u4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5H # LcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEua # bvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvB # QUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb # /wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETR # kPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAsswggI0AgEB # MIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQL # Ex1UaGFsZXMgVFNTIEVTTjpFNUE2LUUyN0MtNTkyRTElMCMGA1UEAxMcTWljcm9z # b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAaK1aUve8+7wQ # 04B76Lb7jB9MwHuggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDANBgkqhkiG9w0BAQUFAAIFAOgGqJAwIhgPMjAyMzA1MTEwNzM0NDBaGA8yMDIz # MDUxMjA3MzQ0MFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA6AaokAIBADAHAgEA # AgIT9jAHAgEAAgIQqjAKAgUA6Af6EAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor # BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA # A4GBAGoMRplx123gKC3jMgWBNDWzCf0T/sWLpusnJYZFZGpClJswksqvb7hoDMVs # 53SVlb1m15ARQ5WVqlz7UVnyfygpgpxhG68mvx/EKD9f5aekwaQKrg/KN2wAIMDE # osrssC2cC+F2ltJle/Ks0b3Y8leUFxwo8DPhsnaZnVHAr96NMYIEDTCCBAkCAQEw # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG+9CCi7pbWINYA # AQAAAb4wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B # CRABBDAvBgkqhkiG9w0BCQQxIgQgPoY08VWnCIaSkcgBvWPmdT5x3XJb9DpTvzO9 # kYp6AUQwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCU7oqvrfb87L1ltc+u # EQ+J00CD8V5/srdJmD4PGOEMLzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAABvvQgou6W1iDWAAEAAAG+MCIEIJRYI1Rl7YC64dmS1vpa # 7/+00B1V9GMx+yqBHFoG7RLuMA0GCSqGSIb3DQEBCwUABIICABdKmRk1eXnqhDHb # 8vTzl/wh11keN0hsDm8aIk4iewU3FJ7i/uwomAF9njDzAk/BIMI+5rOFXqpDXpm2 # WXI6+YB6mne99/Gp/6DJ0jiC6LhrxUuAt2mdbY7LeoYO0UuhbLY5LgXku+ulmizm # jxZc0LCCGsPKB1vH+jtP0GGGBao4VCrhoPhzJ4+KoS6gXvYgk0HO8LdLnaKZNikj # 03cAlOsbNLoftueUKXQap2k5wUHZnN/Nq+lRG3oyjTosG6X6IOXF7p6Ol/6vXPGr # wnplj81+iCh9WWLQZUxip059RuCzJmu0xOpM90JJxHxjLVreOzsnAbKgAp0Ymurw # dY9Ir7CK/q9YK4Xe5G6Mz8t5+Q0A0u8TMMzfS8W0RV4bBXtsMc01WDJsAMEf8+NV # MghwSlAZ2MheSmJQxDULS6L54rpS576V/akOJjYAQK6LthYJRc8qhiEP5boNSNB2 # ZffhZtHhy/64BOEblzAgfB5yZBrrPsyeGjRdIqHCVz6zORM3q91VVGnH13JJkWnt # lDC7DauS2saq3m2EMfN5fFlJWOaUoIc9eJKMqAxQyGgrpsVcEDc9SZPdNB0gp0EK # qGUcQY32s+mra6LcYp+wTot8I+MNZedcHS44hF0kXV/gkrcHIKWWcPcXcvTyLibC # WGy/OLCeM9knluz7Wvl+3h5ljJff # SIG # End signature block |