/*
 * Process Hacker -
 *   process tree list
 *
 * Copyright (C) 2010-2016 wj32
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * The process tree list manages a list of tree nodes and handles callback
 * events generated by the underlying treenew control. Retrieval of
 * certain types of process information is also performed here, on the
 * GUI thread (see PH_PROCESS_NODE.ValidMask). This is done for columns
 * that require data not supplied by the process provider.
 */

#include <phapp.h>
#include <proctree.h>
#include <procprv.h>
#include <mapimg.h>
#include <settings.h>
#include <extmgri.h>
#include <colmgr.h>
#include <phplug.h>
#include <cpysave.h>
#include <emenu.h>
#include <verify.h>

typedef enum _PHP_AGGREGATE_TYPE
{
    AggregateTypeFloat,
    AggregateTypeInt32,
    AggregateTypeInt64,
    AggregateTypeIntPtr
} PHP_AGGREGATE_TYPE;

typedef enum _PHP_AGGREGATE_LOCATION
{
    AggregateLocationProcessNode,
    AggregateLocationProcessItem
} PHP_AGGREGATE_LOCATION;

VOID PhpRemoveProcessNode(
    _In_ PPH_PROCESS_NODE ProcessNode
    );

VOID PhpUpdateNeedCyclesInformation(
    VOID
    );

VOID PhpUpdateProcessNodeCycles(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    );

LONG PhpProcessTreeNewPostSortFunction(
    _In_ LONG Result,
    _In_ PVOID Node1,
    _In_ PVOID Node2,
    _In_ PH_SORT_ORDER SortOrder
    );

BOOLEAN NTAPI PhpProcessTreeNewCallback(
    _In_ HWND hwnd,
    _In_ PH_TREENEW_MESSAGE Message,
    _In_opt_ PVOID Parameter1,
    _In_opt_ PVOID Parameter2,
    _In_opt_ PVOID Context
    );

static HWND ProcessTreeListHandle;
static ULONG ProcessTreeListSortColumn;
static PH_SORT_ORDER ProcessTreeListSortOrder;
static PH_CM_MANAGER ProcessTreeListCm;

static PPH_HASH_ENTRY ProcessNodeHashSet[256] = PH_HASH_SET_INIT; // hashtable of all nodes
static PPH_LIST ProcessNodeList; // list of all nodes, used when sorting is enabled
static PPH_LIST ProcessNodeRootList; // list of root nodes

BOOLEAN PhProcessTreeListStateHighlighting = TRUE;
static PPH_POINTER_LIST ProcessNodeStateList = NULL; // list of nodes which need to be processed

static PH_TN_FILTER_SUPPORT FilterSupport;
static BOOLEAN NeedCyclesInformation = FALSE;

static HDC GraphContext = NULL;
static ULONG GraphContextWidth = 0;
static ULONG GraphContextHeight = 0;
static HBITMAP GraphOldBitmap;
static HBITMAP GraphBitmap = NULL;
static PVOID GraphBits = NULL;

VOID PhProcessTreeListInitialization(
    VOID
    )
{
    ProcessNodeList = PhCreateList(40);
    ProcessNodeRootList = PhCreateList(10);
}

VOID PhInitializeProcessTreeList(
    _In_ HWND hwnd
    )
{
    ProcessTreeListHandle = hwnd;
    PhSetControlTheme(ProcessTreeListHandle, L"explorer");
    TreeNew_SetExtendedFlags(hwnd, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT);
    SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, MAXSHORT);

    TreeNew_SetCallback(hwnd, PhpProcessTreeNewCallback, NULL);

    TreeNew_SetMaxId(hwnd, PHPRTLC_MAXIMUM - 1);

    TreeNew_SetRedraw(hwnd, FALSE);

    // Default columns
    PhAddTreeNewColumn(hwnd, PHPRTLC_NAME, TRUE, L"Name", 200, PH_ALIGN_LEFT, -2, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_PID, TRUE, L"PID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_CPU, TRUE, L"CPU", 45, PH_ALIGN_RIGHT, 1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOTOTALRATE, TRUE, L"I/O total rate", 70, PH_ALIGN_RIGHT, 2, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIVATEBYTES, TRUE, L"Private bytes", 70, PH_ALIGN_RIGHT, 3, DT_RIGHT, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_USERNAME, TRUE, L"User name", 140, PH_ALIGN_LEFT, 4, DT_PATH_ELLIPSIS);
    PhAddTreeNewColumn(hwnd, PHPRTLC_DESCRIPTION, TRUE, L"Description", 180, PH_ALIGN_LEFT, 5, 0);

    // Customizable columns (1)
    PhAddTreeNewColumn(hwnd, PHPRTLC_COMPANYNAME, FALSE, L"Company name", 180, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_VERSION, FALSE, L"Version", 100, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_FILENAME, FALSE, L"File name", 180, PH_ALIGN_LEFT, -1, DT_PATH_ELLIPSIS);
    PhAddTreeNewColumn(hwnd, PHPRTLC_COMMANDLINE, FALSE, L"Command line", 180, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKPRIVATEBYTES, FALSE, L"Peak private bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_WORKINGSET, FALSE, L"Working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKWORKINGSET, FALSE, L"Peak working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIVATEWS, FALSE, L"Private WS", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_SHAREDWS, FALSE, L"Shared WS", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_SHAREABLEWS, FALSE, L"Shareable WS", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_VIRTUALSIZE, FALSE, L"Virtual size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKVIRTUALSIZE, FALSE, L"Peak virtual size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEFAULTS, FALSE, L"Page faults", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_SESSIONID, FALSE, L"Session ID", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIORITYCLASS, FALSE, L"Priority class", 100, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_BASEPRIORITY, FALSE, L"Base priority", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);

    // Customizable columns (2)
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_THREADS, FALSE, L"Threads", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_HANDLES, FALSE, L"Handles", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_GDIHANDLES, FALSE, L"GDI handles", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_USERHANDLES, FALSE, L"USER handles", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IORORATE, FALSE, L"I/O read+other rate", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRATE, FALSE, L"I/O write rate", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_INTEGRITY, FALSE, L"Integrity", 100, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOPRIORITY, FALSE, L"I/O priority", 70, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEPRIORITY, FALSE, L"Page priority", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_STARTTIME, FALSE, L"Start time", 100, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_TOTALCPUTIME, FALSE, L"Total CPU time", 90, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_KERNELCPUTIME, FALSE, L"Kernel CPU time", 90, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_USERCPUTIME, FALSE, L"User CPU time", 90, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_VERIFICATIONSTATUS, FALSE, L"Verification status", 70, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_VERIFIEDSIGNER, FALSE, L"Verified signer", 100, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_ASLR, FALSE, L"ASLR", 50, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_RELATIVESTARTTIME, FALSE, L"Relative start time", 180, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_BITS, FALSE, L"Bits", 50, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_ELEVATION, FALSE, L"Elevation", 60, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_WINDOWTITLE, FALSE, L"Window title", 120, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_WINDOWSTATUS, FALSE, L"Window status", 60, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_CYCLES, FALSE, L"Cycles", 110, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_CYCLESDELTA, FALSE, L"Cycles delta", 90, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx2(hwnd, PHPRTLC_CPUHISTORY, FALSE, L"CPU history", 100, PH_ALIGN_LEFT, -1, 0, TN_COLUMN_FLAG_CUSTOMDRAW | TN_COLUMN_FLAG_SORTDESCENDING);
    PhAddTreeNewColumnEx2(hwnd, PHPRTLC_PRIVATEBYTESHISTORY, FALSE, L"Private bytes history", 100, PH_ALIGN_LEFT, -1, 0, TN_COLUMN_FLAG_CUSTOMDRAW | TN_COLUMN_FLAG_SORTDESCENDING);
    PhAddTreeNewColumnEx2(hwnd, PHPRTLC_IOHISTORY, FALSE, L"I/O history", 100, PH_ALIGN_LEFT, -1, 0, TN_COLUMN_FLAG_CUSTOMDRAW | TN_COLUMN_FLAG_SORTDESCENDING);
    PhAddTreeNewColumn(hwnd, PHPRTLC_DEP, FALSE, L"DEP", 100, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_VIRTUALIZED, FALSE, L"Virtualized", 80, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_CONTEXTSWITCHES, FALSE, L"Context switches", 100, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_CONTEXTSWITCHESDELTA, FALSE, L"Context switches delta", 80, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEFAULTSDELTA, FALSE, L"Page faults delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);

    // I/O group columns
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOREADS, FALSE, L"I/O reads", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRITES, FALSE, L"I/O writes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOOTHER, FALSE, L"I/O other", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOREADBYTES, FALSE, L"I/O read bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRITEBYTES, FALSE, L"I/O write bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOOTHERBYTES, FALSE, L"I/O other bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOREADSDELTA, FALSE, L"I/O reads delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRITESDELTA, FALSE, L"I/O writes delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOOTHERDELTA, FALSE, L"I/O other delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);

    // Customizable columns (3)
    PhAddTreeNewColumn(hwnd, PHPRTLC_OSCONTEXT, FALSE, L"OS context", 100, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEDPOOL, FALSE, L"Paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKPAGEDPOOL, FALSE, L"Peak paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_NONPAGEDPOOL, FALSE, L"Non-paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKNONPAGEDPOOL, FALSE, L"Peak non-paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_MINIMUMWORKINGSET, FALSE, L"Minimum working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_MAXIMUMWORKINGSET, FALSE, L"Maximum working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIVATEBYTESDELTA, FALSE, L"Private bytes delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);
    PhAddTreeNewColumn(hwnd, PHPRTLC_SUBSYSTEM, FALSE, L"Subsystem", 110, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_PACKAGENAME, FALSE, L"Package name", 160, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_APPID, FALSE, L"App ID", 160, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumn(hwnd, PHPRTLC_DPIAWARENESS, FALSE, L"DPI awareness", 110, PH_ALIGN_LEFT, -1, 0);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_CFGUARD, FALSE, L"CF Guard", 70, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_TIMESTAMP, FALSE, L"Time stamp", 140, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_FILEMODIFIEDTIME, FALSE, L"File modified time", 140, PH_ALIGN_LEFT, -1, 0, TRUE);
    PhAddTreeNewColumnEx(hwnd, PHPRTLC_FILESIZE, FALSE, L"File size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE);

    TreeNew_SetRedraw(hwnd, TRUE);

    TreeNew_SetTriState(hwnd, TRUE);
    TreeNew_SetSort(hwnd, 0, NoSortOrder);

    PhCmInitializeManager(&ProcessTreeListCm, hwnd, PHPRTLC_MAXIMUM, PhpProcessTreeNewPostSortFunction);

    if (PhPluginsEnabled)
    {
        PH_PLUGIN_TREENEW_INFORMATION treeNewInfo;

        treeNewInfo.TreeNewHandle = hwnd;
        treeNewInfo.CmData = &ProcessTreeListCm;
        PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing), &treeNewInfo);
    }

    PhInitializeTreeNewFilterSupport(&FilterSupport, hwnd, ProcessNodeList);
}

VOID PhLoadSettingsProcessTreeList(
    VOID
    )
{
    PPH_STRING settings;
    PPH_STRING sortSettings;

    settings = PhGetStringSetting(L"ProcessTreeListColumns");
    sortSettings = PhGetStringSetting(L"ProcessTreeListSort");
    PhCmLoadSettingsEx(ProcessTreeListHandle, &ProcessTreeListCm, 0, &settings->sr, &sortSettings->sr);
    PhDereferenceObject(settings);
    PhDereferenceObject(sortSettings);

    if (PhGetIntegerSetting(L"EnableInstantTooltips"))
    {
        SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_INITIAL, 0);
    }

    PhpUpdateNeedCyclesInformation();
}

VOID PhSaveSettingsProcessTreeList(
    VOID
    )
{
    PPH_STRING settings;
    PPH_STRING sortSettings;

    settings = PhCmSaveSettingsEx(ProcessTreeListHandle, &ProcessTreeListCm, 0, &sortSettings);
    PhSetStringSetting2(L"ProcessTreeListColumns", &settings->sr);
    PhSetStringSetting2(L"ProcessTreeListSort", &sortSettings->sr);
    PhDereferenceObject(settings);
    PhDereferenceObject(sortSettings);
}

VOID PhReloadSettingsProcessTreeList(
    VOID
    )
{
    SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_INITIAL,
        PhGetIntegerSetting(L"EnableInstantTooltips") ? 0 : -1);
}

struct _PH_TN_FILTER_SUPPORT *PhGetFilterSupportProcessTreeList(
    VOID
    )
{
    return &FilterSupport;
}

FORCEINLINE BOOLEAN PhCompareProcessNode(
    _In_ PPH_PROCESS_NODE Value1,
    _In_ PPH_PROCESS_NODE Value2
    )
{
    return Value1->ProcessId == Value2->ProcessId;
}

FORCEINLINE ULONG PhHashProcessNode(
    _In_ PPH_PROCESS_NODE Value
    )
{
    return HandleToUlong(Value->ProcessId) / 4;
}

FORCEINLINE BOOLEAN PhpValidateParentCreateTime(
    _In_ PPH_PROCESS_NODE Child,
    _In_ PPH_PROCESS_NODE Parent
    )
{
    return
        PH_IS_FAKE_PROCESS_ID(Child->ProcessId) ||
        Parent->ProcessItem->CreateTime.QuadPart <= Child->ProcessItem->CreateTime.QuadPart;
}

