Resources/GroupPolicy.psm1

Import-Module $(Join-Path -Path $PSScriptRoot -ChildPath 'DscBaseline.helper.psm1')

function Get-GroupPolicyRegistry
{
    <#
      .SYNOPSIS
        Parses gpresults for registry values.
    #>


    [OutputType([System.String[]])]
    [CmdletBinding()]
    param( )

    $returnValue = @()

    [string[]] $contents = Invoke-GPResult

    for($i=0;$i -lt $contents.Length;$i++)
    {
        if([System.String]::IsNullOrWhiteSpace($contents[$i]) -eq $false)
        {
            if($contents[$i].Trim().StartsWith('GPO: '))
            {
                if($contents[$($i+1)].Trim().StartsWith('Folder Id: ') -and `
                $contents[$($i+2)].Trim().StartsWith('Value: ') -and `
                $contents[$($i+3)].Trim().StartsWith('State: '))
                {
                    $regEntry = @{
                                    'GPO'       = "$($contents[$i].Split(':')[1].Trim())";
                                    'Folder Id' = "$($contents[$($i+1)].Split(':')[1].Trim())";
                                    'Value'     = "$($contents[$($i+2)].Split(':')[1].Trim())";
                                    'State'     = "$($contents[$($i+3)].Split(':')[1].Trim())";
                                    'HexValue'  = '';
                                    'ParentFolder' = '';
                                    'ValueType' = '';
                                    'ValueName' = '';
                                }

                    $returnValue += Get-RegistryValueDetails -RegObj $regEntry
                                                                            
                    $i = $i + 3
                }
            }
        }
    }

    return $returnValue

} # end Get-GroupPolicyRegistry

function Invoke-GPResult
{
    <#
      .SYNOPSIS
        Private function that wraps gpresult.exe
      .EXAMPLE
        Invoke-GPResult
      .NOTES
        Modified auditpol.exe wrapper
      .LINK
        https://github.com/dsccommunity/AuditPolicyDsc/blob/dev/DSCResources/AuditPolicyResourceHelper/AuditPolicyResourceHelper.psm1
    #>

    
    [OutputType([System.String[]])]
    [CmdletBinding()]
    param()

    $gpResultReturn = ''

    [string[]] $gpResultArguments = @('/scope COMPUTER','/Z')

    try 
    {
        # Use System.Diagnostics.Process to process the gpresult command
        $process = New-Object System.Diagnostics.Process
        $process.StartInfo.Arguments = $gpResultArguments
        $process.StartInfo.CreateNoWindow = $true
        $process.StartInfo.FileName = 'gpresult.exe'
        $process.StartInfo.RedirectStandardOutput = $true
        $process.StartInfo.UseShellExecute = $false
        $null = $process.Start()
        
        $gpResultReturn = $process.StandardOutput.ReadToEnd()

        if([System.String]::IsNullOrEmpty($gpResultReturn))
        {
            throw

        } else {

            if($gpResultReturn.Trim().ToLower().StartsWith('error: access denied.'))
            {
                throw
            }
        }

        $process.Dispose()
    }
    catch [System.ComponentModel.Win32Exception] {
        # Catch error if the gpresult command is not found on the system
        Write-Error -Message 'gpresult.exe not found.'
    }
    catch {
        
        # Catch the error thrown if the lastexitcode is not 0
        [string] $errorString  = $error[0].Exception + `
                                " `nLASTEXITCODE = $LASTEXITCODE" + `
                                " `nCommand = gpresult.exe $gpResultArguments" + `
                                " `nUser = $($env:USERDOMAIN)\$($env:USERNAME)" + `
                                " `nMake sure that you are running as the local administrator or have opened the command prompt using the `'Run as administrator`' option.`n"
        
        Write-Error -Message $errorString
    }
    
    return $gpResultReturn.Split([System.Environment]::Newline,[System.StringSplitOptions]::RemoveEmptyEntries)

} # end function Invoke-GPResult

