From d59c21b3088343567d5a0022aac509a504fb6b34 Mon Sep 17 00:00:00 2001 From: AbdulRaoufMKamal Date: Thu, 20 Feb 2025 00:33:25 +0200 Subject: [PATCH 1/4] Updated StartProcessAsCurrentUser method to be able to run apps that require administrative priviliges too. --- AppWithPrivileges/App.xaml | 9 ++ AppWithPrivileges/App.xaml.cs | 14 +++ AppWithPrivileges/AppWithPrivileges.csproj | 12 +++ AppWithPrivileges/AssemblyInfo.cs | 10 ++ AppWithPrivileges/MainWindow.xaml | 15 +++ AppWithPrivileges/MainWindow.xaml.cs | 24 +++++ AppWithPrivileges/app.manifest | 79 ++++++++++++++++ DemoModernService/DemoModernService.cs | 13 ++- DemoModernService/DemoModernService.csproj | 2 +- DemoService/App.config | 6 +- DemoService/DemoService.csproj | 7 +- DemoService/packages.config | 4 + ProcessExtensions/ProcessExtensions.cs | 102 +++++++++------------ solution.sln | 6 ++ 14 files changed, 237 insertions(+), 66 deletions(-) create mode 100644 AppWithPrivileges/App.xaml create mode 100644 AppWithPrivileges/App.xaml.cs create mode 100644 AppWithPrivileges/AppWithPrivileges.csproj create mode 100644 AppWithPrivileges/AssemblyInfo.cs create mode 100644 AppWithPrivileges/MainWindow.xaml create mode 100644 AppWithPrivileges/MainWindow.xaml.cs create mode 100644 AppWithPrivileges/app.manifest create mode 100644 DemoService/packages.config diff --git a/AppWithPrivileges/App.xaml b/AppWithPrivileges/App.xaml new file mode 100644 index 0000000..7011b70 --- /dev/null +++ b/AppWithPrivileges/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/AppWithPrivileges/App.xaml.cs b/AppWithPrivileges/App.xaml.cs new file mode 100644 index 0000000..c38f72a --- /dev/null +++ b/AppWithPrivileges/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace AppWithPrivileges +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/AppWithPrivileges/AppWithPrivileges.csproj b/AppWithPrivileges/AppWithPrivileges.csproj new file mode 100644 index 0000000..ae10916 --- /dev/null +++ b/AppWithPrivileges/AppWithPrivileges.csproj @@ -0,0 +1,12 @@ + + + + WinExe + net6.0-windows + enable + enable + true + app.manifest + + + diff --git a/AppWithPrivileges/AssemblyInfo.cs b/AppWithPrivileges/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/AppWithPrivileges/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/AppWithPrivileges/MainWindow.xaml b/AppWithPrivileges/MainWindow.xaml new file mode 100644 index 0000000..48f4a00 --- /dev/null +++ b/AppWithPrivileges/MainWindow.xaml @@ -0,0 +1,15 @@ + + + + + diff --git a/AppWithPrivileges/MainWindow.xaml.cs b/AppWithPrivileges/MainWindow.xaml.cs new file mode 100644 index 0000000..8f14c53 --- /dev/null +++ b/AppWithPrivileges/MainWindow.xaml.cs @@ -0,0 +1,24 @@ +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace AppWithPrivileges +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/AppWithPrivileges/app.manifest b/AppWithPrivileges/app.manifest new file mode 100644 index 0000000..438ee0d --- /dev/null +++ b/AppWithPrivileges/app.manifest @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DemoModernService/DemoModernService.cs b/DemoModernService/DemoModernService.cs index c30d601..955c734 100644 --- a/DemoModernService/DemoModernService.cs +++ b/DemoModernService/DemoModernService.cs @@ -1,6 +1,7 @@  using Microsoft.Extensions.Hosting; using murrayju.ProcessExtensions; +using System.Diagnostics; namespace DemoModernService; @@ -8,6 +9,16 @@ internal class DemoModernService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - ProcessExtensions.StartProcessAsCurrentUser("calc.exe"); + string path = AppDomain.CurrentDomain.BaseDirectory; + int levelsUp = 5; + for (int i = 0; i < levelsUp; i++) + { + path = Directory.GetParent(path)?.FullName ?? path; + } + Debug.WriteLine("OUTPUT PATH: " + path); + string appPath = "AppWithPrivileges.exe"; + string? workDir = Path.Combine(path, "AppWithPrivileges", "bin", "Debug", "net6.0-windows"); + ProcessExtensions.StartProcessAsCurrentUser(appPath: appPath, workDir: workDir); + //ProcessExtensions.StartProcessAsCurrentUser("calc.exe"); } } \ No newline at end of file diff --git a/DemoModernService/DemoModernService.csproj b/DemoModernService/DemoModernService.csproj index fda8c1f..8d21618 100644 --- a/DemoModernService/DemoModernService.csproj +++ b/DemoModernService/DemoModernService.csproj @@ -8,7 +8,7 @@ - + diff --git a/DemoService/App.config b/DemoService/App.config index aee9adf..4bfa005 100644 --- a/DemoService/App.config +++ b/DemoService/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/DemoService/DemoService.csproj b/DemoService/DemoService.csproj index d1f0ec5..1bf7a06 100644 --- a/DemoService/DemoService.csproj +++ b/DemoService/DemoService.csproj @@ -8,10 +8,11 @@ Exe demo DemoService - v4.8.1 + v4.8 512 true true + AnyCPU @@ -38,6 +39,9 @@ ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.Configuration.Install.dll + + ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll + ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.ServiceProcess.dll @@ -68,6 +72,7 @@ Always + diff --git a/DemoService/packages.config b/DemoService/packages.config new file mode 100644 index 0000000..5e8760a --- /dev/null +++ b/DemoService/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ProcessExtensions/ProcessExtensions.cs b/ProcessExtensions/ProcessExtensions.cs index e7dcc65..79f75dc 100644 --- a/ProcessExtensions/ProcessExtensions.cs +++ b/ProcessExtensions/ProcessExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -17,11 +18,10 @@ public static class ProcessExtensions private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; - - private const uint INVALID_SESSION_ID = 0xFFFFFFFF; - private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; private const int STARTF_USESHOWWINDOW = 0x00000001; - + + private const uint TOKEN_ALL_ACCESS = 0xF01FF; + #endregion #region DllImports @@ -47,7 +47,7 @@ private static extern bool DuplicateTokenEx( IntPtr lpThreadAttributes, int TokenType, int ImpersonationLevel, - ref IntPtr DuplicateTokenHandle); + out IntPtr DuplicateTokenHandle); [DllImport("userenv.dll", SetLastError = true)] private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); @@ -76,6 +76,12 @@ private static extern int WTSEnumerateSessions( [DllImport("Wtsapi32.dll")] private static extern void WTSFreeMemory(IntPtr ppSessionInfo); + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool SetTokenInformation(IntPtr tokenHandle, int tokenInformationClass, ref int tokenInformation, int tokenInformationLength); + #endregion #region Win32 Structs @@ -171,77 +177,42 @@ private struct WTS_SESSION_INFO #endregion - // Gets the user token from the currently active session - private static bool GetSessionUserToken(ref IntPtr phUserToken) - { - var bResult = false; - var hImpersonationToken = IntPtr.Zero; - var activeSessionId = INVALID_SESSION_ID; - var pSessionInfo = IntPtr.Zero; - var sessionCount = 0; - - // Get a handle to the user access token for the current active session. - if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) - { - var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); - var current = pSessionInfo; - - for (var i = 0; i < sessionCount; i++) - { - var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO)); - current += arrayElementSize; - - if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) - { - activeSessionId = si.SessionID; - } - } - - WTSFreeMemory(pSessionInfo); - } - - // If enumerating did not work, fall back to the old method - if (activeSessionId == INVALID_SESSION_ID) - { - activeSessionId = WTSGetActiveConsoleSessionId(); - } - - if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0) - { - // Convert the impersonation token to a primary token - bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, - (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, - ref phUserToken); - - CloseHandle(hImpersonationToken); - } - - return bResult; - } - public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true) { - var hUserToken = IntPtr.Zero; + var startInfo = new STARTUPINFO(); var procInfo = new PROCESS_INFORMATION(); - var pEnv = IntPtr.Zero; int iResultOfCreateProcessAsUser; + var pEnv = IntPtr.Zero; + IntPtr currentProcessToken = IntPtr.Zero; + IntPtr duplicatedToken; + + uint interactiveSessionId = WTSGetActiveConsoleSessionId(); + startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); try { - if (!GetSessionUserToken(ref hUserToken)) + + if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ALL_ACCESS, out currentProcessToken)) { - throw new ProcessCreationException("StartProcessAsCurrentUser: GetSessionUserToken failed."); + throw new Exception("OpenProcessToken failed. Error: " + Marshal.GetLastWin32Error()); } + if (!DuplicateTokenEx(currentProcessToken, TOKEN_ALL_ACCESS, IntPtr.Zero, + (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, + (int)TOKEN_TYPE.TokenPrimary, + out duplicatedToken)) + { + throw new Exception("DuplicateTokenEx failed. Error: " + Marshal.GetLastWin32Error()); + } uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); startInfo.dwFlags = STARTF_USESHOWWINDOW; startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); startInfo.lpDesktop = "winsta0\\default"; - if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) + if (!CreateEnvironmentBlock(ref pEnv, duplicatedToken, false)) { throw new ProcessCreationException("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); } @@ -251,7 +222,18 @@ public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = nu Directory.SetCurrentDirectory(workDir); } - if (!CreateProcessAsUser(hUserToken, + int uiValue = 1; + IntPtr pUIValue = Marshal.AllocHGlobal(sizeof(int)); + Marshal.WriteInt32(pUIValue, uiValue); + + int sessionId = (int)interactiveSessionId; + if (!SetTokenInformation(duplicatedToken, 12, ref sessionId, sizeof(int))) + { + iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); + throw new ProcessCreationException("StartProcessAsCurrentUser: SetTokenInformation failed. Error Code - " + iResultOfCreateProcessAsUser); + } + + if (!CreateProcessAsUser(duplicatedToken, appPath, // Application Name cmdLine, // Command Line IntPtr.Zero, @@ -271,7 +253,7 @@ public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = nu } finally { - CloseHandle(hUserToken); + CloseHandle(currentProcessToken); if (pEnv != IntPtr.Zero) { DestroyEnvironmentBlock(pEnv); diff --git a/solution.sln b/solution.sln index 1877f69..7026a8a 100644 --- a/solution.sln +++ b/solution.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppWithPrivileges", "AppWithPrivileges\AppWithPrivileges.csproj", "{FB499E9E-A4E8-4201-9FD9-E70A0C80A9DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,6 +34,10 @@ Global {75EE3B51-5A96-473C-9E68-C36B728A6E2B}.Debug|Any CPU.Build.0 = Debug|Any CPU {75EE3B51-5A96-473C-9E68-C36B728A6E2B}.Release|Any CPU.ActiveCfg = Release|Any CPU {75EE3B51-5A96-473C-9E68-C36B728A6E2B}.Release|Any CPU.Build.0 = Release|Any CPU + {FB499E9E-A4E8-4201-9FD9-E70A0C80A9DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB499E9E-A4E8-4201-9FD9-E70A0C80A9DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB499E9E-A4E8-4201-9FD9-E70A0C80A9DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB499E9E-A4E8-4201-9FD9-E70A0C80A9DD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d77c1c45eb067d0a13244b2a1ef7fd33bbc4f6c3 Mon Sep 17 00:00:00 2001 From: AbdulRaoufMKamal Date: Thu, 20 Feb 2025 00:42:08 +0200 Subject: [PATCH 2/4] Updated Readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 07d9bc9..9f96a0a 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,5 @@ Similarly, the `DemoModernService` project uses .NET 8.0, and a build will copy For either version, CD to the bin directory and run `createService` to install and start the service. It will launch `calc.exe` as soon as it starts. After that, run `deleteService` to stop and uninstall the service. +## Major Addition +Now you can run apps that require administrative privileges using the SetTokenInformation which can be used to update the session Id in which the app runs and hence show the app UI, note that you need to set the workDir parameter to the working directory which contains the app itself. See [this stack overflow answer](https://stackoverflow.com/questions/33212984/createprocessasuser-with-elevated-privileges) From 2a886c7f861b7c65f408ac156f19ec28bdf83683 Mon Sep 17 00:00:00 2001 From: AbdulRaoufMKamal Date: Tue, 25 Mar 2025 00:09:10 +0200 Subject: [PATCH 3/4] Refactored demo and process extensions classes, added option asAdmin to allow the user to choose whether to launch the process with admin privileges or not. --- DemoModernService/DemoModernService.cs | 22 ++-- DemoService/DemoService.cs | 2 +- ProcessExtensions/ProcessExtensions.cs | 134 +++++++++++++++------ ProcessExtensions/ProcessExtensions.csproj | 5 + 4 files changed, 112 insertions(+), 51 deletions(-) diff --git a/DemoModernService/DemoModernService.cs b/DemoModernService/DemoModernService.cs index 955c734..5bad53e 100644 --- a/DemoModernService/DemoModernService.cs +++ b/DemoModernService/DemoModernService.cs @@ -9,16 +9,18 @@ internal class DemoModernService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - string path = AppDomain.CurrentDomain.BaseDirectory; - int levelsUp = 5; - for (int i = 0; i < levelsUp; i++) - { - path = Directory.GetParent(path)?.FullName ?? path; - } - Debug.WriteLine("OUTPUT PATH: " + path); string appPath = "AppWithPrivileges.exe"; - string? workDir = Path.Combine(path, "AppWithPrivileges", "bin", "Debug", "net6.0-windows"); - ProcessExtensions.StartProcessAsCurrentUser(appPath: appPath, workDir: workDir); - //ProcessExtensions.StartProcessAsCurrentUser("calc.exe"); + + string solutionDirectory = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.Parent.Parent.FullName; + string targetProjectName = Path.GetFileNameWithoutExtension(appPath); + string workDir = Path.Combine(solutionDirectory, targetProjectName, "bin", "Debug", "net6.0-windows"); + + /* NOTE YOU MUST COMMENT ONE OF THE BELOW LINES BASED ON YOUR CHOICE OF RUNNING THE SERVICE */ + + // DEBUG MODE + ProcessExtensions.LaunchProcess(appPath: appPath, workDir: workDir, asAdmin: false); + + // SERVICE MODE + ProcessExtensions.LaunchProcess(appPath: appPath, workDir: workDir, asAdmin: true); } } \ No newline at end of file diff --git a/DemoService/DemoService.cs b/DemoService/DemoService.cs index f8c9174..0478175 100644 --- a/DemoService/DemoService.cs +++ b/DemoService/DemoService.cs @@ -12,7 +12,7 @@ public DemoService() protected override void OnStart(string[] args) { - ProcessExtensions.StartProcessAsCurrentUser("calc.exe"); + ProcessExtensions.LaunchProcess("calc.exe"); } protected override void OnStop() diff --git a/ProcessExtensions/ProcessExtensions.cs b/ProcessExtensions/ProcessExtensions.cs index 79f75dc..891bdb8 100644 --- a/ProcessExtensions/ProcessExtensions.cs +++ b/ProcessExtensions/ProcessExtensions.cs @@ -1,7 +1,7 @@ -using System; -using System.Diagnostics; -using System.IO; +using System.IO; +using System; using System.Runtime.InteropServices; +using System.Security.Principal; namespace murrayju.ProcessExtensions { @@ -18,10 +18,16 @@ public static class ProcessExtensions private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; + + private const uint INVALID_SESSION_ID = 0xFFFFFFFF; + private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; private const int STARTF_USESHOWWINDOW = 0x00000001; private const uint TOKEN_ALL_ACCESS = 0xF01FF; + private static IntPtr executionToken = IntPtr.Zero; + public static uint activeSessionId = INVALID_SESSION_ID; + #endregion #region DllImports @@ -49,6 +55,13 @@ private static extern bool DuplicateTokenEx( int ImpersonationLevel, out IntPtr DuplicateTokenHandle); + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool SetTokenInformation( + IntPtr tokenHandle, + TokenInformationClass tokenInformationClass, + ref int tokenInformation, + int tokenInformationLength); + [DllImport("userenv.dll", SetLastError = true)] private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); @@ -62,8 +75,6 @@ private static extern bool DuplicateTokenEx( [DllImport("kernel32.dll")] private static extern uint WTSGetActiveConsoleSessionId(); - [DllImport("Wtsapi32.dll")] - private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); [DllImport("wtsapi32.dll", SetLastError = true)] private static extern int WTSEnumerateSessions( @@ -76,12 +87,6 @@ private static extern int WTSEnumerateSessions( [DllImport("Wtsapi32.dll")] private static extern void WTSFreeMemory(IntPtr ppSessionInfo); - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool SetTokenInformation(IntPtr tokenHandle, int tokenInformationClass, ref int tokenInformation, int tokenInformationLength); - #endregion #region Win32 Structs @@ -164,6 +169,11 @@ private enum TOKEN_TYPE TokenImpersonation = 2 } + private enum TokenInformationClass + { + TokenSessionId = 12 + } + [StructLayout(LayoutKind.Sequential)] private struct WTS_SESSION_INFO { @@ -177,42 +187,95 @@ private struct WTS_SESSION_INFO #endregion - public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true) + + // Gets the user token from the currently active session + private static bool GetSessionUserToken(ref IntPtr userToken, bool asAdmin) { + var bResult = false; + var hImpersonationToken = IntPtr.Zero; + + var pSessionInfo = IntPtr.Zero; + var sessionCount = 0; + + // Get a handle to the user access token for the current active session. + if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) + { + var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); + var current = pSessionInfo; + + for (var i = 0; i < sessionCount; i++) + { + var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO)); + current += arrayElementSize; + + if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) + { + activeSessionId = si.SessionID; + } + } + + WTSFreeMemory(pSessionInfo); + } + + // If enumerating did not work, fall back to the old method + if (activeSessionId == INVALID_SESSION_ID) + { + activeSessionId = WTSGetActiveConsoleSessionId(); + } + // Convert the execution token to a primary token + if(asAdmin) + { + bResult = DuplicateTokenEx(userToken, ((uint)TokenAccessLevels.AllAccess), IntPtr.Zero, + (int)TokenImpersonationLevel.Impersonation, (int)TOKEN_TYPE.TokenPrimary, + out executionToken); + } + else + { + bResult = DuplicateTokenEx(userToken, 0, IntPtr.Zero, + (int)TokenImpersonationLevel.Impersonation, (int)TOKEN_TYPE.TokenPrimary, + out executionToken); + } + + CloseHandle(userToken); + + return bResult; + } + + public static bool LaunchProcess(string appPath, string cmdLine = null, string workDir = null, bool visible = true, bool asAdmin = false) + { + var hUserToken = WindowsIdentity.GetCurrent().Token; var startInfo = new STARTUPINFO(); var procInfo = new PROCESS_INFORMATION(); - int iResultOfCreateProcessAsUser; - var pEnv = IntPtr.Zero; - IntPtr currentProcessToken = IntPtr.Zero; - IntPtr duplicatedToken; + int iResultOfCreateProcessAsUser; - uint interactiveSessionId = WTSGetActiveConsoleSessionId(); startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); try { - - if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ALL_ACCESS, out currentProcessToken)) + //workDir = Path.GetDirectoryName(appPath); + if (!GetSessionUserToken(ref hUserToken, asAdmin)) { - throw new Exception("OpenProcessToken failed. Error: " + Marshal.GetLastWin32Error()); + throw new ProcessCreationException("StartProcessAsCurrentUser: GetSessionUserToken failed."); } - if (!DuplicateTokenEx(currentProcessToken, TOKEN_ALL_ACCESS, IntPtr.Zero, - (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, - (int)TOKEN_TYPE.TokenPrimary, - out duplicatedToken)) + int sessionId = (int)activeSessionId; + if (asAdmin && !SetTokenInformation(executionToken, TokenInformationClass.TokenSessionId, ref sessionId, sizeof(int))) { - throw new Exception("DuplicateTokenEx failed. Error: " + Marshal.GetLastWin32Error()); + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Failed to set token information."); } + + PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION(); + + // Allow the app to be interactive with the desktop uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); startInfo.dwFlags = STARTF_USESHOWWINDOW; startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); startInfo.lpDesktop = "winsta0\\default"; - if (!CreateEnvironmentBlock(ref pEnv, duplicatedToken, false)) + if (!CreateEnvironmentBlock(ref pEnv, executionToken, false)) { throw new ProcessCreationException("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); } @@ -222,18 +285,7 @@ public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = nu Directory.SetCurrentDirectory(workDir); } - int uiValue = 1; - IntPtr pUIValue = Marshal.AllocHGlobal(sizeof(int)); - Marshal.WriteInt32(pUIValue, uiValue); - - int sessionId = (int)interactiveSessionId; - if (!SetTokenInformation(duplicatedToken, 12, ref sessionId, sizeof(int))) - { - iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); - throw new ProcessCreationException("StartProcessAsCurrentUser: SetTokenInformation failed. Error Code - " + iResultOfCreateProcessAsUser); - } - - if (!CreateProcessAsUser(duplicatedToken, + if (!CreateProcessAsUser(executionToken, appPath, // Application Name cmdLine, // Command Line IntPtr.Zero, @@ -253,7 +305,7 @@ public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = nu } finally { - CloseHandle(currentProcessToken); + CloseHandle(executionToken); if (pEnv != IntPtr.Zero) { DestroyEnvironmentBlock(pEnv); @@ -266,4 +318,6 @@ public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = nu } } -} + + +} \ No newline at end of file diff --git a/ProcessExtensions/ProcessExtensions.csproj b/ProcessExtensions/ProcessExtensions.csproj index dbdcea4..77a6280 100644 --- a/ProcessExtensions/ProcessExtensions.csproj +++ b/ProcessExtensions/ProcessExtensions.csproj @@ -4,4 +4,9 @@ netstandard2.0 + + + + + From 1fd3c7fbfbecc414b9de40aea393ab66c39b2fd8 Mon Sep 17 00:00:00 2001 From: AbdulRaoufMKamal Date: Wed, 26 Mar 2025 22:47:02 +0200 Subject: [PATCH 4/4] Made LaunchProcess session-aware. --- DemoModernService/DemoModernService.cs | 8 +------- ProcessExtensions/ProcessExtensions.cs | 8 +++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/DemoModernService/DemoModernService.cs b/DemoModernService/DemoModernService.cs index 5bad53e..649ce1e 100644 --- a/DemoModernService/DemoModernService.cs +++ b/DemoModernService/DemoModernService.cs @@ -15,12 +15,6 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) string targetProjectName = Path.GetFileNameWithoutExtension(appPath); string workDir = Path.Combine(solutionDirectory, targetProjectName, "bin", "Debug", "net6.0-windows"); - /* NOTE YOU MUST COMMENT ONE OF THE BELOW LINES BASED ON YOUR CHOICE OF RUNNING THE SERVICE */ - - // DEBUG MODE - ProcessExtensions.LaunchProcess(appPath: appPath, workDir: workDir, asAdmin: false); - - // SERVICE MODE - ProcessExtensions.LaunchProcess(appPath: appPath, workDir: workDir, asAdmin: true); + ProcessExtensions.LaunchProcess(appPath: appPath, workDir: workDir); } } \ No newline at end of file diff --git a/ProcessExtensions/ProcessExtensions.cs b/ProcessExtensions/ProcessExtensions.cs index 891bdb8..3eed107 100644 --- a/ProcessExtensions/ProcessExtensions.cs +++ b/ProcessExtensions/ProcessExtensions.cs @@ -242,7 +242,7 @@ private static bool GetSessionUserToken(ref IntPtr userToken, bool asAdmin) return bResult; } - public static bool LaunchProcess(string appPath, string cmdLine = null, string workDir = null, bool visible = true, bool asAdmin = false) + public static bool LaunchProcess(string appPath, string cmdLine = null, string workDir = null, bool visible = true) { var hUserToken = WindowsIdentity.GetCurrent().Token; var startInfo = new STARTUPINFO(); @@ -250,19 +250,21 @@ public static bool LaunchProcess(string appPath, string cmdLine = null, string w var pEnv = IntPtr.Zero; int iResultOfCreateProcessAsUser; + bool systemAccount = WindowsIdentity.GetCurrent().IsSystem; + startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); try { //workDir = Path.GetDirectoryName(appPath); - if (!GetSessionUserToken(ref hUserToken, asAdmin)) + if (!GetSessionUserToken(ref hUserToken, systemAccount)) { throw new ProcessCreationException("StartProcessAsCurrentUser: GetSessionUserToken failed."); } int sessionId = (int)activeSessionId; - if (asAdmin && !SetTokenInformation(executionToken, TokenInformationClass.TokenSessionId, ref sessionId, sizeof(int))) + if (systemAccount && !SetTokenInformation(executionToken, TokenInformationClass.TokenSessionId, ref sessionId, sizeof(int))) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Failed to set token information."); }