/*
 * Process Hacker Extended Services -
 *   trigger editor
 *
 * Copyright (C) 2011-2015 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/>.
 */

#include "extsrv.h"

typedef struct _ES_TRIGGER_DATA
{
    ULONG Type;
    union
    {
        PPH_STRING String;
        struct
        {
            PVOID Binary;
            ULONG BinaryLength;
        };
        UCHAR Byte;
        ULONG64 UInt64;
    };
} ES_TRIGGER_DATA, *PES_TRIGGER_DATA;

typedef struct _ES_TRIGGER_INFO
{
    ULONG Type;
    PGUID Subtype;
    ULONG Action;
    PPH_LIST DataList;
    GUID SubtypeBuffer;
} ES_TRIGGER_INFO, *PES_TRIGGER_INFO;

typedef struct _ES_TRIGGER_CONTEXT
{
    PPH_SERVICE_ITEM ServiceItem;
    HWND WindowHandle;
    HWND TriggersLv;
    BOOLEAN Dirty;
    ULONG InitialNumberOfTriggers;
    PPH_LIST InfoList;

    // Trigger dialog box
    PES_TRIGGER_INFO EditingInfo;
    ULONG LastSelectedType;
    PPH_STRING LastCustomSubType;

    // Value dialog box
    PPH_STRING EditingValue;
} ES_TRIGGER_CONTEXT, *PES_TRIGGER_CONTEXT;

typedef struct _TYPE_ENTRY
{
    ULONG Type;
    PWSTR Name;
} TYPE_ENTRY, PTYPE_ENTRY;

typedef struct _SUBTYPE_ENTRY
{
    ULONG Type;
    PGUID Guid;
    PWSTR Name;
} SUBTYPE_ENTRY, PSUBTYPE_ENTRY;

typedef struct _ETW_PUBLISHER_ENTRY
{
    PPH_STRING PublisherName;
    GUID Guid;
} ETW_PUBLISHER_ENTRY, *PETW_PUBLISHER_ENTRY;

INT_PTR CALLBACK EspServiceTriggerDlgProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    );

INT_PTR CALLBACK ValueDlgProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    );

static GUID NetworkManagerFirstIpAddressArrivalGuid = { 0x4f27f2de, 0x14e2, 0x430b, { 0xa5, 0x49, 0x7c, 0xd4, 0x8c, 0xbc, 0x82, 0x45 } };
static GUID NetworkManagerLastIpAddressRemovalGuid = { 0xcc4ba62a, 0x162e, 0x4648, { 0x84, 0x7a, 0xb6, 0xbd, 0xf9, 0x93, 0xe3, 0x35 } };
static GUID DomainJoinGuid = { 0x1ce20aba, 0x9851, 0x4421, { 0x94, 0x30, 0x1d, 0xde, 0xb7, 0x66, 0xe8, 0x09 } };
static GUID DomainLeaveGuid = { 0xddaf516e, 0x58c2, 0x4866, { 0x95, 0x74, 0xc3, 0xb6, 0x15, 0xd4, 0x2e, 0xa1 } };
static GUID FirewallPortOpenGuid = { 0xb7569e07, 0x8421, 0x4ee0, { 0xad, 0x10, 0x86, 0x91, 0x5a, 0xfd, 0xad, 0x09 } };
static GUID FirewallPortCloseGuid = { 0xa144ed38, 0x8e12, 0x4de4, { 0x9d, 0x96, 0xe6, 0x47, 0x40, 0xb1, 0xa5, 0x24 } };
static GUID MachinePolicyPresentGuid = { 0x659fcae6, 0x5bdb, 0x4da9, { 0xb1, 0xff, 0xca, 0x2a, 0x17, 0x8d, 0x46, 0xe0 } };
static GUID UserPolicyPresentGuid = { 0x54fb46c8, 0xf089, 0x464c, { 0xb1, 0xfd, 0x59, 0xd1, 0xb6, 0x2c, 0x3b, 0x50 } };
static GUID RpcInterfaceEventGuid = { 0xbc90d167, 0x9470, 0x4139, { 0xa9, 0xba, 0xbe, 0x0b, 0xbb, 0xf5, 0xb7, 0x4d } };
static GUID NamedPipeEventGuid = { 0x1f81d131, 0x3fac, 0x4537, { 0x9e, 0x0c, 0x7e, 0x7b, 0x0c, 0x2f, 0x4b, 0x55 } };
static GUID SubTypeUnknownGuid; // dummy

static TYPE_ENTRY TypeEntries[] =
{
    { SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL, L"Device interface arrival" },
    { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, L"IP address availability" },
    { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, L"Domain join" },
    { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, L"Firewall port event" },
    { SERVICE_TRIGGER_TYPE_GROUP_POLICY, L"Group policy" },
    { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, L"Network endpoint" },
    { SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE, L"Custom system state change" },
    { SERVICE_TRIGGER_TYPE_CUSTOM, L"Custom" }
};

static SUBTYPE_ENTRY SubTypeEntries[] =
{
    { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, NULL, L"IP address" },
    { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, &NetworkManagerFirstIpAddressArrivalGuid, L"IP address: First arrival" },
    { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, &NetworkManagerLastIpAddressRemovalGuid, L"IP address: Last removal" },
    { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, &SubTypeUnknownGuid, L"IP address: Unknown" },
    { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, NULL, L"Domain" },
    { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, &DomainJoinGuid, L"Domain: Join" },
    { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, &DomainLeaveGuid, L"Domain: Leave" },
    { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, &SubTypeUnknownGuid, L"Domain: Unknown" },
    { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, NULL, L"Firewall port" },
    { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, &FirewallPortOpenGuid, L"Firewall port: Open" },
    { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, &FirewallPortCloseGuid, L"Firewall port: Close" },
    { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, &SubTypeUnknownGuid, L"Firewall port: Unknown" },
    { SERVICE_TRIGGER_TYPE_GROUP_POLICY, NULL, L"Group policy change" },
    { SERVICE_TRIGGER_TYPE_GROUP_POLICY, &MachinePolicyPresentGuid, L"Group policy change: Machine" },
    { SERVICE_TRIGGER_TYPE_GROUP_POLICY, &UserPolicyPresentGuid, L"Group policy change: User" },
    { SERVICE_TRIGGER_TYPE_GROUP_POLICY, &SubTypeUnknownGuid, L"Group policy change: Unknown" },
    { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, NULL, L"Network endpoint" },
    { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, &RpcInterfaceEventGuid, L"Network endpoint: RPC interface" },
    { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, &NamedPipeEventGuid, L"Network endpoint: Named pipe" },
    { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, &SubTypeUnknownGuid, L"Network endpoint: Unknown" }
};

