/*
 * Copyright (c) 2022 Winsider Seminars & Solutions, Inc.  All rights reserved.
 *
 * This file is part of System Informer.
 *
 * Authors:
 *
 *     wj32    2010-2015
 *     dmex    2020-2023
 *
 */

#include "extsrv.h"

typedef struct _SERVICE_OTHER_CONTEXT
{
    PPH_SERVICE_ITEM ServiceItem;

    struct
    {
        ULONG Ready : 1;
        ULONG Dirty : 1;
        ULONG PreshutdownTimeoutValid : 1;
        ULONG RequiredPrivilegesValid : 1;
        ULONG SidTypeValid : 1;
        ULONG LaunchProtectedValid : 1;
    };
    HWND PrivilegesLv;
    PPH_LIST PrivilegeList;
    PH_LAYOUT_MANAGER LayoutManager;
    ULONG OriginalLaunchProtected;
} SERVICE_OTHER_CONTEXT, *PSERVICE_OTHER_CONTEXT;

static _RtlCreateServiceSid RtlCreateServiceSid_I = NULL;

static PH_KEY_VALUE_PAIR EspServiceSidTypePairs[] =
{
    SIP(L"None", SERVICE_SID_TYPE_NONE),
    SIP(L"Restricted", SERVICE_SID_TYPE_RESTRICTED),
    SIP(L"Unrestricted", SERVICE_SID_TYPE_UNRESTRICTED)
};

static PH_KEY_VALUE_PAIR EspServiceLaunchProtectedPairs[] =
{
    SIP(L"None", SERVICE_LAUNCH_PROTECTED_NONE),
    SIP(L"Full (Windows)", SERVICE_LAUNCH_PROTECTED_WINDOWS),
    SIP(L"Light (Windows)", SERVICE_LAUNCH_PROTECTED_WINDOWS_LIGHT),
    SIP(L"Light (Antimalware)", SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT),
    SIP(L"Light (StoreApp)", 0x4),
};

static WCHAR *EspServiceSidTypeStrings[3] = { L"None", L"Restricted", L"Unrestricted" };
static WCHAR *EspServiceLaunchProtectedStrings[4] = { L"None", L"Full (Windows)", L"Light (Windows)", L"Light (Antimalware)" };

PCWSTR EspGetServiceSidTypeString(
    _In_ ULONG SidType
    )
{
    PCWSTR string;

    if (PhFindStringSiKeyValuePairs(
        EspServiceSidTypePairs,
        sizeof(EspServiceSidTypePairs),
        SidType,
        &string
        ))
        return string;
    else
        return L"Unknown";
}

ULONG EspGetServiceSidTypeInteger(
    _In_ PCWSTR SidType
    )
{
    ULONG integer;

    if (PhFindIntegerSiKeyValuePairs(
        EspServiceSidTypePairs,
        sizeof(EspServiceSidTypePairs),
        SidType,
        &integer
        ))
        return integer;
    else
        return ULONG_MAX;
}

PCWSTR EspGetServiceLaunchProtectedString(
    _In_ ULONG LaunchProtected
    )
{
    PCWSTR string;

    if (PhFindStringSiKeyValuePairs(
        EspServiceLaunchProtectedPairs,
        sizeof(EspServiceLaunchProtectedPairs),
        LaunchProtected,
        &string
        ))
        return string;
    else
        return L"Unknown";
}

ULONG EspGetServiceLaunchProtectedInteger(
    _In_ PCWSTR LaunchProtected
    )
{
    ULONG integer;

    if (PhFindIntegerSiKeyValuePairs(
        EspServiceLaunchProtectedPairs,
        sizeof(EspServiceLaunchProtectedPairs),
        LaunchProtected,
        &integer
        ))
        return integer;
    else
        return ULONG_MAX;
}

