Classes/RegeditManager.ps1

class RegeditManager {
    [LibraryContext]$Context
    [string]$DefaultKey

    RegeditManager([LibraryContext]$context) {
        $this.Context = $context
        $this.DefaultKey = "SOFTWARE\Kittl-Partner\Intune\$($context.Meta['InstanceName'])"
    }

    # Simple setter
    [void] SetRegeditKey([string]$path, [string]$keyName, [object]$value) {
        $this.Context.LogManager.Log("Setting '$path\$keyName' = '$value'")
    
        $views = @(
            [Microsoft.Win32.RegistryView]::Registry64 
            #[Microsoft.Win32.RegistryView]::Registry32 #=> ignore 32bit regedit!
        )
    
        foreach ($view in $views) {
            try {
                $this.Context.LogManager.Log("Processing '$path' in $view")
    
                # Split the root and subkey (e.g., HKLM:\Software\MyApp)
                $parts = $path -split ":", 2
                $hiveName = $parts[0]
                $subKey   = $parts[1].TrimStart('\')
    
                # Map root key string to RegistryHive enum
                $hive = $null
                switch -Regex ($hiveName) {
                    '^HKLM' { $hive = [Microsoft.Win32.RegistryHive]::LocalMachine }
                    '^HKCU' { $hive = [Microsoft.Win32.RegistryHive]::CurrentUser }
                    '^HKCR' { $hive = [Microsoft.Win32.RegistryHive]::ClassesRoot }
                    '^HKU'  { $hive = [Microsoft.Win32.RegistryHive]::Users }
                    '^HKCC' { $hive = [Microsoft.Win32.RegistryHive]::CurrentConfig }
                    default { throw "Unknown registry hive: $hiveName" }
                }
    
                # Open or create key in the correct registry view
                $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
                $regKey = $baseKey.CreateSubKey($subKey, $true)
    
                if ($null -ne $keyName -and '' -ne $keyName) {
                    if ($regKey.GetValueNames() -contains $keyName) {
                        # Get the existing type
                        $kind = $regKey.GetValueKind($keyName)
                        Write-Host "'$path\$keyName' already exists and has type $kind"
                    } else {
                        $kind, $value = $this.GetValueKindAndCoercedValue($value)
                    }
                    $regKey.SetValue($keyName, $value, $kind)
                    $this.Context.LogManager.Log("Set '$path\$keyName' = '$value' [${kind}] in $view")
                } else {
                    $this.Context.LogManager.Log("Empty key name => No setting empty default for '$path' in $view")
                    $regKey.SetValue('', '', [Microsoft.Win32.RegistryValueKind]::String)
                }
    
                $regKey.Close()
                $baseKey.Close()
            }
            catch {
                $this.Context.LogManager.Log("Error processing '$path' in ${view}: $_", "ERROR")
            }
        }
    }
    

    # Write Intune Flag
    [void] WriteIntuneFlag([string]$KeyPath, [hashtable]$Data, [bool]$AddLastRun)
    {
        if (-not $KeyPath) { $KeyPath = $this.DefaultKey }
        if (-not $Data)    { throw "Data hashtable cannot be null or empty." }

        
        if ($this.Context.Meta.RunContext -eq "User") {
            $hiveStr = "HKEY_CURRENT_USER"
        } else {
            $hiveStr = "HKEY_LOCAL_MACHINE"
        }
        $this.Context.LogManager.Log("Writing intune regedit entries to $hiveStr\$KeyPath")

        $views = @(
            'Registry64'
            #'Registry32' #=> ignore 32bit regedit!
            )
        foreach ($view in $views) {
            $base = $null
            $rk   = $null
            try {
                if ($this.Context.Meta.RunContext -eq "User") {   
                    $hive = [Microsoft.Win32.RegistryHive]::CurrentUser
                } else {
                    $hive = [Microsoft.Win32.RegistryHive]::LocalMachine
                }

                $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, [Microsoft.Win32.RegistryView]::$view)
                $rk = $base.CreateSubKey($KeyPath)

                foreach ($name in $Data.Keys) {
                    $value = $Data[$name]

                    if ($null -eq $value) {
                        # Treat $null as "remove this value" to avoid writing invalid data
                        try { $rk.DeleteValue($name, $true) } catch {}
                        continue
                    }

                    $kind, $out = $this.GetValueKindAndCoercedValue($value)
                    $this.Context.LogManager.Log("-> Setting ${name} = ${out} [${kind}] in ${view}")
                    $rk.SetValue($name, $out, $kind)
                }

                if ($AddLastRun) {
                    $rk.SetValue('LastRun', (Get-Date).ToString('s'), [Microsoft.Win32.RegistryValueKind]::String)
                }
            }
            finally {
                if ($rk)   { $rk.Close() }
                if ($base) { $base.Close() }
            }
        }

        $this.Context.LogManager.Log("Wrote intune regedit entries to $hiveStr\$KeyPath")
    }