static PH_STRINGREF PublishersKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\");

PES_TRIGGER_DATA EspCreateTriggerData(
    _In_opt_ PSERVICE_TRIGGER_SPECIFIC_DATA_ITEM DataItem
    )
{
    PES_TRIGGER_DATA data;

    data = PhAllocate(sizeof(ES_TRIGGER_DATA));
    memset(data, 0, sizeof(ES_TRIGGER_DATA));

    if (DataItem)
    {
        data->Type = DataItem->dwDataType;

        if (data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING)
        {
            if (DataItem->pData && DataItem->cbData >= 2)
                data->String = PhCreateStringEx((PWSTR)DataItem->pData, DataItem->cbData - 2); // exclude final null terminator
            else
                data->String = PhReferenceEmptyString();
        }
        else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY)
        {
            data->BinaryLength = DataItem->cbData;
            data->Binary = PhAllocateCopy(DataItem->pData, DataItem->cbData);
        }
        else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_LEVEL)
        {
            if (DataItem->cbData == sizeof(UCHAR))
                data->Byte = *(PUCHAR)DataItem->pData;
        }
        else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY || data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL)
        {
            if (DataItem->cbData == sizeof(ULONG64))
                data->UInt64 = *(PULONG64)DataItem->pData;
        }
    }

    return data;
}

PES_TRIGGER_DATA EspCloneTriggerData(
    _In_ PES_TRIGGER_DATA Data
    )
{
    PES_TRIGGER_DATA newData;

    newData = PhAllocateCopy(Data, sizeof(ES_TRIGGER_DATA));

    if (newData->Type == SERVICE_TRIGGER_DATA_TYPE_STRING)
    {
        if (newData->String)
            newData->String = PhDuplicateString(newData->String);
    }
    else if (newData->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY)
    {
        if (newData->Binary)
            newData->Binary = PhAllocateCopy(newData->Binary, newData->BinaryLength);
    }

    return newData;
}

VOID EspDestroyTriggerData(
    _In_ PES_TRIGGER_DATA Data
    )
{
    if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING)
    {
        if (Data->String)
            PhDereferenceObject(Data->String);
    }
    else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY)
    {
        if (Data->Binary)
            PhFree(Data->Binary);
    }

    PhFree(Data);
}

PES_TRIGGER_INFO EspCreateTriggerInfo(
    _In_opt_ PSERVICE_TRIGGER Trigger
    )
{
    PES_TRIGGER_INFO info;

    info = PhAllocate(sizeof(ES_TRIGGER_INFO));
    memset(info, 0, sizeof(ES_TRIGGER_INFO));

    if (Trigger)
    {
        info->Type = Trigger->dwTriggerType;

        if (Trigger->pTriggerSubtype)
        {
            info->SubtypeBuffer = *Trigger->pTriggerSubtype;
            info->Subtype = &info->SubtypeBuffer;
        }

        info->Action = Trigger->dwAction;

        if (
            info->Type == SERVICE_TRIGGER_TYPE_CUSTOM ||
            info->Type == SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL ||
            info->Type == SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT ||
            info->Type == SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT
            )
        {
            ULONG i;

            info->DataList = PhCreateList(Trigger->cDataItems);

            for (i = 0; i < Trigger->cDataItems; i++)
            {
                PhAddItemList(info->DataList, EspCreateTriggerData(&Trigger->pDataItems[i]));
            }
        }
    }

    return info;
}

PES_TRIGGER_INFO EspCloneTriggerInfo(
    _In_ PES_TRIGGER_INFO Info
    )
{
    PES_TRIGGER_INFO newInfo;

    newInfo = PhAllocateCopy(Info, sizeof(ES_TRIGGER_INFO));

    if (newInfo->Subtype == &Info->SubtypeBuffer)
        newInfo->Subtype = &newInfo->SubtypeBuffer;

    if (newInfo->DataList)
    {
        ULONG i;

        newInfo->DataList = PhCreateList(Info->DataList->AllocatedCount);
        newInfo->DataList->Count = Info->DataList->Count;

        for (i = 0; i < Info->DataList->Count; i++)
            newInfo->DataList->Items[i] = EspCloneTriggerData(Info->DataList->Items[i]);
    }

    return newInfo;
}

VOID EspDestroyTriggerInfo(
    _In_ PES_TRIGGER_INFO Info
    )
{
    if (Info->DataList)
    {
        ULONG i;

        for (i = 0; i < Info->DataList->Count; i++)
        {
            EspDestroyTriggerData(Info->DataList->Items[i]);
        }

        PhDereferenceObject(Info->DataList);
    }

    PhFree(Info);
}

VOID EspClearTriggerInfoList(
    _In_ PPH_LIST List
    )
{
    ULONG i;

    for (i = 0; i < List->Count; i++)
    {
        EspDestroyTriggerInfo(List->Items[i]);
    }

    PhClearList(List);
}

struct _ES_TRIGGER_CONTEXT *EsCreateServiceTriggerContext(
    _In_ PPH_SERVICE_ITEM ServiceItem,
    _In_ HWND WindowHandle,
    _In_ HWND TriggersLv
    )
{
    PES_TRIGGER_CONTEXT context;

    context = PhAllocate(sizeof(ES_TRIGGER_CONTEXT));
    memset(context, 0, sizeof(ES_TRIGGER_CONTEXT));
    context->ServiceItem = ServiceItem;
    context->WindowHandle = WindowHandle;
    context->TriggersLv = TriggersLv;
    context->InfoList = PhCreateList(4);

    PhSetListViewStyle(TriggersLv, FALSE, TRUE);
    PhSetControlTheme(TriggersLv, L"explorer");
    PhAddListViewColumn(TriggersLv, 0, 0, 0, LVCFMT_LEFT, 300, L"Trigger");
    PhAddListViewColumn(TriggersLv, 1, 1, 1, LVCFMT_LEFT, 60, L"Action");
    PhSetExtendedListView(TriggersLv);

    EnableWindow(GetDlgItem(WindowHandle, IDC_EDIT), FALSE);
    EnableWindow(GetDlgItem(WindowHandle, IDC_DELETE), FALSE);

    return context;
}

VOID EsDestroyServiceTriggerContext(
    _In_ struct _ES_TRIGGER_CONTEXT *Context
    )
{
    ULONG i;

    for (i = 0; i < Context->InfoList->Count; i++)
    {
        EspDestroyTriggerInfo(Context->InfoList->Items[i]);
    }

    PhDereferenceObject(Context->InfoList);
    PhFree(Context);
}

