Public/Get-IntuneAgentLog.ps1

function Get-IntuneAgentLog {
    <#
    .EXTERNALHELP IntuneScriptLab-Help.xml
    .SYNOPSIS
        Reads the Intune Management Extension logs as objects, with the events the agent's lines record.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Read')]
    [OutputType('IntuneScriptLab.AgentLogEntry')]
    param(
        [Parameter(ParameterSetName = 'Read', Position = 0)]
        [string[]]$Path,

        [Parameter(ParameterSetName = 'Read')]
        [ValidateSet('Agent', 'AppWorkload', 'HealthScripts', 'AgentExecutor', 'All')]
        [string[]]$Log = @('Agent', 'AppWorkload', 'HealthScripts', 'AgentExecutor'),

        [Parameter(ParameterSetName = 'Read')]
        [string[]]$Id,

        [Parameter(ParameterSetName = 'Read')]
        [string[]]$EventName,

        [Parameter(ParameterSetName = 'Read')]
        [string]$Pattern,

        [Parameter(ParameterSetName = 'Read')]
        [ValidateSet('Information', 'Warning', 'Error')]
        [string[]]$Level,

        [Parameter(ParameterSetName = 'Read')]
        [datetime]$After,

        [Parameter(ParameterSetName = 'Read')]
        [datetime]$Before,

        [Parameter(ParameterSetName = 'Read')]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Last,

        [Parameter(ParameterSetName = 'List', Mandatory)]
        [switch]$ListEvent
    )
    Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')"

    # Touching the table once compiles it, for -ListEvent and for validating -EventName
    $null = Get-IslAgentLogEvent -Message ''
    if ($ListEvent) {
        foreach ($definition in $script:IslAgentLogEvents) {
            [pscustomobject]@{
                PSTypeName = 'IntuneScriptLab.AgentLogEventDefinition'
                Event      = $definition.Event
                Pattern    = $definition.Regex.ToString()
            }
        }
        return
    }
    $knownEvents = @($script:IslAgentLogEvents | ForEach-Object { $_.Event })
    foreach ($name in $EventName) {
        if ($name -notin $knownEvents) {
            throw "Unknown event '$name'. Get-IntuneAgentLog -ListEvent shows the names"
        }
    }

    if (-not $Path) {
        $programData = if ($env:ProgramData) { $env:ProgramData } else { 'C:\ProgramData' }
        $Path = @(Join-Path -Path $programData -ChildPath 'Microsoft\IntuneManagementExtension\Logs')
    }
    $filters = @{
        Agent         = 'IntuneManagementExtension*.log'
        AppWorkload   = 'AppWorkload*.log'
        HealthScripts = 'HealthScripts*.log'
        AgentExecutor = 'AgentExecutor*.log'
        All           = '*.log'
    }
    $files = foreach ($item in $Path) {
        $resolved = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
        if (Test-Path -LiteralPath $resolved -PathType Container) {
            foreach ($name in $Log) {
                Get-ChildItem -LiteralPath $resolved -Filter $filters[$name] -File | ForEach-Object { $_.FullName }
            }
        }
        else { $resolved }
    }
    $files = @($files | Sort-Object -Unique)
    if (-not $files) {
        Write-Warning "No log files under $($Path -join ', ')"
        return
    }

    # The parser applies the filters to the raw record, before it builds an entry; the event filter
    # waits for the classification, which runs once per file over every kept message rather than
    # once per entry
    $parseSplat = @{}
    foreach ($name in 'Level', 'After', 'Before', 'Pattern', 'Id') {
        if ($PSBoundParameters.ContainsKey($name)) { $parseSplat[$name] = $PSBoundParameters[$name] }
    }
    if ($EventName) {
        # A message that holds none of the wanted events' leading literals cannot be one of them.
        # One event without a literal (its pattern starts with a group) means no such shortcut
        $needles = @(foreach ($definition in $script:IslAgentLogEvents) {
                if ($definition.Event -in $EventName) { $definition.Needle }
            })
        if ($needles.Count -eq @($EventName | Select-Object -Unique).Count) { $parseSplat.Contains = $needles }
    }
    $entries = foreach ($file in $files) {
        # A file's last write is at or after its newest entry, so a rolled-over file written
        # before -After has nothing to give and is not read
        if ($PSBoundParameters.ContainsKey('After') -and (Get-Item -LiteralPath $file).LastWriteTime -lt $After) {
            Write-Verbose "Skipping $file, last written before $After"
            continue
        }
        Write-Verbose "Reading $file"
        $kept = @(ConvertFrom-IslCmTraceLog -Path $file @parseSplat)
        if (-not $kept.Count) { continue }
        $classified = @(Get-IslAgentLogEvent -Message @($kept | ForEach-Object { $_.Message }))
        for ($index = 0; $index -lt $kept.Count; $index++) {
            $entry = $kept[$index]
            $classification = $classified[$index]
            if ($EventName -and $classification.Event -notin $EventName) { continue }
            $entry.Event = $classification.Event
            $entry.Detail = $classification.Detail
            $entry.Id = $classification.Id
            $entry
        }
    }
    $entries = @($entries | Sort-Object -Property Time, Log, Line)
    if ($Last -and $entries.Count -gt $Last) {
        $entries = $entries[($entries.Count - $Last)..($entries.Count - 1)]
    }
    Write-Verbose "Completed $($MyInvocation.MyCommand.Name): $($entries.Count) entries from $($files.Count) files"
    $entries
}