From a7b020b0ffaccc0ccbf271f63602a990902f3d8b Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Thu, 30 Apr 2026 22:59:19 +0800
Subject: [PATCH] =?UTF-8?q?improve:UIA=E7=BD=AE=E9=A1=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/AppRestartHelper.cs | 7 ++
Ink Canvas/Helpers/UIAccessHelper.cs | 115 +++++++++++++++++++++++++
2 files changed, 122 insertions(+)
diff --git a/Ink Canvas/Helpers/AppRestartHelper.cs b/Ink Canvas/Helpers/AppRestartHelper.cs
index 73fbd184..f7c4157f 100644
--- a/Ink Canvas/Helpers/AppRestartHelper.cs
+++ b/Ink Canvas/Helpers/AppRestartHelper.cs
@@ -39,6 +39,13 @@ namespace Ink_Canvas.Helpers
}
else
{
+ // 当前已是管理员时,直接通过用户令牌降权启动,避免经由 explorer 中转的延迟
+ if (IsRunningAsAdmin() && UIAccessHelper.RestartAsNormalUser())
+ {
+ Application.Current.Shutdown();
+ return;
+ }
+
Process.Start("explorer.exe", "\"" + exePath + "\"");
}
diff --git a/Ink Canvas/Helpers/UIAccessHelper.cs b/Ink Canvas/Helpers/UIAccessHelper.cs
index cc0335d1..dfae8688 100644
--- a/Ink Canvas/Helpers/UIAccessHelper.cs
+++ b/Ink Canvas/Helpers/UIAccessHelper.cs
@@ -32,8 +32,14 @@ namespace Ink_Canvas.Helpers
// TOKEN_INFORMATION_CLASS
private const int TokenSessionId = 12;
+ private const int TokenElevationType = 18;
private const int TokenUIAccess = 26;
+ // TOKEN_ELEVATION_TYPE
+ private const int TokenElevationTypeDefault = 1;
+ private const int TokenElevationTypeFull = 2;
+ private const int TokenElevationTypeLimited = 3;
+
private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
private const uint TH32CS_SNAPPROCESS = 0x00000002;
@@ -277,6 +283,39 @@ namespace Ink_Canvas.Helpers
}
}
+ ///
+ /// 以普通用户权限(非提升)重启自身。
+ /// 通过获取 explorer.exe / ctfmon.exe 的非特权令牌,再用 CreateProcessWithTokenW 启动新进程,
+ /// 避免经由 explorer.exe 中转可能产生的 UAC 提示或丢失参数问题。
+ /// 成功时调用方应立即退出当前进程。
+ ///
+ /// 追加到新进程的额外命令行参数。
+ public static bool RestartAsNormalUser(string extraArgs = null)
+ {
+ try
+ {
+ if (!GetUserPrimaryToken(out IntPtr userToken))
+ {
+ LogHelper.WriteLogToFile($"UIAccess | 获取用户令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
+ return false;
+ }
+
+ try
+ {
+ return LaunchWithToken(userToken, extraArgs);
+ }
+ finally
+ {
+ CloseHandle(userToken);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"UIAccess | RestartAsNormalUser 异常: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
#endregion
#region Token Manipulation
@@ -467,6 +506,82 @@ namespace Ink_Canvas.Helpers
finally { CloseHandle(hProc); }
}
+ ///
+ /// 从 explorer.exe / ctfmon.exe 取得普通用户(非提升)令牌的主令牌副本,用于降权启动。
+ /// 仅当当前进程为管理员时才能成功。
+ ///
+ private static bool GetUserPrimaryToken(out IntPtr userToken)
+ {
+ userToken = IntPtr.Zero;
+
+ string[] candidates = { "explorer.exe", "ctfmon.exe" };
+ foreach (var name in candidates)
+ {
+ IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero) continue;
+
+ try
+ {
+ var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
+ bool more = Process32FirstW(snapshot, ref pe);
+ while (more)
+ {
+ if (string.Equals(pe.szExeFile, name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (TryDuplicateUserPrimaryToken(pe.th32ProcessID, out userToken))
+ {
+ LogHelper.WriteLogToFile($"UIAccess | 已从 {name} (PID={pe.th32ProcessID}) 取得用户令牌");
+ return true;
+ }
+ }
+ more = Process32NextW(snapshot, ref pe);
+ }
+ }
+ finally { CloseHandle(snapshot); }
+ }
+
+ return false;
+ }
+
+ private static bool TryDuplicateUserPrimaryToken(uint pid, out IntPtr dupToken)
+ {
+ dupToken = IntPtr.Zero;
+
+ IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
+ if (hProc == IntPtr.Zero) return false;
+
+ try
+ {
+ if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
+ return false;
+
+ try
+ {
+ // 仅接受非提升令牌(否则降权失败)
+ IntPtr elevBuf = Marshal.AllocHGlobal(sizeof(int));
+ try
+ {
+ if (!GetTokenInformation(hToken, TokenElevationType, elevBuf, sizeof(int), out _))
+ return false;
+ int elev = Marshal.ReadInt32(elevBuf);
+ if (elev == TokenElevationTypeFull)
+ return false; // 该进程是提升令牌,跳过
+ }
+ finally { Marshal.FreeHGlobal(elevBuf); }
+
+ return DuplicateTokenEx(
+ hToken,
+ TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
+ IntPtr.Zero,
+ SecurityAnonymous,
+ TokenPrimary,
+ out dupToken);
+ }
+ finally { CloseHandle(hToken); }
+ }
+ finally { CloseHandle(hProc); }
+ }
+
#endregion
#region Process Launch