WindowsAPI查缺补漏-杂谈

程序开机自启动

之前有讲过,这里讲些别的方法。

将程序快捷方式写入开机自启动程序目录法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <windows.h>
#include <tchar.h>
#include <Shlobj.h>
#include <strsafe.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL MyCreateShortcut(LPTSTR lpszDestFileName, LPTSTR lpszShortcutFileName,LPTSTR lpszWorkingDirectory, WORD wHotKey, int iShowCmd, LPTSTR lpszDescription);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
GUID guid = { 0xB97D20BB, 0xF46A, 0x4C97, {0xBA, 0x10, 0x5E, 0x36, 0x08, 0x43, 0x08, 0x54} };
LPTSTR lpStrStartup; // 返回当前用户的开机自动启动程序目录
TCHAR szDestFileName[MAX_PATH] = { 0 }; // 可执行文件完整路径
TCHAR szFileName[MAX_PATH] = { 0 }; // 可执行文件名称
TCHAR szShortcutFileName[MAX_PATH] = { 0 }; // 快捷方式的保存路径
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OK: {
// 获取当前用户的开机自动启动程序目录
SHGetKnownFolderPath(guid, 0, NULL, &lpStrStartup);
// 获取当前进程的可执行文件完整路径
GetModuleFileName(NULL, szDestFileName, _countof(szDestFileName));
// 拼凑快捷方式的保存路径
// 开机自动启动程序目录后面加一个反斜杠
StringCchCopy(szShortcutFileName, _countof(szShortcutFileName), lpStrStartup);
if (szShortcutFileName[_tcslen(szShortcutFileName) - 1] != TEXT('\\'))
StringCchCat(szShortcutFileName, _countof(szShortcutFileName), TEXT("\\"));
// 可执行文件名称.lnk
StringCchCopy(szFileName, _countof(szFileName), _tcsrchr(szDestFileName, TEXT('\\')) + 1);
*(_tcsrchr(szFileName, TEXT('.')) + 1) = TEXT('\0');
StringCchCat(szFileName, _countof(szFileName), TEXT("lnk"));
// 开机自动启动程序目录\可执行文件名称.lnk
StringCchCat(szShortcutFileName, _countof(szShortcutFileName), szFileName);
// 调用自定义函数MyCreateShortcut创建快捷方式
MyCreateShortcut(szDestFileName, szShortcutFileName, NULL, 0, 0, NULL);
CoTaskMemFree(lpStrStartup);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
/*********************************************************************************
* 函数功能: 通过调用COM库接口函数创建程序快捷方式
* 输入参数的说明:
1. lpszDestFileName参数表示快捷方式指向的目标文件路径,必须指定
2. lpszShortcutFileName参数表示快捷方式的保存路径(扩展名为.lnk),必须指定
3. lpszWorkingDirectory参数表示起始位置(工作目录),如果设置为NULL表示程序所在目录
4. wHotKey参数表示快捷键,设置为0表示不设置快捷键
5. iShowCmd参数表示运行方式,可以设置为SW_SHOWNORMAL、SW_SHOWMINNOACTIVE或SW_SHOWMAXIMIZED
分别表示常规窗口、最小化或最大化,设置为0表示常规窗口
6. lpszDescription参数表示备注(描述),可以设置为NULL
* 注意:该函数需要使用tchar.h和Shlobj.h头文件
**********************************************************************************/
BOOL MyCreateShortcut(LPTSTR lpszDestFileName, LPTSTR lpszShortcutFileName, LPTSTR lpszWorkingDirectory, WORD wHotKey, int iShowCmd, LPTSTR lpszDescription) {
HRESULT hr;
if (lpszDestFileName == NULL || lpszShortcutFileName == NULL)
return FALSE;
// 初始化COM库
CoInitializeEx(NULL, 0);
// 创建一个IShellLink对象
IShellLink* pShellLink;
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_SERVER, IID_IShellLink, (LPVOID*)&pShellLink);
if (FAILED(hr))
return FALSE;
// 使用返回的IShellLink对象中的方法设置快捷方式的属性
// 目标文件路径
pShellLink->SetPath(lpszDestFileName);
// 起始位置(工作目录)
if (!lpszWorkingDirectory) {
TCHAR szWorkingDirectory[MAX_PATH] = { 0 };
StringCchCopy(szWorkingDirectory, _countof(szWorkingDirectory), lpszDestFileName);
LPTSTR lpsz = _tcsrchr(szWorkingDirectory, TEXT('\\'));
*lpsz = TEXT('\0');
pShellLink->SetWorkingDirectory(szWorkingDirectory);
}
else
pShellLink->SetWorkingDirectory(lpszWorkingDirectory);
// 快捷键(低字节表示虚拟键码,高字节表示修饰键)
if (wHotKey != 0)
pShellLink->SetHotkey(wHotKey);
// 运行方式
if (!iShowCmd)
pShellLink->SetShowCmd(SW_SHOWNORMAL);
else
pShellLink->SetShowCmd(iShowCmd);
// 备注(描述)
if (lpszDescription != NULL)
pShellLink->SetDescription(lpszDescription);
// 调用IShellLink的父类IUnknown中的QueryInterface方法获取IPersistFile对象
IPersistFile* pPersistFile;
hr = pShellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&pPersistFile);
if (FAILED(hr)) {
pShellLink->Release();
return FALSE;
};
// 使用获取到的IPersistFile对象中的Save方法保存快捷方式到指定位置
pPersistFile->Save(lpszShortcutFileName, TRUE);
// 释放相关对象
pPersistFile->Release();
pShellLink->Release();
// 关闭COM库
CoUninitialize();
return TRUE;
};

创建计划任务法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <windows.h>
#include <Taskschd.h>
#include <comdef.h>
#include "resource.h"
#pragma comment(lib, "taskschd.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 创建任务计划
BOOL MyCreateTaskScheduler(LPCTSTR lpszTaskName, LPCTSTR lpszProgramPath,LPCTSTR lpszParameter, LPCTSTR lpszAuthor);
// 删除任务计划
BOOL MyDeleteTaskScheduler(LPCTSTR lpszTaskName);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OK: {
if (MyCreateTaskScheduler(TEXT("开机运行HelloWindows"), TEXT("F:\\Source\\Windows\\Chapter2\\HelloWindows\\Debug\\HelloWindows.exe"), NULL, TEXT("老王出品")))
MessageBox(hwndDlg, TEXT("创建任务计划成功"), TEXT("操作成功"), MB_OK);
break;
};
case IDC_BTN_DELETE: {
if (MyDeleteTaskScheduler(TEXT("开机运行HelloWindows")))
MessageBox(hwndDlg, TEXT("删除任务计划成功"), TEXT("操作成功"), MB_OK);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
/*********************************************************************************
* 函数功能: 通过调用COM库接口函数创建任务计划实现程序开机自动启动
* 输入参数的说明:
1. lpszTaskName参数表示任务名称,必须指定
2. lpszProgramPath参数表示要开机自动启动的程序路径,必须指定
3. lpszParameter参数表示程序参数,不需要可以设置为NULL
4. lpszAuthor参数表示创建者,不需要可以设置为NULL
* 注意:该函数需要使用Taskschd.h和comdef.h头文件,并需要taskschd.lib导入库
**********************************************************************************/
BOOL MyCreateTaskScheduler(LPCTSTR lpszTaskName, LPCTSTR lpszProgramPath, LPCTSTR lpszParameter, LPCTSTR lpszAuthor) {
HRESULT hr;
if (!lpszTaskName || !lpszProgramPath)
return FALSE;
// 初始化COM并设置COM安全级别
hr = CoInitializeEx(NULL, 0);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
// 创建一个ITaskService对象
ITaskService* pTaskService;
hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_SERVER, IID_ITaskService, (LPVOID*)&pTaskService);
// 先连接到本地电脑任务服务,然后才可以使用ITaskService对象中的其他方法
hr = pTaskService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
// 获取根任务文件夹的路径
ITaskFolder* pTaskFolder;
hr = pTaskService->GetFolder((BSTR)TEXT("\\"), &pTaskFolder);
// 如果已经存在相同任务名称的任务计划,则删除
pTaskFolder->DeleteTask((BSTR)lpszTaskName, 0);
// 创建ITaskDefinition对象(任务定义对象),使用ITaskDefinition对象定义任务的信息
ITaskDefinition* pTaskDefinition;
hr = pTaskService->NewTask(0, &pTaskDefinition);
pTaskService->Release();
// 设置注册信息
IRegistrationInfo* pRegistrationInfo;
hr = pTaskDefinition->get_RegistrationInfo(&pRegistrationInfo);
hr = pRegistrationInfo->put_Author((BSTR)lpszAuthor);
pRegistrationInfo->Release();
// 设置主体信息
IPrincipal* pPrincipal;
hr = pTaskDefinition->get_Principal(&pPrincipal);
hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
pPrincipal->Release();
// 设置配置信息
ITaskSettings* pTaskSettings;
hr = pTaskDefinition->get_Settings(&pTaskSettings);
hr = pTaskSettings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
hr = pTaskSettings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
hr = pTaskSettings->put_AllowDemandStart(VARIANT_TRUE);
hr = pTaskSettings->put_StartWhenAvailable(VARIANT_FALSE);
hr = pTaskSettings->put_MultipleInstances(TASK_INSTANCES_PARALLEL);
hr = pTaskSettings->put_WakeToRun(VARIANT_TRUE);
pTaskSettings->Release();
// 获取IActionCollection对象
IActionCollection* pActionCollection;
hr = pTaskDefinition->get_Actions(&pActionCollection);
// 创建执行操作
IAction* pAction;
hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
pActionCollection->Release();
// 获取IExecAction对象
IExecAction* pExecAction;
hr = pAction->QueryInterface(IID_IExecAction, (LPVOID*)&pExecAction);
pAction->Release();
// 设置可执行文件路径和参数
hr = pExecAction->put_Path((BSTR)lpszProgramPath);
hr = pExecAction->put_Arguments((BSTR)lpszParameter);
pExecAction->Release();
// 获取ITriggerCollection对象
ITriggerCollection* pTriggerCollection;
hr = pTaskDefinition->get_Triggers(&pTriggerCollection);
// 创建触发器
ITrigger* pTrigger;
hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger);
pTriggerCollection->Release();
// 保存任务到根任务文件夹
IRegisteredTask* pRegisteredTask;
hr = pTaskFolder->RegisterTaskDefinition((BSTR)lpszTaskName, pTaskDefinition, TASK_CREATE_OR_UPDATE, _variant_t(), _variant_t(), TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(TEXT("")), &pRegisteredTask);
// 释放相关对象
pRegisteredTask->Release();
pTaskDefinition->Release();
pTaskFolder->Release();
// 关闭COM库
CoUninitialize();
return TRUE;
};
BOOL MyDeleteTaskScheduler(LPCTSTR lpszTaskName) {
HRESULT hr;
if (!lpszTaskName)
return FALSE;
// 初始化COM并设置COM安全级别
hr = CoInitializeEx(NULL, 0);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
// 创建一个ITaskService对象
ITaskService* pTaskService;
hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_SERVER, IID_ITaskService, (LPVOID*)&pTaskService);
// 先连接到本地电脑任务服务,然后才可以使用ITaskService对象中的其他方法
hr = pTaskService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
// 获取根任务文件夹的路径
ITaskFolder* pTaskFolder;
hr = pTaskService->GetFolder((BSTR)TEXT("\\"), &pTaskFolder);
// 删除任务计划
pTaskFolder->DeleteTask((BSTR)lpszTaskName, 0);
// 释放相关对象
pTaskFolder->Release();
pTaskService->Release();
// 关闭COM库
CoUninitialize();
return TRUE;
};

