Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add registry whatif #465

Merged
merged 12 commits into from
Jun 26, 2024
4 changes: 3 additions & 1 deletion dsc/tests/dsc_whatif.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ Describe 'whatif tests' {
$what_if_result.results.result.beforeState._exist | Should -Be $set_result.results.result.beforeState._exist
$what_if_result.results.result.beforeState.keyPath | Should -Be $set_result.results.result.beforeState.keyPath
$what_if_result.results.result.afterState.KeyPath | Should -Be $set_result.results.result.afterState.keyPath
$what_if_result.results.result.changedProperties | Should -Be $set_result.results.result.changedProperties
# can be changed back to the following once _metadata is pulled out of resource return
# $what_if_result.results.result.changedProperties | Should -Be $set_result.results.result.changedProperties
$what_if_result.results.result.changedProperties | Should -Be @('_metadata', '_exist')
$what_if_result.hadErrors | Should -BeFalse
$what_if_result.results.Count | Should -Be 1
$LASTEXITCODE | Should -Be 0
Expand Down
13 changes: 13 additions & 0 deletions registry/registry.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@
}
]
},
"whatIf": {
"executable": "registry",
"args": [
"config",
"set",
"-w",
{
"jsonInputArg": "--input",
"mandatory": true
}
],
"return": "state"
},
"exitCodes": {
"0": "Success",
"1": "Invalid parameter",
Expand Down
2 changes: 2 additions & 0 deletions registry/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum ConfigSubCommand {
Set {
#[clap(short, long, required = true, help = "The registry JSON input.")]
input: String,
#[clap(short = 'w', long, help = "Run as a what-if operation instead of applying the registry configuration")]
what_if: bool,
},
#[clap(name = "delete", about = "Delete registry configuration.")]
Delete {
Expand Down
12 changes: 11 additions & 1 deletion registry/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ pub enum RegistryValueData {
QWord(u64),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename = "Registry", deny_unknown_fields)]
pub struct Registry {
/// The path to the registry key.
#[serde(rename = "keyPath")]
pub key_path: String,
/// The information from a config set --what-if operation.
#[serde(rename = "_metadata", skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
/// The name of the registry value.
#[serde(rename = "valueName", skip_serializing_if = "Option::is_none")]
pub value_name: Option<String>,
Expand All @@ -29,3 +32,10 @@ pub struct Registry {
#[serde(rename = "_exist", skip_serializing_if = "Option::is_none")]
pub exist: Option<bool>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Metadata {
#[serde(rename = "whatIf", skip_serializing_if = "Option::is_none")]
pub what_if: Option<Vec<String>>
}
14 changes: 11 additions & 3 deletions registry/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,24 @@ fn main() {
}
}
},
args::ConfigSubCommand::Set{input} => {
let reg_helper = match RegistryHelper::new(&input) {
args::ConfigSubCommand::Set{input, what_if} => {
let mut reg_helper = match RegistryHelper::new(&input) {
Ok(reg_helper) => reg_helper,
Err(err) => {
eprintln!("Error: {err}");
exit(EXIT_INVALID_INPUT);
}
};
if what_if {
reg_helper.enable_what_if();
}
match reg_helper.set() {
Ok(()) => {},
Ok(reg_config) => {
if let Some(config) = reg_config {
let json = serde_json::to_string(&config).unwrap();
println!("{json}");
}
},
Err(err) => {
eprintln!("Error: {err}");
exit(EXIT_REGISTRY_ERROR);
Expand Down
100 changes: 73 additions & 27 deletions registry/src/registry_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

use registry::{Data, Hive, RegKey, Security, key, value};
use utfx::{U16CString, UCString};
use crate::config::{Registry, RegistryValueData};
use crate::config::{Metadata, Registry, RegistryValueData};
use crate::error::RegistryError;

pub struct RegistryHelper {
config: Registry,
hive: Hive,
subkey: String,
what_if: bool,
}

impl RegistryHelper {
Expand All @@ -26,10 +27,15 @@ impl RegistryHelper {
config: registry,
hive,
subkey: subkey.to_string(),
what_if: false
}
)
}

pub fn enable_what_if(&mut self) {
self.what_if = true;
}

pub fn get(&self) -> Result<Registry, RegistryError> {
let exist: bool;
let (reg_key, _subkey) = match self.open(Security::Read) {
Expand All @@ -40,9 +46,8 @@ impl RegistryHelper {
exist = false;
return Ok(Registry {
key_path: self.config.key_path.clone(),
value_name: None,
value_data: None,
exist: Some(exist),
..Default::default()
});
},
Err(e) => return Err(e),
Expand All @@ -56,8 +61,8 @@ impl RegistryHelper {
return Ok(Registry {
key_path: self.config.key_path.clone(),
value_name: Some(value_name.clone()),
value_data: None,
exist: Some(exist),
..Default::default()
});
},
Err(e) => return Err(RegistryError::RegistryValue(e)),
Expand All @@ -67,21 +72,20 @@ impl RegistryHelper {
key_path: self.config.key_path.clone(),
value_name: Some(value_name.clone()),
value_data: Some(convert_reg_value(&value)?),
exist: None,
..Default::default()
})
} else {
Ok(Registry {
key_path: self.config.key_path.clone(),
value_name: None,
value_data: None,
exist: None,
..Default::default()
})
}
}

pub fn set(&self) -> Result<(), RegistryError> {
pub fn set(&self) -> Result<Option<Registry>, RegistryError> {
let mut what_if_metadata: Vec<String> = Vec::new();
let reg_key = match self.open(Security::Write) {
Ok((reg_key, _subkey)) => reg_key,
Ok((reg_key, _subkey)) => Some(reg_key),
// handle NotFound error
Err(RegistryError::RegistryKeyNotFound(_)) => {
// if the key doesn't exist, some of the parent keys may
Expand All @@ -91,58 +95,89 @@ impl RegistryHelper {
let mut reg_key = parent_key;
for subkey in subkeys {
let Ok(path) = UCString::<u16>::from_str(subkey) else {
return Err(RegistryError::Utf16Conversion("subkey".to_string()));
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("subkey".to_string()));
};

reg_key = reg_key.create(path, Security::CreateSubKey)?;
if self.what_if {
what_if_metadata.push(format!("key: {subkey} not found, would create it"));
}
else {
reg_key = reg_key.create(path, Security::CreateSubKey)?;
}
}
if self.what_if {
None
}
else {
Some(self.open(Security::Write)?.0)
}

self.open(Security::Write)?.0
},
Err(e) => return Err(e),
Err(e) => return self.handle_error_or_what_if(e)
};

if let Some(value_data) = &self.config.value_data {
let Ok(value_name) = U16CString::from_str(self.config.value_name.as_ref().unwrap()) else {
return Err(RegistryError::Utf16Conversion("valueName".to_string()));
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueName".to_string()));
};

match value_data {
let data = match value_data {
RegistryValueData::String(s) => {
let Ok(utf16) = U16CString::from_str(s) else {
return Err(RegistryError::Utf16Conversion("valueData".to_string()));
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string()));
};
reg_key.set_value(&value_name, &Data::String(utf16))?;
Data::String(utf16)
},
RegistryValueData::ExpandString(s) => {
let Ok(utf16) = U16CString::from_str(s) else {
return Err(RegistryError::Utf16Conversion("valueData".to_string()));
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string()));
};
reg_key.set_value(&value_name, &Data::ExpandString(utf16))?;
Data::ExpandString(utf16)
},
RegistryValueData::Binary(b) => {
reg_key.set_value(&value_name, &Data::Binary(b.clone()))?;
Data::Binary(b.clone())
},
RegistryValueData::DWord(d) => {
reg_key.set_value(&value_name, &Data::U32(*d))?;
Data::U32(*d)
},
RegistryValueData::MultiString(m) => {
let mut m16: Vec<UCString<u16>> = Vec::<UCString<u16>>::new();
for s in m {
let Ok(utf16) = U16CString::from_str(s) else {
return Err(RegistryError::Utf16Conversion("valueData".to_string()));
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string()));
};
m16.push(utf16);
}
reg_key.set_value(&value_name, &Data::MultiString(m16))?;
Data::MultiString(m16)
},
RegistryValueData::QWord(q) => {
reg_key.set_value(&value_name, &Data::U64(*q))?;
Data::U64(*q)
},
};

if self.what_if {
return Ok(Some(Registry {
key_path: self.config.key_path.clone(),
value_data: Some(convert_reg_value(&data)?),
value_name: self.config.value_name.clone(),
metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) })},
..Default::default()
}));
}

if let Some(reg_key) = reg_key {
reg_key.set_value(&value_name, &data)?;
};
}

Ok(())
if self.what_if {
return Ok(Some(Registry {
key_path: self.config.key_path.clone(),
metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) })},
..Default::default()
}));
}

Ok(None)
}

pub fn remove(&self) -> Result<(), RegistryError> {
Expand Down Expand Up @@ -215,6 +250,17 @@ impl RegistryHelper {

Ok((parent_key, subkeys))
}

fn handle_error_or_what_if(&self, error: RegistryError) -> Result<Option<Registry>, RegistryError> {
if self.what_if {
return Ok(Some(Registry {
key_path: self.config.key_path.clone(),
metadata: Some(Metadata { what_if: Some(vec![error.to_string()]) }),
..Default::default()
}));
}
Err(error)
}
}

fn get_hive_from_path(path: &str) -> Result<(Hive, &str), RegistryError> {
Expand Down
Loading
Loading