Skip to content

Commit

Permalink
Use PowerShell on Windows for improved reliability (#188)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
tim-stasse and sindresorhus committed Aug 21, 2020
1 parent ed14bbf commit f7ca0d3
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 46 deletions.
8 changes: 2 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,9 @@ declare namespace open {
readonly app?: string | readonly string[];

/**
Uses `URL` to encode the `target` before executing it.
__deprecated__
The use with targets that are not URLs is not recommended.
Especially useful when dealing with the [double-quotes on Windows](https://github.com/sindresorhus/open#double-quotes-on-windows) caveat.
@default false
This option will be removed in the next major release.
*/
readonly url?: boolean;

Expand Down
72 changes: 32 additions & 40 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const {promisify} = require('util');
const path = require('path');
const childProcess = require('child_process');
const fs = require('fs');
const url = require('url');
const isWsl = require('is-wsl');
const isDocker = require('is-docker');

Expand All @@ -28,30 +27,19 @@ module.exports = async (target, options) => {
options = {
wait: false,
background: false,
url: false,
allowNonzeroExitCode: false,
...options
};

let command;
let app;
let appArguments = [];
const cliArguments = [];
const childProcessOptions = {};

if (Array.isArray(options.app)) {
appArguments = options.app.slice(1);
options.app = options.app[0];
}

// Encodes the target as if it were an URL. Especially useful to get
// double-quotes through the “double-quotes on Windows caveat”, but it
// can be used on any platform.
if (options.url) {
target = new url.URL(target).href;

if (isWsl) {
target = target.replace(/&/g, '^&');
}
app = options.app[0];
}

if (process.platform === 'darwin') {
Expand All @@ -65,49 +53,53 @@ module.exports = async (target, options) => {
cliArguments.push('--background');
}

if (options.app) {
cliArguments.push('-a', options.app);
if (app) {
cliArguments.push('-a', app);
}
} else if (process.platform === 'win32' || (isWsl && !isDocker())) {
command = 'cmd' + (isWsl ? '.exe' : '');
cliArguments.push('/s', '/c', 'start', '""', '/b');
command = 'powershell' + (isWsl ? '.exe' : '');
cliArguments.push(
'-NoProfile',
'-NonInteractive',
'–ExecutionPolicy',
'Bypass',
'-EncodedCommand'
);

if (!isWsl) {
// Always quoting target allows for URLs/paths to have spaces and unmarked characters, as `cmd.exe` will
// interpret them as plain text to be forwarded as one unique argument. Enabling `windowsVerbatimArguments`
// disables Node.js's default quotes and escapes handling (https://git.io/fjdem).
// References:
// - Issues #17, #44, #55, #77, #101, #115
// - Pull requests: #74, #98
//
// As a result, all double-quotes are stripped from the `target` and do not get to your desired destination.
target = `"${target}"`;
childProcessOptions.windowsVerbatimArguments = true;

if (options.app) {
options.app = `"${options.app}"`;
}
}

const encodedArguments = ['Start'];

if (options.wait) {
cliArguments.push('/wait');
encodedArguments.push('-Wait');
}

if (options.app) {
if (isWsl && options.app.startsWith('/mnt/')) {
const windowsPath = await wslToWindowsPath(options.app);
options.app = windowsPath;
if (app) {
if (isWsl && app.startsWith('/mnt/')) {
const windowsPath = await wslToWindowsPath(app);
app = windowsPath;
}

cliArguments.push(options.app);
// Double quote with double quotes to ensure the inner quotes are passed through.
// Inner quotes are delimited for PowerShell interpretation with backticks.
encodedArguments.push(`"\`"${app}\`""`, '-ArgumentList');
appArguments.unshift(target);
} else {
encodedArguments.push(`"\`"${target}\`""`);
}

if (appArguments.length > 0) {
cliArguments.push(...appArguments);
appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
encodedArguments.push(appArguments.join(','));
}

// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
} else {
if (options.app) {
command = options.app;
if (app) {
command = app;
} else {
// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
const isBundled = !__dirname || __dirname === '/';
Expand Down

0 comments on commit f7ca0d3

Please sign in to comment.