PPH_PROCESS_NODE PhAddProcessNode(
    _In_ PPH_PROCESS_ITEM ProcessItem,
    _In_ ULONG RunId
    )
{
    PPH_PROCESS_NODE processNode;
    PPH_PROCESS_NODE parentNode;
    ULONG i;

    processNode = PhAllocate(PhEmGetObjectSize(EmProcessNodeType, sizeof(PH_PROCESS_NODE)));
    memset(processNode, 0, sizeof(PH_PROCESS_NODE));
    PhInitializeTreeNewNode(&processNode->Node);

    if (PhProcessTreeListStateHighlighting && RunId != 1)
    {
        PhChangeShStateTn(
            &processNode->Node,
            &processNode->ShState,
            &ProcessNodeStateList,
            NewItemState,
            PhCsColorNew,
            NULL
            );
    }

    processNode->ProcessId = ProcessItem->ProcessId;
    processNode->ProcessItem = ProcessItem;
    PhReferenceObject(ProcessItem);

    memset(processNode->TextCache, 0, sizeof(PH_STRINGREF) * PHPRTLC_MAXIMUM);
    processNode->Node.TextCache = processNode->TextCache;
    processNode->Node.TextCacheSize = PHPRTLC_MAXIMUM;

    processNode->Children = PhCreateList(1);

    // Find this process' parent and add the process to it if we found it.
    if (
        (parentNode = PhFindProcessNode(ProcessItem->ParentProcessId)) &&
        PhpValidateParentCreateTime(processNode, parentNode)
        )
    {
        PhAddItemList(parentNode->Children, processNode);
        processNode->Parent = parentNode;
    }
    else
    {
        // No parent, add to root list.
        processNode->Parent = NULL;
        PhAddItemList(ProcessNodeRootList, processNode);
    }

    // Find this process' children and move them to this node.

    for (i = 0; i < ProcessNodeRootList->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNodeRootList->Items[i];

        if (
            node != processNode && // for cases where the parent PID = PID (e.g. System Idle Process)
            node->ProcessItem->ParentProcessId == ProcessItem->ProcessId &&
            PhpValidateParentCreateTime(node, processNode)
            )
        {
            node->Parent = processNode;
            PhAddItemList(processNode->Children, node);
        }
    }

    for (i = 0; i < processNode->Children->Count; i++)
    {
        PhRemoveItemList(
            ProcessNodeRootList,
            PhFindItemList(ProcessNodeRootList, processNode->Children->Items[i])
            );
    }

    PhAddEntryHashSet(
        ProcessNodeHashSet,
        PH_HASH_SET_SIZE(ProcessNodeHashSet),
        &processNode->HashEntry,
        PhHashProcessNode(processNode)
        );
    PhAddItemList(ProcessNodeList, processNode);

    if (PhCsCollapseServicesOnStart)
    {
        static PH_STRINGREF servicesBaseName = PH_STRINGREF_INIT(L"\\services.exe");
        static BOOLEAN servicesFound = FALSE;
        static PPH_STRING servicesFileName = NULL;

        if (!servicesFound)
        {
            if (!servicesFileName)
            {
                PPH_STRING systemDirectory;

                systemDirectory = PhGetSystemDirectory();
                servicesFileName = PhConcatStringRef2(&systemDirectory->sr, &servicesBaseName);
                PhDereferenceObject(systemDirectory);
            }

            // If this process is services.exe, collapse the node and free the string.
            if (
                ProcessItem->FileName &&
                PhEqualString(ProcessItem->FileName, servicesFileName, TRUE)
                )
            {
                processNode->Node.Expanded = FALSE;
                PhDereferenceObject(servicesFileName);
                servicesFileName = NULL;
                servicesFound = TRUE;
            }
        }
    }

    if (WindowsVersion >= WINDOWS_7 && PhEnableCycleCpuUsage && ProcessItem->ProcessId == INTERRUPTS_PROCESS_ID)
        PhInitializeStringRef(&processNode->DescriptionText, L"Interrupts and DPCs");

    if (FilterSupport.FilterList)
        processNode->Node.Visible = PhApplyTreeNewFiltersToNode(&FilterSupport, &processNode->Node);

    PhEmCallObjectOperation(EmProcessNodeType, processNode, EmObjectCreate);

    TreeNew_NodesStructured(ProcessTreeListHandle);

    return processNode;
}

PPH_PROCESS_NODE PhFindProcessNode(
    _In_ HANDLE ProcessId
    )
{
    PH_PROCESS_NODE lookupNode;
    PPH_HASH_ENTRY entry;
    PPH_PROCESS_NODE node;

    lookupNode.ProcessId = ProcessId;
    entry = PhFindEntryHashSet(
        ProcessNodeHashSet,
        PH_HASH_SET_SIZE(ProcessNodeHashSet),
        PhHashProcessNode(&lookupNode)
        );

    for (; entry; entry = entry->Next)
    {
        node = CONTAINING_RECORD(entry, PH_PROCESS_NODE, HashEntry);

        if (PhCompareProcessNode(&lookupNode, node))
            return node;
    }

    return NULL;
}

VOID PhRemoveProcessNode(
    _In_ PPH_PROCESS_NODE ProcessNode
    )
{
    // Remove from the hashtable here to avoid problems in case the key is re-used.
    PhRemoveEntryHashSet(ProcessNodeHashSet, PH_HASH_SET_SIZE(ProcessNodeHashSet), &ProcessNode->HashEntry);

    if (PhProcessTreeListStateHighlighting)
    {
        PhChangeShStateTn(
            &ProcessNode->Node,
            &ProcessNode->ShState,
            &ProcessNodeStateList,
            RemovingItemState,
            PhCsColorRemoved,
            ProcessTreeListHandle
            );
    }
    else
    {
        PhpRemoveProcessNode(ProcessNode);
    }
}

VOID PhpRemoveProcessNode(
    _In_ PPH_PROCESS_NODE ProcessNode
    )
{
    ULONG index;
    ULONG i;

    PhEmCallObjectOperation(EmProcessNodeType, ProcessNode, EmObjectDelete);

    if (ProcessNode->Parent)
    {
        // Remove the node from its parent.

        if ((index = PhFindItemList(ProcessNode->Parent->Children, ProcessNode)) != -1)
            PhRemoveItemList(ProcessNode->Parent->Children, index);
    }
    else
    {
        // Remove the node from the root list.

        if ((index = PhFindItemList(ProcessNodeRootList, ProcessNode)) != -1)
            PhRemoveItemList(ProcessNodeRootList, index);
    }

    // Move the node's children to the root list.
    for (i = 0; i < ProcessNode->Children->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNode->Children->Items[i];

        node->Parent = NULL;
        PhAddItemList(ProcessNodeRootList, node);
    }

    // Remove from list and cleanup.

    if ((index = PhFindItemList(ProcessNodeList, ProcessNode)) != -1)
        PhRemoveItemList(ProcessNodeList, index);

    PhDereferenceObject(ProcessNode->Children);

    PhClearReference(&ProcessNode->WindowText);
    PhClearReference(&ProcessNode->AppIdText);

    PhClearReference(&ProcessNode->TooltipText);

    PhClearReference(&ProcessNode->IoTotalRateText);
    PhClearReference(&ProcessNode->PrivateBytesText);
    PhClearReference(&ProcessNode->PeakPrivateBytesText);
    PhClearReference(&ProcessNode->WorkingSetText);
    PhClearReference(&ProcessNode->PeakWorkingSetText);
    PhClearReference(&ProcessNode->PrivateWsText);
    PhClearReference(&ProcessNode->SharedWsText);
    PhClearReference(&ProcessNode->ShareableWsText);
    PhClearReference(&ProcessNode->VirtualSizeText);
    PhClearReference(&ProcessNode->PeakVirtualSizeText);
    PhClearReference(&ProcessNode->PageFaultsText);
    PhClearReference(&ProcessNode->IoRoRateText);
    PhClearReference(&ProcessNode->IoWRateText);
    PhClearReference(&ProcessNode->StartTimeText);
    PhClearReference(&ProcessNode->TotalCpuTimeText);
    PhClearReference(&ProcessNode->KernelCpuTimeText);
    PhClearReference(&ProcessNode->UserCpuTimeText);
    PhClearReference(&ProcessNode->RelativeStartTimeText);
    PhClearReference(&ProcessNode->WindowTitleText);
    PhClearReference(&ProcessNode->CyclesText);
    PhClearReference(&ProcessNode->CyclesDeltaText);
    PhClearReference(&ProcessNode->ContextSwitchesText);
    PhClearReference(&ProcessNode->ContextSwitchesDeltaText);
    PhClearReference(&ProcessNode->PageFaultsDeltaText);

    for (i = 0; i < PHPRTLC_IOGROUP_COUNT; i++)
        PhClearReference(&ProcessNode->IoGroupText[i]);

    PhClearReference(&ProcessNode->PagedPoolText);
    PhClearReference(&ProcessNode->PeakPagedPoolText);
    PhClearReference(&ProcessNode->NonPagedPoolText);
    PhClearReference(&ProcessNode->PeakNonPagedPoolText);
    PhClearReference(&ProcessNode->MinimumWorkingSetText);
    PhClearReference(&ProcessNode->MaximumWorkingSetText);
    PhClearReference(&ProcessNode->PrivateBytesDeltaText);
    PhClearReference(&ProcessNode->TimeStampText);
    PhClearReference(&ProcessNode->FileModifiedTimeText);
    PhClearReference(&ProcessNode->FileSizeText);

    PhDeleteGraphBuffers(&ProcessNode->CpuGraphBuffers);
    PhDeleteGraphBuffers(&ProcessNode->PrivateGraphBuffers);
    PhDeleteGraphBuffers(&ProcessNode->IoGraphBuffers);

    PhDereferenceObject(ProcessNode->ProcessItem);

    PhFree(ProcessNode);

    TreeNew_NodesStructured(ProcessTreeListHandle);
}

VOID PhUpdateProcessNode(
    _In_ PPH_PROCESS_NODE ProcessNode
    )
{
    memset(ProcessNode->TextCache, 0, sizeof(PH_STRINGREF) * PHPRTLC_MAXIMUM);

    if (ProcessNode->TooltipText)
    {
        PhDereferenceObject(ProcessNode->TooltipText);
        ProcessNode->TooltipText = NULL;
    }

    PhInvalidateTreeNewNode(&ProcessNode->Node, TN_CACHE_COLOR | TN_CACHE_ICON);
    TreeNew_InvalidateNode(ProcessTreeListHandle, &ProcessNode->Node);
}

VOID PhTickProcessNodes(
    VOID
    )
{
    ULONG i;
    PH_TREENEW_VIEW_PARTS viewParts;
    BOOLEAN fullyInvalidated;
    RECT rect;

    // Text invalidation, node updates

    for (i = 0; i < ProcessNodeList->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNodeList->Items[i];

        // The name and PID never change, so we don't invalidate that.
        memset(&node->TextCache[2], 0, sizeof(PH_STRINGREF) * (PHPRTLC_MAXIMUM - 2));
        node->ValidMask &= PHPN_OSCONTEXT | PHPN_IMAGE | PHPN_DPIAWARENESS; // Items that always remain valid

        // Invalidate graph buffers.
        node->CpuGraphBuffers.Valid = FALSE;
        node->PrivateGraphBuffers.Valid = FALSE;
        node->IoGraphBuffers.Valid = FALSE;

        // Updates cycles if necessary.
        if (NeedCyclesInformation)
            PhpUpdateProcessNodeCycles(node);
    }

    fullyInvalidated = FALSE;

    if (ProcessTreeListSortOrder != NoSortOrder)
    {
        // Force a rebuild to sort the items.
        TreeNew_NodesStructured(ProcessTreeListHandle);
        fullyInvalidated = TRUE;
    }

    // State highlighting
    PH_TICK_SH_STATE_TN(PH_PROCESS_NODE, ShState, ProcessNodeStateList, PhpRemoveProcessNode, PhCsHighlightingDuration, ProcessTreeListHandle, TRUE, &fullyInvalidated);

    if (!fullyInvalidated)
    {
        // The first column doesn't need to be invalidated because the process name never changes, and
        // icon changes are handled by the modified event. This small optimization can save more than
        // 10 million cycles per update (on my machine).
        TreeNew_GetViewParts(ProcessTreeListHandle, &viewParts);
        rect.left = viewParts.NormalLeft;
        rect.top = viewParts.HeaderHeight;
        rect.right = viewParts.ClientRect.right - viewParts.VScrollWidth;
        rect.bottom = viewParts.ClientRect.bottom;
        InvalidateRect(ProcessTreeListHandle, &rect, FALSE);
    }
}

static VOID PhpNeedGraphContext(
    _In_ HDC hdc,
    _In_ ULONG Width,
    _In_ ULONG Height
    )
{
    BITMAPINFOHEADER header;

    // If we already have a graph context and it's the right size, then return immediately.
    if (GraphContextWidth == Width && GraphContextHeight == Height)
        return;

    if (GraphContext)
    {
        // The original bitmap must be selected back into the context, otherwise
        // the bitmap can't be deleted.
        SelectObject(GraphContext, GraphBitmap);
        DeleteObject(GraphBitmap);
        DeleteDC(GraphContext);

        GraphContext = NULL;
        GraphBitmap = NULL;
        GraphBits = NULL;
    }

    GraphContext = CreateCompatibleDC(hdc);

    memset(&header, 0, sizeof(BITMAPINFOHEADER));
    header.biSize = sizeof(BITMAPINFOHEADER);
    header.biWidth = Width;
    header.biHeight = Height;
    header.biPlanes = 1;
    header.biBitCount = 32;
    GraphBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &GraphBits, NULL, 0);
    GraphOldBitmap = SelectObject(GraphContext, GraphBitmap);
}

static BOOLEAN PhpFormatInt32GroupDigits(
    _In_ ULONG Value,
    _Out_writes_bytes_(BufferLength) PWCHAR Buffer,
    _In_ ULONG BufferLength,
    _Out_opt_ PPH_STRINGREF String
    )
{
    PH_FORMAT format;
    SIZE_T returnLength;

    PhInitFormatU(&format, Value);
    format.Type |= FormatGroupDigits;

    if (PhFormatToBuffer(&format, 1, Buffer, BufferLength, &returnLength))
    {
        if (String)
        {
            String->Buffer = Buffer;
            String->Length = returnLength - sizeof(WCHAR);
        }

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

FORCEINLINE PVOID PhpFieldForAggregate(
    _In_ PPH_PROCESS_NODE ProcessNode,
    _In_ PHP_AGGREGATE_LOCATION Location,
    _In_ SIZE_T FieldOffset
    )
{
    PVOID object;

    switch (Location)
    {
    case AggregateLocationProcessNode:
        object = ProcessNode;
        break;
    case AggregateLocationProcessItem:
        object = ProcessNode->ProcessItem;
        break;
    default:
        PhRaiseStatus(STATUS_INVALID_PARAMETER);
    }

    return PTR_ADD_OFFSET(object, FieldOffset);
}

FORCEINLINE VOID PhpAccumulateField(
    _Inout_ PVOID Accumulator,
    _In_ PVOID Value,
    _In_ PHP_AGGREGATE_TYPE Type
    )
{
    switch (Type)
    {
    case AggregateTypeFloat:
        *(PFLOAT)Accumulator += *(PFLOAT)Value;
        break;
    case AggregateTypeInt32:
        *(PULONG)Accumulator += *(PULONG)Value;
        break;
    case AggregateTypeInt64:
        *(PULONG64)Accumulator += *(PULONG64)Value;
        break;
    case AggregateTypeIntPtr:
        *(PULONG_PTR)Accumulator += *(PULONG_PTR)Value;
        break;
    }
}

static VOID PhpAggregateField(
    _In_ PPH_PROCESS_NODE ProcessNode,
    _In_ PHP_AGGREGATE_TYPE Type,
    _In_ PHP_AGGREGATE_LOCATION Location,
    _In_ SIZE_T FieldOffset,
    _Inout_ PVOID AggregatedValue
    )
{
    ULONG i;

    PhpAccumulateField(AggregatedValue, PhpFieldForAggregate(ProcessNode, Location, FieldOffset), Type);

    for (i = 0; i < ProcessNode->Children->Count; i++)
    {
        PhpAggregateField(ProcessNode->Children->Items[i], Type, Location, FieldOffset, AggregatedValue);
    }
}

static VOID PhpAggregateFieldIfNeeded(
    _In_ PPH_PROCESS_NODE ProcessNode,
    _In_ PHP_AGGREGATE_TYPE Type,
    _In_ PHP_AGGREGATE_LOCATION Location,
    _In_ SIZE_T FieldOffset,
    _Inout_ PVOID AggregatedValue
    )
{
    if (!PhCsPropagateCpuUsage || ProcessNode->Node.Expanded || ProcessTreeListSortOrder != NoSortOrder)
    {
        PhpAccumulateField(AggregatedValue, PhpFieldForAggregate(ProcessNode, Location, FieldOffset), Type);
    }
    else
    {
        PhpAggregateField(ProcessNode, Type, Location, FieldOffset, AggregatedValue);
    }
}

static VOID PhpUpdateProcessNodeWsCounters(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_WSCOUNTERS))
    {
        BOOLEAN success = FALSE;
        HANDLE processHandle;

        if (NT_SUCCESS(PhOpenProcess(
            &processHandle,
            PROCESS_QUERY_INFORMATION,
            ProcessNode->ProcessItem->ProcessId
            )))
        {
            if (NT_SUCCESS(PhGetProcessWsCounters(
                processHandle,
                &ProcessNode->WsCounters
                )))
                success = TRUE;

            NtClose(processHandle);
        }

        if (!success)
            memset(&ProcessNode->WsCounters, 0, sizeof(PH_PROCESS_WS_COUNTERS));

        ProcessNode->ValidMask |= PHPN_WSCOUNTERS;
    }
}

