Functions/extras/InstallerRegistration.ps1

#region Microsoft Derived Functions from MicrosoftProgram_Install_and_Uninstall.meta.diagcab\MSIMATSFN.ps1

#################################################################################
# Copyright (c) 2011, Microsoft Corporation. All rights reserved.
#
# You may use this code and information and create derivative works of it,
# provided that the following conditions are met:
# 1. This code and information and any derivative works may only be used for
# troubleshooting a) Windows and b) products for Windows, in either case using
# the Windows Troubleshooting Platform
# 2. Any copies of this code and information
# and any derivative works must retain the above copyright notice, this list of
# conditions and the following disclaimer.
# 3. THIS CODE AND INFORMATION IS PROVIDED `AS IS'' WITHOUT WARRANTY OF ANY
# KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. IF THIS
# CODE AND INFORMATION IS USED OR MODIFIED, THE ENTIRE RISK OF USE OR RESULTS IN
# CONNECTION WITH THE USE OF THIS CODE AND INFORMATION REMAINS WITH THE USER.
#################################################################################

function MATSFingerPrint {
    Param (
        $ProductCode,
        $Value,
        $Type,
        $DateTimeRun
    )
    if (!(test-path "HKLM:\Software\Microsoft\MATS\WindowsInstaller\$ProductCode\$DateTimeRun")){
        New-Item -Path hklm:\software\Microsoft\MATS\ -ErrorAction SilentlyContinue
        New-Item -Path hklm:\software\Microsoft\MATS\WindowsInstaller -ErrorAction SilentlyContinue
        New-Item -Path hklm:\software\Microsoft\MATS\WindowsInstaller\$ProductCode -ErrorAction SilentlyContinue
        New-Item -Path hklm:\software\Microsoft\MATS\WindowsInstaller\$ProductCode\$DateTimeRun -ErrorAction SilentlyContinue
    }
    New-ItemProperty hklm:\software\Microsoft\MATS\WindowsInstaller\$ProductCode\$DateTimeRun -name $Value -value $Type -propertyType String
}

function UninstallProduct {
$ProductUninstall=
@"
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
public class UninstallProduct2
{
public enum INSTALLUILEVEL
{
    INSTALLUILEVEL_NOCHANGE = 0, // UI level is unchanged
    INSTALLUILEVEL_DEFAULT = 1, // default UI is used
    INSTALLUILEVEL_NONE = 2, // completely silent installation
    INSTALLUILEVEL_BASIC = 3, // simple progress and error handling
    INSTALLUILEVEL_REDUCED = 4, // authored UI, wizard dialogs suppressed
    INSTALLUILEVEL_FULL = 5, // authored UI with wizards, progress, errors
    INSTALLUILEVEL_ENDDIALOG = 0x80, // display success/failure dialog at end of install
    INSTALLUILEVEL_PROGRESSONLY = 0x40, // display only progress dialog
    INSTALLUILEVEL_HIDECANCEL = 0x20, // do not display the cancel button in basic UI
    INSTALLUILEVEL_SOURCERESONLY = 0x100, // force display of source resolution even if quiet
}
    [DllImport("msi.dll")]
            public static extern int MsiConfigureProduct(
            string szProduct, // product code
            int iInstallLevel, // install level
            int eInstallState // install state
            );
            [DllImport("msi.dll", SetLastError = true)]
    public static extern int MsiSetInternalUI(INSTALLUILEVEL dwUILevel, ref int phWnd);
            public static int LogandUninstallProduct(string szProductCode)
    {
                        int phWnd=0;
                        MsiSetInternalUI(INSTALLUILEVEL.INSTALLUILEVEL_NONE, ref phWnd);
            int iErrorCode = MsiConfigureProduct(szProductCode, 1, 2);
            return (iErrorCode);
    }
}
"@

                
    if ([UninstallProduct2] -eq $null) {
        #Code here will never be run; the if statement with throw an exception because the type doesn't exist
    } else {
        #type is loaded, do nothing
    }

    Trap [Exception] {
        #type doesn't exist. Load it
        $type = Add-Type -TypeDefinition $ProductUninstall -PassThru
        continue
    }
}

