Audit every disk, CPU, and memory reconfiguration across your PROD and DR vCenters — exported to timestamped CSV reports and emailed to your team on a schedule.
In any vSphere environment, knowing who changed what and when on a VM is essential for security audits, change management sign-off, and capacity planning. vCenter stores task and event history natively — but there is no built-in report that extracts it in a structured, shareable format.
These two PowerCLI scripts tap directly into the vSphere Task Manager and Event Manager APIs to capture every ReconfigVM_Task event from the last 36 hours — including disk additions and expansions, CPU count changes, and memory resizes. Results are written to timestamped CSV files and emailed to your operations team automatically through a Jenkins scheduled build.
vCenter alarms fire once per event and produce no consolidated audit trail. This approach captures a rolling 36-hour window across both PROD and DR vCenters into structured CSV files that your change management team can retain, filter, and action.
The scripts follow a four-step process using the vSphere Managed Object Browser (MOB) APIs — no vCenter plugins, no third-party tools required.
Each ReconfigVM_Task links back to a VmReconfiguredEvent via its EventChainId. The device change script inspects ConfigSpec.DeviceChange to find disk operations; the CPU/memory script reads ConfigSpec.NumCPUs and ConfigSpec.MemoryMB directly. Both scripts destroy their collectors after use to stay within vCenter's 32-collector-per-session limit.
Run Install-Module VMware.PowerCLI -Scope AllUsers -Force. Minimum version: 13.x. Set Set-PowerCLIConfiguration -InvalidCertificateAction Ignore if your vCenter uses a self-signed cert.
Create a dedicated service account in vCenter with read-only role at the root datacenter level. Never use an admin account for scheduled reporting jobs.
Store vCenter credentials as Jenkins Username/Password credentials (ID: vcenter-prod-creds and vcenter-dr-creds). Add the SMTP relay as a Secret Text credential (ID: smtp-server). All are injected as environment variables at runtime via the Credentials Binding plugin — no secrets in the script files.
Create C:\VM_Config_Changes\ on the Windows agent. Ensure the Jenkins service account has write access. A UNC path to a shared file server also works.
Confirm your SMTP relay accepts unauthenticated relay from the Jenkins agent's IP, or configure SMTP authentication credentials separately and pass them via -Credential on Send-MailMessage.
This script scans for all ReconfigVM_Task events in the past 36 hours and extracts any disk device operations — additions, resizes, or removals — capturing the device type, operation, disk label, and capacity in GB. The report is saved to a timestamped CSV on the agent.
vCenter allows a maximum of 32 simultaneous collectors per session. Both scripts call DestroyCollector() explicitly after each inner loop. If a run is interrupted, orphaned collectors can block subsequent executions — a Disconnect-VIServer in a finally block is good practice for production hardening.
<# .SYNOPSIS VM Config Change Tracker — Disk Device Operations .DESCRIPTION Connects to a vCenter server and uses the vSphere Task Manager and Event Manager APIs to detect all ReconfigVM_Task events in the past 36 hours. Filters for VmReconfiguredEvent entries and extracts disk device changes (add, edit, remove). Exports results to a timestamped CSV file. .NOTES Author : admin@automatewithravi.com Version : 1.0 Requires: VMware.PowerCLI 13.x or later #> # ── Credentials injected by Jenkins Credentials Binding plugin ────────────── $vcServer = $Env:VCENTER_SERVER $vcUsername = $Env:VCENTER_USERNAME $vcPassword = $Env:VCENTER_PASSWORD Connect-VIServer -Server $vcServer -User $vcUsername -Password $vcPassword Set-PowerCLIConfiguration -DefaultVIServerMode multiple -Confirm:$false # ── Configuration ─────────────────────────────────────────────────────────── $hours = 36 # Rolling window in hours $taskPageSize = 999 # Task collector page size (max supported) $eventPageSize = 100 # Event collector page size $report = @() $taskMgr = Get-View TaskManager $eventMgr = Get-View eventManager # ── Build task filter for the rolling time window ─────────────────────────── $tFilter = New-Object VMware.Vim.TaskFilterSpec $tFilter.Time = New-Object VMware.Vim.TaskFilterSpecByTime $tFilter.Time.beginTime = (Get-Date).AddHours(-$hours) $tFilter.Time.timeType = "startedTime" $tCollector = Get-View ($taskMgr.CreateCollectorForTasks($tFilter)) $null = $tCollector.RewindCollector $tasks = $tCollector.ReadNextTasks($taskPageSize) # ── Iterate tasks, match ReconfigVM_Task, fetch linked events ──────────────── while ($tasks) { $tasks | Where-Object { $_.Name -eq 'ReconfigVM_Task' } | ForEach-Object { $task = $_ $eFilter = New-Object VMware.Vim.EventFilterSpec $eFilter.eventChainId = $task.EventChainId $eCollector = Get-View ($eventMgr.CreateCollectorForEvents($eFilter)) $events = $eCollector.ReadNextEvents($eventPageSize) while ($events) { $events | ForEach-Object { switch ($_.GetType().Name) { 'VmReconfiguredEvent' { $_.ConfigSpec.DeviceChange | Where-Object { $_.Device -ne $null } | ForEach-Object { $report += [PSCustomObject]@{ VMname = $task.EntityName Start = $task.StartTime Finish = $task.CompleteTime Result = $task.State User = $task.Reason.UserName Device = $_.Device.GetType().Name Operation = $_.Operation HDDLabel = $_.Device.DeviceInfo.Label HDDCapacity_GB = [math]::Round($_.Device.CapacityInKb / 1MB, 0) } } } } } $events = $eCollector.ReadNextEvents($eventPageSize) } # Destroy event collector — vCenter allows max 32 per session $eCollector.DestroyCollector() } $tasks = $tCollector.ReadNextTasks($taskPageSize) } # Destroy task collector $tCollector.DestroyCollector() # ── Export to timestamped CSV ──────────────────────────────────────────────── $timestamp = (Get-Date).ToString('yyyy-MM-dd_HHmm') $outputPath = "C:\VM_Config_Changes\VM-DeviceChanges_$timestamp.csv" $report | Sort-Object Start | Export-Csv $outputPath -NoTypeInformation -UseCulture Write-Output "[INFO] Report saved: $outputPath — $($report.Count) record(s)" Disconnect-VIServer -Confirm:$false -ErrorAction SilentlyContinue
This companion script targets the same ReconfigVM_Task event stream but reads ConfigSpec.NumCPUs and ConfigSpec.MemoryMB directly. Any reconfiguration where at least one of these values is non-zero is captured. Empty columns in the output mean that field was not changed in that particular task.
<# .SYNOPSIS VM Config Change Tracker — CPU and Memory Operations .DESCRIPTION Connects to a vCenter server and uses the vSphere Task Manager and Event Manager APIs to detect all ReconfigVM_Task events in the past 36 hours. Filters for VmReconfiguredEvent entries where CPU count or memory was changed and exports results to a timestamped CSV. Sends email notification on completion. .NOTES Author : admin@automatewithravi.com Version : 1.0 Requires: VMware.PowerCLI 13.x or later #> # ── Credentials injected by Jenkins Credentials Binding plugin ────────────── $vcServer = $Env:VCENTER_SERVER $vcUsername = $Env:VCENTER_USERNAME $vcPassword = $Env:VCENTER_PASSWORD Connect-VIServer -Server $vcServer -User $vcUsername -Password $vcPassword Set-PowerCLIConfiguration -DefaultVIServerMode multiple -Confirm:$false # ── Configuration ─────────────────────────────────────────────────────────── $hours = 36 $taskPageSize = 999 $eventPageSize = 100 $report = @() $taskMgr = Get-View TaskManager $eventMgr = Get-View eventManager # ── Build task filter ──────────────────────────────────────────────────────── $tFilter = New-Object VMware.Vim.TaskFilterSpec $tFilter.Time = New-Object VMware.Vim.TaskFilterSpecByTime $tFilter.Time.beginTime = (Get-Date).AddHours(-$hours) $tFilter.Time.timeType = "startedTime" $tCollector = Get-View ($taskMgr.CreateCollectorForTasks($tFilter)) $null = $tCollector.RewindCollector $tasks = $tCollector.ReadNextTasks($taskPageSize) # ── Iterate tasks and extract CPU / memory changes ─────────────────────────── while ($tasks) { $tasks | Where-Object { $_.Name -eq 'ReconfigVM_Task' } | ForEach-Object { $task = $_ $eFilter = New-Object VMware.Vim.EventFilterSpec $eFilter.eventChainId = $task.EventChainId $eCollector = Get-View ($eventMgr.CreateCollectorForEvents($eFilter)) $events = $eCollector.ReadNextEvents($eventPageSize) while ($events) { $events | ForEach-Object { switch ($_.GetType().Name) { 'VmReconfiguredEvent' { $_.ConfigSpec | Where-Object { $_.NumCPUs -ne 0 -or $_.MemoryMB -ne 0 } | ForEach-Object { $report += [PSCustomObject]@{ VMname = $task.EntityName Start = $task.StartTime Finish = $task.CompleteTime Result = $task.State User = $task.Reason.UserName Memory_MB = if ($_.MemoryMB -ne 0) { $_.MemoryMB } else { '' } NumCPU = if ($_.NumCPUs -ne 0) { $_.NumCPUs } else { '' } } } } } } $events = $eCollector.ReadNextEvents($eventPageSize) } $eCollector.DestroyCollector() } $tasks = $tCollector.ReadNextTasks($taskPageSize) } $tCollector.DestroyCollector() # ── Export to timestamped CSV ──────────────────────────────────────────────── $timestamp = (Get-Date).ToString('yyyy-MM-dd_HHmm') $outputPath = "C:\VM_Config_Changes\VM-CpuMemoryChanges_$timestamp.csv" $report | Sort-Object Start | Export-Csv $outputPath -NoTypeInformation -UseCulture Write-Output "[INFO] Report saved: $outputPath — $($report.Count) record(s)" Disconnect-VIServer -Confirm:$false -ErrorAction SilentlyContinue # ── Email notification to operations team ──────────────────────────────────── $smtpServer = $Env:SMTP_SERVER $fromAddress = 'vcenter-alerts@automatewithravi.com' $toAddresses = 'ops-team@automatewithravi.com', 'infra-lead@automatewithravi.com' $subject = "vCenter VM Config Change Report — " + (Get-Date -Format 'dd-MM-yyyy') $body = @" Hi Team, The VM reconfiguration audit job has completed successfully. Please find attached the timestamped CSV reports covering disk, CPU, and memory changes detected in the last 36 hours across PROD and DR vCenters. Reports are also archived in: C:\VM_Config_Changes\ Please do not reply to this email — this mailbox is unmonitored. Thank you "@ Send-MailMessage -SmtpServer $smtpServer ` -From $fromAddress ` -To $toAddresses ` -Subject $subject ` -Body $body ` -Attachments $outputPath Write-Output "[INFO] Email sent to: $($toAddresses -join ', ')"
Below are representative examples of both reports when opened in Excel. The Device Change report tracks disk operations; the CPU/Memory report tracks compute resizes. Both are sorted by Start time ascending.
| VMname | Start | Finish | Result | User | Device | Operation | HDDLabel | HDDCapacity_GB |
|---|---|---|---|---|---|---|---|---|
| PROD-APP-01 | 07/06/2026 08:14:22 | 07/06/2026 08:14:35 | success | svc-vcenter@vsphere.local | VirtualDisk | edit | Hard disk 1 | 100 |
| DR-DB-03 | 07/06/2026 10:02:11 | 07/06/2026 10:02:30 | success | admin@vsphere.local | VirtualDisk | add | Hard disk 2 | 200 |
| PROD-WEB-07 | 07/06/2026 14:45:09 | 07/06/2026 14:45:19 | success | svc-vcenter@vsphere.local | VirtualDisk | edit | Hard disk 1 | 150 |
| DR-APP-02 | 08/06/2026 02:12:55 | 08/06/2026 02:13:01 | error | admin@vsphere.local | VirtualDisk | edit | Hard disk 3 | 500 |
| VMname | Start | Finish | Result | User | Memory_MB | NumCPU |
|---|---|---|---|---|---|---|
| PROD-SQL-01 | 07/06/2026 07:30:00 | 07/06/2026 07:30:45 | success | svc-vcenter@vsphere.local | 32768 | 8 |
| DR-WEB-05 | 07/06/2026 11:15:22 | 07/06/2026 11:15:50 | success | admin@vsphere.local | 16384 | |
| PROD-APP-04 | 08/06/2026 03:05:10 | 08/06/2026 03:05:42 | success | svc-vcenter@vsphere.local | 4 |
An empty NumCPU column means only memory changed; an empty Memory_MB means only CPU changed. Both populated = combined resize in a single reconfigure task. For disk reports: Operation: add = new disk provisioned; edit = existing disk expanded or shrunk.
You now have two production-ready PowerCLI scripts that audit all VM reconfiguration events across PROD and DR vCenters, export structured CSV reports, and email your operations team automatically — all with zero hardcoded credentials and built for Jenkins scheduled execution.
vSphere Task/Event API integration via PowerCLI → two audit scripts (disk device changes + CPU/memory changes) → timestamped CSV exports → automated ops team email → Jenkins cron schedule covering PROD and DR vCenters.