Microsoft.WinGet.DSC.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. using namespace System.Collections.Generic # Check that we are running as an administrator function Assert-IsAdministrator { $windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $windowsPrincipal = New-Object -TypeName 'System.Security.Principal.WindowsPrincipal' -ArgumentList @( $windowsIdentity ) $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator if (-not $windowsPrincipal.IsInRole($adminRole)) { New-InvalidOperationException -Message "This resource must run as an Administrator." } } #region enums enum WinGetAction { Partial Full } enum WinGetEnsure { Absent Present } enum WinGetMatchOption { Equals EqualsCaseInsensitive StartsWithCaseInsensitive ContainsCaseInsensitive } enum WinGetInstallMode { Default Silent Interactive } enum WinGetTrustLevel { Undefined None Trusted } #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() { $userSettings = Get-WinGetUserSetting $result = @{ SID = '' Settings = $userSettings } return $result } # Tests if desired properties match. [bool] Test() { $hashArgs = @{ UserSettings = $this.Settings } if ($this.Action -eq [WinGetAction]::Partial) { $hashArgs.Add('IgnoreNotSet', $true) } return Test-WinGetUserSetting @hashArgs } # Sets the desired properties. [void] Set() { $hashArgs = @{ UserSettings = $this.Settings } if ($this.Action -eq [WinGetAction]::Partial) { $hashArgs.Add('Merge', $true) } Set-WinGetUserSetting @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() { $settingsJson = Get-WinGetSetting # 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 # 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 WinGetSource { [DscProperty(Key, Mandatory)] [string]$Name [DscProperty(Mandatory)] [string]$Argument [DscProperty()] [string]$Type [DscProperty()] [WinGetTrustLevel]$TrustLevel = [WinGetTrustLevel]::Undefined [DscProperty()] [nullable[bool]]$Explicit = $null [DscProperty()] [WinGetEnsure]$Ensure = [WinGetEnsure]::Present [WinGetSource] Get() { if ([String]::IsNullOrWhiteSpace($this.Name)) { throw "A value must be provided for WinGetSource::Name" } $currentSource = $null try { $currentSource = Get-WinGetSource -Name $this.Name } catch { } $result = [WinGetSource]::new() if ($currentSource) { $result.Ensure = [WinGetEnsure]::Present $result.Name = $currentSource.Name $result.Argument = $currentSource.Argument $result.Type = $currentSource.Type $result.TrustLevel = $currentSource.TrustLevel $result.Explicit = $currentSource.Explicit } else { $result.Ensure = [WinGetEnsure]::Absent $result.Name = $this.Name } return $result } [bool] Test() { return $this.TestAgainstCurrent($this.Get()) } [void] Set() { Assert-IsAdministrator $currentSource = $this.Get() $removeSource = $false $resetSource = $false $addSource = $false if ($this.Ensure -eq [WinGetEnsure]::Present) { if ($currentSource.Ensure -eq [WinGetEnsure]::Present) { if (-not $this.TestAgainstCurrent($currentSource)) { $resetSource = $true $addSource = $true } # else in desired state } else { $addSource = $true } } else { if ($currentSource.Ensure -eq [WinGetEnsure]::Present) { $removeSource = $true } # else in desired state (Absent) } if ($removeSource) { Remove-WinGetSource -Name $this.Name } # Only remove OR reset should be true, not both elseif ($resetSource) { Reset-WinGetSource -Name $this.Name } if ($addSource) { $hashArgs = @{ Name = $this.Name Argument = $this.Argument } if (-not [string]::IsNullOrWhiteSpace($this.Type)) { $hashArgs.Add("Type", $this.Type) } if ($this.TrustLevel -ne [WinGetTrustLevel]::Undefined) { $hashArgs.Add("TrustLevel", $this.TrustLevel) } if ($null -ne $this.Explicit) { $hashArgs.Add("Explicit", $this.Explicit) } Add-WinGetSource @hashArgs } } # Test $this against a value retrieved from Get # We don't need to check Name because it is the Key for Get [bool] hidden TestAgainstCurrent([WinGetSource]$currentSource) { if ($this.Ensure -eq [WinGetEnsure]::Absent -and $currentSource.Ensure -eq [WinGetEnsure]::Absent) { return $true } if ($this.Ensure -ne $currentSource.Ensure -or $this.Argument -ne $currentSource.Argument) { return $false } if (-not([string]::IsNullOrWhiteSpace($this.Type)) -and $this.Type -ne $currentSource.Type) { return $false } if ($this.TrustLevel -ne [WinGetTrustLevel]::Undefined -and $this.TrustLevel -ne $currentSource.TrustLevel) { return $false } if ($null -ne $this.Explicit -and $this.Explicit -ne $currentSource.Explicit) { return $false } return $true } } # 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() { 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() { 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(Key)] [string]$Source [DscProperty()] [string]$Version [DscProperty()] [WinGetEnsure]$Ensure = [WinGetEnsure]::Present [DscProperty()] [WinGetMatchOption]$MatchOption = [WinGetMatchOption]::EqualsCaseInsensitive [DscProperty()] [bool]$UseLatest = $false [DSCProperty()] [WinGetInstallMode]$InstallMode = [WinGetInstallMode]::Silent [PSObject] hidden $CatalogPackage = $null [WinGetPackage] Get() { if ([String]::IsNullOrWhiteSpace($this.Id)) { throw "A value must be provided for WinGetPackage::Id" } $result = [WinGetPackage]::new() $hashArgs = @{ Id = $this.Id MatchOption = $this.MatchOption } if (-not([string]::IsNullOrWhiteSpace($this.Source))) { $hashArgs.Add("Source", $this.Source) } $result.CatalogPackage = Get-WinGetPackage @hashArgs if ($null -ne $result.CatalogPackage) { $result.Ensure = [WinGetEnsure]::Present $result.Id = $result.CatalogPackage.Id $result.Source = $result.CatalogPackage.Source $result.Version = $result.CatalogPackage.InstalledVersion $result.UseLatest = -not $result.CatalogPackage.IsUpdateAvailable } else { $result.Ensure = [WinGetEnsure]::Absent $result.Id = $this.Id $result.MatchOption = $this.MatchOption $result.Source = $this.Source } return $result } [bool] Test() { return $this.TestAgainstCurrent($this.Get()) } [void] Set() { $currentPackage = $this.Get() if (-not $this.TestAgainstCurrent($currentPackage)) { $hashArgs = @{ Id = $this.Id MatchOption = $this.MatchOption Mode = $this.InstallMode } if ($this.Ensure -eq [WinGetEnsure]::Present) { if (-not([string]::IsNullOrWhiteSpace($this.Source))) { $hashArgs.Add("Source", $this.Source) } if ($currentPackage.Ensure -eq [WinGetEnsure]::Present) { if ($this.UseLatest) { $this.TryUpdate($hashArgs) } elseif (-not([string]::IsNullOrWhiteSpace($this.Version))) { $hashArgs.Add("Version", $this.Version) $compareResult = $currentPackage.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() } } } [bool] hidden TestAgainstCurrent([WinGetPackage]$currentPackage) { if ($this.Ensure -eq [WinGetEnsure]::Absent -and $currentPackage.Ensure -eq [WinGetEnsure]::Absent) { return $true } $this.CatalogPackage = $currentPackage.CatalogPackage if ($this.Ensure -ne $currentPackage.Ensure) { return $false } # At this point we know is installed. # If asked for latest, but there are updates available. if ($this.UseLatest) { if (-not $currentPackage.UseLatest) { return $false } } # If there is an specific version, compare with the current installed version. elseif (-not ([string]::IsNullOrWhiteSpace($this.Version))) { $compareResult = $currentPackage.CatalogPackage.CompareToVersion($this.Version) if ($compareResult -ne 'Equal') { return $false } } return $true } 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 # MIIoOQYJKoZIhvcNAQcCoIIoKjCCKCYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCvvubd+lz5+1G9 # cyax+vrFFZCZJdcLuwAD7eAy+gTV2qCCDYUwggYDMIID66ADAgECAhMzAAAEA73V # 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/Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAQDvdWVXQ87GK0AAAAA # BAMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIP/o # NsMyIq+pzkpIoDLcL+seVB+Od+SoWpBF/QotOTe4MEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAfglW2nqF9dXUqtO8suO8lINNC99TscfIga5O # BNPiwSU+w5ZgoYwcr6oPFG1gc+FRoniqCL96qNb7u54S6E0waXAdNyh5ao/kxztR # vfm5dHQ5RZjLWYXGp8Qn3UWQ69YG9+HkNd3fLNSpS8Tmsh+X7QxEnaYSs7rnn9xU # xtOqeLtIXgmElzsgo51Hpn3Hc3fzFZkYI9sHuPNh3fD4B1UzlLcTOqjp5fGQPVRq # D60PKKqnSBf5XZ+kjPTvsKvoFS3MWGgsgcM9vJYOWIEl6xu6DrOBhN1umauhV0RR # 1+mSaRiFgnqoXl5lnYh4vGBjLEy5w5GDozMLCyYGFLuEWQNxTKGCF5QwgheQBgor # BgEEAYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCB1qMMABLUyVapwIs35Whg+g28eA3syllfJ # 1nlMwUA06wIGZ7ev9NDzGBMyMDI1MDIyODAzMjIwMy42MTJaMASAAgH0oIHRpIHO # MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL # ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk # IFRTUyBFU046RTAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l # LVN0YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgITMwAAAgsRnVYpkvm/hQAB # AAACCzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDAeFw0yNTAxMzAxOTQyNThaFw0yNjA0MjIxOTQyNThaMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqrPitRjAXqFh2IHzQYD3u # ykDPyJF+79e5CkY4aYsb93QVun4fZ3Ju/0WHrtAF3JswSiAVl7p1H2zFKrvyhaVu # RYcSc7YuyP0GHEVq7YVS5uF3YLlLeoyGOPKSXGs6agW60CqVBhPQ+2n49e6YD9wG # v6Y0HmBKmnQqY/AKJijgUiRulb1ovNEcTZmTNRu1mY+0JjiEus+eF66VNoBv1a2M # W0JPYbFBhPzFHlddFXcjf2qIkb5BYWsFL7QlBjXApf2HmNrPzG36g1ybo/KnRjSg # IRpHeYXxBIaCEGtR1EmpJ90OSFHxUu7eIjVfenqnVtag0yAQY7zEWSXMN6+CHjv3 # SBNtm5ZIRyyCsUZG8454K+865bw7FwuH8vk5Q+07K5lFY02eBDw3UKzWjWvqTp2p # K8MTa4kozvlKgrSGp5sh57GnkjlvNvt78NXbZTVIrwS7xcIGjbvS/2r5lRDT+Q3P # 2tT+g6KDPdLntlcbFdHuuzyJyx0WfCr8zHv8wGCB3qPObRXK4opAInSQ4j5iS28K # ATJGwQabRueZvhvd9Od0wcFYOb4orUv1dD5XwFyKlGDPMcTPOQr0gxmEQVrLiJEo # LyyW8EV/aDFUXToxyhfzWZ6Dc0l9eeth1Et2NQ3A/qBR5x33pjKdHJVJ5xpp2AI3 # ZzNYLDCqO1lthz1GaSz+PQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFGZcLIjfr+l6 # WeMuhE9gsxe98j/+MB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G # A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs # BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH # AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCaKPVn6GLcnkbP # EdM0R9q4Zm0+7JfG05+pmqP6nA4SwT26k9HlJQjqw/+WkiQLD4owJxooIr9MDZbi # ZX6ypPhF+g1P5u8BOEXPYYkOWpzFGLRLtlZHvfxpqAIa7mjLGHDzKr/102AXaD4m # GydEwaLGhUn9DBGdMm5dhiisWAqb/LN4lm4OuX4YLqKcW/0yScHKgprGgLY+6pqv # 0zPU74j7eCr+PDTNYM8tFJ/btUnBNLyOE4WZwBIq4tnvXjd2cCOtgUnoQjFU1ZY7 # ZWdny3BJbf3hBrb3NB2IU4nu622tVrb1fNkwdvT501WRUBMd9oFf4xifj2j2Clbv # 1XGljXmd6yJjvt+bBuvJLUuc9m+vMKOWyRwUdvOl/E5a8zV3MrjCnY6fIrLQNzBO # Z6klICPCi+2GqbViM0CI6CbZypei5Rr9hJbH8rZEzjaYWLnr/XPsU0wr2Tn6L9dJ # x2q/LAoK+oviAInj0aP4iRrMyUSO6KL2KwY6zJc6SDxbHkwYHdQRrPNP3SutMg6L # gBSvtmfqwgaXIHkCoiUFEAz9cGIqvgjGpGppKTcTuoo3EEgp/zRd0wxW0QqmV3yg # YGicen30KAWHrKFC8Sbwc6qC4podVZYJZmirHBP/uo7sQne5H0xtdvDmXDUfy5gN # jLljQIUsJhQSyyXbSjSb2a5jhOUfxzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb # 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 # ELQdVTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp # Y2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkUwMDItMDVF # MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMK # AQEwBwYFKw4DAhoDFQCoQndUJN3Ppq2xh8RhtsR35NCZwaCBgzCBgKR+MHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA62tohzAi # GA8yMDI1MDIyNzIyNDExMVoYDzIwMjUwMjI4MjI0MTExWjB0MDoGCisGAQQBhFkK # BAExLDAqMAoCBQDra2iHAgEAMAcCAQACAgZLMAcCAQACAhIRMAoCBQDrbLoHAgEA # MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI # AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAA5YFDQ4sHQy/ZB7JHrtXBfN6/5J # cocN2mAkO7rdzIqeYcUx+Rz3E3iSJcu6amsijK4WlNaOqokKrUoJJCpX42o4rgKL # /rPtsnJ/boGxqWqEMYr5TOsJNQ0yqAhnucU2IoftSfytPwJBdIM/uavTUZDXvLb6 # jzm/80df9F5iW0Tta1amAeUe1DsayOuhaV/B22tzilZ4aMzgLbL1f5xF+h6+U57m # xGZMNcEOo1hkADlzX8BbSlqO09+6L4MF9e1FzgjghrLACWVRAtqp+02/scEBxR8P # zJ+K9OK07cXR/5Z0vXxtGF9YGBYZ6KlmDyx4+4ySufXNdtO5tZUTAGOJPQcxggQN # MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgsR # nVYpkvm/hQABAAACCzANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDSMBGXqLk+byH7oYKhyCIZs4VF # AO1dLiFhKKelHx10YTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIDTVdKu6 # N77bh0wdOyF+ogRN8vKJcw5jnf2/EussYkozMIGYMIGApH4wfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAILEZ1WKZL5v4UAAQAAAgswIgQgX8Jn4NvD # l5oStAHEKsRLd/pE6JWAGUattmExszLqUkQwDQYJKoZIhvcNAQELBQAEggIAHXi8 # 4txlk9+d2hhEYZPBVokAyO8GvYC18U06biK9XiCWvP1JT1W34ki297EVvp5TDW5F # KfNKHEQCK6Csd5pUvpO8mTdRumlopgNQSc/gjlNEcgOYI3Ks4hFTJzx1I1XxCN/c # H8E7AxTrSJEYhvOrqp9pdmZXaDW8ppZCZTretel4zYDNVuJgRjfpeyTtsqcspKET # amnuQbeFv7PwkRxxhqFVDd0fQFCXpRI2hqZdyQDhO/T+rh78S0zTL1u7CfeDiG7/ # KyX30Pk3bW4TZZvNVmqZI76KdqMgPiOCjyJLHa9bhE/8azorcCdX/drsaB3v8M0+ # HsZKLLxyT6uiy0qnnx8bJzlOGulYRg9Tk0Ec3D33JzRR1fxs9MIaHagybcP2bT5r # LRmQxAuuX+hLJiw6CNExXN9G6FhiK4pr1KXeLxVuSUvGjUAk5g1NhjjAYGBYNidV # NMhjWVQc14ZHMXL7UNzJXcP9NyNVj9Ln85rQB/8YYoIdfyp/jKG+QATGFQQSiKWC # DMKXTPWnf0HTCOcCxXOISUiIhjlcDlFMrRIyWDpC30WsnJFYmTW3AEiFgIcqC9MF # EQTdfjvx+Y0wiZsfMD5lCcxqgJ1aghbfT2lDkJ0WdzwyZvy5oqgkBGX09jEYCTOs # joc+vEQD1cL3OPFqVFBLFU2F4NU78ZMxWfUcdD4= # SIG # End signature block |