# VBForums CodeBank > CodeBank - Visual Basic .NET >  Redirect Process Input/Output with Windows API

## chris128

If you ever have a situation where you cannot use the built-in Process class to launch a process (like if you need to launch the process with a specific access token for example) but you need to be able to redirect the input and output (StdIn and StdOut) of the process to your own program then this should help you out. This only really applies if you are wanting to launch and interact with console applications because GUI applications dont tend to ever look at StdIn or StdOut.
It is not very straight forward and took me ages to get it working so I would certainly advise people to try and use the built in Process class and its redirection abilities wherever possible rather than using this method... but as mentioned above, unfortunately there are some scenarios where this is not possible.

Ive attached the project files for a demo application I've written that lets you launch a process, send text input to it, and read its output in a background thread. I have added loads of comments to it but feel free to ask if you have any questions about how it works. 
See end of post for attached project files, but here's an example of what it does:



So as you can see in the example above, I've launched command prompt (hidden) and sent the command "dir" to it (which makes it list all files and folders in the current folder) then sent the command "exit" which makes the command prompt terminate itself, and the program realises and shows an appropriate message.


There are a few quirks, which are as follows:
*---* On Windows XP some characters in the output do not get displayed correctly - no idea why this is, as I had the same issue on Windows 7 but then I changed the encoding of the text to the encoding that command prompt uses (cp850) rather than ASCII and that sorted it out on Windows 7 but made no difference on XP. Its only some special characters so most people probably wont ever notice this.
*---* Telnet does not want to work at all, even though every other program I've tried does.
*---* On Windows XP the output for some commands doesnt have line breaks where it should - ipconfig is the main one I've had issues with. Its still usable, just a bit harder to read because everything is on one line (wrapped). Again this is not an issue on Windows 7.


This code uses the following Windows APIs:
*CreatePipe*
*ReadFile* (for reading the pipe, not a file  :Smilie: )
*WriteFile* (same as above)
*CloseHandle*
*DuplicateHandle*
*CreateProcess*
*PeekNamedPipe*
*WaitForMultipleObjects*
which have of course all been added to my Windows API library that I'm writing  :Smilie:  see here for more details - http://www.vbforums.com/showthread.php?p=3813700

Project files for redirection demo attached below.

----------


## minitech

When wouldn't you be able to use the built-in class? Just wondering.

----------


## chris128

