Public/Restore-CPSAssemblies.ps1

function Restore-CPSAssemblies {
    [CmdletBinding()]
    param (
        # The name of the remote computer where the assemblies will be replaced
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,    

        # Path to a text file containing the names of corrupt assemblies, one per line
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$CorruptAssembliesList,

        # Path to the directory containing clean copies of the assemblies
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$RepairSource,

        # Name of the local administrator account to be granted permissions
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$AdminAccount
    )

    begin {
        # Initialization code if needed
    }
    process {
        $ScriptBlock = {
            $CorruptAssembliesList = $using:CorruptAssembliesList
            $RepairSource = $using:RepairSource
            $AdminAccount = $using:AdminAccount

            # Validate input parameters
            if (-not (Test-Path $CorruptAssembliesList)) {
                throw "The file $CorruptAssembliesList does not exist."
            }
            if (-not (Test-Path $RepairSource)) {
                throw "The directory $RepairSource does not exist."
            }

            # Import the corrupt assembly names from a text file
            $CorruptAssemblies = Get-Content -Path $CorruptAssembliesList

            # Define the component store path
            $CPS = Join-Path -Path $env:SystemRoot -ChildPath "WinSxS\"

            # Ensure a trailing backslash for the repair source path
            $FormattedRepairSource = $RepairSource.TrimEnd('\') + "\"

            # Try repairing the assemblies
            try {
                # Take ownership of the component store parent folder
                takeown /f $CPS | Out-Null

                # Grant Full Control of the component store and inheriting objects to the admin account
                icacls $CPS /grant "${AdminAccount}:(OI)(CI)(F)" | Out-Null

                Write-Host
                Write-Host "Successfully took control of the component store."
                Write-Host

                # Delete each of the corrupt assemblies, place a clean copy in the component store, and restore permissions
                foreach ($Assembly in $CorruptAssemblies) {
                    # Define the corrupt assembly path
                    $AssemblyPath = Join-Path -Path $CPS -ChildPath $Assembly
                    
                    # Define the clean assembly copy path
                    $CleanCopyPath = Join-Path -Path $FormattedRepairSource -ChildPath $Assembly

                    try {
                        # Validate the clean assembly path
                        if (-not (Test-Path $CleanCopyPath)) {
                            Write-Warning "Clean assembly not found in repair source: $FormattedRepairSource"
                            Write-Warning "Skipping: $Assembly"
                            continue
                        }

                        if (Test-Path $AssemblyPath) {
                            Write-Host "Attempting to repair: $Assembly"
                            Write-Host "Taking control of the corrupt assembly."
                            
                            # Take ownership of the corrupt assembly
                            takeown /f $AssemblyPath /r /d y | Out-Null

                            # Assign full control of the corrupt assembly to the admin account
                            icacls $AssemblyPath /grant "${AdminAccount}:(OI)(CI)(F)" /t | Out-Null

                            Write-Host "Removing corrupt assembly."

                            # Delete the corrupt assembly
                            Remove-Item -Path $AssemblyPath -Recurse -Force | Out-Null

                            Write-Host "Successfully removed."
                        }
                        else {
                            Write-Warning "Assembly not found in component store: $AssemblyPath"
                            Write-Warning "Ignoring corrupt assembly removal. Moving on to replacement."
                        }

                        Write-Host "Attempting to replace with clean copy."

                        # Copy the clean copy of the assembly into the component store
                        Copy-Item -Path $CleanCopyPath -Destination $CPS -Recurse -Force | Out-Null

                        Write-Host "Setting permissions on the clean assembly."

                        # Disable permissions inheritance on the clean assembly
                        icacls $AssemblyPath /inheritance:d /t | Out-Null
                        
                        # Return ownership of the assembly to TrustedInstaller
                        icacls $AssemblyPath /setowner "NT SERVICE\TrustedInstaller" /t | Out-Null
                        
                        # Remove full control of the assembly from the admin account
                        icacls $AssemblyPath /remove:g $AdminAccount /t | Out-Null

                        Write-Host "Successfully replaced assembly: $Assembly"
                        Write-Host
                    }
                    catch {
                        Write-Error "Failed to process assembly ${Assembly}: $_"
                        Write-Host
                    }
                }
            }
            finally {
                Write-Host "Restoring permissions on the component store."

                # Ensure the component store permissions are returned to its original state
                icacls $CPS /setowner "NT SERVICE\TrustedInstaller" | Out-Null
                icacls $CPS /remove:g $AdminAccount | Out-Null

                Write-Host "Component store permissions restored to TrustedInstaller."
            }
        }

        Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock
    }
    end {
        Write-Host
    }
}