Skip to content

Commit c6d721d

Browse files
drrakendu78claude
andcommitted
release: v1.0.9 - fix os error 740 (auto-elevate before update)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 616f822 commit c6d721d

8 files changed

Lines changed: 193 additions & 5 deletions

File tree

.release-notes-v1.0.9.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# UniCreate - Patch Notes v1.0.9
2+
3+
Date: 2026-05-13
4+
Release tag: `v1.0.9`
5+
Repository: `drrakendu78/UniCreate`
6+
7+
## Highlights
8+
9+
- **Auto-update no longer fails with `os error 740`** on non-admin sessions: UniCreate now restarts itself as administrator automatically before launching the updater. One-click update again works end to end.
10+
- **All updater error messages translated to English** (previously some surfaced the localized Windows error text, e.g. "L'opération demandée nécessite une élévation").
11+
12+
## Changes
13+
14+
### 1) Fix — `ERROR_ELEVATION_REQUIRED` (740) on auto-update
15+
16+
- Windows installer-detection heuristic forces UAC elevation for any `.exe` whose name contains `updater` / `setup` / `install`. Launching `UniCreate-Updater.exe` from a non-admin process therefore fails with OS error 740, which the previous build surfaced as a cryptic toast.
17+
- UniCreate now checks `is_running_as_admin` before triggering the updater. When the app is not elevated, it persists the pending download URL + filename in `localStorage` (`unicreate-pending-update`), calls a new `restart_as_admin` Tauri command, and resumes the install automatically once the app reboots with elevated privileges. A single UAC prompt now suffices — no need to manually relaunch the app as admin.
18+
19+
Impacted files:
20+
- `src-tauri/Cargo.toml` (added `is_elevated` dependency on Windows)
21+
- `src-tauri/src/lib.rs` (new `is_running_as_admin` and `restart_as_admin` Tauri commands)
22+
- `src-tauri/src/github.rs` (`start_silent_update` now translates `raw_os_error() == 740` into a clear English message)
23+
- `src/App.tsx` (`openUpdateAction` checks admin first + resumes pending update on next boot)
24+
25+
### 2) Updater error messages are now consistently in English
26+
27+
- The pre-existing English strings stay unchanged.
28+
- The 740 case used to fall through to `std::io::Error::Display`, which Windows localizes (French users saw "L'opération demandée nécessite une élévation"). It now returns: *"Update requires administrator privileges. Please restart UniCreate as administrator and try again."*
29+
30+
Impacted files:
31+
- `src-tauri/src/github.rs`
32+
33+
### 3) Version bump
34+
35+
- App version updated to `1.0.9`.
36+
37+
## Checksums (SHA-256)
38+
39+
| File | SHA-256 |
40+
|------|---------|
41+
| `UniCreate_1.0.9_x64-setup.exe` | `a56a1fb2a1b498e92da853f8f380bc357ed0ead56760e40c80f5af64760464fd` |
42+
| `UniCreate_1.0.9_x64_en-US.msi` | `4f59bd03348e5024090aec16f564341f6163f5c6fa1398645d10604dbf55c3e4` |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "unicreate",
33
"private": true,
4-
"version": "1.0.8",
4+
"version": "1.0.9",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "unicreate"
3-
version = "1.0.8"
3+
version = "1.0.9"
44
description = "UniCreate - WinGet Manifest Creator GUI"
55
authors = ["Drrakendu78"]
66
edition = "2021"
@@ -28,3 +28,6 @@ dirs = "6.0"
2828
base64 = "0.22"
2929
zip = "2"
3030
keyring = { version = "3", features = ["windows-native"] }
31+
32+
[target.'cfg(windows)'.dependencies]
33+
is_elevated = "0.1"

src-tauri/src/github.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,16 @@ pub fn start_silent_update(download_url: &str, file_name: Option<&str>) -> Resul
283283
"--pid", &current_pid.to_string(),
284284
])
285285
.spawn()
286-
.map_err(|e| format!("Cannot start updater: {}", e))?;
286+
.map_err(|e| {
287+
// ERROR_ELEVATION_REQUIRED (740): Windows installer-detection heuristic
288+
// auto-elevates any .exe whose name contains "updater" / "setup" / "install".
289+
// Running the updater from a non-admin process fails with this code.
290+
if e.raw_os_error() == Some(740) {
291+
"Update requires administrator privileges. Please restart UniCreate as administrator and try again.".to_string()
292+
} else {
293+
format!("Cannot start updater: {}", e)
294+
}
295+
})?;
287296

288297
Ok(())
289298
}

src-tauri/src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,64 @@ fn start_silent_update(download_url: String, file_name: Option<String>) -> Resul
131131
github::start_silent_update(&download_url, file_name.as_deref())
132132
}
133133