系统服务

概览

OpenSCManager建立到指定计算机上服务控制管理器的链接,并打开指定服务控制管理器数据库,成功返回服务控制管理器句柄,失败返回NULL,不需要时用CloseServiceHandle关闭服务句柄。

EnumServicesStatuesEx枚举服务控制管理器数据库中的服务,返回一个ENUM_SERVICE_STATUS_PROCESS结构数组,每个数组元素对应一个服务的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _ENUM_SERVICE_STATUS_PROCESS {
LPTSTR lpServiceName; //服务名称
LPTSTR lpDisplayName; //用来标识服务的显示名称
SERVICE_STATUS_PROCESS ServiceStatusProcess;
} ENUM_SERVICE_STATUS_PROCESS, * LPENUM_SERVICE_STATUS_PROCESS;
typedef struct _SERVICE_STATUS_PROCESS {
DWORD dwServiceType; //服务类型
DWORD dwCurrentState; //服务当前状态
DWORD dwControlsAccepted; //服务可接收并在其处理函数中处理的控制代码 以及对应类型的通知
DWORD dwWin32ExitCode; //用于报告启动或停止时发生错误的错误代码
DWORD dwServiceSpecificExitCode; //服务启动或停止时发生错误的服务特定错误代码
DWORD dwCheckPoint; //用于跟踪服务操作的进度
DWORD dwWaitHint; //服务操作所需估计事件
DWORD dwProcessId; //服务的进程ID
DWORD dwServiceFlags; //服务是否在系统进程中运行
} SERVICE_STATUS_PROCESS, * LPSERVICE_STATUS_PROCESS;

dwCurrentState可以是:

枚举值 含义
SERVICE_START_PENDING 正在启动
SERVICE_RUNNING 正在运行
SERVICE_STOP_PENDING 正在停止
SERVICE_STOPPED 已停止
SERVICE_PAUSE_PENDING 正在暂停
SERVICE_PAUSED 已暂停
SERVICE_CONTINUE_PENDING 正在继续

dwControlsAccepted字段常用的有:

枚举值 含义
SERVICE_ACCEPT_STOP 可以停止
SERVICE_ACCEPT_PAUSE_CONTINUE 可以暂停并继续

OpenService打开服务,执行成功返回服务句柄,失败NULL,不需要时用CloseServiceHandle关闭服务句柄。再用QueryServiceConfig查询该服务配置参数,返回QUERY_SERVICE_CONFIG结构:

1
2
3
4
5
6
7
8
9
10
11
typedef struct _QUERY_SERVICE_CONFIG {
DWORD dwServiceType; //服务类型
DWORD dwStartType; //启动类型
DWORD dwErrorControl; //无法启动时采取的措施
LPTSTR lpBinaryPathName; //服务的文件路径
LPTSTR lpLoadOrderGroup; //服务所属加载顺序组名称
DWORD dwTagId; //lpLoadOrderGroup指定组中此服务UID
LPTSTR lpDependencies; //服务名或加载顺序组名的数组指针
LPTSTR lpServiceStartName; //进程运行时将登录的账户名称
LPTSTR lpDisplayName; //服务显示名称
} QUERY_SERVICE_CONFIG, * LPQUERY_SERVICE_CONFIG;

dwStartType有:

枚举值 含义
SERVICE_BOOT_START 自动启动,用于驱动程序
SERVICE_SYSTEM_START 手动启动,用于驱动程序
SERVICE_AUTO_START 自动启动
SERVICE_DEMAND_START 手动启动
SERVICE_DISABLED 禁用

QueryServiceConfig2可查询服务其他配置,其中第2个参数为SERVICE_CONFIG_DESCRIPTION时返回SERVICE_DESCRIPTION结构:

1
2
3
typedef struct _SERVICE_DESCRIPTION {
LPTSTR lpDescription; //服务描述字符串
} SERVICE_DESCRIPTION, *LPSERVICE_DESCRIPTION;

StartService

启动指定服务:

1
2
3
4
5
BOOL WINAPI StartService(
_In_ SC_HANDLE hService, //服务句柄 需要SERVICE_START访问权限
_In_ DWORD dwNumServiceArgs, //lpServiceArgVectos数组元素个数
_In_opt_ LPCTSTR* lpServiceArgVectors //传递给服务的ServiceMain函数的参数数组
);//成功返回非0 失败0

ControlService

向指定服务发送控制代码:

1
2
3
4
5
BOOL WINAPI ControlService(
_In_ SC_HANDLE hService, //服务句柄
_In_ DWORD dwControl, //控制代码
_Out_ LPSERVICE_STATUS lpServiceStatus //返回最新服务状态信息
); //成功返回非0 失败0

dwControl控制代码可以是:

枚举值 含义
SERVICE_CONTROL_STOP 通知服务停止服务,需要SERVICE_STOP访问权限
SERVICE_CONTROL_PAUSE 通知服务暂停服务,需要SERVICE_PAUSE_CONTINUE访问权限
SERVICE_CONTROL_CONTINUE 通知暂停的服务恢复,需要SERVICE_PAUSE_CONTINUE访问权限
SERVICE_CONTROL_SHUTDOWN 通知服务系统正在关闭

ChangeServiceConfig

更改指定服务配置参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL WINAPI ChangeServiceConfig(
_In_ SC_HANDLE hService, //服务句柄 需要SERVICE_CHANGE_CONFIG访问权限
_In_ DWORD dwServiceType, //服务类型 不需要则设置为SERVICE_NO_CHANGE
_In_ DWORD dwStartType, //启动类型 不需要则设置为SERVICE_NO_CHANGE
_In_ DWORD dwErrorControl, //错误控制 不需要则设置为SERVICE_NO_CHANGE
_In_opt_ LPCTSTR lpBinaryPathName, //文件路径
_In_opt_ LPCTSTR lpLoadOrderGroup, //所属加载顺序组名
_Out_opt_ LPDWORD lpdwTagId, //返回lpLoadOrderGroup指定组中此服务UID
_In_opt_ LPCTSTR lpDependencies, //服务名或加载顺序组名数组指针
_In_opt_ LPCTSTR lpServiceStartName, //服务进程运行时将登录的账户名称
_In_opt_ LPCTSTR lpPassword, //lpServiceStartName指定账户名的密码
_In_opt_ LPCTSTR lpDisplayName //服务显示名称
); //成功返回非0 失败0

要更改指定服务其他配置参数用ChangeServiceConfig2

CreateService

创建一个服务对象并将其添加到服务控制管理器数据库中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SC_HANDLE WINAPI CreateService(
_In_ SC_HANDLE hSCManager, //SCM数据库句柄 需要SC_MANAGER_CREATE_SERVICE权限
_In_ LPCTSTR lpServiceName, //服务名称 最长256字符
_In_opt_ LPCTSTR lpDisplayName, //显示名称 最长256字符
_In_ DWORD dwDesiredAccess, //服务访问权限 可SERVICE_ALL_ACCESS
_In_ DWORD dwServiceType, //服务类型 通常SERVICE_WIN32_OWN_PROCESS
_In_ DWORD dwStartType, //启动类型 可SERVICE_AUTO_START
_In_ DWORD dwErrorControl, //错误控制 可SERVICE_ERROR_NORMAL
_In_opt_ LPCTSTR lpBinaryPathName, //服务文件路径 包含空格则用引号引起来
_In_opt_ LPCTSTR lpLoadOrderGroup, //服务所属加载顺序组名
_Out_opt_ LPDWORD lpdwTagId, //返回lpLoadOrderGroup组中此服务UID
_In_opt_ LPCTSTR lpDependencies, //服务名或加载顺序组名称的数组指针
_In_opt_ LPCTSTR lpServiceStartName, //服务进程运行时将登录的账户名称
_In_opt_ LPCTSTR lpPassword //lpServiceStartName指定的账户名密码
); //成功返回服务句柄 失败NULL

DeleteService

将指定服务从服务控制管理器数据库中删除:

1
2
3
BOOL DeleteService(
_In_ SC_HANDLE hService //服务句柄 需要DELETE访问权限
);

