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!

11
  • 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? Commented Jun 20, 2016 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 ... Commented Jun 21, 2016 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. Commented Jun 21, 2016 at 12:28
  • @specializt: Thanks! I will take that advice. But for now, could you help me with the problem I am facing? Commented Jun 21, 2016 at 12:36
  • Is there a problem reading and Writing input and output as Strings? Commented Jun 21, 2016 at 12:41

2 Answers 2

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")

Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.