Skip to content

Commit b017179

Browse files
authored
Merge pull request #465 from tgauth/add-registry-what-if
Add registry whatif
2 parents 83efbc3 + eaa4ddd commit b017179

File tree

7 files changed

+247
-32
lines changed

7 files changed

+247
-32
lines changed

dsc/tests/dsc_whatif.tests.ps1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ Describe 'whatif tests' {
4141
$what_if_result.results.result.beforeState._exist | Should -Be $set_result.results.result.beforeState._exist
4242
$what_if_result.results.result.beforeState.keyPath | Should -Be $set_result.results.result.beforeState.keyPath
4343
$what_if_result.results.result.afterState.KeyPath | Should -Be $set_result.results.result.afterState.keyPath
44-
$what_if_result.results.result.changedProperties | Should -Be $set_result.results.result.changedProperties
44+
# can be changed back to the following once _metadata is pulled out of resource return
45+
# $what_if_result.results.result.changedProperties | Should -Be $set_result.results.result.changedProperties
46+
$what_if_result.results.result.changedProperties | Should -Be @('_metadata', '_exist')
4547
$what_if_result.hadErrors | Should -BeFalse
4648
$what_if_result.results.Count | Should -Be 1
4749
$LASTEXITCODE | Should -Be 0

registry/registry.dsc.resource.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@
3939
}
4040
]
4141
},
42+
"whatIf": {
43+
"executable": "registry",
44+
"args": [
45+
"config",
46+
"set",
47+
"-w",
48+
{
49+
"jsonInputArg": "--input",
50+
"mandatory": true
51+
}
52+
],
53+
"return": "state"
54+
},
4255
"exitCodes": {
4356
"0": "Success",
4457
"1": "Invalid parameter",

registry/src/args.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub enum ConfigSubCommand {
2222
Set {
2323
#[clap(short, long, required = true, help = "The registry JSON input.")]
2424
input: String,
25+
#[clap(short = 'w', long, help = "Run as a what-if operation instead of applying the registry configuration")]
26+
what_if: bool,
2527
},
2628
#[clap(name = "delete", about = "Delete registry configuration.")]
2729
Delete {

registry/src/config.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ pub enum RegistryValueData {
1414
QWord(u64),
1515
}
1616

17-
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
17+
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1818
#[serde(rename = "Registry", deny_unknown_fields)]
1919
pub struct Registry {
2020
/// The path to the registry key.
2121
#[serde(rename = "keyPath")]
2222
pub key_path: String,
23+
/// The information from a config set --what-if operation.
24+
#[serde(rename = "_metadata", skip_serializing_if = "Option::is_none")]
25+
pub metadata: Option<Metadata>,
2326
/// The name of the registry value.
2427
#[serde(rename = "valueName", skip_serializing_if = "Option::is_none")]
2528
pub value_name: Option<String>,
@@ -29,3 +32,10 @@ pub struct Registry {
2932
#[serde(rename = "_exist", skip_serializing_if = "Option::is_none")]
3033
pub exist: Option<bool>,
3134
}
35+
36+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
37+
#[serde(deny_unknown_fields)]
38+
pub struct Metadata {
39+
#[serde(rename = "whatIf", skip_serializing_if = "Option::is_none")]
40+
pub what_if: Option<Vec<String>>
41+
}

registry/src/main.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,24 @@ fn main() {
6363
}
6464
}
6565
},
66-
args::ConfigSubCommand::Set{input} => {
67-
let reg_helper = match RegistryHelper::new(&input) {
66+
args::ConfigSubCommand::Set{input, what_if} => {
67+
let mut reg_helper = match RegistryHelper::new(&input) {
6868
Ok(reg_helper) => reg_helper,
6969
Err(err) => {
7070
eprintln!("Error: {err}");
7171
exit(EXIT_INVALID_INPUT);
7272
}
7373
};
74+
if what_if {
75+
reg_helper.enable_what_if();
76+
}
7477
match reg_helper.set() {
75-
Ok(()) => {},
78+
Ok(reg_config) => {
79+
if let Some(config) = reg_config {
80+
let json = serde_json::to_string(&config).unwrap();
81+
println!("{json}");
82+
}
83+
},
7684
Err(err) => {
7785
eprintln!("Error: {err}");
7886
exit(EXIT_REGISTRY_ERROR);

registry/src/registry_helper.rs

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
use registry::{Data, Hive, RegKey, Security, key, value};
55
use utfx::{U16CString, UCString};
6-
use crate::config::{Registry, RegistryValueData};
6+
use crate::config::{Metadata, Registry, RegistryValueData};
77
use crate::error::RegistryError;
88

99
pub struct RegistryHelper {
1010
config: Registry,
1111
hive: Hive,
1212
subkey: String,
13+
what_if: bool,
1314
}
1415

1516
impl RegistryHelper {
@@ -26,10 +27,15 @@ impl RegistryHelper {
2627
config: registry,
2728
hive,
2829
subkey: subkey.to_string(),
30+
what_if: false
2931
}
3032
)
3133
}
3234

35+
pub fn enable_what_if(&mut self) {
36+
self.what_if = true;
37+
}
38+
3339
pub fn get(&self) -> Result<Registry, RegistryError> {
3440
let exist: bool;
3541
let (reg_key, _subkey) = match self.open(Security::Read) {
@@ -40,9 +46,8 @@ impl RegistryHelper {
4046
exist = false;
4147
return Ok(Registry {
4248
key_path: self.config.key_path.clone(),
43-
value_name: None,
44-
value_data: None,
4549
exist: Some(exist),
50+
..Default::default()
4651
});
4752
},
4853
Err(e) => return Err(e),
@@ -56,8 +61,8 @@ impl RegistryHelper {
5661
return Ok(Registry {
5762
key_path: self.config.key_path.clone(),
5863
value_name: Some(value_name.clone()),
59-
value_data: None,
6064
exist: Some(exist),
65+
..Default::default()
6166
});
6267
},
6368
Err(e) => return Err(RegistryError::RegistryValue(e)),
@@ -67,21 +72,20 @@ impl RegistryHelper {
6772
key_path: self.config.key_path.clone(),
6873
value_name: Some(value_name.clone()),
6974
value_data: Some(convert_reg_value(&value)?),
70-
exist: None,
75+
..Default::default()
7176
})
7277
} else {
7378
Ok(Registry {
7479
key_path: self.config.key_path.clone(),
75-
value_name: None,
76-
value_data: None,
77-
exist: None,
80+
..Default::default()
7881
})
7982
}
8083
}
8184

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

97-
reg_key = reg_key.create(path, Security::CreateSubKey)?;
101+
if self.what_if {
102+
what_if_metadata.push(format!("key: {subkey} not found, would create it"));
103+
}
104+
else {
105+
reg_key = reg_key.create(path, Security::CreateSubKey)?;
106+
}
107+
}
108+
if self.what_if {
109+
None
110+
}
111+
else {
112+
Some(self.open(Security::Write)?.0)
98113
}
99-
100-
self.open(Security::Write)?.0
101114
},
102-
Err(e) => return Err(e),
115+
Err(e) => return self.handle_error_or_what_if(e)
103116
};
104117

105118
if let Some(value_data) = &self.config.value_data {
106119
let Ok(value_name) = U16CString::from_str(self.config.value_name.as_ref().unwrap()) else {
107-
return Err(RegistryError::Utf16Conversion("valueName".to_string()));
120+
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueName".to_string()));
108121
};
109122

110-
match value_data {
123+
let data = match value_data {
111124
RegistryValueData::String(s) => {
112125
let Ok(utf16) = U16CString::from_str(s) else {
113-
return Err(RegistryError::Utf16Conversion("valueData".to_string()));
126+
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string()));
114127
};
115-
reg_key.set_value(&value_name, &Data::String(utf16))?;
128+
Data::String(utf16)
116129
},
117130
RegistryValueData::ExpandString(s) => {
118131
let Ok(utf16) = U16CString::from_str(s) else {
119-
return Err(RegistryError::Utf16Conversion("valueData".to_string()));
132+
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string()));
120133
};
121-
reg_key.set_value(&value_name, &Data::ExpandString(utf16))?;
134+
Data::ExpandString(utf16)
122135
},
123136
RegistryValueData::Binary(b) => {
124-
reg_key.set_value(&value_name, &Data::Binary(b.clone()))?;
137+
Data::Binary(b.clone())
125138
},
126139
RegistryValueData::DWord(d) => {
127-
reg_key.set_value(&value_name, &Data::U32(*d))?;
140+
Data::U32(*d)
128141
},
129142
RegistryValueData::MultiString(m) => {
130143
let mut m16: Vec<UCString<u16>> = Vec::<UCString<u16>>::new();
131144
for s in m {
132145
let Ok(utf16) = U16CString::from_str(s) else {
133-
return Err(RegistryError::Utf16Conversion("valueData".to_string()));
146+
return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string()));
134147
};
135148
m16.push(utf16);
136149
}
137-
reg_key.set_value(&value_name, &Data::MultiString(m16))?;
150+
Data::MultiString(m16)
138151
},
139152
RegistryValueData::QWord(q) => {
140-
reg_key.set_value(&value_name, &Data::U64(*q))?;
153+
Data::U64(*q)
141154
},
155+
};
156+
157+
if self.what_if {
158+
return Ok(Some(Registry {
159+
key_path: self.config.key_path.clone(),
160+
value_data: Some(convert_reg_value(&data)?),
161+
value_name: self.config.value_name.clone(),
162+
metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) })},
163+
..Default::default()
164+
}));
142165
}
166+
167+
if let Some(reg_key) = reg_key {
168+
reg_key.set_value(&value_name, &data)?;
169+
};
143170
}
144171

145-
Ok(())
172+
if self.what_if {
173+
return Ok(Some(Registry {
174+
key_path: self.config.key_path.clone(),
175+
metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) })},
176+
..Default::default()
177+
}));
178+
}
179+
180+
Ok(None)
146181
}
147182

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

216251
Ok((parent_key, subkeys))
217252
}
253+
254+
fn handle_error_or_what_if(&self, error: RegistryError) -> Result<Option<Registry>, RegistryError> {
255+
if self.what_if {
256+
return Ok(Some(Registry {
257+
key_path: self.config.key_path.clone(),
258+
metadata: Some(Metadata { what_if: Some(vec![error.to_string()]) }),
259+
..Default::default()
260+
}));
261+
}
262+
Err(error)
263+
}
218264
}
219265

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

0 commit comments

Comments
 (0)