实例:系统服务管理器仿真

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
#include <windows.h>
#include <tchar.h>
#include <Commctrl.h>
#include <strsafe.h>
#include "resource.h"
#pragma comment(lib, "Comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
typedef struct _ITEMDATA{
TCHAR m_szServiceName[256]; // 服务名称
TCHAR m_szDisplayName[256]; // 显示名称
DWORD m_dwCurrentState; // 服务状态
DWORD m_dwControlsAccepted; // 控制代码
DWORD m_dwStartType; // 启动类型
}ITEMDATA, * PITEMDATA;
// 全局变量
HINSTANCE g_hInstance;
HWND g_hwndDlg; // 对话框窗口句柄
HWND g_hwndList; // 列表视图控件句柄
BOOL g_bAscendingDisplayName; // 是否已按显示名称升序排列
BOOL g_bAscendingCurrentState; // 是否已按服务状态升序排列
BOOL g_bAscendingStartType; // 是否已按启动类型升序排列
LPCTSTR arrlpStrCurrentState[] = { TEXT(""), TEXT("已停止"), TEXT("正在启动"), TEXT("正在停止"),TEXT("正在运行"), TEXT("正在继续"), TEXT("正在暂停"), TEXT("已暂停") };
LPCTSTR arrlpStrStartType[] = { TEXT("自动(用于驱动程序服务)"), TEXT("手动(用于驱动程序服务)"),TEXT("自动"), TEXT("手动"), TEXT("禁用") };
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK DialogProcCreateService(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 获取服务列表
BOOL GetServiceList();
// 列表视图控件排序回调函数
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
// 获取选中服务的当前状态和配置参数,启用禁用相关菜单项
VOID QueryServiceStatusAndConfig();
// 启动服务
BOOL StartTheService();
// 控制服务状态,停止服务、暂停服务、继续服务
BOOL ControlCurrentState(DWORD dwControl, DWORD dwNewCurrentState);
// 控制启动类型,设为自动启动、设为手动启动、设为已禁用
BOOL ChangeTheServiceConfig(DWORD dwStartType);
// 添加服务
BOOL CreateAService(LPCTSTR lpBinaryPathName, LPCTSTR lpServiceName, LPCTSTR lpDisplayName, DWORD dwStartType, DWORD dwServiceType = SERVICE_WIN32_OWN_PROCESS, LPCTSTR lpDescription = NULL, DWORD dwDesiredAccess = SERVICE_ALL_ACCESS, DWORD dwErrorControl = SERVICE_ERROR_NORMAL);
// 删除服务
BOOL DeleteTheService();
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){
INT_PTR nResult;
switch (uMsg){
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
g_hwndList = GetDlgItem(hwndDlg, IDC_LIST_SERVICE);
// 设置列表视图控件的扩展样式
SendMessage(g_hwndList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
// 设置列标题:服务的显示名称、服务状态、启动类型、文件路径、服务描述
LVCOLUMN lvc;
ZeroMemory(&lvc, sizeof(LVCOLUMN));
lvc.mask = LVCF_SUBITEM | LVCF_WIDTH | LVCF_TEXT;
lvc.iSubItem = 0; lvc.cx = 260; lvc.pszText = (LPTSTR)TEXT("服务的显示名称");
SendMessage(g_hwndList, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc);
lvc.iSubItem = 1; lvc.cx = 80; lvc.pszText = (LPTSTR)TEXT("服务状态");
SendMessage(g_hwndList, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc);
lvc.iSubItem = 2; lvc.cx = 80; lvc.pszText = (LPTSTR)TEXT("启动类型");
SendMessage(g_hwndList, LVM_INSERTCOLUMN, 2, (LPARAM)&lvc);
lvc.iSubItem = 3; lvc.cx = 300; lvc.pszText = (LPTSTR)TEXT("文件路径");
SendMessage(g_hwndList, LVM_INSERTCOLUMN, 3, (LPARAM)&lvc);
lvc.iSubItem = 4; lvc.cx = 300; lvc.pszText = (LPTSTR)TEXT("服务描述");
SendMessage(g_hwndList, LVM_INSERTCOLUMN, 4, (LPARAM)&lvc);
// 获取服务列表
GetServiceList();
return TRUE;
};
case WM_SIZE: {
// 根据父窗口客户区的大小调整列表视图控件的大小
MoveWindow(g_hwndList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return TRUE;
};
case WM_NOTIFY: {
// 当用户点击列标题的时候
if (((LPNMHDR)lParam)->code == LVN_COLUMNCLICK) {
LPNMLISTVIEW pNMLV = (LPNMLISTVIEW)lParam;
if (pNMLV->iSubItem < 3) {
SendMessage(g_hwndList, LVM_SORTITEMS, pNMLV->iSubItem, (LPARAM)CompareFunc);
switch (pNMLV->iSubItem) {
case 0: {
g_bAscendingDisplayName = ~g_bAscendingDisplayName;
break;
};
case 1: {
g_bAscendingCurrentState = ~g_bAscendingCurrentState;
break;
};
case 2: {
g_bAscendingStartType = ~g_bAscendingStartType;
break;
};
};
};
}
// 弹出快捷菜单
else if (((LPNMHDR)lParam)->code == NM_RCLICK) {
if (((LPNMITEMACTIVATE)lParam)->iItem < 0)
return FALSE;
POINT pt = { 0 };
GetCursorPos(&pt);
TrackPopupMenu(GetSubMenu(GetMenu(hwndDlg), 0), TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hwndDlg, NULL);
};
return TRUE;
};
case WM_INITMENUPOPUP: {
// 在显示弹出菜单之前,获取选中服务的当前状态和配置参数,启用禁用相关菜单项
QueryServiceStatusAndConfig();
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case ID_SERVICE_ADD: {
nResult = DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_CREATESERVICE), hwndDlg, DialogProcCreateService, NULL);
if (nResult == 2)
MessageBox(hwndDlg, TEXT("创建服务成功"), TEXT("成功提示"), MB_OK);
else if (nResult == 1)
MessageBox(hwndDlg, TEXT("创建服务失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_DELETE: {
if (DeleteTheService())
MessageBox(hwndDlg, TEXT("删除服务成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("删除服务失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_START: {
if (StartTheService())
MessageBox(hwndDlg, TEXT("启动服务成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("启动服务失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_STOP: {
if (ControlCurrentState(SERVICE_CONTROL_STOP, SERVICE_STOPPED))
MessageBox(hwndDlg, TEXT("停止服务成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("停止服务失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_PAUSE: {
if (ControlCurrentState(SERVICE_CONTROL_PAUSE, SERVICE_PAUSED))
MessageBox(hwndDlg, TEXT("暂停服务成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("暂停服务失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_CONTINUE: {
if (ControlCurrentState(SERVICE_CONTROL_CONTINUE, SERVICE_RUNNING))
MessageBox(hwndDlg, TEXT("继续服务成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("继续服务失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_AUTO: {
if (ChangeTheServiceConfig(SERVICE_AUTO_START))
MessageBox(hwndDlg, TEXT("设为自动启动成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("设为自动启动失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_DEMAND: {
if (ChangeTheServiceConfig(SERVICE_DEMAND_START))
MessageBox(hwndDlg, TEXT("设为手动启动成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("设为手动启动失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_DISABLE: {
if (ChangeTheServiceConfig(SERVICE_DISABLED))
MessageBox(hwndDlg, TEXT("设为已禁用成功"), TEXT("成功提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("设为已禁用失败"), TEXT("错误提示"), MB_OK);
break;
};
case ID_SERVICE_REFRESH: {
// 刷新列表的时候按显示名称升序排列
g_bAscendingDisplayName = FALSE;
GetServiceList();
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
BOOL GetServiceList() {
SC_HANDLE hSCManager = NULL; // 服务控制管理器数据库的句柄
LPBYTE lpServices = NULL; // 缓冲区指针,返回ENUM_SERVICE_STATUS_PROCESS结构数组
DWORD dwcbBufSize = 0; // 上面缓冲区的大小
DWORD dwcbBytesNeeded; // lpServices设为NULL,dwcbBufSize设为0,返回所需的缓冲区大小
DWORD dwServicesReturned; // 返回服务个数
DWORD dwResumeHandle = 0; // 枚举的起点
LVITEM lvi = { 0 };
// 打开本地计算机的服务控制管理器数据库,返回服务控制管理器数据库的句柄
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!hSCManager)
return FALSE;
// 枚举服务控制管理器数据库中的服务,首先获取所需的缓冲区大小(字节单位)
EnumServicesStatusEx(hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, dwcbBufSize, &dwcbBytesNeeded, &dwServicesReturned, &dwResumeHandle, NULL);
// 分配合适大小的缓冲区进行枚举服务
lpServices = new BYTE[dwcbBytesNeeded];
ZeroMemory(lpServices, dwcbBytesNeeded);
dwResumeHandle = 0;
EnumServicesStatusEx(hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, lpServices, dwcbBytesNeeded, &dwcbBytesNeeded, &dwServicesReturned, &dwResumeHandle, NULL);
// 先释放所有项目数据,然后删除所有列表项
ZeroMemory(&lvi, sizeof(LVITEM));
int nCount = SendMessage(g_hwndList, LVM_GETITEMCOUNT, 0, 0);
for (int i = 0; i < nCount; i++) {
lvi.iItem = i; lvi.mask = LVIF_PARAM;
SendMessage(g_hwndList, LVM_GETITEM, 0, (LPARAM)&lvi);
delete (PITEMDATA)(lvi.lParam);
};
SendMessage(g_hwndList, LVM_DELETEALLITEMS, 0, 0);
LPENUM_SERVICE_STATUS_PROCESS pEnumServiceStatus = (LPENUM_SERVICE_STATUS_PROCESS)lpServices;
ZeroMemory(&lvi, sizeof(LVITEM));
// 遍历获取到的ENUM_SERVICE_STATUS_PROCESS结构数组
for (DWORD i = 0; i < dwServicesReturned; i++) {
// 设置与列表项相关联的项目数据
PITEMDATA pItemData = new ITEMDATA;
ZeroMemory(pItemData, sizeof(ITEMDATA));
StringCchCopy(pItemData->m_szServiceName, _countof(pItemData->m_szServiceName), pEnumServiceStatus[i].lpServiceName);
StringCchCopy(pItemData->m_szDisplayName, _countof(pItemData->m_szDisplayName), pEnumServiceStatus[i].lpDisplayName);
pItemData->m_dwCurrentState = pEnumServiceStatus[i].ServiceStatusProcess.dwCurrentState;
pItemData->m_dwControlsAccepted = pEnumServiceStatus[i].ServiceStatusProcess.dwControlsAccepted;
lvi.iItem = SendMessage(g_hwndList, LVM_GETITEMCOUNT, 0, 0);
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.lParam = (LPARAM)pItemData;
// 第1列,服务的显示名称
lvi.iSubItem = 0; lvi.pszText = pEnumServiceStatus[i].lpDisplayName;
SendMessage(g_hwndList, LVM_INSERTITEM, 0, (LPARAM)&lvi);
lvi.mask = LVIF_TEXT;
// 第2列,服务状态
lvi.iSubItem = 1; lvi.pszText = (LPTSTR)arrlpStrCurrentState[pEnumServiceStatus[i].ServiceStatusProcess.dwCurrentState];
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
// 第3列,启动类型
SC_HANDLE hService;
LPQUERY_SERVICE_CONFIG lpServiceConfig = NULL;// 返回服务配置参数的缓冲区指针
DWORD dwcbBufSizeService = 0; // 上面缓冲区的大小
DWORD dwcbBytesNeededService; // 上面两参数设为NULL和0,返回所需缓冲区大小
// 打开服务返回一个服务句柄
hService = OpenService(hSCManager, pEnumServiceStatus[i].lpServiceName, SERVICE_QUERY_CONFIG);
// 查询该服务的配置参数,首先获取所需的缓冲区大小(字节单位)
QueryServiceConfig(hService, NULL, 0, &dwcbBytesNeededService);
// 分配合适大小的缓冲区查询该服务的配置参数
lpServiceConfig = (LPQUERY_SERVICE_CONFIG)new BYTE[dwcbBytesNeededService];
ZeroMemory(lpServiceConfig, dwcbBytesNeededService);
QueryServiceConfig(hService, lpServiceConfig, dwcbBytesNeededService, &dwcbBytesNeededService);
lvi.iSubItem = 2; lvi.pszText = (LPTSTR)arrlpStrStartType[lpServiceConfig->dwStartType];
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
pItemData->m_dwStartType = lpServiceConfig->dwStartType;
// 第4列,文件路径
lvi.iSubItem = 3; lvi.pszText = lpServiceConfig->lpBinaryPathName;
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
delete[]lpServiceConfig;
// 第5列,服务描述
LPBYTE lpBufferService2; // 返回服务其他配置参数的缓冲区指针
DWORD dwcbBufSizeService2 = 0; // 上面缓冲区的大小
DWORD dwcbBytesNeededService2; // 上面两参数设为NULL和0,返回所需缓冲区大小
// 查询该服务的其他配置参数,首先获取所需的缓冲区大小(字节单位)
QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &dwcbBytesNeededService2);
// 分配合适大小的缓冲区查询该服务的其他配置参数
lpBufferService2 = new BYTE[dwcbBytesNeededService2];
ZeroMemory(lpBufferService2, dwcbBytesNeededService2);
QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, lpBufferService2, dwcbBytesNeededService2, &dwcbBytesNeededService2);
lvi.iSubItem = 4; lvi.pszText = ((LPSERVICE_DESCRIPTION)lpBufferService2)->lpDescription;
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
delete[]lpBufferService2;
CloseServiceHandle(hService);
};
// 按显示名称升序排列
SendMessage(g_hwndList, LVM_SORTITEMS, 0, (LPARAM)CompareFunc);
g_bAscendingDisplayName = ~g_bAscendingDisplayName;
delete[]lpServices;
// 关闭服务控制管理器数据库句柄
CloseServiceHandle(hSCManager);
return TRUE;
};
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) {
LPCTSTR lpStr1, lpStr2;
switch (lParamSort) {
case 0: {
lpStr1 = ((PITEMDATA)lParam1)->m_szDisplayName;
lpStr2 = ((PITEMDATA)lParam2)->m_szDisplayName;
if (!g_bAscendingDisplayName)
return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, lpStr1, -1, lpStr2, -1, NULL, NULL, NULL) - 2;
else
return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, lpStr2, -1, lpStr1, -1, NULL, NULL, NULL) - 2;
break;
};
case 1: {
lpStr1 = arrlpStrCurrentState[((PITEMDATA)lParam1)->m_dwCurrentState];
lpStr2 = arrlpStrCurrentState[((PITEMDATA)lParam2)->m_dwCurrentState];
if (!g_bAscendingCurrentState)
return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, lpStr1, -1, lpStr2, -1, NULL, NULL, NULL) - 2;
else
return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, lpStr2, -1, lpStr1, -1, NULL, NULL, NULL) - 2;
break;
};
case 2: {
lpStr1 = arrlpStrStartType[((PITEMDATA)lParam1)->m_dwStartType];
lpStr2 = arrlpStrStartType[((PITEMDATA)lParam2)->m_dwStartType];
if (!g_bAscendingStartType)
return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, lpStr1, -1, lpStr2, -1, NULL, NULL, NULL) - 2;
else
return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, lpStr2, -1, lpStr1, -1, NULL, NULL, NULL) - 2;
break;
};
};
return 0;
};
VOID QueryServiceStatusAndConfig() {
int nSelected;
LVITEM lvi = { 0 };
PITEMDATA pItemData;
HMENU hMenu = GetMenu(g_hwndDlg);
nSelected = SendMessage(g_hwndList, LVM_GETSELECTIONMARK, 0, 0);
// 因为是调用GetMenu获取的菜单栏句柄,而不是调用LoadMenu加载的菜单资源,
// 所以每次都需要设置每个菜单项的状态,只有添加服务和刷新列表这两个菜单项一直处于启用状态
if (nSelected < 0) {
EnableMenuItem(hMenu, ID_SERVICE_DELETE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_START, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_STOP, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_PAUSE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_CONTINUE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_AUTO, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_DEMAND, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_DISABLE, MF_BYCOMMAND | MF_DISABLED);
return;
}
else {
EnableMenuItem(hMenu, ID_SERVICE_DELETE, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_START, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_STOP, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_PAUSE, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_CONTINUE, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_AUTO, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_DEMAND, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(hMenu, ID_SERVICE_DISABLE, MF_BYCOMMAND | MF_ENABLED);
};
// 获取选中列表项的项目数据
lvi.mask = LVIF_PARAM; lvi.iItem = nSelected;
SendMessage(g_hwndList, LVM_GETITEM, 0, (LPARAM)&lvi);
pItemData = (PITEMDATA)(lvi.lParam);
// 禁用启动服务、停止服务、暂停服务、继续服务这些菜单项中不应该启用的
if (pItemData->m_dwCurrentState == SERVICE_START_PENDING || pItemData->m_dwCurrentState == SERVICE_RUNNING) {
EnableMenuItem(hMenu, ID_SERVICE_START, MF_BYCOMMAND | MF_DISABLED);
if (!(pItemData->m_dwControlsAccepted & SERVICE_ACCEPT_STOP))
EnableMenuItem(hMenu, ID_SERVICE_STOP, MF_BYCOMMAND | MF_DISABLED);
if (!(pItemData->m_dwControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE))
EnableMenuItem(hMenu, ID_SERVICE_PAUSE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_CONTINUE, MF_BYCOMMAND | MF_DISABLED);
}
else if (pItemData->m_dwCurrentState == SERVICE_STOP_PENDING || pItemData->m_dwCurrentState == SERVICE_STOPPED) {
EnableMenuItem(hMenu, ID_SERVICE_STOP, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_PAUSE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_CONTINUE, MF_BYCOMMAND | MF_DISABLED);
}
else if (pItemData->m_dwCurrentState == SERVICE_PAUSE_PENDING || pItemData->m_dwCurrentState == SERVICE_PAUSED) {
EnableMenuItem(hMenu, ID_SERVICE_START, MF_BYCOMMAND | MF_DISABLED);
if (!(pItemData->m_dwControlsAccepted & SERVICE_ACCEPT_STOP))
EnableMenuItem(hMenu, ID_SERVICE_STOP, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_PAUSE, MF_BYCOMMAND | MF_DISABLED);
if (!(pItemData->m_dwControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE))
EnableMenuItem(hMenu, ID_SERVICE_CONTINUE, MF_BYCOMMAND | MF_DISABLED);
}
else if (pItemData->m_dwCurrentState == SERVICE_CONTINUE_PENDING) {
EnableMenuItem(hMenu, ID_SERVICE_START, MF_BYCOMMAND | MF_DISABLED);
if (!(pItemData->m_dwControlsAccepted & SERVICE_ACCEPT_STOP))
EnableMenuItem(hMenu, ID_SERVICE_STOP, MF_BYCOMMAND | MF_DISABLED);
if (!(pItemData->m_dwControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE))
EnableMenuItem(hMenu, ID_SERVICE_PAUSE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SERVICE_CONTINUE, MF_BYCOMMAND | MF_DISABLED);
};
// 禁用设为自动启动、设为手动启动、设为已禁用这些菜单项中不应该启用的
if (pItemData->m_dwStartType == SERVICE_AUTO_START)
EnableMenuItem(hMenu, ID_SERVICE_AUTO, MF_BYCOMMAND | MF_DISABLED);
else if (pItemData->m_dwStartType == SERVICE_DEMAND_START)
EnableMenuItem(hMenu, ID_SERVICE_DEMAND, MF_BYCOMMAND | MF_DISABLED);
else if (pItemData->m_dwStartType == SERVICE_DISABLED)
EnableMenuItem(hMenu, ID_SERVICE_DISABLE, MF_BYCOMMAND | MF_DISABLED);
return;
};
BOOL StartTheService() {
int nSelected;
LVITEM lvi = { 0 };
PITEMDATA pItemData;
SC_HANDLE hSCManager = NULL; // 服务控制管理器数据库的句柄
SC_HANDLE hService; // 服务句柄
// 获取选中列表项的项目数据
nSelected = SendMessage(g_hwndList, LVM_GETSELECTIONMARK, 0, 0);
lvi.mask = LVIF_PARAM; lvi.iItem = nSelected;
SendMessage(g_hwndList, LVM_GETITEM, 0, (LPARAM)&lvi);
pItemData = (PITEMDATA)(lvi.lParam);
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCManager)
return FALSE;
hService = OpenService(hSCManager, pItemData->m_szServiceName, SERVICE_START);
if (!hService) {
CloseServiceHandle(hSCManager);
return FALSE;
};
if (!StartService(hService, 0, NULL)) {
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return FALSE;
};
pItemData->m_dwCurrentState = SERVICE_RUNNING;
lvi.mask = LVIF_TEXT;
lvi.iItem = nSelected; lvi.iSubItem = 1;
lvi.pszText = (LPTSTR)arrlpStrCurrentState[SERVICE_RUNNING];
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return TRUE;
};
BOOL ControlCurrentState(DWORD dwControl, DWORD dwNewCurrentState) {
int nSelected;
LVITEM lvi = { 0 };
PITEMDATA pItemData;
SC_HANDLE hSCManager = NULL; // 服务控制管理器数据库的句柄
SC_HANDLE hService; // 服务句柄
SERVICE_STATUS serviceStatus = { 0 };
// 获取选中列表项的项目数据
nSelected = SendMessage(g_hwndList, LVM_GETSELECTIONMARK, 0, 0);
lvi.mask = LVIF_PARAM; lvi.iItem = nSelected;
SendMessage(g_hwndList, LVM_GETITEM, 0, (LPARAM)&lvi);
pItemData = (PITEMDATA)(lvi.lParam);
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCManager)
return FALSE;
hService = OpenService(hSCManager, pItemData->m_szServiceName, SERVICE_STOP | SERVICE_PAUSE_CONTINUE);
if (!hService) {
CloseServiceHandle(hSCManager);
return FALSE;
};
if (!ControlService(hService, dwControl, &serviceStatus)) {
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return FALSE;
};
// 实际编程中应根据返回的最新服务状态信息的SERVICE_STATUS结构进行合理化处理,
// 而不应该简单地使用传递过来的dwNewCurrentState参数
pItemData->m_dwCurrentState = dwNewCurrentState;
lvi.mask = LVIF_TEXT;
lvi.iItem = nSelected; lvi.iSubItem = 1;
lvi.pszText = (LPTSTR)arrlpStrCurrentState[dwNewCurrentState];
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return TRUE;
};
BOOL ChangeTheServiceConfig(DWORD dwStartType) {
int nSelected;
LVITEM lvi = { 0 };
PITEMDATA pItemData;
SC_HANDLE hSCManager = NULL; // 服务控制管理器数据库的句柄
SC_HANDLE hService; // 服务句柄
// 获取选中列表项的项目数据
nSelected = SendMessage(g_hwndList, LVM_GETSELECTIONMARK, 0, 0);
lvi.mask = LVIF_PARAM; lvi.iItem = nSelected;
SendMessage(g_hwndList, LVM_GETITEM, 0, (LPARAM)&lvi);
pItemData = (PITEMDATA)(lvi.lParam);
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCManager)
return FALSE;
hService = OpenService(hSCManager, pItemData->m_szServiceName, SERVICE_CHANGE_CONFIG);
if (!hService) {
CloseServiceHandle(hSCManager);
return FALSE;
};
if (!ChangeServiceConfig(hService, SERVICE_NO_CHANGE, dwStartType, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) {
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return FALSE;
};
pItemData->m_dwStartType = dwStartType;
lvi.mask = LVIF_TEXT;
lvi.iItem = nSelected; lvi.iSubItem = 2;
lvi.pszText = (LPTSTR)arrlpStrStartType[dwStartType];
SendMessage(g_hwndList, LVM_SETITEM, 0, (LPARAM)&lvi);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return TRUE;
};
//////////////////////////////////////////////////////////////////////////
INT_PTR CALLBACK DialogProcCreateService(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szFile[MAX_PATH] = { 0 }; // 返回用户选择的文件名的缓冲区
TCHAR szFileTitle[MAX_PATH] = { 0 }; // 返回用户所选文件的文件名和扩展名的缓冲区
OPENFILENAME ofn = { sizeof(OPENFILENAME) };
static HWND hwndCombo;
int nIndex, nCount;
BOOL bRet = FALSE;
TCHAR szBinaryPathName[MAX_PATH] = { 0 }; // 文件路径
TCHAR szServiceName[256] = { 0 }; // 服务名称
TCHAR szDisplayName[256] = { 0 }; // 显示名称
TCHAR szDescription[1024] = { 0 }; // 服务描述
DWORD dwStartType; // 启动类型
switch (uMsg) {
case WM_INITDIALOG: {
// 添加列表项,设置项目数据为服务的启动类型数值,默认选中自动启动
hwndCombo = GetDlgItem(hwndDlg, IDC_COMBO_STARTTYPE);
nIndex = SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)TEXT("自动启动"));
SendMessage(hwndCombo, CB_SETITEMDATA, nIndex, 0x00000002);
nIndex = SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)TEXT("手动启动"));
SendMessage(hwndCombo, CB_SETITEMDATA, nIndex, 0x00000003);
nIndex = SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)TEXT("禁用"));
SendMessage(hwndCombo, CB_SETITEMDATA, nIndex, 0x00000004);
SendMessage(hwndCombo, CB_SETCURSEL, 0, 0);
// 设置服务名称、显示名称和服务描述编辑控件的最大字符个数
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_SERVICENAME), EM_SETLIMITTEXT, _countof(szServiceName) - 1, 0);
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_DISPLAYNAME), EM_SETLIMITTEXT, _countof(szDisplayName) - 1, 0);
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_DESCRIPTION), EM_SETLIMITTEXT, _countof(szDescription) - 1, 0);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("EXE文件(*.exe)\0*.exe\0所有文件(*.*)\0*.*\0");
ofn.lpstrFile = szFile;
ofn.nMaxFile = _countof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = _countof(szFileTitle);
ofn.lpstrTitle = TEXT("请选择要打开的文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn)) {
*(_tcsrchr(szFileTitle, TEXT('.'))) = TEXT('\0');
SetDlgItemText(hwndDlg, IDC_EDIT_BINARYPATHNAME, szFile);
SetDlgItemText(hwndDlg, IDC_EDIT_SERVICENAME, szFileTitle);
SetDlgItemText(hwndDlg, IDC_EDIT_DISPLAYNAME, szFileTitle);
};
break;
};
case IDOK: {
nCount = GetDlgItemText(hwndDlg, IDC_EDIT_BINARYPATHNAME, szBinaryPathName, _countof(szBinaryPathName));
if (nCount < 5) {
MessageBox(hwndDlg, TEXT("请输入服务的完整路径"), TEXT("错误提示"), MB_OK);
return TRUE;
};
nCount = GetDlgItemText(hwndDlg, IDC_EDIT_SERVICENAME, szServiceName, _countof(szServiceName));
if (nCount < 1) {
MessageBox(hwndDlg, TEXT("请输入服务名称"), TEXT("错误提示"), MB_OK);
return TRUE;
};
nCount = GetDlgItemText(hwndDlg, IDC_EDIT_DISPLAYNAME, szDisplayName, _countof(szDisplayName));
if (nCount < 1) {
MessageBox(hwndDlg, TEXT("请输入显示名称"), TEXT("错误提示"), MB_OK);
return TRUE;
};
GetDlgItemText(hwndDlg, IDC_EDIT_DESCRIPTION, szDescription, _countof(szDescription));
nIndex = SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
dwStartType = SendMessage(hwndCombo, CB_GETITEMDATA, nIndex, 0);
// 调用自定义函数CreateAService创建服务
bRet = CreateAService(szBinaryPathName, szServiceName, szDisplayName, dwStartType, SERVICE_WIN32_OWN_PROCESS, szDescription);
if (bRet)
EndDialog(hwndDlg, 2);
else
EndDialog(hwndDlg, 1);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
BOOL CreateAService(LPCTSTR lpBinaryPathName, LPCTSTR lpServiceName, LPCTSTR lpDisplayName, DWORD dwStartType, DWORD dwServiceType, LPCTSTR lpDescription, DWORD dwDesiredAccess, DWORD dwErrorControl) {
SC_HANDLE hSCManager = NULL; // 服务控制管理器数据库的句柄
SC_HANDLE hService; // 服务句柄
SERVICE_DESCRIPTION serviceDescription = { 0 }; // 服务描述
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!hSCManager)
return FALSE;
// 创建服务
hService = CreateService(hSCManager, lpServiceName, lpDisplayName, dwDesiredAccess, dwServiceType, dwStartType, dwErrorControl, lpBinaryPathName, NULL, NULL, NULL, NULL, NULL);
if (!hService) {
CloseServiceHandle(hSCManager);
return FALSE;
};
// 设置服务描述
if (!lpDescription) {
serviceDescription.lpDescription = (LPTSTR)lpDescription;
ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &serviceDescription);
};
// 刷新列表,按显示名称升序排列
g_bAscendingDisplayName = FALSE;
GetServiceList();
// 找到新添加列表项的索引
LVFINDINFO lvfi = { 0 };
lvfi.flags = LVFI_STRING;
lvfi.psz = lpDisplayName;
int nIndex = SendMessage(g_hwndList, LVM_FINDITEM, -1, (LPARAM)&lvfi);
// 设置为选中状态
LVITEM lvi = { 0 }; lvi.state = lvi.stateMask = LVIS_SELECTED;
SendMessage(g_hwndList, LVM_SETITEMSTATE, nIndex, (LPARAM)&lvi);
// 把新添加的列表项滚动到可见视图中
int nCount = SendMessage(g_hwndList, LVM_GETCOUNTPERPAGE, 0, 0);
if (nIndex > nCount) {
DWORD dw = SendMessage(g_hwndList, LVM_APPROXIMATEVIEWRECT, nIndex - 2, MAKELPARAM(-1, -1));
SendMessage(g_hwndList, LVM_SCROLL, 0, HIWORD(dw));
};
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return TRUE;
};
BOOL DeleteTheService() {
int nSelected;
LVITEM lvi = { 0 };
PITEMDATA pItemData;
SC_HANDLE hSCManager = NULL; // 服务控制管理器数据库的句柄
SC_HANDLE hService; // 服务句柄
// 获取选中列表项的项目数据
nSelected = SendMessage(g_hwndList, LVM_GETSELECTIONMARK, 0, 0);
lvi.mask = LVIF_PARAM; lvi.iItem = nSelected;
SendMessage(g_hwndList, LVM_GETITEM, 0, (LPARAM)&lvi);
pItemData = (PITEMDATA)(lvi.lParam);
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCManager)
return FALSE;
hService = OpenService(hSCManager, pItemData->m_szServiceName, DELETE);
if (!hService) {
CloseServiceHandle(hSCManager);
return FALSE;
};
if (!DeleteService(hService))
return FALSE;
else {
delete[]pItemData;
SendMessage(g_hwndList, LVM_DELETEITEM, nSelected, 0);
};
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return TRUE;
};

服务程序的编写

概览

服务类型可以是:

枚举值 含义
SERVICE_KERNEL_DRIVER 驱动程序服务
SERVICE_FILE_SYSTEM_DRIVER 文件系统驱动程序服务
SERVICE_WIN32_OWN_PROCESS 在自己进程中运行的服务
SERVICE_WIN32_SHARE_PROCESS 与一个或多个其他服务共享一个进程

当服务控制管理器SCM启动服务程序时,将等待服务程序调用StartServiceCtrlDispatcher。SCM通过命名管道向服务控制调度线程发送控制请求,该线程创建新线程以在启动新服务时调用对应ServiceMain,并调用服务对应处理函数来处理服务控制请求。其中服务入口点函数定义格式为:

1
2
3
4
VOID WINAPI ServiceMain(
_In_ DWORD dwArgc, //lpszArgv参数字符串数组元素个数
_In_ LPTSTR* lpszArgv //参数字符串数组 第一个参数为服务名称 没有为NULL
);

ServiceMain中应先初始化全局变量,并立即用RegisterServiceCtrlHandlerEx注册一个服务控制函数来处理该服务的控制请求。随后执行初始化工作:当初始化工作执行时间预计少于1秒时直接在ServiceMain中初始化,超过1秒应选下面俩方法之一,对于自启动服务推荐第一种:

  • SetServiceStatus,其中第二个参数SERVICE_STATUS.dwCurrentState=SERVICE_RUNNING;SERVICE_STATUS.dwControlsAccepted=0;,标识服务正在运行但不接受任何控制请求,这样SCM可去管理其他服务,而不是已知等待服务初始化完成。
  • 同上但SERVICE_STATUS.dwCurrentState=SERVICE_PENDING;,表示服务正在启动中且不接受任何控制请求,启动该服务的程序可用QueryServiceStatusEx从SCM获取最新检查点值并可用该值向用户报告进度。

初始化完成后用SetServiceStatus将服务状态设置为SERVICE_RUNNING并指定服务可接收的控制请求。最后执行服务任务。

服务控制处理函数应根据传递来的控制代码执行对应任务,并用SetServiceStatus将新的服务状态报告给SCM,处理完后可返回NO_ERROR。

StartServiceCtrlDispatcher

将调用线程连接到服务控制管理器,使线程称为服务控制调度进程。

1
2
3
BOOL WINAPI StartServiceCtrlDispatcher(
_In_ CONST LPSERVICE_TABLE_ENTRY lpServiceTable
); //成功返回非0 失败0

参数lpServiceTable指向一个SERVICE_TABLE_ENTRY结构数组,最后一个数组元素以空结构结尾。

1
2
3
4
typedef struct _SERVICE_TABLE_ENTRY {
LPTSTR lpServiceName; //服务名称
LPSERVICE_MAIN_FUNCTIONW lpServiceProc; //服务入口点函数ServiceMain指针
}SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;

RegisterServiceCtrlHandlerEx

注册一个服务控制处理函数来处理对服务的控制请求:

1
2
3
4
5
SERVICE_STATUS_HANDLER WINAPI RegisterServiceCtrlHandlerEx(
_In_ LPCTSTR lpServiceName, //服务名称
_In_ LPHANDLER_FUNCITON_EX lpHandlerProc, //服务控制处理函数指针
_In_opt_ LPVOID lpContext //用户自定义数据
); //成功返回服务状态句柄 失败0

其中服务控制处理函数定义格式为:

1
2
3
4
5
6
DWORD WINAPI HandlerEx(
_In_ DWORD dwControl, //控制代码
_In_ DWORD dwEventType, //发生事件类型
_In_ LPVOID lpEventData, //事件数据
_In_ LPVOID lpContext //用户自定义数据
);

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <Windows.h>
#include <tchar.h>
#include <wtsapi32.h>
#include <Userenv.h>
#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")
// 服务入口点函数
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv);
// 服务控制处理函数
DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
// 全局变量
TCHAR g_szServiceName[] = TEXT("MyService"); // 服务名称
SERVICE_STATUS_HANDLE g_hServiceStatus; // 服务状态句柄
SERVICE_STATUS g_serviceStatus = { 0 }; // 服务状态结构
int _tmain(int argc, TCHAR* argv[]) {
CONST SERVICE_TABLE_ENTRY serviceTableEntry[] = { {g_szServiceName, ServiceMain}, {NULL, NULL} };
// 将调用线程连接到SCM,从而使该线程成为服务控制调度线程
StartServiceCtrlDispatcher(serviceTableEntry);
return 0;
};
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) {
// 注册一个服务控制处理函数HandlerEx,该函数返回一个服务状态句柄hServiceStatus
g_hServiceStatus = RegisterServiceCtrlHandlerEx(g_szServiceName, HandlerEx, NULL);
g_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_serviceStatus.dwCurrentState = SERVICE_RUNNING;
g_serviceStatus.dwControlsAccepted = 0;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
// 初始化工作
Sleep(2000);
g_serviceStatus.dwCurrentState = SERVICE_RUNNING;
g_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
// 执行服务任务,这里可以是你想执行的任何代码
ShellExecute(NULL, TEXT("open"), TEXT("F:\\Source\\Windows\\Chapter6\\HelloWindows7\\Debug\\HelloWindows.exe"), NULL, NULL, SW_SHOW);
return;
};
DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) {
switch (dwControl) {
case SERVICE_CONTROL_SHUTDOWN: {};
case SERVICE_CONTROL_STOP: {
g_serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
// 可以做一些清理工作
g_serviceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
break;
};
case SERVICE_CONTROL_PAUSE: {
g_serviceStatus.dwCurrentState = SERVICE_PAUSE_PENDING;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
g_serviceStatus.dwCurrentState = SERVICE_PAUSED;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
break;
};
case SERVICE_CONTROL_CONTINUE: {
g_serviceStatus.dwCurrentState = SERVICE_CONTINUE_PENDING;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
g_serviceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_hServiceStatus, &g_serviceStatus);
break;
};
};
return NO_ERROR;
};

突破SESSION 0隔离创建GUI

概览

WTS开头函数指的是Windows终端服务。

可用WTSSendMessage在用户会话中显示一个消息框,如需要实现更复杂的GUI用CreateProcessAsUser在用户会话创建一个进程。

访问令牌时描述进程或线程安全环境的一个内核对象,信息包括与进程或线程关联的用户账户ID和特权。用户登录时系统将用户密码与安全数据库中信息比较,通过验证时系统生成一个访问令牌,该用户账户运行的每个进程都有此访问令牌副本。

要用CreateProcessAsUser需要用WTSQueryUserToken,并建议在创建线程前:

  • DuplicateTokenEx复制一份用户访问令牌句柄,用来创建进程。需要时用SetTokenInformation设置访问令牌信息,如设置会话ID为当前登录用户即可以使新进程以SYSTEM身份运行。
  • CreateEnvironmentBlock获取环境变量块,用于创建进程的lpEnvironment参数。

WTSSendMessage

在用户会话中显示消息框:

1
2
3
4
5
TCHAR szTitle[] = TEXT("消息标题");
TCHAR szMessage[] = TEXT("消息内容");
DWORD dwSessionId, dwResponse;
dwSessionId = WTSGetActiveConsoleSessionId();
WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSessionId, szTitle, sizeof(szTitle), szMessage, sizeof(szMessage), MB_OK, 0, &dwResponse, TRUE);

