Skip to content

Commit f7ca0d3

Browse files
tim-stassesindresorhus
andauthoredAug 21, 2020
Use PowerShell on Windows for improved reliability (#188)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent ed14bbf commit f7ca0d3

File tree

2 files changed

+34
-46
lines changed

2 files changed

+34
-46
lines changed
 

‎index.d.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,9 @@ declare namespace open {
3333
readonly app?: string | readonly string[];
3434

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

‎index.js

+32-40
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const {promisify} = require('util');
33
const path = require('path');
44
const childProcess = require('child_process');
55
const fs = require('fs');
6-
const url = require('url');
76
const isWsl = require('is-wsl');
87
const isDocker = require('is-docker');
98

@@ -28,30 +27,19 @@ module.exports = async (target, options) => {
2827
options = {
2928
wait: false,
3029
background: false,
31-
url: false,
3230
allowNonzeroExitCode: false,
3331
...options
3432
};
3533

3634
let command;
35+
let app;
3736
let appArguments = [];
3837
const cliArguments = [];
3938
const childProcessOptions = {};
4039

4140
if (Array.isArray(options.app)) {
4241
appArguments = options.app.slice(1);
43-
options.app = options.app[0];
44-
}
45-
46-
// Encodes the target as if it were an URL. Especially useful to get
47-
// double-quotes through the “double-quotes on Windows caveat”, but it
48-
// can be used on any platform.
49-
if (options.url) {
50-
target = new url.URL(target).href;
51-
52-
if (isWsl) {
53-
target = target.replace(/&/g, '^&');
54-
}
42+
app = options.app[0];
5543
}
5644

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

68-
if (options.app) {
69-
cliArguments.push('-a', options.app);
56+
if (app) {
57+
cliArguments.push('-a', app);
7058
}
7159
} else if (process.platform === 'win32' || (isWsl && !isDocker())) {
72-
command = 'cmd' + (isWsl ? '.exe' : '');
73-
cliArguments.push('/s', '/c', 'start', '""', '/b');
60+
command = 'powershell' + (isWsl ? '.exe' : '');
61+
cliArguments.push(
62+
'-NoProfile',
63+
'-NonInteractive',
64+
'–ExecutionPolicy',
65+
'Bypass',
66+
'-EncodedCommand'
67+
);
7468

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

73+
const encodedArguments = ['Start'];
74+
9275
if (options.wait) {
93-
cliArguments.push('/wait');
76+
encodedArguments.push('-Wait');
9477
}
9578

96-
if (options.app) {
97-
if (isWsl && options.app.startsWith('/mnt/')) {
98-
const windowsPath = await wslToWindowsPath(options.app);
99-
options.app = windowsPath;
79+
if (app) {
80+
if (isWsl && app.startsWith('/mnt/')) {
81+
const windowsPath = await wslToWindowsPath(app);
82+
app = windowsPath;
10083
}
10184

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

10593
if (appArguments.length > 0) {
106-
cliArguments.push(...appArguments);
94+
appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
95+
encodedArguments.push(appArguments.join(','));
10796
}
97+
98+
// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
99+
target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
108100
} else {
109-
if (options.app) {
110-
command = options.app;
101+
if (app) {
102+
command = app;
111103
} else {
112104
// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
113105
const isBundled = !__dirname || __dirname === '/';

0 commit comments

Comments
 (0)
Please sign in to comment.