Search

Remove Ghost Device Script

When migrating Windows OS from one hardware architecture to another (ESX to AHV, P2V, etc.), it is always a good practice to uninstall the previously used device drivers for hardware that is no longer present. This script will identify those and remove them.

Publication date:

September 22, 2021

Author:

Ed McAndrew

Intended Audience Level:

Intermediate

Code Sample Type:

Snippet

Nutanix Technology or Product:

General

Minimum Product Version:

ANY

Script/Code Language:

PowerShell

API Code Sample (yes/no):

No

REST API Version:

N/A

Version Release Status:

Not Specified

REST API or SDK:

Not Specified

Code Sample Details

<#
.notes
##############################################################################
#	 	 Remove Ghost Device Script
#	 	 Filename			:	  NTNX_RemoveGhostDevices.ps1
#	 	 Script Version		:	  1.2.14
##############################################################################
.prerequisites
	1. Powershell 5 or above ($psversiontable.psversion.major)
.synopsis
    Find ghost devices and uninstall them.
.usage
	PS C:\> NTNX_RemoveGhostDevices.ps1 -runaction List [ Dry Run: Get list, but do not remove anything ]
	PS C:\> NTNX_RemoveGhostDevices.ps1 -runaction Commit -confirm $true [ Commit to removing devices ]