WTSGetActiveConsoleSessionId

获取当前连接到物理控制台的会话ID。物理控制台指屏幕、鼠标和键盘,这里即获取当前登录用户会话ID:

1
DWORD WTSGetActiveConsoleSessionId(VOID); //成功返回当前登录用户会话ID 通常1 没有用户登录0xFFFFFFFF

CreateProcessAsUser

在用户会话中创建一个具有GUI的进程,一些参数同CreateProcess

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL WINAPI CreateProcess(
_In_opt_ HANDLE hToken, //访问令牌句柄
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBTUES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);

WTSQueryUserToken

查询指定会话ID对应的登录用户的主访问令牌:

1
2
3
4
BOOL WTSQueryUserToken(
_In_ ULONG SessionId, //会话ID
_Out_ PHANDLE phToken //返回主访问令牌句柄
); //成功返回非0 失败0

需要用CloseHandle关闭句柄。

DuplicateTokenEx

复制一份访问令牌句柄:

1
2
3
4
5
6
7
8
BOOL WINAPI DuplicateTokenEx(
_In_ HANDLE hExistingToken, //现有令牌句柄
_In_ DWORD dwDesiredAccess, //新令牌句柄访问权限
_In_opt_ LPSECURITY_ATTRIBUTES lpTokenAttributes, //安全描述符 通常NULL
_In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, //新令牌模拟级别
_In_ TOKEN_TYPE TokenType, //新令牌类型
_Out_ PHANDLE phNewToken //返回新令牌句柄
); //成功返回非0 失败0