NTSTATUS EspLoadOtherInfo(
    _In_ HWND WindowHandle,
    _In_ PSERVICE_OTHER_CONTEXT Context
    )
{
    NTSTATUS status;
    SC_HANDLE serviceHandle;
    SERVICE_PRESHUTDOWN_INFO preshutdownInfo;
    LPSERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivilegesInfo;
    SERVICE_SID_INFO sidInfo;
    SERVICE_LAUNCH_PROTECTED_INFO launchProtectedInfo;

    status = PhOpenService(&serviceHandle, SERVICE_QUERY_CONFIG, PhGetString(Context->ServiceItem->Name));

    if (!NT_SUCCESS(status))
        return status;

    // Preshutdown timeout

    if (NT_SUCCESS(PhQueryServiceConfig2(
        serviceHandle,
        SERVICE_CONFIG_PRESHUTDOWN_INFO,
        &preshutdownInfo,
        sizeof(SERVICE_PRESHUTDOWN_INFO),
        NULL
        )))
    {
        PhSetDialogItemValue(WindowHandle, IDC_PRESHUTDOWNTIMEOUT, preshutdownInfo.dwPreshutdownTimeout, FALSE);
        Context->PreshutdownTimeoutValid = TRUE;
    }

    // Required privileges

    if (NT_SUCCESS(PhQueryServiceVariableSize(
        serviceHandle,
        SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO,
        &requiredPrivilegesInfo
        )))
    {
        PWSTR privilege;
        ULONG privilegeLength;
        INT lvItemIndex;
        PH_STRINGREF privilegeSr;
        PPH_STRING privilegeString;
        PPH_STRING displayName;

        if (requiredPrivilegesInfo->pmszRequiredPrivileges)
        {
            for (privilege = requiredPrivilegesInfo->pmszRequiredPrivileges; *privilege; privilege += PhCountStringZ(privilege) + 1)
            {
                privilegeLength = (ULONG)PhCountStringZ(privilege);

                if (privilegeLength == 0)
                    continue;

                privilegeString = PhCreateStringEx(privilege, privilegeLength * sizeof(WCHAR));
                PhAddItemList(Context->PrivilegeList, privilegeString);

                lvItemIndex = PhAddListViewItem(Context->PrivilegesLv, MAXINT, privilege, privilegeString);
                privilegeSr.Buffer = privilege;
                privilegeSr.Length = privilegeLength * sizeof(WCHAR);

                if (PhLookupPrivilegeDisplayName(&privilegeSr, &displayName))
                {
                    PhSetListViewSubItem(Context->PrivilegesLv, lvItemIndex, 1, displayName->Buffer);
                    PhDereferenceObject(displayName);
                }
            }

            ExtendedListView_SortItems(Context->PrivilegesLv);
        }

        PhFree(requiredPrivilegesInfo);
        Context->RequiredPrivilegesValid = TRUE;
    }

    // SID type

    if (NT_SUCCESS(PhQueryServiceConfig2(
        serviceHandle,
        SERVICE_CONFIG_SERVICE_SID_INFO,
        &sidInfo,
        sizeof(SERVICE_SID_INFO),
        NULL
        )))
    {
        PhSelectComboBoxString(GetDlgItem(WindowHandle, IDC_SIDTYPE),
            EspGetServiceSidTypeString(sidInfo.dwServiceSidType), FALSE);
        Context->SidTypeValid = TRUE;
    }

    // Launch protected

    if (NT_SUCCESS(PhQueryServiceConfig2(
        serviceHandle,
        SERVICE_CONFIG_LAUNCH_PROTECTED,
        &launchProtectedInfo,
        sizeof(SERVICE_LAUNCH_PROTECTED_INFO),
        NULL
        )))
    {
        PhSelectComboBoxString(GetDlgItem(WindowHandle, IDC_PROTECTION),
            EspGetServiceLaunchProtectedString(launchProtectedInfo.dwLaunchProtected), FALSE);
        Context->LaunchProtectedValid = TRUE;
        Context->OriginalLaunchProtected = launchProtectedInfo.dwLaunchProtected;
    }

    PhCloseServiceHandle(serviceHandle);

    return status;
}

