Virtual Machine Script Extracts and Examples

From vwiki
Revision as of 12:19, 20 January 2012 by Sstrutt (talk | contribs) (Initial creation - content from Script_Extracts_and_Examples_(PowerCLI) page)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

VM's with Host and Cluster List

$vms = Get-VM | sort -property Name
foreach ($vm in $vms)
{
     $vm | Get-Cluster | Select-Object @{Name="VM"; Expression={$vm.name}},@{Name="Current Host"; Expression={$vm.host}},Name
}

VM's Inventory CSV

Creates a CSV export list of virtual machines.

$start = Get-Date

# Create table for output
# Name	DC	OS	UUID	IP	Cluster		ESX's

$table = New-Object system.Data.DataTable "Results"

$col1 = New-Object system.Data.DataColumn Name,([string])
$col2 = New-Object system.Data.DataColumn DC,([string])
$col3 = New-Object system.Data.DataColumn OS,([string])
$col4 = New-Object system.Data.DataColumn UUID,([string])
$col5 = New-Object system.Data.DataColumn MgmtIP,([string])
$col6 = New-Object system.Data.DataColumn Cluster,([string])
#$col7 = New-Object system.Data.DataColumn ESXs,([string])

$table.columns.add($col1)
$table.columns.add($col2)
$table.columns.add($col3)
$table.columns.add($col4)
$table.columns.add($col5)
$table.columns.add($col6)
#$table.columns.add($col7)

$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
"Created table after $duration secs"

# Get VMs object
$vms = Get-VM | Sort -property Name

$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
"Got object list of VM's after $duration secs"

foreach ($vm in $vms)
{
	$row = $table.NewRow()
	
	$row.Name = (Get-VM -Name $vm).Name
	$row.DC = (Get-Datacenter -VM $vm).Name
	$row.OS = (Get-VMGuest -VM $vm).OSFullName
	$row.UUID = %{(Get-View $vm.Id).config.uuid}
	$row.MgmtIP =  [string]::join(" ", ((Get-VMGuest -VM $vm).IPAddress))			# Need to join potential list of IP's
	$row.Cluster = (Get-Cluster -VM $vm).Name
	
	$table.Rows.Add($row)
	"Added row for $vm"
}

$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
"Populated table after $duration secs"

$table | Format-Table
$table | Export-Csv -path result.csv

VM's with Snapshots Running

This script generates an email report of virtual machines that have snapshots running.

Be aware that there's a bug in the way Get-Snapshot handles VM's in VI4, in that for the snapshot size it tends to report the maximum size a snapshot file could become, not the actual size that it is. As a result there is a workaround to found here - http://blogs.vmware.com/vipowershell/2010/09/snapshot-size.html. I've found that this reports smaller sizes for VI3 snapshots as well (which match the actual disk usage in the few occasions I've bothered to check).

$OutputFile = "VM-Snapshot.csv"

function Log ($text) {
    [int]$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
    Write-Host "$duration secs | $text"
}

function Log-NoNewLine ($text) {
    [int]$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
    Write-Host "$duration secs | $text" -nonewline
}

$start = Get-Date

$table = New-Object system.Data.DataTable "Results"
$table.columns.add((New-Object system.Data.DataColumn Name,([string])))
$table.columns.add((New-Object system.Data.DataColumn Folder,([string])))
$table.columns.add((New-Object system.Data.DataColumn Snap,([string])))
$table.columns.add((New-Object system.Data.DataColumn Created,([datetime])))
$table.columns.add((New-Object system.Data.DataColumn Size_MB,([single])))
$table.columns.add((New-Object system.Data.DataColumn PowerState,([string])))

Log("Getting list of VMs to check...")
$VMs = Get-VM | Where {$_.PowerState -eq "PoweredOn"} | Sort -Property Name
Log ("Got list of " + ($VMs.Count) + " VMs to check")

$VMno = 0
$VMtot = $VMs.Count

foreach ($vm in $VMs) {
    $VMno = $VMno + 1
    Log-NoNewLine "[$VMno/$VMtot] $vm - "
    
    $Snaps = Get-Snapshot -VM $vm
    if ($Snaps) {
        foreach ($snap in $snaps) {
            $row = $table.NewRow()
            $row.Name = $vm.Name 
            $row.Folder = Get-Folder -Id $vm.FolderId
            $row.Snap = $snap.Name
            $row.Created = $snap.Created
            $row.Size_MB = $snap.SizeMB
            $row.PowerState = $snap.PowerState
            Write-Host $row.Snap "| Created" $row.Created "| Size" $row.Size_MB "MB | " $row.PowerState
        }
    } else {
        Write-Host "No snapshot"
    }
}