PPH_STRING EspLookupEtwPublisherName(
    _In_ PGUID Guid
    )
{
    PPH_STRING guidString;
    PPH_STRING keyName;
    HANDLE keyHandle;
    PPH_STRING publisherName = NULL;

    // Copied from ProcessHacker\hndlinfo.c.

    guidString = PhFormatGuid(Guid);

    keyName = PhConcatStringRef2(&PublishersKeyName, &guidString->sr);

    if (NT_SUCCESS(PhOpenKey(
        &keyHandle,
        KEY_READ,
        PH_KEY_LOCAL_MACHINE,
        &keyName->sr,
        0
        )))
    {
        publisherName = PhQueryRegistryString(keyHandle, NULL);

        if (publisherName && publisherName->Length == 0)
        {
            PhDereferenceObject(publisherName);
            publisherName = NULL;
        }

        NtClose(keyHandle);
    }

    PhDereferenceObject(keyName);

    if (publisherName)
    {
        PhDereferenceObject(guidString);
        return publisherName;
    }
    else
    {
        return guidString;
    }
}

BOOLEAN EspEnumerateEtwPublishers(
    _Out_ PETW_PUBLISHER_ENTRY *Entries,
    _Out_ PULONG NumberOfEntries
    )
{
    NTSTATUS status;
    HANDLE publishersKeyHandle;
    ULONG index;
    PKEY_BASIC_INFORMATION buffer;
    ULONG bufferSize;
    PETW_PUBLISHER_ENTRY entries;
    ULONG numberOfEntries;
    ULONG allocatedEntries;

    if (!NT_SUCCESS(PhOpenKey(
        &publishersKeyHandle,
        KEY_READ,
        PH_KEY_LOCAL_MACHINE,
        &PublishersKeyName,
        0
        )))
    {
        return FALSE;
    }

    numberOfEntries = 0;
    allocatedEntries = 256;
    entries = PhAllocate(allocatedEntries * sizeof(ETW_PUBLISHER_ENTRY));

    index = 0;
    bufferSize = 0x100;
    buffer = PhAllocate(0x100);

    while (TRUE)
    {
        status = NtEnumerateKey(
            publishersKeyHandle,
            index,
            KeyBasicInformation,
            buffer,
            bufferSize,
            &bufferSize
            );

        if (NT_SUCCESS(status))
        {
            UNICODE_STRING nameUs;
            PH_STRINGREF name;
            HANDLE keyHandle;
            GUID guid;
            PPH_STRING publisherName;

            nameUs.Buffer = buffer->Name;
            nameUs.Length = (USHORT)buffer->NameLength;
            name.Buffer = buffer->Name;
            name.Length = buffer->NameLength;

            // Make sure this is a valid publisher key.
            if (NT_SUCCESS(RtlGUIDFromString(&nameUs, &guid)))
            {
                if (NT_SUCCESS(PhOpenKey(
                    &keyHandle,
                    KEY_READ,
                    publishersKeyHandle,
                    &name,
                    0
                    )))
                {
                    publisherName = PhQueryRegistryString(keyHandle, NULL);

                    if (publisherName)
                    {
                        if (publisherName->Length != 0)
                        {
                            PETW_PUBLISHER_ENTRY entry;

                            if (numberOfEntries == allocatedEntries)
                            {
                                allocatedEntries *= 2;
                                entries = PhReAllocate(entries, allocatedEntries * sizeof(ETW_PUBLISHER_ENTRY));
                            }

                            entry = &entries[numberOfEntries++];
                            entry->PublisherName = publisherName;
                            entry->Guid = guid;
                        }
                        else
                        {
                            PhDereferenceObject(publisherName);
                        }
                    }

                    NtClose(keyHandle);
                }
            }

            index++;
        }
        else if (status == STATUS_NO_MORE_ENTRIES)
        {
            break;
        }
        else if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL)
        {
            PhFree(buffer);
            buffer = PhAllocate(bufferSize);
        }
        else
        {
            break;
        }
    }

    PhFree(buffer);
    NtClose(publishersKeyHandle);

    *Entries = entries;
    *NumberOfEntries = numberOfEntries;

    return TRUE;
}

BOOLEAN EspLookupEtwPublisherGuid(
    _In_ PPH_STRINGREF PublisherName,
    _Out_ PGUID Guid
    )
{
    BOOLEAN result;
    PETW_PUBLISHER_ENTRY entries;
    ULONG numberOfEntries;
    ULONG i;

    if (!EspEnumerateEtwPublishers(&entries, &numberOfEntries))
        return FALSE;

    result = FALSE;

    for (i = 0; i < numberOfEntries; i++)
    {
        if (!result && PhEqualStringRef(&entries[i].PublisherName->sr, PublisherName, TRUE))
        {
            *Guid = entries[i].Guid;
            result = TRUE;
        }

        PhDereferenceObject(entries[i].PublisherName);
    }

    PhFree(entries);

    return result;
}

