Have you ever stared down the barrel of endless repetitive clicks in VMware vSphere, knowing you’re in for hours of mindless work? Recently, I faced a scenario where I needed to update hundreds of virtual NICs across just as many virtual machines. Manual changes would have been soul-crushing, so I turned to VMware PowerCLI and transformed this monotonous task into a 5-minute automated script. Here’s how I did it.


The Problem: Manual Network Changes Are Soul-Crushing

Picture this: You have dozens, maybe hundreds, of VMs, each with multiple network adapters needing reassignment to a new distributed port group. The manual process looks something like this:

  1. Open each VM’s settings.
  2. Click through every network adapter.
  3. Select the new network.
  4. Wait for the change to apply.
  5. Repeat until your will to live fades.

The task was not just time-consuming but error-prone. I knew there had to be a better way.


Enter PowerCLI: Your New Best Friend

PowerCLI is VMware’s powerful scripting toolkit that supercharges PowerShell to manage your VMware environment programmatically. Here’s how to get started:

Installing PowerCLI

Open PowerShell as Administrator and run:

Install-Module -Name VMware.PowerCLI -Scope CurrentUser

Accept the prompts, and voilà! You’re ready to automate.


The Journey to Automation Nirvana

My path to automating network changes wasn’t without its hiccups. Let me walk you through the evolution of my approach.

First Attempt: The Naive Approach

Initially, I wrote a script to update only “unassigned” network adapters. Logical, right? Wrong. What PowerCLI considered “unassigned” didn’t always match what I saw in the vSphere UI.

Second Try: The Brute Force Method

I decided to reassign all the adapters, regardless of their current state. While functional, this approach overwhelmed vCenter, leading to failed operations.

Final Solution: The Smart Approach

The breakthrough came with a more intelligent script that:

  • Validates host state before making changes.
  • Introduces delays between operations to keep vCenter happy.
  • Includes robust error handling and logging.
  • Verifies changes to ensure accuracy.

The Magic Script That Does It All

Here’s the polished script that saved the day:


<#
.SYNOPSIS
    This script connects to vCenter, finds all VMs matching specific criteria, 
    and assigns unassigned virtual NICs to a specified Distributed Port Group.

.DESCRIPTION
    1. Connects to vCenter Server.
    2. Searches for VMs in "Datacenter Immaculate Constellation" -> "DELL-R720-CLUSTER" -> Host "192.168.88.5".
    3. Filters only VMs that are powered off.
    4. Assigns all empty NICs to "TRUNK-space-holder-Arista-DPort-Group".
    5. Prints progress and errors to the console.

.NOTES
    Tested with PowerCLI 12+ on vCenter 8.
#>

# ---[ 1. Setup and Connect to vCenter ]---
Write-Host "Starting script..."

# Enable PowerCLI logging
$logPath = Join-Path $PSScriptRoot "powercli_log.txt"
Write-Host "Enabling PowerCLI logging to: $logPath"
Set-PowerCLIConfiguration -Scope User -LogLevel Warning -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
Start-Transcript -Path $logPath -Force

Write-Host "Connecting to vCenter..."

try {
    # Update with your vCenter server name and credentials
    Connect-VIServer -Server "vcenterserver.dc1.local" -User "YOUR_USERNAME" -Password "YOUR_PASSWORD"
    Write-Host "Successfully connected to vCenter."
}
catch {
    Write-Host "ERROR: Failed to connect to vCenter. Error details:"
    Write-Host $_
    return
}

# ---[ 2. Locate the Datacenter, Cluster, and Host ]---
Write-Host "Locating Datacenter, Cluster, and Host..."

