exec.h
1 #pragma once 2 3 #define WIN32_LEAN_AND_MEAN 4 #include <Windows.h> 5 6 // disable warning 26471 - Don't use reinterpret_cast. A cast from void* can use static_cast 7 // disable warning 26492 - Don't use const_cast to cast away const 8 // disable warning 26493 - Don't use C-style casts 9 // Disable 26497 for winrt - This function function-name could be marked constexpr if compile-time evaluation is desired. 10 #pragma warning(push) 11 #pragma warning(disable : 26471 26492 26493 26497) 12 #include <wil/resource.h> 13 #pragma warning(pop) 14 15 #include <optional> 16 #include <string> 17 18 inline std::optional<std::string> exec_and_read_output(const std::wstring_view command, DWORD timeout_ms = 30000) 19 { 20 SECURITY_ATTRIBUTES saAttr{ sizeof(saAttr) }; 21 saAttr.bInheritHandle = false; 22 23 constexpr size_t bufferSize = 4096; 24 // We must use a named pipe for async I/O 25 char pipename[MAX_PATH + 1]; 26 if (!GetTempFileNameA(R"(\\.\pipe\)", "tmp", 1, pipename)) 27 { 28 return std::nullopt; 29 } 30 31 wil::unique_handle readPipe{ CreateNamedPipeA(pipename, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &saAttr) }; 32 33 saAttr.bInheritHandle = true; 34 wil::unique_handle writePipe{ CreateFileA(pipename, GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) }; 35 36 if (!readPipe || !writePipe) 37 { 38 return std::nullopt; 39 } 40 41 PROCESS_INFORMATION piProcInfo{}; 42 STARTUPINFOW siStartInfo{ sizeof(siStartInfo) }; 43 44 siStartInfo.hStdError = writePipe.get(); 45 siStartInfo.hStdOutput = writePipe.get(); 46 siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; 47 siStartInfo.wShowWindow = SW_HIDE; 48 49 std::wstring cmdLine{ command }; 50 if (!CreateProcessW(nullptr, 51 cmdLine.data(), 52 nullptr, 53 nullptr, 54 true, 55 NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE, 56 nullptr, 57 nullptr, 58 &siStartInfo, 59 &piProcInfo)) 60 { 61 return std::nullopt; 62 } 63 // Child process inherited the write end of the pipe, we can close it now 64 writePipe.reset(); 65 66 auto closeProcessHandles = wil::scope_exit([&] { 67 CloseHandle(piProcInfo.hThread); 68 CloseHandle(piProcInfo.hProcess); 69 }); 70 71 std::string childOutput; 72 bool processExited = false; 73 for (;;) 74 { 75 char buffer[bufferSize]; 76 DWORD gotBytes = 0; 77 wil::unique_handle IOEvent{ CreateEventW(nullptr, true, false, nullptr) }; 78 OVERLAPPED overlapped{ .hEvent = IOEvent.get() }; 79 ReadFile(readPipe.get(), buffer, sizeof(buffer), nullptr, &overlapped); 80 81 const std::array<HANDLE, 2> handlesToWait = { overlapped.hEvent, piProcInfo.hProcess }; 82 switch (WaitForMultipleObjects(1 + !processExited, handlesToWait.data(), false, timeout_ms)) 83 { 84 case WAIT_OBJECT_0 + 1: 85 if (!processExited) 86 { 87 // When the process exits, we can reduce timeout and read the rest of the output w/o possibly big timeout 88 timeout_ms = 1000; 89 processExited = true; 90 closeProcessHandles.reset(); 91 } 92 [[fallthrough]]; 93 case WAIT_OBJECT_0: 94 if (GetOverlappedResultEx(readPipe.get(), &overlapped, &gotBytes, timeout_ms, true)) 95 { 96 childOutput += std::string_view{ buffer, gotBytes }; 97 break; 98 } 99 // Timeout 100 [[fallthrough]]; 101 default: 102 goto exit; 103 } 104 } 105 exit: 106 CancelIo(readPipe.get()); 107 return childOutput; 108 }