function CreateRegistryFileRecoveryFile {
    Param($ProductCode)
    $root=$Env:SystemDrive
    $DirectoryPath= $root+"\MATS\$ProductCode"
    if (!(Test-Path -path $DirectoryPath)) { 
        [void]( New-Item  "$DirectoryPath" -type directory -force -ErrorAction SilentlyContinue) #Create the backup folder
    }

$WriteFile=
@"
Function RestoreFilesFromXML
{
[void](New-PSDrive -Name HKCR -PSProvider registry -root HKEY_CLASSES_ROOT) # Allows access to registry HKEY_CLASSES_ROOT
if ((Test-Path -path "$DirectoryPath\\FileBackupTemplate.xml"))
{
    `$xml =[xml] (get-content "$DirectoryPath\\FileBackupTemplate.xml")
    `$root =`$xml.FileBackup.File
    foreach (`$XMLItems in `$xml.FileBackup.File)
    {
            if (!(Test-Path `$XMLItems.FileBackupLocation))
            {
                if (!(Test-Path -path `$XMLItems.FileBackupLocation.substring(0,(`$XMLItems.FileBackupLocation.LastIndexOf("\")))))
                    {
                            [void](New-Item `$XMLItems.FileBackupLocation.substring(0,(`$XMLItems.FileBackupLocation.LastIndexOf("\"))) -type directory) #Create the backup directories ready to copy the files into then
                    }
                Copy-Item `$XMLItems.FileDestination -Destination (`$XMLItems.FileBackupLocation) -ErrorAction SilentlyContinue #copy files to the backup folder
            }
    }
}
}
 
Function RestoreRegistryFiles
{
[void](New-PSDrive -Name HKCR -PSProvider registry -root HKEY_CLASSES_ROOT) # Allows access to registry HKEY_CLASSES_ROOT
if ((Test-Path -path "$DirectoryPath\registryBackupTemplate.xml"))
{
    `$xml =[xml] (get-content "$DirectoryPath\registryBackupTemplate.xml")
    `$root =`$xml.RegistryBackup.Registry
    foreach (`$XMLItems in `$xml.RegistryBackup.Registry)
    {
            if (!(CheckRegistryValueExists -RegRoot `$XMLItems.RegistryHive -RegValue `$XMLItems.RegistryName))
            {
            if (`$XMLItems.RegistryType -eq "MultiString")
            {
            [string[]]`$MultiString=`$XMLItems.RegistryValue.Split(";")
            new-item -path `$XMLItems.RegistryHive -ErrorAction SilentlyContinue | Out-Null
            New-ItemProperty `$XMLItems.RegistryHive `$XMLItems.RegistryName -value `$MultiString -propertyType `$XMLItems.RegistryType
            }
            elseif(`$XMLItems.RegistryType -eq "Binary")
            {
            new-item -path `$XMLItems.RegistryHive
            [Byte[]]`$ByteArray = @()
            foreach(`$byte in `$XMLItems.RegistryValue.Split(",")){`$ByteArray += `$byte}
            New-ItemProperty `$XMLItems.RegistryHive `$XMLItems.RegistryName -value `$ByteArray -propertyType `$XMLItems.RegistryType
            }
            elseif(`$XMLItems.RegistryType -eq "Dword")
            {
            `$DwordValue =Hex2Dec `$XMLItems.RegistryValue
            new-item -path `$XMLItems.RegistryHive -ErrorAction SilentlyContinue | Out-Null
            New-ItemProperty `$XMLItems.RegistryHive `$XMLItems.RegistryName -value `$DwordValue -propertyType `$XMLItems.RegistryType
            }
            else
            {
            ##Main Registry key missing so create here
            new-item -path `$XMLItems.RegistryHive -ErrorAction SilentlyContinue | Out-Null
            New-ItemProperty `$XMLItems.RegistryHive `$XMLItems.RegistryName -value `$XMLItems.RegistryValue -propertyType `$XMLItems.RegistryType
            }
            }
        }
}
}
Function CheckRegistryValueExists
{
Param(`$RegRoot,`$RegValue)
Get-ItemProperty `$RegRoot `$RegValue -ErrorAction SilentlyContinue | Out-Null
`$?
}
function Hex2Dec
{
param(`$HEX)
ForEach (`$value in `$HEX)
{
[Convert]::ToInt32(`$value,16)
}
}
 
function Test-Administrator
{
`$user = [Security.Principal.WindowsIdentity]::GetCurrent()
(New-Object Security.Principal.WindowsPrincipal `$user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
`$UsersTemp= `$Env:temp
Copy-Item "$DirectoryPath\\RestoreYourFilesAndRegistry.ps1" -Destination (`$UsersTemp+"\RestoreYourFilesAndRegistry.ps1") -ErrorAction SilentlyContinue
If(Test-Administrator)
{
RestoreRegistryFiles
RestoreFilesFromXML
}
else
{
cls
`$ElevatedProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell"
`$ElevatedProcess.Arguments = (`$UsersTemp+"\RestoreYourFilesAndRegistry.ps1")
`$ElevatedProcess.Verb = "runas"
[System.Diagnostics.Process]::Start(`$ElevatedProcess)
exit
}
"@


$writefile | Out-File $DirectoryPath\RestoreYourFilesAndRegistry.ps1 -ErrorAction SilentlyContinue | Out-Null  

}
function Compressed {
$sourceDelete = @"
    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Text;
    using Microsoft.Win32;
public class CleanUpRegistry
{
    public static string ReverseString(string szGUID)
    {
            char[] arr = szGUID.ToCharArray();
            Array.Reverse(arr);
            return new string(arr);
    }
    public static string CompressGUID(string szStandardGUID)
    {
            return (ReverseString(szStandardGUID.Substring(1, 8)) +
                ReverseString(szStandardGUID.Substring(10, 4)) +
                ReverseString(szStandardGUID.Substring(15, 4)) +
                ReverseString(szStandardGUID.Substring(20, 2)) +
                ReverseString(szStandardGUID.Substring(22, 2)) +
                ReverseString(szStandardGUID.Substring(25, 2)) +
                ReverseString(szStandardGUID.Substring(27, 2)) +
                ReverseString(szStandardGUID.Substring(29, 2)) +
                ReverseString(szStandardGUID.Substring(31, 2)) +
                ReverseString(szStandardGUID.Substring(33, 2)) +
                ReverseString(szStandardGUID.Substring(35, 2)));
    }
}
"@

            
    if ([CleanUpRegistry] -eq $null) {
        #Code here will never be run; the if statement with throw an exception because the type doesn't exist
    } else {
        #type is loaded, do nothing
    }

    Trap [Exception] {
        #type doesn't exist. Load it
        $type=Add-Type -TypeDefinition $sourceDelete -PassThru
        continue
    }
}