When you want to use an access token (http://msdn.microsoft.com/en-us/libr...(v=VS.85).aspx) to launch the process as a specific user without having to provide the user's username and password (its not as unsecure as it sounds lol)

----------


## taigon

Alright Chris, I'm back and I'm going to redirect the input/output through a named pipe over the network :Smilie:  I'll let you know how it goes.

----------


## chris128

Cool  :Smilie:  almost a year ago exactly since I posed this thread lol I've been doing some work recently with launching processes in the user's session from a service on Windows 7 (as services run in their own session in Vista/Win7 and therefore any programs they launch will not be seen by the user). You may well already have a feature like that working (or might not be interested in the user seeing the new process) but if it helps I've put some of the code on my blog here: http://cjwdev.wordpress.com/2011/06/...-on-windows-7/

Let me know if it looks like it might be useful to you and I'll send you the source code for an app I wrote recently that uses this, as the code on the blog is just a basic example that doesn't deal with setting up the user's environmental variables for the new process and doesn't close all of the handles that need closing.

----------


## taigon

I've had the remote execution going for about a year now but they were solely interactive processes although the nice thing is that I could pull the security token from the Explorer process and launch the application as the currently logged in user which is one feature that isn't included in most tools. Probably a security risk for corporations to some extent seeing as with that token I could impersonate and decrypt any data I want access to but it's pretty interesting. 

I spent the last 2 days figuring out how to incorporate your example into mine. After a ton of research and trial/error it seems that the only possible way to re-direct the standard output/input through a named pipe is to first create a standard pipe and associate the handles the way you have to the process. First you need to create a server pipe on the client side of the application. As the output is being obtained from the process, you can connect to it from the server side and provide the output. I now have this working both ways for the input and output. I'm about half way done compiling a library that I have absolutely no problem with sharing with everyone which will allow you to call a single static function to create a new process remotely. There are 2 events so far which indicate when standard output has been received and when process details have been received by the client end of the named pipe so that you can hook onto those events and output them to a textbox or whatever you want.

I still need to add in the code to launch the application as the SYSTEM account and as the currently logged in user because I decided to completely re-do this tool in C# instead of VB as I found that my level of understanding this API has considerably increased since switching languages.

I will be sure to update you as soon as possible. This is my main focus now because I need to incorporate this function into another large application Ive developed.

----------


## chris128

thanks for the update, look forward to seeing the finished library. By the way you don't have to grab the access token from explorer, you can just use WTSQueryUserToken and pass in the user's session ID (which you can get from WTSGetActiveConsoleSessionId). I haven't seen your code but all the other examples I've seen online where people get the token from explorer they then have to mess around duplicating the handle and doing various other things, so personally I find this method to be neater but hey if your code is working fine as it is then probably no point changing it.

----------


## moti barski

what is telnet (mentioned in post #1) ?

----------


## taigon

I'm actually going to try out those 2 methods you have mentioned instead because I've been hearing that some virtual Windows environments have several explorer.exe processes running on the same machine which would definately cause a problem. I'd prefer to obtain the access token another way so that this tool can be used in these scenarios as well.

The finished library will contain the remote server executable embeded as a resource. The client will make a copy of the executable on the remote systems Admin$ share, create and start a new service using WMI, start a command pipe server loop to accept process start commands to obtain process information and StdIn commands to redirect to the process itself. The client just has a single loop to send out the required process information and then it creates a new named pipe server to receive StdOut data along with process start and termination return values. So I'm hoping that once this is complete, anyone can just import my library and call a function like 

public static Create(string Username, string Password, string Domain, string Process, string Arguements, bool StartInteractive, bool PromptUser, bool WaitForProcessExit)

and then hook onto these 2 events

private delegate void StdInData_Received(object sender, StdInData_Received_EventArgs e);
public static event StdInData_Received StdInData_Received_Event;

private delegate void ProcessInfo_Received(object sender, ProcessInfo_Received_EventArgs e);
public static event ProcessInfo_Received ProcessInfo_Received_Event;


and receive the string data from the custom event arguement class making it easy to update a textbox or whatever and nothing to really customize other than your interface.

Afterwards the server notifies the client to stop processing and the client will stop and remove the service and delete the server executable. I also coded it so that it will accept a maximum of 10 connections to the named pipe server in order to run several processes remotely on the same machine at a time. The server just keeps track of the processes in an array and communicates the index back to the client. When the client sends commands back it states which part of the array contains the process it is working with. When a process exits, it loops through all of the other process arrays and notifies each client that the array has been resized and what the new index is for it's processing. But now that I think of it, I forgot to make the client re-create it's server pipe with the new index number. I haven't had too much time to test everything yet because I did most of this all from scratch in less than a days worth of time. Also having to port from VB to C# can be annoying because of some of the syntax. In your example you have a line when sending commands to the StdIn Pipe that uses something like String & VbCrLf & VbCr. Well I got stuck for probably an hour because of it. LoL. C# it was String + "\r\n\r";

----------


## chris128

haha yeah its always the stupid little things that take a long time :P

As for the multiple explorer.exe processes, that will only happen if multiple people are logged on (which can happen easily on Windows 7 as it now supports fast user switching even on a domain, or on any server running Terminal Services). So you need to think about how you want to handle that situation - if your app is supposed to launch a process interactively so that the logged on user can see it, what should it do if multiple people are logged on? Should it just launch it for the person logged on to the console session (which is usually the person physically sat at the machine using it, but in the case of terminal servers this will often be no one as only admins would ever log on to the console session), or should it launch the process once for every logged on user, or should it just fail because it is impossible to know which session to launch the process in? The method I outlined in that blog post will just launch the process in the console session. I'll post the source code for my app that uses this technique in another post in a sec. I've only tested it on Windows 7 though as all of our machines at work are now Windows 7, so no idea if it will work on XP correctly.

Oh and also I would loose the WMI stuff - a lot of places don't allow WMI through their workstations firewalls plus WMI is generally considered quite slow (and in my experience unreliable). You could just use the CreateService API (its in my Windows API Library if you are interested) as I believe that only requires the same ports to be open in the firewall that would already need to be open for you to get to the ADMIN$ share. Test it out though obviously, as it may also require the Remote Registry service to be running, which it isnt by default in Windows 7 (but you can easily start the service remotely from your code if not). Which brings me on to my next point...

Make sure you test this on XP and Windows 7 if you are going to be offering this to the developer community to use, as there are major differences between the two when talking about services and you will probably find you need to do things slightly differently depending on which OS the code is running on.

----------


## chris128

Here's the code for the app I wrote that is purely designed to launch a specified process in the desktop session of the logged on user (the console session user):


vb.net Code:
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 & _
                          "Developed by Chris Wright (cwright@cjwdev.co.uk)" & 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

----------


## taigon

Nice, I'm going to try that out once I get around to adding the option to launch as the current user into my tool. Thanks Chris. I can't wait to get this finished! lol. Need more time in a day.

----------


## taigon

Hey Chris,

I really need some help right now. I haven't tried your last posting yet because I'm having an issue with the named pipe.

The client side application opens a server based "Response" pipe for new output from the server and process information.

The server side just has the server based "Command Pipe" to accept new commands.

If I run the server on a remote machine with the same account as the client, the communication back and forth between the 2 server pipes works perfectly. When I start the server on the remote machine as the SYSTEM account and host it as a service, when the server tries to reply back to the client's SERVER pipe, it get's the access denied message because the default security descriptor of the named pipe created on the client side is using defaults which doesn't include a remote machines SYSTEM account.

I managed to create a new security descriptor and pass it to the SECURITY_ATTRIBUTES structure but when I call CreateNamedPipe it fails indicating that I am accessing protected memory.



```
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateNamedPipe(string lpName, uint dwOpenMode,
           uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize,
           uint nDefaultTimeOut, SECURITY_ATTRIBUTES lpSecurityAttributes);
```

The orginal API import set the lpSecurityAttributes type as an IntPtr and the creation of the pipe was fine. I changed it to the SECURITY_ATTRIBUTES type instead so I can pass the new security attributes with the custom security descriptor.



```
[DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = false)]         
        private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor([In] string StringSecurityDescriptor, [In] uint StringSDRevision, [Out] out IntPtr SecurityDescriptor, [Out] out int SecurityDescriptorSize); 

private const string SECURITYDESCRIPTORSTRING = "O:WDG:WDD:(A;;FA;;;WD)";

            /////////////testing
            IntPtr securityDescriptorPtr = IntPtr.Zero;
            int securityDescriptorSize = 0;

            bool Result = ConvertStringSecurityDescriptorToSecurityDescriptor(SECURITYDESCRIPTORSTRING, 1, out securityDescriptorPtr, out securityDescriptorSize);
            
            SECURITY_ATTRIBUTES SA = new SECURITY_ATTRIBUTES();
            SA.nLength = Marshal.SizeOf(SA);
            SA.bInheritHandle = false;
            SA.lpSecurityDescriptor = securityDescriptorPtr;
            /////////////////////////


            //Create the command pipe handle.
            int PipeHandle = CreatePipe("\\\\.\\pipe\\nRPInPipe" + RemoteProcessIndex.ToString(), (uint)BUFFERSIZE, 10, SA);    

        private static int CreatePipe(string PipeName, uint BUFFERSIZE, uint MaxConnections, SECURITY_ATTRIBUTES SecurityAttributes)
        {
            uint openMode = (int)PIPE_ACCESS_DUPLEX | (uint)FILE_FLAG_WRITE_THROUGH;
            uint pipeMode = PIPE_WAIT | PIPE_TYPE_MESSAGE;

            return CreateNamedPipe(PipeName, openMode, pipeMode, MaxConnections, BUFFERSIZE, BUFFERSIZE, 10000, SecurityAttributes).ToInt32();
        }
```

If I change the API import back to an IntPtr type for the securityattributes and pass IntPtr.zero, it will work fine to create the pipe but with the securityattributes that are useless for what I want to do.

Do you know how to either create a new IntPtr to reference the SA structure that's being passed so I can use my original API or do you know why it is failing in the creation of the pipe when I pass the structure to it? Just so you know, the ConvertStringSecurityDescriptorToSecurityDescriptor is working fine and is returning the intptr to the security descriptor that was created.

Please help, lol. I've been trying to figure this out for hours now.

----------


## chris128

I'm not great with C# so sorry if you are already doing this and I just can't see it, but it sounds like you need to pass that argument (the SECURITY_ATTRIBUTES one) in ByRef rather than ByVal. I can explain why if you want but just try it first and see if it fixes the problem

EDIT: Just did some googling to see how you pass arguments ByRef in C# so I can give you an example of what I think you need to do now. Basically just change this line:



```
 
return CreateNamedPipe(PipeName, openMode, pipeMode, MaxConnections, BUFFERSIZE, BUFFERSIZE, 10000, SecurityAttributes).ToInt32();
```

to this:



```
return CreateNamedPipe(PipeName, openMode, pipeMode, MaxConnections, BUFFERSIZE, BUFFERSIZE, 10000, ref SecurityAttributes).ToInt32();
```

----------


## taigon

Hi Chris, I actually just figured it out. Now the only problem is my security descriptor string. I'll let you know as soon as possible.

----------


## chris128

Ah ok cool, was it what I said?

Oh and I hope you realise you have proper hijacked this thread now haha  :Smilie:

----------


## taigon

LoL, sorry about the hi-jacking. I got it to work properly now. Communication is working perfectly between the 2 server pipes.

First it was the structure for the SECURITY_ATTRIBUTES. Had to change it to a class like so.

        [StructLayout(LayoutKind.Sequential)]         
        internal class SECURITY_ATTRIBUTES
        {
            public int nLength;
            public SafeLocalMemHandle lpSecurityDescriptor;
            public bool bInheritHandle;
        }

I found a million examples of people having issues with this and had to combine info from a lot of them.

the lpSecurityDescriptor was change from an intptr to a custom class type "SafeLocalMemHandle".



```
        [SuppressUnmanagedCodeSecurity,
        HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
        internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeLocalMemHandle()
                : base(true)
            {
            }

            public SafeLocalMemHandle(IntPtr preexistingHandle, bool ownsHandle)
                : base(ownsHandle)
            {
                base.SetHandle(preexistingHandle);
            }

            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success),
            DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern IntPtr LocalFree(IntPtr hMem);

            protected override bool ReleaseHandle()
            {
                return (LocalFree(base.handle) == IntPtr.Zero);
            }
        }
```

and also my API had to be changed for CreateNamedPipe



```
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CreateNamedPipe(string pipeName,
            uint openMode, uint pipeMode, int maxInstances,
            int outBufferSize, int inBufferSize, uint defaultTimeout,
            SECURITY_ATTRIBUTES securityAttributes);
```

Then my SECURITYDESCRIOPTORSTRING

        private const string SECURITYDESCRIPTORSTRING = "D:A;OICI;GRGW;;;WD)";


And the API to convert the security descriptor string into a pointer.



```
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
            string sddlSecurityDescriptor, int sddlRevision,
            out SafeLocalMemHandle pSecurityDescriptor,
            IntPtr securityDescriptorSize);
```


Then the rest of the code worked.



```
            SafeLocalMemHandle securityDescriptorPtr = null;
            
            bool Result = ConvertStringSecurityDescriptorToSecurityDescriptor(SECURITYDESCRIPTORSTRING, 1, out securityDescriptorPtr, IntPtr.Zero);
            
            SECURITY_ATTRIBUTES SA = new SECURITY_ATTRIBUTES();
            SA.nLength = Marshal.SizeOf(SA);
            SA.bInheritHandle = false;
            SA.lpSecurityDescriptor = securityDescriptorPtr;
            /////////////////////////
            
            //Create the command pipe handle.
            int PipeHandle = CreatePipe("\\\\.\\pipe\\nRPInPipe" + RemoteProcessIndex.ToString(), BUFFERSIZE, 10, SA);
```

----------


## chris128

You didn't need to make the structure a class  :Smilie:  you could just pass the structure in ByRef, has the same effect. The reason it didnt work as a structure originally is because the API expects a *pointer*  to one of these structures, not the actual structure itself - which is why it works when you define the argument as IntPtr and not when you pass in the structure.

Structures are "Value" Types and classes are "Reference" Types - meaning when you pass a structure around you are passing the actual data, but when you pass a class around you are passing in a pointer to the data. My suggestion of passing the structure in ByRef (or just "ref" in C#) means that it will pass in a pointer to the structure - and equally by changing it to be a class instead of a structure you have achieved the same thing, as classes always get a pointer to them passed in (if you were to pass in a class ByRef then you would be using a pointer to a pointer).

You can't always use your method of just changing the structure to a class in every scenario, which is why I'm explaining all this  :Smilie: 

Glad to hear it is all working anyway!  :Big Grin:  I'm going to be making something very similar in the near future so I'm sure this will all come in handy

----------


## taigon

Hi Chris,

Actually, before I started messing around with everything, the only thing I did was try to pass the structure byref to the API function. This didn't work either. It may be though that the class for the SecurityDescriptor that I found is what was causing the problem. I don't know but I got it working, lol. Good to know about classes being entirely passed by reference and structures by value. I'm sure I read that already at some point but I forgot:P

Now I've realized why I had 2 separate executables for the server side. The function CreateProcessWithLogonW doesn't work properly from a processes launched by the local SYSTEM account but obtaining the currently logged on users security token first and launching a second "server type" processes embedded into the original server allows you to call CreateProcessWithLogonW under that users account, no matter if they have admin rights or not, and allows you to launch an application as another domain user.

If you have any suggestions for as to why I can't use this function under the SYSTEM account, please let me know. I'm not going to use that code provided to obtain the current users security token.

----------


## chris128

Don't use CreateProcessWithLogon then, use CreateProcessAsUser - this definitely works from a service running as Local System on Windows 7 as I used it the other day. No idea how well it works on XP though.

----------


## taigon

Hi Chris,

I tried your suggestion of using CreateProcessAsUser. It seems that it works perfectly fine to launch an interactive process on the remote desktop from the SYSTEM account but as soon as I try re-directing the standard output and input, etc, the process starts and then ends fairly quick with a startup code of 0 and an exit code of 0. The redirection of the output works perfectly fine use CreateProcess. Any ideas? I want to be able to launch the process interactive for use on the remote system and non-interactive, in which it re-directs the output to my named pipe back to the client. Very strange.

----------


## chris128

I think I recall having a similar problem when I was writing the code in the original post in this thread, I'm sure it was something to do with the handles to the named pipe being inherited (or not being inherited) by the child process. Sorry I can't give you anything more specific than that... 

EDIT:
I think that's what this bit of the code was for:

vb.net Code:
'Duplicate the "write end" handle for the input pipe and make the duplicated handle non inheritable
        If Not WindowsAPIs.DuplicateHandle(New HandleRef(Me, Process.GetCurrentProcess.Handle), TmpWriteInputHandle, _
                               New HandleRef(Me, Process.GetCurrentProcess.Handle), CurrentInputHandle, 0, False, WindowsAPIs.DUPLICATE_SAME_ACCESS) Then
            Throw New System.ComponentModel.Win32Exception
        End If
         'Duplicate the "read end" handle for the output pipe and make the duplicated handle non inheritable
        If Not WindowsAPIs.DuplicateHandle(New HandleRef(Me, Process.GetCurrentProcess.Handle), TmpReadOutputHandle, _
                               New HandleRef(Me, Process.GetCurrentProcess.Handle), CurrentOutputHandle, 0, False, WindowsAPIs.DUPLICATE_SAME_ACCESS) Then
            Throw New System.ComponentModel.Win32Exception
        End If
         'Close the original handles we got from CreatePipe before we start the process so that it does 
        'not inherit these handles.
        TmpReadOutputHandle.Close()
        TmpWriteInputHandle.Close()

----------


## taigon

Hi Chris,

I already have the duplication of the handles in place in the same manor. I'm going to try this actually and see if it works. Very strange because the same code works fine with just CreateProcess.

1. use WTSGetActiveConsoleSessionId to get the ID of the current active Windows session at the console (i.e. the machine keyboard and display, as opposed to WTS sessions).

2. use WTSQueryUserToken to get the token for that session.

3. use DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary, &hTokenDup) to duplicate that token.

4. use CreateEnvironmentBlock to create an environment that you will be passing to the process.

5. use CreateProcessAsUser with the duplicated token and the created environment. Actually, we use CreateProcessAsUserW, since the A version had some sort of bug on some older systems.

6. Don't forget to CloseHandle on the various tokens, etc, and to DestroyEnvironmentBlock the environment.

----------


## taigon

Hi Chris,

After 3 days of messing around with this, I finally got CreateProcessAsUser to launch a process as another domain user under the system account and can perform this operation interactively and non-interactively.



```
                    IntPtr hProcessToken = IntPtr.Zero;                                        
                    TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();

                    tp.Privileges = new LUID_AND_ATTRIBUTES[1];
                                      
                    Result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, ref hProcessToken);

                    if (!Result | hProcessToken == IntPtr.Zero)
                        Console.WriteLine("OpenProcessToken Failed: " + Marshal.GetLastWin32Error());

                    Result = LookupPrivilegeValue(null, SE_TCB_NAME, ref tp.Privileges[0].Luid);

                    if (!Result)
                        Console.WriteLine("LookupPrivilegeValue Failed: " + Marshal.GetLastWin32Error());

                    tp.PrivilegeCount = 1;
                    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

                    Result = AdjustTokenPrivileges(hProcessToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);

                    if (!Result)
                        Console.WriteLine("AdjustTokenPrivileges Failed: " + Marshal.GetLastWin32Error());

                    Result = LogonUser(p.Process.UserName.Split('\\')[1], p.Process.UserName.Split('\\')[0], p.Process.Password, (int)Logon32Type.Interactive, 0, out hProcessToken);

                    Result = CreateProcessAsUserW(hProcessToken, Process, string.Empty, SA, SA, true, (UInt32)32, IntPtr.Zero, System.IO.Directory.GetCurrentDirectory(), si, ref ProcessInfo);
```

----------


## taigon

Hi Chris,

And now I've finally got the option to launch the process as the currently logged in user working interactively and non-interactively.



```
                    uint ConsoleSessionId = WTSGetActiveConsoleSessionId();                                     
                    
                    IntPtr lpBuffer = IntPtr.Zero;
                    int Count = 0 ;                    
                    //IntPtr i;
                                        
                    Result = WTSQueryUserToken(ConsoleSessionId, out UserToken);
                                        
                    if (!Result)
                        Console.WriteLine("WTSQueryUserToken Failed: " + Marshal.GetLastWin32Error());

                    //If no token obtained then no one is logged onto the console. Check for active remote desktop session.
                    if (UserToken == IntPtr.Zero)
                    {
                        Result = Convert.ToBoolean(WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref lpBuffer, ref Count));

                        int dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));

                        Int64 current = (int)lpBuffer;
                                                
                        if (Result)
                        {
                            for (int i = 0; i < Count; i++)
                            {
                                WTS_SESSION_INFO session = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));

                                current += dataSize;

                                if (session.State == WTS_CONNECTSTATE_CLASS.WTSActive)
                                    Result = WTSQueryUserToken((uint)session.SessionID, out UserToken);

                                if (!Result)
                                    Console.WriteLine("WTSQueryUserToken Failed: " + Marshal.GetLastWin32Error());                                
                            }
                                                                                
                            WTSFreeMemory(lpBuffer);                                                      
                        }
```

Now I can complete the easy stuff and soon I'll have a library available for use within anyone else's .Net application that functions just like PSEXEC on WinXP machines. I haven't had a chance to test on any newer OS's yet but this is working perfectly now without requiring the use of a second executable.

----------


## chris128

Cool, glad to hear you got it all working  :Smilie:  You may find you run into problems on Vista/Win7 though as services work a bit differently in that they all run in a dedicated isolated session (session 0). Let me know how it goes though  :Smilie:

----------


## taigon

This is the reason why I created a second executable to run with the server service to launch a new gui application as a different user. Now I have to do this again.

http://stackoverflow.com/questions/2...ui-application

----------


## chris128

I dont see why you need a second executable though, I mean why launch that second executable instead of just launching whatever process that executable is going to launch directly? The way I see it you either do this:
Service.exe -> [USER'S SESSION] -> Launcher.exe -> TargetProgram.exe
or just do this:
Service.exe -> [USER'S SESSION] -> TargetProgram.exe

Unless I'm missing something... I know I managed to get the second approach working on Windows 7 without a problem. In fact launching a GUI app in the user's session directly from a service was easier than having the service launch a command line app that then launched the GUI app in the user's session because of console inheritance issues.

----------


## taigon

Service.exe -> [CURRENTLY LOGGED IN USER's SESSION]-> TargetGUIApplication.exe works fine.

Service.exe -> [LOGONUSER ALTERNATE CREDENTIALS]->TargetGUIApplication.exe does not work. It launches the program it shows up transparent and you can't use the GUI application.

Service.exe -> [LOGONUSER ALTERNATE CREDENTIALS]->TargetConsoleApplication.exe works perfectly fine.

It seems that Service processes running as SYSTEM can't properly launch GUI applications interactively so therefore I will have to do...

Service.exe -> [CURRENTLY LOGGED IN USER's SESSION] -> Launcher.exe -> [LOGONUSER ALTERNATE CREDENTIALS] -> TargetGUIApplication.exe

----------


## chris128

Oh right, that seems odd... I might have a play around and see if I can get it working.

----------


## taigon

Let me know if you do so I can eliminate this second server executable. LoL

----------


## taigon

Hey Chris do you know off hand what character string would be used to send a return code to the stdin without specifying a command first. I currently use command + "\r\n" + "\r" but remOving the command prefix doesn't send a new carriage return.

----------


## chris128

Sorry I'm not quite sure what you mean - can you explain and give me some examples?

----------


## taigon

Lol sorry I'm in training at work right now. In your examPle to redirect input and output, the writefile function to send input to the process has a carriage return and a line feed followed by another carriage return suffixing the string to send to the process.

----------


## chris128

Yeah but I don't get what you mean when you say "send a return code to stdin". If you want to send a number to it then just send it as a string - but I'm assuming you already know that so I think I'm missing something

----------


## taigon

Ok, I'm back at my desk now. What I was trying to say is how can I send just a single carriage return to the stdin handle without any actual data.



```
        public static void SendInput(string message, ref RemoteProcessPipes.CommandPipeData p)
        {
            if (p.CurrentProcessHandle != null && !p.stdIn.IsClosed)
            {
                byte[] MessageBytes = Encoding.GetEncoding(850).GetBytes(message + "\r\n" + "\r");
                uint BytesWriten = 0;

                if (!WriteFile(p.stdIn, MessageBytes, (uint)MessageBytes.Length - 1, ref BytesWriten, 0))
                    OutPipe.Connect(@"\\" + p.ClientHostName + @"\pipe\nRPInPipe" + p.Index.ToString(), "There was a problem sending input to the process. The last error reported was: " + new System.ComponentModel.Win32Exception().Message);

                p.ThreadSignal.Set();
            }
            else
                OutPipe.Connect(@"\\" + p.ClientHostName + @"\pipe\nRPInPipe" + p.Index.ToString(), "No process to send input to");
        }
```

as you can see, the message is converted into bytes with a \r\n ,"vbcrlf", + a \r, "vbcr", suffixing the message. What if I just want to send the carriage return only. For instance, if the remote process is using a Console.ReadLine(), waiting for a carriage return, I want to send just the "\r\n" + "\r" but it doesn't seem to work.

----------


## chris128

This works perfectly fine for me to make a program that is waiting for input (e.g console.readline) continue


vb Code:
Dim MessageBytes() As Byte = System.Text.Encoding.GetEncoding(850).GetBytes(vbCrLf & vbCr)

This is what you get for switching to C#  :Big Grin:

----------


## taigon

LoL, Ok, I'm gonna try to figure this one out then. Almost out of time for the day, lol. I switched to C# mostly because every job I look for out their in relation to programming are looking for C# .Net programmers. Not too many VB programmer positions around here. At least if I know both of them, and master 1 of them, I should be good :Smilie:

----------


## chris128

Yeah I was only joking lol I have the luxury of working for myself (when it comes to programming anyway) so I can use whatever language I want, but if I was thinking of going for a programming job I would certainly learn C#  :Smilie:

----------


## taigon

Ok, Chris, I'm stuck.

Just so you know, I don't use ANY WMI API for the windows service component of this tool.

The main problem is that after I create a service on a remote machine, I can't start it on a Windows 7 machine....This all works perfect on XP.

The error during the startup process returns 1053, "The service did not respond to the start or control request in a timely fashion".

It seems that the same error is reported when you try to start the same service using the windows management console.

This is the code that starts the service...



```
	
	[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private static extern bool StartService(IntPtr hService, Int32 dwNumServiceArgs, string lpServiceArgVectors);

public static bool Start(IntPtr p_Service)
	{
		if (StartService(p_Service, 0, null) == true) {
			return true;
		} else {
			Console.WriteLine("Error starting service: Error#" + Marshal.GetLastWin32Error().ToString());
			return false;
		}
	}
```

I'm assuming that this is more a problem with the service executable rather than the way I'm trying to start the process? Do you have any ideas because I've found way to many google results for this error without any solutions. Is it because the windows service is trying to be run with the interactive service option turned on?

----------


## chris128

Yeah that is definitely a problem with the service EXE rather than the way you are trying to start it. It basically means that you have something in your service's "Start" method (or it might be called OnStart or something like that) that hangs or takes too long. That Start method needs to complete and return within a maximum of I think 30 seconds - most services start within a couple of seconds. So I would do some debugging within that method of your service and see where it is getting stuck on the Windows 7 machines.

Oh and by the way, you can start services via managed .NET code, you don't need to use Windows API for that  :Smilie:  Just use the System.ServiceProcess.ServiceController class (although I guess it might be easier to use that Windows API if you already have a handle to the service from creating it)

----------


## taigon

Hi Chris, I did some digging and what I think the problem might be is that on Windows 7 machines, services are no longer permitted to communicate interactively with any window stations and my service was explicitly creating the service as interactive and succeeding at doing so using windows API in a nice .net wrapper that I created. I think it may be failing on start due to the interactive parameter so I'm going to test this tomorrow. This is all I can think of because the xp service starts in seconds.

----------


## chris128

Services can still be marked as interactive in Windows 7 - they will still start, but just when they try to display something interactively the user will be prompted to switch to another desktop to see the message. I think this is to encourage developers not to show things interactively from services (as it is not a nice experience for the user having to switch to a separate desktop just to see your service's message) but to make sure users do still see the things that services display in case it is something important

----------


## taigon

Oh ok, so if that is true then my interactive prompts should still technically work. I haven't had a chance to test on Win 7 until now but I need to at least get the service started. I can't even start my service manually from the computer management console. It works perfect in XP. Do you have an example of any working windows 7 services that can run as the system account?

----------


## chris128

Can you post the code that is in your OnStart method in the service EXE?

----------


## taigon

I just recently changed it as you can see from the commented line and I just added the base.onstart(args). I haven't gotten to test it yet.



```
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.ServiceProcess;

namespace NRP
{
    internal sealed class NRPServer : ServiceBase
    {
        static void Main()
        {
            ServiceBase.Run(new NRPServer() { ServiceName = "NiRDs Remote Process", CanStop = true });
            //NRPServer.Run(new NRPServer() { ServiceName = "NiRDs Remote Process", CanStop = true });
        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);

            Thread t = new Thread(Start);
            t.IsBackground = true;
            t.Start();
        }

        private void Start()
        {
            Thread t = new Thread(RemoteProcessPipes.StartCommandPipe); //New background thread to listen for new connections..
            t.IsBackground = true;
            t.Start(); //Start the thread

            while (t.IsAlive) 
                Thread.Sleep(1000); //Loop in the main thread until the primary server pipe thread has ended. Then exit.
        }
    }
}
```

----------


## chris128

Well there's your problem - the Start method is going to continuously loop until the other thread ends (which presumably doesn't happen for a while) so the Start method never finishes. Why do you need to do that loop? Why not just start the other thread and that's it?
EDIT: Oh sorry, I didn't look at the OnStart method, didn't realise that Start method was being run in another thread (though I still don't see the need for that loop)

----------


## taigon

True, that loop does look strange and I don't know why I added it but it wasn't causing an issue in XP. The OnStart method of the service itself completes after starting the new thread....I dunno. I'm going to test again later. lol

----------


## chris128

Did you create this project by using the Windows Service project template in Visual Studio or did you just use a console app and edited that to make it a service?

----------


## taigon

I'm actually using visual c# express which doesn't give me a service template so I just started a new console app and inherit the servicebase.

----------


## chris128

Hmm I would be tempted to try it using the proper service template, I'll send you a service project and you can use it to test with if you want

----------


## taigon

Yes I'll definitely take a copy of a service project that works and ill try compiling it and running it on a win7 machine that we have at work. It might just have something to do with the custom win7 image that they create.

----------


## chris128

Blank service project attached

----------


## taigon

Hey Chris,

So what I did was copied that service template and compile a service that literally does nothing. I tested this service on an XP machine and it started fine. I tried on a Windows 7 machine, it tries to start, but immediately, within milliseconds, replies back stating that the service did not start in time.....

Does it matter that I'm compiling the service executable on a windows XP machine? I didn't think that would make a difference.

----------


## chris128

That is weird. I've created services with that same template in the past that have worked fine on Windows 7. Are you sure its not just your Windows 7 test machine that has a general problem? Does it definitely have the version of the .NET Framework that you are targeting installed on it for example?

EDIT: I just tested it with that exact template that I sent you, literally didn't change anything and just compiled it. Then I used the sc.exe built in to windows (which calls the CreateService API) to create the service like so:


```
sc create displayname= test binpath= C:\ExampleService.exe
```

and then I started it and it started fine (this is on a Windows 7 x64 machine)

----------


## taigon

I have just recently made a slight change to the way the service starts. I'm just waiting to test it. I will make sure that .Net 4.0 is installed on the test Win7 machine. Also, I should probably try just compiling a standard executable and see if it runs normally on the system. You don't think that there would be any issue with compiling it on a WinXP machine do you?

----------


## chris128

No there should be absolutely no difference in compiling it on an XP machine compared to Windows 7 - its the same compiler that will be running and that has nothing to do with the OS or the development machine because it compiles to MS IL code rather than machine code.

----------


## taigon

Yeah, that's what I thought. I'll give it a try again soon with the new service start code.

----------


## shragel

Is there a way to modify the startinconsolesession.exe to login a user if no console is running?

----------


## chris128

> Is there a way to modify the startinconsolesession.exe to login a user if no console is running?


Not easily...

----------


## taigon

Hey Chris,

Just to update you on my remote execution tool, I have sucessfully created a working application with a GUI interface that allows the creation of a remote processes on a Windows XP machine as either the currently logged in user, a domain user, or the SYSTEM account with console output re-direction through a named pipe from server to client. Also interactively on a Windows 7 machine, as the SYSTEM account on the seperate SYSTEM account's window station, and non-interactively for the domain account.

The issue with locating the security context of the currently logged in user was resolved by using API rather than using the security token of explorer.exe, to make a query on whether or not someone is currently using the console session or if it is a remote desktop session. This tool will NOT work if there are multiple active remote desktop sessions as currently it is targetting the first one available that is in use. I could potentially prompt the user with a list of active remote desktop sessions to make a choice, listing the userid's of each session for clarity, but I haven't gotten around to this yet.

If no one is logged into the machine then the option for an interactive process is disabled.

The tool also distinguishes which version of windows is running on the remote machine through the registry. Basically to use this tool you would need admin rights to the remote registry, access to create and modify services and write access to the Windows System32 folder on the remote machine.

Also, I did not implement any support for Windows 2000 OS.

There is no point in targeting the Current Users security context to make a process run interactively on Windows 7 OS unless you want to install some kind of service on every single machine that you want to have the process interact with the user for and make it start under the security context of the user when they log in using a policy or something. Our security policy at work doesn't permit us to run processes non-interactively as another user so I didn't implement that.

I also included an option to prompt the remote user to start the process with a default timeout that notifies the client application. This option can be specified when calling the function to remotely start the process from the DLL. Also you can specify custom prompt strings in case you want a different message for the user to see when accepting the request to start the process. I did this because I also created another executable that installs a print queue so I copy the executable to the remote machine, use the remote tool to start the process and provide queue information, and prompt the user to install the particular queue name onto the machine.

I've tested this remote execution with MSIExec, cscript, and wscript processes to install/un-install application on a remote machine or queue the install or removal in the SMS delivery client that our workstations have and receive back a return value from the process exiting.

This tool was implemented into a much larger network admin application that I have also created that is now being used by several hundred IT analysts in my place of work, that is under copy write protection, but the remote execution portion of the application is owned by myself.

As soon as I have time I am going to re-create the visual interface of the copy written code for use with the pre-compiled library that does all the real work and make a few modifications with some more testing and I will distribute a copy of both the application and library as a package for others to use.

So as a conclusion to the entire topic of creating an application that functions in the same way as the PSEXEC binary, it is 100&#37; possible to make this work using either MS Visual C# or MS Visual Basic .NET programming languages.

----------


## chris128

I'm impressed  :Big Grin:  and glad to see you got it all working in the end. Hope my contributions did help a little  :Smilie:  I would love to see your library if you get chance to release it too

----------


## taigon

You certainly did help very much with several aspects especially re-directing the standard output/input. It took a while to get it through the named pipe to the client machine but it works flawlessly now. I'm still debating on sharing any of the actual code because there is a LOT of money in having that type of source code secured but I'm definately willing to help anyone else out that needs some tips and release a copy of the binary when I have the time. It may be a few more months before I release it but it will definately be worth it because of the capabilities it has. I'll keep you up to date.

----------

