Skip to content

Add flag for getting command line arguments of processes #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"sources": [
"src/addon.cc",
"src/process.cc",
"src/worker.cc"
"src/worker.cc",
"src/process_commandline.cc"
],
"include_dirs": [
"<!(node -e \"require('nan')\")"
Expand Down
4 changes: 3 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { IProcessInfo, IProcessTreeNode, IProcessCpuInfo } from 'windows-process

export enum ProcessDataFlag {
None = 0,
Memory = 1
Memory = 1,
CommandLine = 2
}

interface IRequest {
Expand Down Expand Up @@ -41,6 +42,7 @@ function buildProcessTree( rootPid: number, processList: IProcessInfo[]): IProce
pid: rootProcess.pid,
name: rootProcess.name,
memory: rootProcess.memory,
commandLine: rootProcess.commandLine,
children: childIndexes.map(c => buildProcessTree(c.pid, processList))
};
}
Expand Down
38 changes: 38 additions & 0 deletions lib/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ describe('getRawProcessList', () => {
}, ProcessDataFlag.Memory);
}, ProcessDataFlag.None);
});

it('should return command line information only when the flag is set', (done) => {
// commandLine should be undefined when flag is not set
native.getProcessList((list) => {
assert.equal(list.every(p => p.commandLine === undefined), true);

// commandLine should be a string when flag is set
native.getProcessList((list) => {
assert.equal(list.every(p => typeof p.commandLine === 'string'), true);
done();
}, ProcessDataFlag.CommandLine);
}, ProcessDataFlag.None);
});
});

describe('getProcessList', () => {
Expand All @@ -89,6 +102,7 @@ describe('getProcessList', () => {
assert.equal(list[0].name, 'node.exe');
assert.equal(list[0].pid, process.pid);
assert.equal(list[0].memory, undefined);
assert.equal(list[0].commandLine, undefined);
done();
});
});
Expand All @@ -103,6 +117,19 @@ describe('getProcessList', () => {
}, ProcessDataFlag.Memory);
});

it('should return command line information only if the flag is set', (done) => {
getProcessList(process.pid, (list) => {
assert.equal(list.length, 1);
assert.equal(list[0].name, 'node.exe');
assert.equal(list[0].pid, process.pid);
assert.equal(typeof list[0].commandLine, 'string');
// CommandLine is "<path to node> <path to mocha> lib/test.js"
assert.equal(list[0].commandLine.indexOf('mocha') > 0, true);
assert.equal(list[0].commandLine.indexOf('lib/test.js') > 0, true);
done();
}, ProcessDataFlag.CommandLine);
});

