A lightweight installer framework for Windows applications. Write a Lua script describing your installer wizard, bundle it with your files, and distribute a single setup executable.
| Project | Type | Description |
|---|---|---|
DeployPaladin |
WinExe (Avalonia) | The installer UI engine — runs the wizard, extracts files, writes registry keys |
DeployPaladin.Builder |
Console | CLI tool that bundles a payload folder into a self-extracting installer |
- .NET 9 SDK (for building from source)
- Windows (registry features are Windows-only)
# Publish the installer engine (base exe that becomes each setup.exe)
dotnet publish DeployPaladin.csproj -c Release -r win-x64 --self-contained -o .\publish
# Publish the builder CLI (standalone, no .NET required to run)
dotnet publish DeployPaladin.Builder\DeployPaladin.Builder.csproj -c Release -o .\publish-builderThis gives you two standalone executables:
publish\DeployPaladin.exe— the base installer enginepublish-builder\DeployPaladin.Builder.exe— the builder CLI (~13 MB, self-contained, no .NET runtime needed)
You can copy both files anywhere. The builder is a standalone tool — put it on a build server, a USB stick, or wherever you create installers.
Create a folder with your application files, a LICENSE.txt, and an installer.lua script:
MyAppInstaller/
├── installer.lua
├── LICENSE.txt
├── icon.ico
├── MyApp.exe
├── library.dll
└── assets/
└── ...
SetMetadata("AppName", "My Application")
SetMetadata("Version", "1.0.0")
SetMetadata("Company", "My Company")
SetTheme("Windows11") -- "Windows11" or "Aqua"
SetInstallDirSuffix("MyApp")
SetAppIcon("icon.ico") -- optional: custom window icon
AddStep("Welcome", { title = "Welcome", description = "Click Next to begin." })
AddStep("License", { title = "License", description = "Review the terms.", contentFile = "LICENSE.txt" })
AddStep("Folder", { title = "Location", description = "Choose install folder." })
AddStep("Shortcuts", { title = "Shortcuts", description = "Choose optional shortcuts." })
AddStep("Install", { title = "Installing...", description = "Please wait." })
AddStep("Finish", { title = "Done!", description = "Installation complete." })
MkDir("%INSTALLDIR%")
CopyFiles("MyApp.exe", "%INSTALLDIR%/MyApp.exe")
CopyFiles("library.dll", "%INSTALLDIR%/library.dll")
CopyDir("assets", "%INSTALLDIR%/assets")
CreateShortcut("%INSTALLDIR%/MyApp.exe", "%DESKTOP%", "My Application",
{ label = "Create Desktop Shortcut", isOptional = true, isSelected = true, icon = "%INSTALLDIR%/icon.ico" })
CreateShortcut("%INSTALLDIR%/MyApp.exe", "%STARTMENU%", "My Application")
CheckRegistry("HKCU", "Software\\MyCompany\\MyApp", "InstallDir")
CreateRegistry("HKCU", "Software\\MyCompany\\MyApp", "InstallDir", "%INSTALLDIR%")DeployPaladin.Builder.exe --payload .\MyAppInstaller --base .\DeployPaladin.exe --output .\MyApp_Setup.exeOr from the repo with the SDK:
dotnet run --project DeployPaladin.Builder -- --payload .\MyAppInstaller --base .\publish\DeployPaladin.exe --output .\MyApp_Setup.exe| Flag | Short | Description |
|---|---|---|
--payload |
-p |
Folder containing installer.lua and referenced payload files |
--base |
-b |
Path to the published DeployPaladin installer executable |
--output |
-o |
Path for the output bundled setup executable |
--help |
-h |
Show help |
This produces a single MyApp_Setup.exe that contains the installer engine and all your payload files.
Ship MyApp_Setup.exe. End users double-click it and get a familiar wizard experience.
During development you can run the installer UI directly. It will look for installer.lua and payload files relative to the executable's directory:
dotnet run --project DeployPaladin.csproj| Function | Description |
|---|---|
SetMetadata("AppName", "...") |
Application name shown in the title bar |
SetMetadata("Version", "...") |
Version string |
SetMetadata("Company", "...") |
Company name |
SetTheme("Windows11") |
UI theme — "Windows11" or "Aqua" |
SetInstallDirSuffix("...") |
Subfolder appended to the chosen install directory |
SetAppIcon("icon.ico") |
Custom window icon (.ico file from the payload) |
AddStep(type, { title = "...", description = "...", contentFile = "..." })Step types: Welcome, License, Folder, Shortcuts, Install, Finish
| Option | Description |
|---|---|
contentFile |
Loads a text file (e.g., LICENSE.txt) and displays it in a scrollable box on that step |
requireScroll |
When true, the user must scroll to the bottom before they can proceed (useful for license steps) |
The Shortcuts step displays checkboxes for any optional shortcut actions (those with isOptional = true), letting the user choose which shortcuts to create before installation begins.
| Function | Description |
|---|---|
MkDir("%INSTALLDIR%") |
Create a directory |
CopyFiles("source", "dest") |
Copy/extract a single file from the payload to disk |
CopyDir("sourceDir", "destDir") |
Copy/extract an entire directory from the payload to disk (preserves subfolder structure) |
CreateShortcut(target, location, name, options?) |
Create a .lnk shortcut (see below) |
CreateRegistry("HKCU", "key", "value", "data") |
Write a registry value |
CheckRegistry("HKCU", "key", "value") |
Check if already installed (shows reinstall/uninstall prompt) |
CreateShortcut("%INSTALLDIR%/App.exe", "%DESKTOP%", "My App", {
label = "Create Desktop Shortcut", -- display text on the Shortcuts step
isOptional = true, -- show as a user-toggleable checkbox
isSelected = true, -- default checkbox state (default: true)
icon = "%INSTALLDIR%/icon.ico" -- optional shortcut icon
})When isOptional is false (the default), the shortcut is always created. When true, it appears as a checkbox on the Shortcuts wizard step so the user can opt in or out.
| Variable | Expands to |
|---|---|
%INSTALLDIR% |
The user's chosen install directory + suffix |
%PROGRAMFILES% |
C:\Program Files |
%LOCALAPPDATA% |
C:\Users\<user>\AppData\Local |
%DESKTOP% |
User's desktop folder |
%STARTMENU% |
C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs |
%SYSTEM% |
C:\Windows\System32 |
The builder appends data to the end of the base executable:
[original exe bytes] [zip payload] [zip length: 4 bytes, int32] [magic: "PLDN"]
At runtime, BundleReader seeks to the end of its own exe, finds the PLDN marker, reads the zip length, and loads the payload into memory.
MIT

