0%

UAC与Windows服务(service)开发

从Vista开始引入了UAC技术,应用程序在Windows下需要进行一些高权限操作时(访问高权限文件夹/注册表),是需要Administrator权限的,如果不是以Administrator权限运行,则对这些位置的写入会被重定向,具体的重定向策略见:http://blog.csdn.net/suixiangzhe/article/details/50503047  , 以及微软官方的文档:https://msdn.microsoft.com/en-us/library/bb756960.aspx 解决这个问题可以用比较“粗暴”的形式,那就是声明应用程序需要以Administrator权限运行,这样在每次运行应用程序时系统都会弹出对话框要求用户选择是否运行。对于VS来说,只要修改项目属性-链接器-清单文件,UAC执行级别改为"requireAdministrator"即可。如果用Qt-MinGW,则先添加rc文件,再添加一个manifest.xml,声明  ;如果是Qt-VS编译器,manifest的方式貌似不太好使了,可以在项目文件中添加:QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\'\" 。

这种粗暴的方式在绝大部分情况下是没有问题的,因为大部分Windows用户使用的账号都是管理员账号,是可以用Administrator权限来运行程序的,更不用说像我这种使用内置的Administrator账号还关闭了UAC提示的。 但是,由于场景特殊,开始有用户需要使用非管理员账号来登录Windows使用我们的应用了,在这种情况下,运行声明需要Administrator权限的时Windows会弹出来登录框要求必须用有Administrator权限的用户身份来运行,而用户是不拥有管理员账号的,这意味着应用在非管理员账号下是无法使用的。

可我们的应用必须要做一些高权限的操作,比如安装证书、写注册表、对自身进行更新(有可能安装在高权限文件夹下),那解决方案几乎只有一种了:system权限。 在Windows中,通过at命令执行的定时任务、作为服务运行的应用程序,都是以system权限运行的,system权限可以说是系统的最高权限。  所以需要将应用分拆成两个应用程序了:主程序不再要求管理员权限、负责界面和原来的大部分业务逻辑,另外新建一个服务程序、在服务程序中进行一些高权限操作、以及执行自动更新,2个应用程序可以通过IPC等方式进行通信。 创建一个Windows服务应用的资料还是比较多的,方式也分为2种:

  1. 在VS中新建一个ATL项目,选择"服务"类型即可。这种方式的优点是VS已经集成好了服务各生命周期的处理方法--简单地说就是大部分代码都写好了直接继承重写某些方法即可,但缺点代码比较冗杂,而且这种服务会依赖于微软的COM服务,有依赖的服务在启动时如果依赖服务未启动则自身也会启动失败。 开发参考文档见:http://www.cnblogs.com/hbccdf/p/3477644.html
  2. 直接用Windows API来实现服务。好消息是微软提供了示例,代码也比较简单:https://code.msdn.microsoft.com/windowsapps/CppWindowsService-cacf4948/sourcecode?fileId=21604&pathId=1207334389 。 这种方式代码简洁直观,缺点是没法直接运行(必须先安装服务,再运行服务,因为它不在"服务上下文"中所以不能直接运行--应该像ATL服务那样是有解决方案的但没有去研究)所以不太好调试,不过加个延时再用VS附加到进程上就行了不算太坑。

在服务中注意至少是有两个线程的,一个是控制服务启停的线程--我称它为“控制线程”,一个是服务自身的主线程,在开发时注意一下线程问题即可。  

还有两个要解决的问题是以system权限运行的进程是不能显示界面的,而且system权限进程直接创建的进程也是system权限的。 好在微软提供了一系列的方法来解决这两个问题,对于第一个问题,服务虽然不能显示界面但能够向活动用户的界面“发送”简单的对话框,用WTSSendMessage函数即可;而且其实也可以让服务进程跟界面进程进行通信,让界面进程来显示某些信息即可。 对于第二个问题,也提供了CreateProcessAsUser函数来以某些用户的身份启动进程。 详细的文档见:http://blog.csdn.net/zuishikonghuan/article/details/47614907

不过要注意CreateProcessAsUser是有个小坑的,用绕口令般的方式描述一下: 如果服务进程A(system身份)直接创建了进程B(也是system身份),然后进程B(system身份)用CreateProcessAsUser来尝试以用户身份启动进程是会失败的。  因为A是在服务上下文中的它可以直接用CreateProcessAsUser来创建用户身份进程,但B是以JOB的形式运行的,所以如果进程B要用CreateProcessAsUser需要在dwCreationFlags中添加CREATE_BREAKAWAY_FROM_JOB,详见:https://blogs.msdn.microsoft.com/alejacma/2012/03/09/createprocessasuser-fails-with-error-5-access-denied-when-using-jobs/