Merge pull request #957 from pulsar-edit/rework-windows-path-modification

[core] (Windows) Remove all `Machine` PATH handling, add safety mechanisms
This commit is contained in:
DeeDeeG 2024-04-06 15:41:43 -04:00 committed by GitHub
commit ace4180d9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 125 additions and 162 deletions

View File

@ -20,14 +20,7 @@ export default class SystemPanel {
WinShell.fileHandler.isRegistered((i) => { this.refs.fileHandlerCheckbox.checked = i })
WinShell.fileContextMenu.isRegistered((i) => { this.refs.fileContextMenuCheckbox.checked = i })
WinShell.folderContextMenu.isRegistered((i) => { this.refs.folderContextMenuCheckbox.checked = i })
if (this.isLikelyUserInstall()) {
WinShell.pathUser.isRegistered((i) => { this.refs.addToPathCheckbox.checked = i })
} else {
WinShell.pathMachine.isRegistered((i) => { this.refs.addToPathMachineCheckbox.checked = i })
// Check if Pulsar is running as Admin. To know if the user can modify the machine path
WinShell.runningAsAdmin((i) => { this.refs.addToPathMachineCheckbox.disabled = !i })
}
WinShell.pathUser.isRegistered((i) => { this.refs.addToPathCheckbox.checked = i })
}
destroy () {
@ -120,63 +113,29 @@ export default class SystemPanel {
}
}
isLikelyUserInstall() {
let resourcePath = atom.applicationDelegate.getWindowLoadSettings().resourcePath;
if (resourcePath.includes("AppData\\Local\\Programs\\pulsar")) {
return true;
} else {
return false;
}
}
getPathUI() {
if (this.isLikelyUserInstall()) {
return (
<div className='control-group'>
<div className='controls'>
<div className='checkbox'>
<label for='system.windows.add-to-path'>
<input
ref='addToPathCheckbox'
id='system.windows.add-to-path'
className='input-checkbox'
type='checkbox'
onclick={(e) => {
this.setRegistration(WinShell.pathUser, e.target.checked)
}} />
<div className='setting-title'>Add Pulsar to PATH (User Install)</div>
<div className='setting-description'>
Add Pulsar to Windows PATH to enable CLI usage.
</div>
</label>
</div>
return (
<div className='control-group'>
<div className='controls'>
<div className='checkbox'>
<label for='system.windows.add-to-path'>
<input
ref='addToPathCheckbox'
id='system.windows.add-to-path'
className='input-checkbox'
type='checkbox'
onclick={(e) => {
this.setRegistration(WinShell.pathUser, e.target.checked)
}} />
<div className='setting-title'>Add Pulsar to PATH</div>
<div className='setting-description'>
Add Pulsar to Windows PATH to enable CLI usage. (May require a reboot to take effect.)
</div>
</label>
</div>
</div>
);
} else {
return (
<div className='control-group'>
<div className='controls'>
<div className='checkbox'>
<label for='system.windows.add-to-path-machine'>
<input
ref='addToPathMachineCheckbox'
id='system.windows.add-to-path-machine'
className='input-checkbox'
type='checkbox'
onclick={(e) => {
this.setRegistration(WinShell.pathMachine, e.target.checked)
}} />
<div className='setting-title'>Add Pulsar to PATH (Machine Install)</div>
<div className='setting-description'>
Add Pulsar to Windows PATH for machine installs. Requires administrative privileges.
</div>
</label>
</div>
</div>
</div>
);
}
</div>
);
}
focus () {

View File

@ -3,97 +3,94 @@
# Example Usage:
# Pulsar User Installation:
# .\_.ps1 -installMode User -installdir "$INSTDIR" -remove 0
# .\_.ps1 -installdir "$INSTDIR" -remove FALSE
# Pulsar Machine Installation:
# .\_.ps1 -installMode Machine -installdir "$INSTDIR" -remove 0
# .\_.ps1 -installdir "$INSTDIR" -remove FALSE
# Pulsar User Uninstallation:
# .\_.ps1 -installMode User -installdir "$INSTDIR" -remove 1
# .\_.ps1 -installdir "$INSTDIR" -remove TRUE
# Pulsar Machine Uninstallation:
# .\_.ps1 -installMode Machine -installdir "$INSTDIR" -remove 1
# .\_.ps1 -installdir "$INSTDIR" -remove TRUE
param ($installMode,$installdir,$remove)
# For safe interaction with environment variables taken from:
# https://github.com/chocolatey/choco/blob/HEAD/src/chocolatey.resources/helpers/functions/Set-EnvironmentVariable.ps1
# https://github.com/chocolatey/choco/blob/HEAD/src/chocolatey.resources/helpers/functions/Get-EnvironmentVariable.ps1
# https://github.com/chocolatey/choco/blob/HEAD/src/chocolatey.resources/helpers/functions/Install-ChocolateyPath.ps1
param ($installdir,$remove)
# When self-elevating, we can't pass a raw boolean. Meaning we accept anything then convert
$remove = [System.Convert]::ToBoolean($remove)
# Only when modifying the Machine PATH, it takes much longer than expected. So here's a loading bar
$prog = 1
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
if ($installMode -eq "Machine") {
# PowerShell needs to be running as Admin to modify the Machine Variables
# So lets attempt to self-elevate
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
$processOptions = @{
FilePath = "PowerShell.exe"
Wait = $true
PassThru = $true
Verb = "RunAs"
ArgumentList = "-File `"" + $MyInvocation.MyCommand.Path + "`" -installMode $installMode -installdir `"" + $installdir + "`" -remove $remove"
}
Start-Process @processOptions
Exit
}
}
}
if (-not $remove) {
if ($installMode -eq "User" -or $installMode -eq "Machine") {
# We want to add Pulsar path values
$prog = 25
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
# Lets first save a copy of the users current path
$env:Path > prior2addition.txt;
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";$installdir\resources;$installdir\resources\app\ppm\bin", $installMode)
$originalPathToInstall = $installdir
$prog = 50
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
$pulsarPath = $installdir + "\resources";
$ppmPath = $installdir + "\resources\app\ppm\bin";
# While this originally attempting to use the string '%USERPROFILE%' to avoid taking
# space on the PATH, whatever reads this path at startup in Pulsar, can't handle
# the variable, and instead creates the directory of the same name
# within the current folder. But only when opened via the context menu, terminal
# is fine.
$exitCode = [Environment]::SetEnvironmentVariable("ATOM_HOME", "$env:UserProfile\.pulsar", $installMode)
# Get the current PATH variable
$envPath = $env:PATH;
if (!$envPath.toLower().Contains($installdir.ToLower())) {
# we don't already have the correct environment variable
$actualPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User);
$prog = 100
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
$statementTerminator = ";";
Exit $exitCode
$pathToInstall = $pulsarPath + $statementTerminator + $ppmPath + $statementTerminator;
# Does the path end in ';'?
$hasStatementTerminator = $actualPath -ne $null -and $actualPath.EndsWith($statementTerminator);
# If the last digit is not ';', then add it
if (!$hasStatementTerminator -and $actualPath -ne $null) {
$pathToInstall = $statementTerminator + $pathToInstall;
}
$actualPath = $actualPath + $pathToInstall;
# Now to actually set the path to the system
$registryType = [Microsoft.Win32.RegistryValueKind]::ExpandString;
$keyHive = "HKEY_CURRENT_USER";
$registryKey = "Environment";
$exitCode = [Microsoft.Win32.Registry]::SetValue($keyHive + "\" + $registryKey, "Path", $actualPath, [System.EnvironmentVariableTarget]::User);
Exit $exitCode;
} else {
Write-Host "Pulsar is already present on the User PATH.";
}
} else {
if ($installMode -eq "User" -or $installMode -eq "Machine") {
# We want to remove Pulsar from the user path
$prog = 25
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
# Lets first save a copy of the users current path
$env:Path > prior2removal.txt;
$path = [Environment]::GetEnvironmentVariable("Path", $installMode)
$pulsarPath = $installdir + "\resources";
$ppmPath = $installdir + "\resources\app\ppm\bin";
$prog = 50
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
# Get the current PATH variable
$envPath = $env:PATH;
if ($envPath.toLower().Contains($installdir.ToLower())) {
# the install dir is in fact on the path
$actualPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User);
# Remove unwanted element from path
$path = ($path.Split(";") | Where-Object { $_ -ne "$installdir\resources" }) -join ";"
$path = ($path.Split(";") | Where-Object { $_ -ne "$installdir\resources\app\ppm\bin" }) -join ";"
$actualPath = ($actualPath.Split(";") | Where-Object { $_ -ne $ppmPath }) -join ";";
# Order is important, as Pulsar's path INCLUDES ppm's path
$actualPath = ($actualPath.Split(";") | Where-Object { $_ -ne $pulsarPath }) -join ";";
$prog = 75
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
# Now to actually set the path to the system
$registryType = [Microsoft.Win32.RegistryValueKind]::ExpandString;
$keyHive = "HKEY_CURRENT_USER";
$registryKey = "Environment";
# Set our new path
[Environment]::SetEnvironmentVariable("Path", $path, $installMode)
$exitCode = [Microsoft.Win32.Registry]::SetValue($keyHive + "\" + $registryKey, "Path", $actualPath, [System.EnvironmentVariableTarget]::User);
$prog = 90
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
# Set ATOM_HOME path
$exitCode = [Environment]::SetEnvironmentVariable("ATOM_HOME", $null, $installMode)
$prog = 100
Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
Exit $exitCode
} # Else we have been given bad params, and will silently exit
Exit $exitCode;
} else {
Write-Host "Pulsar is not present on the User PATH.";
}
}

