Răsfoiți Sursa

feat(bundler) Update on Windows without UAC Prompt (#2155)

Ben Briggs 4 ani în urmă
părinte
comite
58129e0a95

+ 5 - 0
.changes/wix-update-on-windows-without-uac.md

@@ -0,0 +1,5 @@
+--
+"tauri-bundler": patch
+--
+
+Added option to update a Tauri application without Windows UAC Prompt.

+ 41 - 1
core/tauri/src/updater/core.rs

@@ -526,10 +526,12 @@ fn copy_files_and_run(tmp_dir: tempfile::TempDir, extract_path: PathBuf) -> Resu
 #[cfg(target_os = "windows")]
 #[allow(clippy::unnecessary_wraps)]
 fn copy_files_and_run(tmp_dir: tempfile::TempDir, _extract_path: PathBuf) -> Result {
+  use crate::api::file::Move;
+
   let paths = read_dir(&tmp_dir)?;
   // This consumes the TempDir without deleting directory on the filesystem,
   // meaning that the directory will no longer be automatically deleted.
-  tmp_dir.into_path();
+  let tmp_path = tmp_dir.into_path();
   for path in paths {
     let found_path = path?.path();
     // we support 2 type of files exe & msi for now
@@ -542,6 +544,44 @@ fn copy_files_and_run(tmp_dir: tempfile::TempDir, _extract_path: PathBuf) -> Res
 
       exit(0);
     } else if found_path.extension() == Some(OsStr::new("msi")) {
+      if let Some(bin_name) = std::env::current_exe()
+        .ok()
+        .and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
+        .and_then(|s| s.into_string().ok())
+      {
+        let product_name = bin_name.replace(".exe", "");
+
+        // Check if there is a task that enables the updater to skip the UAC prompt
+        let update_task_name = format!("Update {} - Skip UAC", product_name);
+        if let Ok(status) = Command::new("schtasks")
+          .arg("/QUERY")
+          .arg("/TN")
+          .arg(update_task_name.clone())
+          .status()
+        {
+          if status.success() {
+            // Rename the MSI to the match file name the Skip UAC task is expecting it to be
+            let temp_msi = tmp_path.with_file_name(bin_name).with_extension("msi");
+            Move::from_source(&found_path)
+              .to_dest(&temp_msi)
+              .expect("Unable to move update MSI");
+            let exit_status = Command::new("schtasks")
+              .arg("/RUN")
+              .arg("/TN")
+              .arg(update_task_name)
+              .status()
+              .expect("failed to start updater task");
+
+            if exit_status.success() {
+              // Successfully launched task that skips the UAC prompt
+              exit(0);
+            }
+          }
+
+          // Failed to run update task. Following UAC Path
+        }
+      }
+
       // restart should be handled by WIX as we exit the process
       Command::new("msiexec.exe")
         .arg("/i")

+ 8 - 0
examples/updater/src-tauri/tauri.conf.json

@@ -34,6 +34,14 @@
         "minimumSystemVersion": "",
         "useBootstrapper": false,
         "exceptionDomain": ""
+      },
+      "windows": {
+        "certificateThumbprint": null,
+        "digestAlgorithm": null,
+        "timestampUrl": null,
+        "wix": {
+          "enableElevatedUpdateTask": true
+        }
       }
     },
     "allowlist": {

+ 2 - 0
tooling/bundler/src/bundle/settings.rs

@@ -196,6 +196,8 @@ pub struct WixSettings {
   pub skip_webview_install: bool,
   /// The path to the LICENSE file.
   pub license: Option<String>,
+  /// Create an elevated update task within Windows Task Scheduler
+  pub enable_elevated_update_task: bool,
 }
 
 /// The Windows bundle settings.

+ 29 - 1
tooling/bundler/src/bundle/windows/msi/wix.rs

@@ -423,7 +423,7 @@ pub fn build_wix_app_installer(
         let license_contents = std::fs::read_to_string(&license_path)?;
         let license_rtf = format!(
           r#"{{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{{\fonttbl{{\f0\fnil\fcharset0 Calibri;}}}}
-{{\*\generator Riched20 10.0.18362}}\viewkind4\uc1 
+{{\*\generator Riched20 10.0.18362}}\viewkind4\uc1
 \pard\sa200\sl276\slmult1\f0\fs22\lang9 {}\par
 }}
  "#,
@@ -513,6 +513,7 @@ pub fn build_wix_app_installer(
   let mut handlebars = Handlebars::new();
   let mut has_custom_template = false;
   let mut install_webview = true;
+  let mut enable_elevated_update_task = false;
 
   if let Some(wix) = &settings.windows().wix {
     data.insert("component_group_refs", to_json(&wix.component_group_refs));
@@ -522,6 +523,7 @@ pub fn build_wix_app_installer(
     data.insert("merge_refs", to_json(&wix.merge_refs));
     fragment_paths = wix.fragment_paths.clone();
     install_webview = !wix.skip_webview_install;
+    enable_elevated_update_task = wix.enable_elevated_update_task;
 
     if let Some(temp_path) = &wix.template {
       let template = std::fs::read_to_string(temp_path)?;
@@ -550,6 +552,32 @@ pub fn build_wix_app_installer(
 
   create_dir_all(&output_path)?;
 
+  if enable_elevated_update_task {
+    // Create the update task XML
+    let mut skip_uac_task = Handlebars::new();
+    let xml = include_str!("../templates/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 temp_xml_path = output_path.join("update.xml");
+    let update_content = skip_uac_task.render("update.xml", &data)?;
+    write(&temp_xml_path, update_content)?;
+
+    // Create the Powershell script to install the task
+    let mut skip_uac_task_installer = Handlebars::new();
+    let xml = include_str!("../templates/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 temp_ps1_path = output_path.join("install-task.ps1");
+    let install_script_content = skip_uac_task_installer.render("install-task.ps1", &data)?;
+    write(&temp_ps1_path, install_script_content.clone())?;
+
+    data.insert("enable_elevated_update_task", to_json(true));
+  }
+
   let main_wxs_path = output_path.join("main.wxs");
   write(&main_wxs_path, handlebars.render("main.wxs", &data)?)?;
 

+ 28 - 0
tooling/bundler/src/bundle/windows/templates/install-task.ps1

@@ -0,0 +1,28 @@
+# Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-License-Identifier: MIT
+# Adapted from https://superuser.com/a/532109
+param([string]$ChangeDir, [switch]$Elevated)
+
+function Test-Admin {
+    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
+    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
+}
+
+if ((Test-Admin) -eq $false) {
+    if ($elevated) {
+        # tried to elevate, did not work, aborting
+    }
+    else {
+        $InstallDirectory = Get-Location
+        $ArgList = ('-File "{0}" -ChangeDir "{1}" -Elevated' -f ($myinvocation.MyCommand.Definition, $InstallDirectory))
+        Start-Process powershell.exe -WindowStyle hidden -Verb RunAs -ArgumentList $ArgList
+    }
+    exit
+}
+
+if ($ChangeDir -ne "") {
+    # Change directories to the install path
+    Set-Location -Path $ChangeDir
+}
+SCHTASKS.EXE /CREATE /XML update.xml /TN "Update {{{product_name}}} - Skip UAC" /F

+ 42 - 0
tooling/bundler/src/bundle/windows/templates/main.wxs

@@ -87,6 +87,14 @@
                 <File Id="Path_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
             </Component>
             {{/each~}}
+            {{#if enable_elevated_update_task}}
+            <Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
+                <File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
+            </Component>
+            <Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
+                <File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
+            </Component>
+            {{/if}}
             {{{resources}}}
             <Component Id="CMP_UninstallShortcut" Guid="*">
 
@@ -147,6 +155,11 @@
                 <ComponentRef Id="{{ resource_file_id }}"/>
             {{/each~}}
 
+            {{#if enable_elevated_update_task}}
+                <ComponentRef Id="UpdateTask" />
+                <ComponentRef Id="UpdateTaskInstaller" />
+            {{/if}}
+
             <Feature Id="ShortcutsFeature"
                 Title="Shortcuts"
                 Level="1">
@@ -200,6 +213,35 @@
         </InstallExecuteSequence>
         {{/if}}
 
+        {{#if enable_elevated_update_task}}
+        <!-- Install an elevated update task within Windows Task Scheduler -->
+        <CustomAction
+            Id="CreateUpdateTask"
+            Return="check"
+            Directory="INSTALLDIR"
+            Execute="commit"
+            Impersonate="yes"
+            ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
+        <InstallExecuteSequence>
+            <Custom Action='CreateUpdateTask' Before='InstallFinalize'>
+                NOT(REMOVE)
+            </Custom>
+        </InstallExecuteSequence>
+        <!-- Remove elevated update task during uninstall -->
+        <CustomAction
+            Id="DeleteUpdateTask"
+            Return="check"
+            Directory="INSTALLDIR"
+            Execute="deferred"
+            Impersonate="no"
+            ExeCommand="powershell.exe -WindowStyle hidden &quot;schtasks /DELETE /TN 'Update {{{product_name}}} - Skip UAC' /F&quot;" />
+        <InstallExecuteSequence>
+            <Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
+                (REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
+            </Custom>
+        </InstallExecuteSequence>
+        {{/if}}
+
         <SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
     </Product>
 </Wix>

+ 43 - 0
tooling/bundler/src/bundle/windows/templates/update-task.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-16"?>
+<!--
+  Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+  SPDX-License-Identifier: Apache-2.0
+  SPDX-License-Identifier: MIT
+-->
+<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
+  <RegistrationInfo>
+    <URI>\Update {{{product_name}}} - Skip UAC</URI>
+  </RegistrationInfo>
+  <Triggers />
+  <Principals>
+    <Principal id="Author">
+      <LogonType>InteractiveToken</LogonType>
+      <RunLevel>HighestAvailable</RunLevel>
+    </Principal>
+  </Principals>
+  <Settings>
+    <MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
+    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
+    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
+    <AllowHardTerminate>false</AllowHardTerminate>
+    <StartWhenAvailable>false</StartWhenAvailable>
+    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
+    <IdleSettings>
+      <StopOnIdleEnd>true</StopOnIdleEnd>
+      <RestartOnIdle>false</RestartOnIdle>
+    </IdleSettings>
+    <AllowStartOnDemand>true</AllowStartOnDemand>
+    <Enabled>true</Enabled>
+    <Hidden>false</Hidden>
+    <RunOnlyIfIdle>false</RunOnlyIfIdle>
+    <WakeToRun>false</WakeToRun>
+    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
+    <Priority>7</Priority>
+  </Settings>
+  <Actions Context="Author">
+    <Exec>
+      <Command>cmd.exe</Command>
+      <Arguments>/c "msiexec.exe /i %TEMP%\\{{{product_name}}}.msi /qb+"</Arguments>
+    </Exec>
+  </Actions>
+</Task>

+ 2 - 0
tooling/cli.rs/config_definition.rs

@@ -80,6 +80,8 @@ pub struct WixConfig {
   pub skip_webview_install: bool,
   /// Path to the license file.
   pub license: Option<String>,
+  #[serde(default)]
+  pub enable_elevated_update_task: bool,
 }
 
 #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]

+ 4 - 0
tooling/cli.rs/schema.json

@@ -1288,6 +1288,10 @@
             "type": "string"
           }
         },
+        "enableElevatedUpdateTask": {
+          "default": false,
+          "type": "boolean"
+        },
         "featureGroupRefs": {
           "default": [],
           "type": "array",

+ 1 - 0
tooling/cli.rs/src/helpers/config.rs

@@ -26,6 +26,7 @@ impl From<WixConfig> for tauri_bundler::WixSettings {
       merge_refs: config.merge_refs,
       skip_webview_install: config.skip_webview_install,
       license: config.license,
+      enable_elevated_update_task: config.enable_elevated_update_task,
     }
   }
 }