windows服务守护界面程序

编写windows服务与普通控制台程序相比,入口不同,需要重写某些函数。基本套路都差不多,不过我按照这边的例子写完,服务一直卡在正在启动。这里是在外网找到的一个简单的服务例子,下面程序就是在这个基础上编写的。

服务程序写起来比较简单,不过一般大家编写服务都是为了作为守护程序。当主程序挂了后再给它带起来,或在开机时带起主程序(注意: 注销不是重启,注销后服务保持注销前状态,因为注销不影响session0)。

然而大部分的主程序都是界面程序,直接用ShellExecuteA等windows接口启动界面程序,在任务管理器中能看到进程,但并没有界面。

原理:

用户登录后,会创建多个会话。所有会话都有多个窗口、桌面、工作站。但这些会话中的所有工作中同时仅有一个交互工作站(交互工作中可切换),也可以叫做winsta0,winsta0的有三个标准的桌面: 登录桌面,默认界面(交互桌面)、disconnect,这里是介绍

WinSta0 是唯一的一个可以显示用户界面和接收用户输入的窗口站。其他所有的窗口站都是非交互的,也就说它们不能显示用户界面,也不能接收用户输入。

而我们的服务一般都处于其他session下(原因下面介绍)。在服务进程中启动我们的主程序,那么主程序也会放在相同windo station中。但是当前的用户交互界面是在session y下的某一工作站中,因此只创建出了主程序进程,而没有界面。

那么在session x中如何创建出界面程序:

这里提一下:以跨时代意义的Vista系统为分界线,之前的服务启动后会由操作系统分配到合适的session和station中。而vista及之后系统,session0作为一个没有交互的会话,存储着windows服务以及其他不需要与用户交互的程序。

vista之前系统目前存在极少,在最后的链接1 中也有解决方案,这里不详谈。

vista之后,由于windows单独出一个session0,就是为了分隔出不需要交互的程序。所以需要通过session0通知session x,然后开启界面程序.

 

主要函数代码如下。

//层级:session/station(winsta0)/desktop
//服务属于session0,没有名为winsta0的station,也就没有其下的交互界面
//需要通过session0通知当前交互界面的session,开启界面程序
DWORD _stdcall LaunchAppIntoDifferentSession(LPTSTR lpFileName, LPTSTR lpCommand)
{
	DWORD dwRet = 0;
	HANDLE hUserToken = NULL;
	HANDLE hUserTokenDup = NULL;
	HANDLE hPToken = NULL;

	// Log the client on to the local computer.
	DWORD dwSessionId;
	dwSessionId = WTSGetActiveConsoleSessionId();
	std::string sessionLogStr = std::string("LaunchAppIntoDifferentSession: Active console session id: ") + std::to_string(dwSessionId);
	writeLog(sessionLogStr);

	do {
		WTSQueryUserToken(dwSessionId, &hUserToken);

		DWORD dwCreationFlags;
		dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

		STARTUPINFO si;
		PROCESS_INFORMATION pi;
		ZeroMemory(&si, sizeof(STARTUPINFO));
		si.cb = sizeof(STARTUPINFO);
		si.lpDesktop = "winsta0\\default";
		//si.wShowWindow = SW_SHOW;
		//si.dwFlags = STARTF_USESHOWWINDOW /*|STARTF_USESTDHANDLES*/;
		ZeroMemory(&pi, sizeof(pi));
		TOKEN_PRIVILEGES tp;
		LUID luid;

		if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
			| TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID
			| TOKEN_READ | TOKEN_WRITE, &hPToken)) {
			dwRet = GetLastError();
			break;
		}

		if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
			dwRet = GetLastError();
			break;
		}

		tp.PrivilegeCount = 1;
		tp.Privileges[0].Luid = luid;
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

		if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hUserTokenDup)) {
			dwRet = GetLastError();
			break;
		}

		//Adjust Token privilege
		if (!SetTokenInformation(hUserTokenDup, TokenSessionId, (void*)& dwSessionId, sizeof(DWORD))) {
			dwRet = GetLastError();
			break;
		}

		if (!AdjustTokenPrivileges(hUserTokenDup, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, NULL)) {
			dwRet = GetLastError();
			break;
		}

		LPVOID pEnv = NULL;
		if (CreateEnvironmentBlock(&pEnv, hUserTokenDup, TRUE)) {
			dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
		}
		else
			pEnv = NULL;


		// Launch the process in the client's logon session.
		if (!CreateProcessAsUser(hUserTokenDup,    // client's access token
			lpFileName,        // file to execute
			lpCommand,        // command line
			NULL,            // pointer to process SECURITY_ATTRIBUTES
			NULL,            // pointer to thread SECURITY_ATTRIBUTES
			FALSE,            // handles are not inheritable
			dwCreationFlags,// creation flags
			pEnv,          // pointer to new environment block
			NULL,          // name of current directory
			&si,            // pointer to STARTUPINFO structure
			&pi            // receives information about new process
		)) {
			dwRet = GetLastError();
			break;
		}

		if (pEnv){
			DestroyEnvironmentBlock(pEnv);
		}
	} while (0);

	//Perform All the Close Handles task
	if (NULL != hUserToken) {
		CloseHandle(hUserToken);
	}

	if (NULL != hUserTokenDup) {
		CloseHandle(hUserTokenDup);
	}

	if (NULL != hPToken) {
		CloseHandle(hPToken);
	}

	return dwRet;
}

