How to Set Up Log Analytics with Windows Event Logs on Windows Server 2025

Windows Event Logs are the primary audit trail for security events, application errors, and system state changes on any Windows Server. Collecting those logs into an Azure Log Analytics workspace centralizes your data, enables powerful Kusto Query Language (KQL) searches, and lays the foundation for a full SIEM using Microsoft Sentinel. Windows Server 2025 pairs excellently with the modern Azure Monitor Agent (AMA), which replaces the legacy Log Analytics agent (MMA) and introduces granular, channel-level filtering through Data Collection Rules (DCRs). This tutorial covers every step from workspace creation through Sentinel enablement, including XPath event filtering and advanced KQL analytics.

Prerequisites

  • Windows Server 2025 connected to Azure Arc (see the Azure Monitor tutorial for Arc onboarding steps)
  • Azure Monitor Agent (AMA) deployed to the Arc-connected server
  • Azure subscription with Log Analytics Contributor and Microsoft Sentinel Contributor roles
  • PowerShell Az module (Az.OperationalInsights, Az.Monitor) installed on your admin workstation
  • Outbound HTTPS to *.ods.opinsights.azure.com and *.oms.opinsights.azure.com
  • At least 90 days log retention planned (recommended for security compliance)

Step 1: Create the Log Analytics Workspace

If you already have a workspace, skip to Step 2. Otherwise, create a dedicated workspace for security log collection. It is best practice to separate security logs from operational monitoring logs to control costs and apply independent RBAC.

Import-Module Az.OperationalInsights

Connect-AzAccount
Set-AzContext -SubscriptionId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

$workspace = New-AzOperationalInsightsWorkspace `
    -ResourceGroupName "rg-security" `
    -Name              "law-security-ws2025" `
    -Location          "eastus" `
    -Sku               "PerGB2018" `
    -RetentionInDays   180  # 180 days for compliance requirements

Write-Host "Workspace ID  : $($workspace.CustomerId)"
Write-Host "Workspace RID : $($workspace.ResourceId)"

# Enable immutable archival for compliance (90 days hot + 90 days archive)
$workspaceRid = $workspace.ResourceId
$token = (Get-AzAccessToken).Token

$archiveBody = @{
    properties = @{
        retentionInDays        = 90
        totalRetentionInDays   = 180
    }
} | ConvertTo-Json

Invoke-RestMethod `
    -Method  Patch `
    -Uri     "https://management.azure.com$workspaceRid`?api-version=2023-09-01" `
    -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
    -Body    $archiveBody

Step 2: Configure a DCR to Collect Windows Event Log Channels

Data Collection Rules are the AMA’s instruction set. You specify which event channels to collect using W3C XPath 1.0 queries. The three most important channels for security are Security, System, and Application.

$subId          = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$rg             = "rg-security"
$workspaceRid   = $workspace.ResourceId
$token          = (Get-AzAccessToken).Token
$dcrName        = "dcr-security-eventlogs"

$dcrBody = @{
    location   = "eastus"
    properties = @{
        dataSources = @{
            windowsEventLogs = @(
                @{
                    name    = "securityChannel"
                    streams = @("Microsoft-SecurityEvent")
                    xPathQueries = @(
                        # All Audit Success and Audit Failure events in Security log
                        "Security!*[System[(band(Keywords,13510798882111488))]]"
                    )
                }
                @{
                    name    = "systemChannel"
                    streams = @("Microsoft-Event")
                    xPathQueries = @(
                        # Critical, Error, and Warning events from System
                        "System!*[System[(Level=1 or Level=2 or Level=3)]]"
                    )
                }
                @{
                    name    = "applicationChannel"
                    streams = @("Microsoft-Event")
                    xPathQueries = @(
                        # Critical and Error events from Application
                        "Application!*[System[(Level=1 or Level=2)]]"
                    )
                }
            )
        }
        destinations = @{
            logAnalytics = @(
                @{
                    workspaceResourceId = $workspaceRid
                    name                = "securityWorkspace"
                }
            )
        }
        dataFlows = @(
            @{
                streams      = @("Microsoft-SecurityEvent")
                destinations = @("securityWorkspace")
                outputStream = "Microsoft-SecurityEvent"
            }
            @{
                streams      = @("Microsoft-Event")
                destinations = @("securityWorkspace")
                outputStream = "Microsoft-Event"
            }
        )
    }
} | ConvertTo-Json -Depth 10

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

Step 3: Advanced XPath Filtering — Specific Event IDs Only

