Module/Rule.Registry/Convert/Methods.ps1

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

#region Method Functions
<#
    .SYNOPSIS
        Determines what function to use to extract the registry key from a string. This is used to
        account for all of the different variations on registry setting in different STIGs.
 
    .PARAMETER stigString
        This is an array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryKey
{
    [CmdletBinding()]
    [OutputType([string[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    $result = @()
    if (Test-SingleLineRegistryRule -CheckContent $checkContent)
    {
        $result = Get-SingleLineRegistryPath -CheckContent $checkContent

        if ($result -match 'HKEY_LOCAL_MACHINE\\Software\\McAfee\\\s\(32-bit\)|HKLM\\Software\\Wow6432Node\\McAfee\\\s\(64-bit\)')
        {
            $result = Get-McAfeeRegistryPath -CheckContent $CheckContent
        }

        if ($result -match "!")
        {
            $result = $result.Substring(0, $result.IndexOf('!'))
        }
    }
    else
    {
        # Get the registry hive from the content string
        $registryHive = Get-RegistryHiveFromWindowsStig -CheckContent $checkContent

        # Get the registry path from the content string
        $registryPath = Get-RegistryPathFromWindowsStig -CheckContent $checkContent

        foreach ($path in $registryPath)
        {
            $result += ($registryHive + $path)
        }
    }

    $result
}

<#
    .SYNOPSIS
        Extract the registry key root from a string.
 
    .PARAMETER CheckContent
        An array of the raw string data taken from the STIG setting.
#>

function Get-RegistryHiveFromWindowsStig
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    # Get the second index of the list, which should be the hive and remove spaces.
    $hive = ( ( $checkContent | Select-String -Pattern $regularExpression.RegistryHive ) -split ":" )[1]

    if ( -not [string]::IsNullOrEmpty( $hive ) )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $true"

        $hive = $hive.trim()

        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Trimmed : $hive"
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $false"
        throw "Registry hive was not found in check content."
    }

    $hive
}

<#
    .SYNOPSIS
        Extract the registry path from a string.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting. the raw sting data taken from the STIG setting.
#>

function Get-RegistryPathFromWindowsStig
{
    [CmdletBinding()]
    [OutputType([string[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    $result = @()
    $paths = ( $checkContent | Select-String -Pattern $regularExpression.registryPath )

    if ( [string]::IsNullOrEmpty($paths) )
    {
        throw "Registry path was not found in check content."
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $false"
    }
    else
    {
        foreach ( $path in $paths.Line )
        {
            if ( $path -match ':' )
            {
                # Get the second index of the list, which should be the path and remove spaces.
                $path = (($path -split ":")[1])
            }

            $path = $path.trim().TrimEnd("\")

            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Trimmed : $path"

            # There are several cases where the leading backslash is missing, so add it back.
            if ( $path -notmatch "^\\" )
            {
                $path = "\$path"
                Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Fixed Leading Backslash : $path"
            }

            $result += $path
        }
    }

    $result
}

<#
    .SYNOPSIS
        Extract the registry value type from a string.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryValueType
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    # The Office format is different to check which way to send the strings.
    if ( Test-SingleLineStigFormat -CheckContent $checkContent )
    {
        [string] $type = Get-RegistryValueTypeFromSingleLineStig -CheckContent $checkContent
    }
    else
    {
        # Get the second index of the list, which should be the data type and remove spaces.
        [string] $type = Get-RegistryValueTypeFromWindowsStig -CheckContent $checkContent
    }

    [string] $dscRegistryValueType = $dscRegistryValueType.$type
    # Verify the registry type against the dscRegistryValueType data section.
    if ( -not [string]::IsNullOrEmpty( $dscRegistryValueType ) )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Convert Type : $dscRegistryValueType "
        # Set the dsc format of the registry type
        [string] $return = $dscRegistryValueType
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] DSC Format Not Found For Type : $type"
        return
    }

    $return
}

<#
    .SYNOPSIS
        Tests that the ValueType is able to be used in a STIG
 
    .PARAMETER TestValueType
        The string to test against known good ValueTypes
#>

function Test-RegistryValueType
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $TestValueType
    )

    foreach ($valueType in $dscRegistryValueType.Keys)
    {
        if ($TestValueType -match $valueType)
        {
            $return = $valueType
            break
        }
    }

    if ($null -eq $return)
    {
        $return = $TestValueType
    }

    return $return
}

<#
    .SYNOPSIS
        Extract the registry value type from a Windows STIG string.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryValueTypeFromWindowsStig
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    $type = ( $checkContent | Select-String -Pattern $regularExpression.registryEntryType ).Matches.Value

    if ( -not [string]::IsNullOrEmpty( $type ) )
    {
        # Get the second index of the list, which should be the data type and remove spaces.
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $type"

        $type = ( ($type -split ":")[1] ).trim()

        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Trimmed : $type"
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $false"
        # If we get here, there is nothing to verify so return.
        return
    }

    $type
}

<#
    .SYNOPSIS
        Extract the registry value type from a string.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryValueName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    # The Office format is different to check which way to send the strings.
    if ( Test-SingleLineStigFormat -CheckContent $checkContent )
    {
        Get-RegistryValueNameFromSingleLineStig -CheckContent $checkContent
    }
    else
    {
        Get-RegistryValueNameFromWindowsStig -CheckContent $checkContent
    }
}

<#
    .SYNOPSIS
        Extract the registry value name from a string.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryValueNameFromWindowsStig
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    # Get the second index of the list, which should be the data type and remove spaces
    [string] $name = ( ( $checkContent |
                Select-String -Pattern $regularExpression.registryValueName ) -split ":" )[1]

    if ( -not [string]::IsNullOrEmpty( $name ) )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $true"

        $return = $name.trim()

        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Trimmed : $name"
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $false"
        return
    }

    return $return
}

<#
    .SYNOPSIS
        Looks for multiple patterns in the value string to extract out the value to return or determine
        if additional processing is required. For example if an allowable range detected, additional
        functions need to be called to convert the text into powershell operators.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryValueData
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    # The Office format is different to check which way to send the strings.
    switch ( $true )
    {
        { Test-SingleLineStigFormat -CheckContent $checkContent }
        {
            return Get-RegistryValueDataFromSingleStig -CheckContent $checkContent
        }
        default
        {
            return Get-RegistryValueDataFromWindowsStig -CheckContent $checkContent
        }
    }
}

<#
    .SYNOPSIS
        Looks for multiple patterns in the value string to extract out the value to return or determine
        if additional processing is required. For example if an allowable range detected, additional
        functions need to be called to convert the text into powershell operators.
 
    .PARAMETER CheckContent
        An array of the raw sting data taken from the STIG setting.
#>

function Get-RegistryValueDataFromWindowsStig
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    if ($this.ValueName -eq "LegalNoticeText")
    {
        $return = ($this.RawString | Select-String -Pattern 'You are accessing[^"]+(?<=details.)').Matches.Value
    }
    else
    {
        $valueString = ($checkContent | Select-String -Pattern $regularExpression.registryValueData)

        <#
            Get the second index of the list, which should be the data and remove spaces
        #>

        [string] $initialData = ( $valueString -replace $regularExpression.registryValueData )

        if ( -not [string]::IsNullOrEmpty( $initialData ) )
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $true"

            $return = $initialData.trim()

            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Trimmed : $return"
        }
        else
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Found : $false"
            # If the no data was found return, becasue there is nothing to further process.
            return
        }
    }

    $return
}

<#
    .SYNOPSIS
        Checks if a string contains the literal word Blank
 
    .PARAMETER ValueDataString
        String from the STIG to check
 
    .NOTES
        This is an edge case function.
#>

function Test-RegistryValueDataIsBlank
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]
        $ValueDataString
    )
    <#
        There is an edge case that returns the string (Blank) with the expected return to be an
        empty string. No further processing is necessary, so simply return the empty string.
    #>

    if ( $ValueDataString -Match $regularExpression.blankString )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $true"
        return $true
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $false"
        return $false
    }
}

<#
    .SYNOPSIS
        Checks if a string contains the literal word Enabled or Disabled
 
    .PARAMETER ValueDataString
        String from the STIG to check
 
    .NOTES
        This is an edge case function.
#>

function Test-RegistryValueDataIsEnabledOrDisabled
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]
        $ValueDataString
    )
    <#
        Here is an edge case that returns the string (Blank) with the expected return to be an
        empty string. No further processing is necessary, so simply return the empty string.
    #>

    if ( $ValueDataString -Match $regularExpression.enabledOrDisabled )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $true"
        return $true
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $false"
        return $false
    }
}

<#
    .SYNOPSIS
        Checks if a string contains the literal word Enabled or Disabled
 
    .PARAMETER ValueDataString
        String from the STIG to check
 
    .NOTES
        This is an edge case function.
#>

function Get-ValidEnabledOrDisabled
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ValueType,

        [Parameter(Mandatory = $true)]
        [string]
        $ValueData
    )
    <#
        There is an edge case where Enabled|Disabled is used in place of the Dword int
        Get the integer value for the string, otherwise leave the data value as is.
    #>

    if ( $ValueType -eq 'Dword' -and -not (Test-IsValidDword -ValueData $ValueData) )
    {
        ConvertTo-ValidDword -ValueData $ValueData
    }
    else
    {
        $ValueData
    }
}

<#
    .SYNOPSIS
        Checks if a string contains a hexadecimal number
 
    .PARAMETER ValueDataString
        String from the STIG to check
 
    .NOTES
        This is an edge case function.
#>

function Test-RegistryValueDataIsHexCode
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]
        $ValueDataString
    )
    <#
        There is an edge case that returns the string (Blank) with the expected return to be an
        empty string. No further processing is necessary, so simply return the empty string.
    #>

    if ( $ValueDataString -Match $regularExpression.hexCode )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $true"
        return $true
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $false"
        return $false
    }
}

<#
    .SYNOPSIS
        Returns the integer of a hexadecimal number
 
    .PARAMETER ValueDataString
        String from the STIG to Convert
 
    .NOTES
        Extract the hex code if it exists, convert to int32 and set the output value. This ignores the
        int that usually accompanies the hex value in parentheses.
#>

function Get-IntegerFromHex
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ValueDataString
    )

    $ValueDataString -Match $regularExpression.hexCode | Out-Null

    try
    {
        [convert]::ToInt32($matches[0], 16)
    }
    catch
    {
        throw "Could not convert $($matches[0]) into an integer"
    }
}

<#
    .SYNOPSIS
        Checks if a string contains a hexadecimal number
 
    .PARAMETER ValueDataString
        String from the STIG to check
 
    .NOTES
        This will match any lines that start with an integer (of any length) as the value to be set
#>

function Test-RegistryValueDataIsInteger
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]
        $ValueDataString
    )

    if ( $ValueDataString -Match $regularExpression.leadingIntegerUnbound -and
            $ValueDataString -NotMatch $regularExpression.hardenUncPathValues )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $true"
        return $true
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $false"
        return $false
    }
}

<#
    .SYNOPSIS
        Returns the number from a string
 
    .PARAMETER ValueDataString
        String from the STIG to Convert
#>

function Get-NumberFromString
{
    [CmdletBinding()]
    [OutputType([int])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ValueDataString
    )

    $string = Select-String -InputObject $ValueDataString `
                            -Pattern $regularExpression.leadingIntegerUnbound
    if ($null -eq $string)
    {
        throw
    }
    else
    {
        return $string.Matches[0].Value
    }
}

<#
    .SYNOPSIS
        Determines if a STIG check has a range of valid options.
 
    .DESCRIPTION
        There are serveral instances where a STIG check allows for a range of compliant values. This
        function reads the value string of a registry entry and if it discovers a sentence structure
        that provides for more than one value and $true flag is returned. If a fixed value is found
        a $false bool is returned.
 
    .PARAMETER ValueDataString
        The string to be tested.
 
    .EXAMPLE
        This example turns $true
        Test-RegistryValueDataContainsRange -ValueDataString "Value: 0x00008000 (32768) (or greater)"
 
    .EXAMPLE
        This example turns $false
        Test-RegistryValueDataContainsRange -ValueDataString "Value: 1"
 
    .NOTES
        General notes
#>

function Test-RegistryValueDataContainsRange
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]
        $ValueDataString
    )

    # Is in a word boundary since it is a common pattern
    if ($ValueDataString -match $regularExpression.registryValueRange -and
         $ValueDataString -notmatch 'Disabled or' -and
         $ValueDataString -notmatch 'You are accessing')
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $true"
        return $true
    }
    else
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] $false"
        return $false
    }
}

<#
    .SYNOPSIS
        Formats a string value into a multiline string by spliting it on a space or comma space format.
 
    .PARAMETER ValueDataString
        The registry value data string to split.
 
    .NOTES
        General notes
#>

function Format-MultiStringRegistryData
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ValueDataString
    )

    $regEx = "\s|,\s"

    Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Formatting Multi String Data"

    return ( $ValueDataString -split $regEx ) -join ";"
}

<#
    .SYNOPSIS
        Formats a string value into a multiline string by spliting it on a space or comma space foramt.
 
    .PARAMETER CheckStrings
        The registry value data string to split.
 
    .NOTES
        General notes
#>

function Get-MultiValueRegistryStringData
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckStrings
    )

    $multiStringEntries = [String]$CheckStrings |
        Select-String -Pattern $regularExpression.MultiStringNamedPipe -AllMatches

    $multiStringList = @()
    foreach ( $entry in $multiStringEntries.Matches )
    {
        $multiStringList += $entry.Value.ToString().Trim()
    }

    return $multiStringList -join ";"
}

<#
    .SYNOPSIS
        Verifies that the discovered dword is an integer.
 
    .DESCRIPTION
        Dword registry data can only contain integers. This function provides a quick validation of
        the data that was extracted from the stig string to further increase the confidence of the
        conversion process.
 
    .PARAMETER ValueData
        The string to be tested.
 
    .EXAMPLE
        This example turns $true
        Test-IsValidDword -ValueData "3"
 
    .EXAMPLE
        This example turns $false
        Test-IsValidDword -ValueData "Three"
 
    .NOTES
        General notes
#>

function Test-IsValidDword
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]
        $ValueData
    )
    <#
        Since this is a simple validation function we only need to know if it is a valide integer.
        If .Net can't figure it out, neither can we.
    #>

    try
    {
        [void] [System.Convert]::ToInt32( $ValueData )
    }
    catch [System.Exception]
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Valid Dword : $false"
        return $false
    }

    Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Valid Dword : $true"
    return $true
}

<#
    .SYNOPSIS
        Convert a string field into the correct dword value.
 
    .DESCRIPTION
        Several STIG settings provide the dword value in text or enumation format
        This function converts the english text value back into a a bit flag the dword accepts.
 
    .PARAMETER ValueData
        The text string to convert.
 
    .EXAMPLE
        In this example the string value "Enabled" is converted into the integer 1 and returned
 
        ConvertTo-ValidDword -ValueData "Enabled"
 
    .NOTES
        General notes
#>

function ConvertTo-ValidDword
{
    [CmdletBinding()]
    [OutputType([int])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ValueData
    )

    $conversionTable = @{
        Enabled  = 1
        Disabled = 0
    }

    <#
        There is an edge case the puts the data in the '1 (Enabled)' format
        pull out the string and convert it to the integer.
    #>

    $ValueData -Match $regularExpression.enabledOrDisabled | Out-Null

    $ValueData = $matches[0]
    if ( $null -ne $conversionTable.$ValueData )
    {
        Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Valid Dword : $conversionTable.$ValueData"
        $conversionTable.$ValueData
    }
    else
    {
        throw "'$ValueData' is not a valid dword enumeration."
    }
}

<#
    .SYNOPSIS
        There are several rules that publish multiple registry settings in a single rule.
        This function will check for multiple entries. Some of the entries have a single
        Hive or path and multiple values.
 
    .PARAMETER CheckContent
        The standard check content string to look for duplicate entries.
 
    .NOTES
        General notes
#>

function Test-MultipleRegistryEntries
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    if (Test-SingleLineStigFormat -CheckContent $checkContent)
    {
        $matches = $checkContent | Select-String -Pattern "(HKLM|HKCU)\\(?!Software\\McAfee)" -AllMatches

        if ($matches.Matches.Count -gt 1 -and $matches -match 'outlook\\security')
        {
            return $false
        }

        if ( $matches.Matches.Count -gt 1 )
        {
            return $true
        }

        return $false
    }
    else
    {
        [int] $hiveCount = ($checkContent |
                Select-String -Pattern $regularExpression.registryHive ).Count

        [int] $pathCount = ($checkContent |
                Select-String -Pattern $regularExpression.registryPath ).Count

        [int] $valueCount = ($checkContent |
                Select-String -Pattern $regularExpression.registryValueData ).Count

        [int] $valueNameCount = ($checkContent |
                Select-String -Pattern $regularExpression.registryValueName ).Count

        if ( ( $hiveCount + $pathCount + $valueCount + $valueNameCount ) -gt 4 )
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Multiple Entries : $true"
            return $true
        }
        else
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Multiple Entries : $false"
            return $false
        }
    }
}

<#
    .SYNOPSIS
        Splits multiple registry entries from a single check into individual check strings.
 
    .PARAMETER CheckContent
        The standard check content string to split.
 
    .NOTES
        General notes
#>

function Split-MultipleRegistryEntries
{
    [CmdletBinding()]
    [OutputType([System.Collections.ArrayList])]
    param
    (
        [Parameter(Mandatory = $true)]
        [psobject]
        $CheckContent
    )

    [int] $registryEntryCounter = 0
    [System.Collections.ArrayList] $registryEntries = @()

    if ( Test-SingleLineStigFormat -CheckContent $checkContent )
    {
        $paths = $checkContent | Select-String "(HKLM|HKCU)\\" -AllMatches

        if ( $paths.Matches.Count -gt 1 )
        {
            if ( $paths -match 'Procedure:' )
            {
                $paths = $($checkContent -join " ") -Split "AND(\s*)Procedure:"
            }

            if ( $checkContent -match 'Navigate to:' )
            {
                $keys = @()
                $paths = @()
                foreach ($line in $checkContent)
                {
                    if ( $line -match '^(HKLM|HKCU)' )
                    {
                        $keys += $line
                    }

                    if ( $line -match 'REG_DWORD value' )
                    {
                        foreach ($key in $keys)
                        {
                            $add = $key, $line -join " "
                            $paths += $add
                        }
                        $keys = @()
                    }
                }
            }
        }

        if ($paths.Count -lt 2)
        {
            if ( $paths -match " and the " )
            {
                $paths = $paths -split " and the "
            }
            else
            {
                $paths = $paths -split " and "
            }
        }
        foreach ($path in $paths)
        {
            if (![string]::IsNullOrWhiteSpace($path))
            {
                [void] $registryEntries.Add( $path )
                $registryEntryCounter ++
            }
        }
    }
    else
    {
        $hives  = $checkContent | Select-String -Pattern $regularExpression.registryHive
        $paths  = $checkContent | Select-String -Pattern $regularExpression.registryPath
        $types  = $checkContent | Select-String -Pattern $regularExpression.registryEntryType
        $names  = $checkContent | Select-String -Pattern $regularExpression.registryValueName
        $values = $checkContent | Select-String -Pattern $regularExpression.registryValueData

        # If a check contains a multiple registry hives, then reference each one that is discovered.
        if ( $hives.Count -gt 1 )
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Hives : $($hives.Count)"

            foreach ( $registryRule in $hives )
            {
                $newSplitRegistryEntry = @(
                    $hives[$registryEntryCounter],
                    $paths[$registryEntryCounter],
                    $types[$registryEntryCounter],
                    $names[$registryEntryCounter],
                    $values[$registryEntryCounter]) -join "`r`n"

                [void] $registryEntries.Add( $newSplitRegistryEntry )
                $registryEntryCounter ++
            }
        }
        <#
            If a check contains only the registry hive, but have multiple/unique paths,type,names,and values, then reference the single
            hive for each path that is discovered.
        #>

        elseif ($paths.count -gt 1 -and $types.count -eq 1 -and $names.count -eq 1 -and $values.count -eq 1)
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Paths : $($paths.count)"

            foreach ($registryRule in $paths)
            {
                $newSplitRegistryEntry = @(
                    $hives[0],
                    $paths[$registryEntryCounter],
                    $types[0],
                    $names[0],
                    $values[0]) -join "`r`n"

                [void] $registryEntries.Add( $newSplitRegistryEntry )
                $registryEntryCounter ++
            }
        }
        <#
            If a check contains a single registry hive, path, type, and value, but multiple value names, then reference
            the single hive hive, path, type, and value for each value name that is discovered.
        #>

        elseif ($names.count -gt 1 -and $types.count -eq 1 -and $values.count -eq 1)
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Values : $($names.count)"

            foreach ($registryRule in $names)
            {
                $newSplitRegistryEntry = @(
                    $hives[0],
                    $paths[0],
                    $types[0],
                    $names[$registryEntryCounter],
                    $values[0]) -join "`r`n"

                [void] $registryEntries.Add( $newSplitRegistryEntry )
                $registryEntryCounter ++
            }
        }
        <#
            If a check contains a single registry hive and path, but multiple values, then reference
            the single hive and path for each value name that is discovered.
        #>

        elseif ($names.count -gt 1 -and $types.count -gt 1)
        {
            Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Values : $($names.count)"

            foreach ($registryRule in $names)
            {
                $newSplitRegistryEntry = @(
                    $hives[0],
                    $paths[0],
                    $types[$registryEntryCounter],
                    $names[$registryEntryCounter],
                    $values[$registryEntryCounter]) -join "`r`n"

                [void] $registryEntries.Add( $newSplitRegistryEntry )
                $registryEntryCounter ++
            }
        }
        elseif ($hives.count -eq 1 -and $paths.count -gt 1 -and $types.count -eq 1 -and $names.count -eq 1 -and $values.count -eq 1)
        {
            foreach ( $registryRule in $names )
            {
                $newSplitRegistryEntry = @(
                    $hives[0],
                    $paths[$registryEntryCounter],
                    $types[0],
                    $names[0],
                    $values[0]) -join "`r`n"

                [void] $registryEntries.Add( $newSplitRegistryEntry )
                $registryEntryCounter ++
            }
        }
        elseif ($hives.count -eq 1 -and $paths.count -eq 1 -and $types.count -eq 1 -and $names.count -gt 1 -and $values.count -gt 1)
        {
            foreach ($registryRule in $values)
            {
                $newSplitRegistryEntry = @(
                    $hives[0],
                    $paths[0],
                    $types[0],
                    $names[$registryEntryCounter],
                    $values[$registryEntryCounter]) -join "`r`n"

                [void] $registryEntries.Add( $newSplitRegistryEntry )
                $registryEntryCounter ++
            }
        }
    }

    return $registryEntries
}

<#
    .SYNOPSIS
        Creates a registry pattern table and increments the pattern count from the single line functions
 
    .PARAMETER Pattern
        A registry rule pattern that has been applied
 
    .PARAMETER Rule
        Specifies a rule to include in output
 
    .NOTES
        Rules are not currently being captured in the results
        It is an optional parameter that can be included in the future
#>

function Set-RegistryPatternLog
{
    [CmdletBinding()]
    [OutputType([Object])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $Pattern,

        [Parameter()]
        [string]
        $Rule
    )

    <#
       Load table with patterns from Core data file.
       Build the in-memory table of patterns
    #>

    if (-not $global:patternTable)
    {
        $nonestedItems = $global:SingleLineRegistryPath.GetEnumerator() |
        Where-Object { $_.Value['Select'] -ne $null }

        $nestedItems = $global:SingleLineRegistryPath.GetEnumerator() |
        Where-Object { $_.Value['Select'] -eq $null } | Select-Object {$_.Value } -ExpandProperty Value

        $regPathTable = $nonestedItems.GetEnumerator() |
        ForEach-Object { New-Object -TypeName PSObject -Property @{Pattern=$_.Value['Select']; Count=0; Type='RegistryPath'}}

        $regPathTable += $nestedItems.GetEnumerator() |
        Where-Object { $_.Value['Select'] -ne $null } |
        ForEach-Object { New-Object -TypeName PSObject -Property @{Pattern=$_.Value['Select']; Count=0; Type='RegistryPath'}}

        $regValueTypeTable = $global:SingleLineRegistryValueType.GetEnumerator() |
        Where-Object { $_.Value['Select'] -ne $null } |
        ForEach-Object { New-Object -TypeName PSObject -Property @{Pattern=$_.Value['Select']; Count=0; Type='ValueType'}}

        $regValueNameTable = $global:SingleLineRegistryValueName.GetEnumerator() |
        Where-Object { $_.Value['Select'] -ne $null } |
        ForEach-Object { New-Object -TypeName PSObject -Property @{Pattern=$_.Value['Select']; Count=0; Type='ValueName'}}

        $regValueDataTable = $global:SingleLineRegistryValueData.GetEnumerator() |
        Where-Object { $_.Value['Select'] -ne $null } |
        ForEach-Object { New-Object -TypeName PSObject -Property @{Pattern=$_.Value['Select']; Count=0; Type='ValueData'}}

        $valueTypeTable = $regValueTypeTable |
        Group-Object -Property "Pattern" |
        ForEach-Object{ $_.Group | Select-Object 'Pattern','Count', 'Type' -First 1}

        $valueNameTable = $regValueNameTable |
        Group-Object -Property "Pattern" |
        ForEach-Object{ $_.Group | Select-Object 'Pattern','Count', 'Type' -First 1}

        $valueDataTable = $regValueDataTable |
        Group-Object -Property "Pattern" |
        ForEach-Object{ $_.Group | Select-Object 'Pattern','Count', 'Type' -First 1}

        $global:patternTable = $regPathTable + $valueTypeTable + $valueNameTable + $valueDataTable
    }

    # Find pattern in table and increment count
    $searchResult = $global:patternTable | Where-Object { $_.Pattern -eq $Pattern}
    if ($searchResult)
    {
        $searchResult.Count ++
    }
}

<#
    .SYNOPSIS
        Lists registry rule patterns along with counts for the number of rules that use each pattern.
 
    .PARAMETER Path
        Specifies a path to a directory with (unprocessed) xccdf.xml files or a specific xccdf.xml file.
        Path should be StigData\Archive\{Directory Name} or StigData\Archive\{DirectoryName}\{*.xccdf.xml}
 
    .Notes
        Expression patterns are only for Registry Rules, this could change in the future
#>

function Get-RegistryPatternLog
{
    [CmdletBinding()]
    [OutputType([Object])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    try
    {
        # If $Path is a directory, get all files contained in it
        $isFolder = Test-Path $Path -pathType Container
        if ($isFolder)
        {
            $files = Get-ChildItem -Path $Path -Filter '*.xml'
            foreach ($file in $files)
            {
                if (Test-StigProcessed $file.FullName)
                {
                    ConvertFrom-StigXccdf -Path $file.FullName | Out-Null
                }
            }
        }

        # If $Path is a file, process it
        $isFile = Test-Path $Path -pathType Leaf
        if ($isFile)
        {
            if (Test-StigProcessed $Path)
            {
                ConvertFrom-StigXccdf -Path $Path | Out-Null
            }
        }
    }
    catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
    {
        Write-Output "The path or file was not found: [$Path]"
    }
    catch [System.IO.IOException]
    {
        Write-Output "Error accessing path or file at: [$Path]"
    }

    # Return patterns table with counts
    return $global:patternTable
}

<#
    .SYNOPSIS
        Test if the check-content contains mitigations polices to enable.
 
    .PARAMETER Path
        Specifies the check-content element in the xccdf
 
    .Notes
        Currently all rules in the STIG state the policies referenced need to be enabled.
        However that could change in the future or in other STIGs so we need to check for both conditions (Enabled|Disabled)
#>

function Test-StigProcessed
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string[]]
        $Path
    )
    # Setup, check $Path for Processed
    [xml]$XmlDocument = Get-Content -Path $Path
    $id = $XmlDocument.Benchmark | Select-Object id

    $version = $Path | Select-String -Pattern '(?<=_)V.*(?=_)' |
    ForEach-Object { $_.Matches[0] -replace "V", "" -replace "R","\." }

    $conversionPath = Get-Item "$($PSScriptRoot)..\..\..\StigData\Processed"
    #Write-Host $testPath
    $hasConversion = Get-ChildItem -Path $conversionPath -recurse | Where-Object { $_ | Select-String -Pattern $id.id } | Where-Object { $_ | Select-String -Pattern $version }
    #$hasConversion = Get-ChildItem -Path ..\..\..\StigData\Processed -recurse | Where-Object { $_ | Select-String -Pattern $id.id } | Where-Object { $_ | Select-String -Pattern $version }

    if ($hasConversion)
    {
        return $true
    }
    else
    {
        return $false
    }
}
#endregion