Sometimes when an update has an issue it can be useful to downgrade, this is what this change allows for (especially when using Chocolatey for package management). The [official Powershell documentation](https://docs.microsoft.com/en-us/powershell/module/appx/add-appxpackage?view=win10-ps) was not very verbose about this flag - https://github.com/MicrosoftDocs/windows-powershell-docs/issues/1398 So I'm assuming the flag has a similar behaviour as the one described here: https://docs.microsoft.com/en-us/uwp/schemas/appinstallerschema/element-force-update-from-any-version
644 lines
25 KiB
PowerShell
644 lines
25 KiB
PowerShell
#
|
|
# Install.ps1 is a PowerShell script designed to sideload the Fluent Terminal
|
|
# app. To run this script from Explorer, right-click on its icon and choose
|
|
# "Run with PowerShell".
|
|
#
|
|
# It is heavily based on the Add-AppxDevPackage.ps1 script generated by
|
|
# Visual Studio. Visual Studio supplies this script in the folder generated
|
|
# with its "Prepare Package" command. The same folder will also contain the
|
|
# app package (a .appx file), the signing certificate (a .cer file), and a
|
|
# "Dependencies" subfolder containing all the framework packages used by the
|
|
# app.
|
|
#
|
|
# This script simplifies installing these packages by automating the
|
|
# following functions:
|
|
# 1. Find the app package and signing certificate in the script directory
|
|
# 2. Prompt the user to enable sideloading apps and to install the
|
|
# certificate if necessary
|
|
# 3. Find dependency packages that are applicable to the operating system's
|
|
# CPU architecture
|
|
# 4. Install the package along with all applicable dependencies
|
|
#
|
|
# All command line parameters are reserved for use internally by the script.
|
|
# Users should launch this script from Explorer.
|
|
#
|
|
|
|
# .Link
|
|
# http://go.microsoft.com/fwlink/?LinkId=243053
|
|
|
|
param(
|
|
[switch]$Force = $false,
|
|
[switch]$ForceContextMenu = $false,
|
|
[switch]$GetDeveloperLicense = $false,
|
|
[string]$CertificatePath = $null
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# UI Strings
|
|
class UiStrings
|
|
{
|
|
static [string] $PromptYesString = "&Yes"
|
|
static [string] $PromptNoString = "&No"
|
|
static [string] $BundleFound = "Found bundle: {0}"
|
|
static [string] $PackageFound = "Found package: {0}"
|
|
static [string] $CertificateFound = "Found certificate: {0}"
|
|
static [string] $DependenciesFound = "Found dependency package(s):"
|
|
static [string] $GettingDeveloperLicense = "Please select `"Sideload apps`" or `"Developer mode`" in the following window."
|
|
static [string] $InstallingCertificate = "Installing certificate..."
|
|
static [string] $InstallingPackage = "`nInstalling app..."
|
|
static [string] $AcquireLicenseSuccessful = "Sideloading was successfully enabled."
|
|
static [string] $InstallCertificateSuccessful = "The certificate was successfully installed."
|
|
static [string] $PromptContextMenuInstall = "Fluent Terminal can be integrated into the Windows Explorer context menu, allowing you to quickly open the terminal to a directory.`n`nDo you want to enable this integration?`n`n"
|
|
static [string] $Success = "`nSuccess: Your app was successfully installed."
|
|
static [string] $WarningInstallCert = "`nYou are about to install a digital certificate to your computer's Trusted People certificate store. Doing so carries serious security risk and should only be done if you trust the originator of this digital certificate.`n`nWhen you are done using this app, you should manually remove the associated digital certificate. Instructions for doing so can be found here: http://go.microsoft.com/fwlink/?LinkId=243053`n`nAre you sure you wish to continue?`n`n"
|
|
static [string] $ElevateActions = "`nBefore installing this app, you need to do the following:"
|
|
static [string] $ElevateActionDevLicense = "`t- Enable sideloading apps"
|
|
static [string] $ElevateActionCertificate = "`t- Install the signing certificate"
|
|
static [string] $ElevateActionsContinue = "Administrator credentials are required to continue. Please accept the UAC prompt and provide your administrator password if asked."
|
|
static [string] $ErrorForceElevate = "You must provide administrator credentials to proceed. Please run this script without the -Force parameter or from an elevated PowerShell window."
|
|
static [string] $ErrorForceDeveloperLicense = "Enabling sideloading requires user interaction. Please rerun the script without the -Force parameter."
|
|
static [string] $ErrorLaunchAdminFailed = "Error: Could not start a new process as administrator."
|
|
static [string] $ErrorNoScriptPath = "Error: You must launch this script from a file."
|
|
static [string] $ErrorNoPackageFound = "Error: No package or bundle found in the script directory. Please make sure the package or bundle you want to install is placed in the same directory as this script."
|
|
static [string] $ErrorManyPackagesFound = "Error: More than one package or bundle found in the script directory. Please make sure only the package or bundle you want to install is placed in the same directory as this script."
|
|
static [string] $ErrorPackageUnsigned = "Error: The package or bundle is not digitally signed or its signature is corrupted."
|
|
static [string] $ErrorNoCertificateFound = "Error: No certificate found in the script directory. Please make sure the certificate used to sign the package or bundle you are installing is placed in the same directory as this script."
|
|
static [string] $ErrorManyCertificatesFound = "Error: More than one certificate found in the script directory. Please make sure only the certificate used to sign the package or bundle you are installing is placed in the same directory as this script."
|
|
static [string] $ErrorBadCertificate = "Error: The file `"{0}`" is not a valid digital certificate. CertUtil returned with error code {1}."
|
|
static [string] $ErrorExpiredCertificate = "Error: The developer certificate `"{0}`" has expired. One possible cause is the system clock isn't set to the correct date and time. If the system settings are correct, contact the app owner to re-create a package or bundle with a valid certificate."
|
|
static [string] $ErrorCertificateMismatch = "Error: The certificate does not match the one used to sign the package or bundle."
|
|
static [string] $ErrorCertIsCA = "Error: The certificate can't be a certificate authority."
|
|
static [string] $ErrorBannedKeyUsage = "Error: The certificate can't have the following key usage: {0}. Key usage must be unspecified or equal to `"DigitalSignature`"."
|
|
static [string] $ErrorBannedEKU = "Error: The certificate can't have the following extended key usage: {0}. Only the Code Signing and Lifetime Signing EKUs are allowed."
|
|
static [string] $ErrorNoBasicConstraints = "Error: The certificate is missing the basic constraints extension."
|
|
static [string] $ErrorNoCodeSigningEku = "Error: The certificate is missing the extended key usage for Code Signing."
|
|
static [string] $ErrorInstallCertificateCancelled = "Error: Installation of the certificate was cancelled."
|
|
static [string] $ErrorCertUtilInstallFailed = "Error: Could not install the certificate. CertUtil returned with error code {0}."
|
|
static [string] $ErrorGetDeveloperLicenseFailed = "Error: Sideloading is not enabled."
|
|
static [string] $ErrorInstallCertificateFailed = "Error: Could not install the certificate. Status: {0}. For more information, see http://go.microsoft.com/fwlink/?LinkID=252740."
|
|
static [string] $ErrorAddPackageFailed = "Error: Could not install the app."
|
|
static [string] $ErrorAddPackageFailedWithCert = "Error: Could not install the app. To ensure security, please consider uninstalling the signing certificate until you can install the app. Instructions for doing so can be found here:`nhttp://go.microsoft.com/fwlink/?LinkId=243053}"
|
|
}
|
|
|
|
$ScriptPath = $null
|
|
try
|
|
{
|
|
$ScriptPath = (Get-Variable MyInvocation).Value.MyCommand.Path
|
|
$ScriptDir = Split-Path -Parent $ScriptPath
|
|
}
|
|
catch {}
|
|
|
|
if (!$ScriptPath)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorNoScriptPath) $ErrorCodes.NoScriptPath
|
|
}
|
|
|
|
$ErrorCodes = Data {
|
|
ConvertFrom-StringData @'
|
|
Success = 0
|
|
NoScriptPath = 1
|
|
NoPackageFound = 2
|
|
ManyPackagesFound = 3
|
|
NoCertificateFound = 4
|
|
ManyCertificatesFound = 5
|
|
BadCertificate = 6
|
|
PackageUnsigned = 7
|
|
CertificateMismatch = 8
|
|
ForceElevate = 9
|
|
LaunchAdminFailed = 10
|
|
GetDeveloperLicenseFailed = 11
|
|
InstallCertificateFailed = 12
|
|
AddPackageFailed = 13
|
|
ForceDeveloperLicense = 14
|
|
CertUtilInstallFailed = 17
|
|
CertIsCA = 18
|
|
BannedEKU = 19
|
|
NoBasicConstraints = 20
|
|
NoCodeSigningEku = 21
|
|
InstallCertificateCancelled = 22
|
|
BannedKeyUsage = 23
|
|
ExpiredCertificate = 24
|
|
'@
|
|
}
|
|
|
|
function PrintMessageAndExit($ErrorMessage, $ReturnCode)
|
|
{
|
|
Write-Host $ErrorMessage
|
|
if (!$Force)
|
|
{
|
|
Pause
|
|
}
|
|
exit $ReturnCode
|
|
}
|
|
|
|
#
|
|
# Warns the user about installing certificates, and presents a Yes/No prompt
|
|
# to confirm the action. The default is set to No.
|
|
#
|
|
function ConfirmCertificateInstall
|
|
{
|
|
$Answer = $host.UI.PromptForChoice(
|
|
"",
|
|
([UiStrings]::WarningInstallCert),
|
|
[System.Management.Automation.Host.ChoiceDescription[]]@(([UiStrings]::PromptYesString), ([UiStrings]::PromptNoString)),
|
|
1)
|
|
|
|
return $Answer -eq 0
|
|
}
|
|
|
|
#
|
|
# Asks the user whether they want to install context menu integration for Fluent Terminal,
|
|
# and presents a Yes/No prompt to confirm the action. The default is set to No.
|
|
#
|
|
function ConfirmContextMenuInstall
|
|
{
|
|
$Answer = $host.UI.PromptForChoice(
|
|
"",
|
|
([UiStrings]::PromptContextMenuInstall),
|
|
[System.Management.Automation.Host.ChoiceDescription[]]@(([UiStrings]::PromptYesString), ([UiStrings]::PromptNoString)),
|
|
1)
|
|
|
|
return $Answer -eq 0
|
|
}
|
|
|
|
#
|
|
# Validates whether a file is a valid certificate using CertUtil.
|
|
# This needs to be done before calling Get-PfxCertificate on the file, otherwise
|
|
# the user will get a cryptic "Password: " prompt for invalid certs.
|
|
#
|
|
function ValidateCertificateFormat($FilePath)
|
|
{
|
|
# certutil -verify prints a lot of text that we don't need, so it's redirected to $null here
|
|
certutil.exe -verify $FilePath > $null
|
|
if ($LastExitCode -lt 0)
|
|
{
|
|
PrintMessageAndExit (([UiStrings]::ErrorBadCertificate) -f $FilePath, $LastExitCode) $ErrorCodes.BadCertificate
|
|
}
|
|
|
|
# Check if certificate is expired
|
|
$cert = Get-PfxCertificate $FilePath
|
|
if (($cert.NotBefore -gt (Get-Date)) -or ($cert.NotAfter -lt (Get-Date)))
|
|
{
|
|
PrintMessageAndExit (([UiStrings]::ErrorExpiredCertificate) -f $FilePath) $ErrorCodes.ExpiredCertificate
|
|
}
|
|
}
|
|
|
|
#
|
|
# Verify that the developer certificate meets the following restrictions:
|
|
# - The certificate must contain a Basic Constraints extension, and its
|
|
# Certificate Authority (CA) property must be false.
|
|
# - The certificate's Key Usage extension must be either absent, or set to
|
|
# only DigitalSignature.
|
|
# - The certificate must contain an Extended Key Usage (EKU) extension with
|
|
# Code Signing usage.
|
|
# - The certificate must NOT contain any other EKU except Code Signing and
|
|
# Lifetime Signing.
|
|
#
|
|
# These restrictions are enforced to decrease security risks that arise from
|
|
# trusting digital certificates.
|
|
#
|
|
function CheckCertificateRestrictions
|
|
{
|
|
Set-Variable -Name BasicConstraintsExtensionOid -Value "2.5.29.19" -Option Constant
|
|
Set-Variable -Name KeyUsageExtensionOid -Value "2.5.29.15" -Option Constant
|
|
Set-Variable -Name EkuExtensionOid -Value "2.5.29.37" -Option Constant
|
|
Set-Variable -Name CodeSigningEkuOid -Value "1.3.6.1.5.5.7.3.3" -Option Constant
|
|
Set-Variable -Name LifetimeSigningEkuOid -Value "1.3.6.1.4.1.311.10.3.13" -Option Constant
|
|
|
|
$CertificateExtensions = (Get-PfxCertificate $CertificatePath).Extensions
|
|
$HasBasicConstraints = $false
|
|
$HasCodeSigningEku = $false
|
|
|
|
foreach ($Extension in $CertificateExtensions)
|
|
{
|
|
# Certificate must contain the Basic Constraints extension
|
|
if ($Extension.oid.value -eq $BasicConstraintsExtensionOid)
|
|
{
|
|
# CA property must be false
|
|
if ($Extension.CertificateAuthority)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorCertIsCA) $ErrorCodes.CertIsCA
|
|
}
|
|
$HasBasicConstraints = $true
|
|
}
|
|
|
|
# If key usage is present, it must be set to digital signature
|
|
elseif ($Extension.oid.value -eq $KeyUsageExtensionOid)
|
|
{
|
|
if ($Extension.KeyUsages -ne "DigitalSignature")
|
|
{
|
|
PrintMessageAndExit (([UiStrings]::ErrorBannedKeyUsage) -f $Extension.KeyUsages) $ErrorCodes.BannedKeyUsage
|
|
}
|
|
}
|
|
|
|
elseif ($Extension.oid.value -eq $EkuExtensionOid)
|
|
{
|
|
# Certificate must contain the Code Signing EKU
|
|
$EKUs = $Extension.EnhancedKeyUsages.Value
|
|
if ($EKUs -contains $CodeSigningEkuOid)
|
|
{
|
|
$HasCodeSigningEKU = $True
|
|
}
|
|
|
|
# EKUs other than code signing and lifetime signing are not allowed
|
|
foreach ($EKU in $EKUs)
|
|
{
|
|
if ($EKU -ne $CodeSigningEkuOid -and $EKU -ne $LifetimeSigningEkuOid)
|
|
{
|
|
PrintMessageAndExit (([UiStrings]::ErrorBannedEKU) -f $EKU) $ErrorCodes.BannedEKU
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$HasBasicConstraints)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorNoBasicConstraints) $ErrorCodes.NoBasicConstraints
|
|
}
|
|
if (!$HasCodeSigningEKU)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorNoCodeSigningEku) $ErrorCodes.NoCodeSigningEku
|
|
}
|
|
}
|
|
|
|
#
|
|
# Performs operations that require administrative privileges:
|
|
# - Prompt the user to obtain a developer license
|
|
# - Install the developer certificate (if -Force is not specified, also prompts the user to confirm)
|
|
#
|
|
function DoElevatedOperations
|
|
{
|
|
if ($GetDeveloperLicense)
|
|
{
|
|
if ($Force)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorForceDeveloperLicense) $ErrorCodes.ForceDeveloperLicense
|
|
}
|
|
|
|
Write-Host ([UiStrings]::GettingDeveloperLicense)
|
|
Pause
|
|
|
|
try
|
|
{
|
|
Show-WindowsDeveloperLicenseRegistration
|
|
Pause
|
|
}
|
|
catch
|
|
{
|
|
$Error[0] # Dump details about the last error
|
|
PrintMessageAndExit ([UiStrings]::ErrorGetDeveloperLicenseFailed) $ErrorCodes.GetDeveloperLicenseFailed
|
|
}
|
|
}
|
|
|
|
if ($CertificatePath)
|
|
{
|
|
Write-Host ([UiStrings]::InstallingCertificate)
|
|
|
|
# Make sure certificate format is valid and usage constraints are followed
|
|
ValidateCertificateFormat $CertificatePath
|
|
CheckCertificateRestrictions
|
|
|
|
# If -Force is not specified, warn the user and get consent
|
|
if ($Force -or (ConfirmCertificateInstall))
|
|
{
|
|
# Add cert to store
|
|
certutil.exe -addstore TrustedPeople $CertificatePath
|
|
if ($LastExitCode -lt 0)
|
|
{
|
|
PrintMessageAndExit (([UiStrings]::ErrorCertUtilInstallFailed) -f $LastExitCode) $ErrorCodes.CertUtilInstallFailed
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorInstallCertificateCancelled) $ErrorCodes.InstallCertificateCancelled
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Checks whether the machine has app sideloading enabled.
|
|
#
|
|
function CheckIfNeedToEnableSideloading
|
|
{
|
|
$RegistryPath = "hklm:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
|
|
$ValueName = "AllowAllTrustedApps"
|
|
|
|
$Result = $true
|
|
if (Test-Path $RegistryPath)
|
|
{
|
|
$SideloadingEnabledProperty = (Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue)
|
|
if ($null -ne $SideloadingEnabledProperty -and $SideloadingEnabledProperty.$ValueName -eq 1)
|
|
{
|
|
$Result = $false
|
|
}
|
|
}
|
|
|
|
return $Result
|
|
}
|
|
|
|
#
|
|
# Launches an elevated process running the current script to perform tasks
|
|
# that require administrative privileges. This function waits until the
|
|
# elevated process terminates, and checks whether those tasks were successful.
|
|
#
|
|
function LaunchElevated
|
|
{
|
|
# Set up command line arguments to the elevated process
|
|
$RelaunchArgs = '-ExecutionPolicy Unrestricted -file "' + $ScriptPath + '"'
|
|
|
|
if ($Force)
|
|
{
|
|
$RelaunchArgs += ' -Force'
|
|
}
|
|
if ($NeedDeveloperLicense)
|
|
{
|
|
$RelaunchArgs += ' -GetDeveloperLicense'
|
|
}
|
|
if ($NeedInstallCertificate)
|
|
{
|
|
$RelaunchArgs += ' -CertificatePath "' + $DeveloperCertificatePath.FullName + '"'
|
|
}
|
|
|
|
# Launch the process and wait for it to finish
|
|
try
|
|
{
|
|
$AdminProcess = Start-Process "$PsHome\PowerShell.exe" -Verb RunAs -ArgumentList $RelaunchArgs -PassThru
|
|
}
|
|
catch
|
|
{
|
|
$Error[0] # Dump details about the last error
|
|
PrintMessageAndExit ([UiStrings]::ErrorLaunchAdminFailed) $ErrorCodes.LaunchAdminFailed
|
|
}
|
|
|
|
while (!($AdminProcess.HasExited))
|
|
{
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
|
|
# Check if all elevated operations were successful
|
|
if ($NeedDeveloperLicense)
|
|
{
|
|
if (CheckIfNeedToEnableSideloading)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorGetDeveloperLicenseFailed) $ErrorCodes.GetDeveloperLicenseFailed
|
|
}
|
|
else
|
|
{
|
|
Write-Host ([UiStrings]::AcquireLicenseSuccessful)
|
|
}
|
|
}
|
|
if ($NeedInstallCertificate)
|
|
{
|
|
$Signature = Get-AuthenticodeSignature $DeveloperPackagePath -Verbose
|
|
if ($Signature.Status -ne "Valid")
|
|
{
|
|
PrintMessageAndExit (([UiStrings]::ErrorInstallCertificateFailed) -f $Signature.Status) $ErrorCodes.InstallCertificateFailed
|
|
}
|
|
else
|
|
{
|
|
Write-Host ([UiStrings]::InstallCertificateSuccessful)
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Finds all applicable dependency packages according to OS architecture, and
|
|
# installs the developer package with its dependencies. The expected layout
|
|
# of dependencies is:
|
|
#
|
|
# <current dir>
|
|
# \Dependencies
|
|
# <Architecture neutral dependencies>.appx
|
|
# \x86
|
|
# <x86 dependencies>.appx
|
|
# \x64
|
|
# <x64 dependencies>.appx
|
|
# \arm
|
|
# <arm dependencies>.appx
|
|
#
|
|
function InstallPackageWithDependencies
|
|
{
|
|
$DependencyPackagesDir = (Join-Path $ScriptDir "Dependencies")
|
|
$DependencyPackages = @()
|
|
if (Test-Path $DependencyPackagesDir)
|
|
{
|
|
# Get architecture-neutral dependencies
|
|
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "*.appx") | Where-Object { $_.Mode -NotMatch "d" }
|
|
|
|
# Get architecture-specific dependencies
|
|
if (($Env:Processor_Architecture -eq "x86" -or $Env:Processor_Architecture -eq "amd64") -and (Test-Path (Join-Path $DependencyPackagesDir "x86")))
|
|
{
|
|
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "x86\*.appx") | Where-Object { $_.Mode -NotMatch "d" }
|
|
}
|
|
if (($Env:Processor_Architecture -eq "amd64") -and (Test-Path (Join-Path $DependencyPackagesDir "x64")))
|
|
{
|
|
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "x64\*.appx") | Where-Object { $_.Mode -NotMatch "d" }
|
|
}
|
|
if (($Env:Processor_Architecture -eq "arm") -and (Test-Path (Join-Path $DependencyPackagesDir "arm")))
|
|
{
|
|
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "arm\*.appx") | Where-Object { $_.Mode -NotMatch "d" }
|
|
}
|
|
}
|
|
Write-Host ([UiStrings]::InstallingPackage)
|
|
|
|
$AddPackageSucceeded = $False
|
|
try
|
|
{
|
|
if ($DependencyPackages.FullName.Count -gt 0)
|
|
{
|
|
Write-Host ([UiStrings]::DependenciesFound)
|
|
$DependencyPackages.FullName
|
|
Add-AppxPackage -Path $DeveloperPackagePath.FullName -DependencyPath $DependencyPackages.FullName -ForceApplicationShutdown -ForceUpdateFromAnyVersion
|
|
}
|
|
else
|
|
{
|
|
Add-AppxPackage -Path $DeveloperPackagePath.FullName -ForceApplicationShutdown -ForceUpdateFromAnyVersion
|
|
}
|
|
$AddPackageSucceeded = $?
|
|
}
|
|
catch
|
|
{
|
|
$Error[0] # Dump details about the last error
|
|
}
|
|
|
|
if (!$AddPackageSucceeded)
|
|
{
|
|
if ($NeedInstallCertificate)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorAddPackageFailedWithCert) $ErrorCodes.AddPackageFailed
|
|
}
|
|
else
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorAddPackageFailed) $ErrorCodes.AddPackageFailed
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Adds a regkey with the specified value, creating any missing keys as needed.
|
|
#
|
|
function ForceAddRegKeyValue($RegistryPath, $Name, $Type, $Value)
|
|
{
|
|
if (!(Test-Path $RegistryPath))
|
|
{
|
|
New-Item -Path $RegistryPath -Force | Out-Null
|
|
}
|
|
|
|
New-ItemProperty -Path $RegistryPath -Name $Name -PropertyType $Type -Value $Value -Force | Out-Null
|
|
}
|
|
|
|
#
|
|
# Adds the needed regkeys to enable Explorer context menu integration.
|
|
#
|
|
function InstallContextMenuIntegration
|
|
{
|
|
ForceAddRegKeyValue `
|
|
-RegistryPath "hkcu:\Software\Classes\Directory\shell\Open Fluent Terminal here\command"`
|
|
-Name "(Default)"`
|
|
-Type "ExpandString"`
|
|
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%1`""
|
|
|
|
ForceAddRegKeyValue `
|
|
-RegistryPath "hkcu:\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command"`
|
|
-Name "(Default)"`
|
|
-Type "ExpandString"`
|
|
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%V`""
|
|
|
|
ForceAddRegKeyValue `
|
|
-RegistryPath "hkcu:\Software\Classes\Directory\LibraryFolder\shell\Open Fluent Terminal here\command"`
|
|
-Name "(Default)"`
|
|
-Type "ExpandString"`
|
|
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%V`""
|
|
}
|
|
|
|
#
|
|
# Main script logic when the user launches the script without parameters.
|
|
#
|
|
function DoStandardOperations
|
|
{
|
|
# List all .appxbundle files in the script directory
|
|
$BundlePath = Get-ChildItem (Join-Path $ScriptDir "*.appxbundle") | Where-Object { $_.Mode -NotMatch "d" }
|
|
$BundleCount = ($BundlePath | Measure-Object).Count
|
|
|
|
# There must be exactly one bundle
|
|
if ($BundleCount -lt 1)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorNoPackageFound) $ErrorCodes.NoPackageFound
|
|
}
|
|
elseif ($BundleCount -gt 1)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorManyPackagesFound) $ErrorCodes.ManyPackagesFound
|
|
}
|
|
|
|
# First attempt to install a bundle.
|
|
if ($BundleCount -eq 1)
|
|
{
|
|
$DeveloperPackagePath = $BundlePath
|
|
Write-Host (([UiStrings]::BundleFound) -f $DeveloperPackagePath.FullName)
|
|
}
|
|
|
|
# The package must be signed
|
|
$PackageSignature = Get-AuthenticodeSignature $DeveloperPackagePath
|
|
$PackageCertificate = $PackageSignature.SignerCertificate
|
|
if (!$PackageCertificate)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorPackageUnsigned) $ErrorCodes.PackageUnsigned
|
|
}
|
|
|
|
# Test if the package signature is trusted. If not, the corresponding certificate
|
|
# needs to be present in the current directory and needs to be installed.
|
|
$NeedInstallCertificate = ($PackageSignature.Status -ne "Valid")
|
|
|
|
if ($NeedInstallCertificate)
|
|
{
|
|
# List all .cer files in the script directory
|
|
$DeveloperCertificatePath = Get-ChildItem (Join-Path $ScriptDir "*.cer") | Where-Object { $_.Mode -NotMatch "d" }
|
|
$DeveloperCertificateCount = ($DeveloperCertificatePath | Measure-Object).Count
|
|
|
|
# There must be exactly 1 certificate
|
|
if ($DeveloperCertificateCount -lt 1)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorNoCertificateFound) $ErrorCodes.NoCertificateFound
|
|
}
|
|
elseif ($DeveloperCertificateCount -gt 1)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorManyCertificatesFound) $ErrorCodes.ManyCertificatesFound
|
|
}
|
|
|
|
Write-Host (([UiStrings]::CertificateFound) -f $DeveloperCertificatePath.FullName)
|
|
|
|
# The .cer file must have the format of a valid certificate
|
|
ValidateCertificateFormat $DeveloperCertificatePath
|
|
|
|
# The package signature must match the certificate file
|
|
if ($PackageCertificate -ne (Get-PfxCertificate $DeveloperCertificatePath))
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorCertificateMismatch) $ErrorCodes.CertificateMismatch
|
|
}
|
|
}
|
|
|
|
$NeedDeveloperLicense = CheckIfNeedToEnableSideloading
|
|
|
|
# Relaunch the script elevated with the necessary parameters if needed
|
|
if ($NeedDeveloperLicense -or $NeedInstallCertificate)
|
|
{
|
|
Write-Host ([UiStrings]::ElevateActions)
|
|
if ($NeedDeveloperLicense)
|
|
{
|
|
Write-Host ([UiStrings]::ElevateActionDevLicense)
|
|
}
|
|
if ($NeedInstallCertificate)
|
|
{
|
|
Write-Host ([UiStrings]::ElevateActionCertificate)
|
|
}
|
|
|
|
$IsAlreadyElevated = ([Security.Principal.WindowsIdentity]::GetCurrent().Groups.Value -contains "S-1-5-32-544")
|
|
if ($IsAlreadyElevated)
|
|
{
|
|
if ($Force -and $NeedDeveloperLicense)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorForceDeveloperLicense) $ErrorCodes.ForceDeveloperLicense
|
|
}
|
|
if ($Force -and $NeedInstallCertificate)
|
|
{
|
|
Write-Warning ([UiStrings]::WarningInstallCert)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($Force)
|
|
{
|
|
PrintMessageAndExit ([UiStrings]::ErrorForceElevate) $ErrorCodes.ForceElevate
|
|
}
|
|
else
|
|
{
|
|
Write-Host ([UiStrings]::ElevateActionsContinue)
|
|
Pause
|
|
}
|
|
}
|
|
|
|
LaunchElevated
|
|
}
|
|
|
|
InstallPackageWithDependencies
|
|
|
|
if ($ForceContextMenu -or (ConfirmContextMenuInstall))
|
|
{
|
|
InstallContextMenuIntegration
|
|
}
|
|
}
|
|
|
|
#
|
|
# Main script entry point
|
|
#
|
|
if ($GetDeveloperLicense -or $CertificatePath)
|
|
{
|
|
DoElevatedOperations
|
|
}
|
|
else
|
|
{
|
|
DoStandardOperations
|
|
PrintMessageAndExit ([UiStrings]::Success) $ErrorCodes.Success
|
|
}
|