Windows RestartManeger重启管理器

介绍

  重启管理器API可以消除或是减少在完成安装或是更新的过程中系统需要重启的次数。软件安装或是更新过程之所以需要重启系统的原因在于一些需要更新的文件正在被运行中的程序或服务使用。而重启管理器可以关闭或重启除了关键系统服务之外的所有程序和服务。

  重启管理器DLL导出了一些可以被标准或是自定义的安装包加载的公共C接口。安装程序可以使用重启管理器注册在安装或是更新过程中将被替换的文件,查看是否有程序或服务正在使用该文件,从而找到正在被使用以至于无法对其更新的文件。重启管理器可以关闭和重启那些正在使用待更新文件的非关键进程和服务。安装程序可以利用正在被使用的文件名、进程ID(PID)、windows服务的短名(short-name),以关闭和重启对应的应用程序和服务。

  重启管理器为桌面应用程序开发而提供。使用windows installer version 4.0的安装程序自动使用重启管理器以减少系统重启次数。

  重启管理器API从windows vista和windows Server 2008开始提供。所有API由一个单独的DLL提供。

关键系统服务

判断程序是否是关键系统服务:

  关键系统服务无法通过重启管理器在不重启系统的情况下关闭和重启,更新任何被这些服务使用的文件需要系统重启。

  确定一个进程是否为关键系统服务:

  1. 使用函数RmRegisterResources注册该进程;
  2. 调用函数RmGetList 获取结构体RM_PROCESS_INFO
  3. 返回的RM_PROCESS_INFO结构体中的成员ApplicationType包含了一个RM_APP_TYPE枚举值。如果这个值被设置为RmCriticial即表示该进程为关键系统进程

  关键系统服务包括smss.execsrss.exewininit.exelogonui.exelsass.exeservices.exewinlogon.exeSystem、启动RPCSS的svchost.exe、启动Dcom/PnP的svchost.exe等。

重启管理器API使用

  重启管理器提供了一系列的API以供开发人员使用。

  下面尝试简单使用重启管理器关闭占用文件C:\\Windows\\SysWOW64\\gpapi.dll的进程,根据我在windows 10上的测试,使用RmGetList时需要指定nProcInfo参数值,也就是需要填写的RM_PROCESS_INFO结构体数量。看API资料时msdn又没说清楚,花了我好久时间,又是修改测试,又是debug,网上的一些代码也不对,++。关于nProcInfo,可以通过传入NULL值执行RmGetList,执行结束后nProcInfoNeeded参数值即为需要的结构体大小,此时的函数返回值为ERROR_MORE_DATA(0xEA),具体见MSDN。

/*
	使用重启管理器关闭指定资源的相关进程
*/

#include "windows.h"
#include "stdio.h"
#include "restartmanager.h"
#pragma comment(lib, "Rstrtmgr.lib")

LPCSTR getAppType(INT type) {

	if (1000 == type)
		return "RmCritical";

	LPCSTR appTypeArr[] = { "RmUnknownApp","RmMainWindow","RmOtherWindow","RmService","RmExplorer","RmConsole" };
	return appTypeArr[type];
}

LPCSTR getAppStatus(INT status) {
	LPCSTR appStatusArr[] = { "RmStatusUnknown", "RmStatusRunning", "RmStatusStopped", "RmStatusStoppedOther",
		"RmStatusRestarted", "RmStatusErrorOnStop", "RmStatusErrorOnRestart", "RmStatusShutdownMasked",
		"RmStatusRestartMasked" };
	int idx = 0;
	while (status) {
		status >>= 1;
		idx++;
	}
	return appStatusArr[idx];
}

void printRgAffectedAppsList(INT nProcInfo, RM_PROCESS_INFO *rgAffectedApps) {

	printf("%8s%35s%35s%20s%30s%15s%15s\n",
		"PID", "AppName", "ServiceShortName", "ApplicationType",
		"AppStatus", "TSSessionID", "RestartAble");
	// PID   AppName   ServiceName   ApplicationType   AppStatus   TSSessionID   RestartAble
	for (int i = 0; i < nProcInfo; i++) {
		printf("%8d", rgAffectedApps[i].Process.dwProcessId);	// pid
		printf("%35ws", rgAffectedApps[i].strAppName);		// appname
		printf("%35ws", rgAffectedApps[i].strServiceShortName);		// ServiceName
		printf("%20s", getAppType(rgAffectedApps[i].ApplicationType));		// ApplicationType
		printf("%30s", getAppStatus(rgAffectedApps[i].AppStatus));		// AppStatus
		printf("%15d", rgAffectedApps[i].TSSessionId);
		printf("%15s\n", rgAffectedApps[i].bRestartable ? "True" : "False");
	}
}

