今天就来聊聊我捣鼓CreateProcessAsUser
这个东西的经历。这玩意儿一开始真把我搞得有点头大,但弄明白也就那么回事儿。
起因是这样的,那会儿我手头在做一个系统服务类的程序。这种程序,一般都是在后台默默运行的,用的是系统账户,比如SYSTEM什么的。问题来,有时候需要在用户登录桌面后,由这个服务启动一个程序给用户看,比如说弹个提示窗口,或者运行一个需要用户交互的工具。
一开始我想得简单,直接用CreateProcess
不就行?试一下,立马傻眼。服务跑的地方跟咱们能看到的桌面,它不是一个“频道”里的,尤其在比较新的Windows系统上,搞个什么Session 0隔离。你直接在服务里CreateProcess
,那程序要么起不来,要么起来也看不见,因为它压根没在当前用户能交互的那个桌面上。
这时候就得另寻出路。 查些资料,翻些文档,CreateProcessAsUser
这个函数就冒出来。看名字大概能猜到,就是“以某个用户的身份创建进程”。这不正好对上我的需求嘛
实践过程中的折腾
知道用啥,下一步就是怎么用。这CreateProcessAsUser
用起来比CreateProcess
要麻烦不少。
- 第一步,得找到目标用户。 就是当前登录到桌面的那个用户。服务本身可不知道现在是谁登录,得想办法获取到这个用户的“身份凭证”,也就是所谓的“令牌”(Token)。当时我用的方法大概是通过查活动会话(Active Session)来找到用户,然后用一些API(具体哪个记不太清,好像跟WTS开头的函数有关)去拿到这个用户的令牌。
- 第二步,令牌还不能直接用。 直接拿到的令牌(主令牌)好像有些限制,不能直接用来创建进程。需要把它复制一份,搞个“副本令牌”(模拟令牌或者主令牌副本都行,看具体需求)。当时我就卡在这儿,复制令牌有好几个参数,试好几次才搞对,需要有足够的权限才能启动进程。
- 第三步,准备启动环境。 跟
CreateProcess
类似,也得准备一堆启动参数,比如要运行的程序路径、命令行参数什么的。但CreateProcessAsUser
有个特别的地方,你得明确告诉它,这个新进程要在哪个“窗口站”(Window Station)和“桌面”(Desktop)上运行。默认的服务环境跟用户的交互桌面环境不一样,这里得指定成用户当前正在用的那个,通常是"WinSta0\Default"。这一步也挺关键的, 要是没弄对,程序可能在后台启动,但用户界面就是出不来。 - 一步,调用函数。 把准备好的副本令牌、程序路径、启动参数、环境信息一股脑儿塞给
CreateProcessAsUser
。如果前面都没错,运气好的话,“Duang”一下,程序就以目标用户的身份,在他/她的桌面上跑起来。
我记得最清楚的一次折腾, 是程序确实启动,任务管理器里也能看到进程,但桌面上死活不见窗口。查半天,抓耳挠腮的,才定位到是那个“窗口站”和“桌面”指定得不对。改之后,窗口立马就出来。那时候真是松一口气,感觉跟打通任督二脉似的。
还有一次是权限问题,复制令牌的时候没搞对,导致启动的程序权限不足,运行一会儿就自己崩。又是一顿排查,调整复制令牌时的权限参数才解决。
总结一下
CreateProcessAsUser
这东西确实是解决“服务程序想在用户桌面启动进程”这类问题的正规军。虽然过程比直接CreateProcess
要绕一些,需要处理用户令牌、会话环境这些东西,但一旦你把流程理顺,实际用起来还是挺可靠的。
对我来说,搞定这个过程,也算是填补Windows底层机制认知上的一个坑。以后再遇到类似场景,心里就有底多。分享出来,希望能给可能遇到同样问题的朋友一点小小的参考。