From 7910d20e503c7c8fe921718a982a6844256bf115 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:54:36 -0500 Subject: [PATCH] add a new command to query the registry on windows (#6670) * add a new command to query the registry on windows * cross platform tweaks * return nushell datatype * change visibility of exec and registry commands --- Cargo.lock | 1 + crates/nu-command/Cargo.toml | 3 + crates/nu-command/src/default_context.rs | 7 +- crates/nu-command/src/system/exec.rs | 30 +- crates/nu-command/src/system/mod.rs | 6 + .../nu-command/src/system/registry_query.rs | 273 ++++++++++++++++++ 6 files changed, 294 insertions(+), 26 deletions(-) create mode 100644 crates/nu-command/src/system/registry_query.rs diff --git a/Cargo.lock b/Cargo.lock index 2ae8631b1..3821a5416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2759,6 +2759,7 @@ dependencies = [ "wax", "which", "windows", + "winreg", ] [[package]] diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index b131b4267..285f72137 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -93,6 +93,9 @@ wax = { version = "0.5.0", features = ["diagnostics"] } rusqlite = { version = "0.28.0", features = ["bundled"], optional = true } sqlparser = { version = "0.23.0", features = ["serde"], optional = true } +[target.'cfg(windows)'.dependencies] +winreg = "0.10.1" + [target.'cfg(unix)'.dependencies] umask = "2.0.0" users = "0.11.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 0372e1bec..b36104821 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -158,12 +158,17 @@ pub fn create_default_context() -> EngineState { bind_command! { Benchmark, Complete, - Exec, External, NuCheck, Sys, }; + #[cfg(unix)] + bind_command! { Exec } + + #[cfg(windows)] + bind_command! { RegistryQuery } + #[cfg(any( target_os = "android", target_os = "linux", diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index 43e1be17e..91a29622b 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -1,8 +1,11 @@ +use super::run_external::ExternalCommand; +use nu_engine::{current_dir, env_to_strings, CallExt}; use nu_protocol::{ - ast::Call, + ast::{Call, Expr}, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, }; +use std::os::unix::process::CommandExt; #[derive(Clone)] pub struct Exec; @@ -57,19 +60,11 @@ impl Command for Exec { } } -#[cfg(unix)] fn exec( engine_state: &EngineState, stack: &mut Stack, call: &Call, ) -> Result { - use std::os::unix::process::CommandExt; - - use super::run_external::ExternalCommand; - use nu_engine::{current_dir, env_to_strings, CallExt}; - use nu_protocol::ast::Expr; - use nu_protocol::Spanned; - let name: Spanned = call.req(engine_state, stack, 0)?; let name_span = name.span; @@ -113,18 +108,3 @@ fn exec( Vec::new(), )) } - -#[cfg(not(unix))] -fn exec( - _engine_state: &EngineState, - _stack: &mut Stack, - call: &Call, -) -> Result { - Err(ShellError::GenericError( - "Error on exec".to_string(), - "exec is not supported on your platform".to_string(), - Some(call.head), - None, - Vec::new(), - )) -} diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index f0504589c..15bcf13fa 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -1,5 +1,6 @@ mod benchmark; mod complete; +#[cfg(unix)] mod exec; mod nu_check; #[cfg(any( @@ -9,12 +10,15 @@ mod nu_check; target_os = "windows" ))] mod ps; +#[cfg(windows)] +mod registry_query; mod run_external; mod sys; mod which_; pub use benchmark::Benchmark; pub use complete::Complete; +#[cfg(unix)] pub use exec::Exec; pub use nu_check::NuCheck; #[cfg(any( @@ -24,6 +28,8 @@ pub use nu_check::NuCheck; target_os = "windows" ))] pub use ps::Ps; +#[cfg(windows)] +pub use registry_query::RegistryQuery; pub use run_external::{External, ExternalCommand}; pub use sys::Sys; pub use which_::Which; diff --git a/crates/nu-command/src/system/registry_query.rs b/crates/nu-command/src/system/registry_query.rs new file mode 100644 index 000000000..a46a4ccd3 --- /dev/null +++ b/crates/nu-command/src/system/registry_query.rs @@ -0,0 +1,273 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, + Signature, Span, Spanned, SyntaxShape, Value, +}; +use winreg::{enums::*, RegKey}; + +#[derive(Clone)] +pub struct RegistryQuery; + +struct RegistryQueryArgs { + hkcr: bool, + hkcu: bool, + hklm: bool, + hku: bool, + hkpd: bool, + hkpt: bool, + hkpnls: bool, + hkcc: bool, + hkdd: bool, + hkculs: bool, + key: String, +} + +impl Command for RegistryQuery { + fn name(&self) -> &str { + "registry query" + } + + fn signature(&self) -> Signature { + Signature::build("registry query") + .switch("hkcr", "query the hkey_classes_root hive", None) + .switch("hkcu", "query the hkey_current_user hive", None) + .switch("hklm", "query the hkey_local_machine hive", None) + .switch("hku", "query the hkey_users hive", None) + .switch("hkpd", "query the hkey_performance_data hive", None) + .switch("hkpt", "query the hkey_performance_text hive", None) + .switch("hkpnls", "query the hkey_performance_nls_text hive", None) + .switch("hkcc", "query the hkey_current_config hive", None) + .switch("hkdd", "query the hkey_dyn_data hive", None) + .switch( + "hkculs", + "query the hkey_current_user_local_settings hive", + None, + ) + .required("key", SyntaxShape::String, "registry key to query") + .optional( + "value", + SyntaxShape::String, + "optionally supply a registry value to query", + ) + .category(Category::System) + } + + fn usage(&self) -> &str { + "Query the Windows registry." + } + + fn extra_usage(&self) -> &str { + "Currently supported only on Windows systems." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + registry_query(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Query the HKEY_CURRENT_USER hive", + example: "registry query --hkcu environment", + result: None, + }, + Example { + description: "Query the HKEY_LOCAL_MACHINE hive", + example: r"registry query --hklm 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'", + result: None, + }, + ] + } +} + +fn registry_query( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let registry_key: Spanned = call.req(engine_state, stack, 0)?; + let registry_key_span = ®istry_key.clone().span; + let registry_value: Option> = call.opt(engine_state, stack, 1)?; + + let reg_params = RegistryQueryArgs { + hkcr: call.has_flag("hkcr"), + hkcu: call.has_flag("hkcu"), + hklm: call.has_flag("hklm"), + hku: call.has_flag("hku"), + hkpd: call.has_flag("hkpd"), + hkpt: call.has_flag("hkpt"), + hkpnls: call.has_flag("hkpnls"), + hkcc: call.has_flag("hkcc"), + hkdd: call.has_flag("hkdd"), + hkculs: call.has_flag("hkculs"), + key: registry_key.item, + }; + + let reg_key = get_reg_key(reg_params)?; + + if registry_value.is_none() { + let mut reg_values = vec![]; + for (name, val) in reg_key.enum_values().flatten() { + let (nu_value, reg_type) = reg_value_to_nu_value(val); + reg_values.push(Value::Record { + cols: vec!["name".to_string(), "value".to_string(), "type".to_string()], + vals: vec![ + Value::string(name, Span::test_data()), + nu_value, + Value::string(format!("{:?}", reg_type), Span::test_data()), + ], + span: *registry_key_span, + }) + } + Ok(reg_values.into_pipeline_data(engine_state.ctrlc.clone())) + } else { + match registry_value { + Some(value) => { + let reg_value = reg_key.get_raw_value(value.item.as_str()); + match reg_value { + Ok(val) => { + let (nu_value, reg_type) = reg_value_to_nu_value(val); + Ok(Value::Record { + cols: vec!["name".to_string(), "value".to_string(), "type".to_string()], + vals: vec![ + Value::string(value.item, Span::test_data()), + nu_value, + Value::string(format!("{:?}", reg_type), Span::test_data()), + ], + span: value.span, + } + .into_pipeline_data()) + } + Err(_) => Ok(Value::Error { + error: ShellError::GenericError( + "Unable to find registry key/value".to_string(), + format!("Registry value: {} was not found", value.item), + Some(value.span), + None, + Vec::new(), + ), + } + .into_pipeline_data()), + } + } + None => Ok(Value::nothing(Span::test_data()).into_pipeline_data()), + } + } +} + +fn get_reg_key(reg_params: RegistryQueryArgs) -> Result { + let mut key_count = 0; + let registry_key = if reg_params.hkcr { + key_count += 1; + RegKey::predef(HKEY_CLASSES_ROOT).open_subkey(reg_params.key)? + } else if reg_params.hkcu { + key_count += 1; + RegKey::predef(HKEY_CURRENT_USER).open_subkey(reg_params.key)? + } else if reg_params.hklm { + key_count += 1; + RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey(reg_params.key)? + } else if reg_params.hku { + key_count += 1; + RegKey::predef(HKEY_USERS).open_subkey(reg_params.key)? + } else if reg_params.hkpd { + key_count += 1; + RegKey::predef(HKEY_PERFORMANCE_DATA).open_subkey(reg_params.key)? + } else if reg_params.hkpt { + key_count += 1; + RegKey::predef(HKEY_PERFORMANCE_TEXT).open_subkey(reg_params.key)? + } else if reg_params.hkpnls { + key_count += 1; + RegKey::predef(HKEY_PERFORMANCE_NLSTEXT).open_subkey(reg_params.key)? + } else if reg_params.hkcc { + key_count += 1; + RegKey::predef(HKEY_CURRENT_CONFIG).open_subkey(reg_params.key)? + } else if reg_params.hkdd { + key_count += 1; + RegKey::predef(HKEY_DYN_DATA).open_subkey(reg_params.key)? + } else if reg_params.hkculs { + key_count += 1; + RegKey::predef(HKEY_CURRENT_USER_LOCAL_SETTINGS).open_subkey(reg_params.key)? + } else { + RegKey::predef(HKEY_CURRENT_USER).open_subkey(reg_params.key)? + }; + + if key_count > 1 { + return Err(ShellError::GenericError( + "Only one registry key can be specified".into(), + "Only one registry key can be specified".into(), + Some(Span::test_data()), + None, + Vec::new(), + )); + } + Ok(registry_key) +} + +fn reg_value_to_nu_value( + reg_value: winreg::RegValue, +) -> (nu_protocol::Value, winreg::enums::RegType) { + match reg_value.vtype { + REG_NONE => (Value::nothing(Span::test_data()), reg_value.vtype), + REG_SZ => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_EXPAND_SZ => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_BINARY => ( + Value::binary(reg_value.bytes, Span::test_data()), + reg_value.vtype, + ), + REG_DWORD => ( + Value::int( + unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64, + Span::test_data(), + ), + reg_value.vtype, + ), + REG_DWORD_BIG_ENDIAN => ( + Value::int( + unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64, + Span::test_data(), + ), + reg_value.vtype, + ), + REG_LINK => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_MULTI_SZ => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_RESOURCE_LIST => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_FULL_RESOURCE_DESCRIPTOR => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_RESOURCE_REQUIREMENTS_LIST => ( + Value::string(reg_value.to_string(), Span::test_data()), + reg_value.vtype, + ), + REG_QWORD => ( + Value::int( + unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64, + Span::test_data(), + ), + reg_value.vtype, + ), + } +}