Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

How to optimise Windows Server and Remote Desktop on VMware

Guide
HostingVMwarePerformance

Optimise the operation users find slow, then use resource measurements to explain it. Windows startup, application launch, report execution, file access and RDP display response are different workloads. One synthetic disk score cannot establish that all of them became faster.

The procedures below are for an authorised lab VM. They have not been executed for this article. Administrator access is needed for the power-plan experiment and for some counter sets; read-only access is enough for part of the inventory.

1. Establish an application acceptance contract

Select one installed application and a reproducible operation on synthetic data. Define the beginning and end precisely. For example: start a known application, open the same test document, verify its expected content, perform a fixed search, then close without saving.

Record executable and application version, data hash, session type, screen resolution, concurrent users and expected result. A process that exists or reaches input-idle is not equivalent to an application that has opened the document correctly.

Use several warm-up runs, then at least enough repetitions to expose variability. Thirty observations can support an initial distribution; very high-percentile claims need substantially more data. Keep failed or timed-out operations in the results rather than dropping them from the average.

2. Record the starting state

From PowerShell, collect simple inventory without serialising entire live objects:

Get-CimInstance Win32_OperatingSystem |
  Select-Object Caption,Version,TotalVisibleMemorySize,FreePhysicalMemory
Get-CimInstance Win32_Processor |
  Select-Object Name,NumberOfCores,NumberOfLogicalProcessors
Get-CimInstance Win32_PageFileUsage |
  Select-Object Name,AllocatedBaseSize,CurrentUsage,PeakUsage
Get-Process | Sort-Object PrivateMemorySize64 -Descending |
  Select-Object -First 12 Name,Id,WorkingSet64,PrivateMemorySize64
powercfg /getactivescheme
powercfg /list

Also record VMware Tools version, NIC and storage-controller type, disk free space, VM resource limits and guest patch level. Keep the information local or in an approved diagnostic repository. Process names and application inventories can be sensitive operational information.

Require sufficient free disk and memory before running a workload. A collector must not become the dominant consumer. Use bounded samples, flat CSV and process-specific cleanup; never kill every Notepad or application process by name.

3. Capture Windows counters during the workload

Use an elevated PowerShell window if required. On a non-English system, discover local counter names with Get-Counter -ListSet '*' or Performance Monitor rather than assuming English paths.

$paths = @(
  '\Processor(_Total)\% Processor Time',
  '\System\Processor Queue Length',
  '\Memory\Available MBytes',
  '\Memory\Committed Bytes',
  '\Memory\Commit Limit',
  '\Memory\Pages Input/sec',
  '\PhysicalDisk(_Total)\Avg. Disk sec/Read',
  '\PhysicalDisk(_Total)\Avg. Disk sec/Write'
)
Get-Counter -Counter $paths -SampleInterval 1 -MaxSamples 120 |
  ForEach-Object {
    $utc = $_.Timestamp.ToUniversalTime().ToString('o')
    $_.CounterSamples | ForEach-Object {
      [pscustomobject]@{
        UTC = $utc
        Path = [string]$_.Path
        Value = [double]$_.CookedValue
        Status = [long]$_.Status
      }
    }
  } | Export-Csv './windows-baseline.csv' -NoTypeInformation

Run the application sequence during that two-minute collection. Verify that requested counters were actually available and that sample status codes are valid. An inaccessible counter is unknown, not zero. The parameter and access behaviour are documented in Get-Counter help: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-counter?view=powershell-5.1.

Correlate with ESXi CPU Ready, ballooning and swap activity using guide 3. Memory available in the guest and host reclamation are separate measurements.

4. Measure RDS responsiveness at the correct boundary

In Performance Monitor, add User Input Delay per Process and User Input Delay per Session for the actual test session. Match process instances to their session/PID. Record the counter while typing and navigating the application, not while leaving it idle.

The counter represents the maximum time input waits for an application message loop during the interval. It is not network round-trip time or the delay before pixels appear at the client. Microsoft's User Input Delay documentation: https://learn.microsoft.com/en-us/windows-server/remote/remote-desktop-services/rds-rdsh-performance-counters/ explains the scope.

If queue delay rises with guest CPU saturation, investigate the application and session density. If queue delay remains low but the remote display is slow, measure the client/network/graphics path separately. If the counter is missing, check privileges and supported configuration; do not invent a replacement measurement.

For capacity planning, sample several realistic concurrent sessions. Microsoft describes session memory in terms of OS overhead plus per-session requirements, but workload diversity and sharing mean a single idle session is not a capacity proof. RDSH tuning guidance: https://learn.microsoft.com/en-us/windows-server/administration/performance-tuning/role/remote-desktop/session-hosts

5. Test a Windows power-plan hypothesis

High Performance is a candidate for some latency-sensitive workloads, not a universal prescription. A guest does not control every host firmware or ESXi power decision. Microsoft's power-tuning guidance: https://learn.microsoft.com/en-us/windows-server/administration/performance-tuning/hardware/power/power-performance-tuning recommends judging the actual workload.

Check elevation before collecting another baseline:

$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
  throw 'An elevated lab session is required for the power-plan experiment'
}
$active = powercfg /getactivescheme | Out-String
$original = [regex]::Match($active,
  '[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').Value
if (-not $original) { throw 'Cannot identify the original power plan' }
$original | Set-Content './original-power-plan.txt'

Use the listed GUID for the existing High Performance plan, not a guessed OEM plan. Run the same application sequence under the original plan (A), under High Performance (B), and again under the original plan (A). Record the active GUID at each stage.

# Configuration change: lab only. Run the approved measurement during this block.
try {
  powercfg /setactive '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
  if ($LASTEXITCODE -ne 0) { throw 'Power-plan change was rejected' }
  powercfg /getactivescheme
  # Perform and record the same bounded application sequence here.
}
finally {
  powercfg /setactive $original
  if ($LASTEXITCODE -ne 0) { throw 'Original plan could not be restored' }
  powercfg /getactivescheme
}

This is an experiment scaffold, not an automatic benchmark. finally is not an independent watchdog if the process or VM dies. Keep the recorded GUID and an independent recovery session available; automate a timed restoration before unattended use. The switches are documented in powercfg help: https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/powercfg-command-line-options.

6. Treat caches and drivers as separate experiments

Compare initial and repeated document access, but label the initial run “first observed” unless a controlled cold-cache state is established. An initial run may already benefit from prior system activity. Do not flush production caches or reboot users' servers to manufacture a cold score.

Do not disable SysMain, Defender, the pagefile or security features merely because a tuning list recommends it. Collect evidence of their contribution. Any exclusion needs workload-specific vendor guidance and security review.

A VMXNET3 or PVSCSI change requires supported guest drivers and a separate maintenance plan. Record IP configuration and boot-controller recovery requirements. Change one layer at a time; otherwise a faster result cannot be attributed reliably.

Acceptance and rollback

Compare correctness, failure rate, median and tail response, host contention and resource headroom. Reject a change whose apparent gain disappears when the original baseline is repeated. Do not publish a percentage from one fast run.

Restore the saved power plan and verify its GUID. Restore other settings from the recorded configuration rather than resetting Windows broadly. Treat capacity results as valid only for the tested application mix and concurrent session count.

Related topics: SQL resource management, VM rightsizing and network-path diagnosis.

Learning notes

  • Application usability needs an explicit expected result.
  • Input queue delay and remote screen delay are different measurements.
  • Cache state and host load can make an uncontrolled comparison look like an optimisation.

Sources and review status

See the source ledger for dates, version scope and limits, the author self-check, and the series index. Independent review and lab validation remain pending.