Skip to content
This repository has been archived by the owner on Dec 6, 2022. It is now read-only.

Commit

Permalink
Launch Chrome in unelevated state on Windows platform by using `wmic …
Browse files Browse the repository at this point in the history
…call create`.
  • Loading branch information
changsi-an committed Mar 16, 2018
1 parent 8660c6a commit 5ca41a7
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 13 deletions.
3 changes: 2 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const watchedSources = [
];

const scripts = [
'src/terminateProcess.sh'
'src/terminateProcess.sh',
'src/launchUnelevated.js'
];

const lintSources = [
Expand Down
91 changes: 79 additions & 12 deletions src/chromeDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';

import {ChromeDebugAdapter as CoreDebugAdapter, logger, utils as coreUtils, ISourceMapPathOverrides, ChromeDebugSession} from 'vscode-chrome-debug-core';
import {ChromeDebugAdapter as CoreDebugAdapter, logger, utils as coreUtils, ISourceMapPathOverrides, ChromeDebugSession, telemetry} from 'vscode-chrome-debug-core';
import {spawn, ChildProcess, fork, execSync} from 'child_process';
import {Crdp} from 'vscode-chrome-debug-core';
import {DebugProtocol} from 'vscode-debugprotocol';
Expand Down Expand Up @@ -53,7 +53,7 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
args.breakOnLoadStrategy = 'instrument';
}

