/ src / common / utils / exec.h
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  }