if ($table.rows.Count) {
    Log "Completed, writing out table..."
     
    $table | Format-Table
    $table | Export-Csv -path $OutputFile
    
    #Send via email
    $smtp = New-Object Net.Mail.SmtpClient -arg "smtpserver"
    $msg = New-Object Net.Mail.MailMessage
    $attach = New-Object Net.Mail.Attachment($OutputFile)
    
    $msg.From = "from"
    $msg.To.Add = "to"
    $msg.Subject = "Snapshots found running!!"
    $msg.Body = "$table.rows.Count snapshots found to be running, see attachment for further info"
    
    $smtp.Send($msg)
    $attach.Dispose()
}

Log "Completed!"

To implement the Get-Snapshot size bug fix, download the workaround file from the link above. I've placed it in a sub-folder called lib, hence I've included it into my main script by using...

. .\lib\getsnapshotsize-1.ps1

...then I've just replaced Get-Snapshot with CalculateVMSnapshotsSizeMB

VM's Effective CPU Shares

Calculates the effective relative CPU shares of VM's contained within resource pools. Can only handle resource pool depth of 1.

function Log ($text) {
    [int]$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
    Write-Host "$duration secs | $text"
}
$start = Get-Date

Log "Initialising output tables..."

$tSummary = New-Object system.Data.DataTable "Summary Results"
$tSummary.columns.add((New-Object system.Data.DataColumn Name,([string])))
$tSummary.columns.add((New-Object system.Data.DataColumn CPU_Shares,([int])))
$tSummary.columns.add((New-Object system.Data.DataColumn VMs,([int])))
$tSummary.columns.add((New-Object system.Data.DataColumn Weighted,([single])))

$tDetail = New-Object system.Data.DataTable "Detailed Results"
$tDetail.columns.add((New-Object system.Data.DataColumn Res_Pool,([string])))
$tDetail.columns.add((New-Object system.Data.DataColumn VM,([string])))
$tDetail.columns.add((New-Object system.Data.DataColumn Res_CPU_Shares,([int])))
$tDetail.columns.add((New-Object system.Data.DataColumn VM_CPU_Shares,([int])))
$tDetail.columns.add((New-Object system.Data.DataColumn Eff_VM_Shares,([single])))

Log "Getting resource pools..."
$ResPools = Get-ResourcePool

ForEach ($ResPool in $ResPools) {
    $VMs = Get-VM -Location $ResPool -NoRecursion | Where {$_.PowerState -eq "PoweredOn"}
    If (!$VMs) {         #Skip any resource pools with no VM's
        Log ("Skipping " + $ResPool.Name + " (no VMs)")
        Continue
    }
    $rSummary = $tSummary.NewRow()
    
    Log ("Analysing " + $ResPool.Name + "...")
    $rSummary.Name = $ResPool.Name
    $rSummary.CPU_Shares = $ResPool.NumCpuShares
    
    If (!$VMs.Count) {   # If only 1 VM in ResPool we don't get a normal array returned (so !VMs.Count is NULL)
        $rSummary.VMs = 1
    } else {
        $rSummary.VMs = $VMs.Count
    }
    $rSummary.Weighted = $rSummary.CPU_Shares / $rSummary.VMs
    $tSummary.Rows.Add($rSummary)
    
    # Get each VM's shares
    $totShares = 0
    $VMshares = @{}
    ForEach ($vm in $VMs) {
        $VMShares[$vm.Name] = ($vm | Get-VMResourceConfiguration).NumCpuShares
        $totShares += $VMShares[$vm.Name]
    }
    
    # Work out each VM's proportional CPU share
    ForEach ($vm in $VMs) {
        $rDetail = $tDetail.NewRow()
        
        $rDetail.Res_Pool = $ResPool.Name
        $rDetail.VM = $vm.Name
        $rDetail.Res_CPU_Shares = $ResPool.NumCpuShares
        $rDetail.VM_CPU_Shares = $VMShares[$vm.Name]
        If ($ResPool.Name -eq "Resources") {
            $rDetail.Eff_VM_Shares = $rDetail.VM_CPU_Shares
        } else {
            $rDetail.Eff_VM_Shares = $ResPool.NumCpuShares * ($rDetail.VM_CPU_Shares / $totShares)
        }
        $tDetail.Rows.Add($rDetail)
    } 
}

Log "Done! Writing results out..."
$tSummary | Export-Csv -path ESX-ResPoolInfo-Summary.csv
$tDetail | Export-Csv -path ESX-ResPoolInfo-Detail.csv
$tSummary

Ping All VM's On ESX

Useful sanity check prior to and after network level changes

$esxToFind = "MyESX*"

$ESX = get-vmhost $esxToFind
if (!$ESX) {
    Write-Host "ERROR: No ESX found with name matching expression $esxToFind" -Background Red -ForegroundColor DarkRed
    Exit
}

# Get list of VM's on $ESX
$VMs = $ESX | get-vm | Where {$_.PowerState -eq "PoweredOn"} | Sort -property Name 
$objPing = New-Object system.Net.NetworkInformation.Ping

Foreach ($VM in $VMs) {
    Write-Host $VM.Name.PadRight(20) -nonewline
    
    # Get guest's primary IP address
    $ip = (Get-VMGuest -VM $vm).IPAddress[0]
    if (!$ip) {
        Write-Host "NULL - Skipping test" -Background DarkYellow -ForegroundColor Yellow
        Continue
    }
    Write-Host $ip.PadRight(17) -nonewline
    if ($ip -eq '0.0.0.0') {
        Write-Host "Skipping" -Background DarkYellow -ForegroundColor Yellow
        Continue
    }
        
    [string]$res = $objPing.Send($ip).Status
    if ($res.CompareTo("Success")) {                    # Returns 1 if $res doesn't match "Success" !!
        Write-Host $res -BackgroundColor DarkRed -ForegroundColor Red
    } else {
        Write-Host $res -BackgroundColor DarkGreen -ForegroundColor Green
    }
}

Enable Change Block Tracking

This script enables CBT for all VM's managed by the vCentre its run on. It runs through all VM’s managed by the VC and determines which can be changed, then run through and changes them.

Based on the work by ICT-Freak found at http://ict-freak.nl/2009/12/14/powercli-enable-changed-block-tracking/

$log = "VM-EnableCBT.log"

# =====================================================================================================
# Functions...

function Log ($text) {
    [int]$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
    Write-Host "$duration secs | $text"
}

Function EnableChangeTracking{
    param($vmObj)
    $vmView = Get-View -VIObject $vmObj
    
    Log ($vmObj.Name + " - Reconfiguring VM for CBT...")
    $vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
    $vmConfigSpec.ChangeTrackingEnabled = $true
    $task = $vmView.ReconfigVM_Task($vmConfigSpec)
    Wait-Task -Task (Get-VIObjectByVIView -MORef $task) -ErrorAction "SilentlyContinue"
    Sleep 1

    Log ($vmObj.Name + " - Creating temp snapshot...")
    $snap = New-Snapshot -Name "Temp snapshot to apply CBT" -VM $vm
    
    Sleep 1

    Log ($vmObj.Name + " - Removing temp snapshot...")
    Remove-Snapshot -Snapshot $snap -Confirm:$false
    Log ($vmObj.Name + " - Completed")
}

# ======================================================================================================
# The actual script...

$start = Get-Date

Start-Transcript -Path $log
Log "Started script run at $start"

$VMsToDo = @()

$VMs = Get-VM | Sort -Property Name
foreach ($vm in $VMs) {
    if (!(($vm.ExtensionData.Config.Extraconfig |?{$_.Key -eq "ctkEnabled"}).Value) -and $vm.Version.ToString() -eq "v7") {
        $VMsToDo += $vm
        Log ($vm.Name + " - Added to ToDo list")
    } else {
        if ((($vm.ExtensionData.Config.Extraconfig |?{$_.Key -eq "ctkEnabled"}).Value)) {
            Log ($vm.Name + " - CBT already enabled")
        } elseif ($vm.Version.ToString() -ne "v7") {
            Log ($vm.Name + " - Not v7, VM hardware version needs to be upgraded !!!")
        } 
    }
            
}
Log ("Found " + $VMsToDo.Count + " to enable")

foreach ($vm in $VMsToDo) {
    EnableChangeTracking $vm
}