static VOID PhpUpdateProcessNodeGdiUserHandles(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_GDIUSERHANDLES))
    {
        if (ProcessNode->ProcessItem->QueryHandle)
        {
            ProcessNode->GdiHandles = GetGuiResources(ProcessNode->ProcessItem->QueryHandle, GR_GDIOBJECTS);
            ProcessNode->UserHandles = GetGuiResources(ProcessNode->ProcessItem->QueryHandle, GR_USEROBJECTS);
        }
        else
        {
            ProcessNode->GdiHandles = 0;
            ProcessNode->UserHandles = 0;
        }

        ProcessNode->ValidMask |= PHPN_GDIUSERHANDLES;
    }
}

static VOID PhpUpdateProcessNodeIoPagePriority(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_IOPAGEPRIORITY))
    {
        if (ProcessNode->ProcessItem->QueryHandle)
        {
            if (!NT_SUCCESS(PhGetProcessIoPriority(ProcessNode->ProcessItem->QueryHandle, &ProcessNode->IoPriority)))
                ProcessNode->IoPriority = -1;
            if (!NT_SUCCESS(PhGetProcessPagePriority(ProcessNode->ProcessItem->QueryHandle, &ProcessNode->PagePriority)))
                ProcessNode->PagePriority = -1;
        }
        else
        {
            ProcessNode->IoPriority = -1;
            ProcessNode->PagePriority = -1;
        }

        ProcessNode->ValidMask |= PHPN_IOPAGEPRIORITY;
    }
}

static VOID PhpUpdateProcessNodeWindow(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_WINDOW))
    {
        ProcessNode->WindowHandle = PhGetProcessMainWindow(ProcessNode->ProcessId, ProcessNode->ProcessItem->QueryHandle);

        PhClearReference(&ProcessNode->WindowText);

        if (ProcessNode->WindowHandle)
        {
            PhGetWindowTextEx(ProcessNode->WindowHandle, PH_GET_WINDOW_TEXT_INTERNAL, &ProcessNode->WindowText);
            ProcessNode->WindowHung = !!IsHungAppWindow(ProcessNode->WindowHandle);
        }

        ProcessNode->ValidMask |= PHPN_WINDOW;
    }
}

static VOID PhpUpdateProcessNodeDepStatus(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_DEPSTATUS))
    {
        HANDLE processHandle;
        ULONG depStatus;

        depStatus = 0;

#ifdef _WIN64
        if (ProcessNode->ProcessItem->IsWow64)
#else
        if (TRUE)
#endif
        {
            if (NT_SUCCESS(PhOpenProcess(
                &processHandle,
                PROCESS_QUERY_INFORMATION,
                ProcessNode->ProcessItem->ProcessId
                )))
            {
                PhGetProcessDepStatus(processHandle, &depStatus);
                NtClose(processHandle);
            }
        }
        else
        {
            if (ProcessNode->ProcessItem->QueryHandle)
                depStatus = PH_PROCESS_DEP_ENABLED | PH_PROCESS_DEP_PERMANENT;
        }

        ProcessNode->DepStatus = depStatus;

        ProcessNode->ValidMask |= PHPN_DEPSTATUS;
    }
}

static VOID PhpUpdateProcessNodeToken(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_TOKEN))
    {
        HANDLE tokenHandle;

        ProcessNode->VirtualizationAllowed = FALSE;
        ProcessNode->VirtualizationEnabled = FALSE;

        if (WINDOWS_HAS_UAC && ProcessNode->ProcessItem->QueryHandle)
        {
            if (NT_SUCCESS(PhOpenProcessToken(
                ProcessNode->ProcessItem->QueryHandle,
                TOKEN_QUERY,
                &tokenHandle
                )))
            {
                if (NT_SUCCESS(PhGetTokenIsVirtualizationAllowed(tokenHandle, &ProcessNode->VirtualizationAllowed)) &&
                    ProcessNode->VirtualizationAllowed)
                {
                    if (!NT_SUCCESS(PhGetTokenIsVirtualizationEnabled(tokenHandle, &ProcessNode->VirtualizationEnabled)))
                    {
                        ProcessNode->VirtualizationAllowed = FALSE; // display N/A on error
                    }
                }

                NtClose(tokenHandle);
            }
        }

        ProcessNode->ValidMask |= PHPN_TOKEN;
    }
}

static VOID PhpUpdateProcessOsContext(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_OSCONTEXT))
    {
        HANDLE processHandle;

        if (WindowsVersion >= WINDOWS_7)
        {
            if (NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessNode->ProcessId)))
            {
                if (NT_SUCCESS(PhGetProcessSwitchContext(processHandle, &ProcessNode->OsContextGuid)))
                {
                    if (memcmp(&ProcessNode->OsContextGuid, &WINTHRESHOLD_CONTEXT_GUID, sizeof(GUID)) == 0)
                        ProcessNode->OsContextVersion = WINDOWS_10;
                    else if (memcmp(&ProcessNode->OsContextGuid, &WINBLUE_CONTEXT_GUID, sizeof(GUID)) == 0)
                        ProcessNode->OsContextVersion = WINDOWS_8_1;
                    else if (memcmp(&ProcessNode->OsContextGuid, &WIN8_CONTEXT_GUID, sizeof(GUID)) == 0)
                        ProcessNode->OsContextVersion = WINDOWS_8;
                    else if (memcmp(&ProcessNode->OsContextGuid, &WIN7_CONTEXT_GUID, sizeof(GUID)) == 0)
                        ProcessNode->OsContextVersion = WINDOWS_7;
                    else if (memcmp(&ProcessNode->OsContextGuid, &VISTA_CONTEXT_GUID, sizeof(GUID)) == 0)
                        ProcessNode->OsContextVersion = WINDOWS_VISTA;
                    else if (memcmp(&ProcessNode->OsContextGuid, &XP_CONTEXT_GUID, sizeof(GUID)) == 0)
                        ProcessNode->OsContextVersion = WINDOWS_XP;
                }

                NtClose(processHandle);
            }
        }

        ProcessNode->ValidMask |= PHPN_OSCONTEXT;
    }
}

static VOID PhpUpdateProcessNodeQuotaLimits(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_QUOTALIMITS))
    {
        QUOTA_LIMITS quotaLimits;

        if (ProcessNode->ProcessItem->QueryHandle && NT_SUCCESS(NtQueryInformationProcess(
            ProcessNode->ProcessItem->QueryHandle,
            ProcessQuotaLimits,
            &quotaLimits,
            sizeof(QUOTA_LIMITS),
            NULL
            )))
        {
            ProcessNode->MinimumWorkingSetSize = quotaLimits.MinimumWorkingSetSize;
            ProcessNode->MaximumWorkingSetSize = quotaLimits.MaximumWorkingSetSize;
        }
        else
        {
            ProcessNode->MinimumWorkingSetSize = 0;
            ProcessNode->MaximumWorkingSetSize = 0;
        }

        ProcessNode->ValidMask |= PHPN_QUOTALIMITS;
    }
}

static VOID PhpUpdateProcessNodeImage(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_IMAGE))
    {
        HANDLE processHandle;
        PROCESS_BASIC_INFORMATION basicInfo;
        PVOID imageBaseAddress;
        PH_REMOTE_MAPPED_IMAGE mappedImage;

        if (NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessNode->ProcessId)))
        {
            if (NT_SUCCESS(PhGetProcessBasicInformation(processHandle, &basicInfo)))
            {
                if (NT_SUCCESS(NtReadVirtualMemory(
                    processHandle,
                    PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ImageBaseAddress)),
                    &imageBaseAddress,
                    sizeof(PVOID),
                    NULL
                    )))
                {
                    if (NT_SUCCESS(PhLoadRemoteMappedImage(processHandle, imageBaseAddress, &mappedImage)))
                    {
                        ProcessNode->ImageTimeDateStamp = mappedImage.NtHeaders->FileHeader.TimeDateStamp;
                        ProcessNode->ImageCharacteristics = mappedImage.NtHeaders->FileHeader.Characteristics;

                        if (mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
                        {
                            ProcessNode->ImageSubsystem = ((PIMAGE_OPTIONAL_HEADER32)&mappedImage.NtHeaders->OptionalHeader)->Subsystem;
                            ProcessNode->ImageDllCharacteristics = ((PIMAGE_OPTIONAL_HEADER32)&mappedImage.NtHeaders->OptionalHeader)->DllCharacteristics;
                        }
                        else if (mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
                        {
                            ProcessNode->ImageSubsystem = ((PIMAGE_OPTIONAL_HEADER64)&mappedImage.NtHeaders->OptionalHeader)->Subsystem;
                            ProcessNode->ImageDllCharacteristics = ((PIMAGE_OPTIONAL_HEADER64)&mappedImage.NtHeaders->OptionalHeader)->DllCharacteristics;
                        }

                        PhUnloadRemoteMappedImage(&mappedImage);
                    }
                }
            }

            NtClose(processHandle);
        }

        ProcessNode->ValidMask |= PHPN_IMAGE;
    }
}

static VOID PhpUpdateProcessNodeAppId(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_APPID))
    {
        HANDLE processHandle;
        ULONG windowFlags;
        PPH_STRING windowTitle;

        PhClearReference(&ProcessNode->AppIdText);

        if (!NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessNode->ProcessId)))
        {
            if (WindowsVersion >= WINDOWS_7)
            {
                if (!NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessNode->ProcessId)))
                    goto Done;
            }
            else
            {
                goto Done;
            }
        }

        if (NT_SUCCESS(PhGetProcessWindowTitle(
            processHandle,
            &windowFlags,
            &windowTitle
            )))
        {
            if (windowFlags & STARTF_TITLEISAPPID)
                ProcessNode->AppIdText = windowTitle;
            else
                PhDereferenceObject(windowTitle);
        }

        NtClose(processHandle);

Done:
        ProcessNode->ValidMask |= PHPN_APPID;
    }
}

static VOID PhpUpdateProcessNodeDpiAwareness(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    static PH_INITONCE initOnce = PH_INITONCE_INIT;
    static BOOL (WINAPI *getProcessDpiAwarenessInternal)(
        _In_ HANDLE hprocess,
        _Out_ ULONG *value
        );

    if (PhBeginInitOnce(&initOnce))
    {
        getProcessDpiAwarenessInternal = PhGetModuleProcAddress(L"user32.dll", "GetProcessDpiAwarenessInternal");
        PhEndInitOnce(&initOnce);
    }

    if (!getProcessDpiAwarenessInternal)
        return;

    if (!(ProcessNode->ValidMask & PHPN_DPIAWARENESS))
    {
        if (ProcessNode->ProcessItem->QueryHandle)
        {
            ULONG dpiAwareness;

            if (getProcessDpiAwarenessInternal(ProcessNode->ProcessItem->QueryHandle, &dpiAwareness))
                ProcessNode->DpiAwareness = dpiAwareness + 1;
        }

        ProcessNode->ValidMask |= PHPN_DPIAWARENESS;
    }
}

static VOID PhpUpdateProcessNodeFileAttributes(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (!(ProcessNode->ValidMask & PHPN_FILEATTRIBUTES))
    {
        FILE_NETWORK_OPEN_INFORMATION networkOpenInfo;

        if (ProcessNode->ProcessItem->FileName && NT_SUCCESS(PhQueryFullAttributesFileWin32(
            ProcessNode->ProcessItem->FileName->Buffer,
            &networkOpenInfo
            )))
        {
            ProcessNode->FileLastWriteTime = networkOpenInfo.LastWriteTime;
            ProcessNode->FileEndOfFile = networkOpenInfo.EndOfFile;
        }
        else
        {
            ProcessNode->FileEndOfFile.QuadPart = -1;
        }

        ProcessNode->ValidMask |= PHPN_FILEATTRIBUTES;
    }
}

static VOID PhpUpdateNeedCyclesInformation(
    VOID
    )
{
    PH_TREENEW_COLUMN column;

    NeedCyclesInformation = FALSE;

    // Before Windows Vista, there is no cycle time measurement.
    // On Windows 7 and above, cycle time information is available directly from the process item.
    // We only need to query cycle time separately for Windows Vista.
    if (WindowsVersion != WINDOWS_VISTA)
        return;

    TreeNew_GetColumn(ProcessTreeListHandle, PHPRTLC_CYCLES, &column);

    if (column.Visible)
    {
        NeedCyclesInformation = TRUE;
        return;
    }

    TreeNew_GetColumn(ProcessTreeListHandle, PHPRTLC_CYCLESDELTA, &column);

    if (column.Visible)
    {
        NeedCyclesInformation = TRUE;
        return;
    }
}

