docs: add virtio cleanup instructions
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
# 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:
|
||||
|
||||
```powershell
|
||||
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.
|
||||
|
||||
```powershell
|
||||
$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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Uninstall matching MSI packages found during discovery. Use the product codes discovered on that VM, not stale codes from another machine.
|
||||
|
||||
```powershell
|
||||
msiexec.exe /x '{PRODUCT-CODE-FROM-DISCOVERY}' /qn /norestart
|
||||
```
|
||||
|
||||
3. If the Oracle Windows VirtIO driver package has an uninstall string, run its silent uninstall command from the registry entry.
|
||||
|
||||
4. 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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```powershell
|
||||
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.
|
||||
|
||||
5. Remove only exact target service keys if they remain.
|
||||
|
||||
```powershell
|
||||
$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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Remove leftover binaries and install folders that match the target stack.
|
||||
|
||||
```powershell
|
||||
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.
|
||||
|
||||
7. 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:
|
||||
|
||||
```powershell
|
||||
$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`:
|
||||
|
||||
```powershell
|
||||
$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.
|
||||
Reference in New Issue
Block a user