VOID EspFormatTriggerInfo(
    _In_ PES_TRIGGER_INFO Info,
    _Out_ PWSTR *TriggerString,
    _Out_ PWSTR *ActionString,
    _Out_ PPH_STRING *StringUsed
    )
{
    PPH_STRING stringUsed = NULL;
    PWSTR triggerString = NULL;
    PWSTR actionString;
    ULONG i;
    BOOLEAN typeFound;
    BOOLEAN subTypeFound;

    switch (Info->Type)
    {
    case SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL:
        {
            PPH_STRING guidString;

            if (!Info->Subtype)
            {
                triggerString = L"Device interface arrival";
            }
            else
            {
                guidString = PhFormatGuid(Info->Subtype);
                stringUsed = PhConcatStrings2(L"Device interface arrival: ", guidString->Buffer);
                triggerString = stringUsed->Buffer;
            }
        }
        break;
    case SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE:
        {
            PPH_STRING guidString;

            if (!Info->Subtype)
            {
                triggerString = L"Custom system state change";
            }
            else
            {
                guidString = PhFormatGuid(Info->Subtype);
                stringUsed = PhConcatStrings2(L"Custom system state change: ", guidString->Buffer);
                triggerString = stringUsed->Buffer;
            }
        }
        break;
    case SERVICE_TRIGGER_TYPE_CUSTOM:
        {
            if (Info->Subtype)
            {
                PPH_STRING publisherName;

                // Try to lookup the publisher name from the GUID.
                publisherName = EspLookupEtwPublisherName(Info->Subtype);
                stringUsed = PhConcatStrings2(L"Custom: ", publisherName->Buffer);
                PhDereferenceObject(publisherName);
                triggerString = stringUsed->Buffer;
            }
            else
            {
                triggerString = L"Custom";
            }
        }
        break;
    default:
        {
            typeFound = FALSE;
            subTypeFound = FALSE;

            for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++)
            {
                if (SubTypeEntries[i].Type == Info->Type)
                {
                    typeFound = TRUE;

                    if (!Info->Subtype && !SubTypeEntries[i].Guid)
                    {
                        subTypeFound = TRUE;
                        triggerString = SubTypeEntries[i].Name;
                        break;
                    }
                    else if (Info->Subtype && SubTypeEntries[i].Guid && memcmp(Info->Subtype, SubTypeEntries[i].Guid, sizeof(GUID)) == 0)
                    {
                        subTypeFound = TRUE;
                        triggerString = SubTypeEntries[i].Name;
                        break;
                    }
                    else if (!subTypeFound && SubTypeEntries[i].Guid == &SubTypeUnknownGuid)
                    {
                        triggerString = SubTypeEntries[i].Name;
                        break;
                    }
                }
            }

            if (!typeFound)
            {
                triggerString = L"Unknown";
            }
        }
        break;
    }

    switch (Info->Action)
    {
    case SERVICE_TRIGGER_ACTION_SERVICE_START:
        actionString = L"Start";
        break;
    case SERVICE_TRIGGER_ACTION_SERVICE_STOP:
        actionString = L"Stop";
        break;
    default:
        actionString = L"Unknown";
        break;
    }

    *TriggerString = triggerString;
    *ActionString = actionString;
    *StringUsed = stringUsed;
}

VOID EsLoadServiceTriggerInfo(
    _In_ struct _ES_TRIGGER_CONTEXT *Context,
    _In_ SC_HANDLE ServiceHandle
    )
{
    PSERVICE_TRIGGER_INFO triggerInfo;
    ULONG i;

    EspClearTriggerInfoList(Context->InfoList);

    if (triggerInfo = PhQueryServiceVariableSize(ServiceHandle, SERVICE_CONFIG_TRIGGER_INFO))
    {
        for (i = 0; i < triggerInfo->cTriggers; i++)
        {
            PSERVICE_TRIGGER trigger = &triggerInfo->pTriggers[i];
            PES_TRIGGER_INFO info;
            PWSTR triggerString;
            PWSTR actionString;
            PPH_STRING stringUsed;
            INT lvItemIndex;

            info = EspCreateTriggerInfo(trigger);
            PhAddItemList(Context->InfoList, info);

            EspFormatTriggerInfo(info, &triggerString, &actionString, &stringUsed);

            lvItemIndex = PhAddListViewItem(Context->TriggersLv, MAXINT, triggerString, info);
            PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 1, actionString);

            if (stringUsed)
                PhDereferenceObject(stringUsed);
        }

        Context->InitialNumberOfTriggers = triggerInfo->cTriggers;

        ExtendedListView_SortItems(Context->TriggersLv);

        PhFree(triggerInfo);
    }
}

BOOLEAN EsSaveServiceTriggerInfo(
    _In_ struct _ES_TRIGGER_CONTEXT *Context,
    _Out_ PULONG Win32Result
    )
{
    BOOLEAN result = TRUE;
    PH_AUTO_POOL autoPool;
    SC_HANDLE serviceHandle;
    SERVICE_TRIGGER_INFO triggerInfo;
    ULONG i;
    ULONG j;

    if (!Context->Dirty)
        return TRUE;

    // Do not try to change trigger information if we didn't have any triggers before and we don't
    // have any now. ChangeServiceConfig2 returns an error in this situation.
    if (Context->InitialNumberOfTriggers == 0 && Context->InfoList->Count == 0)
        return TRUE;

    PhInitializeAutoPool(&autoPool);

    memset(&triggerInfo, 0, sizeof(SERVICE_TRIGGER_INFO));
    triggerInfo.cTriggers = Context->InfoList->Count;

    // pTriggers needs to be NULL when there are no triggers.
    if (Context->InfoList->Count != 0)
    {
        triggerInfo.pTriggers = PH_AUTO(PhCreateAlloc(Context->InfoList->Count * sizeof(SERVICE_TRIGGER)));
        memset(triggerInfo.pTriggers, 0, Context->InfoList->Count * sizeof(SERVICE_TRIGGER));

        for (i = 0; i < Context->InfoList->Count; i++)
        {
            PSERVICE_TRIGGER trigger = &triggerInfo.pTriggers[i];
            PES_TRIGGER_INFO info = Context->InfoList->Items[i];

            trigger->dwTriggerType = info->Type;
            trigger->dwAction = info->Action;
            trigger->pTriggerSubtype = info->Subtype;

            if (info->DataList && info->DataList->Count != 0)
            {
                trigger->cDataItems = info->DataList->Count;
                trigger->pDataItems = PH_AUTO(PhCreateAlloc(info->DataList->Count * sizeof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM)));

                for (j = 0; j < info->DataList->Count; j++)
                {
                    PSERVICE_TRIGGER_SPECIFIC_DATA_ITEM dataItem = &trigger->pDataItems[j];
                    PES_TRIGGER_DATA data = info->DataList->Items[j];

                    dataItem->dwDataType = data->Type;

                    if (data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING)
                    {
                        dataItem->cbData = (ULONG)data->String->Length + 2; // include null terminator
                        dataItem->pData = (PBYTE)data->String->Buffer;
                    }
                    else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY)
                    {
                        dataItem->cbData = data->BinaryLength;
                        dataItem->pData = data->Binary;
                    }
                    else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_LEVEL)
                    {
                        dataItem->cbData = sizeof(UCHAR);
                        dataItem->pData = (PBYTE)&data->Byte;
                    }
                    else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY || data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL)
                    {
                        dataItem->cbData = sizeof(ULONG64);
                        dataItem->pData = (PBYTE)&data->UInt64;
                    }
                }
            }
        }
    }

    if (serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_CHANGE_CONFIG))
    {
        if (!ChangeServiceConfig2(serviceHandle, SERVICE_CONFIG_TRIGGER_INFO, &triggerInfo))
        {
            result = FALSE;
            *Win32Result = GetLastError();
        }

        CloseServiceHandle(serviceHandle);
    }
    else
    {
        result = FALSE;
        *Win32Result = GetLastError();

        if (*Win32Result == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated)
        {
            // Elevate using phsvc.
            if (PhUiConnectToPhSvc(Context->WindowHandle, FALSE))
            {
                NTSTATUS status;

                result = TRUE;

                if (!NT_SUCCESS(status = PhSvcCallChangeServiceConfig2(Context->ServiceItem->Name->Buffer,
                    SERVICE_CONFIG_TRIGGER_INFO, &triggerInfo)))
                {
                    result = FALSE;
                    *Win32Result = PhNtStatusToDosError(status);
                }

                PhUiDisconnectFromPhSvc();
            }
            else
            {
                // User cancelled elevation.
                *Win32Result = ERROR_CANCELLED;
            }
        }
    }

    PhDeleteAutoPool(&autoPool);

    return result;
}