Collecting every Security event can generate millions of records per day and drive up costs. Use XPath predicates to filter to only the event IDs you care about. The most security-relevant IDs for logon auditing are 4624 (successful logon) and 4625 (failed logon).

# Targeted DCR for logon events only
$dcrNameTargeted = "dcr-logon-events"

$targetedDcrBody = @{
    location   = "eastus"
    properties = @{
        dataSources = @{
            windowsEventLogs = @(
                @{
                    name    = "logonEventsSource"
                    streams = @("Microsoft-SecurityEvent")
                    xPathQueries = @(
                        # Logon success (4624) and failure (4625)
                        "Security!*[System[(EventID=4624 or EventID=4625)]]"
                        # Account lockout (4740) and password change (4723, 4724)
                        "Security!*[System[(EventID=4740 or EventID=4723 or EventID=4724)]]"
                        # Privilege use — sensitive privilege (4673) and special logon (4672)
                        "Security!*[System[(EventID=4672 or EventID=4673)]]"
                        # Process creation with command line (4688) — requires audit policy
                        "Security!*[System[(EventID=4688)]]"
                    )
                }
            )
        }
        destinations = @{
            logAnalytics = @(
                @{
                    workspaceResourceId = $workspaceRid
                    name                = "securityWorkspace"
                }
            )
        }
        dataFlows = @(
            @{
                streams      = @("Microsoft-SecurityEvent")
                destinations = @("securityWorkspace")
                outputStream = "Microsoft-SecurityEvent"
            }
        )
    }
} | ConvertTo-Json -Depth 10

Invoke-RestMethod `
    -Method  Put `
    -Uri     "https://management.azure.com/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.Insights/dataCollectionRules/$dcrNameTargeted`?api-version=2022-06-01" `
    -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
    -Body    $targetedDcrBody

# Test XPath filter locally on the server before deploying the DCR
# Run this ON the Windows Server 2025 machine:
$xpath = "*[System[(EventID=4624 or EventID=4625)]]"
Get-WinEvent -LogName Security -FilterXPath $xpath -MaxEvents 10 |
    Select-Object TimeCreated, Id, Message

Step 4: Associate the DCR with the Arc-Connected Server

$machineId   = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.HybridCompute/machines/WS2025-SRV01"
$dcrId       = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.Insights/dataCollectionRules/$dcrNameTargeted"

$assocBody = @{
    properties = @{ dataCollectionRuleId = $dcrId }
} | ConvertTo-Json

Invoke-RestMethod `
    -Method  Put `
    -Uri     "https://management.azure.com$machineId/providers/Microsoft.Insights/dataCollectionRuleAssociations/dcra-logon-events?api-version=2022-06-01" `
    -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
    -Body    $assocBody

# Verify association
Invoke-RestMethod `
    -Method  Get `
    -Uri     "https://management.azure.com$machineId/providers/Microsoft.Insights/dataCollectionRuleAssociations?api-version=2022-06-01" `
    -Headers @{ Authorization = "Bearer $token" } |
    Select-Object -ExpandProperty value |
    Select-Object name, @{n="dcrId";e={$_.properties.dataCollectionRuleId}}

Step 5: Query Collected Events with KQL

After data begins flowing (typically within 10 minutes of DCR association), run KQL queries in the Log Analytics workspace. The SecurityEvent table receives data from the Microsoft-SecurityEvent stream.

Import-Module Az.OperationalInsights

$workspaceId = $workspace.CustomerId

# Failed logon attempts — count by account and computer
$failedLogonQuery = @"
SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 4625
| summarize FailedAttempts = count() by Account, Computer, IpAddress
| where FailedAttempts > 5
| order by FailedAttempts desc
"@

Invoke-AzOperationalInsightsQuery `
    -WorkspaceId $workspaceId `
    -Query       $failedLogonQuery |
    Select-Object -ExpandProperty Results

# Successful logons after business hours (before 07:00 or after 19:00)
$afterHoursQuery = @"
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID == 4624
| where LogonType == 2 or LogonType == 10  // Interactive or RemoteInteractive
| extend Hour = datetime_part("Hour", TimeGenerated)
| where Hour  19
| project TimeGenerated, Account, Computer, LogonType, IpAddress
| order by TimeGenerated desc
"@

Invoke-AzOperationalInsightsQuery `
    -WorkspaceId $workspaceId `
    -Query       $afterHoursQuery |
    Select-Object -ExpandProperty Results

# Accounts with both successful AND failed logons (potential credential stuffing)
$bruteForceQuery = @"
let failed = SecurityEvent
    | where EventID == 4625
    | summarize FailCount = count() by Account, Computer;
let success = SecurityEvent
    | where EventID == 4624
    | summarize SuccessCount = count() by Account, Computer;
failed
| join kind=inner success on Account, Computer
| where FailCount > 10 and SuccessCount >= 1
| project Account, Computer, FailCount, SuccessCount
| order by FailCount desc
"@

Invoke-AzOperationalInsightsQuery `
    -WorkspaceId $workspaceId `
    -Query       $bruteForceQuery |
    Select-Object -ExpandProperty Results

Step 6: Enable Microsoft Sentinel over the Log Analytics Workspace

Microsoft Sentinel transforms your Log Analytics workspace into a full SIEM, adding threat intelligence, analytics rules, playbooks, and SOAR capabilities.

# Install the Sentinel solution (enable Sentinel on the workspace)
$token        = (Get-AzAccessToken).Token
$workspaceRid = $workspace.ResourceId

Invoke-RestMethod `
    -Method  Put `
    -Uri     "https://management.azure.com$workspaceRid/providers/Microsoft.SecurityInsights/onboardingStates/default?api-version=2022-12-01-preview" `
    -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
    -Body    (@{ properties = @{} } | ConvertTo-Json)

