Automation using Power Cli
- sathyahraj

- 5 days ago
- 6 min read
<#
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






Comments