function Get-RegistryValueDetails
{
    <#
      .SYNOPSIS
        Gets details of registry value.
      .DESCRIPTION
        Tests multiple hives until a value is found since gpresult output doesn't
        specify the root hive. While almost always it will be HKLM, adding
        additional checks to be sure.
 
        Querying the registry key in this manner is necessary to obtain the
        value type (e.g. SZ, DWORD, etc.)
    #>


    [OutputType([System.Collections.Hashtable])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.Collections.Hashtable]
        # Registry Object Hashtable
        $RegObj
    )

    $valueTypes = @{ 'REG_SZ'        = 'String';
                     'REG-BINARY'    = 'Binary';
                     'REG_DWORD'     = 'DWord';
                     'REG_QWORD'     = 'QWord';
                     'REG_MULTI_SZ'  = 'MultiString';
                     'REG_EXPAND_SZ' = 'ExpandString';
                    }
    
    [string[]] $registryHives = 'HKEY_LOCAL_MACHINE','HKEY_CURRENT_USER','HKEY_USERS','HKEY_CURRENT_CONFIG','HKEY_CLASSES_ROOT'

    $regKey    = $RegObj['Folder Id'].Split('\')[-1]
    $regParent = $RegObj['Folder Id'].Replace($('\' + $regKey),'')

    for($i=0;$i -lt $registryHives.Length;$i++)
    {
        $queryName = "`"$($registryHives[$i])\$($regParent)`""

        [string[]] $regQuery = Invoke-RegExe -RegistryLocation $queryName

        $result = $regQuery -imatch "^$regKey\s*(REG_SZ|REG-BINARY|REG_DWORD|REG_QWORD|REG_MULTI_SZ|REG_EXPAND_SZ).*$"
   
        if($result -ne $false)
        {
            [string[]] $itemArray = $regQuery[1].Split(' ',[System.StringSplitOptions]::RemoveEmptyEntries) | %{ $_.Trim() }

            $RegObj['HexValue']     = $itemArray[-1]
            $RegObj['ParentFolder'] = Join-Path -Path $registryHives[$i] -ChildPath $regParent
            $RegObj['ValueType']    = $valueTypes[$($itemArray[1])]
            $RegObj['ValueName']    = $regKey
            
            $i = 20
        }
    }

    return $RegObj

} # end function Get-RegistryValueDetails

function Invoke-RegExe
{
    <#
      .SYNOPSIS
        Private function that wraps reg.exe
      .EXAMPLE
        Invoke-RegExe
      .NOTES
        Modified auditpol.exe wrapper
      .LINK
        https://github.com/dsccommunity/AuditPolicyDsc/blob/dev/DSCResources/AuditPolicyResourceHelper/AuditPolicyResourceHelper.psm1
    #>


    [OutputType([System.String[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.String]
        # Registry location to query
        $RegistryLocation
    )

    # set the base commands to execute
    $regExeArguments = @('query',$RegistryLocation)
    $regExeReturn = ''
    
    try {
        # Use System.Diagnostics.Process to process the reg.exe command
        $process = New-Object System.Diagnostics.Process
        $process.StartInfo.Arguments = $regExeArguments
        $process.StartInfo.CreateNoWindow = $true
        $process.StartInfo.FileName = 'reg.exe'
        $process.StartInfo.RedirectStandardOutput = $true
        $process.StartInfo.UseShellExecute = $false
        
        if($process.Start())
        {
            [string] $regExeReturn = $process.StandardOutput.ReadToEnd()

            if($regExeReturn -imatch "^.*error: access is denied\..*")
            {
                throw
            
            } else {

                if($regExeReturn -imatch "^\s*$")
                {
                    throw
                }
                
                return $regExeReturn.Split([System.Environment]::Newline,[System.StringSplitOptions]::RemoveEmptyEntries) | %{ $_.Trim() }
            }

        } else { 
        
            throw 
        }
        
        $process.Dispose()
    }
    catch [System.ComponentModel.Win32Exception] {
        # Catch error if the reg.exe command is not found on the system
        Write-Error -Message 'reg.exe not found.'
    }
    catch {
        # Catch the error thrown if the lastexitcode is not 0
        [String] $errorString = $error[0].Exception + `
                               "`nLASTEXITCODE = $LASTEXITCODE" + `
                               " `nCommand = reg.exe $($regExeArguments -Join ' ')" + `
                               " `nUser = $($env:USERDOMAIN)\$($env:USERNAME)" + `
                               " `nUser must be running in an elevated prompt." + `
                               " `nMake sure that you are running as the local administrator or have opened the command prompt using the `'Run as administrator`' option.`n"
        
        Write-Error -Message $errorString
    }

    return $regExeReturn

} # end function Invoke-RegExe

function Get-DscBaselineGroupPolicy
{
    <#
      .SYNOPSIS
        Converts Group Policy registry settings to DSC registry values.
    #>

    
    [OutputType([System.String])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$false)]
        [ValidateScript({Test-Path -Path $_ -PathType Container})]
        [System.String]
        # Folder to save new .ps1 file
        $Folder = (Get-Location).Path
    )

    [string[]] $dscConfig = @()
    
    $destFile = Join-Path -Path $Folder -ChildPath 'DscBaselineGroupPolicy.ps1'
    $settings = Get-GroupPolicyRegistry

    $dscConfig += 'Configuration DscBaselineGroupPolicy'
    $dscConfig += '{'
    $dscConfig += " # Generated: $(Get-Date) on $($env:COMPUTERNAME)"
    $dscConfig += ' # ref: https://github.com/PowerShell/PSDscResources#registry'
    $dscConfig += ''
    $dscConfig += ' Import-DscResource -Module PSDscResources -Name Registry'
    $dscConfig += ''
    $dscConfig += ' Node localhost'
    $dscConfig += ' {'

    foreach($entry in $settings)
    {
        if([System.String]::IsNullOrEmpty($entry['ParentFolder']) -eq $false)
        {
            # adding random to ensure name is unique
            $dscConfigName = Convertto-DscConfigurationName -InputObj "$($entry['GPO']) $($entry['ValueName']) $(Get-Random -Maximum 9999)"

            $dscConfig += "$($global:CONFIG_INDENT)Registry $dscConfigName"
            $dscConfig += "$($global:CONFIG_INDENT){"
            $dscConfig += "$($global:CONFIG_INDENT) Key = '$($entry['ParentFolder'])'"
            $dscConfig += "$($global:CONFIG_INDENT) Ensure = 'Present'"
            $dscConfig += "$($global:CONFIG_INDENT) ValueName = '$($entry['ValueName'])'"
            $dscConfig += "$($global:CONFIG_INDENT) ValueType = '$($entry['ValueType'])'"
            $dscConfig += "$($global:CONFIG_INDENT) ValueData = '$($entry['HexValue'])'"
            $dscConfig += "$($global:CONFIG_INDENT) Force = `$true"
            $dscConfig += "$($global:CONFIG_INDENT) Hex = `$true"
            $dscConfig += "$($global:CONFIG_INDENT)}"
        }
    }

    $dscConfig += ' }'
    $dscConfig += '}'
    $dscConfig += ''
    $dscConfig += ". DscBaselineGroupPolicy -OutputPath $(Join-Path $Folder -ChildPath 'DscBaselineGroupPolicy') -Verbose"
    $dscConfig += ''

    Out-File -FilePath $destFile -InputObject $dscConfig -Encoding ASCII -Force

    return $destFile

} # end function Get-DscBaselineGroupPolicy

Export-ModuleMember -Function 'Get-DscBaselineGroupPolicy'