Redirect Input and Output of Powershell.exe to Pipes in C++

会有一股神秘感。 提交于 2019-12-12 14:23:51

问题


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!


回答1:


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




回答2:


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.



来源:https://stackoverflow.com/questions/37919984/redirect-input-and-output-of-powershell-exe-to-pipes-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!