From 63c03d8b857e2589d7cd854981fac8bec558a6c7 Mon Sep 17 00:00:00 2001 From: Justin Murray Date: Sun, 22 Mar 2015 13:38:10 -0400 Subject: [PATCH] Made a proper class library, and added a demo project. Fixes #1. --- .gitignore | 4 + CreateProcessAsUser.cs | 265 ------------------ DemoService/App.config | 6 + DemoService/DemoService.Designer.cs | 37 +++ DemoService/DemoService.cs | 22 ++ DemoService/DemoService.csproj | 84 ++++++ DemoService/Program.cs | 25 ++ DemoService/ProjectInstaller.Designer.cs | 60 +++++ DemoService/ProjectInstaller.cs | 19 ++ DemoService/ProjectInstaller.resx | 129 +++++++++ DemoService/Properties/AssemblyInfo.cs | 36 +++ DemoService/createService.bat | 3 + DemoService/deleteService.bat | 3 + ProcessExtensions/ProcessExtensions.cs | 268 +++++++++++++++++++ ProcessExtensions/ProcessExtensions.csproj | 53 ++++ ProcessExtensions/Properties/AssemblyInfo.cs | 36 +++ README.md | 2 +- solution.sln | 33 +++ 18 files changed, 819 insertions(+), 266 deletions(-) create mode 100644 .gitignore delete mode 100644 CreateProcessAsUser.cs create mode 100644 DemoService/App.config create mode 100644 DemoService/DemoService.Designer.cs create mode 100644 DemoService/DemoService.cs create mode 100644 DemoService/DemoService.csproj create mode 100644 DemoService/Program.cs create mode 100644 DemoService/ProjectInstaller.Designer.cs create mode 100644 DemoService/ProjectInstaller.cs create mode 100644 DemoService/ProjectInstaller.resx create mode 100644 DemoService/Properties/AssemblyInfo.cs create mode 100644 DemoService/createService.bat create mode 100644 DemoService/deleteService.bat create mode 100644 ProcessExtensions/ProcessExtensions.cs create mode 100644 ProcessExtensions/ProcessExtensions.csproj create mode 100644 ProcessExtensions/Properties/AssemblyInfo.cs create mode 100644 solution.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10f3f7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +obj/ +solution.sln.ide/ +bin/ +*.InstallLog diff --git a/CreateProcessAsUser.cs b/CreateProcessAsUser.cs deleted file mode 100644 index 261ca4c..0000000 --- a/CreateProcessAsUser.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace murrayju -{ - internal class CreateProcessAsUser - { - #region Win32 Constants - - private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; - 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; - - #endregion - - #region DllImports - - [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] - public static extern bool CreateProcessAsUser( - IntPtr hToken, - String lpApplicationName, - String lpCommandLine, - IntPtr lpProcessAttributes, - IntPtr lpThreadAttributes, - bool bInheritHandle, - uint dwCreationFlags, - IntPtr lpEnvironment, - String lpCurrentDirectory, - ref STARTUPINFO lpStartupInfo, - out PROCESS_INFORMATION lpProcessInformation); - - [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] - public static extern bool DuplicateTokenEx( - IntPtr ExistingTokenHandle, - uint dwDesiredAccess, - IntPtr lpThreadAttributes, - int TokenType, - int ImpersonationLevel, - ref IntPtr DuplicateTokenHandle); - - [DllImport("userenv.dll", SetLastError = true)] - public static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); - - [DllImport("userenv.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle(IntPtr hSnapshot); - - [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( - IntPtr hServer, - int Reserved, - int Version, - ref IntPtr ppSessionInfo, - ref int pCount); - - #endregion - - #region Win32 Structs - - public enum SW - { - SW_HIDE = 0, - SW_SHOWNORMAL = 1, - SW_NORMAL = 1, - SW_SHOWMINIMIZED = 2, - SW_SHOWMAXIMIZED = 3, - SW_MAXIMIZE = 3, - SW_SHOWNOACTIVATE = 4, - SW_SHOW = 5, - SW_MINIMIZE = 6, - SW_SHOWMINNOACTIVE = 7, - SW_SHOWNA = 8, - SW_RESTORE = 9, - SW_SHOWDEFAULT = 10, - SW_MAX = 10 - } - - public enum WTS_CONNECTSTATE_CLASS - { - WTSActive, - WTSConnected, - WTSConnectQuery, - WTSShadow, - WTSDisconnected, - WTSIdle, - WTSListen, - WTSReset, - WTSDown, - WTSInit - } - - [StructLayout(LayoutKind.Sequential)] - public struct PROCESS_INFORMATION - { - public IntPtr hProcess; - public IntPtr hThread; - public uint dwProcessId; - public uint dwThreadId; - } - - private enum SECURITY_IMPERSONATION_LEVEL - { - SecurityAnonymous = 0, - SecurityIdentification = 1, - SecurityImpersonation = 2, - SecurityDelegation = 3, - } - - [StructLayout(LayoutKind.Sequential)] - public struct STARTUPINFO - { - public int cb; - public String lpReserved; - public String lpDesktop; - public String lpTitle; - public uint dwX; - public uint dwY; - public uint dwXSize; - public uint dwYSize; - public uint dwXCountChars; - public uint dwYCountChars; - public uint dwFillAttribute; - public uint dwFlags; - public short wShowWindow; - public short cbReserved2; - public IntPtr lpReserved2; - public IntPtr hStdInput; - public IntPtr hStdOutput; - public IntPtr hStdError; - } - - private enum TOKEN_TYPE - { - TokenPrimary = 1, - TokenImpersonation = 2 - } - - [StructLayout(LayoutKind.Sequential)] - private struct WTS_SESSION_INFO - { - public readonly UInt32 SessionID; - - [MarshalAs(UnmanagedType.LPStr)] public readonly String pWinStationName; - - public readonly WTS_CONNECTSTATE_CLASS State; - } - - #endregion - - 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 = (int) 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; - } - } - } - - // 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 LaunchUserProcess(string appPath, string cmdLine, string workDir, bool visible) - { - var hUserToken = IntPtr.Zero; - var startInfo = new STARTUPINFO(); - var procInfo = new PROCESS_INFORMATION(); - var pEnv = IntPtr.Zero; - int iResultOfCreateProcessAsUser; - - startInfo.cb = Marshal.SizeOf(typeof (STARTUPINFO)); - - try - { - if (!GetSessionUserToken(ref hUserToken)) - { - throw new Exception("LaunchUserProcess: GetSessionUserToken failed."); - } - - uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); - startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); - startInfo.lpDesktop = "winsta0\\default"; - - if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) - { - throw new Exception("LaunchUserProcess: CreateEnvironmentBlock failed."); - } - - if (!CreateProcessAsUser(hUserToken, - appPath, // Application Name - cmdLine, // Command Line - IntPtr.Zero, - IntPtr.Zero, - false, - dwCreationFlags, - pEnv, - workDir, // Working directory - ref startInfo, - out procInfo)) - { - throw new Exception("LaunchUserProcess: CreateProcessAsUser failed.\n"); - } - - iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); - } - finally - { - CloseHandle(hUserToken); - if (pEnv != IntPtr.Zero) - { - DestroyEnvironmentBlock(pEnv); - } - CloseHandle(procInfo.hThread); - CloseHandle(procInfo.hProcess); - } - - return true; - } - } -} diff --git a/DemoService/App.config b/DemoService/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/DemoService/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DemoService/DemoService.Designer.cs b/DemoService/DemoService.Designer.cs new file mode 100644 index 0000000..1c9cbdc --- /dev/null +++ b/DemoService/DemoService.Designer.cs @@ -0,0 +1,37 @@ +namespace demo +{ + partial class DemoService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + + #endregion + } +} diff --git a/DemoService/DemoService.cs b/DemoService/DemoService.cs new file mode 100644 index 0000000..f8c9174 --- /dev/null +++ b/DemoService/DemoService.cs @@ -0,0 +1,22 @@ +using murrayju.ProcessExtensions; +using System.ServiceProcess; + +namespace demo +{ + public partial class DemoService : ServiceBase + { + public DemoService() + { + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + ProcessExtensions.StartProcessAsCurrentUser("calc.exe"); + } + + protected override void OnStop() + { + } + } +} diff --git a/DemoService/DemoService.csproj b/DemoService/DemoService.csproj new file mode 100644 index 0000000..882857d --- /dev/null +++ b/DemoService/DemoService.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {07E69AE0-DAF1-4538-80A6-700159C91B9B} + WinExe + Properties + DemoService + DemoService + v4.5 + 512 + + + AnyCPU + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + Component + + + DemoService.cs + + + + Component + + + ProjectInstaller.cs + + + + + + + + + {770a3d71-31c3-46ae-97ad-a0325d8462fb} + ProcessExtensions + + + + + ProjectInstaller.cs + + + + + \ No newline at end of file diff --git a/DemoService/Program.cs b/DemoService/Program.cs new file mode 100644 index 0000000..be054c1 --- /dev/null +++ b/DemoService/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace demo +{ + static class Program + { + /// + /// The main entry point for the application. + /// + static void Main() + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new DemoService() + }; + ServiceBase.Run(ServicesToRun); + } + } +} diff --git a/DemoService/ProjectInstaller.Designer.cs b/DemoService/ProjectInstaller.Designer.cs new file mode 100644 index 0000000..8ebdd8a --- /dev/null +++ b/DemoService/ProjectInstaller.Designer.cs @@ -0,0 +1,60 @@ +namespace DemoService +{ + partial class ProjectInstaller + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); + this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); + // + // serviceProcessInstaller1 + // + this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; + this.serviceProcessInstaller1.Password = null; + this.serviceProcessInstaller1.Username = null; + // + // serviceInstaller1 + // + this.serviceInstaller1.Description = "An example service"; + this.serviceInstaller1.DisplayName = "DemoService"; + this.serviceInstaller1.ServiceName = "DemoService"; + this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; + // + // ProjectInstaller + // + this.Installers.AddRange(new System.Configuration.Install.Installer[] { + this.serviceProcessInstaller1, + this.serviceInstaller1}); + + } + + #endregion + + private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; + private System.ServiceProcess.ServiceInstaller serviceInstaller1; + } +} \ No newline at end of file diff --git a/DemoService/ProjectInstaller.cs b/DemoService/ProjectInstaller.cs new file mode 100644 index 0000000..1dd7064 --- /dev/null +++ b/DemoService/ProjectInstaller.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration.Install; +using System.Linq; +using System.Threading.Tasks; + +namespace DemoService +{ + [RunInstaller(true)] + public partial class ProjectInstaller : System.Configuration.Install.Installer + { + public ProjectInstaller() + { + InitializeComponent(); + } + } +} diff --git a/DemoService/ProjectInstaller.resx b/DemoService/ProjectInstaller.resx new file mode 100644 index 0000000..5c8b468 --- /dev/null +++ b/DemoService/ProjectInstaller.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 196, 17 + + + False + + \ No newline at end of file diff --git a/DemoService/Properties/AssemblyInfo.cs b/DemoService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d206926 --- /dev/null +++ b/DemoService/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DemoService")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("addpcs")] +[assembly: AssemblyProduct("DemoService")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("07e69ae0-daf1-4538-80a6-700159c91b9b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DemoService/createService.bat b/DemoService/createService.bat new file mode 100644 index 0000000..6662092 --- /dev/null +++ b/DemoService/createService.bat @@ -0,0 +1,3 @@ +@echo off +%windir%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe bin\DemoService.exe +sc start DemoService diff --git a/DemoService/deleteService.bat b/DemoService/deleteService.bat new file mode 100644 index 0000000..9a4e5a5 --- /dev/null +++ b/DemoService/deleteService.bat @@ -0,0 +1,3 @@ +@echo off +sc stop DemoService +%windir%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /u bin\DemoService.exe diff --git a/ProcessExtensions/ProcessExtensions.cs b/ProcessExtensions/ProcessExtensions.cs new file mode 100644 index 0000000..c3fd638 --- /dev/null +++ b/ProcessExtensions/ProcessExtensions.cs @@ -0,0 +1,268 @@ +using System; +using System.Runtime.InteropServices; + +namespace murrayju.ProcessExtensions +{ + public static class ProcessExtensions + { + #region Win32 Constants + + private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; + 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; + + #endregion + + #region DllImports + + [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + private static extern bool CreateProcessAsUser( + IntPtr hToken, + String lpApplicationName, + String lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandle, + uint dwCreationFlags, + IntPtr lpEnvironment, + String lpCurrentDirectory, + ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] + private static extern bool DuplicateTokenEx( + IntPtr ExistingTokenHandle, + uint dwDesiredAccess, + IntPtr lpThreadAttributes, + int TokenType, + int ImpersonationLevel, + ref IntPtr DuplicateTokenHandle); + + [DllImport("userenv.dll", SetLastError = true)] + private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); + + [DllImport("userenv.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hSnapshot); + + [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( + IntPtr hServer, + int Reserved, + int Version, + ref IntPtr ppSessionInfo, + ref int pCount); + + #endregion + + #region Win32 Structs + + private enum SW + { + SW_HIDE = 0, + SW_SHOWNORMAL = 1, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_SHOWMAXIMIZED = 3, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_MAX = 10 + } + + private enum WTS_CONNECTSTATE_CLASS + { + WTSActive, + WTSConnected, + WTSConnectQuery, + WTSShadow, + WTSDisconnected, + WTSIdle, + WTSListen, + WTSReset, + WTSDown, + WTSInit + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public uint dwProcessId; + public uint dwThreadId; + } + + private enum SECURITY_IMPERSONATION_LEVEL + { + SecurityAnonymous = 0, + SecurityIdentification = 1, + SecurityImpersonation = 2, + SecurityDelegation = 3, + } + + [StructLayout(LayoutKind.Sequential)] + private struct STARTUPINFO + { + public int cb; + public String lpReserved; + public String lpDesktop; + public String lpTitle; + public uint dwX; + public uint dwY; + public uint dwXSize; + public uint dwYSize; + public uint dwXCountChars; + public uint dwYCountChars; + public uint dwFillAttribute; + public uint dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + private enum TOKEN_TYPE + { + TokenPrimary = 1, + TokenImpersonation = 2 + } + + [StructLayout(LayoutKind.Sequential)] + private struct WTS_SESSION_INFO + { + public readonly UInt32 SessionID; + + [MarshalAs(UnmanagedType.LPStr)] + public readonly String pWinStationName; + + public readonly WTS_CONNECTSTATE_CLASS State; + } + + #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 = (int)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; + } + } + } + + // 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; + + startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); + + try + { + if (!GetSessionUserToken(ref hUserToken)) + { + throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed."); + } + + uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); + startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); + startInfo.lpDesktop = "winsta0\\default"; + + if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) + { + throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); + } + + if (!CreateProcessAsUser(hUserToken, + appPath, // Application Name + cmdLine, // Command Line + IntPtr.Zero, + IntPtr.Zero, + false, + dwCreationFlags, + pEnv, + workDir, // Working directory + ref startInfo, + out procInfo)) + { + throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed.\n"); + } + + iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); + } + finally + { + CloseHandle(hUserToken); + if (pEnv != IntPtr.Zero) + { + DestroyEnvironmentBlock(pEnv); + } + CloseHandle(procInfo.hThread); + CloseHandle(procInfo.hProcess); + } + + return true; + } + + } +} diff --git a/ProcessExtensions/ProcessExtensions.csproj b/ProcessExtensions/ProcessExtensions.csproj new file mode 100644 index 0000000..e673013 --- /dev/null +++ b/ProcessExtensions/ProcessExtensions.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {770A3D71-31C3-46AE-97AD-A0325D8462FB} + Library + Properties + ProcessExtensions + ProcessExtensions + v4.5 + 512 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProcessExtensions/Properties/AssemblyInfo.cs b/ProcessExtensions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9398f6b --- /dev/null +++ b/ProcessExtensions/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ProcessExtensions")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("addpcs")] +[assembly: AssemblyProduct("ProcessExtensions")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("770a3d71-31c3-46ae-97ad-a0325d8462fb")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md index 4c377f1..419099e 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,5 @@ Note that the process must have the appropriate (admin) privileges for this to w ## Usage ``` -CreateProcessAsUser.LaunchUserProcess(null, "calc", null, true); +ProcessExtensions.StartProcessAsCurrentUser("calc.exe"); ``` diff --git a/solution.sln b/solution.sln new file mode 100644 index 0000000..bff806f --- /dev/null +++ b/solution.sln @@ -0,0 +1,33 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.22310.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoService", "DemoService\DemoService.csproj", "{07E69AE0-DAF1-4538-80A6-700159C91B9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessExtensions", "ProcessExtensions\ProcessExtensions.csproj", "{770A3D71-31C3-46AE-97AD-A0325D8462FB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CBE27F9B-9C17-4A66-B7D0-9DDEEF10138E}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {07E69AE0-DAF1-4538-80A6-700159C91B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E69AE0-DAF1-4538-80A6-700159C91B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E69AE0-DAF1-4538-80A6-700159C91B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E69AE0-DAF1-4538-80A6-700159C91B9B}.Release|Any CPU.Build.0 = Release|Any CPU + {770A3D71-31C3-46AE-97AD-A0325D8462FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {770A3D71-31C3-46AE-97AD-A0325D8462FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {770A3D71-31C3-46AE-97AD-A0325D8462FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {770A3D71-31C3-46AE-97AD-A0325D8462FB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal