Windows/TestHarnesses/T1112_ModifyRegistry/Registry.ps1

function Set-ATHRegistry {
<#
.SYNOPSIS
 
Creates a registry key or sets a registry value.
 
Technique ID: T1112 (Modify Registry)
 
.DESCRIPTION
 
Set-ATHRegistry automates the creation of registry keys and values using a variety of methods. Depending upon the tool or API, the implementation of each method is unique and differs. Set-ATHRegistry abstracts the complexity of building registry modification test cases in a way that is implementation-independent.
 
.PARAMETER Method
 
Specifies the method by which the registry key/value will be created. The following methods are supported:
 
* PowerShell - Performs registry operations natively in PowerShell
* RegExeCommandLine - Performs registry operations with the reg.exe command-line
* WMI - Performs registry operations using the StdRegProv WMI class within the root/default namespace. All registry modifications will occur within the context of a wmiprvse.exe process.
* VBScriptWscriptShellRegWrite - Executes VBScript code that calls the WScript.Shell RegWrite method. Note: this method can only be used to set registry values. The RegWrite method does not support creating registry keys. The RegWrite method also does not support registry value types of MultiString, Binary, and QWord.
* JScriptWscriptShellRegWrite - Executes JScript code that calls the WScript.Shell RegWrite method. Note: this method can only be used to set registry values. The RegWrite method does not support creating registry keys. The RegWrite method also does not support registry value types of MultiString, Binary, and QWord.
 
.PARAMETER Hive
 
Specifies the desired registry hive. Supported hives: HKCR, HKCU, HKLM, HKU
 
.PARAMETER KeyPath
 
Specifies the registry key path.
 
.PARAMETER ValueName
 
Specifies the registry value name to set.
 
.PARAMETER ValueString
 
Specifies a string to set for the specified registry value.
 
.PARAMETER ValueExpandString
 
Specifies a string containing unexpanded references to environment variables (for example, "%PATH%") to set for the specified registry value.
 
.PARAMETER ValueMultiString
 
Specifies a string array to set for the specified registry value.
 
.PARAMETER ValueBinary
 
Specifies a byte array to set for the specified registry value.
 
.PARAMETER ValueDword
 
Specifies a 32-bit value to set for the specified registry value.
 
.PARAMETER ValueQword
 
Specifies a 64-bit value to set for the specified registry value.
 
.PARAMETER Force
 
Specifies that if the registry key or value already exists, Set-ATHRegistry will overwrite the key/value.
 
.PARAMETER OnlyOutputTestSample
 
Specifies that the registry key/value should not be set. This switch is intended to have Set-ATHRegistry auto-generate copy-pastable code that can be used elsewhere. This auto-generated content is surfaced via the TestSampleContent property.
 
.OUTPUTS
 
PSObject
 
Outputs an object consisting of relevant execution details. The following object properties may be populated:
 
* TechniqueID - Specifies the relevant MITRE ATT&CK Technique ID.
* TestSuccess - Will be set to True if it was determined that the registry was successfully set. This property will not be set if -OnlyOutputTestSample is supplied.
* Method - Specifies the method by which the registry key/value was set. Supported values: PowerShell, RegExeCommandLine, WMI, VBScriptWscriptShellRegWrite, JScriptWscriptShellRegWrite
* SetKeyOnly - Will be set to True if only a registry key is being created.
* KeyPath - Specifies the registry key path including the hive name.
* ValueName - Specifies the registry value that was modified.
* ValueType - Specifies the registry value type that was set. Supported values: String, ExpandString, MultiString, Binary, DWord, QWord
* ValueContent - Specifies the registry content that was set.
* PreviousValueType - Specifies the registry value type prior to being set. This property will be empty if the value was created for the first time.
* PreviousValueContent - Specifies the registry content prior to being set. This property will be empty if the value was created for the first time.
* ProcessId - Specifies the process ID of the process that set the registry key/value. Note, this property will be empty if the WMI method is used since there is no straightforward way to infer the correct wmiprvse.exe process.
* ProcessPath - Specifies the process path of the process that set the registry key/value. Note, this property will be empty if the WMI method is used since there is no straightforward way to infer the correct wmiprvse.exe process.
* ProcessCommandLine - Specifies the process command-line of the process that set the registry key/value. Note, this property will be empty if the WMI method is used since there is no straightforward way to infer the correct wmiprvse.exe process.
* TestSampleContent - Specifies auto-generated content that can be used to set the registry key/value in other testing contexts depending upon which method was selected.
 
.EXAMPLE
 
Set-ATHRegistry -Method PowerShell -Hive HKCU -KeyPath 'Foo\Bar\Baz\Bin'
 
Creates a HKEY_CURRENT_USER\Foo\Bar\Baz\Bin key using PowerShell.
 
.EXAMPLE
 
Set-ATHRegistry -Method RegExeCommandLine -Hive HKCU -KeyPath 'Foo\Bar\Baz\Bin'
 
Creates a HKEY_CURRENT_USER\Foo\Bar\Baz\Bin key using reg.exe.
 
.EXAMPLE
 
Set-ATHRegistry -Method WMI -Hive HKCU -KeyPath 'Foo\Bar\Baz\Bin' -ValueName 'Hello' -ValueString 'World' -Force
 
Writes the string "World" to the "Hello" value in the HKEY_CURRENT_USER\Foo\Bar\Baz\Bin using WMI, overwriting any existing value.
 
.EXAMPLE
 
Set-ATHRegistry -Method VBScriptWscriptShellRegWrite -Hive HKCU -KeyPath 'Foo\Bar\Baz\Bin' -ValueName 'Hello' -ValueExpandString '%windir%' -Force
 
Writes the expand string "%windir%" to the "Hello" value in the HKEY_CURRENT_USER\Foo\Bar\Baz\Bin using VBScript, overwriting any existing value.
 
#>


    [CmdletBinding()]
    param (
        [String]
        [ValidateSet('PowerShell', 'RegExeCommandLine', 'WMI', 'VBScriptWscriptShellRegWrite', 'JScriptWscriptShellRegWrite')]
        $Method = 'PowerShell',

        [Parameter(Mandatory)]
        [String]
        [ValidateSet('HKCR', 'HKCU', 'HKLM', 'HKU')]
        $Hive,

        [Parameter(Mandatory, ParameterSetName = 'ValueQword')]
        [Parameter(Mandatory, ParameterSetName = 'ValueDword')]
        [Parameter(Mandatory, ParameterSetName = 'ValueBinary')]
        [Parameter(Mandatory, ParameterSetName = 'ValueMultiString')]
        [Parameter(Mandatory, ParameterSetName = 'ValueExpandString')]
        [Parameter(Mandatory, ParameterSetName = 'ValueString')]
        [Parameter(Mandatory, ParameterSetName = 'KeyOnly')]
        [String]
        $KeyPath,

        [Parameter(Mandatory, ParameterSetName = 'ValueString')]
        [Parameter(Mandatory, ParameterSetName = 'ValueExpandString')]
        [Parameter(Mandatory, ParameterSetName = 'ValueMultiString')]
        [Parameter(Mandatory, ParameterSetName = 'ValueBinary')]
        [Parameter(Mandatory, ParameterSetName = 'ValueDword')]
        [Parameter(Mandatory, ParameterSetName = 'ValueQword')]
        [String]
        $ValueName,

        [Parameter(Mandatory, ParameterSetName = 'ValueString')]
        [String]
        $ValueString,

        [Parameter(Mandatory, ParameterSetName = 'ValueExpandString')]
        [String]
        $ValueExpandString,

        [Parameter(Mandatory, ParameterSetName = 'ValueMultiString')]
        [String[]]
        $ValueMultiString,

        [Parameter(Mandatory, ParameterSetName = 'ValueBinary')]
        [Byte[]]
        $ValueBinary,

        [Parameter(Mandatory, ParameterSetName = 'ValueDword')]
        [Int32]
        $ValueDword,

        [Parameter(Mandatory, ParameterSetName = 'ValueQword')]
        [Int64]
        $ValueQword,

        [Switch]
        $Force,

        [Switch]
        $OnlyOutputTestSample
    )

    $DefaultDirectory = $PWD.Path
    $DefaultVBScriptFileName = 'test.vbs'
    $DefaultJScriptFileName  = 'test.js'

    $SetKeyOnly = $False
    $DeleteKeyFirst = $False
    $OverwriteValue = $False
    $PreviousValueType = $null
    $PreviousValueContent = $null
    $ValueType = $null
    $ValueContent = $null
    $SetKeyResult = $null
    $SuppliedValueName = $null
    $ValueContent = $null
    $TestSuccess = $null
    $ProcessId = $null
    $ProcessPath = $null
    $ProcessCommandLine = $null
    $TestSampleContent = $null

    # Resolve the full hive name
    switch ($Hive) {
        'HKCR' { $HiveName = 'HKEY_CLASSES_ROOT'  }
        'HKCU' { $HiveName = 'HKEY_CURRENT_USER'  }
        'HKLM' { $HiveName = 'HKEY_LOCAL_MACHINE' }
        'HKU'  { $HiveName = 'HKEY_USERS'         }
    }

    [UInt32] $HiveValue = @{
        'HKCR' = ([UInt32] 2147483648) # 0x80000000
        'HKCU' = ([UInt32] 2147483649) # 0x80000001
        'HKLM' = ([UInt32] 2147483650) # 0x80000002
        'HKU'  = ([UInt32] 2147483651) # 0x80000003
    }[$Hive]

    $ResolvedKeyPath = "$HiveName\$KeyPath"
    $ResolvedKeyParentPath = Split-Path -Path $ResolvedKeyPath -Parent
    $ResolvedKeyName = Split-Path -Path $ResolvedKeyPath -Leaf

    if ($PSCmdlet.ParameterSetName -eq 'KeyOnly') {
        $SetKeyOnly = $True

        if (@('VBScriptWscriptShellRegWrite', 'JScriptWscriptShellRegWrite') -contains $Method) {
            Write-Error 'This method does not support the creation of just registry keys. To use this method, you must supply a value name.'
            return
        }
    }

    $KeyItem = Get-Item -Path "Registry::$ResolvedKeyPath" -ErrorAction Ignore

    if ($SetKeyOnly) {
        if ($KeyItem -and $Force) {
            $DeleteKeyFirst = $True
        } elseif ($KeyItem -and !$OnlyOutputTestSample) {
            Write-Error "The following key path already exists: $ResolvedKeyPath. Either first delete the key or use the -Force switch to delete the key and recreate it."
            return
        }
    } else {
        $IsDefaultValueName = $False

        if ($ValueName -eq '(Default)') { $IsDefaultValueName = $True }

        $SuppliedValueName = $ValueName

        switch ($PSCmdlet.ParameterSetName) {
            'ValueString' {
                $ValueContent = $ValueString
                $ValueType = ([Microsoft.Win32.RegistryValueKind]::String)
                $ValueTypeString = 'REG_SZ'
            }

            'ValueExpandString' {
                $ValueContent = $ValueExpandString
                $ValueType = ([Microsoft.Win32.RegistryValueKind]::ExpandString)
                $ValueTypeString = 'REG_EXPAND_SZ'

                # Extract the supplied environment variables exist
                # Don't perform strict validation on them. Only extract them for debugging/validation purposes.
                $PercentSignCount = $ValueExpandString.ToCharArray() | Where-Object { $_ -eq '%' } | Measure-Object | Select-Object -ExpandProperty Count

                if (($PercentSignCount -eq 0) -or (($PercentSignCount % 2) -eq 1)) {
                    Write-Warning 'Either an odd number of percent signs was supplied or none were supplied at all. Validate the supplied expand string and ensure that environment variables to be expanded were supplied correctly.'
                }

                $EnvRegex = [Regex] '%(?<envvar>.*?)%'

                foreach ($Match in $EnvRegex.Matches($ValueExpandString)) {
                    $EnvVarSupplied = $Match.Groups['envvar'].Value

                    Write-Verbose "The following environment variable was supplied: %$($EnvVarSupplied)%"

                    if (-not (Get-Item -Path "Env:\$EnvVarSupplied" -ErrorAction Ignore)) {
                        Write-Warning "The following environment variable is not currently defined: %$($EnvVarSupplied)%"
                    }
                }
            }

            'ValueMultiString' {
                $ValueContent = $ValueMultiString
                $ValueType = ([Microsoft.Win32.RegistryValueKind]::MultiString)
                $ValueTypeString = 'REG_MULTI_SZ'
            }

            'ValueBinary' {
                $ValueContent = $ValueBinary
                $ValueType = ([Microsoft.Win32.RegistryValueKind]::Binary)
                $ValueTypeString = 'REG_BINARY'
            }

            'ValueDword' {
                $ValueContent = $ValueDword
                $ValueType = ([Microsoft.Win32.RegistryValueKind]::DWord)
                $ValueTypeString = 'REG_DWORD'
            }

            'ValueQword' {
                $ValueContent = $ValueQword
                $ValueType = ([Microsoft.Win32.RegistryValueKind]::QWord)
                $ValueTypeString = 'REG_QWORD'
            }
        }

        # See if the registry value already exists
        $ValueItem = Get-ItemProperty -Path "Registry::$ResolvedKeyPath" -Name $ValueName -ErrorAction Ignore

        if ($ValueItem) {
            if ($IsDefaultValueName) {
                $PreviousValueType = $KeyItem.GetValueKind('')
                $PreviousValueContent = $KeyItem.GetValue('', $null, 'DoNotExpandEnvironmentNames')
            } else {
                $PreviousValueType = $KeyItem.GetValueKind($ValueName)
                $PreviousValueContent = $KeyItem.GetValue($ValueName, $null, 'DoNotExpandEnvironmentNames')
            }
        }

        if ($ValueItem -and $Force) {
            $OverwriteValue = $True
        } elseif ($ValueItem -and !$OnlyOutputTestSample) {
            Write-Error "The following key path/value already exists: $ResolvedKeyPath - $ValueName ($PreviousValueType). Either first delete the value or use the -Force switch to overwrite it."
            return
        }
    }

    switch ($Method) {
        'PowerShell' {
            if ($SetKeyOnly) {
                $TestSampleContent = "New-Item -Path 'Registry::$ResolvedKeyParentPath' -Name '$ResolvedKeyName' -Force"

                if ($DeleteKeyFirst) { $TestSampleContent = "Remove-Item -Path 'Registry::$ResolvedKeyPath' -Recurse; $TestSampleContent" }

                if (-not $OnlyOutputTestSample) {
                    if ($DeleteKeyFirst) {
                        Remove-Item -Path "Registry::$ResolvedKeyPath" -Recurse -ErrorAction Stop
                    }

                    $SetKeyResult = New-Item -Path "Registry::$ResolvedKeyParentPath" -Name $ResolvedKeyName -Force -ErrorAction Stop

                    if ($SetKeyResult) {
                        $TestSuccess = $True
                        $ProcessId = $PID

                        $ProcessInfo = Get-CimInstance -ClassName Win32_Process -Property CommandLine, ExecutablePath -Filter "ProcessID = $PID" -Verbose:$False
                        $ProcessPath = $ProcessInfo.ExecutablePath
                        $ProcessCommandLine = $ProcessInfo.CommandLine
                    }
                }
            } else {
                switch ("$ValueType") {
                    'String' { $ValueString = "'$ValueContent'" }

                    'ExpandString' { $ValueString = "'$ValueContent'" }

                    'MultiString' { $ValueString = "@($(($ValueContent | ForEach-Object { "'$_'" }) -join ', '))" }

                    'Binary' { $ValueString = "@($(($ValueContent | ForEach-Object { "0x$($_.ToString('X2'))" }) -join ','))" }

                    'DWord' { $ValueString = $ValueContent }

                    'QWord' { $ValueString = $ValueContent }
                }

                $TestSampleContent = "Set-ItemProperty -Path 'Registry::$ResolvedKeyPath' -Name '$ValueName' -Type $ValueType -Value $ValueString"

                if (-not $KeyItem) {
                    # PowerShell first needs to create the key
                    $TestSampleContent = "New-Item -Path 'Registry::$ResolvedKeyParentPath' -Name '$ResolvedKeyName'; $TestSampleContent"
                }

                if (-not $OnlyOutputTestSample) {
                    # First create the key
                    if (-not $KeyItem) { $null = New-Item -Path "Registry::$ResolvedKeyParentPath" -Name $ResolvedKeyName -ErrorAction Stop }

                    $SetValueResult = Set-ItemProperty -Path "Registry::$ResolvedKeyPath" -Name $ValueName -Value $ValueContent -Type $ValueType -PassThru -ErrorAction Stop

                    if ($SetValueResult) {
                        $TestSuccess = $True
                        $ProcessId = $PID

                        $ProcessInfo = Get-CimInstance -ClassName Win32_Process -Property CommandLine, ExecutablePath -Filter "ProcessID = $PID"  -Verbose:$False
                        $ProcessPath = $ProcessInfo.ExecutablePath
                        $ProcessCommandLine = $ProcessInfo.CommandLine
                    }
                }
            }
        }

        'VBScriptWscriptShellRegWrite' {
            $CScriptExePath = Get-Command -Name cscript.exe -ErrorAction Stop | Select-Object -ExpandProperty Source

            $ResolvedScriptFilePath = Join-Path -Path $DefaultDirectory -ChildPath $DefaultVBScriptFileName

            $ResolvedKeyPathValue = "$Hive\$KeyPath\$ValueName"

            if ($IsDefaultValueName) { $ResolvedKeyPathValue = "$HiveName\$KeyPath\" }

            $TestSampleContentTemplate = "WScript.CreateObject(`"WScript.Shell`").RegWrite `"$ResolvedKeyPathValue`", REPLACEVALUECONTENT, `"REPLACEREGVALUETYPE`""

            switch ("$ValueType") {
                'String' {
                    $TestSampleContent = $TestSampleContentTemplate.Replace('REPLACEREGVALUETYPE', $ValueTypeString).Replace('REPLACEVALUECONTENT', "`"$ValueContent`"")
                }

                'ExpandString' {
                    $TestSampleContent = $TestSampleContentTemplate.Replace('REPLACEREGVALUETYPE', $ValueTypeString).Replace('REPLACEVALUECONTENT', "`"$ValueContent`"")
                }

                'MultiString' {
                    Write-Error 'The WScript.Shell RegWrite method does not support REG_MULTI_SZ writing more than 4 bytes. RegWrite supports the following registry value types: REG_SZ, REG_EXPAND_SZ, REG_DWORD.'
                    return
                }

                'Binary' {
                    Write-Error 'The WScript.Shell RegWrite method does not support REG_BINARY writing more than 4 bytes. RegWrite supports the following registry value types: REG_SZ, REG_EXPAND_SZ, REG_DWORD.'
                    return
                }

                'DWord' {
                    $TestSampleContent = $TestSampleContentTemplate.Replace('REPLACEREGVALUETYPE', $ValueTypeString).Replace('REPLACEVALUECONTENT', $ValueContent)
                }

                'QWord' {
                    Write-Error 'The WScript.Shell RegWrite method does not support REG_QWORD. RegWrite supports the following registry value types: REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_DWORD.'
                    return
                }
            }

            if (-not $OnlyOutputTestSample) {
                # Write the script to disk and execute it
                Out-File -FilePath $ResolvedScriptFilePath -InputObject $TestSampleContent -ErrorAction Stop

                $ProcessCommandLine = "`"$CScriptExePath`" `"$ResolvedScriptFilePath`""

                Write-Verbose "cscript.exe command line: $ProcessCommandLine"

                $Process = Start-Process -FilePath $CScriptExePath -ArgumentList @("`"$ResolvedScriptFilePath`"") -PassThru -Wait -ErrorAction Stop

                if ($Process.ExitCode -ne 0) {
                    Write-Error "cscript.exe exited with a non-successful error code: $($Process.ExitCode)"
                    return
                }

                $ProcessId = $Process.Id
                $ProcessPath = $Process.StartInfo.FileName
            }
        }

        'JScriptWscriptShellRegWrite' {
            $CScriptExePath = Get-Command -Name cscript.exe -ErrorAction Stop | Select-Object -ExpandProperty Source

            $ResolvedScriptFilePath = Join-Path -Path $DefaultDirectory -ChildPath $DefaultJScriptFileName

            $ResolvedKeyPathValue = "$Hive\$KeyPath\$ValueName".Replace('\', '\\')

            if ($IsDefaultValueName) { $ResolvedKeyPathValue = "$HiveName\$KeyPath\".Replace('\', '\\') }

            $TestSampleContentTemplate = "(new ActiveXObject(`"WScript.Shell`")).RegWrite(`"$ResolvedKeyPathValue`", REPLACEVALUECONTENT, `"REPLACEREGVALUETYPE`");"

            switch ("$ValueType") {
                'String' {
                    $TestSampleContent = $TestSampleContentTemplate.Replace('REPLACEREGVALUETYPE', $ValueTypeString).Replace('REPLACEVALUECONTENT', "`"$ValueContent`"")
                }

                'ExpandString' {
                    $TestSampleContent = $TestSampleContentTemplate.Replace('REPLACEREGVALUETYPE', $ValueTypeString).Replace('REPLACEVALUECONTENT', "`"$ValueContent`"")
                }

                'MultiString' {
                    Write-Error 'The WScript.Shell RegWrite method does not support REG_MULTI_SZ writing more than 4 bytes. RegWrite supports the following registry value types: REG_SZ, REG_EXPAND_SZ, REG_DWORD.'
                    return
                }

                'Binary' {
                    Write-Error 'The WScript.Shell RegWrite method does not support REG_BINARY writing more than 4 bytes. RegWrite supports the following registry value types: REG_SZ, REG_EXPAND_SZ, REG_DWORD.'
                    return
                }

                'DWord' {
                    $TestSampleContent = $TestSampleContentTemplate.Replace('REPLACEREGVALUETYPE', $ValueTypeString).Replace('REPLACEVALUECONTENT', $ValueContent)
                }

                'QWord' {
                    Write-Error 'The WScript.Shell RegWrite method does not support REG_QWORD. RegWrite supports the following registry value types: REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_DWORD.'
                    return
                }
            }

            if (-not $OnlyOutputTestSample) {
                # Write the script to disk and execute it
                Out-File -FilePath $ResolvedScriptFilePath -InputObject $TestSampleContent -ErrorAction Stop

                $ProcessCommandLine = "`"$CScriptExePath`" `"$ResolvedScriptFilePath`""

                Write-Verbose "cscript.exe command line: $ProcessCommandLine"

                $Process = Start-Process -FilePath $CScriptExePath -ArgumentList @("`"$ResolvedScriptFilePath`"") -PassThru -Wait -ErrorAction Stop

                if ($Process.ExitCode -ne 0) {
                    Write-Error "cscript.exe exited with a non-successful error code: $($Process.ExitCode)"
                    return
                }

                $ProcessId = $Process.Id
                $ProcessPath = $Process.StartInfo.FileName
            }
        }

        'RegExeCommandLine' {
            $RegExePath = Get-Command -Name reg.exe -ErrorAction Stop | Select-Object -ExpandProperty Source

            $ResolvedKeyPathRegExe = "$Hive\$KeyPath"

            $RegCommandLineArgs = New-Object -TypeName 'System.Collections.Generic.List[String]'
            $RegCommandLineArgs.Add('ADD')

            if ($ResolvedKeyPathRegExe -match '\s') {
                # Wrap the key path in double quotes if it contains whitespace
                $RegCommandLineArgs.Add("`"$ResolvedKeyPathRegExe`"")
            } else {
                $RegCommandLineArgs.Add($ResolvedKeyPathRegExe)
            }

            if ($SetKeyOnly) {
                if ($DeleteKeyFirst) {
                    $RegCommandLineArgs.Add('/f')
                }

                $TestSampleContent = "`"$RegExePath`" $($RegCommandLineArgs -join ' ')"

                Write-Verbose "reg.exe command line: $TestSampleContent"

                if (-not $OnlyOutputTestSample) {
                    $ProcessCommandLine = $TestSampleContent

                    $Process = Start-Process -FilePath $RegExePath -ArgumentList $RegCommandLineArgs -PassThru -Wait -ErrorAction Stop

                    if ($Process.ExitCode -ne 0) {
                        Write-Error "reg.exe exited with a non-successful error code: $($Process.ExitCode)"
                        return
                    }

                    $ProcessId = $Process.Id
                    $ProcessPath = $Process.StartInfo.FileName
                }
            } else {
                $WrapValueStringInQuotes = $False

                switch ("$ValueType") {
                    'String' {
                        if ($ValueContent -match '\s') { $WrapValueStringInQuotes = $True }

                        $ValueString = $ValueContent
                    }

                    'ExpandString' {
                        if ($ValueContent -match '\s') { $WrapValueStringInQuotes = $True }

                        $ValueString = $ValueContent
                    }

                    'MultiString' {
                        $ValueString = $ValueContent -join '\0'
                    }

                    'Binary' {
                        $ValueString = ($ValueContent | ForEach-Object { $_.ToString('x2') }) -join ''
                    }

                    'DWord' {
                        $ValueString = "0x$($ValueContent.ToString('x8'))"
                    }

                    'QWord' {
                        $ValueString = "0x$($ValueContent.ToString('x16'))"
                    }
                }

                if ($IsDefaultValueName) {
                    # "adds an empty value name (Default) for the key."
                    $RegCommandLineArgs.Add('/ve')
                    $RegCommandLineArgs.Add('/t')
                    $RegCommandLineArgs.Add($ValueTypeString)
                } else {
                    $RegCommandLineArgs.Add('/v')

                    if ($ValueName -match '\s') {
                        # Wrap the value name in double quotes if it contains whitespace
                        $RegCommandLineArgs.Add("`"$ValueName`"")
                    } else {
                        $RegCommandLineArgs.Add($ValueName)
                    }

                    $RegCommandLineArgs.Add('/t')
                    $RegCommandLineArgs.Add($ValueTypeString)
                }

                # The '/d' arg won't be appended if the supplied value is null. Not supplying '/d' will null out the value.
                if ($null -ne $ValueContent) {
                    $RegCommandLineArgs.Add('/d')

                    if ($WrapValueStringInQuotes) {
                        $RegCommandLineArgs.Add("`"$ValueString`"")
                    } else {
                        $RegCommandLineArgs.Add($ValueString)
                    }
                }

                if ($OverwriteValue) { $RegCommandLineArgs.Add('/f') }

                $TestSampleContent = "`"$RegExePath`" $($RegCommandLineArgs -join ' ')"

                if (-not $OnlyOutputTestSample) {
                    Write-Verbose "reg.exe command line: $ProcessCommandLine"
                    $ProcessCommandLine = $TestSampleContent

                    $Process = Start-Process -FilePath $RegExePath -ArgumentList $RegCommandLineArgs -PassThru -Wait -ErrorAction Stop

                    if ($Process.ExitCode -ne 0) {
                        Write-Error "reg.exe exited with a non-successful error code: $($Process.ExitCode)"
                        return
                    }

                    $ProcessId = $Process.Id
                    $ProcessPath = $Process.StartInfo.FileName
                }
            }
        }

        'WMI' {
            $StdRegProvMethodArgs = @{
                hDefKey = $HiveValue
                sSubKeyName = $KeyPath
            }

            if ($SetKeyOnly) {
                $StdRegProvMethod = 'CreateKey'
            } else {
                if ($IsDefaultValueName) {
                    $ValueNameToSupply = ''
                    $StdRegProvMethodArgs['sValueName'] = $ValueNameToSupply
                } else {
                    $ValueNameToSupply = $ValueName
                    $StdRegProvMethodArgs['sValueName'] = $ValueNameToSupply
                }

                switch ("$ValueType") {
                    'String' {
                        $StdRegProvMethod = 'SetStringValue'
                        $ParamName = 'sValue'
                        $StdRegProvMethodArgs[$ParamName] = $ValueContent
                        $ValueString = "'$ValueContent'"
                    }

                    'ExpandString' {
                        $StdRegProvMethod = 'SetExpandedStringValue'
                        $ParamName = 'sValue'
                        $StdRegProvMethodArgs[$ParamName] = $ValueContent
                        $ValueString = "'$ValueContent'"
                    }

                    'MultiString' {
                        $StdRegProvMethod = 'SetMultiStringValue'
                        $ParamName = 'sValue'
                        $StdRegProvMethodArgs[$ParamName] = $ValueContent
                        $ValueString = "@($(($ValueContent | ForEach-Object { "'$_'" }) -join ', '))"
                    }

                    'Binary' {
                        $StdRegProvMethod = 'SetBinaryValue'
                        $ParamName = 'uValue'
                        $StdRegProvMethodArgs[$ParamName] = $ValueContent
                        $ValueString = "([Byte[]] @($(($ValueContent | ForEach-Object { "0x$($_.ToString('X2'))" }) -join ', ')))"
                    }

                    'DWord' {
                        $StdRegProvMethod = 'SetDWORDValue'
                        $ParamName = 'uValue'
                        # Convert the Int32 argument to UInt32
                        [UInt32] $UnsignedValue = [UInt32] "0x$($ValueContent.ToString('X8'))"
                        $ValueString = "([UInt32] '0x$($ValueContent.ToString('X8'))')"
                        $StdRegProvMethodArgs[$ParamName] = $UnsignedValue
                    }

                    'QWord' {
                        $StdRegProvMethod = 'SetQWORDValue'
                        $ParamName = 'uValue'
                        # Convert the Int64 argument to UInt64
                        [UInt64] $UnsignedValue = [UInt64] "0x$($ValueContent.ToString('X16'))"
                        $ValueString = "([UInt64] '0x$($ValueContent.ToString('X16'))')"
                        $StdRegProvMethodArgs[$ParamName] = $UnsignedValue
                    }
                }
            }

            $SetKeyTemplate = @"
Invoke-CimMethod -Namespace 'ROOT/default' -ClassName 'StdRegProv' -MethodName 'CreateKey' -Arguments @{
    hDefKey = ([UInt32] $HiveValue)
    sSubKeyName = '$KeyPath'
}
"@


            $SetValueTemplate = @"
Invoke-CimMethod -Namespace 'ROOT/default' -ClassName 'StdRegProv' -MethodName '$StdRegProvMethod' -Arguments @{
    hDefKey = ([UInt32] $HiveValue)
    sSubKeyName = '$KeyPath'
    sValueName = '$ValueNameToSupply'
    $ParamName = $ValueString
}
"@


            if ($SetKeyOnly) {
                $TestSampleContent = $SetKeyTemplate
            } else {
                if (-not $KeyItem) {
                    # The key needs to first be set
                    $TestSampleContent = @"
$SetKeyTemplate
 
$SetValueTemplate
"@

                } else {
                    $TestSampleContent = $SetValueTemplate
                }
            }

            if (-not $OnlyOutputTestSample) {
                if ((-not $SetKeyOnly) -and (-not $KeyItem)) {
                    # The key needs to be created prior to setting the value
                    $StdRegProvMethodResult = Invoke-CimMethod -Namespace 'ROOT/default' -ClassName 'StdRegProv' -MethodName 'CreateKey' -Verbose:$False -ErrorAction Stop -Arguments @{
                        hDefKey = $HiveValue
                        sSubKeyName = $KeyPath
                    }

                    if ($StdRegProvMethodResult.ReturnValue -ne 0) {
                        Write-Error "Failed to successfully execute the StdRegProv CreateKey method. Error code: $($StdRegProvMethodResult.ReturnValue)"
                        return
                    }
                }
                
                $StdRegProvMethodResult = Invoke-CimMethod -Namespace 'ROOT/default' -ClassName 'StdRegProv' -MethodName $StdRegProvMethod -Arguments $StdRegProvMethodArgs -Verbose:$False -ErrorAction Stop

                if ($StdRegProvMethodResult.ReturnValue -ne 0) {
                    Write-Error "Failed to successfully execute the StdRegProv $StdRegProvMethod method. Error code: $($StdRegProvMethodResult.ReturnValue)"
                    return
                }
            }
        }
    }

    if (-not $OnlyOutputTestSample) {
        # Validate that the key/value was set as expected
        if ($SetKeyOnly) {
            $KeyItem = Get-Item -Path "Registry::$ResolvedKeyPath" -ErrorAction Ignore

            if ((-not $KeyItem) -or ($KeyItem.Name -ne $ResolvedKeyPath)) {
                Write-Error "reg.exe failed to set the following key path: $ResolvedKeyPath"
                return
            }
        } else {
            $ValueItem = Get-ItemProperty -Path "Registry::$ResolvedKeyPath" -Name $ValueName -ErrorAction Stop

            if ($ValueItem) {
                $KeyItem = Get-Item -Path "Registry::$ResolvedKeyPath" -ErrorAction Stop

                if ($IsDefaultValueName) {
                    $SetValueType = $KeyItem.GetValueKind('')
                    $SetValueContent = $KeyItem.GetValue('', $null, 'DoNotExpandEnvironmentNames')
                } else {
                    $SetValueType = $KeyItem.GetValueKind($ValueName)
                    $SetValueContent = $KeyItem.GetValue($ValueName, $null, 'DoNotExpandEnvironmentNames')
                }
            } else {
                Write-Error "Failed to set the following registry value: $ResolvedKeyPath - $ValueName ($PreviousValueType)"
                return
            }

            if ($SetValueType -ne $ValueType) {
                Write-Error "Failed to set the expected registry value content. Actual: $SetValueType. Expected: $ValueType."
                return
            }

            if (Compare-Object -ReferenceObject $SetValueContent -DifferenceObject $ValueContent) {
                Write-Error 'Failed to set the expected registry value content.'
                return
            }
        }

        $TestSuccess = $True
    }

    [PSCustomObject] @{
        TechniqueID = 'T1112'
        TestSuccess = $TestSuccess
        Method = $Method
        SetKeyOnly = $SetKeyOnly
        KeyPath = $ResolvedKeyPath
        ValueName = $SuppliedValueName
        ValueType = $ValueType
        ValueContent = $ValueContent
        PreviousValueType = $PreviousValueType
        PreviousValueContent = $PreviousValueContent
        ProcessId = $ProcessId
        ProcessPath = $ProcessPath
        ProcessCommandLine = $ProcessCommandLine
        TestSampleContent = $TestSampleContent
    }
}