Tutorials

Getting Started

5 min read ยท No prerequisites ยท Wraps demo/01-hello-world

What you'll build

By the end of this tutorial, you'll have a working MSI installer that places a file on the user's computer. Along the way, you'll learn what FalkForge is, how it works, and how to inspect the output.

What is an MSI file?

MSI is the standard Windows Installer format. IT departments use it to deploy software silently via Group Policy and SCCM. Unlike a plain EXE installer, an MSI tracks every file, registry entry, and shortcut it installs — so Windows can repair and cleanly uninstall the software later.

FalkForge compiles your C# description into this format. You describe what to install; FalkForge generates the database tables that Windows Installer needs.

Prerequisites

.NET 10 SDK installed. A text editor or IDE. That's it.

No repo checkout? This walkthrough opens demo/01-hello-world from the FalkForge source tree, but you don't need to clone anything to follow along. dotnet tool install -g FalkForge.Tool --prerelease then forge init --name "My App" scaffolds an equivalent starter project (same Installer.Build entry point, MsiDialogSet.Minimal, and a Start Menu shortcut) you can open side by side with this page — dotnet run builds it the same way. The scaffold skips the localization, cabinet, and reproducible-build calls covered further down; add them yourself once you've read those sections.

The Code

Open demo/01-hello-world/Program.cs. This is the entire installer — about 35 lines of C#.

Imports

using FalkForge;
using FalkForge.Compiler.Msi;
using FalkForge.Localization;
using FalkForge.Models;

These bring in the FalkForge API. FalkForge provides the entry point. Compiler.Msi contains the MSI compiler. Localization adds culture support. Models provides types like KnownFolder and CompressionLevel.

The entry point

// A minimal installer: one file, no features, Minimal dialog set.
return Installer.Build(args, package =>
{
    // ... package configuration ...
}, new MsiCompiler());

This is the entry point. You pass a lambda that describes your installer and a compiler that turns the description into an MSI file. FalkForge calls your lambda, validates the result, and writes the output. The return passes the exit code back to the OS — zero means success.

Deep dive: The build pipeline

Installer.Build runs a five-step pipeline:

  1. Parse args — command-line arguments control output path and verbosity.
  2. Run builder — your lambda populates a PackageBuilder with metadata, files, and settings.
  3. Validate — FalkForge checks for missing fields, invalid GUIDs, and structural errors. Each rule has a code like PKG001.
  4. Compile — the MsiCompiler creates database tables, packs files into cabinets, and writes the MSI.
  5. Return exit code — 0 on success, non-zero on failure.

Product metadata

package.Name = "Hello World";
package.Manufacturer = "Demo";
package.Version = new Version(1, 0, 0);

Describes your product. The name and manufacturer appear in Programs & Features (Add/Remove Programs). The version follows the standard Major.Minor.Build format.

Deep dive: UpgradeCode

Every MSI product has an UpgradeCode — a GUID that ties all versions of your product together. Windows uses it to detect existing installations during upgrades. FalkForge generates one automatically from your product name and manufacturer if you don't set it explicitly.

Important: Never change the UpgradeCode after you ship. If you do, Windows treats the new version as a completely different product and won't upgrade the old one.

Installer UI

package.UseDialogSet(MsiDialogSet.Minimal);

Sets the installer UI. Minimal shows a single Install button — no feature tree, no directory chooser. Perfect for simple installers.

Deep dive: Built-in dialog sets

FalkForge ships five built-in dialog templates:

  • Minimal — Welcome page and progress bar. No choices.
  • InstallDir — Lets the user pick the installation folder.
  • FeatureTree — Displays a tree of features the user can toggle on or off.
  • Mondo — Combines directory selection and feature tree for maximum user control.
  • Advanced — Adds typical/custom/complete install types on top of Mondo.

Localization

package.Localization(loc => loc
    .AddBuiltInCultures()
    .DefaultCulture("en-US")
    .DetectCulture());

Adds the built-in language strings (English and Swedish), sets English as the default, and enables automatic culture detection. The MSI language matches the user's OS locale when possible.

Cabinet settings and reproducible builds

// Cabinet file settings โ€” naming template, compression level, embedding
package.MediaTemplate(mt =>
{
    mt.CabinetTemplate("data{0}.cab");
    mt.CompressionLevel(CompressionLevel.High);
    mt.EmbedCabinet(true);
});

// Enable deterministic builds (same source โ†’ identical MSI output)
package.Reproducible();

// Enable Windows Restart Manager โ€” gracefully close files-in-use during install
package.EnableRestartManagerSupport();

MediaTemplate controls how payload files are compressed. EmbedCabinet(true) stores everything inside the MSI itself — one file to distribute. Reproducible() ensures the same source always produces a byte-identical MSI, useful for build verification. EnableRestartManagerSupport() lets Windows gracefully close applications that have files in use during installation, at every UI level — FalkForge emits the MsiRMFilesInUse dialog that full-UI installs require to make the same prompt.

Deep dive: What are cabinets?

MSI files contain compressed archives called cabinets. Your files are packed into one or more cabinets inside the MSI. The CabinetTemplate controls the naming pattern — {0} is replaced with a sequence number. FalkForge handles the compression and embedding automatically.

When EmbedCabinet is false, the cabinet files sit alongside the MSI as separate .cab files. Embedding is more convenient for distribution; external cabinets are useful for network installs where bandwidth matters.

Adding files

package.Files(files => files
    .Add("payload/hello.txt")
    .To(KnownFolder.ProgramFiles / "Demo" / "HelloWorld"));

Adds a file to the installer and sets where it goes on the target machine. The / operator builds directory paths — this installs to C:\Program Files\Demo\HelloWorld\hello.txt.

Deep dive: Components and features

Under the hood, Windows Installer organizes files into components and features.

A component is the smallest installable unit — typically one file plus its registry entries. Each component has a unique GUID. Windows tracks components to know exactly what's installed.

A feature groups components that the user can select or deselect. Even this simple installer gets a default feature automatically. In more complex installers, you define features explicitly so users can choose what to install.

FalkForge generates component GUIDs and creates the default feature for you. You only need to think about features when you want user-selectable parts.

That's it — 37 lines of C# and you have a working installer.

Try it

dotnet run --project demo/01-hello-world

This compiles the installer definition and produces a .msi file. You can double-click it to install, or inspect it with the CLI:

forge inspect path/to/output.msi

The inspect command shows the MSI's tables, files, features, and properties — useful for verifying the output without installing it.

What's next