diff --git a/packages/settings-view/lib/system-windows-panel.js b/packages/settings-view/lib/system-windows-panel.js
index 604dc217b1..439e7b5894 100644
--- a/packages/settings-view/lib/system-windows-panel.js
+++ b/packages/settings-view/lib/system-windows-panel.js
@@ -20,6 +20,14 @@ 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 })
+ }
}
destroy () {
@@ -36,7 +44,7 @@ export default class SystemPanel {
System Settings
-
These settings determine how Atom integrates with your operating system.
+
These settings determine how Pulsar integrates with your operating system.
@@ -95,6 +103,7 @@ export default class SystemPanel {
+ { this.getPathUI() }
@@ -111,6 +120,65 @@ 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 (
+
+
+
+
+
+
+
+ );
+ } else {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+ }
+
focus () {
this.element.focus()
}
diff --git a/resources/win/installer.nsh b/resources/win/installer.nsh
new file mode 100644
index 0000000000..8e9b601a80
--- /dev/null
+++ b/resources/win/installer.nsh
@@ -0,0 +1,15 @@
+!macro customInstall
+ # Set the 'InstallLocation' Registry Key for GitHub Desktop
+ WriteRegStr SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY}" "InstallLocation" "$INSTDIR"
+!macroend
+
+!macro customUnInstall
+ # This uninstall script is ready to go. Just a question if we want to modify PATH on uninstall
+ ${if} $installMode == "all"
+ # Machine Install
+ #ExecWait 'powershell -ExecutionPolicy Bypass -WindowStyle Hidden -File "$INSTDIR\resources\modifyWindowsPath.ps1" -installMode Machine -installdir "$INSTDIR" -remove 1'
+ ${else}
+ # User Install
+ #ExecWait 'powershell -ExecutionPolicy Bypass -WindowStyle Hidden -File "$INSTDIR\resources\modifyWindowsPath.ps1" -installMode User -installdir "$INSTDIR" -remove 1'
+ ${endif}
+!macroend
diff --git a/resources/win/modifyWindowsPath.ps1 b/resources/win/modifyWindowsPath.ps1
new file mode 100644
index 0000000000..f02be5f1e9
--- /dev/null
+++ b/resources/win/modifyWindowsPath.ps1
@@ -0,0 +1,99 @@
+# Modify Windows PATH for Pulsar
+# Sets Pulsar & PPM into PATH, adds 'ATOM_HOME' env var
+
+# Example Usage:
+# Pulsar User Installation:
+# .\_.ps1 -installMode User -installdir "$INSTDIR" -remove 0
+# Pulsar Machine Installation:
+# .\_.ps1 -installMode Machine -installdir "$INSTDIR" -remove 0
+# Pulsar User Uninstallation:
+# .\_.ps1 -installMode User -installdir "$INSTDIR" -remove 1
+# Pulsar Machine Uninstallation:
+# .\_.ps1 -installMode Machine -installdir "$INSTDIR" -remove 1
+
+param ($installMode,$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") {
+
+ $prog = 25
+ Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
+
+ [Environment]::SetEnvironmentVariable("Path", $env:Path + ";$installdir\resources;$installdir\resources\app\ppm\bin", $installMode)
+
+ $prog = 50
+ Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
+
+ # 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)
+
+ $prog = 100
+ Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
+
+ Exit $exitCode
+ }
+} else {
+ if ($installMode -eq "User" -or $installMode -eq "Machine") {
+
+ $prog = 25
+ Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
+
+ $path = [Environment]::GetEnvironmentVariable("Path", $installMode)
+
+ $prog = 50
+ Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
+
+ # 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 ";"
+
+ $prog = 75
+ Write-Progress -Activity "Modifying Pulsar ($installdir) on the PATH..." -Status "$prog% Complete:" -PercentComplete $prog
+
+ # Set our new path
+ [Environment]::SetEnvironmentVariable("Path", $path, $installMode)
+
+ $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
+}
diff --git a/script/electron-builder.js b/script/electron-builder.js
index d84cefc774..a88a3050df 100644
--- a/script/electron-builder.js
+++ b/script/electron-builder.js
@@ -213,6 +213,10 @@ let options = {
"from": "resources/win/pulsar.js",
"to": "pulsar.js"
},
+ {
+ "from": "resources/win/modifyWindowsPath.ps1",
+ "to": "modifyWindowsPath.ps1"
+ }
],
"target": [
{ "target": "nsis" },
@@ -227,11 +231,12 @@ let options = {
"runAfterFinish": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
- "guid": "0949b555-c22c-56b7-873a-a960bdefa81f"
+ "guid": "0949b555-c22c-56b7-873a-a960bdefa81f",
// The GUID is generated from Electron-Builder based on our AppID
// Hardcoding it here means it will always be used as generated from
// the AppID 'dev.pulsar-edit.pulsar'. If this value ever changes,
// A PR to GitHub Desktop must be made with the updated value
+ "include": "resources/win/installer.nsh"
},
"extraMetadata": {
},
diff --git a/src/main-process/win-shell.js b/src/main-process/win-shell.js
index 366eb513cd..27bbaf4f9c 100644
--- a/src/main-process/win-shell.js
+++ b/src/main-process/win-shell.js
@@ -1,5 +1,6 @@
const Registry = require('winreg');
const Path = require('path');
+const ChildProcess = require('child_process');
const getAppName = require('../get-app-name');
const appName = getAppName();
@@ -73,6 +74,144 @@ class ShellOption {
}
}
+class PathOption {
+ constructor(installType) {
+ // installType MUST be 'User' or 'Machine'
+ 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";
+ }
+
+ // 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);
+ this.getPulsarPath = this.getPulsarPath.bind(this);
+ }
+
+ isRegistered(callback) {
+ let installRegKey = new Registry({
+ hive: this.hive,
+ key: this.HKPATH
+ });
+
+ let isInstalled = false;
+
+ installRegKey.values((err, items) => {
+ if (err) {
+ callback(err);
+ } else {
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].name === "Path") {
+ let winPath = items[i].value;
+ if (winPath.includes("Pulsar\\resources") || winPath.includes("Pulsar\\resources\\app\\ppm\\bin")) {
+ isInstalled = true;
+ }
+ }
+ }
+ callback(isInstalled);
+ }
+ });
+ }
+
+ register(callback) {
+ this.getPulsarPath().then((pulsarPath) => {
+ const child = ChildProcess.execFile(
+ `${pulsarPath}\\resources\\modifyWindowsPath.ps1`,
+ ['-installMode', this.installMode, '-installdir', `"${pulsarPath}"`, '-remove', '0'],
+ { shell: "powershell.exe" },
+ (error, stdout, stderr) =>
+ {
+ if (error) {
+ atom.notifications.addError(`Error Running Script: ${error.toString()}`);
+ callback(error);
+ } else {
+ return callback();
+ }
+ });
+ }).catch((err) => {
+ return callback(err);
+ });
+ }
+
+ deregister(callback) {
+ this.isRegistered(isRegistered => {
+ if (isRegistered) {
+ this.getPulsarPath().then((pulsarPath) => {
+ const child = ChildProcess.execFile(
+ `${pulsarPath}\\resources\\modifyWindowsPath.ps1`,
+ ['-installMode', this.installMode, '-installdir', `"${pulsarPath}"`, '-remove', '1'],
+ { shell: "powershell.exe" },
+ (error, stdout, stderr) =>
+ {
+ if (error) {
+ atom.notifications.addError(`Error Running Script: ${error.toString()}`);
+ callback(error);
+ } else {
+ return callback();
+ }
+ });
+ }).catch((err) => {
+ return callback(err);
+ });
+ } else {
+ callback(null, false);
+ }
+ });
+ }
+
+ getPulsarPath() {
+ return new Promise((resolve, reject) => {
+ let pulsarPath = "";
+ let pulsarPathReg = new Registry({
+ hive: this.hive,
+ key: this.installReg
+ }).get("InstallLocation", (err, val) => {
+ if (err) {
+ reject(err);
+ } else {
+ pulsarPath = val.value;
+
+ if (pulsarPath.length === 0) {
+ reject("Unable to find Pulsar Install Path");
+ }
+
+ // 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);
+ }
+ });
+ });
+ }
+}
+
+// Function that can inform is Pulsar is running as the administrator on Windows
+exports.runningAsAdmin = (callback) => {
+ const child = ChildProcess.exec("NET SESSION", (error, stdout, stderr) => {
+ if (stderr.length === 0) {
+ callback(true);
+ } else {
+ callback(false);
+ }
+ });
+};
+
exports.appName = appName;
exports.fileHandler = new ShellOption(
@@ -102,3 +241,5 @@ exports.folderBackgroundContextMenu = new ShellOption(
`\\Software\\Classes\\Directory\\background\\shell\\${appName}`,
JSON.parse(JSON.stringify(contextParts).replace('%1', '%V'))
);
+exports.pathUser = new PathOption("User");
+exports.pathMachine = new PathOption("Machine");