首先这个问题我目前找到解决方案了. 这里主要是希望Quicker能从官方上同时增加这个解决方案
这个我刚才搜了一下. 遇到的人还不少
使用管理员启动的quicker,通过它启动的其他软件里使用SetParent会有异常 - Quicker
这个是我原来的提问的
表现就是有些软件通过Quicker打开. 界面会错乱
这个根本原因是UIAccess有继承. 只要是用Quicker启动的软件. 无论是先用Quicker启动A A再启动B B再启动C C在启动D 都会影响最终的程序受UIAccess. 所以Quicker本身做什么操作目前无法解决这个问题
Quicker启动的都会有UIAccess.如下图

这个问题解决方法有两种. 一个是把Quicker移除C盘. 但是这个副作用也大. 更新了又会回到C盘
且移出C盘权限不够了,url也会失效. 这样会导致网页壁纸不能用url链接设置. 原来的自动启动也会失效. 副作用大
我这边最终的解决问题是两个:
1.如果是本身自己的软件可以修改代码. 软件可以检测自己是否在UIAccess下. 如果在UIAccess下面. 就重新把自己挂在explorer上
挂在explorer启动应该跟手动启动程序一样, 不会有什么问题.
2.另外做个c#独立exe启动器程序. 这个程序做中间桥梁. 检测如果自己在UIAccess下. 然后自己重新启动把自己挂在explorer上.
这样这个exe启动器再启动对应的全部程序. 就可以让后面的程序都会在explorer上. 避免受到UIAcess影响
(当然还有第三个解决方案我没有试. 就是把程序加入到任务计划里.直接用任务计划启动. 因为任务计划本身就是系统的. 启动东西不会出问题. 但是我觉得这个方案太复杂了)
不过此问题之前和Ai讨论过. 如果本身Explorer进程不存在. 那么会有运行失败的风险. 不过我觉得Explorer一般很少会不存在,因为都是普通用户.
关了Explorer他们也无法操作桌面了
我觉得崔大可以尝试看看能否按照方案2来做一个什么选项或者什么. 能让启动的程序剥离UIAccess, 从Explorer启动. 这样可以使很多软件界面变得正常
下面是AI的详解:
问题背景: 当 Quicker 运行在 UIAccess 模式下时,通过 Quicker 启动的子进程(如某些重度依赖窗口管理的 IDE、编辑器)会继承该特权或被系统判定为“辅助功能链路”。对于某些频繁操作 TopMost、Activate 和多层级窗口的程序,会触发 Windows 的前台保护机制(Input Suppression)。
具体症状:
子进程窗口显示正常,层级正常。
输入失效: 鼠标无法点击窗口内部元素(因为被系统层拦截),但系统级快捷键(如 Alt+F4)依然有效。
根因: UIAccess 改变了 Windows 的前台信任规则。当“UIAccess 链路 + 频繁抢夺前台 + TopMost”同时出现时,Windows 会限制该进程的输入分发。
目前用户的临时解决方案: 开发者必须在子进程中主动检测权限,并利用 ShellExecute 或 Token 转换,强制通过 Explorer.exe(普通 Medium 权限)重开程序来剥离 UIAccess 标识。
对 Quicker 的建议: 希望 Quicker 官方能在“启动参数”或“运行操作”中增加一个选项:“通过 Explorer 启动(剥离 UIAccess 继承)”。
实现逻辑: 调用 IShellDispatch2::ShellExecute 指向 Explorer.exe 来启动目标程序,或者利用 CreateProcessWithTokenW 获取 Explorer 的 Token。
意义: 这样可以避免 Quicker 的高级权限“污染”子进程,解决这类工具软件在 UIAccess 模式下的兼容性顽疾。
然后提供一下我的中转程序的c#代码介绍. 可以供老大参考:
# UIAccess 剥离/自检逻辑分析(编辑器启动)
## 结论概览(这个项目“怎么做到剥离 UIAccess”)
这个项目的做法并不是“获取 UIAccess”,而是**在启动时检测当前进程是否意外带有 UIAccess**,如果是,则**立刻用 Explorer(桌面壳进程)的普通用户令牌重新启动自身**,从而把进程拉回到“正常桌面用户态”(即无 UIAccess / 普通 token)。
也就是说:
- **检测**:通过 `GetTokenInformation(TokenUIAccess)` 判断当前进程 token 是否包含 UIAccess。
- **剥离**:若检测为 UIAccess,则
- 找到同 Session 的 `explorer.exe`
- `OpenProcessToken` 拿到 explorer token
- `DuplicateTokenEx` 复制成 Primary Token
- `CreateProcessWithTokenW` 用该 Primary Token 启动本程序
- 当前实例直接 `Shutdown/Exit`
该方案的关键点:
- UIAccess 属性属于 token 侧能力,**不能在同一进程内“关掉 UIAccess”**;因此只能通过**换 token 重启进程**来实现“剥离”。
- 选择 Explorer token 的原因:Explorer 代表当前交互桌面用户(同会话),属于“正常、非 UIAccess”的典型用户 token 来源。
---
## 入口位置(权威逻辑在哪里)
- 文件:`App.xaml.cs`
- 方法:`Application_Startup(...)`
- 关键调用:
```csharp
// 启动自检:若当前进程被以 UIAccess 启动(例如第三方工具导致),则用 Explorer 用户令牌重启自身,恢复到正常桌面用户态。
if (SelfRelaunchAsExplorerUser.RelaunchIfUiAccess(e?.Args))
{
Shutdown();
return;
}
```
说明:只要 `RelaunchIfUiAccess` 返回 `true`,就表示已完成“用 Explorer 用户态拉起新实例”的动作,当前实例应立即退出,避免双实例并行。
---
## 1) UIAccess 检测:IsCurrentProcessUiAccess
位置:`App.xaml.cs` -> `SelfRelaunchAsExplorerUser.IsCurrentProcessUiAccess()`
```csharp
private static bool IsCurrentProcessUiAccess()
{
IntPtr hToken = IntPtr.Zero;
try
{
if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_QUERY, out hToken))
return false;
int uiAccess = 0;
int retLen = 0;
bool ok = GetTokenInformation(
hToken,
TOKEN_INFORMATION_CLASS.TokenUIAccess,
out uiAccess,
sizeof(int),
out retLen);
if (!ok) return false;
return uiAccess != 0;
}
finally
{
if (hToken != IntPtr.Zero) CloseHandle(hToken);
}
}
```
关键点:
- `TOKEN_INFORMATION_CLASS.TokenUIAccess = 26`
- `GetTokenInformation(..., TokenUIAccess, ...)` 返回的 `uiAccess != 0` 即视为当前进程为 UIAccess token。
---
## 2) 防无限重启:--relaunch-clean
位置:`App.xaml.cs` -> `SelfRelaunchAsExplorerUser.RelaunchIfUiAccess(...)`
```csharp
private const string RelaunchFlag = "--relaunch-clean";
if (args.Any(a => string.Equals(a, RelaunchFlag, StringComparison.OrdinalIgnoreCase)))
return false;
```
含义:
- 新进程启动时会在原参数基础上追加 `--relaunch-clean`
- 如果检测到该标记,则不再触发重启(避免异常情况下进入循环重启)
---
## 3) 用 Explorer 用户 token 重启:LaunchAsExplorerUser
### 3.1 获取 Explorer token:GetExplorerUserToken
```csharp
private static IntPtr GetExplorerUserToken()
{
int mySession = Process.GetCurrentProcess().SessionId;
Process explorer = Process.GetProcessesByName("explorer")
.Where(p =>
{
try { return p.SessionId == mySession; }
catch { return false; }
})
.OrderBy(p => p.Id)
.FirstOrDefault();
if (explorer == null)
explorer = Process.GetProcessesByName("explorer").OrderBy(p => p.Id).FirstOrDefault();
if (explorer == null)
throw new Exception("explorer.exe not found.");
IntPtr hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, explorer.Id);
if (hProcess == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error(), "OpenProcess(explorer) failed.");
try
{
IntPtr hToken;
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, out hToken))
throw new Win32Exception(Marshal.GetLastWin32Error(), "OpenProcessToken(explorer) failed.");
return hToken;
}
finally
{
CloseHandle(hProcess);
}
}
```
要点:
- 优先选择**同 Session** 的 Explorer(更贴近当前交互桌面)
- 拿到的是 Explorer 的 **Process Token**(后续还要 Duplicate 成 Primary Token)
### 3.2 Duplicate 为 Primary Token + CreateProcessWithTokenW
```csharp
private static void LaunchAsExplorerUser(string cmdLine, string workDir)
{
IntPtr hUserToken = IntPtr.Zero;
IntPtr hPrimaryToken = IntPtr.Zero;
IntPtr pEnv = IntPtr.Zero;
try
{
hUserToken = GetExplorerUserToken();
if (hUserToken == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error(), "GetExplorerUserToken failed.");
if (!DuplicateTokenEx(
hUserToken,
TOKEN_ALL_ACCESS,
IntPtr.Zero,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
out hPrimaryToken))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "DuplicateTokenEx failed.");
}
if (!CreateEnvironmentBlock(out pEnv, hPrimaryToken, false))
pEnv = IntPtr.Zero;
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(typeof(STARTUPINFO));
si.lpDesktop = @"winsta0\default";
PROCESS_INFORMATION pi;
uint flags = CREATE_UNICODE_ENVIRONMENT;
bool ok = CreateProcessWithTokenW(
hPrimaryToken,
LogonFlags.WithProfile,
null,
cmdLine,
flags,
pEnv == IntPtr.Zero ? IntPtr.Zero : pEnv,
workDir,
ref si,
out pi);
if (!ok)
throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateProcessWithTokenW failed.");
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
finally
{
if (pEnv != IntPtr.Zero) DestroyEnvironmentBlock(pEnv);
if (hPrimaryToken != IntPtr.Zero) CloseHandle(hPrimaryToken);
if (hUserToken != IntPtr.Zero) CloseHandle(hUserToken);
}
}
```
关键点:
- `DuplicateTokenEx(..., TokenPrimary, out hPrimaryToken)`:`CreateProcessWithTokenW` 需要 Primary Token。
- `CreateEnvironmentBlock/DestroyEnvironmentBlock`:为新进程准备用户环境变量(失败则退化为 `null`)。
- `si.lpDesktop = "winsta0\default"`:确保进程在交互桌面启动。
---
## 4) 组装命令行(保留原参数 + 防环标记)
```csharp
var cmdLine = BuildCommandLine(new[] { exePath }.Concat(args).Concat(new[] { RelaunchFlag }).ToArray());
```
其中 `BuildCommandLine/Quote` 负责对参数做引号转义,避免路径空格导致启动失败。
---
## 相关 Win32 / PInvoke 常量与枚举(参数参考)
```csharp
private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ALL_ACCESS = 0xF01FF;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private enum TOKEN_INFORMATION_CLASS
{
TokenUIAccess = 26,
}
```
以及关键 API:
- `OpenProcess`
- `OpenProcessToken`
- `GetTokenInformation`
- `DuplicateTokenEx`
- `CreateProcessWithTokenW`
- `CreateEnvironmentBlock` / `DestroyEnvironmentBlock`
- `CloseHandle`
---
## 与 UI(MainWindow)关系
`MainWindow.xaml / MainWindow.xaml.cs` 主要是启动器 UI 与交互逻辑(NoActivate、Topmost、全局热键、鼠标/键盘 Hook 等),**并不负责 UIAccess 剥离**。
UIAccess 相关逻辑全部集中在 `App.xaml.cs` 的 `SelfRelaunchAsExplorerUser`。
---
## 风险点/注意事项(用作参数时建议写明)