Windows Server

Windows Server Performance Monitoring: Diagnostics and Optimization

Windows Server Performance Monitoring: Diagnostics and Optimization

ForEach-Object { Write-Host " $([math]::Round($_.CookedValue, 2))%" -ForegroundColor Green }

Memory

Write-Host 'nMemory:' -ForegroundColor Yellow $os = Get-CimInstance Win32_OperatingSystem
$totalMem = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeMem = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedMem = $totalMem - $freeMem $memPercent = [math]::Round(($usedMem / $totalMem) * 100, 2)
Write-Host " Total: $totalMem GB" -ForegroundColor Green Write-Host " Used: $usedMem GB ($memPercent%)" -ForegroundColor Green Write-Host " Free: $freeMem GB" -ForegroundColor Green

Memory

Disk activity

Write-Host 'nDisk Activity:' -ForegroundColor Yellow Get-Counter '\PhysicalDisk(_Total)\Disk Bytes/sec' | Select-Object -ExpandProperty CounterSamples | ForEach-Object { $diskMBps = [math]::Round($_.CookedValue / 1MB, 2) Write-Host " $diskMBps MB/s" -ForegroundColor Green
}

Disk activity

Network

Write-Host 'nNetwork Activity:' -ForegroundColor Yellow Get-Counter '\Network Interface(*)\Bytes Total/sec' | Select-Object -ExpandProperty CounterSamples | Where-Object { $.CookedValue -gt 0 } |
ForEach-Object {
$netMbps = [math]::Round(($
.CookedValue * 8) / 1MB, 2)
Write-Host " $($_.InstanceName): `$netMbps Mbps" -ForegroundColor Green
}

Network

Top CPU processes

Write-Host 'nTop CPU Processes:' -ForegroundColor Yellow Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 | Format-Table Name, @{N='CPU(s)';E={[math]::Round($.CPU, 2)}}, @{N='Memory(MB)';E={[math]::Round(`$.WorkingSet64 / 1MB, 2)}} -AutoSize

Top CPU processes

Start-Sleep -Seconds 2```
}
"@

$monitorScript | Out-File "C:\Scripts\RealtimeMonitor.ps1"


## Process-Specific Monitoring

```powershell

![Process-Specific Monitoring](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec5-monitoring.jpg)

## Monitor specific process
$processName = "w3wp"
$process = Get-Process -Name $processName

![Monitor specific process](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec6-monitoring.jpg)


## CPU usage
$cpuBefore = $process.CPU
Start-Sleep -Seconds 1
$process = Get-Process -Name $processName
$cpuAfter = $process.CPU
$cpuUsage = $cpuAfter - $cpuBefore

![CPU usage](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec7-generic.jpg)


Write-Host "Process: $($process.Name)" -ForegroundColor Cyan
Write-Host "CPU: $([math]::Round($cpuUsage, 2)) seconds" -ForegroundColor Yellow
Write-Host "Memory: $([math]::Round($process.WorkingSet64 / 1MB, 2)) MB" -ForegroundColor Yellow
Write-Host "Threads: $($process.Threads.Count)" -ForegroundColor Yellow
Write-Host "Handles: $($process.HandleCount)" -ForegroundColor Yellow

## Get process threads
$process.Threads | Select-Object Id, ThreadState, TotalProcessorTime | Format-Table

![Get process threads](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec8-generic.jpg)

Event Viewer Diagnostics

Querying Event Logs

Event Viewer Diagnostics

## View recent errors
Get-EventLog -LogName System -EntryType Error -Newest 50 | 
```text
Select-Object TimeGenerated, Source, EventID, Message | 
Format-Table -AutoSize

View recent errors

Query specific event ID

Get-EventLog -LogName System -InstanceId 1074 -Newest 20 # System shutdown events

Query specific event ID

Search application log

Get-EventLog -LogName Application -EntryType Warning, Error -Newest 100 |

Group-Object Source | 
Sort-Object Count -Descending | 
Select-Object Count, Name | 
Format-Table -AutoSize

Search application log

Use Get-WinEvent for advanced filtering

Use Get-WinEvent for advanced filtering

Figure: Power Apps gallery control – flexible item template with connected data source.

$filter = @{

LogName = 'System'
Level = 2  # Error
StartTime = (Get-Date).AddDays(-1)```
}
Get-WinEvent -FilterHashtable $filter | 
```text
Select-Object TimeCreated, Id, LevelDisplayName, Message | 
Format-Table -Wrap

![Use Get-WinEvent for advanced filtering](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec13-advanced.jpg)


## Creating Custom Views

```powershell

![Creating Custom Views](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec14-implementation.jpg)

## Export custom view as XML
$viewXml = @"
<QueryList>
  <Query Id="0" Path="System">
```sql
<Select Path="System">
  *[System[(Level=1 or Level=2) and TimeCreated[timediff(@SystemTime) &lt;= 86400000]]]
</Select>```
  </Query>
</QueryList>
"@

![Export custom view as XML](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec15-implementation.jpg)


$viewXml | Out-File "C:\EventViews\CriticalErrors-24h.xml"

## Query using custom view
Get-WinEvent -FilterXml (Get-Content "C:\EventViews\CriticalErrors-24h.xml" -Raw)

![Query using custom view](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec16-database.jpg)

Event Subscriptions

Event Subscriptions

Figure: Configuration and management dashboard with status overview.


![Event Subscriptions](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec17-implementation.jpg)

## Configure event forwarding (requires WinRM)
Enable-PSRemoting -Force

![Configure event forwarding (requires WinRM)](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec18-implementation.jpg)


## On collector server, configure subscription
wecutil cs /c:"C:\EventSubscriptions\RemoteErrors.xml"

![On collector server, configure subscription](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec19-implementation.jpg)


## Example subscription XML
$subscriptionXml = @"
<Subscription xmlns="http://schemas.microsoft.com/2006/03/windows/events/subscription">
  <SubscriptionId>RemoteErrors</SubscriptionId>
  <Description>Collect errors from remote servers</Description>
  <Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri>
  <ConfigurationMode>Custom</ConfigurationMode>
  <Delivery Mode="Push">
```text
<Batching>
  <MaxLatencyTime>300000</MaxLatencyTime>
</Batching>
<PushSettings>
  <Heartbeat Interval="3600000"/>
</PushSettings>```

![Example subscription XML](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec20-implementation.jpg)

  </Delivery>
  <Query>
```sql
<![CDATA[
<QueryList>
  <Query Id="0">
    <Select Path="System">*[System[(Level=1 or Level=2)]]</Select>
  </Query>
</QueryList>
]]>```
  </Query>
  <ReadExistingEvents>true</ReadExistingEvents>
  <TransportName>http</TransportName>
  <ContentFormat>RenderedText</ContentFormat>
  <Locale Language="en-US"/>
</Subscription>
"@

$subscriptionXml | Out-File "C:\EventSubscriptions\RemoteErrors.xml"

Troubleshooting Methodology

Systematic Diagnostics

Troubleshooting Methodology

## Performance diagnostics script
$diagScript = @"
Write-Host '========================================' -ForegroundColor Cyan
Write-Host 'Windows Server Performance Diagnostics' -ForegroundColor Cyan
Write-Host '========================================`n' -ForegroundColor Cyan

![Performance diagnostics script](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec22-performance.jpg)


## 1. Identify symptoms
Write-Host '1. System Overview:' -ForegroundColor Yellow
`$os = Get-CimInstance Win32_OperatingSystem
Write-Host "  OS: `$(`$os.Caption)" -ForegroundColor Green
Write-Host "  Uptime: `$(([datetime]::Now - `$os.LastBootUpTime).Days) days" -ForegroundColor Green

![1. Identify symptoms](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec23-generic.jpg)


## 2. Check CPU
Write-Host '`n2. CPU Analysis:' -ForegroundColor Yellow
`$cpu = Get-Counter '\Processor(_Total)\% Processor Time' | Select-Object -ExpandProperty CounterSamples
Write-Host "  CPU Usage: `$([math]::Round(`$cpu.CookedValue, 2))%" -ForegroundColor $(if (`$cpu.CookedValue -gt 80) {'Red'} else {'Green'})

![2. Check CPU](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec24-generic.jpg)


`$topCpuProcesses = Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
Write-Host "  Top CPU Processes:" -ForegroundColor Green
`$topCpuProcesses | Format-Table Name, @{N='CPU(s)';E={[math]::Round(`$_.CPU, 2)}} -AutoSize

## 3. Check Memory
Write-Host '3. Memory Analysis:' -ForegroundColor Yellow
`$freeMem = [math]::Round(`$os.FreePhysicalMemory / 1MB, 2)
Write-Host "  Available Memory: `$freeMem GB" -ForegroundColor $(if (`$freeMem -lt 1) {'Red'} elseif (`$freeMem -lt 2) {'Yellow'} else {'Green'})

![3. Check Memory](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec25-generic.jpg)


`$topMemProcesses = Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 5
Write-Host "  Top Memory Processes:" -ForegroundColor Green
`$topMemProcesses | Format-Table Name, @{N='Memory(MB)';E={[math]::Round(`$_.WorkingSet64 / 1MB, 2)}} -AutoSize

## 4. Check Disk
Write-Host '4. Disk Analysis:' -ForegroundColor Yellow
Get-Volume | Where-Object { `$_.DriveLetter } | ForEach-Object {
```powershell
`$freePercent = [math]::Round((`$_.SizeRemaining / `$_.Size) * 100, 2)
`$color = if (`$freePercent -lt 10) {'Red'} elseif (`$freePercent -lt 20) {'Yellow'} else {'Green'}
Write-Host "  `$(`$_.DriveLetter): `$freePercent% free" -ForegroundColor `$color```
}

![4. Check Disk](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec26-implementation.jpg)


`$diskQueue = Get-Counter '\PhysicalDisk(_Total)\Avg. Disk Queue Length' | Select-Object -ExpandProperty CounterSamples
Write-Host "  Disk Queue: `$([math]::Round(`$diskQueue.CookedValue, 2))" -ForegroundColor $(if (`$diskQueue.CookedValue -gt 2) {'Red'} else {'Green'})

## 5. Check Network
Write-Host '`n5. Network Analysis:' -ForegroundColor Yellow
Get-NetAdapter | Where-Object { `$_.Status -eq 'Up' } | ForEach-Object {
```powershell
Write-Host "  `$(`$_.Name): `$(`$_.LinkSpeed)" -ForegroundColor Green```
}

![5. Check Network](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec27-network.jpg)


## 6. Recent errors
Write-Host '`n6. Recent Errors (Last 24h):' -ForegroundColor Yellow
`$errors = Get-EventLog -LogName System -EntryType Error -After (Get-Date).AddDays(-1) -ErrorAction SilentlyContinue
Write-Host "  System Errors: `$(`$errors.Count)" -ForegroundColor $(if (`$errors.Count -gt 10) {'Red'} else {'Green'})

![6. Recent errors](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec28-troubleshooting.jpg)


if (`$errors.Count -gt 0) {
```text
`$errorGroups = `$errors | Group-Object Source | Sort-Object Count -Descending | Select-Object -First 5
Write-Host "  Top Error Sources:" -ForegroundColor Green
`$errorGroups | Format-Table Count, Name -AutoSize```
}

Write-Host '`n========================================' -ForegroundColor Cyan
Write-Host 'Diagnostics Complete' -ForegroundColor Cyan
Write-Host '========================================' -ForegroundColor Cyan
"@

$diagScript | Out-File "C:\Scripts\PerformanceDiag.ps1"

Bottleneck Identification

Bottleneck Identification

Figure: Copilot Studio – topic flow designer with test chat panel.


![Bottleneck Identification](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec29-implementation.jpg)

## Bottleneck detection script
$bottleneckScript = @"
function Test-CPUBottleneck {
```powershell
`$cpu = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue
`$queueLength = (Get-Counter '\System\Processor Queue Length').CounterSamples.CookedValue

![Bottleneck detection script](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec30-implementation.jpg)


if (`$cpu -gt 80 -or `$queueLength -gt 10) {
    [PSCustomObject]@{
        Type = 'CPU'
        Severity = if (`$cpu -gt 90) {'Critical'} else {'Warning'}
        Details = "CPU: `$([math]::Round(`$cpu, 2))%, Queue: `$queueLength"
        Recommendation = 'Identify high CPU processes. Consider adding CPU cores or optimizing applications.'
    }
}```
}

function Test-MemoryBottleneck {
```powershell
`$os = Get-CimInstance Win32_OperatingSystem
`$freeGB = `$os.FreePhysicalMemory / 1MB
`$pagesPerSec = (Get-Counter '\Memory\Pages/sec').CounterSamples.CookedValue

if (`$freeGB -lt 1 -or `$pagesPerSec -gt 1000) {
    [PSCustomObject]@{
        Type = 'Memory'
        Severity = if (`$freeGB -lt 0.5) {'Critical'} else {'Warning'}
        Details = "Free: `$([math]::Round(`$freeGB, 2)) GB, Paging: `$([math]::Round(`$pagesPerSec, 0))/sec"
        Recommendation = 'Close unnecessary applications. Add more RAM or optimize memory usage.'
    }
}```
}

function Test-DiskBottleneck {
```powershell
`$diskQueue = (Get-Counter '\PhysicalDisk(_Total)\Avg. Disk Queue Length').CounterSamples.CookedValue
`$diskLatency = (Get-Counter '\PhysicalDisk(_Total)\Avg. Disk sec/Read').CounterSamples.CookedValue * 1000

if (`$diskQueue -gt 2 -or `$diskLatency -gt 20) {
    [PSCustomObject]@{
        Type = 'Disk'
        Severity = if (`$diskQueue -gt 5 -or `$diskLatency -gt 50) {'Critical'} else {'Warning'}
        Details = "Queue: `$([math]::Round(`$diskQueue, 2)), Latency: `$([math]::Round(`$diskLatency, 2)) ms"
        Recommendation = 'Check disk health. Upgrade to SSD or add more disks. Optimize I/O patterns.'
    }
}```
}

function Test-NetworkBottleneck {
```powershell
`$adapters = Get-NetAdapter | Where-Object { `$_.Status -eq 'Up' }
foreach (`$adapter in `$adapters) {
    `$counter = Get-Counter "\Network Interface(`$(`$adapter.InterfaceDescription))\Output Queue Length" -ErrorAction SilentlyContinue
    if (`$counter -and `$counter.CounterSamples.CookedValue -gt 2) {
        [PSCustomObject]@{
            Type = 'Network'
            Severity = 'Warning'
            Details = "`$(`$adapter.Name): Output queue `$(`$counter.CounterSamples.CookedValue)"
            Recommendation = 'Check network utilization. Upgrade network adapter or optimize network traffic.'
        }
    }
}```
}

Write-Host 'Scanning for performance bottlenecks...' -ForegroundColor Cyan
`$bottlenecks = @()
`$bottlenecks += Test-CPUBottleneck
`$bottlenecks += Test-MemoryBottleneck
`$bottlenecks += Test-DiskBottleneck
`$bottlenecks += Test-NetworkBottleneck

if (`$bottlenecks) {
```text
Write-Host '`nBottlenecks detected:' -ForegroundColor Red
`$bottlenecks | Format-Table Type, Severity, Details, Recommendation -Wrap```
} else {
```text
Write-Host '`nNo bottlenecks detected. System performance is healthy.' -ForegroundColor Green```
}
"@

$bottleneckScript | Out-File "C:\Scripts\DetectBottlenecks.ps1"

Optimization Techniques

Service Optimization

Optimization Techniques

## Disable unnecessary services
$servicesToDisable = @(
```text
'XblAuthManager'    # Xbox Live Auth Manager
'XblGameSave'       # Xbox Live Game Save
'XboxGipSvc'        # Xbox Accessory Management
'XboxNetApiSvc'     # Xbox Live Networking```
)

![Disable unnecessary services](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec32-implementation.jpg)


foreach ($service in $servicesToDisable) {
```powershell
$svc = Get-Service -Name $service -ErrorAction SilentlyContinue
if ($svc) {
    Stop-Service -Name $service -Force
    Set-Service -Name $service -StartupType Disabled
    Write-Host "Disabled: $service" -ForegroundColor Green
}```
}

Visual Effects Optimization

Visual Effects Optimization

Figure: Power BI Desktop – report canvas with visuals, fields, and format pane.


![Visual Effects Optimization](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec33-bestpractice.jpg)

## Disable visual effects for better performance
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects"
Set-ItemProperty -Path $regPath -Name "VisualFXSetting" -Value 2  # Best performance

![Disable visual effects for better performance](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec34-performance.jpg)


## Disable animations
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop\WindowMetrics" -Name "MinAnimate" -Value "0"

![Disable animations](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec35-generic.jpg)

Startup Optimization


![Startup Optimization](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec36-bestpractice.jpg)

## List startup programs
Get-CimInstance Win32_StartupCommand | 
```text
Select-Object Name, Command, Location, User | 
Format-Table -AutoSize

List startup programs

Disable startup program (use Task Manager or modify registry)

Disable startup program (use Task Manager or modify registry)

Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "ProgramName"

Remove-ItemProperty -Path

Figure: SharePoint Migration Tool – progress dashboard with status and error log.


![Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "ProgramName"](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec39-migration.jpg)

## Architecture Decision and Tradeoffs

When designing server infrastructure solutions with Windows Server, consider these key architectural trade-offs:

| Approach | Best For | Tradeoff |
|----------|----------|----------|
| Managed / platform service | Rapid delivery, reduced ops burden | Less customisation, potential vendor lock-in |
| Custom / self-hosted | Full control, advanced tuning | Higher operational overhead and cost |

> **Recommendation:** Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

## Validation and Versioning

- Last validated: April 2026
- Validate examples against your tenant, region, and SKU constraints before production rollout.
- Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

## Security and Governance Considerations

- Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
- Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
- Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

## Cost and Performance Notes

- Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
- Baseline performance with synthetic and real-user checks before and after major changes.
- Scale resources with measured thresholds and revisit sizing after usage pattern changes.

## Official Microsoft References

- https://learn.microsoft.com/windows-server/
- https://learn.microsoft.com/windows/security/
- https://learn.microsoft.com/azure/azure-arc/

## Public Examples from Official Sources

- These examples are sourced from official public Microsoft documentation and sample repositories.
- Documentation examples: https://learn.microsoft.com/windows-server/
- Sample repositories: https://github.com/microsoft/Windows-Containers
- Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

## Key Takeaways

- Performance Monitor tracks CPU, memory, disk, and network metrics
- Data collector sets gather performance data over time
- Resource Monitor provides real-time resource analysis
- Event Viewer diagnoses system and application errors
- Systematic methodology identifies performance bottlenecks
- CPU, memory, disk, and network bottlenecks require different solutions
- Service and startup optimization improves system performance
- Regular monitoring prevents performance degradation

![Key Takeaways](/images/articles/windows-server/2025-11-03-windows-server-performance-monitoring-diagnostics-optimization-sec40-takeaway.jpg)


## Next Steps

- Set up performance monitoring with data collectors
- Create custom Event Viewer filters for critical events
- Implement automated performance diagnostics
- Establish performance baselines
- Schedule regular bottleneck scans
- Optimize services and startup programs
- Document performance trends


## Additional Resources

- [Performance Monitor](https://learn.microsoft.com/windows-server/administration/windows-commands/perfmon)
- [Resource Monitor](https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/cc770601(v=ws.11))
- [Event Viewer](https://learn.microsoft.com/shows/inside/event-viewer)
- [Performance Tuning](https://learn.microsoft.com/windows-server/administration/performance-tuning/)


---

*Monitor. Diagnose. Optimize. Improve.*
AI Assistant
AI Assistant

Article Assistant

Ask me about this article

AI
Hi! I'm here to help you understand this article. Ask me anything about the content, concepts, or implementation details.