A lightweight, deterministic package installer for Windows supporting .msi and .nupkg formats.
Inspired by the elegant simplicity of macOS package installation /usr/sbin/installer — this tool provides a simple, Chocolatey-free alternative for installing packages with a clean command-line interface.
This tool follows the Unix philosophy: Do one thing and do it well.
Chocolatey is a powerful tool, but it's often excessive for simple package installation tasks. Its package management model is more akin to brew on macOS—feature-rich, but with added complexity, state management, and overhead that aren't necessary when you just want to install a package quickly and cleanly.
All we want to run is installer.exe --pkg /path/to/pkg --target / on Windows.
- Maintains its own cache and complex state management
- Requires a "source" concept with folder scanning
- Overly complex for simple package installation
- Heavy dependencies and slow performance
- Uses
.nupkgextension (we prefer.msi)
A minimal, focused installer that:
- No cache - runs directly from package location
- No --source - you specify
--pkg <pathToPackage>directly - Lightweight - mimics
/usr/sbin/installerbehavior - Deterministic - predictable, simple operation
- Native MSI support - in-process DTF API, no
msiexecdependency - Native .NET - single executable, no external dependencies
Regardless of output format (.msi or .nupkg), every cimipkg
project has the same source layout:
my-package/
├── build-info.yaml # Package metadata (required)
├── payload/ # Files to install (optional)
├── .env # Signing credentials + script variables (optional, gitignored)
└── scripts/ # PowerShell install/uninstall scripts (optional)
├── preinstall.ps1
├── postinstall.ps1
└── uninstall.ps1
cimipkg compiles this into whichever output format you choose. The source
structure never changes — only the packaging does.
.msi is now the default output format for cimipkg
and the recommended format for all new packages. cimipkg builds MSIs natively
via the DTF API (WixToolset.Dtf.WindowsInstaller) — no WiX Toolset or
msiexec dependency at build time.
sbin-installer processes cimipkg-built MSIs via the same DTF API in-process,
providing better error handling, progress callbacks, and conflict resolution
than shell-out to msiexec.exe. It can install any standard MSI, but for
third-party or non-cimipkg MSIs you're generally better off using msiexec
directly — sbin-installer's upgrade logic (UpgradeCode detection, conflict
removal) is tuned for the conventions cimipkg embeds.
What's inside a cimipkg-built MSI:
| MSI feature | Detail |
|---|---|
| Payload files | Embedded in a compressed CAB archive inside the MSI |
| PowerShell scripts | Compiled to silent VBScript custom actions with pwsh 7 runtime detection (falls back to powershell.exe 5.1) |
| build-info.yaml | Serialized into the CIMIAN_PKG_BUILD_INFO MSI property for metadata round-trip |
| UpgradeCode | Deterministic UUID v5 derived from product.identifier — stable across versions, enables automatic upgrade |
| File versioning | PE FileVersion extracted from binaries and stamped in the MSI File table; unversioned files get a synthetic version from the package version so every build overwrites on-disk files |
| Signing | Authenticode-signed via signtool if a certificate is configured |
How sbin-installer processes MSIs:
- Reads
ProductName,ProductVersion,UpgradeCodefrom the MSI property table - Detects conflicting products (by UpgradeCode or display name) — handles WiX-to-cimipkg transitions — and removes them before installing, so the new package's files are laid down last
- Installs the new MSI silently via the in-process DTF API (
ALLUSERS=1,REBOOT=ReallySuppress). NoREINSTALL/repair pass — a plain install lets the cimipkg MSI run its own sequence (scripts via custom actions, payload overwrite via synthetic File.Version, supersede via the Upgrade table). Invoking the repair engine would trip Windows SecureRepair and abort with 1603/1625 on managed clients, so it is deliberately never used. - Logs to
%TEMP%\cimian_msi_*.log
A .nupkg file is a ZIP archive following the NuGet package specification,
commonly used by Chocolatey. Build with cimipkg --nupkg <project>.
package.nupkg
├── [Content_Types].xml
├── package.nuspec
├── _rels/
│ └── .rels
└── tools/
├── chocolateyInstall.ps1 # Payload copy + postinstall scripts
├── chocolateyBeforeModify.ps1 # Preinstall scripts (runs before upgrade/uninstall)
├── chocolateyUninstall.ps1 # Uninstall script
└── payload/
└── example.txt
Chocolatey limitation: chocolateyBeforeModify.ps1 only fires when an
existing package is being upgraded or uninstalled — not on a fresh install.
sbin-installer does not have this limitation and runs chocolateyBeforeModify.ps1
unconditionally before every install.
sbin-installer installs .nupkg (NuGet/Chocolatey) packages with automatic compatibility shims:
What's Supported:
- All NuGet schema versions (2010/07, 2011/08, 2012/06, 2013/01, etc.)
- Common Chocolatey helper functions automatically shimmed:
Install-ChocolateyPath,Install-ChocolateyEnvironmentVariableInstall-ChocolateyPackage,Install-ChocolateyZipPackageGet-ChocolateyWebFile,Get-ChocolateyUnzip,Install-ChocolateyShortcut- Plus utility functions for architecture detection and environment management
- Automatic detection of
tools/chocolatey*.ps1scripts with helper injection - Tested with real packages (osquery 5.19.0, and more)
Example:
installer.exe osquery.5.19.0.nupkgWhat's NOT Supported:
- Package management database and state tracking
- Chocolatey sources/feeds and repository management
- Automatic dependency resolution
- Package upgrades and side-by-side versions
- All ~50+ Chocolatey helpers (only common ones)
When to Use:
- Yes: Deploying via Intune, SCCM, or scripts with local/network
.nupkgfiles - Yes: Need lightweight, deterministic installation without Chocolatey overhead
- Yes: Want macOS-style
/usr/sbin/installerbehavior on Windows - No: Need full package management with dependencies and updates → Use Chocolatey
MSI packages:
- Read metadata (ProductName, ProductVersion, UpgradeCode) from MSI property table
- Detect and remove conflicting products (by UpgradeCode or display name) before installing
- Install silently via the DTF in-process API — plain install, no
REINSTALL/repair pass - Custom actions (preinstall/postinstall/uninstall) run automatically as part of the MSI sequence
nupkg packages:
- Extract the package to a temporary directory
- Run pre-install script (
chocolateyBeforeModify.ps1) - Mirror payload to the install location
- Run post-install script (
chocolateyInstall.ps1) - Clean up temporary extraction directory
installer --pkg package.msi --target /
installer --pkg package.nupkg --target /
installer --pkg C:\packages\myapp.msi --target C:\Program Filesinstaller --pkginfo --pkg package.msi
installer --query RestartAction --pkg package.msi
installer --query name --pkg package.msi
installer --query version --pkg package.msiinstaller --dominfo
installer --volinfo
installer --versinstaller --pkg package.msi --target / --verbose
installer --pkg package.msi --target / --dumplogClosely mirrors macOS /usr/sbin/installer options:
| Option | Description |
|---|---|
--pkg <path> |
Path to the package file to install (.msi or .nupkg) |
--target <device> |
Target directory (/, CurrentUserHomeDirectory, drive letter, or path) |
--pkginfo |
Display package information |
--dominfo |
Display domains available for installation |
--volinfo |
Display volumes available for installation |
--query <flag> |
Query package metadata (RestartAction, name, version, etc.) |
--verbose |
Display detailed information |
--verboseR |
Display detailed information with simplified progress |
--dumplog |
Write log information to standard error |
--plist |
Display information in XML plist format |
--allowUntrusted |
Allow untrusted package signatures |
--vers |
Display version information |
--config |
Display command line parameters |
The --target parameter supports multiple formats:
/→C:\(system root)CurrentUserHomeDirectory→ User's home folder/Volumes/Drive→Drive:\(Windows drive mapping)C→C:\(drive letter)- Any absolute path → Used directly
Packages are created using cimipkg. MSI is the default output format:
# Build an MSI (default)
cimipkg <project_directory>
# Build a Chocolatey .nupkg
cimipkg --nupkg <project_directory>project/
├── build-info.yaml # Package metadata (required)
├── payload/ # Files to install (optional)
├── .env # Signing credentials + script variables (optional, gitignored)
└── scripts/ # PowerShell install/uninstall scripts (optional)
├── preinstall.ps1 # Runs before payload is copied
├── postinstall.ps1 # Runs after payload is copied
└── uninstall.ps1 # Runs when the package is removed
product:
name: MyApplication
version: ${TIMESTAMP}
identifier: com.company.myapplication
developer: ACME Corp
description: A sample application package
install_location: C:\Program Files\MyApplication
postinstall_action: none
signing_thumbprint: ${SIGNING_CERT_THUMBPRINT}Any ${NAME} placeholder in build-info.yaml is resolved from built-in tokens
(TIMESTAMP, DATE, DATETIME), a .env file, or process environment
variables. This lets signing credentials stay out of source control. See the
cimipkg README for
the full placeholder reference.
- Automatic elevation detection - Warns when admin rights are required
- Target validation - Ensures appropriate permissions for installation paths
- Script execution - Runs PowerShell scripts with bypass execution policy
- Signature validation - Supports
--allowUntrustedfor unsigned packages
| Feature | Chocolatey | sbin-installer |
|---|---|---|
| Cache management | Complex | None |
| Source repositories | Required | Direct file path |
| Package format | .nupkg |
.msi (default) + .nupkg |
| MSI support | Via msiexec shelling |
Native DTF in-process API |
| Dependency resolution | Full tree | Simple list |
| Script support | tools\chocolatey*.ps1 | Yes (Supported via shim + scripts/*.ps1 + MSI custom actions) |
| Chocolatey helpers | Native | Yes (Common helpers shimmed) |
| BeforeModify on fresh install | No | Yes (always runs preinstall) |
| Performance | Slow | Fast |
| Complexity | High | Minimal |
| Package database | Yes | No |
| Use case | Package management | Direct installation |
MSI Installer (Recommended)
msiexec /i sbin-installer.msi /quietPortable Executable
installer --pkg package.msi --target /- GitHub Actions CI/CD: Automated builds for x64/ARM64/x86 architectures
- Code Signing: Certificate-based signing with timestamp validation
- Silent Installation:
msiexec /i sbin-installer.msi /quiet - Elevation Handling: Auto-detects
sudo(Win11 22H2+) or PowerShell UAC - Path Management: Uses
%ProgramW6432%for proper architecture detection
.\build.ps1
.\build.ps1 -Clean -Test
.\build.ps1 -SkipMsi
.\build.ps1 -Version "2025.09.18.1200"
.\build.ps1 -Configuration Release.\build.ps1 -ListCerts
.\build.ps1 -FindCertSubject "Your Company"
.\build.ps1
.\build.ps1 -CertificateThumbprint "THUMBPRINT"
.\build.ps1 -Install.\build.ps1 -Install
.\install.ps1 -ForceFeatures:
- Auto-detects and uses code signing certificates when available
- Auto-detects architecture (x64, ARM64, x86) and uses appropriate Program Files
- Uses
%ProgramW6432%for x64/ARM64 systems - Adds
C:\Program Files\sbinto system PATH - Enables
installercommand globally
dotnet publish src/installer/installer.csproj --configuration Release --runtime win-x64 --self-contained -o dist
.\build.ps1 -SkipMsi
.\build.ps1 -Version "2025.12.25.1430"Note: MSI packaging is included by default but may require elevated permissions on some systems. Use -SkipMsi for environments with restricted permissions. Version format follows YYYY.MM.DD.HHMM timestamp convention.
- Windows 10/11 or Windows Server 2019+
- PowerShell for script execution
- Administrator privileges for system-wide installations