对于dwDesiredAccess,为0表示使用与现有令牌句柄相同访问权限,为MAXIMUM_ALLOWED表示使用对调用者有效的所有访问权限。在这个使用场景下ImpersonationLevel为SecurityIdentification,TokenType为TokenPrimary。TokenPrimary表示新令牌是可以在CreateProcessAsUser中使用的主令牌,允许模仿客户端服务程序创建具有客户端安全环境的进程。

需要CloseHandle关闭令牌句柄。

CreateEnvironmentBlock

获取指定用户环境变量块:

1
2
3
4
5
BOOL WINAPI CreateEnvironmentBlock(
_Out_ LPVOID* lpEnvironment, //返回环境变量块指针
_In_opt_ HANDLE hToken, //访问令牌句柄 为NULL则返回环境块仅包含系统变量
_In_ BOOL bInherit //是否继承当前进程环境变量
);

默认情况下CreateProcessAsUser的lpEnvironment参数接收ANSI字符,所以其dwCreationFlags应指定CREATE_UNICODE_ENVIRONMENT。用DestroyEnvironmentBlock释放环境变量块。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BOOL CreateUIProcess(LPCTSTR lpApplicationName, LPTSTR lpCommandLine) {
DWORD dwSessionId; // 当前登录用户的Session ID
HANDLE hProcessToken = NULL, hProcessTokenDup = NULL; // 访问令牌句柄
LPVOID lpEnvironment = NULL;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
// 获取与服务进程关联的访问令牌
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hProcessToken);
// 复制一份访问令牌句柄
DuplicateTokenEx(hProcessToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hProcessTokenDup);
CloseHandle(hProcessToken);
// 设置访问令牌句柄的Session ID为当前登录用户
dwSessionId = WTSGetActiveConsoleSessionId();
SetTokenInformation(hProcessTokenDup, TokenSessionId, &dwSessionId, sizeof(dwSessionId));
// 获取当前登录用户的环境变量块
CreateEnvironmentBlock(&lpEnvironment, hProcessTokenDup, FALSE);
// 创建进程
CreateProcessAsUser(hProcessTokenDup, lpApplicationName, lpCommandLine, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &si, &pi);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(hProcessTokenDup);
DestroyEnvironmentBlock(lpEnvironment);
return TRUE;
};

