/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as cp from 'promisify-child-process'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); export interface ShellCommandOptions { workingDirectory?: string; additionalEnvironmentVariables?: NodeJS.ProcessEnv; commandTitle?: string; argument?: string; } export class ShellExecutionHelper { constructor(protected _outputChannel: vscode.OutputChannel) { } /** * spawns the shell command with arguments and redirects the error and output to ADS output channel */ public async runStreamedCommand(command: string, options?: ShellCommandOptions, sensitiveData: string[] = [], timeout: number = 5 * 60 * 1000): Promise { const stdoutData: string[] = []; let cmdOutputMessage = command; sensitiveData.forEach(element => { cmdOutputMessage = cmdOutputMessage.replace(element, '***'); }); this._outputChannel.appendLine(` > ${cmdOutputMessage}`); const spawnOptions = { cwd: options && options.workingDirectory, env: Object.assign({}, process.env, options && options.additionalEnvironmentVariables), encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, // 10 Mb of output can be captured. shell: true, detached: false, windowsHide: true, timeout: timeout }; try { const child = cp.spawn(command, [], spawnOptions); this._outputChannel.show(); // Add listeners to print stdout and stderr and exit code void child.on('exit', (code: number | null, signal: string | null) => { if (code !== null) { this._outputChannel.appendLine(localize('sqlDatabaseProjects.RunStreamedCommand.ExitedWithCode', " >>> {0} … exited with code: {1}", cmdOutputMessage, code)); } else { this._outputChannel.appendLine(localize('sqlDatabaseProjects.RunStreamedCommand.ExitedWithSignal', " >>> {0} … exited with signal: {1}", cmdOutputMessage, signal)); } }); child.stdout!.on('data', (data: string | Buffer) => { stdoutData.push(data.toString()); ShellExecutionHelper.outputDataChunk(this._outputChannel, data, localize('sqlDatabaseProjects.RunCommand.stdout', " stdout: ")); }); child.stderr!.on('data', (data: string | Buffer) => { ShellExecutionHelper.outputDataChunk(this._outputChannel, data, localize('sqlDatabaseProjects.RunCommand.stderr', " stderr: ")); }); await child; return stdoutData.join(''); } catch (err) { // removing sensitive data from the exception sensitiveData.forEach(element => { err.cmd = err.cmd?.replace(element, '***'); err.message = err.message?.replace(element, '***'); }); throw err; } } private static outputDataChunk(outputChannel: vscode.OutputChannel, data: string | Buffer, header: string): void { data.toString().split(/\r?\n/) .forEach(line => { if (outputChannel) { outputChannel.appendLine(header + line); } }); } }