/* * Wrapper over pragtical.exe that when compiled as pragtical.com provides * console ready stdout/stderr for normal behavior on CMD or PowerShell. */ #include #include #include #include #include /** * Get pragtical.exe absolute path relative to pragtical.com */ static void get_exe_filename(char *buf, int sz) { int len; wchar_t *buf_w = malloc(sizeof(wchar_t) * sz); if (buf_w) { len = GetModuleFileNameW(NULL, buf_w, sz - 1); buf_w[len] = L'\0'; // Convert wide string to multi-byte (UTF-8) if (!WideCharToMultiByte(CP_UTF8, 0, buf_w, -1, buf, sz, NULL, NULL)) { buf[0] = '\0'; } else { // Replace .com extension with .exe char *ext = strrchr(buf, '.'); if (ext && strcmp(ext, ".com") == 0) { strcpy(ext, ".exe"); } } free(buf_w); } else { buf[0] = '\0'; } } /** * Attaches to the given command stdin, stdout, and stderr and redirects them to itself. */ void executeCommand(const char *command, DWORD *exit_code) { // Create pipes for stdin, stdout, and stderr HANDLE hStdInRead, hStdInWrite; HANDLE hStdOutRead, hStdOutWrite; HANDLE hStdErrRead, hStdErrWrite; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; // Allow child processes to inherit the handles sa.lpSecurityDescriptor = NULL; // Create pipe for stdin if (!CreatePipe(&hStdInRead, &hStdInWrite, &sa, 0)) { fprintf(stderr, "Error creating stdin pipe\n"); return; } // Create pipe for stdout if (!CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0)) { fprintf(stderr, "Error creating stdout pipe\n"); return; } // Create pipe for stderr if (!CreatePipe(&hStdErrRead, &hStdErrWrite, &sa, 0)) { fprintf(stderr, "Error creating stderr pipe\n"); return; } // Set the write end of the pipes to not be inherited SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(hStdOutWrite, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(hStdErrWrite, HANDLE_FLAG_INHERIT, 0); // Prepare to create the process STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.hStdInput = hStdInRead; // Redirect stdin si.hStdOutput = hStdOutWrite; // Redirect stdout si.hStdError = hStdErrWrite; // Redirect stderr si.dwFlags |= STARTF_USESTDHANDLES; ZeroMemory(&pi, sizeof(pi)); // Create the child process if (!CreateProcess(NULL, (LPSTR)command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { fprintf(stderr, "Error creating process\n"); return; } // Close unused ends of the pipes CloseHandle(hStdInRead); CloseHandle(hStdOutWrite); CloseHandle(hStdErrWrite); // Input and output handling loops char buffer[4096]; DWORD bytesRead, bytesWritten; // Read input from the parent process and write to the child process while (true) { if (PeekNamedPipe(hStdInWrite, NULL, 0, NULL, &bytesRead, NULL) && bytesRead > 0) { if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer) - 1, &bytesRead, NULL)) { WriteFile(hStdInWrite, buffer, bytesRead, &bytesWritten, NULL); } } // Read from stdout if (ReadFile(hStdOutRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) { buffer[bytesRead] = '\0'; // Null-terminate the string printf("%s", buffer); } // Read from stderr if (ReadFile(hStdErrRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) { buffer[bytesRead] = '\0'; // Null-terminate the string fprintf(stderr, "%s", buffer); } // Check if the process is still running DWORD waitResult = WaitForSingleObject(pi.hProcess, 0); if (waitResult == WAIT_OBJECT_0) { break; // Process has finished } Sleep(10); // Avoid busy waiting } // Get the exit code of the child process GetExitCodeProcess(pi.hProcess, exit_code); // Clean up handles CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hStdOutRead); CloseHandle(hStdErrRead); CloseHandle(hStdInWrite); } int main(int argc, char *argv[]) { // Allocate initial memory for the command size_t commandSize = 3072; char *command = (char *)malloc(commandSize); if (command == NULL) { fprintf(stderr, "Memory allocation failed\n"); return 1; } // Start with the executable name char exename[2048]; get_exe_filename(exename, sizeof(exename)); snprintf(command, commandSize, "\"%s\"", exename); // Append command-line arguments for (int i = 1; i < argc; i++) { size_t argLength = strlen(argv[i]) + 3; // +3 for space and double quotes if (strlen(command) + argLength >= commandSize) { // Reallocate memory if needed commandSize *= 2; // Double the size command = (char *)realloc(command, commandSize); if (command == NULL) { fprintf(stderr, "Memory reallocation failed\n"); return 1; } } strcat(command, " \""); strcat(command, argv[i]); strcat(command, "\""); } // Enable ANSI escape codes in Windows 10 and later OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (GetVersionEx(&osvi)) { if (osvi.dwMajorVersion >= 10) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); DWORD mode; GetConsoleMode(hConsole, &mode); SetConsoleMode(hConsole, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); } } SetEnvironmentVariable("PRAGTICAL_COM_WRAP", "1"); // Execute the command and store the exit code DWORD exit_code = 0; executeCommand(command, &exit_code); // Free allocated memory free(command); return exit_code; }