diff --git a/binding.gyp b/binding.gyp index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..0bb2af7923b6e6f1f0da40cae8067304cd1fea14 100644 --- a/binding.gyp +++ b/binding.gyp @@ -3,7 +3,6 @@ { "target_name": "windows_process_tree", "dependencies": [ - " -#include -#include - -uint32_t GetRawProcessList(std::vector& process_info, - DWORD process_data_flags) { - // Fetch the PID and PPIDs - PROCESSENTRY32 process_entry = { 0 }; - DWORD parent_pid = 0; - uint32_t process_count = 0; - HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - process_entry.dwSize = sizeof(PROCESSENTRY32); - if (Process32First(snapshot_handle, &process_entry)) { - do { - if (process_entry.th32ProcessID != 0) { - ProcessInfo pinfo; - pinfo.pid = process_entry.th32ProcessID; - pinfo.ppid = process_entry.th32ParentProcessID; - - if (MEMORY & process_data_flags) { - GetProcessMemoryUsage(pinfo); - } - - if (COMMANDLINE & process_data_flags) { - GetProcessCommandLine(pinfo); - } - - strcpy(pinfo.name, process_entry.szExeFile); - process_info.push_back(std::move(pinfo)); - process_count++; - } - } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry)); - } - - CloseHandle(snapshot_handle); - return process_count; -} - -void GetProcessMemoryUsage(ProcessInfo& process_info) { - DWORD pid = process_info.pid; - HANDLE hProcess; - PROCESS_MEMORY_COUNTERS pmc; - - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid); - - if (hProcess == NULL) { - return; - } - - if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) { - process_info.memory = (DWORD)pmc.WorkingSetSize; - } - - CloseHandle(hProcess); -} - -// Per documentation, it is not recommended to add or subtract values from the FILETIME -// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows. -// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead. -// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx -ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) { - ULARGE_INTEGER kt, ut; - kt.LowPart = (*kernelTime).dwLowDateTime; - kt.HighPart = (*kernelTime).dwHighDateTime; - - ut.LowPart = (*userTime).dwLowDateTime; - ut.HighPart = (*userTime).dwHighDateTime; - - return kt.QuadPart + ut.QuadPart; -} - -void GetCpuUsage(Cpu& cpu_info, bool first_pass) { - DWORD pid = cpu_info.pid; - HANDLE hProcess; - - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid); - - if (hProcess == NULL) { - return; - } - - FILETIME creationTime, exitTime, kernelTime, userTime; - FILETIME sysIdleTime, sysKernelTime, sysUserTime; - if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime) - && GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) { - if (first_pass) { - cpu_info.initialProcRunTime = GetTotalTime(&kernelTime, &userTime); - cpu_info.initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime); - } else { - ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime); - ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime); - - cpu_info.cpu = 100.0 * (endProcTime - cpu_info.initialProcRunTime) / (endSysTime - cpu_info.initialSystemTime); - } - } else { - cpu_info.cpu = std::numeric_limits::quiet_NaN(); - } - - CloseHandle(hProcess); +/*--------------------------------------------------------------------------------------------- + * 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 +#include +#include + +uint32_t GetRawProcessList(std::vector& process_info, + DWORD process_data_flags) { + // Fetch the PID and PPIDs + PROCESSENTRY32 process_entry = { 0 }; + DWORD parent_pid = 0; + uint32_t process_count = 0; + HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + process_entry.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(snapshot_handle, &process_entry)) { + do { + if (process_entry.th32ProcessID != 0) { + // Value-initialize: `memory` is otherwise stack garbage when the flag is unset. + ProcessInfo pinfo{}; + pinfo.pid = process_entry.th32ProcessID; + pinfo.ppid = process_entry.th32ParentProcessID; + + if (MEMORY & process_data_flags) { + GetProcessMemoryUsage(pinfo); + } + + if (COMMANDLINE & process_data_flags) { + GetProcessCommandLine(pinfo); + } + + strcpy(pinfo.name, process_entry.szExeFile); + process_info.push_back(std::move(pinfo)); + process_count++; + } + } while (Process32Next(snapshot_handle, &process_entry)); + } + + CloseHandle(snapshot_handle); + return process_count; +} + +void GetProcessMemoryUsage(ProcessInfo& process_info) { + DWORD pid = process_info.pid; + HANDLE hProcess; + PROCESS_MEMORY_COUNTERS pmc; + + // PROCESS_VM_READ is never used here -- GetProcessMemoryInfo reads counters the + // kernel keeps, not the address space -- and acquiring it is what EDR scores. + hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid); + ======= "AdditionalOptions": [ "/guard:cf", "/sdl", diff --git a/lib/index.js b/lib/index.js index 9747a7402600cd252859144d32580ed45c8c93f7..8b1f467f2c18289f75419d30a43d6fa3a516c4cc 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,11 +7,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllProcesses = exports.getProcessTree = exports.getProcessCpuUsage = exports.getProcessList = exports.filterProcessList = exports.buildProcessTree = exports.ProcessDataFlag = void 0; const util_1 = require("util"); const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined; +exports.supportedProcessDataFlags = native === undefined ? undefined : native.supportedProcessDataFlags; var ProcessDataFlag; (function (ProcessDataFlag) { ProcessDataFlag[ProcessDataFlag["None"] = 0] = "None"; ProcessDataFlag[ProcessDataFlag["Memory"] = 1] = "Memory"; ProcessDataFlag[ProcessDataFlag["CommandLine"] = 2] = "CommandLine"; + ProcessDataFlag[ProcessDataFlag["CreationTime"] = 4] = "CreationTime"; })(ProcessDataFlag = exports.ProcessDataFlag || (exports.ProcessDataFlag = {})); // requestInProgress is used for any function that uses CreateToolhelp32Snapshot, as multiple calls // to this cannot be done at the same time. @@ -66,11 +68,12 @@ function buildProcessTree(rootPid, processList, maxDepth = MAX_FILTER_DEPTH) { // • the properties are inlined/splatted // • the 'ppid' field is omitted // • the depth of the tree is limited by `maxDepth` - const buildNode = ({ info: { pid, name, memory, commandLine }, children }, depth) => ({ + const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }, depth) => ({ pid, name, memory, commandLine, + creationTimeMs, children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [], }); return buildNode(root, maxDepth); diff --git a/lib/index.ts b/lib/index.ts index f9aa005d9ced9e42885b8a976de5eb5bd61899ee..270967c1d1192b37cb6b095457bf52b213f88e8d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -6,12 +6,15 @@ import { promisify } from 'util'; const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined; +/** The flag bits this compiled addon reports; undefined off win32. */ +export const supportedProcessDataFlags: number | undefined = native?.supportedProcessDataFlags; import { IProcessInfo, IProcessTreeNode, IProcessCpuInfo } from '@vscode/windows-process-tree'; export enum ProcessDataFlag { None = 0, Memory = 1, - CommandLine = 2 + CommandLine = 2, + CreationTime = 4 } type RequestCallback = (processList: IProcessInfo[]) => void; @@ -81,11 +84,12 @@ export function buildProcessTree(rootPid: number, processList: Iterable ({ + const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }: IProcessInfoNode, depth: number): IProcessTreeNode => ({ pid, name, memory, commandLine, + creationTimeMs, children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [], }); diff --git a/src/addon.cc b/src/addon.cc index 9214aff281251e797a70ecb9f6e0b52932a0503f..722edd42ddb4740296bfc47582a181bd6d00c464 100644 --- a/src/addon.cc +++ b/src/addon.cc @@ -53,6 +53,10 @@ void GetProcessCpuUsage(const Napi::CallbackInfo& args) { Napi::Object Init(Napi::Env env, Napi::Object exports) { exports.Set("getProcessList", Napi::Function::New(env, GetProcessList)); exports.Set("getProcessCpuUsage", Napi::Function::New(env, GetProcessCpuUsage)); + // Lets a caller prove THIS BINARY understands CREATIONTIME. The JS enum is + // patched source and says nothing about what the .node was compiled from. + exports.Set("supportedProcessDataFlags", + Napi::Number::New(env, MEMORY | COMMANDLINE | CREATIONTIME)); return exports; } diff --git a/src/process.cc b/src/process.cc index 3eea92077c4d1d433119361d5c432881859131e9..c69442e2917b8474dbabaa1aa5b2c5ce746a56e7 100644 --- a/src/process.cc +++ b/src/process.cc @@ -21,7 +21,7 @@ uint32_t GetRawProcessList(std::vector& process_info, if (Process32First(snapshot_handle, &process_entry)) { do { if (process_entry.th32ProcessID != 0) { - ProcessInfo pinfo; + ProcessInfo pinfo{}; pinfo.pid = process_entry.th32ProcessID; pinfo.ppid = process_entry.th32ParentProcessID; @@ -33,17 +33,43 @@ uint32_t GetRawProcessList(std::vector& process_info, GetProcessCommandLine(pinfo); } + if (CREATIONTIME & process_data_flags) { + GetProcessCreationTime(pinfo); + } + strcpy(pinfo.name, process_entry.szExeFile); process_info.push_back(std::move(pinfo)); process_count++; } - } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry)); + } while (Process32Next(snapshot_handle, &process_entry)); } CloseHandle(snapshot_handle); return process_count; } +void GetProcessCreationTime(ProcessInfo& process_info) { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_info.pid); >>>>>>> 41a13099e8 (fix(windows): make the compiled addon prove its own CreationTime support) + if (hProcess == NULL) { + return; + } + + if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) { + process_info.memory = (DWORD)pmc.WorkingSetSize; + } + + CloseHandle(hProcess); +} + <<<<<<< HEAD +// Per documentation, it is not recommended to add or subtract values from the FILETIME +// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows. +// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx +ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) { + ULARGE_INTEGER kt, ut; + kt.LowPart = (*kernelTime).dwLowDateTime; + kt.HighPart = (*kernelTime).dwHighDateTime; + + ut.LowPart = (*userTime).dwLowDateTime; + ut.HighPart = (*userTime).dwHighDateTime; + + return kt.QuadPart + ut.QuadPart; +} + +void GetCpuUsage(Cpu& cpu_info, bool first_pass) { + DWORD pid = cpu_info.pid; + HANDLE hProcess; + + // GetProcessTimes needs no more than PROCESS_QUERY_LIMITED_INFORMATION. + hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid); + + if (hProcess == NULL) { + return; + } + + FILETIME creationTime, exitTime, kernelTime, userTime; + FILETIME sysIdleTime, sysKernelTime, sysUserTime; + if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime) + && GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) { + if (first_pass) { + cpu_info.initialProcRunTime = GetTotalTime(&kernelTime, &userTime); + cpu_info.initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime); + } else { + ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime); + ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime); + + cpu_info.cpu = 100.0 * (endProcTime - cpu_info.initialProcRunTime) / (endSysTime - cpu_info.initialSystemTime); ======= void GetProcessMemoryUsage(ProcessInfo& process_info) { DWORD pid = process_info.pid; HANDLE hProcess; diff --git a/src/process.h b/src/process.h index 82f8e4bcfa742551e5d874a7632736a7611d7aa7..087bf4ca81768918922a1e26836c70d7aec349ec 100644 --- a/src/process.h +++ b/src/process.h @@ -22,18 +22,22 @@ struct ProcessInfo { DWORD ppid; DWORD memory; // Reported in bytes std::string commandLine; + ULONGLONG creationTimeMs; }; enum ProcessDataFlags { NONE = 0, MEMORY = 1, - COMMANDLINE = 2 + COMMANDLINE = 2, + CREATIONTIME = 4 }; uint32_t GetRawProcessList(std::vector& process_info, DWORD flags); void GetProcessMemoryUsage(ProcessInfo& process_info); +void GetProcessCreationTime(ProcessInfo& process_info); + void GetCpuUsage(Cpu& cpu_info, bool first_run); #endif // SRC_PROCESS_H_ diff --git a/src/process_worker.cc b/src/process_worker.cc index c9e3457a759c1acaa2644231a4917d45aed951f8..89f3339d5dc3b454397941c958b95965afed5788 100644 --- a/src/process_worker.cc +++ b/src/process_worker.cc @@ -43,6 +43,11 @@ void GetProcessesWorker::OnOK() { Napi::String::New(env, pinfo.commandLine)); } + if ((CREATIONTIME & process_data_flags_) && pinfo.creationTimeMs != 0) { + object.Set("creationTimeMs", + Napi::Number::New(env, static_cast(pinfo.creationTimeMs))); >>>>>>> 41a13099e8 (fix(windows): make the compiled addon prove its own CreationTime support) + } + } else { + cpu_info.cpu = std::numeric_limits::quiet_NaN(); + } + <<<<<<< HEAD + CloseHandle(hProcess); } \ No newline at end of file diff --git a/src/process_commandline.cc b/src/process_commandline.cc index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3210c3cfd 100644 --- a/src/process_commandline.cc +++ b/src/process_commandline.cc @@ -1,67 +1,125 @@ -/*--------------------------------------------------------------------------------------------- - * 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 -#include -#include - -bool GetProcessCommandLine(ProcessInfo& process_info) { - HINSTANCE ntdll = GetModuleHandleW(L"ntdll.dll"); - if (!ntdll) { - return false; - } - - decltype(NtQueryInformationProcess)* nt_query_information_process = - reinterpret_cast( - GetProcAddress(ntdll, "NtQueryInformationProcess")); - - if (!nt_query_information_process) { - return false; - } - - PROCESS_BASIC_INFORMATION pbi{}; - PEB peb = {NULL}; - RTL_USER_PROCESS_PARAMETERS process_parameters = {NULL}; - - // Get process handle - DWORD pid = process_info.pid; - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (hProcess == INVALID_HANDLE_VALUE) { - return false; - } - - // Get Process Environment Block (PEB) - NTSTATUS status = nt_query_information_process(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr); - if (NT_SUCCESS(status) && pbi.PebBaseAddress) { - // Read PEB - if (ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) { - // Read the processs parameters - if (ReadProcessMemory(hProcess, peb.ProcessParameters, &process_parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), nullptr)) { - if (process_parameters.CommandLine.Length > 0) { - std::wstring buffer; - buffer.resize(process_parameters.CommandLine.Length / sizeof(wchar_t)); - if (ReadProcessMemory(hProcess, process_parameters.CommandLine.Buffer, &buffer[0], process_parameters.CommandLine.Length, nullptr)) { - int wide_length = static_cast(buffer.length()); - int charcount = WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length, - NULL, 0, NULL, NULL); - if (charcount) { - process_info.commandLine.resize(static_cast(charcount)); - WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length, - &process_info.commandLine[0], charcount, - NULL, NULL); - } - CloseHandle(hProcess); - return true; - } - } - } - } - } - - CloseHandle(hProcess); - return false; -} +/*--------------------------------------------------------------------------------------------- + * 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 +#include +#include + +namespace { + +// Windows 8.1 and later hand back a process's command line as a UNICODE_STRING +// the kernel builds, needing only PROCESS_QUERY_LIMITED_INFORMATION. +// +// There is deliberately no PEB fallback. Reading the command line out of the +// target's address space -- opening it for VM reads and then chaining +// memory reads across every pid on a timer -- is the credential-dumping +// primitive this reader exists to not perform, so it is absent from the binary +// rather than one anomalous NTSTATUS away. Electron's floor is Windows 10, so +// every OS Orca supports has this class; if a hooked ntdll refuses it anyway, +// the command line comes back empty, which callers already handle, instead of +// silently reinstating the primitive on exactly the instrumented machines this +// reader was written for. +const ULONG kProcessCommandLineInformation = 60; + +const NTSTATUS kStatusInfoLengthMismatch = static_cast(0xC0000004L); +const NTSTATUS kStatusBufferTooSmall = static_cast(0xC0000023L); + +// A command line is a UNICODE_STRING, whose Length is a USHORT, so the kernel +// can never need more than the header plus 64 KiB. Refusing anything larger +// keeps a bogus size from throwing bad_alloc out of a scan that has already +// walked most of the table. +const ULONG kMaxCommandLineBytes = sizeof(UNICODE_STRING) + 0xFFFF + sizeof(wchar_t); + +// winternl.h's PROCESSINFOCLASS does not name class 60 and its enumerator range +// stops far short of it, so the class travels as a ULONG rather than a cast enum. +typedef NTSTATUS(NTAPI* NtQueryInformationProcessFn)(HANDLE, ULONG, PVOID, ULONG, PULONG); + +// ntdll ships no import library for this entry point; it has to be resolved. +NtQueryInformationProcessFn ResolveNtQueryInformationProcess() { + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + return nullptr; + } + return reinterpret_cast( + GetProcAddress(ntdll, "NtQueryInformationProcess")); +} + +NtQueryInformationProcessFn NtQueryInformationProcessEntry() { + static NtQueryInformationProcessFn entry = ResolveNtQueryInformationProcess(); + return entry; +} + +bool StoreCommandLineUtf8(ProcessInfo& process_info, const wchar_t* data, size_t wide_length) { + if (wide_length == 0) { + return false; + } + int length = static_cast(wide_length); + int charcount = WideCharToMultiByte(CP_UTF8, 0, data, length, NULL, 0, NULL, NULL); + if (!charcount) { + return false; + } + process_info.commandLine.resize(static_cast(charcount)); + WideCharToMultiByte(CP_UTF8, 0, data, length, &process_info.commandLine[0], charcount, NULL, + NULL); + return true; +} + +} // namespace + +bool GetProcessCommandLine(ProcessInfo& process_info) { + NtQueryInformationProcessFn query = NtQueryInformationProcessEntry(); + if (!query) { + return false; + } + + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_info.pid); + if (process == NULL) { + return false; + } + + ULONG size = 0; + NTSTATUS status = query(process, kProcessCommandLineInformation, nullptr, 0, &size); + if (NT_SUCCESS(status)) { + // Nothing was written, so there is no command line to read. + CloseHandle(process); + return false; + } + if (status != kStatusInfoLengthMismatch && status != kStatusBufferTooSmall) { + CloseHandle(process); + return false; + } + if (size < sizeof(UNICODE_STRING) || size > kMaxCommandLineBytes) { + CloseHandle(process); + return false; + } + + std::vector buffer(size); + status = query(process, kProcessCommandLineInformation, &buffer[0], size, &size); + CloseHandle(process); + if (!NT_SUCCESS(status)) { + return false; + } + + // Header and characters arrive in one allocation, but treat the header as + // untrusted: a hooked ntdll is the case this reader is written for, and an + // unchecked Buffer/Length here would be an over-read encoded straight into JS. + // Bound against buffer.size(), never `size` -- the second query overwrote it. + const UNICODE_STRING* command_line = reinterpret_cast(&buffer[0]); + const unsigned char* begin = &buffer[0]; + const unsigned char* end = begin + buffer.size(); + const unsigned char* chars = reinterpret_cast(command_line->Buffer); + if (chars == nullptr || chars < begin + sizeof(UNICODE_STRING) || chars > end || + command_line->Length > static_cast(end - chars)) { + return false; + } + + // True only when a command line was actually stored, so "empty" and "not + // recovered" stay the same answer they were before this reader replaced the + // PEB read. `src/process.cc` discards the result either way. + return StoreCommandLineUtf8(process_info, command_line->Buffer, + command_line->Length / sizeof(wchar_t)); +} ======= result.Set(i, object); } diff --git a/typings/windows-process-tree.d.ts b/typings/windows-process-tree.d.ts index 08bdac2fdc5ead6f0fcfb5ee5a021e2298c7d523..2c515242d78f9d8b8e63271d86f59d1024d71604 100644 --- a/typings/windows-process-tree.d.ts +++ b/typings/windows-process-tree.d.ts @@ -7,9 +7,17 @@ declare module '@vscode/windows-process-tree' { export enum ProcessDataFlag { None = 0, Memory = 1, - CommandLine = 2 + CommandLine = 2, + CreationTime = 4 } + /** + * The flag bits the compiled addon actually understands, or undefined off + * win32. `ProcessDataFlag` above is source; this is what the binary reports, + * so it is the only way to tell a patched build from a stale prebuilt. + */ + export const supportedProcessDataFlags: number | undefined; + export interface IProcessInfo { pid: number; ppid: number; @@ -24,6 +32,9 @@ declare module '@vscode/windows-process-tree' { * The string returned is at most 512 chars, strings exceeding this length are truncated. */ commandLine?: string; + + /** Process creation time in Unix milliseconds. */ + creationTimeMs?: number; } export interface IProcessCpuInfo extends IProcessInfo { @@ -35,6 +46,7 @@ declare module '@vscode/windows-process-tree' { name: string; memory?: number; commandLine?: string; + creationTimeMs?: number; children: IProcessTreeNode[]; } >>>>>>> 41a13099e8 (fix(windows): make the compiled addon prove its own CreationTime support)