try {
    $datacenterName = "Datacenter Carlton II"
    $clusterName    = "DELL-R720-CLUSTER"
    $hostName       = "192.168.88.5"

    # Get the Datacenter object
    $dc = Get-Datacenter -Name $datacenterName -ErrorAction Stop
    
    # Get the Cluster object in that Datacenter
    $cluster = Get-Cluster -Name $clusterName -Location $dc -ErrorAction Stop

    # Get the Host object in that Cluster
    $vmHost = Get-VMHost -Name $hostName -Location $cluster -ErrorAction Stop

    Write-Host "Datacenter found: $($dc.Name)"
    Write-Host "Cluster found: $($cluster.Name)"
    Write-Host "Target Host found: $($vmHost.Name)"
    
    # Double check we're working with the correct host
    if ($vmHost.Name -ne $hostName) {
        Write-Host "ERROR: Retrieved host does not match target host name" -ForegroundColor Red
        Disconnect-VIServer -Confirm:$false
        return
    }

    Write-Host "`nWARNING: This script will only process VMs on host $hostName" -ForegroundColor Yellow
    Write-Host "Other hosts in the cluster will not be affected`n"

    # Get all hosts in cluster for reference
    $allHosts = Get-VMHost -Location $cluster
    Write-Host "Hosts in cluster $($cluster.Name):"
    foreach ($h in $allHosts) {
        if ($h.Name -eq $hostName) {
            Write-Host " * $($h.Name) (TARGET HOST)" -ForegroundColor Yellow
        } else {
            Write-Host " - $($h.Name) (will not be modified)"
        }
    }
    Write-Host ""

    # Check host state
    Write-Host "`nChecking host state..."
    Write-Host "Host State: $($vmHost.State)"
    Write-Host "Host Connection State: $($vmHost.ConnectionState)"
    Write-Host "Host Power State: $($vmHost.PowerState)"
    
    if ($vmHost.State -ne "Connected" -or $vmHost.ConnectionState -ne "Connected") {
        throw "Host is not in a valid state for network operations. Current state: $($vmHost.State), Connection state: $($vmHost.ConnectionState)"
    }
}
catch {
    Write-Host "ERROR: Unable to locate Datacenter, Cluster, or Host. Error details:"
    Write-Host $_
    Disconnect-VIServer -Confirm:$false
    return
}

# ---[ 3. Find All Matching VMs ]---
Write-Host "Searching for powered-off VMs..."

try {
    $vmList = Get-VM -Location $vmHost |
        Where-Object { $_.PowerState -eq "PoweredOff" }

    if (!$vmList) {
        Write-Host "No powered-off VMs found on host '$hostName'."
        return
    }
    else {
        Write-Host "Found the following powered-off VMs on host '$hostName':"
        $vmList | ForEach-Object { Write-Host " - $($_.Name)" }

        # ---[ 4. Assign Each Unassigned NIC to the Specified DPort Group ]---
        $dportGroup = Get-VDPortgroup -Name "TRUNK-space-holder-Arista-DPort-Group" -ErrorAction Stop
        if (!$dportGroup) {
            Write-Host "ERROR: Could not find distributed port group 'TRUNK-space-holder-Arista-DPort-Group'" -ForegroundColor Red
            return
        }

        Write-Host "`nFound distributed port group: $($dportGroup.Name)" -ForegroundColor Green
        Write-Host "Port group key: $($dportGroup.Key)`n" -ForegroundColor Green

        foreach ($vm in $vmList) {
            Write-Host "`nProcessing VM: $($vm.Name)" -ForegroundColor Yellow
            
            # Get all network adapters for the VM
            $nics = Get-NetworkAdapter -VM $vm
            Write-Host "  Found $($nics.Count) network adapter(s)"

            foreach ($nic in $nics) {
                Write-Host "`n  Processing NIC: $($nic.Name)"
                Write-Host "    Current Network: [$($nic.NetworkName)]"
                
                try {
                    # Check host state again before each operation
                    $hostState = Get-VMHost -Name $hostName
                    if ($hostState.State -ne "Connected" -or $hostState.ConnectionState -ne "Connected") {
                        throw "Host is not in a valid state for network operations. Current state: $($hostState.State), Connection state: $($hostState.ConnectionState)"
                    }
                    
                    # First remove the network assignment
                    Write-Host "    Removing current network assignment..." -ForegroundColor Yellow
                    $nic | Set-NetworkAdapter -NetworkName "" -Confirm:$false -ErrorAction Stop
                    Start-Sleep -Seconds 2  # Wait 2 seconds between operations
                    
                    # Then assign the new network
                    Write-Host "    Assigning to '$($dportGroup.Name)'..." -ForegroundColor Yellow
                    $nic | Set-NetworkAdapter -Portgroup $dportGroup -Confirm:$false -ErrorAction Stop
                    Start-Sleep -Seconds 2  # Wait 2 seconds after assignment
                    
                    # Verify the change
                    $updatedNic = Get-NetworkAdapter -VM $vm | Where-Object { $_.Id -eq $nic.Id }
                    Write-Host "    New Network Assignment: [$($updatedNic.NetworkName)]" -ForegroundColor Green
                }
                catch {
                    Write-Host "    ERROR: Failed to update network. Error details:" -ForegroundColor Red
                    Write-Host "    $_" -ForegroundColor Red
                    Write-Host "    Host State at time of error: $($hostState.State)"
                    Write-Host "    Host Connection State at time of error: $($hostState.ConnectionState)"
                    continue  # Continue with next NIC instead of stopping
                }
            }
            
            # Wait 5 seconds between VMs to avoid overwhelming vCenter
            Write-Host "`n  Waiting 5 seconds before processing next VM..." -ForegroundColor Blue
            Start-Sleep -Seconds 5
        }
    }
}
catch {
    Write-Host "ERROR: An issue occurred while searching for or updating VMs. Error details:"
    Write-Host $_ -ForegroundColor Red
}

