Import-RegFile function: imports Reg file to PSObject or directly to registry


#region Function Import-RegFile
Function Import-RegFile {
<#
.SYNOPSIS
	Reads a .REG file and exports to a PsObject with the following properties: Key, Name, Value, Type
	NEW: Applies the .REG file to the Registry (Beta Quality)
.DESCRIPTION
	Reads a .REG file and exports to a PsObject with the following properties: Key, Name, Value, Type
	Handles huge .REG files that aren't easily rewritable in your deployment script.
	Generates a PS object collection of properties that can used by PSADT's Set-RegistryKey
.PARAMETER Path
	Path of the .REG file to import
.PARAMETER ApplyRegFile
	Applies the .REG file to the Registry (Beta Quality)
.EXAMPLE
	Returns hash table only. (Does not import anything)
	Import-RegFile -Path "C:\registry.reg"
.EXAMPLE
	Import masive REG file *And* feeds it to Set-RegistryKey
	$RegFileObject = Import-RegFile -Path "C:\Users\Username\Desktop\registry.reg"
	ForEach ($RegFileRow in $RegFileObject) {
		Set-RegistryKey -Key $RegFileRow.Key -Name $RegFileRow.Name -Type $RegFileRow.Type -Value $RegFileRow.Value
	}
.EXAMPLE
	Returns hash table *AND* imports at the same time (beta quality)
	Import-RegFile -Path "C:\registry.reg" -ApplyRegFile $true
.NOTES
	Authors:
	rivardma ( 9 may 2019 )
		-Modified to be more robust and PSADT-fied Script from: 
		https://social.technet.microsoft.com/Forums/scriptcenter/en-US/a623f698-4364-40bf-84a1-1fbae3852c1d/powershell-how-to-parse-a-registry-text-file?forum=ITCG
	Denis St-Pierre 21 Nov 2019 (Ottawa, Canada)
		-Improved Error handling and logging
		-Fixed handling of default @ values
		-added support for NONE type
		-Added comments section/header
	Denis St-Pierre 25 Mar 2020 (Ottawa, Canada)
		-Added -ApplyRegFile parameter
	Limitations: 
	-Only tested on Windows 10.
	-Cannot handle REG key deletion and REG value deletion via REG file. e.g  [-hklm\remove\key]
	-Cannot be used as a splat directly. Must treat as an array of HashTables and parse each line as per example.
#>
	[CmdLetBinding()]
	Param(
		 [Parameter(Mandatory=$true)]
		 [string]$Path,
		 [Parameter(Mandatory=$false)]
		 [Bool]$ApplyRegFile=$False
	)
	Begin {
		## Get the name of this function and write header
		[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
		Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
		#Set the special character used in .REG file to define end of string
		$endOfString = [char]0x0000
		
		If (-not (Test-path -LiteralPath $Path)) {
			Write-Log -Message "REG file [$Path] does not exist" -Source ${CmdletName} -Severity 3
			Throw "REG file [$Path] does not exist" 
		}
	}
	Process {
		Try {
			$fi	= Get-Item $Path
			$file = $fi.OpenText()
			Write-Log -Message "REG file successfully loaded: [$Path]" -Source ${CmdletName}
			While (-not ($file.EndOfStream)){
				Remove-Variable hash -ErrorAction SilentlyContinue #Needed to make error messages accurate
				$line = $file.ReadLine()
				If ($line.Length -gt 0) {
					If ($line -match '^\[(.*)\]$' ) {
						If ($emptyFolder) {
							#$hash = @{Key=$key;Name="(Default)";Value=$null;Type="String"}
							If (-not $ApplyRegFile) {
								#Value cannot be $null when calling Set-Registry value, but seems ok when only building array
								$hash = @{Key=$key;Name="(Default)";Value=$null;Type="String"}
							}
							Write-Log -Message "Empty registry key found: Key=[$key]" -Source ${CmdletName}
							#New-Object  PSObject -Property $hash #Set-RegistryKey cannot handle Value=$null
							Write-Log -Message "Value is null, not including in output" -Source ${CmdletName}
						}
						$key = $matches[1]
						$emptyFolder = $true
					}
					If ($line -match '=') {
						$emptyFolder = $false
						while (($line.Trim().Substring($line.Trim().Length -1,1) -eq "\") -or ($line.Trim().Split("=",2)[1].Substring(0,1) -eq """" -and $line.Trim().Substring($line.Trim().Length -1,1) -ne """")){
							If ($line.Trim().Substring($line.Trim().Length -1,1) -eq "\"){
								#Not finished hex value
								$line = $line.Replace("\","").Trim() + $file.ReadLine().Trim()
							} Else {
								#String not finished, return carriage causes a new line in the file
								$line = $line.Trim() + "`r`n" + $file.ReadLine().Trim()
							}
						}
						$a = $line.Split("=",2)
						$name = $a[0].Replace('"','')
						If ($name -eq "@"){$name = "(Default)"}
						If ($a[1].Substring(0,1) -eq """"){
							$value = $a[1].Substring(1,$a[1].Length-2).Replace("\\","\").Replace("\""","""")
							$type = 'String'
						} Else {
							$b = $a[1].Split(':',2)
							If ($b[0] -eq "dword"){
								$type = "Dword"
								$value = [convert]::toint32($b[1],16)
							} ElseIf($b[0] -eq "hex(0)"){
								$type = "None"
								$value=[byte[]]($b[1].Split(',') | ForEach-Object { "0x$_"})
							} ElseIf($b[0] -eq "hex"){
								$type = "Binary"
								$value=[byte[]]($b[1].Split(',') | ForEach-Object { "0x$_"})
							} ElseIf($b[0] -eq "hex(b)"){
								$type = "QWord"
								$value = [BitConverter]::ToInt64([byte[]]($b[1].Split(',') | ForEach-Object { "0x$_"}),0)
							} ElseIf($b[0] -eq "hex(2)"){
								$type = "ExpandString"
								$value = [System.Text.Encoding]::Unicode.GetString([byte[]]($b[1].Split(',') | ForEach-Object { "0x$_"})).Trim($endOfString)
							} ElseIf($b[0] -eq "hex(7)"){
								$type = "MultiString"
								$value = [System.Text.Encoding]::Unicode.GetString([byte[]]($b[1].Split(',') | ForEach-Object { "0x$_"})).Trim($endOfString).Split($endOfString)
							} Else{
								$type = "Unknown" #CAVEAT: this will make Set-RegistryKey throw an error
							}
						}
						$hash = @{Key=$key;Name=$name;Value=$value;Type=$type}
						Write-Log -Message "Registry key found with the following properties: Key=$key, Name=$name, Value=$value, Type=$type" -Source ${CmdletName}
						New-Object  PSObject -Property $hash
					}
				}
				If ($ApplyRegFile) {
					ForEach ($RegFileRow in $hash) {
						Write-log $("Applying [Key=$($hash.key);Name=$($hash.name);Value=$($hash.value);Type=$($hash.type)]") -Source ${CmdletName} -Severity 5
						Set-RegistryKey -Key $RegFileRow.Key -Name $RegFileRow.Name -Type $RegFileRow.Type -Value $RegFileRow.Value
					}
				}
			}
		}
		catch {
			Write-Log -Message "Failed to parse the REG file [$Path]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
			Write-Log -Message "`$line=[$line] `$type=[$type]" -Source ${CmdletName}
		}
	}
	End {
		Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
	}
}
#endregion Function Import-RegFile