Windows Server monitoring: baselines, events and alerts

“Is the server slow, or is it always like this?” You can only answer that if you measured it before. Windows Server monitoring means collecting the right counters and events, knowing what normal looks like, and hearing about problems before users do.

In this lesson you’ll read live performance counters, build a baseline with a data collector set, forward error events from every server to one collector with Windows Event Forwarding, and trigger a simple alert from an event.

TimeYou needBy the end you can
About 90 minutesDC01, DC02 and SRV01Read key performance counters, create and analyse a baseline, set up Windows Event Forwarding, build event-triggered alerts and avoid alert fatigue

What it is, in one line

Windows Server monitoring is watching servers’ performance counters, event logs and services over time, comparing them with a known baseline, and alerting when something drifts or fails.

In Simple Words

A doctor’s check-up chart

A good doctor writes down your normal temperature, pulse and blood pressure when you’re healthy. Later, when you feel ill, she compares today’s numbers with that chart and can see what changed.

A baseline is that chart for a server: normal CPU, memory, disk and network use. Event logs are the patient’s diary of what happened. Alerts are the alarm on the hospital monitor.

And if the monitor beeps every minute for nothing, the nurses start ignoring it. That’s alert fatigue, and it’s as dangerous as no monitoring.

Engineer's View

Counters, baselines, WEF and alerts

Key counters: CPU (Processor(_Total)\% Processor Time, Processor Queue Length), memory (Memory\Available MBytes, Pages/sec), disk (Avg. Disk sec/Read and /Write, where over 20 ms is slow for most workloads, Current Disk Queue Length, % Free Space) and network (Bytes Total/sec, errors). Read them with Get-Counter, Performance Monitor, or Windows Admin Center.

Baselines: a data collector set (Performance Monitor or logman) records counters to a .blg file at a fixed interval. Collect over normal business cycles, keep the files, and compare during incidents with relog or PowerShell.

Events: Windows Event Forwarding (WEF) sends chosen events from many servers to a collector’s ForwardedEvents log over WinRM, using subscriptions configured by Group Policy (source-initiated). From there, a SIEM or Azure Monitor can collect them.

Alerts: event-triggered scheduled tasks, Performance Monitor alerts, or proper monitoring tools (Azure Monitor with Arc, System Center Operations Manager, Zabbix, PRTG). Alert on symptoms users feel and on things that will break soon, not on every warning.

How it works, step by step

How events from many servers end up in one place:

Windows Server monitoring: Windows Event Forwarding from DCs to the SRV01 collector

Each source server reads the subscription from Group Policy, connects to the collector over WinRM (port 5985) and pushes matching events as they happen. You read one log instead of signing in to every server.

What you’ll set up in the lab:

Windows Server monitoring counters: CPU, memory, disk and network to check first
  1. Live counters on SRV01 with Get-Counter.
  2. Baseline: a data collector set, SRV01-Baseline, sampling every 15 seconds into a circular file.
  3. WEF: SRV01 as collector, DC01 and DC02 as sources, forwarding errors and critical events from System and Application.
  4. Alert: a scheduled task that writes an alert line when a specific event appears.

Hands-on Lab: Windows Server monitoring for the lab servers

Start DC01, DC02 and SRV01. Work on SRV01 as SAATLAB\adm.rahul in PowerShell as administrator unless a step says otherwise.

1Read live performance counters

On SRV01:

$counters = '\Processor(_Total)\% Processor Time', '\Memory\Available MBytes', '\LogicalDisk(C:)\% Free Space', '\PhysicalDisk(_Total)\Avg. Disk sec/Read'
Get-Counter -Counter $counters -SampleInterval 2 -MaxSamples 1 | Select-Object -ExpandProperty CounterSamples | Select-Object Path, @{n='Value';e={[math]::Round($_.CookedValue,3)}}

You should see something like this:

Path                                             Value
----                                             -----
\\srv01\processor(_total)\% processor time       4.127
\\srv01\memory\available mbytes                    812
\\srv01\logicaldisk(c:)\% free space            61.834
\\srv01\physicaldisk(_total)\avg. disk sec/read  0.002

Avg. Disk sec/Read is in seconds, so 0.002 is 2 ms: fast. On its own, one sample tells you little.