# Enable the Windows Security Events data connector (uses AMA)
$connectorBody = @{
    kind       = "WindowsSecurityEvents"
    properties = @{
        dataTypes = @{
            windowsSecurityEvents = @{ state = "Enabled" }
        }
    }
} | ConvertTo-Json

Invoke-RestMethod `
    -Method  Put `
    -Uri     "https://management.azure.com$workspaceRid/providers/Microsoft.SecurityInsights/dataConnectors/WindowsSecurityEvents?api-version=2022-12-01-preview" `
    -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
    -Body    $connectorBody

Step 7: KQL Parsing and Enrichment Functions

Save frequently used KQL queries as functions in Log Analytics to simplify complex analytics rules and reduce duplication.

# Save a KQL function for failed logon enrichment
$functionBody = @{
    properties = @{
        category    = "Security"
        displayName = "FailedLogonEnriched"
        query       = @"
SecurityEvent
| where EventID == 4625
| extend
    LogonTypeName = case(
        LogonType == 2,  "Interactive",
        LogonType == 3,  "Network",
        LogonType == 4,  "Batch",
        LogonType == 5,  "Service",
        LogonType == 7,  "Unlock",
        LogonType == 10, "RemoteInteractive",
        LogonType == 11, "CachedInteractive",
        "Other"
    ),
    RiskScore = case(
        LogonType == 10 and Status == "0xc000006d", 80,  // RDP bad password
        LogonType == 3  and Status == "0xc0000064", 60,  // Network bad username
        30
    )
| project TimeGenerated, Account, Computer, IpAddress, LogonTypeName, Status, RiskScore
"@
        functionAlias = "FailedLogonEnriched"
        functionParameters = ""
    }
} | ConvertTo-Json -Depth 5

Invoke-RestMethod `
    -Method  Put `
    -Uri     "https://management.azure.com$workspaceRid/savedSearches/FailedLogonEnriched?api-version=2020-08-01" `
    -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
    -Body    $functionBody

# After the function is saved, use it in queries:
$useFunctionQuery = @"
FailedLogonEnriched
| where TimeGenerated > ago(1h)
| where RiskScore >= 60
| summarize HighRiskCount = count() by Account, Computer, IpAddress
"@

Invoke-AzOperationalInsightsQuery `
    -WorkspaceId $workspaceId `
    -Query       $useFunctionQuery |
    Select-Object -ExpandProperty Results

Conclusion

You now have a production-grade Windows Event Log collection pipeline for Windows Server 2025. The Azure Monitor Agent collects precisely the event channels and IDs you specified via XPath filters in your Data Collection Rule, sending them to a Log Analytics workspace with appropriate retention. KQL queries surface actionable security insights—from failed logon spikes to after-hours access—and saved KQL functions reduce repetition as your analytics library grows. With Microsoft Sentinel enabled on the same workspace, you gain access to built-in threat detection analytics rules, playbooks, and incident management that elevate your environment from simple log collection to a fully operational SIEM. As your infrastructure grows, simply create additional DCR associations for new Arc-connected servers to bring them into the same collection and detection pipeline.