3

I'm trying to automate via PowerShell a number of time consuming tasks that I have to preform to make a new VM template, one of which is removing all of the NICs from the VM and cleaning up the Device Manager of non present devices.

After removing the NICs from the VM, I've tried using the following code snippets, which do the same thing, to clean up Device Manager.

wmic nic where "(servicename is null)" delete

 

gwmi win32_networkadapter | ?{$_.ServiceName -eq $null} | rwmi

In both cases I receive the error "Provider is not capable of the attempted operation". Reviewing the event logs for WMI-Activity didn't seem to help: ResultCode = 0x80041024; PossibleCause = Unknown.

Has anyone be able to do something similar that removes the non present devices or is able to find an issue with the above commands?

EDIT: I've tried using DevCon to remove the device, but it seems to only work with present devices. I'm now combing through the registry to see if there is a specific key or set of keys that if removed would remove the NIC from Device Manager.

Hive
  • 193
  • 1
  • 4
  • 17
  • 1
    I don't believe powershell supports this functionality yet, you can only disable the device, not install/uninstall device drivers. There is always good old command line tools that you can call from powershell though: https://support.microsoft.com/en-us/kb/311272 – Cole9350 Jul 02 '15 at 19:38
  • Thanks for the KB. I was hoping to be able to use built-in command line rather than another tool, but I guess I'll have to give a try with DevCon. – Hive Jul 02 '15 at 20:24
  • Tried using DevCon and was getting errors (remove failed), read up on some forums and saw that DevCon only works on present devices. – Hive Jul 02 '15 at 21:38

4 Answers4

9

This one bugged me for a while, and I came up with a bit more of manual approach, but it works, so hopefully this will help others:

1) first make sure your list off devices you're about to purge are correct:

Get-PnpDevice -class net | ? Status -eq Unknown | Select FriendlyName,InstanceId

2) If you're happy with the list of Get-NetAdapters you're about to Kaibosh, run the following:

$Devs = Get-PnpDevice -class net | ? Status -eq Unknown | Select FriendlyName,InstanceId

ForEach ($Dev in $Devs) {
    Write-Host "Removing $($Dev.FriendlyName)" -ForegroundColor Cyan
    $RemoveKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\$($Dev.InstanceId)"
    Get-Item $RemoveKey | Select-Object -ExpandProperty Property | %{ Remove-ItemProperty -Path $RemoveKey -Name $_ -Verbose }
}
Write-Host "Done.  Please restart!" -ForegroundColor Green

NOTE: Your list of adapters at step 1 must ONLY contain adapters you want to purge. If you have extras in there, adjust the filter (? Status -eq XXX, eg: ? FriendlyName -like "Broadcom*") accordingly!

tormentum
  • 91
  • 1
  • 2
  • Doesn't removing a property "HKLM:\SYSTEM\CurrentControlSet\Enum" need NT AUTHORITY\SYSTEM privileges? – slayerbot Aug 25 '21 at 11:42
  • @tormentum: The script seems to work perfectly, but I had some issues if there is exactly one net device with Status being Unknown. In this case it seems, that the ForEach loop behaves somehow differently. Do you know, why? – Aleph0 Sep 15 '21 at 06:13
  • 1
    @Aleph0: To force a Powershell pipeline to be an array, even with only one element, surround the pipeline with @( ... ): `$Devs = @(Get-PnpDevice -class net | ? Status -eq Unknown | Select FriendlyName,InstanceId)` – Katie Stafford Dec 19 '21 at 20:05
  • @KatieStafford: Many thanks for the remark, I'll try it as soon as possible. – Aleph0 Dec 19 '21 at 20:23
4

I managed to solve a similar problem thanks to this page. I improved that script a bit and published it through my repo here: removeGhosts.ps1

With this version removing all hidden network devices can be done like this:

$ removeGhosts.ps1 -narrowbyclass Net

