windows7 default桌面,winlogon桌面和screensaver桌面的截屏

这个项目已经结束快两年了,由于公司需要,所以又来做个总结。

当时需要做屏幕截图,但是在winlogon桌面(ctrl+alt+del 或 登录界面 或 UAC)和screensave桌面(屏保下)截图都是黑屏,所以就引来了这个问题。

解决这个问题涉及到的东西比较多,包括session,window station,service,desktop什么的。关于他们的前世今生的问题今天我们就不讨论了,有兴趣的同胞们可以去查一查,这里有一个简单的概括http://blog.sina.com.cn/s/blog_42a88fc10100o6ez.html


总之我们记住本机windows的所有用户交互操作都在seesion0下的winsta0下的几个桌面中(default,winlogon,screensaver)。

在这里我们需要记住三件事:

1.同一时刻只能有一个桌面与用户进行交互(能接受鼠标,键盘输入),我们称之为InputDesktop

2.我们的操作(即截图)只能在InputDesktop中才能生效。

3.想在InputDesktop中进行有效操作的process需要一定的用户权限,普通进程无法进行有效操作。


基于以上三点,我们基本就能解决问题了。

首先,我们需要获得inputDesktop,这个可以使用windowsApi OpenInputDesktop()获得。(具体使用方法请查MSDN)。

注:其实最先还应该将我们的进程附到winsta0,OpenWinstation(winsta0,SetProcessWinstation(winsta0),由于我们的进程基本只能在winsta0中运行,所以一般不需这一步。


其次,是将我们的操作附到InputDesktop中,当然这个也是使用windows的API SetThreadDesktop(),这个函数会将我们当前线程切换到InputDesktop。

注意:在切换前我们应该调用 GetThreadDesktop将当前桌面保存起来,以便在特定操作(本文指截图)完成后再将线程切换回原来的desktop。


最后,我们需要给我们的进程赋予足够的权限。本文中,我们从服务继承SYSTEM权限(其他方式还未研究),主要有以下几个步骤:

a.写一个windows 服务,在服务中调用OpenProcessToken,取得服务的token。

b.调用DuplicateTokenEx复制服务的token到tokenDup。

c.然后调用SetTokenInfomation()对tokenDup进行一些其他设置。

d.最后调用CreateProcessAsUser(),以tokenDup为参数启动我们的程序,这样我们的程序就具有了SYSTEM权限了。

注:这里使用的是两个程序,我们的逻辑处理程序单独写,设为A,然后通过写一个服务B来启动程序A以便使A具有system权限。


虽然写起来没几个字,但是不得不说当时也是痛苦的调查了好几天。在此希望能给大家带来帮助,让后来者少走弯路。


为了方便理解,这个地方贴两段代码:

1.windows服务启动功能进程的代码

BOOL StartProcess(const TCHAR *appNameAndPath, const TCHAR* param, PROCESS_INFORMATION *procInfo)
{
	HANDLE hTokenThis = NULL;
	HANDLE hTokenDup = NULL;
	HANDLE hThisProcess = GetCurrentProcess();
	BOOL bResult = FALSE;
	bResult = OpenProcessToken(hThisProcess, TOKEN_ALL_ACCESS, &hTokenThis);
	if(!bResult)
	{
		return FALSE;
	}

	bResult = DuplicateTokenEx(hTokenThis, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup);
	if(!bResult)
	{
		return FALSE;
	}


	DWORD dwSessionId = WTSGetActiveConsoleSessionId();
	bResult = SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(DWORD));
	DWORD UIAccess = 1;
	SetTokenInformation(hTokenDup, TokenUIAccess, &UIAccess, sizeof(DWORD));
	if(!bResult)
	{
		return FALSE;
	}

	STARTUPINFO si;
	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.lpDesktop = TEXT("WinSta0\\default");

	LPVOID pEnv = NULL;
	DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;

	bResult = CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE);
	if(!bResult)
	{
		return FALSE;
	}    
	
	bResult = CreateProcessAsUser(hTokenDup, appNameAndPath, (LPWSTR)param, NULL, NULL, FALSE, dwCreationFlag, pEnv, NULL, &si, procInfo);
	if(!bResult)
	{
		return FALSE;
	}

	if(hTokenDup)
	{
		CloseHandle(hTokenDup);
	}	

	return TRUE;
}


二,功能进程切换desktop并截图的代码(其中GetDesktopBitmap(picNum)是截图函数,picNum没有意义,这是当初调研的时候写的,因为懒直接截图就存硬盘了,picnum是为了防止存的文件重名):

BOOL GetInputDesktop()
{
	HDESK   currentDesk = NULL;
	HDESK inputDesk = NULL;

	currentDesk = GetThreadDesktop(GetCurrentThreadId());
	if(!currentDesk)
	{
		return FALSE;
	}		

	inputDesk   =   OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);   
	if   (inputDesk   ==   NULL)   
	{
		DWORD tempError = GetLastError();
		return   FALSE;
	}

	if(!SetThreadDesktop(inputDesk))
	{
		DWORD tempError = GetLastError();
		CloseDesktop(inputDesk);
		return   FALSE;
	}

	GetDesktopBitmap(picNum);

	if(!SetThreadDesktop(currentDesk))
	{
		DWORD tempError = GetLastError();
		return   FALSE;
	}

	if(!CloseDesktop(inputDesk))
	{
		return FALSE;
	}

	return TRUE;
}


由于项目代码不能泄露,所以以上代码是当初调研的时候写的测试代码,所以可能有很多神奇的地方和随意的写法,望勿见怪。但是这两个代码肯定能用,刚写文章之前我还测试了一遍。如果大家要试验的话,拷过去修改一下应该就可以了。





你可能感兴趣的:(Windows)