Clone a Standard vSwitch from one ESXi Host to Another

One of my customers is standardizing the configurations on their HP C7000 enclosures (they've been set up at various sites by various administrators with varying involvement from the architecture team).  As such, we need to stand up some temporary resources so that we can take down the main enclosure for reconfiguration.  That's fine, we can easily ship a smaller enclosure to each site for temporary compute resources.  Some of the sites are using Standard vSwitches, so we need to be able to quickly copy the networking configuration over from their existing blades to these new, slightly different blades.  As I see it, we had 2 good options:

1) Capture a Host Profile with the desired vSwitch configuration.  Delete every other component of the Host Profile so that only the networking section is applied; design the new ESXi hosts so that the vSwitches'll work with the old vmnic-to-vswitch configurations.

2) Write a script to clone the vSwitch from ones ESXi host to another.

If you know anything about me, you can probably guess that I went with the scripting option.  Host profiles are great and all, but I like the amount of control that a script gives me, as it allows me to know exactly what is happening.  So, after a bit of work one morning, I came up with the script below.

The syntax is pretty straight forward - it takes 3 arguments.  It needs to know the source ESXi host, the name of the vSwitch on that ESXi host and the name of the destination ESXi host.  It'll then run through and create that same vSwitch on the destination.  If that vSwitch already exists on the destination, it will warn the user and prompt for permission to continue.  If permission is granted, it will only create new Port Groups on that vSwitchl it will not adjust any Port Groups that already exist.

As always, this is posted for educational purposes.  While it worked for me in this particular situation, that's no guarantee that it'll work for you, yadda yadda yadda.  If you find/fix any bugs, please let me know and I'll happily include your improvements!

#Copies one host's standard vSwitch to another host
#Author: Jason Coleman (virtuallyjason.blogspot.com)
#Usage: Clone-VSS.ps1 -s [the name of the ESX host to copy the vSwitch from] -v [the name of the vSwitch to copy] -d [the name of the ESX host to copy the vSwitch to]
param
(
   [alias("s")]
   [string]$sourceHostString = $(read-host -Prompt "Enter the source Host"),
   [alias("v")]
   [string]$sourceVSwitchString = $(read-host -Prompt "Enter the Source Standard Virtual Switch"),
   [alias("d")]
   [string]$destinationHostString = $(read-host -Prompt "Enter the Destination Host")
)

#Get the destination host
$thisHost = get-vmhost $destinationHostString

#Get the source vSwitch and do error checking
$sVSwitch = get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString -errorAction silentlycontinue
if (!($sVSwitch))
{
   write-host "$sourceVSwitchString was not found on $sourceHostString" -foreground "red"
   exit 1
}
if ($sVSwitch.count -ne 1)
{
   write-host "'$sourceVswitchString' returned multiple vSwitches; please use a more specific string." -foreground "red"
   $sVSwitch
   exit 4
}
if ($thisHost | get-virtualSwitch -name $sourceVSwitchString -errorAction silentlycontinue)
{   
   if ((($thisHost | get-virtualSwitch -name $sourceVSwitchString).uid) -like "*DistributedSwitch*")
   {
      write-host "$sourceVSwitchString is a Distributed vSwitch, exiting." -foreground "red"
      exit 3
   }
   $continue = read-host "vSwitch $sourceVSwitchString already exists on $destinationHostString; continue? [yes|no]"
   if (!($continue -like "y*"))
   {
      exit 2
   }
}
else
{
   #If the VSS doesn't already exist, create it
   $thisHost | new-virtualSwitch -name $sVSwitch.name > $null
}

#Make new Port Groups on the VSS
$destSwitch = $thisHost | get-virtualSwitch -name $sVSwitch.name
foreach ($thisPG in ($sVSwitch | get-virtualportgroup))
{
   #Skip this Port Group if it already exists on the destination vSwitch
   if ($destSwitch | get-virtualportgroup -name "$($thisPG.Name)" -errorAction silentlycontinue)
   {
      echo "$($thisPG.Name) already exists, skipping."
   }
   else
   {
      echo "Creating Port Group: $($thisPG.Name)."
      new-virtualportgroup -virtualswitch $destSwitch -name "$($thisPG.Name)" > $null
      #Assign a VLAN tag if there is one on the source Port Group
      if ($thisPG.vlanid -ne 0)
      {
         get-virtualportgroup -virtualswitch $destSwitch -name "$($thisPG.Name)" | Set-VirtualPortGroup -vlanid $thisPG.vlanid > $null
      }      
   }
}