return super.launch(args).then(() => {
return super.launch(args).then(async () => {
let runtimeExecutable: string;
if (args.runtimeExecutable) {
const re = findExecutable(args.runtimeExecutable);
Expand Down Expand Up @@ -115,12 +115,15 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
chromeArgs.push(launchUrl);
}

this._chromeProc = this.spawnChrome(runtimeExecutable, chromeArgs, chromeEnv, chromeWorkingDir, !!args.runtimeExecutable);
this._chromeProc.on('error', (err) => {
const errMsg = 'Chrome error: ' + err;
logger.error(errMsg);
this.terminateSession(errMsg);
});
this._chromeProc = await this.spawnChrome(runtimeExecutable, chromeArgs, chromeEnv, chromeWorkingDir, !!args.runtimeExecutable,
args.shouldLaunchChromeUnelevated);
if (this._chromeProc) {
this._chromeProc.on('error', (err) => {
const errMsg = 'Chrome error: ' + err;
logger.error(errMsg);
this.terminateSession(errMsg);
});
}

return args.noDebug ? undefined :
this.doAttach(port, launchUrl || args.urlFilter, args.address, args.timeout, undefined, args.extraCRDPChannelPort);
Expand Down Expand Up @@ -222,7 +225,7 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
// Disconnect before killing Chrome, because running "taskkill" when it's paused sometimes doesn't kill it
super.disconnect(args);

if (this._chromeProc && !hadTerminated) {
if ( (this._chromeProc || (!this._chromeProc && this._chromePID)) && !hadTerminated) {
// Only kill Chrome if the 'disconnect' originated from vscode. If we previously terminated
// due to Chrome shutting down, or devtools taking over, don't kill Chrome.
if (coreUtils.getPlatform() === coreUtils.Platform.Windows && this._chromePID) {
Expand All @@ -242,7 +245,7 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
try {
execSync(taskkillCmd);
} catch (e) {}
} else {
} else if (this._chromeProc) {
logger.log('Killing Chrome process');
this._chromeProc.kill('SIGINT');
}
Expand All @@ -260,9 +263,34 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
Promise.resolve();
}

private spawnChrome(chromePath: string, chromeArgs: string[], env: {[key: string]: string}, cwd: string, usingRuntimeExecutable: boolean): ChildProcess {
private async spawnChrome(chromePath: string, chromeArgs: string[], env: {[key: string]: string},
cwd: string, usingRuntimeExecutable: boolean, shouldLaunchUnelevated: boolean): Promise<ChildProcess> {
this.events.emitStepStarted("LaunchTarget.LaunchExe");
if (coreUtils.getPlatform() === coreUtils.Platform.Windows && !usingRuntimeExecutable) {
const platform = coreUtils.getPlatform();
if (platform === coreUtils.Platform.Windows && shouldLaunchUnelevated) {
const semaphoreFile = path.join(os.tmpdir(), 'launchedUnelevatedChromeProcess.id');
if (fs.existsSync(semaphoreFile)) { // remove the previous semaphoreFile if it exists.
fs.unlinkSync(semaphoreFile);
}
const chromeProc = fork(getChromeSpawnHelperPath(),
[`${process.env.windir}\\System32\\cscript.exe`, path.join(__dirname, 'launchUnelevated.js'),
semaphoreFile, chromePath, ...chromeArgs], {});

chromeProc.unref();
await new Promise<void>((resolve, reject) => {
chromeProc.on('message', resolve);
});

const pidStr = await findNewlyLaunchedChromeProcess(semaphoreFile);

if (pidStr) {
logger.log(`Parsed output file and got Chrome PID ${pidStr}`);
this._chromePID = parseInt(pidStr, 10);
}

// Cannot get the real Chrome process, so return null.
return null;
} else if (platform === coreUtils.Platform.Windows && !usingRuntimeExecutable) {
const options = {
execArgv: [],
silent: true
Expand Down Expand Up @@ -403,3 +431,42 @@ function findExecutable(program: string): string | undefined {

return undefined;
}

async function findNewlyLaunchedChromeProcess(semaphoreFile: string): Promise<string> {
const regexPattern = /processid\s+=\s+(\d+)\s*;/i
let lastAccessFileContent: string;
for (let i = 0 ; i < 25; i++) {
if (fs.existsSync(semaphoreFile)) {
lastAccessFileContent = fs.readFileSync(semaphoreFile, {
encoding: 'utf16le'
}).toString();

const lines = lastAccessFileContent.split("\n");

const matchedLines = (lines || []).filter(line => line.match(regexPattern))
if (matchedLines.length > 1) {
throw new Error(`Unexpected semaphore file format ${lines}`);
}

if (matchedLines.length === 1) {
const match = matchedLines[0].match(regexPattern);
return match[1];
}
// else == 0, wait for 200 ms delay and try again.
}
await new Promise<void>((resolve) => {
setTimeout(resolve, 200);
})
}

const error = new Error(`Cannot acquire Chrome process id`);
let telemetryProperties: any = {
semaphoreFileContent: lastAccessFileContent
};

coreUtils.fillErrorDetails(telemetryProperties, error);

telemetry.telemetry.reportEvent('error', telemetryProperties);

return null;
}
1 change: 1 addition & 0 deletions src/chromeDebugInterfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ILaunchRequestArgs extends Core.ILaunchRequestArgs, ICommonRequ
userDataDir?: string;
breakOnLoad?: boolean;
_clientOverlayPausedMessage?: string;
shouldLaunchChromeUnelevated?: boolean;
}

export interface IAttachRequestArgs extends Core.IAttachRequestArgs, ICommonRequestArgs {
Expand Down
28 changes: 28 additions & 0 deletions src/launchUnelevated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var objShell = new ActiveXObject("shell.application");
var objShellWindows = objShell.Windows();
if (objShellWindows != null)
{
var fso = new ActiveXObject("Scripting.FileSystemObject");
var tempFile = WScript.Arguments(0);
var file = fso.OpenTextFile(tempFile, 2, true, 0);
file.WriteLine("");
file.Close();

// Build up the parameters for launching the application and getting the process id
var command = "cmd";
var params = "/c \"wmic /OUTPUT:" + tempFile + " process call create \"";
for (var i = 1; i < WScript.Arguments.length; i++) {
params += WScript.Arguments(i) + " ";
}
params += "\"\"";

for (var i = 0; i < objShellWindows.count; i++)
{
var item = objShellWindows.Item(i);
if (item)
{
item.Document.Application.ShellExecute(command, params);
break;
}
}
}

0 comments on commit 5ca41a7

Please sign in to comment.