Log("All completed")
Stop-Transcript

VM's Recently Created

I run the following script twice a week (via a scheduled task), to generate a report of VM's recently created, as a catch-all reminder to check VM backup's, monitoring, etc etc have been set-up as required. The number of days history it searches through can either be altered in the script (the $days parameter), or passed as a command line parameter when run as a scheduled task.

The script borrows heavily from http://get-admin.com/blog/how-to/vmware/powercli-get-every-vm-added-to-vcenter-in-the-last-30-days/, which is where the real genius of the script comes from.

I use a username and password file for credentials, see Store Password Securely for more info, plus a CSV with a list of vCentres in (one column with a heading of "vc").

$UserFile = "User.fil"
$PassFile = "Pass.fil"                           # Encrypted file to store password in
$VC_List = "ESX-Check.csv"
$EmailTo = "you@domain.com"
$days = 2
$NotFoundText = "Not found/gone?"

# Library funcs
function Log ($text) {
    [int]$duration = (New-TimeSpan $start (Get-Date)).TotalSeconds
    Write-Host "$duration secs | $text"
}

$start = Get-Date
Start-Transcript -Path VMs-Created.log -Append
Log("Started script at $start hrs")

if (!$args[0]) {
    Log "No argument passed, using default of VM's created in last $days days."
} else {
    $days = $args[0]
    Log "Checking for VM's created in last $days days."
}

# Load VMware PS Snapin
Log("Loading VMware PowerShell Snapin...")
Add-PsSnapin *VMware*


# Functions ===================================================================================================================================

function Get-Container ($objVM) {
    # Returns either the containing vApp or Folder

    if ($objVM.VApp) {
        Return $objVM.VApp.Name
    } else {
        Return $objVM.Folder.Name
    }
}
    

function Get-VMsCreated ([int]$LastDays) {
    # Modified from http://get-admin.com/blog/how-to/vmware/powercli-get-every-vm-added-to-vcenter-in-the-last-30-days/

    $EventFilterSpecByTime = New-Object VMware.Vim.EventFilterSpecByTime
    if ($LastDays) {
        $EventFilterSpecByTime.BeginTime = (get-date).AddDays(-$($LastDays))
        $EventFilterSpecByTime.EndTime = get-date
    }
    $EventFilterSpec = New-Object VMware.Vim.EventFilterSpec
    $EventFilterSpec.Time = $EventFilterSpecByTime
    $EventFilterSpec.DisableFullMessage = $False
    $EventFilterSpec.Type = "VmCreatedEvent","VmDeployedEvent","VmClonedEvent","VmDiscoveredEvent","VmRegisteredEvent"
    $EventManager = Get-View EventManager
    $NewVmTasks = $EventManager.QueryEvents($EventFilterSpec)
    
    $VMs = @()
 
    Foreach ($Task in $NewVmTasks)
    {
        # If VM was deployed from a template/cloned from VM then record which template/VM.
        If ($Task.Template -and ($Task.SrcTemplate.Vm)) {
             $srcMachine = (Get-View $Task.SrcTemplate.Vm -Property name).Name
        } elseif ($Task.SourceVm.Vm) {
            $srcVM = Get-VM -Id $Task.SourceVm.Vm
            $srcMachine = (Get-Container $srcVM) + "\" + $srcVM.Name
        } Else {
            $srcMachine = $null
        }
        
        # Create output
        $vmCreated = "" | Select VC_Name, VM_Path, VM_Name, Created, ByUser, Method, Source
        $vmCreated.VC_Name = $vc.vc
        try {
            $vmCreated.VM_Path = Get-Container (Get-VM -Id $Task.Vm.Vm -errorAction stop)
            if ($vmCreated.VM_Path -eq "vm") {
                $vmCreated.VM_Path = "(root)"
            }
            
        } catch {
            Log("VM not found " + $Task.Vm.name)
            $vmCreated.VM_Path = $NotFoundText
        }
            
        #$vmCreated.VM_Path = Get-Container (Get-VM -Id $Task.Vm.Vm)
        $vmCreated.VM_Name = $Task.Vm.name
        $vmCreated.Created = $Task.CreatedTime
        $vmCreated.ByUser = [regex]::Replace($Task.UserName, "YourDomain\\", "")
        $vmCreated.Method = $Task.gettype().name     # VmDeployedEvent - from template, VmClonedEvent - from existing VM, VmCreatedEvent - from scratch/Convertor import
        $vmCreated.Source = $srcMachine
        $VMs += $vmCreated
    } 
    
    $VMs
}

