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