Alright, so I don't know if this is the "recommended" way of doing it, but this is the solution I came up with (actual code here):
use windows_registry::Key;
const ENVIRONMENT: &str = "Environment";
const PATH: &str = "Path";
const DELIMITER: char = ';';
/// Appends `path` to the user's `PATH` environment variable.
pub fn add_to_path(path: &str) -> std::io::Result<()> {
let key = open_user_environment_key()?;
let mut path_var = key.get_string(PATH).map_err(std::io::Error::other)?;
if path_var
.rsplit(DELIMITER) // using `rsplit` because it'll likely be near the end
.any(|p| p == path)
{
// already in path, so no work is needed
return Ok(());
}
if !path_var.ends_with(DELIMITER) {
path_var.push(DELIMITER);
}
path_var.push_str(path);
write_to_path_variable(&key, path_var)?;
Ok(())
}
/// Removes all instances of `path` from the user's `PATH` environment variable.
pub fn remove_from_path(path: &str) -> std::io::Result<()> {
let key = open_user_environment_key()?;
let path_var = key.get_string(PATH).map_err(std::io::Error::other)?;
let mut new_path_var = String::with_capacity(path_var.len() - path.len());
let mut needs_delimiter = false;
for p in path_var.split(DELIMITER).filter(|p| *p != path) {
if needs_delimiter {
new_path_var.push(DELIMITER);
}
new_path_var.push_str(p);
needs_delimiter = true;
}
if path_var.len() == new_path_var.len() {
// nothing to remove, so no work is needed
return Ok(());
}
write_to_path_variable(&key, new_path_var)?;
Ok(())
}
/// Opens the user's environment registry key in read/write mode.
fn open_user_environment_key() -> std::io::Result<Key> {
windows_registry::CURRENT_USER
.options()
.read()
.write()
.open(ENVIRONMENT)
.map_err(std::io::Error::other)
}
/// Write `value` to the user's `PATH` environment variable.
fn write_to_path_variable(key: &Key, value: String) -> std::io::Result<()> {
key.set_string(PATH, value).map_err(std::io::Error::other)
}
This seems to work, but I'm open to any suggestions.