Difference between revisions of "Script Extracts and Examples (PowerCLI)"

From vwiki
Jump to navigation Jump to search
m (Added link to ESX/HBA.NIC driver script for incoming external link)
(Deleted page contents, redirect to Category)
Line 1: Line 1:
* '''Looking for examples of how to launch from command line or scheduler - see [[Getting_Started_(PowerCLI)#Scheduling|Getting_Started_(PowerCLI) > Scheduling]]'''
#REDIRECT [[:Category:Category:PowerCLI]]
* '''Looking for script to get ESX BIOS version - see [[ESX_Script_Extracts_and_Examples#ESX_BIOS,_NIC_and_HBA_Driver_Versions|ESX_BIOS,_NIC_and_HBA_Driver_Versions]]'''
 
{{Depreciated|category=PowerCLI}}
 
== Virtual Machine ==
=== VM's with Host and Cluster List ===
<source lang="powershell">
$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
}
</source>
 
=== VM's Inventory CSV ===
 
<source lang="powershell">
$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
</source>
 
=== VM's with Snapshots Running ===
Be aware that there's a bug in the way <code>Get-Snapshot</code> 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).
 
<source lang="powershell">
$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!"
</source>
 
To implement the <code>Get-Snapshot</code> size bug, download the workaround file from the link above.  I've placed it in a sub-folder called <code>lib</code>, hence I've included it into my main script by using...
<source lang="powershell">
. .\lib\getsnapshotsize-1.ps1
</source>
...then I've just replaced <code>Get-Snapshot</code> with <code>CalculateVMSnapshotsSizeMB</code>
 
=== 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.
 
<source lang="powershell">
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
</source>
 
=== Ping All VM's On ESX ===
Useful sanity check prior to and after network level changes
<source lang="Powershell">
$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
    }
}
</source>
 
=== 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/
 
<source lang="Powershell">
$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
</source>
 
=== 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 <code>$days</code> 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|Store Password Securely]] for more info, plus a CSV with a list of vCentres in (one column with a heading of "vc").
 
<source lang="Powershell">
$UserFile = "User.fil"
$PassFile = "Pass.fil"                          # Encrypted file to store password in
$VC_List = "ESX-Check.csv"
$EmailTo = "you@domain.com"
$MailServer = "mail.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 $MailServer
$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
</source>
 
=== 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).
 
<source lang="Powershell">
<#
=============================================================================================
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
</source>
 
=== Migrate VMs Between vCentres ===
The following script will migrate virtual machines from one vCentre to another, so long as both have a shared datastore.
 
<source lang="Powershell">
<# ========================================================================================================================================
  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
</source>
 
== ESX ==
=== ESX NIC Info ===
Provides a list of all vmnic speeds for ESX's connected to vCentre
<source lang="powershell">
$ESXs = Get-VMHost
 
Foreach ($esx in $ESXs) {
    Write-Host $esx.Name $esx.State
    $pNICs = (Get-VMHost -Name "MyESX*" | Get-View).Config.Network.Pnic
 
    $result = @{}
    Foreach ($pNIC in $pNICs) {
        $result[$pNIC.Device] = $pNIC.LinkSpeed.SpeedMB
    }
    $result = $result.GetEnumerator() | Sort-Object -Property Name
    $result
    Write-Host
}
</source>
 
=== ESX Log Tail ===
'''''Note that the below only seems to work for ESX3.5, I gave up trying to get it work for both v3 and v4, its a lot easier to just enable [[ESX#ESXi_Tech_Support_Mode|ESXi Tech Support Mode]] and tail the log'''''
 
Pointless for ESX (you can just use tail from the Service Console on the [[ESX#Useful_paths_.2F_logfiles|appropriate log]]), but a godsend it you're using ESXi (and have got used to tailing ESX logs).
<source lang="powershell">
$PollInterval = 1000    # msec (default - can be overrided by user)
 
#Get ESX to poll
$ESXs = Get-VMHost | Sort-Object -Property Name
Write-Host "`nWhich ESX's log do you want to tail?"
$num = 0
foreach ($esx in $ESXs) {
    Write-Host "[$num]" $ESXs[$num].Name
    $num++
}
$num = 0
$num = Read-Host "? [$num] "
$ESX = Get-VMHost $ESXs[$num]
Write-Host " "
 
# Get Log keys to look at
$keys = Get-LogType -VMHost $ESX
Write-Host "`nWhich log do you want to tail?"
$num = 0
foreach ($key in $keys) {
    Write-Host "[$num]" $keys[$num].Key
    $num++
}
$num = 0
$num = Read-Host "? [$num] "
$logKey = $keys[$num].Key
 
# Set polling interval
$PollInterval = Read-Host "`nWhat polling interval do you want to use (msec)? [$PollInterval]"
 
Write-Host "`nStarting log tail (press Ctrl+C to escape)...`n"
 
# First pass of log
$ESXLog = Get-Log $LogKey -VMHost $ESX
 
#Display last $lines
$LineNo = $ESXLog.LastLineNum - $lines
While ($LineNo -le $ESXLog.LastLineNum) {
    Write-Host $ESXlog.Entries[$LineNo]
    $LineNo++
}
 
#Polling loop
While (1) {
    Start-Sleep -Milliseconds $PollInterval
    try {
        $ESXLog = Get-Log $logKey -VMHost $ESX -StartLineNum $LineNo
    } catch {
        if (Select-String -InputObject $_ -pattern "The 0 argument is less than the minimum allowed range of 1" -Quiet) {
            Write-Host "ESX log is rolling over..."
            $ESXLog = Get-Log $logKey -VMHost $ESX
        } else {
            Write-Host "UNEXPECTED ERROR: $_"
            Exit
        }
    }
    $ESXlog.Entries
    $LineNo = $ESXLog.LastLineNum
}
</source>
 
=== ESX Discovered Networks Hint ===
This function provides the discovered network hints for the network interface its passed.  Bear in mind that its just a hint, for an ESX to be aware of a particular vLAN it needs to see traffic.  If there's no traffic it will show nothing.
 
Adapted from the following article on the VMware site blog http://blogs.vmware.com/vipowershell/2010/02/how-to-find-out-what-vlans-your-esx-hosts-can-really-see.html
 
<source lang="powershell">
function Get-ObservedIPRange {
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true,HelpMessage="Physical NIC from Get-VMHostNetworkAdapter")]
[VMware.VimAutomation.Client20.Host.NIC.PhysicalNicImpl]
$Nic
)
 
process {
$hostView = Get-VMHost -Id $Nic.VMHostId | Get-View -Property ConfigManager
$ns = Get-View $hostView.ConfigManager.NetworkSystem
$hints = $ns.QueryNetworkHint($Nic.Name)
 
foreach ($hint in $hints) {
foreach ($subnet in $hint.subnet) {
$observed = New-Object -TypeName PSObject
$observed | Add-Member -MemberType NoteProperty -Name Device -Value $Nic.Name
$observed | Add-Member -MemberType NoteProperty -Name VlanId -Value $subnet.VlanId
$observed | Add-Member -MemberType NoteProperty -Name IPSubnet -Value $subnet.IPSubnet
$observed | Add-Member -MemberType NoteProperty -Name BitRatePerSec -Value $nic.BitRatePerSec
Write-Output $observed
}
}
}
}
 
# Example use:
 
 
$result = Get-VMHost MyESX* | Get-VMHostNetworkAdapter | Where {$_.Name -Match ".*vmnic*"} | Get-ObservedIPRange | Sort-Object -Property Device, VlanId
$result | Export-Csv -path ESX-vLANs-Observed.csv
 
</source>
 