LOGICAL EspSetListViewItemParam(
    _In_ HWND ListViewHandle,
    _In_ INT Index,
    _In_ PVOID Param
    )
{
    LVITEM item;

    item.mask = LVIF_PARAM;
    item.iItem = Index;
    item.iSubItem = 0;
    item.lParam = (LPARAM)Param;

    return ListView_SetItem(ListViewHandle, &item);
}

VOID EsHandleEventServiceTrigger(
    _In_ struct _ES_TRIGGER_CONTEXT *Context,
    _In_ ULONG Event
    )
{
    switch (Event)
    {
    case ES_TRIGGER_EVENT_NEW:
        {
            Context->EditingInfo = EspCreateTriggerInfo(NULL);
            Context->EditingInfo->Type = SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY;
            Context->EditingInfo->SubtypeBuffer = NetworkManagerFirstIpAddressArrivalGuid;
            Context->EditingInfo->Subtype = &Context->EditingInfo->SubtypeBuffer;
            Context->EditingInfo->Action = SERVICE_TRIGGER_ACTION_SERVICE_START;

            if (DialogBoxParam(
                PluginInstance->DllBase,
                MAKEINTRESOURCE(IDD_SRVTRIGGER),
                Context->WindowHandle,
                EspServiceTriggerDlgProc,
                (LPARAM)Context
                ) == IDOK)
            {
                PWSTR triggerString;
                PWSTR actionString;
                PPH_STRING stringUsed;
                INT lvItemIndex;

                Context->Dirty = TRUE;
                PhAddItemList(Context->InfoList, Context->EditingInfo);

                EspFormatTriggerInfo(Context->EditingInfo, &triggerString, &actionString, &stringUsed);

                lvItemIndex = PhAddListViewItem(Context->TriggersLv, MAXINT, triggerString, Context->EditingInfo);
                PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 1, actionString);

                if (stringUsed)
                    PhDereferenceObject(stringUsed);
            }
            else
            {
                EspDestroyTriggerInfo(Context->EditingInfo);
            }

            Context->EditingInfo = NULL;
        }
        break;
    case ES_TRIGGER_EVENT_EDIT:
        {
            INT lvItemIndex;
            PES_TRIGGER_INFO info;
            ULONG index;

            lvItemIndex = ListView_GetNextItem(Context->TriggersLv, -1, LVNI_SELECTED);

            if (lvItemIndex != -1 && PhGetListViewItemParam(Context->TriggersLv, lvItemIndex, (PVOID *)&info))
            {
                index = PhFindItemList(Context->InfoList, info);

                if (index != -1)
                {
                    Context->EditingInfo = EspCloneTriggerInfo(info);

                    if (DialogBoxParam(
                        PluginInstance->DllBase,
                        MAKEINTRESOURCE(IDD_SRVTRIGGER),
                        Context->WindowHandle,
                        EspServiceTriggerDlgProc,
                        (LPARAM)Context
                        ) == IDOK)
                    {
                        PWSTR triggerString;
                        PWSTR actionString;
                        PPH_STRING stringUsed;

                        Context->Dirty = TRUE;
                        EspDestroyTriggerInfo(Context->InfoList->Items[index]);
                        Context->InfoList->Items[index] = Context->EditingInfo;

                        EspFormatTriggerInfo(Context->EditingInfo, &triggerString, &actionString, &stringUsed);
                        PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 0, triggerString);
                        PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 1, actionString);

                        if (stringUsed)
                            PhDereferenceObject(stringUsed);

                        EspSetListViewItemParam(Context->TriggersLv, lvItemIndex, Context->EditingInfo);
                    }
                    else
                    {
                        EspDestroyTriggerInfo(Context->EditingInfo);
                    }

                    Context->EditingInfo = NULL;
                }
            }
        }
        break;
    case ES_TRIGGER_EVENT_DELETE:
        {
            INT lvItemIndex;
            PES_TRIGGER_INFO info;
            ULONG index;

            lvItemIndex = ListView_GetNextItem(Context->TriggersLv, -1, LVNI_SELECTED);

            if (lvItemIndex != -1 && PhGetListViewItemParam(Context->TriggersLv, lvItemIndex, (PVOID *)&info))
            {
                index = PhFindItemList(Context->InfoList, info);

                if (index != -1)
                {
                    EspDestroyTriggerInfo(info);
                    PhRemoveItemList(Context->InfoList, index);
                    PhRemoveListViewItem(Context->TriggersLv, lvItemIndex);
                }
            }

            Context->Dirty = TRUE;
        }
        break;
    case ES_TRIGGER_EVENT_SELECTIONCHANGED:
        {
            ULONG selectedCount;

            selectedCount = ListView_GetSelectedCount(Context->TriggersLv);

            EnableWindow(GetDlgItem(Context->WindowHandle, IDC_EDIT), selectedCount == 1);
            EnableWindow(GetDlgItem(Context->WindowHandle, IDC_DELETE), selectedCount == 1);
        }
        break;
    }
}

ULONG EspTriggerTypeStringToInteger(
    _In_ PWSTR String
    )
{
    ULONG i;

    for (i = 0; i < sizeof(TypeEntries) / sizeof(TYPE_ENTRY); i++)
    {
        if (PhEqualStringZ(TypeEntries[i].Name, String, FALSE))
            return TypeEntries[i].Type;
    }

    return 0;
}

static int __cdecl EtwPublisherByNameCompareFunction(
    _In_ const void *elem1,
    _In_ const void *elem2
    )
{
    PETW_PUBLISHER_ENTRY entry1 = (PETW_PUBLISHER_ENTRY)elem1;
    PETW_PUBLISHER_ENTRY entry2 = (PETW_PUBLISHER_ENTRY)elem2;

    return PhCompareString(entry1->PublisherName, entry2->PublisherName, TRUE);
}