用户账户控制UAC

自动提示用户提升权限

项目属性中链接器的清单文件设置页中有UAC执行级别选项,可选值有:

枚举值 含义
requireAdministrator 程序必须以管理员权限启动,否则不运行
highestAvailable 程序以当前用户可获得的最高权限运行
asInvoker 程序以父进程相同权限运行

ShellExecuteEx以管理员权限启动

函数声明:

1
2
3
BOOL ShellExecuteEx(
_Inout_ LPSHELLEXECUTEINFO pExecInfo
);

其中参数结构包含要执行的应用程序信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _SHELLEXECUTEINFOA {
DWORD cbSize;
ULONG fMask;
HWND hwnd;
LPCSTR lpVerb; //为runas表示以管理员权限启动
LPCSTR lpFile; //指定应用程序
LPCSTR lpParameters;
LPCSTR lpDirectory;
int nShow;
HINSTANCE hInstApp;
void* lpIDList;
LPCSTR lpClass;
HKEY hkeyClass;
DWORD dwHotKey;
union {
HANDLE hIcon;
HANDLE hMonitor;
} DUMMYUNIONNAME;
HANDLE hProcess;
} SHELLEXECUTEINFOA, * LPSHELLEXECUTEINFOA;

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = TEXT("runas");
sei.lpFile = TEXT("F:\\...\\xxx.exe");
sei.nShow = SW_SHOWNORMAL;
DWORD dwStatus;
if (!ShellExecuteEx(&sei)) {
dwStatus = GetLastError();
if (dwStatus == ERROR_CANCELLED) {
//用户拒绝提权
}
else if (dwStatus == ERROR_FILE_NOT_FOUND) {
//文件未找到
};
};

