Skip to content

Commit 979a3f9

Browse files
committed
Adding initial code
1 parent 5c5f67c commit 979a3f9

2 files changed

Lines changed: 277 additions & 1 deletion

File tree

CreateProcessAsUser.cs

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace murrayju
5+
{
6+
internal class CreateProcessAsUser
7+
{
8+
#region Win32 Constants
9+
10+
private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
11+
private const int CREATE_NO_WINDOW = 0x08000000;
12+
13+
private const int CREATE_NEW_CONSOLE = 0x00000010;
14+
15+
private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
16+
private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
17+
18+
#endregion
19+
20+
#region DllImports
21+
22+
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
23+
public static extern bool CreateProcessAsUser(
24+
IntPtr hToken,
25+
String lpApplicationName,
26+
String lpCommandLine,
27+
IntPtr lpProcessAttributes,
28+
IntPtr lpThreadAttributes,
29+
bool bInheritHandle,
30+
uint dwCreationFlags,
31+
IntPtr lpEnvironment,
32+
String lpCurrentDirectory,
33+
ref STARTUPINFO lpStartupInfo,
34+
out PROCESS_INFORMATION lpProcessInformation);
35+
36+
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
37+
public static extern bool DuplicateTokenEx(
38+
IntPtr ExistingTokenHandle,
39+
uint dwDesiredAccess,
40+
IntPtr lpThreadAttributes,
41+
int TokenType,
42+
int ImpersonationLevel,
43+
ref IntPtr DuplicateTokenHandle);
44+
45+
[DllImport("userenv.dll", SetLastError = true)]
46+
public static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
47+
48+
[DllImport("userenv.dll", SetLastError = true)]
49+
[return: MarshalAs(UnmanagedType.Bool)]
50+
private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
51+
52+
[DllImport("kernel32.dll", SetLastError = true)]
53+
private static extern bool CloseHandle(IntPtr hSnapshot);
54+
55+
[DllImport("kernel32.dll")]
56+
private static extern uint WTSGetActiveConsoleSessionId();
57+
58+
[DllImport("Wtsapi32.dll")]
59+
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
60+
61+
[DllImport("wtsapi32.dll", SetLastError = true)]
62+
private static extern int WTSEnumerateSessions(
63+
IntPtr hServer,
64+
int Reserved,
65+
int Version,
66+
ref IntPtr ppSessionInfo,
67+
ref int pCount);
68+
69+
#endregion
70+
71+
#region Win32 Structs
72+
73+
public enum SW
74+
{
75+
SW_HIDE = 0,
76+
SW_SHOWNORMAL = 1,
77+
SW_NORMAL = 1,
78+
SW_SHOWMINIMIZED = 2,
79+
SW_SHOWMAXIMIZED = 3,
80+
SW_MAXIMIZE = 3,
81+
SW_SHOWNOACTIVATE = 4,
82+
SW_SHOW = 5,
83+
SW_MINIMIZE = 6,
84+
SW_SHOWMINNOACTIVE = 7,
85+
SW_SHOWNA = 8,
86+
SW_RESTORE = 9,
87+
SW_SHOWDEFAULT = 10,
88+
SW_MAX = 10
89+
}
90+
91+
public enum WTS_CONNECTSTATE_CLASS
92+
{
93+
WTSActive,
94+
WTSConnected,
95+
WTSConnectQuery,
96+
WTSShadow,
97+
WTSDisconnected,
98+
WTSIdle,
99+
WTSListen,
100+
WTSReset,
101+
WTSDown,
102+
WTSInit
103+
}
104+
105+
[StructLayout(LayoutKind.Sequential)]
106+
public struct PROCESS_INFORMATION
107+
{
108+
public IntPtr hProcess;
109+
public IntPtr hThread;
110+
public uint dwProcessId;
111+
public uint dwThreadId;
112+
}
113+
114+
private enum SECURITY_IMPERSONATION_LEVEL
115+
{
116+
SecurityAnonymous = 0,
117+
SecurityIdentification = 1,
118+
SecurityImpersonation = 2,
119+
SecurityDelegation = 3,
120+
}
121+
122+
[StructLayout(LayoutKind.Sequential)]
123+
public struct STARTUPINFO
124+
{
125+
public int cb;
126+
public String lpReserved;
127+
public String lpDesktop;
128+
public String lpTitle;
129+
public uint dwX;
130+
public uint dwY;
131+
public uint dwXSize;
132+
public uint dwYSize;
133+
public uint dwXCountChars;
134+
public uint dwYCountChars;
135+
public uint dwFillAttribute;
136+
public uint dwFlags;
137+
public short wShowWindow;
138+
public short cbReserved2;
139+
public IntPtr lpReserved2;
140+
public IntPtr hStdInput;
141+
public IntPtr hStdOutput;
142+
public IntPtr hStdError;
143+
}
144+
145+
private enum TOKEN_TYPE
146+
{
147+
TokenPrimary = 1,
148+
TokenImpersonation = 2
149+
}
150+
151+
[StructLayout(LayoutKind.Sequential)]
152+
private struct WTS_SESSION_INFO
153+
{
154+
public readonly UInt32 SessionID;
155+
156+
[MarshalAs(UnmanagedType.LPStr)] public readonly String pWinStationName;
157+
158+
public readonly WTS_CONNECTSTATE_CLASS State;
159+
}
160+
161+
#endregion
162+
163+
private static bool GetSessionUserToken(ref IntPtr phUserToken)
164+
{
165+
var bResult = false;
166+
var hImpersonationToken = IntPtr.Zero;
167+
var activeSessionId = INVALID_SESSION_ID;
168+
var pSessionInfo = IntPtr.Zero;
169+
var sessionCount = 0;
170+
171+
// Get a handle to the user access token for the current active session.
172+
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
173+
{
174+
var arrayElementSize = Marshal.SizeOf(typeof (WTS_SESSION_INFO));
175+
var current = (int) pSessionInfo;
176+
177+
for (var i = 0; i < sessionCount; i++)
178+
{
179+
var si = (WTS_SESSION_INFO) Marshal.PtrToStructure((IntPtr) current, typeof (WTS_SESSION_INFO));
180+
current += arrayElementSize;
181+
182+
if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
183+
{
184+
activeSessionId = si.SessionID;
185+
}
186+
}
187+
}
188+
189+
// If enumerating did not work, fall back to the old method
190+
if (activeSessionId == INVALID_SESSION_ID)
191+
{
192+
activeSessionId = WTSGetActiveConsoleSessionId();
193+
}
194+
195+
if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
196+
{
197+
// Convert the impersonation token to a primary token
198+
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
199+
(int) SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int) TOKEN_TYPE.TokenPrimary,
200+
ref phUserToken);
201+
202+
CloseHandle(hImpersonationToken);
203+
}
204+
205+
return bResult;
206+
}
207+
208+
public static bool LaunchUserProcess(string appPath, string cmdLine, string workDir, bool visible)
209+
{
210+
var hUserToken = IntPtr.Zero;
211+
var startInfo = new STARTUPINFO();
212+
var procInfo = new PROCESS_INFORMATION();
213+
var pEnv = IntPtr.Zero;
214+
int iResultOfCreateProcessAsUser;
215+
216+
startInfo.cb = Marshal.SizeOf(typeof (STARTUPINFO));
217+
218+
try
219+
{
220+
if (!GetSessionUserToken(ref hUserToken))
221+
{
222+
throw new Exception("LaunchUserProcess: GetSessionUserToken failed.");
223+
}
224+
225+
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
226+
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
227+
startInfo.lpDesktop = "winsta0\\default";
228+
229+
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
230+
{
231+
throw new Exception("LaunchUserProcess: CreateEnvironmentBlock failed.");
232+
}
233+
234+
if (!CreateProcessAsUser(hUserToken,
235+
appPath, // Application Name
236+
cmdLine, // Command Line
237+
IntPtr.Zero,
238+
IntPtr.Zero,
239+
false,
240+
dwCreationFlags,
241+
pEnv,
242+
workDir, // Working directory
243+
ref startInfo,
244+
out procInfo))
245+
{
246+
throw new Exception("LaunchUserProcess: CreateProcessAsUser failed.\n");
247+
}
248+
249+
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
250+
}
251+
finally
252+
{
253+
CloseHandle(hUserToken);
254+
if (pEnv != IntPtr.Zero)
255+
{
256+
DestroyEnvironmentBlock(pEnv);
257+
}
258+
CloseHandle(procInfo.hThread);
259+
CloseHandle(procInfo.hProcess);
260+
}
261+
262+
return true;
263+
}
264+
}
265+
}

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
CreateProcessAsUser
22
===================
33

4-
Creates a process in a different Windows session
4+
This uses the Win32 apis to
5+
1. Find the currently active user session
6+
2. Spawn a new process in that session
7+
8+
This allows a process running in a different session (such as a windows service) to start a process with a graphical user interface that the user must see.
9+
10+
Note that the process must have the appropriate (admin) privileges for this to work correctly.
11+
12+
## Usage
13+
```
14+
CreateProcessAsUser.LaunchUserProcess(null, "calc", null, true);
15+
```

0 commit comments

Comments
 (0)