Windows APIs: Command-Line Execution

This section details the Windows APIs that facilitate the execution of commands and programs from the command line, allowing for programmatic control over processes and scriptable interactions with the operating system.

Introduction to Command-Line Execution APIs

Windows provides a rich set of Application Programming Interfaces (APIs) for managing and interacting with the command-line environment. These APIs are crucial for tasks such as launching external applications, redirecting input/output, and monitoring process execution.

Key Functions for Command Execution

CreateProcess()

The CreateProcess() function is the primary API for creating a new process and its primary thread. It provides extensive control over the new process's environment, security attributes, and startup information. This function is fundamental for launching any executable program from within another application or script.

Parameters:


BOOL CreateProcess(
    LPCSTR lpApplicationName,
    LPSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCSTR lpCurrentDirectory,
    LPSTARTUPINFOA lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
);
        

ShellExecute() and ShellExecuteEx()

These functions provide a higher-level interface for executing applications, opening documents, or printing documents. They leverage the Windows shell to determine how to handle the request, often invoking the default associated application.

These functions are convenient for user-facing operations where the exact executable might not be known or when you want to rely on default system behavior.

CreateProcessAsUser()

This function creates a new process running in the security context of a specified user account. It requires appropriate privileges to impersonate the target user.

Input/Output Redirection

When launching processes, it's often necessary to capture their standard output, standard error, or provide them with standard input programmatically. This is achieved using pipes.

CreatePipe()

CreatePipe() creates an anonymous pipe, which is a unidirectional data stream. You typically create two pipes: one for standard output and one for standard input of the child process.

STARTUPINFO Structure

The STARTUPINFO structure, used with CreateProcess(), has members such as hStdInput, hStdOutput, and hStdError. These members are set to the handles of the pipe ends to redirect the child process's standard streams.

Important Note on Redirection

When redirecting standard output or error, remember to set the dwFlags member of the STARTUPINFO structure to STARTF_USESTDHANDLES and assign the write end of the pipe to hStdOutput and/or hStdError.

Example: Launching a Command and Capturing Output

The following conceptual example illustrates how to use CreateProcess() with I/O redirection to execute a command and read its output:


#include <windows.h>
#include <iostream>
#include <string>

int main() {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    HANDLE hReadPipe, hWritePipe;
    SECURITY_ATTRIBUTES sa;

    // Set up the security attributes for the pipe
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;

    // Create a pipe for the child process's STDOUT
    if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
        std::cerr << "Failed to create pipe." << std::endl;
        return 1;
    }

    // Ensure the read handle to the pipe for STDOUT is not inherited
    if (!SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0)) {
        std::cerr << "Failed to set handle information for read pipe." << std::endl;
        CloseHandle(hReadPipe);
        CloseHandle(hWritePipe);
        return 1;
    }

    // Set up the STARTUPINFO structure
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.hStdError = hWritePipe; // Redirect STDERR to the same pipe as STDOUT
    si.hStdOutput = hWritePipe; // Redirect STDOUT to the pipe
    si.dwFlags |= STARTF_USESTDHANDLES;

    // Command to execute (e.g., "dir" or "ipconfig")
    char commandLine[] = "cmd.exe /c dir"; // Example command

    // Create the child process
    if (!CreateProcess(NULL,          // No module name (use command line)
                       commandLine,   // Command line
                       NULL,          // Process handle not inheritable
                       NULL,          // Thread handle not inheritable
                       TRUE,          // Set handle inheritance to TRUE
                       0,             // No creation flags
                       NULL,          // Use parent's environment block
                       NULL,          // Use parent's starting directory
                       &si,           // Pointer to STARTUPINFO structure
                       &pi)) {        // Pointer to PROCESS_INFORMATION structure
        std::cerr << "CreateProcess failed (" << GetLastError() << ")." << std::endl;
        CloseHandle(hReadPipe);
        CloseHandle(hWritePipe);
        return 1;
    }

    // Close the write end of the pipe in the parent process,
    // as it is not needed here.
    CloseHandle(hWritePipe);

    // Read output from the child process
    char buffer[4096];
    DWORD dwRead;
    std::string result = "";

    while (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL) && dwRead > 0) {
        buffer[dwRead] = '\0'; // Null-terminate the string
        result += buffer;
    }

    std::cout << "--- Command Output ---" << std::endl;
    std::cout << result << std::endl;
    std::cout << "----------------------" << std::endl;

    // Wait until child process exits
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Close process and thread handles
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    CloseHandle(hReadPipe); // Close the read pipe handle

    return 0;
}
        

Process Management

Beyond creation, Windows APIs offer functions to manage existing processes:

Command-Line Arguments

When a program is executed from the command line, it can receive arguments. The lpCommandLine parameter of CreateProcess() is where these are specified. Typically, the first argument is the program name itself, followed by its parameters.

Tip for Robustness

Always validate the return values of API functions and use GetLastError() to diagnose errors. Properly manage handle resources by closing them when they are no longer needed.

Related APIs