PythonPip3Dsc.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. using namespace System.Collections.Generic #region Functions function Invoke-Process { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$FilePath, [Parameter()] [ValidateNotNullOrEmpty()] [string]$ArgumentList ) try { $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $FilePath $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.WindowStyle = 'Hidden' $pinfo.CreateNoWindow = $true $pinfo.Arguments = $ArgumentList $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $stOut = @() # using ReadLine() instead of ReadToEnd() for building array object. ReadToEnd() gave different output than ReadLine() in some cases. while (-not $p.StandardOutput.EndOfStream) { $stOut += $p.StandardOutput.ReadLine() } $stErr = @() while (-not $p.StandardError.EndOfStream) { $stErr += $p.StandardError.ReadLine() } $result = [pscustomobject]@{ Title = ($MyInvocation.MyCommand).Name Command = $FilePath Arguments = $ArgumentList StdOut = $stOut StdErr = $stErr ExitCode = $p.ExitCode } $p.WaitForExit() return $result } catch { Write-Verbose -Message "Error occurred while executing the command: $FilePath $ArgumentList. Error:" Write-Verbose -Message $stErr } } function Get-Pip3Path { if ($IsWindows) { # Note: When installing 64-bit version, the registry key: HKLM:\SOFTWARE\Wow6432Node\Python\PythonCore\*\InstallPath was empty. $userUninstallRegistry = 'HKCU:\SOFTWARE\Python\PythonCore\*\InstallPath' $machineUninstallRegistry = 'HKLM:\SOFTWARE\Python\PythonCore\*\InstallPath' $installLocationProperty = 'ExecutablePath' $pipExe = TryGetRegistryValue -Key $userUninstallRegistry -Property $installLocationProperty if ($null -ne $pipExe) { $userInstallLocation = Join-Path (Split-Path $pipExe -Parent) 'Scripts\pip3.exe' if ($userInstallLocation) { return $userInstallLocation } } $pipExe = TryGetRegistryValue -Key $machineUninstallRegistry -Property $installLocationProperty if ($null -ne $pipExe) { $machineInstallLocation = Join-Path (Split-Path $pipExe -Parent) 'Scripts\pip3.exe' if ($machineInstallLocation) { return $machineInstallLocation } } } elseif ($IsMacOS) { $pipExe = Join-Path '/Library' 'Frameworks' 'Python.framework' 'Versions' 'Current' 'bin' 'python' if (Test-Path -Path $pipExe) { return $pipExe } $pipExe = (Get-Command -Name 'pip3' -ErrorAction SilentlyContinue).Source if ($pipExe) { return $pipExe } } elseif ($IsLinux) { $pipExe = Join-Path '/usr/bin' 'pip3' if (Test-Path $pipExe) { return $pipExe } $pipExe = (Get-Command -Name 'pip3' -ErrorAction SilentlyContinue).Source if ($pipExe) { return $pipExe } } else { throw 'Operating system not supported.' } } function Assert-Pip3 { # Try invoking pip3 help with the alias. If it fails, switch to calling npm.cmd directly. # This may occur if npm is installed in the same shell window and the alias is not updated until the shell window is restarted. try { Invoke-Pip3 -command 'help' return } catch {} if (Test-Path -Path $global:pip3ExePath) { $global:usePip3Exe = $true Write-Verbose "Calling pip3.exe from install location: $global:usePip3Exe" } else { throw 'Python is not installed' } } function Get-PackageNameWithVersion { param ( [Parameter(Mandatory = $true)] [string]$PackageName, [Parameter(Mandatory = $false)] [AllowNull()] [string]$Version ) if ($PSBoundParameters.ContainsKey('Version') -and -not ([string]::IsNullOrEmpty($Version))) { $packageName = $PackageName + '==' + $Version } return $packageName } function Invoke-Pip3Install { param ( [Parameter()] [string]$PackageName, [Parameter()] [string]$Arguments, [Parameter()] [string]$Version, [Parameter()] [switch]$IsUpdate, [Parameter()] [switch]$DryRun ) $command = [List[string]]::new() $command.Add('install') $command.Add((Get-PackageNameWithVersion -PackageName $PackageName -Version $Version)) if ($IsUpdate.IsPresent) { $command.Add('--force-reinstall') } if ($DryRun.IsPresent) { $command.Add('--dry-run') } $command.Add($Arguments) Write-Verbose -Message "Executing 'pip' install with command: $command" $result = Invoke-Pip3 -command $command return $result } function Invoke-Pip3Uninstall { param ( [Parameter()] [string]$PackageName, [Parameter()] [string]$Arguments, [Parameter()] [string]$Version ) $command = [List[string]]::new() $command.Add('uninstall') $command.Add((Get-PackageNameWithVersion -PackageName $PackageName -Version $Version)) $command.Add($Arguments) # '--yes' is needed to ignore conformation required for uninstalls $command.Add('--yes') return Invoke-Pip3 -command $command } function GetPip3CurrentState { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [AllowNull()] [hashtable[]] $Package, [Parameter(Mandatory = $true)] [AllowNull()] [hashtable] $Parameters ) # Filter out the installed packages from the parameters because it is not a valid parameter when calling .ToHashTable() in the class for comparison if ($Parameters.ContainsKey('InstalledPackages')) { $Parameters.Remove('InstalledPackages') } $out = @{ Exist = $false PackageName = $Parameters.PackageName Version = $Parameters.Version Arguments = $Parameters.Arguments InstalledPackages = $Package } foreach ($entry in $Package) { if ($entry.PackageName -eq $Parameters.PackageName) { Write-Verbose -Message "Package exist: $($entry.name)" $out.Exist = $true $out.Version = $entry.version if ($Parameters.ContainsKey('version') -and $entry.version -ne $Parameters.version) { Write-Verbose -Message "Package exist, but version is different: $($entry.version)" $out.Exist = $false } } } return $out } function GetInstalledPip3Packages { $Arguments = [List[string]]::new() $Arguments.Add('list') $Arguments.Add('--format=json') if ($global:usePip3Exe) { $command = "& '$global:pip3ExePath' " + $Arguments } else { $command = '& pip3 ' + $Arguments } $res = Invoke-Expression -Command $command | ConvertFrom-Json $result = $res | ForEach-Object { @{ PackageName = $_.name Version = $_.version } } return $result } function Invoke-Pip3 { param ( [Parameter(Mandatory)] [string]$command ) if ($global:usePip3Exe) { return Invoke-Process -FilePath $global:pip3ExePath -ArgumentList $command } else { return Invoke-Process -FilePath pip3 -ArgumentList $command } } function TryGetRegistryValue { param ( [Parameter(Mandatory = $true)] [string]$Key, [Parameter(Mandatory = $true)] [string]$Property ) if (Test-Path -Path $Key) { try { return (Get-ItemProperty -Path $Key | Select-Object -ExpandProperty $Property) } catch { Write-Verbose "Property `"$($Property)`" could not be found." } } else { Write-Verbose 'Registry key does not exist.' } } #endregion Functions $global:usePip3Exe = $false $global:pip3ExePath = Get-Pip3Path # Assert once that pip3 is already installed on the system. Assert-Pip3 #region DSCResources <# .SYNOPSIS The `Pip3Package` DSC Resource allows you to install, update, and uninstall Python packages using pip3. .PARAMETER SID The security identifier. This is a key property and should not be set manually. .PARAMETER Exist Indicates whether the package should exist. Defaults to $true. .PARAMETER Package The name of the Python package to manage. This is a mandatory property. For a list of Python packages, see https://pypi.org/. .PARAMETER Version The version of the Python package to manage. If not specified, the latest version will be used. .PARAMETER Arguments Additional arguments to pass to pip3. .PARAMETER InstalledPackages A list of installed packages. This property is not configurable. .EXAMPLE PS C:\> Invoke-DscResource -ModuleName PythonPip3Dsc -Name Pip3Package -Method Set -Property @{ Package = 'flask' } This example installs the Flask package. .EXAMPLE PS C:\> Invoke-DscResource -ModuleName PythonPip3Dsc -Name Pip3Package -Method Get -Property @{ Package = 'flask'; Version = '1.1.4' } This example shows how to get the current state of the Flask package with version. If the version is not found, the latest version will be used if flask is found. .EXAMPLE PS C:\> Invoke-DscResource -ModuleName PythonPip3Dsc -Name Pip3Package -Method Get -Property @{ Package = 'django'; Exist = $false } This example shows how Django can be removed from the system. #> [DSCResource()] class Pip3Package { [DscProperty(Key, Mandatory)] [string]$PackageName [DscProperty()] [string]$Version [DscProperty()] [string]$Arguments [DscProperty()] [bool] $Exist = $true [DscProperty(NotConfigurable)] [hashtable[]]$InstalledPackages [Pip3Package] Get() { $this.InstalledPackages = GetInstalledPip3Packages $currentState = GetPip3CurrentState -Package $this.InstalledPackages -Parameters $this.ToHashTable() return $currentState } [bool] Test() { $currentState = $this.Get() if ($currentState.Exist -ne $this.Exist) { return $false } if (-not ([string]::IsNullOrEmpty($this.Version))) { if ($this.Version -ne $currentState.Version) { return $false } } return $true } [void] Set() { if ($this.Test()) { return } $currentPackage = $this.InstalledPackages | Where-Object { $_.PackageName -eq $this.PackageName } if ($currentPackage -and $currentPackage.Version -ne $this.Version -and $this.Exist) { Invoke-Pip3Install -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments -IsUpdate } elseif ($this.Exist) { Invoke-Pip3Install -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments } else { Invoke-Pip3Uninstall -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments } } [string] WhatIf() { if ($this.Exist) { $whatIfState = Invoke-Pip3Install -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments -DryRun $whatIfResult = $whatIfState.StdOut if ($whatIfState.ExitCode -ne 0) { $whatIfResult = $whatIfState.StdErr } $out = @{ PackageName = $this.PackageName _metaData = @{ whatIf = $whatIfResult } } } else { # Uninstall does not have --dry-run param $out = @{} } return ($out | ConvertTo-Json -Depth 10 -Compress) } static [Pip3Package[]] Export() { $packages = GetInstalledPip3Packages $out = [List[Pip3Package]]::new() foreach ($package in $packages) { $in = [Pip3Package]@{ PackageName = $package.PackageName Version = $package.version Exist = $true Arguments = $null InstalledPackages = $packages } $out.Add($in) } return $out } #region Pip3Package Helper functions [hashtable] ToHashTable() { $parameters = @{} foreach ($property in $this.PSObject.Properties) { if (-not ([string]::IsNullOrEmpty($property.Value))) { $parameters[$property.Name] = $property.Value } } return $parameters } #endRegion Pip3Package Helper functions } #endregion DSCResources # SIG # Begin signature block # MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD0ofYqg1gmFmNY # Js76fq9ICCtdZkd/inwXut2dEV6yRqCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIKWWLirAaj9prbe4zgAX5q1w # Ok8dxGjYgql/zuBJwRrzMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEALVNypXNP9tYDvMcAShz7Y/gqhKRLjUIduslzHRF0ZCjvj50IDvOnyrg/ # cXxlSnp+kAVtB0AG030UBovakpyzNCwv9+Kq1OGPeDuRG+HmjIvTH4diYzMmZoLy # JujcksVy5CtiddzIue8+nbIykGt2f0JMHFH4KbVhHzJrLQQhhlyOawxsA+7yvg7B # aNucU2xf2c1NGNtjICZvZC6/3cyNdC4TpjHVSDSEyBdmhA9Ni6ohaZ4DeTNgH2Vi # hZcuMRjTMNDQbJWyPeEDkvgDz7lkQq4u5hLDBNqO5GCQJ7u6LLmlvGQaE+sOSUtJ # mGLndNbrgLhd54CshiWh1BJpjMzvTaGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC # F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq # hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCAO3gIWM4KOcWpvJ/WCJ92Uj82DWwz0TSzIeEuFEEyUNAIGaC4JajF9 # GBMyMDI1MDUyODIwMjc0Ni41ODNaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # Tjo0QzFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAAB/xI4fPfBZdahAAEAAAH/MA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0 # MDcyNTE4MzExOVoXDTI1MTAyMjE4MzExOVowgdMxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w # ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjRDMUEt # MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyeiV0pB7bg8/qc/mkiDd # JXnzJWPYgk9mTGeI3pzQpsyrRJREWcKYHd/9db+g3z4dU4VCkAZEXqvkxP5QNTtB # G5Ipexpph4PhbiJKwvX+US4KkSFhf1wflDAY1tu9CQqhhxfHFV7vhtmqHLCCmDxh # ZPmCBh9/XfFJQIUwVZR8RtUkgzmN9bmWiYgfX0R+bDAnncUdtp1xjGmCpdBMygk/ # K0h3bUTUzQHb4kPf2ylkKPoWFYn2GNYgWw8PGBUO0vTMKjYD6pLeBP0hZDh5P3f4 # xhGLm6x98xuIQp/RFnzBbgthySXGl+NT1cZAqGyEhT7L0SdR7qQlv5pwDNerbK3Y # SEDKk3sDh9S60hLJNqP71iHKkG175HAyg6zmE5p3fONr9/fIEpPAlC8YisxXaGX4 # RpDBYVKpGj0FCZwisiZsxm0X9w6ZSk8OOXf8JxTYWIqfRuWzdUir0Z3jiOOtaDq7 # XdypB4gZrhr90KcPTDRwvy60zrQca/1D1J7PQJAJObbiaboi12usV8axtlT/dCeP # C4ndcFcar1v+fnClhs9u3Fn6LkHDRZfNzhXgLDEwb6dA4y3s6G+gQ35o90j2i6am # aa8JsV/cCF+iDSGzAxZY1sQ1mrdMmzxfWzXN6sPJMy49tdsWTIgZWVOSS9uUHhSY # kbgMxnLeiKXeB5MB9QMcOScCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBTD+pXk/rT/ # d7E/0QE7hH0wz+6UYTAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww # bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF # BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAOSNN5MpLiyun # m866frWIi0hdazKNLgRp3WZPfhYgPC3K/DNMzLliYQUAp6WtgolIrativXjOG1lI # jayG9r6ew4H1n5XZdDfJ12DLjopap5e1iU/Yk0eutPyfOievfbsIzTk/G51+uiUJ # k772nVzau6hI2KGyGBJOvAbAVFR0g8ppZwLghT4z3mkGZjq/O4Z/PcmVGtjGps2T # CtI4rZjPNW8O4c/4aJRmYQ/NdW91JRrOXRpyXrTKUPe3kN8N56jpl9kotLhdvd89 # RbOsJNf2XzqbAV7XjV4caCglA2btzDxcyffwXhLu9HMU3dLYTAI91gTNUF7BA9q1 # EvSlCKKlN8N10Y4iU0nyIkfpRxYyAbRyq5QPYPJHGA0Ty0PD83aCt79Ra0IdDIMS # uwXlpUnyIyxwrDylgfOGyysWBwQ/js249bqQOYPdpyOdgRe8tXdGrgDoBeuVOK+c # RClXpimNYwr61oZ2/kPMzVrzRUYMkBXe9WqdSezh8tytuulYYcRK95qihF0irQs6 # /WOQJltQX79lzFXE9FFln9Mix0as+C4HPzd+S0bBN3A3XRROwAv016ICuT8hY1In # yW7jwVmN+OkQ1zei66LrU5RtAz0nTxx5OePyjnTaItTSY4OGuGU1SXaH49JSP3t8 # yGYA/vorbW4VneeD721FgwaJToHFkOIwggdxMIIFWaADAgECAhMzAAAAFcXna54C # m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp # Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy # MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51 # yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY # 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9 # cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN # 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua # Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74 # kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2 # K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5 # TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk # i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q # BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri # Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC # BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw # MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp # b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm # ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM # 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW # OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4 # FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw # xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX # fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX # VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC # onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU # 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG # ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # Tjo0QzFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAqROMbMS8JcUlcnPkwRLFRPXFspmggYMw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF # AAIFAOvhwdIwIhgPMjAyNTA1MjgxNzA5MzhaGA8yMDI1MDUyOTE3MDkzOFowdzA9 # BgorBgEEAYRZCgQBMS8wLTAKAgUA6+HB0gIBADAKAgEAAgIkMQIB/zAHAgEAAgIT # yDAKAgUA6+MTUgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow # CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQBpjyqg7fkA # 3iv2nZxUCxM+1lbHy6TS978FF91yo2N/TezzMMheBiVs5ZsOG0FPNCEO9NrSUGHO # P0lptYoSoKtsWn93mqW1G80qDT1CO2OO9uFaepRXZM5u18Q8tpEtTxZlnAduw47C # z+bEg+hEoWdHKJjELhVtVK6FxA2K2OqKXtN19fihv4yvLmtVDeIk+mSDPl3O9smq # VnIp8ZX2RM2I+714qTrVl1hX29e0/6GrSeVVIuFRymtucHmP4j6953AmAJ6Vgf8x # pC1nt7iqzpWcd6dt3A3TS0OynlOkMt2kdzasZmrlKcaDgr4m4ix0VFxghoWI0JAu # BiWCIzAaeoqHMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAH/Ejh898Fl1qEAAQAAAf8wDQYJYIZIAWUDBAIBBQCgggFKMBoG # CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgYNor2vmf # 9ToJCbLW0TsQ8WloxDtoJNW11zg0Pddnl7gwgfoGCyqGSIb3DQEJEAIvMYHqMIHn # MIHkMIG9BCDkMu++yQJ3aaycIuMT6vA7JNuMaVOI3qDjSEV8upyn/TCBmDCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB/xI4fPfBZdahAAEA # AAH/MCIEIAkCUnv9pjzZGZeW9ZXxjCoVaRz+CF0FPIvD3okKPDo2MA0GCSqGSIb3 # DQEBCwUABIICAL+4j6kVWagu1c9E29lxB61PgyPhqu2YhV/RoYumB+ppvAtqf6sa # PBRePuMIYRgb6lCWMKyob4TGKb1QuIqjCqCnRU9MyVJIpGoi4sKwimfw6TC3O4Ki # AccWBuK+8GqA5E7QWfrN+OSIAwBk5h0bsctVjruGjoXuncZyO5I7+mU8DcD7Pc+I # rmrjqJmq1Uzykae05p4RbnaAIgtteoTr0ttrxVWl7uBSjonHq9O5w58ACPBf69XI # IRp3HCYy7GK5wL6H/4joPcROH4ZqvaRB9hvIblP/IVFIEUbqiHL1CSzb+rBgPSUf # KA32yLd/T4r6l/pOmngGv9wO8ZYp12H6z6RKVVN6ch33QxARb3RbXH3ZAwUnmEym # Ulo+xBcKnLmrdU5uY6NOFZV2J3zT9m9Pec4AfFDvW0LhuaH49POdlyVkEIuSg3NO # 94dtRLFLPxrfvbnP8WgJMoWbBj5mHIDvZNSzPgZGw6KJ2vWqwExj043E0m2g660u # UWc51tfiMMCdd8iUkKjY4eMg1xRpEh4SZeBQ0Me4j9AFubDs9PR2r1CPBdiIwYH6 # 9vytjEYGC4K/ykfTbTkgSU3jPUny5B8WhVD03bWmhe4QVixCXA8b6UF6AEj4TmFx # wVhegHP/ibC9dYzsJPLcLZahncCO3X4rZETAedIRWRd5dy+OlfSa/lpY # SIG # End signature block |