134+
#[tauri::command]
135+
fn is_running_as_admin() -> bool {
136+
#[cfg(target_os = "windows")]
137+
{
138+
is_elevated::is_elevated()
139+
}
140+
#[cfg(not(target_os = "windows"))]
141+
{
142+
false
143+
}
144+
}
145+
146+
#[tauri::command]
147+
async fn restart_as_admin(app_handle: tauri::AppHandle) -> Result<(), String> {
148+
#[cfg(target_os = "windows")]
149+
{
150+
use std::os::windows::process::CommandExt;
151+
use std::process::Command;
152+
const CREATE_NO_WINDOW: u32 = 0x08000000;
153+
154+
let exe = std::env::current_exe().map_err(|e| e.to_string())?;
155+
let exe_str = exe
156+
.to_str()
157+
.ok_or_else(|| "Invalid executable path".to_string())?;
158+
159+
if exe_str.contains("WindowsApps") {
160+
return Err("Microsoft Store apps cannot be elevated to administrator.".to_string());
161+
}
162+
163+
let escaped = exe_str.replace("'", "''");
164+
let ps_command = format!("Start-Process -FilePath '{}' -Verb RunAs", escaped);
165+
let powershell_path = std::path::Path::new("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
166+
let ps = if powershell_path.exists() {
167+
powershell_path.to_str().unwrap()
168+
} else {
169+
"powershell.exe"
170+
};
171+
172+
match Command::new(ps)
173+
.creation_flags(CREATE_NO_WINDOW)
174+
.args(["-NoProfile", "-WindowStyle", "Hidden", "-Command", &ps_command])
175+
.spawn()
176+
{
177+
Ok(_) => {
178+
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
179+
app_handle.exit(0);
180+
Ok(())
181+
}
182+
Err(e) => Err(format!("Failed to restart as administrator: {}. Try running the app as admin manually.", e)),
183+
}
184+
}
185+
#[cfg(not(target_os = "windows"))]
186+
{
187+
let _ = app_handle;
188+
Err("Elevation not supported on this platform.".to_string())
189+
}
190+
}
191+
134192
#[tauri::command]
135193
async fn start_device_flow() -> Result<github::DeviceFlowStart, String> {
136194
github::start_device_flow().await
@@ -301,6 +359,8 @@ pub fn run() {
301359
check_package_exists,
302360
check_app_update,
303361
start_silent_update,
362+
is_running_as_admin,
363+
restart_as_admin,
304364
start_device_flow,
305365
poll_device_flow,
306366
authenticate_github,

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "UniCreate",
4-
"version": "1.0.8",
4+
"version": "1.0.9",
55
"identifier": "com.drrakendu78.unicreate",
66
"build": {
77
"beforeDevCommand": "npm run dev",

src/App.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import logoMarkUrl from "@/assets/logo-mark.png";
2121

2222
const appWindow = getCurrentWindow();
2323
const DISMISSED_UPDATE_VERSION_KEY = "unicreate-dismissed-update-version";
24+
const PENDING_UPDATE_KEY = "unicreate-pending-update";
2425

2526
function Toasts() {
2627
const { toasts, removeToast } = useToastStore();
@@ -294,6 +295,43 @@ function App() {
294295
};
295296
}, [autoCheckUpdates]);
296297

298+
// Resume a pending update after the user restarted the app as admin.
299+
// PENDING_UPDATE_KEY is set just before calling restart_as_admin in openUpdateAction.
300+
// Once we're back with elevated privileges, we kick off start_silent_update directly.
301+
useEffect(() => {
302+
let cancelled = false;
303+
(async () => {
304+
let pending: { downloadUrl: string; downloadName?: string; latestVersion?: string } | null = null;
305+
try {
306+
const raw = localStorage.getItem(PENDING_UPDATE_KEY);
307+
if (raw) pending = JSON.parse(raw);
308+
} catch { pending = null; }
309+
if (!pending?.downloadUrl) return;
310+
311+
const isAdmin = await invoke<boolean>("is_running_as_admin").catch(() => false);
312+
if (cancelled) return;
313+
314+
// Always clear the flag once: avoids an infinite restart loop if anything fails.
315+
try { localStorage.removeItem(PENDING_UPDATE_KEY); } catch {}
316+
317+
if (!isAdmin) return; // elevation refused or failed: give up silently
318+
319+
try {
320+
await invoke("start_silent_update", {
321+
downloadUrl: pending.downloadUrl,
322+
fileName: pending.downloadName,
323+
});
324+
if (pending.latestVersion) {
325+
localStorage.setItem(DISMISSED_UPDATE_VERSION_KEY, pending.latestVersion);
326+
}
327+
await appWindow.close().catch(() => {});
328+
} catch (e) {
329+
addToast(`Update failed: ${String(e)}`, "error");
330+
}
331+
})();
332+
return () => { cancelled = true; };
333+
}, [addToast]);
334+
297335
const dismissUpdatePopup = () => {
298336
if (appUpdateInfo) {
299337
localStorage.setItem(DISMISSED_UPDATE_VERSION_KEY, appUpdateInfo.latestVersion);
@@ -310,6 +348,32 @@ function App() {
310348

311349
setIsApplyingUpdate(true);
312350

351+
// Windows installer-detection heuristic forces UAC elevation for any .exe whose
352+
// name contains "updater" / "setup". Launching the updater from a non-admin
353+
// process fails with ERROR_ELEVATION_REQUIRED (740). To avoid this, we restart
354+
// the app as admin first, persist the update info, and resume the install on
355+
// next boot (see the useEffect that reads PENDING_UPDATE_KEY).
356+
const isAdmin = await invoke<boolean>("is_running_as_admin").catch(() => true);
357+
if (!isAdmin) {
358+
try {
359+
localStorage.setItem(PENDING_UPDATE_KEY, JSON.stringify({
360+
downloadUrl: appUpdateInfo.downloadUrl,
361+
downloadName: appUpdateInfo.downloadName,
362+
latestVersion: appUpdateInfo.latestVersion,
363+
}));
364+
} catch {}
365+
addToast("Restarting as administrator to install the update...", "info");
366+
try {
367+
await invoke("restart_as_admin");
368+
// App closes after this call.
369+
} catch (e) {
370+
try { localStorage.removeItem(PENDING_UPDATE_KEY); } catch {}
371+
setIsApplyingUpdate(false);
372+
addToast(`Failed to restart as administrator: ${String(e)}`, "error");
373+
}
374+
return;
375+
}
376+
313377
try {
314378
await invoke("start_silent_update", {
315379
downloadUrl: appUpdateInfo.downloadUrl,

0 commit comments

Comments
 (0)