Private/Resolve-WUDuplicateProfiles.ps1

function Resolve-WUDuplicateProfiles {
    <#
    .SYNOPSIS
        Detects and resolves duplicate user profile registry entries that block Windows upgrades.
 
    .DESCRIPTION
        This function identifies orphaned SID entries in the ProfileList registry that point to
        the same user profile path, which causes Windows Setup to fail with error 0x8007001F
        (ERROR_GEN_FAILURE) and 0x000004E5 (ERROR_BAD_PROFILE).
         
        This commonly occurs after domain/workgroup changes where old SIDs remain registered
        while new SIDs are created for the same profile path.
 
    .PARAMETER LogPath
        Path to the log file for logging operations.
 
    .PARAMETER WhatIf
        Shows what would happen without making changes.
 
    .EXAMPLE
        Resolve-WUDuplicateProfiles -LogPath "C:\Windows\Temp\repair.log"
         
        Detects and removes orphaned profile SIDs that conflict with active accounts.
 
    .OUTPUTS
        PSCustomObject with detection and remediation results.
 
    .NOTES
        Name: Resolve-WUDuplicateProfiles
        Author: Anthony Balloi - CSOLVE
        Version: 1.0.0
         
        Common error codes resolved:
        - 0x8007001F: ERROR_GEN_FAILURE (device not functioning)
        - 0x000004E5: ERROR_BAD_PROFILE (user profile invalid)
         
        Requires Administrator privileges to modify HKLM registry.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string]$LogPath
    )

    $result = [PSCustomObject]@{
        DuplicatesDetected = 0
        DuplicateProfiles = @()
        OrphanedSIDsRemoved = 0
        RemediationSuccessful = $false
        Errors = @()
    }

    try {
        Write-WULog -Message "Checking for duplicate user profile registry entries..." -LogPath $LogPath

        # Query all Administrator profile SIDs (ending in -500)
        $profileListPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
        $adminProfiles = Get-ChildItem $profileListPath | Where-Object {
            $_.PSChildName -match '^S-1-5-21-.+-500$'
        }

        if ($adminProfiles.Count -eq 0) {
            Write-WULog -Message "No Administrator profile entries found in registry" -LogPath $LogPath
            return $result
        }

        Write-WULog -Message "Found $($adminProfiles.Count) Administrator profile SID(s) in registry" -LogPath $LogPath

        # Group by ProfileImagePath to find duplicates
        $profilePaths = @{}
        foreach ($profile in $adminProfiles) {
            $sid = $profile.PSChildName
            $imagePath = (Get-ItemProperty $profile.PSPath -Name ProfileImagePath -ErrorAction SilentlyContinue).ProfileImagePath
            
            if ($imagePath) {
                # Normalize path for comparison (case-insensitive)
                $normalizedPath = $imagePath.ToLower()
                
                if ($profilePaths.ContainsKey($normalizedPath)) {
                    $profilePaths[$normalizedPath] += @($sid)
                } else {
                    $profilePaths[$normalizedPath] = @($sid)
                }
                
                Write-WULog -Message " SID: $sid -> Path: $imagePath" -LogPath $LogPath
            }
        }

        # Identify duplicates
        $duplicates = $profilePaths.GetEnumerator() | Where-Object { $_.Value.Count -gt 1 }
        
        if ($duplicates.Count -eq 0) {
            Write-WULog -Message "No duplicate profile paths detected" -LogPath $LogPath
            $result.RemediationSuccessful = $true
            return $result
        }

        $result.DuplicatesDetected = $duplicates.Count
        Write-WULog -Message "DETECTED: $($duplicates.Count) duplicate profile path(s)" -Level Warning -LogPath $LogPath

        # Get active local users to identify which SID is valid
        $localUsers = Get-LocalUser -ErrorAction SilentlyContinue

        foreach ($duplicate in $duplicates) {
            $profilePath = $duplicate.Key
            $sids = $duplicate.Value
            
            Write-WULog -Message "Duplicate profile path: $profilePath" -Level Warning -LogPath $LogPath
            Write-WULog -Message " Multiple SIDs detected: $($sids -join ', ')" -LogPath $LogPath

            $duplicateInfo = [PSCustomObject]@{
                ProfilePath = $profilePath
                SIDs = $sids
                ActiveSID = $null
                OrphanedSIDs = @()
            }

            # Identify the active SID by matching against local users
            $activeSID = $null
            foreach ($sid in $sids) {
                $matchingUser = $localUsers | Where-Object { $_.SID.Value -eq $sid }
                if ($matchingUser) {
                    $activeSID = $sid
                    $duplicateInfo.ActiveSID = $sid
                    Write-WULog -Message " Active SID (matches local user '$($matchingUser.Name)'): $sid" -LogPath $LogPath
                    break
                }
            }

            # If no active SID found, use the most recent (last in array) as a heuristic
            if (-not $activeSID) {
                $activeSID = $sids[-1]
                $duplicateInfo.ActiveSID = $activeSID
                Write-WULog -Message " No matching local user - assuming newest SID is active: $activeSID" -Level Warning -LogPath $LogPath
            }

            # Mark other SIDs as orphaned
            $orphanedSIDs = $sids | Where-Object { $_ -ne $activeSID }
            $duplicateInfo.OrphanedSIDs = $orphanedSIDs
            $result.DuplicateProfiles += $duplicateInfo

            # Remove orphaned SIDs
            foreach ($orphanedSID in $orphanedSIDs) {
                $orphanedKeyPath = Join-Path $profileListPath $orphanedSID
                
                Write-WULog -Message " Removing orphaned SID registry key: $orphanedSID" -LogPath $LogPath
                
                if ($PSCmdlet.ShouldProcess($orphanedKeyPath, "Remove orphaned profile registry key")) {
                    try {
                        Remove-Item $orphanedKeyPath -Recurse -Force -ErrorAction Stop
                        $result.OrphanedSIDsRemoved++
                        Write-WULog -Message " Successfully removed orphaned SID: $orphanedSID" -LogPath $LogPath
                    }
                    catch {
                        $errorMsg = "Failed to remove orphaned SID $orphanedSID : $($_.Exception.Message)"
                        Write-WULog -Message " $errorMsg" -Level Error -LogPath $LogPath
                        $result.Errors += $errorMsg
                    }
                }
            }
        }

        # Verify cleanup
        if ($result.OrphanedSIDsRemoved -gt 0) {
            Write-WULog -Message "Successfully removed $($result.OrphanedSIDsRemoved) orphaned profile SID(s)" -LogPath $LogPath
            $result.RemediationSuccessful = $true
            
            # Verify duplicates are gone
            $adminProfilesAfter = Get-ChildItem $profileListPath | Where-Object {
                $_.PSChildName -match '^S-1-5-21-.+-500$'
            }
            Write-WULog -Message "Verification: $($adminProfilesAfter.Count) Administrator profile SID(s) remain" -LogPath $LogPath
        }
        elseif ($result.Errors.Count -eq 0) {
            $result.RemediationSuccessful = $true
        }
    }
    catch {
        $errorMsg = "Failed to resolve duplicate profiles: $($_.Exception.Message)"
        Write-WULog -Message $errorMsg -Level Error -LogPath $LogPath
        $result.Errors += $errorMsg
    }

    return $result
}