use std::fmt;
use thiserror::Error;
use winreg::enums::*;
use winreg::RegKey;
const RUN_KEY_CU: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
const RUN_KEY_LM: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
#[derive(Error, Debug)]
pub enum Error {
#[error("failed to open registry key [{0}]: {1}")]
OpenKey(Scope, #[source] std::io::Error),
#[error("failed to add startup entry '{0}': {1}")]
Add(String, #[source] std::io::Error),
#[error("failed to remove startup entry '{0}': {1}")]
Remove(String, #[source] std::io::Error),
#[error("failed to query startup entry '{0}': {1}")]
Query(String, #[source] std::io::Error),
#[error("failed to enumerate startup entries: {0}")]
Enumerate(#[source] std::io::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Scope {
User,
Machine,
}
impl fmt::Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Scope::User => write!(f, "HKCU"),
Scope::Machine => write!(f, "HKLM"),
}
}
}
impl Scope {
fn open_run_key(&self) -> Result<RegKey, Error> {
let (hkey, path) = match self {
Scope::User => (HKEY_CURRENT_USER, RUN_KEY_CU),
Scope::Machine => (HKEY_LOCAL_MACHINE, RUN_KEY_LM),
};
RegKey::predef(hkey)
.open_subkey_with_flags(path, KEY_READ | KEY_WRITE)
.map_err(|e| Error::OpenKey(*self, e))
}
fn open_run_key_readonly(&self) -> Result<RegKey, Error> {
let (hkey, path) = match self {
Scope::User => (HKEY_CURRENT_USER, RUN_KEY_CU),
Scope::Machine => (HKEY_LOCAL_MACHINE, RUN_KEY_LM),
};
RegKey::predef(hkey)
.open_subkey_with_flags(path, KEY_READ)
.map_err(|e| Error::OpenKey(*self, e))
}
pub fn list(&self) -> Result<Vec<Entry>, Error> {
let key = self.open_run_key_readonly()?;
let mut entries = Vec::new();
for name_result in key.enum_values() {
let (name, value) = name_result.map_err(Error::Enumerate)?;
let command = match value {
winreg::RegValue {
vtype: REG_SZ | REG_EXPAND_SZ,
bytes,
} => String::from_utf16(
&bytes
.as_chunks::<2>()
.0
.iter()
.map(|b| u16::from_le_bytes([b[0], b[1]]))
.collect::<Vec<_>>(),
)
.unwrap_or_else(|_| String::from_utf8_lossy(&bytes).into_owned())
.trim_end_matches('\0')
.to_owned(),
_ => continue, };
entries.push(Entry {
name,
command,
scope: *self,
});
}
Ok(entries)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Entry {
pub name: String,
pub command: String,
pub scope: Scope,
}
impl Entry {
pub fn remove(&self) -> Result<(), Error> {
crate::remove(&self.name, self.scope)
}
}
pub fn add(name: &str, command: &str, scope: Scope) -> Result<(), Error> {
let key = scope.open_run_key()?;
key.set_value(name, &command)
.map_err(|e| Error::Add(name.into(), e))?;
Ok(())
}
pub fn remove(name: &str, scope: Scope) -> Result<(), Error> {
let key = scope.open_run_key()?;
match key.delete_value(name) {
Ok(()) => Ok(()),
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(Error::Remove(name.into(), e)),
}
}
pub fn exists(name: &str, scope: Scope) -> Result<bool, Error> {
let key = scope.open_run_key_readonly()?;
match key.get_value::<String, _>(name) {
Ok(_) => Ok(true),
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(Error::Query(name.into(), e)),
}
}
pub fn list() -> Result<Vec<Entry>, Error> {
let mut all = Vec::new();
for scope in [Scope::User, Scope::Machine] {
match scope.list() {
Ok(mut entries) => all.append(&mut entries),
Err(_) if scope == Scope::Machine => {}
Err(e) => return Err(e),
}
}
Ok(all)
}