=== ESX CDP Info ===
Adapted from posted by LucD on VMware forum http://communities.vmware.com/message/977487
<source lang="powershell">
Get-VMHost | Sort -Property Name | %{Get-View $_.ID} | %{$esxname = $_.Name; Get-View $_.ConfigManager.NetworkSystem} | %{
  foreach($physnic in $_.NetworkInfo.Pnic){
    $pnicInfo = $_.QueryNetworkHint($physnic.Device)
    foreach($hint in $pnicInfo){
      Write-Host $esxname $physnic.Device $hint.connectedSwitchPort.DevId $hint.connectedSwitchPort.PortId
    }
  }
}
</source>
 
=== ESX BIOS, NIC and HBA Driver Versions ===
Getting BIOS, NIC and HBA driver versions is dependant on what's reported to the ESX by the hardware vendor's CIM provider.  This script was written using various HP servers and may well only work for them.
 
<source lang="Powershell">
# ===============================================================
# ESX Inventory Getter
# ===============================================================
# Simon Strutt        November 2010
# ===============================================================
#
# Version 1
# - Initial Creation
#
# Version 2 - Dec 2010
# - Added BiosVer, CpuCores, CpuModel, HbaModel, NicModel
# - Bugfix: Corrected VC connection handling
# - Removed dependancy on obseleted properties (from upgrade to PowerCLI v4.1.1)
#
# Limitations
# - Tested on HP DL380 (G7), BL465 (G7), BL495 (G6), BL685 (G5)
# - Supports 1 distinct HBA model, 2 distinct NIC models
#
# ================================================================
 
$start = Get-Date
$OutputFile = "ESXs.csv"
$VC_List = "ESX-Check.csv"
$UserFile = "User.fil"
$PassFile = "Pass.fil"                          # Encrypted file to store password in
$Results = @()
 
# Include library files
. .\lib\Standard.ps1
 
Start-Transcript -Path ESX-Inventory.log
Log "Started script run at $start"
 
# Function-U-like ===================================================================================
 
Function Get-NicDriverVersion ($view, $driver) {
    # Gets the CIM provided driver version (tallies up driver name with CIM software component)
    $result = ($view.Runtime.HealthSystemRuntime.SystemHealthInfo.NumericSensorInfo | Where {$_.Name -like $driver + " driver*"} | Get-Unique).Name
    $result = ([regex]::Matches($result, "(\b\d)(.*)(?=\s)"))[0].Value        # HP regex to extract "2.102.440.0" for example
    $result
}
 
# ===================================================================================================
 
# 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)
 