    # Private helper (can be placed in the class as a hidden method or outside as a local function)
    hidden [object[]] GetValueKindAndCoercedValue([object] $Value) {
        if ($Value -is [int] -or $Value -is [int32]) {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::DWord, [int]$Value))
        }
        elseif ($Value -is [long] -or $Value -is [int64]) {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::QWord, [long]$Value))
        }
        elseif ($Value -is [byte[]]) {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::Binary, [byte[]]$Value))
        }
        elseif ($Value -is [string[]]) {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::MultiString, [string[]]$Value))
        }
        elseif ($Value -is [bool]) {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::DWord, [int]([bool]$Value)))
        }
        elseif ($Value -is [datetime]) {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::String, ($Value).ToString('s')))
        }
        elseif ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
            $tmp = foreach ($i in $Value) { [string]$i }
            if ($null -eq $tmp) { $tmp = @() }
            $asStrings = [string[]]$tmp           # <<< wichtiger Cast
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::MultiString, $asStrings))
        }
        else {
            return ([object[]]@([Microsoft.Win32.RegistryValueKind]::String, $Value.ToString()))
        }
    }

    # Remove Intune Flag (entire key)
    [void] RemoveIntuneFlag([string]$KeyPath)
    {
        if (-not $KeyPath) { $KeyPath = $this.DefaultKey }

        # Normalize to a subkey path relative to HKLM (no HKLM:\ prefix) etc.
        if ($this.Context.Meta.RunContext -eq "User") {
            $hiveStr = "HKEY_CURRENT_USER"
            $normalized = $KeyPath -replace '^(?i:HKCU:\\|HKEY_CURRENT_USER\\)', ''
        } else {
            $hiveStr = "HKEY_LOCAL_MACHINE"
            $normalized = $KeyPath -replace '^(?i:HKLM:\\|HKEY_LOCAL_MACHINE\\)', ''
        }

        $views = @(
            'Registry64'
            #'Registry32' #=> ignore 32bit regedit!
            )

        foreach ($view in $views) {
            $base = $null
            try {
                if ($this.Context.Meta.RunContext -eq "User") {   
                    $hive = [Microsoft.Win32.RegistryHive]::CurrentUser
                } else {
                    $hive = [Microsoft.Win32.RegistryHive]::LocalMachine
                }

                $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, [Microsoft.Win32.RegistryView]::$view)
                
                # Check if it exists first (purely for clearer logging)
                $existing = $base.OpenSubKey($normalized)
                if ($existing) { $existing.Close() }

                if ($existing) {
                    # Delete entire key tree; do not throw if it vanished between check and delete
                    $base.DeleteSubKeyTree($normalized, $false)
                    # Sanity check
                    $post = $base.OpenSubKey($normalized)
                    if ($post) {
                        $post.Close()
                        $this.Context.LogManager.Log("'$hiveStr\$normalized' still present after delete in $view.", "ERROR")
                    } else {
                        $this.Context.LogManager.Log("Deleted registry key '$hiveStr\$normalized' in $view.")
                    }
                }
                else {
                    $this.Context.LogManager.Log("Key '$hiveStr\$normalized' not found in $view; nothing to delete.", "ERROR")
                }
            }
            finally {
                if ($base) { $base.Close() }
            }
        }

        $this.Context.LogManager.Log("Processed deletion for intune regedit '$hiveStr\$normalized' in both views.")
    }

}