如何从系统服务启动屏幕保护程序

我有几个变种启动屏幕保护程序。 我最喜欢的是

[DllImport("user32.dll", SetLastError = false)] private static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); private void startScreensaver() { UInt32 WM_SYSCOMMAND = 0x112; IntPtr SC_SCREENSAVE = new IntPtr(0xf140); IntPtr hWnd = GetDesktopWindow(); SendMessage(hWnd, WM_SYSCOMMAND, SC_SCREENSAVE, new IntPtr(0)); } 

我的问题是,我想从系统服务启动屏幕保护程序。 如果我例如想要在会话locking后立即启动屏幕保护程序(只是为了certificate概念),我可以尝试

 protected override void OnSessionChange(SessionChangeDescription changeDescription) { base.OnSessionChange(changeDescription); if (changeDescription.Reason == SessionChangeReason.SessionLock) startScreensaver(); } 

这不起作用,我认为原因是该服务与安装

ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;

无法访问用户的会话。 我可以实现一个小程序,在用户会话中运行,由服务触发屏幕保护程序触发…但这不是一个好方法。

有什么build议么? 谢谢。

编辑:显然这个问题是关系到GetDesktopWindow(); 电话,但我不知道如何解决这个问题

更新:

根据Erics的build议,我现在迭代所有窗口站(使用OpenWindowStation),然后为所有这些迭代所有桌面(使用EnumDesktops)。 然后,我使用OpenDesktop打开桌面,并将句柄存储到桌面。 我的标准Windows安装产生到以下的windowStation列表:桌面:dskHandle

  • WinSta0:默认值:732
  • WinSta0:断开:760
  • WinSta0:Winlogon中:784
  • msswindowstation:mssrestricteddesk:0

我现在开始一个新的线程,其中我

 [DllImport("user32.dll", SetLastError = true)] static extern bool SetThreadDesktop(IntPtr hDesktop); 

然后调用上面的startScreensaver()方法。 IntPtr hWnd = GetDesktopWindow()确实返回合理的结果,但屏幕保护程序没有启动。 在里面

 [DllImport("user32.dll")] static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess); 

我使用GENERIC_ALL = 0x10000000作为dwDesiredAccess。 正如Farzin所说,我查了一下

允许服务与桌面进行交互

我不是一个win32或pInvoke亲,所以我现在完全失去了。 可以解释一下所有的东西如何协同工作? 有没有更好的build议? 我想要做的就是从系统服务调用屏幕保护程序。

好。 我用希望帮助的链接格式化了这个帖子,然后当点击Post时得到了这个:

哎呀! 您的答案无法提交,因为:•我们很抱歉,但作为垃圾邮件防范机制,新用户最多只能发布两个超链接。 获得超过10个声望发布更多的超链接。

所以,在这里你去,完全不可读的发布….

去超链接的这个消息: http : //social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/89485c95-61e5-46ea-84c7-5d8e03081c61

Erik,Farzin Zaker和OP,不要使用SERVICE_INTERACTIVE_PROCESS标志,也不要依赖交互式服务技术。 自Windows Vista以来,这种方法已经被淘汰。 [更多]:

从微软自己的话来说:

重要服务从Windows Vista开始,不能直接与用户交互。 因此,标题为“使用交互式服务”一节中提到的技术不应在新代码中使用。

所以,即使上面提到的任何方法都起作用,它们也不会成为任何“黑客”,并且可能会停止在任何新版本甚至更新Windows中工作。

你最好的选择就是通过你自己提到的“ 我可以实现一个运行在用户会话中的小程序,这个程序由服务触发屏幕保护程序 ”。

