Add UAC elevation support with manifest

- Added guruconnect.manifest requesting highestAvailable privileges
- Using winres to embed manifest in executable
- Added is_elevated() function to detect admin status
- Logs elevation status on startup
- Manifest includes Windows 7-11 compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 16:12:03 -07:00
parent c57429f26a
commit 43f15b0b1a
5 changed files with 123 additions and 3 deletions

25
Cargo.lock generated
View File

@@ -1029,7 +1029,7 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"toml", "toml 0.8.2",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"tray-icon", "tray-icon",
@@ -1037,6 +1037,7 @@ dependencies = [
"uuid", "uuid",
"windows", "windows",
"windows-service", "windows-service",
"winres",
"zstd", "zstd",
] ]
@@ -1061,7 +1062,7 @@ dependencies = [
"sqlx", "sqlx",
"thiserror 1.0.69", "thiserror 1.0.69",
"tokio", "tokio",
"toml", "toml 0.8.2",
"tower", "tower",
"tower-http", "tower-http",
"tracing", "tracing",
@@ -2968,7 +2969,7 @@ dependencies = [
"cfg-expr", "cfg-expr",
"heck 0.5.0", "heck 0.5.0",
"pkg-config", "pkg-config",
"toml", "toml 0.8.2",
"version-compare", "version-compare",
] ]
@@ -3172,6 +3173,15 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.2" version = "0.8.2"
@@ -3969,6 +3979,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winres"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
"toml 0.5.11",
]
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.46.0" version = "0.46.0"

View File

@@ -79,6 +79,7 @@ windows-service = "0.7"
[build-dependencies] [build-dependencies]
prost-build = "0.13" prost-build = "0.13"
winres = "0.1"
[profile.release] [profile.release]
lto = true lto = true

View File

@@ -7,5 +7,26 @@ fn main() -> Result<()> {
// Rerun if proto changes // Rerun if proto changes
println!("cargo:rerun-if-changed=../proto/guruconnect.proto"); println!("cargo:rerun-if-changed=../proto/guruconnect.proto");
// On Windows, embed the manifest for UAC elevation
#[cfg(target_os = "windows")]
{
println!("cargo:rerun-if-changed=guruconnect.manifest");
let mut res = winres::WindowsResource::new();
res.set_manifest_file("guruconnect.manifest");
res.set("ProductName", "GuruConnect Agent");
res.set("FileDescription", "GuruConnect Remote Desktop Agent");
res.set("LegalCopyright", "Copyright (c) AZ Computer Guru");
res.set_icon("guruconnect.ico"); // Optional: add icon if available
// Only compile if the manifest exists
if std::path::Path::new("guruconnect.manifest").exists() {
if let Err(e) = res.compile() {
// Don't fail the build if resource compilation fails
eprintln!("Warning: Failed to compile Windows resources: {}", e);
}
}
}
Ok(()) Ok(())
} }

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="GuruConnect.Agent"
type="win32"
/>
<description>GuruConnect Remote Desktop Agent</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<!-- Request highest available privileges (admin if possible, user otherwise) -->
<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -28,6 +28,10 @@ use tracing_subscriber::FmtSubscriber;
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION}; use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)] #[cfg(windows)]
use windows::core::PCWSTR; use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
/// Extract a 6-digit support code from the executable's filename. /// Extract a 6-digit support code from the executable's filename.
/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe" /// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
@@ -56,6 +60,38 @@ fn extract_code_from_filename() -> Option<String> {
None None
} }
/// Check if the process is running with elevated privileges (Windows only)
#[cfg(windows)]
fn is_elevated() -> bool {
unsafe {
let mut token_handle = windows::Win32::Foundation::HANDLE::default();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() {
return false;
}
let mut elevation = TOKEN_ELEVATION::default();
let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
let result = GetTokenInformation(
token_handle,
TokenElevation,
Some(&mut elevation as *mut _ as *mut _),
size,
&mut size,
);
let _ = windows::Win32::Foundation::CloseHandle(token_handle);
result.is_ok() && elevation.TokenIsElevated != 0
}
}
#[cfg(not(windows))]
fn is_elevated() -> bool {
// On non-Windows, check if running as root
unsafe { libc::geteuid() == 0 }
}
/// Show a message box to the user (Windows only) /// Show a message box to the user (Windows only)
#[cfg(windows)] #[cfg(windows)]
fn show_message_box(title: &str, message: &str) { fn show_message_box(title: &str, message: &str) {
@@ -98,6 +134,13 @@ async fn main() -> Result<()> {
info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION")); info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));
// Check and log elevation status
if is_elevated() {
info!("Running with elevated (administrator) privileges");
} else {
info!("Running with standard user privileges");
}
// Extract support code from executable filename // Extract support code from executable filename
// e.g., GuruConnect-123456.exe -> 123456 // e.g., GuruConnect-123456.exe -> 123456
let support_code = extract_code_from_filename(); let support_code = extract_code_from_filename();