🖥️ VMware Automation

Track VMware VM Configuration Changes
with PowerCLI & Jenkins

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.

📅 June 2026 ⏱ 10 min read ✍️ Ravindrakumar Narayanan
← Back to Tech Blogs

Table of Contents

  1. Overview & Use Case
  2. How It Works
  3. Prerequisites
  4. Script 1 — Disk Device Change Audit
  5. Script 2 — CPU & Memory Change Audit
  6. Sample CSV Report Output
  7. Summary & Next Steps

1. Overview & Use Case

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.

💡
Why not just use vCenter alarms?

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.

2. How It Works

The scripts follow a four-step process using the vSphere Managed Object Browser (MOB) APIs — no vCenter plugins, no third-party tools required.

🔍
Task Filter
TaskFilterSpec scans last 36 hrs for ReconfigVM_Task
📡
Event Lookup
EventFilterSpec fetches VmReconfiguredEvent per task chain
📊
CSV Export
Timestamped CSV saved to C:\VM_Config_Changes\
📧
Email Alert
CSV attached and sent to ops team via SMTP relay

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.

3. Prerequisites

1
VMware PowerCLI installed on the Jenkins Windows agent

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.

2
Read-only vCenter service account

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.

3
Jenkins credentials configured

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.

4
Output directory created on the Jenkins agent

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.

5
SMTP relay accessible from the agent

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.

4. Script 1 — Disk Device Change Audit

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.

⚠️
Collector limit reminder

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.

PowerShell — VM-DeviceChanges.ps1
<#
.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

5. Script 2 — CPU & Memory Change Audit

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.

PowerShell — VM-CpuMemoryChanges.ps1
<#
.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 ', ')"

6. Sample CSV Report Output

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.

Report 1 — VM-DeviceChanges CSV

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

Report 2 — VM-CpuMemoryChanges CSV

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
📋
Reading the reports

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.

7. Summary & Next Steps

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.

What you've built

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.

VMware PowerCLI PowerShell Jenkins vSphere ReconfigVM_Task VmReconfiguredEvent VM Audit CI/CD CSV Report PROD & DR DevOps