Files
cds-ai/task instructions/cleanup-virtio.md
T

318 lines
12 KiB
Markdown

# 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.