Repository: tauri-apps/tauri (language: rust)

Issue:
Run tauri project as a windows service
### Describe the problem

I aim to run **my Tauri project** as a **Windows service**. To achieve this, I need to use the WiX Util extension, which includes the ServiceInstall element necessary for setting up Windows services.

Here’s the code I am trying to use:
```
<DirectoryRef Id="INSTALLDIR">
...
 <Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
                <File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
                 <ServiceInstall Id="AgentUiServiceInstaller" Type="ownProcess" Vital="yes" Name="GetAppAgentUIService" DisplayName="GetAppAgentUIService" Description="GetApp's Agent UI is running here" Start="demand" Account="LocalSystem" ErrorControl="ignore" Interactive="no">
                    <util:ServiceConfig FirstFailureActionType='restart' SecondFailureActionType='restart' ThirdFailureActionType='restart' RestartServiceDelayInSeconds='60' ResetPeriodInDays='1'/>
                </ServiceInstall>

                <ServiceControl Id="AgentUiServiceControl" Name="GetAppAgentUIService" Stop="both" Remove="uninstall" Start="install" Wait="yes" />
            </Component>
...
</DirectoryRef>
```

## The problem
The current Tauri template does not support adding the WiX Util extension in the main template, so I am attempting to use a custom fragment to include it. However, this workaround has significant limitations:

### 1. Installing the Full Component via Custom Fragment:
The ```.exe``` file cannot be installed directly through the custom fragment because there is no way to reference the ```app_exe_source``` variable within the fragment. The custom fragment does not support accessing bundler variables like ```{{app_exe_source}}```, which limits its usability.

### 2. Installing the Service Without the File Element:
If I attempt to install the service without specifying the file element, there is no way to link the service to the executable file. As a result, the service configuration cannot specify which file to execute when starting the service.


### Describe the solution you'd like

Ideally, there would be two potential solutions, both of which seem to be quite simple to implement::

1. Enable support for the WiX Util extension directly in the main Tauri template, like you doing with the custom fragment.
2. Allow custom fragments to access bundler variables like ```{{app_exe_source}}```, enabling the fragment to correctly reference the ```.exe``` file and establish a link between the service and the executable.

### Alternatives considered

_No response_

### Additional context

_No response_


Discussion / hints:
I believe you can just use a custom template which would solve your problem.

> * Enable support for the WiX Util extension directly in the main Tauri template, like you doing with the custom fragment.

`WixUIExtension.dll` and `WixUtilExtension.dll` are loaded by default, and any extension specified in a fragment will be automatically discovered using this regex `"http://schemas.microsoft.com/wix/(\\w+)"`

> * Allow custom fragments to access bundler variables like `{{app_exe_source}}`, enabling the fragment to correctly reference the `.exe` file and establish a link between the service and the executable.

This we can do

Fix patch:
diff --git a/.changes/bundler-fragment-handlebars.md b/.changes/bundler-fragment-handlebars.md
new file mode 100644
index 000000000000..d836fec27ad5
--- /dev/null
+++ b/.changes/bundler-fragment-handlebars.md
@@ -0,0 +1,7 @@
+---
+"tauri-bundler": "minor:feat"
+"tauri-cli": "minor:feat"
+---
+
+Process `bundle > windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it.
+
diff --git a/Cargo.lock b/Cargo.lock
index 07a50590efe6..c40491d85073 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8758,9 +8758,9 @@ dependencies = [
 
 [[package]]
 name = "tao"
-version = "0.30.2"
+version = "0.30.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06e48d7c56b3f7425d061886e8ce3b6acfab1993682ed70bef50fd133d721ee6"
+checksum = "a0dbbebe82d02044dfa481adca1550d6dd7bd16e086bc34fa0fbecceb5a63751"
 dependencies = [
  "bitflags 2.6.0",
  "cocoa 0.26.0",
diff --git a/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs b/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs
index ce6d6170c160..f6942a47f97c 100644
--- a/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs
+++ b/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs
@@ -106,10 +106,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
   // initialize shell script template.
   let mut handlebars = Handlebars::new();
   handlebars.register_escape_fn(handlebars::no_escape);
-  handlebars
-    .register_template_string("appimage", include_str!("./appimage"))
-    .expect("Failed to register template for handlebars");
-  let temp = handlebars.render("appimage", &sh_map)?;
+  let temp = handlebars.render_template(include_str!("./appimage"), &sh_map)?;
 
   // create the shell script file in the target/ folder.
   let sh_file = output_path.join("build_appimage.sh");
diff --git a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs
index f9daaf25718e..bc633a4be9f3 100644
--- a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs
+++ b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs
@@ -726,38 +726,26 @@ pub fn build_wix_app_installer(
     );
 
     // Create the update task XML
-    let mut skip_uac_task = Handlebars::new();
+    let skip_uac_task = Handlebars::new();
     let xml = include_str!("./update-task.xml");
-    skip_uac_task
-      .register_template_string("update.xml", xml)
-      .map_err(|e| e.to_string())
-      .expect("Failed to setup Update Task handlebars");
+    let update_content = skip_uac_task.render_template(xml, &data)?;
     let temp_xml_path = output_path.join("update.xml");
-    let update_content = skip_uac_task.render("update.xml", &data)?;
     fs::write(temp_xml_path, update_content)?;
 
     // Create the Powershell script to install the task
     let mut skip_uac_task_installer = Handlebars::new();
     skip_uac_task_installer.register_escape_fn(handlebars::no_escape);
     let xml = include_str!("./install-task.ps1");
-    skip_uac_task_installer
-      .register_template_string("install-task.ps1", xml)
-      .map_err(|e| e.to_string())
-      .expect("Failed to setup Update Task Installer handlebars");
+    let install_script_content = skip_uac_task_installer.render_template(xml, &data)?;
     let temp_ps1_path = output_path.join("install-task.ps1");
-    let install_script_content = skip_uac_task_installer.render("install-task.ps1", &data)?;
     fs::write(temp_ps1_path, install_script_content)?;
 
     // Create the Powershell script to uninstall the task
     let mut skip_uac_task_uninstaller = Handlebars::new();
     skip_uac_task_uninstaller.register_escape_fn(handlebars::no_escape);
     let xml = include_str!("./uninstall-task.ps1");
-    skip_uac_task_uninstaller
-      .register_template_string("uninstall-task.ps1", xml)
-      .map_err(|e| e.to_string())
-      .expect("Failed to setup Update Task Uninstaller handlebars");
+    let install_script_content = skip_uac_task_uninstaller.render_template(xml, &data)?;
     let temp_ps1_path = output_path.join("uninstall-task.ps1");
-    let install_script_content = skip_uac_task_uninstaller.render("uninstall-task.ps1", &data)?;
     fs::write(temp_ps1_path, install_script_content)?;
 
     data.insert("enable_elevated_update_task", to_json(true));
@@ -772,7 +760,9 @@ pub fn build_wix_app_installer(
   let extension_regex = Regex::new("\"http://schemas.microsoft.com/wix/(\\w+)\"")?;
   for fragment_path in fragment_paths {
     let fragment_path = current_dir.join(fragment_path);
-    let fragment = fs::read_to_string(&fragment_path)?;
+    let fragment_content = fs::read_to_string(&fragment_path)?;
+    let fragment_handlebars = Handlebars::new();
+    let fragment = fragment_handlebars.render_template(&fragment_content, &data)?;
     let mut extensions = Vec::new();
     for cap in extension_regex.captures_iter(&fragment) {
       extensions.push(wix_toolset_path.join(format!("Wix{}.dll", &cap[1])));


Test patch: