top of page

Automation using Power Cli

<#

PowerCLI - vSphere Full Monitoring Automation

File: PowerCLI - vSphere Full Monitoring Automation.ps1

Purpose: Complete, production-ready PowerCLI automation script collection for comprehensive vSphere monitoring.

Author: Sathyay2k

Date: 2025-10-28


Contents:

1. Prerequisites and secure credential storage

2. Configuration section (variables)

3. Helper functions (connect, logging, email, push to ELK/Influx)

4. Data collectors:

- Inventory (VMs, Hosts, Datastores, Networks)

- Performance metrics (CPU, Memory, Disk, Network) using Get-Stat

- Events & recent tasks

- Datastore utilization and datastore latency (if available)

- VMTools status & snapshots

- Host hardware health (if vCenter exposes via Get-VMHost | Get-VMHostHardwareStatus)

5. Report generation (CSV + simple HTML)

6. Example: Push metrics to Elasticsearch/InfluxDB (HTTP APIs)

7. Scheduling examples (Windows Task Scheduler + Azure Automation notes)

8. Logging & rotation


Notes:

- Tested with PowerCLI 12.x+ (module: VMware.PowerCLI). Run `Update-Module VMware.PowerCLI` and run PowerShell as admin.

- For production, store credentials securely: use a PSCredential exported to an encrypted file (Export-Clixml) tied to the service account user. See Save-Cred function below.

- Adjust Get-Stat intervals depending on scale — heavy usage may load vCenter.

#>


#region Prerequisites

# Install PowerCLI if not present

# Install-Module -Name VMware.PowerCLI -Scope AllUsers

# Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope AllUsers -Confirm:$false

# Enable TLS if needed: [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12


#region Configuration

$Config = [PSCustomObject]@{

VCenterServers = @('vcenter1.example.com','vcenter2.example.com')

CredFile = "$env:ProgramData\vcenter_monitor\vcenter_cred.xml" # encrypted with Export-Clixml by the service account

OutputDir = "$env:ProgramData\vcenter_monitor\reports"

LogDir = "$env:ProgramData\vcenter_monitor\logs"

RetainDays = 30

MetricsWindowMinutes = 5 # Get-Stat window

Email = [PSCustomObject]@{

Enabled = $false

SmtpServer = 'smtp.example.com'

From = 'vcenter-monitor@example.com'

To = 'ops@example.com'

SubjectPrefix = '[vCenter-Monitor] '

}

Elastic = [PSCustomObject]@{

Enabled = $false

Url = 'http://elasticsearch.example:9200/_bulk'

IndexPrefix = 'vsphere-metrics'

}

}


# Create directories

New-Item -Path $Config.OutputDir -ItemType Directory -Force | Out-Null

New-Item -Path $Config.LogDir -ItemType Directory -Force | Out-Null


#region Helpers: Credentials & Logging

function Save-VCenterCredential {

param(

[Parameter(Mandatory=$true)] [string] $Path

)

# Run interactively one time as the service account

$cred = Get-Credential -Message 'Enter vCenter service account (domain\user)'

$cred | Export-Clixml -Path $Path

Write-Host "Saved credential to $Path (encrypted for current user)

Note: Only the user that exported the file can decrypt it." -ForegroundColor Green

}


function Get-VCenterCredential {

param([string] $Path)

if (-Not (Test-Path $Path)) { throw "Credential file not found: $Path" }

try {

return Import-Clixml -Path $Path

} catch {

throw "Unable to import credential file. Ensure it was created by the same account and on the same machine.";

}

}


function Write-Log {

param(

[string] $Message,

[string] $Level = 'INFO'

)

$ts = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')

$line = "[$ts] [$Level] $Message"

$logFile = Join-Path $Config.LogDir "monitor-$(Get-Date -Format yyyy-MM-dd).log"

Add-Content -Path $logFile -Value $line

}


#region Connection helper

function Connect-AllVCenters {

param([PSCredential] $Credential)

$connected = @()

foreach ($vc in $Config.VCenterServers) {

try {

Write-Log "Connecting to $vc"

$s = Connect-VIServer -Server $vc -Credential $Credential -WarningAction SilentlyContinue -ErrorAction Stop

$connected += $s

} catch {

Write-Log "Failed connect to $vc : $_" 'ERROR'

}

}

return $connected

}


#region Data collectors

function Get-InfrastructureInventory {

param([string] $OutCsv)

$vms = Get-VM | Select-Object Name, PowerState, NumCPU, MemoryGB, @{Name='Host';Expression={$_.VMHost.Name}}, @{Name='Datastore';Expression={($_.ExtensionData.Storage.PerDatastoreUsage | Where-Object { $_.Key -ne $null } | Select-Object -First 1).Key -replace '^/datastore/',''}}, Guest.OSFullName

$hosts = Get-VMHost | Select-Object Name, @{N='CPU(s)';E={$_.NumCpu}}, CpuUsageMhz, MemoryTotalMB, MemoryUsageMB, ConnectionState

$ds = Get-Datastore | Select-Object Name, FreeSpaceGB, CapacityGB, @{N='Usage%';E={if ($_.CapacityGB -gt 0){[math]::Round((($_.CapacityGB - $_.FreeSpaceGB)/$_.CapacityGB)*100,2)}else{0}}}


$report = [PSCustomObject]@{

Generated = Get-Date

VMCount = $vms.Count

HostCount = $hosts.Count

DatastoreCount = $ds.Count

}

$out = @{VMS=$vms; Hosts=$hosts; Datastores=$ds; Summary=$report}

if ($OutCsv) {

$vms | Export-Csv -Path (Join-Path $Config.OutputDir "vms-$((Get-Date).ToString('yyyyMMdd-HHmm')).csv") -NoTypeInformation -Force

$hosts | Export-Csv -Path (Join-Path $Config.OutputDir "hosts-$((Get-Date).ToString('yyyyMMdd-HHmm')).csv") -NoTypeInformation -Force

$ds | Export-Csv -Path (Join-Path $Config.OutputDir "datastores-$((Get-Date).ToString('yyyyMMdd-HHmm')).csv") -NoTypeInformation -Force

}

return $out

}


function Get-RecentEventsAndTasks {

param(

[int] $MinutesBack = 60

)

$since = (Get-Date).AddMinutes(-1 * $MinutesBack)

$events = Get-VIEvent -Start $since -MaxSamples 1000 | Sort-Object CreatedTime -Descending

$tasks = Get-Task | Where-Object { $_.StartTime -ge $since } | Sort-Object StartTime -Descending

return @{Events=$events; Tasks=$tasks}

}


function Get-PerformanceMetrics {

param(

[int] $WindowMinutes = 5

,[string[]] $MetricList = @('cpu.usage.average','mem.usage.average','net.usage.average','disk.read.average','disk.write.average')

)

$end = Get-Date

$start = $end.AddMinutes(-1 * $WindowMinutes)


# Example: collect per-VM aggregated metrics

$vmList = Get-VM

$metrics = foreach ($vm in $vmList) {

$entry = [PSCustomObject]@{ VM = $vm.Name }

foreach ($metricPath in $MetricList) {

try {

$stat = Get-Stat -Entity $vm -Stat $metricPath -Start $start -Finish $end -RealTime:$false -ErrorAction SilentlyContinue | Select-Object -Last 1

$value = if ($stat) { [math]::Round($stat.Value,2) } else { $null }

$name = ($metricPath -replace '\.','_')

$entry | Add-Member -NotePropertyName $name -NotePropertyValue $value

} catch {

$entry | Add-Member -NotePropertyName ($metricPath -replace '\.','_') -NotePropertyValue $null

}

}

$entry

}

return $metrics

}


function Get-DatastoreHealth {

param()

# Get capacity and free space, report under-threshold stores

$ds = Get-Datastore | Select-Object Name, FreeSpaceGB, CapacityGB, @{N='UsagePercent';E={if($_.CapacityGB -gt 0){[math]::Round((($_.CapacityGB - $_.FreeSpaceGB)/$_.CapacityGB)*100,2)}else{0}}}

$threshold = 85

$warn = $ds | Where-Object { $_.UsagePercent -ge $threshold }

return @{All=$ds; Warnings=$warn}

}


function Get-VMHealthChecks {

param()

$vms = Get-VM | Select-Object Name, PowerState, @{N='VMToolsRunning';E={$_.ExtensionData.Guest.ToolsRunningStatus}}, @{N='IP';E={$_.Guest.IPAddress -join ','}}, @{N='SnapshotCount';E={(Get-Snapshot -VM $_ -ErrorAction SilentlyContinue | Measure-Object).Count}}

$withOldSnapshots = Get-VM | Where-Object { (Get-Snapshot -VM $_ -ErrorAction SilentlyContinue | Where-Object { $_.Created -lt (Get-Date).AddDays(-7) }).Count -gt 0 }

return @{VMs=$vms; OldSnapshots=$withOldSnapshots}

}


function Get-HostHardwareHealth {

param()

# Limited by vCenter / host hardware provider. This returns N/A on many deployments.

try {

$hosts = Get-VMHost | ForEach-Object {

$h = $_

$health = $null

try { $health = Get-VMHostHardware -VMHost $h -ErrorAction SilentlyContinue } catch { }

[PSCustomObject]@{ Host=$h.Name; ConnectionState=$h.ConnectionState; Health=$health }

}

return $hosts

} catch {

return $null

}

}


#region Report & Alerts

function Send-EmailAlert {

param(

[string] $Subject,

[string] $Body

)

if (-not $Config.Email.Enabled) { Write-Log "Email disabled in config"; return }

try {

Send-MailMessage -SmtpServer $Config.Email.SmtpServer -From $Config.Email.From -To $Config.Email.To -Subject ($Config.Email.SubjectPrefix + $Subject) -Body $Body -BodyAsHtml

Write-Log "Sent email: $Subject"

} catch {

Write-Log "Failed to send email: $_" 'ERROR'

}

}


function Generate-HTMLSummary {

param(

$Inventory, $PerfMetrics, $DatastoreHealth, $Events, [string] $OutFile

)

$html = @"

<html><head><title>vSphere Monitoring Summary - $(Get-Date)</title></head><body>

<h1>vSphere Monitoring Summary - $(Get-Date)</h1>

<h2>Summary</h2>

<p>VMs: $($Inventory.Summary.VMCount) Hosts: $($Inventory.Summary.HostCount) Datastores: $($Inventory.Summary.DatastoreCount)</p>

<h2>Datastore warnings</h2>

<table border='1'><tr><th>Name</th><th>Usage%</th><th>Free GB</th><th>Capacity GB</th></tr>

"@

foreach ($d in $DatastoreHealth.Warnings) {

$html += "<tr><td>$($d.Name)</td><td>$($d.UsagePercent)</td><td>$($d.FreeSpaceGB)</td><td>$($d.CapacityGB)</td></tr>`n"

}

$html += "</table>`n<h2>Recent events</h2><ul>`n"

foreach ($e in $Events | Select-Object -First 20) {

$html += "<li>$($e.CreatedTime) - $($e.FullFormattedMessage)</li>`n"

}

$html += "</ul></body></html>"

$html | Out-File -FilePath $OutFile -Encoding utf8 -Force

Write-Log "Wrote HTML report to $OutFile"

}


#region Push to Elastic/Influx example

function Push-ToElasticsearchBulk {

param(

[Parameter(Mandatory=$true)][array] $Docs

)

if (-not $Config.Elastic.Enabled) { Write-Log 'Elastic disabled'; return }

# Build bulk payload

$payload = ''

foreach ($d in $Docs) {

$meta = @{index = @{_index = "${($Config.Elastic.IndexPrefix)}-$(Get-Date -Format yyyy.MM.dd)"}}

$payload += (ConvertTo-Json $meta -Compress) + "`n"

$payload += (ConvertTo-Json $d -Compress) + "`n"

}

try {

$resp = Invoke-RestMethod -Uri $Config.Elastic.Url -Method Post -Body $payload -ContentType 'application/x-ndjson'

Write-Log "Elastic push response: $($resp.took)"

} catch {

Write-Log "Elastic push failed: $_" 'ERROR'

}

}


#region Main run logic

function Run-MonitorCycle {

param(

[PSCredential] $Credential

)

$timestamp = Get-Date -Format 'yyyyMMdd-HHmm'

Write-Log "Starting cycle"


Connect-AllVCenters -Credential $Credential | Out-Null


$inventory = Get-InfrastructureInventory -OutCsv $true

$eventsTasks = Get-RecentEventsAndTasks -MinutesBack 60

$perf = Get-PerformanceMetrics -WindowMinutes $Config.MetricsWindowMinutes

$dsHealth = Get-DatastoreHealth

$vmHealth = Get-VMHealthChecks

$hostHealth = Get-HostHardwareHealth


# Generate HTML

$outHtml = Join-Path $Config.OutputDir "vcenter-summary-$timestamp.html"

Generate-HTMLSummary -Inventory $inventory -PerfMetrics $perf -DatastoreHealth $dsHealth -Events $eventsTasks.Events -OutFile $outHtml


# push metrics to Elastic if enabled

if ($Config.Elastic.Enabled) {

$docs = @()

foreach ($m in $perf) {

$doc = [PSCustomObject]@{

timestamp = (Get-Date).ToString('o')

vm = $m.VM

metrics = $m | Select-Object -ExcludeProperty VM

}

$docs += $doc

}

Push-ToElasticsearchBulk -Docs $docs

}


# Checks and alerts

if ($dsHealth.Warnings.Count -gt 0) {

$body = "Datastore usage warnings:`n" + ($dsHealth.Warnings | Format-Table -AutoSize | Out-String)

Send-EmailAlert -Subject 'Datastore usage high' -Body $body

}


# Disconnect

Disconnect-VIServer -Server * -Confirm:$false | Out-Null

Write-Log "Cycle complete"

}


#region Entrypoint

# Intended to be run by schedule as the service account that can decrypt cred file

try {

$cred = Get-VCenterCredential -Path $Config.CredFile

Run-MonitorCycle -Credential $cred

} catch {

Write-Log "Fatal error: $_" 'ERROR'

}


#region Scheduling example (one-time interactive step)

<#

# 1) Create credential file (run once interactively as the service account):

Save-VCenterCredential -Path $Config.CredFile


# 2) Place this script to C:\ProgramData\vcenter_monitor\monitor.ps1

# 3) Create Windows scheduled task (run as the same service account that created the cred file):

$action = New-ScheduledTaskAction -Execute 'PowerShell.exe' -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \"C:\ProgramData\vcenter_monitor\monitor.ps1\""

$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 365)

Register-ScheduledTask -TaskName 'vCenter-Monitor' -Action $action -Trigger $trigger -User 'DOMAIN\svc-vcenter-monitor' -RunLevel Highest


# Alternatively use schtasks.exe

schtasks /Create /SC MINUTE /MO 5 /TN "vCenter-Monitor" /TR "PowerShell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \"C:\ProgramData\vcenter_monitor\monitor.ps1\"" /RU DOMAIN\svc-vcenter-monitor

#>


#region Maintenance: log rotation

# Rotate logs older than $Config.RetainDays

Get-ChildItem -Path $Config.LogDir -Filter '*.log' | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-1 * $Config.RetainDays) } | Remove-Item -Force -ErrorAction SilentlyContinue


# End of script

 
 
 

Recent Posts

See All
VMware Real Time Scenario Interview Q & A

Part III Scenario 61: VM Network Adapter Type Mismatch Leading to Throughput & Latency Issues In a virtualised environment, several Windows and Linux VMs were upgraded from older hardware generations.

 
 
 

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page