Skip to content

Commit

Permalink
feat: support admin startup (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigoden committed Apr 17, 2024
1 parent d2dbbaf commit 9cb1cd4
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 101 deletions.
37 changes: 8 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,35 +55,14 @@ hotkey = alt+tab
ignore_minimal = no
```

## Running as Administrator at Logon (Optional)

Running `window-switcher.exe` with standard permissions limits its functionality, especially when interacting with system apps like Task Manager that require admin rights. Elevating its privileges enables seamless interaction with all applications.

You can easily accomplish this using Task Scheduler. Just follow these steps:

1. **Open Task Scheduler**: You can do this by searching for "Task Scheduler" in the Start menu.
2. **Create a New Task**: In the Task Scheduler, navigate to "Action" > "Create Task..."
3. **Configure General Tab**:
- Give your task a name (e.g. WindowSwitcher)
- Check "Run only when user is logged on".
- Check "Run with highest privileges".
4. **Configure Triggers Tab**:
- Click "New..."
- For "Begin the task", choose "At logo on"
- For "Settings", check "Special User"
5. **Configure Actions Tab**:
- Click "New...".
- For "Action", choose "Start a program".
- Browse and select the program you want to start or input the path manually.
6. **OK/Save**: Once you've configured your task, click "OK" to save it. You might be prompted to enter an admin password.

For your convenience, we've provided a PowerShell script that automates the process.

Run the following script in an administrator PowerShell window:

```ps1
.\run-as-admin-at-logon.ps1 <path-to-window-switcher.exe>
```
## Running as Administrator (Optional)

While not mandatory, running `window-switcher.exe` with administrator privileges unlocks its full potential, especially when working with system applications like Task Manager that require elevated permissions. This allows for smoother interactions with all types of applications.

**Administrator Privileges and Startup Options:**

* **Running as Admin + Enabling Startup:** Launches `window-switcher.exe` with administrator privileges every time you start your computer.
* **Running without Admin + Enabling Startup:** Launches `window-switcher.exe` with regular user privileges at startup.

## License

Expand Down
35 changes: 0 additions & 35 deletions run-as-admin-at-logon.ps1

This file was deleted.

9 changes: 6 additions & 3 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::startup::Startup;
use crate::trayicon::TrayIcon;
use crate::utils::{
check_error, create_hicon_from_resource, get_foreground_window, get_module_icon,
get_module_icon_ex, get_uwp_icon_data, get_window_user_data, is_iconic_window, list_windows,
set_foreground_window, set_window_user_data, CheckError,
get_module_icon_ex, get_uwp_icon_data, get_window_user_data, is_iconic_window,
is_running_as_admin, list_windows, set_foreground_window, set_window_user_data, CheckError,
};

use crate::painter::{GdiAAPainter, ICON_BORDER_SIZE, WINDOW_BORDER_SIZE};
Expand Down Expand Up @@ -75,7 +75,10 @@ impl App {
false => None,
};

let startup = Startup::init()?;
let is_admin = is_running_as_admin()?;
debug!("is_admin {is_admin}");

let startup = Startup::init(is_admin)?;

let mut app = App {
hwnd,
Expand Down
84 changes: 50 additions & 34 deletions src/startup.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,77 @@
use anyhow::Result;
use windows::core::w;
use windows::core::PCWSTR;
use windows::core::{w, PCWSTR};

use crate::utils::get_exe_path;
use crate::utils::RegKey;
use crate::utils::{get_exe_path, RegKey, ScheduleTask};

const TASK_NAME: &str = "WindowSwitcher";
const HKEY_RUN: PCWSTR = w!("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
const HKEY_NAME: PCWSTR = w!("Window Switcher");

#[derive(Default)]
pub struct Startup {
pub is_enable: bool,
pub task: Option<ScheduleTask>,
pub exe_path: Vec<u16>,
}

impl Startup {
pub fn init() -> Result<Self> {
let enable = Self::detect()?;
Ok(Self { is_enable: enable })
pub fn init(is_admin: bool) -> Result<Self> {
let exe_path = get_exe_path();
let (task, is_enable) = if is_admin {
let exe_path_str = String::from_utf16_lossy(&exe_path);
let task = ScheduleTask::new(TASK_NAME, &exe_path_str);
let is_enable = task.exist()?;
(Some(task), is_enable)
} else {
(None, reg_is_enable(&exe_path)?)
};
Ok(Self {
is_enable,
exe_path,
task,
})
}

pub fn toggle(&mut self) -> Result<()> {
let is_enable = self.is_enable;
if is_enable {
Self::disable()?;
if self.is_enable {
match &self.task {
Some(task) => task.delete()?,
None => reg_disable()?,
}
self.is_enable = false;
} else {
Self::enable()?;
match &self.task {
Some(task) => task.create()?,
None => reg_enable(&self.exe_path)?,
};
self.is_enable = true;
}
Ok(())
}
}

fn detect() -> Result<bool> {
let key = win_run_key()?;
let value = match key.get_value()? {
Some(value) => value,
None => return Ok(false),
};
let path = get_exe_path();
Ok(value == path)
}
fn reg_key() -> Result<RegKey> {
RegKey::new_hkcu(HKEY_RUN, HKEY_NAME)
}

fn enable() -> Result<()> {
let key = win_run_key()?;
let path = get_exe_path();
let path = unsafe { path.align_to::<u8>().1 };
key.set_value(path)?;
Ok(())
}
fn reg_is_enable(exe_path: &[u16]) -> Result<bool> {
let key = reg_key()?;
let value = match key.get_value()? {
Some(value) => value,
None => return Ok(false),
};
Ok(value == exe_path)
}

fn disable() -> Result<()> {
let key = win_run_key()?;
key.delete_value()?;
Ok(())
}
fn reg_enable(exe_path: &[u16]) -> Result<()> {
let key = reg_key()?;
let path = unsafe { exe_path.align_to::<u8>().1 };
key.set_value(path)?;
Ok(())
}

fn win_run_key() -> Result<RegKey> {
RegKey::new_hkcu(HKEY_RUN, HKEY_NAME)
fn reg_disable() -> Result<()> {
let key = reg_key()?;
key.delete_value()?;
Ok(())
}
34 changes: 34 additions & 0 deletions src/utils/admin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use anyhow::{anyhow, Result};
use windows::Win32::{
Foundation::{CloseHandle, HANDLE},
Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY},
System::Threading::{GetCurrentProcess, OpenProcessToken},
};

pub fn is_running_as_admin() -> Result<bool> {
is_running_as_admin_impl()
.map_err(|err| anyhow!("Failed to verify if the program is running as admin, {err}"))
}

fn is_running_as_admin_impl() -> Result<bool> {
let is_elevated = unsafe {
let mut token_handle: HANDLE = HANDLE(0);
let mut elevation = TOKEN_ELEVATION::default();
let mut returned_length = 0;
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle)?;

let token_information = GetTokenInformation(
token_handle,
TokenElevation,
Some(&mut elevation as *mut _ as *mut _),
std::mem::size_of::<TOKEN_ELEVATION>() as u32,
&mut returned_length,
);

CloseHandle(token_handle)?;

token_information?;
elevation.TokenIsElevated != 0
};
Ok(is_elevated)
}
4 changes: 4 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
mod admin;
mod check_error;
mod regedit;
mod schedule_task;
mod single_instance;
mod window;
mod windows_icon;

pub use admin::*;
pub use check_error::*;
pub use regedit::*;
pub use schedule_task::*;
pub use single_instance::*;
pub use window::*;
pub use windows_icon::get_module_icon_ex;
Expand Down
71 changes: 71 additions & 0 deletions src/utils/schedule_task.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::{os::windows::process::CommandExt, process::Command};

use anyhow::{bail, Result};
use windows::Win32::System::Threading::CREATE_NO_WINDOW;

#[derive(Debug)]
pub struct ScheduleTask {
name: String,
exe_path: String,
}

impl ScheduleTask {
pub fn new(name: &str, exe_path: &str) -> Self {
Self {
name: name.to_string(),
exe_path: exe_path.to_string(),
}
}

pub fn create(&self) -> Result<()> {
let output = Command::new("schtasks")
.creation_flags(CREATE_NO_WINDOW.0) // CREATE_NO_WINDOW flag
.args([
"/create",
"/tn",
&self.name,
"/tr",
&self.exe_path,
"/sc",
"onlogon",
"/rl",
"highest",
"/it",
"/f",
])
.output()?;
if !output.status.success() {
bail!(
"Fail to create scheduled task, {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(())
}

pub fn delete(&self) -> Result<()> {
let output = Command::new("schtasks")
.creation_flags(CREATE_NO_WINDOW.0) // CREATE_NO_WINDOW flag
.args(["/delete", "/tn", &self.name, "/f"])
.output()?;
if !output.status.success() {
bail!(
"Fail to delete scheduled task, {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(())
}

pub fn exist(&self) -> Result<bool> {
let output = Command::new("schtasks")
.creation_flags(CREATE_NO_WINDOW.0) // CREATE_NO_WINDOW flag
.args(["/query", "/tn", &self.name])
.output()?;
if output.status.success() {
Ok(true)
} else {
Ok(false)
}
}
}

0 comments on commit 9cb1cd4

Please sign in to comment.