通过这样的方式确实可以在服务中启动界面程序了.

这样的作法有个问题,当你使用远程桌面连接时,WTSGetActiveConsoleSessionId返回的是你登录进入的session,而非显示界面的session.

解决:

使用函数WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfo, &sessionInfoCount);遍历所有session。当获得active的session时,

通过WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, sessionInfo[i].SessionId, WTSUserName, &pBuffer, &dwBufferLen);得到session的userName。结果如下:

远程机器:

本机:

可以看出来,当你处于远程连接状态时,WTSGetActiveConsoleSessionId返回的sessionID是1,而真正的交互界面session为2。而且windows把session0的winstationName设置为Services,把当前登录的用户的winstationName设置为了Console。

因此可以通过遍历session,判断session的State,当其等于WTSActive时,表明是交互界面。

 

 

初次接触windows服务的人员可能不清楚后续要做的事:

1、打开cmd,sc create XXX(服务名) binPath= "c:/test/xxxx.exe"            binPath后不能有空格,=后需要有空格。创建成功后,打开任务管理器可以在服务中看到改服务

2、sc start XXX;          开启服务,如果需要开机自启,可以右键打开服务,找到服务右键属性,设置启动类型为自动。

 sc stop XXX;关闭服务

sc delete XXX;删除服务

 

 

完整代码在此处

参考链接1:Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框

参考链接2:在任意的远程桌面的session中运行指定的程序

参考链接3:windows编程的偏门概念: 会话(Session), 窗口站(Window Station), 桌面

参考链接4:windows session机制深入解析

参考链接5:一些关于windows station的介绍

 

 

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Windows守护进程(Windows Service)是在Windows操作系统上运行的后台进程,可以在系统启动时自动启动,并且在后台持续运行,提供某种服务或功能。与常规的应用程序不同,守护进程通常不与用户界面交互,而是在系统后台运行,无需用户登录或交互。 Windows守护进程可以执行各种任务,例如定期执行某些操作、提供网络服务、监控硬件设备等。它们可以通过Windows服务管理器进行配置和控制,可以设置为自动启动、手动启动或禁用。 开发Windows守护进程可以使用多种编程语言和开发框架,例如C++、C#、VB.NET等。在开发过程中,通常需要处理以下几个关键步骤: 1. 定义服务功能:确定守护进程需要提供的服务或功能,包括定义服务名称、描述、启动类型等。 2. 编写服务代码:使用所选的编程语言和开发框架编写守护进程的代码逻辑,包括启动、停止、暂停、恢复等操作。 3. 安装和配置服务:将守护进程安装到操作系统中,并进行必要的配置,例如设置启动类型、依赖关系等。 4. 控制和监控服务:使用Windows服务管理器或命令行工具来控制和监控守护进程的状态,例如启动、停止、暂停、恢复等操作。 需要注意的是,开发Windows守护进程需要一定的系统编程知识和理解操作系统的相关原理。在开发过程中,还需要考虑服务的稳定性、安全性和性能等方面的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值