Public/Install-Program.ps1
<# .SYNOPSIS Install a Program using PowerShellGet/PackageManagement Modules OR the Chocolatey CmdLine. .DESCRIPTION This function was written to make program installation on Windows as easy and generic as possible by leveraging existing solutions such as PackageManagement/PowerShellGet and the Chocolatey CmdLine. Default behavior for this function (using only the -ProgramName parameter) is to try installation via PackageManagement/PowerShellGet. If that fails for whatever reason, then the Chocolatey CmdLine is used (it will be installed if it isn't already). You can use more specific parameters to change this default behavior (i.e. ONLY try installation via PowerShellGet/PackageManagement or ONLY try installation via Chocolatey CmdLine). If you use the -ResolveCommandPath parameter, this function will attempt to find the Main Executable associated with the Program you are installing. If the .exe does NOT have the same name as the Program, the function may need additional information provided via the -CommandName and/or -ExpectedInstallLocation parameters in order to find the Main Executable. .NOTES .PARAMETER ProgramName This parameter is MANDATORY. This paramter takes a string that represents the name of the program that you'd like to install. .PARAMETER CommandName This parameter is OPTIONAL. This parameter takes a string that represents the name of the main executable for the installed program. For example, if you are installing 'openssh', the value of this parameter should be 'ssh'. .PARAMETER PreRelease This parameter is OPTIONAL. This parameter is a switch. If used, the latest version of the program in the pre-release branch (if one exists) will be installed. .PARAMETER GetPreviousVersion This parameter is OPTIONAL. This parameter is a switch. If used, the version preceding the latest version of the program will be installed. .PARAMETER UsePowerShellGet This parameter is OPTIONAL. This parameter is a switch. If used the function will attempt program installation using ONLY PackageManagement/PowerShellGet Modules. If installation using those modules fails, the function halts and returns the relevant error message(s). Installation via the Chocolatey CmdLine will NOT be attempted. .PARAMETER ForceChocoInstallScript This parameter is OPTIONAL. This parameter is a switch. If the program being installed is from the Chocolatey Package Repository, using this parameter will force running the program's associated 'chocolateyinstall.ps1' script. This switch exists because some Chocolatey packages do NOT run 'chocolateyinstall.ps1' by default, meaning that 'Get-Package' could report that a program is 'Installed' when it actually is not. .PARAMETER UseChocolateyCmdLine This parameter is OPTIONAL. This parameter is a switch. If used the function will attempt installation using ONLY the Chocolatey CmdLine. (The Chocolatey CmdLine will be installed if it is not already). If installation via the Chocolatey CmdLine fails for whatever reason, the function halts and returns the relevant error message(s). .PARAMETER UpdatePackageManagement This parameter is OPTIONAL. This parameter is a switch. If used, PowerShellGet/PackageManagement Modules will be updated before any install actions take place. WARNING: If the Modules are updated, you may need to open a new PowerShell Session before they can be used. .PARAMETER ExpectedInstallLocation This parameter is OPTIONAL. This parameter takes a string that represents the full path to a directory that will contain main executable associated with the program to be installed. This directory does NOT have to be the immediate parent directory of the .exe. If you are absolutely certain you know where the Main Executable for the program to be installed will be, then use this parameter to speed things up. .PARAMETER ScanCDriveForMainExeIfNecessary This parameter is OPTIONAL. This parameter is a switch. If used in conjunction with the -CommandName parameter, this function will scan the entire C Drive until it finds a .exe that matches the values provided to the -CommandName parameter. .PARAMETER ResolveCommandPath This parameter is OPTIONAL. This parameter is a switch. This switch is meant to be used in situations where you are not certain what the name of the Main Executable of the program to be installed will be. This switch will provide an array of exe files associated with the program installation in the 'PossibleMainExecutables' property of the function's output. .EXAMPLE # Open an elevated PowerShell Session, import the module, and - PS C:\Users\zeroadmin> Install-Program -ProgramName kubernetes-cli -CommandName kubectl.exe .EXAMPLE # Open an elevated PowerShell Session, import the module, and - PS C:\Users\zeroadmin> Install-Program -ProgramName awscli -CommandName aws.exe -UsePowerShellGet .EXAMPLE # Open an elevated PowerShell Session, import the module, and - PS C:\Users\zeroadmin> Install-Program -ProgramName VisualStudioCode -CommandName Code.exe -UseChocolateyCmdLine .EXAMPLE # If the Program Name and Main Executable are the same, then this is all you need for the function to find the Main Executable PS C:\Users\zeroadmin> Install-Program -ProgramName vagrant #> function Install-Program { [CmdletBinding(DefaultParameterSetName='ChocoCmdLine')] Param ( [Parameter( Mandatory=$True, Position=0 )] [string]$ProgramName, [Parameter(Mandatory=$False)] [string]$CommandName, [Parameter(Mandatory=$False)] [switch]$PreRelease, [Parameter(Mandatory=$False)] [switch]$GetPreviousVersion, [Parameter( Mandatory=$False, ParameterSetName='PackageManagement' )] [switch]$UsePowerShellGet, [Parameter( Mandatory=$False, ParameterSetName='PackageManagement' )] [switch]$ForceChocoInstallScript, [Parameter(Mandatory=$False)] [switch]$UseChocolateyCmdLine, [Parameter(Mandatory=$False)] [switch]$UpdatePackageManagement, [Parameter(Mandatory=$False)] [string]$ExpectedInstallLocation, [Parameter(Mandatory=$False)] [switch]$ScanCDriveForMainExeIfNecessary, [Parameter(Mandatory=$False)] [switch]$ResolveCommandPath ) ##### BEGIN Native Helper Functions ##### # The below function adds Paths from System PATH that aren't present in $env:Path (this probably shouldn't # be an issue, because $env:Path pulls from System PATH...but sometimes profile.ps1 scripts do weird things # and also $env:Path wouldn't necessarily be updated within the same PS session where a program is installed...) function Synchronize-SystemPathEnvPath { $SystemPath = $(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path $SystemPathArray = $SystemPath -split ";" | foreach {if (-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} $EnvPathArray = $env:Path -split ";" | foreach {if (-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} # => means that $EnvPathArray HAS the paths but $SystemPathArray DOES NOT # <= means that $SystemPathArray HAS the paths but $EnvPathArray DOES NOT $PathComparison = Compare-Object $SystemPathArray $EnvPathArray [System.Collections.ArrayList][Array]$SystemPathsThatWeWantToAddToEnvPath = $($PathComparison | Where-Object {$_.SideIndicator -eq "<="}).InputObject if ($SystemPathsThatWeWantToAddToEnvPath.Count -gt 0) { foreach ($NewPath in $SystemPathsThatWeWantToAddToEnvPath) { if ($env:Path[-1] -eq ";") { $env:Path = "$env:Path$NewPath" } else { $env:Path = "$env:Path;$NewPath" } } } } # Outputs [System.Collections.ArrayList]$ExePath function Adjudicate-ExePath { [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [string]$ProgramName, [Parameter(Mandatory=$True)] [string]$OriginalSystemPath, [Parameter(Mandatory=$True)] [string]$OriginalEnvPath, [Parameter(Mandatory=$True)] [string]$FinalCommandName, [Parameter(Mandatory=$False)] [string]$ExpectedInstallLocation ) # ...search for it in the $ExpectedInstallLocation if that parameter is provided by the user... if ($ExpectedInstallLocation) { [System.Collections.ArrayList][Array]$ExePath = $(Get-ChildItem -Path $ExpectedInstallLocation -File -Recurse -Filter "*$FinalCommandName.exe").FullName } # If we don't have $ExpectedInstallLocation provided... if (!$ExpectedInstallLocation) { # ...then we can compare $OriginalSystemPath to the current System PATH to potentially # figure out which directories *might* contain the main executable. $OriginalSystemPathArray = $OriginalSystemPath -split ";" | foreach {if (-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} $OriginalEnvPathArray = $OriginalEnvPath -split ";" | foreach {if (-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} $CurrentSystemPath = $(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path $CurrentSystemPathArray = $CurrentSystemPath -split ";" | foreach {if (-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} $CurrentEnvPath = $env:Path $CurrentEnvPathArray = $CurrentEnvPath -split ";" | foreach {if (-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} $OriginalVsCurrentSystemPathComparison = Compare-Object $OriginalSystemPathArray $CurrentSystemPathArray $OriginalVsCurrentEnvPathComparison = Compare-Object $OriginalEnvPathArray $CurrentEnvPathArray [System.Collections.ArrayList]$DirectoriesToSearch = @() if ($OriginalVsCurrentSystemPathComparison -ne $null) { # => means that $CurrentSystemPathArray has some new directories [System.Collections.ArrayList][Array]$NewSystemPathDirs = $($OriginalVsCurrentSystemPathComparison | Where-Object {$_.SideIndicator -eq "=>"}).InputObject if ($NewSystemPathDirs.Count -gt 0) { foreach ($dir in $NewSystemPathDirs) { $null = $DirectoriesToSearch.Add($dir) } } } if ($OriginalVsCurrentEnvPathComparison -ne $null) { # => means that $CurrentEnvPathArray has some new directories [System.Collections.ArrayList][Array]$NewEnvPathDirs = $($OriginalVsCurrentEnvPathComparison | Where-Object {$_.SideIndicator -eq "=>"}).InputObject if ($NewEnvPathDirs.Count -gt 0) { foreach ($dir in $NewEnvPathDirs) { $null = $DirectoriesToSearch.Add($dir) } } } if ($DirectoriesToSearch.Count -gt 0) { $DirectoriesToSearchFinal = $($DirectoriesToSearch | Sort-Object | Get-Unique) | foreach {if (Test-Path $_) {$_}} $DirectoriesToSearchFinal = $DirectoriesToSearchFinal | Where-Object {$_ -match "$ProgramName"} [System.Collections.ArrayList]$ExePath = @() foreach ($dir in $DirectoriesToSearchFinal) { [Array]$ExeFiles = $(Get-ChildItem -Path $dir -File -Filter "*$FinalCommandName.exe").FullName if ($ExeFiles.Count -gt 0) { $null = $ExePath.Add($ExeFiles) } } # If there IS a difference in original vs current System PATH / $Env:Path, but we # still DO NOT find the main executable in those diff directories (i.e. $ExePath is still not set), # it's possible that the name of the main executable that we're looking for is actually # incorrect...in which case just tell the user that we can't find the expected main # executable name and provide a list of other .exe files that we found in the diff dirs. if (!$ExePath -or $ExePath.Count -eq 0) { [System.Collections.ArrayList]$ExePath = @() foreach ($dir in $DirectoriesToSearchFinal) { [Array]$ExeFiles = $(Get-ChildItem -Path $dir -File -Filter "*.exe").FullName foreach ($File in $ExeFiles) { $null = $ExePath.Add($File) } } } } } $ExePath | Sort-Object | Get-Unique } ##### END Native Helper Functions ##### ##### BEGIN Variable/Parameter Transforms and PreRun Prep ##### # Invoke-WebRequest fix... [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" Push-Location if (!$(GetElevation)) { Write-Error "The $($MyInvocation.MyCommand.Name) function must be ran from an elevated PowerShell Session (i.e. 'Run as Administrator')! Halting!" $global:FunctionResult = "1" return } Write-Host "Please wait..." $global:FunctionResult = "0" $MyFunctionsUrl = "https://raw.githubusercontent.com/pldmgg/misc-powershell/master/MyFunctions" $null = Install-PackageProvider -Name Nuget -Force -Confirm:$False $null = Set-PSRepository -Name PSGallery -InstallationPolicy Trusted $null = Install-PackageProvider -Name Chocolatey -Force -Confirm:$False $null = Set-PackageSource -Name chocolatey -Trusted -Force if ($UpdatePackageManagement) { if (![bool]$(Get-Command Update-PackageManagement -ErrorAction SilentlyContinue)) { $UpdatePMFunctionUrl = "$MyFunctionsUrl/PowerShellCore_Compatible/Update-PackageManagement.ps1" try { Invoke-Expression $([System.Net.WebClient]::new().DownloadString($UpdatePMFunctionUrl)) } catch { Write-Error $_ Write-Error "Unable to load the Update-PackageManagement function! Halting!" $global:FunctionResult = "1" return } } try { $global:FunctionResult = "0" $null = Update-PackageManagement -AddChocolateyPackageProvider -ErrorAction SilentlyContinue -ErrorVariable UPMErr if ($UPMErr -and $global:FunctionResult -eq "1") {throw "The Update-PackageManagement function failed! Halting!"} } catch { Write-Error $_ Write-Host "Errors from the Update-PackageManagement function are as follows:" Write-Error $($UPMErr | Out-String) $global:FunctionResult = "1" return } } if ($UseChocolateyCmdLine -or $(!$UsePowerShellGet -and !$UseChocolateyCmdLine)) { if (![bool]$(Get-Command Install-ChocolateyCmdLine -ErrorAction SilentlyContinue)) { $InstallCCFunctionUrl = "$MyFunctionsUrl/Install-ChocolateyCmdLine.ps1" try { Invoke-Expression $([System.Net.WebClient]::new().DownloadString($InstallCCFunctionUrl)) } catch { Write-Error $_ Write-Error "Unable to load the Install-ChocolateyCmdLine function! Halting!" $global:FunctionResult = "1" return } } } if (![bool]$(Get-Command Update-ChocolateyEnv -ErrorAction SilentlyContinue)) { $RefreshCEFunctionUrl = "$MyFunctionsUrl/PowerShellCore_Compatible/Update-ChocolateyEnv.ps1" try { Invoke-Expression $([System.Net.WebClient]::new().DownloadString($RefreshCEFunctionUrl)) } catch { Write-Error $_ Write-Error "Unable to load the Update-ChocolateyEnv function! Halting!" $global:FunctionResult = "1" return } } # Get-AllPackageInfo try { #$null = clist --local-only $PackageManagerInstallObjects = Get-AllPackageInfo -ProgramName $ProgramName -ErrorAction SilentlyContinue [array]$ChocolateyInstalledProgramObjects = $PackageManagerInstallObjects.ChocolateyInstalledProgramObjects [array]$PSGetInstalledPackageObjects = $PackageManagerInstallObjects.PSGetInstalledPackageObjects [array]$RegistryProperties = $PackageManagerInstallObjects.RegistryProperties } catch { Write-Error $_ $global:FunctionResult = "1" return } $AllPackageManagementVersions = Find-Package -Name $ProgramName -Source chocolatey -AllVersions -EA SilentlyContinue if ([bool]$AllPackageManagementVersions) { $PackageManagementLatestVersion = $($AllPackageManagementVersions | Sort-Object -Property Version -EA SilentlyContinue)[-1] $PackageManagementPreviousVersion = $($AllPackageManagementVersions | Sort-Object -Property Version -EA SilentlyContinue)[-2] } if ([bool]$(Get-Command choco -EA SilentlyContinue)) { $AllChocoVersions = choco list $ProgramName -e --all if ([bool]$AllChocoVersions) { $ChocoLatestVersion = $($AllChocoVersions[1] -split "[\s]")[1].Trim() $ChocoPreviousVersion = $($AllChocoVersions[2] -split "[\s]")[1].Trim() } } if ($PSGetInstalledPackageObjects.Count -gt 0) { if ($PSGetInstalledPackageObjects.Count -eq 1) { $PackageManagementCurrentInstalledPackage = $PSGetInstalledPackageObjects $PackageManagementLatestVersion = $(Find-Package -Name $PSGetInstalledPackageObjects.Name -Source chocolatey -AllVersions | Sort-Object -Property Version)[-1] $PackageManagementPreviousVersion = $(Find-Package -Name $PSGetInstalledPackageObjects.Name -Source chocolatey -AllVersions | Sort-Object -Property Version)[-2] } if ($PSGetInstalledPackageObjects.Count -gt 1) { $ExactMatchCheck = $PSGetInstalledPackageObjects | Where-Object {$_.Name -eq $ProgramName} if (!$ExactMatchCheck) { Write-Warning "The following Programs are currently installed and match the string '$ProgramName':" for ($i=0; $i -lt $PSGetInstalledPackageObjects.Count; $i++) { Write-Host "$i) $($PSGetInstalledPackageObjects[$i].Name)" } $ValidChoiceNumbers = 0..$($PSGetInstalledPackageObjects.Count-1) $ProgramChoiceNumber = Read-Host -Prompt " Please choose the number that corresponds to the Program you would like to update:" while ($ValidChoiceNumbers -notcontains $ProgramChoiceNumber) { Write-Warning "'$ProgramChoiceNumber' is not a valid option. Please choose: $($ValidChoicenumbers -join ", ")" $ProgramChoiceNumber = Read-Host -Prompt " Please choose the number that corresponds to the Program you would like to update:" } $ProgramName = $PSGetInstalledPackageObjects[$ProgramChoiceNumber].Name $PackageManagementLatestVersion = $(Find-Package -Name $UpdatedProgramName -Source chocolatey -AllVersions | Sort-Object -Property Version)[-1] } else { $PackageManagementLatestVersion = $(Find-Package -Name $ProgramName -Source chocolatey -AllVersions | Sort-Object -Property Version)[-1] } } } if ($ChocolateyInstalledProgramObjects.Count -gt 0) { if ($ChocolateyInstalledProgramObjects.Count -gt 1) { $ExactMatchCheck = $ChocolateyInstalledProgramObjects | Where-Object {$_.ProgramName -eq $ProgramName} if (!$ExactMatchCheck) { Write-Warning "The following Programs are currently installed and match the string '$ProgramName':" for ($i=0; $i -lt $ChocolateyInstalledProgramObjects.Count; $i++) { Write-Host "$i) $($ChocolateyInstalledProgramObjects[$i].ProgramName)" } $ValidChoiceNumbers = 0..$($ChocolateyInstalledProgramObjects.Count-1) $ProgramChoiceNumber = Read-Host -Prompt " Please choose the number that corresponds to the Program you would like to update:" while ($ValidChoiceNumbers -notcontains $ProgramChoiceNumber) { Write-Warning "'$ProgramChoiceNumber' is not a valid option. Please choose: $($ValidChoicenumbers -join ", ")" $ProgramChoiceNumber = Read-Host -Prompt " Please choose the number that corresponds to the Program you would like to update:" } $ProgramName = $ChocolateyInstalledProgramObjects[$ProgramChoiceNumber].ProgramName } } # Also get a list of outdated packages in case this Install-Program function is used to update a package $ChocolateyOutdatedProgramsPrep = choco outdated $UpperLineMatch = $ChocolateyOutdatedProgramsPrep -match "Output is package name" $LowerLineMatch = $ChocolateyOutdatedProgramsPrep -match "Chocolatey has determined" $UpperIndex = $ChocolateyOutdatedProgramsPrep.IndexOf($UpperLineMatch) + 2 $LowerIndex = $ChocolateyOutdatedProgramsPrep.IndexOf($LowerLineMatch) - 2 $ChocolateyOutdatedPrograms = $ChocolateyOutdatedProgramsPrep[$UpperIndex..$LowerIndex] [System.Collections.ArrayList]$ChocolateyOutdatedProgramsPSObjects = @() foreach ($line in $ChocolateyOutdatedPrograms) { $ParsedLine = $line -split "\|" $Program = $ParsedLine[0] $CurrentInstalledVersion = $ParsedLine[1] $LatestAvailableVersion = $ParsedLine[2] $PSObject = [pscustomobject]@{ ProgramName = $Program CurrentInstalledVersion = $CurrentInstalledVersion LatestAvailableVersion = $LatestAvailableVersion } $null = $ChocolateyOutdatedProgramsPSObjects.Add($PSObject) } # Get all available Chocolatey Versions $AllChocoVersions = choco list $ProgramName -e --all # Get the latest version of $ProgramName from chocolatey $ChocoLatestVersion = $($AllChocoVersions[1] -split "[\s]")[1].Trim() # Also get the previous version of $ProgramName in case we want the previous version $ChocoPreviousVersion = $($AllChocoVersions[2] -split "[\s]")[1].Trim() } if ($CommandName -match "\.exe") { $CommandName = $CommandName -replace "\.exe","" } $FinalCommandName = if ($CommandName) {$CommandName} else {$ProgramName} # Save the original System PATH and $env:Path before we do anything, just in case $OriginalSystemPath = $(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path $OriginalEnvPath = $env:Path Synchronize-SystemPathEnvPath $env:Path = Update-ChocolateyEnv -ErrorAction SilentlyContinue ##### END Variable/Parameter Transforms and PreRun Prep ##### ##### BEGIN Main Body ##### $CheckLatestVersion = $( $PackageManagementCurrentInstalledPackage.Version -ne $PackageManagementLatestVersion.Version -or $ChocolateyOutdatedProgramsPSObjects.ProgramName -contains $ProgramName ) $CheckPreviousVersion = $( $PackageManagementCurrentInstalledPackage.Version -ne $PackageManagementPreviousVersion.Version -or $ChocoPreviousVersion -ne $ChocolateyInstalledProgramObjects.Version ) if (!$GetPreviousVersion) { $VersionCheck = $CheckPreviousVersion $PackageManagementRequiredVersion = $PackageManagementLatestVersion.Version $ChocoRequiredVersion = $ChocoLatestVersion } else { $VersionCheck = $CheckLatestVersion $PackageManagementRequiredVersion = $PackageManagementPreviousVersion.Version $ChocoRequiredVersion = $ChocoPreviousVersion } # Install $ProgramName if it's not already or if it's outdated... if ($($PackageManagementInstalledPrograms.Name -notcontains $ProgramName -and $ChocolateyInstalledProgramsPSObjects.ProgramName -notcontains $ProgramName) -or $VersionCheck ) { if ($UsePowerShellGet -or $(!$UsePowerShellGet -and !$UseChocolateyCmdLine) -or $PackageManagementInstalledPrograms.Name -contains $ProgramName -and $ChocolateyInstalledProgramsPSObjects.ProgramName -notcontains $ProgramName ) { $InstallPackageSplatParams = @{ Name = $ProgramName Force = $True ErrorAction = "SilentlyContinue" ErrorVariable = "InstallError" WarningAction = "SilentlyContinue" } if ([bool]$PackageManagementRequiredVersion) { $InstallPackageSplatParams.Add("RequiredVersion",$PackageManagementRequiredVersion) } if ($PreRelease) { $LatestVersion = $(Find-Package $ProgramName -AllVersions)[-1].Version $InstallPackageSplatParams.Add("MinimumVersion",$LatestVersion) } # NOTE: The PackageManagement install of $ProgramName is unreliable, so just in case, fallback to the Chocolatey cmdline for install $null = Install-Package @InstallPackageSplatParams if ($InstallError.Count -gt 0) { $null = Uninstall-Package $ProgramName -Force -ErrorAction SilentlyContinue Write-Warning "There was a problem installing $ProgramName via PackageManagement/PowerShellGet!" if ($UsePowerShellGet) { Write-Error "One or more errors occurred during the installation of $ProgramName via the the PackageManagement/PowerShellGet Modules failed! Installation has been rolled back! Halting!" Write-Host "Errors for the Install-Package cmdlet are as follows:" Write-Error $($InstallError | Out-String) $global:FunctionResult = "1" return } else { Write-Host "Trying install via Chocolatey CmdLine..." $PMInstall = $False } } else { $PMInstall = $True # Since Installation via PackageManagement/PowerShellGet was succesful, let's update $env:Path with the # latest from System PATH before we go nuts trying to find the main executable manually Synchronize-SystemPathEnvPath $env:Path = $($(Update-ChocolateyEnv -ErrorAction SilentlyContinue) -split ";" | foreach { if (-not [System.String]::IsNullOrWhiteSpace($_) -and $(Test-Path $_)) {$_} }) -join ";" } } if (!$PMInstall -or $UseChocolateyCmdLine -or $ChocolateyInstalledProgramsPSObjects.ProgramName -contains $ProgramName ) { try { Write-Host "Refreshing `$env:Path..." $global:FunctionResult = "0" $env:Path = Update-ChocolateyEnv -ErrorAction SilentlyContinue -ErrorVariable RCEErr # The first time we attempt to Update-ChocolateyEnv, Chocolatey CmdLine and/or the # Chocolatey Package Provider legitimately might not be installed, # so if the Update-ChocolateyEnv function throws that error, we can ignore it if ($RCEErr.Count -gt 0 -and $global:FunctionResult -eq "1" -and ![bool]$($RCEErr -match "Neither the Chocolatey PackageProvider nor the Chocolatey CmdLine appears to be installed!")) { throw "The Update-ChocolateyEnv function failed! Halting!" } } catch { Write-Error $_ Write-Host "Errors from the Update-ChocolateyEnv function are as follows:" Write-Error $($RCEErr | Out-String) $global:FunctionResult = "1" return } # Make sure Chocolatey CmdLine is installed...if not, install it if (![bool]$(Get-Command choco -ErrorAction SilentlyContinue)) { try { $global:FunctionResult = "0" $null = Install-ChocolateyCmdLine -ErrorAction SilentlyContinue -ErrorVariable ICCErr -WarningAction SilentlyContinue if ($ICCErr -and $global:FunctionResult -eq "1") {throw "The Install-ChocolateyCmdLine function failed! Halting!"} } catch { Write-Error $_ Write-Host "Errors from the Install-ChocolateyCmdline function are as follows:" Write-Error $($ICCErr | Out-String) $global:FunctionResult = "1" return } } try { # TODO: Figure out how to handle errors from choco.exe. Some we can ignore, others # we shouldn't. But I'm not sure what all of the possibilities are so I can't # control for them... if ($PreRelease) { $Arguments = "$ProgramName --pre -y" } elseif ([bool]$ChocoRequiredVersion) { $Arguments = "$ProgramName -y --version $ChocoRequiredVersion" } else { $Arguments = "$ProgramName -y" } $ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo #$ProcessInfo.WorkingDirectory = $BinaryPath | Split-Path -Parent $ProcessInfo.FileName = $(Get-Command cup).Source $ProcessInfo.RedirectStandardError = $true $ProcessInfo.RedirectStandardOutput = $true $ProcessInfo.UseShellExecute = $false $ProcessInfo.Arguments = $Arguments $Process = New-Object System.Diagnostics.Process $Process.StartInfo = $ProcessInfo $Process.Start() | Out-Null # Below $FinishedInAlottedTime returns boolean true/false # Give it 60 seconds to finish installing, otherwise, kill choco.exe $FinishedInAlottedTime = $Process.WaitForExit(60000) if (!$FinishedInAlottedTime) { $Process.Kill() } $stdout = $Process.StandardOutput.ReadToEnd() $stderr = $Process.StandardError.ReadToEnd() $AllOutput = $stdout + $stderr if ($(clist --local-only $ProgramName)[1] -notmatch $ProgramName) { Write-Error "There was a problem installing the program '$ProgramName' via 'cup $Arguments'! Halting!" $global:FunctionResult = "1" return } $ChocoInstall = $true # Since Installation via the Chocolatey CmdLine was succesful, let's update $env:Path with the # latest from System PATH before we go nuts trying to find the main executable manually Synchronize-SystemPathEnvPath $env:Path = Update-ChocolateyEnv -ErrorAction SilentlyContinue } catch { Write-Error "There was a problem installing $ProgramName using the Chocolatey cmdline! Halting!" $global:FunctionResult = "1" return } } if ($ResolveCommandPath -or $PSBoundParameters['CommandName']) { ## BEGIN Try to Find Main Executable Post Install ## # Now the parent directory of $ProgramName's main executable should be part of the SYSTEM Path # (and therefore part of $env:Path). If not, try to find it in Chocolatey directories... if ($(Get-Command $FinalCommandName -ErrorAction SilentlyContinue).CommandType -eq "Alias") { while (Test-Path Alias:\$FinalCommandName) { Remove-Item Alias:\$FinalCommandName } } if (![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue)) { try { Write-Host "Refreshing `$env:Path..." $global:FunctionResult = "0" $env:Path = Update-ChocolateyEnv -ErrorAction SilentlyContinue -ErrorVariable RCEErr if ($RCEErr.Count -gt 0 -and $global:FunctionResult -eq "1") {throw "The Update-ChocolateyEnv function failed! Halting!"} } catch { Write-Error $_ Write-Host "Errors from the Update-ChocolateyEnv function are as follows:" Write-Error $($RCEErr | Out-String) $global:FunctionResult = "1" return } } # If we still can't find the main executable... if (![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue) -and $(!$ExePath -or $ExePath.Count -eq 0)) { $env:Path = Update-ChocolateyEnv -ErrorAction SilentlyContinue if ($ExpectedInstallLocation) { [System.Collections.ArrayList][Array]$ExePath = Adjudicate-ExePath -ProgramName $ProgramName -OriginalSystemPath $OriginalSystemPath -OriginalEnvPath $OriginalEnvPath -FinalCommandName $FinalCommandName -ExpectedInstallLocation $ExpectedInstallLocation } else { [System.Collections.ArrayList][Array]$ExePath = Adjudicate-ExePath -ProgramName $ProgramName -OriginalSystemPath $OriginalSystemPath -OriginalEnvPath $OriginalEnvPath -FinalCommandName $FinalCommandName } } # Determine if there's an exact match for the $FinalCommandName if (![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue)) { if ($ExePath.Count -ge 1) { if ([bool]$($ExePath -match "\\$FinalCommandName.exe$")) { $FoundExactCommandMatch = $True } } } # If we STILL can't find the main executable... if ($(![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue) -and $(!$ExePath -or $ExePath.Count -eq 0)) -or $(!$FoundExactCommandMatch -and $PSBoundParameters['CommandName']) -or $($ResolveCommandPath -and !$FoundExactCommandMatch) -or $ForceChocoInstallScript) { # If, at this point we don't have $ExePath, if we did a $ChocoInstall, then we have to give up... # ...but if we did a $PMInstall, then it's possible that PackageManagement/PowerShellGet just # didn't run the chocolateyInstall.ps1 script that sometimes comes bundled with Packages from the # Chocolatey Package Provider/Repo. So try running that... if ($ChocoInstall) { if (![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue)) { #Write-Warning "Unable to find main executable for $ProgramName!" $MainExeSearchFail = $True } } if ($PMInstall -or $ForceChocoInstallScript) { [System.Collections.ArrayList]$PossibleChocolateyInstallScripts = @() if (Test-Path "C:\Chocolatey") { $ChocoScriptsA = Get-ChildItem -Path "C:\Chocolatey" -Recurse -File -Filter "*chocolateyinstall.ps1" | Where-Object {$($(Get-Date) - $_.CreationTime).TotalMinutes -lt 5} foreach ($Script in $ChocoScriptsA) { $null = $PossibleChocolateyInstallScripts.Add($Script) } } if (Test-Path "C:\ProgramData\chocolatey") { $ChocoScriptsB = Get-ChildItem -Path "C:\ProgramData\chocolatey" -Recurse -File -Filter "*chocolateyinstall.ps1" | Where-Object {$($(Get-Date) - $_.CreationTime).TotalMinutes -lt 5} foreach ($Script in $ChocoScriptsB) { $null = $PossibleChocolateyInstallScripts.Add($Script) } } [System.Collections.ArrayList][Array]$ChocolateyInstallScriptSearch = $PossibleChocolateyInstallScripts.FullName | Where-Object {$_ -match ".*?$ProgramName.*?chocolateyinstall.ps1$"} if ($ChocolateyInstallScriptSearch.Count -eq 0) { Write-Warning "Unable to find main the Chocolatey Install Script for $ProgramName PowerShellGet install!" $MainExeSearchFail = $True } if ($ChocolateyInstallScriptSearch.Count -eq 1) { $ChocolateyInstallScript = $ChocolateyInstallScriptSearch[0] } if ($ChocolateyInstallScriptSearch.Count -gt 1) { $ChocolateyInstallScript = $($ChocolateyInstallScriptSearch | Sort-Object LastWriteTime)[-1] } if ($ChocolateyInstallScript) { try { Write-Host "Trying the Chocolatey Install script from $ChocolateyInstallScript..." -ForegroundColor Yellow # Make sure Chocolatey Modules / helper scripts are loaded if (Test-Path "C:\ProgramData\chocolatey") { $ChocoPath = "C:\ProgramData\chocolatey" } elseif (Test-Path "C:\Chocolatey") { $ChocoPath = "C:\Chocolatey" } $ChocoInstallerModuleFileItem = Get-ChildItem -Path $ChocoPath -Recurse -File | Where-Object {$_.FullName -match $ProgramName -and $_.FullName -match "chocolateyinstaller\.psm1"} $ChocoProfileModuleFileItem = Get-ChildItem -Path $ChocoPath -Recurse -File | Where-Object {$_.FullName -match $ProgramName -and $_.FullName -match "chocolateyProfile\.psm1"} $ChocoScriptRunnerFileItem = Get-ChildItem -Path $ChocoPath -Recurse -File | Where-Object {$_.FullName -match $ProgramName -and $_.FullName -match "chocolateyScriptRunner\.ps1"} $ChocoTabExpansionFileItem = Get-ChildItem -Path $ChocoPath -Recurse -File | Where-Object {$_.FullName -match $ProgramName -and $_.FullName -match "chocolateyTabExpansion\.ps1"} if ($ChocoInstallerModuleFileItem) { Import-Module $ChocoInstallerModuleFileItem.FullName -ErrorAction SilentlyContinue $ChocoHelpersDir = $ChocoInstallerModuleFileItem.Directory } elseif ($ChocoProfileModuleFileItem) { Import-Module $ChocoProfileModuleFileItem.FullName -ErrorAction SilentlyContinue $ChocoHelpersDir = $ChocoProfileModuleFileItem.Directory } elseif ($ChocoScriptRunnerFileItem) { $ChocoHelpersDir = $ChocoScriptRunnerFileItem.Directory } elseif ($ChocoTabExpansionFileItem) { $ChocoHelpersDir = $ChocoTabExpansionFileItem.Directory } # Run the install script $tempfile = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()) $null = & $ChocolateyInstallScript *>&1 | Out-File $tempfile if (Test-Path $tempfile) {Remove-Item $tempfile -Force} # Now that the $ChocolateyInstallScript ran, search for the main executable again Synchronize-SystemPathEnvPath $env:Path = Update-ChocolateyEnv -ErrorAction SilentlyContinue if ($ExpectedInstallLocation) { [System.Collections.ArrayList][Array]$ExePath = Adjudicate-ExePath -ProgramName $ProgramName -OriginalSystemPath $OriginalSystemPath -OriginalEnvPath $OriginalEnvPath -FinalCommandName $FinalCommandName -ExpectedInstallLocation $ExpectedInstallLocation } else { [System.Collections.ArrayList][Array]$ExePath = Adjudicate-ExePath -ProgramName $ProgramName -OriginalSystemPath $OriginalSystemPath -OriginalEnvPath $OriginalEnvPath -FinalCommandName $FinalCommandName } # If we STILL don't have $ExePath, then we have to give up... if (!$ExePath -or $ExePath.Count -eq 0) { #Write-Warning "Unable to find main executable for $ProgramName!" $MainExeSearchFail = $True } } catch { Write-Error $_ Write-Error "The Chocolatey Install Script $ChocolateyInstallScript has failed!" # If PackageManagement/PowerShellGet is ERRONEOUSLY reporting that the program was installed # use the Uninstall-Package cmdlet to wipe it out. This scenario happens when PackageManagement/ # PackageManagement/PowerShellGet gets a Package from the Chocolatey Package Provider/Repo but # fails to run the chocolateyInstall.ps1 script for some reason. if ([bool]$(Get-Package $ProgramName -ErrorAction SilentlyContinue)) { $null = Uninstall-Package $ProgramName -Force -ErrorAction SilentlyContinue } if (!$UsePowerShellGet -and !$ForceChocoInstallScript) { Remove-Module chocolateyinstaller -ErrorAction SilentlyContinue Remove-Module chocolateyProfile -ErrorAction SilentlyContinue # Now we need to try the Chocolatey CmdLine. Easiest way to do this at this point is to just # invoke the function again with the same parameters, but specify -UseChocolateyCmdLine $BoundParametersDictionary = $PSCmdlet.MyInvocation.BoundParameters $InstallProgramSplatParams = @{} foreach ($kvpair in $BoundParametersDictionary.GetEnumerator()) { $key = $kvpair.Key $value = $BoundParametersDictionary[$key] if ($key -notmatch "UsePowerShellGet|ForceChocoInstallScript" -and $InstallProgramSplatParams.Keys -notcontains $key) { $InstallProgramSplatParams.Add($key,$value) } } if ($InstallProgramSplatParams.Keys -notcontains "UseChocolateyCmdLine") { $InstallProgramSplatParams.Add("UseChocolateyCmdLine",$True) } if ($InstallProgramSplatParams.Keys -notcontains "NoUpdatePackageManagement") { $InstallProgramSplatParams.Add("NoUpdatePackageManagement",$True) } $PMInstall = $False Install-Program @InstallProgramSplatParams return } else { $global:FunctionResult = "1" return } } } } } ## END Try to Find Main Executable Post Install ## } } else { if ($ChocolateyInstalledProgramsPSObjects.ProgramName -contains $ProgramName) { Write-Warning "$ProgramName is already installed via the Chocolatey CmdLine!" $AlreadyInstalled = $True } elseif ([bool]$(Get-Package $ProgramName -ErrorAction SilentlyContinue)) { Write-Warning "$ProgramName is already installed via PackageManagement/PowerShellGet!" $AlreadyInstalled = $True } } # If we weren't able to find the main executable (or any potential main executables) for # $ProgramName, offer the option to scan the whole C:\ drive (with some obvious exceptions) if ($MainExeSearchFail -and $($ResolveCommandPath -or $PSBoundParameters['CommandName'] -or $PSBoundParameters['ScanCDriveForMainExeIfNecessary']) -and ![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue) ) { if (!$ScanCDriveForMainExeIfNecessary -and !$ResolveCommandPath -and !$PSBoundParameters['CommandName']) { $ScanCDriveChoice = Read-Host -Prompt "Would you like to scan C:\ for $FinalCommandName.exe? NOTE: This search excludes system directories but still could take some time. [Yes\No]" while ($ScanCDriveChoice -notmatch "Yes|yes|Y|y|No|no|N|n") { Write-Host "$ScanDriveChoice is not a valid input. Please enter 'Yes' or 'No'" $ScanCDriveChoice = Read-Host -Prompt "Would you like to scan C:\ for $FinalCommandName.exe? NOTE: This search excludes system directories but still could take some time. [Yes\No]" } } if ($ScanCDriveChoice -match "Yes|yes|Y|y" -or $ScanCDriveForMainExeIfNecessary -or $ResolveCommandPath -or $PSBoundParameters['CommandName']) { $DirectoriesToSearchRecursively = $(Get-ChildItem -Path "C:\" -Directory | Where-Object {$_.Name -notmatch "Windows|PerfLogs|Microsoft"}).FullName [System.Collections.ArrayList]$ExePath = @() # Try to find a directory that matches the $ProgramName [System.Collections.ArrayList]$FoundMatchingDirs = @() foreach ($dir in $DirectoriesToSearchRecursively) { $DirectoriesIndex = Get-ChildItem -Path $dir -Recurse -Directory foreach ($subdirItem in $DirectoriesIndex) { if ($subdirItem.FullName -match $ProgramName) { $null = $FoundMatchingDirs.Add($subdiritem) } } } foreach ($MatchingDirItem in $FoundMatchingDirs) { $FilesIndex = Get-ChildItem -Path $MatchingDirItem.FullName -Recurse -File foreach ($FilePath in $FilesIndex.Fullname) { if ($FilePath -match "(.*?)$FinalCommandName([^\\]+)") { $null = $ExePath.Add($FilePath) } } } } } if ($ResolveCommandPath -or $PSBoundParameters['CommandName']) { # Finalize $env:Path if ([bool]$($ExePath -match "\\$FinalCommandName.exe$")) { $PathToAdd = $($ExePath -match "\\$FinalCommandName.exe$") | Split-Path -Parent $env:Path = $PathToAdd + ";" + $env:Path } $FinalEnvPathArray = $env:Path -split ";" | foreach {if(-not [System.String]::IsNullOrWhiteSpace($_)) {$_}} $FinalEnvPathString = $($FinalEnvPathArray | foreach {if (Test-Path $_) {$_}}) -join ";" $env:Path = $FinalEnvPathString if (![bool]$(Get-Command $FinalCommandName -ErrorAction SilentlyContinue)) { # Try to determine Main Executable if (!$ExePath -or $ExePath.Count -eq 0) { Write-Warning "Unable to find main executable for $ProgramName!" $FinalExeLocation = "NotFound" } elseif ($ExePath.Count -eq 1) { $UpdatedFinalCommandName = $ExePath | Split-Path -Leaf try { $FinalExeLocation = $(Get-Command $UpdatedFinalCommandName -ErrorAction SilentlyContinue).Source } catch { $FinalExeLocation = $ExePath | Where-Object {$($_ | Split-Path -Leaf) -match "\.exe$"} } } elseif ($ExePath.Count -gt 1) { if (![bool]$($ExePath -match "\\$FinalCommandName.exe$")) { Write-Warning "No exact match for main executable $FinalCommandName.exe was found. However, other executables associated with $ProgramName were found." } $FinalExeLocation = $ExePath | Where-Object {$($_ | Split-Path -Leaf) -match "\.exe$"} } } else { $FinalExeLocation = $(Get-Command $FinalCommandName).Source } } if ($ChocoInstall) { $InstallManager = "choco.exe" $InstallCheck = $(clist --local-only $ProgramName)[1] } if ($PMInstall -or [bool]$(Get-Package $ProgramName -ProviderName Chocolatey -ErrorAction SilentlyContinue)) { $InstallManager = "PowerShellGet" $InstallCheck = Get-Package $ProgramName -ErrorAction SilentlyContinue } if ($AlreadyInstalled) { $InstallAction = "AlreadyInstalled" } elseif ($PackageManagementCurrentInstalledPackage.Version -ne $PackageManagementLatestVersion.Version -or $ChocolateyOutdatedProgramsPSObjects.ProgramName -contains $ProgramName ) { $InstallAction = "Updated" } else { $InstallAction = "FreshInstall" } $env:Path = Update-ChocolateyEnv 1..3 | foreach {Pop-Location} Write-Host "The program '$ProgramName' was installed successfully!" -ForegroundColor Green $OutputHT = [ordered]@{ InstallManager = $InstallManager InstallAction = $InstallAction InstallCheck = $InstallCheck } if ([array]$($FinalExeLocation).Count -gt 1) { $OutputHT.Add("PossibleMainExecutables",$FinalExeLocation) } else { $OutputHT.Add("MainExecutable",$FinalExeLocation) } $OutputHT.Add("OriginalSystemPath",$OriginalSystemPath) $OutputHT.Add("CurrentSystemPath",$(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path) $OutputHT.Add("OriginalEnvPath",$OriginalEnvPath) $OutputHT.Add("CurrentEnvPath",$env:Path) [pscustomobject]$OutputHT ##### END Main Body ##### } # SIG # Begin signature block # MIIMiAYJKoZIhvcNAQcCoIIMeTCCDHUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU0iIYuBh/t+Jz114mfRTgWsFL # 3Mygggn9MIIEJjCCAw6gAwIBAgITawAAAB/Nnq77QGja+wAAAAAAHzANBgkqhkiG # 9w0BAQsFADAwMQwwCgYDVQQGEwNMQUIxDTALBgNVBAoTBFpFUk8xETAPBgNVBAMT # CFplcm9EQzAxMB4XDTE3MDkyMDIxMDM1OFoXDTE5MDkyMDIxMTM1OFowPTETMBEG # CgmSJomT8ixkARkWA0xBQjEUMBIGCgmSJomT8ixkARkWBFpFUk8xEDAOBgNVBAMT # B1plcm9TQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCwqv+ROc1 # bpJmKx+8rPUUfT3kPSUYeDxY8GXU2RrWcL5TSZ6AVJsvNpj+7d94OEmPZate7h4d # gJnhCSyh2/3v0BHBdgPzLcveLpxPiSWpTnqSWlLUW2NMFRRojZRscdA+e+9QotOB # aZmnLDrlePQe5W7S1CxbVu+W0H5/ukte5h6gsKa0ktNJ6X9nOPiGBMn1LcZV/Ksl # lUyuTc7KKYydYjbSSv2rQ4qmZCQHqxyNWVub1IiEP7ClqCYqeCdsTtfw4Y3WKxDI # JaPmWzlHNs0nkEjvnAJhsRdLFbvY5C2KJIenxR0gA79U8Xd6+cZanrBUNbUC8GCN # wYkYp4A4Jx+9AgMBAAGjggEqMIIBJjASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsG # AQQBgjcVAgQWBBQ/0jsn2LS8aZiDw0omqt9+KWpj3DAdBgNVHQ4EFgQUicLX4r2C # Kn0Zf5NYut8n7bkyhf4wGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDgYDVR0P # AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUdpW6phL2RQNF # 7AZBgQV4tgr7OE0wMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL3BraS9jZXJ0ZGF0 # YS9aZXJvREMwMS5jcmwwPAYIKwYBBQUHAQEEMDAuMCwGCCsGAQUFBzAChiBodHRw # Oi8vcGtpL2NlcnRkYXRhL1plcm9EQzAxLmNydDANBgkqhkiG9w0BAQsFAAOCAQEA # tyX7aHk8vUM2WTQKINtrHKJJi29HaxhPaHrNZ0c32H70YZoFFaryM0GMowEaDbj0 # a3ShBuQWfW7bD7Z4DmNc5Q6cp7JeDKSZHwe5JWFGrl7DlSFSab/+a0GQgtG05dXW # YVQsrwgfTDRXkmpLQxvSxAbxKiGrnuS+kaYmzRVDYWSZHwHFNgxeZ/La9/8FdCir # MXdJEAGzG+9TwO9JvJSyoGTzu7n93IQp6QteRlaYVemd5/fYqBhtskk1zDiv9edk # mHHpRWf9Xo94ZPEy7BqmDuixm4LdmmzIcFWqGGMo51hvzz0EaE8K5HuNvNaUB/hq # MTOIB5145K8bFOoKHO4LkTCCBc8wggS3oAMCAQICE1gAAAH5oOvjAv3166MAAQAA # AfkwDQYJKoZIhvcNAQELBQAwPTETMBEGCgmSJomT8ixkARkWA0xBQjEUMBIGCgmS # JomT8ixkARkWBFpFUk8xEDAOBgNVBAMTB1plcm9TQ0EwHhcNMTcwOTIwMjE0MTIy # WhcNMTkwOTIwMjExMzU4WjBpMQswCQYDVQQGEwJVUzELMAkGA1UECBMCUEExFTAT # BgNVBAcTDFBoaWxhZGVscGhpYTEVMBMGA1UEChMMRGlNYWdnaW8gSW5jMQswCQYD # VQQLEwJJVDESMBAGA1UEAxMJWmVyb0NvZGUyMIIBIjANBgkqhkiG9w0BAQEFAAOC # AQ8AMIIBCgKCAQEAxX0+4yas6xfiaNVVVZJB2aRK+gS3iEMLx8wMF3kLJYLJyR+l # rcGF/x3gMxcvkKJQouLuChjh2+i7Ra1aO37ch3X3KDMZIoWrSzbbvqdBlwax7Gsm # BdLH9HZimSMCVgux0IfkClvnOlrc7Wpv1jqgvseRku5YKnNm1JD+91JDp/hBWRxR # 3Qg2OR667FJd1Q/5FWwAdrzoQbFUuvAyeVl7TNW0n1XUHRgq9+ZYawb+fxl1ruTj # 3MoktaLVzFKWqeHPKvgUTTnXvEbLh9RzX1eApZfTJmnUjBcl1tCQbSzLYkfJlJO6 # eRUHZwojUK+TkidfklU2SpgvyJm2DhCtssFWiQIDAQABo4ICmjCCApYwDgYDVR0P # AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBS5d2bhatXq # eUDFo9KltQWHthbPKzAfBgNVHSMEGDAWgBSJwtfivYIqfRl/k1i63yftuTKF/jCB # 6QYDVR0fBIHhMIHeMIHboIHYoIHVhoGubGRhcDovLy9DTj1aZXJvU0NBKDEpLENO # PVplcm9TQ0EsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNl # cnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9emVybyxEQz1sYWI/Y2VydGlmaWNh # dGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlv # blBvaW50hiJodHRwOi8vcGtpL2NlcnRkYXRhL1plcm9TQ0EoMSkuY3JsMIHmBggr # BgEFBQcBAQSB2TCB1jCBowYIKwYBBQUHMAKGgZZsZGFwOi8vL0NOPVplcm9TQ0Es # Q049QUlBLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENO # PUNvbmZpZ3VyYXRpb24sREM9emVybyxEQz1sYWI/Y0FDZXJ0aWZpY2F0ZT9iYXNl # P29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwLgYIKwYBBQUHMAKG # Imh0dHA6Ly9wa2kvY2VydGRhdGEvWmVyb1NDQSgxKS5jcnQwPQYJKwYBBAGCNxUH # BDAwLgYmKwYBBAGCNxUIg7j0P4Sb8nmD8Y84g7C3MobRzXiBJ6HzzB+P2VUCAWQC # AQUwGwYJKwYBBAGCNxUKBA4wDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOC # AQEAszRRF+YTPhd9UbkJZy/pZQIqTjpXLpbhxWzs1ECTwtIbJPiI4dhAVAjrzkGj # DyXYWmpnNsyk19qE82AX75G9FLESfHbtesUXnrhbnsov4/D/qmXk/1KD9CE0lQHF # Lu2DvOsdf2mp2pjdeBgKMRuy4cZ0VCc/myO7uy7dq0CvVdXRsQC6Fqtr7yob9NbE # OdUYDBAGrt5ZAkw5YeL8H9E3JLGXtE7ir3ksT6Ki1mont2epJfHkO5JkmOI6XVtg # anuOGbo62885BOiXLu5+H2Fg+8ueTP40zFhfLh3e3Kj6Lm/NdovqqTBAsk04tFW9 # Hp4gWfVc0gTDwok3rHOrfIY35TGCAfUwggHxAgEBMFQwPTETMBEGCgmSJomT8ixk # ARkWA0xBQjEUMBIGCgmSJomT8ixkARkWBFpFUk8xEDAOBgNVBAMTB1plcm9TQ0EC # E1gAAAH5oOvjAv3166MAAQAAAfkwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwx # CjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGC # NwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFMkeZRH3P1Zse5dr # 5hgmuKB/23YSMA0GCSqGSIb3DQEBAQUABIIBABxom1aMpq4BRi+/F4RpTx78LMdi # jRG3itjFOeveIYcluSyBm927rjTGaGaZR0IOFPvtGUO90GBqMsQKVyvPWPxOXFBr # t6D2qA9lJrWD7wRne+ZyZAHaVrIXmu+nLbq8Hg3uDOg1DlpJ9kNQSBF6rVZnUcjk # 6OnDI0ar6D/NeczdHwpEoqecmpbIUdF7ePi8g5vohlOrWnjDExzDORWY7zZExZ3N # vhNJEnOiRPP5JmHkjtY7PEB3P4sCrlRV+NJGN26TGX5gwza8LrsUTiW+tP1h7cU9 # NSSalh96/0SfJCzkOwjqcSFEnD55NkpegoX7XR6QZyY/aHTn5Vh6glhmRIs= # SIG # End signature block |