function RegistryHiveReplace {
    Param(
        [string]$RegistryKey
    )

    $RegistryKey=$RegistryKey.toupper()
    $RegistryKey=$RegistryKey -Replace("HKEY_LOCAL_MACHINE","HKLM:") 
    $RegistryKey=$RegistryKey -Replace("HKEY_CLASSES_ROOT","HKCR:")      
    $RegistryKey=$RegistryKey -Replace("HKEY_CURRENT_USER","HKCU:")  
    $RegistryKey=$RegistryKey -Replace("HKEY_USERS","HKU:")  
    $RegistryKey
}

function GetRegistryType {
$GetRegistryTypeCode=
@"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public class Registry
{
    [DllImport("advapi32.dll", EntryPoint = "RegQueryValueExA")]
    public static extern int RegQueryValueEx(int hKey, string lpValueName, int lpReserved, out int lpType, StringBuilder lpData, ref int lpcbData);
    [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
    public static extern int RegOpenKeyEx(Microsoft.Win32.RegistryHive hKey, string subKey, int ulOptions, int samDesired, out int phkResult);
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern int RegCloseKey(int hKey);
enum RegistryRights
    {
        ReadKey = 131097,
        WriteKey = 131078
    }
static Microsoft.Win32.RegistryHive RegHive(string szRegHive)
    {
        Microsoft.Win32.RegistryHive rhRegHiveOut;
         
        switch (szRegHive)
        {
            case "HKLM":
                rhRegHiveOut = Microsoft.Win32.RegistryHive.LocalMachine;
                break;
            case "HKCR":
                rhRegHiveOut = Microsoft.Win32.RegistryHive.ClassesRoot ;
                break;
            case "HKCU":
                rhRegHiveOut = Microsoft.Win32.RegistryHive.CurrentUser;
                break;
            case "HKU":
                rhRegHiveOut = Microsoft.Win32.RegistryHive.Users;
                break;
            default:
                rhRegHiveOut = Microsoft.Win32.RegistryHive .LocalMachine;
                break;
        }
        return rhRegHiveOut;
    }
    static string RegValueType(int iRegType)
    {
        string szRegistryType = "REG_NONE";
        switch (iRegType)
        {
            case 0:
                szRegistryType = "REG_NONE";
                break;
            case 1:
                szRegistryType = "String";
                break;
            case 2:
                szRegistryType = "ExpandString";
                break;
            case 3:
                szRegistryType = "Binary";//Binary
                break;
            case 4:
                szRegistryType = "DWord";
                break;
            case 5:
                szRegistryType = "DWord"; //"REG_DWORD_BIG_ENDIAN";
                break;
            case 6:
                szRegistryType = "REG_LINK";
                break;
            case 7:
                szRegistryType = "MultiString";//multistring
                break;
        }
         
        return szRegistryType;
    }
public static string GetType(string szHive,string szRegRoot, string szRegValue,int iWow)
{
 
        //iwow 64bit =256 and 32 = 512
        int hKeyVal = 0, type = 0, hwndOpenKey=0;
        string szType = "NA", szValue="NA" ;
        int valueRet = RegOpenKeyEx(RegHive(szHive), @szRegRoot, 0, (int)RegistryRights.ReadKey | iWow, out hKeyVal);
        if (valueRet == 0)
        {
            int iBuffSize = 10,iError=0;
            bool blSize = false;
            while (!blSize)
            {
                StringBuilder sb = new StringBuilder(iBuffSize);
                hwndOpenKey = iBuffSize;
                try
                {
                    iError = RegQueryValueEx(hKeyVal, szRegValue, 0, out type, sb, ref hwndOpenKey);
                }
                catch { }
                if (iError == 234)
                {
                    iBuffSize = iBuffSize + 1;
                }
                else if (iError == 0)
                {
                    blSize = true;
                    szValue = sb.ToString();
                    szType = RegValueType(type);
                }
                else
                {
                    blSize = true;
                }
                sb.Remove(0, sb.Length);
            }
        }
        return szType;
        //RegCloseKey(hKeyVal);
}
}
"@

            
    if ([Registry] -eq $null)
    {
    #Code here will never be run; the if statement with throw an exception because the type doesn't exist
    } else {
    #type is loaded, do nothing
    }

    Trap [Exception]{
        #type doesn't exist. Load it
        $type = Add-Type -TypeDefinition $GetRegistryTypeCode -PassThru
        continue
    }
}

Function CheckRegistryValueExists {
    param(
        [string]$RegRoot,
        [string]$RegValue
    )
    Get-ItemProperty -path $RegRoot -name $RegValue -ErrorAction SilentlyContinue | Out-Null  
    
    if (!$?){
        #check64bit
        $RegRoot=$RegRoot.tolower()
        $RegValue=$RegValue.tolower()
        $Regroot=$Regroot.Replace("software\","software\wow6432node\")
        $Regroot=$Regroot.Replace("hkcr:\","hkcr:\wow6432node\")
        Get-ItemProperty $RegRoot $RegValue -ErrorAction SilentlyContinue | Out-Null  
    }
    $?
}

function ProductListing {
$ProductListing=
@"
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
public class MakeStringTest
    {
        [DllImport("msi.dll")]
        public static extern Int32 MsiEnumProducts(int iProductIndex, StringBuilder lpProductBuf);
        [DllImport("msi.dll")]
        public static extern Int32 MsiGetProductInfo(string szProduct, string szProperty, StringBuilder lpValueBuf, ref int pcchValueBuf);
        public static ArrayList AllProductCodes = new ArrayList();
        public static ArrayList HashArrary = new ArrayList();
        public static ArrayList loop()
        {
            AllProductCodes = MsiEnumProducts();
            HashArrary = MsiGetProductInfoExtended(AllProductCodes);
            return (HashArrary);
        }
        public static string GetMSIProductInformation(string szProductCode, string szInformationRequested)
        {
            string szReturnValue = "";
            StringBuilder sb = new StringBuilder(1024);
            int iret = 1024, iErrorReturn = 0;
            iErrorReturn = MsiGetProductInfo(szProductCode, szInformationRequested, sb, ref iret);
            if (iErrorReturn == 0)
            {
                szReturnValue = sb.ToString();
            }
            else
            {
                szReturnValue = "Unknown";
            }
            return szReturnValue;
        }
        public static ArrayList MsiGetProductInfoExtended(ArrayList alAllProductCodes)
        {
            ArrayList alProdList = new ArrayList();
            string szProductName = "";
            foreach (string szCheck in alAllProductCodes)
            {
                szProductName = GetMSIProductInformation(szCheck, "ProductName");
                if (szProductName == "")
                {
                    szProductName = "Name not available";
                }
                Hashtable Hash = new Hashtable();
                Hash.Add("Name", szProductName);
                Hash.Add("Value", szCheck);
                Hash.Add("Description", szCheck);
                Hash.Add("ExtensionPoint", "<Default/><Icon>@resource.dll,-104</Icon>");
                alProdList.Add(Hash);
            }
            return alProdList;
        }
        public static ArrayList MsiEnumProducts()
        {
            int iErrorReturn = 0, iProductIndex = 0;
            StringBuilder szProdCode = new StringBuilder(39);
            string szProductCode = "";
            ArrayList alProdCodes = new ArrayList();
            while (iErrorReturn != 259)
            {
                iErrorReturn = MsiEnumProducts(iProductIndex, szProdCode);
                if (iErrorReturn == 0)
                {
                    szProductCode = szProdCode.ToString();
                    alProdCodes.Add(szProductCode);
                }
 
                iProductIndex++;
            }
            return (alProdCodes);
        }
    }
"@

            
    if ([MakeString] -eq $null){
        #Code here will never be run; the if statement with throw an exception because the type doesn't exist
    } else {
        #type is loaded, do nothing
    }

    Trap [Exception] {
        #type doesn't exist. Load it
        $type = Add-Type -TypeDefinition $ProductListing -PassThru
        continue
    }
}

function BackUpRegistry {
    Param(
        $Item,
        $DeleteParent
    )

    switch ($Item.substring(0,2)) { 
        "-1"{
            $QueryAssignmenttype=[MakeString]::GetProductInfo($ProductCode,"AssignmentType") # cant seem to find where this is defined
            switch($QueryAssignmenttype){
                "0"     { $Item=$Item.Replace("21:","HKCU:")}
                "1"     { $Item=$Item.Replace("02:","HKLM:")}
                default{ $Item=$Item.Replace("02:","HKLM:")}
            }
        }    
        "00"{ $Item=$Item.Replace("00:","HKCR:")} 
        "01"{ $Item=$Item.Replace("01:","HKCU:")}
        "02"{ $Item=$Item.Replace("02:","HKLM:")} 
        "03"{ $Item=$Item.Replace("03:","HKU:")}
        "20"{ $Item=$Item.Replace("20:","HKCR:")} 
        "21"{ $Item=$Item.Replace("21:","HKCU:")}
        "22"{ $Item=$Item.Replace("22:","HKLM:")} 
        "23"{ $Item=$Item.Replace("23:","HKU:")}
    }      
            
    $RegRoot = $Item.substring(0,$Item.LastIndexOf("\")) #Get Registry root value
    $RegValue =$Item.substring($Item.LastIndexOf("\")+1,$Item.length-$Item.LastIndexof("\")-1) #Get registry value
    $Exista=$false
    $Existsa=CheckRegistryValueExists -RegRoot $Regroot -RegValue $RegValue #Does this key still exist in the registry? MSI thinks it does
    
    if (!$Existsa) { #Maybe a path on the reg info so search value for known good
        $InLoop=$true
        $iCounter=0
        $Slash =$Item.IndexOf("\",$iCounter)+1

        while($InLoop){
        $TempRoot=$Item.substring(0,$Slash)
        $TempValue=$Item.substring($Slash,($Item.length-$Slash))
            $Existsa=CheckRegistryValueExists  -RegRoot $TempRoot  -RegValue $TempValue
            if (!$Existsa){
                $iCounter=$Slash
                $Slash =$Item.IndexOf("\",$iCounter)+1
                If ($Slash -eq 0 -or $TempRoot -eq "") { #$Slash =-1 before DEBUG
                    $InLoop=$False
                }         
            } else {
        #FOUND IT return here
                $InLoop=$False
                $RegRoot=$TempRoot
                $RegValue=$TempValue
                $RegistryType=[Registry]::GetType($item.substring(0,($item.IndexOf(":"))),$RegRoot.substring($RegRoot.indexOf(":")+2),$RegValue,256)
            }
        }
    }

    if ($Existsa){ #If we have found the registry key lets continue
        $RegistryType="NA"
        $RegistryType= [Registry]::GetType($item.substring(0,($item.IndexOf(":"))),$RegRoot.substring($RegRoot.indexOf(":")+2),$RegValue,256)  #Wow32 to false for registry redirection
        
        If($RegistryType -ne "NA"){
            WriteXMLRegistry $RegistryType $RegRoot $RegValue $DeleteParent
        }

        $RegistryType="NA"
        $regroot=$regroot.tolower()
        $regroot=$regroot.replace("software\","software\wow6432node\")
        $regroot=$regroot.replace("HKCR:","HKCR:\WOW6432node\")
        $regroot=$regroot.replace("hkcr:","HKCR:\WOW6432node\")
        $RegistryType = [Registry]::GetType($item.substring(0,($item.IndexOf(":"))),$RegRoot.substring($RegRoot.indexOf(":")+2),$RegValue,256) #Wow32 to false for registry redirection
        If ($RegistryType -ne "NA") {
            WriteXMLRegistry $RegistryType $RegRoot $RegValue $DeleteParent
        }
    }
    $RegistryCountFailures
}

function WriteXMLRegistry
{
    param(
        $RegistryType,
        $RegRoot,
        $RegValue,
        $DeleteParent
    )

    $root=$Env:SystemDrive
    $DirectoryPath= $root+"\MATS\$ProductCode"
    if (!($RegContent=Get-ItemProperty $RegRoot $RegValue)){
        #If the registry key is missing or has issues fail out
        return $True
    }
        
    if ($Regcontent -eq ""){
        $Failure=$true
    }

    $OutcomeContent=""
    [string]$RegName=""
        
    switch ($RegistryType){
        "String" {
            $RegName=$RegContent.$RegValue
        }
            
        "MultiString" {
            foreach ($Value in $RegContent.$RegValue) {
                $RegName=$RegName+$value+";"
            }

            $RegName=$RegName.substring(0,$RegName.length-1)
        }
        
        "Binary" {
            foreach ($Value in $RegContent.$RegValue){
                $RegName=$RegName+$value+","
            }
            $RegName=$RegName.substring(0,$RegName.length-1)
        }
            
        default{
            Foreach ($Value in $RegContent.$RegValue){
                $Outcome= "{0:x}" -f $Value
                $OutcomeContent=$OutcomeContent+$Outcome
            }
            $RegContent.$RegValue=$OutcomeContent
            $RegName=$RegContent.$RegValue
        }
    }
        
    $xml = New-Object xml
    If (!(Test-Path -path $DirectoryPath\registryBackupTemplate.xml)) { 
        $root = $xml.CreateElement("RegistryBackup")
        [void]$xml.AppendChild($root)
        $Record = $xml.CreateElement("Registry")
        #$Record.PSBase.InnerText = $RegRoot
        [void]$root.AppendChild($Record)
        $RegistryHive= $xml.CreateElement("RegistryHive")
        $RegistryHive.PSBase.InnerText = ($RegRoot)
        [void]$Record.AppendChild($RegistryHive)
        $XMLDeleteParent = $xml.CreateElement("RegistryDeleteParent")
        $XMLDeleteParent.PSBase.InnerText = [string]$DeleteParent
        [void]$Record.AppendChild($XMLDeleteParent)
        $Name = $xml.CreateElement("RegistryType")
        $Name.PSBase.InnerText = $RegistryType
        [void]$Record.AppendChild($name)
        $Value = $xml.CreateElement("RegistryName")
        $Value.PSBase.InnerText = ($RegValue)
        [void]$Record.AppendChild($Value)
        $Date = $xml.CreateElement("RegistryValue")
        $Date.PSBase.InnerText = ($RegName)
        [void]$Record.AppendChild($date)
        if (!(Test-Path -path $DirectoryPath)) { 
            [void]( New-Item  "$DirectoryPath" -type directory -force )#Create the backup folder for the files we will delete
        }

        [void]$xml.Save("$DirectoryPath\registryBackupTemplate.xml")
    } else {
        $xml =[xml] (get-content "$DirectoryPath\registryBackupTemplate.xml")
        $root = $xml.RegistryBackup
        $Record = $xml.CreateElement("Registry")
        [void]$root.AppendChild($Record)
        $RegistryHive= $xml.CreateElement("RegistryHive")
        $RegistryHive.PSBase.InnerText = ($RegRoot)
        [void]$Record.AppendChild($RegistryHive)
        $XMLDeleteParent = $xml.CreateElement("RegistryDeleteParent")
        $XMLDeleteParent.PSBase.InnerText = [string]$DeleteParent
        [void]$Record.AppendChild($XMLDeleteParent)
        $Name = $xml.CreateElement("RegistryType")
        $Name.PSBase.InnerText = $RegistryType
        [void]$Record.AppendChild($name)
        $Value = $xml.CreateElement("RegistryName")
        $Value.PSBase.InnerText = ($RegValue)
        [void]$Record.AppendChild($Value)
        $Date = $xml.CreateElement("RegistryValue")
        $Date.PSBase.InnerText = ($RegName)
        [void]$Record.AppendChild($date)
        [void]$xml.Save("$DirectoryPath\registryBackupTemplate.xml")
    }       
}

function RapidProductRegistryRemove {
    Param(
        $ProductCode
    )
    CreateRegistryFileRecoveryFile($ProductCode)
    New-PSDrive -Name HKCR -PSProvider registry -root HKEY_CLASSES_ROOT # Allows access to registry HKEY_CLASSES_ROOT
    
    $CompressedGUID=[CleanupRegistry]::CompressGUID([string]$ProductCode)

    if ($CompressedGUID) {
        ##################HKLM
        $RPRHives=Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Userdata\ #IF need to delete component then add back recurse HKL,HKCR code here
        foreach($SubKey in $RPRHives) { 
            $RegistryKey=RegistryHiveReplace $SubKey.name
    
            if (Test-Path "$RegistryKey\Products\$CompressedGUID") {        
                New-ItemProperty "$RegistryKey\Products\$CompressedGUID" -name "RPRTAG" -value "Backup" -propertyType "String" -ErrorAction SilentlyContinue | Out-Null  
                BackupRegistry ("$RegistryKey\Products\$CompressedGUID"+"\RPRTAG") $False
                $RegistryBackup=dir "$RegistryKey\Products\$CompressedGUID" -recurse
                if ($RegistryBackup) {
                    foreach ($a in $RegistryBackup) {
                        foreach($Property in $a.Property) {
                            $a=RegistryHiveReplace $a
                            BackupRegistry ($a+"\"+$Property) $False
                        }
                    }
                }
            
                del "$RegistryKey\Products\$CompressedGUID" -recurse #Special delete for parent
            }
        }

        ############HKCR
        if (Test-Path "HKCR:\Installer\Products\$CompressedGUID" ) {
            $RegistryBackup=dir "HKCR:\Installer\Products\$CompressedGUID" -recurse
            if ($RegistryBackup) {
                    $ParentValues= Get-ItemProperty -Path "HKCR:\Installer\Products\$CompressedGUID" 
                    #[string]$rr=get-itemproperty $Parentvalues
                    [string]$temp=[string]$ParentValues
                    $temp=$temp.Replace("@{","")
                    $temp=$temp.Replace("}","")
                    foreach ($ParentItem in $temp.Split(";")){
                        #FN to backup the root elements
                        $BackupSplit= $ParentItem.substring(0,$ParentItem.indexof("=")).trim()
                        BackupRegistry("HKCR:\Installer\Products\$CompressedGUID\$BackupSplit") $False
                    }
        
                    foreach ($a in $RegistryBackup){
                        if ($a.Property -ne ""){
                            foreach($Property in $a.Property){
                                    $a=RegistryHiveReplace $a
                                    BackupRegistry($a+"\"+$Property) $False
                            }
                        } else {
                            New-ItemProperty (RegistryHiveReplace $a.name) -name "RPRTAG" -value "Backup" -propertyType "String" -ErrorAction SilentlyContinue | Out-Null 
                            BackupRegistry((RegistryHiveReplace $a.name) +"\RPRTAG") $False
                        }
                    }
                    del "HKCR:\Installer\Products\$CompressedGUID" -recurse #Special delete for parent
            }
        }

        ############HKCU
        if (Test-Path "HKCU:\Software\Microsoft\Installer\Products\$CompressedGUID" ){
            $RegistryBackup=dir "HKCU:\Software\Microsoft\Installer\Products\$CompressedGUID" -recurse
            if ($RegistryBackup){
                $ParentValues= Get-ItemProperty -Path "HKCU:\Software\Microsoft\Installer\Products\$CompressedGUID"
                
                [string]$SplitString=[string]$ParentValues
                $SplitString=$SplitString.Replace("@{","")
                $SplitString=$SplitString.Replace("}","")
                
                foreach ($ParentItem in $SplitString.Split(";")) {
                    #FN to backup the root elements
                    $BackupSplit= $ParentItem.substring(0,$ParentItem.indexof("=")).trim()
                    BackupRegistry("HKCU:\Software\Microsoft\Installer\Products\$CompressedGUID\$BackupSplit") $False
                }
    
                foreach ($a in $RegistryBackup){
                    if ($a.Property -ne ""){
                        foreach($Property in $a.Property){
                                $a=RegistryHiveReplace $a
                                BackupRegistry($a+"\"+$Property) $False
                        }
                    } else {
                        New-ItemProperty (RegistryHiveReplace $a.name) -name "RPRTAG" -value "Backup" -propertyType "String" -ErrorAction SilentlyContinue | Out-Null 
                        BackupRegistry((RegistryHiveReplace $a.name) +"\RPRTAG") $False
                    }
                }
                del "HKCU:\Software\Microsoft\Installer\Products\$CompressedGUID" -recurse #Special delete for parent
            }
        }
    }
}


#endregion

<#
.SYNOPSIS
    Forcefully removes Windows Installer (MSI) product registrations when normal
    uninstall or repair is not possible.
 
.DESCRIPTION
    This function scrubs Windows Installer registration for a given product when
    cached MSI/MSP files are missing and neither repair nor uninstall can complete.
 
    > WARNING: This is a destructive recovery tool. It does not remove files
    > from disk, only the MSI/MSP registration data in the registry and Windows
    > Installer metadata. Use it only when standard uninstall/repair paths fail.
 
    This function wraps and adapts routines originally authored by Microsoft as
    part of their Program Install and Uninstall Troubleshooter. Specifically,
    the underlying helper functions are extracted and adapted from:
        MicrosoftProgram_Install_and_Uninstall.meta.diagcab\MSIMATSFN.ps1
 
    Original Microsoft tool and details:
    https://support.microsoft.com/en-us/topic/fix-problems-that-block-programs-from-being-installed-or-removed-cca7d1b6-65a9-3d98-426b-e9f927e1eb4d
 
.PARAMETER Filter
    A scriptblock filter used to target one or more registered MSI installations
    from the registry. The scriptblock is evaluated against uninstall registry keys.
 
    Example values:
        { $_.DisplayName -like "*SQL*" }
        { $_.Publisher -eq "Microsoft Corporation" -and $_.DisplayName -like "*Agent*" }
        { $_.ProductCode -eq "{B9E5C55D-B7B3-4A36-BBFE-18543570333D}"}
 
.EXAMPLE
    PS> Remove-InstallerRegistration -Filter { $_.DisplayName -like "Azure Connected Machine Agent*" -and $_.DisplayVersion -eq "1.56.03167" }
 
    Removes registration data for all MSI products with DisplayName matching "Azure Connected Machine Agent*" and DisplayVersion equal to "1.56.03167"
 
.EXAMPLE
    PS> Remove-InstallerRegistration -Filter { $_.Publisher -eq "Microsoft Corporation" -and $_.DisplayName -like "*SQL*" }
 
    Removes registration for Microsoft SQL Server-related products.
 
.NOTES
    Author: Joey Eckelbarger
    Derived from Microsoft’s Program Install/Uninstall Troubleshooter files (MSIMATSFN.ps1).
 
    License Note:
        The embedded helper functions are Microsoft-authored and used under the
        terms of their published diagnostic tool conditions. They are included here
        solely to enable advanced recovery where cached MSI/MSP files cannot be sourced
        and attempts to repair the installation via MSI fail.
 
    Limitations:
        - Removes registry/metadata only. Program files may remain on disk.
        - Should be considered a last resort to enable reinstallation.
        - Always test in a lab environment prior to production use.
#>

function Remove-InstallerRegistration {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory=$true)]
        [ScriptBlock]$Filter
    )

    $ErrorActionPreference = "Continue"

    [array]$productToScrub = Get-InstallerRegistration -Filter $Filter 

    if($productToScrub.Count -eq 0){
        Write-Warning "No products matching filter: $Filter"
        return
    }

    $DateTimeRun="{0:yyyy.MM.dd.HH.mm.ss}" -f (get-date) #Run Date\Time

    foreach($product in $productToScrub){
        if ($PSCmdlet.ShouldProcess(
            "$($product.DisplayName)-$($Product.DisplayVersion)-$($product.ProductCode)", 
            "Scrub Installer Registration",
            "This will forcibly remove registry keys, cached installer metadata, and uninstall entries for ProductCode: $($product.ProductCode)"
        )) {
            MATSFingerPrint $product.ProductCode "Version_RS_RapidProductRemoval" "1.3" $DateTimeRun
            MATSFingerPrint $product.ProductCode "ProductName" $product.DisplayName $DateTimeRun
            $root=$Env:SystemDrive
            $DirectoryPath= $root+"\MATS\$($product.ProductCode)"
            
            "Scrubbing Product Code $($product.ProductCode)"
            
            $value=[UninstallProduct2]::LogandUninstallProduct($product.ProductCode) # try Uninstall with the Windows Installer -x switch first
            MATSFingerPrint $product.ProductCode "msiexec -x" $value $DateTimeRun

            RapidProductRegistryRemove $product.ProductCode
            MATSFingerPrint $product.ProductCode "RPR" $true $DateTimeRun
            Get-Item $($product.PSPath) | Remove-Item -Recurse -Force -Confirm:$false
        }
    }
}

<#
.SYNOPSIS
    Lists MSI-based registered installations from the Windows Installer registry.
 
.DESCRIPTION
    Queries the standard registry locations for MSI-based products and returns
    their registered properties along with the ProductCode (GUID).
 
    This is a helper function intended to let you explore what products are
    registered before using Remove-InstallerRegistration to scrub them.
 
.PARAMETER Filter
    A scriptblock filter used to target results. This filter is evaluated
    against each registry entry object.
 
    Example:
        { $_.DisplayName -like "*SQL*" }
        { $_.Publisher -eq "Microsoft Corporation" }
 
.EXAMPLE
    PS> Get-InstallerRegistration
 
    Returns all MSI-registered products.
 
.EXAMPLE
    PS> Get-InstallerRegistration -Filter { $_.DisplayName -like "Azure Connected Machine Agent*" }
 
    Lists all registered Azure Connected Machine Agent installs.
 
.OUTPUTS
    PSCustomObject with properties from the registry entry, plus:
    - ProductCode (GUID from registry key name)
 
.NOTES
    Author: Joey Eckelbarger
#>

function Get-InstallerRegistration {
    param(
        [ScriptBlock]$Filter
    )

    $registeredInstallPaths = if([Environment]::Is64BitOperatingSystem){
        'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
    } else {
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
    }

    [array]$allRegisteredProducts = Get-ChildItem $registeredInstallPaths |
        Where-Object { $_.PsPath -like "*{*-*-*-*}"}  | # fixes errors if host has unexpected (non-guid) uninstall entries in registry
        Foreach-Object { Get-ItemProperty $_.PSPath } |
        Select-Object *,@{Name='ProductCode';E={$_.PSChildName}}  # add ProductCode (which is parent reg folder name) as property to the objs

    if($Filter){
        $matchingFilter = $allRegisteredProducts | Where-Object -FilterScript $Filter
        if($matchingFilter.Count -eq 0){
            Write-Warning "No products matching filter: $Filter"
            return
        } else {
            return $matchingFilter
        }
    } else {
        return $allRegisteredProducts
    }
}