foreach ($vc in $VCs) {
   
    # Connect to VC
    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
    }
   
    # Get ESX objects
    Log("Getting list of ESXs to check on " + ($vc.vc) + "...")
    $ESXs = Get-VMHost -Server $vc.vc | Sort -property Name
    Log("Got list of " + ($ESXs.Count) + " ESXs to check")
   
    ForEach ($ESX in $ESXs) {
        $row = "" | Select VC, Cluster, Name, IP, Version, Make, Model, BiosVer, CpuCores, CpuModel, HbaModel, HbaDriver, HbaDriverVer, Nic1Model, Nic1Driver, Nic1DriverVer, Nic2Model, Nic2Driver, Nic2DriverVer
        Log($ESX.Name)
       
        # Store objects which get re-used for efficiency
        $ESXview = Get-View -VIObject $ESX
        $VMHostNetworkAdapter = Get-VMHostNetworkAdapter -VMHost $esx
       
        # Get the basics
        $row.VC = $vc.vc
        $row.Cluster = $ESX.Parent
        $row.Name = $ESX.Name.Split(".")[0]
        $row.IP =  ($VMHostNetworkAdapter | Where {$_.ManagementTrafficEnabled -eq "True" -or $_.DeviceName -like "vswif*" -or $_.Name -eq "vmk0"}).IP
        $row.Version = $ESX.Version + " (" + $ESX.Build + ")"
        $row.Make = $ESX.Manufacturer
        $row.Model = $ESX.Model
       
        # Now onto the more ESX version / hardware specfic stuff (new versions of hardware will require further work below)
       
        # BIOS
        if ($ESXView.Hardware.BiosInfo) {    # Works on some systems
            $row.BiosVer = $ESXview.Hardware.BiosInfo.BiosVersion + " " + $ESXview.Hardware.BiosInfo.ReleaseDate.ToString("yyyy-MM-dd")  # Need date for HP servers as they use same version no for diff versions!
        } else {
            $row.BiosVer = ($ESXview.Runtime.HealthSystemRuntime.SystemHealthInfo.NumericSensorInfo | Where {$_.Name -like "*BIOS*"}).Name
            $row.BiosVer = ([regex]::Matches($row.BiosVer, "[A-Z]\d{2} 20\d{2}-\d{2}-\d{2}"))[0].Value          # HP regex to extract "A19 2010-09-30" for example
        }
       
        # CPU info
        $row.CpuCores = $ESXview.Hardware.CpuInfo.NumCpuCores.ToString() + " (" + $ESXview.Hardware.CpuPkg.count + "x" + $ESXview.Hardware.CpuPkg[0].ThreadId.count + ")"
        $row.CpuModel = $ESX.ExtensionData.Summary.Hardware.CpuModel
       
        # HBA info (script assumes only one HBA model in use)
        $row.HbaModel = ($ESX.ExtensionData.Config.StorageDevice.HostBusAdapter | Where {$_.Key -like "*FibreChannel*"})[0].Model
        $row.HbaDriver = ($ESX.ExtensionData.Config.StorageDevice.HostBusAdapter | Where {$_.Key -like "*FibreChannel*"})[0].Driver    # Includes version for ESX3
        $row.HbaDriverVer = ($ESXview.Runtime.HealthSystemRuntime.SystemHealthInfo.NumericSensorInfo | Where {$_.Name -like "*" + $row.HbaDriver + "*"}).Name
        $row.HbaDriverVer = ([regex]::Matches($row.HbaDriverVer, "(\b\d)(.*?)(?=\s)"))[0].Value
       
        # NIC info (script only supports two distinct NIC types)
        $nics = $ESXview.Hardware.PciDevice | Where {$_.ClassId -eq 512} | Select VendorName, DeviceName, Id | Sort-Object -Property DeviceName -Unique
        if ($nics.count) {
            $row.Nic1Model = $nics[0].VendorName + " " + $nics[0].Devicename
            $row.Nic2Model = $nics[1].VendorName + " " + $nics[1].Devicename
           
            # Use PCI ID to match up NIC hardware type with driver name
            $row.Nic1Driver = ($VMHostNetworkAdapter | Where {$_.ExtensionData.Pci -eq $nics[0].Id}).ExtensionData.Driver
            $row.Nic2Driver = ($VMHostNetworkAdapter | Where {$_.ExtensionData.Pci -eq $nics[1].Id}).ExtensionData.Driver
           
            $row.Nic1DriverVer = Get-NicDriverVersion $ESXview $row.Nic1Driver
            $row.Nic2DriverVer = Get-NicDriverVersion $ESXview $row.Nic2Driver
        } else {
            # Annoyingly, $nics is not an array if there's only one NIC type, hence seperate handling
            $row.Nic1Model = $nics.VendorName + " " + $nics.Devicename
            $row.Nic1Driver = ($VMHostNetworkAdapter | Where {$_.ExtensionData.Pci -eq $nics.Id}).ExtensionData.Driver
            $row.Nic1DriverVer = Get-NicDriverVersion $ESXview $row.Nic1Driver
        }
       
        $Results = $Results + $row
    }
    Disconnect-VIServer -Server $VCconn -Confirm:$false
}
$Results | Format-Table *
Log("Writing results to $OutputFile...")
$Results | Export-Csv -path $OutputFile -NoTypeInformation
 
Log("All completed")
Stop-Transcript
       
</source>
 
=== Daily ESX Up/Down Check ===
Simple script that runs every morning just before I start work to provide a basic sanity check email to glance through over the first tea of the day.
 