# Business =================================================================================================================================

# Load list of VC's
try {
    $VCs = Import-CSV $VC_List
} catch {
    Log("ERROR: Failed to load list of vC's")
    Exit
}

# Load password credential from encrypted file
$pass = Get-Content $PassFile | ConvertTo-SecureString
$user = Get-Content $UserFile
$cred = New-Object System.Management.Automation.PsCredential($user, $pass)

# Disconnect any existing VI Server sessions (otherwise you can end up with duplicate VM's)
if ($DefaultVIServers.Count) {
    Log("Disconnect existing vCentre server connections...")
    Disconnect-VIServer -Server * -Force -Confirm:$false
}

$VMsCreated = @()

# Get VM's from each VC
foreach ($vc in $VCs) {
    #if ($vc.vc -eq "lab-manager") {
    #    Log("Skipping " + $vc.vc)
    #    Continue
    #}
    Log("Checking for VMs on " + $vc.vc)
    try {
        $VCconn = Connect-VIServer -Server $vc.vc -Credential $cred -errorAction stop
    } catch [VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.InvalidLogin] {
        Log("ERROR: Unable to connect to " + $vc.vc + ", invalid logon error !!")
        Log("Abandoning further script processing in order to prevent potential account lockout.")
        Exit
    } catch {
        Log("ERROR: Unable to connect to " + $vc.vc)
        Log($_)
        Continue
    }
    
    $VMsCreated += Get-VMsCreated $days
    Log("VMs created count now " + $VMsCreated.Length)
    Disconnect-VIServer -Server $VCconn -Confirm:$false
}

$VMsCreated

# Create HTML for email
[string]$msgHTML = "<!-- Genenerated by $ScriptName at " + (Get-Date).ToString() + " hrs -->`n"
$msgHTML += "<html xmlns='http://www.w3.org/TR/REC-html40'>`n`n<head>`n<meta http-equiv=Content-Type content='text/html; charset=uk'`n>"
$msgHTML += "<meta name=Generator content='VMs Recently Created script'>`n<meta name=Author content='Simon Strutt'>`n</head>`n`n"
$msgHTML += "<body style='font-family: arial; font-size: 12;'>`n"
$msgHTML += "The following VM's have been created in the last $days days (ie since " + ((Get-Date).AddDays(-$days)).ToString("HH:mm \h\r\s\, ddd dd-MMM-yyyy") + "),"
$msgHTML += " and backup jobs may need to be updated as a result.<br><br>`n"
if (!$VMsCreated.Length) {
    $msgHTML += "None !!"
} else {
    $msgHTML += "<table border=1 style='border-width: 1px; border-spacing: 1; border-color: black; background-color: #faf0e6; font-family: arial; font-size: 11;'>`n"
    $msgHTML += "<tr style='border-width: 2px; border-color: black; background-color: #fcf7f8;'><th>vCentre<th title='vApp or parent folder'>Container<th>VM<th>Created<th>By User<th>Method<th>Source`n"
    foreach ($vm in $VMsCreated) {
        if ($vm.Created) {
            $msgHTML += "<tr><td>" + $vm.VC_Name
            if ($vm.VM_Path -eq $NotFoundText) {
                $msgHTML += "<td>" + $vm.VM_Path + "<td>" + $vm.VM_Name
            } else {
                $msgHTML += "<td style='font-weight: bold;'>" + $vm.VM_Path + "<td style='font-weight: bold;'>" + $vm.VM_Name
            }
            $msgHTML += "<td style='text-align: right;'>" + ($vm.Created).ToString("HH:mm ddd dd-MM-yy") + "<td>" + $vm.ByUser + "<td>" + $vm.Method + "<td>" + $vm.Source + "`n"
        }
    }
    $msgHTML += "</table><br><br>`n"
    $msgHTML += "Generated by script: " + ($MyInvocation.MyCommand.Name) + "<br>`n"
    $msgHTML += "Sent from machine  : $env:computername</body></html>"
}

# Send email
$smtp = New-Object Net.Mail.SmtpClient -arg "mailrelay.uk.michaelpage.local"
$msg = New-Object Net.Mail.MailMessage
        