View File

@ -74,27 +74,16 @@ class ShellOption {
}
class PathOption {
constructor(installType) {
// installType MUST be 'User' or 'Machine'
constructor() {
this.HKPATH;
this.hive;
this.installReg = "\\SOFTWARE\\0949b555-c22c-56b7-873a-a960bdefa81f";
this.installMode = installType;
if (installType === "User") {
this.HKPATH = "\\Environment";
this.hive = "HKCU";
} else if (installType === "Machine") {
this.HKPATH = "\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
this.hive = "HKLM";
}
// We no longer support an `installType`
// Only managing the path of the current user
this.HKPATH = "\\Environment";
this.hive = "HKCU";
// Unfortunately, we can only manage the PATH for a per user installation.
// While the PowerShell script does support setting the PATH for a Machine
// install, we can't yet check that.
// https://github.com/fresc81/node-winreg/tree/1.2.1#troubleshooting
// This can only be done if Pulsar is run as Admin, with a user with Admin privs
// So we will pretend a user install is all that matters here
this.isRegistered = this.isRegistered.bind(this);
this.register = this.register.bind(this);
this.deregister = this.deregister.bind(this);
@ -129,19 +118,24 @@ class PathOption {
register(callback) {
this.getPulsarPath().then((pulsarPath) => {
const child = ChildProcess.execFile(
`${pulsarPath}\\resources\\modifyWindowsPath.ps1`,
['-installMode', this.installMode, '-installdir', `"${pulsarPath}"`, '-remove', '0'],
`powershell.exe -File '${pulsarPath}\\resources\\modifyWindowsPath.ps1'`,
['-installdir', `'${pulsarPath}'`, '-remove', 'FALSE'],
{ shell: "powershell.exe" },
(error, stdout, stderr) =>
{
if (error) {
atom.notifications.addError(`Error Running Script: ${error.toString()}`);
console.log(`Add Pulsar to PATH: ${error}`);
atom.notifications.addError(`Error Running Script: ${error.toString()}`, { dismissable: true });
callback(error);
} else {
console.log("Add Pulsar to PATH:");
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
return callback();
}
});
}).catch((err) => {
console.error(`Add Pulsar to PATH error caught: ${err}`);
return callback(err);
});
}
@ -151,19 +145,24 @@ class PathOption {
if (isRegistered) {
this.getPulsarPath().then((pulsarPath) => {
const child = ChildProcess.execFile(
`${pulsarPath}\\resources\\modifyWindowsPath.ps1`,
['-installMode', this.installMode, '-installdir', `"${pulsarPath}"`, '-remove', '1'],
`powershell.exe -File '${pulsarPath}\\resources\\modifyWindowsPath.ps1'`,
['-installdir', `'${pulsarPath}'`, '-remove', 'TRUE'],
{ shell: "powershell.exe" },
(error, stdout, stderr) =>
{
if (error) {
atom.notifications.addError(`Error Running Script: ${error.toString()}`);
console.error(`Remove Pulsar from PATH: ${error}`);
atom.notifications.addError(`Error Running Script: ${error.toString()}`, { dismissable: true });
callback(error);
} else {
console.log("Remove Pulsar from PATH:");
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
return callback();
}
});
}).catch((err) => {
console.error(`Remove Pulsar from PATH error caught: ${err}`);
return callback(err);
});
} else {
@ -180,20 +179,29 @@ class PathOption {
key: this.installReg
}).get("InstallLocation", (err, val) => {
if (err) {
reject(err);
console.error(err);
let location = process.resourcesPath;
if (typeof location !== "string" || location.length < 1) {
console.error(`Unable to locate Pulsar PATH via fallback methods: '${location}'`);
reject(err);
} else {
resolve(Path.dirname(location));
}
} else {
pulsarPath = val.value;
if (pulsarPath.length === 0) {
reject("Unable to find Pulsar Install Path");
console.error("Unable to find Pulsar Install Path");
let location = process.resourcesPath;
if (typeof location !== "string" || location.length < 1) {
console.error(`Unable to locate Pulsar PATH via fallback methods: '${location}'`);
reject("Unable to find Pulsar Install Path");
} else {
resolve(Path.dirname(location));
}
}
// When we are modifying Machine values, we can't accept spaces in the
// path. There's likely some combination of escapes to fix this, but
// I was unable to find them. For now we will check for the default
// Machine install location, and remove the space.
let safePulsarPath = pulsarPath.replace("Program Files", "PROGRA~1");
resolve(safePulsarPath);
resolve(pulsarPath);
}
});
});
@ -241,4 +249,3 @@ exports.folderBackgroundContextMenu = new ShellOption(
JSON.parse(JSON.stringify(contextParts).replace('%1', '%V'))
);
exports.pathUser = new PathOption("User");
exports.pathMachine = new PathOption("Machine");