Hyper-V: How to remove disabled virtual network adapters from the parent partition

In May, I mentioned that we introduced a checkbox in the Hyper-V UI for Windows 7 and Windows Server 2008 R2 which determines whether a virtual NIC is created in the parent partition for External Virtual Networks. (To get a better understanding of Hyper-V networking and what this means, take a look at this article.)

Many customers follow our best practice of having at least two physical NICs, one for use by the parent partition (aka Management Operating System), and one or more for use by virtual machines. But using the “v1” UI, there’s no option to not have the parent virtual NIC created. Two common ways to resolve this are to disable the virtual NIC it in “Network Connections” on the control panel, or unbinding all protocols from the virtual NIC. The disabling option tends (in my experience) to be the more common choice of the two.

There are, of course, several scripts out there to create what I termed a “dedicated” network (ie one which is dedicated for use only by virtual machines) last year. Ben has a couple of samples here in both VBScript and PowerShell, and Robert has a sample here. These are better approaches to disabling NICs or removing protocol bindings.

To date, I haven’t come across a script which removes disabled virtual NICs in the parent partition. So here is one such example. This will hopefully assist you in being more “compliant” with our best practice recommendations in an existing Hyper-V deployment.

Obviously with any script which does destructive things, I urge caution and that you make careful note of your configuration before running it, and remember the standard disclaimer on my blog.

 //
 // VirtualSwitchManagementService object.  Logical wrapper class for Switch Management Service
 //
 function
 VirtualSwitchManagementService(
     Server,
     User,
     Password
     )
 {
     //
     // Define instance fields.
     //    
     this.m_VirtualizationNamespace  = null;
     
     this.m_VirtualSwitchManagementService = null;
         
  
     //
     // Instance methods
     //        
     VirtualSwitchManagementService.prototype.DeleteInternalEthernetPort = 
     function(
         InternalEthernetPort
         )
  
     /*++
 
     Description:
 
         Deletes an internal ethernet port
         
     Arguments:
 
         InternalEthernetPort - Msvm_InternalEthernetPort to delete
         
     Return Value:
 
         SWbemMethod.OutParameters object.
 
     --*/
  
     {
         var methodName = "DeleteInternalEthernetPort";
  
         var inParams = this.m_VirtualSwitchManagementService.Methods_(methodName).inParameters.SpawnInstance_();
         
         inParams.InternalEthernetPort = InternalEthernetPort.Path_.Path;
         
         return this.m_VirtualSwitchManagementService.ExecMethod_(methodName, inParams);
     }
  
     //
     // Utility functions
     //
     
     VirtualSwitchManagementService.prototype.WaitForNetworkJob = 
     function(
         OutParams
         )
  
     /*++
 
     Description:
 
         WMI calls will exit with some type of return result.  Some will require 
         a little more processing before they are complete. This handles those 
         states after a wmi call.
 
     Arguments:
 
         OutParams - the parameters returned by the wmi call.
 
     Return Value:
 
         Status code
 
     --*/
  
     {
         if (OutParams.ReturnValue == 4096)
         {
             var jobStateStarting        = 3;
             var jobStateRunning         = 4;
             var jobStateCompleted       = 7;
     
             var networkJob;
  
             do
             {
                 WScript.Sleep(1000);
                 
                 networkJob = this.m_VirtualizationNamespace.Get(OutParams.Job);
  
             } while ((networkJob.JobState == jobStateStarting) || 
                      (networkJob.JobState == jobStateRunning));
  
             if (networkJob.JobState != jobStateCompleted)
             {
                 throw(new Error(networkJob.ErrorCode,
                                 networkJob.Description + " failed: " + networkJob.ErrorDescription));
             }
             
             return networkJob.ErrorCode;
         }
  
         return OutParams.ReturnValue;
     }
     
     
     //
     // Aggregate functions
     //
     VirtualSwitchManagementService.prototype.DeleteInternalEthernetPortAndWait = 
     function(
         InternalEthernetPort
         )
     /*++
 
     Description:
 
         Deletes an internal ethernet port
         
     Arguments:
 
         InternalEthernetPort - Msvm_InternalEthernetPort to delete
         
     Return Value:
 
         SWbemMethod.OutParameters object.
 
     --*/
     
     {
         var outParams = this.DeleteInternalEthernetPort(InternalEthernetPort);
  
         var wmiRetValue = this.WaitForNetworkJob(outParams);
  
         if (wmiRetValue != 0)
         {
             throw(new Error(wmiRetValue, "DeleteInternalEthernetPortAndWait failed"));
         }
     }
  
     //
     // Constructor code
     //
     
     if (Server == null)
     {
         Server = WScript.CreateObject("WScript.Network").ComputerName; 
     }
     
     //
     // Set Namespace fields
     //
     var locator = new ActiveXObject("WbemScripting.SWbemLocator");
  
     this.m_VirtualizationNamespace = locator.ConnectServer(Server, "root\\virtualization", User, Password);
     
     //
     // Set Msvm_VirtualSwitchManagementService field
     //
     var physicalComputerSystem = 
             this.m_VirtualizationNamespace.Get(
                     "Msvm_ComputerSystem.CreationClassName='Msvm_ComputerSystem',Name='" + Server + "'");
  
     var list = physicalComputerSystem.Associators_(
                         "Msvm_HostedService",
                         "Msvm_VirtualSwitchManagementService",
                         "Dependent");        
  
     this.m_VirtualSwitchManagementService = list.ItemIndex(0)
 }
  
  
 //
 // main
 // 
  
 var wshShell = WScript.CreateObject("WScript.Shell");
  
 var g_NvspWmi   = null;
 var g_CimV2     = null;
  
 Main();
  
 function Main()
 {
     g_NvspWmi   = new VirtualSwitchManagementService();
  
     WScript.Echo("Looking for root\\cimv2...");
     var locator = new ActiveXObject("WbemScripting.SWbemLocator");
     g_CimV2 = locator.ConnectServer("", "root\\cimv2", "", "");
         
     WScript.Echo("");
     WScript.Echo("Looking for internal (parent) virtual nics...");
     var list = g_NvspWmi.m_VirtualizationNamespace.ExecQuery("SELECT * FROM Msvm_InternalEthernetPort");
     for (i = 0; i < list.Count; i++)
     {
         var next = list.ItemIndex(i);
  
         // find correpsonding Win32_NetworkAdapter
         var adapters = g_CimV2.ExecQuery("SELECT * FROM Win32_NetworkAdapter WHERE GUID='" + next.DeviceID + "'");
         for (j = 0; j < adapters.Count; j++)
         {
             var adapter = adapters.ItemIndex(j);
             if (adapter.NetEnabled == false)
             {
                 WScript.echo("Deleting '" + next.ElementName + "' because it is disabled.");
                 g_NvspWmi.DeleteInternalEthernetPortAndWait(next);
             }
             else
             {
                 WScript.echo("Not deleting '" + next.ElementName + "' because it is enabled.");
             }
         }
     }
  
     WScript.Echo("");
     WScript.Echo("Finished!");
 }

The script should be saved with a .js extension to run it from the command line. Or you can get it as a text file from here.

One thing I would note is that after running the script, you’ll notice in the “v1” UI when you select an External Virtual Network, the message “This virtual network switch cannot be configured. You might not have permission to perform this task, or it might have been reconfigured in the parent partition” is displayed. This limits your ability to change the network type to an Internal or Private network. However, you can still remove the network if necessary.

Virtual Network Manager

Another point to note is that if you are running Hyper-V v1 RTM on Windows Server 2008 SP1, have disabled virtual NICs in the parent partition, then apply Service Pack 2, disabled NICs are re-enabled after applying the service pack. Hence, I would recommend you remove disabled virtual NICs from the parent partition if you have no need for them prior to applying SP2.

And for completeness, if you ever need to completely remove all virtual networks from your system (it crops up as a question every now and then), there’s a useful script you can find here. Obviously though, I recommend extreme caution before running that script.

Cheers,
John.

And again, thanks to Keith for his assistance :)