|
18 | 18 | using Windows.Win32; |
19 | 19 | using Windows.Win32.Foundation; |
20 | 20 | using Windows.Win32.Graphics.Dwm; |
| 21 | +using Windows.Win32.Security; |
| 22 | +using Windows.Win32.System.Threading; |
21 | 23 | using Windows.Win32.UI.Input.KeyboardAndMouse; |
22 | 24 | using Windows.Win32.UI.Shell.Common; |
23 | 25 | using Windows.Win32.UI.WindowsAndMessaging; |
@@ -802,6 +804,140 @@ public static bool IsAdministrator() |
802 | 804 | return principal.IsInRole(WindowsBuiltInRole.Administrator); |
803 | 805 | } |
804 | 806 |
|
| 807 | + /// <summary> |
| 808 | + /// Inspired by <see href="https://github.com/jay/RunAsDesktopUser"> |
| 809 | + /// Document: <see href="https://learn.microsoft.com/en-us/archive/blogs/aaron_margosis/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app"> |
| 810 | + /// </summary> |
| 811 | + public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string currentDir, out string errorInfo) |
| 812 | + { |
| 813 | + STARTUPINFOW si = new(); |
| 814 | + PROCESS_INFORMATION pi = new(); |
| 815 | + errorInfo = string.Empty; |
| 816 | + HANDLE hShellProcess = HANDLE.Null, hShellProcessToken = HANDLE.Null, hPrimaryToken = HANDLE.Null; |
| 817 | + HWND hwnd; |
| 818 | + uint dwPID; |
| 819 | + |
| 820 | + // 1. Enable the SeIncreaseQuotaPrivilege in your current token |
| 821 | + if (!PInvoke.OpenProcessToken(PInvoke.GetCurrentProcess_SafeHandle(), TOKEN_ACCESS_MASK.TOKEN_ADJUST_PRIVILEGES, out var hProcessToken)) |
| 822 | + { |
| 823 | + errorInfo = $"OpenProcessToken failed: {Marshal.GetLastWin32Error()}"; |
| 824 | + return false; |
| 825 | + } |
| 826 | + |
| 827 | + if (!PInvoke.LookupPrivilegeValue(null, PInvoke.SE_INCREASE_QUOTA_NAME, out var luid)) |
| 828 | + { |
| 829 | + errorInfo = $"LookupPrivilegeValue failed: {Marshal.GetLastWin32Error()}"; |
| 830 | + hProcessToken.Dispose();//PInvoke.CloseHandle(hProcessToken); |
| 831 | + return false; |
| 832 | + } |
| 833 | + |
| 834 | + var tp = new TOKEN_PRIVILEGES |
| 835 | + { |
| 836 | + PrivilegeCount = 1, |
| 837 | + Privileges = new() |
| 838 | + { |
| 839 | + e0 = new LUID_AND_ATTRIBUTES |
| 840 | + { |
| 841 | + Luid = luid, |
| 842 | + Attributes = TOKEN_PRIVILEGES_ATTRIBUTES.SE_PRIVILEGE_ENABLED |
| 843 | + } |
| 844 | + } |
| 845 | + }; |
| 846 | + |
| 847 | + PInvoke.AdjustTokenPrivileges(hProcessToken, false, &tp, 0, null, null); |
| 848 | + var lastError = Marshal.GetLastWin32Error(); |
| 849 | + hProcessToken.Dispose();//PInvoke.CloseHandle(hProcessToken); |
| 850 | + |
| 851 | + if (lastError != 0) |
| 852 | + { |
| 853 | + errorInfo = $"AdjustTokenPrivileges failed: {lastError}"; |
| 854 | + return false; |
| 855 | + } |
| 856 | + |
| 857 | +retry: |
| 858 | + // 2. Get an HWND representing the desktop shell |
| 859 | + hwnd = PInvoke.GetShellWindow(); |
| 860 | + if (hwnd == HWND.Null) |
| 861 | + { |
| 862 | + errorInfo = "No desktop shell is present."; |
| 863 | + return false; |
| 864 | + } |
| 865 | + |
| 866 | + // 3. Get the Process ID (PID) of the process associated with that window |
| 867 | + _ = PInvoke.GetWindowThreadProcessId(hwnd, &dwPID); |
| 868 | + if (dwPID == 0) |
| 869 | + { |
| 870 | + errorInfo = "Unable to get PID of desktop shell."; |
| 871 | + return false; |
| 872 | + } |
| 873 | + |
| 874 | + // 4. Open that process |
| 875 | + hShellProcess = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION, false, dwPID); |
| 876 | + if (hShellProcess == IntPtr.Zero) |
| 877 | + { |
| 878 | + errorInfo = $"Can't open desktop shell process: {Marshal.GetLastWin32Error()}"; |
| 879 | + return false; |
| 880 | + } |
| 881 | + |
| 882 | + if (hwnd != PInvoke.GetShellWindow()) |
| 883 | + { |
| 884 | + PInvoke.CloseHandle(hShellProcess); |
| 885 | + goto retry; |
| 886 | + } |
| 887 | + |
| 888 | + _ = PInvoke.GetWindowThreadProcessId(hwnd, &dwPID); |
| 889 | + if (dwPID != PInvoke.GetProcessId(hShellProcess)) |
| 890 | + { |
| 891 | + PInvoke.CloseHandle(hShellProcess); |
| 892 | + goto retry; |
| 893 | + } |
| 894 | + |
| 895 | + // 5. Get the access token from that process |
| 896 | + if (!PInvoke.OpenProcessToken(hShellProcess, TOKEN_ACCESS_MASK.TOKEN_DUPLICATE, &hShellProcessToken)) |
| 897 | + { |
| 898 | + errorInfo = $"Can't get process token of desktop shell: {Marshal.GetLastWin32Error()}"; |
| 899 | + goto cleanup; |
| 900 | + } |
| 901 | + |
| 902 | + // 6. Make a primary token with that token |
| 903 | + var tokenRights = TOKEN_ACCESS_MASK.TOKEN_QUERY | TOKEN_ACCESS_MASK.TOKEN_ASSIGN_PRIMARY | |
| 904 | + TOKEN_ACCESS_MASK.TOKEN_DUPLICATE | TOKEN_ACCESS_MASK.TOKEN_ADJUST_DEFAULT | |
| 905 | + TOKEN_ACCESS_MASK.TOKEN_ADJUST_SESSIONID; |
| 906 | + if (!PInvoke.DuplicateTokenEx(hShellProcessToken, tokenRights, null, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, &hPrimaryToken)) |
| 907 | + { |
| 908 | + errorInfo = $"Can't get primary token: {Marshal.GetLastWin32Error()}"; |
| 909 | + goto cleanup; |
| 910 | + } |
| 911 | + |
| 912 | + // 7. Start the new process with that primary token |
| 913 | + fixed (char* appPtr = app) |
| 914 | + fixed (char* cmdLinePtr = cmdLine) |
| 915 | + fixed (char* currentDirPtr = currentDir) |
| 916 | + { |
| 917 | + if (!PInvoke.CreateProcessWithToken(hPrimaryToken, |
| 918 | + 0 /*CREATE_PROCESS_LOGON_FLAGS.LOGON_WITH_PROFILE*/, |
| 919 | + appPtr, |
| 920 | + cmdLinePtr, |
| 921 | + 0/*PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE*/, |
| 922 | + null, |
| 923 | + currentDirPtr, |
| 924 | + &si, |
| 925 | + &pi)) |
| 926 | + { |
| 927 | + errorInfo = $"CreateProcessWithTokenW failed: {Marshal.GetLastWin32Error()}"; |
| 928 | + goto cleanup; |
| 929 | + } |
| 930 | + } |
| 931 | + |
| 932 | + return true; |
| 933 | + |
| 934 | +cleanup: |
| 935 | + if (hShellProcessToken != HANDLE.Null) PInvoke.CloseHandle(hShellProcessToken); |
| 936 | + if (hPrimaryToken != HANDLE.Null) PInvoke.CloseHandle(hPrimaryToken); |
| 937 | + if (hShellProcess != HANDLE.Null) PInvoke.CloseHandle(hShellProcess); |
| 938 | + return false; |
| 939 | + } |
| 940 | + |
805 | 941 | #endregion |
806 | 942 | } |
807 | 943 | } |
0 commit comments