# ---[ 5. Cleanup / Disconnect from vCenter ]---
try {
    Write-Host "`nDisconnecting from vCenter..."
    Disconnect-VIServer -Confirm:$false
    Write-Host "Script completed."
}
catch {
    Write-Host "ERROR: Could not disconnect from vCenter properly. Error details:"
    Write-Host $_ -ForegroundColor Red
}
finally {
    # Stop logging
    Stop-Transcript
}

How It Works

1. Initial Setup and Safety

The script enables logging and ignores certificate warnings for seamless execution.

2. Smart Host State Validation

It checks that the host is ready for changes, avoiding issues with disconnected or powered-off hosts.

3. Careful Network Changes

Network adapters are reassigned with delays to prevent overwhelming vCenter.

4. Robust Verification

Each change is verified to ensure it was applied successfully.


Results: Time Saved, Sanity Preserved

Here’s the math:

  • 50 VMs with 3 NICs each = 150 changes
  • Manual time: ~2–3 hours
  • Script time: ~5 minutes
  • Errors: Zero
  • Bonus: I actually enjoyed a coffee break while the script ran.

Pro Tips From the Trenches

  1. Always validate host state – Ensure the host is connected and ready.
  2. Introduce delays – Prevent vCenter from throwing errors due to rapid requests.
  3. Log everything – You’ll thank yourself later.
  4. Verify changes – Mistakes happen, but not on your watch.
  5. Graceful error handling – Scripts should recover from unexpected issues.

Ready to Try It Yourself?

Here’s how to get started:

  1. Install PowerCLI and connect to your vCenter.
  2. Copy the script and update the connection details.
  3. Run it.
  4. Enjoy your newfound free time.

Conclusion

PowerCLI isn’t just a tool—it’s a superpower for VMware administrators. Automating repetitive tasks not only saves time but also reduces errors, giving you more bandwidth for meaningful work.

Have you automated any VMware tasks with PowerCLI? Share your stories in the comments—I’d love to hear how you’re making VMware work smarter, not harder!

Happy scripting! 🚀

Aliens? Any thoughts?

This site uses Akismet to reduce spam. Learn how your comment data is processed.