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:
25
Cargo.lock
generated
25
Cargo.lock
generated
@@ -1029,7 +1029,7 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"toml",
|
||||
"toml 0.8.2",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tray-icon",
|
||||
@@ -1037,6 +1037,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"windows",
|
||||
"windows-service",
|
||||
"winres",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
@@ -1061,7 +1062,7 @@ dependencies = [
|
||||
"sqlx",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"toml",
|
||||
"toml 0.8.2",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
@@ -2968,7 +2969,7 @@ dependencies = [
|
||||
"cfg-expr",
|
||||
"heck 0.5.0",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"toml 0.8.2",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
@@ -3172,6 +3173,15 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
@@ -3969,6 +3979,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
|
||||
@@ -79,6 +79,7 @@ windows-service = "0.7"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
||||
winres = "0.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -7,5 +7,26 @@ fn main() -> Result<()> {
|
||||
// Rerun if proto changes
|
||||
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(())
|
||||
}
|
||||
|
||||
36
agent/guruconnect.manifest
Normal file
36
agent/guruconnect.manifest
Normal 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>
|
||||
@@ -28,6 +28,10 @@ use tracing_subscriber::FmtSubscriber;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
|
||||
#[cfg(windows)]
|
||||
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.
|
||||
/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
|
||||
@@ -56,6 +60,38 @@ fn extract_code_from_filename() -> Option<String> {
|
||||
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)
|
||||
#[cfg(windows)]
|
||||
fn show_message_box(title: &str, message: &str) {
|
||||
@@ -98,6 +134,13 @@ async fn main() -> Result<()> {
|
||||
|
||||
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
|
||||
// e.g., GuruConnect-123456.exe -> 123456
|
||||
let support_code = extract_code_from_filename();
|
||||
|
||||
Reference in New Issue
Block a user