@@ -3,54 +3,46 @@ import { net } from 'appium/support';
33import { promisify } from 'node:util' ;
44import { exec } from 'node:child_process' ;
55import B from 'bluebird' ;
6+ import { log } from './logger' ;
67
78const execAsync = promisify ( exec ) ;
89
910/**
1011 * This API triggers UAC when necessary
11- * unlike the 'spawn' call used by teen_process's exec.
12- * See https://github.com/nodejs/node-v0.x-archive/issues/6797
1312 *
1413 * @param {string } cmd
1514 * @param {string[] } args
1615 * @param {import('node:child_process').ExecOptions & {timeoutMs?: number} } opts
1716 * @returns {Promise<{stdout: string; stderr: string;}> }
1817 * @throws {import('node:child_process').ExecException }
18+ *
19+ * Notes:
20+ * - If the UAC prompt is cancelled by the user, Start-Process returns a non-zero exit code.
1921 */
20- export async function shellExec ( cmd , args = [ ] , opts = { } ) {
22+ export async function runElevated ( cmd , args = [ ] , opts = { } ) {
2123 const {
2224 timeoutMs = 60 * 1000 * 5
2325 } = opts ;
24- const fullCmd = [ cmd , ...args ] . map ( escapeWindowsArg ) . join ( ' ' ) ;
25- const { stdout, stderr } = await B . resolve ( execAsync ( fullCmd , opts ) )
26- . timeout ( timeoutMs , `The command '${ fullCmd } ' timed out after ${ timeoutMs } ms` ) ;
26+
27+ const escapePSSingleQuoted = ( /** @type {string } */ str ) => `'${ String ( str ) . replace ( / ' / g, "''" ) } '` ;
28+ const psFilePath = escapePSSingleQuoted ( cmd ) ;
29+ const psArgList = _ . isEmpty ( args ) ? "''" : args . map ( escapePSSingleQuoted ) . join ( ',' ) ;
30+ // Build the PowerShell Start-Process command (safe quoting for inner tokens)
31+ const psCommand = `Start-Process -FilePath ${ psFilePath } -ArgumentList ${ psArgList } -Verb RunAs` ;
32+ // Wrap the PowerShell command in double-quotes for the outer shell call.
33+ // We avoid additional interpolation here by using only single-quoted literals inside the PS command.
34+ const fullCmd = `powershell -NoProfile -Command "${ psCommand } "` ;
35+ log . debug ( `Executing command: ${ fullCmd } ` ) ;
36+ const { stdout, stderr } = /** @type {any } */ (
37+ await B . resolve ( execAsync ( fullCmd , opts ) )
38+ . timeout ( timeoutMs , `The command '${ fullCmd } ' timed out after ${ timeoutMs } ms` )
39+ ) ;
2740 return {
2841 stdout : _ . isString ( stdout ) ? stdout : stdout . toString ( ) ,
2942 stderr : _ . isString ( stderr ) ? stderr : stderr . toString ( ) ,
3043 } ;
3144}
3245
33- /**
34- * Escapes a string to be used as a Windows command line argument
35- *
36- * @param {string } arg
37- * @returns {string }
38- */
39- function escapeWindowsArg ( arg ) {
40- if ( ! arg ) {
41- return '""' ;
42- }
43-
44- const needsQuotes = / [ \s " ] / g. test ( arg ) ;
45- if ( ! needsQuotes ) {
46- return arg ;
47- }
48-
49- // Escape double quotes and backslashes before quotes
50- const escaped = arg . replace ( / ( \\ * ) " / g, '$1$1\\"' ) . replace ( / ( \\ + ) $ / , '$1$1' ) ;
51- return `"${ escaped } "` ;
52- }
53-
5446/**
5547 *
5648 * @param {string } srcUrl
0 commit comments