PPH_STRING EspGetServiceSidString(
    _In_ PPH_STRINGREF ServiceName
    )
{
    BYTE serviceSidBuffer[SECURITY_MAX_SID_SIZE] = { 0 };
    ULONG serviceSidLength = sizeof(serviceSidBuffer);
    PSID serviceSid = (PSID)serviceSidBuffer;
    UNICODE_STRING serviceName;

    if (!RtlCreateServiceSid_I)
    {
        if (!(RtlCreateServiceSid_I = PhGetModuleProcAddress(L"ntdll.dll", "RtlCreateServiceSid")))
            return NULL;
    }

    PhStringRefToUnicodeString(ServiceName, &serviceName);

    if (NT_SUCCESS(RtlCreateServiceSid_I(&serviceName, serviceSid, &serviceSidLength)))
    {
        return PhSidToStringSid(serviceSid);
    }

    return NULL;
}

NTSTATUS EspChangeServiceConfig2(
    _In_ PWSTR ServiceName,
    _In_opt_ SC_HANDLE ServiceHandle,
    _In_ ULONG InfoLevel,
    _In_ PVOID Info
    )
{
    if (ServiceHandle)
        return PhChangeServiceConfig2(ServiceHandle, InfoLevel, Info);
    else
        return PhSvcCallChangeServiceConfig2(ServiceName, InfoLevel, Info);
}

static int __cdecl EspPrivilegeNameCompareFunction(
    _In_ const void *elem1,
    _In_ const void *elem2
    )
{
    PCWSTR string1 = *(PCWSTR *)elem1;
    PCWSTR string2 = *(PCWSTR *)elem2;

    return PhCompareStringZ(string1, string2, TRUE);
}

NTSTATUS NTAPI EspEnumeratePrivilegesCallback(
    _In_ PPOLICY_PRIVILEGE_DEFINITION Privileges,
    _In_ ULONG NumberOfPrivileges,
    _In_ PVOID Context
    )
{
    for (ULONG i = 0; i < NumberOfPrivileges; i++)
    {
        PhAddItemList(Context, PhaCreateStringEx(Privileges[i].Name.Buffer, Privileges[i].Name.Length)->Buffer);
    }

    return STATUS_SUCCESS;
}