static VOID PhpUpdateProcessNodeCycles(
    _Inout_ PPH_PROCESS_NODE ProcessNode
    )
{
    if (ProcessNode->ProcessId == SYSTEM_IDLE_PROCESS_ID)
    {
        PULARGE_INTEGER idleThreadCycleTimes;
        ULONG64 cycleTime;
        ULONG i;

        // System Idle Process requires special treatment.

        idleThreadCycleTimes = PhAllocate(
            sizeof(ULARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors
            );

        if (NT_SUCCESS(NtQuerySystemInformation(
            SystemProcessorIdleCycleTimeInformation,
            idleThreadCycleTimes,
            sizeof(ULARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors,
            NULL
            )))
        {
            cycleTime = 0;

            for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
                cycleTime += idleThreadCycleTimes[i].QuadPart;

            PhUpdateDelta(&ProcessNode->CyclesDelta, cycleTime);
        }

        PhFree(idleThreadCycleTimes);
    }
    else if (ProcessNode->ProcessItem->QueryHandle)
    {
        ULONG64 cycleTime;

        if (NT_SUCCESS(PhGetProcessCycleTime(ProcessNode->ProcessItem->QueryHandle, &cycleTime)))
        {
            PhUpdateDelta(&ProcessNode->CyclesDelta, cycleTime);
        }
    }

    if (ProcessNode->CyclesDelta.Value != 0)
        PhMoveReference(&ProcessNode->CyclesText, PhFormatUInt64(ProcessNode->CyclesDelta.Value, TRUE));
    else
        PhClearReference(&ProcessNode->CyclesText);

    if (ProcessNode->CyclesDelta.Delta != 0)
        PhMoveReference(&ProcessNode->CyclesDeltaText, PhFormatUInt64(ProcessNode->CyclesDelta.Delta, TRUE));
    else
        PhClearReference(&ProcessNode->CyclesDeltaText);
}

#define SORT_FUNCTION(Column) PhpProcessTreeNewCompare##Column

#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpProcessTreeNewCompare##Column( \
    _In_ const void *_elem1, \
    _In_ const void *_elem2 \
    ) \
{ \
    PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)_elem1; \
    PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)_elem2; \
    PPH_PROCESS_ITEM processItem1 = node1->ProcessItem; \
    PPH_PROCESS_ITEM processItem2 = node2->ProcessItem; \
    int sortResult = 0;

#define END_SORT_FUNCTION \
    if (sortResult == 0) \
        sortResult = intptrcmp((LONG_PTR)processItem1->ProcessId, (LONG_PTR)processItem2->ProcessId); \
    \
    return PhModifySort(sortResult, ProcessTreeListSortOrder); \
}

LONG PhpProcessTreeNewPostSortFunction(
    _In_ LONG Result,
    _In_ PVOID Node1,
    _In_ PVOID Node2,
    _In_ PH_SORT_ORDER SortOrder
    )
{
    if (Result == 0)
        Result = intptrcmp((LONG_PTR)((PPH_PROCESS_NODE)Node1)->ProcessItem->ProcessId, (LONG_PTR)((PPH_PROCESS_NODE)Node2)->ProcessItem->ProcessId);

    return PhModifySort(Result, SortOrder);
}

