How to Set Up Monitoring with Prometheus Windows Exporter on Windows Server 2025

The windows_exporter (formerly wmi_exporter) is the de-facto Prometheus exporter for Windows systems, maintained by the Prometheus Community and available at github.com/prometheus-community/windows_exporter. It exposes over 30 collectors covering CPU, memory, disk, network, services, processes, IIS, MSSQL, Active Directory, and more — all as standard Prometheus metrics on port 9182. Combined with a Prometheus server and Grafana, you get a production-grade observability stack for your Windows Server 2025 fleet with minimal configuration and no proprietary agents. This guide covers downloading and installing the exporter as a Windows service, selecting collectors, opening the firewall, configuring Prometheus to scrape it, understanding the most important Windows metrics, and importing a ready-made Grafana dashboard.

Prerequisites

  • Windows Server 2025 (Standard or Datacenter)
  • Local administrator rights
  • PowerShell 5.1 or 7.x
  • A Prometheus server (any platform) that can reach port 9182 on the Windows hosts
  • Grafana instance (optional but recommended) — can run on the Prometheus server or separately
  • Outbound internet access on the Windows server to download the exporter, or access to an internal file share

Step 1: Download windows_exporter

Download the latest MSI or standalone executable from the GitHub releases page. The MSI is the recommended approach for Windows Server as it installs the service automatically:

# Check latest release on GitHub and download MSI
$releaseApi = "https://api.github.com/repos/prometheus-community/windows_exporter/releases/latest"
$latest     = Invoke-RestMethod -Uri $releaseApi -Headers @{ "User-Agent" = "PS/7" }
$msiAsset   = $latest.assets | Where-Object { $_.name -match "windows_exporter.*amd64.*.msi" }

Write-Host "Downloading: $($msiAsset.name)"
Invoke-WebRequest -Uri $msiAsset.browser_download_url `
  -OutFile "C:Tempwindows_exporter.msi"

If you prefer the standalone .exe (useful when you need to customise startup flags manually):

$exeAsset = $latest.assets | Where-Object { $_.name -match "windows_exporter.*amd64.exe$" }
Invoke-WebRequest -Uri $exeAsset.browser_download_url `
  -OutFile "C:Program Fileswindows_exporterwindows_exporter.exe"

Step 2: Install as a Windows Service via MSI

The MSI installer accepts a ENABLED_COLLECTORS property that pre-configures which collectors are active. Install with a production-appropriate collector set:

$collectors = "cpu,cs,logical_disk,net,os,process,service,system,iis,tcp,memory"

msiexec.exe /i "C:Tempwindows_exporter.msi" /qn `
  ENABLED_COLLECTORS="$collectors" `
  LISTEN_PORT=9182 `
  TEXTFILE_DIR="C:ProgramDatawindows_exportertextfile_inputs"

# Verify the service was created and is running
Get-Service -Name "windows_exporter" | Select-Object Name, Status, StartType

The MSI creates the service with automatic startup. If the service did not start automatically:

Start-Service -Name "windows_exporter"

Step 3: Install via sc.exe with Custom Flags (Standalone EXE)

If you downloaded the standalone executable instead of the MSI, register it as a service manually using sc.exe:

# Create installation directory
New-Item -ItemType Directory -Path "C:Program Fileswindows_exporter" -Force

# Copy the executable
Copy-Item "C:Tempwindows_exporter.exe" "C:Program Fileswindows_exporterwindows_exporter.exe"

# Register as Windows service
$binPath = '"C:Program Fileswindows_exporterwindows_exporter.exe" ' +
           '--collectors.enabled cpu,cs,logical_disk,net,os,process,service,system,iis,tcp,memory ' +
           '--telemetry.addr 0.0.0.0:9182 ' +
           '--log.level info'

sc.exe create windows_exporter `
  binPath= $binPath `
  start= auto `
  DisplayName= "Prometheus Windows Exporter"

sc.exe description windows_exporter "Exposes Windows metrics to Prometheus on port 9182"

# Configure failure recovery
sc.exe failure windows_exporter reset= 86400 actions= restart/5000/restart/10000/restart/30000

# Start the service
sc.exe start windows_exporter

Step 4: Verify the Metrics Endpoint

Test that the exporter is serving metrics on the expected port:

# Check locally
Invoke-WebRequest -Uri "http://localhost:9182/metrics" -UseBasicParsing | 
  Select-Object -ExpandProperty Content | 
  Select-String "windows_os_physical_memory" | 
  Select-Object -First 5

# Check health endpoint
Invoke-WebRequest -Uri "http://localhost:9182/health" -UseBasicParsing

You should see output like:

# HELP windows_os_physical_memory_free_bytes ...
# TYPE windows_os_physical_memory_free_bytes gauge
windows_os_physical_memory_free_bytes 4.294967296e+09
windows_cpu_time_total{core="0,0",mode="idle"} 12345.67
windows_logical_disk_free_bytes{volume="C:"} 5.36870912e+10

Step 5: Add a Windows Firewall Rule

Open inbound TCP 9182 so the Prometheus server can scrape the exporter:

New-NetFirewallRule `
  -DisplayName "Prometheus windows_exporter" `
  -Direction Inbound `
  -Protocol TCP `
  -LocalPort 9182 `
  -Action Allow `
  -Profile Domain,Private `
  -RemoteAddress "10.0.0.0/8"  # restrict to your monitoring subnet

Replace 10.0.0.0/8 with your Prometheus server’s IP or subnet. Do not leave port 9182 open to the public internet.

Step 6: Configure Prometheus to Scrape Windows Hosts

On your Prometheus server, add a scrape job to prometheus.yml. The recommended approach for multiple Windows servers is to use a static file or service discovery:

# prometheus.yml — add under scrape_configs:
scrape_configs:
  - job_name: "windows_servers"
    scrape_interval: 15s
    scrape_timeout: 10s
    static_configs:
      - targets:
          - "10.0.1.10:9182"   # webserver01
          - "10.0.1.11:9182"   # webserver02
          - "10.0.1.20:9182"   # appserver01
          - "10.0.1.30:9182"   # dbserver01
        labels:
          environment: "production"
          datacenter: "dc01"

  # Alternatively, use file-based service discovery for dynamic inventories
  - job_name: "windows_servers_dynamic"
    file_sd_configs:
      - files:
          - "/etc/prometheus/targets/windows_*.yml"
        refresh_interval: 30s

An example targets file (/etc/prometheus/targets/windows_web.yml):

- targets:
    - "10.0.1.10:9182"
    - "10.0.1.11:9182"
  labels:
    role: "web"
    environment: "production"

Reload Prometheus after editing the configuration:

curl -X POST http://prometheus-server:9090/-/reload

Step 7: Key Windows Metrics to Monitor

Once Prometheus is scraping successfully, the following metrics are the most operationally important for Windows Server 2025:

CPU

# CPU usage per core (percentage of time NOT in idle mode)
100 - (avg by (instance) (rate(windows_cpu_time_total{mode="idle"}[5m])) * 100)

# System-wide CPU usage percentage
100 - avg(rate(windows_cpu_time_total{mode="idle"}[5m])) * 100

Memory

# Free physical memory in bytes
windows_os_physical_memory_free_bytes

# Memory usage percentage
100 - (windows_os_physical_memory_free_bytes / windows_cs_physical_memory_bytes * 100)

# Committed bytes (virtual memory pressure)
windows_os_virtual_memory_free_bytes

Disk

# Free bytes per logical disk
windows_logical_disk_free_bytes{volume="C:"}

# Disk I/O read/write bytes per second
rate(windows_logical_disk_read_bytes_total{volume="D:"}[5m])
rate(windows_logical_disk_write_bytes_total{volume="D:"}[5m])

# Disk queue length (> 2 indicates saturation)
windows_logical_disk_avg_disk_queue_length

Network

# Inbound/outbound bytes per second per interface
rate(windows_net_bytes_received_total{nic!~".*Loopback.*"}[5m])
rate(windows_net_bytes_sent_total{nic!~".*Loopback.*"}[5m])

Services

# Detect stopped services that should be running
windows_service_state{state="stopped", start_mode="auto"}

Step 8: Import the Grafana Dashboard

The community maintains a comprehensive pre-built Grafana dashboard for windows_exporter. Import Grafana Dashboard ID 14694 (Windows Node — windows_exporter):

  1. In Grafana, go to Dashboards → Import
  2. Enter dashboard ID 14694 and click Load
  3. Select your Prometheus data source
  4. Click Import

You can also import via the Grafana HTTP API:

# Download the dashboard JSON
Invoke-WebRequest `
  -Uri "https://grafana.com/api/dashboards/14694/revisions/latest/download" `
  -OutFile "C:Tempwindows_dashboard.json"

# Import via Grafana API
$dashJson = Get-Content "C:Tempwindows_dashboard.json" -Raw | ConvertFrom-Json
$importBody = @{
    dashboard = $dashJson
    overwrite  = $true
    inputs     = @(@{ name = "DS_PROMETHEUS"; type = "datasource"; pluginId = "prometheus"; value = "Prometheus" })
} | ConvertTo-Json -Depth 20

Invoke-RestMethod `
  -Uri "http://grafana-server:3000/api/dashboards/import" `
  -Method POST `
  -Headers @{ "Content-Type" = "application/json"; "Authorization" = "Bearer $env:GRAFANA_API_KEY" } `
  -Body $importBody

Step 9: Enabling the Textfile Collector for Custom Metrics

The textfile collector allows you to expose custom metrics by writing .prom files to a directory. This is ideal for application-level metrics or scheduled checks:

# Write a custom metric from a scheduled PowerShell task
$metricsPath = "C:ProgramDatawindows_exportertextfile_inputsapp_health.prom"

$pendingJobs = (Get-ChildItem "D:JobQueuePending" -Filter "*.job").Count
$failedJobs  = (Get-ChildItem "D:JobQueueFailed"  -Filter "*.job").Count

@"
# HELP myapp_job_queue_pending Number of pending jobs in queue
# TYPE myapp_job_queue_pending gauge
myapp_job_queue_pending $pendingJobs
# HELP myapp_job_queue_failed Number of failed jobs
# TYPE myapp_job_queue_failed gauge
myapp_job_queue_failed $failedJobs
"@ | Set-Content -Path $metricsPath -Encoding ASCII

These metrics appear at the /metrics endpoint within 15 seconds and can be graphed and alerted on in Prometheus and Grafana exactly like built-in metrics.

Conclusion

You now have a fully operational Prometheus monitoring stack for Windows Server 2025 — the windows_exporter service exposes dozens of system metrics on port 9182, Prometheus scrapes and stores them, and Grafana dashboard 14694 provides immediate operational visibility. The textfile collector extends the system cleanly for application-level observability without additional agents. For production alerting, add Prometheus alerting rules for high CPU (above 80% for 5 minutes), low disk space (below 10% free), stopped auto-start services, and high memory commit ratio, and route alerts through Alertmanager to your PagerDuty, Slack, or Teams notification channel.