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
orcalone because unrelated Windows keys such asEhStorClasscan be caught. - If
pnputilreports that a reboot is needed, reboot during an approved window and re-run verification afterward. - Some Windows builds do not support
pnputil /remove-deviceand may not haveRemove-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
- 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
}
}
- 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
-
If the Oracle Windows VirtIO driver package has an uninstall string, run its silent uninstall command from the registry entry.
-
Delete only exact matching DriverStore packages. First map
Published NametoOriginal NameandProvider Namefrompnputil /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.
- 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
}
}
- 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.
- 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 vendorVEN_1AF4. - No
vio*.sys,balloon*.sys,netkvm*.sys,pvpanic*.sys, orqemufwcfg*.sysbinaries. - 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.