This will ask for confirmation for each device before the removal, the -Force option can suppress this behaviour. Without any option the script will remove all hidden devices which sounds like something the questioner also interested in.

  • I skimmed this script and tried it out. It appears to work great, though it has a lot of output that can't be suppressed. I'd have to knock that all out before I could use it in my script and the main code appears to be some form of C. – Roman Nov 06 '20 at 16:41
  • Great idea, I should add some verbosity levels. – István Siroki Nov 07 '20 at 17:17
-1

This registry key contains all of the hardware settings of the machine within the registry:
HKEY_LOCAL_MACHINE\system\currentcontrolset\enum

First query the present and enabled Network Adapters through WMI and get their PNPDeviceId. This value will tell you which subkey the Network Adapters are located in.

Next query the registry for each subkey and find all of the Adapters. Parse the full registry key to cut down to the same length as the PNPDeviceId values; roughly PCI\VEN_80AD&DEV_15A2&SUBSYS_062D1028&REV_02\2&11483669&0&C9.

Compare the two lists and find any orphaned registry keys. Once found, deleting the registry keys by enumerating the system account will remove the Network Adapter from Device Manager. I used PSExec.exe to run the reg delete command as the system account.

Here is some code to perform what I just explained.

# Declare variables
[string]$regIds = "";
[string]$Orphans = "";
[array]$SubKeys = @();
[array]$RegKeys = @();

