【发布时间】:2015-03-24 14:34:46
【问题描述】:
我有一个用 C# 构建的 ClickOnce 应用程序。如您所知,ClickOnce 应用程序在设计上不能以管理权限运行。但是,如果您需要执行需要提升权限的功能,您可以将另一个任意 EXE 与 ClickOnce 应用程序一起打包,并让您的应用程序根据需要调用该单独的 EXE。
当然,问题在于每次调用单独的应用程序时,它都会触发一个 UAC 提示,询问用户是否真的想以提升的权限运行。这个特定的应用程序处于一个非常封闭的环境中,我可以确保我总是希望提升权限的部分运行。但是,当呈现给普通用户时,有时 UAC 提示看起来会很吓人。
因此,为了缓解这种情况,我以一种只触发一次的方式构建了我的 EXE,然后继续在后台运行并监听 WinEvents。然后我的主进程会根据需要触发各种 WinEvent,我将它们放在 AIA_START (0xA000) 和 AIA_END (0xAFFF) 之间的范围内,因为这些是社区保留的事件。
还和我在一起吗?到目前为止,我所描述的效果很好。但是,它仍然需要用户在每次启动应用程序时按一次是到 UAC 提示。我意识到我可以做得更好。
通过创建一个触发我的辅助 EXE 的 Windows 计划任务,我只需在安装时在 UAC 提示符上单击“是”即可。之后,我就可以简单地触发计划任务,进而以提升的权限启动 EXE。这似乎也奏效了。我可以验证我的 EXE 是否以管理权限启动,而且我每次都有效地绕过了 UAC 提示,这很棒。
但是,现在我从计划任务启动 EXE 而不是直接启动它,它似乎不再监听我在 ClickOnce 应用程序中触发的 WinEvents。或者,如果它正在倾听(我猜是这样),那么有什么东西阻碍了这种交流。我不确定是什么阻碍了事情,因为看起来两个进程仍在 Windows 中的同一用户帐户下运行。
有谁知道为什么我的辅助进程在通过计划任务启动时似乎不再响应 WinEvents?我该如何解决这个问题?我意识到我可以使用不同的通信线路(在 TCP 端口上侦听、在共享文件上连续轮询等),但由于我已经编写了用于发送和侦听 WinEvents 的代码,我更愿意继续使用那个。
如果你好奇,下面是我用来监听 WinEvents 的代码的简要概述:
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
private void App_OnStartup(object sender, StartupEventArgs e)
{
var handler = new WinEventDelegate(MyHandlerMethod);
SetWinEventHook(0xA000, 0xAFFF, IntPtr.Zero, handler, 0, 0, 0x0002);
}
private void MyHandlerMethod(IntPtr hook, uint evt, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
// handle the incoming WinEvents here
}
根据要求,我正在执行以下操作来创建计划任务。我必须使用 XML 文件创建它,以便可以将 DisallowStartIfOnBatteries 属性设置为 false。所以使用下面的 XML 文件...
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2015-03-20T11:10:15</Date>
<Author>myuser</Author>
</RegistrationInfo>
<Triggers>
<TimeTrigger>
<StartBoundary>2014-01-01T00:00:00</StartBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>myuser</UserId>
<LogonType>Password</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\MySecondaryApp.exe</Command>
</Exec>
</Actions>
</Task>
...我调用schtasks.exe /Create /TN MySecondaryApp /XML task.xml /RU myuser /RP mypassword 来创建任务。此操作确实需要用户在 UAC 提示符下按 Yes,但显然 create 是一次性操作,这意味着我可以在安装过程中自己完成。从那时起,我所要做的就是打电话给schtasks.exe /Run /TN MySecondaryApp 来开始这个过程。因为已经在任务上设置了“以最高权限运行”属性,所以这绕过了对任何更多 UAC 提示的需要。
就像我说的,我已经验证了它的工作原理。以这种方式运行时似乎不起作用的是 WinEvents 侦听器。我刚刚遇到another SO question,它说在不同会话中运行的进程将无法在 WinEvents 中运行,这并没有让我对此抱有太大希望。
【问题讨论】:
-
能看到定时任务吗?
-
@MatthewFrontino - 添加了有关计划任务的信息。
-
在你的原则中试试这个:
InteractiveToken IE:“仅在用户登录时运行”单选按钮。有一个无 GUI 模式的任务可能会导致您丢失 WinMessage。 -
好吧,现在你有两个问题。谷歌“windows bypass uipi”以获得更好的方法。
-
@MatthewFrontino - 太棒了!将 LogonType 更改为 InteractiveToken(然后从我的 create 语句中删除 /RU 和 /RP 参数)就像一个魅力!如果您想提交它作为答案,我会接受。
标签: c# windows winapi clickonce interprocess