Skip to content

Commit 5a581ae

Browse files
committed
feat: add appWorkingDir, prerun, postrun, and isolatedScriptExecution capabilities
1 parent 4c70038 commit 5a581ae

File tree

4 files changed

+112
-2
lines changed

4 files changed

+112
-2
lines changed

lib/commands/extension.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,11 @@ export async function executePowerShellScript(this: NovaWindowsDriver, script: s
292292
}
293293

294294
const scriptToExecute = pwsh`${script}`;
295-
return await this.sendPowerShellCommand(scriptToExecute);
295+
if (this.caps.isolatedScriptExecution) {
296+
return await this.sendIsolatedPowerShellCommand(scriptToExecute);
297+
} else {
298+
return await this.sendPowerShellCommand(scriptToExecute);
299+
}
296300
}
297301

298302
export async function executeKeys(this: NovaWindowsDriver, keyActions: { actions: KeyAction | KeyAction[], forceUnicode: boolean }) {

lib/commands/powershell.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ export async function startPowerShellSession(this: NovaWindowsDriver): Promise<v
2525

2626
this.powerShell = powerShell;
2727

28+
if (this.caps.appWorkingDir) {
29+
const envVarsSet: Set<string> = new Set();
30+
const matches = this.caps.appWorkingDir.matchAll(/%([^%]+)%/g);
31+
32+
for (const match of matches) {
33+
envVarsSet.add(match[1]);
34+
}
35+
const envVars = Array.from(envVarsSet);
36+
for (const envVar of envVars) {
37+
this.caps.appWorkingDir = this.caps.appWorkingDir.replaceAll(`%${envVar}%`, process.env[envVar.toUpperCase()] ?? '');
38+
}
39+
this.sendPowerShellCommand(`Set-Location -Path '${this.caps.appWorkingDir}'`);
40+
}
41+
2842
await this.sendPowerShellCommand(SET_UTF8_ENCODING);
2943
await this.sendPowerShellCommand(ADD_NECESSARY_ASSEMBLIES);
3044
await this.sendPowerShellCommand(USE_UI_AUTOMATION_CLIENT);
@@ -68,6 +82,75 @@ export async function startPowerShellSession(this: NovaWindowsDriver): Promise<v
6882
}
6983
}
7084

85+
export async function sendIsolatedPowerShellCommand(this: NovaWindowsDriver, command: string): Promise<string> {
86+
const magicNumber = 0xF2EE;
87+
88+
const powerShell = spawn('powershell.exe', ['-NoExit', '-Command', '-']);
89+
try {
90+
powerShell.stdout.setEncoding('utf8');
91+
powerShell.stdout.setEncoding('utf8');
92+
93+
powerShell.stdout.on('data', (chunk: any) => {
94+
this.powerShellStdOut += chunk.toString();
95+
});
96+
97+
powerShell.stderr.on('data', (chunk: any) => {
98+
this.powerShellStdErr += chunk.toString();
99+
});
100+
101+
const result = await new Promise<string>((resolve, reject) => {
102+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
103+
const powerShell = this.powerShell!;
104+
105+
this.powerShellStdOut = '';
106+
this.powerShellStdErr = '';
107+
108+
powerShell.stdin.write(`${SET_UTF8_ENCODING}\n`);
109+
if (this.caps.appWorkingDir) {
110+
const envVarsSet: Set<string> = new Set();
111+
const matches = this.caps.appWorkingDir.matchAll(/%([^%]+)%/g);
112+
113+
for (const match of matches) {
114+
envVarsSet.add(match[1]);
115+
}
116+
const envVars = Array.from(envVarsSet);
117+
for (const envVar of envVars) {
118+
this.caps.appWorkingDir = this.caps.appWorkingDir.replaceAll(`%${envVar}%`, process.env[envVar.toUpperCase()] ?? '');
119+
}
120+
powerShell.stdin.write(`Set-Location -Path '${this.caps.appWorkingDir}'\n`);
121+
}
122+
powerShell.stdin.write(`${command}\n`);
123+
powerShell.stdin.write(/* ps1 */ `Write-Output $([char]0x${magicNumber.toString(16)})\n`);
124+
125+
const onData: Parameters<typeof powerShell.stdout.on>[1] = ((chunk: any) => {
126+
const magicChar = String.fromCharCode(magicNumber);
127+
if (chunk.toString().includes(magicChar)) {
128+
powerShell.stdout.off('data', onData);
129+
if (this.powerShellStdErr) {
130+
reject(new errors.UnknownError(this.powerShellStdErr));
131+
} else {
132+
resolve(this.powerShellStdOut.replace(`${magicChar}`, '').trim());
133+
}
134+
}
135+
}).bind(this);
136+
137+
powerShell.stdout.on('data', onData);
138+
});
139+
140+
// commented out for now to avoid cluttering the logs with long command outputs
141+
// this.log.debug(`PowerShell command executed:\n${command}\n\nCommand output below:\n${result}\n --------`);
142+
143+
return result;
144+
} finally {
145+
// Ensure the isolated PowerShell process is terminated
146+
try {
147+
powerShell.kill();
148+
} catch (e) {
149+
this.log.warn(`Failed to terminate isolated PowerShell process: ${e}`);
150+
}
151+
}
152+
}
153+
71154
export async function sendPowerShellCommand(this: NovaWindowsDriver, command: string): Promise<string> {
72155
const magicNumber = 0xF2EE;
73156

lib/constraints.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,20 @@ export const UI_AUTOMATION_DRIVER_CONSTRAINTS = {
2222
isBoolean: true,
2323
},
2424
appArguments: {
25-
isString: true
25+
isString: true,
26+
},
27+
appWorkingDir: {
28+
isString: true,
2629
},
30+
prerun: {
31+
isObject: true,
32+
},
33+
postrun: {
34+
isObject: true,
35+
},
36+
isolatedScriptExecution: {
37+
isBoolean: true,
38+
}
2739
} as const satisfies Constraints;
2840

2941
export default UI_AUTOMATION_DRIVER_CONSTRAINTS;

lib/driver.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ export class NovaWindowsDriver extends BaseDriver<NovaWindowsDriverConstraints,
178178

179179
await this.startPowerShellSession();
180180

181+
if (this.caps.prerun) {
182+
this.log.info('Executing prerun PowerShell script...');
183+
await this.executePowerShellScript(this.caps.prerun as Exclude<Parameters<typeof commands['executePowerShellScript']>[0], string>);
184+
}
185+
181186
setDpiAwareness();
182187
this.log.debug(`Started session ${sessionId}.`);
183188
return [sessionId, caps];
@@ -202,6 +207,12 @@ export class NovaWindowsDriver extends BaseDriver<NovaWindowsDriverConstraints,
202207
}
203208
} // change to close the whole process, not only the window
204209
await this.terminatePowerShellSession();
210+
211+
if (this.caps.postrun) {
212+
this.log.info('Executing postrun PowerShell script...');
213+
await this.executePowerShellScript(this.caps.postrun as Exclude<Parameters<typeof commands['executePowerShellScript']>[0], string>);
214+
}
215+
205216
await super.deleteSession(sessionId);
206217
}
207218

0 commit comments

Comments
 (0)