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

710 lines
24 KiB
C

/*
* Process Hacker Extended Services -
* recovery information
*
* Copyright (C) 2010-2011 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 _SERVICE_RECOVERY_CONTEXT
{
PPH_SERVICE_ITEM ServiceItem;
ULONG NumberOfActions;
BOOLEAN EnableFlagCheckBox;
ULONG RebootAfter; // in ms
PPH_STRING RebootMessage;
BOOLEAN Ready;
BOOLEAN Dirty;
} SERVICE_RECOVERY_CONTEXT, *PSERVICE_RECOVERY_CONTEXT;
static PH_KEY_VALUE_PAIR ServiceActionPairs[] =
{
SIP(L"Take no action", SC_ACTION_NONE),
SIP(L"Restart the service", SC_ACTION_RESTART),
SIP(L"Run a program", SC_ACTION_RUN_COMMAND),
SIP(L"Restart the computer", SC_ACTION_REBOOT)
};
INT_PTR CALLBACK RestartComputerDlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
VOID EspAddServiceActionStrings(
_In_ HWND ComboBoxHandle
)
{
ULONG i;
for (i = 0; i < sizeof(ServiceActionPairs) / sizeof(PH_KEY_VALUE_PAIR); i++)
ComboBox_AddString(ComboBoxHandle, (PWSTR)ServiceActionPairs[i].Key);
PhSelectComboBoxString(ComboBoxHandle, (PWSTR)ServiceActionPairs[0].Key, FALSE);
}
SC_ACTION_TYPE EspStringToServiceAction(
_In_ PWSTR String
)
{
ULONG integer;
if (PhFindIntegerSiKeyValuePairs(ServiceActionPairs, sizeof(ServiceActionPairs), String, &integer))
return integer;
else
return 0;
}
PWSTR EspServiceActionToString(
_In_ SC_ACTION_TYPE ActionType
)
{
PWSTR string;
if (PhFindStringSiKeyValuePairs(ServiceActionPairs, sizeof(ServiceActionPairs), ActionType, &string))
return string;
else
return NULL;
}
SC_ACTION_TYPE ComboBoxToServiceAction(
_In_ HWND ComboBoxHandle
)
{
PPH_STRING string;
string = PH_AUTO(PhGetComboBoxString(ComboBoxHandle, ComboBox_GetCurSel(ComboBoxHandle)));
if (!string)
return SC_ACTION_NONE;
return EspStringToServiceAction(string->Buffer);
}
VOID ServiceActionToComboBox(
_In_ HWND ComboBoxHandle,
_In_ SC_ACTION_TYPE ActionType
)
{
PWSTR string;
if (string = EspServiceActionToString(ActionType))
PhSelectComboBoxString(ComboBoxHandle, string, FALSE);
else
PhSelectComboBoxString(ComboBoxHandle, (PWSTR)ServiceActionPairs[0].Key, FALSE);
}
VOID EspFixControls(
_In_ HWND hwndDlg,
_In_ PSERVICE_RECOVERY_CONTEXT Context
)
{
SC_ACTION_TYPE action1;
SC_ACTION_TYPE action2;
SC_ACTION_TYPE actionS;
BOOLEAN enableRestart;
BOOLEAN enableReboot;
BOOLEAN enableCommand;
action1 = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE));
action2 = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SECONDFAILURE));
actionS = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES));
EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), Context->EnableFlagCheckBox);
enableRestart = action1 == SC_ACTION_RESTART || action2 == SC_ACTION_RESTART || actionS == SC_ACTION_RESTART;
enableReboot = action1 == SC_ACTION_REBOOT || action2 == SC_ACTION_REBOOT || actionS == SC_ACTION_REBOOT;
enableCommand = action1 == SC_ACTION_RUN_COMMAND || action2 == SC_ACTION_RUN_COMMAND || actionS == SC_ACTION_RUN_COMMAND;
EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER_LABEL), enableRestart);
EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER), enableRestart);
EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER_MINUTES), enableRestart);
EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTCOMPUTEROPTIONS), enableReboot);
EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_GROUP), enableCommand);
EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_LABEL), enableCommand);
EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM), enableCommand);
EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE), enableCommand);
EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_INFO), enableCommand);
}
NTSTATUS EspLoadRecoveryInfo(
_In_ HWND hwndDlg,
_In_ PSERVICE_RECOVERY_CONTEXT Context
)
{
NTSTATUS status = STATUS_SUCCESS;
SC_HANDLE serviceHandle;
LPSERVICE_FAILURE_ACTIONS failureActions;
SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag;
SC_ACTION_TYPE lastType;
ULONG returnLength;
ULONG i;
if (!(serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG)))
return NTSTATUS_FROM_WIN32(GetLastError());
if (!(failureActions = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS)))
{
CloseServiceHandle(serviceHandle);
return NTSTATUS_FROM_WIN32(GetLastError());
}
// Failure action types
Context->NumberOfActions = failureActions->cActions;
if (failureActions->cActions != 0 && failureActions->cActions != 3)
status = STATUS_SOME_NOT_MAPPED;
// If failure actions are not defined for a particular fail count, the
// last failure action is used. Here we duplicate this behaviour when there
// are fewer than 3 failure actions.
lastType = SC_ACTION_NONE;
ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE),
failureActions->cActions >= 1 ? (lastType = failureActions->lpsaActions[0].Type) : lastType);
ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_SECONDFAILURE),
failureActions->cActions >= 2 ? (lastType = failureActions->lpsaActions[1].Type) : lastType);
ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES),
failureActions->cActions >= 3 ? (lastType = failureActions->lpsaActions[2].Type) : lastType);
// Reset fail count after
SetDlgItemInt(hwndDlg, IDC_RESETFAILCOUNT, failureActions->dwResetPeriod / (60 * 60 * 24), FALSE); // s to days
// Restart service after
SetDlgItemText(hwndDlg, IDC_RESTARTSERVICEAFTER, L"1");
for (i = 0; i < failureActions->cActions; i++)
{
if (failureActions->lpsaActions[i].Type == SC_ACTION_RESTART)
{
if (failureActions->lpsaActions[i].Delay != 0)
{
SetDlgItemInt(hwndDlg, IDC_RESTARTSERVICEAFTER,
failureActions->lpsaActions[i].Delay / (1000 * 60), FALSE); // ms to min
}
break;
}
}
// Enable actions for stops with errors
// This is Vista and above only.
if (WindowsVersion >= WINDOWS_VISTA && QueryServiceConfig2(
serviceHandle,
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
(BYTE *)&failureActionsFlag,
sizeof(SERVICE_FAILURE_ACTIONS_FLAG),
&returnLength
))
{
Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS),
failureActionsFlag.fFailureActionsOnNonCrashFailures ? BST_CHECKED : BST_UNCHECKED);
Context->EnableFlagCheckBox = TRUE;
}
else
{
Context->EnableFlagCheckBox = FALSE;
}
// Restart computer options
Context->RebootAfter = 1 * 1000 * 60;
for (i = 0; i < failureActions->cActions; i++)
{
if (failureActions->lpsaActions[i].Type == SC_ACTION_REBOOT)
{
if (failureActions->lpsaActions[i].Delay != 0)
Context->RebootAfter = failureActions->lpsaActions[i].Delay;
break;
}
}
if (failureActions->lpRebootMsg && failureActions->lpRebootMsg[0] != 0)
PhMoveReference(&Context->RebootMessage, PhCreateString(failureActions->lpRebootMsg));
else
PhClearReference(&Context->RebootMessage);
// Run program
SetDlgItemText(hwndDlg, IDC_RUNPROGRAM, failureActions->lpCommand);
PhFree(failureActions);
CloseServiceHandle(serviceHandle);
return status;
}
INT_PTR CALLBACK EspServiceRecoveryDlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
PSERVICE_RECOVERY_CONTEXT context;
if (uMsg == WM_INITDIALOG)
{
context = PhAllocate(sizeof(SERVICE_RECOVERY_CONTEXT));
memset(context, 0, sizeof(SERVICE_RECOVERY_CONTEXT));
SetProp(hwndDlg, L"Context", (HANDLE)context);
}
else
{
context = (PSERVICE_RECOVERY_CONTEXT)GetProp(hwndDlg, L"Context");
if (uMsg == WM_DESTROY)
RemoveProp(hwndDlg, L"Context");
}
if (!context)
return FALSE;
switch (uMsg)
{
case WM_INITDIALOG:
{
NTSTATUS status;
LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam;
PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam;
context->ServiceItem = serviceItem;
EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE));
EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_SECONDFAILURE));
EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES));
status = EspLoadRecoveryInfo(hwndDlg, context);
if (status == STATUS_SOME_NOT_MAPPED)
{
if (context->NumberOfActions > 3)
{
PhShowWarning(
hwndDlg,
L"The service has %lu failure actions configured, but this program only supports editing 3. "
L"If you save the recovery information using this program, the additional failure actions will be lost.",
context->NumberOfActions
);
}
}
else if (!NT_SUCCESS(status))
{
SetDlgItemText(hwndDlg, IDC_RESETFAILCOUNT, L"0");
if (WindowsVersion >= WINDOWS_VISTA)
{
context->EnableFlagCheckBox = TRUE;
EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), TRUE);
}
PhShowWarning(hwndDlg, L"Unable to query service recovery information: %s",
((PPH_STRING)PH_AUTO(PhGetNtMessage(status)))->Buffer);
}
EspFixControls(hwndDlg, context);
context->Ready = TRUE;
}
break;
case WM_DESTROY:
{
PhClearReference(&context->RebootMessage);
PhFree(context);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_FIRSTFAILURE:
case IDC_SECONDFAILURE:
case IDC_SUBSEQUENTFAILURES:
{
if (HIWORD(wParam) == CBN_SELCHANGE)
{
EspFixControls(hwndDlg, context);
}
}
break;
case IDC_RESTARTCOMPUTEROPTIONS:
{
DialogBoxParam(
PluginInstance->DllBase,
MAKEINTRESOURCE(IDD_RESTARTCOMP),
hwndDlg,
RestartComputerDlgProc,
(LPARAM)context
);
}
break;
case IDC_BROWSE:
{
static PH_FILETYPE_FILTER filters[] =
{
{ L"Executable files (*.exe;*.cmd;*.bat)", L"*.exe;*.cmd;*.bat" },
{ L"All files (*.*)", L"*.*" }
};
PVOID fileDialog;
PPH_STRING fileName;
fileDialog = PhCreateOpenFileDialog();
PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER));
fileName = PhaGetDlgItemText(hwndDlg, IDC_RUNPROGRAM);
PhSetFileDialogFileName(fileDialog, fileName->Buffer);
if (PhShowFileDialog(hwndDlg, fileDialog))
{
fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog));
SetDlgItemText(hwndDlg, IDC_RUNPROGRAM, fileName->Buffer);
}
PhFreeFileDialog(fileDialog);
}
break;
case IDC_ENABLEFORERRORSTOPS:
{
context->Dirty = TRUE;
}
break;
}
switch (HIWORD(wParam))
{
case EN_CHANGE:
case CBN_SELCHANGE:
{
if (context->Ready)
context->Dirty = TRUE;
}
break;
}
}
break;
case WM_NOTIFY:
{
LPNMHDR header = (LPNMHDR)lParam;
switch (header->code)
{
case PSN_KILLACTIVE:
{
SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE);
}
return TRUE;
case PSN_APPLY:
{
NTSTATUS status;
PPH_SERVICE_ITEM serviceItem = context->ServiceItem;
SC_HANDLE serviceHandle;
ULONG restartServiceAfter;
SERVICE_FAILURE_ACTIONS failureActions;
SC_ACTION actions[3];
ULONG i;
BOOLEAN enableRestart = FALSE;
SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
if (!context->Dirty)
{
return TRUE;
}
// Build the failure actions structure.
failureActions.dwResetPeriod = GetDlgItemInt(hwndDlg, IDC_RESETFAILCOUNT, NULL, FALSE) * 60 * 60 * 24;
failureActions.lpRebootMsg = PhGetStringOrEmpty(context->RebootMessage);
failureActions.lpCommand = PhaGetDlgItemText(hwndDlg, IDC_RUNPROGRAM)->Buffer;
failureActions.cActions = 3;
failureActions.lpsaActions = actions;
actions[0].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE));
actions[1].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SECONDFAILURE));
actions[2].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES));
restartServiceAfter = GetDlgItemInt(hwndDlg, IDC_RESTARTSERVICEAFTER, NULL, FALSE) * 1000 * 60;
for (i = 0; i < 3; i++)
{
switch (actions[i].Type)
{
case SC_ACTION_RESTART:
actions[i].Delay = restartServiceAfter;
enableRestart = TRUE;
break;
case SC_ACTION_REBOOT:
actions[i].Delay = context->RebootAfter;
break;
case SC_ACTION_RUN_COMMAND:
actions[i].Delay = 0;
break;
}
}
// Try to save the changes.
serviceHandle = PhOpenService(
serviceItem->Name->Buffer,
SERVICE_CHANGE_CONFIG | (enableRestart ? SERVICE_START : 0) // SC_ACTION_RESTART requires SERVICE_START
);
if (serviceHandle)
{
if (ChangeServiceConfig2(
serviceHandle,
SERVICE_CONFIG_FAILURE_ACTIONS,
&failureActions
))
{
if (context->EnableFlagCheckBox)
{
SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag;
failureActionsFlag.fFailureActionsOnNonCrashFailures =
Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS)) == BST_CHECKED;
ChangeServiceConfig2(
serviceHandle,
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
&failureActionsFlag
);
}
CloseServiceHandle(serviceHandle);
}
else
{
CloseServiceHandle(serviceHandle);
goto ErrorCase;
}
}
else
{
if (GetLastError() == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated)
{
// Elevate using phsvc.
if (PhUiConnectToPhSvc(hwndDlg, FALSE))
{
if (NT_SUCCESS(status = PhSvcCallChangeServiceConfig2(
serviceItem->Name->Buffer,
SERVICE_CONFIG_FAILURE_ACTIONS,
&failureActions
)))
{
if (context->EnableFlagCheckBox)
{
SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag;
failureActionsFlag.fFailureActionsOnNonCrashFailures =
Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS)) == BST_CHECKED;
PhSvcCallChangeServiceConfig2(
serviceItem->Name->Buffer,
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
&failureActionsFlag
);
}
}
PhUiDisconnectFromPhSvc();
if (!NT_SUCCESS(status))
{
SetLastError(PhNtStatusToDosError(status));
goto ErrorCase;
}
}
else
{
// User cancelled elevation.
SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID);
}
}
else
{
goto ErrorCase;
}
}
return TRUE;
ErrorCase:
if (PhShowMessage(
hwndDlg,
MB_ICONERROR | MB_RETRYCANCEL,
L"Unable to change service recovery information: %s",
((PPH_STRING)PH_AUTO(PhGetWin32Message(GetLastError())))->Buffer
) == IDRETRY)
{
SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID);
}
}
return TRUE;
}
}
break;
}
return FALSE;
}
INT_PTR CALLBACK EspServiceRecovery2DlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
return FALSE;
}
INT_PTR CALLBACK RestartComputerDlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
PSERVICE_RECOVERY_CONTEXT context;
if (uMsg == WM_INITDIALOG)
{
context = (PSERVICE_RECOVERY_CONTEXT)lParam;
SetProp(hwndDlg, L"Context", (HANDLE)context);
}
else
{
context = (PSERVICE_RECOVERY_CONTEXT)GetProp(hwndDlg, L"Context");
if (uMsg == WM_DESTROY)
RemoveProp(hwndDlg, L"Context");
}
if (!context)
return FALSE;
switch (uMsg)
{
case WM_INITDIALOG:
{
PhCenterWindow(hwndDlg, GetParent(hwndDlg));
SetDlgItemInt(hwndDlg, IDC_RESTARTCOMPAFTER, context->RebootAfter / (1000 * 60), FALSE); // ms to min
Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), context->RebootMessage ? BST_CHECKED : BST_UNCHECKED);
SetDlgItemText(hwndDlg, IDC_RESTARTMESSAGE, PhGetString(context->RebootMessage));
SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_RESTARTCOMPAFTER), TRUE);
Edit_SetSel(GetDlgItem(hwndDlg, IDC_RESTARTCOMPAFTER), 0, -1);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDCANCEL:
EndDialog(hwndDlg, IDCANCEL);
break;
case IDOK:
{
context->RebootAfter = GetDlgItemInt(hwndDlg, IDC_RESTARTCOMPAFTER, NULL, FALSE) * 1000 * 60;
if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE)) == BST_CHECKED)
PhMoveReference(&context->RebootMessage, PhGetWindowText(GetDlgItem(hwndDlg, IDC_RESTARTMESSAGE)));
else
PhClearReference(&context->RebootMessage);
context->Dirty = TRUE;
EndDialog(hwndDlg, IDOK);
}
break;
case IDC_USEDEFAULTMESSAGE:
{
PPH_STRING message;
PWSTR computerName;
ULONG bufferSize;
BOOLEAN allocated = TRUE;
// Get the computer name.
bufferSize = 64;
computerName = PhAllocate((bufferSize + 1) * sizeof(WCHAR));
if (!GetComputerName(computerName, &bufferSize))
{
PhFree(computerName);
computerName = PhAllocate((bufferSize + 1) * sizeof(WCHAR));
if (!GetComputerName(computerName, &bufferSize))
{
PhFree(computerName);
computerName = L"(unknown)";
allocated = FALSE;
}
}
// This message is exactly the same as the one in the Services console,
// except the double spaces are replaced by single spaces.
message = PhaFormatString(
L"Your computer is connected to the computer named %s. "
L"The %s service on %s has ended unexpectedly. "
L"%s will restart automatically, and then you can reestablish the connection.",
computerName,
context->ServiceItem->Name->Buffer,
computerName,
computerName
);
SetDlgItemText(hwndDlg, IDC_RESTARTMESSAGE, message->Buffer);
if (allocated)
PhFree(computerName);
Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), BST_CHECKED);
}
break;
case IDC_RESTARTMESSAGE:
{
if (HIWORD(wParam) == EN_CHANGE)
{
// A zero length restart message disables it, so we might as well uncheck the box.
Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE),
GetWindowTextLength(GetDlgItem(hwndDlg, IDC_RESTARTMESSAGE)) != 0 ? BST_CHECKED : BST_UNCHECKED);
}
}
break;
}
}
break;
}
return FALSE;
}