diff --git a/lib/dsc-lib-registry/src/lib.rs b/lib/dsc-lib-registry/src/lib.rs index 7b2326bb4..48e18affc 100644 --- a/lib/dsc-lib-registry/src/lib.rs +++ b/lib/dsc-lib-registry/src/lib.rs @@ -259,13 +259,22 @@ impl RegistryHelper { /// /// * `RegistryError` - The error that occurred. pub fn remove(&self) -> Result, RegistryError> { - let (reg_key, _subkey) = match self.open(Security::AllAccess) { + // For deleting a value, we need SetValue permission (KEY_SET_VALUE). + // Try to open with the minimal required permission. + // If that fails due to permission, try with AllAccess as a fallback. + let (reg_key, _subkey) = match self.open(Security::SetValue) { Ok(reg_key) => reg_key, // handle NotFound error Err(RegistryError::RegistryKeyNotFound(_)) => { eprintln!("{}", t!("registry_helper.removeErrorKeyNotExist")); return Ok(None); }, + Err(RegistryError::RegistryKey(key::Error::PermissionDenied(_, _))) => { + match self.open(Security::AllAccess) { + Ok(reg_key) => reg_key, + Err(e) => return self.handle_error_or_what_if(e), + } + }, Err(e) => return self.handle_error_or_what_if(e), }; @@ -289,10 +298,11 @@ impl RegistryHelper { Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryValue(e)), } } else { - // to delete the key, we need to open the parent key first + // to delete the key, we need to open the parent key with CreateSubKey permission + // which includes the ability to delete subkeys let parent_path = get_parent_key_path(&self.config.key_path); let (hive, parent_subkey) = get_hive_from_path(parent_path)?; - let parent_reg_key = match hive.open(parent_subkey, Security::AllAccess) { + let parent_reg_key = match hive.open(parent_subkey, Security::CreateSubKey) { Ok(k) => k, Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryKey(e)), }; diff --git a/resources/registry/registry.dsc.resource.json b/resources/registry/registry.dsc.resource.json index 505050921..8a5490869 100644 --- a/resources/registry/registry.dsc.resource.json +++ b/resources/registry/registry.dsc.resource.json @@ -5,7 +5,7 @@ "tags": [ "Windows" ], - "version": "0.1.0", + "version": "1.0.0", "get": { "executable": "registry", "args": [ diff --git a/resources/registry/tests/registry.config.set.tests.ps1 b/resources/registry/tests/registry.config.set.tests.ps1 index 7090be8a0..7b6cb404c 100644 --- a/resources/registry/tests/registry.config.set.tests.ps1 +++ b/resources/registry/tests/registry.config.set.tests.ps1 @@ -145,4 +145,55 @@ Describe 'registry config set tests' { $LASTEXITCODE | Should -Be 0 $out.results[0].result.afterState._exist | Should -Be $false } + + It 'Can delete value from system-protected key with minimal permissions' -Skip:(!$IsWindows) { + $testKeyPath = 'HKLM:\Software\Policies\Microsoft\Windows\Appx' + if (-not (Test-Path $testKeyPath)) { + Set-ItResult -Skipped -Because "Test key path '$testKeyPath' does not exist" + return + } + + $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) + $isElevated = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + + if (-not $isElevated) { + Set-ItResult -Skipped -Because "Test requires elevated privileges" + return + } + + $setJson = @' + { + "keyPath": "HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\Appx", + "valueName": "DSCTestValue", + "valueData": { + "String": "TestData" + } + } +'@ + $out = registry config set --input $setJson 2>$null + $LASTEXITCODE | Should -Be 0 + + $getJson = @' + { + "keyPath": "HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\Appx", + "valueName": "DSCTestValue" + } +'@ + $result = registry config get --input $getJson 2>$null | ConvertFrom-Json + $result.valueName | Should -Be 'DSCTestValue' + $result.valueData.String | Should -Be 'TestData' + + $deleteJson = @' + { + "keyPath": "HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\Appx", + "valueName": "DSCTestValue" + } +'@ + $out = registry config delete --input $deleteJson 2>$null + $LASTEXITCODE | Should -Be 0 + + $result = registry config get --input $getJson 2>$null | ConvertFrom-Json + $result._exist | Should -Be $false + $result.valueData | Should -BeNullOrEmpty + } }