VOID EspFixServiceTriggerControls(
    _In_ HWND hwndDlg,
    _In_ PES_TRIGGER_CONTEXT Context
    )
{
    HWND typeComboBox;
    HWND subTypeComboBox;
    ULONG i;
    PPH_STRING selectedTypeString;
    ULONG type;
    PPH_STRING selectedSubTypeString;

    typeComboBox = GetDlgItem(hwndDlg, IDC_TYPE);
    subTypeComboBox = GetDlgItem(hwndDlg, IDC_SUBTYPE);

    selectedTypeString = PhGetWindowText(typeComboBox);
    type = EspTriggerTypeStringToInteger(selectedTypeString->Buffer);
    PhDereferenceObject(selectedTypeString);

    if (Context->LastSelectedType != type)
    {
        // Change the contents of the subtype combo box based on the type.

        ComboBox_ResetContent(subTypeComboBox);

        switch (type)
        {
        case SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL:
            {
                ComboBox_AddString(subTypeComboBox, L"Custom");
            }
            break;
        case SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE:
            {
                ComboBox_AddString(subTypeComboBox, L"Custom");
            }
            break;
        case SERVICE_TRIGGER_TYPE_CUSTOM:
            {
                PETW_PUBLISHER_ENTRY entries;
                ULONG numberOfEntries;
                ULONG i;

                ComboBox_AddString(subTypeComboBox, L"Custom");

                // Display a list of publishers.
                if (EspEnumerateEtwPublishers(&entries, &numberOfEntries))
                {
                    // Sort the list by name.
                    qsort(entries, numberOfEntries, sizeof(ETW_PUBLISHER_ENTRY), EtwPublisherByNameCompareFunction);

                    for (i = 0; i < numberOfEntries; i++)
                    {
                        ComboBox_AddString(subTypeComboBox, entries[i].PublisherName->Buffer);
                        PhDereferenceObject(entries[i].PublisherName);
                    }

                    PhFree(entries);
                }
            }
            break;
        default:
            for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++)
            {
                if (SubTypeEntries[i].Type == type && SubTypeEntries[i].Guid && SubTypeEntries[i].Guid != &SubTypeUnknownGuid)
                {
                    ComboBox_AddString(subTypeComboBox, SubTypeEntries[i].Name);
                }
            }
            break;
        }

        ComboBox_SetCurSel(subTypeComboBox, 0);

        Context->LastSelectedType = type;
    }

    selectedSubTypeString = PhGetWindowText(subTypeComboBox);

    if (PhEqualString2(selectedSubTypeString, L"Custom", FALSE))
    {
        EnableWindow(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM), TRUE);
        SetDlgItemText(hwndDlg, IDC_SUBTYPECUSTOM, Context->LastCustomSubType->Buffer);
    }
    else
    {
        if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM)))
        {
            EnableWindow(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM), FALSE);
            PhMoveReference(&Context->LastCustomSubType, PhGetWindowText(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM)));
            SetDlgItemText(hwndDlg, IDC_SUBTYPECUSTOM, L"");
        }
    }

    PhDereferenceObject(selectedSubTypeString);
}

PPH_STRING EspConvertNullsToNewLines(
    _In_ PPH_STRING String
    )
{
    PH_STRING_BUILDER sb;
    ULONG i;

    PhInitializeStringBuilder(&sb, String->Length);

    for (i = 0; i < (ULONG)String->Length / 2; i++)
    {
        if (String->Buffer[i] == 0)
        {
            PhAppendStringBuilderEx(&sb, L"\r\n", 4);
            continue;
        }

        PhAppendCharStringBuilder(&sb, String->Buffer[i]);
    }

    return PhFinalStringBuilderString(&sb);
}

PPH_STRING EspConvertNewLinesToNulls(
    _In_ PPH_STRING String
    )
{
    PPH_STRING text;
    SIZE_T count;
    SIZE_T i;

    text = PhCreateStringEx(NULL, String->Length + 2); // plus one character for an extra null terminator (see below)
    text->Length = 0;
    count = 0;

    for (i = 0; i < String->Length / 2; i++)
    {
        // Lines are terminated by "\r\n".
        if (String->Buffer[i] == '\r')
        {
            continue;
        }

        if (String->Buffer[i] == '\n')
        {
            text->Buffer[count++] = 0;
            continue;
        }

        text->Buffer[count++] = String->Buffer[i];
    }

    if (count != 0)
    {
        // Make sure we have an extra null terminator at the end, as required of multistrings.
        if (text->Buffer[count - 1] != 0)
            text->Buffer[count++] = 0;
    }

    text->Length = count * 2;

    return text;
}

PPH_STRING EspConvertNullsToSpaces(
    _In_ PPH_STRING String
    )
{
    PPH_STRING text;
    SIZE_T j;

    text = PhDuplicateString(String);

    for (j = 0; j < text->Length / 2; j++)
    {
        if (text->Buffer[j] == 0)
            text->Buffer[j] = ' ';
    }

    return text;
}

VOID EspFormatTriggerData(
    _In_ PES_TRIGGER_DATA Data,
    _Out_ PPH_STRING *Text
    )
{
    if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING)
    {
        // This check works for both normal strings and multistrings.
        if (Data->String->Length != 0)
        {
            // Prepare the text for display by replacing null characters with spaces.
            *Text = EspConvertNullsToSpaces(Data->String);
        }
        else
        {
            *Text = PhCreateString(L"(empty string)");
        }
    }
    else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY)
    {
        *Text = PhCreateString(L"(binary data)");
    }
    else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_LEVEL)
    {
        *Text = PhFormatString(L"(level) %u", Data->Byte);
    }
    else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY)
    {
        *Text = PhFormatString(L"(keyword any) 0x%I64x", Data->UInt64);
    }
    else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL)
    {
        *Text = PhFormatString(L"(keyword all) 0x%I64x", Data->UInt64);
    }
    else
    {
        *Text = PhCreateString(L"(unknown type)");
    }
}