$msg.From = "VI-Mgmt@sandfordit.com"
$msg.To.Add($EmailTo)
$msg.Subject = "VMs created in last $days days"
$msg.IsBodyHTML = $true
$msg.Body = $msgHTML
  
$smtp.Send($msg)

Stop-transcript

Add Attributes to VMs from CSV

You have the option in vCentre to create your own custom attributes to any of the standard objects. This can be incredibly useful if you have a sprawling estate of VM's to manage for various different parts of your company, as you can record information like Owning Dept., Product Family etc against your VM's.

The only problem is that you then have fields that have to be manually updated (that and the fact that the info is only held in a VM's vCentre database records, not its VMX, so if you have to re-add a machine to vCentre, you'll lose the custom attributes you populated.

The following script allows you to update VM attributes from a CSV file (which can be very convenient if you've just deployed 20 VM's for a project).

<#
=============================================================================================
 VM Attribute Updater
=============================================================================================
 Simon Strutt  -  March 2010
=============================================================================================

Version 1
 - Initial creation!
 
Version 2
 - Added $DontBlank option
 
=============================================================================================
#>

$start = Get-Date
$CSVinput = "VM-Attribs-ToUpdate.csv"
$Attributes = "Function", "Owner", "RequestID", "Template", "BackupFrequency"      # Custom attributes (fields should already exist in vCentre), should be column headings in CSV

$UserFile = "User.fil"
$PassFile = "Pass.fil"

$DontBlank = 1     # Don't wipe existing populated fields, with blank stuff from spreadsheet

# Include library files
. .\lib\Standard.ps1

Start-Transcript -Path VM-Attrib-Update.log -Append
Log "Started script run at $start" 

try {
    $ToUpdate = Import-CSV $CSVinput -errorAction Stop
} catch {
    Log "ERROR: Failed to load CSV Update file($CSVinput)"
    Log $_
    Exit
}
$ToUpdate

# Load password credential from encrypted file
try {
    $pass = Get-Content $PassFile -errorAction Stop | ConvertTo-SecureString 
    $user = Get-Content $UserFile -errorAction Stop
    $cred = New-Object System.Management.Automation.PsCredential($user, $pass)
} catch {
    Log "ERROR: Failed to load credentials to use"
    Log $_
    Exit
}

# Disconnect any existing VI Server sessions
if ($DefaultVIServers.Count) {
    Log("Disconnect existing vCentre server connections...")
    Disconnect-VIServer -Server * -Force -Confirm:$false
}

$VCs = $ToUpdate | Select -Property vc -Unique

if (!$VCs.Count) {
    Log ("Found " + $ToUpdate.Count + " VM's to update, accross 1 vCentre")
} else {
    Log ("Found " + $ToUpdate.Count + " VM's to update, accross " + $VCs.Count + " vCentres")
}

foreach ($vc in $VCs) {
    try {
        Log("Connecting to " + $vc.vc)
        $VCconn = Connect-VIServer -Server $vc.vc -Credential $cred -errorAction Stop
    } catch [VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.InvalidLogin] {
        Log("Unable to connect to vCentre, invalid logon error !!")
        Log("Abandoning further script processing in order to prevent potential account lockout.")
        Break
    } catch {
        Log("Unable to connect to vCentre - " + $_)
        Continue
    }
    
    # Check attribute fields exist
    $ExistingAttribs = Get-CustomAttribute -TargetType VirtualMachine
    foreach ($attrib in $Attributes) {
        if (!($ExistingAttribs |?{$_.Name -eq $attrib})) {
            Log("Adding attribute to VC: $attrib")
            New-CustomAttribute -Name $attrib -TargetType VirtualMachine
        }
    }
    
    foreach ($vm in $ToUpdate) {
        if ($vc.vc -ne $vm.vc) {
            Continue
        }
        
        try {
            if ($vm.Folder) {
                $objVM = Get-VM -Location $vm.Folder -Name $vm.vm -errorAction Stop
            } else {
                $objVM = Get-VM -Name $vm.vm -errorAction Stop
            }
        } catch {
            Log ("ERROR: Failed to find VM: " + $vm.vm + " in folder: " + $vm.Folder)
            Continue
        }
        
        if ($objVM.Count -gt 1) {
            Log ("ERROR: Ambiguous VM - found " + $objVM.Count + " possibles for: " + $vm.vm + " in folder: " + $vm.Folder)
            Continue
        }          
        
        $ExistingAnnotats = Get-Annotation -Entity $objVM
        foreach ($attrib in $Attributes) {
            if ($vm.$attrib -eq ($ExistingAnnotats |?{$_.Name -eq $attrib} | Select -ExpandProperty Value)) {
                Log ($vm.vm + " $attrib already set/correct")
                Continue
            }

            if (!$vm.$attrib.Length) {              # CSV field blank
                if ($DontBlank) {                   #  but don't blank
                    Log ($vm.vm + ": $attrib no change")
                } else {
                    Log ($vm.vm + ": $attrib ---> BLANK")
                    Set-Annotation -Entity $objVM -CustomAttribute $attrib -Value $vm.$attrib
                }
            } else {
                Log ($vm.vm + ": $attrib ---> " + $vm.$attrib)
                Set-Annotation -Entity $objVM -CustomAttribute $attrib -Value $vm.$attrib
            }
        }
    }
    Disconnect-VIServer -Server $VCconn -Confirm:$false
}
Stop-Transcript

Migrate VMs Between vCentres

The following script will migrate virtual machines from one vCentre to another, so long as both have a shared datastore.

<# ========================================================================================================================================
  Virtual Machine IntraVC Migrater
  =========================================================================================================================================
  Simon Strutt        Jan 2012
  =========================================================================================================================================

  Moves VM's from one vCentre to another (requires VMs to be on storage visible to both vCentres)

 Version 1 
  - Initial creation
  
  ========================================================================================================================================#>
  
$SourceVC = "vcentreA"
$SourceVapp = "vApp"

$DestVC = "vcentreB"
$DestCluster = "Cluster"
$DestFolder = "Folder"

$UserFile = "User.fil"
$PassFile = "Pass.fil"


# Functions ---------------------------------------------------------------------------------

function Log ($text) {
    $stamp = (Get-Date).ToString("HH:mm:ss.fff")
    Write-Host "$stamp | $text"
}

# Business part of script -------------------------------------------------------------------

Start-Transcript -Path VM-Move-VC.log -Append

# Load password credential from encrypted file
try {
    $pass = Get-Content $PassFile -errorAction Stop | ConvertTo-SecureString 
    $user = Get-Content $UserFile -errorAction Stop
    $cred = New-Object System.Management.Automation.PsCredential($user, $pass)
} catch {
    Log "ERROR: Failed to load credentials to use"
    Log $_
    Exit
}

# Disconnect any existing VI Server sessions
if ($DefaultVIServers.Count) {
    Log("Disconnect existing vCentre server connections...")
    Disconnect-VIServer -Server * -Force -Confirm:$false
}

# Connect to source VC
try {
    Log "Connecting to $SourceVC"
    $VCconn = Connect-VIServer -Server $SourceVC -Credential $cred -errorAction Stop
} catch {
    Log("Unable to connect to vCentre - " + $_)
    Exit
}

# Get list of VMs to move
$VMs = Get-VM -Location (Get-vApp $SourceVapp) | Sort
Log "VMXs to reregister..."
$VMs2Move = @()
foreach ($vm in $VMs) {
    $vm2move = "" | Select Name, Path
    $vm2move.Name = $vm.name
    $vm2move.Path = $vm.ExtensionData.Config.Files.VmPathName
    $VMs2Move += $vm2move
    Log ($vm2move.Name + " " + $vm2move.Path)
}
#$VMs | Get-View | %{$_.Config.Files.VmPathName} | Sort

# Unregister VMs
foreach ($vm in $VMs) {
    Log ("Unregister " + $vm.Name)
    Remove-VM -VM $vm -DeletePermanently:$false -Confirm:$false
}

Disconnect-VIServer -Server $VCconn -Confirm:$false

# Connect to destination VC
try {
    Log "Connecting to $DestVC"
    $VCconn = Connect-VIServer -Server $DestVC -Credential $cred -errorAction Stop
} catch {
    Log("Unable to connect to vCentre - " + $_)
    Exit
}

# Register VMs
foreach ($vm in $VMs2Move) {
    Log ("Register " + $vm.Name)
    New-VM -VMFilePath $vm.Path -VMHost (Get-Cluster $DestCluster | Get-VMHost | Get-Random) -Location (Get-Folder $DestFolder)
}

Disconnect-VIServer -Server $VCconn -Confirm:$false
Stop-Transcript