Building & Packaging

Distributing Python applications to end-users (especially in enterprise environments) can be challenging due to Python runtime requirements and dependency management. To solve this, PVE VDI Client provides automated scripts utilizing pyinstaller (for compiling the Python code into a standalone binary) and the WiX Toolset (for creating professional Windows MSI installers).

This section explains how to build the binaries yourself, which is necessary if you intend to fork the project, change hardcoded logic, or replace the default icons.

Building a Linux Binary

You can compile the Python script into a standalone Linux executable. This executable will contain the Python runtime and all necessary libraries (like proxmoxer and PySimpleGUI), meaning you can copy it to other Linux machines of the same architecture without installing Python packages.

Requirements

  • A Linux build environment (Debian/Ubuntu recommended).
  • git, python3-pip, python3-tk (or python3-tkinter).

Build Steps

  1. Clone and Prep:

    git clone https://github.com/joshpatten/PVE-VDIClient.git
    cd ./PVE-VDIClient/
    chmod +x requirements.sh
    ./requirements.sh

  2. Install PyInstaller:

    pip3 install pyinstaller

  3. Compile the Binary: Because the proxmoxer library does dynamic loading of backend requests, PyInstaller cannot always detect its dependencies automatically. We must explicitly declare them using --hidden-import.

    pyinstaller --onefile --noconsole --noconfirm \
      --hidden-import proxmoxer.backends \
      --hidden-import proxmoxer.backends.https \
      --hidden-import proxmoxer.backends.https.AuthenticationError \
      --hidden-import proxmoxer.core \
      --hidden-import proxmoxer.core.ResourceException \
      --hidden-import subprocess.TimeoutExpired \
      --hidden-import subprocess.CalledProcessError \
      --hidden-import requests.exceptions \
      --hidden-import requests.exceptions.ReadTimeout \
      --hidden-import requests.exceptions.ConnectTimeout \
      --hidden-import requests.exceptions.ConnectionError \
      vdiclient.py

Once PyInstaller finishes, the compiled, portable executable will be located at dist/vdiclient.


Building for Windows (Executable & MSI)

The project includes batch files to easily build a Windows executable and subsequently package it into a .msi installer. MSIs are the industry standard for Windows mass-deployment (via GPO/SCCM/Intune).

Windows Build Prerequisites

  1. Python 3.12 installed on Windows and added to your system PATH.
  2. WiX Toolset (v3.11) installed. WiX is the engine that generates MSI files from XML. Download it from wixtoolset.org.
  3. Install Python build requirements by running requirements.bat in the project root.

The Build Process

Execute the provided batch script from the project root in an administrative command prompt:

build_vdiclient.bat

What this script does behind the scenes:

  1. PyInstaller Compilation: It runs pyinstaller against vdiclient.py, similar to the Linux build, but uses --noupx and attaches the vdiicon.ico file to the generated .exe. It outputs to a folder (dist\vdiclient), rather than a single file, to improve startup times.
  2. Asset Staging: It copies required UI assets (vdiclient.png and vdiicon.ico) directly into the dist\vdiclient distribution folder so the GUI can find them at runtime.
  3. MSI Generation: It navigates into the dist directory and executes the custom createmsi.py script, feeding it vdiclient.json.

Understanding createmsi.py and vdiclient.json

Creating MSI installers manually using WiX XML (.wxs) files is notoriously difficult and verbose. To simplify this, the project uses a custom Python script (dist/createmsi.py) that acts as an intelligent wrapper around the WiX compiler (candle.exe) and linker (light.exe).

vdiclient.json

This JSON file acts as the blueprint for the MSI installer. If you fork the project, you must edit this file.

  • upgrade_guid: A unique UUID. Crucial: If you fork this project for your company and change the product name, generate a new GUID here. This is how Windows determines if an installer is an upgrade to an existing app or an entirely new program.
  • version: Must be incremented for Windows to apply upgrades over existing installations.
  • parts: Instructs the script which directories to package. It targets the vdiclient folder created by PyInstaller.
  • license_file: Points to License.rtf, which is embedded directly into the MSI installation wizard.

createmsi.py

The script reads vdiclient.json, recursively scans the PyInstaller output directory, dynamically generates the massive WiX Source XML file (vdiclient.wxs), and invokes the WiX toolset. It handles complex MSI requirements like generating unique Component GUIDs, adding Start Menu shortcuts, setting Windows Registry uninstaller entries, and automatically embedding the Visual C++ redistributable merge modules (.msm) if required by Python.