Is 812 MB of available memory normal for SRV01? Only a baseline can say.

✅ Checkpoint: Get-Counter returns four values for SRV01.

2Record a baseline with a data collector set

On SRV01:

logman create counter SRV01-Baseline -c "\Processor(_Total)\% Processor Time" "\System\Processor Queue Length" "\Memory\Available MBytes" "\Memory\Pages/sec" "\PhysicalDisk(_Total)\Avg. Disk sec/Transfer" "\PhysicalDisk(_Total)\Current Disk Queue Length" "\Network Interface(*)\Bytes Total/sec" -si 00:00:15 -o C:\PerfLogs\SRV01-Baseline -f bincirc -max 200
logman start SRV01-Baseline
logman query SRV01-Baseline | Select-String 'Status|Output Location|Sample Interval'

You should see something like this:

The command completed successfully.
The command completed successfully.
Status:               Running
Output Location:      C:\PerfLogs\SRV01-Baseline_000001.blg
Sample Interval:      15 second(s)

-f bincirc -max 200 writes a circular binary file of at most 200 MB, so the baseline can run for weeks without filling the disk. Let it run while you do the rest of the lesson, then analyse it:

Later, on SRV01:

logman stop SRV01-Baseline
relog C:\PerfLogs\SRV01-Baseline_000001.blg -f csv -o C:\PerfLogs\baseline.csv -y | Out-Null
$rows = Import-Csv C:\PerfLogs\baseline.csv
$cpu = ($rows | Select-Object -ExpandProperty ($rows[0].PSObject.Properties.Name | Where-Object { $_ -like '*Processor Time' })) | Where-Object { $_.Trim() } | ForEach-Object { [double]$_ }
'CPU average {0:N1}%, max {1:N1}% over {2} samples' -f ($cpu | Measure-Object -Average).Average, ($cpu | Measure-Object -Maximum).Maximum, $cpu.Count
logman start SRV01-Baseline

You should see something like this:

CPU average 3.8%, max 41.2% over 240 samples

✅ Checkpoint: SRV01-Baseline is collecting every 15 seconds and you calculated SRV01’s average and peak CPU from it.

3Make SRV01 an event collector

On SRV01, configure the collector service and create a subscription for errors:

wecutil qc /q
@'
<Subscription xmlns="http://schemas.microsoft.com/2006/03/windows/events/subscription">
  <SubscriptionId>Server-Errors</SubscriptionId>
  <SubscriptionType>SourceInitiated</SubscriptionType>
  <Description>Critical and error events from servers and DCs</Description>
  <Enabled>true</Enabled>
  <Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri>
  <ConfigurationMode>Normal</ConfigurationMode>
  <Query><![CDATA[<QueryList><Query Id="0"><Select Path="System">*[System[(Level=1 or Level=2)]]</Select><Select Path="Application">*[System[(Level=1 or Level=2)]]</Select></Query></QueryList>]]></Query>
  <ReadExistingEvents>false</ReadExistingEvents>
  <TransportName>HTTP</TransportName>
  <ContentFormat>RenderedText</ContentFormat>
  <LogFile>ForwardedEvents</LogFile>
  <AllowedSourceDomainComputers>O:NSG:BAD:P(A;;GA;;;DC)(A;;GA;;;DD)S:</AllowedSourceDomainComputers>
</Subscription>
'@ | Set-Content C:\Scripts\Server-Errors.xml
wecutil cs C:\Scripts\Server-Errors.xml
wecutil gs Server-Errors | Select-String 'SubscriptionType|Enabled|AllowedSourceDomainComputers'

You should see something like this:

SubscriptionType: SourceInitiated
Enabled: true
AllowedSourceDomainComputers: O:NSG:BAD:P(A;;GA;;;DC)(A;;GA;;;DD)S:

The SDDL allows Domain Computers (DC) and Domain Controllers (DD) to send events. Level 1 is Critical and 2 is Error. Source-initiated means each server finds the collector by Group Policy, so new servers join automatically.

✅ Checkpoint: The Server-Errors subscription exists on SRV01 and is enabled.

4Point the DCs at the collector with Group Policy

On DC01:

New-GPO -Name 'C-Event-Forwarding' | New-GPLink -Target 'OU=Domain Controllers,DC=saatlab,DC=internal' | Out-Null
Set-GPRegistryValue -Name 'C-Event-Forwarding' -Key 'HKLM\Software\Policies\Microsoft\Windows\EventLog\EventForwarding\SubscriptionManager' -ValueName 1 -Type String -Value 'Server=http://SRV01.saatlab.internal:5985/wsman/SubscriptionManager/WEC,Refresh=60' | Out-Null
Invoke-Command -ComputerName DC01, DC02 -ScriptBlock { gpupdate /force | Out-Null }

That’s the setting Computer Configuration, Administrative Templates, Windows Components, Event Forwarding, Configure target Subscription Manager. Link the same GPO to the Servers OU to include member servers.

Create a test error on DC02, then check the collector:

Invoke-Command -ComputerName DC02 -ScriptBlock { eventcreate /T ERROR /ID 999 /L APPLICATION /SO SaatLabTest /D "Test error for event forwarding" }
Start-Sleep -Seconds 90
wecutil gr Server-Errors | Select-String 'saatlab.internal|RunTimeStatus'
Get-WinEvent -LogName ForwardedEvents -MaxEvents 1 | Select-Object MachineName, Id, ProviderName, Message

You should see something like this:

        DC01.saatlab.internal
            RunTimeStatus: Active
        DC02.saatlab.internal
            RunTimeStatus: Active

MachineName           Id ProviderName Message
-----------           -- ------------ -------
DC02.saatlab.internal 999 SaatLabTest  Test error for event forwarding

✅ Checkpoint: Both DCs show Active in the subscription, and DC02’s test error is in SRV01’s ForwardedEvents log.

Windows Server monitoring lab: both DCs active and a forwarded test error on SRV01
Both DCs forward to SRV01, and DC02’s test error has arrived.

5Turn an event into an alert

On SRV01, create a task that fires whenever a forwarded event 999 arrives:

$q = "<QueryList><Query Id='0' Path='ForwardedEvents'><Select Path='ForwardedEvents'>*[System[EventID=999]]</Select></Query></QueryList>"
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -Command Add-Content C:\Scripts\alerts.log ('{0} ALERT event 999 received' -f (Get-Date))"
$trigger = New-CimInstance -CimClass (Get-CimClass -Namespace ROOT\Microsoft\Windows\TaskScheduler -ClassName MSFT_TaskEventTrigger) -ClientOnly -Property @{ Enabled = $true; Subscription = $q }
Register-ScheduledTask -TaskName 'Alert on event 999' -Action $action -Trigger $trigger -User SYSTEM | Out-Null
Invoke-Command -ComputerName DC02 -ScriptBlock { eventcreate /T ERROR /ID 999 /L APPLICATION /SO SaatLabTest /D "Second test" }
Start-Sleep -Seconds 90; Get-Content C:\Scripts\alerts.log

You should see something like this:

20/10/2026 02:14:37 ALERT event 999 received

In real life the action sends an email, posts to Teams or opens a ticket. Pick a few events that really matter (a failed backup, a cluster node leaving, a disk filling up) rather than every error.

✅ Checkpoint: The second test event reached SRV01 and the task wrote an alert line to C:\Scripts\alerts.log.

Break It, Then Fix It

Monitoring fails quietly. Here are two ways it stops without telling you.

1Break it: a typo in the subscription manager address

On DC01, set the GPO value with a typo in the collector name and refresh DC02:

Set-GPRegistryValue -Name 'C-Event-Forwarding' -Key 'HKLM\Software\Policies\Microsoft\Windows\EventLog\EventForwarding\SubscriptionManager' -ValueName 1 -Type String -Value 'Server=http://SRV10.saatlab.internal:5985/wsman/SubscriptionManager/WEC,Refresh=60' | Out-Null
Invoke-Command -ComputerName DC02 -ScriptBlock { gpupdate /force | Out-Null; Restart-Service WinRM }

After a few minutes, check the source’s forwarding log on DC02:

Invoke-Command -ComputerName DC02 -ScriptBlock {
    Get-WinEvent -LogName 'Microsoft-Windows-Forwarding/Operational' -MaxEvents 1 | Select-Object Id, LevelDisplayName, Message
} | Format-List

