Files

12 KiB

Cleanup VirtIO/QEMU/Oracle/Red Hat Windows Drivers

Use this task note when a Windows VM needs VirtIO, QEMU guest agent, Oracle VirtIO, or Red Hat VirtIO remnants removed. Keep the scope to one explicitly named host.

Safety Rules

  • Confirm the hostname and IP before making changes.
  • Remove only VirtIO/QEMU/Oracle/Red Hat components.
  • Do not remove Microsoft, VMware, Intel, LSI, PVSCSI, or normal Windows storage/network drivers.
  • Use exact allowlists for service keys and driver package names.
  • Do not delete service keys by broad regex. In particular, never match on orc alone because unrelated Windows keys such as EhStorClass can be caught.
  • If pnputil reports that a reboot is needed, reboot during an approved window and re-run verification afterward.
  • Some Windows builds do not support pnputil /remove-device and may not have Remove-PnpDevice. For phantom device instances, use exact instance IDs and SetupAPI instead of broad registry deletion.

Discovery

Run from an elevated PowerShell session on the target VM:

hostname
Get-NetIPAddress -AddressFamily IPv4 | Select-Object IPAddress,InterfaceAlias

Write-Host "`n=== Installed products ==="
Get-Package '*virtio*','*VirtIO*','*QEMU*','*Oracle*','*Red Hat*' -ErrorAction SilentlyContinue |
  Format-Table Name, Version, ProviderName -AutoSize