相信我,我花了无数个小时试图做你想做的( 错误的方式 ),我失败了。 以下是Microsoft如何在软件中自行完成这项工作,以及您需要如何执行此操作:

  1. 在你的系统服务中创建一个全局命名的自动重置事件,将其状态设置为非sginaled。 确保调整此事件的安全描述符,使其被“Everyone”读取和同步。 更多[这里]和[这里]和[这里]创建一个安全描述符。 如果您稍后不想处理ERROR_ACCESS_DENIED,则此步骤非常重要。

  2. 制作一个具有隐藏窗口的小型Win32 GUI程序。 在开始时,它将打开由上述服务创建的全局事件。 如果我用C ++写它,它会看起来像这样:OpenEvent(READ_CONTROL | SYNCHRONIZE,FALSE,_T(“Global \\ Whatever_name_you_use”)); 然后创建一个工作线程,只需等待这个事件通过[同步函数]中的一个WaitFor * Object API发送信号。 当然,确保工作线程处理这个小的GUI程序关闭时的情况。

  3. 全局命名自动重置事件变为信号时,从工作线程运行以下代码。 通过wParam = SC_SCREENSAVE和lParam = 0将WM_SYSCOMMAND通知发送到主GUI线程中的窗口,或者通过从主GUI线程调用DefWindowProc()API来完成。 这应该启动GUI程序正在运行的用户的当前设置的屏幕保护程序。

  4. 如果你想开始一个特定的屏幕保护程序,那么你可以简单地使用ShellExecute和你的GUI程序中的/ s参数来运行它。 (当然,在全局命名的自动重置事件发出时,从工作线程完成。)所有的屏保通常放置在“%WINDIR%\ System32”文件夹中。 他们有.scr扩展名。

  5. 好的,现在如何从系统服务中激活它。

  6. 当您需要运行屏幕保护程序时,您需要确保您的小型GUI程序正在当前活动的用户会话中运行。 活跃的部分是重要的。 这里有两个应用程序。 第一。 每当用户会话变为活动状态时(通过关闭此GUI程序的副本来停止处于活动状态的活动),您可以执行此操作,可以通过通过全局命名事件发出命令来关闭它,并且可以跟踪用户会话将通过捕获SERVICE_CONTROL_SESSIONCHANGE通知从系统服务和ServiceHandlerEx()中更改。)当需要激活屏幕保护程序并立即关闭时,也可以正确运行此GUI程序。 我会把它留给你选择哪种方法。 主要的一点是,你必须以某种方式在一个活动的用户会话中运行你的GUI程序,并使用全局命名的事件来与之通信。 (对于cource来说,你可以包含IPC的其他方式)在我的书中,全局事件最简单的表达布尔型或“是”和“否”类型的命令。)我需要立即告诉你,在另一个用户会话中的进程是这里最劳动密集的部分,文档记录不足,难以调试。 简而言之,您需要使用系统服务中的CreateProcessAsUser()API,但是困难的部分是准备调用该API。 不幸的是,关于如何调用它并没有明确的共识,网络上有一大堆的建议可能会有所不同。 为我工作的步骤如下:

    • 把你的图形用户界面程序放到一个可以访问的地方(即使是最不具有特权的用户)。 由于它是系统服务的一部分,因此您可以使用“%WINDIR%\ System32”,但确保在不再需要时将其从中删除!

    • 通过调用WTSEnumerateSessions()来获取当前的活动会话并查看WTSActive状态的会话。

    • WTSQueryUserToken()获取活动的用户会话令牌

    • DuplicateTokenEx(,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&);

    • 通过调用CreateEnvironmentBlock()创建环境字符串块

    • 通过调用LoadUserProfile()来加载用户配置文件。 您可以使用以下API收集所有必要的信息:配置文件路径的NetUserGetInfo()和获取会话用户名的WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,WTSUserName,&,&)。

    • 并通过调用ImpersonateLoggedOnUser()模拟该用户

    • 此时,在您放置GUI程序的位置调用CreateProcessAsUser()。 让我再说一遍,你必须从你刚刚模拟的用户可访问的位置运行它! 这里常见的错误是从一个像这样的位置运行它:“ C:\ Users \ SomeUserName \ AppData \ Roaming ”。 这个调用可能看起来像这样:CreateProcessAsUser(hToken2,NULL,pNonConstOrStaticBufferWithPathToGUIProgram,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,pEnvironmentBlock,NULL,&pSTARTUPINFO,&pPROCESS_INFORMATION);

    • 总是回复imporsonation:RevertToSelf();

    • WaitForInputIdle()确保您的GUI进程已经启动并到达消息泵。

    • 通过调用UnloadUserProfile(),DestroyEnvironmentBlock(),WTSFreeMemory(),CloseHandle()等进行清理。

    • 现在你可以通过调用SetEvent()来设置你的全局命名自动重置事件来通知你的GUI进程来启动屏幕保护程序。 你完成了! 您可能还希望从GUI程序启用某种反向的反馈,以确保屏幕保护程序实际上已启动,但是我将保留给您。 请再次参考IPC的[手段]了解如何做到这一点。

作为一个结论,让我说,上述方法是通过无数的论坛发帖和搜集多个网页搜集来收集的。 而且,是的,我明白这个方法有多笨重和笨重,但是,嘿,这就是Windows,不是这样:)如果你想简单的话,去OS X或iOS。 那是我最终做的…

干杯。

PS。 世界上谁在这个论坛上的格式规则? 这是最难的东西打入,并获得可读性….

去你的服务,右键点击服务,然后在LogOn选项卡中设置下面的项目为true:

允许服务与桌面进行交互

如果你想在安装上做到这一点:

 public WindowsServiceInstaller() { // This call is required by the Designer. InitializeComponent(); ServiceInstaller si = new ServiceInstaller(); si.ServiceName = "WindowsService1"; si.DisplayName = "WindowsService1"; si.StartType = ServiceStartMode.Manual; this.Installers.Add(si); ServiceProcessInstaller spi = new ServiceProcessInstaller(); spi.Account = System.ServiceProcess.ServiceAccount.LocalSystem; spi.Password = null; spi.Username = null; this.Installers.Add(spi); // Here is where we set the bit on the value in the registry. // Grab the subkey to our service RegistryKey ckey = Registry.LocalMachine.OpenSubKey( @"SYSTEM\CurrentControlSet\Services\WindowsService1", true); // Good to always do error checking! if(ckey != null) { // Ok now lets make sure the "Type" value is there, //and then do our bitwise operation on it. if(ckey.GetValue("Type") != null) { ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256)); } } } 

参考: http : //www.codeproject.com/KB/install/cswindowsservicedesktop.aspx

在安装时,可以使服务与桌面交互(将CreateService SERVICE_INTERACTIVE_PROCESS传递给CreateService )。 否则(可能有访问问题 – 我没有尝试过),你需要从Window Station和桌面功能开始 。

你需要做的是找到登录用户窗口站( EnumWindowStationsOpenWindowStation ),桌面( EnumDesktopsOpenDesktop ),创建一个线程和SetThreadDesktop ,然后最终使用GetDesktopWindow