Script: Creating an external virtual network with Hyper-V

Moving on with the series, we arrive at creating an external virtual network.

VBScript:

 option explicit 
  
 Dim HyperVServer
 Dim SwitchFriendlyName
 Dim TypeLib
 Dim SwitchName
 Dim InternalEthernetPortFriendlyName
 Dim InternalSwitchPortFriendlyName
 Dim InternalSwitchPortName
 Dim InternalEthernetPortName
 Dim ExternalSwitchPortFriendlyName
 Dim ExternalSwitchPortName
 Dim ExternalEthernetPort
 Dim ExternalEthernetPorts
 Dim ExternalEthernetPortName
 Dim ExternalEthernetPortString
 Dim ScopeofResidence
 Dim WMIService 
 Dim VirtualSwitchManagementService
 Dim Switch
 Dim InternalSwitchPort
 Dim ExternalSwitchPort
 Dim InParam
 Dim OutParams
 Dim Job
 Dim Test
  
 'Prompt for the Hyper-V Server to use
 HyperVServer = InputBox("Specify the Hyper-V Server to create the internal virtual network switch:")
  
 'Get an instance of the WMI Service in the virtualization namespace.
 set WMIService = GetObject("winmgmts:\\" & HyperVServer & "\root\virtualization")
  
 'Get the Msvm_VirtualSwitchManagementService object
 set VirtualSwitchManagementService = WMIService.ExecQuery("select * from Msvm_VirtualSwitchManagementService").ItemIndex(0)
  
 'Get all possible physical network adapters that can be used for a switch
 Set ExternalEthernetPorts = WMIService.ExecQuery("Select * From Msvm_ExternalEthernetPort WHERE IsBound=False AND EnabledState=2")
  
 'Build the string to ask what physical network adapter to use
 ExternalEthernetPortString = "The following network adapters are available:" & chr(10) & chr(10)
  
 for each ExternalEthernetPort in ExternalEthernetPorts
    ExternalEthernetPortString = ExternalEthernetPortString & ExternalEthernetPort.Name & chr(10)
 next       
  
 ExternalEthernetPortString = ExternalEthernetPortString & chr(10) & "Please type in the (exact) name of the network adapter you want to use for the external connection:"
  
 'Setup a loop to keep on asking for a network adapter until we get a valid response
 Test = true
  
 while test
    ExternalEthernetPortName = InputBox(ExternalEthernetPortString)
    on error resume next
    Set ExternalEthernetPort = WMIService.ExecQuery("Select * From Msvm_ExternalEthernetPort WHERE Name='" & ExternalEthernetPortName & "' AND IsBound=False AND EnabledState=2").ItemIndex(0)       
    If Err.Number = 0 Then
       Test = false
    End If
    on error goto 0
 Wend
  
 'Get friendly name for the external virtual network switch (and the internal ethernet port)
 SwitchFriendlyName = InputBox("Specify the name of the new external virtual network switch:")
 InternalEthernetPortFriendlyName = SwitchFriendlyName 
  
 'Set the friendly name for the internal switch port and external switch port
 InternalSwitchPortFriendlyName = "InternalSwitchPort"
 ExternalSwitchPortFriendlyName = "ExternalSwitchPort"
  
 'Generate GUIDs for the unique switch name, internal switch port name, internal ethernet port name and external switch port name
 Set TypeLib = CreateObject("Scriptlet.TypeLib")
 SwitchName = TypeLib.Guid
  
 Set TypeLib = CreateObject("Scriptlet.TypeLib")
 InternalSwitchPortName = TypeLib.Guid
  
 Set TypeLib = CreateObject("Scriptlet.TypeLib")
 InternalEthernetPortName = TypeLib.Guid
  
 Set TypeLib = CreateObject("Scriptlet.TypeLib")
 ExternalSwitchPortName = TypeLib.Guid
  
 'Create a new virtual network switch
  
 'Setup the input parameter list
 set InParam = VirtualSwitchManagementService.Methods_("CreateSwitch").InParameters.SpawnInstance_()
 InParam.FriendlyName = SwitchFriendlyName
 InParam.Name = SwitchName 
 InParam.NumLearnableAddresses = 1024
 InParam.ScopeofResidence = null
  
 'Execute the method and store the results in OutParam
 set OutParams = VirtualSwitchManagementService.ExecMethod_("CreateSwitch", InParam)
  
 'Get the new switch object out of the results    
 Set Switch = WMIService.Get(OutParams.CreatedVirtualSwitch)
  
 'Create a new internal switch port
  
 'Setup the input parameter list
 set InParam = VirtualSwitchManagementService.Methods_("CreateSwitchPort").InParameters.SpawnInstance_()
 InParam.VirtualSwitch = Switch.Path_.Path
 InParam.FriendlyName = InternalSwitchPortFriendlyName
 InParam.Name = InternalSwitchPortName 
 InParam.ScopeofResidence = null
  
 'Execute the method and store the results in OutParam
 set OutParams = VirtualSwitchManagementService.ExecMethod_("CreateSwitchPort", InParam)
  
 'Get the new internal switch port out of the results    
 Set InternalSwitchPort = WMIService.Get(OutParams.CreatedSwitchPort)
  
 'Create a new external switch port
  
 'Setup the input parameter list
 set InParam = VirtualSwitchManagementService.Methods_("CreateSwitchPort").InParameters.SpawnInstance_()
 InParam.VirtualSwitch = Switch.Path_.Path
 InParam.FriendlyName = ExternalSwitchPortFriendlyName
 InParam.Name = ExternalSwitchPortName 
 InParam.ScopeofResidence = null
  
 'Execute the method and store the results in OutParam
 set OutParams = VirtualSwitchManagementService.ExecMethod_("CreateSwitchPort", InParam)
  
 'Get the new external switch port out of the results    
 Set ExternalSwitchPort = WMIService.Get(OutParams.CreatedSwitchPort)
  
 'Pull it all together with a call to SetupSwitch
  
 'Setup the input parameter list
 set InParam = VirtualSwitchManagementService.Methods_("SetupSwitch").InParameters.SpawnInstance_()
 InParam.ExternalSwitchPort = ExternalSwitchPort.Path_.Path
 InParam.InternalSwitchPort = InternalSwitchPort.Path_.Path
 InParam.ExternalEthernetPort = ExternalEthernetPort.Path_.Path
 InParam.InternalEthernetPortName = InternalEthernetPortName
 InParam.InternalEthernetPortFriendlyName = InternalEthernetPortFriendlyName
  
 'Execute the method and store the results in OutParams
 set OutParams = VirtualSwitchManagementService.ExecMethod_("SetupSwitch", InParam)
  
 'Check to see if the job completed synchronously
 if (OutParams.ReturnValue = 0) then
    Wscript.Echo "External virtual network created."
 elseif (OutParams.ReturnValue <> 4096) then
    Wscript.Echo "Failed to create external virtual network." & OutParams.ReturnValue
 else   
  
    'Get the job object
    set Job = WMIService.Get(OutParams.Job)
  
     'Wait for the job to complete (3 == starting, 4 == running)
    while (Job.JobState = 3) or (Job.JobState = 4)
       Wscript.Echo "Creating virtual network. " & Job.PercentComplete & "% complete"
       WScript.Sleep(1000)
  
       'Refresh the job object
       set Job = WMIService.Get(OutParams.Job)
    Wend
  
    'Provide details if the job fails (7 == complete)
    if (Job.JobState <> 7) then
       Wscript.Echo "External virtual network created."
       Wscript.Echo "ErrorCode:" & Job.ErrorCode
       Wscript.Echo "ErrorDescription:" & Job.ErrorDescription
    else
       Wscript.Echo "Failed to create external virtual network."
    end If
 end if

 