You’ll see something like this:

Id               : 105
LevelDisplayName : Error
Message          : The forwarder is having a problem communicating with subscription manager at address
                   http://SRV10.saatlab.internal:5985/wsman/SubscriptionManager/WEC. Error code is 2150859193 and Error
                   Message is The WinRM client cannot process the request because the server name cannot be resolved.

The collector shows nothing wrong; it simply stops receiving events from DC02. Only the source’s Forwarding log explains why. A single wrong character in a GPO can blind your monitoring for every server it applies to.

Fix it: correct the address and refresh:

Set-GPRegistryValue -Name 'C-Event-Forwarding' -Key 'HKLM\Software\Policies\Microsoft\Windows\EventLog\EventForwarding\SubscriptionManager' -ValueName 1 -Type String -Value 'Server=http://SRV01.saatlab.internal:5985/wsman/SubscriptionManager/WEC,Refresh=60' | Out-Null
Invoke-Command -ComputerName DC02 -ScriptBlock { gpupdate /force | Out-Null }

✅ Checkpoint: DC02 logged event 105 with the typo and shows Active in wecutil gr Server-Errors after the fix.

Windows Server monitoring lab: forwarding error 105 from a wrong collector address
A one-letter typo in the collector name stops forwarding; only the source’s log says why.

2Break it: a baseline that stops after a restart

Restart SRV01, sign in, and check the data collector set:

logman query SRV01-Baseline | Select-String 'Status'

You’ll see this:

Status:               Stopped

Data collector sets started by hand don’t start again after a reboot. After the next patch night, your baseline silently ends, and when you need data from last week, there’s none.

Fix it: start it automatically at boot with a scheduled task:

$a = New-ScheduledTaskAction -Execute 'logman.exe' -Argument 'start SRV01-Baseline'
$t = New-ScheduledTaskTrigger -AtStartup
Register-ScheduledTask -TaskName 'Start SRV01 baseline' -Action $a -Trigger $t -User SYSTEM -RunLevel Highest | Out-Null
Restart-Computer

After the restart:

logman query SRV01-Baseline | Select-String 'Status'

You should see this:

Status:               Running

✅ Checkpoint: The collector was Stopped after a restart, and Running after adding the startup task.

This happened at work

3,000 emails and one that mattered

A hosting company’s monitoring sent an email for every warning on every server: service restarts, slow disks for a second, certificate notices, backup retries. The shared inbox received about 3,000 alerts a day, and the team had set up a rule to file them away.

One of those emails said a RAID array on a database server was degraded. It sat unread for nine days. Then a second disk in the same array failed, and the database was lost.

The restore from backup took fourteen hours.

The review found the warning had been sent correctly and on time. The problem was the other 2,999 emails around it.

The fix: alerts were split into “act now” (paging, a few per week) and “review daily” (a dashboard), duplicates were removed, and every alert got an owner and a runbook. Hardware health alerts went straight to “act now”.

The lesson: monitoring that alerts on everything alerts on nothing. Fewer, meaningful alerts get acted on.

Common mistakes and troubleshooting

Mistake or symptomWhat happensWhat to do instead
No baselineYou can’t tell slow from normalCollect counters over normal weeks and keep them
Data collector set not restarted after rebootThe baseline silently stopsStart it at boot with a task or schedule
Collector files without size limitsLogs fill the diskCircular files with a maximum size
WEF subscription manager typoSources stop forwarding quietlyCheck Forwarding/Operational on sources; wecutil gr on the collector
Alerting on every warningAlert fatigue; real problems ignoredFew actionable alerts with owners and runbooks
Only CPU and memory watchedDisk latency and free space problems missedWatch disk latency, queue and free space too
Monitoring the monitor isn’t doneThe monitoring server fails unnoticedHeartbeat checks from a second system
Reading events server by serverSlow incident responseForward to a collector or SIEM
One-off Get-Counter checks during incidentsNo comparison pointCompare with the stored baseline

Interview questions

Click a question to open it. Under each one you’ll find what the interviewer is really looking for, and one good way to answer.

Fresher level

1. Which performance counters would you check first on a slow server?

What they’re really checking: Do you know the basics?

