How to Use Azure Monitor with Windows Server 2025
Azure Monitor is Microsoft’s unified observability platform, and while it is best known for monitoring Azure-hosted virtual machines, it is equally powerful for on-premises servers running Windows Server 2025. By combining Azure Arc with the Azure Monitor Agent (AMA), you can stream Windows Event Logs, performance counters, and custom metrics from your data center directly into Azure, apply Kusto Query Language (KQL) analytics, configure metric alerts, and build rich visualizations with Azure Monitor Workbooks—all without migrating your workloads to the cloud. This tutorial walks you through every step of that end-to-end setup.
Prerequisites
- Windows Server 2025 (Standard or Datacenter) with internet access or an ExpressRoute/VPN connection to Azure
- An Azure subscription with at least Contributor rights on the target resource group
- Azure CLI 2.55+ or Azure PowerShell Az module 11+ installed on an admin workstation
- The
Az.ConnectedMachine,Az.Monitor, andAz.OperationalInsightsPowerShell modules - A Log Analytics workspace already created (or you will create one in Step 2)
- Outbound HTTPS (443) allowed from the server to Azure endpoints (
*.ods.opinsights.azure.com,*.oms.opinsights.azure.com,management.azure.com)
Step 1: Connect the Server to Azure Arc
Azure Arc projects your on-premises server as an Azure resource, which is required before you can deploy the Azure Monitor Agent extension. Run the following commands on the Windows Server 2025 machine with an account that has local administrator rights.
# Install the Azure Connected Machine agent (onboarding script from Azure Portal)
# Download the Arc onboarding script generated from:
# Azure Portal > Azure Arc > Servers > + Add > Add a single server
# Run the generated script (example below shows the key parameters)
$env:SUBSCRIPTION_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$env:RESOURCE_GROUP = "rg-monitoring"
$env:TENANT_ID = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
$env:LOCATION = "eastus"
$env:AUTH_TYPE = "token"
# The onboarding script calls the MSI installer:
Invoke-WebRequest `
-Uri "https://aka.ms/azcmagent-windows" `
-OutFile "$env:TEMPinstall_windows_azcmagent.ps1"
& "$env:TEMPinstall_windows_azcmagent.ps1"
# Connect the agent to your Azure subscription
azcmagent connect `
--subscription-id $env:SUBSCRIPTION_ID `
--resource-group $env:RESOURCE_GROUP `
--tenant-id $env:TENANT_ID `
--location $env:LOCATION `
--correlation-id (New-Guid).ToString()
After the connection completes, verify the agent status:
azcmagent show
# Expected: Status = Connected
Step 2: Create a Log Analytics Workspace
A Log Analytics workspace is the data store that Azure Monitor uses to persist logs and make them queryable with KQL. If you already have one, record its Resource ID for later steps.
Import-Module Az.OperationalInsights
Connect-AzAccount -TenantId "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
Set-AzContext -SubscriptionId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$workspace = New-AzOperationalInsightsWorkspace `
-ResourceGroupName "rg-monitoring" `
-Name "law-winserver2025" `
-Location "eastus" `
-Sku "PerGB2018" `
-RetentionInDays 90
Write-Host "Workspace ID: $($workspace.CustomerId)"
Write-Host "Workspace Resource ID: $($workspace.ResourceId)"
Step 3: Deploy the Azure Monitor Agent via Arc Extension
The Azure Monitor Agent replaces the legacy MMA/OMS agent and is deployed as an Arc machine extension. You can deploy it with PowerShell or enforce it across all Arc-connected servers using Azure Policy.
Import-Module Az.ConnectedMachine
# Deploy AMA extension to the Arc-connected server
New-AzConnectedMachineExtension `
-ResourceGroupName "rg-monitoring" `
-MachineName "WS2025-SRV01" `
-Location "eastus" `
-Name "AzureMonitorWindowsAgent" `
-ExtensionType "AzureMonitorWindowsAgent" `
-Publisher "Microsoft.Azure.Monitor" `
-TypeHandlerVersion "1.10" `
-EnableAutomaticUpgrade
# Verify the extension is provisioned
Get-AzConnectedMachineExtension `
-ResourceGroupName "rg-monitoring" `
-MachineName "WS2025-SRV01" |
Select-Object Name, ProvisioningState, TypeHandlerVersion
For large fleets, assign the built-in policy “Configure Arc-enabled machines running Windows to run Azure Monitor Agent” (Policy ID: ca817e41-e85a-4783-bc7f-dc532d36235e) at the subscription or management group level.
Step 4: Create a Data Collection Rule (DCR)
Data Collection Rules define what data AMA collects and where it sends it. A single DCR can collect Windows Event Logs and Performance Counters simultaneously.
$workspaceResourceId = $workspace.ResourceId
$dcrBody = @{
location = "eastus"
properties = @{
dataSources = @{
windowsEventLogs = @(
@{
name = "eventLogsDataSource"
streams = @("Microsoft-Event")
xPathQueries = @(
"Application!*[System[(Level=1 or Level=2 or Level=3)]]"
"System!*[System[(Level=1 or Level=2 or Level=3)]]"
"Security!*[System[(band(Keywords,13510798882111488))]]"
)
}
)
performanceCounters = @(
@{
name = "perfCountersDataSource"
streams = @("Microsoft-Perf")
samplingFrequencyInSeconds = 60
counterSpecifiers = @(
"Processor(_Total)% Processor Time"
"MemoryAvailable MBytes"
"LogicalDisk(_Total)% Free Space"
"LogicalDisk(_Total)Disk Reads/sec"
"LogicalDisk(_Total)Disk Writes/sec"
"Network Interface(*)Bytes Total/sec"
)
}
)
}
destinations = @{
logAnalytics = @(
@{
workspaceResourceId = $workspaceResourceId
name = "centralWorkspace"
}
)
}
dataFlows = @(
@{
streams = @("Microsoft-Event")
destinations = @("centralWorkspace")
}
@{
streams = @("Microsoft-Perf")
destinations = @("centralWorkspace")
}
)
}
} | ConvertTo-Json -Depth 10
# Create the DCR via REST
$token = (Get-AzAccessToken).Token
$subId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$rg = "rg-monitoring"
$dcrName = "dcr-winserver2025"
Invoke-RestMethod `
-Method Put `
-Uri "https://management.azure.com/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.Insights/dataCollectionRules/$dcrName?api-version=2022-06-01" `
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
-Body $dcrBody
After the DCR is created, associate it with the Arc-connected server:
$dcrResourceId = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.Insights/dataCollectionRules/$dcrName"
$machineId = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.HybridCompute/machines/WS2025-SRV01"
$assocBody = @{
properties = @{
dataCollectionRuleId = $dcrResourceId
}
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "https://management.azure.com$machineId/providers/Microsoft.Insights/dataCollectionRuleAssociations/dcra-winserver2025?api-version=2022-06-01" `
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
-Body $assocBody
Step 5: Query Data with KQL in Log Analytics
Once data begins flowing (allow 5–15 minutes after DCR association), open the Log Analytics workspace in the Azure Portal and run KQL queries:
# Run KQL queries via PowerShell using Invoke-AzOperationalInsightsQuery
Import-Module Az.OperationalInsights
$workspaceId = $workspace.CustomerId
# Top 10 most frequent events in the last hour
$query1 = @"
Event
| where TimeGenerated > ago(1h)
| summarize Count = count() by EventID, RenderedDescription
| top 10 by Count desc
"@
Invoke-AzOperationalInsightsQuery -WorkspaceId $workspaceId -Query $query1 |
Select-Object -ExpandProperty Results
# CPU utilization average over 5-minute windows
$query2 = @"
Perf
| where ObjectName == "Processor" and CounterName == "% Processor Time"
| where InstanceName == "_Total"
| summarize AvgCPU = avg(CounterValue) by bin(TimeGenerated, 5m), Computer
| order by TimeGenerated desc
"@
Invoke-AzOperationalInsightsQuery -WorkspaceId $workspaceId -Query $query2 |
Select-Object -ExpandProperty Results
Step 6: Enable VM Insights for Azure Monitor Metrics
VM Insights extends Azure Monitor with pre-built performance and dependency maps. For Arc-connected servers, enable it through the Azure Portal under Azure Monitor > Virtual Machines > Enable, or via CLI:
# Enable VM Insights on Arc machine (requires Dependency Agent for map feature)
New-AzConnectedMachineExtension `
-ResourceGroupName "rg-monitoring" `
-MachineName "WS2025-SRV01" `
-Location "eastus" `
-Name "DependencyAgentWindows" `
-ExtensionType "DependencyAgentWindows" `
-Publisher "Microsoft.Azure.Monitoring.DependencyAgent" `
-TypeHandlerVersion "9.10" `
-EnableAutomaticUpgrade
Step 7: Configure Metric Alerts
Create alerts that fire when CPU exceeds 90% or available disk drops below 10 GB. For Arc-connected machines, metric alerts use the Log-based metrics routed through Log Analytics.
Import-Module Az.Monitor
# Action group for email notification
$emailReceiver = New-AzActionGroupReceiver `
-Name "OpsTeam" `
-EmailAddress "[email protected]"
$actionGroup = Set-AzActionGroup `
-ResourceGroupName "rg-monitoring" `
-Name "ag-server-alerts" `
-ShortName "SrvAlerts" `
-Receiver $emailReceiver
$actionGroupId = $actionGroup.Id
# Scheduled Query Rule — CPU > 90% for 5 minutes
$cpuQuery = @"
Perf
| where ObjectName == "Processor" and CounterName == "% Processor Time" and InstanceName == "_Total"
| where Computer == "WS2025-SRV01"
| summarize AvgCPU = avg(CounterValue) by bin(TimeGenerated, 5m)
| where AvgCPU > 90
"@
New-AzScheduledQueryRule `
-ResourceGroupName "rg-monitoring" `
-Name "alert-cpu-high" `
-Location "eastus" `
-ActionGroupResourceId @($actionGroupId) `
-Severity 2 `
-WindowSize (New-TimeSpan -Minutes 5) `
-EvaluationFrequency (New-TimeSpan -Minutes 5) `
-Query $cpuQuery `
-Scope @($workspace.ResourceId) `
-CriterionAllOf @(
New-AzScheduledQueryRuleConditionObject `
-Query $cpuQuery `
-TimeAggregation "Count" `
-Operator "GreaterThan" `
-Threshold 0
)
# Disk space alert — less than 10 GB free
$diskQuery = @"
Perf
| where ObjectName == "LogicalDisk" and CounterName == "Free Megabytes" and InstanceName == "C:"
| where Computer == "WS2025-SRV01"
| summarize AvgFreeMB = avg(CounterValue) by bin(TimeGenerated, 15m)
| where AvgFreeMB < 10240
"@
New-AzScheduledQueryRule `
-ResourceGroupName "rg-monitoring" `
-Name "alert-disk-low" `
-Location "eastus" `
-ActionGroupResourceId @($actionGroupId) `
-Severity 1 `
-WindowSize (New-TimeSpan -Minutes 15) `
-EvaluationFrequency (New-TimeSpan -Minutes 15) `
-Query $diskQuery `
-Scope @($workspace.ResourceId) `
-CriterionAllOf @(
New-AzScheduledQueryRuleConditionObject `
-Query $diskQuery `
-TimeAggregation "Count" `
-Operator "GreaterThan" `
-Threshold 0
)
Step 8: Create Azure Monitor Workbooks
Azure Monitor Workbooks combine KQL queries, parameters, and charts into interactive dashboards. Create a workbook programmatically or use the built-in VM Insights Performance workbook template from the Azure Portal under Azure Monitor > Workbooks. For custom workbooks via ARM:
# List available workbook templates for VM Insights
$token = (Get-AzAccessToken).Token
$subId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Invoke-RestMethod `
-Method Get `
-Uri "https://management.azure.com/subscriptions/$subId/providers/microsoft.insights/workbooktemplates?api-version=2020-11-20" `
-Headers @{ Authorization = "Bearer $token" } |
Select-Object -ExpandProperty value |
Where-Object { $_.properties.templateData -match "performance" } |
Select-Object name, @{n="galleries";e={$_.properties.galleries}}
In the Azure Portal, navigate to Log Analytics workspace > Workbooks > + New, add a query tile with the CPU KQL query from Step 5, add a time range parameter, pin it to your Azure dashboard, and share the workbook URL with your operations team.
Conclusion
You have now built a complete observability pipeline for Windows Server 2025 using Azure Monitor without migrating the server to the cloud. The Azure Arc agent bridges your on-premises infrastructure to Azure’s management plane, the Azure Monitor Agent collects events and performance counters according to your Data Collection Rule, KQL queries surface actionable insights in Log Analytics, metric alerts proactively notify your team when thresholds are breached, and Azure Monitor Workbooks present that data in shareable, interactive dashboards. As your estate grows, this same pattern scales by simply assigning DCR associations and Arc policies at the subscription level—giving you consistent observability across hundreds of servers with minimal per-server configuration.