powershell-utils 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.d.ts +65 -12
  2. package/index.js +40 -19
  3. package/package.json +1 -1
  4. package/readme.md +69 -13
package/index.d.ts CHANGED
@@ -1,12 +1,31 @@
1
- import type {ExecFileOptions} from 'node:child_process';
1
+ import type {ExecFileOptions, ExecFileSyncOptions} from 'node:child_process';
2
2
 
3
- export type ExecutePowerShellOptions = ExecFileOptions & {
3
+ export type ExecutePowerShellOptions = Omit<ExecFileOptions, 'shell'> & {
4
4
  /**
5
5
  Path to PowerShell executable.
6
6
 
7
7
  @default powerShellPath()
8
8
  */
9
9
  readonly powerShellPath?: string;
10
+
11
+ /**
12
+ Not supported. PowerShell is always executed directly to prevent shell injection.
13
+ */
14
+ readonly shell?: never;
15
+ };
16
+
17
+ export type ExecutePowerShellSyncOptions = Omit<ExecFileSyncOptions, 'shell'> & {
18
+ /**
19
+ Path to PowerShell executable.
20
+
21
+ @default powerShellPath()
22
+ */
23
+ readonly powerShellPath?: string;
24
+
25
+ /**
26
+ Not supported. PowerShell is always executed directly to prevent shell injection.
27
+ */
28
+ readonly shell?: never;
10
29
  };
11
30
 
12
31
  export type ExecutePowerShellResult = {
@@ -70,17 +89,9 @@ export function executePowerShell(
70
89
 
71
90
  export namespace executePowerShell {
72
91
  /**
73
- Standard PowerShell arguments that prefix the encoded command.
92
+ Standard PowerShell arguments that prefix the encoded command: `['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand']`
74
93
 
75
- Use these when manually building PowerShell execution arguments for `spawn()`, `execFile()`, etc.
76
-
77
- @example
78
- ```
79
- import {executePowerShell} from 'powershell-utils';
80
-
81
- const arguments_ = [...executePowerShell.argumentsPrefix, encodedCommand];
82
- childProcess.spawn(powerShellPath(), arguments_);
83
- ```
94
+ Exposed for debugging or for advanced use cases where you need to customize the arguments. For most cases, use `createArguments()` instead.
84
95
  */
85
96
  export const argumentsPrefix: readonly string[];
86
97
 
@@ -104,6 +115,10 @@ export namespace executePowerShell {
104
115
  /**
105
116
  Escape a string argument for use in PowerShell single-quoted strings.
106
117
 
118
+ This makes a value safe as a PowerShell string literal, not as an argument to a native program. PowerShell removes the outer quotes before passing a value onwards, and the receiving process then re-splits it under its own command-line rules, where spaces and double quotes are delimiters. For example, `Start-Process -ArgumentList` receives `'a b'` as two arguments rather than one.
119
+
120
+ Escaping also does not make an untrusted value safe to use as the program that `Start-Process` runs. Validate such values against an allowlist.
121
+
107
122
  @param value - The value to escape.
108
123
  @returns Escaped and quoted string ready for PowerShell.
109
124
 
@@ -119,4 +134,42 @@ export namespace executePowerShell {
119
134
  ```
120
135
  */
121
136
  export function escapeArgument(value: unknown): string;
137
+
138
+ /**
139
+ Create the full arguments array for PowerShell execution.
140
+
141
+ Combines `argumentsPrefix` with the encoded command. Useful when using `spawn()`, `execFile()`, or other process execution methods.
142
+
143
+ @param command - The PowerShell command.
144
+ @returns Array of arguments ready to pass to a process spawner.
145
+
146
+ @example
147
+ ```
148
+ import {spawn} from 'node:child_process';
149
+ import {powerShellPath, executePowerShell} from 'powershell-utils';
150
+
151
+ const args = executePowerShell.createArguments('Get-Process');
152
+ spawn(powerShellPath(), args);
153
+ ```
154
+ */
155
+ export function createArguments(command: string): string[];
122
156
  }
157
+
158
+ /**
159
+ Execute a PowerShell command synchronously.
160
+
161
+ @param command - The PowerShell command to execute.
162
+ @returns The stdout output as a string.
163
+
164
+ @example
165
+ ```
166
+ import {executePowerShellSync} from 'powershell-utils';
167
+
168
+ const stdout = executePowerShellSync('Get-Process');
169
+ console.log(stdout);
170
+ ```
171
+ */
172
+ export function executePowerShellSync(
173
+ command: string,
174
+ options?: ExecutePowerShellSyncOptions
175
+ ): string;
package/index.js CHANGED
@@ -24,35 +24,56 @@ export const canAccessPowerShell = async () => {
24
24
  return canAccessCache;
25
25
  };
26
26
 
27
+ const argumentsPrefix = [
28
+ '-NoProfile',
29
+ '-NonInteractive',
30
+ '-ExecutionPolicy',
31
+ 'Bypass',
32
+ '-EncodedCommand',
33
+ ];
34
+
35
+ const encodeCommand = command => Buffer.from(command, 'utf16le').toString('base64');
36
+
37
+ /*
38
+ PowerShell's tokenizer treats the typographic single quotes U+2018, U+2019, U+201A, and U+201B as string delimiters, exactly like the ASCII one, and it does not require the closing quote to match the opening quote. Escaping only the ASCII quote would let any of the others terminate the string and inject a statement. Doubling works for all of them.
39
+ */
40
+ const escapeArgument = value => `'${String(value).replaceAll(/['‘’‚‛]/g, match => match + match)}'`;
41
+
42
+ const createArguments = command => [...argumentsPrefix, encodeCommand(command)];
43
+
44
+ const createExecFileOptions = options => ({
45
+ encoding: 'utf8',
46
+ ...options,
47
+ shell: false,
48
+ });
49
+
27
50
  export const executePowerShell = async (command, options = {}) => {
28
51
  const {
29
52
  powerShellPath: psPath,
30
53
  ...execFileOptions
31
54
  } = options;
32
55
 
33
- const encodedCommand = executePowerShell.encodeCommand(command);
34
-
35
56
  return execFile(
36
57
  psPath ?? powerShellPath(),
37
- [
38
- ...executePowerShell.argumentsPrefix,
39
- encodedCommand,
40
- ],
41
- {
42
- encoding: 'utf8',
43
- ...execFileOptions,
44
- },
58
+ createArguments(command),
59
+ createExecFileOptions(execFileOptions),
45
60
  );
46
61
  };
47
62
 
48
- executePowerShell.argumentsPrefix = [
49
- '-NoProfile',
50
- '-NonInteractive',
51
- '-ExecutionPolicy',
52
- 'Bypass',
53
- '-EncodedCommand',
54
- ];
63
+ executePowerShell.argumentsPrefix = argumentsPrefix;
64
+ executePowerShell.encodeCommand = encodeCommand;
65
+ executePowerShell.escapeArgument = escapeArgument;
66
+ executePowerShell.createArguments = createArguments;
55
67
 
56
- executePowerShell.encodeCommand = command => Buffer.from(command, 'utf16le').toString('base64');
68
+ export const executePowerShellSync = (command, options = {}) => {
69
+ const {
70
+ powerShellPath: psPath,
71
+ ...execFileOptions
72
+ } = options;
57
73
 
58
- executePowerShell.escapeArgument = value => `'${String(value).replaceAll('\'', '\'\'')}'`;
74
+ return childProcess.execFileSync(
75
+ psPath ?? powerShellPath(),
76
+ createArguments(command),
77
+ createExecFileOptions(execFileOptions),
78
+ );
79
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "powershell-utils",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Utilities for executing PowerShell commands",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/powershell-utils",
package/readme.md CHANGED
@@ -13,11 +13,9 @@ npm install powershell-utils
13
13
  ```js
14
14
  import {executePowerShell, powerShellPath} from 'powershell-utils';
15
15
 
16
- // Execute a PowerShell command
17
16
  const {stdout} = await executePowerShell('Get-Process');
18
17
  console.log(stdout);
19
18
 
20
- // Get PowerShell path
21
19
  console.log(powerShellPath());
22
20
  //=> 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
23
21
  ```
@@ -64,7 +62,6 @@ Execute a PowerShell command.
64
62
  ```js
65
63
  import {executePowerShell} from 'powershell-utils';
66
64
 
67
- // Execute a PowerShell command
68
65
  const {stdout} = await executePowerShell('Get-Process');
69
66
  console.log(stdout);
70
67
  ```
@@ -79,7 +76,7 @@ The PowerShell command to execute.
79
76
 
80
77
  Type: `object`
81
78
 
82
- The below option and also all options in Node.js [`child_process.execFile()`](https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback) are supported.
79
+ The below option and all options except `shell` in Node.js [`child_process.execFile()`](https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback) are supported. PowerShell is always executed directly to prevent shell injection.
83
80
 
84
81
  ##### powerShellPath
85
82
 
@@ -99,16 +96,9 @@ Character encoding for stdout and stderr.
99
96
 
100
97
  Type: `string[]`
101
98
 
102
- Standard PowerShell arguments that prefix the encoded command.
99
+ Standard PowerShell arguments that prefix the encoded command: `['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand']`
103
100
 
104
- Use these when manually building PowerShell execution arguments for `spawn()`, `execFile()`, etc.
105
-
106
- ```js
107
- import {executePowerShell} from 'powershell-utils';
108
-
109
- const arguments_ = [...executePowerShell.argumentsPrefix, encodedCommand];
110
- childProcess.spawn(powerShellPath(), arguments_);
111
- ```
101
+ Exposed for debugging or for advanced use cases where you need to customize the arguments. For most cases, use `createArguments()` instead.
112
102
 
113
103
  ### executePowerShell.encodeCommand(command)
114
104
 
@@ -136,6 +126,11 @@ Returns: `string`
136
126
 
137
127
  Escape a string argument for use in PowerShell single-quoted strings.
138
128
 
129
+ > [!IMPORTANT]
130
+ > This makes a value safe as a PowerShell string literal, not as an argument to a native program. PowerShell removes the outer quotes before passing a value onwards, and the receiving process then re-splits it under its own command-line rules, where spaces and double quotes are delimiters. For example, `Start-Process -ArgumentList` receives `'a b'` as two arguments rather than one.
131
+ >
132
+ > Escaping also does not make an untrusted value safe to use as the program that `Start-Process` runs. Validate such values against an allowlist.
133
+
139
134
  #### value
140
135
 
141
136
  Type: `unknown`
@@ -151,3 +146,64 @@ const escaped = executePowerShell.escapeArgument("it's a test");
151
146
  // Use in command building
152
147
  const command = `Start-Process ${executePowerShell.escapeArgument(appName)}`;
153
148
  ```
149
+
150
+ ### executePowerShell.createArguments(command)
151
+
152
+ Returns: `string[]`
153
+
154
+ Create the full arguments array for PowerShell execution.
155
+
156
+ Combines `argumentsPrefix` with the encoded command. Useful when using `spawn()`, `execFile()`, or other process execution methods.
157
+
158
+ #### command
159
+
160
+ Type: `string`
161
+
162
+ The PowerShell command.
163
+
164
+ ```js
165
+ import {spawn} from 'node:child_process';
166
+ import {powerShellPath, executePowerShell} from 'powershell-utils';
167
+
168
+ const args = executePowerShell.createArguments('Get-Process');
169
+ spawn(powerShellPath(), args);
170
+ ```
171
+
172
+ ### executePowerShellSync(command, options?)
173
+
174
+ Returns: `string`
175
+
176
+ Execute a PowerShell command synchronously.
177
+
178
+ ```js
179
+ import {executePowerShellSync} from 'powershell-utils';
180
+
181
+ const stdout = executePowerShellSync('Get-Process');
182
+ console.log(stdout);
183
+ ```
184
+
185
+ #### command
186
+
187
+ Type: `string`
188
+
189
+ The PowerShell command to execute.
190
+
191
+ #### options
192
+
193
+ Type: `object`
194
+
195
+ The below option and all options except `shell` in Node.js [`child_process.execFileSync()`](https://nodejs.org/api/child_process.html#child_processexecfilesyncfile-args-options) are supported. PowerShell is always executed directly to prevent shell injection.
196
+
197
+ ##### powerShellPath
198
+
199
+ Type: `string`\
200
+ Default: `powerShellPath()`
201
+
202
+ Path to PowerShell executable.
203
+
204
+ ##### encoding
205
+
206
+ Type: `string`\
207
+ Default: `'utf8'`
208
+
209
+ Character encoding for the output.