How you could answer: Processor % Processor Time and queue length, Memory Available MBytes and Pages/sec, disk Avg. Disk sec/Read and Write and queue length, and network Bytes Total/sec.

2. What is a performance baseline?

What they’re really checking: Do you understand the idea?

How you could answer: A record of normal performance over time, used to compare against when something seems wrong.

3. Where do you look for errors on a Windows server?

What they’re really checking: Do you know the logs?

How you could answer: Event Viewer: System, Application and Security logs, plus Applications and Services logs for specific roles, or Get-WinEvent.

2–5 years experience

4. How does Windows Event Forwarding work?

What they’re really checking: Have you configured WEF?

How you could answer: A collector runs the Windows Event Collector service with subscriptions; sources get the subscription manager address via Group Policy and push matching events over WinRM to the ForwardedEvents log.

5. What disk latency is a problem?

What they’re really checking: Do you know thresholds?

How you could answer: Generally consistent averages above 20 ms for reads or writes on general workloads, lower for databases; compare with the baseline and the storage system’s own metrics.

6. How do you keep a data collector set running long term?

What they’re really checking: Do you avoid silent failures?

How you could answer: Use circular logs with size limits, start it at boot through a task or the set’s schedule, and check its status in your daily report.

Senior level

7. Design monitoring for 200 Windows servers.

What they’re really checking: Do you think in layers?

How you could answer: Central tool (Azure Monitor with Arc, SCOM or similar) for metrics and availability, WEF or agents for events into a SIEM, baselines per role, a small set of actionable alerts with runbooks, dashboards for trends, and monitoring of the monitoring itself.

8. How do you reduce alert fatigue?

What they’re really checking: Do you manage alert quality?

How you could answer: Alert on user-impacting symptoms and imminent failures, set thresholds from baselines, deduplicate and correlate, route by owner and urgency, review noisy alerts weekly, and require a runbook for each alert.

9. What security events would you forward from DCs?

What they’re really checking: Do you link monitoring and security?

How you could answer: Account logon failures and lockouts (4625, 4771, 4740), privileged group changes (4728, 4732, 4756), new accounts (4720), service tickets for Kerberoasting (4769 with RC4), audit policy changes (4719) and log clearing (1102).

Quick quiz

Q1. Avg. Disk sec/Read shows 0.025. What does that mean?

  1. 2.5 ms
  2. 25 ms, which is slow for many workloads
  3. 25 seconds
Show answer

B — The counter is in seconds.

Q2. What does -f bincirc -max 200 do for a data collector set?

  1. Compresses data
  2. Keeps a circular file of at most 200 MB
  3. Samples every 200 seconds
Show answer

B — It can run for weeks safely.

Q3. Where did the WEF typo show up?

  1. On the collector
  2. In the source’s Forwarding/Operational log
  3. Nowhere
Show answer

B — Check sources when events stop.

Q4. Why was the baseline Stopped after a restart?

  1. It crashed
  2. Manually started sets don’t restart at boot
  3. The disk was full
Show answer

B — Start it with a startup task.

Q5. In the work story, why was the RAID warning missed?

  1. Monitoring was off
  2. It was buried among 3,000 daily alerts
  3. The email bounced
Show answer

B — Fewer, meaningful alerts get acted on.

Quick recap

  • Windows Server monitoring combines counters, events and alerts, compared with a baseline.
  • Watch CPU, memory, disk latency, free space and network; Get-Counter reads them live.
  • Record baselines with logman data collector sets in circular files, and start them at boot.
  • Windows Event Forwarding collects events from many servers into ForwardedEvents.
  • Turn important events into alerts with event-triggered tasks or a monitoring tool.
  • Avoid alert fatigue: few, actionable alerts with owners.

Next lesson

Lesson 40: Modern management → You’ll install Windows Admin Center, manage SRV01 and the cluster from a browser, and look at Azure Arc, hotpatching and pay-as-you-go licensing for Server 2025.

Further reading: Windows Server monitoring

Want to go deeper into Windows Server monitoring? Microsoft’s Windows Event Forwarding guide explains subscriptions in depth, and the logman command reference covers data collector sets. You can also see the full plan for this course on the Windows Server Administration course page.

Scroll to Top