INT_PTR CALLBACK EspServiceTriggerDlgProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    PES_TRIGGER_CONTEXT context;

    if (uMsg == WM_INITDIALOG)
    {
        context = (PES_TRIGGER_CONTEXT)lParam;
        SetProp(hwndDlg, L"Context", (HANDLE)context);
    }
    else
    {
        context = (PES_TRIGGER_CONTEXT)GetProp(hwndDlg, L"Context");

        if (uMsg == WM_DESTROY)
            RemoveProp(hwndDlg, L"Context");
    }

    if (!context)
        return FALSE;

    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            HWND typeComboBox;
            HWND actionComboBox;
            HWND lvHandle;
            ULONG i;

            context->LastSelectedType = 0;

            if (context->EditingInfo->Subtype)
                context->LastCustomSubType = PhFormatGuid(context->EditingInfo->Subtype);
            else
                context->LastCustomSubType = PhReferenceEmptyString();

            typeComboBox = GetDlgItem(hwndDlg, IDC_TYPE);
            actionComboBox = GetDlgItem(hwndDlg, IDC_ACTION);

            for (i = 0; i < sizeof(TypeEntries) / sizeof(TYPE_ENTRY); i++)
            {
                ComboBox_AddString(typeComboBox, TypeEntries[i].Name);

                if (TypeEntries[i].Type == context->EditingInfo->Type)
                {
                    PhSelectComboBoxString(typeComboBox, TypeEntries[i].Name, FALSE);
                }
            }

            ComboBox_AddString(actionComboBox, L"Start");
            ComboBox_AddString(actionComboBox, L"Stop");
            ComboBox_SetCurSel(actionComboBox, context->EditingInfo->Action == SERVICE_TRIGGER_ACTION_SERVICE_START ? 0 : 1);

            EspFixServiceTriggerControls(hwndDlg, context);

            if (context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_CUSTOM)
            {
                for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++)
                {
                    if (
                        SubTypeEntries[i].Type == context->EditingInfo->Type &&
                        SubTypeEntries[i].Guid &&
                        context->EditingInfo->Subtype &&
                        memcmp(SubTypeEntries[i].Guid, context->EditingInfo->Subtype, sizeof(GUID)) == 0
                        )
                    {
                        PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_SUBTYPE), SubTypeEntries[i].Name, FALSE);
                        break;
                    }
                }
            }
            else
            {
                if (context->EditingInfo->Subtype)
                {
                    PPH_STRING publisherName;

                    // Try to select the publisher name in the subtype list.
                    publisherName = EspLookupEtwPublisherName(context->EditingInfo->Subtype);
                    PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_SUBTYPE), publisherName->Buffer, FALSE);
                    PhDereferenceObject(publisherName);
                }
            }

            // Call a second time since the state of the custom subtype text box may have changed.
            EspFixServiceTriggerControls(hwndDlg, context);

            lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
            PhSetListViewStyle(lvHandle, FALSE, TRUE);
            PhSetControlTheme(lvHandle, L"explorer");
            PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 280, L"Data");

            if (context->EditingInfo->DataList)
            {
                for (i = 0; i < context->EditingInfo->DataList->Count; i++)
                {
                    PES_TRIGGER_DATA data;
                    PPH_STRING text;
                    INT lvItemIndex;

                    data = context->EditingInfo->DataList->Items[i];

                    EspFormatTriggerData(data, &text);
                    lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, text->Buffer, data);
                    PhDereferenceObject(text);
                }
            }

            EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), FALSE);
            EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE);
        }
        break;
    case WM_DESTROY:
        {
            PhClearReference(&context->LastCustomSubType);
        }
        break;
    case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
            case IDC_TYPE:
                if (HIWORD(wParam) == CBN_SELCHANGE)
                {
                    EspFixServiceTriggerControls(hwndDlg, context);
                }
                break;
            case IDC_SUBTYPE:
                if (HIWORD(wParam) == CBN_SELCHANGE)
                {
                    EspFixServiceTriggerControls(hwndDlg, context);
                }
                break;
            case IDC_NEW:
                {
                    HWND lvHandle;

                    lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
                    context->EditingValue = PhReferenceEmptyString();

                    if (DialogBoxParam(
                        PluginInstance->DllBase,
                        MAKEINTRESOURCE(IDD_VALUE),
                        hwndDlg,
                        ValueDlgProc,
                        (LPARAM)context
                        ) == IDOK)
                    {
                        PES_TRIGGER_DATA data;
                        PPH_STRING text;
                        INT lvItemIndex;

                        data = EspCreateTriggerData(NULL);
                        data->Type = SERVICE_TRIGGER_DATA_TYPE_STRING;
                        data->String = EspConvertNewLinesToNulls(context->EditingValue);

                        if (!context->EditingInfo->DataList)
                            context->EditingInfo->DataList = PhCreateList(4);

                        PhAddItemList(context->EditingInfo->DataList, data);

                        EspFormatTriggerData(data, &text);
                        lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, text->Buffer, data);
                        PhDereferenceObject(text);
                    }

                    PhClearReference(&context->EditingValue);
                }
                break;
            case IDC_EDIT:
                {
                    HWND lvHandle;
                    INT lvItemIndex;
                    PES_TRIGGER_DATA data;
                    ULONG index;

                    lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
                    lvItemIndex = ListView_GetNextItem(lvHandle, -1, LVNI_SELECTED);

                    if (
                        lvItemIndex != -1 && PhGetListViewItemParam(lvHandle, lvItemIndex, (PVOID *)&data) &&
                        data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING // editing binary values is not supported
                        )
                    {
                        index = PhFindItemList(context->EditingInfo->DataList, data);

                        if (index != -1)
                        {
                            context->EditingValue = EspConvertNullsToNewLines(data->String);

                            if (DialogBoxParam(
                                PluginInstance->DllBase,
                                MAKEINTRESOURCE(IDD_VALUE),
                                hwndDlg,
                                ValueDlgProc,
                                (LPARAM)context
                                ) == IDOK)
                            {
                                PPH_STRING text;

                                PhMoveReference(&data->String, EspConvertNewLinesToNulls(context->EditingValue));

                                EspFormatTriggerData(data, &text);
                                PhSetListViewSubItem(lvHandle, lvItemIndex, 0, text->Buffer);
                                PhDereferenceObject(text);
                            }

                            PhClearReference(&context->EditingValue);
                        }
                    }
                }
                break;
            case IDC_DELETE:
                {
                    HWND lvHandle;
                    INT lvItemIndex;
                    PES_TRIGGER_DATA data;
                    ULONG index;

                    lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
                    lvItemIndex = ListView_GetNextItem(lvHandle, -1, LVNI_SELECTED);

                    if (lvItemIndex != -1 && PhGetListViewItemParam(lvHandle, lvItemIndex, (PVOID *)&data))
                    {
                        index = PhFindItemList(context->EditingInfo->DataList, data);

                        if (index != -1)
                        {
                            EspDestroyTriggerData(data);
                            PhRemoveItemList(context->EditingInfo->DataList, index);
                            PhRemoveListViewItem(lvHandle, lvItemIndex);
                        }
                    }
                }
                break;
            case IDCANCEL:
                EndDialog(hwndDlg, IDCANCEL);
                break;
            case IDOK:
                {
                    PH_AUTO_POOL autoPool;
                    PPH_STRING typeString;
                    PPH_STRING subTypeString;
                    PPH_STRING customSubTypeString;
                    PPH_STRING actionString;
                    ULONG i;

                    PhInitializeAutoPool(&autoPool);

                    typeString = PhaGetDlgItemText(hwndDlg, IDC_TYPE);
                    subTypeString = PhaGetDlgItemText(hwndDlg, IDC_SUBTYPE);
                    customSubTypeString = PhaGetDlgItemText(hwndDlg, IDC_SUBTYPECUSTOM);
                    actionString = PhaGetDlgItemText(hwndDlg, IDC_ACTION);

                    for (i = 0; i < sizeof(TypeEntries) / sizeof(TYPE_ENTRY); i++)
                    {
                        if (PhEqualStringZ(TypeEntries[i].Name, typeString->Buffer, FALSE))
                        {
                            context->EditingInfo->Type = TypeEntries[i].Type;
                            break;
                        }
                    }

                    if (!PhEqualString2(subTypeString, L"Custom", FALSE))
                    {
                        if (context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_CUSTOM)
                        {
                            for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++)
                            {
                                if (
                                    SubTypeEntries[i].Type == context->EditingInfo->Type &&
                                    PhEqualString2(subTypeString, SubTypeEntries[i].Name, FALSE)
                                    )
                                {
                                    context->EditingInfo->SubtypeBuffer = *SubTypeEntries[i].Guid;
                                    context->EditingInfo->Subtype = &context->EditingInfo->SubtypeBuffer;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            if (!EspLookupEtwPublisherGuid(&subTypeString->sr, &context->EditingInfo->SubtypeBuffer))
                            {
                                PhShowError(hwndDlg, L"Unable to find the ETW publisher GUID.");
                                goto DoNotClose;
                            }

                            context->EditingInfo->Subtype = &context->EditingInfo->SubtypeBuffer;
                        }
                    }
                    else
                    {
                        UNICODE_STRING guidString;

                        PhStringRefToUnicodeString(&customSubTypeString->sr, &guidString);

                        // Trim whitespace.

                        while (guidString.Length != 0 && *guidString.Buffer == ' ')
                        {
                            guidString.Buffer++;
                            guidString.Length -= 2;
                        }

                        while (guidString.Length != 0 && guidString.Buffer[guidString.Length / 2 - 1] == ' ')
                        {
                            guidString.Length -= 2;
                        }

                        if (NT_SUCCESS(RtlGUIDFromString(&guidString, &context->EditingInfo->SubtypeBuffer)))
                        {
                            context->EditingInfo->Subtype = &context->EditingInfo->SubtypeBuffer;
                        }
                        else
                        {
                            PhShowError(hwndDlg, L"The custom subtype is invalid. Please ensure that the string is a valid GUID: \"{x-x-x-x-x}\".");
                            goto DoNotClose;
                        }
                    }

                    if (PhEqualString2(actionString, L"Start", FALSE))
                        context->EditingInfo->Action = SERVICE_TRIGGER_ACTION_SERVICE_START;
                    else
                        context->EditingInfo->Action = SERVICE_TRIGGER_ACTION_SERVICE_STOP;

                    if (
                        context->EditingInfo->DataList &&
                        context->EditingInfo->DataList->Count != 0 &&
                        context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL &&
                        context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT &&
                        context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT &&
                        context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_CUSTOM
                        )
                    {
                        // This trigger has data items, but the trigger type doesn't allow them.
                        if (PhShowMessage(
                            hwndDlg,
                            MB_OKCANCEL | MB_ICONWARNING,
                            L"The trigger type \"%s\" does not allow data items to be configured. "
                            L"If you continue, they will be removed.",
                            typeString->Buffer
                            ) != IDOK)
                        {
                            goto DoNotClose;
                        }

                        for (i = 0; i < context->EditingInfo->DataList->Count; i++)
                        {
                            EspDestroyTriggerData(context->EditingInfo->DataList->Items[i]);
                        }

                        PhClearReference(&context->EditingInfo->DataList);
                    }

                    EndDialog(hwndDlg, IDOK);

DoNotClose:
                    PhDeleteAutoPool(&autoPool);
                }
                break;
            }
        }
        break;
    case WM_NOTIFY:
        {
            LPNMHDR header = (LPNMHDR)lParam;
            HWND lvHandle;

            lvHandle = GetDlgItem(hwndDlg, IDC_LIST);

            switch (header->code)
            {
            case LVN_ITEMCHANGED:
                {
                    if (header->hwndFrom == lvHandle)
                    {
                        if (ListView_GetSelectedCount(lvHandle) == 1)
                        {
                            PES_TRIGGER_DATA data = PhGetSelectedListViewItemParam(GetDlgItem(hwndDlg, IDC_LIST));

                            // Editing binary data is not supported.
                            EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), data && data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING);
                            EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), TRUE);
                        }
                        else
                        {
                            EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), FALSE);
                            EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE);
                        }
                    }
                }
                break;
            case NM_DBLCLK:
                {
                    if (header->hwndFrom == lvHandle)
                    {
                        SendMessage(hwndDlg, WM_COMMAND, IDC_EDIT, 0);
                    }
                }
                break;
            }
        }
        break;
    }

    return FALSE;
}

INT_PTR CALLBACK ValueDlgProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    PES_TRIGGER_CONTEXT context;

    if (uMsg == WM_INITDIALOG)
    {
        context = (PES_TRIGGER_CONTEXT)lParam;
        SetProp(hwndDlg, L"Context", (HANDLE)context);
    }
    else
    {
        context = (PES_TRIGGER_CONTEXT)GetProp(hwndDlg, L"Context");

        if (uMsg == WM_DESTROY)
            RemoveProp(hwndDlg, L"Context");
    }

    if (!context)
        return FALSE;

    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            SetDlgItemText(hwndDlg, IDC_VALUES, context->EditingValue->Buffer);
            SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_VALUES), TRUE);
            Edit_SetSel(GetDlgItem(hwndDlg, IDC_VALUES), 0, -1);
        }
        break;
    case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
            case IDCANCEL:
                EndDialog(hwndDlg, IDCANCEL);
                break;
            case IDOK:
                PhMoveReference(&context->EditingValue, PhGetWindowText(GetDlgItem(hwndDlg, IDC_VALUES)));
                EndDialog(hwndDlg, IDOK);
                break;
            }
        }
        break;
    }

    return FALSE;
}
