OZORegistry.psm1

# CLASSES
Class OZORegistryKey {
    # PROPERTIES: Booleans, Strings
    [Boolean] $Display      = $false
    [Boolean] $keyExists    = $true
    [Boolean] $keyPathValid = $true
    [Boolean] $namesRead    = $true
    [String]  $keyPath      = $null
    # PROPERTIES: PSCustomObjects
    Hidden [PSCustomObject] $Key    = $null
    Hidden [PSCustomObject] $Logger = $null
    # PROPERTIES: PSCustomObject Lists
    Hidden [System.Collections.Generic.List[PSCustomObject]] $Names = @()
    # METHODS: Constructor method
    OZORegistryKey([String]$KeyPath,[Boolean]$Display) {
        # Set properties
        $this.Display = $Display
        # Create a logger
        $this.Logger = (New-OZOLogger)
        # Determine if Display is false and if so disable Logger display
        If ($this.Display -eq $false) { $this.Logger.SetConsoleOutput("off") }
        # Call ValidateKeyPath to determine if KeyPath format is valid [and populate this.KeyPath]
        If ($this.ValidateKeyPath($KeyPath) -eq $true) {
            # Key is valid
            $this.Logger.Write(("Using key path " + $this.keyPath + "."),"Information")
            # Call ReadKey to set keyExists [and populate registryKey]
            If ($this.ReadKey() -eq $true) {
                # Retrieved key; call ReadKeyNames [and populate Names]
                If ($this.ReadKeyNames() -eq $true) {
                    # Able to read all key names
                    $this.Logger.Write(($this.keyPath + " exists and all names read."),"Information")
                    # Display key for user-interactive sessions
                    $this.DisplayKey()                    
                } Else {
                    # No names found or unable to read all names; log
                    $this.Logger.Write(($this.keyPath + " contains no names or some namues could not be read."),"Warning")
                    $this.namesRead = $false
                }
            } Else {
                # Unable to retrieve key; log
                $this.Logger.Write(("Key `"" + $KeyPath + "`" could not be read."),"Error")
                $this.keyExists = $false
                $this.namesRead = $false
            }
        } Else {
            # Key format is not valid; log
            $this.Logger.Write(("Key `"" + $KeyPath + "`" is not a valid path."),"Error")
            $this.keyExists = $false
            $this.keyPathValid  = $false
            $this.namesRead = $false
        }
    }
    # METHODS: Validate key path method
    Hidden [Boolean] ValidateKeyPath([String]$KeyPath) {
        # Control variable
        [Boolean] $Return = $true
        # Local variables
        [Array]  $validShortStrings = @("HKCR:","HKCU:","HKLM:","HKU:","HKCC:")
        [Array]  $validLongStrings  = @("HKEY_CLASSES_ROOT","HKEY_CURRENT_USER","HKEY_LOCAL_MACHINE","HKEY_USERS","HKEY_CURRENT_CONFIG")
        [String] $keyPathStart      = $null
        # Determine if KeyPath contains a \ character
        If ($KeyPath -Like "*\*") {
            # KeyPath contains a \ character; split the string on \ and store the first string as keyPathStart
            $keyPathStart = ($KeyPath -Split "\\" )[0]
            # Determine if the first string is found in valid*Strings
            If (($validShortStrings + $validLongStrings) -Contains $keyPathStart) {
                # First string appears in valid*Strings lists; determine if key is in validShortStrings
                If ($validShortStrings -Contains $keyPathStart) {
                    # Key is in the short format; convert to long format and set this.keyPath
                    $this.keyPath = ("Registry::" + (Convert-OZORegistryPath -RegistryPath $KeyPath))
                } Else {
                    # Key is in the long format; set this.keyPath
                    $this.keyPath = ("Registry::" + $KeyPath)
                }
            } Else {
                # First string does not appear in valid*Strings
                $Return = $false
            }
        } Else {
            # Path does not contain a \ character
            $Return = $false
        }
        # return
        return $Return
    }
    # METHODS: Read key method
    Hidden [Boolean] ReadKey() {
        # Control variable
        [Boolean] $Return = $true
         # Determine that Key is valid and set
        If ($this.keyPathValid -eq $true -And $null -ne $this.keyPath) {
            # Key is valid and set; try to get the key
            Try {
                $this.Key = (Get-Item -Path $this.keyPath -ErrorAction Stop)
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        } Else {
            $Return = $false
        }
        # Return
        return $Return
    }
    # METHODS: Read key names method
    Hidden [Boolean] ReadKeyNames() {
        # Control variable
        [Boolean] $Return = $true
        # Determine that registryKey is populated
        If ($null -ne $this.Key) {
            # Registry key is populated; determine if it has no properties
            If ($this.Key.Property.Count -eq 0) {
                # There are no properties; nothing to do
                $Return = $false
            # Otherwise determine if the Property count is 1 and it's Name is "(default)"
            } ElseIf ($this.Key.ValueCount -eq 1 -And $this.Key.Property -Contains "(default)") {
                # Key contains only one property and it is the (default) property; nothing to do
                $Return = $false
            } Else {
                # Key contains one or more names that are not "(default)"; iterate through the names
                ForEach ($keyProperty in $this.Key.Property) {
                    $this.Names.Add(([OZORegistryKeyName]::new($keyProperty,$this.Key.GetValue($keyProperty,$null,"DoNotExpandEnvironmentNames"))))
                }
                # Determine if the count of registry values does not match the number of objects in the Names list
                If ($this.Key.ValueCount -ne $this.Names.Count) {
                    # The counts do not match (some name could not be represented with a RegistryKeyName object); clear Names
                    $this.Names = @()
                    $Return = $false
                }
            }
        }
        # Return
        return $Return
    }
    # METHODS: Set display method
    [Void] SetDisplay([Boolean]$DisplayBool) {
        # Determine if SetValue is true
        If ($DisplayBool -eq $true) {
            # SetValue is true
            $this.Display = $true
        } Else {
            # SetValue is false
            $this.Display = $false
        }
    }
    # METHODS: Display key method
    [Void] DisplayKey() {
        # Control variable
        [Boolean] $changesPending = $false
        # Determine if session is user-interactive
        If ((Get-OZOUserInteractive) -eq $true -And $this.Display -eq $true) {
            # Session is user-interactive and Display is True; determine if the counts do not match
            If ($this.Key.ValueCount -ne $this.Names.Count) {
                # Counts do not match; Names have been added or removed
                $changesPending = $true
            }
            # Iterate through the key properties
            ForEach ($Property in $this.Key.Property) {
                # Determine if the Name is found in Names
                If ($this.Names.Name -Contains $Property) {
                    # Determine if the Key.Property value does not match the Name.Value
                    If ($this.Key.GetValue($Property,$null,"DoNotExpandEnvironmentNames") -ne $this.ReturnKeyNameValue($Property)) {
                        # Values do not match
                        $changesPending = $true
                    }
                }
            }
            # Display the key representation
            $this.Names | Select-Object -Property Name,Type,Value | Format-Table | Out-Host
            # Determine if any changes are pending
            If ($changesPending -eq $true) {
                "`r`nThere are pending changes. Use ProcessChanges() to apply these changes to the registry key." | Out-Host
            }
        }
    }
    # METHODS: Return registry key name value method
    [Object] ReturnKeyNameValue($Name) {
        # Local variables
        [PSCustomObject] $keyName = $null
        # Determine if the key path is valid
        If ($this.keyPathValid -eq $true) {
            # Key path is valid; determine if the key exists
            If ($this.keyExists -eq $true) {
                # Key exists; determine if the names were read
                If ($this.namesRead -eq $true) {
                    # Names were read; determine if Names contains Name
                    If ($this.Names.Name -Contains $Name) {
                        # Get the specific Name as keyName
                        $keyName = ($this.Names | Where-Object {$_.Name -eq $Name})
                        # Switch on Type and return the value as the corresponding type
                        Switch ($keyName.Type.ToLower()) {
                            "binary"       { return [Byte[]]   $keyName.Value   }
                            "dword"        { return [Int32]    $keyName.Value   }
                            "expandstring" { return [String]   $keyName.Value   }
                            "multstring"   { return [String[]] $keyName.Value   }
                            "qword"        { return [Int64]    $keyName.Value   }
                            "string"       { return [String]   $keyName.Value   }
                            default        { return [String]   "Unhandled type" }
                        }
                    } Else {
                        # Name not found in Names.Name
                        return "Name not found in Key"
                    }
                } Else {
                    # Could not read names
                    return "Could not read key names"
                }
            } Else {
                # Key path does not exist
                return "Key not found"
            }
        } Else {
            # Key Path is not valid
            return "Key path is not valid"
        }
        #Return
        return "Unsupported"
    }
    # METHODS: Return registry key name type method
    [String] ReturnKeyNameType($Name) {
        # Determine if key is valid
        If ($this.keyPathValid -eq $true) {
            # Key path is valid; determine if the key exists
            If ($this.keyExists -eq $true) {
                # Key exists; determine if the names were read
                If ($this.namesRead -eq $true) {
                    # Names were read; determine if Name exists in Names list
                    If ($this.Names.Name -Contains $Name) {
                        # Name exists in Names list; return the value as string
                        return ($this.Names | Where-Object {$_.Name -eq $Name}).Type
                    }
                } Else {
                    # Could not read names
                    return "Could not read key names"
                }
            } Else {
                # Key path does not exist
                return "Key not found"
            }
        } Else {
            # Key Path is not valid
            return "Key path is not valid"
        }
        # Return
        return "Unsupported"
    }
    # METHODS: Add key name method
    [Boolean] AddKeyName([String]$Name,$Value) {
        # Control variable
        [Boolean] $Return = $true
        # Determine if Name already exists in Names
        If ($this.Names.Name -Contains $Name) {
            # Names already contains Name
            $this.Logger.Write(("Key already contains a " + $Name + " name; skipping."),"Warning")
            $Return = $false
        } Else {
            # Names does not already contain this Name; add to list
            $this.Names.Add(([OZORegistryKeyName]::new($Name,$Value)))
        }
        # Display key values for user-interactive sessions
        $this.DisplayKey()
        # Return
        return $Return
    }
    # METHODS: Add key name method
    [Boolean] RemoveKeyName([String]$Name) {
        # Control variable
        [Boolean] $Return = $true
        # Determine if Name exists in Names
        If ($this.Names.Name -Contains $Name) {
            # Names contains Name; remove it
            $this.Names.Remove(($this.Names | Where-Object {$_.Name -eq $Name}))
        } Else {
            # Names does not contain Name; nothing to do
            $this.Logger.Write(("Key does not contain a " + $Name + " name; skipping."),"Warning")
            $Return = $false
        }
        # Display key values for user-interactive sessions
        $this.DisplayKey()
        # Return
        return $Return
    }
    # METHODS: Update key name method
    [Boolean] UpdateKeyName([String]$Name,$Value) {
        # Control variable
        [Boolean] $Return = $true
        # Determine if this Name exists in Names
        If ($this.Names.Name -Contains $Name) {
            # Namee exists; determine if types match
            If (($this.Names | Where-Object {$_.Name -eq $Name}).Value.GetType() -eq $Value.GetType()) {
                # Types match; update with new Value
                ($this.Names | Where-Object {$_.Name -eq $Name}).Value = $Value
            } Else {
                $this.Logger.Write("Types do not match; name will not be updated.","Warning")
                $Return = $false
            }
        } Else {
            # Value does not exist; add it
            $Return = ($this.AddKeyName([String]$Name,$Value))
        }
        # Display key values for user-interactive sessions
        $this.DisplayKey()
        # Return
        return $Return
    }
    # METHODS: Process changes method
    [Boolean] ProcessChanges() {
        # Control variable
        [Boolean] $Return = $true
        # Determine that operator is local Administrator
        If ((Test-OZOLocalAdministrator) -eq $true) {
            # Operator is local Administrator; determine if key does not exist
            If ([Boolean](Test-Path -Path $this.keyPath -ErrorAction SilentlyContinue) -eq $false) {
                # Key does not exist; try to create it
                Try {
                    New-Item -Path $this.keyPath -ErrorAction Stop
                    # Success; iterate through Values
                    ForEach ($Name in $this.Names) {
                        # Try to create each value
                        Try {
                            New-ItemProperty -Path $this.keyPath -Name $Name.Name -Value $Name.Value -PropertyType $Name.Type -ErrorAction Stop
                            # Success
                        } Catch {
                            # Failure
                            $this.Logger.Write(("Error creating " + $Name.Name + " in " + $this.keyPath + "."),"Error")
                            $Return = $false
                        }
                    }
                } Catch {
                    # Failure
                    $this.Logger.Write(("Failed to create registry key" + $this.keyPath + "."),"Error")
                    $Return = $false
                }
            } Else {
                # The key already exists; perform the remove operation - iterate through the properties in Key
                ForEach ($Property in $this.Key.Property) {
                    # Determine if the property is not found in Values.Name
                    If ($this.Names.Name -NotContains $Property) {
                        # Property is not found in Values.Name; try to delete it
                        Try {
                            Remove-ItemProperty -Path $this.keyPath -Name $Property -ErrorAction Stop
                            # Success
                        } Catch {
                            # Failure
                            $this.Logger.Write(("Failed to remove " + $Property + " from " + $this.keyPath + "."),"Error")
                            $Return = $false
                        }
                    }
                }
                # Perform the update operation - iterate through the Names
                ForEach ($Name in $this.Names) {
                    # Determine that Key.Property contains Name.Name
                    If ($this.Key.Property -Contains $Name.Name) {
                        # Key.Property contains Name.Name; determine that Name.Value is different from the Key.Property value
                        If ($Name.Value -ne $this.Key.GetValue($Name.Name)) {
                            # Name.Value and the Key.Property value do not match; determine if the types match
                            If ($Name.Type -eq $this.Key.GetValueKind($Name.Name)) {
                                # Types match; try to update
                                Try {
                                    Set-ItemProperty -Path $this.keyPath -Name $Name.Name -Value $Name.Value
                                    # Success
                                } Catch {
                                    # Failure
                                    $this.Logger.Write(("Unable to update " + $Name.Name + " in " + $this.keyPath + "."),"Error")
                                    $Return = $false
                                }
                            } Else {
                                # Types do not match
                                $this.Logger.Write(("Type mismatch when attempting to update " + $Name.Name + " in " + $this.keyPath + "."),"Error")
                                $Return = $false
                            }
                        }
                    }
                }
                # Perform the add operation - iterate through the Names in Value
                ForEach ($Name in $this.Names) {
                    # Determine if the properties in Key do not contain Value
                    If ($this.Key.Property -NotContains $Name.Name) {
                        # Key does not contain Value; try to add it
                        Try {
                            New-ItemProperty -Path $this.keyPath -Name $Name.Name -Value $Name.Value -PropertyType $Name.Type -ErrorAction Stop
                            # Success
                        } Catch {
                            # Failure
                            $this.Logger.Write(("Failed to add " + $Name.Name + " to " + $this.keyPath),"Error")
                            $Return = $false
                        }
                    }
                    
                }
            }
            # Reread the key
            $this.ReadKey()
        } Else {
            # Operator is not local Administrator
            $this.Logger.Write("Writing registry keys requires Administrator privileges.","Error")
            $Return = $false
        }
        # Display the key for user-interactive sessions
        $this.DisplayKey()
        # Return
        return $Return
    }
}

Class OZORegistryKeyName {
    # PROPERTIES
    [String]  $Name = $null
    [String]  $Type = $null
    # METHODS: Constructor method [Binary]
    OZORegistryKeyName([String]$Name,[Byte[]]$Value) {
        # Set properties
        $this.Name = $Name
        $this.Type = "Binary"
        # Add and set Data property
        Add-Member -InputObject $this -MemberType NoteProperty -Name "Value" -TypeName "Byte[]" -Value $Value -Force
    }
    # METHODS: Constructor method [Dword]
    OZORegistryKeyName([String]$Name,[Int32]$Value) {
        # Set properties
        $this.Name = $Name
        $this.Type = "Dword"
        # Add and set Data property
        Add-Member -InputObject $this -MemberType NoteProperty -Name "Value" -TypeName "Int32" -Value $Value -Force
    }
    # METHODS: Constructor method [ExpandString|String]
    OZORegistryKeyName([String]$Name,[String]$Value) {
        # Set properties
        $this.Name = $Name
        # Determine if string contains a % character
        If ($Value -Like "*%*") {
            # Data contains a %
            $this.Type = "ExpandString"
        } Else {
            # Data does not contain a % character
            $this.Type = "String"
        }
        # Add the Data property with the correct type
        Add-Member -InputObject $this -MemberType NoteProperty -Name "Value" -TypeName "String" -Value $Value -Force
    }
    # METHODS: Constructor method [MultString]
    OZORegistryKeyName([String]$Name,[String[]]$Value) {
        # Set properties
        $this.Name = $Name
        $this.Type = "MultString"
        # Add and set Data property
        Add-Member -InputObject $this -MemberType NoteProperty -Name "Value" -TypeName "String[]" -Value $Value -Force
    }
    # METHODS: Constructor method [Qword]
    OZORegistryKeyName([String]$Name,[Int64]$Value) {
        # Set properties
        $this.Name = $Name
        $this.Type = "Qword"
        # Add and set Data property
        Add-Member -InputObject $this -MemberType NoteProperty -Name "Value" -TypeName "Int64" -Value $Value -Force
    }
}

# FUNCTIONS
Function Convert-OZORegistryPath {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Converts a registry string from one format to another, e.g., "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion" to "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion". If the input string is invalid, it is returned unmodified.
        .PARAMETER RegistryPath
        The registry string to convert. Accepts pipeline input.
        .EXAMPLE
        Convert-OZORegistryPath -Path "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion"
        HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion
        .EXAMPLE
        Convert-OZORegistryPath -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion"
        HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion
        .EXAMPLE
        "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion" | Convert-OZORegistryPath
        HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion
        .LINK
        https://github.com/onezeroone-dev/OZORegistry-PowerShell-Module/blob/main/Documentation/Convert-OZORegistryPath.md
    #>

    # Parameters
    [CmdletBinding()] Param (
        [Parameter(Mandatory=$true,HelpMessage="The registry path to convert")][String]$RegistryPath
    )
    # Switch on RegistryPath
    Switch($RegistryPath) {
        {$_ -Like "HKCR:\*"              } { return $RegistryPath.Replace("HKCR:","HKEY_CLASSES_ROOT")   }
        {$_ -Like "HKCU:\*"              } { return $RegistryPath.Replace("HKCU:","HKEY_CURRENT_USER")   }
        {$_ -Like "HKLM:\*"              } { return $RegistryPath.Replace("HKLM:","HKEY_LOCAL_MACHINE")  }
        {$_ -Like "HKU:\*"               } { return $RegistryPath.Replace("HKU:" ,"HKEY_USERS")          }
        {$_ -Like "HKCC:\*"              } { return $RegistryPath.Replace("HKCC:","HKEY_CURRENT_CONFIG") }
        {$_ -Like "HKEY_CLASSES_ROOT\*"  } { return $RegistryPath.Replace("HKEY_CLASSES_ROOT","HKCR:")   }
        {$_ -Like "HKEY_CURRENT_USER\*"  } { return $RegistryPath.Replace("HKEY_CURRENT_USER","HKCU:")   }
        {$_ -Like "HKEY_LOCAL_MACHINE\*" } { return $RegistryPath.Replace("HKEY_LOCAL_MACHINE","HKLM:")  }
        {$_ -Like "HKEY_USERS\*"         } { return $RegistryPath.Replace("HKEY_USERS","HKU:")           }
        {$_ -Like "HKEY_CURRENT_CONFIG\*"} { return $RegistryPath.Replace("HKEY_CURRENT_CONFIG","HKCC:") }
        default                            { return $RegistryPath                                        }
    }
}

Function Get-OZORegistryKey {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns an OZORegistryKey object that represents a registry key (whether existing or not). The object contains methods for reading, adding, updating, and removing key values; and a method for processing (writing) the changes to the registry. This function (and resulting object) is the most robust and flexible use of this module.
        .PARAMETER Key
        The registry key in the short ("HKLM:\...") or long ("HKEY_LOCAL_MACHINE\...") format. Key may be an existing key or a new (non-existing) key.
        .PARAMETER Display
        Display console messages (effective only for user-interactive sessions).
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion" -Display)
        Using key path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion.
        Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion exists and all values read.

        Name Type Value
        ---- ---- ----
        ProgramFilesDir String C:\Program Files
        CommonFilesDir String C:\Program Files\Common Files
        ProgramFilesDir (x86) String C:\Program Files (x86)
        CommonFilesDir (x86) String C:\Program Files (x86)\Common Files
        CommonW6432Dir String C:\Program Files\Common Files
        DevicePath ExpandString %SystemRoot%\inf
        MediaPathUnexpanded ExpandString %SystemRoot%\Media
        ProgramFilesPath ExpandString %ProgramFiles%
        ProgramW6432Dir String C:\Program Files
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion")
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion")
        $ozoRegistryKey.DisplayKeyValues()
        $ozoRegistryKey.SetDisplay($true)
        $ozoRegistryKey.DisplayKeyValues()

        Name Type Value
        ---- ---- ----
        ProgramFilesDir String C:\Program Files
        CommonFilesDir String C:\Program Files\Common Files
        ProgramFilesDir (x86) String C:\Program Files (x86)
        CommonFilesDir (x86) String C:\Program Files (x86)\Common Files
        CommonW6432Dir String C:\Program Files\Common Files
        DevicePath ExpandString %SystemRoot%\inf
        MediaPathUnexpanded ExpandString %SystemRoot%\Media
        ProgramFilesPath ExpandString %ProgramFiles%
        ProgramW6432Dir String C:\Program Files
        ```
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion")
        $ozoRegistryKey.ReturnKeyNameValue("ProgramFilesDir")
        C:\Program Files
        $ozoRegistryKey.ReturnKeyNameType("ProgramFilesDIr")
        String
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\One Zero One")
        If (($ozoRegistryKey.AddKeyName("Version","1.0.0")) -eq $true) {
            $ozoRegistryKey.ProcessChanges()
        }
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\One Zero One")
        If (($ozoRegistryKey.UpdateKeyName("Version","2.0.0")) -eq $true) {
            $ozoRegistryKey.ProcessChanges()
        }
        .EXAMPLE
        $ozoRegistryKey = (Get-OZORegistryKey -Key "HKEY_LOCAL_MACHINE\SOFTWARE\One Zero One")
        If (($ozoRegistryKey.UpdateName("Version")) -eq $true) {
            $ozoRegistryKey.ProcessChanges()
        }
        .LINK
        https://github.com/onezeroone-dev/OZORegistry-PowerShell-Module/blob/main/Documentation/Get-OZORegistryKey.md
        .NOTES
        Messages as written to the Windows Event Viewer One Zero One provider when available. Otherwise, messages are written to the Microsoft-Windows-PowerShell provider under event ID 4100.
    #>

    [CmdLetBinding()] Param (
        [Parameter(Mandatory=$true,HelpMessage="The registry key")][String]$Key,
        [Parameter(Mandatory=$false,HelpMessage="Display console messages")][Switch]$Display
    )
    # Return an OZORegistryKey object
    If ($Display -eq $true ) {
        $PSCmdlet.WriteObject(([OZORegistryKey]::new($Key,$true)))
    } Else {
        $PSCmdlet.WriteObject(([OZORegistryKey]::new($Key,$false)))
    }
}

Function Read-OZORegistryKeyNameValue {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        A simple function for returning the value for a single registry key name. Returns "Key path is not valid" if the path is not valid, "Key not found" if the path does not exist, "Could not read key names" if the key names could not be read, and "Unhandled data type" if the data cannot be returned.
        .PARAMETER Key
        The registry key.
        .PARAMETER Name
        The key name.
        .EXAMPLE
        Read-OZORegistryKeyNameValue -Key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion" -Name "ProgramFilesDir"
        C:\Program Files
        .EXAMPLE
        Read-OZORegistryKeyNameValue -Key "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion" -Name "ProgramFilesDir"
        C:\Program Files
        .LINK
        https://github.com/onezeroone-dev/OZORegistry-PowerShell-Module/blob/main/Documentation/Read-OZORegistryKeyNameValue.md
        .NOTES
        Messages as written to the Windows Event Viewer One Zero One provider when available. Otherwise, messages are written to the Microsoft-Windows-PowerShell provider under event ID 4100.
    #>

    # Parameters
    [CmdletBinding()] Param (
        [Parameter(Mandatory=$true,HelpMessage="The registry key")][String]$Key,
        [Parameter(Mandatory=$true,HelpMessage="The key name")][String]$Name
    )
    # Instantiate an OZORegistryKey object and return the value data
    return ([OZORegistryKey]::new($Key,$false)).ReturnKeyNameValue($Name)
}

Function Read-OZORegistryKeyNameType {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        A simple function for returning the _type_ of a single registry key name. Returns "Key path is not valid" if the path is not valid, "Key not found" if the path does not exist, "Could not read key names" if the key names could not be read, and "Unhandled data type" if the data cannot be returned.
        .PARAMETER Key
        The registry key.
        .PARAMETER Name
        The key name.
        .EXAMPLE
        Read-OZORegistryKeyNameType -Key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion" -Name "ProgramFilesDir"
        String
        .EXAMPLE
        Read-OZORegistryKeyNameType -Key "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion" -Name "ProgramFilesDir"
        String
        .LINK
        https://github.com/onezeroone-dev/OZORegistry-PowerShell-Module/blob/main/Documentation/Read-OZORegistryKeyNameType.md
        .NOTES
        Messages as written to the Windows Event Viewer One Zero One provider when available. Otherwise, messages are written to the Microsoft-Windows-PowerShell provider under event ID 4100.
    #>

    # Parameters
    [CmdletBinding()] Param (
        [Parameter(Mandatory=$true,HelpMessage="The registry key")][String]$Key,
        [Parameter(Mandatory=$true,HelpMessage="The key name")][String]$Name
    )
    # Instantiate an OZORegistryKey object and return the value type
    return ([OZORegistryKey]::new($Key,$false)).ReturnKeyNameType($Name)
}

Function Write-OZORegistryKeyNameValue {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        A simple function for adding or updating a single registry key name. If the key + name does not exist, it will be created. If it does exist, it will be updated. Returns True on success and False on failure. Failures are typically due to inadequate permissions or type mismatches.
        .PARAMETER Key
        The registry key.
        .PARAMETER Name
        The key name.
        .PARAMETER Value
        The name value.
        .PARAMETER Type
        The type. Valid types are "Binary", "Dword", "ExpandString", "MultString", "Qword", and "String". Defaults to "String".
        .EXAMPLE
        Write-OZORegistryKeyValueData -Key "HKEY_LOCAL_MACHINE\SOFTWARE\One Zero One" -Value "Acronym" -Data "OZO" -Type "String"
        .LINK
        https://github.com/onezeroone-dev/OZORegistry-PowerShell-Module/blob/main/Documentation/Write-OZORegistryKeyNameValue.md
        .NOTES
        Requires Administrator privileges. Messages as written to the Windows Event Viewer One Zero One provider when available. Otherwise, messages are written to the Microsoft-Windows-PowerShell provider under event ID 4100.
    #>

    # Parameters
    [CmdletBinding()] Param (
        [Parameter(Mandatory=$true,HelpMessage="The registry key")][String]$Key,
        [Parameter(Mandatory=$true,HelpMessage="The key name")][String]$Name,
        [Parameter(Mandatory=$true,HelpMessage="The name value")]$Value,
        [Parameter(Mandatory=$false,HelpMessage="The type")][ValidateSet("Binary","Dword","ExpandString","MultString","Qword","String")][String]$Type = "String"
    )
    # Instantiate an OZORegistryKey object and return the result of UpdateKeyValue
    [PSCustomObject] $ozoRegistryKey = ([OZORegistryKey]::new($Key,$false))
    # Determine if the name + value can be updated or added
    If ($ozoRegistryKey.UpdateKeyName($Name,$Value) -eq $true) {
        # Name + value added; return the result of ProcessChanges
        return ($ozoRegistryKey.ProcessChanges())
    }
}

Export-ModuleMember -Function Convert-OZORegistryString,Get-OZORegistryKey,Read-OZORegistryKeyNameValue,Read-OZORegistryKeyNameType,Write-OZORegistryKeyNameValue

# SIG # Begin signature block
# MIIfcQYJKoZIhvcNAQcCoIIfYjCCH14CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC5rQN+lvF0wRS/
# 7RcGtRElngh/dIc1LLivpN+wm1RjzqCCDPgwggZyMIIEWqADAgECAghkM1HTxzif
# CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
# EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G
# A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe
# Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w
# DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv
# cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD
# QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG
# bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU
# Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX
# 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC
# GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB
# wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk
# jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X
# gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg
# Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge
# VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR
# 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV
# t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF
# MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH
# AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE
# CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g
# LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G
# A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ
# KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH
# 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x
# xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT
# V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P
# Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc
# +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ
# LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV
# HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3
# NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9
# XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz
# iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od
# MIIGfjCCBGagAwIBAgIQZ2iSsNbwOsjnLExSAX6F6DANBgkqhkiG9w0BAQsFADB4
# MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x
# ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu
# ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI0MTExNjEwMzUyOFoXDTI1MTEx
# NjEwMzUyOFowZTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYD
# VQQHDAZEZW52ZXIxGDAWBgNVBAoMD0FuZHJldyBMaWV2ZXJ0ejEYMBYGA1UEAwwP
# QW5kcmV3IExpZXZlcnR6MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA
# vIBAQzK0aahepOrPmvCEqfd6dMZC4GvV7kflKwrn4QPJGfqhFmUtadP1e3ange8O
# QZ3/w7UjOTAUNUHfhjbSgUBlKjbS6EWQKZuRFzI3SNkMJkcjTX4uS2P4QsnwM+SW
# IE5me3CTssdjtgue+Iiy53TMgW8JpoxiULVxmm3bhCRUAgxWeT6tzjytR1UyGcMc
# cm/YE6TOgsCHiZoo4X4HJD9iHDrNldArq04Jl6FsADxEswttKyfqpIRJLoAysVl1
# f8CEDBwhszJrEXBnAlWViJFfNY+dKP4jhf7lLqSvPCuADqP2jvM0Ym5I8qDGMz9j
# XPSMLF58MFB4vM4viS7nLRFJ8S1Q98vQvB8W4kk0WPuiZbZTHsROzohE1VSbLnIY
# ag5dDOWI8L6yutAsfdZFYFmSTKcMSiOj5VbK4LhAJUL2G8vPwpTGFgr+cEp0p62F
# P0WXK+/cRfGqodI5S+bg+9rQTD9zf829DwraSRAt5P5zrQk4WPst3JW/vIKNx7cV
# AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFTC/hCVAJPN
# avXnwNfZsku4jwzjMHoGCCsGAQUFBwEBBG4wbDBIBggrBgEFBQcwAoY8aHR0cDov
# L2NlcnQuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQwOTYt
# UjEuY2VyMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTBRBgNVHSAE
# SjBIMAgGBmeBDAEEATA8BgwrBgEEAYKpMAEDAwEwLDAqBggrBgEFBQcCARYeaHR0
# cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMD
# ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMY29tLVN1
# YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNybDAdBgNVHQ4EFgQUSj8HrSK7
# f/j+Dz31jJFhOF7rJUMwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IC
# AQBf4lcc6FUJ1W/opNz8yjS9qLUy9cQt0s35BhasB5QoTbDaW4jv9xnFGhQVg6n+
# jhL0i94Vsywd/MRBb8lYGpuBZnS/7LHuRZu7qUuud+IMDyRHIyBK6koN5bfyA5VY
# c7bFbNpbe1s1hMWke8di4qgMLZKDfyG/RtA0swf5t4UgQLPP0h+koZ8X8V5+P0V0
# 1HsdXyXd+ojo38EoZyCKfQL2aAwMPwzZfCbmI5SRXNOc6K8oqXzQcendhlKSfVBo
# Zgpi+1updqbD4jmJfYdK5AYPxJ3YH6td6ETtr8owL+bmX8lQjlXPOwVnC11rVlNB
# VjqtaJRUClLtiNiYSTKVfjdmGVJ4+sNov0dWhHc0A9o5NX/05VVYTlImuJpnG5Og
# o7w6kWRdsgE8gM58jWf7XfI6aQS0Np/z2B+ZBj0K93khEHBX7cvvORa92LCHiVeP
# km+zEAMXgxIPs/e8cmcc/o3CORgzEwxlH9Z3UOWCuXSHD3P2RPNDAY+WPdjSHm9f
# JFlGq+f9iKyedxYa/NNjNag/5EbZ+Z2NldtSMNeFdsejGJ/TJHF1PyJd4aXx9J1i
# B/IZBOoJYyh9xpQ3ljZUKE/4otPi7INpuDFwgWiUHZZJVvrGTWwxH1Yhf8P+VpFf
# aNqsBuvklUcUDs3RNE0f1qlgFfcnAepFF+RiBRqmsj29fjGCEc8wghHLAgEBMIGM
# MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv
# bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu
# aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ
# YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK
# KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG
# 9w0BCQQxIgQg6OzF0q5CjbLxPxOCeof3YDjcL/Cv3xHXF9cO6hXQqz8wDQYJKoZI
# hvcNAQEBBQAEggGApHNsb0gLjlYq8C+2fK9t8e9D/J6OGbiMo7n9dVqe8/g39/pk
# Eo4aziUaR9fvae3YNA5VSy5h5UcSkKQRpnsuSetFfSPDc88slRsxQa3dWikja5MT
# y6tx+BO0Z1TKhgvTWebKSDiopAXFGSL/FDiIpp7x/RXygu4sc8FDbYM/9FidT64L
# g7xo2MoLV3SO6ah7QGueYwAZLrjjMAya86jK+f/U11Q83i8k55wHWJLONZbBxmuJ
# z5qbiBkMbOtgkA+Q6YBEnHAF+DpnFvtvRi5RxduAL7icn6yC2MXO/fKkgfaqJ82S
# a0vKZUQkIk7aHCh2hVG7MJqCEVbh/a98haswK0GodBpbmdJwY69T8WAA5tX0EJvi
# 5zzbt5ZK0SJvaroHvsiYYI904iqhNl1Ubt40twJe7b5iVGuKXrRO8ybtW/qWjqcS
# SPqvil+JeUI8f/mZIzNgakSUwgaAX5+f3IMiiUtYygWQbmmXzZi4Hvu30KAlVYBR
# QF5I5ggHLQQMGZ9EoYIPFTCCDxEGCisGAQQBgjcDAwExgg8BMIIO/QYJKoZIhvcN
# AQcCoIIO7jCCDuoCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm
# MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCAg1GZef8iJqEmb
# BIIA1kBN1SU2E9zJYwA1flkNC3lYJQIIaUIYMCvfAWQYDzIwMjUwNjA1MTg0NjA2
# WjADAgEBoIIMADCCBPwwggLkoAMCAQICEFparOgaNW60YoaNV33gPccwDQYJKoZI
# hvcNAQELBQAwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQH
# DAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBU
# aW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjEwHhcNMjQwMjE5MTYxODE5WhcN
# MzQwMjE2MTYxODE4WjBuMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
# BgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMSowKAYDVQQDDCFTU0wu
# Y29tIFRpbWVzdGFtcGluZyBVbml0IDIwMjQgRTEwWTATBgcqhkjOPQIBBggqhkjO
# PQMBBwNCAASnYXL1MOl6xIMUlgVC49zonduUbdkyb0piy2i8t3JlQEwA74cjK8g9
# mRC8GH1cAAVMIr8M2HdZpVgkV1LXBLB8o4IBWjCCAVYwHwYDVR0jBBgwFoAUDJ0Q
# JY6apxuZh0PPCH7hvYGQ9M8wUQYIKwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVo
# dHRwOi8vY2VydC5zc2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIx
# LmNlcjBRBgNVHSAESjBIMDwGDCsGAQQBgqkwAQMGATAsMCoGCCsGAQUFBwIBFh5o
# dHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkwCAYGZ4EMAQQCMBYGA1UdJQEB
# /wQMMAoGCCsGAQUFBwMIMEYGA1UdHwQ/MD0wO6A5oDeGNWh0dHA6Ly9jcmxzLnNz
# bC5jb20vU1NMLmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY3JsMB0GA1UdDgQW
# BBRQTySs77U+YxMjCZIm7Lo6luRdIjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcN
# AQELBQADggIBAJigjwMAkbyrxGRBf0Ih4r+rbCB57lTuwViC6nH2fZSciMogpqSz
# rSeVZ2eIb5vhj9rT7jqWXZn02Fncs4YTrA1QyxJW36yjC4jl5/bsFCaWuXzGXt2Y
# 6Ifp//A3Z0sNTMWTTBobmceM3sqnovdX9ToRFP+29r5yQnPcgRTI2PvrVSqLxY9E
# yk9/0cviM3W29YBl080ENblRcu3Y8RsfzRtVT/2snuDocRxvRYmd0TPaMgIj2xII
# 651QnPp1hiq9xU0AyovLzbsi5wlR5Ip4i/i8+x+HwYJNety5cYtdWJ7uQP6YaZtW
# /jNoHp76qNftq/IlSx6xEYBRjFBxHSq2fzhUQ5oBawk2OsZ2j0wOf7q7AqjCt6t/
# +fbmWjrAWYWZGj/RLjltqdFPBpIKqdhjVIxaGgzVhaE/xHKBg4k4DfFZkBYJ9BWu
# P93Tm+paWBDwXI7Fg3alGsboErWPWlvwMAmpeJUjeKLZY26JPLt9ZWceTVWuIyuj
# erqb5IMmeqLJm5iFq/Qy4YPGyPiolw5w1k9OeO4ErmS2FKvk1ejvw4SWR+S1VyWn
# ktY442WaoStxBCCVWZdMWFeB+EpL8uoQNq1MhSt/sIUjUudkyZLIbMVQjj7b6gPX
# nD6mS8FgWiCAhuM1a/hgA+6o1sJWizHdmcpYDhyNzorf9KVRE6iR7rcmMIIG/DCC
# BOSgAwIBAgIQbVIYcIfoI02FYADQgI+TVjANBgkqhkiG9w0BAQsFADB8MQswCQYD
# VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNV
# BAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENlcnRp
# ZmljYXRpb24gQXV0aG9yaXR5IFJTQTAeFw0xOTExMTMxODUwMDVaFw0zNDExMTIx
# ODUwMDVaMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwH
# SG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGlt
# ZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEArlEQE9L5PCCgIIXeyVAcZMnh/cXpNP8KfzFI6HJaxV6oYf3x
# h/dRXPu35tDBwhOwPsJjoqgY/Tg6yQGBqt65t94wpx0rAgTVgEGMqGri6vCI6rEt
# SZVy9vagzTDHcGfFDc0Eu71mTAyeNCUhjaYTBkyANqp9m6IRrYEXOKdd/eREsqVD
# mhryd7dBTS9wbipm+mHLTHEFBdrKqKDM3fPYdBOro3bwQ6OmcDZ1qMY+2Jn1o0l4
# N9wORrmPcpuEGTOThFYKPHm8/wfoMocgizTYYeDG/+MbwkwjFZjWKwb4hoHT2WK8
# pvGW/OE0Apkrl9CZSy2ulitWjuqpcCEm2/W1RofOunpCm5Qv10T9tIALtQo73GHI
# lIDU6xhYPH/ACYEDzgnNfwgnWiUmMISaUnYXijp0IBEoDZmGT4RTguiCmjAFF5OV
# NbY03BQoBb7wK17SuGswFlDjtWN33ZXSAS+i45My1AmCTZBV6obAVXDzLgdJ1A1r
# yyXz4prLYyfJReEuhAsVp5VouzhJVcE57dRrUanmPcnb7xi57VPhXnCuw26hw1Hd
# +ulK3jJEgbc3rwHPWqqGT541TI7xaldaWDo85k4lR2bQHPNGwHxXuSy3yczyOg57
# TcqqG6cE3r0KR6jwzfaqjTvN695GsPAPY/h2YksNgF+XBnUD9JBtL4c34AcCAwEA
# AaOCAYEwggF9MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU3QQJB6L1
# en1SUxKSle44gCUNplkwgYMGCCsGAQUFBwEBBHcwdTBRBggrBgEFBQcwAoZFaHR0
# cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tUm9vdENlcnRpZmljYXRp
# b25BdXRob3JpdHlSU0EuY3J0MCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3Ns
# LmNvbTA/BgNVHSAEODA2MDQGBFUdIAAwLDAqBggrBgEFBQcCARYeaHR0cHM6Ly93
# d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMIMDsGA1Ud
# HwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5jb20vc3NsLmNvbS1yc2EtUm9v
# dENBLmNybDAdBgNVHQ4EFgQUDJ0QJY6apxuZh0PPCH7hvYGQ9M8wDgYDVR0PAQH/
# BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCSGXUNplpCzxkH2fL8lPrAm/AV6USW
# Wi9xM91Q5RN7mZN3D8T7cm1Xy7qmnItFukgdtiUzLbQokDJyFTrF1pyLgGw/2hU3
# FJEywSN8crPsBGo812lyWFgAg0uOwUYw7WJQ1teICycX/Fug0KB94xwxhsvJBiRT
# pQyhu/2Kyu1Bnx7QQBA1XupcmfhbQrK5O3Q/yIi//kN0OkhQEiS0NlyPPYoRboHW
# C++wogzV6yNjBbKUBrMFxABqR7mkA0x1Kfy3Ud08qyLC5Z86C7JFBrMBfyhfPpKV
# lIiiTQuKz1rTa8ZW12ERoHRHcfEjI1EwwpZXXK5J5RcW6h7FZq/cZE9kLRZhvnRK
# tb+X7CCtLx2h61ozDJmifYvuKhiUg9LLWH0Or9D3XU+xKRsRnfOuwHWuhWch8G7k
# EmnTG9CtD9Dgtq+68KgVHtAWjKk2ui1s1iLYAYxnDm13jMZm0KpRM9mLQHBK5Gb4
# dFgAQwxOFPBslf99hXWgLyYE33vTIi9p0gYqGHv4OZh1ElgGsvyKdUUJkAr5hfbD
# X6pYScJI8v9VNYm1JEyFAV9x4MpskL6kE2Sy8rOqS9rQnVnIyPWLi8N9K4GZvPit
# /Oy+8nFL6q5kN2SZbox5d69YYFe+rN1sDD4CpNWwBBTI/q0V4pkgvhL99IV2Xasj
# HZf4peSrHdL4RjGCAlcwggJTAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
# DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt
# BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa
# WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G
# CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA2MDUxODQ2MDZaMCgGCSqG
# SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ
# BDEiBCCz/FWaEHO8V7/opBn8X5gX9eh98zmlInUgEeVqKTA7RjCByQYLKoZIhvcN
# AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29
# iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV
# BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t
# IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9
# xzAKBggqhkjOPQQDAgRGMEQCIEhB1p1U6qBG6OCzh3FXYjP6I+k/5rC34wccJIsI
# ag0VAiAf+eZCsQwcIJJL3LvjZoWf4JZXPrrzho7A7ErkmiWNFg==
# SIG # End signature block