use std::ptr;
use windows_sys::Win32::{
Foundation::{ERROR_FILE_NOT_FOUND, ERROR_NO_MORE_ITEMS, ERROR_SUCCESS},
Storage::FileSystem::DELETE,
System::Registry::{
HKEY, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_ENUMERATE_SUB_KEYS, KEY_QUERY_VALUE,
KEY_READ, KEY_SET_VALUE, KEY_WOW64_64KEY, KEY_WRITE, REG_DWORD, REG_OPTION_NON_VOLATILE,
REG_SZ, RegCloseKey, RegCreateKeyExW, RegDeleteKeyExW, RegDeleteTreeW, RegEnumKeyExW,
RegOpenKeyExW, RegQueryValueExW, RegSetValueExW,
},
};
use crate::{
error::{Error, Result},
wide::{from_wide, wide},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Root {
LocalMachine,
CurrentUser,
}
impl Root {
fn handle(self) -> HKEY {
match self {
Root::LocalMachine => HKEY_LOCAL_MACHINE,
Root::CurrentUser => HKEY_CURRENT_USER,
}
}
}
pub fn read_string(root: Root, path: &str, name: &str) -> Result<Option<String>> {
let key = match Key::open(root, path, KEY_READ) {
Ok(key) => key,
Err(error) if error.is_not_found() => return Ok(None),
Err(error) => return Err(error),
};
let name = wide(name);
let mut kind = 0u32;
let mut size = 0u32;
let status = unsafe {
RegQueryValueExW(
key.0,
name.as_ptr(),
ptr::null_mut(),
&mut kind,
ptr::null_mut(),
&mut size,
)
};
if status == ERROR_FILE_NOT_FOUND {
return Ok(None);
}
if status != ERROR_SUCCESS {
return Err(Error::code("RegQueryValueExW", status));
}
let mut buffer = vec![0u16; (size as usize).div_ceil(2) + 1];
let mut size = (buffer.len() * 2) as u32;
let status = unsafe {
RegQueryValueExW(
key.0,
name.as_ptr(),
ptr::null_mut(),
&mut kind,
buffer.as_mut_ptr().cast(),
&mut size,
)
};
if status != ERROR_SUCCESS {
return Err(Error::code("RegQueryValueExW", status));
}
Ok(Some(from_wide(&buffer)))
}
pub fn write_string(root: Root, path: &str, name: &str, value: &str) -> Result<()> {
let key = Key::create(root, path)?;
let name = wide(name);
let value = wide(value);
let bytes = (value.len() * 2) as u32;
let status = unsafe {
RegSetValueExW(
key.0,
name.as_ptr(),
0,
REG_SZ,
value.as_ptr().cast(),
bytes,
)
};
if status != ERROR_SUCCESS {
return Err(Error::code("RegSetValueExW", status));
}
Ok(())
}
pub fn write_dword(root: Root, path: &str, name: &str, value: u32) -> Result<()> {
let key = Key::create(root, path)?;
let name = wide(name);
let status = unsafe {
RegSetValueExW(
key.0,
name.as_ptr(),
0,
REG_DWORD,
(&raw const value).cast(),
4,
)
};
if status != ERROR_SUCCESS {
return Err(Error::code("RegSetValueExW", status));
}
Ok(())
}
pub fn subkeys(root: Root, path: &str) -> Result<Vec<String>> {
let key = match Key::open(root, path, KEY_READ) {
Ok(key) => key,
Err(error) if error.is_not_found() => return Ok(Vec::new()),
Err(error) => return Err(error),
};
let mut names = Vec::new();
for index in 0.. {
let mut buffer = [0u16; 256];
let mut length = buffer.len() as u32;
let status = unsafe {
RegEnumKeyExW(
key.0,
index,
buffer.as_mut_ptr(),
&mut length,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
if status == ERROR_NO_MORE_ITEMS {
break;
}
if status != ERROR_SUCCESS {
return Err(Error::code("RegEnumKeyExW", status));
}
names.push(from_wide(&buffer[..length as usize]));
}
Ok(names)
}
pub fn delete_tree(root: Root, path: &str) -> Result<()> {
if path.trim_matches('\\').is_empty() {
return Err(Error::saying(
"RegDeleteTreeW",
"refused to delete everything under a root key",
));
}
let access = DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE;
let key = match Key::open(root, path, access) {
Ok(key) => key,
Err(error) if error.is_not_found() => return Ok(()),
Err(error) => return Err(error),
};
let status = unsafe { RegDeleteTreeW(key.0, ptr::null()) };
if status != ERROR_SUCCESS {
return Err(Error::code("RegDeleteTreeW", status));
}
drop(key);
let path = wide(path);
let status = unsafe { RegDeleteKeyExW(root.handle(), path.as_ptr(), KEY_WOW64_64KEY, 0) };
if status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND {
return Ok(());
}
Err(Error::code("RegDeleteKeyExW", status))
}
struct Key(HKEY);
impl Key {
fn open(root: Root, path: &str, access: u32) -> Result<Self> {
let path = wide(path);
let mut key: HKEY = ptr::null_mut();
let status = unsafe {
RegOpenKeyExW(
root.handle(),
path.as_ptr(),
0,
access | KEY_WOW64_64KEY,
&mut key,
)
};
if status != ERROR_SUCCESS {
return Err(Error::code("RegOpenKeyExW", status));
}
Ok(Self(key))
}
fn create(root: Root, path: &str) -> Result<Self> {
let path = wide(path);
let mut key: HKEY = ptr::null_mut();
let status = unsafe {
RegCreateKeyExW(
root.handle(),
path.as_ptr(),
0,
ptr::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE | KEY_WOW64_64KEY,
ptr::null(),
&mut key,
ptr::null_mut(),
)
};
if status != ERROR_SUCCESS {
return Err(Error::code("RegCreateKeyExW", status));
}
Ok(Self(key))
}
}
impl Drop for Key {
fn drop(&mut self) {
unsafe { RegCloseKey(self.0) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subkeys_lists_what_is_under_a_key() {
let under = subkeys(
Root::LocalMachine,
r"Software\Microsoft\Windows\CurrentVersion",
)
.expect("a key every machine has");
assert!(under.iter().any(|name| name == "Uninstall"), "{under:?}");
}
#[test]
fn a_key_that_is_not_there_has_nothing_under_it() {
let under = subkeys(Root::CurrentUser, r"Software\wininskit-never-existed").unwrap();
assert!(under.is_empty());
}
#[test]
fn deleting_a_root_is_refused() {
for root in ["", r"\"] {
assert!(delete_tree(Root::CurrentUser, root).is_err(), "{root:?}");
}
}
#[test]
fn deleting_a_tree_takes_the_key_and_everything_under_it() {
const KEY: &str = r"Software\wininskit-test-delete-tree";
write_string(
Root::CurrentUser,
&format!(r"{KEY}\Line\Deep"),
"name",
"value",
)
.unwrap();
write_string(Root::CurrentUser, KEY, "", "folder").unwrap();
assert_eq!(
subkeys(Root::CurrentUser, KEY).unwrap(),
vec!["Line".to_string()]
);
delete_tree(Root::CurrentUser, KEY).expect("delete it");
assert_eq!(read_string(Root::CurrentUser, KEY, "").unwrap(), None);
assert!(subkeys(Root::CurrentUser, KEY).unwrap().is_empty());
delete_tree(Root::CurrentUser, KEY).expect("already gone");
}
}