5

I am trying to execute powershell commands in C++ and get its output through pipes.

My program works perfectly for cmd.exe. However, when I try to do the same thing with powershell.exe, I only get "W" as an output.

I have commented the line in the code below that needs to be modified to execute powershell.exe Below is my code that works for cmd.exe:

        HANDLE stdinRd, stdinWr, stdoutRd, stdoutWr;
        DWORD readFromCmd();
        DWORD writeToCmd(CString command);
        int main(int argc,char* argv[])
        {
            SECURITY_ATTRIBUTES sa={sizeof(SECURITY_ATTRIBUTES), NULL, true};
            if(!CreatePipe(&stdinRd, &stdinWr, &sa, 1000000) || !CreatePipe(&stdoutRd,&stdoutWr, &sa, 1000000)) 
            {
                printf("CreatePipe()");
            }
            STARTUPINFO si;
            PROCESS_INFORMATION pi;
            GetStartupInfo(&si);
            si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
            si.wShowWindow = SW_HIDE;
            si.hStdOutput = stdoutWr;
            si.hStdError = stdoutWr;                  
            si.hStdInput = stdinRd; 

    // If powershell.exe is invoked, it does not work, however works for cmd.exe    
            //if(!CreateProcess(TEXT("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"), NULL, NULL, NULL, TRUE,0, NULL, TEXT("C:\\Windows"), &si, &pi))
            if(!CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"), NULL, NULL, NULL, TRUE,0, NULL, TEXT("C:\\Windows"), &si, &pi))
            {
                printf("CreateProcess()");  
                printf("CreateProcess() failed in initiatecmd(CString,int) method",0);
                return -1;
            }

            writeToCmd(L"dir");
            Sleep(1000);
            readFromCmd();
            getchar();
            TerminateProcess(pi.hProcess,0);
            CloseHandle(pi.hProcess);
            return 0;

        }
        DWORD writeToCmd(CString command)
        {
            DWORD ret;
            DWORD numberofbyteswritten;
            command.AppendChar('\n');

            LPSTR command_ANSI;
            int size_needed = WideCharToMultiByte(CP_UTF8,0,command.GetString(),-1,NULL,0,NULL,NULL);
            command_ANSI = (LPSTR) calloc(1, ( size_needed + 1 )* sizeof(char));
            WideCharToMultiByte(CP_UTF8,0,command.GetString(),-1,command_ANSI,size_needed,NULL,NULL);

            ret = WriteFile(stdinWr, command_ANSI, size_needed-1, &numberofbyteswritten, NULL);
            if(ret==0)
            {
                printf("WriteFile()");
                printf("WriteFile() method failed in writeToCmd(CString) method",0);
                return 0;
            }

            CStringA temp;
            temp.Format("%d",numberofbyteswritten);
            temp += " bytes (Command:";
            temp+=command;
            temp+=") are successfully written to cmd";
            printf("%s",temp);
            return 1;
        }

        DWORD readFromCmd()
        {
            CString output_jsonstring;
            DWORD ret;
            DWORD dwRead;

            while(1)
            {
                DWORD totalbytesavailable;

                if(PeekNamedPipe(stdoutRd, NULL, 0, NULL, &totalbytesavailable, 0) == 0)
                {
                    printf("PeekNamedPipe()");
                    printf("PeekNamedPipe() method failed in responseHandler() method",0);
                    return 0;
                }
                if(totalbytesavailable != 0)
                {
                    char output_cmd[1000000];
                    if(ReadFile(stdoutRd, output_cmd, min(1000000,totalbytesavailable), &dwRead, NULL)==0)
                    {
                        printf("ReadFile()");
                        printf("ReadFile() method failed in responseHandler() method",0);
                        return 0;
                    }
                    int min = min(1000000,totalbytesavailable);
                    output_cmd[min]='\0';
                    printf("\n%s",output_cmd);
                }   
                if(totalbytesavailable == 0)
                    break;

                Sleep(100);
            }
            return 1;
        }

If the CreateProcess() is used for powershell, it does not work the same way, but I get only W as output.

What is the reason for this? And How to get over this problem?

EDIT 1 : If I display the output_cmd in a loop character by character as output_cmd[i] where i = 0 to strlen(output_cmd), I get an output as given below:

i n d o w s P o w e r S h e l l C o p y r i g h t ( C ) 2 0 1 4 M i c r o s o f t C o r p o r a t i o n . A l l r i g h t s r e s e r v e d .

P S C : \ W i n d o w s >

and the application hangs after that! It doesn't take in any input, or give any output after that!

K K
  • 61
  • 1
  • 4
  • I would imagine the difference being Powershell returning an array of objects instead of a plain string. What output do you get by using this command `dir | out-string`? – Lieven Keersmaekers Jun 20 '16 at 11:47
  • btw : *"redirecting to pipes"* doesnt make any sense since every input/output of every windows process already **is** piped, thats how process I/O works - even Linux works like that. You can change attached pipes, chain or even merge them but there wont be anything else besides pipe I/O. Most of the time, *unnamed pipes* are used - you can switch to *named pipes* instead if you like ... – specializt Jun 21 '16 at 10:47
  • also `while(1)` is *bad code* and **will** make your application crash at some point. Quite a few people here on SO will tell you otherwise - but quite a few people also are hobby programmers ... just a friendly advice. Dont use endless loops. Ever. – specializt Jun 21 '16 at 12:28
  • @specializt: Thanks! I will take that advice. But for now, could you help me with the problem I am facing? – K K Jun 21 '16 at 12:36
  • Is there a problem reading and Writing input and output as Strings? – K K Jun 21 '16 at 12:41
  • it has been a long time for me but AFAIR it isnt necessary to convert between ANSI and WCHAR - as long as both sides understand it ... i remember reading and writing WCHAR-strings without problems. Your bug could be inside of your conversion-functions. Have you already debugged and checked for that? – specializt Jun 21 '16 at 13:06
  • @specializt : I have been using that conversion from Unicode to ANSI code quite sometime now. It isnt the problem. Besides, this is a perfectly working code for cmd.exe. It only hangs when powershell.exe comes in! i guess there should be the problem then! – K K Jun 21 '16 at 13:17
  • indeed ... cmd.exe is ancient, powershell is quite recent, i would'nt be surprised if it expects (and writes) native Unicode / WCHAR – specializt Jun 21 '16 at 13:41
  • @specializt: It gives me the same output with Unicode. I checked that out as well! What do you suggest? – K K Jun 21 '16 at 13:52
  • @LievenKeersmaekers: I get the same output! – K K Jun 21 '16 at 13:54
  • I read your reply yesterday but that was my one and only hunch, sorry. I didn't reply further to not clutter your comments to much. – Lieven Keersmaekers Jun 21 '16 at 15:04

2 Answers2

0

You passed string to wrong place:

CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe")

actually the first parameter should be NULL: CreateProcess(NULL, TEXT("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")

shawn
  • 1
  • 2
0

Your main point of confusion seem to be around wide characters or byte characters. In classic ASCII strings, each character is one byte. Modern systems use Unicode, and the two most popular flavors are UTF-8 (popular on unix) and UTF-16 which most of the Windows API uses. Windows most often (always?) uses the little-endian variety, where the first byte is the lower 8 bits and the second byte is the upper 8 bits. In unicode, the first 127 codepoints are backward compatible with the first 127 characters of ASCII, so the letter "W" in ASCII is 0x57 and in UTF-16 it is 0x57 0x00.

You are mixing ReadFile with printf. ReadFile uses an explicit length for the buffer and bytes read, and so it can happily transfer UTF-16 as binary data. However, printf comes from an old tradition of ASCII strings that are terminated with a NUL byte. So from printf's perspective you are giving it a string of length 1 because the second byte is 0x00.

Have a look at this question about wide characters with printf to see what you should do differently.

By default, PowerShell writes UTF-16 to its console, where-as the old cmd.exe was still using ASCII strings. It turns out that PowerShell doesn't use it's input handle at all though, unless you pass the option -Command -. With that option however, it switches back to ASCII strings for output and input. So, all you really need to do is pass that command line option, and things should start working just like for Cmd.exe.

I was working on a perl module for this, not C++, but you might find my source code helpful.

BTW, I'm disturbed by the other mis-information on this page:

  • In Windows, Pipe handles, Console handles, and File handles each have different behavior and are not "all pipes". It is valid to say they are all Handles, and that you can read/write to each of them and use them for stdin/stdout/stderr of a program.

  • while(1) { if (!condition) break; ... } is absolutely functionally equivalent to while(condition) { ... } and there is no reason to avoid it aside from style. If your condition doesn't comfortably fit in a one-line expression it is perfectly reasonable to use while(1).

  • You should NOT set the first argument of CreateProcess to NULL because it un-ambiguously tells windows which program you intend to execute. If you pass it in the second argument then you need to make sure it is quoted properly because a path with a space in it could run a different program than intended or even become a security bug. You don't have to use the first argument, but do it if you can.

M Conrad
  • 183
  • 8