关于 WPF 项目在 Windows 中权限问题的新思考
UAC 权限机制
何为 UAC
用户帐户控制(User Account Control,简写作 UAC) 是微软公司在其 Windows Vista 及更高版本操作系统中采用的一种控制机制。其原理是通知用户是否对应用程序使用硬盘驱动器和系统文件授权,以达到帮助阻止恶意程序(有时也称为 “恶意软件”)损坏系统的效果。
UAC 的表现
相信大多数人都见过这个窗口,也就是当一个新的 Win10 系统在一开始安装软件时总能见到的一个窗口。
它表示即将运行的程序在向当前 Windows 登录账户申请管理员权限,一旦拥有管理员权限,程序就可以做一些更深入的操作,往往涉及到磁盘的读写、注册表的访问、剪贴板的使用等等
WPF 下对 UAC 不兼容的地方
Drop 事件
不兼容表现
当 A 程序和 B 程序的权限层级不在同一层时,是无法直接从其中一个程序拖动项目到另一个程序的
因为 Drop 事件是通过处理 Window 系统的消息流抓取拖动消息来处理拖动事件的,而(打个比方)不同权限层级的消息流不是同一个流,自然 A 程序就捕获不到 B 程序中的拖动消息
临床表现
: 无法从桌面拖动文件到以管理员权限运行的 WPF 程序中(桌面是由资源管理器(explorer.exe)提供的),因为资源管理器始终以用户权限(User Access)运行
传统解决办法
让用户界面和后台脚本权限分离,以用户权限运行界面,后台脚本则以管理员权限运行
如何实现?百度上有很多教程,可以自行搜索
系统访问
不兼容表现
非管理员权限运行的程序不能:
- 访问注册表 (这意味着你不能使用自定义文件格式、不能开机自启动、不能添加软件安装信息…)
- 向 C 盘写入文件 (这意味着你不能创建开始菜单捷径、不能在 AppData 文件夹中写入数据、不能固定到任务栏…)
综上所述,如果你的程序没有管理员权限,那基本就是废的了
传统解决办法
强制使用管理员权限启动;或者在运行过程中通过 WinAPI 请求用户给予管理员权限
新思路
想法由来
一次偶然的偶然,我在使用 System.Diagnostics.Process.Start() 方法启动一个外部程序时,惊奇地发现,即使那个外部程序要求使用管理员权限启动,且我的程序是在用户权限下运行,任然可以启动那个外部程序(登录账户是管理员账户)
于是,一个大胆的念头在我脑海中诞生了:
为什么不把所有需要管理员权限的代码写在一个外部程序中呢
实现
- 新建一个 C# 控制台程序,在项目上右键–> 属性–> 应用程序–> 输出类型–> 选择
Windows 应用程序
(这一步保证调用这个程序时不会弹出命令行的窗口) - 将需要管理员权限才能执行的代码写在这个项目中,然后在主程序中添加此项目的引用 (这样这个项目就会生成在主程序目录下)
- 接着在主程序需要执行那串代码的地方写:
System.Diagnostics.Process.Start($"{Environment.CurrentDirectory}\\引用项目名称.exe", "参数")
- 在引用项目中添加参数处理 例如:
static void Main(string[] args)
{
string filePath = "";
if ((args != null) && (args.Length > 0))
{
for (int i = 0; i < args.Length; i++)
{
// 对于路径中间带空格的会自动分割成多个参数传入
filePath += " " + args[i];
}
filePath.Trim(); //去掉首尾的空格
if (filePath.Contains("-r")) //如果参数中包含 -r 则执行以下代码
{
System.Threading.Thread.Sleep(5 * 1000);
System.Diagnostics.Process.Start($"{Environment.CurrentDirectory}\\abc.exe");
}
}
}
- 在项目上右键–> 属性–> 安全性–> 勾选后又取消勾选
启用ClickOnce安全设置
- 项目中 Properties 下新增了一个 app.manifest 文件,双击打开
- 找到如下代码段:
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。n
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则删除此
元素。
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
- 把
requestedExecutionLevel
节点的level
属性修改为highestAvailable
- 大功告成
- 此时,你可以发现,这个外部程序的图标在资源管理器中已经带上了一个小盾牌的标志