Search code examples
powershellpowercli

PowerCLI strange behaviour of variable


Trying to collect few properties of VMs, but for some reasons all entries in the output contains the info about the last VM only

The $CSV basically contains a couple of VM names:

VMName
Centos1
Centos2

Here is the code I'm using:

$VMdata = @()
$line = '' | Select VMName, VMToolStatus, VMToolVersion, UUID, Tag, Notes

foreach($entry in $csv){
    $line.VMName = $entry.VMname
    $line.VMToolStatus = (get-vm $entry.VMname).ExtensionData.Guest.ToolsRunningStatus
    $line.VMToolVersion = (get-vm $entry.VMname).ExtensionData.Guest.ToolsVersion
    $line.UUID = (get-vm $entry.VMname).ExtensionData.Config.UUID
    $line.Notes = (get-vm $entry.VMname).Notes
    $line.Tag = get-vm $entry.VMname | Get-TagAssignment | Select -ExpandProperty Tag  | select Name
    $VMdata += $line
}

$VMdata | Export-Csv -Path c:\report.csv -NoTypeInformation -Force

here is the CSV output - as you can see both lines contain info about VM Centos2.

"VMName","VMToolStatus","VMToolVersion","UUID","Tag","Notes","StartupOrder"

"CentOS2","guestToolsRunning","2147483647","564d7fd7-e58f-e546-ecdf-c347e35cd453",,"Test Note",

"CentOS2","guestToolsRunning","2147483647","564d7fd7-e58f-e546-ecdf-c347e35cd453",,"Test Note",

When I debug it I can see that after first cycle $line is updated with correct information of VM Centos1 and then it is added to $VMData.

However, when the second cycle starts, e.g. after executing line.VMName = $entry.VMname I can see that both variables $line and $VMdata are updated with CentOS2 name.

So, my question is why $VMdata gets updated along with $line?

I used this piece of code before and it worked just fine.

I am running the following version of PS

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  1480 

VMware PowerCLI 6.5 Release 1 build 4624819


Solution

  • You can fix this by moving the $line = '' part inside the ForEach loop:

    $VMdata = @()
    
    foreach($entry in $csv){
        $line = '' | Select VMName, VMToolStatus, VMToolVersion, UUID, Tag, Notes
        $line.VMName = $entry.VMname
        $line.VMToolStatus = (get-vm $entry.VMname).ExtensionData.Guest.ToolsRunningStatus
        $line.VMToolVersion = (get-vm $entry.VMname).ExtensionData.Guest.ToolsVersion
        $line.UUID = (get-vm $entry.VMname).ExtensionData.Config.UUID
        $line.Notes = (get-vm $entry.VMname).Notes
        $line.Tag = get-vm $entry.VMname | Get-TagAssignment | Select -ExpandProperty Tag  | select Name
        $VMdata += $line
    }
    
    $VMdata | Export-Csv -Path c:\report.csv -NoTypeInformation -Force
    

    I believe the problem occurs because in this instance the PowerShell variable is acting like a pointer (a reference type), so when you update $line the second time it actually affects the existing result in $vmdata.

    By moving $line = '' inside the loop you reset the variable on each iteration so it doesn't act this way.

    I actually however recommend you do this instead:

    $CSV | ForEach-Object {
        $Props = @{
            VMName = $_.VMname
            VMToolStatus = (get-vm $_.VMname).ExtensionData.Guest.ToolsRunningStatus
            VMToolVersion = (get-vm $_.VMname).ExtensionData.Guest.ToolsVersion
            UUID = (get-vm $_.VMname).ExtensionData.Config.UUID
            Notes = (get-vm $_.VMname).Notes
            Tag = (get-vm $_.VMname | Get-TagAssignment | Select -ExpandProperty Tag  | select Name)
        }
        New-Object -TypeName PSObject -Property $Props
    } | Export-Csv -Path c:\report.csv -NoTypeInformation -Force
    

    This uses a hashtable to create a PowerShell object within a ForEach-Object loop which you can then pipe the output to directly to Export-CSV.