Use when implementing dependency installation systems - covers bundled portable executables in MSI installers, unified detection/execution code paths, and Windows installer best practices
Implement bundled dependency installation for Windows MSI installers. Use when building portable executable distributions with unified detection/execution code paths that check bundled locations first, ensuring ~100% success without external package managers.
/plugin marketplace add SecurityRonin/ronin-marketplace/plugin install packaging-skills@ronin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
See also: For the full DMG/MSI/DEB packaging workflow, GitHub Actions release automation, and Homebrew updates, see the
build-cross-platform-packagesskill.
Use this skill when:
Modern approach: Bundle portable executables directly in the installer.
Traditional Approach (DEPRECATED):
Layer 1: Scoop/Chocolatey with DNS fallback
Layer 2: Alternative package manager
Layer 3: Public DNS retry
Layer 4: Manual instructions
Modern Approach (RECOMMENDED):
Layer 1: Bundled portable executables in MSI
- No network required during installation
- Specific tested versions
- ~100% installation success rate
- No dependency on external package managers
Why bundled dependencies:
Trade-offs:
Complete workflow from download to MSI:
# GitHub Actions workflow
jobs:
prepare-bundled-deps:
runs-on: ubuntu-latest
steps:
- name: Download portable dependencies
run: |
# ExifTool (portable Perl)
curl -L https://exiftool.org/exiftool-13.41_64.zip -o exiftool.zip
# Tesseract (Windows installer, extract with 7z)
curl -L https://github.com/UB-Mannheim/tesseract/.../tesseract.exe -o tesseract.exe
# FFmpeg (static build)
curl -L https://github.com/BtbN/FFmpeg-Builds/.../ffmpeg.zip -o ffmpeg.zip
# ImageMagick (portable)
curl -L https://github.com/ImageMagick/.../ImageMagick.7z -o imagemagick.7z
- name: Extract and organize
run: |
mkdir -p deps/{exiftool,tesseract,ffmpeg,imagemagick}
unzip exiftool.zip -d deps/exiftool/
7z x tesseract.exe -o deps/tesseract/
unzip ffmpeg.zip && cp bin/* deps/ffmpeg/
7z x imagemagick.7z -o deps/imagemagick/
- name: Create single ZIP
run: |
cd deps
zip -r ../deps-windows.zip .
- name: Upload as artifact (not release asset)
uses: actions/upload-artifact@v4
with:
name: bundled-deps-windows
path: deps-windows.zip
retention-days: 7
build-msi:
needs: prepare-bundled-deps
runs-on: windows-latest
steps:
- name: Download bundled deps artifact
uses: actions/download-artifact@v4
with:
name: bundled-deps-windows
path: .
- name: Extract for WiX
run: |
Expand-Archive deps-windows.zip -Destination target/bundled-deps
- name: Build MSI
run: wix build installer/app.wxs
Key points:
Directory structure in MSI:
<Package Name="myapp" Version="1.0.0">
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="myapp">
<!-- Application binaries -->
<Directory Id="DEPSFOLDER" Name="deps">
<Directory Id="EXIFTOOLFOLDER" Name="exiftool" />
<Directory Id="TESSERACTFOLDER" Name="tesseract" />
<Directory Id="FFMPEGFOLDER" Name="ffmpeg" />
<Directory Id="IMAGEMAGICKFOLDER" Name="imagemagick" />
</Directory>
</Directory>
</StandardDirectory>
<!-- ExifTool Component -->
<DirectoryRef Id="EXIFTOOLFOLDER">
<Component Id="ExifToolDep">
<File Source="target\bundled-deps\exiftool\exiftool.exe" KeyPath="yes" />
<Environment Id="PATH_EXIFTOOL"
Name="PATH"
Value="[EXIFTOOLFOLDER]"
Permanent="yes"
Part="last"
Action="set"
System="yes" />
</Component>
</DirectoryRef>
<!-- Similar for Tesseract, FFmpeg, ImageMagick -->
</Package>
Installation result:
C:\Program Files\myapp\
├── myapp.exe
├── myapp-gui.exe
└── deps\
├── exiftool\exiftool.exe
├── tesseract\tesseract.exe
├── ffmpeg\ffmpeg.exe + ffprobe.exe
└── imagemagick\magick.exe
Each directory automatically added to system PATH during installation.
THE CRITICAL RULE: Detection and execution MUST use identical path resolution.
Problem: Split code paths cause "detected but won't execute" bugs.
// ❌ WRONG - Duplicate logic
fn is_tool_available() -> bool {
Command::new("tool").output().is_ok() // PATH only
}
fn use_tool() {
Command::new("tool")... // Different code path!
}
Solution: Single source of truth for path resolution.
#[derive(Debug, Clone, PartialEq)]
pub enum Dependency {
ExifTool,
Tesseract,
FFmpeg,
ImageMagick,
}
impl Dependency {
pub fn name(&self) -> &str {
match self {
Dependency::ExifTool => "exiftool",
Dependency::Tesseract => "tesseract",
Dependency::FFmpeg => "ffmpeg",
Dependency::ImageMagick => "imagemagick",
}
}
/// Directory name where MSI installs the tool
fn bundled_dir_name(&self) -> &str {
match self {
Dependency::ExifTool => "exiftool",
Dependency::Tesseract => "tesseract",
Dependency::FFmpeg => "ffmpeg",
Dependency::ImageMagick => "imagemagick",
}
}
/// Actual executable name (may differ from directory name)
fn exe_name(&self) -> &str {
match self {
Dependency::ExifTool => "exiftool",
Dependency::Tesseract => "tesseract",
Dependency::FFmpeg => "ffmpeg",
Dependency::ImageMagick => "magick", // ImageMagick v7+ uses magick.exe
}
}
/// Find executable path - checks bundled location FIRST
pub fn find_executable(&self) -> Option<PathBuf> {
#[cfg(windows)]
{
// Priority 1: Bundled MSI location
if let Ok(programfiles) = std::env::var("PROGRAMFILES") {
let bundled_dir = PathBuf::from(&programfiles)
.join("myapp")
.join("deps")
.join(self.bundled_dir_name());
let exe_path = bundled_dir.join(format!("{}.exe", self.exe_name()));
if exe_path.exists() {
return Some(exe_path);
}
}
}
// Priority 2: System PATH
if which::which(self.exe_name()).is_ok() {
return Some(PathBuf::from(self.exe_name()));
}
// Priority 3: Fallback names (e.g., "convert" for ImageMagick)
for name in self.fallback_names() {
if which::which(name).is_ok() {
return Some(PathBuf::from(name));
}
}
None
}
/// Create Command - ALWAYS uses find_executable()
pub fn create_command(&self) -> Option<Command> {
self.find_executable().map(Command::new)
}
/// Check availability - uses create_command()
pub fn is_available(&self) -> bool {
if let Some(mut cmd) = self.create_command() {
let result = match self {
Dependency::ExifTool => cmd.arg("-ver").output(),
Dependency::Tesseract => cmd.arg("--version").output(),
Dependency::FFmpeg => cmd.arg("-version").output(),
Dependency::ImageMagick => cmd.arg("-version").output(),
};
result.map(|o| o.status.success()).unwrap_or(false)
} else {
false
}
}
fn fallback_names(&self) -> &[&str] {
match self {
Dependency::ImageMagick => &["magick", "convert"],
_ => &[],
}
}
}
Usage in application code:
// ✅ CORRECT - Unified code path
fn run_ocr(image_path: &Path) -> Result<String> {
let mut cmd = Dependency::Tesseract
.create_command()
.context("Tesseract not available")?;
let output = cmd
.arg(image_path)
.arg("stdout")
.output()?;
Ok(String::from_utf8(output.stdout)?)
}
Critical details:
is_available()) uses same code as execution (create_command())Problem: GUI launched from MSI inherits old environment before PATH refresh.
Solution: Prepend bundled directories to PATH at application startup.
// In main.rs - call before any dependency usage
fn setup_path() {
use std::env;
let current_path = env::var("PATH").unwrap_or_default();
let mut additional_paths: Vec<String> = if cfg!(windows) {
let mut paths = Vec::new();
// Bundled MSI installer dependencies (HIGHEST PRIORITY)
if let Ok(programfiles) = env::var("PROGRAMFILES") {
paths.push(format!("{}\\myapp\\deps\\exiftool", programfiles));
paths.push(format!("{}\\myapp\\deps\\tesseract", programfiles));
paths.push(format!("{}\\myapp\\deps\\ffmpeg", programfiles));
paths.push(format!("{}\\myapp\\deps\\imagemagick", programfiles));
}
paths
} else {
vec![]
};
// Build new PATH
if cfg!(windows) {
let new_path = if additional_paths.is_empty() {
current_path
} else {
format!("{};{}", additional_paths.join(";"), current_path)
};
env::set_var("PATH", new_path);
}
}
fn main() {
setup_path(); // Call FIRST
// Rest of application logic...
}
Why this works:
When bundling portable executables, you MUST comply with licenses:
Verify all licenses allow redistribution:
Create /LICENSES/ directory with license texts
Create /third_party/<dep>/NOTICE attribution files
For LGPL (FFmpeg): Include source link or code
Update README.md with third-party attributions
Create .gitignore in /deps/ (never commit binaries to git)
REUSE spec structure:
project/
├── LICENSES/
│ ├── GPL-3.0-or-later.txt
│ ├── Apache-2.0.txt
│ └── MIT.txt
├── third_party/
│ ├── exiftool/
│ │ └── NOTICE
│ ├── tesseract/
│ │ └── NOTICE
│ └── ffmpeg/
│ └── NOTICE (include source link for LGPL)
└── README.md (attribution section)
Example NOTICE file:
ExifTool by Phil Harvey
License: GPL-1.0-or-later OR Artistic-2.0
Homepage: https://exiftool.org
Bundled version: 13.41
Copyright (c) 2003-2024 Phil Harvey
target/bundled-deps/ before WiX build7z x tesseract-setup.exe -o deps/tesseract/autobuild-2024-11-04-12-55)# WRONG - Visible to end users, causes confusion
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
files: deps-windows.zip
Problem: Users think they need to download both MSI and ZIP.
Solution: Use GitHub Actions artifacts instead.
// WRONG - Duplicate logic
fn is_tool_available() -> bool {
Command::new("tool").output().is_ok()
}
fn use_tool() {
Command::new("tool")... // Different path!
}
Problem: Detection succeeds but execution fails.
Solution: Both use Dependency::Tool methods.
// WRONG - Fails for MSI-spawned processes
pub fn find_executable(&self) -> Option<PathBuf> {
which::which(self.name()).ok()
}
Problem: MSI-spawned GUI has stale PATH.
Solution: Check bundled location FIRST, then PATH.
// WRONG - Assumes directory matches executable
let path = bundled_dir.join(format!("{}.exe", self.name()));
Problem: ImageMagick installed to imagemagick/ but executable is magick.exe.
Solution: Separate bundled_dir_name() and exe_name().
From production deployment (bundled dependency architecture):
✅ Installation success rate: ~100% (up from ~90% with Scoop/Chocolatey)
✅ Instant dependency availability
✅ Version control
✅ Detection reliability
Common issue resolved: GUI showing "dependencies missing" despite successful MSI installation.
Root cause: MSI-spawned GUI inherited old environment without updated PATH.
Solution: Bundled location checked first, GUI setup_path() prepends at startup.
Modern dependency installation uses bundled portable executables embedded in MSI installers.
Key principles:
Directory structure:
C:\Program Files\myapp\deps\
├── exiftool\exiftool.exe
├── tesseract\tesseract.exe (+ tessdata/)
├── ffmpeg\ffmpeg.exe + ffprobe.exe
└── imagemagick\magick.exe (+ DLLs)
Detection priority:
C:\Program Files\myapp\deps\)Result: ~100% installation success, instant availability, version control, no network dependency.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.