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 # MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # 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/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA # A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGfB # 5QKoVKPOAHLItxSQLu2EwHZQYa9Ly5EttLgMwjrDMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAEZXWqfCQPdA3tHL4Dt08FVhUZfBVPY0Pj1+z # D2x0XmBdRCkiNcy7gIHscZlI71SwxqcVS8Y2Rqx1CdFZgBlDojc4HNsv+//E3Q6i # s6bs7luPAF5eMBC/ll4ihc3u8Iu6lznLdQaF+DnbUYfjmfYDLY6459TFCCUbk8W8 # Jx9lXBuj9TsDnB7jY370YEpjDXnWJM4y7p5hzsrCl649VLMHprYlCnu2QuvAzzag # cb3QNjfnahk3pFmn4k3NJNYGHMmmim56AOaPzg+xtLPpzpRON7hQnWVNXTSdJz38 # pg8m8Lmw2MKsmodlaKaQcyXaQHkNPQAF1n6UR0fdmomvReQ1XqGCFykwghclBgor # BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCDJYcO8nROWHBGPj6UUCxZKWPEe2N1sWA7+ # P2fMxL3cbwIGZD/SpT1oGBMyMDIzMDUxNjE4NDE1NC4wNTZaMASAAgH0oIHYpIHV # MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT # HVRoYWxlcyBUU1MgRVNOOjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3Nv # ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAGybkAD # f26plJIAAQAAAbIwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # UENBIDIwMTAwHhcNMjIwOTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z # b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjowODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqi # ZTIde/lQ4rC+Bml5f/Wuq/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jc # ldWOGUB+HzBuwllYyP3fx4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNM # A2/nskjIf2IoiG9J0qLYr8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDw # MrwUr8Wja6jFpUTny8tg0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwh # mJoib3vkKJyVxbuFLRhVXxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR6 # 9xvTjU51ZgP7BR4YDEWq7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J # 8w1sEM4XDUF2UZNy829NVCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+ # hkAT3ipGn8/KmDwDtzAbnfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7 # wQsQe6R60Zk9dPdNzw0MK5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSpl # YwdmPLzzfDtXyuy/GCU5I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqN # UvoN7ncTT+MB4Vpvf1QBppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNV # HQ4EFgQUmfIngFzZEZlPkjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D # 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l # LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD # ggIBANxHtu3FzIabaDbWqswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhA # Dfbqor1zyLi95Y6GQnvIWUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscH # ZF3wlrJUfPUIDGVP0lCYVse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGe # Oc+0DoGzB+LieObJ2Q0AtEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuve # nwdTwqB/LT6CNPaElwFeKxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96m # d0eiMjiMEO22LbOzmLMNd3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopB # l0p/00li30DN5wEQ5If35Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5 # X+fC/k5ExUCrOs3/VzGRCZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1C # sUcD1voFktld4mKMjE+uDEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl # 2aL4v1OhL2+JwO1Tx3B0irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBi # aKmVTQGoOb+j1Qa7q+cixGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIB # AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0 # IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1 # WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O # 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn # hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t # 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq # D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP # frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW # rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv # 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb # r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten # IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc # xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a # j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB # MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU # n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw # QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E # b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/ # MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ # oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p # Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB # BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v # Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h # LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x # 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p # y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A # oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC # HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB # 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt # yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3 # rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV # v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24 # 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw # Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB # 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk # TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjowODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNs # jn9cF9AUSTqggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN # BgkqhkiG9w0BAQUFAAIFAOgN6CgwIhgPMjAyMzA1MTYxOTMxNTJaGA8yMDIzMDUx # NzE5MzE1MlowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA6A3oKAIBADAHAgEAAgIB # EDAHAgEAAgIRNDAKAgUA6A85qAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE # AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB # AJs0ETTSjGzv+u2Uslc8FLdmTNouHSBFwGBKFg0B3lf5G8qjBHXGkEk+nlnoGZRH # M108BOq1GKfg4S9VgoEH9m3A5f8JBXXEwip9kgTTz2O7BOtnNh+QG6RJ4Cq0O+nn # aSOLHPJMheHkIVDEbK8+khTdwFNHDo4+MommVqm/nAXUMYIEDTCCBAkCAQEwgZMw # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGybkADf26plJIAAQAA # AbIwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB # BDAvBgkqhkiG9w0BCQQxIgQgAQQmc4v5t0rvpptYGvrY3+Nsm2TTaTFa+HdqKYJB # A8EwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBTeM485+E+t4PEVieUoFKX # 7PVyLo/nzu+htJPCG04+NTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwAhMzAAABsm5AA39uqZSSAAEAAAGyMCIEII0GlJBQyJKx7Zusn4ZEhto5 # ovDyJNfLfMVe8rq5+GwiMA0GCSqGSIb3DQEBCwUABIICAInoIY5djtI0Sz2o2Hla # 4qJP3oJHg6PYKozejyGrpQvLgKOPQ2tt9Gqr6VWbnLjjhBts7cl9I0G37G0ZXQVr # mOIzwHW1/5neJU7jLJvnL2lcyPkwlVYmSP6QbZO06zhqoz2hxqD4enKva7Z5Vi/j # QGQqFlnUHXMqCLb7SCz0okOy1/r1qNB9nOU8cGGbDRTAQOvtD1jLbDJKlBal4Y9A # fe09hrQF5EmIdYiB59qc+Ke6Ml9dcATywvLG6lzKCkfp54ySbsMdTK2xNlPRTs7m # bHTaEiO8fOzH97uwrAzpjYgWZ+X/aua3CZ+t+zvcGBs/SXyapcDULkDx71pbtdKr # WTV1goJmzdM3Y07uk3TFLWRqewX82l+M/7Em83KUa42By5DG8LxVUGm3zo3czttD # LUKpfR/r2MoECuNmfvBBoSfq3lAmRVL31rXxLAYD/eFU7GX9whsdTa9IRJQexwfr # PNfjZfsyK+LQpJscbH8PbGm0eSxdLI5G5phq0vHlfZ135mTP1E9vWG81mk8SNRF1 # 4xJmSpLIF8l4SAoyLGiLvUidcbhlelposVqWM4/bEEZccg0vg8B91BP4iuXpN5ZD # X0h86aRK0m+JJDsNIWlhX5jTvT7UYP8bXxfcp3kc10GvX63IHZIsgNgOIHxwKbne # 3nlLhrPXefA45GP6md77Y0Y+ # SIG # End signature block |