PowerShell:

 # Prompt for the Hyper-V Server to use
 $HyperVServer = Read-Host "Specify the Hyper-V Server to use (enter '.' for the local computer)"
  
 # List the available physical network adapters
 Write-Host "The following physical network adapters are avaiable for an external virtual network:"
  
 # Get the collection of external network adapters that are not associated with switches
 $query = "Select * From Msvm_ExternalEthernetPort WHERE IsBound=False AND EnabledState=2"
 $ExternalEthernetPorts = gwmi -namespace "root\virtualization" -Query $query -computername $HyperVServer
  
 # Output the elementname for each external nic
 foreach ($Nic in $ExternalEthernetPorts)       
    {          
       Write-Host $Nic.Name       
    } 
  
 # Keep on asking for a physical network adapter until we get a valid answer
 $ExternalEthernetPort = $null
  
 While ($ExternalEthernetPort -eq $null)
    {
    # Ask for the NIC name to use for the switch
    $ExternalEthernetPortName = Read-Host "Please type in the (exact) name of the network adapter you want to use for the external connection"
  
    # Get the WMI object for the external nic
    $query = "Select * From Msvm_ExternalEthernetPort WHERE Name='" + $ExternalEthernetPortName + "' AND IsBound=False AND EnabledState=2"
    $ExternalEthernetPort = gwmi -namespace "root\virtualization" -Query $query -computername $HyperVServer
    }
  
 # Get friendly name for the external virtual network switch (and internal ethernet port)
 $SwitchFriendlyName = Read-Host "Specify the name of the new external virtual network switch"
 $InternalEthernetPortFriendlyName = $SwitchFriendlyName
  
 # Set the friendly name for the internal switch port and external switch port
 $InternalSwitchPortFriendlyName = "InternalSwitchPort"
 $ExternalSwitchPortFriendlyName = "ExternalSwitchPort"
  
 # Generate GUIDs for the unique switch name, internal switch port name, internal ethernet port name and external switch port name
 $SwitchName = [guid]::NewGuid().ToString()
 $InternalSwitchPortName = [guid]::NewGuid().ToString()
 $InternalEthernetPortName = [guid]::NewGuid().ToString()
 $ExternalSwitchPortName = [guid]::NewGuid().ToString()
  
 # Setup some other values that will be used
 $NumLearnableAddresses = 1024
 $ScopeOfResidence = ""
  
 # Get the Msvm_VirtualSwitchManagementService WMI Object on the system we are going to be working with
 $VirtualSwitchManagementService = gwmi Msvm_VirtualSwitchManagementService -namespace "root\virtualization" -computername $HyperVServer
  
 # Create a new switch with 1024 learnable addresses
 $Result = $VirtualSwitchManagementService.CreateSwitch($SwitchName, $SwitchFriendlyName, $NumLearnableAddresses, $ScopeOfResidence) 
  
 # Get the WMI object for the new switch out of the results
 $Switch = [WMI]$Result.CreatedVirtualSwitch 
  
 # Create Internal Switch Port 
 $Result = $VirtualSwitchManagementService.CreateSwitchPort($Switch, $InternalSwitchPortName, $InternalSwitchPortFriendlyName, $ScopeOfResidence)
  
 # Get the WMI object for the new switch port out of the results
 $InternalSwitchPort = [WMI]$Result.CreatedSwitchPort 
  
 # Create External Switch Port 
 $Result = $VirtualSwitchManagementService.CreateSwitchPort($Switch, $ExternalSwitchPortName, $ExternalSwitchPortFriendlyName, $ScopeOfResidence)
  
 # Get the WMI object for the new switch port out of the results
 $ExternalSwitchPort = [WMI]$Result.CreatedSwitchPort 
  
 # Pull it all together with a call to SeutpSwitch
 $Result = $VirtualSwitchManagementService.SetupSwitch($ExternalSwitchPort, $InternalSwitchPort, $ExternalEthernetPort, $InternalEthernetPortName, $InternalEthernetPortFriendlyName)
  
 # Return success if the return value is "0"
 if ($Result.ReturnValue -eq 0)
    {write-host "External virtual network created."} 
  
 # If the return value is not "0" or "4096" then the operation failed
 ElseIf ($Result.ReturnValue -ne 4096)
    {write-host "Failed to create external virtual network."}
  
   Else
    {# Get the job object
     $job=[WMI]$Result.job
  
     # Provide updates if the jobstate is "3" (starting) or "4" (running)
     while ($job.JobState -eq 3 -or $job.JobState -eq 4)
       {write-host $job.PercentComplete
        start-sleep 1
  
        #Refresh the job object
        $job=[WMI]$Result.job}
  
      # A jobstate of "7" means success
     if ($job.JobState -eq 7)
        {write-host "External virtual network created."}
       Else
        {write-host "Failed to create external virtual network."
         write-host "ErrorCode:" $job.ErrorCode
         write-host "ErrorDescription" $job.ErrorDescription}
    }