Bypass UAC

绕过UAC提权提示以管理员权限运行程序,即Bypass UAC,方法很多但失效也很快,这里用COM提升名字对象技术实现。

CoCreateInstanceAsAdmin指定CMSTPLUA组件的CLSID和ICMLuaUtil接口的IID,即可返回ICMLuaUtil接口指针,用它的ShellExec方法即可。其中ICMLuaUtil接口需要自己定义,CoCreateInstanceAsAdmin要自己定义但官方MSDN已提供。

还需要将DLL注入系统信任程序中执行,如taskmgr.exe、CompMgrmtLauncher.exe和rundll32.dll,这里选择直接拿rundll32.exe运行DLL,该程序32位和64位DLL都能运行,命令格式为:

1
rundll32.exe DllName FunctionName [Arguments]

其中DllName必须DLL完整路径名,FunctionName为导出函数名称。Arguments为可选的传递给导出函数的参数。其中导出函数原型为:

1
2
3
4
5
6
VOID CALLBACK FunctionName(
HWND hwnd,
HINSTANCE hInstance,
LPSTR lpCmdLine,
int nCmdShow
);

如果需要禁用用户计算机UAC,可以把HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System下的EnableLUA键值项设置为0。

这里被运行的DLL头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#pragma once
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif
typedef interface ICMLuaUtil ICMLuaUtil;
typedef struct ICMLuaUtilVtbl {
BEGIN_INTERFACE
HRESULT(STDMETHODCALLTYPE* QueryInterface)(
__RPC__in ICMLuaUtil* This,
__RPC__in REFIID riid,
_COM_Outptr_ void** ppvObject
);
ULONG(STDMETHODCALLTYPE* AddRef)(__RPC__in ICMLuaUtil* This);
ULONG(STDMETHODCALLTYPE* Release)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method1)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method2)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method3)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method4)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method5)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method6)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* ShellExec)(
__RPC__in ICMLuaUtil* This,
_In_ LPCWSTR lpFile,
_In_opt_ LPCTSTR lpParameters,
_In_opt_ LPCTSTR lpDirectory,
_In_ ULONG fMask,
_In_ ULONG nShow
);
HRESULT(STDMETHODCALLTYPE* SetRegistryStringValue)(
__RPC__in ICMLuaUtil* This,
_In_ HKEY hKey,
_In_opt_ LPCTSTR lpSubKey,
_In_opt_ LPCTSTR lpValueName,
_In_ LPCTSTR lpValueString
);
HRESULT(STDMETHODCALLTYPE* Method9)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method10)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method11)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method12)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method13)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method14)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method15)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method16)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method17)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method18)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method19)(__RPC__in ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method20)(__RPC__in ICMLuaUtil* This);
END_INTERFACE
} *PICMLuaUtilVtbl;
interface ICMLuaUtil {
CONST_VTBL struct ICMLuaUtilVtbl* lpVtbl;
};
// 导出函数
DLL_API VOID CALLBACK PassUAC(HWND hwnd, HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow);
// 内部函数
HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, LPVOID* ppVoid);

DLL源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <Windows.h>
#include <objbase.h>
#include <tchar.h>
#include <strsafe.h>
#define DLL_EXPORT
#include "PassUAC.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
// 导出函数
VOID CALLBACK PassUAC(HWND hwnd, HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow) {
CLSID clsid;
IID iid;
ICMLuaUtil* pCMLuaUtil = NULL;
LPVOID pVoid = NULL;
// CMSTPLUA组件的CLSID:3E5FC7F9-9A51-4367-9063-A120244FBEC7
CLSIDFromString(L"{3E5FC7F9-9A51-4367-9063-A120244FBEC7}", &clsid);
// ICMLuaUtil接口的IID:6EDD6D74-C007-4E75-B76A-E5740995E24C
IIDFromString(L"{6EDD6D74-C007-4E75-B76A-E5740995E24C}", &iid);
// 初始化COM库
CoInitializeEx(NULL, 0);
// 以管理员权限创建COM对象,返回ICMLuaUtil接口的指针
CoCreateInstanceAsAdmin(NULL, clsid, iid, (LPVOID*)&pCMLuaUtil);
// 命令行参数转换为宽字节
int nCchWideChar = MultiByteToWideChar(CP_ACP, 0, lpCmdLine, -1, NULL, 0);
WCHAR* lpWideCharStr = new WCHAR[nCchWideChar];
MultiByteToWideChar(CP_ACP, 0, lpCmdLine, -1, lpWideCharStr, nCchWideChar);
// 启动程序
pCMLuaUtil->lpVtbl->ShellExec(pCMLuaUtil, lpWideCharStr, NULL, NULL, 0, SW_SHOWNORMAL);
// 清理工作
pCMLuaUtil->lpVtbl->Release(pCMLuaUtil);
delete[] lpWideCharStr;
CoUninitialize();
return;
};
// 内部函数
HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, LPVOID* ppVoid) {
WCHAR wszCLSID[50] = { 0 };
WCHAR wszDisplayName[300] = { 0 };
BIND_OPTS3 bindOpts3;
// 构造显示名称,格式:Elevation:Administrator!new:{guid}
StringFromGUID2(rclsid, wszCLSID, _countof(wszCLSID));
StringCchPrintfW(wszDisplayName, _countof(wszDisplayName), L"Elevation:Administrator!new:%s", wszCLSID);
ZeroMemory(&bindOpts3, sizeof(BIND_OPTS3));
bindOpts3.cbStruct = sizeof(BIND_OPTS3);
bindOpts3.dwClassContext = CLSCTX_LOCAL_SERVER;
bindOpts3.hwnd = hwnd;
// 根据显示名称创建名字对象,返回riid参数指定的接口指针
return CoGetObject(wszDisplayName, &bindOpts3, riid, ppVoid);
};