BEGIN_SORT_FUNCTION(Name)
{
    sortResult = PhCompareString(processItem1->ProcessName, processItem2->ProcessName, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Pid)
{
    // Use signed int so DPCs and Interrupts are placed above System Idle Process.
    sortResult = intptrcmp((LONG_PTR)processItem1->ProcessId, (LONG_PTR)processItem2->ProcessId);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Cpu)
{
    sortResult = singlecmp(processItem1->CpuUsage, processItem2->CpuUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoTotalRate)
{
    sortResult = uint64cmp(
        processItem1->IoReadDelta.Delta + processItem1->IoWriteDelta.Delta + processItem1->IoOtherDelta.Delta,
        processItem2->IoReadDelta.Delta + processItem2->IoWriteDelta.Delta + processItem2->IoOtherDelta.Delta
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PrivateBytes)
{
    sortResult = uintptrcmp(processItem1->VmCounters.PagefileUsage, processItem2->VmCounters.PagefileUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(UserName)
{
    sortResult = PhCompareStringWithNull(processItem1->UserName, processItem2->UserName, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Description)
{
    PH_STRINGREF sr1;
    PH_STRINGREF sr2;

    sr1 = processItem1->VersionInfo.FileDescription ? processItem1->VersionInfo.FileDescription->sr : node1->DescriptionText;
    sr2 = processItem2->VersionInfo.FileDescription ? processItem2->VersionInfo.FileDescription->sr : node2->DescriptionText;

    sortResult = PhCompareStringRef(&sr1, &sr2, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(CompanyName)
{
    sortResult = PhCompareStringWithNull(
        processItem1->VersionInfo.CompanyName,
        processItem2->VersionInfo.CompanyName,
        TRUE
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Version)
{
    sortResult = PhCompareStringWithNull(
        processItem1->VersionInfo.FileVersion,
        processItem2->VersionInfo.FileVersion,
        TRUE
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(FileName)
{
    sortResult = PhCompareStringWithNull(
        processItem1->FileName,
        processItem2->FileName,
        TRUE
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(CommandLine)
{
    sortResult = PhCompareStringWithNull(
        processItem1->CommandLine,
        processItem2->CommandLine,
        TRUE
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PeakPrivateBytes)
{
    sortResult = uintptrcmp(processItem1->VmCounters.PeakPagefileUsage, processItem2->VmCounters.PeakPagefileUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(WorkingSet)
{
    sortResult = uintptrcmp(processItem1->VmCounters.WorkingSetSize, processItem2->VmCounters.WorkingSetSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PeakWorkingSet)
{
    sortResult = uintptrcmp(processItem1->VmCounters.PeakWorkingSetSize, processItem2->VmCounters.PeakWorkingSetSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PrivateWs)
{
    PhpUpdateProcessNodeWsCounters(node1);
    PhpUpdateProcessNodeWsCounters(node2);
    sortResult = uintptrcmp(node1->WsCounters.NumberOfPrivatePages, node2->WsCounters.NumberOfPrivatePages);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PrivateWsWin7)
{
    sortResult = uintptrcmp(processItem1->WorkingSetPrivateSize, processItem2->WorkingSetPrivateSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(SharedWs)
{
    PhpUpdateProcessNodeWsCounters(node1);
    PhpUpdateProcessNodeWsCounters(node2);
    sortResult = uintptrcmp(node1->WsCounters.NumberOfSharedPages, node2->WsCounters.NumberOfSharedPages);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(ShareableWs)
{
    PhpUpdateProcessNodeWsCounters(node1);
    PhpUpdateProcessNodeWsCounters(node2);
    sortResult = uintptrcmp(node1->WsCounters.NumberOfShareablePages, node2->WsCounters.NumberOfShareablePages);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(VirtualSize)
{
    sortResult = uintptrcmp(processItem1->VmCounters.VirtualSize, processItem2->VmCounters.VirtualSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PeakVirtualSize)
{
    sortResult = uintptrcmp(processItem1->VmCounters.PeakVirtualSize, processItem2->VmCounters.PeakVirtualSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PageFaults)
{
    sortResult = uintcmp(processItem1->VmCounters.PageFaultCount, processItem2->VmCounters.PageFaultCount);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(SessionId)
{
    sortResult = uintcmp(processItem1->SessionId, processItem2->SessionId);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(BasePriority)
{
    sortResult = intcmp(processItem1->BasePriority, processItem2->BasePriority);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Threads)
{
    sortResult = uintcmp(processItem1->NumberOfThreads, processItem2->NumberOfThreads);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Handles)
{
    sortResult = uintcmp(processItem1->NumberOfHandles, processItem2->NumberOfHandles);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(GdiHandles)
{
    sortResult = uintcmp(node1->GdiHandles, node2->GdiHandles);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(UserHandles)
{
    sortResult = uintcmp(node1->UserHandles, node2->UserHandles);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoRoRate)
{
    sortResult = uint64cmp(
        processItem1->IoReadDelta.Delta + processItem1->IoOtherDelta.Delta,
        processItem2->IoReadDelta.Delta + processItem2->IoOtherDelta.Delta
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoWRate)
{
    sortResult = uint64cmp(
        processItem1->IoWriteDelta.Delta,
        processItem2->IoWriteDelta.Delta
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Integrity)
{
    sortResult = uintcmp(processItem1->IntegrityLevel, processItem2->IntegrityLevel);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoPriority)
{
    sortResult = uintcmp(node1->IoPriority, node2->IoPriority);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PagePriority)
{
    sortResult = uintcmp(node1->PagePriority, node2->PagePriority);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(StartTime)
{
    sortResult = int64cmp(processItem1->CreateTime.QuadPart, processItem2->CreateTime.QuadPart);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(TotalCpuTime)
{
    sortResult = uint64cmp(
        processItem1->KernelTime.QuadPart + processItem1->UserTime.QuadPart,
        processItem2->KernelTime.QuadPart + processItem2->UserTime.QuadPart
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(KernelCpuTime)
{
    sortResult = uint64cmp(
        processItem1->KernelTime.QuadPart,
        processItem2->KernelTime.QuadPart
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(UserCpuTime)
{
    sortResult = uint64cmp(
        processItem1->UserTime.QuadPart,
        processItem2->UserTime.QuadPart
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(VerificationStatus)
{
    sortResult = intcmp(processItem1->VerifyResult, processItem2->VerifyResult);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(VerifiedSigner)
{
    sortResult = PhCompareStringWithNull(
        processItem1->VerifySignerName,
        processItem2->VerifySignerName,
        TRUE
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Aslr)
{
    PhpUpdateProcessNodeImage(node1);
    PhpUpdateProcessNodeImage(node2);
    sortResult = intcmp(
        node1->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE,
        node2->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(RelativeStartTime)
{
    sortResult = -int64cmp(processItem1->CreateTime.QuadPart, processItem2->CreateTime.QuadPart);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Bits)
{
    sortResult = intcmp(processItem1->IsWow64Valid, processItem2->IsWow64Valid);

    if (sortResult == 0)
        sortResult = intcmp(processItem1->IsWow64, processItem2->IsWow64);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Elevation)
{
    ULONG key1;
    ULONG key2;

    switch (processItem1->ElevationType)
    {
    case TokenElevationTypeFull:
        key1 = 2;
        break;
    case TokenElevationTypeLimited:
        key1 = 1;
        break;
    default:
        key1 = 0;
        break;
    }

    switch (processItem2->ElevationType)
    {
    case TokenElevationTypeFull:
        key2 = 2;
        break;
    case TokenElevationTypeLimited:
        key2 = 1;
        break;
    default:
        key2 = 0;
        break;
    }

    sortResult = intcmp(key1, key2);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(WindowTitle)
{
    PhpUpdateProcessNodeWindow(node1);
    PhpUpdateProcessNodeWindow(node2);
    sortResult = PhCompareStringWithNull(node1->WindowText, node2->WindowText, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(WindowStatus)
{
    PhpUpdateProcessNodeWindow(node1);
    PhpUpdateProcessNodeWindow(node2);
    sortResult = intcmp(node1->WindowHung, node2->WindowHung);

    // Make sure all processes with windows get grouped together.
    if (sortResult == 0)
        sortResult = intcmp(!!node1->WindowHandle, !!node2->WindowHandle);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Cycles)
{
    sortResult = uint64cmp(node1->CyclesDelta.Value, node2->CyclesDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(CyclesWin7)
{
    sortResult = uint64cmp(processItem1->CycleTimeDelta.Value, processItem2->CycleTimeDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(CyclesDelta)
{
    sortResult = uint64cmp(node1->CyclesDelta.Delta, node2->CyclesDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(CyclesDeltaWin7)
{
    sortResult = uint64cmp(processItem1->CycleTimeDelta.Delta, processItem2->CycleTimeDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Dep)
{
    PhpUpdateProcessNodeDepStatus(node1);
    PhpUpdateProcessNodeDepStatus(node2);
    sortResult = uintcmp(node1->DepStatus, node2->DepStatus);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Virtualized)
{
    PhpUpdateProcessNodeToken(node1);
    PhpUpdateProcessNodeToken(node2);
    sortResult = intcmp(node1->VirtualizationEnabled, node2->VirtualizationEnabled);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(ContextSwitches)
{
    sortResult = uintcmp(processItem1->ContextSwitchesDelta.Value, processItem2->ContextSwitchesDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(ContextSwitchesDelta)
{
    sortResult = intcmp((LONG)processItem1->ContextSwitchesDelta.Delta, (LONG)processItem2->ContextSwitchesDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PageFaultsDelta)
{
    sortResult = uintcmp(processItem1->PageFaultsDelta.Delta, processItem2->PageFaultsDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoReads)
{
    sortResult = uint64cmp(processItem1->IoReadCountDelta.Value, processItem2->IoReadCountDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoWrites)
{
    sortResult = uint64cmp(processItem1->IoWriteCountDelta.Value, processItem2->IoWriteCountDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoOther)
{
    sortResult = uint64cmp(processItem1->IoOtherCountDelta.Value, processItem2->IoOtherCountDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoReadBytes)
{
    sortResult = uint64cmp(processItem1->IoReadDelta.Value, processItem2->IoReadDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoWriteBytes)
{
    sortResult = uint64cmp(processItem1->IoWriteDelta.Value, processItem2->IoWriteDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoOtherBytes)
{
    sortResult = uint64cmp(processItem1->IoOtherDelta.Value, processItem2->IoOtherDelta.Value);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoReadsDelta)
{
    sortResult = uint64cmp(processItem1->IoReadCountDelta.Delta, processItem2->IoReadCountDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoWritesDelta)
{
    sortResult = uint64cmp(processItem1->IoWriteCountDelta.Delta, processItem2->IoWriteCountDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(IoOtherDelta)
{
    sortResult = uint64cmp(processItem1->IoOtherCountDelta.Delta, processItem2->IoOtherCountDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(OsContext)
{
    PhpUpdateProcessOsContext(node1);
    PhpUpdateProcessOsContext(node2);
    sortResult = uintcmp(node1->OsContextVersion, node2->OsContextVersion);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PagedPool)
{
    sortResult = uintptrcmp(processItem1->VmCounters.QuotaPagedPoolUsage, processItem2->VmCounters.QuotaPagedPoolUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PeakPagedPool)
{
    sortResult = uintptrcmp(processItem1->VmCounters.QuotaPeakPagedPoolUsage, processItem2->VmCounters.QuotaPeakPagedPoolUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(NonPagedPool)
{
    sortResult = uintptrcmp(processItem1->VmCounters.QuotaNonPagedPoolUsage, processItem2->VmCounters.QuotaNonPagedPoolUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PeakNonPagedPool)
{
    sortResult = uintptrcmp(processItem1->VmCounters.QuotaPeakNonPagedPoolUsage, processItem2->VmCounters.QuotaPeakNonPagedPoolUsage);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(MinimumWorkingSet)
{
    PhpUpdateProcessNodeQuotaLimits(node1);
    PhpUpdateProcessNodeQuotaLimits(node2);
    sortResult = uintptrcmp(node1->MinimumWorkingSetSize, node2->MinimumWorkingSetSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(MaximumWorkingSet)
{
    PhpUpdateProcessNodeQuotaLimits(node1);
    PhpUpdateProcessNodeQuotaLimits(node2);
    sortResult = uintptrcmp(node1->MaximumWorkingSetSize, node2->MaximumWorkingSetSize);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PrivateBytesDelta)
{
    sortResult = intptrcmp(processItem1->PrivateBytesDelta.Delta, processItem2->PrivateBytesDelta.Delta);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Subsystem)
{
    PhpUpdateProcessNodeImage(node1);
    PhpUpdateProcessNodeImage(node2);
    sortResult = intcmp(node1->ImageSubsystem, node2->ImageSubsystem);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(PackageName)
{
    sortResult = PhCompareStringWithNull(processItem1->PackageFullName, processItem2->PackageFullName, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(AppId)
{
    PhpUpdateProcessNodeAppId(node1);
    PhpUpdateProcessNodeAppId(node2);
    sortResult = PhCompareStringWithNull(node1->AppIdText, node2->AppIdText, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(DpiAwareness)
{
    PhpUpdateProcessNodeDpiAwareness(node1);
    PhpUpdateProcessNodeDpiAwareness(node2);
    sortResult = uintcmp(node1->DpiAwareness, node2->DpiAwareness);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(CfGuard)
{
    PhpUpdateProcessNodeImage(node1);
    PhpUpdateProcessNodeImage(node2);
    sortResult = intcmp(
        node1->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF,
        node2->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF
        );
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(TimeStamp)
{
    PhpUpdateProcessNodeImage(node1);
    PhpUpdateProcessNodeImage(node2);
    sortResult = uintcmp(node1->ImageTimeDateStamp, node2->ImageTimeDateStamp);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(FileModifiedTime)
{
    PhpUpdateProcessNodeFileAttributes(node1);
    PhpUpdateProcessNodeFileAttributes(node2);
    sortResult = int64cmp(node1->FileLastWriteTime.QuadPart, node2->FileLastWriteTime.QuadPart);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(FileSize)
{
    PhpUpdateProcessNodeFileAttributes(node1);
    PhpUpdateProcessNodeFileAttributes(node2);
    sortResult = int64cmp(node1->FileEndOfFile.QuadPart, node2->FileEndOfFile.QuadPart);
}
END_SORT_FUNCTION

BOOLEAN NTAPI PhpProcessTreeNewCallback(
    _In_ HWND hwnd,
    _In_ PH_TREENEW_MESSAGE Message,
    _In_opt_ PVOID Parameter1,
    _In_opt_ PVOID Parameter2,
    _In_opt_ PVOID Context
    )
{
    PPH_PROCESS_NODE node;

    if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &ProcessTreeListCm))
        return TRUE;

    switch (Message)
    {
    case TreeNewGetChildren:
        {
            PPH_TREENEW_GET_CHILDREN getChildren = Parameter1;

            node = (PPH_PROCESS_NODE)getChildren->Node;

            if (ProcessTreeListSortOrder == NoSortOrder)
            {
                if (!node)
                {
                    getChildren->Children = (PPH_TREENEW_NODE *)ProcessNodeRootList->Items;
                    getChildren->NumberOfChildren = ProcessNodeRootList->Count;
                }
                else
                {
                    getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items;
                    getChildren->NumberOfChildren = node->Children->Count;
                }
            }
            else
            {
                if (!node)
                {
                    static PVOID sortFunctions[] =
                    {
                        SORT_FUNCTION(Name),
                        SORT_FUNCTION(Pid),
                        SORT_FUNCTION(Cpu),
                        SORT_FUNCTION(IoTotalRate),
                        SORT_FUNCTION(PrivateBytes),
                        SORT_FUNCTION(UserName),
                        SORT_FUNCTION(Description),
                        SORT_FUNCTION(CompanyName),
                        SORT_FUNCTION(Version),
                        SORT_FUNCTION(FileName),
                        SORT_FUNCTION(CommandLine),
                        SORT_FUNCTION(PeakPrivateBytes),
                        SORT_FUNCTION(WorkingSet),
                        SORT_FUNCTION(PeakWorkingSet),
                        SORT_FUNCTION(PrivateWs),
                        SORT_FUNCTION(SharedWs),
                        SORT_FUNCTION(ShareableWs),
                        SORT_FUNCTION(VirtualSize),
                        SORT_FUNCTION(PeakVirtualSize),
                        SORT_FUNCTION(PageFaults),
                        SORT_FUNCTION(SessionId),
                        SORT_FUNCTION(BasePriority), // Priority Class
                        SORT_FUNCTION(BasePriority),
                        SORT_FUNCTION(Threads),
                        SORT_FUNCTION(Handles),
                        SORT_FUNCTION(GdiHandles),
                        SORT_FUNCTION(UserHandles),
                        SORT_FUNCTION(IoRoRate),
                        SORT_FUNCTION(IoWRate),
                        SORT_FUNCTION(Integrity),
                        SORT_FUNCTION(IoPriority),
                        SORT_FUNCTION(PagePriority),
                        SORT_FUNCTION(StartTime),
                        SORT_FUNCTION(TotalCpuTime),
                        SORT_FUNCTION(KernelCpuTime),
                        SORT_FUNCTION(UserCpuTime),
                        SORT_FUNCTION(VerificationStatus),
                        SORT_FUNCTION(VerifiedSigner),
                        SORT_FUNCTION(Aslr),
                        SORT_FUNCTION(RelativeStartTime),
                        SORT_FUNCTION(Bits),
                        SORT_FUNCTION(Elevation),
                        SORT_FUNCTION(WindowTitle),
                        SORT_FUNCTION(WindowStatus),
                        SORT_FUNCTION(Cycles),
                        SORT_FUNCTION(CyclesDelta),
                        SORT_FUNCTION(Cpu), // CPU History
                        SORT_FUNCTION(PrivateBytes), // Private Bytes History
                        SORT_FUNCTION(IoTotalRate), // I/O History
                        SORT_FUNCTION(Dep),
                        SORT_FUNCTION(Virtualized),
                        SORT_FUNCTION(ContextSwitches),
                        SORT_FUNCTION(ContextSwitchesDelta),
                        SORT_FUNCTION(PageFaultsDelta),
                        SORT_FUNCTION(IoReads),
                        SORT_FUNCTION(IoWrites),
                        SORT_FUNCTION(IoOther),
                        SORT_FUNCTION(IoReadBytes),
                        SORT_FUNCTION(IoWriteBytes),
                        SORT_FUNCTION(IoOtherBytes),
                        SORT_FUNCTION(IoReadsDelta),
                        SORT_FUNCTION(IoWritesDelta),
                        SORT_FUNCTION(IoOtherDelta),
                        SORT_FUNCTION(OsContext),
                        SORT_FUNCTION(PagedPool),
                        SORT_FUNCTION(PeakPagedPool),
                        SORT_FUNCTION(NonPagedPool),
                        SORT_FUNCTION(PeakNonPagedPool),
                        SORT_FUNCTION(MinimumWorkingSet),
                        SORT_FUNCTION(MaximumWorkingSet),
                        SORT_FUNCTION(PrivateBytesDelta),
                        SORT_FUNCTION(Subsystem),
                        SORT_FUNCTION(PackageName),
                        SORT_FUNCTION(AppId),
                        SORT_FUNCTION(DpiAwareness),
                        SORT_FUNCTION(CfGuard),
                        SORT_FUNCTION(TimeStamp),
                        SORT_FUNCTION(FileModifiedTime),
                        SORT_FUNCTION(FileSize)
                    };
                    static PH_INITONCE initOnce = PH_INITONCE_INIT;
                    int (__cdecl *sortFunction)(const void *, const void *);

                    if (PhBeginInitOnce(&initOnce))
                    {
                        if (WindowsVersion >= WINDOWS_7)
                        {
                            sortFunctions[PHPRTLC_PRIVATEWS] = SORT_FUNCTION(PrivateWsWin7);
                            sortFunctions[PHPRTLC_CYCLES] = SORT_FUNCTION(CyclesWin7);
                            sortFunctions[PHPRTLC_CYCLESDELTA] = SORT_FUNCTION(CyclesDeltaWin7);
                        }

                        PhEndInitOnce(&initOnce);
                    }

                    if (!PhCmForwardSort(
                        (PPH_TREENEW_NODE *)ProcessNodeList->Items,
                        ProcessNodeList->Count,
                        ProcessTreeListSortColumn,
                        ProcessTreeListSortOrder,
                        &ProcessTreeListCm
                        ))
                    {
                        if (ProcessTreeListSortColumn < PHPRTLC_MAXIMUM)
                            sortFunction = sortFunctions[ProcessTreeListSortColumn];
                        else
                            sortFunction = NULL;

                        if (sortFunction)
                        {
                            qsort(ProcessNodeList->Items, ProcessNodeList->Count, sizeof(PVOID), sortFunction);
                        }
                    }

                    getChildren->Children = (PPH_TREENEW_NODE *)ProcessNodeList->Items;
                    getChildren->NumberOfChildren = ProcessNodeList->Count;
                }
            }
        }
        return TRUE;
    case TreeNewIsLeaf:
        {
            PPH_TREENEW_IS_LEAF isLeaf = Parameter1;

            node = (PPH_PROCESS_NODE)isLeaf->Node;

            if (ProcessTreeListSortOrder == NoSortOrder)
                isLeaf->IsLeaf = node->Children->Count == 0;
            else
                isLeaf->IsLeaf = TRUE;
        }
        return TRUE;
    case TreeNewGetCellText:
        {
            PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1;
            PPH_PROCESS_ITEM processItem;

            node = (PPH_PROCESS_NODE)getCellText->Node;
            processItem = node->ProcessItem;

            switch (getCellText->Id)
            {
            case PHPRTLC_NAME:
                getCellText->Text = processItem->ProcessName->sr;
                break;
            case PHPRTLC_PID:
                PhInitializeStringRefLongHint(&getCellText->Text, processItem->ProcessIdString);
                break;
            case PHPRTLC_CPU:
                {
                    FLOAT cpuUsage = 0;

                    PhpAggregateFieldIfNeeded(node, AggregateTypeFloat, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, CpuUsage), &cpuUsage);
                    cpuUsage *= 100;

                    if (cpuUsage >= 0.01)
                    {
                        PH_FORMAT format;
                        SIZE_T returnLength;

                        PhInitFormatF(&format, cpuUsage, 2);

                        if (PhFormatToBuffer(&format, 1, node->CpuUsageText, sizeof(node->CpuUsageText), &returnLength))
                        {
                            getCellText->Text.Buffer = node->CpuUsageText;
                            getCellText->Text.Length = returnLength - sizeof(WCHAR); // minus null terminator
                        }
                    }
                    else if (cpuUsage != 0 && PhCsShowCpuBelow001)
                    {
                        PH_FORMAT format[2];
                        SIZE_T returnLength;

                        PhInitFormatS(&format[0], L"< ");
                        PhInitFormatF(&format[1], 0.01, 2);

                        if (PhFormatToBuffer(format, 2, node->CpuUsageText, sizeof(node->CpuUsageText), &returnLength))
                        {
                            getCellText->Text.Buffer = node->CpuUsageText;
                            getCellText->Text.Length = returnLength - sizeof(WCHAR);
                        }
                    }
                }
                break;
            case PHPRTLC_IOTOTALRATE:
                {
                    ULONG64 number = 0;

                    if (processItem->IoReadDelta.Delta != processItem->IoReadDelta.Value) // delta is wrong on first run of process provider
                    {
                        PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoReadDelta.Delta), &number);
                        PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoWriteDelta.Delta), &number);
                        PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoOtherDelta.Delta), &number);
                        number *= 1000;
                        number /= PhCsUpdateInterval;
                    }

                    if (number != 0)
                    {
                        PH_FORMAT format[2];

                        PhInitFormatSize(&format[0], number);
                        PhInitFormatS(&format[1], L"/s");
                        PhMoveReference(&node->IoTotalRateText, PhFormat(format, 2, 0));
                        getCellText->Text = node->IoTotalRateText->sr;
                    }
                }
                break;
            case PHPRTLC_PRIVATEBYTES:
                {
                    SIZE_T value = 0;
                    PhpAggregateFieldIfNeeded(node, AggregateTypeIntPtr, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, VmCounters.PagefileUsage), &value);
                    PhMoveReference(&node->PrivateBytesText, PhFormatSize(value, -1));
                    getCellText->Text = node->PrivateBytesText->sr;
                }
                break;
            case PHPRTLC_USERNAME:
                getCellText->Text = PhGetStringRef(processItem->UserName);
                break;
            case PHPRTLC_DESCRIPTION:
                if (processItem->VersionInfo.FileDescription)
                    getCellText->Text = processItem->VersionInfo.FileDescription->sr;
                else
                    getCellText->Text = node->DescriptionText;
                break;
            case PHPRTLC_COMPANYNAME:
                getCellText->Text = PhGetStringRef(processItem->VersionInfo.CompanyName);
                break;
            case PHPRTLC_VERSION:
                getCellText->Text = PhGetStringRef(processItem->VersionInfo.FileVersion);
                break;
            case PHPRTLC_FILENAME:
                getCellText->Text = PhGetStringRef(processItem->FileName);
                break;
            case PHPRTLC_COMMANDLINE:
                getCellText->Text = PhGetStringRef(processItem->CommandLine);
                break;
            case PHPRTLC_PEAKPRIVATEBYTES:
                PhMoveReference(&node->PeakPrivateBytesText, PhFormatSize(processItem->VmCounters.PeakPagefileUsage, -1));
                getCellText->Text = node->PeakPrivateBytesText->sr;
                break;
            case PHPRTLC_WORKINGSET:
                {
                    SIZE_T value = 0;
                    PhpAggregateFieldIfNeeded(node, AggregateTypeIntPtr, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, VmCounters.WorkingSetSize), &value);
                    PhMoveReference(&node->WorkingSetText, PhFormatSize(value, -1));
                    getCellText->Text = node->WorkingSetText->sr;
                }
                break;
            case PHPRTLC_PEAKWORKINGSET:
                PhMoveReference(&node->PeakWorkingSetText, PhFormatSize(processItem->VmCounters.PeakWorkingSetSize, -1));
                getCellText->Text = node->PeakWorkingSetText->sr;
                break;
            case PHPRTLC_PRIVATEWS:
                if (WindowsVersion >= WINDOWS_7)
                {
                    PhMoveReference(&node->PrivateWsText, PhFormatSize(processItem->WorkingSetPrivateSize, -1));
                }
                else
                {
                    PhpUpdateProcessNodeWsCounters(node);
                    PhMoveReference(&node->PrivateWsText, PhFormatSize((ULONG64)node->WsCounters.NumberOfPrivatePages * PAGE_SIZE, -1));
                }
                getCellText->Text = node->PrivateWsText->sr;
                break;
            case PHPRTLC_SHAREDWS:
                PhpUpdateProcessNodeWsCounters(node);
                PhMoveReference(&node->SharedWsText, PhFormatSize((ULONG64)node->WsCounters.NumberOfSharedPages * PAGE_SIZE, -1));
                getCellText->Text = node->SharedWsText->sr;
                break;
            case PHPRTLC_SHAREABLEWS:
                PhpUpdateProcessNodeWsCounters(node);
                PhMoveReference(&node->ShareableWsText, PhFormatSize((ULONG64)node->WsCounters.NumberOfShareablePages * PAGE_SIZE, -1));
                getCellText->Text = node->ShareableWsText->sr;
                break;
            case PHPRTLC_VIRTUALSIZE:
                PhMoveReference(&node->VirtualSizeText, PhFormatSize(processItem->VmCounters.VirtualSize, -1));
                getCellText->Text = node->VirtualSizeText->sr;
                break;
            case PHPRTLC_PEAKVIRTUALSIZE:
                PhMoveReference(&node->PeakVirtualSizeText, PhFormatSize(processItem->VmCounters.PeakVirtualSize, -1));
                getCellText->Text = node->PeakVirtualSizeText->sr;
                break;
            case PHPRTLC_PAGEFAULTS:
                PhMoveReference(&node->PageFaultsText, PhFormatUInt64(processItem->VmCounters.PageFaultCount, TRUE));
                getCellText->Text = node->PageFaultsText->sr;
                break;
            case PHPRTLC_SESSIONID:
                PhInitializeStringRefLongHint(&getCellText->Text, processItem->SessionIdString);
                break;
            case PHPRTLC_PRIORITYCLASS:
                PhInitializeStringRefLongHint(&getCellText->Text, PhGetProcessPriorityClassString(processItem->PriorityClass));
                break;
            case PHPRTLC_BASEPRIORITY:
                PhPrintInt32(node->BasePriorityText, processItem->BasePriority);
                PhInitializeStringRefLongHint(&getCellText->Text, node->BasePriorityText);
                break;
            case PHPRTLC_THREADS:
                {
                    ULONG value = 0;
                    PhpAggregateFieldIfNeeded(node, AggregateTypeInt32, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, NumberOfThreads), &value);
                    PhpFormatInt32GroupDigits(value, node->ThreadsText, sizeof(node->ThreadsText), &getCellText->Text);
                }
                break;
            case PHPRTLC_HANDLES:
                {
                    ULONG value = 0;
                    PhpAggregateFieldIfNeeded(node, AggregateTypeInt32, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, NumberOfHandles), &value);
                    PhpFormatInt32GroupDigits(value, node->HandlesText, sizeof(node->HandlesText), &getCellText->Text);
                }
                break;
            case PHPRTLC_GDIHANDLES:
                PhpUpdateProcessNodeGdiUserHandles(node);
                PhpFormatInt32GroupDigits(node->GdiHandles, node->GdiHandlesText, sizeof(node->GdiHandlesText), &getCellText->Text);
                break;
            case PHPRTLC_USERHANDLES:
                PhpUpdateProcessNodeGdiUserHandles(node);
                PhpFormatInt32GroupDigits(node->UserHandles, node->UserHandlesText, sizeof(node->UserHandlesText), &getCellText->Text);
                break;
            case PHPRTLC_IORORATE:
                {
                    ULONG64 number = 0;

                    if (processItem->IoReadDelta.Delta != processItem->IoReadDelta.Value)
                    {
                        PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoReadDelta.Delta), &number);
                        PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoOtherDelta.Delta), &number);
                        number *= 1000;
                        number /= PhCsUpdateInterval;
                    }

                    if (number != 0)
                    {
                        PH_FORMAT format[2];

                        PhInitFormatSize(&format[0], number);
                        PhInitFormatS(&format[1], L"/s");
                        PhMoveReference(&node->IoRoRateText, PhFormat(format, 2, 0));
                        getCellText->Text = node->IoRoRateText->sr;
                    }
                }
                break;
            case PHPRTLC_IOWRATE:
                {
                    ULONG64 number = 0;

                    if (processItem->IoReadDelta.Delta != processItem->IoReadDelta.Value)
                    {
                        PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoWriteDelta.Delta), &number);
                        number *= 1000;
                        number /= PhCsUpdateInterval;
                    }

                    if (number != 0)
                    {
                        PH_FORMAT format[2];

                        PhInitFormatSize(&format[0], number);
                        PhInitFormatS(&format[1], L"/s");
                        PhMoveReference(&node->IoWRateText, PhFormat(format, 2, 0));
                        getCellText->Text = node->IoWRateText->sr;
                    }
                }
                break;
            case PHPRTLC_INTEGRITY:
                if (processItem->IntegrityString)
                    PhInitializeStringRefLongHint(&getCellText->Text, processItem->IntegrityString);
                break;
            case PHPRTLC_IOPRIORITY:
                PhpUpdateProcessNodeIoPagePriority(node);
                if (node->IoPriority != -1 && node->IoPriority < MaxIoPriorityTypes)
                    PhInitializeStringRefLongHint(&getCellText->Text, PhIoPriorityHintNames[node->IoPriority]);
                break;
            case PHPRTLC_PAGEPRIORITY:
                PhpUpdateProcessNodeIoPagePriority(node);
                if (node->PagePriority != -1 && node->PagePriority <= MEMORY_PRIORITY_NORMAL)
                    PhInitializeStringRefLongHint(&getCellText->Text, PhPagePriorityNames[node->PagePriority]);
                break;
            case PHPRTLC_STARTTIME:
                if (processItem->CreateTime.QuadPart != 0)
                {
                    SYSTEMTIME systemTime;

                    PhLargeIntegerToLocalSystemTime(&systemTime, &processItem->CreateTime);
                    PhMoveReference(&node->StartTimeText, PhFormatDateTime(&systemTime));
                    getCellText->Text = node->StartTimeText->sr;
                }
                break;
            case PHPRTLC_TOTALCPUTIME:
                PhMoveReference(&node->TotalCpuTimeText, PhFormatTimeSpan(
                    processItem->KernelTime.QuadPart + processItem->UserTime.QuadPart,
                    PH_TIMESPAN_HMSM
                    ));
                getCellText->Text = node->TotalCpuTimeText->sr;
                break;
            case PHPRTLC_KERNELCPUTIME:
                PhMoveReference(&node->KernelCpuTimeText, PhFormatTimeSpan(
                    processItem->KernelTime.QuadPart,
                    PH_TIMESPAN_HMSM
                    ));
                getCellText->Text = node->KernelCpuTimeText->sr;
                break;
            case PHPRTLC_USERCPUTIME:
                PhMoveReference(&node->UserCpuTimeText, PhFormatTimeSpan(
                    processItem->UserTime.QuadPart,
                    PH_TIMESPAN_HMSM
                    ));
                getCellText->Text = node->UserCpuTimeText->sr;
                break;
            case PHPRTLC_VERIFICATIONSTATUS:
                if (processItem->VerifyResult == VrTrusted)
                    PhInitializeStringRef(&getCellText->Text, L"Trusted");
                break;
            case PHPRTLC_VERIFIEDSIGNER:
                getCellText->Text = PhGetStringRef(processItem->VerifySignerName);
                break;
            case PHPRTLC_ASLR:
                PhpUpdateProcessNodeImage(node);

                if (WindowsVersion >= WINDOWS_VISTA)
                {
                    if (node->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
                        PhInitializeStringRef(&getCellText->Text, L"ASLR");
                }
                else
                {
                    PhInitializeStringRef(&getCellText->Text, L"N/A");
                }
                break;
            case PHPRTLC_RELATIVESTARTTIME:
                {
                    if (processItem->CreateTime.QuadPart != 0)
                    {
                        LARGE_INTEGER currentTime;
                        PPH_STRING startTimeString;

                        PhQuerySystemTime(&currentTime);
                        startTimeString = PhFormatTimeSpanRelative(currentTime.QuadPart - processItem->CreateTime.QuadPart);
                        PhMoveReference(&node->RelativeStartTimeText, PhConcatStrings2(startTimeString->Buffer, L" ago"));
                        PhDereferenceObject(startTimeString);
                        getCellText->Text = node->RelativeStartTimeText->sr;
                    }
                }
                break;
            case PHPRTLC_BITS:
#ifdef _WIN64
                if (processItem->IsWow64Valid)
                    PhInitializeStringRef(&getCellText->Text, processItem->IsWow64 ? L"32" : L"64");
#else
                PhInitializeStringRef(&getCellText->Text, L"32");
#endif
                break;
            case PHPRTLC_ELEVATION:
                {
                    PWSTR type;

                    if (WINDOWS_HAS_UAC)
                    {
                        switch (processItem->ElevationType)
                        {
                        case TokenElevationTypeDefault:
                            type = L"N/A";
                            break;
                        case TokenElevationTypeLimited:
                            type = L"Limited";
                            break;
                        case TokenElevationTypeFull:
                            type = L"Full";
                            break;
                        default:
                            type = L"N/A";
                            break;
                        }
                    }
                    else
                    {
                        type = L"";
                    }

                    PhInitializeStringRefLongHint(&getCellText->Text, type);
                }
                break;
            case PHPRTLC_WINDOWTITLE:
                PhpUpdateProcessNodeWindow(node);
                PhSwapReference(&node->WindowTitleText, node->WindowText);
                getCellText->Text = PhGetStringRef(node->WindowTitleText);
                break;
            case PHPRTLC_WINDOWSTATUS:
                PhpUpdateProcessNodeWindow(node);

                if (node->WindowHandle)
                    PhInitializeStringRef(&getCellText->Text, node->WindowHung ? L"Not responding" : L"Running");

                break;
            case PHPRTLC_CYCLES:
                if (WindowsVersion >= WINDOWS_7)
                {
                    ULONG64 value = 0;
                    PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, CycleTimeDelta.Value), &value);

                    if (value != 0)
                    {
                        PhMoveReference(&node->CyclesText, PhFormatUInt64(value, TRUE));
                        getCellText->Text = node->CyclesText->sr;
                    }
                }
                else
                {
                    getCellText->Text = PhGetStringRef(node->CyclesText);
                }
                break;
            case PHPRTLC_CYCLESDELTA:
                if (WindowsVersion >= WINDOWS_7)
                {
                    ULONG64 value = 0;
                    PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, CycleTimeDelta.Delta), &value);

                    if (value != 0)
                    {
                        PhMoveReference(&node->CyclesDeltaText, PhFormatUInt64(value, TRUE));
                        getCellText->Text = node->CyclesDeltaText->sr;
                    }
                }
                else
                {
                    getCellText->Text = PhGetStringRef(node->CyclesDeltaText);
                }
                break;
            case PHPRTLC_DEP:
                PhpUpdateProcessNodeDepStatus(node);

                if (node->DepStatus & PH_PROCESS_DEP_ENABLED)
                {
                    if (node->DepStatus & PH_PROCESS_DEP_PERMANENT)
                        PhInitializeStringRef(&getCellText->Text, L"DEP (permanent)");
                    else
                        PhInitializeStringRef(&getCellText->Text, L"DEP");
                }

                break;
            case PHPRTLC_VIRTUALIZED:
                PhpUpdateProcessNodeToken(node);

                if (node->VirtualizationEnabled)
                    PhInitializeStringRef(&getCellText->Text, L"Virtualized");

                break;
            case PHPRTLC_CONTEXTSWITCHES:
                if (processItem->ContextSwitchesDelta.Value != 0)
                {
                    PhMoveReference(&node->ContextSwitchesText, PhFormatUInt64(processItem->ContextSwitchesDelta.Value, TRUE));
                    getCellText->Text = node->ContextSwitchesText->sr;
                }
                break;
            case PHPRTLC_CONTEXTSWITCHESDELTA:
                if ((LONG)processItem->ContextSwitchesDelta.Delta > 0) // the delta may be negative if a thread exits - just don't show anything
                {
                    PhMoveReference(&node->ContextSwitchesDeltaText, PhFormatUInt64(processItem->ContextSwitchesDelta.Delta, TRUE));
                    getCellText->Text = node->ContextSwitchesDeltaText->sr;
                }
                break;
            case PHPRTLC_PAGEFAULTSDELTA:
                if (processItem->PageFaultsDelta.Delta != 0)
                {
                    PhMoveReference(&node->PageFaultsDeltaText, PhFormatUInt64(processItem->PageFaultsDelta.Delta, TRUE));
                    getCellText->Text = node->PageFaultsDeltaText->sr;
                }
                break;
            case PHPRTLC_IOREADS:
                if (processItem->IoReadCountDelta.Value != 0)
                {
                    PhMoveReference(&node->IoGroupText[0], PhFormatUInt64(processItem->IoReadCountDelta.Value, TRUE));
                    getCellText->Text = node->IoGroupText[0]->sr;
                }
                break;
            case PHPRTLC_IOWRITES:
                if (processItem->IoWriteCountDelta.Value != 0)
                {
                    PhMoveReference(&node->IoGroupText[1], PhFormatUInt64(processItem->IoWriteCountDelta.Value, TRUE));
                    getCellText->Text = node->IoGroupText[1]->sr;
                }
                break;
            case PHPRTLC_IOOTHER:
                if (processItem->IoOtherCountDelta.Value != 0)
                {
                    PhMoveReference(&node->IoGroupText[2], PhFormatUInt64(processItem->IoOtherCountDelta.Value, TRUE));
                    getCellText->Text = node->IoGroupText[2]->sr;
                }
                break;
            case PHPRTLC_IOREADBYTES:
                if (processItem->IoReadDelta.Value != 0)
                {
                    PhMoveReference(&node->IoGroupText[3], PhFormatSize(processItem->IoReadDelta.Value, -1));
                    getCellText->Text = node->IoGroupText[3]->sr;
                }
                break;
            case PHPRTLC_IOWRITEBYTES:
                if (processItem->IoWriteDelta.Value != 0)
                {
                    PhMoveReference(&node->IoGroupText[4], PhFormatSize(processItem->IoWriteDelta.Value, -1));
                    getCellText->Text = node->IoGroupText[4]->sr;
                }
                break;
            case PHPRTLC_IOOTHERBYTES:
                if (processItem->IoOtherDelta.Value != 0)
                {
                    PhMoveReference(&node->IoGroupText[5], PhFormatSize(processItem->IoOtherDelta.Value, -1));
                    getCellText->Text = node->IoGroupText[5]->sr;
                }
                break;
            case PHPRTLC_IOREADSDELTA:
                if (processItem->IoReadCountDelta.Delta != 0)
                {
                    PhMoveReference(&node->IoGroupText[6], PhFormatUInt64(processItem->IoReadCountDelta.Delta, TRUE));
                    getCellText->Text = node->IoGroupText[6]->sr;
                }
                break;
            case PHPRTLC_IOWRITESDELTA:
                if (processItem->IoWriteCountDelta.Delta != 0)
                {
                    PhMoveReference(&node->IoGroupText[7], PhFormatUInt64(processItem->IoWriteCountDelta.Delta, TRUE));
                    getCellText->Text = node->IoGroupText[7]->sr;
                }
                break;
            case PHPRTLC_IOOTHERDELTA:
                if (processItem->IoOtherCountDelta.Delta != 0)
                {
                    PhMoveReference(&node->IoGroupText[8], PhFormatUInt64(processItem->IoOtherCountDelta.Delta, TRUE));
                    getCellText->Text = node->IoGroupText[8]->sr;
                }
                break;
            case PHPRTLC_OSCONTEXT:
                PhpUpdateProcessOsContext(node);

                if (WindowsVersion >= WINDOWS_7)
                {
                    switch (node->OsContextVersion)
                    {
                    case WINDOWS_10:
                        PhInitializeStringRef(&getCellText->Text, L"Windows 10");
                        break;
                    case WINDOWS_8_1:
                        PhInitializeStringRef(&getCellText->Text, L"Windows 8.1");
                        break;
                    case WINDOWS_8:
                        PhInitializeStringRef(&getCellText->Text, L"Windows 8");
                        break;
                    case WINDOWS_7:
                        PhInitializeStringRef(&getCellText->Text, L"Windows 7");
                        break;
                    case WINDOWS_VISTA:
                        PhInitializeStringRef(&getCellText->Text, L"Windows Vista");
                        break;
                    case WINDOWS_XP:
                        PhInitializeStringRef(&getCellText->Text, L"Windows XP");
                        break;
                    }
                }
                else
                {
                    PhInitializeStringRef(&getCellText->Text, L"N/A");
                }
                break;
            case PHPRTLC_PAGEDPOOL:
                PhMoveReference(&node->PagedPoolText, PhFormatSize(processItem->VmCounters.QuotaPagedPoolUsage, -1));
                getCellText->Text = node->PagedPoolText->sr;
                break;
            case PHPRTLC_PEAKPAGEDPOOL:
                PhMoveReference(&node->PeakPagedPoolText, PhFormatSize(processItem->VmCounters.QuotaPeakPagedPoolUsage, -1));
                getCellText->Text = node->PeakPagedPoolText->sr;
                break;
            case PHPRTLC_NONPAGEDPOOL:
                PhMoveReference(&node->NonPagedPoolText, PhFormatSize(processItem->VmCounters.QuotaNonPagedPoolUsage, -1));
                getCellText->Text = node->NonPagedPoolText->sr;
                break;
            case PHPRTLC_PEAKNONPAGEDPOOL:
                PhMoveReference(&node->PeakNonPagedPoolText, PhFormatSize(processItem->VmCounters.QuotaPeakNonPagedPoolUsage, -1));
                getCellText->Text = node->PeakNonPagedPoolText->sr;
                break;
            case PHPRTLC_MINIMUMWORKINGSET:
                PhpUpdateProcessNodeQuotaLimits(node);
                PhMoveReference(&node->MinimumWorkingSetText, PhFormatSize(node->MinimumWorkingSetSize, -1));
                getCellText->Text = node->MinimumWorkingSetText->sr;
                break;
            case PHPRTLC_MAXIMUMWORKINGSET:
                PhpUpdateProcessNodeQuotaLimits(node);
                PhMoveReference(&node->MaximumWorkingSetText, PhFormatSize(node->MaximumWorkingSetSize, -1));
                getCellText->Text = node->MaximumWorkingSetText->sr;
                break;
            case PHPRTLC_PRIVATEBYTESDELTA:
                {
                    LONG_PTR delta;

                    delta = processItem->PrivateBytesDelta.Delta;

                    if (delta != 0)
                    {
                        PH_FORMAT format[2];

                        if (delta > 0)
                        {
                            PhInitFormatC(&format[0], '+');
                        }
                        else
                        {
                            PhInitFormatC(&format[0], '-');
                            delta = -delta;
                        }

                        format[1].Type = SizeFormatType | FormatUseRadix;
                        format[1].Radix = (UCHAR)PhMaxSizeUnit;
                        format[1].u.Size = delta;

                        PhMoveReference(&node->PrivateBytesDeltaText, PhFormat(format, 2, 0));
                        getCellText->Text = node->PrivateBytesDeltaText->sr;
                    }
                }
                break;
            case PHPRTLC_SUBSYSTEM:
                PhpUpdateProcessNodeImage(node);

                switch (node->ImageSubsystem)
                {
                case 0:
                    break;
                case IMAGE_SUBSYSTEM_NATIVE:
                    PhInitializeStringRef(&getCellText->Text, L"Native");
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_GUI:
                    PhInitializeStringRef(&getCellText->Text, L"Windows");
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_CUI:
                    PhInitializeStringRef(&getCellText->Text, L"Windows console");
                    break;
                case IMAGE_SUBSYSTEM_OS2_CUI:
                    PhInitializeStringRef(&getCellText->Text, L"OS/2");
                    break;
                case IMAGE_SUBSYSTEM_POSIX_CUI:
                    PhInitializeStringRef(&getCellText->Text, L"POSIX");
                    break;
                default:
                    PhInitializeStringRef(&getCellText->Text, L"Unknown");
                    break;
                }
                break;
            case PHPRTLC_PACKAGENAME:
                getCellText->Text = PhGetStringRef(processItem->PackageFullName);
                break;
            case PHPRTLC_APPID:
                PhpUpdateProcessNodeAppId(node);
                getCellText->Text = PhGetStringRef(node->AppIdText);
                break;
            case PHPRTLC_DPIAWARENESS:
                PhpUpdateProcessNodeDpiAwareness(node);

                switch (node->DpiAwareness)
                {
                case 0:
                    break;
                case 1:
                    PhInitializeStringRef(&getCellText->Text, L"Unaware");
                    break;
                case 2:
                    PhInitializeStringRef(&getCellText->Text, L"System aware");
                    break;
                case 3:
                    PhInitializeStringRef(&getCellText->Text, L"Per-monitor aware");
                    break;
                }
                break;
            case PHPRTLC_CFGUARD:
                PhpUpdateProcessNodeImage(node);

                if (WindowsVersion >= WINDOWS_8_1)
                {
                    if (node->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF)
                        PhInitializeStringRef(&getCellText->Text, L"CF Guard");
                }
                else
                {
                    PhInitializeStringRef(&getCellText->Text, L"N/A");
                }
                break;
            case PHPRTLC_TIMESTAMP:
                PhpUpdateProcessNodeImage(node);

                if (node->ImageTimeDateStamp != 0)
                {
                    LARGE_INTEGER time;
                    SYSTEMTIME systemTime;

                    RtlSecondsSince1970ToTime(node->ImageTimeDateStamp, &time);
                    PhLargeIntegerToLocalSystemTime(&systemTime, &time);

                    PhMoveReference(&node->TimeStampText, PhFormatDateTime(&systemTime));
                    getCellText->Text = node->TimeStampText->sr;
                }
                break;
            case PHPRTLC_FILEMODIFIEDTIME:
                PhpUpdateProcessNodeFileAttributes(node);

                if (node->FileLastWriteTime.QuadPart != 0)
                {
                    SYSTEMTIME systemTime;

                    PhLargeIntegerToLocalSystemTime(&systemTime, &node->FileLastWriteTime);
                    PhMoveReference(&node->FileModifiedTimeText, PhFormatDateTime(&systemTime));
                    getCellText->Text = node->FileModifiedTimeText->sr;
                }
                break;
            case PHPRTLC_FILESIZE:
                PhpUpdateProcessNodeFileAttributes(node);

                if (node->FileEndOfFile.QuadPart != -1)
                {
                    PhMoveReference(&node->FileSizeText, PhFormatSize(node->FileEndOfFile.QuadPart, -1));
                    getCellText->Text = node->FileSizeText->sr;
                }
                break;
            default:
                return FALSE;
            }

            getCellText->Flags = TN_CACHE;
        }
        return TRUE;
    case TreeNewGetNodeColor:
        {
            PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1;
            PPH_PROCESS_ITEM processItem;

            node = (PPH_PROCESS_NODE)getNodeColor->Node;
            processItem = node->ProcessItem;

            if (PhPluginsEnabled)
            {
                PH_PLUGIN_GET_HIGHLIGHTING_COLOR getHighlightingColor;

                getHighlightingColor.Parameter = processItem;
                getHighlightingColor.BackColor = RGB(0xff, 0xff, 0xff);
                getHighlightingColor.Handled = FALSE;
                getHighlightingColor.Cache = FALSE;

                PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackGetProcessHighlightingColor), &getHighlightingColor);

                if (getHighlightingColor.Handled)
                {
                    getNodeColor->BackColor = getHighlightingColor.BackColor;
                    getNodeColor->Flags = TN_AUTO_FORECOLOR;

                    if (getHighlightingColor.Cache)
                        getNodeColor->Flags |= TN_CACHE;

                    return TRUE;
                }
            }

            if (!processItem)
                ; // Dummy
            else if (PhCsUseColorDebuggedProcesses && processItem->IsBeingDebugged)
                getNodeColor->BackColor = PhCsColorDebuggedProcesses;
            else if (PhCsUseColorSuspended && processItem->IsSuspended)
                getNodeColor->BackColor = PhCsColorSuspended;
            else if (PhCsUseColorElevatedProcesses && processItem->IsElevated)
                getNodeColor->BackColor = PhCsColorElevatedProcesses;
            else if (PhCsUseColorImmersiveProcesses && processItem->IsImmersive)
                getNodeColor->BackColor = PhCsColorImmersiveProcesses;
            else if (PhCsUseColorDotNet && processItem->IsDotNet)
                getNodeColor->BackColor = PhCsColorDotNet;
            else if (PhCsUseColorPacked && processItem->IsPacked)
                getNodeColor->BackColor = PhCsColorPacked;
            else if (PhCsUseColorWow64Processes && processItem->IsWow64)
                getNodeColor->BackColor = PhCsColorWow64Processes;
            else if (PhCsUseColorJobProcesses && processItem->IsInSignificantJob)
                getNodeColor->BackColor = PhCsColorJobProcesses;
            else if (PhCsUseColorServiceProcesses && processItem->ServiceList && processItem->ServiceList->Count != 0)
                getNodeColor->BackColor = PhCsColorServiceProcesses;
            else if (
                PhCsUseColorSystemProcesses &&
                processItem->UserName &&
                PhEqualString(processItem->UserName, PhLocalSystemName, TRUE)
                )
                getNodeColor->BackColor = PhCsColorSystemProcesses;
            else if (
                PhCsUseColorOwnProcesses &&
                processItem->UserName &&
                PhCurrentUserName &&
                PhEqualString(processItem->UserName, PhCurrentUserName, TRUE)
                )
                getNodeColor->BackColor = PhCsColorOwnProcesses;

            getNodeColor->Flags = TN_CACHE | TN_AUTO_FORECOLOR;
        }
        return TRUE;
    case TreeNewGetNodeIcon:
        {
            PPH_TREENEW_GET_NODE_ICON getNodeIcon = Parameter1;

            node = (PPH_PROCESS_NODE)getNodeIcon->Node;

            if (node->ProcessItem->SmallIcon)
            {
                getNodeIcon->Icon = node->ProcessItem->SmallIcon;
            }
            else
            {
                PhGetStockApplicationIcon(&getNodeIcon->Icon, NULL);
            }

            getNodeIcon->Flags = TN_CACHE;
        }
        return TRUE;
    case TreeNewGetCellTooltip:
        {
            PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1;
            ULONG tickCount;

            node = (PPH_PROCESS_NODE)getCellTooltip->Node;

            if (getCellTooltip->Column->Id != 0)
                return FALSE;

            tickCount = GetTickCount();

            if ((LONG)(node->TooltipTextValidToTickCount - tickCount) < 0)
                PhClearReference(&node->TooltipText);
            if (!node->TooltipText)
                node->TooltipText = PhGetProcessTooltipText(node->ProcessItem, &node->TooltipTextValidToTickCount);

            if (!PhIsNullOrEmptyString(node->TooltipText))
            {
                getCellTooltip->Text = node->TooltipText->sr;
                getCellTooltip->Unfolding = FALSE;
                getCellTooltip->MaximumWidth = -1;
            }
            else
            {
                return FALSE;
            }
        }
        return TRUE;
    case TreeNewCustomDraw:
        {
            PPH_TREENEW_CUSTOM_DRAW customDraw = Parameter1;
            PPH_PROCESS_ITEM processItem;
            RECT rect;
            PH_GRAPH_DRAW_INFO drawInfo;

            node = (PPH_PROCESS_NODE)customDraw->Node;
            processItem = node->ProcessItem;
            rect = customDraw->CellRect;

            if (rect.right - rect.left <= 1)
                break; // nothing to draw

            // Generic graph pre-processing
            switch (customDraw->Column->Id)
            {
            case PHPRTLC_CPUHISTORY:
            case PHPRTLC_PRIVATEBYTESHISTORY:
            case PHPRTLC_IOHISTORY:
                memset(&drawInfo, 0, sizeof(PH_GRAPH_DRAW_INFO));
                drawInfo.Width = rect.right - rect.left - 1; // leave a small gap
                drawInfo.Height = rect.bottom - rect.top - 1; // leave a small gap
                drawInfo.Step = 2;
                drawInfo.BackColor = RGB(0x00, 0x00, 0x00);
                break;
            }

            // Specific graph processing
            switch (customDraw->Column->Id)
            {
            case PHPRTLC_CPUHISTORY:
                {
                    drawInfo.Flags = PH_GRAPH_USE_LINE_2;
                    drawInfo.LineColor1 = PhCsColorCpuKernel;
                    drawInfo.LineColor2 = PhCsColorCpuUser;
                    drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorCpuKernel);
                    drawInfo.LineBackColor2 = PhHalveColorBrightness(PhCsColorCpuUser);

                    PhGetDrawInfoGraphBuffers(
                        &node->CpuGraphBuffers,
                        &drawInfo,
                        processItem->CpuKernelHistory.Count
                        );

                    if (!node->CpuGraphBuffers.Valid)
                    {
                        PhCopyCircularBuffer_FLOAT(&processItem->CpuKernelHistory,
                            node->CpuGraphBuffers.Data1, drawInfo.LineDataCount);
                        PhCopyCircularBuffer_FLOAT(&processItem->CpuUserHistory,
                            node->CpuGraphBuffers.Data2, drawInfo.LineDataCount);
                        node->CpuGraphBuffers.Valid = TRUE;
                    }
                }
                break;
            case PHPRTLC_PRIVATEBYTESHISTORY:
                {
                    drawInfo.Flags = 0;
                    drawInfo.LineColor1 = PhCsColorPrivate;
                    drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorPrivate);

                    PhGetDrawInfoGraphBuffers(
                        &node->PrivateGraphBuffers,
                        &drawInfo,
                        processItem->PrivateBytesHistory.Count
                        );

                    if (!node->PrivateGraphBuffers.Valid)
                    {
                        ULONG i;
                        FLOAT total;
                        FLOAT max;

                        for (i = 0; i < drawInfo.LineDataCount; i++)
                        {
                            node->PrivateGraphBuffers.Data1[i] =
                                (FLOAT)PhGetItemCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, i);
                        }

                        // This makes it easier for the user to see what processes are hogging memory.
                        // Scaling is still *not* consistent across all graphs.
                        total = (FLOAT)PhPerfInformation.CommittedPages * PAGE_SIZE / 4; // divide by 4 to make the scaling a bit better
                        max = (FLOAT)processItem->VmCounters.PeakPagefileUsage;

                        if (max < total)
                            max = total;

                        if (max != 0)
                        {
                            // Scale the data.
                            PhDivideSinglesBySingle(
                                node->PrivateGraphBuffers.Data1,
                                max,
                                drawInfo.LineDataCount
                                );
                        }

                        node->PrivateGraphBuffers.Valid = TRUE;
                    }
                }
                break;
            case PHPRTLC_IOHISTORY:
                {
                    drawInfo.Flags = PH_GRAPH_USE_LINE_2;
                    drawInfo.LineColor1 = PhCsColorIoReadOther;
                    drawInfo.LineColor2 = PhCsColorIoWrite;
                    drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorIoReadOther);
                    drawInfo.LineBackColor2 = PhHalveColorBrightness(PhCsColorIoWrite);

                    PhGetDrawInfoGraphBuffers(
                        &node->IoGraphBuffers,
                        &drawInfo,
                        processItem->IoReadHistory.Count
                        );

                    if (!node->IoGraphBuffers.Valid)
                    {
                        ULONG i;
                        FLOAT total;
                        FLOAT max = 0;

                        for (i = 0; i < drawInfo.LineDataCount; i++)
                        {
                            FLOAT data1;
                            FLOAT data2;

                            node->IoGraphBuffers.Data1[i] = data1 =
                                (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoReadHistory, i) +
                                (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoOtherHistory, i);
                            node->IoGraphBuffers.Data2[i] = data2 =
                                (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoWriteHistory, i);

                            if (max < data1 + data2)
                                max = data1 + data2;
                        }

                        // Make the scaling a bit more consistent across the processes.
                        // It does *not* scale all graphs using the same maximum.
                        total = (FLOAT)(PhIoReadDelta.Delta + PhIoWriteDelta.Delta + PhIoOtherDelta.Delta);

                        if (max < total)
                            max = total;

                        if (max != 0)
                        {
                            // Scale the data.

                            PhDivideSinglesBySingle(
                                node->IoGraphBuffers.Data1,
                                max,
                                drawInfo.LineDataCount
                                );
                            PhDivideSinglesBySingle(
                                node->IoGraphBuffers.Data2,
                                max,
                                drawInfo.LineDataCount
                                );
                        }

                        node->IoGraphBuffers.Valid = TRUE;
                    }
                }
                break;
            }

            // Draw the graph.
            switch (customDraw->Column->Id)
            {
            case PHPRTLC_CPUHISTORY:
            case PHPRTLC_PRIVATEBYTESHISTORY:
            case PHPRTLC_IOHISTORY:
                PhpNeedGraphContext(customDraw->Dc, drawInfo.Width, drawInfo.Height);

                if (GraphBits)
                {
                    PhDrawGraphDirect(GraphContext, GraphBits, &drawInfo);
                    BitBlt(
                        customDraw->Dc,
                        rect.left,
                        rect.top + 1, // + 1 for small gap
                        drawInfo.Width,
                        drawInfo.Height,
                        GraphContext,
                        0,
                        0,
                        SRCCOPY
                        );
                }

                break;
            }
        }
        return TRUE;
    case TreeNewColumnResized:
        {
            PPH_TREENEW_COLUMN column = Parameter1;
            ULONG i;

            if (column->Id == PHPRTLC_CPUHISTORY || column->Id == PHPRTLC_IOHISTORY || column->Id == PHPRTLC_PRIVATEBYTESHISTORY)
            {
                for (i = 0; i < ProcessNodeList->Count; i++)
                {
                    node = ProcessNodeList->Items[i];

                    if (column->Id == PHPRTLC_CPUHISTORY)
                        node->CpuGraphBuffers.Valid = FALSE;
                    if (column->Id == PHPRTLC_IOHISTORY)
                        node->IoGraphBuffers.Valid = FALSE;
                    if (column->Id == PHPRTLC_PRIVATEBYTESHISTORY)
                        node->PrivateGraphBuffers.Valid = FALSE;
                }
            }
        }
        return TRUE;
    case TreeNewSortChanged:
        {
            TreeNew_GetSort(hwnd, &ProcessTreeListSortColumn, &ProcessTreeListSortOrder);
            // Force a rebuild to sort the items.
            TreeNew_NodesStructured(hwnd);
        }
        return TRUE;
    case TreeNewKeyDown:
        {
            PPH_TREENEW_KEY_EVENT keyEvent = Parameter1;

            switch (keyEvent->VirtualKey)
            {
            case 'C':
                if (GetKeyState(VK_CONTROL) < 0)
                    SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_COPY, 0);
                break;
            case 'A':
                if (GetKeyState(VK_CONTROL) < 0)
                    TreeNew_SelectRange(ProcessTreeListHandle, 0, -1);
                break;
            case VK_DELETE:
                if (GetKeyState(VK_SHIFT) >= 0)
                    SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_TERMINATE, 0);
                else
                    SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_TERMINATETREE, 0);
                break;
            case VK_RETURN:
                if (GetKeyState(VK_CONTROL) >= 0)
                    SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_PROPERTIES, 0);
                else
                    SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_OPENFILELOCATION, 0);
                break;
            }
        }
        return TRUE;
    case TreeNewHeaderRightClick:
        {
            PH_TN_COLUMN_MENU_DATA data;

            data.TreeNewHandle = hwnd;
            data.MouseEvent = Parameter1;
            data.DefaultSortColumn = 0;
            data.DefaultSortOrder = NoSortOrder;
            PhInitializeTreeNewColumnMenuEx(&data, PH_TN_COLUMN_MENU_SHOW_RESET_SORT);

            data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT,
                PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y);
            PhHandleTreeNewColumnMenu(&data);

            if (data.ProcessedId == PH_TN_COLUMN_MENU_HIDE_COLUMN_ID || data.ProcessedId == PH_TN_COLUMN_MENU_CHOOSE_COLUMNS_ID)
                PhpUpdateNeedCyclesInformation();

            PhDeleteTreeNewColumnMenu(&data);
        }
        return TRUE;
    case TreeNewLeftDoubleClick:
        {
            SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_PROPERTIES, 0);
        }
        return TRUE;
    case TreeNewContextMenu:
        {
            PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1;

            PhShowProcessContextMenu(contextMenu);
        }
        return TRUE;
    case TreeNewNodeExpanding:
        {
            node = Parameter1;

            if (PhCsPropagateCpuUsage)
                PhUpdateProcessNode(node);
        }
        return TRUE;
    }

    return FALSE;
}

PPH_PROCESS_ITEM PhGetSelectedProcessItem(
    VOID
    )
{
    PPH_PROCESS_ITEM processItem = NULL;
    ULONG i;

    for (i = 0; i < ProcessNodeList->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNodeList->Items[i];

        if (node->Node.Selected)
        {
            processItem = node->ProcessItem;
            break;
        }
    }

    return processItem;
}

VOID PhGetSelectedProcessItems(
    _Out_ PPH_PROCESS_ITEM **Processes,
    _Out_ PULONG NumberOfProcesses
    )
{
    PH_ARRAY array;
    ULONG i;

    PhInitializeArray(&array, sizeof(PVOID), 2);

    for (i = 0; i < ProcessNodeList->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNodeList->Items[i];

        if (node->Node.Selected)
            PhAddItemArray(&array, &node->ProcessItem);
    }

    *NumberOfProcesses = (ULONG)array.Count;
    *Processes = PhFinalArrayItems(&array);
}

VOID PhDeselectAllProcessNodes(
    VOID
    )
{
    TreeNew_DeselectRange(ProcessTreeListHandle, 0, -1);
}

VOID PhExpandAllProcessNodes(
    _In_ BOOLEAN Expand
    )
{
    ULONG i;
    BOOLEAN needsRestructure = FALSE;

    for (i = 0; i < ProcessNodeList->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNodeList->Items[i];

        if (node->Children->Count != 0 && node->Node.Expanded != Expand)
        {
            node->Node.Expanded = Expand;
            needsRestructure = TRUE;
        }
    }

    if (needsRestructure)
        TreeNew_NodesStructured(ProcessTreeListHandle);
}

VOID PhInvalidateAllProcessNodes(
    VOID
    )
{
    ULONG i;

    for (i = 0; i < ProcessNodeList->Count; i++)
    {
        PPH_PROCESS_NODE node = ProcessNodeList->Items[i];

        memset(node->TextCache, 0, sizeof(PH_STRINGREF) * PHPRTLC_MAXIMUM);
        PhInvalidateTreeNewNode(&node->Node, TN_CACHE_COLOR);
        node->ValidMask = 0;

        // Invalidate graph buffers.
        node->CpuGraphBuffers.Valid = FALSE;
        node->PrivateGraphBuffers.Valid = FALSE;
        node->IoGraphBuffers.Valid = FALSE;
    }

    InvalidateRect(ProcessTreeListHandle, NULL, FALSE);
}

VOID PhSelectAndEnsureVisibleProcessNode(
    _In_ PPH_PROCESS_NODE ProcessNode
    )
{
    PhSelectAndEnsureVisibleProcessNodes(&ProcessNode, 1);
}

VOID PhSelectAndEnsureVisibleProcessNodes(
    _In_ PPH_PROCESS_NODE *ProcessNodes,
    _In_ ULONG NumberOfProcessNodes
    )
{
    ULONG i;
    PPH_PROCESS_NODE leader = NULL;
    PPH_PROCESS_NODE node;
    BOOLEAN needsRestructure = FALSE;

    PhDeselectAllProcessNodes();

    for (i = 0; i < NumberOfProcessNodes; i++)
    {
        if (ProcessNodes[i]->Node.Visible)
        {
            leader = ProcessNodes[i];
            break;
        }
    }

    if (!leader)
        return;

    // Expand recursively upwards, and select the nodes.

    for (i = 0; i < NumberOfProcessNodes; i++)
    {
        if (!ProcessNodes[i]->Node.Visible)
            continue;

        node = ProcessNodes[i]->Parent;

        while (node)
        {
            if (!node->Node.Expanded)
                needsRestructure = TRUE;

            node->Node.Expanded = TRUE;
            node = node->Parent;
        }

        ProcessNodes[i]->Node.Selected = TRUE;
    }

    if (needsRestructure)
        TreeNew_NodesStructured(ProcessTreeListHandle);

    TreeNew_SetFocusNode(ProcessTreeListHandle, &leader->Node);
    TreeNew_SetMarkNode(ProcessTreeListHandle, &leader->Node);
    TreeNew_EnsureVisible(ProcessTreeListHandle, &leader->Node);
    TreeNew_InvalidateNode(ProcessTreeListHandle, &leader->Node);
}

VOID PhpPopulateTableWithProcessNodes(
    _In_ HWND TreeListHandle,
    _In_ PPH_PROCESS_NODE Node,
    _In_ ULONG Level,
    _In_ PPH_STRING **Table,
    _Inout_ PULONG Index,
    _In_ PULONG DisplayToId,
    _In_ ULONG Columns
    )
{
    ULONG i;

    for (i = 0; i < Columns; i++)
    {
        PH_TREENEW_GET_CELL_TEXT getCellText;
        PPH_STRING text;

        getCellText.Node = &Node->Node;
        getCellText.Id = DisplayToId[i];
        PhInitializeEmptyStringRef(&getCellText.Text);
        TreeNew_GetCellText(TreeListHandle, &getCellText);

        if (i != 0)
        {
            text = PhaCreateStringEx(getCellText.Text.Buffer, getCellText.Text.Length);
        }
        else
        {
            // If this is the first column in the row, add some indentation.
            text = PhaCreateStringEx(
                NULL,
                getCellText.Text.Length + Level * 2 * sizeof(WCHAR)
                );
            wmemset(text->Buffer, ' ', Level * 2);
            memcpy(&text->Buffer[Level * 2], getCellText.Text.Buffer, getCellText.Text.Length);
        }

        Table[*Index][i] = text;
    }

    (*Index)++;

    // Process the children.
    for (i = 0; i < Node->Children->Count; i++)
    {
        PhpPopulateTableWithProcessNodes(
            TreeListHandle,
            Node->Children->Items[i],
            Level + 1,
            Table,
            Index,
            DisplayToId,
            Columns
            );
    }
}

PPH_LIST PhGetProcessTreeListLines(
    _In_ HWND TreeListHandle,
    _In_ ULONG NumberOfNodes,
    _In_ PPH_LIST RootNodes,
    _In_ ULONG Mode
    )
{
    PH_AUTO_POOL autoPool;
    PPH_LIST lines;
    // The number of rows in the table (including +1 for the column headers).
    ULONG rows;
    // The number of columns.
    ULONG columns;
    // A column display index to ID map.
    PULONG displayToId;
    // A column display index to text map.
    PWSTR *displayToText;
    // The actual string table.
    PPH_STRING **table;
    ULONG i;
    ULONG j;

    // Use a local auto-pool to make memory mangement a bit less painful.
    PhInitializeAutoPool(&autoPool);

    rows = NumberOfNodes + 1;

    // Create the display index to ID map.
    PhMapDisplayIndexTreeNew(TreeListHandle, &displayToId, &displayToText, &columns);

    PhaCreateTextTable(&table, rows, columns);

    // Populate the first row with the column headers.
    for (i = 0; i < columns; i++)
    {
        table[0][i] = PhaCreateString(displayToText[i]);
    }

    // Go through the nodes in the process tree and populate each cell of the table.

    j = 1; // index starts at one because the first row contains the column headers.

    for (i = 0; i < RootNodes->Count; i++)
    {
        PhpPopulateTableWithProcessNodes(
            TreeListHandle,
            RootNodes->Items[i],
            0,
            table,
            &j,
            displayToId,
            columns
            );
    }

    PhFree(displayToId);
    PhFree(displayToText);

    lines = PhaFormatTextTable(table, rows, columns, Mode);

    PhDeleteAutoPool(&autoPool);

    return lines;
}

VOID PhCopyProcessTree(
    VOID
    )
{
    PPH_STRING text;

    text = PhGetTreeNewText(ProcessTreeListHandle, 0);
    PhSetClipboardString(ProcessTreeListHandle, &text->sr);
    PhDereferenceObject(text);
}

VOID PhWriteProcessTree(
    _Inout_ PPH_FILE_STREAM FileStream,
    _In_ ULONG Mode
    )
{
    PPH_LIST lines;
    ULONG i;

    lines = PhGetProcessTreeListLines(
        ProcessTreeListHandle,
        ProcessNodeList->Count,
        ProcessNodeRootList,
        Mode
        );

    for (i = 0; i < lines->Count; i++)
    {
        PPH_STRING line;

        line = lines->Items[i];
        PhWriteStringAsUtf8FileStream(FileStream, &line->sr);
        PhDereferenceObject(line);
        PhWriteStringAsUtf8FileStream2(FileStream, L"\r\n");
    }

    PhDereferenceObject(lines);
}

PPH_LIST PhDuplicateProcessNodeList(
    VOID
    )
{
    PPH_LIST newList;

    newList = PhCreateList(ProcessNodeList->Count);
    PhInsertItemsList(newList, 0, ProcessNodeList->Items, ProcessNodeList->Count);

    return newList;
}