Let’s step through the flow here:

  • First we iterate over all instances of Msvm_ExternalEthernetPort to let the user know what network adapters are available
  • Then we create a new virtual network (MSVM_VirtualSwitchManagementService.CreateSwitch)
  • Next we create an internal switch port and an external switch port (both using MSVM_VirtualSwitchManagementService.CreateSwitchPort)
    • This is making two jacks on the switch.  One to be connected to the physical network adapter and one to be connected to a virtual network adapter in the parent partition
  • Finally we hook everything together (MSVM_VirtualSwitchManagementService.SetupSwitch)
    • The SetupSwitch command not only connects the physical network adapter to the virtual network switch, but also creates a new virtual network adapter in the parent partition and copies the protocol configuration from the physical network adapter to the virtual network adapter

Some extra points to make:

  • When looking at physical network adapters (MSVM_ExternalEthernetPort):
    • Network adapters that are already connected to a virtual network will return “true” for “IsBound”
    • If you do not check the EnabledState you will see entries for old network adapters that are no longer present in the physical computer
    • You really should be using the “DeviceID” to uniquely identify network adapters, as the name is not guaranteed to be unique – but as no one knows the device ID, and the chances of a duplicate name are incredibly low – I prefer to use the name
  • Since I am asking the user to type in a long string for the network name – I have put in a loop so that if they make a mistake they have a chance to enter it again.
  • Most of the above methods return a result object that contains a reference to the WMI object that has been created.  You will see in the code that after each method call we then need to go and grab this object out of the results.
    • The results also contain a return code that indicates success or failure.  For brevity of code I am not checking this value and am assuming success on each of these calls – however for correctness you should be checking this value.
    • MSVM_VirtualSwitchManagementService.SetupSwitch is the one method that does not complete synchronously so you need to handle the job object for it.
  • The friendly name for the internal ethernet port is what appears as the device name for the virtual network adapter in the parent partition (when you open up the network connection management UI under Windows).  In these scripts I make it the same as the virtual network name – which is what the Hyper-V UI does – but you can make it anything you want.
  • For all the non-friendly names I am generating GUIDs.  This is not required but is strongly recommended.  These values are never displayed in the UI and are used for internal purposes and *must* be unique.
  • I have hardcoded the value for the friendly name of the internal switch port and external switch port.  I am doing this because the are never displayed in the UI and do not need to be unique.

Cheers,
Ben