Write-Host "`n=== Uninstall registry entries ==="
$uninstallRoots = @(
  'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
  'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
Get-ItemProperty $uninstallRoots -ErrorAction SilentlyContinue |
  Where-Object {
    $_.DisplayName -match 'VirtIO|virtio|QEMU|Oracle Windows VirtIO|Red Hat'
  } |
  Select-Object DisplayName, DisplayVersion, PSChildName, UninstallString, QuietUninstallString

Write-Host "`n=== Driver Store ==="
pnputil /enum-drivers |
  Select-String -Pattern 'viostor|vioscsi|balloon|netkvm|vioser|vioinput|viofs|Oracle|Red Hat|QEMU|VirtIO' -Context 6,0

Write-Host "`n=== Devices ==="
Get-PnpDevice -PresentOnly:$false |
  Where-Object {
    $_.FriendlyName -match 'VirtIO|QEMU|Oracle|Red Hat' -or
    $_.Manufacturer -match 'VirtIO|QEMU|Oracle|Red Hat'
  } |
  Format-Table Status, Class, FriendlyName, InstanceId -AutoSize

Write-Host "`n=== Problem or phantom target devices ==="
Get-PnpDevice -PresentOnly:$false |
  Where-Object {
    ($_.FriendlyName -match 'VirtIO|QEMU|Oracle|Red Hat' -or $_.InstanceId -match 'VEN_1AF4') -and
    ($_.Status -ne 'OK' -or $_.Class -eq 'Unknown' -or $_.Class -eq 'Other')
  } |
  Select-Object Status,Class,FriendlyName,InstanceId,Problem |
  Format-List

Write-Host "`n=== Network adapters ==="
Get-NetAdapter -IncludeHidden |
  Format-Table Name, InterfaceDescription, Status, MacAddress -AutoSize

Write-Host "`n=== Driver binaries ==="
Get-ChildItem C:\Windows\System32\drivers\vio*.sys,
              C:\Windows\System32\drivers\balloon*.sys,
              C:\Windows\System32\drivers\netkvm*.sys,
              C:\Windows\System32\drivers\pvpanic*.sys,
              C:\Windows\System32\drivers\qemufwcfg*.sys `
  -ErrorAction SilentlyContinue |
  Format-Table Name, Length, LastWriteTime -AutoSize

Cleanup Sequence

  1. Stop and remove only exact QEMU/Oracle/VirtIO service names if present.
$targetServices = @(
  'qemu-ga',
  'QEMU-GA',
  'Oracle BalloonService',
  'Oracle VirtIO Service',
  'vgpusrvorc'
)

foreach ($svc in $targetServices) {
  $service = Get-Service -Name $svc -ErrorAction SilentlyContinue
  if ($service) {
    Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
    sc.exe delete $svc
  }
}
  1. Uninstall matching MSI packages found during discovery. Use the product codes discovered on that VM, not stale codes from another machine.
msiexec.exe /x '{PRODUCT-CODE-FROM-DISCOVERY}' /qn /norestart
  1. If the Oracle Windows VirtIO driver package has an uninstall string, run its silent uninstall command from the registry entry.

  2. Delete only exact matching DriverStore packages. First map Published Name to Original Name and Provider Name from pnputil /enum-drivers, then delete only approved packages.

Approved original names/providers:

vioscsi.inf       Red Hat, Inc.
viostor.inf       Red Hat, Inc.
viostororc.inf    Oracle, Inc.
netkvmorc.inf     Oracle, Inc.
vioscsiorc.inf    Oracle, Inc.
vioserorc.inf     Oracle, Inc.
pvpanicorc.inf    Oracle, Inc.
qemufwcfgorc.inf  QEMU
viogpudorc.inf    Oracle, Inc.
balloonorc.inf    Oracle, Inc.

Delete each approved published name:

pnputil /delete-driver oem##.inf /uninstall /force

Do not delete VMware pvscsi.inf, Microsoft storage drivers, Intel NIC drivers, or any package whose original name/provider is not in the approved list.

  1. Remove only exact target service keys if they remain.
$targetServiceKeys = @(
  'viostor',
  'vioscsi',
  'viostororc',
  'vioscsiorc',
  'balloon',
  'balloonorc',
  'netkvm',
  'netkvmorc',
  'vioser',
  'vioserorc',
  'vioinput',
  'viofs',
  'pvpanic',
  'pvpanicorc',
  'qemufwcfg',
  'qemufwcfgorc',
  'qemu-ga',
  'QEMU-GA',
  'Oracle BalloonService',
  'Oracle VirtIO Service',
  'vgpusrvorc'
)

foreach ($keyName in $targetServiceKeys) {
  $key = "HKLM:\SYSTEM\CurrentControlSet\Services\$keyName"
  if (Test-Path $key) {
    Remove-Item $key -Recurse -Force
  }
}
  1. Remove leftover binaries and install folders that match the target stack.
Remove-Item C:\Windows\System32\drivers\vio*.sys,
            C:\Windows\System32\drivers\balloon*.sys,
            C:\Windows\System32\drivers\netkvm*.sys,
            C:\Windows\System32\drivers\pvpanic*.sys,
            C:\Windows\System32\drivers\qemufwcfg*.sys `
  -Force -ErrorAction SilentlyContinue

Remove-Item 'C:\Program Files\Virtio-Win',
            'C:\Program Files\QEMU-ga',
            'C:\Program Files\qemu-ga',
            'C:\Program Files (x86)\Oracle Corporation\Oracle Windows VirtIO Drivers' `
  -Recurse -Force -ErrorAction SilentlyContinue

Remove InstallShield cache directories only when discovery proves they belong to Oracle Windows VirtIO.

  1. Remove non-present phantom target devices only by exact instance ID.

If Device Manager still shows target devices under Other devices, first verify the exact InstanceId, FriendlyName, and hardware IDs. On older Windows builds, pnputil /remove-device may not exist and the Remove-PnpDevice cmdlet may not be available. In that case, use SetupAPI against exact target instance IDs.

Example for exact phantom Oracle VirtIO SCSI instances:

$source = @"
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

public static class DeviceRemover
{
    private const int DIGCF_ALLCLASSES = 0x00000004;
    private const int DIF_REMOVE = 0x00000005;
    private const int DI_REMOVEDEVICE_GLOBAL = 0x00000001;

    [StructLayout(LayoutKind.Sequential)]
    private struct SP_DEVINFO_DATA
    {
        public int cbSize;
        public Guid ClassGuid;
        public int DevInst;
        public IntPtr Reserved;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SP_CLASSINSTALL_HEADER
    {
        public int cbSize;
        public int InstallFunction;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SP_REMOVEDEVICE_PARAMS
    {
        public SP_CLASSINSTALL_HEADER ClassInstallHeader;
        public int Scope;
        public int HwProfile;
    }

    [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr SetupDiGetClassDevs(IntPtr ClassGuid, string Enumerator, IntPtr hwndParent, int Flags);

    [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool SetupDiOpenDeviceInfo(IntPtr DeviceInfoSet, string DeviceInstanceId, IntPtr hwndParent, int OpenFlags, ref SP_DEVINFO_DATA DeviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiSetClassInstallParams(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref SP_REMOVEDEVICE_PARAMS ClassInstallParams, int ClassInstallParamsSize);

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiCallClassInstaller(int InstallFunction, IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

    public static void RemoveExact(string instanceId)
    {
        IntPtr set = SetupDiGetClassDevs(IntPtr.Zero, null, IntPtr.Zero, DIGCF_ALLCLASSES);
        if (set == IntPtr.Zero || set.ToInt64() == -1)
            throw new Win32Exception(Marshal.GetLastWin32Error(), "SetupDiGetClassDevs failed");

        try
        {
            SP_DEVINFO_DATA data = new SP_DEVINFO_DATA();
            data.cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));

            if (!SetupDiOpenDeviceInfo(set, instanceId, IntPtr.Zero, 0, ref data))
                throw new Win32Exception(Marshal.GetLastWin32Error(), "SetupDiOpenDeviceInfo failed for " + instanceId);

            SP_REMOVEDEVICE_PARAMS remove = new SP_REMOVEDEVICE_PARAMS();
            remove.ClassInstallHeader.cbSize = Marshal.SizeOf(typeof(SP_CLASSINSTALL_HEADER));
            remove.ClassInstallHeader.InstallFunction = DIF_REMOVE;
            remove.Scope = DI_REMOVEDEVICE_GLOBAL;
            remove.HwProfile = 0;

            if (!SetupDiSetClassInstallParams(set, ref data, ref remove, Marshal.SizeOf(typeof(SP_REMOVEDEVICE_PARAMS))))
                throw new Win32Exception(Marshal.GetLastWin32Error(), "SetupDiSetClassInstallParams failed for " + instanceId);

            if (!SetupDiCallClassInstaller(DIF_REMOVE, set, ref data))
                throw new Win32Exception(Marshal.GetLastWin32Error(), "SetupDiCallClassInstaller DIF_REMOVE failed for " + instanceId);
        }
        finally
        {
            SetupDiDestroyDeviceInfoList(set);
        }
    }
}
"@

Add-Type -TypeDefinition $source

$targetInstanceIds = @(
  'PCI\VEN_1AF4&DEV_1048&SUBSYS_1100108E&REV_01\4&18E4EEC6&0&0000',
  'PCI\VEN_1AF4&DEV_1004&SUBSYS_0008108E&REV_00\3&13C0B0C5&0&10'
)

foreach ($id in $targetInstanceIds) {
  $dev = Get-PnpDevice -PresentOnly:$false -InstanceId $id -ErrorAction SilentlyContinue
  if ($dev) {
    [DeviceRemover]::RemoveExact($id)
  }
}

Final Verification

Re-run the discovery commands. A clean result should show:

  • No installed packages matching VirtIO/QEMU/Oracle/Red Hat.
  • No DriverStore matches for the target VirtIO/QEMU/Oracle/Red Hat packages.
  • No QEMU/Oracle/VirtIO services or exact service keys from the allowlist.
  • No problem or phantom devices matching VirtIO, QEMU, Oracle, Red Hat, or PCI vendor VEN_1AF4.
  • No vio*.sys, balloon*.sys, netkvm*.sys, pvpanic*.sys, or qemufwcfg*.sys binaries.
  • No C:\Program Files\Virtio-Win, QEMU guest agent folder, or Oracle Windows VirtIO folder.
  • The active network adapter should still be the expected non-VirtIO adapter.

Recovery Note

If an overly broad cleanup accidentally removes the Windows Enhanced Storage Class service key, restore EhStorClass:

$key = 'HKLM:\SYSTEM\CurrentControlSet\Services\EhStorClass'
New-Item -Path $key -Force | Out-Null
New-ItemProperty -Path $key -Name Type -PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $key -Name Start -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $key -Name ErrorControl -PropertyType DWord -Value 3 -Force | Out-Null
New-ItemProperty -Path $key -Name Group -PropertyType String -Value 'SCSI Class' -Force | Out-Null
New-ItemProperty -Path $key -Name ImagePath -PropertyType ExpandString -Value 'System32\drivers\EhStorClass.sys' -Force | Out-Null
New-ItemProperty -Path $key -Name DisplayName -PropertyType String -Value '@%SystemRoot%\System32\drivers\EhStorClass.sys,-100' -Force | Out-Null
New-ItemProperty -Path $key -Name Description -PropertyType String -Value '@%SystemRoot%\System32\drivers\EhStorClass.sys,-101' -Force | Out-Null
Test-Path C:\Windows\System32\drivers\EhStorClass.sys

The real fix is prevention: only exact allowlisted names should be removed.