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 1 commit
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_arguments.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,
Arguments = 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this again we should probably name this "CommandLine" instead of Arguments as that's the Windows terminology for the exe+args.

}

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,
arguments: rootProcess.arguments,
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 argument information only when the flag is set', (done) => {
// Arguments should be undefined when flag is not set
native.getProcessList((list) => {
assert.equal(list.every(p => p.arguments === undefined), true);

// Arguments should be a string when flag is set
native.getProcessList((list) => {
assert.equal(list.every(p => typeof p.arguments === 'string'), true);
done();
}, ProcessDataFlag.Arguments);
}, 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].arguments, undefined);
done();
});
});
Expand All @@ -103,6 +117,19 @@ describe('getProcessList', () => {
}, ProcessDataFlag.Memory);
});

it('should return argument 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].arguments, 'string');
// Arguments is "<path to node> <path to mocha> lib/test.js"
assert.equal(list[0].arguments.indexOf('mocha') > 0, true);
assert.equal(list[0].arguments.indexOf('lib/test.js') > 0, true);
done();
}, ProcessDataFlag.Arguments);
});

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.arguments, 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 arguments 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.arguments, 'string');
assert.equal(tree.children.length, 0);
done();
}, ProcessDataFlag.Arguments);
});

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_arguments.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 (ARGUMENTS & *process_data_flags) {
GetProcessCommandLineArguments(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 arguments[512];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice below you trim if the command line goes over, is there a actual maximum value we could use here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual maximum is 8191 (at least for processes run from cmd line), but increasing it beyond ~720 causes the JS tests to error out without any message. I tried switching to allocating the ProcessInfo array on the heap, but that causes a heap out of memory exception when running the tests with the current size.

Earlier I thought I was seeing nondeterministic failures when it was set to 720, so I'm wary of increasing it close to that. My guess is that it failed because it wasn't able to find a piece of contiguous memory large enough, even though its not a particularly large array, ~54k.

};

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

void GetRawProcessList(ProcessInfo process_info[1024], uint32_t* process_count, DWORD* flags);
Expand Down
79 changes: 79 additions & 0 deletions src/process_arguments.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_arguments.h"
#include <windows.h>
#include <winternl.h>
#include <psapi.h>

bool GetProcessCommandLineArguments(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 arguments property
DWORD buffer_size = process_parameters.CommandLine.Length >= sizeof(process_info[*process_count].arguments)
? sizeof(process_info[*process_count].arguments) - sizeof(TCHAR)
: process_parameters.CommandLine.Length;

WideCharToMultiByte(CP_ACP, 0, buffer,
(int)(buffer_size / sizeof(WCHAR)),
process_info[*process_count].arguments, sizeof(process_info[*process_count].arguments),
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_arguments.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_ARGUMENTS_H_
#define SRC_PROCESS_ARGUMENTS_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 GetProcessCommandLineArguments(ProcessInfo process_info[1024], uint32_t *process_count);

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

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

Nan::Set(result, i, Nan::New<v8::Value>(object));
}
v8::Local<v8::Value> argv[] = { result };
Expand Down
7 changes: 4 additions & 3 deletions typings/windows-process-tree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
declare module 'windows-process-tree' {
export enum ProcessDataFlag {
None = 0,
Memory = 1
Memory = 1,
Arguments = 2
}

export interface IProcessInfo {
pid: number;
ppid: number;
name: string;
memory?: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc explaining the unit would be good

arguments?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commandLine

}

export interface IProcessCpuInfo extends IProcessInfo {
Expand All @@ -24,11 +26,10 @@ declare module 'windows-process-tree' {
pid: number;
name: string;
memory?: number;
arguments?: 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should omit the types here as they are redundant and could succumb to bit rot

Expand Down