Comments

  1. Running the "get-vmhost $host | get-virtualswitch -name $vsname" by itself (against any given host) will return 3 identical values. This also happens with a brand new ESXi host. The vSwitch0 is with a bonded NIC. Example:

    Name NumPorts Mtu Notes
    ---- -------- --- -----
    vSwitch0 4352 1500
    vSwitch0 4352 1500
    vSwitch0 4352 1500

    Any suggestions on how to isolate the to and from virtual switches?

    ReplyDelete
    Replies
    1. I'm unable to replicate that issue.

      If you use "get-vmhost $host" (although $host is an invalid variable), do you only get 1 host? I suspect that you're getting 3 hosts, and then the get-virtualswitch cmdlet is returning the vSwitch0 switch from each of the 3 hosts. You can also get a better perspective on what's happening by piping your output like this:

      get-vmhost $host | get-virtualswitch -name $vsname | select name,vmhost

      That'll list the name of each vSwitch that's returned as well as the ESXi host on which that switch is defined.

      Delete
    2. I believe this was due to having multiple connections to the vCenter left open at the time. Sorry about that. The script works great! Working on a way to also include standby and active adapter configs.

      Delete
    3. I'm glad to hear that you were able to work it out! Be careful about automating vmnic assignments, as an error there could leave a host isolated (and therefore all of its VMs offline) or create some other bad situation.

      Delete
  2. I am using PowerCLI connected to my vCenter server. When I run the script to copy vSwitch3 from 1 ESXi host to another it errors out saying 'vSwitch3' returned multiple vSwitches. There are other ESXi hosts in this vCenter environment with a vSwitch3 but not the 2 I am working with. Any ideas?

    ReplyDelete
    Replies
    1. Is it possible that your -sourceHostString is returning multiple ESXi hosts? Try doing a "get-vmhost (whatever you typed for that parameter)". If it returns multiple ESXi hosts, type something more specific so that it only returns a single host.

      I had to edit this, because I'd originally enclosed to "whatever you typed for that parameter" in angle brackets which got parsed as HTML ;)

      Delete
    2. get-vmhost only returns the single host I enter

      Delete
    3. Hmm, well the next thing to set these variables:
      $sourceHostString = "name"
      $sourceVSwitchString = "vswitch3"

      and then to execute this line:
      get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString

      And see what you get. That error will only come up if that command returns not 1 vSwitch and the if case before it should deal with the case of 0 vswitches.

      Delete
  3. That works:
    PowerCLI C:\Install> get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString

    Name NumPorts Mtu Notes
    ---- -------- --- -----
    vSwitch3 10752 1500


    However if I run the clone-vss script it fails:
    PowerCLI C:\Install> .\Clone-VSS.ps1 -s srvesx46-wtucs.mydomain.org -v vSwitch3 -d srvesx49-wtucs.mydomain.org
    'vSwitch3' returned multiple vSwitches; please use a more specific string.

    Name NumPorts Mtu Notes
    ---- -------- --- -----
    vSwitch3 10752 1500

    ReplyDelete
    Replies
    1. That's super weird. Add these lines in after the $sVSwitch variable is set in the script, which will cause it to generate some output to understand why it's going to that error condition:

      write-host "==="
      $sVSwitch
      read-host "==="

      We'll see whatever is in that variable bracketed by the equals signs, and then it'll pause for input. I'm curious about what's in there, given that running those commands independently seems to work fine.

      Delete
  4. Here it is - could it be because it's looking at the vSwitch name across all hosts and not just on the source host?

    PowerCLI C:\Install> .\Clone-VSS.ps1 -s srvesx46-wtucs.mydomain.org -v vSwitch3 -d srvesx49-wtucs.mydomain.org
    ===

    Name NumPorts Mtu Notes
    ---- -------- --- -----
    vSwitch3 10752 1500
    ===:





    ReplyDelete
    Replies
    1. When you pipe it a host, it should only pull from that host.

      Try changing this line:
      $sVSwitch = get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString -errorAction silentlycontinue

      to these two lines:
      $svSwitch = @()
      $sVSwitch += get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString -errorAction silentlycontinue

      That will force the variable to be an array of vSwitch objects; I wonder if that object is not responding to .count in the expected manner when it's by itself. You could prove this out by outputting $svswitch.count before making this change.

      Delete
    2. With the line changed to these 2 lines:

      #Get the source vSwitch and do error checking
      $svSwitch = @()
      $svSwitch += get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString -errorAction silentlycontinue

      I get this error:

      PowerCLI C:\Install> .\Clone-VSS.ps1 -s srvesx46-wtucs.mydomain.org -v vSwitch3 -d srvesx49-wtucs.mydomain.org

      New-VirtualSwitch : Cannot validate argument on parameter 'Name'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
      At C:\Install\Clone-VSS.ps1:48 char:39
      + $thisHost | new-virtualSwitch -name <<<< $sVSwitch.name > $null
      + CategoryInfo : InvalidData: (:) [New-VirtualSwitch], ParameterBindingValidationException
      + FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutomation.ViCore.Cmdlets.Commands.Host.NewVirtualSwitch

      Get-VirtualSwitch : Cannot validate argument on parameter 'Name'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
      At C:\Install\Clone-VSS.ps1:52 char:50
      + $destSwitch = $thisHost | get-virtualSwitch -name <<<< $sVSwitch.name
      + CategoryInfo : InvalidData: (:) [Get-VirtualSwitch], ParameterBindingValidationException
      + FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutomation.ViCore.Cmdlets.Commands.Host.GetVirtualSwitch

      Delete
    3. Also I added the lines to output the count prior to making the above change and it comes out blank?:

      #Get the source vSwitch and do error checking
      $svSwitch = get-vmhost $sourceHostString | get-virtualswitch -name $sourceVSwitchString -errorAction silentlycontinue

      write-host "==="
      $sVSwitch.count
      read-host "==="

      PowerCLI C:\Install> .\Clone-VSS.ps1 -s srvesx46-wtucs.mydomain.org -v vSwitch3 -d srvesx49-wtucs.mydomain.org
      ===
      ===:


      Delete
    4. What version of PowerShell are you running? Use $psversiontable.psversion to display it. I have a bad habit of using some lazy techniques that only get interpreted correctly in Major version 4 or higher; I wonder if you're running into one of those (things like counting single objects).

      Regardless, it looks like it's pulling the data that it's supposed to be pulling (as far as the vSwitch goes), so you could just remove that entire error check section and it'll probably work. If you're on an older version of PowerShell, it may crash somewhere else though.

      Delete
  5. Looks older. I was just using our old 2008 R2 vcenter server:

    PowerCLI C:\Install> $psversiontable.psversion

    Major Minor Build Revision
    ----- ----- ----- --------
    2 0 -1 -1

    ReplyDelete
    Replies
    1. I'd strongly recommend upgrading that to version 4; it's part of the Windows Management Framework and introduces a lot of helpful features. Also, my scripts will probably start working ;)

      Delete
  6. That was it! I upgraded and it worked fine! Sorry to take up your time and thank you so much. Going to save me a lot of time creating standard switches with tons of port groups so we can migrate hosts to a new vcenter instance.

    ReplyDelete
    Replies
    1. Glad I could help. In fact, I've done a *lot* of work on the process of migrating between vCenter instances (largely in the context of either an upgrade or moving to the vCenter Appliance). I documented my process pretty extensively and created a series of scripts to help recreate the VM folder structure, permissions, etc. in the new vCenter. If you're curious about how I approached the situation, I put my notes online here: https://virtuallyjason.blogspot.com/2016/09/migrating-from-vcenter-55-on-windows-to.html

      Delete
  7. Jason, any chance you have a script that will do this with the entire networking component? Including Distributed Switch?

    ReplyDelete
    Replies
    1. I'm not entirely clear what you're asking. Are you asking for a script to join an ESXi host to a distribute switch? The Add-VDSwitchVMHost cmdlet should help you to do that, if it's what you're looking for. If you're looking for a way to clone a VDS configuration onto a VSS, that is included in my method for migrating to a vCenter 6 environment, here: https://virtuallyjason.blogspot.com/2016/09/migrating-from-vcenter-55-on-windows-to.html

      Delete
  8. Script worked like a charm and didn't need to modify a single line!!
    Thanks for this Jason!

    ReplyDelete
  9. Any chance you can help me make one small modification? I'd like to specify a destination vSwitch to copy too. so source would be vSwitch0 but dest on the new host would be vSwitch 1.

    ReplyDelete
    Replies
    1. Sure - just change this line:
      $destSwitch = $thisHost | get-virtualSwitch -name $sVSwitch.name
      into this:
      $destSwitch = $thisHost | get-virtualSwitch -name "vSwitch1"

      Delete
  10. Hi Jason,
    This script works nicely but I'm unable to configure the IP settings of the newly-created/copied vswitch. Is there a way to do this and still have this tab?
    Thanks!

    ReplyDelete
    Replies
    1. Are you referring to vmkernel port groups? This script only creates VM Port Groups; you'll have to manually manipulate the VMK port groups. I don't believe that this script does anything unusual re: vSwitch creation (after all, it's using the standard new-virtualSwitch cmdlet), so I don't know why you wouldn't be able to manipulate it in that way.

      Delete
  11. Thanks!! This worked like a charm on ESXI 5.5

    ReplyDelete

Post a Comment

Sorry guys, I've been getting a lot of spam recently, so I've had to turn on comment moderation. I'll do my best to moderate them swiftly after they're submitted,

Popular posts from this blog

Orphaned VMDK Files

Migrating from one vCenter to Another, Improved

Copying VM Folders and Permissions from One vCenter to Another