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