it('should return a list containing this process\'s child processes', done => {
cps.push(child_process.spawn('cmd.exe'));
pollUntil(() => {
Expand Down Expand Up @@ -162,6 +189,7 @@ describe('getProcessTree', () => {
assert.equal(tree.name, 'node.exe');
assert.equal(tree.pid, process.pid);
assert.equal(tree.memory, undefined);
assert.equal(tree.commandLine, undefined);
assert.equal(tree.children.length, 0);
done();
});
Expand All @@ -177,6 +205,16 @@ describe('getProcessTree', () => {
}, ProcessDataFlag.Memory);
});

it('should return a tree containing this process\'s command line if the flag is set', done => {
getProcessTree(process.pid, (tree) => {
assert.equal(tree.name, 'node.exe');
assert.equal(tree.pid, process.pid);
assert.equal(typeof tree.commandLine, 'string');
assert.equal(tree.children.length, 0);
done();
}, ProcessDataFlag.CommandLine);
});

it('should return a tree containing this process\'s child processes', done => {
cps.push(child_process.spawn('cmd.exe'));
pollUntil(() => {
Expand Down
5 changes: 5 additions & 0 deletions src/process.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

#include "process.h"
#include "process_commandline.h"

#include <tlhelp32.h>
#include <psapi.h>
Expand All @@ -27,6 +28,10 @@ void GetRawProcessList(ProcessInfo process_info[1024], uint32_t* process_count,
GetProcessMemoryUsage(process_info, process_count);
}

if (COMMANDLINE & *process_data_flags) {
GetProcessCommandLine(process_info, process_count);
}

strcpy(process_info[*process_count].name, process_entry.szExeFile);
(*process_count)++;
}
Expand Down
4 changes: 3 additions & 1 deletion src/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ struct ProcessInfo {
DWORD pid;
DWORD ppid;
DWORD memory; // Reported in bytes
TCHAR commandLine[512];
};

enum ProcessDataFlags {
NONE = 0,
MEMORY = 1
MEMORY = 1,
COMMANDLINE = 2
};

void GetRawProcessList(ProcessInfo process_info[1024], uint32_t* process_count, DWORD* flags);
Expand Down
79 changes: 79 additions & 0 deletions src/process_commandline.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

#include "process.h"
#include "process_commandline.h"
#include <windows.h>
#include <winternl.h>
#include <psapi.h>

bool GetProcessCommandLine(ProcessInfo process_info[1024], uint32_t *process_count) {
pfnNtQueryInformationProcess gNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");

PPROCESS_BASIC_INFORMATION pbi = NULL;
PEB peb = {NULL};
RTL_USER_PROCESS_PARAMETERS process_parameters = {NULL};

// Get process handle
DWORD pid = process_info[*process_count].pid;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess == INVALID_HANDLE_VALUE) {
return FALSE;
}

// Get process basic information
HANDLE heap = GetProcessHeap();
DWORD pbi_size = sizeof(PROCESS_BASIC_INFORMATION);
pbi = (PROCESS_BASIC_INFORMATION *)HeapAlloc(heap, HEAP_ZERO_MEMORY, pbi_size);
if (!pbi) {
CloseHandle(hProcess);
return FALSE;
}

// Get Process Environment Block (PEB)
DWORD size_needed;
NTSTATUS status = gNtQueryInformationProcess(hProcess, ProcessBasicInformation, pbi, pbi_size, &size_needed);
if (status >= 0 && pbi->PebBaseAddress) {

// Read PEB
SIZE_T bytes_read;
if (ReadProcessMemory(hProcess, pbi->PebBaseAddress, &peb, sizeof(peb), &bytes_read)) {

// Read the processs parameters
bytes_read = 0;
if (ReadProcessMemory(hProcess, peb.ProcessParameters, &process_parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), &bytes_read)) {
if (process_parameters.CommandLine.Length > 0) {

// Allocate space to read the command line parameter
WCHAR *buffer = NULL;
buffer = (WCHAR *)HeapAlloc(heap, HEAP_ZERO_MEMORY, process_parameters.CommandLine.Length);
if (buffer) {
if (ReadProcessMemory(hProcess, process_parameters.CommandLine.Buffer, buffer, process_parameters.CommandLine.Length, &bytes_read)) {

// Copy only as much as will fit in the commandLine property
DWORD buffer_size = process_parameters.CommandLine.Length >= sizeof(process_info[*process_count].commandLine)
? sizeof(process_info[*process_count].commandLine) - sizeof(TCHAR)
: process_parameters.CommandLine.Length;

WideCharToMultiByte(CP_ACP, 0, buffer,
(int)(buffer_size / sizeof(WCHAR)),
process_info[*process_count].commandLine, sizeof(process_info[*process_count].commandLine),
NULL, NULL);

CloseHandle(hProcess);
HeapFree(heap, 0, pbi);
return true;
}
}
}
}
}
}

CloseHandle(hProcess);
HeapFree(heap, 0, pbi);
return false;
}
21 changes: 21 additions & 0 deletions src/process_commandline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

#ifndef SRC_PROCESS_COMMANDLINE_H_
#define SRC_PROCESS_COMMANDLINE_H_

#include "process.h"
#include <winternl.h>

typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);

bool GetProcessCommandLine(ProcessInfo process_info[1024], uint32_t *process_count);

#endif
6 changes: 6 additions & 0 deletions src/worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ void Worker::HandleOKCallback() {
Nan::New<v8::Number>(process_info[i].memory));
}

if (COMMANDLINE & *process_data_flags) {
Nan::Set(object, Nan::New<v8::String>("commandLine").ToLocalChecked(),
Nan::New<v8::String>(process_info[i].commandLine).ToLocalChecked());
}

Nan::Set(result, i, Nan::New<v8::Value>(object));
}

v8::Local<v8::Value> argv[] = { result };
callback->Call(1, argv);
}
31 changes: 20 additions & 11 deletions typings/windows-process-tree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@
declare module 'windows-process-tree' {
export enum ProcessDataFlag {
None = 0,
Memory = 1
Memory = 1,
CommandLine = 2
}

export interface IProcessInfo {
pid: number;
ppid: number;
name: string;

/**
* The working set size of the process, in bytes.
*/
memory?: number;

/**
* The string returned is at most 512 chars, strings exceeding this length are truncated.
*/
commandLine?: string;
}

export interface IProcessCpuInfo extends IProcessInfo {
Expand All @@ -24,31 +34,30 @@ declare module 'windows-process-tree' {
pid: number;
name: string;
memory?: number;
commandLine?: string;
children: IProcessTreeNode[];
}



/**
* Returns a tree of processes with the rootPid process as the root.
* @param {number} rootPid - The pid of the process that will be the root of the tree.
* @param {(tree: IProcessTreeNode) => void} callback - The callback to use with the returned list of processes.
* @param {ProcessDataFlag} flags - The flags for what process data should be included.
* @param rootPid - The pid of the process that will be the root of the tree.
* @param callback - The callback to use with the returned list of processes.
* @param flags - The flags for what process data should be included.
*/
export function getProcessTree(rootPid: number, callback: (tree: IProcessTreeNode) => void, flags?: ProcessDataFlag): void;

/**
* Returns a list of processes containing the rootPid process and all of its descendants.
* @param {number} rootPid - The pid of the process of interest.
* @param {(processList: IProcessInfo[]) => void} callback - The callback to use with the returned set of processes.
* @param {ProcessDataFlag} flags - The flags for what process data should be included.
* @param rootPid - The pid of the process of interest.
* @param callback - The callback to use with the returned set of processes.
* @param flags - The flags for what process data should be included.
*/
export function getProcessList(rootPid: number, callback: (processList: IProcessInfo[]) => void, flags?: ProcessDataFlag): void;

/**
* Returns the list of processes annotated with cpu usage information.
* @param {processList: IProcessInfo[]} processList - The list of processes.
* @param {(processListWithCpu: IProcessCpuInfo[]) => void} callback - The callback to use with the returned list of processes.
* @param processList - The list of processes.
* @param callback - The callback to use with the returned list of processes.
*/
export function getProcessCpuUsage(processList: IProcessInfo[], callback: (processListWithCpu: IProcessCpuInfo[]) => void);
}