# msi — guide
> **Class:** `guide` · audience: a techie packaging a Windows product. The *what* is in
> [`msi-design.md`](msi-design.md).
## Describe the Windows product
```rust
use skidbladnir::msi::{WindowsComponentSpec, WindowsServiceSpec, ServiceStart, WixFile};
let spec = WindowsComponentSpec {
name: "holger".into(),
display_name: "Holger".into(),
version: "0.1.5".into(),
upgrade_code: "…stable-upgrade-code…".into(),
install_dir: r"C:\Program Files\Nordisk\Holger".into(),
service: WindowsServiceSpec {
account: "LocalSystem".into(),
start: ServiceStart::Auto,
delayed: true,
env_block: vec![("HOLGER_DATA".into(), r"C:\ProgramData\Holger".into())],
},
files: vec![WixFile { source: "holger.exe".into(), id: "holgerexe".into() }],
};
```
## Render the WiX source (no tooling needed)
```rust
let wxs = skidbladnir::msi::render_wxs(&spec); // pure — a real Product.wxs string
// each file carries a stable component GUID; the service block is registered
```
## Mint a stable component GUID
```rust
let g = skidbladnir::msi::component_guid("holger-main-exe"); // same id → same GUID, every build
```
Stable GUIDs are what make MSI **upgrades** (not side-by-side installs) work — never randomize them.
## Build the MSI (needs podman + the WiX-in-Wine image)
```rust
use skidbladnir::msi::WixBuilder;
let builder = WixBuilder::new("registry/nordisk/wix-wine:4");
if builder.podman_available() {
builder.build(&spec, std::path::Path::new("holger.msi"))?;
} else {
// build() would bail with a clear message and leave the rendered WXS staged for inspection
}
```
## Gotchas
- `WixBuilder` is gated on **podman at runtime**, not a cargo feature — a machine without podman
gets an honest error (and the staged WXS), never a fake `.msi`.
- The `upgrade_code` and every component GUID must be **stable across versions** — that is how
Windows recognizes an upgrade of the same product. `guid_v5` guarantees stability from the id.