# Query the present and enabled Network Adapters for the PNPDeviceId value
[array]$PNPDeviceIds = (gwmi Win32_NetworkAdapter -Filter "NetEnabled = true").PNPDeviceId;
for ($i = 0; $i -lt $PNPDeviceIds.Count; $i++){
    if ($SubKeys -NotContains $($PNPDeviceIds[$i].Split('\')[0] + "\" + $PNPDeviceIds[$i].Split('\')[1])){
        $SubKeys += $($PNPDeviceIds[$i].Split('\')[0] + "\" + $PNPDeviceIds[$i].Split('\')[1]);
}}

# Query the registry for all of the adapters
foreach ($SubKey in $SubKeys){
    [array]$Keys = reg query "hklm\system\currentcontrolset\enum\$SubKey"
    $Keys = $Keys[1..$($Keys.Count -1)];
    $RegKeys += $Keys;
}
# Parse the Keys
for ($i = 0; $i -lt $RegKeys.Count; $i++){ $regIds += "," + $($RegKeys[$i].Split('\')[4..6] -join '\'); }
$regIds = $regIds.TrimStart(",");

# Compare the registry to the present devices
for ($i = 0; $i -lt $regIds.Split(',').Count; $i++){
    if ($PNPDeviceIds -NotContains $regIds.Split(',')[$i]){
        $Orphans += "," + $regIds.Split(',')[$i];
}}
if ($Orphans.Length -gt 0){ $Orphans = $Orphans.TrimStart(","); }

# Delete the non-present devices
foreach ($Orphan in $Orphans)
{
    psexec.exe -s powershell.exe "reg delete 'hklm\system\currentcontrolset\enum\$Orphan'"
}
Hive
  • 193
  • 1
  • 4
  • 17
  • I get an error: psexec.exe : The term 'psexec.exe' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. – Jules Bartow Sep 11 '16 at 19:37
  • 1
    You need to have psexec.exe, from the [PSTools download](https://download.sysinternals.com/files/PSTools.zip), in the same folder as where you are executing the script from. Or simply drop it in the C:\Windows\System32 folder. – Hive Sep 16 '16 at 15:38
  • This worked for me, thanks! Ran powershell as admin, and then: ``` Get-NetAdapter | Select-Object Name,InterfaceDescription,PnPDeviceID,Status | Format-Table; psexec.exe -s powershell.exe "reg delete 'hklm\system\currentcontrolset\enum\'"; ``` – xorcus Aug 31 '23 at 12:43
-2

Some changes in previos script.

# Declare variables
[string]$regIds = "";
[string]$Orphan = "";
[array]$Orphans = @();
[array]$SubKeys = @();
[array]$RegKeys = @();

# Query the present and enabled Network Adapters for the PNPDeviceId value
[array]$PNPDeviceIds = (gwmi Win32_NetworkAdapter -Filter "NetEnabled =     true").PNPDeviceId;
for ($i = 0; $i -lt $PNPDeviceIds.Count; $i++) {
    if ($SubKeys -NotContains $($PNPDeviceIds[$i].Split('\')[0] + "\" + $PNPDeviceIds[$i].Split('\')[1])) {
        $SubKeys += $($PNPDeviceIds[$i].Split('\')[0] + "\" + $PNPDeviceIds[$i].Split('\')[1])
    }
}

# Query the registry for all of the adapters
foreach ($SubKey in $SubKeys) {
    [array]$Keys = reg query "hklm\system\currentcontrolset\enum\$SubKey"
    $Keys = $Keys[1..$($Keys.Count -1)];
    $RegKeys += $Keys
}
# Parse the Keys
for ($i = 0; $i -lt $RegKeys.Count; $i++) {
    $regIds += "," + $($RegKeys[$i].Split('\')[4..6] -join '\');
}
$regIds = $regIds.TrimStart(",")

# Compare the registry to the present devices
for ($i = 0; $i -lt $regIds.Split(',').Count; $i++) {
    if ($PNPDeviceIds -NotContains $regIds.Split(',')[$i]) {
        $Orphan += "," + $regIds.Split(',')[$i]
    }
}

if ($Orphan.Length -gt 0) {

    $Orphan = $Orphan.TrimStart(",")
    # Debug: Write-Host "Orphan.Lenght = "$Orphan.Length

    # Parse into Objects
    for ($i = 0; $i -lt $Orphan.Split(',').Count; $i++) {
        $Orphans += $Orphan.Split(',')[$i]
    }
    # Debug: Write-Host "Orphans = $Orphans    Orphans.Lenght = "$Orphans.Length

    $Orphan = ""

    # Delete the non-present devices
    foreach ($Orphan in $Orphans)
    {
        $Orphan = "HKLM:\System\CurrentControlSet\Enum\" + $Orphan
        Write-Host "You must have Full Rights and You should be Owner" -    ForegroundColor Black -BackgroundColor White
        Write-Host "Deleting KEY: " -NoNewline
        Write-Host $Orphan -ForegroundColor Yellow -NoNewline

        If (Test-Path -Path $Orphan) {
            Remove-Item -Path $Orphan -Force -Recurse -Confirm:$false -ErrorAction     SilentlyContinue
            if (Test-Path -Path $Orphan) {
                Write-Host "   UnSuccsessfully!" -ForegroundColor Red
                Write-Host $Error[0] -ForegroundColor Cyan
            }
            else {
                Write-Host "   Succsessfully!" -ForegroundColor Green
            }
        }
        else {
            Write-Host "   Error! Path does not exist." -ForegroundColor Red
        }
    }
}
else {
    Write-Host "Unused Network Adapters not be found." -ForegroundColor Yellow
}
  • Welcome to SO. Could you give same information to your changed script, please? What is better/different than in the script already posted by Hive. Thanks. – CKE Sep 18 '18 at 09:27
  • 1
    Again, we won't have any rights to that registry location unless we also change the permissions, hence why Hive said to use psexec -s. So, this script won't work. I didn't read enough to see if it is useful otherwise. – Roman Nov 06 '20 at 18:33
  • Don't use! This script has the line gwmi Win32_NetworkAdapter -Filter "NetEnabled = true" This leads to deleting the registry entries for anything that is not returned here, but that deletes the WAN Miniport adapters, Microsoft Kernel Debug Network Adapter and maybe other things that should not be deleted. So, this solution is trashing stuff that maybe should not be, not simply the hidden "ghost" devices that were removed. – Roman Nov 10 '20 at 23:27