PipedProcess 2.1.3

dotnet add package PipedProcess --version 2.1.3
                    
NuGet\Install-Package PipedProcess -Version 2.1.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PipedProcess" Version="2.1.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PipedProcess" Version="2.1.3" />
                    
Directory.Packages.props
<PackageReference Include="PipedProcess" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PipedProcess --version 2.1.3
                    
#r "nuget: PipedProcess, 2.1.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PipedProcess@2.1.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PipedProcess&version=2.1.3
                    
Install as a Cake Addin
#tool nuget:?package=PipedProcess&version=2.1.3
                    
Install as a Cake Tool

PipedProcess

C++ helper class to create a child process with redirected std in/out/error streams using the Windows API.

What it does

It can be used to pass arbitrary binary input data to the child process via stdin and retrieve the result data via stdout. Errors can be received via stderr.

What it does not

Currently the class can not be used for asynchronous communication (e.g. messages) to and from the child process.

Public Interface

The PipedProcess class provides a simple, exception-free public API that returns error codes for all operations.

Core Methods

// Basic execution
DWORD Run(const char* program, const char* arguments);

// Execution with abort capability
template<class T>
DWORD Run(const char* program, const char* arguments, T& abortEvent);

// Execution with user token (run as different user)
DWORD RunAs(const HANDLE& token, const char* program, const char* arguments);
template<class T>
DWORD RunAs(const HANDLE& token, const char* program, const char* arguments, T& abortEvent);

Input/Output Methods

// Set data to send to child process stdin
void SetStdInData(const char* pData, size_t len);

// Check if output data is available
bool HasStdOutData() const;
bool HasStdErrData() const;

// Retrieve output data (moves data out of internal buffers)
std::string FetchStdOutData();
std::string FetchStdErrData();

Configuration

// Control child process window visibility
enum class WindowMode { Visible = 0, Hidden = 1 };
void SetWindowMode(WindowMode mode);  // Default is Hidden

Return Values

All Run methods return DWORD error codes:

  • 0 = Success (child process completed successfully)
  • ERROR_INVALID_PARAMETER = Null parameters provided
  • ERROR_FILE_NOT_FOUND = Program executable not found
  • ERROR_PATH_NOT_FOUND = Empty program path
  • Other Windows error codes for various failure conditions

Error details are available via FetchStdErrData() when errors occur.

Usage Examples

Basic Usage

#include "PipedProcess/PipedProcess.h"

// Simple command execution
PipedProcess process;
DWORD exitCode = process.Run("cmd.exe", "/c echo Hello World");

if (exitCode == 0 && process.HasStdOutData()) {
    std::string output = process.FetchStdOutData();
    std::cout << "Output: " << output << std::endl;
}

Sending Input Data

// Execute a program that reads from stdin
PipedProcess process;
std::string input = "Hello from parent process\n";
process.SetStdInData(input.c_str(), input.length());

DWORD exitCode = process.Run("sort.exe", "");
if (exitCode == 0) {
    std::string output = process.FetchStdOutData();
    std::cout << "Sorted output: " << output << std::endl;
}

Error Handling

PipedProcess process;
DWORD exitCode = process.Run("nonexistent.exe", "");

if (exitCode != 0) {
    std::cout << "Process failed with exit code: " << exitCode << std::endl;
    
    if (process.HasStdErrData()) {
        std::string errorMsg = process.FetchStdErrData();
        std::cout << "Error details: " << errorMsg << std::endl;
    }
}

Using Abort Events

// Custom abort event
struct MyAbortEvent {
    std::atomic<bool> shouldAbort{false};
    bool IsSet() const { return shouldAbort.load(); }
    void Set() { shouldAbort = true; }
};

PipedProcess process;
MyAbortEvent abortEvent;

// Start long-running process in another thread
auto future = std::async(std::launch::async, [&]() {
    return process.Run("long-running-program.exe", "", abortEvent);
});

// Later, signal abort if needed
abortEvent.Set();
DWORD exitCode = future.get();

Hidden vs Visible Windows

PipedProcess process;

// Show the child process window (useful for debugging)
process.SetWindowMode(PipedProcess::WindowMode::Visible);

// Or keep it hidden (default behavior)
process.SetWindowMode(PipedProcess::WindowMode::Hidden);

DWORD exitCode = process.Run("notepad.exe", "test.txt");

Security Considerations

Important: This library does not validate input parameters. It is the caller's responsibility to ensure:

  • Program paths are trusted and do not contain malicious executable paths
  • Arguments do not contain command injection vectors (e.g., |, &, ;, >, < when used with shell commands)
  • Input data size is reasonable to prevent memory exhaustion
  • Program execution is limited to trusted executables in secure environments

For applications handling untrusted input, implement validation before calling PipedProcess methods.

MIT License

Feel free to use. See LICENSE file for further information.

How to build

The project is a Visual Studio 2022 solution. It should be possible to build it with other compilers, but I have not tested it.

Creating NuGet Package

Prerequisites

Package Creation Steps

  1. Build the solution to ensure all tests pass:

    msbuild PipedProcess.sln -p:Configuration=Release -p:Platform=x64
    
  2. Create the NuGet package using the modern nuspec file:

    nuget pack PipedProcess.nuspec
    

    Or with dotnet CLI:

    dotnet pack PipedProcess.nuspec
    
  3. Publish to NuGet.org (optional):

    nuget push PipedProcess.{version}.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey {your-api-key}
    

Package Contents

The NuGet package includes:

  • Headers: PipedProcess.h, StdPipe.h, UniqueHandle.h in include/PipedProcess/ directory
  • MSBuild targets: Automatic include path configuration and preprocessor definitions
  • Documentation: Inline comments and usage examples

Using the Package

After installing the package in a project, simply include the headers:

#include <PipedProcess/PipedProcess.h>
#include <PipedProcess/StdPipe.h>

The MSBuild targets automatically configure include paths and preprocessor definitions.

Product Compatible and additional computed target framework versions.
native native is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

v2.1.3: Major robustness and reliability improvements:

Enhanced StdPipe Write Operations:
- Robust partial write handling for large data chunks (supports multi-GB transfers)
- Automatic retry logic for incomplete writes with proper progress tracking
- Fixed edge cases in Write() method preventing data loss

Security and Safety Enhancements:
- Added comprehensive null pointer validation with ERROR_INVALID_PARAMETER returns
- Enhanced RAII handle management with UniqueHandle wrapper preventing resource leaks
- Added input validation and security documentation for safe API usage

Performance and Maintainability:
- Simplified argument handling from std::vector to std::string (reduced complexity)
- Fixed incorrect std::move usage in Read() method improving performance
- Added defensive programming patterns with early returns for edge cases

Testing Excellence:
- Comprehensive test suite with 49 test cases and 136 assertions
- Added stress testing with 1MB data transfers validating partial write robustness
- Enhanced binary data handling and multithreaded access testing
- Migrated to modern doctest framework with superior error reporting

All improvements maintain full backward compatibility while significantly improving reliability and performance.