Imports Cjwdev.WindowsApi
Module MainModule
Private Sub ShowUsage()
Console.WriteLine("Launches a process in the currently logged on user's desktop session (and" & vbNewLine & "security context) when called from a Windows service running as Local System" & vbNewLine & vbNewLine & _
"StartInConsoleSession.exe [/W] cmdpath arguments" & vbNewLine & vbNewLine & _
" cmdpath" & vbTab & vbTab & "The full path to the executable file to be launched" & vbNewLine & _
" arguments" & vbTab & vbTab & "The arguments to pass to the executable file specified" & vbNewLine & vbTab & vbTab & vbTab & "in cmdpath. If any arguments contain spaces then they" & vbNewLine & vbTab & vbTab & vbTab & "must be enclosed in speech marks" & vbNewLine & _
" /W" & vbTab & vbTab & vbTab & "Wait for the launched process to exit before returning" & vbNewLine & vbNewLine & vbNewLine & _
"EXAMPLES:" & vbNewLine & _
"StartInConsoleSession.exe C:\Windows\System32\Notepad.exe ""C:\Some File.txt""" & vbNewLine & _
"Launches Notepad and opens the file C:\Some File.txt" & vbNewLine & vbNewLine & _
"StartInConsoleSession.exe /W C:\Windows\System32\cmd.exe" & vbNewLine & _
"Launches command prompt and waits for it to exit before returning" & vbNewLine)
End Sub
Sub Main()
Console.WriteLine(vbNewLine & "StartInConsoleSession.exe" & vbNewLine & _
"Version " & My.Application.Info.Version.ToString & vbNewLine & _
'Check for valid command line arguments
If (My.Application.CommandLineArgs.Count < 1) OrElse (My.Application.CommandLineArgs.Count = 1 AndAlso String.Compare(My.Application.CommandLineArgs(0), "/w", True) = 0) Then
ShowUsage()
Exit Sub
End If
Try
Dim CommandLine As String = String.Empty
Dim FirstArg As Integer = 0
Dim WaitForExit As Boolean = False
'Build command line string for the process we will launch
'Check to see if the first argument is /w as this means we want to wait for the newly launched process to exit
If String.Compare(My.Application.CommandLineArgs(0), "/w", True) = 0 Then
CommandLine = """" & My.Application.CommandLineArgs(1) & """"
FirstArg = 2
WaitForExit = True
Else
CommandLine = """" & My.Application.CommandLineArgs(0) & """"
FirstArg = 1
End If
'Get all other arguments and add them to the final command line string
For i As Integer = FirstArg To My.Application.CommandLineArgs.Count - 1
CommandLine &= " """ & My.Application.CommandLineArgs(i) & """"
Next
'Attempt to launch the process in the currently logged on user's session
LaunchProcessInConsoleSession(CommandLine, WaitForExit)
Catch ex As Exception
Console.WriteLine("Unexpected error: " & ex.Message)
End Try
End Sub
Private Sub LaunchProcessInConsoleSession(ByVal CommandLine As String, ByVal WaitForExit As Boolean)
Console.WriteLine("Command line = " & CommandLine & vbNewLine & "Wait for exit = " & WaitForExit.ToString)
Dim UserTokenHandle As IntPtr = IntPtr.Zero
Dim EnvironmentBlock As IntPtr = IntPtr.Zero
Dim ProcInfo As New ApiDefinitions.PROCESS_INFORMATION
'Not much point in adding comments here, just read the Console.WriteLine strings
Try
Console.WriteLine("Attempting to get console session ID...")
Dim ConsoleSessionId As UInteger = ApiDefinitions.WTSGetActiveConsoleSessionId
Console.WriteLine("Console session ID = " & ConsoleSessionId)
Console.WriteLine("Attempting to get handle to primary access token of console session user...")
Dim QueryTokenResult As Boolean = ApiDefinitions.WTSQueryUserToken(ConsoleSessionId, UserTokenHandle)
If QueryTokenResult AndAlso Not UserTokenHandle = IntPtr.Zero Then
Console.WriteLine("Primary token handle successfully obtained")
Else
Console.WriteLine("Failed to get handle to primary token, the last error reported was: " & New ComponentModel.Win32Exception().Message)
Exit Sub
End If
Console.WriteLine("Creating environmental variable block for user...")
Dim CreateEnvironmentResult As Boolean = ApiDefinitions.CreateEnvironmentBlock(EnvironmentBlock, UserTokenHandle, False)
If CreateEnvironmentResult AndAlso Not EnvironmentBlock = IntPtr.Zero Then
Console.WriteLine("Successfully created environmental variable block")
Else
Console.WriteLine("Failed to create environmental variable block for user, the last error reported was: " & New ComponentModel.Win32Exception().Message)
Exit Sub
End If
Dim StartInfo As New ApiDefinitions.STARTUPINFO
StartInfo.cb = CUInt(Runtime.InteropServices.Marshal.SizeOf(StartInfo))
Console.WriteLine("Attempting to launch process...")
'Launch the new process, using the user's token we obtained from WTSQueryUserToken means it will be launched in their session and we pass in the
'environmental variable block we got from CreateEnvironmentBlock so that the new application gets all of the normal environmental variables that
'a process launched by that user would normally get. Also specify CREATE_NEW_CONSOLE so that command line applications will not inherit the console
'from this application (which will be running in another session so will not work correctly)
Dim CreateProcessResult As Boolean = ApiDefinitions.CreateProcessAsUser(UserTokenHandle, Nothing, CommandLine, IntPtr.Zero, IntPtr.Zero, False, ApiDefinitions.CREATE_UNICODE_ENVIRONMENT Or ApiDefinitions.CREATE_NEW_CONSOLE, EnvironmentBlock, Nothing, StartInfo, ProcInfo)
'Check the result and see if a process was actually launched
If CreateProcessResult AndAlso Not ProcInfo.dwProcessId = 0 Then
Console.WriteLine("Process successfully launched in console session (Process ID = " & ProcInfo.dwProcessId & ")")
Else
Console.WriteLine("Failed to launch process, the last error reported was: " & New ComponentModel.Win32Exception().Message)
End If
Finally
'Clean up, close handles
If Not UserTokenHandle = IntPtr.Zero Then
ApiDefinitions.CloseHandle(UserTokenHandle)
End If
If Not ProcInfo.hProcess = IntPtr.Zero Then
ApiDefinitions.CloseHandle(ProcInfo.hProcess)
End If
If Not ProcInfo.hThread = IntPtr.Zero Then
ApiDefinitions.CloseHandle(ProcInfo.hThread)
End If
If Not EnvironmentBlock = IntPtr.Zero Then
ApiDefinitions.DestroyEnvironmentBlock(EnvironmentBlock)
End If
End Try
'Wait for the process to exit if that has been requested
If WaitForExit AndAlso Not ProcInfo.dwProcessId = 0 Then
Dim LaunchedProcess As Process = Nothing
Try
LaunchedProcess = Process.GetProcessById(CInt(ProcInfo.dwProcessId))
Catch ex As ArgumentException
Console.WriteLine("Process has terminated")
End Try
If Not LaunchedProcess Is Nothing Then
Console.WriteLine("Waiting for process to exit...")
LaunchedProcess.WaitForExit()
Console.WriteLine("Process has terminated")
End If
End If
End Sub
End Module