An item with the same key has already been added - AddPSSnapinCommand

So I’ve just started working with PowerShell 5 and DSC. I am attempting to create a resource that creates a Virtual Machine. However, currently the frustration is too much and I need some pointers here.

I’m not exactly sure if this issue is related to an underlying call inside of the PowerCLI 6.0 Module but it seems to me that I currently do not fully understand why DSC is being weird. I have attempted to load the Module required at the top of my psm1 file. However it seems when the call is made to Connect-VIServer that the module is no longer loaded.

As I recall in watching the Advanced DSC course on MVA, Jason or Jeffrey mentioned that each of the steps was spawned in its own PowerShell instance. So it makes sense that I would need to reload the Module inside of each call to Get/Set/Test. What is troubling is that when I do this, the first call to Connect-VIServer works great. (In the Test function)

Once DSC enters into SET mode and calls the Set function I receive the following error.

An item with the same key has already been added.
    + CategoryInfo          : NotSpecified: (:) [], CimException
    + FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.AddPSSnapinCommand
    + PSComputerName        : localhost

In reading this I assumed that PowerShell/WMI had already loaded the module but was in the process of unloading it. So I tried to sleep for a few seconds before the Import-Module command. No Luck… What am I missing here?

Here is my code for review:

enum VMPowerState
{
    poweredOff
    poweredOn
    suspended
}

enum Ensure
{
    absent 
    present
}

[DscResource()]
class VirtualMachine
{
    [DscProperty(Key)]
    [String]$Name
    
    [DscProperty(Mandatory)]
    [Ensure]$Ensure

    [DscProperty(Mandatory)]
    [String]$Vcenter

    [DscProperty(Mandatory)]
    [PSCredential]$VcenterCredential

    [DscProperty(Mandatory)]
    [String]$Datacenter

    [String]$ResourcePool

    [DscProperty(Mandatory)]
    [UInt32]$Vcpu

    [DscProperty(Mandatory)]
    [UInt32]$MemoryMb

    [DscProperty(Mandatory)]
    [VMPowerState]$PowerState

    [String]$DefaultGateway

    [String]$InitialFolder
    [String]$InitialCluster

    [DscProperty(Mandatory)]
    [String]$InitialTemplate

    [String]$InitialDatastore


    [VirtualMachine]Get()
    {
        if ($this.Find())
        {
            Write-Verbose -Message "Found a Virtual Machine with Name: $($this.Name)"
        } else {
            Write-Verbose -Message "Could not find a Virtual Machine with Name: $($this.Name)"
        }
        
        return $this
    }

    [void] Set()
    {
        $this.Connect()

        try {
            if ($this.Exists()) {
                $this.Update()
            }
            else {
                $this.Create()
            }
        } finally {
            $this.Disconnect()
        }
    }

    [bool] Test()
    {
        return $this.Compare()
    }

    [void] LoadModules()
    {
        Import-Module -Name "VMware.VimAutomation.Core"
    }

    [bool] Exists()
    {
        try {
            Get-VM -Name $this.Name
            return $true
        } catch {
            return $false
        }
    }

    [void] Create()
    {
        Write-Verbose -Message "Creating VM with Name: $($this.Name)"
    }

    [void] Update()
    {
        $this.Connect()
        try {
            $vm = Get-VM -Name $this.Name
        } catch {
            return
        } finally {
            $this.Disconnect()
        }
    
        if ($vm.NumCpu.ToString() -ne $this.Vcpu)
        {
            Write-Verbose -Message "Set-VM -NumCpu $($this.Vcpu)"
        }

        if ($vm.MemoryMB.ToString() -ne $this.MemoryMb)
        {
            Write-Verbose -Message "Set-VM -MemoryMB $($this.MemoryMb)"
        }

        if ($vm.PowerState.ToString() -ne $this.PowerState)
        {
            switch($this.PowerState)
            {
                [VMPowerState]::poweredOff
                {
                    Write-Verbose -Message "Stop-VM -VM $vm"
                }
                [VMPowerState]::poweredOn
                {
                    Write-Verbose -Message "Start-VM -VM $vm"
                }
                [VMPowerState]::suspended
                {
                    Write-Verbose -Message "Suspend-VM -VM $vm"
                }
            }
        }
    }

    [bool] Find()
    {
        $this.Connect()
        try {
            $vm = Get-VM -Name $this.Name
        } catch {
            return $false
        } finally {
            $this.Disconnect()
        }

        $this.Datacenter   = $(Get-Datacenter -VM $vm).Name
        $this.ResourcePool = $vm.ResourcePool.Name
        $this.Vcpu         = $vm.NumCpu.ToString()
        $this.MemoryMb     = $vm.MemoryMB.ToString()
        $this.PowerState   = $vm.PowerState.ToString()

        #####
        # The Guest state may be 'NotRunning' even though the VM is powered on
        # If the VMWare Tools package is not installed or installed improperly
        # This will cause network information about the VM to be unavailable
        #####
        $guestAvailable = $vm.Guest.State -eq "Running"

        if ($guestAvailable)
        {
            $this.DefaultGateway = ($vm.Guest.ExtensionData.IpStack.IPRouteConfig.IpRoute |
                                    Where-Object { $_.Network -eq "0.0.0.0" }).Gateway.IPAddress
        }
        return $true
    }

    [bool] Compare()
    {
        $this.Connect()
        try {
            $vm = Get-VM -Name $this.Name
        } catch {
            return ($this.Ensure -eq [Ensure]::absent)
        } finally {
            $this.Disconnect()
        }


        return `
        (
            ($this.Ensure -eq [Ensure]::present) -and `
            ($vm.NumCpu.ToString() -eq $this.Vcpu) -and `
            ($vm.MemoryMB.ToString() -eq $this.MemoryMb) -and `
            ($vm.PowerState.ToString() -eq $this.PowerState)
        )
    }

    [void] Connect()
    {
        $this.LoadModules()
        Write-Verbose -Message "Connecting to VCenter at: $($this.Vcenter)"
        Connect-VIServer -Server $this.Vcenter -Credential $this.VcenterCredential
    }

    [void] Disconnect()
    {
        Write-Verbose -Message "Disconnecting from VCenter at: $($this.Vcenter)"
        Disconnect-VIServer -Server $this.Vcenter
    }
}

I suspect snap-ins will always be a little wonky. They’re a very old architecture. It seems like you may need to test to see if the snap in is loaded before loading it. Or see if you can just trap and discard the error.

Thanks for the quick reply Don! This is where more weirdness erupts. So I just modified the LoadModules method like so

    [void] LoadModules()
    {
        try {
            Import-Module -Name "VMware.VimAutomation.Core" -ErrorAction Stop
        } catch {
            Write-Verbose -Message "Caught Import-Module Error: $_"
        }
    }

Tried running it again and now I wind up with this little gem again

The term 'Connect-VIServer' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    + CategoryInfo          : ObjectNotFound: (Connect-VIServer:) [], CimException
    + FullyQualifiedErrorId : CommandNotFoundException
    + PSComputerName        : localhost

Update:

I went ahead and tried to enumerate the PSSnapins just to see if the PowerCLI Module was loading the old VMware.VimAutomation.Core snap-in.

The abbreviated results shown below seem to indicate that there was not a snap-in loaded (other than Microsoft.PowerShell.Core)

PSSnapins: Microsoft.PowerShell.Core
Caught Remove-PSSnapin Error: No Windows PowerShell snap-ins matching the pattern 'VMware.VimAutomation.Core' were found. Check the pattern and then try the command again.
...
Loading module from path 'C:\...\Modules\VMware.VimAutomation.Core\VMware.VimAutomation.Core.ps1'.
Dot-sourcing the script file 'C:\...\Modules\VMware.VimAutomation.Core\VMware.VimAutomation.Core.ps1'.
...
Caught Import-Module Error: An item with the same key has already been added.

Updated LoadModules method

    [void] LoadModules()
    {
        Write-Verbose -Message "PSSnapins: $(Get-PSSnapin)"
        try {
            Remove-PSSnapin -Name "VMware.VimAutomation.Core" -ErrorAction Stop
        } catch {
            Write-Verbose -Message "Caught Remove-PSSnapin Error: $_"
        }
        try {
            Import-Module -Name "VMware.VimAutomation.Core" -ErrorAction Stop
        } catch {
            Write-Verbose -Message "Caught Import-Module Error: $_"
        }
    }

Okay, so today is a new day… I came back after a good bit of rest and went back at it. I hammered away for a while until I started thinking about what could be causing this issue. Nevertheless after some PowerCLI directed tests I found a workable solution.

The Problem is that the “VMware.VimAutomation.Core” is loading other dependency Modules/Snapins. So I now have to call my LoadModules() method before using PowerCLI methods and also be very sure to clean everything up before my code returns to the caller.

For the cleanup I have wound up making an UnloadModules() method for cleanliness.

    [void] LoadModules()
    {
        Import-Module -Name "VMware.VimAutomation.Core" -ErrorAction SilentlyContinue -Force
    }

    [void] UnloadModules()
    {
        Remove-Module -Name "VMware.VimAutomation.Core" -ErrorAction SilentlyContinue -Force
        Remove-Module -Name "VMware.VimAutomation.Sdk" -ErrorAction SilentlyContinue -Force
        Remove-PSSnapin -Name "VMware.VimAutomation.Core" -ErrorAction SilentlyContinue
    }