PyAppExec is a cross-platform bootstrapper that prepares a Python application for end users. It locates (or installs) a suitable Python interpreter, provisions an isolated virtual environment, downloads any third-party tooling you bundle, installs Python dependencies, and finally launches your target script - all driven by a simple .ini file specification. For a friendlier setup flow, PyAppExec ships a companion installer with a graphical interface that auto-detects your project layout, copies the launcher into place, generates a tailored .ini you can fine-tune, and produces ready-to-ship launcher folders for Windows/Linux or a branded .app bundle with your icon on macOS.
- Overview
- Key Features
- How It Works
- Repository Layout
- Getting Started
- Configuration
- Bundling External Artifacts
- Sample Application
- Development Notes
- Troubleshooting
- Contributing
- License
- Acknowledgements
Many Python applications require users to install Python, set up virtual environments, download extra outside dependencies, and install Python packages before they can run the app. PyAppExec automates those steps so you can distribute a native binary launcher alongside your Python project. Ship the PyAppExec binary, ship an .ini configuration that describes what the launcher should do, and PyAppExec takes care of the rest.
While PyInstaller, cx_Freeze and similar tools are excellent when you need a completely self-contained binary, PyAppExec deliberately lightens the shipped package, relegating the heavier dependency downloads to the end user’s machine during the first run. Unlike these “freezer” tools that bundle an entire Python runtime and all dependencies into a monolithic executable, PyAppExec keeps your Python project intact and simply orchestrates interpreter provisioning, virtual environments, and external tooling on the user’s machine. That makes updates faster (swap out your Python sources without rebuilding a frozen binary), reduces download size, and keeps the runtime transparent for power users who still want to inspect or modify the Python code.
- Detects the highest priority Python interpreter on the PATH and validates its version.
- Allows per-platform
.inisections so Windows, macOS, and Linux can point at platform-specific scripts, download URLs, and tooling. - On Linux, optionally installs Python via
apt,dnf, orpacmanif a suitable interpreter is not found. - Creates or reuses a per-project virtual environment and keeps a lightweight state file to avoid redundant
pip installruns. - Installs Python package dependencies from a configurable requirements file inside the virtual environment.
- Downloads and, when possible, installs external tools, using integrity checks to avoid re-downloading unchanged files.
- Launches the target Python entry point after the environment is ready, with stdout/stderr feedback in the terminal.
- Accepts optional command-line arguments and environment overrides from the
.ini. - Ships with an optional GUI front-end that surfaces progress, embedded terminal output, and error dialogs.
- Includes a GUI installer plus packaging script that auto-detects project structure, generates a starter
.inifor you to customize, and produces ready-to-share artifacts (launcher folders for Windows/Linux and a branded.appon macOS). - Emits structured logs via
spdlogso you can tail progress or integrate with external log collectors. - Embeds helper Python scripts via GLib resources so the launcher has zero runtime script dependencies.
- PyAppExec reads
pyappexec.iniand selects the[<OS>:main]and[<OS>:requirements]sections that match the current platform (Linux,MacOS,Windows). - It resolves relative paths against the directory that contains the
.inifile. - If no suitable Python interpreter is found, PyAppExec can attempt an installation via common package managers (on Linux) or fall back to the configured download URL so you can prompt users to install it manually.
- A virtual environment is created (or reused) at the configured location.
- Python dependencies from
requirements.txt(or whichever file you point to) are installed inside that environment. A signature file prevents redundant installs when the requirements file has not changed. - For each external requirement defined in the
.inifile, PyAppExec checks whether it is already present and satisfies the minimum version. Missing dependencies are downloaded to thedistrib/directory or installed via a custom command. - Finally, the target Python script is invoked using the virtual environment's interpreter.
.
├── CMakeLists.txt # CMake build configuration for the C++ launcher
├── main.cpp # Entry point that drives the CLI/GUI bootstrapper
├── include/ # Public headers shared between the CLI and GUI layers
├── lib/ # Core launcher implementation (AppBootstrapper, utils, etc.)
├── gui/ # Qt6 widgets for the optional front-end (MainWindow, GuiRunner)
├── resources/ # GLib resource manifest plus embedded Python helper scripts
├── scripts/ # Source copies of the embedded helper scripts
├── LICENSE / README.md # Project metadata and documentation
To build the launcher you need:
- A C++20 toolchain (GCC 11+, Clang 13+, or MSVC 19.30+).
- CMake 3.16 or newer.
pkg-configand the development headers forgio-2.0(part of GLib) — these provideglib-compile-resourcesand the GIO runtime used to embed scripts. On macOS install them withbrew install glib libffi zlib.- Qt 6 (Widgets module) headers and libraries for the optional GUI front-end.
- spdlog (header-only logging library) for structured logging output.
- Boost with the Process headers and the Filesystem/System libraries (Boost.Process depends on the compiled
boost_filesystem+boost_systempair on Linux). curl(Linux/macOS) or the Windows URLMon APIs (already part of Win32) for downloading requirement archives.
Install the toolchain and development headers via your package manager before running CMake. Example commands:
-
Debian-based (Ubuntu, Mint, Pop!_OS):
sudo apt update sudo apt install build-essential cmake pkg-config libglib2.0-dev qt6-base-dev libspdlog-dev libboost-dev libboost-filesystem-dev libboost-system-dev libcurl4-openssl-dev
If
cmakereportsCould not find a package configuration file provided by "boost_filesystem", it means thelibboost-filesystem-devpackage is missing; install it alongsidelibboost-devso Boost.Process can link against the filesystem/system libraries. -
Fedora-based (Fedora, RHEL, CentOS Stream):
sudo dnf install @development-tools cmake pkgconf-pkg-config glib2-devel qt6-qtbase-devel spdlog-devel boost-devel libcurl-devel
-
Arch-based (Arch, Manjaro, EndeavourOS):
sudo pacman -S --needed base-devel cmake pkgconf glib2 qt6-base spdlog boost curl
After installing the dependencies, build with:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel- Install the dependencies:
Homebrew's
brew install glib libffi zlib boost qt spdlog
pkg-configshould exposegio-2.0after those installs; if it does not, exportPKG_CONFIG_PATH="/opt/homebrew/opt/libffi/lib/pkgconfig:/opt/homebrew/lib/pkgconfig:/opt/homebrew/share/pkgconfig:$PKG_CONFIG_PATH"(adjust the prefixes if needed) and rerunpkg-config --modversion gio-2.0to confirm it resolves to the Homebrew install. - Build:
Or run
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --parallelscripts/build_macos.shto apply thePKG_CONFIG_PATHtweak automatically and build in one step.
- Install Visual Studio 2022 (Desktop development with C++) or the standalone Build Tools so
cl.exe, the Windows SDK, and CMake integration are available. - Bootstrap vcpkg and install the dependencies:
git clone https://github.com/microsoft/vcpkg.git C:\dev\vcpkg C:\dev\vcpkg\bootstrap-vcpkg.bat C:\dev\vcpkg\vcpkg.exe install qtbase:x64-windows spdlog:x64-windows
- Configure the project from a “x64 Native Tools Command Prompt for VS” (or Developer PowerShell) so MSVC is on
PATH:cmake -S . -B build ` -DCMAKE_BUILD_TYPE=Release ` -DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake ` -DVCPKG_TARGET_TRIPLET=x64-windows cmake --build build --parallel
If CMake still complains that
Qt6 was not found, confirm thatqtbase:x64-windowswas installed and that the same vcpkg root is passed viaCMAKE_TOOLCHAIN_FILE. SettingVCPKG_ROOT=C:\dev\vcpkgglobally also works. If you run the PowerShell helper (scripts/build_windows.ps1) from the Developer shell, first relax the session policy withSet-ExecutionPolicy -Scope Process -ExecutionPolicy Bypassso the script can execute.
The build step generates resources.c from resources/resources.xml using glib-compile-resources. Ensure that tool is discoverable on your PATH.
Use -DBUILD_LAUNCHER=OFF or -DBUILD_INSTALLER=OFF to selectively build the CLI/GUI launcher or the installer UI.
After building, run the launcher from the project root:
./build/pyappexecPyAppExec first looks for pyappexec.ini in the current directory; if it is not found, it scans each immediate subdirectory. To point at a specific file explicitly, pass --config /path/to/pyappexec.ini.
On macOS, if you distribute PyAppExec inside an .app bundle, launch flags (for example --reset-gui) only work when you either run the embedded binary directly (Your.app/Contents/MacOS/YourBinary --reset-gui) or insert --args when using Finder/open (open Your.app --args --reset-gui). Anything after the .app path without --args is treated as a document, so PyAppExec never sees the flag.
Useful flags
--config /path/to/pyappexec.ini- override the config discovery logic described above.--reset-gui- clear the persisted "hide GUI" preference (handy if you previously suppressed the GUI after a successful run).--help- print a brief overview of PyAppExec and the available flags.--version- print version/author/years/repo.
CLI vs GUI binaries
- GUI binaries:
pyappexecandpyappexec_installer(Win32 subsystem on Windows). Use these for normal double-click/Start Menu usage; CLI flags are only visible when launched from a console. - CLI binary:
pyappexec_cli(console subsystem). Use this for terminal/automation; it supports--help,--version, and the launcher flags above. GUI output is suppressed for--help/--versionon the CLI build.
PyAppExec ships with an optional Qt-based installer that can scaffold a launcher/INI for an existing Python project. Build it via cmake --build build --parallel --target pyappexec_installer (or leave -DBUILD_INSTALLER=ON, which is the default). The installer allows you to:
- Browse to a Python project root and auto-detect the entry point and requirements file.
- Pick an application name and the name of the copied launcher binary (the PyAppExec executable is copied and renamed accordingly).
- On macOS, optionally provide a high-resolution PNG/ICNS icon (otherwise the default PyAppExec icon is used) and the installer will bake it into the generated
.appbundle. - Generate a starter
pyappexec.inifor Linux/Windows/macOS with the common defaults (including the “hide GUI after successful runs” flag). - Open the generated INI in your default editor so you can tweak paths before shipping (the installer simply provides a solid baseline).
Packaging the installer as a macOS .app
- Run
./scripts/package_installer_app.shto producedist/PyAppExec_Installer.<version>.<arch>.app. The script rebuilds the installer binary, converts the iconset underresources/icons/macos/, copies Qt frameworks/plugins viamacdeployqt, and performs an ad-hoccodesignso the bundle launches without Gatekeeper warnings. SetCODESIGN_IDENTITYif you need to sign with a real certificate. - Distribute
PyAppExec_Installer…appdirectly to customers. When they launch it, they can browse to their Python project, (optionally) supply an icon, and the installer will drop the PyAppExec launcher +pyappexec.inialongside the project, ready for redistribution on Linux, Windows, or macOS. - On macOS/Linux, the installer also drops a
reset_pyappexec.command/reset_pyappexec.shhelper next to the INI/launcher; it removes the managed virtual environment defined inpyappexec.ini. - On macOS, you can opt to create a self-contained
.appthat embeds your Python project underContents/Resources/app, the launcher, andpyappexec.ini(rewritten to point at the bundled sources). The installer attempts an ad-hoc codesign; setCODESIGN_IDENTITYto sign with your certificate.
- Copy your built
pyappexecbinary and a tailoredpyappexec.iniinto the root of the Python application you plan to ship (the repo’spyappexec.inishows the recommended layout where every path is relative to the INI). Rename the binary to match your product if you like. - Run the launcher once from that directory to ensure the virtual environment,
distrib/downloads, and GUI suppression preference behave as expected. Remove any temporarydistrib/artifacts you don’t plan to prebundle. - Include a short README/release note in your distribution that states the app is bootstrapped by PyAppExec and links to https://github.com/quicknode-labs/PyAppExec for attribution and support.
Set GUI = true under the relevant [<OS>:main] section to launch the Qt6 front-end. The GUI embeds the CLI output (PowerShell on Windows, Terminal on macOS/Linux), shows a progress indicator, and surfaces blocking error dialogs if anything fails. The window title automatically displays your Python app name followed by “(via PyAppExec)” so end users can immediately see that PyAppExec is handling the bootstrap. When a run completes successfully you can check “Hide GUI after successful runs” before closing the window; PyAppExec remembers that preference (per app) and skips the GUI going forward, automatically re-enabling it if a later run fails. The Help menu also exposes “About PyAppExec” and “About Qt” dialogs for attribution. Pass --no-gui on the command line or set GUI = false to force the traditional CLI experience even when the INI enables the GUI. Use --config /path/to/pyappexec.ini to point the launcher at a specific configuration file, and --reset-gui to clear any saved GUI suppression preference.
Logging
log_consoleandlog_levelin[<OS>:main]control console output; the GUI always captures stdout/stderr internally.- The launcher also writes rotating logs (5 MB x 3 files) under the user's log directory:
%LOCALAPPDATA%\PyAppExec\logs\pyappexec.logon Windows,~/Library/Logs/PyAppExec/pyappexec.logon macOS, and$XDG_STATE_HOME/pyappexec/pyappexec.logor~/.local/state/pyappexec/pyappexec.logon Linux. - The GUI writes its combined output to a log file next to
pyappexec.ininamed.pyappexec_gui.log. If you bundle PyAppExec into a macOS.app, the log lives inside the bundle atYour.app/Contents/MacOS/.pyappexec_gui.logunless you relocate it. For CLI runs, redirect stdout/stderr as desired.
PyAppExec is driven entirely by pyappexec.ini. Each operating system gets its own pair of sections: [Linux:main] and [Linux:requirements], [Windows:main] and [Windows:requirements], and so on. Keep the INI alongside your Python application (like the sample pyappexec.ini) so relative paths resolve naturally; PyAppExec automatically discovers it when launched from the project root.
| Key | Required | Description |
|---|---|---|
python_download_url |
no | URL you can surface to users if you need to link to an installer. The launcher does not download Python automatically from this URL but exposes it for logging and UX hooks. |
python_min_ver |
yes | Minimum acceptable Python version (for example 3.10). |
app_id |
yes | Stable identifier (6–20 alphanumeric characters) used to scope per-app caches/virtualenvs. Keep this consistent across platforms. |
config_root |
no | Root directory for cached state/venv. Defaults per-OS to a user data path (~/Library/Application Support/PyAppExec/<app_id> on macOS, $XDG_DATA_HOME/pyappexec/<app_id> or ~/.local/share/pyappexec/<app_id> on Linux, %LOCALAPPDATA%\\PyAppExec\\<app_id> on Windows). |
python_app_dir |
no | Directory that contains your Python project. Defaults to the INI file's directory. |
exec_app_path |
yes | Entry-point script relative to python_app_dir, usually your main.py. |
exec_app_args |
no | Extra command-line arguments appended after the entry script. Quote values with spaces ("--flag value"). |
exec_env |
no | Semicolon-separated list of KEY=VALUE pairs that override environment variables for the launched app. |
requirements_file |
no | Relative path to the Python requirements file. If omitted, Python dependency installation is skipped. |
virtual_env_dir |
no | Directory to create the virtual environment in. If empty, defaults to <config_root>/venv. If relative, it is resolved against python_app_dir. |
GUI |
no | true to launch the Qt front-end; false to stay purely CLI. Users can also pass --no-gui at runtime to force CLI regardless of config. |
GUI_CLI_HIDE_AFTER_SUCCESS |
no | true to automatically set the “hide GUI/CLI after successful runs” preference once a run succeeds; on Windows CLI builds it also closes the launcher console after the target app starts. |
log_console |
no | true (default) to emit logs to stdout/stderr, false to silence console output (the GUI still captures logs internally). |
log_level |
no | Minimum severity for console logs. Supports trace, debug, info, warn, error, critical, off. |
The GUI installer auto-generates a valid app_id for you; override it if you want a specific identifier shared across platforms or releases.
Example: exec_env = APP_ENV=production;LOG_LEVEL="info" injects two variables, while exec_app_args = --profile default --no-telemetry adds both flags after the entry script.
Define any number of requirement_<n> blocks (numbered sequentially from 1). Each block supports:
| Key | Description |
|---|---|
requirement_<n> |
Human-friendly name, e.g. FFmpeg. |
requirement_<n>_url |
Download URL for an installer or archive. The file is stored under distrib/. |
requirement_<n>_file_name |
Override for the downloaded filename. Defaults to the basename of the URL. |
requirement_<n>_version_check_command |
Command used to detect the installed version, such as ffmpeg -version. |
requirement_<n>_version_regex |
Regular expression with a capture group that extracts the version number. |
requirement_<n>_min_version |
Minimum acceptable version; omit to accept any detected version. |
requirement_<n>_launch_file |
Name of the executable you expect after extraction (used primarily for logging). |
requirement_<n>_capture_stderr |
When true, merges stderr into stdout during version detection. |
requirement_<n>_cmd_params |
Extra parameters appended when running a Windows installer executable. |
requirement_<n>_install_command |
Shell command to run for installing the requirement. If provided, PyAppExec skips the built-in download/extract logic and just runs this command (even when requirement_<n>_url is empty). |
requirement_<n>_append_to_path |
true to prepend the requirement’s bin directory to PATH when launching your app (inferred from the version check command or extract location). |
requirement_<n>_standalone |
true to install/extract to a standalone location instead of distrib/ (Windows zip auto-extract uses %ProgramFiles%/<name> when no install_dir is given). |
requirement_<n>_install_dir |
Explicit install/extract directory used when standalone is true. |
PyAppExec keeps per-requirement status in memory to avoid noisy logs when the version check command is run multiple times.
Archive/installer handling
- Built-in auto-extraction kicks in when both
install_commandandcmd_paramsare empty. On Windows it handles.zipvia PowerShell. On macOS/Linux it will attempt to extract.tar.*viatar,.zipviaunzip, and.7z/.rarvia7z/7zaif those tools are onPATH; otherwise you must provide aninstall_command. - Any other formats (executable installers, pkg/msi, unsupported archives) require an explicit
install_command. - When
append_to_pathistrue, the launcher prepends the requirement’s bin directory toPATHbefore starting your app, whether the requirement was auto-extracted or detected viaversion_check_command.
The repository ships with a Linux configuration that targets the sample app in test/:
[Linux:main]
python_download_url = https://www.python.org/ftp/python/3.13.1/Python-3.13.1.tgz
python_min_ver = 3.10
python_app_dir = test
exec_app_path = src/your_package/__main__.py
requirements_file = requirements.txt
virtual_env_dir = .pyappexec-venv
GUI = true
log_console = true
log_level = info
; exec_app_args = --profile default
; exec_env = APP_ENV=production
[Linux:requirements]
requirement_1 = FFmpeg
requirement_1_url = https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
requirement_1_file_name = ffmpeg-release-amd64-static.tar.xz
requirement_1_version_check_command = ffmpeg -version
requirement_1_version_regex = ^ffmpeg version ([0-9.]+)
requirement_1_min_version = 4.4.2
requirement_1_launch_file = ffmpeg
requirement_1_capture_stderr = false
requirement_1_install_command = sudo apt-get update && sudo apt-get install -y ffmpeg
[Windows:main]
python_download_url = https://www.python.org/ftp/python/3.13.1/python-3.13.1-amd64.exe
python_min_ver = 3.10
python_app_dir = test
exec_app_path = src/your_package/__main__.py
requirements_file = requirements.txt
virtual_env_dir = .pyappexec-venv
GUI = true
log_console = true
log_level = info
[Windows:requirements]
requirement_1 = FFmpeg
requirement_1_url = https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.7z
requirement_1_file_name = ffmpeg-release-essentials.7z
requirement_1_version_check_command = ffmpeg -version
requirement_1_version_regex = ^ffmpeg version ([0-9.]+)
requirement_1_min_version = 7.0
requirement_1_capture_stderr = false
requirement_1_cmd_params = /S /quietOn Windows, optional cmd_params can be supplied to run silent installers.
A macOS profile follows the same pattern with [MacOS:main] and [MacOS:requirements] sections; the sample pyappexec.ini shows how to point those entries at the same application while using a platform-appropriate Python installer URL and Homebrew-based FFmpeg installation command.
PyAppExec installs dependencies by running pip install -r <requirements_file> inside the managed virtual environment. Projects that rely on pyproject.toml-only builds or external tooling can still be launched with a few extra steps:
- Export a lock file: most tools can emit a plain requirements file that PyAppExec can consume (
poetry export --without-hashes --format requirements.txt > requirements.txt,pipenv lock -r > requirements.txt,uv pip compile pyproject.toml -o requirements.txt). Pointrequirements_fileat the exported artifact and refresh it whenever dependencies change. - Delegate to your tool: instead of (or in addition to)
requirements_file, setexec_app_argsor wrap your entry point so the launcher runspoetry run …,pipenv run …,uv run …, etc. You can vendor the tool inside your project and install it viarequirements.txtif needed. - Custom bootstrap scripts: complex setups (building optional wheels, applying migrations, downloading models) can be orchestrated by pointing
exec_app_pathat a bespokebootstrap.pythat shells out to Poetry/Pipenv/uv as required before launching your real app.
Regardless of approach, remember that PyAppExec always executes inside its own virtual environment. If your tool manages environments internally (for example pipenv --venv), either disable the virtual env in pyappexec.ini (point virtual_env_dir to a location you control and skip creating it) or ensure the tool is happy with the interpreter PyAppExec provisions.
Place offline installers, archives, or wheel files in the distrib/ directory. PyAppExec stores downloads here and skips re-downloading when the file already exists with the expected size (checked via HTTP Content-Length). You can pre-populate this directory before shipping your package to avoid runtime downloads.
The test/ directory contains the open-source YT Channel Downloader project as an integration example. To try the full flow:
- Build PyAppExec.
- Ensure
ffmpegis installed or let the Linux configuration install it for you. - Run
./build/pyappexecfrom the repository root. The launcher will set up a virtual environment undertest/.pyappexec-venv, install Python dependencies fromtest/requirements.txt, and start the PyQt application defined intest/main.py.
All content from this README plus additional deep dives is mirrored under readthedocs/ so it can be published on Read the Docs. To preview the site locally:
python -m venv .docs-venv
source .docs-venv/bin/activate
pip install sphinx sphinx-rtd-theme
(cd readthedocs && sphinx-build -b html . _build/html)Open _build/html/index.html in your browser to inspect the generated documentation.
- The launcher automatically embeds
scripts/get_python_version.pyusing GLib resources; editresources/resources.xmlif you need to ship additional helper scripts. - A build generates
.pyappexec_requirements_stateunder the virtual environment directory to cache the requirements signature. Delete it if you need to force a reinstall. - CMake now emits
compile_commands.jsonin your build directory (thanks toCMAKE_EXPORT_COMPILE_COMMANDS=ON). Point Codacy/Cppcheck at that file (or copy it to the repo root) so they analyze the real translation units instead of header-only snapshots. - Boost.Process powers all child process execution. Ensure the runtime has permission to spawn subprocesses and access network resources.
Lightweight smoke tests validate the INI layout, Read the Docs structure, and GUI build configuration. Run them with:
python tests/run_all.pyThe test scripts live under tests/ and can be extended as the project grows.
- Python not detected: Confirm that a compatible interpreter is available on the
PATH. On macOS, apps launched from Finder inherit the default/usr/bin:/bin:/usr/sbin:/sbinPATH, so PyAppExec now probes/opt/homebrew/bin/python3,/usr/local/bin/python3, and/Library/Frameworks/Python.framework/Versions/Current/bin/python3automatically; you can also exportPYAPPEXEC_PYTHON=/absolute/path/to/python3before starting the launcher (or wrap your.appwith that environment) to force a specific interpreter. On Linux, check the console logs to see if PyAppExec attempted package-manager installation. glib-compile-resourcesmissing: Install the GLib development tools package (libglib2.0-dev-binon Debian/Ubuntu,glib2on Arch,brew install glibon macOS).- External requirement download fails: Verify the URL, ensure HTTPS certificates are trusted, and check that the host is reachable in your deployment environment.
- Virtual environment issues: Remove the existing virtual environment directory (for example
test/.pyappexec-venv) and rerun the launcher to force a clean setup.
Issues and pull requests are welcome. Please include reproduction steps and platform details when reporting bugs. If you plan to contribute substantial changes, open a discussion first so we can align on direction.
This project is released under the MIT License.
PyAppExec builds on the excellent work of the open-source community:
- Qt 6 for powering both the launcher and installer UIs.
- Boost.Process for portable process management.
- GLib/GIO for resource embedding and cross-platform file utilities.
- spdlog for fast structured logging.
- ImageMagick for automating icon generation.
Thank you to the maintainers and contributors of these projects for making PyAppExec possible.