int main() {

	DWORD ret = -1, RMSessionHandle = -1;
	WCHAR pSessionKey[CCH_RM_SESSION_KEY + 2];

	UINT nFiles = 1, nApplications = 0, nServices = 0;
	LPCWSTR rgsFilenames[] = { L"C:\\Windows\\SysWOW64\\gpapi.dll" };	// 注册的文件资源
	PRM_UNIQUE_PROCESS rgsApplications = NULL;
	LPCWSTR *rgsServiceNames = NULL;

	UINT nProcInfoNeeded = 0, nProcInfo = 0;
	PRM_PROCESS_INFO rgAffectedApps = NULL;		// 接收正在使用注册资源的进程或服务列表,
												// 分配空间过小会报错ERROR_MORE_DATA,可根据nProcInfoNeeded申请内存
	//RM_PROCESS_INFO rgAffectedApps[30];
	DWORD dwRebootReasons = -1;			// 接收一个枚举值,表示是否需要系统重启,以及重启的理由

	// 启动一个新的重启管理器会话,每个用户只能同时开启64个重启管理器会话
	if (!(ret = RmStartSession(&RMSessionHandle, 0, pSessionKey))) {
		printf("[SUCCESS] 启动重启管理器会话成功...\n");
		if (!(ret = RmRegisterResources(RMSessionHandle,
			nFiles, rgsFilenames,
			nApplications, rgsApplications,
			nServices, rgsServiceNames))) {
			printf("[SUCCESS] 注册资源成功...\n");
			ret = RmGetList(RMSessionHandle, &nProcInfoNeeded, &nProcInfo, rgAffectedApps, &dwRebootReasons);
			if (ERROR_MORE_DATA == ret) {
				ret = -1;
				printf("\t需要结构体 RM_PROCESS_INFO %d 个...\n", nProcInfoNeeded);
				rgAffectedApps = new RM_PROCESS_INFO[nProcInfoNeeded + 1];
				
				nProcInfo = nProcInfoNeeded;	// 重点,这里需要注意一下,要指定接收的结构体数量
				memset(rgAffectedApps, 0, sizeof(rgAffectedApps));
				
				if (!(ret = RmGetList(RMSessionHandle,
					&nProcInfoNeeded, &nProcInfo,
					rgAffectedApps, &dwRebootReasons))) {
					printf("[SUCCESS] 获取受影响进程(服务)列表成功...\n\t列表如下:\n");
					printRgAffectedAppsList(nProcInfo, rgAffectedApps);
					ret = -1;
					if (!(ret = RmShutdown(RMSessionHandle, 0, NULL))) {
						printf("[SUCCESS] 调用RmShutDown关闭占用注册资源的进程(服务)成功...\n");
					}
					else
						printf("[FAILED] RmShutDown调用失败 %d!!!\n", ret);
				}
				else
					printf("[FAILED] 获取受影响进程(服务)列表失败 %d!!!\n", ret);
				free(rgAffectedApps);
				rgAffectedApps = NULL;
			}
		}
		else
			printf("[FAILED] 注册资源失败 %d!!!\n", ret);
		RmEndSession(RMSessionHandle);
	}
	else
		printf("[FAILED] 启动重启管理器会话失败 %d!!!\n", ret);
	return 0;
}

MoveFileEx

  但是如果在安装或是更新的时候,重启管理器无法关闭或是重启服务(进程),必须要系统重启,这个时候可以使用MoveFileEx API,相比于API MoveFile,多了一个参数dwFlag

  使用MOVEFILE_DELAY_UNTIL_REBOOT参数,可以在系统重启时将文件进行替换,此时无任何进程占用目标。

  原理是通过在注册表键值HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ Control\Session Manager\PendingFileRenameOperations下创建一定格式的字符串,以指定文件是被删除还是替换。

  有两种形式:

  • szDstFile\0\0
  • szSrcFile\0szDstFile\0

  其中的\0\0表示两个0值"00 00",前者表示文件删除操作(可认为是重命名为NULL);后者则是文件替换操作,如果MoveFileEx还指定了MOVEFILE_REPLACE_EXISTING标志,在目标文件前还会加上一个感叹号前缀('!')。

参考:

https://docs.microsoft.com/en-us/windows/win32/api/_rstmgr/

posted @ 2020-08-04 23:18  Bl0od  阅读(869)  评论(0编辑  收藏  举报