.disclaimer
	This code is intended as a standalone example. Subject to licensing restrictions defined on nutanix.dev, this can be downloaded, copied and/or modified in any way you see fit.
	Please be aware that all public code samples provided by Nutanix are unofficial in nature, are provided as examples only, are unsupported and will need to be heavily scrutinized and potentially modified before they can be used in a production environment. All such code samples are provided on an as-is basis, and Nutanix expressly disclaims all warranties, express or implied.
	All code samples are © Nutanix, Inc., and are provided as-is under the MIT license. (https://opensource.org/licenses/MIT)
#>
##############################################################################
#////////////////////////////////////////////////////////////////////////////////////////////////
# DO NOT CHANGE THIS!
#////////////////////////////////////////////////////////////////////////////////////////////////
##############################################################################
[CmdletBinding()]
Param (
	[Parameter(Mandatory=$true,HelpMessage="Enter desired script action [Commit/List]")]
	[ValidateSet('Commit','List')]
	[string]$RunAction = 'List',
	[bool]$Confirm = $false
)
##############################################################################
# Set Variables Below
##############################################################################
[string]$my_log_dir = 'c:\temp'
##############################################################################
#////////////////////////////////////////////////////////////////////////////////////////////////
# CHANGE NOTHING BELOW HERE!
#////////////////////////////////////////////////////////////////////////////////////////////////
##############################################################################
$my_list_ghost_devices_only = $false
$my_remove_ghost_devices = $false
switch ($RunAction) {
	"List" { $my_list_ghost_devices_only = $true }
	"Commit"  { $my_remove_ghost_devices = $true }
}
if ($confirm) { $my_confirmed = $true }
$my_scriptpath = $myinvocation.mycommand.path # grab the full path to the scripts execution directory/location.
[string]$my_rundate = (get-date -format "%M-%d-yyyy")
[string]$my_workingdir = split-path $my_scriptpath # split the execution full path from the filename to create a working directory variable.
[string]$my_scriptname = split-path $my_scriptpath -leaf -resolve # grab the script name.
$my_logfile = "$($my_log_dir)\$($my_scriptname.replace('.ps1',''))_$($my_rundate).log"
new-item -itemtype directory -force -path $my_log_dir | out-null
if (test-path $my_logfile) { remove-item $my_logfile -ea silentlycontinue }
try { stop-transcript | out-null } catch [system.invalidoperationexception] {}
start-transcript -path $my_logfile | out-null

function filter-device {
    param ( [system.object]$my_dev )
    $my_class = $my_dev.class
    $my_friendlyname = $my_dev.friendlyname
    $my_matchfilter = $false

    if (($my_matchfilter -eq $false) -and ($my_narrowbyfriendlyname -ne $null)) {
        $my_shouldinclude = $false
        foreach ($my_friendlynamefilter in $my_narrowbyfriendlyname) {
            if ($my_friendlyname -like "*$($my_friendlynamefilter)*") {
                $my_shouldinclude = $true
                break
            }
        }
        $my_matchfilter = !$my_shouldinclude
    }
    return $my_matchfilter
}

function write-coloroutput($foregroundcolor) {
    $my_fc = $host.ui.rawui.foregroundcolor
    $host.ui.rawui.foregroundcolor = $foregroundcolor
    if ($args) { write-output $($args) }
    else { $input | write-output }
    $host.ui.rawui.foregroundcolor = $my_fc
}

function filter-devices {
    param ( [array]$my_devices )
    $my_filtereddevices = @()
    foreach ($my_dev in $my_devices) {
        $my_matchfilter = filter-device -my_dev $my_dev
        if ($my_matchfilter -eq $false) {
            $my_filtereddevices += @($my_dev)
        }
    }
    return $my_filtereddevices
}

function get-ghost-devices {
    param ( [array]$my_devices )
    return ($my_devices | where { $_.installstate -eq $false } | sort -property friendlyname)
}

$my_setup_api = @"
using System;
using System.Diagnostics;
using System.Text;
using System.Runtime.InteropServices;
namespace Win32
{
    public static class SetupApi
    {
         // 1st form using a ClassGUID only, with Enumerator = IntPtr.Zero
        [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetupDiGetClassDevs(
           ref Guid ClassGuid,
           IntPtr Enumerator,
           IntPtr hwndParent,
           int Flags
        );

        // 2nd form uses an Enumerator only, with ClassGUID = IntPtr.Zero
        [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetupDiGetClassDevs(
           IntPtr ClassGuid,
           string Enumerator,
           IntPtr hwndParent,
           int Flags
        );

        [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool SetupDiEnumDeviceInfo(
            IntPtr DeviceInfoSet,
            uint MemberIndex,
            ref SP_DEVINFO_DATA DeviceInfoData
        );

        [DllImport("setupapi.dll", SetLastError = true)]
        public static extern bool SetupDiDestroyDeviceInfoList(
            IntPtr DeviceInfoSet
        );
        [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool SetupDiGetDeviceRegistryProperty(
            IntPtr deviceInfoSet,
            ref SP_DEVINFO_DATA deviceInfoData,
            uint property,
            out UInt32 propertyRegDataType,
            byte[] propertyBuffer,
            uint propertyBufferSize,
            out UInt32 requiredSize
        );
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool SetupDiGetDeviceInstanceId(
            IntPtr DeviceInfoSet,
            ref SP_DEVINFO_DATA DeviceInfoData,
            StringBuilder DeviceInstanceId,
            int DeviceInstanceIdSize,
            out int RequiredSize
        );


        [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool SetupDiRemoveDevice(IntPtr DeviceInfoSet,ref SP_DEVINFO_DATA DeviceInfoData);
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct SP_DEVINFO_DATA
    {
       public uint cbSize;
       public Guid classGuid;
       public uint devInst;
       public IntPtr reserved;
    }
    [Flags]
    public enum DiGetClassFlags : uint
    {
        DIGCF_DEFAULT       = 0x00000001,  // only valid with DIGCF_DEVICEINTERFACE
        DIGCF_PRESENT       = 0x00000002,
        DIGCF_ALLCLASSES    = 0x00000004,
        DIGCF_PROFILE       = 0x00000008,
        DIGCF_DEVICEINTERFACE   = 0x00000010,
    }
    public enum SetupDiGetDeviceRegistryPropertyEnum : uint
    {
         SPDRP_DEVICEDESC          = 0x00000000, // DeviceDesc (R/W)
         SPDRP_HARDWAREID          = 0x00000001, // HardwareID (R/W)
         SPDRP_COMPATIBLEIDS           = 0x00000002, // CompatibleIDs (R/W)
         SPDRP_UNUSED0             = 0x00000003, // unused
         SPDRP_SERVICE             = 0x00000004, // Service (R/W)
         SPDRP_UNUSED1             = 0x00000005, // unused
         SPDRP_UNUSED2             = 0x00000006, // unused
         SPDRP_CLASS               = 0x00000007, // Class (R--tied to ClassGUID)
         SPDRP_CLASSGUID           = 0x00000008, // ClassGUID (R/W)
         SPDRP_DRIVER              = 0x00000009, // Driver (R/W)
         SPDRP_CONFIGFLAGS         = 0x0000000A, // ConfigFlags (R/W)
         SPDRP_MFG             = 0x0000000B, // Mfg (R/W)
         SPDRP_FRIENDLYNAME        = 0x0000000C, // FriendlyName (R/W)
         SPDRP_LOCATION_INFORMATION    = 0x0000000D, // LocationInformation (R/W)
         SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E, // PhysicalDeviceObjectName (R)
         SPDRP_CAPABILITIES        = 0x0000000F, // Capabilities (R)
         SPDRP_UI_NUMBER           = 0x00000010, // UiNumber (R)
         SPDRP_UPPERFILTERS        = 0x00000011, // UpperFilters (R/W)
         SPDRP_LOWERFILTERS        = 0x00000012, // LowerFilters (R/W)
         SPDRP_BUSTYPEGUID         = 0x00000013, // BusTypeGUID (R)
         SPDRP_LEGACYBUSTYPE           = 0x00000014, // LegacyBusType (R)
         SPDRP_BUSNUMBER           = 0x00000015, // BusNumber (R)
         SPDRP_ENUMERATOR_NAME         = 0x00000016, // Enumerator Name (R)
         SPDRP_SECURITY            = 0x00000017, // Security (R/W, binary form)
         SPDRP_SECURITY_SDS        = 0x00000018, // Security (W, SDS form)
         SPDRP_DEVTYPE             = 0x00000019, // Device Type (R/W)
         SPDRP_EXCLUSIVE           = 0x0000001A, // Device is exclusive-access (R/W)
         SPDRP_CHARACTERISTICS         = 0x0000001B, // Device Characteristics (R/W)
         SPDRP_ADDRESS             = 0x0000001C, // Device Address (R)
         SPDRP_UI_NUMBER_DESC_FORMAT       = 0X0000001D, // UiNumberDescFormat (R/W)
         SPDRP_DEVICE_POWER_DATA       = 0x0000001E, // Device Power Data (R)
         SPDRP_REMOVAL_POLICY          = 0x0000001F, // Removal Policy (R)
         SPDRP_REMOVAL_POLICY_HW_DEFAULT   = 0x00000020, // Hardware Removal Policy (R)
         SPDRP_REMOVAL_POLICY_OVERRIDE     = 0x00000021, // Removal Policy Override (RW)
         SPDRP_INSTALL_STATE           = 0x00000022, // Device Install State (R)
         SPDRP_LOCATION_PATHS          = 0x00000023, // Device Location Paths (R)
         SPDRP_BASE_CONTAINERID        = 0x00000024  // Base ContainerID (R)
    }
}
"@
Add-Type -TypeDefinition $my_setup_api

#Array for all removed devices report
$my_removeArray = @()
#Array for all devices report
$my_devicearray = @()

$setupClass = [Guid]::Empty
$my_devs = [Win32.SetupApi]::SetupDiGetClassDevs([ref]$setupClass, [IntPtr]::Zero, [IntPtr]::Zero, [Win32.DiGetClassFlags]::DIGCF_ALLCLASSES)

$my_dev_info = new-object Win32.SP_DEVINFO_DATA
$my_dev_info.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($my_dev_info)

$my_devCount = 0
if ($my_remove_ghost_devices -eq $true) {
	write-coloroutput white "Removing Ghosted Devices"
	write-coloroutput white "**********************"
}

while ([Win32.SetupApi]::SetupDiEnumDeviceInfo($my_devs, $my_devCount, [ref]$my_dev_info)) {
	$propType = 0
	[byte[]]$propBuffer = $null
	$propBufferSize = 0
	[Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_FRIENDLYNAME, [ref]$propType, $propBuffer, 0, [ref]$propBufferSize) | Out-null
	[byte[]]$propBuffer = new-object byte[] $propBufferSize

	#Get HardwareID
	$propTypeHWID = 0
	[byte[]]$propBufferHWID = $null
	$propBufferSizeHWID = 0
	[Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_HARDWAREID, [ref]$propTypeHWID, $propBufferHWID, 0, [ref]$propBufferSizeHWID) | Out-null
	[byte[]]$propBufferHWID = new-object byte[] $propBufferSizeHWID

	#Get DeviceDesc (this name will be used if no friendly name is found)
	$propTypeDD = 0
	[byte[]]$propBufferDD = $null
	$propBufferSizeDD = 0
	[Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_DEVICEDESC, [ref]$propTypeDD, $propBufferDD, 0, [ref]$propBufferSizeDD) | Out-null
	[byte[]]$propBufferDD = new-object byte[] $propBufferSizeDD

	#Get Install State
	$propTypeIS = 0
	[byte[]]$propBufferIS = $null
	$propBufferSizeIS = 0
	[Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_INSTALL_STATE, [ref]$propTypeIS, $propBufferIS, 0, [ref]$propBufferSizeIS) | Out-null
	[byte[]]$propBufferIS = new-object byte[] $propBufferSizeIS

	if(![Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_FRIENDLYNAME, [ref]$propType, $propBuffer, $propBufferSize, [ref]$propBufferSize)){
		[Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_DEVICEDESC, [ref]$propTypeDD, $propBufferDD, $propBufferSizeDD, [ref]$propBufferSizeDD)  | out-null
		$my_friendlyname = [System.Text.Encoding]::Unicode.GetString($propBufferDD)
		if ($my_friendlyname.Length -ge 1) {
			$my_friendlyname = $my_friendlyname.Substring(0,$my_friendlyname.Length-1)
		}
	} else {
		$my_friendlyname = [System.Text.Encoding]::Unicode.GetString($propBuffer)
		if ($my_friendlyname.Length -ge 1) {
			$my_friendlyname = $my_friendlyname.Substring(0,$my_friendlyname.Length-1)
		}
	}

	$my_installstate = [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_INSTALL_STATE, [ref]$propTypeIS, $propBufferIS, $propBufferSizeIS, [ref]$propBufferSizeIS)

	if(![Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($my_devs, [ref]$my_dev_info,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_HARDWAREID, [ref]$propTypeHWID, $propBufferHWID, $propBufferSizeHWID, [ref]$propBufferSizeHWID)){
		$my_hwid = ""
	} else {
		$my_hwid = [System.Text.Encoding]::Unicode.GetString($propBufferHWID)
		$my_hwid = $my_hwid.split([char]0x0000)[0].ToUpper()
	}

	$my_devObj = new-object system.object
	$my_devObj | add-member -type NoteProperty -name FriendlyName -value $my_friendlyname
	$my_devObj | add-member -type NoteProperty -name HWID -value $my_hwid
	$my_devObj | add-member -type NoteProperty -name InstallState -value $my_installstate
	if ($my_devicearray.count -le 0) { sleep 1 }
	$my_devicearray += @($my_devObj)

	if ($my_remove_ghost_devices -eq $true) {
		$my_matchFilter = filter-device -my_dev $my_devObj
		if ($my_installstate -eq $false) {
				write-coloroutput yellow "[$(get-date -format 'yyyy-MM-dd HH:mm:ss')] - Attempting to remove device:[$($my_friendlyname)]"
				if ($my_confirmed -eq $true) {
					$my_removeObj = new-object system.object
					$my_removeObj | add-member -type NoteProperty -name FriendlyName -value $my_friendlyname
					$my_removeObj | add-member -type NoteProperty -name HWID -value $my_hwid
					$my_removeObj | add-member -type NoteProperty -name InstallState -value $my_installstate
					if ([Win32.SetupApi]::SetupDiRemoveDevice($my_devs, [ref]$my_dev_info)) {
						$my_removeArray += @($my_removeObj)
						write-coloroutput green "  Removed device:[$($my_friendlyname)]"
					} else {
						write-coloroutput red "  Failed to remove device:[$($my_friendlyname)]"
					}
				} else {
					write-coloroutput red "  Skipping removal of device:[$($my_friendlyname)], please use ""confirm"" option."
				}
		}
	}
	$my_devCount++
}
if (($my_list_ghost_devices_only) -and (!($my_remove_ghost_devices))) {
	write-coloroutput white "Ghosted Devices"
	write-coloroutput white "**********************"
	$my_ghost_devices = get-ghost-devices -my_devices $my_devicearray
	$my_filteredghostdevices = filter-devices -my_devices $my_ghost_devices
	$my_filteredghostdevices  | sort -property friendlyname | format-table  -autosize
	write-coloroutput white "Total ghosted devices found: $($my_ghost_devices.count)"
}

if ($my_remove_ghost_devices -eq $true) {
	write-coloroutput white "`n"
	write-coloroutput white "**********************"
	write-coloroutput white "Removed Ghosted Devices:"
	write-coloroutput white "**********************"
	$my_removearray  | sort -property friendlyname | format-table  -autosize
	write-coloroutput white "Total removed ghosted devices: $($my_removeArray.count)"
}
write-coloroutput white "`nFinished!"
write-coloroutput cyan "Log file located at: $($my_logfile)`n"
try { stop-transcript | out-null } catch [system.invalidoperationexception] {}

License

©2025 Nutanix, Inc. All rights reserved. Nutanix, the Nutanix logo and all Nutanix product and service names mentioned herein are registered trademarks or trademarks of Nutanix, Inc. in the United States and other countries. All other brand names mentioned herein are for identification purposes only and may be the trademarks of their respective holder(s).

Our decision to link to or reference an external site should not be considered an endorsement of any content on such a site. Certain information contained in this post may relate to, or be based on, studies, publications, surveys and other data obtained from third-party sources and our own internal estimates and research. While we believe these third-party studies, publications, surveys and other data are reliable as of the date of this paper, they have not independently verified unless specifically stated, and we make no representation as to the adequacy, fairness, accuracy, or completeness of any information obtained from a third-party.

All code samples are unofficial, are unsupported and will require extensive modification before use in a production environment. This content may reflect an experiment in a test environment. Results, benefits, savings, or other outcomes described depend on a variety of factors including use case, individual requirements, and operating environments, and this publication should not be construed as a promise or obligation to deliver specific outcomes.

This content may reflect an experiment in a test environment. Results, benefits, savings, or other outcomes described depend on a variety of factors including use case, individual requirements, and operating environments, and this publication should not be construed as a promise or obligation to deliver specific outcomes.