2025-05-13 19:45:22 +03:00

481 lines
17 KiB
C

/*
* Process Hacker Extended Services -
* main program
*
* Copyright (C) 2010-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"
PPH_PLUGIN PluginInstance;
static PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration;
static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration;
static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration;
static PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration;
static PH_CALLBACK_REGISTRATION ServicePropertiesInitializingCallbackRegistration;
static PH_CALLBACK_REGISTRATION ServiceMenuInitializingCallbackRegistration;
VOID NTAPI LoadCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
// Nothing
}
VOID NTAPI ShowOptionsCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
EsShowOptionsDialog((HWND)Parameter);
}
VOID NTAPI MenuItemCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
PPH_PLUGIN_MENU_ITEM menuItem = Parameter;
switch (menuItem->Id)
{
case ID_SERVICE_GOTOSERVICE:
{
ProcessHacker_SelectTabPage(PhMainWndHandle, 1);
ProcessHacker_SelectServiceItem(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context);
}
break;
case ID_SERVICE_START:
{
PhUiStartService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context);
}
break;
case ID_SERVICE_CONTINUE:
{
PhUiContinueService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context);
}
break;
case ID_SERVICE_PAUSE:
{
PhUiPauseService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context);
}
break;
case ID_SERVICE_STOP:
{
PhUiStopService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context);
}
break;
case ID_SERVICE_RESTART:
{
PPH_SERVICE_ITEM serviceItem = menuItem->Context;
SC_HANDLE serviceHandle;
ULONG win32Result = 0;
if (serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_QUERY_STATUS))
{
EsRestartServiceWithProgress(PhMainWndHandle, serviceItem, serviceHandle);
CloseServiceHandle(serviceHandle);
}
else
{
win32Result = GetLastError();
}
if (win32Result != 0)
{
PhShowStatus(
PhMainWndHandle,
PhaFormatString(L"Unable to restart %s", serviceItem->Name->Buffer)->Buffer,
0,
win32Result
);
}
}
break;
}
}
static int __cdecl ServiceForServicesMenuCompare(
_In_ const void *elem1,
_In_ const void *elem2
)
{
PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)elem1;
PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)elem2;
return PhCompareString(serviceItem1->Name, serviceItem2->Name, TRUE);
}
VOID NTAPI ProcessMenuInitializingCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter;
if (
PhGetIntegerSetting(SETTING_NAME_ENABLE_SERVICES_MENU) &&
menuInfo->u.Process.NumberOfProcesses == 1 &&
menuInfo->u.Process.Processes[0]->ServiceList &&
menuInfo->u.Process.Processes[0]->ServiceList->Count != 0
)
{
PPH_PROCESS_ITEM processItem;
PPH_EMENU_ITEM servicesMenuItem = NULL;
ULONG enumerationKey;
PPH_SERVICE_ITEM serviceItem;
PPH_LIST serviceList;
ULONG i;
PPH_EMENU_ITEM priorityMenuItem;
ULONG insertIndex;
processItem = menuInfo->u.Process.Processes[0];
// Create a service list so we can sort it.
serviceList = PH_AUTO(PhCreateList(processItem->ServiceList->Count));
enumerationKey = 0;
PhAcquireQueuedLockShared(&processItem->ServiceListLock);
while (PhEnumPointerList(processItem->ServiceList, &enumerationKey, &serviceItem))
{
PhReferenceObject(serviceItem);
// We need to use the service item when the user chooses a menu item.
PH_AUTO(serviceItem);
PhAddItemList(serviceList, serviceItem);
}
PhReleaseQueuedLockShared(&processItem->ServiceListLock);
// Sort the service list.
qsort(serviceList->Items, serviceList->Count, sizeof(PPH_SERVICE_ITEM), ServiceForServicesMenuCompare);
// If there is only one service:
// * We use the text "Service (Xxx)".
// * There are no extra submenus.
if (serviceList->Count != 1)
{
servicesMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, 0, L"Services", NULL);
}
// Create and add a menu item for each service.
for (i = 0; i < serviceList->Count; i++)
{
PPH_STRING escapedName;
PPH_EMENU_ITEM serviceMenuItem;
PPH_EMENU_ITEM startMenuItem;
PPH_EMENU_ITEM continueMenuItem;
PPH_EMENU_ITEM pauseMenuItem;
PPH_EMENU_ITEM stopMenuItem;
serviceItem = serviceList->Items[i];
escapedName = PH_AUTO(PhEscapeStringForMenuPrefix(&serviceItem->Name->sr));
if (serviceList->Count == 1)
{
// "Service (Xxx)"
escapedName = PhaFormatString(L"Service (%s)", escapedName->Buffer);
}
serviceMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, 0, escapedName->Buffer, NULL);
if (serviceList->Count == 1)
{
// Make this the root submenu that we will insert.
servicesMenuItem = serviceMenuItem;
}
PhInsertEMenuItem(serviceMenuItem, PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_GOTOSERVICE, L"Go to service", serviceItem), -1);
PhInsertEMenuItem(serviceMenuItem, startMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_START, L"Start", serviceItem), -1);
PhInsertEMenuItem(serviceMenuItem, continueMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_CONTINUE, L"Continue", serviceItem), -1);
PhInsertEMenuItem(serviceMenuItem, pauseMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_PAUSE, L"Pause", serviceItem), -1);
PhInsertEMenuItem(serviceMenuItem, stopMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_STOP, L"Stop", serviceItem), -1);
// Massive copy and paste from mainwnd.c.
// == START ==
#define SET_MENU_ITEM_ENABLED(MenuItem, Enabled) if (!(Enabled)) (MenuItem)->Flags |= PH_EMENU_DISABLED;
switch (serviceItem->State)
{
case SERVICE_RUNNING:
{
SET_MENU_ITEM_ENABLED(startMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(continueMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(pauseMenuItem,
serviceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE);
SET_MENU_ITEM_ENABLED(stopMenuItem,
serviceItem->ControlsAccepted & SERVICE_ACCEPT_STOP);
}
break;
case SERVICE_PAUSED:
{
SET_MENU_ITEM_ENABLED(startMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(continueMenuItem,
serviceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE);
SET_MENU_ITEM_ENABLED(pauseMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(stopMenuItem,
serviceItem->ControlsAccepted & SERVICE_ACCEPT_STOP);
}
break;
case SERVICE_STOPPED:
{
SET_MENU_ITEM_ENABLED(continueMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(pauseMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(stopMenuItem, FALSE);
}
break;
case SERVICE_START_PENDING:
case SERVICE_CONTINUE_PENDING:
case SERVICE_PAUSE_PENDING:
case SERVICE_STOP_PENDING:
{
SET_MENU_ITEM_ENABLED(startMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(continueMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(pauseMenuItem, FALSE);
SET_MENU_ITEM_ENABLED(stopMenuItem, FALSE);
}
break;
}
if (!(serviceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE))
{
PhDestroyEMenuItem(continueMenuItem);
PhDestroyEMenuItem(pauseMenuItem);
}
// == END ==
if (serviceList->Count != 1)
PhInsertEMenuItem(servicesMenuItem, serviceMenuItem, -1);
}
// Insert our Services menu after the I/O Priority menu.
priorityMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"I/O Priority", 0);
if (!priorityMenuItem)
priorityMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Priority", 0);
if (priorityMenuItem)
insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, priorityMenuItem) + 1;
else
insertIndex = 0;
PhInsertEMenuItem(menuInfo->Menu, servicesMenuItem, insertIndex);
}
}
NTAPI ServicePropertiesInitializingCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
PPH_PLUGIN_OBJECT_PROPERTIES objectProperties = Parameter;
PROPSHEETPAGE propSheetPage;
PPH_SERVICE_ITEM serviceItem;
serviceItem = objectProperties->Parameter;
// Recovery
if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages)
{
memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE));
propSheetPage.dwSize = sizeof(PROPSHEETPAGE);
propSheetPage.hInstance = PluginInstance->DllBase;
propSheetPage.lParam = (LPARAM)serviceItem;
if (!(serviceItem->Flags & SERVICE_RUNS_IN_SYSTEM_PROCESS))
{
propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVRECOVERY);
propSheetPage.pfnDlgProc = EspServiceRecoveryDlgProc;
}
else
{
// Services which run in system processes don't support failure actions.
// Create a different page with a message saying this.
propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVRECOVERY2);
propSheetPage.pfnDlgProc = EspServiceRecovery2DlgProc;
}
objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage);
}
// Dependencies
if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages)
{
memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE));
propSheetPage.dwSize = sizeof(PROPSHEETPAGE);
propSheetPage.dwFlags = PSP_USETITLE;
propSheetPage.hInstance = PluginInstance->DllBase;
propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVLIST);
propSheetPage.pszTitle = L"Dependencies";
propSheetPage.pfnDlgProc = EspServiceDependenciesDlgProc;
propSheetPage.lParam = (LPARAM)serviceItem;
objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage);
}
// Dependents
if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages)
{
memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE));
propSheetPage.dwSize = sizeof(PROPSHEETPAGE);
propSheetPage.dwFlags = PSP_USETITLE;
propSheetPage.hInstance = PluginInstance->DllBase;
propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVLIST);
propSheetPage.pszTitle = L"Dependents";
propSheetPage.pfnDlgProc = EspServiceDependentsDlgProc;
propSheetPage.lParam = (LPARAM)serviceItem;
objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage);
}
// Other
if (WindowsVersion >= WINDOWS_7 && objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages)
{
memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE));
propSheetPage.dwSize = sizeof(PROPSHEETPAGE);
propSheetPage.dwFlags = PSP_USETITLE;
propSheetPage.hInstance = PluginInstance->DllBase;
propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVTRIGGERS);
propSheetPage.pszTitle = L"Triggers";
propSheetPage.pfnDlgProc = EspServiceTriggersDlgProc;
propSheetPage.lParam = (LPARAM)serviceItem;
objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage);
}
// Other
if (WindowsVersion >= WINDOWS_VISTA && objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages)
{
memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE));
propSheetPage.dwSize = sizeof(PROPSHEETPAGE);
propSheetPage.dwFlags = PSP_USETITLE;
propSheetPage.hInstance = PluginInstance->DllBase;
propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVOTHER);
propSheetPage.pszTitle = L"Other";
propSheetPage.pfnDlgProc = EspServiceOtherDlgProc;
propSheetPage.lParam = (LPARAM)serviceItem;
objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage);
}
}
VOID NTAPI ServiceMenuInitializingCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter;
PPH_EMENU_ITEM menuItem;
ULONG indexOfMenuItem;
if (
menuInfo->u.Service.NumberOfServices == 1 &&
(menuInfo->u.Service.Services[0]->State == SERVICE_RUNNING || menuInfo->u.Service.Services[0]->State == SERVICE_PAUSED)
)
{
// Insert our Restart menu item after the Stop menu item.
menuItem = PhFindEMenuItem(menuInfo->Menu, PH_EMENU_FIND_STARTSWITH, L"Stop", 0);
if (menuItem)
indexOfMenuItem = PhIndexOfEMenuItem(menuInfo->Menu, menuItem);
else
indexOfMenuItem = -1;
PhInsertEMenuItem(
menuInfo->Menu,
PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_RESTART, L"Restart", menuInfo->u.Service.Services[0]),
indexOfMenuItem + 1
);
}
}
LOGICAL DllMain(
_In_ HINSTANCE Instance,
_In_ ULONG Reason,
_Reserved_ PVOID Reserved
)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH:
{
PPH_PLUGIN_INFORMATION info;
PH_SETTING_CREATE settings[] =
{
{ IntegerSettingType, SETTING_NAME_ENABLE_SERVICES_MENU, L"1" }
};
PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info);
if (!PluginInstance)
return FALSE;
info->DisplayName = L"Extended Services";
info->Author = L"wj32";
info->Description = L"Extends service management capabilities.";
info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1113";
info->HasOptions = TRUE;
PhRegisterCallback(
PhGetPluginCallback(PluginInstance, PluginCallbackLoad),
LoadCallback,
NULL,
&PluginLoadCallbackRegistration
);
PhRegisterCallback(
PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions),
ShowOptionsCallback,
NULL,
&PluginShowOptionsCallbackRegistration
);
PhRegisterCallback(
PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem),
MenuItemCallback,
NULL,
&PluginMenuItemCallbackRegistration
);
PhRegisterCallback(
PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing),
ProcessMenuInitializingCallback,
NULL,
&ProcessMenuInitializingCallbackRegistration
);
PhRegisterCallback(
PhGetGeneralCallback(GeneralCallbackServicePropertiesInitializing),
ServicePropertiesInitializingCallback,
NULL,
&ServicePropertiesInitializingCallbackRegistration
);
PhRegisterCallback(
PhGetGeneralCallback(GeneralCallbackServiceMenuInitializing),
ServiceMenuInitializingCallback,
NULL,
&ServiceMenuInitializingCallbackRegistration
);
PhAddSettings(settings, ARRAYSIZE(settings));
}
break;
}
return TRUE;
}