RemoveGhostNetAdapter.psm1


function Remove-GhostNetAdapter {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [switch]$UseAdvancedReset,
        [string]$ExportLogPath
    )

    $logEntries = @()

    Write-Host "Cleaning up ghost (non-present) network adapters..." -ForegroundColor Cyan
    $env:devmgr_show_nonpresent_devices = 1

    # Get hidden (non-present) network adapters
    $hiddenAdapters = Get-PnpDevice -Class Net | Where-Object { $_.Status -eq "Unknown" }

    if ($hiddenAdapters.Count -eq 0) {
        Write-Host "No hidden adapters found." -ForegroundColor Green
    }
    else {
        foreach ($adapter in $hiddenAdapters) {
            $instanceId = $adapter.InstanceId
            $friendlyName = $adapter.FriendlyName
            Write-Host "Removing: $friendlyName" -ForegroundColor Yellow

            $logEntry = [PSCustomObject]@{
                Timestamp    = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
                Adapter      = $friendlyName
                InstanceId   = $instanceId
                RegistryKeys = @()
            }

            try {
                if ($PSCmdlet.ShouldProcess("Adapter $friendlyName", "Disable device")) {
                    Disable-PnpDevice -InstanceId $instanceId -Confirm:$false -ErrorAction SilentlyContinue
                }

                if ($PSCmdlet.ShouldProcess("Adapter $friendlyName", "Remove device")) {
                    $removalResult = & pnputil /remove-device "$instanceId"
                    if ($LASTEXITCODE -ne 0) {
                        Write-Host "Failed to remove $friendlyName via pnputil" -ForegroundColor Red
                    }
                    else {
                        Write-Host "Removed $friendlyName via pnputil" -ForegroundColor Green
                    }
                }
            }
            catch {
                Write-Host "Could not remove $friendlyName" -ForegroundColor Red
            }

            # Clean registry entries
            $connectionBase = "HKLM:\SYSTEM\ControlSet001\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}"
            $networkCardsBase = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards"

            $subKeys = Get-ChildItem -Path $connectionBase -ErrorAction SilentlyContinue | Where-Object { $_.PSIsContainer }

            foreach ($subKey in $subKeys) {
                $adapterGuid = $subKey.PSChildName
                $connectionKeyPath = "$connectionBase\$adapterGuid\Connection"

                if (Test-Path $connectionKeyPath) {
                    try {
                        $pnpId = (Get-ItemProperty -Path $connectionKeyPath -ErrorAction SilentlyContinue).PnPInstanceID
                        if ($pnpId -and $pnpId -eq $instanceId) {

                            if ($PSCmdlet.ShouldProcess("Registry key $connectionBase\$adapterGuid", "Remove")) {
                                $logEntry.RegistryKeys += "$connectionBase\$adapterGuid"
                                Remove-Item -LiteralPath "$connectionBase\$adapterGuid" -Recurse -Force -ErrorAction SilentlyContinue
                            }

                            # Delete corresponding NetworkCards key
                            $netCardKeys = Get-ChildItem $networkCardsBase -ErrorAction SilentlyContinue
                            foreach ($cardKey in $netCardKeys) {
                                $svcName = (Get-ItemProperty -Path $cardKey.PSPath -ErrorAction SilentlyContinue).ServiceName
                                if ($svcName -and $svcName -eq $adapterGuid) {
                                    if ($PSCmdlet.ShouldProcess("Registry key $cardKey.PSPath", "Remove")) {
                                        $logEntry.RegistryKeys += $cardKey.PSPath
                                        Remove-Item -LiteralPath $cardKey.PSPath -Recurse -Force -ErrorAction SilentlyContinue
                                    }
                                }
                            }
                        }
                    }
                    catch {
                        Write-Host "Error processing $connectionKeyPath" -ForegroundColor Red
                    }
                }
            }

            $logEntries += $logEntry
        }
    }

    # Export log if path is specified
    if ($ExportLogPath) {
        $exportDir = Split-Path -Parent $ExportLogPath
        if (-not (Test-Path $exportDir)) {
            Write-Host "Export path directory does not exist: $exportDir" -ForegroundColor Red
            return
        }
        try {
            $logEntries | ConvertTo-Json -Depth 4 | Out-File -FilePath $ExportLogPath -Encoding UTF8
            Write-Host "`nExported log to: $ExportLogPath" -ForegroundColor Green
        }
        catch {
            Write-Host "Failed to write log to $ExportLogPath" -ForegroundColor Red
        }
    }

    # Step 3: Reset Network Stack
    Write-Host "`nPerforming network reset..." -ForegroundColor Cyan

    if ($UseAdvancedReset) {
        $confirm = Read-Host "Advanced reset will generate trace logs in %LOCALAPPDATA%\Temp\NetTraces. Continue? (Y/N)"
        if ($confirm -match '^[Yy]$') {
            try {
                if ($PSCmdlet.ShouldProcess("Network stack", "Run advanced network reset")) {
                    netsh trace diagnose scenario=NetworkReset | Out-Null
                    Write-Host "Advanced network reset complete. Logs saved. Reboot recommended." -ForegroundColor Green
                }
            }
            catch {
                Write-Host "Failed to run advanced reset." -ForegroundColor Red
            }
        }
        else {
            Write-Host "Skipped advanced reset by user choice." -ForegroundColor Yellow
        }
    }
    else {
        try {
            if ($PSCmdlet.ShouldProcess("Network stack", "Run standard network reset")) {
                netsh int ip reset | Out-Null
                ipconfig /flushdns | Out-Null
                Write-Host "Standard network reset complete. Reboot recommended." -ForegroundColor Green
            }
        }
        catch {
            Write-Host "Failed to perform standard reset." -ForegroundColor Red
        }
    }
}

Export-ModuleMember -Function Remove-GhostNetAdapter