第五章 自启动技术
注册表
实现开机自启动的途径和方式有很多种,其中修改注册表方式应用最为广泛。注册表相当是操作系统的数据库,记录着系统中方方面面的数据,其中也不乏直接或间接导致开机自启动的数据。本节介绍向Run注册表中添加程序路径的方式,以实现开机自启动。
实现原理
理解修改注册表实现开机自启动功能的一个重要前提就是,Windows提供了专门的开机自启动注册表。在每次开机完成后,它都会在这个注册表键下遍历键值,以获取键值中的程序路径,并创建进程启动程序。
本节介绍两种修改注册表的方式,它们的主要区别在于注册表键路径。本节介绍其中常见的两个路径,分别是:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
在编程实现上,要修改HKEY_LOCAL_MACHINE主键的注册表,这要求程序要有管理员权限。而修改HKEY_CURRENT_USER主键
的注册表,只需要用户默认权限就可以实现。
如果程序运行在64位Windows系统上,则需要注意注册表重定位的问题。在64位Windows系统中,为了兼容32位程序的正常执行,64位的Windows系统采用重定向机制。系统为关键的文件夹和关键注册表创建了两个副本,使得32位程序在64位系统上不仅能操作关键文件夹和关键注册表,还可以避免与64位程序冲突。
代码实现
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
| BOOL RegCurrentUser(char *lpFileName, char *lpValueName) { HKEY hKey; RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey) RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + lstrlen(lpszFileName))) RegCloseKey(hKey); return TRUE; }
BOOL RegLocalMachine(char *lpFileName, char *lpValueName) { HKEY hKey; RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey) RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + lstrlen(lpszFileName))) RegCloseKey(hKey); return TRUE; }
|
快速启动目录
快速启动目录是一种实现起来最为简单的,不用修改任何系统数据的自启动方法。
实现原理
Windows系统有自带的快速启动的文件夹,它是最为简单的自启动方式,只要把程序放入到这个快速启动文件夹中,系统在启动时就会自动地加载并运行相应的程序,实现开机自启动功能。
快速启动目录并不是一个固定地目录,每台计算机都不相同,但是可以使用 SHGetSpecialFolderPath 函数获取Windows系统中快速启动目录的路径,快速启动目录的CSIDL标识值为CSIDL_STARTUP。
然后,使用CopyFile函数,将想要自启动的程序复制到快速启动目录下即可。当然,为程序创建快捷方式,并把快捷方式放入到快速启动自录中,也同样可以达到开机自启动的效果。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| BOOL AutoRun_Startup(char *lpszSrcFilePath, char *lpszDestFileName) { BOOL bRet = FALSE; char szStartupPath[MAX_PATH] = {0}; char szDestFilePath[MAX_PATH] = {0}; bRet = SHGetSpecialFolderPath(NULL, szStartupPath, CSIDL_STARTUP, TRUE); printf("szStartupPath=%s\n", szStartupPath); if (FALSE == bRet) { return FALSE; } wsprintf(szDestFilePath, "%s\\%s", szStartupPath, lpszDestFileName); bRet = CopyFile(lpszSrcFilePath, szDestFilePath, FALSE); if (FALSE == bRet) { return FALSE; }
return TRUE; }
|
计划任务
Windows系统可以设置计划任务来执行一些定时任务。设置计划任务的触发条件为在用户登录时触发,执行启动指定路径程序的操作,从而实现开机自启动。
实现原理及代码
使用WindowsShell编程实现创建计划任务时,会涉及COM组件接口的调用。要注意的是,编程实现创建计划任务要求具有管理员权限。
1.初始化操作
首先,通过CoInitialize函数来初始化COM接口环境。
其次,调用CoCrateInstance函数创建任务服务对象ITaskService,并将其连接到任务服务上。
最后,从ITaskService对象中获取根任务Root Task Folder的指针对象ITaskFolder,这个指针指向的是新注册的任务。
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
| CMyTaskSchedule::CMyTaskSchedule(void) { m_lpITS = NULL; m_lpRootFolder = NULL; HRESULT hr = ::CoInitialize(NULL); if (FAILED(hr)) { ShowError("CoInitialize", hr); }
hr = ::CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (LPVOID *)(&m_lpITS)); if (FAILED(hr)) { ShowError("CoCreateInstance", hr); }
hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t()); if (FAILED(hr)) { ShowError("ITaskService::Connect", hr); }
hr = m_lpITS->GetFolder(_bstr_t("\\"), &m_lpRootFolder); if (FAILED(hr)) { ShowError("ITaskService::GetFolder", hr); } }
|
2.创建任务计划操作(重点)
首先,从ITaskService对象中创建一个任务定义对象ITaskDefinition,它用来创建任务。
然后,对任务定义对象ITaskDefinition进行设置:
设置注册信息,包括设置作者信息。
设置主体信息,包括登录类型、运行权限。
设置配置信息,包括在使用电池运行时是否停止、是否允许使用电池运行、是否手动运行、是否设置多个实例等。
设置操作信息,包括启动程序,并设置运行程序的路径和参数等。
设置触发器信息,包括用户登录时触发。
最后,使用ITaskFolder对象并利用任务定义对象ITaskDefinition的设置,注册任务计划。
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
| BOOL CMyTaskSchedule::NewTask(char *lpszTaskName, char* lpszProgramPath, char* lpszParameters, char *lpszAuthor) { if (NULL == m_lpRootFolder) { return FALSE; }
Delete(lpszTaskName);
ITaskDefinition *pTaskDefinition = NULL; HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition); if (FAILED(hr)) { ShowError("ITaskService::NewTask", hr); return FALSE; }
IRegistrationInfo *pRegInfo = NULL; CComVariant variantAuthor(NULL); variantAuthor = lpszAuthor; hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo); if (FAILED(hr)) { ShowError("ITaskDefinition::get_RegistrationInfo", hr); return FALSE; }
hr = pRegInfo->put_Author(variantAuthor.bstrVal); pRegInfo->Release();
IPrincipal *pPrincipal = NULL; hr = pTaskDefinition->get_Principal(&pPrincipal); if (FAILED(hr)) { ShowError("ITaskDefinition::get_Principla", hr); return FALSE; }
hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN); hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); pPrincipal->Release();
ITaskSetting* pSetting = NULL; hr = pTaskDefinition->get_Settings(&pSetting); if (FAILED(hr)) { ShowError("ITaskDefinition::get_Setttings", hr); return FALSE; }
hr = pSetting->put_StopIfGoingOnBatteries(VARIANT_FALSE); hr = pSetting->put_DisallowStartIfOnBatteries(VARIANT_FALSE); hr = pSetting->put_AllowDemandStart(VARIANT_TRUE); hr = pSetting->put_StartWhenAvailable(VARIANT_FLASE); hr = pSetting->put_MultipleInstance(TASK_INSTANCES_PARALLEL); pSetting->Release();
IActionCollection *pActionCollect = NULL; hr = pTaskDefinition->get_Actions(&pActionCollect); if (FAILED(hr)) { ShowError("ITaskDefinition::get_Actions", hr); return FALSE; }
IAction* pAction = NULL; hr = pActionCollect->Create(TASK_ACTION_EXEC, &pAction); pActionCollect->Release();
CComVariant variantProgramPath(NULL); CComVariant variantParameters(NULL); IExecAction *pExecAction = NULL; hr = pAction->QueryInterface(IID_IExecAction, (PVOID *)(&pExecAction)); if (FAILED(hr)) { pAction->Release(); ShowError("IAction::QueryInterface", hr); return FALSE; } pAction->Release();
variantProgramPath = lpszProgramPath; variantParameters = lpszParameters; pExecAction->put_Path(variantProgramPath); pExecAction->put_Arguments(variantPraameters.bstrVal); pExecAction->Release();
ITriggerCollection *pTriggers = NULL; hr = pTaskDefinition->get_Triggers(&pTriggers); if (FAILED(hr)) { ShowError("ITaskDefinition::get_Triggers", hr); return FALSE; }
ITrigger *pTrigger = NULL; hr = pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger); if (FAILED(hr)) { ShowError("ITriggerCollection::Create", hr); return FALSE; }
IRegisteredTask *pRegisteredTask = NULL; CComVariant variantTaskName(NULL); variantTaskName = lpszTaskName; hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal, pTaskDefinition, TASK_CREATE_OR_UPDATE, _variant_t(), _variant_t(), TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(""), &pRegisterdTask); if (FAILED(hr)) { pTaskDefinition->Release(); ShowError("ITaskFolder::RegisterTaskDefinition", hr); return FALSE; }
pTaskDefiinition->Release(); pRegisteredTask->Release();
return TRUE; }
|
3.删除计划任务操作
ITaskFolder对象存储着已经注册成功的任务计划信息,程序只需要调用DeleteTask接口函数,并将任务计划的名称传入其中,就可以删除指定名称的计划任务了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| BOOL CMyTaskSchedule::Delete(char* lpszTaskName) { if (NULL == lpszTaskName) { return FALSE; }
CComVariant variantTaskName(NULL); variantTaskName = lpszTaskName; HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0); if (FAILED(hr)) { return FALSE; }
return TRUE; }
|
系统服务
系统进程自启动是通过创建系统服务并设置服务启动类型为自动启动来实现的。
实现原理及代码
1.创建启动系统服务
首先通过OpenSCManager函数打开服务控制管理器数据库并获取数据库的句柄。
若要创建服务,则调用CreateService开始创建服务,指明创建的服务类型是SERVICE_WIN32_OWN_PROCESS系统服务,同时设置SERVICE_AUTO_START为开机自启动;若是其他操作,则调用OpenService打开服务,获取服务句柄。
接着根据服务句柄考虑以下操作,若操作是启动,则使用StartService启动服务;若操作是停止,则使用ControlService停止服务;若操作是删除,则使用DeleteService删除服务。
最后,关团服务柄和服务控制管理器数据库柄。
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
| BOOL SystemServiceOperate(char *lpszDriverPath, int iOperateType) { BOOL bRet = TRUE; char szName[MAX_PATH] = { 0 };
::lstrcpy(szName, lpszDriverPath); ::PathStripPath(szName);
SC_HANDLE shOSCM = NULL, shCS = NULL; SERVICE_STATUS ss; DWORD dwErrorCode = 0; BOOL bSuccess = FALSE; shOSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!shOSCM) { ShowError("OpenSCManager"); return FALSE; }
if (0 != iOperateType) { shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS); if (!shCS) { ShowError("OpenService"); ::CloseServiceHandle(shOSCM); shOSCM = NULL; return FALSE; } }
switch (iOperateType) { case 0: { shCS = ::CreateService(shOSCM, szName, szName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL); if (!shCS) { ShowError("CreateService"); bRet = FALSE; } break; } case 1: { if (!::StartService(shCS, 0, NULL)) { ShowError("StartService"); bRet = FALSE; } break; } case 2: { if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss)) { ShowError("ControlService"); bRet = FALSE; } break; } case 3: { if (!::DeleteService(shCS)) { ShowError("DeleteService"); bRet = FALSE; } break; } default: break; } if (shCS) { ::CloseServiceHandle(shCS); shCS = NULL; } if (shOSCM) { ::CloseServiceHandle(shOSCM); shOSCM = NULL; }
return bRet; }
|
2.系统服务程序的编写
自启动服务程序并不是普通的程序,而是要求程序创建服务入口点函数,否则,不能创建系统服务。
调用系统函数StartServiceCtrlDispatcher将程序主线程 连接到 服务控制管理程序(其中定义了服务人口点函数是ServiceMain)。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher函数。
服务程序main入口函数的代码如下所示。
1 2 3 4 5 6 7 8
| int _tmain(int argc, _TCHAR* argv[]) { SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; ::StartServiceCtrlDispatcher(stDispatchTable);
return 0; }
|
执行服务初始化任务(同时执行的多个服务有多个入口点函数),首先调用RegisterServiceCtrlHandler定义控制处理程序函数(本例中是ServiceCtrlHandle),初始化后通过SetServiceStatus设定运行状态,然后运行服务代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv) { g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);
TellSCM(SERVICE_START_PENDING, 0, 1); TellSCM(SERVICE_RUNNING, 0, 0);
while (TRUE) { Sleep(5000); DoTask(); } }
|