执行也简单,就是拼个命令行字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szRundll32Path[MAX_PATH] = TEXT("C:\\Windows\\System32\\rundll32.exe");
TCHAR szDllPath[MAX_PATH] = TEXT("F:\\Source\\Windows\\Chapter20\\PassUAC\\Debug\\PassUAC.dll");
TCHAR szExcuteFileName[MAX_PATH] = TEXT("F:\\Source\\Windows\\Chapter20\\ProcessList.exe");
TCHAR szParameters[MAX_PATH] = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_CALL: {
wsprintf(szParameters, TEXT("\"%s\" %s \"%s\""), szDllPath, TEXT("PassUAC"), szExcuteFileName);
ShellExecute(NULL, TEXT("open"), szRundll32Path, szParameters, NULL, SW_SHOW);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

用户界面特权隔离

概览

强制完整性控制MIC:使一种控制对安全对象访问的机制,是对自主访问控制的补充,在对安全对象的自主访问控制列表DACL进行访问前需要评估,MIC使用完整性级别和强制策略来评估访问。安全主体和安全对象被分配了完整性级别,完整性级别决定了他们的保护或访问级别。具有低完整性级别的主体无法写入具有中完整性级别的对象,即使该对象DACL允许写访问。完整性级别有4个:“低”、“中”、“高”和“系统”。标准用户获得“中”,提升用户获得“高”。

Windows消息子系统采用了MIC,从而实现了用户界面特权隔离UIPI。UIPI阻止一个进程向完整性级别更高的进程所属窗口发送消息,除了WM_NULL、WM_MOVE、WM_SIZE、WM_GETTEXT、WM_GETTEXTLENGTH、WM_GETTHOTKEY、WM_GETICON、WM_RENDERFORMAT、WM_DRAWCLIPBOARD、WM_CHANGECBCHAIN、WM_THEMECHANGED。启用UIPI后不能用SendMessage/PostMessage向更高权限进程窗口发送消息,函数虽会被成功调用但消息会被删除,也不能用线程挂钩附加到更高权限的进程、不能用日志钩子监控更高权限的进程、不能将DLL注入更高权限的进程等。但低完整性级别与较高完整性级别进程之间通信可用剪贴板、共享内存、IPC、Socket、COM接口和命名管道等。

实例:在启用UAC情况下,explorer.exe完整性级别是“中”。explorer.exe作为父进程启动其他进程都从该父进程继承访问令牌和完整性级别。explorer.exe向软件拖拽文件实际上是explorer.exe向该进程发送WM_DROPFILES消息,想要成功发送则explorer.exee完整性级别必须低于该进程。

ChangeWindowMessageFilterEx

修改指定窗口UIPI消息过滤器:

1
2
3
4
5
6
BOOL WINAPI ChangeWindowMessageFilterEx(
_In_ HWND hwnd, //要修改UIPI消息过滤器的窗口句柄
_In_ UINT message, //允许消息过滤器通过或阻止的消息类型
_In_ DWORD action, //要执行的操作
_Inout_opt_ PCHANGEFILTERSTRUCT pChangeFilterStruct
);

action可以是:

枚举值 含义
MSGFLT_ALLOW 允许消息通过。hwnd窗口能接收message指定的消息,即使来自较低权限进程
MSGFLT_DISALLOW 当消息来自较低权限进程,阻止要传递到hwnd窗口的message指定的消息
MSGFLT_RESET 将hwnd指定窗口的UIPI消息过滤器重置为默认值

pChangeFilterStruct用于获取函数调用的扩展结果信息:

1
2
3
4
typedef struct tagCHANGEFILTERSTRUCT {
DWORD cbSize; //结构大小 单位字节
DWORD ExtStatus; //函数调用的扩展结果信息
} CHANGEFILTERSTRUCT, * PCHANGEFILTERSTRUCT;

ChangeWindowMessageFilter

修改整个进程UIPI消息过滤器:

1
2
3
4
BOOL WINAPI ChangeWindowMessageFilter(
_In_ UINT message,
_In_ DWORD dwFlag //添加MSGFLT_ADD 删除MSGFLT_REMOVE
);

窗口查找与枚举

概览

重叠窗口和弹出窗口都可以是顶级窗口,顶级窗口的父窗口为桌面窗口,一般枚举桌面窗口所有子窗口实现顶级窗口枚举。

1
2
3
4
5
6
7
hwnd = GetWindow(GetDesktopWindow(), GW_CHILD);
wihle(hwnd != NULL) {
GetWindowText(hwnd, szTitle, _countof(szTitle)); //窗口标题
GetClassName(hwnd, szClassName, _countof(szClassName)); //窗口类名
//...
hwnd = GetWindow(hwnd, GW_HWNDNEXT); //下一个子窗口
};

处理完每个顶级窗口句柄后,可继续枚举该窗口的子窗口:

1
2
3
4
5
6
hwndChild = NULL;
while (hwndChild = FindWindowEx(hwnd, hwndChild, NULL, NULL)) {
SendMessage(hwndChild, WM_GETTEXT, _countof(szTitle), (LPARAM)szTitle); //获取其他窗口的子窗口标题只能发送消息
GetClassName(hwndChild, szClassName, _countof(szClassName));
//...
};

这种方法有时不靠谱,可能会陷入死循环或引用已被销毁的窗口句柄。推荐用EnumWindows

FindWindowEx

查找指定父窗口中具有指定窗口类名和窗口标题的子窗口句柄:

1
2
3
4
5
6
HWND FindWindowEx(
_In_opt_ HWND hWndParent, //父窗口句柄 NULL表示桌面窗口作父窗口
_In_opt_ HWND hWndChildAfter, //开始搜索的子窗口句柄
_In_opt_ LPCTSTR lpszClass, //窗口类名
_In_opt_ LPCTSTR lpszWindow //窗口标题
); //成功返回窗口句柄 失败NULL

当hWndParent和hWndChildAfter同时为NULL则查找所有顶级窗口。

EnumWindows

枚举顶级窗口:

1
2
3
4
BOOL EnumWindows(
_In_ WNDENUMPROC lpEnumFunc, //回调函数
_In_ LPARAM lParam //传递给回调函数的参数
);

其中回调函数定义格式为:

1
2
3
4
BOOL CALLBACK EnumWindowsProc(
_In_ HWND hwnd, //顶级窗口句柄
_In_ LPARAM lParam //传递过来的参数
);

回调函数处理完后返回TRUE表示继续枚举,返回FALSE停止枚举。

EnumChildWindows

枚举顶级窗口中的子窗口:

1
2
3
4
5
BOOL EnumChildWindows(
_In_opt_ HWND hWndParent, //父窗口句柄 为NULL等效EnumWindows
_In_ WNDENUMPROC lpEnumFunc, //回调函数 与EnumWindows的回调函数定义格式相同
_In_ LPARAM lParam //传递给回调函数的参数
);

任务栏通知区域图标与气泡通知

Shell_NotifyIcon

在通知区域添加、修改或删除图标:

1
2
3
4
BOOL Shell_NotifyIcon(
_In_ DWORD dwMessage, //要执行的操作
_In_ PNOTIFYICONDATA lpData
);

dwMessage常见值有:

枚举值 含义
NIM_ADD 将图标添加到通知区域
NIM_MODIFY 修改通知区域图标
NIM_DELETE 从通知区域中删除图标

通过lpData的uID或guidItem字段指定图标的ID或GUID。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _NOTIFYICONDATA {
DWORD cbSize; //该结构大小
HWND hWnd; //用于接收通知区域图标的通知消息的窗口句柄
UINT uID; //通知区域图标ID
UINT uFlags; //标志哪个字段有效
UINT uCallbackMessage; //自定义消息ID
HICON hIcon; //要添加、修改或删除的图标句柄
TCHAR szTip[128]; //标准工具提示文本
DWORD dwState;
DWORD dwStateMask;
TCHAR szInfo[256]; //气球通知的文本
union {
UINT uTimeout;
UINT uVersion;
} DUMMYUNIONNAME;
TCHAR szInfoTitle[64]; //气球通知标题
DWORD dwInfoFlags;
GUID guidItem; //图标GUID 指定后覆盖uID字段
HICON hBalloonIcon;
} NOTIFYICONDATA, * PNOTIFYICONDATA;

当通知区域图标上发生鼠标事件时发送uCallbackMessage字段指定的自定义消息,当uVersion字段为0或NOTIFYICON_VERSION时,消息的wParam为通知区域图标ID,lParam为具体鼠标事件如WM_MOUSEMOVE、WM_LBUTTONUP、WM_RBUTTONUP等。

例子

如果需要实现通知区域图标修改或闪动,则创建一个计时器,计时器消息中用用Shell_NotifyIcon和NIM_MODIFY参数,hIcon设置为NULL表示不显示图标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "resource.h"
// 通知消息
#define WM_TRAYMSG (WM_APP + 100)
// 全局变量
HINSTANCE g_hInstance;
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
NOTIFYICONDATA nid = { sizeof(NOTIFYICONDATA) };
POINT pt;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 为程序设置一个图标
SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON_BIRD)));
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case ID_PROGRAM_SHOW: { // 显示 菜单项
ShowWindow(hwndDlg, SW_SHOW);
SetForegroundWindow(hwndDlg);
break;
};
case ID_PROGRAM_EXIT: { // 退出 菜单项
SendMessage(hwndDlg, WM_SYSCOMMAND, SC_CLOSE, 0);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_SYSCOMMAND: {
switch (wParam & 0xFFF0) {
case SC_MINIMIZE: {
// 添加通知区域图标
nid.hWnd = hwndDlg;
nid.uID = IDI_ICON_BIRD;
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_INFO;
nid.uCallbackMessage = WM_TRAYMSG;
nid.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON_BIRD));
StringCchCopy(nid.szTip, _countof(nid.szTip), TEXT("NotifyIcon程序\n作者:老王"));
StringCchCopy(nid.szInfoTitle, _countof(nid.szInfoTitle), TEXT("气球通知的标题"));
StringCchCopy(nid.szInfo, _countof(nid.szInfo), TEXT("气球通知的文本1\n气球通知的文本2"));
Shell_NotifyIcon(NIM_ADD, &nid);
// 隐藏窗口
ShowWindow(hwndDlg, SW_HIDE);
return TRUE;
};
case SC_CLOSE: {
// 删除通知区域图标
nid.hWnd = hwndDlg;
nid.uID = IDI_ICON_BIRD;
Shell_NotifyIcon(NIM_DELETE, &nid);
SendMessage(hwndDlg, WM_CLOSE, 0, 0);
return TRUE;
};
};
return FALSE;
};
case WM_TRAYMSG: {
switch (lParam) {
case WM_LBUTTONUP: { // 鼠标左键点击
// 显示窗口
ShowWindow(hwndDlg, SW_SHOW);
SetForegroundWindow(hwndDlg);
break;
};
case WM_RBUTTONUP: { // 鼠标右键点击
// 弹出快捷菜单
GetCursorPos(&pt);
TrackPopupMenu(GetSubMenu(LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU_POPUP)), 0), TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hwndDlg, NULL);
break;
};
};
return TRUE;
};
};
return FALSE;
};