If you've read my post on adding disks to a Virtual machine, the techniques here should already feel familiar. We create a NIC , and we create a switch port. And then we tell the NIC it is connected to the switch port. Hyper-V creates VM switches which are either bound to a NIC, internal (visible to the Parent partition) or private (visible only to the child VMs). So one of the first things to do when setting up a NIC is to choose the switch, and the first function I'm going to create is Choose-VMSwitch using  the choose-list function I've already shown:  getting the Switches to pass to choose-List is easy enough, just query WMI for MSVM_VirtualSwitch Objects.

    Function Choose-VMSwitch 
    {choose-list  (Get-WmiObject -NameSpace  "root\virtualization" -Class "MsVM_VirtualSwitch") ` 
     @(@{Label="Switch Name"; Expression={$_.ElementName}} ) 
    } 

So now I can have a command Add-VMNic $VM  (Choose-VmSwitch) . Since Hyper-V supports Legacy and VMBus NICs, I have given the option for a -Legacy switch, and to support giving the NIC a fixed MAC address I've added a -MAC switch too.  The PowerShell Filter is much the same as I've shown previously 

  1. If not passed a VM parameter pick up what is in the pipe
  2. If presented  a string as a VM parameter replace it with a VM WMI Object (or array of VMs)
  3. If presented with an array of Strings or VMs call the function recursively passing it each member of the array [go back to step 2]. N.b. Multiple NICS can't  have the same MAC address so ignore the -MAC parameter.
  4. Assuming we've got a WMI object ... get the appropriate Resource Allocation Settings Data object
  5. Set the properties of the RASD; including the MAC address if one was provided. If no Switch parameter was passed set the connection property to an empty string, if one was passed Call New-VmSwitchPort and set the connection property to point to that. The VMBus NIC needs a GUID as an Identifier and the Emulated one does not.
  6. Set up an arguments array, and call the "AddVirtualSystemResources" method of the VirtualSystemManagementService WMI object (which I get with Get-WmiObject -NameSpace  "root\virtualization" -Class "MsVM_virtualSystemManagementService").

So here's the code in full.

   Filter Add-VMNIC 
   {Param ($VM , $Virtualswitch, $mac, [switch]$legacy ) 
    if ($VM -eq $null) {$VM=$_} 
    if ($VM -is [Array]) {if ($legacy) {$VM | ForEach-Object {add-VmNic -VM $_ -Virtualswitch $Virtualswitch -legacy} }                               else  {$VM | ForEach-Object {add-VmNic -VM $_ -Virtualswitch $Virtualswitch} } } 
    if ($VM -is [String]) {$VM=(Get-VM -Machinename $VM ) } 
    if ($VM -is [System.Management.ManagementObject]) { 
        if ($Legacy) {$NicRASD = Get-VMRASD -resType 10 -resSubType 'Microsoft Emulated Ethernet Port' 

                    $NicRASD.ElementName= "Legacy Network Adapter"} 
     else         {$NicRASD = Get-VMRASD -resType 10 -resSubType 'Microsoft Synthetic Ethernet Port'
                    $NicRASD.VirtualSystemIdentifiers=@("{"+[System.GUID]::NewGUID().ToString()+"}")
                    $NicRASD.ElementName= "VMBus Network Adapter"}    
      if ($virtualSwitch -ne $null) {$Newport = new-VmSwitchport $virtualSwitch                                      if ($Newport -eq $null) {$Newport= ""}
                                     $NicRASD.Connection= $newPort}
      if ($mac -ne $null) {$nicRasD.address = $mac                $nicRasD.StaticMacAddress = $true }       $arguments = @($VM.__Path, @( $nicRASD.psbase.GetText([System.Management.TextFormat]::WmiDtd20) ), $null, $null )
       $result = $VSMgtSvc.psbase.invokeMethod("AddVirtualSystemResources", $arguments)   if ($result  -eq 0) {"Added NIC to '$($VM.elementname)'."} else {"Failed to add NIC to '$($VM.elementname)', return code: $Result" }}  $vm = $null }

In this function I call "New-VmSwitchPort", which is a wrapper for a method provided by the Virtual Switch Management Service. Like the the image management service, and the Virtual System Management Service, this is just a WMI Object which we query for. The process goes

  1. If presented with a string as the VirtualSwitch Parameter, replace it with a VirtualSwitch WMI object
  2. Assuming we now have a WMI object, get the SwitchManagementService WMI object.
  3. Get a GUID to use as the port's name and friendly name , and pass it, the Switch object and 2 nulls in one array to the CreateSwitchPort method
  4. The Path to the new port is returned in one of these nulls, so the function picks this up and returns it.
   Function New-VMSwitchPort 
   {Param ($virtualSwitch , $Server=".") 
    if ($Virtualswitch -is [String]) {$Virtualswitch=(Get-WmiObject -computerName $server -NameSpace "root\virtualization" -Query "Select * From MsVM_VirtualSwitch Where elementname = '$Virtualswitch' ")} 
    if ($Virtualswitch -is [System.Management.ManagementObject])  {     $SwitchMgtSvc=(Get-WmiObject -computerName $Virtualswitch.__server -NameSpace  "root\virtualization" -Query "Select * From MsVM_VirtualSwitchManagementService")     [String]$GUID=[System.GUID]::NewGUID().ToString()      $arguments=@($Virtualswitch.__Path, $GUID, $GUID, $null, $null)      $result = $SwitchMgtSvc.psbase.invokeMethod("CreateSwitchPort",$arguments)     if ($result -eq 0) {"Created VirtualSwitchPort on '$($virtualSwitch.elementName)' " | out-host                          @($arguments[4]) }      else               {"Failed to create VirtualSwitchPort on '$($virtualSwitch.elementName)': return code: $Result" | out-host} } 
   } 

There are some extra functions that I won't show here - I've got a "Remove-Port" function and a Set-VMNICPort function - which removes an existing port and adds a newly created one. I've got a Set-VMNICMacAddress function which changes the MAC address after the NIC is created, and Get-VMNIC and Get-VMNICSwitch which build up to give  a Get-VMNICList function along the same lines as the Get-VMDiskList I showed before

 

Bonus link Over on the Virtualization Team blog, Taylor has posted the code to connect the Host machines Network card to a VM switch. I'm going to rework that code slightly for the library I'm building -  I'll have a "Choose-ExternalEthernetPort" and so on.