INT_PTR CALLBACK EspServiceOtherDlgProc(
    _In_ HWND WindowHandle,
    _In_ UINT WindowMessage,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    PSERVICE_OTHER_CONTEXT context;

    if (WindowMessage == WM_INITDIALOG)
    {
        context = PhAllocate(sizeof(SERVICE_OTHER_CONTEXT));
        memset(context, 0, sizeof(SERVICE_OTHER_CONTEXT));

        PhSetWindowContext(WindowHandle, PH_WINDOW_CONTEXT_DEFAULT, context);
    }
    else
    {
        context = PhGetWindowContext(WindowHandle, PH_WINDOW_CONTEXT_DEFAULT);
    }

    if (!context)
        return FALSE;

    switch (WindowMessage)
    {
    case WM_INITDIALOG:
        {
            NTSTATUS status;
            LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam;
            PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam;
            HWND privilegesLv;

            context->ServiceItem = serviceItem;

            context->PrivilegesLv = privilegesLv = GetDlgItem(WindowHandle, IDC_PRIVILEGES);
            PhSetListViewStyle(privilegesLv, FALSE, TRUE);
            PhSetControlTheme(privilegesLv, L"explorer");
            PhAddListViewColumn(privilegesLv, 0, 0, 0, LVCFMT_LEFT, 140, L"Name");
            PhAddListViewColumn(privilegesLv, 1, 1, 1, LVCFMT_LEFT, 220, L"Display name");
            PhSetExtendedListView(privilegesLv);

            context->PrivilegeList = PhCreateList(32);

            if (context->ServiceItem->Type == SERVICE_KERNEL_DRIVER || context->ServiceItem->Type == SERVICE_FILE_SYSTEM_DRIVER)
            {
                // Drivers don't support required privileges.
                EnableWindow(GetDlgItem(WindowHandle, IDC_ADD), FALSE);
            }

            EnableWindow(GetDlgItem(WindowHandle, IDC_REMOVE), FALSE);

            PhAddComboBoxStrings(GetDlgItem(WindowHandle, IDC_SIDTYPE),
                EspServiceSidTypeStrings, sizeof(EspServiceSidTypeStrings) / sizeof(PWSTR));
            PhAddComboBoxStrings(GetDlgItem(WindowHandle, IDC_PROTECTION),
                EspServiceLaunchProtectedStrings, sizeof(EspServiceLaunchProtectedStrings) / sizeof(PWSTR));

            if (PhWindowsVersion < WINDOWS_8_1)
                EnableWindow(GetDlgItem(WindowHandle, IDC_PROTECTION), FALSE);

            PhSetDialogItemText(WindowHandle, IDC_SERVICESID,
                PhGetStringOrDefault(PH_AUTO(EspGetServiceSidString(&serviceItem->Name->sr)), L"N/A"));

            status = EspLoadOtherInfo(WindowHandle, context);

            if (!NT_SUCCESS(status))
            {
                PPH_STRING errorMessage = PhGetNtMessage(status);

                PhShowWarning2(
                    WindowHandle,
                    L"Unable to query service information."
                    L"%s",
                    PhGetStringOrDefault(errorMessage, L"Unknown error.")
                    );

                PhClearReference(&errorMessage);
            }

            context->Ready = TRUE;

            PhInitializeLayoutManager(&context->LayoutManager, WindowHandle);
            PhAddLayoutItem(&context->LayoutManager, GetDlgItem(WindowHandle, IDC_PRIVILEGES), NULL, PH_ANCHOR_ALL);
            PhAddLayoutItem(&context->LayoutManager, GetDlgItem(WindowHandle, IDC_ADD), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT);
            PhAddLayoutItem(&context->LayoutManager, GetDlgItem(WindowHandle, IDC_REMOVE), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT);

            PhInitializeWindowTheme(WindowHandle, !!PhGetIntegerSetting(L"EnableThemeSupport"));
        }
        break;
    case WM_DESTROY:
        {
            PhRemoveWindowContext(WindowHandle, PH_WINDOW_CONTEXT_DEFAULT);

            PhDeleteLayoutManager(&context->LayoutManager);

            if (context->PrivilegeList)
            {
                PhDereferenceObjects(context->PrivilegeList->Items, context->PrivilegeList->Count);
                PhDereferenceObject(context->PrivilegeList);
            }

            PhFree(context);
        }
        break;
    case WM_COMMAND:
        {
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
            case IDC_ADD:
                {
                    NTSTATUS status;
                    ULONG i;
                    PPH_LIST choices;
                    PPH_STRING selectedChoice = NULL;

                    choices = PH_AUTO(PhCreateList(100));

                    status = PhEnumeratePrivileges(EspEnumeratePrivilegesCallback, choices);

                    if (!NT_SUCCESS(status))
                    {
                        PhShowStatus(WindowHandle, L"Unable to open LSA policy", status, 0);
                        break;
                    }

                    qsort(choices->Items, choices->Count, sizeof(PWSTR), EspPrivilegeNameCompareFunction);

                    while (PhaChoiceDialog(
                        WindowHandle,
                        L"Add privilege",
                        L"Select a privilege to add:",
                        (PWSTR *)choices->Items,
                        choices->Count,
                        NULL,
                        PH_CHOICE_DIALOG_CHOICE,
                        &selectedChoice,
                        NULL,
                        NULL
                        ))
                    {
                        BOOLEAN found = FALSE;
                        PPH_STRING privilegeString;
                        INT lvItemIndex;
                        PPH_STRING displayName;

                        // Check for duplicates.
                        for (i = 0; i < context->PrivilegeList->Count; i++)
                        {
                            if (PhEqualString(context->PrivilegeList->Items[i], selectedChoice, FALSE))
                            {
                                found = TRUE;
                                break;
                            }
                        }

                        if (found)
                        {
                            if (PhShowMessage2(
                                WindowHandle,
                                TD_OK_BUTTON | TD_CANCEL_BUTTON,
                                TD_ERROR_ICON,
                                 L"The selected privilege has already been added.",
                                 L"%s",
                                 L""
                                 ) == IDOK)
                            {
                                continue;
                            }
                            else
                            {
                                break;
                            }
                        }

                        PhSetReference(&privilegeString, selectedChoice);
                        PhAddItemList(context->PrivilegeList, privilegeString);

                        lvItemIndex = PhAddListViewItem(context->PrivilegesLv, MAXINT, privilegeString->Buffer, privilegeString);

                        if (PhLookupPrivilegeDisplayName(&privilegeString->sr, &displayName))
                        {
                            PhSetListViewSubItem(context->PrivilegesLv, lvItemIndex, 1, displayName->Buffer);
                            PhDereferenceObject(displayName);
                        }

                        ExtendedListView_SortItems(context->PrivilegesLv);

                        context->Dirty = TRUE;
                        context->RequiredPrivilegesValid = TRUE;

                        break;
                    }
                }
                break;
            case IDC_REMOVE:
                {
                    INT lvItemIndex;
                    PPH_STRING privilegeString;
                    ULONG index;

                    lvItemIndex = PhFindListViewItemByFlags(context->PrivilegesLv, INT_ERROR, LVNI_SELECTED);

                    if (lvItemIndex != INT_ERROR && PhGetListViewItemParam(context->PrivilegesLv, lvItemIndex, (PVOID *)&privilegeString))
                    {
                        index = PhFindItemList(context->PrivilegeList, privilegeString);

                        if (index != ULONG_MAX)
                        {
                            PhDereferenceObject(privilegeString);
                            PhRemoveItemList(context->PrivilegeList, index);
                            PhRemoveListViewItem(context->PrivilegesLv, lvItemIndex);

                            context->Dirty = TRUE;
                            context->RequiredPrivilegesValid = TRUE;
                        }
                    }
                }
                break;
            }

            switch (GET_WM_COMMAND_CMD(wParam, lParam))
            {
            case EN_CHANGE:
            case CBN_SELCHANGE:
                {
                    if (context->Ready)
                    {
                        context->Dirty = TRUE;

                        switch (GET_WM_COMMAND_ID(wParam, lParam))
                        {
                        case IDC_PRESHUTDOWNTIMEOUT:
                            context->PreshutdownTimeoutValid = TRUE;
                            break;
                        case IDC_SIDTYPE:
                            context->SidTypeValid = TRUE;
                            break;
                        case IDC_PROTECTION:
                            context->LaunchProtectedValid = TRUE;
                            break;
                        }
                    }
                }
                break;
            }
        }
        break;
    case WM_NOTIFY:
        {
            LPNMHDR header = (LPNMHDR)lParam;

            switch (header->code)
            {
            case PSN_KILLACTIVE:
                {
                    SetWindowLongPtr(WindowHandle, DWLP_MSGRESULT, FALSE);
                }
                return TRUE;
            case PSN_APPLY:
                {
                    NTSTATUS status;
                    SC_HANDLE serviceHandle = NULL;
                    BOOLEAN connectedToPhSvc = FALSE;
                    PPH_STRING launchProtectedString;
                    ULONG launchProtected;

                    SetWindowLongPtr(WindowHandle, DWLP_MSGRESULT, PSNRET_NOERROR);

                    launchProtectedString = PH_AUTO(PhGetWindowText(GetDlgItem(WindowHandle, IDC_PROTECTION)));
                    launchProtected = EspGetServiceLaunchProtectedInteger(launchProtectedString->Buffer);

                    if (context->LaunchProtectedValid && launchProtected != 0 && launchProtected != context->OriginalLaunchProtected)
                    {
                        INT result;
                        TASKDIALOGCONFIG config = { sizeof(TASKDIALOGCONFIG) };

                        config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW;
                        config.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON;
                        config.nDefaultButton = IDNO;
                        config.hwndParent = WindowHandle;
                        config.pszWindowTitle = SystemInformer_GetWindowName();
                        config.pszMainIcon = TD_WARNING_ICON;
                        config.pszMainInstruction = L"Setting service protection will prevent the service from being controlled, modified, or deleted.";
                        config.pszContent = L"Do you want to continue?";

                        if (!PhShowTaskDialog(
                            &config,
                            &result,
                            NULL,
                            NULL
                            ) || result != IDYES)
                        {
                            SetWindowLongPtr(WindowHandle, DWLP_MSGRESULT, PSNRET_INVALID);
                            return TRUE;
                        }
                    }

                    if (context->Dirty)
                    {
                        SERVICE_PRESHUTDOWN_INFO preshutdownInfo;
                        SERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivilegesInfo;
                        SERVICE_SID_INFO sidInfo;
                        SERVICE_LAUNCH_PROTECTED_INFO launchProtectedInfo;

                        status = PhOpenService(&serviceHandle, SERVICE_CHANGE_CONFIG, PhGetString(context->ServiceItem->Name));

                        if (!NT_SUCCESS(status))
                        {
                            if (status == STATUS_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated)
                            {
                                // Elevate using phsvc.
                                if (PhUiConnectToPhSvc(WindowHandle, FALSE))
                                {
                                    status = STATUS_SUCCESS;
                                    connectedToPhSvc = TRUE;
                                }
                                else
                                {
                                    // User cancelled elevation.
                                    status = STATUS_CANCELLED;
                                    goto Done;
                                }
                            }
                            else
                            {
                                goto Done;
                            }
                        }

                        if (NT_SUCCESS(status) && context->PreshutdownTimeoutValid)
                        {
                            preshutdownInfo.dwPreshutdownTimeout = PhGetDialogItemValue(WindowHandle, IDC_PRESHUTDOWNTIMEOUT);

                            status = EspChangeServiceConfig2(
                                PhGetString(context->ServiceItem->Name),
                                serviceHandle,
                                SERVICE_CONFIG_PRESHUTDOWN_INFO,
                                &preshutdownInfo
                                );
                        }

                        if (NT_SUCCESS(status) && context->RequiredPrivilegesValid)
                        {
                            PH_STRING_BUILDER sb;
                            ULONG i;

                            PhInitializeStringBuilder(&sb, 100);

                            for (i = 0; i < context->PrivilegeList->Count; i++)
                            {
                                PhAppendStringBuilder(&sb, &((PPH_STRING)context->PrivilegeList->Items[i])->sr);
                                PhAppendCharStringBuilder(&sb, 0);
                            }

                            requiredPrivilegesInfo.pmszRequiredPrivileges = sb.String->Buffer;

                            status = EspChangeServiceConfig2(
                                PhGetString(context->ServiceItem->Name),
                                serviceHandle,
                                SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO,
                                &requiredPrivilegesInfo
                                );

                            PhDeleteStringBuilder(&sb);
                        }

                        if (NT_SUCCESS(status) && context->SidTypeValid)
                        {
                            PPH_STRING sidTypeString;

                            sidTypeString = PH_AUTO(PhGetWindowText(GetDlgItem(WindowHandle, IDC_SIDTYPE)));
                            sidInfo.dwServiceSidType = EspGetServiceSidTypeInteger(sidTypeString->Buffer);

                            status = EspChangeServiceConfig2(
                                PhGetString(context->ServiceItem->Name),
                                serviceHandle,
                                SERVICE_CONFIG_SERVICE_SID_INFO,
                                &sidInfo
                                );
                        }

                        if (NT_SUCCESS(status) && context->LaunchProtectedValid)
                        {
                            launchProtectedInfo.dwLaunchProtected = launchProtected;

                            // For now, ignore errors here.

                            EspChangeServiceConfig2(
                                PhGetString(context->ServiceItem->Name),
                                serviceHandle,
                                SERVICE_CONFIG_LAUNCH_PROTECTED,
                                &launchProtectedInfo
                                );
                        }

Done:
                        if (connectedToPhSvc)
                            PhUiDisconnectFromPhSvc();
                        if (serviceHandle)
                            PhCloseServiceHandle(serviceHandle);

                        if (!NT_SUCCESS(status))
                        {
                            if (PhShowContinueStatus(
                                WindowHandle,
                                L"Unable to change service information.",
                                status,
                                0))
                            {
                                SetWindowLongPtr(WindowHandle, DWLP_MSGRESULT, PSNRET_INVALID);
                            }
                        }
                    }

                    return TRUE;
                }
                break;
            case LVN_ITEMCHANGED:
                {
                    if (header->hwndFrom == context->PrivilegesLv)
                    {
                        EnableWindow(GetDlgItem(WindowHandle, IDC_REMOVE), ListView_GetSelectedCount(context->PrivilegesLv) == 1);
                    }
                }
                break;
            }
        }
        break;
    case WM_SIZE:
        {
            PhLayoutManagerLayout(&context->LayoutManager);
        }
        break;
    }

    return FALSE;
}