See the following page for details on how to [[Power_Shell#Store_Password_Securely|Store Password Securely]] (as I have done, loaded though the files specified by <code>$UserFile</code> and <code>$PassFile</code> in the script below).
 
<source lang="Powershell">
#############################################################################################
#
#  ESX Checker
#
#############################################################################################
#
# By Simon Strutt
#
# Version 1 - Aug 10
# - Initial creation
#
# Version 2 - Oct 10
# - Fixed VIServer handling (was never good but stuffed by upgrade to PowerCLI 4.1)
# - Improved email in case of vCentre connection failure
#
# Version 2.1 - Nov 10
# - Minor text changes
#
# Version 2.2 - Nov 10
# - Added InvalidLogon catch to vCentre logon to prevent a/c lockout
#
# Version 3 - Dec 10
# - Truncated displayed ESX hostname (stripped domain)
# - Added "since <datetime>" info to ESX's that aren't in a good state
# - Bugfix: Incorrect email config used when VC list config file load failed
#
# Version 3.1 Jan 11
# - Workaround: Powershell/MS DateTime object returns incorrect (US) date format
#
# Version 3.2 Jan 11
# - Bugfix: Extraneous " hrs" on end of v3.1 fixed datetimes
#
#############################################################################################
 
$DateFormat = "%R hrs, %a %d %b %Y"
 
$EmailBody = "Results of ESX status check as of " + (get-date -uFormat "$DateFormat.`n`n")
$EmailRecipients = "person1@domain.com,person2@domain.com"
$EmailSender = "VI-Mgmt@domain.com"
 
$UserFile = "User.fil"
$PassFile = "Pass.fil"
 
$smtp = New-Object Net.Mail.SmtpClient -arg "smtp-server.domain.com"
$VCAlertToSend = 0
$ESXAlertToSend = 0
$start = Get-Date
 
try {
    $ToCheck = Import-CSV "ESX-Check.csv"
} catch {
    $EmailBody = "ERROR: Failed to load config file, no checks have been done.`n"
    $EmailBody += $_
    $smtp.Send($EmailSender, $EmailRecipients, "ESX-Check: Check failed", $EmailBody)
    Exit
}
 
$pass = Get-Content $PassFile -errorAction Stop | ConvertTo-SecureString
$user = Get-Content $UserFile -errorAction Stop
$cred = New-Object System.Management.Automation.PsCredential($user, $pass)
 
foreach ($vc in $ToCheck) {
    Write-Host "Checking ESXs on" $vc.vc
    $VChasBadESX = 0
    try {
        $VCconn = Connect-VIServer -Server $vc.vc -Credential $cred -NotDefault -errorAction "Stop"
    } catch [VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.InvalidLogin] {
        $EmailBody += ($vc.vc).ToUpper() + "`t" + "Unable to connect to vCentre, invalid logon error !!`n`n"
        $EmailBody += "Abandoning further script processing in order to prevent potential account lockout.`n"
        $VCAlertToSend = 1
        Break
    } catch {
        $EmailBody += ($vc.vc).ToUpper() + "`t" + "Unable to connect to vCentre, ESX's not checked !!`n"
        $VCAlertToSend = 1
        Continue
    }
    $ESXs = Get-VMHost -Server $vc.vc | Where {$_.State -ne "Connected"} | Sort -property Name
    if ($ESXs) {
        foreach ($ESX in $ESXs) {
            # Attempt to work out when ESX went into its bad state
            Switch ($ESX.State) {
                NotResponding {
                    $events = Get-VIEvent -Entity $ESX -Types Error | Where {$_.fullFormattedMessage -like "*is not responding*"}
                }
                Maintenance {
                    $events = Get-VIEvent -Entity $ESX -MaxSamples 1000 | Where {$_.fullFormattedMessage -like "*has entered maintenance mode*"}
                }
                Disconnected {
                    $events = Get-VIEvent -Entity $ESX -MaxSamples 1000 | Where {$_.fullFormattedMessage -like "*Disconnected from*"}
                }
                Default {
                    $events = 0
                }
            }
            If ($events) {
                $SinceString = " since " + (Get-Date $events[0].CreatedTime -uFormat $DateFormat)
            } else {
                $SinceString = ""
            }
                   
            $EmailBody += $vc.vc + "`t" + ($ESX.Name.Split(".")[0]).ToUpper() + "`t" + $ESX.State + $SinceString + "`n"
            $ESXAlertToSend = 1
            $VChasBadESX = 1
        }
    }
    if (!$VChasBadESX) {
        $EmailBody += $vc.vc + "`t" + "All good`n"
    }
    Disconnect-VIServer -Server $VCconn -Confirm:$false
}
 
$end = Get-Date
 
$EmailBody += "`n`n`nCheck started      : " + (Get-Date $start -uFormat $DateFormat) + "`n"
$EmailBody += "Check finished    : " + (Get-Date $end -uFormat $DateFormat) + "`n"
$EmailBody += "Generated by script: " + ($MyInvocation.MyCommand.Name) + "`n"
$EmailBody += "Sent from machine  : $env:computername"
 
$EmailBody
Write-Host "Sending email..."
if ($VCAlertToSend) {
    $smtp.Send($EmailSender, $EmailRecipients, "ESX-Check: Unable to connect to some/all vCentres", $EmailBody)
} elseif ($ESXAlertToSend) {
    $smtp.Send($EmailSender, $EmailRecipients, "ESX-Check: Some ESX's are NOT CONNECTED", $EmailBody)
} else {
    $smtp.Send($EmailSender, $EmailRecipients, "ESX-Check: All connected OK", $EmailBody)
}
if (-not $?) {
    Write-Host "SMTP send failed!!"
    Start-Sleep(30000)
}
</source>
 
== Storage ==
=== VM's with Datastores List ===
List of Virtual Machines, and their datastores (with usage)
<source lang="powershell">
$datastoreExp = @{N="Datastore"; E={ ($_ | get-datastore | select-object -first 1).Name }}
$diskSizeExp = @{N="Total Disk"; E={ ($_ | get-harddisk | measure-object -property CapacityKB -sum).Sum }}
get-vm | select Name, $datastoreExp, $diskSizeExp | sort -property Datastore,"Total Disk"
</source>
 
=== VM Storage Usage ===
'''Total''' storage usage (including any snapshots, logs, etc, etc), not just the VMDK files.
Adapted from post by Arnim van Lieshout http://www.van-lieshout.com/2009/07/how-big-is-my-vm/
 
<source lang="powershell">
function Get-VMDiskUsage($vm2do)
{
    #Initialize variables
    $VMDirs =@()
    $VMSize = 0
 
    $searchSpec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
    $searchSpec.details = New-Object VMware.Vim.FileQueryFlags
    $searchSpec.details.fileSize = $TRUE
 
    Get-View -VIObject $vm2do | % {
        #Create an array with the vm's directories
        $VMDirs += $_.Config.Files.VmPathName.split("/")[0]
        $VMDirs += $_.Config.Files.SnapshotDirectory.split("/")[0]
        $VMDirs += $_.Config.Files.SuspendDirectory.split("/")[0]
        $VMDirs += $_.Config.Files.LogDirectory.split("/")[0]
        #Add directories of the vm's virtual disk files
        foreach ($disk in $_.Layout.Disk) {
            foreach ($diskfile in $disk.diskfile){
                $VMDirs += $diskfile.split("/")[0]
            }
        }
        #Only take unique array items
        $VMDirs = $VMDirs | Sort | Get-Unique
 
        foreach ($dir in $VMDirs){
            $ds = Get-Datastore ($dir.split("[")[1]).split("]")[0]
            $dsb = Get-View (($ds | get-view).Browser)
            $taskMoRef  = $dsb.SearchDatastoreSubFolders_Task($dir,$searchSpec)
            $task = Get-View $taskMoRef
 
            while($task.Info.State -eq "running" -or $task.Info.State -eq "queued"){$task = Get-View $taskMoRef }
            foreach ($result in $task.Info.Result){
                foreach ($file in $result.File){
                    $VMSize += $file.FileSize
                }
            }
        }
    }
 
    # VM disk usage in GB
    $VMSize/1048576000
}
Get-VMDiskUsage (Get-VM "MyVM")
</source>
 
=== ESX Storage Events ===
Creates a CSV of storage events (eg path up/downs).  Useful in order to be analyse storage events, for example if you need to tally up to SAN logs etc.  Note the <code> get-VIEvent </code> is limited to 1000 results, which will typically get 1 - 2 days worth depending on your infrastructure.  To get a longer history would  require multiple calls to <code> get-VIEvent </code> using the <code>-Start</code> and <code>-Finish</code> parameters
<source lang="Powershell">
$OutputFile = "ESX-EventsStorage.csv"
 
$Results = @()
$events = Get-VIEvent -MaxSamples 1000 | Where {$_.EventTypeID -like "esx.*.storage.*"}
foreach ($event in $events) {
    $row = "" | Select Date, Host, EventID, Device, Datastore, Path
    $row.Date = $event.CreatedTime
    $row.Host = $event.Host.Name.Split(".")[0]
    $row.EventID = $event.EventTypeID
    # Allow for Argument Keys being mixed up between "problem" aka fault start and "clear" aka fault end events
    $row.Device = $event.Arguments[0].Value
    if ($row.EventID -like "esx.problem.*") {
        $row.Datastore = $event.Arguments[2].Value
        $row.Path = $event.Arguments[1].Value
    } elseif ($row.EventID -like "esx.clear.*") {
        $row.Datastore = $event.Arguments[1].Value
        $row.Path = $event.Arguments[2].Value
    } else {
        Write-Host "ERROR: Unexpected EventTypeID - " $row.EventID
    }
    $Results = $Results + $row
}
$Results | Format-Table *
$Results | Export-Csv -path $OutputFile -NoTypeInformation
</source>
 
=== ESX Datastore to LUN Mapping (ESX3) ===
Its bizarrely difficult to be able to map VMware presented datastore names to the underlying LUN's, despite the fact that its readily available via the VI Client.  The following was adapted from the work of ''Catman'' found in this forum thread - http://communities.vmware.com/thread/240466#240466.  As I expect to maybe want some of the other fields available during the working my notes from working through this are to be found [[Datastore to LUN Mapping (PowerCLI)|here]]
<source lang="powershell">
# Thieved and adapted from the good work by catman...
# http://communities.vmware.com/thread/240466#240466
 
$objESX = Get-VMHost "My_ESX*"
 
# Get .NET views for host and storage system
$objViewESX = Get-View -id $objESX.id
$objViewESXstorageSys = Get-View -id $objViewESX.ConfigManager.StorageSystem
 
# Get FC HBAs
$HBAs = $objViewESXstorageSys.StorageDeviceInfo.HostBusAdapter | Where-Object {$_.Key -like "*FibreChannelHba*"}
 
foreach ($hba in $HBAs) {
    # Enumerate LUNs
    $LUNcount = $objViewESXstorageSys.StorageDeviceInfo.MultiPathInfo.Lun.Length
   
    for ($LUNidx = 0; $LUNidx -lt $LUNcount; $LUNidx++ ) {
        $objScsiLUN = $objViewESXstorageSys.StorageDeviceInfo.MultiPathInfo.Lun[$LUNidx]
       
        # Enumerate paths on LUN
        $PathCount = $objScsiLUN.Path.Length
       
        for ($PathIdx = 0; $PathIdx -lt $PathCount; $PathIdx++) {
            $objSCSIpath = $objViewESXstorageSys.StorageDeviceInfo.MultiPathInfo.Lun[$LUNidx].Path[$PathIdx]
           
            # Only care about one path, active on current HBA
            if (($objSCSIpath.PathState -eq "active") -and ($objSCSIpath.Adapter -eq $hba.Key)) {
                # Now get the disk that we want
                $objSCSIdisk = $objViewESXstorageSys.StorageDeviceInfo.ScsiLun | Where-Object{ ($_.CanonicalName -eq $objScsiLUN.Id) -and ($_.DeviceType -eq "disk") }
               
                # Now get the datastore info for disk
                $MountCount = $objViewESXstorageSys.FileSystemVolumeInfo.MountInfo.Length
               
                for ($MountIdx = 0; $MountIdx -lt $MountCount; $MountIdx++ ) {
                    if ($objViewESXstorageSys.FileSystemVolumeInfo.MountInfo[$MountIdx].Volume.Type -eq "VMFS" ) {
                        $objVolume = $objViewESXstorageSys.FileSystemVolumeInfo.MountInfo[$MountIdx].Volume
                       
                        $ExtentCount = $objVolume.Extent.Length
                       
                        for ($ExtentIdx = 0; $ExtentIdx -lt $ExtentCount; $ExtentIdx++ ) {
                            $objExtent = $objVolume.Extent[$ExtentIdx]
                           
                            # Match extent name to disk name
                            if ($objExtent.DiskName -eq $objSCSIdisk.CanonicalName) {
                                Write-Host($objSCSIdisk.Vendor + " " + $objSCSIdisk.Model + " " + $objSCSIdisk.CanonicalName + "`t" + $objVolume.Name)
                            }
                        }
                    }
                }
            }
        }
    }
}
</source>
 
Gives an output a bit like this...
<pre>
HP      HSV200          vmhba1:0:20  VMFS-DS-08
HP      HSV200          vmhba1:0:7    VMFS-DS-01
HP      HSV200          vmhba1:0:22  VMFS-DS-10
HP      HSV200          vmhba1:0:8    VMFS-DS-02
HP      HSV200          vmhba1:0:10  VMFS-DS-03
HP      HSV200          vmhba1:0:18  VMFS-DS-07
HP      HSV200          vmhba1:0:21  VMFS-DS-09
HP      HSV200          vmhba1:0:12  VMFS-DS-05
HP      HSV200          vmhba1:0:11  VMFS-DS-04
HP      HSV200          vmhba1:0:3    VMFS-DS-Templates
HP      HSV200          vmhba1:0:2    VMFS-DS-ISOs
HP      HSV200          vmhba1:0:30  VMFS-DS-SCRATCH
HP      HSV200          vmhba1:0:13  VMFS-DS-06
</pre>
 
=== ESX Datastore to LUN Mapping (ESX4) ===
Produces a similar output to the above, but works for ESX4 servers.
 
<source lang="powershell">
function Get-DS-LUNs-v4 ($objESXs) {
    foreach ($cluster in (Get-View -ViewType "ClusterComputeResource")) {
      $vmhostsview = $cluster.host | % { Get-View $_ }
      $vmhostview  = $vmhostsview | Select -first 1
      $ScsiLuns    = $vmhostsview | % { $_.Config.StorageDevice.ScsiLun } | Select -unique *
      $UUIDs      = $ScsiLuns | Select -unique UUID
      $Datastores  = $vmhostsview | % { $_.Config.FileSystemVolume.MountInfo } | % { $_.Volume } | Select -Unique *
      $HostLUN    = $vmhostsview | % { $_.Config.StorageDevice.ScsiTopology.Adapter } | % { $_.Target | % { $_.LUN } } | Select -unique *
      foreach ($UUID in $UUIDs) {
        $Lun = $ScsiLuns | ? { $_.UUID -eq $UUID.UUID } | Select -first 1
        $objVolume              = "" | Select Datastore, Make, Model, LUN
 
        $objVolume.LUN      = ($HostLUN | ? { $_.ScsiLun -eq $Lun.Key } | select -unique LUN).LUN
        $objVolume.Make        = $Lun.Vendor
        $objVolume.Model        = $Lun.Model
        foreach ($vol in $Datastores) {
          if ($vol.extent | % { $_.diskname -eq $Lun.CanonicalName}) {
            $objVolume.Datastore  = $vol.Name
          }
        }
        $objVolume
      }
    }
}
</source>

Revision as of 15:35, 4 October 2016