functions/Get-XdrEndpointDeviceTimeline.ps1

function Get-XdrEndpointDeviceTimeline {
    <#
    .SYNOPSIS
        Retrieves the timeline of events for a specific device from Microsoft Defender XDR.

    .DESCRIPTION
        Gets the timeline of security events for a device from the Microsoft Defender XDR portal with options to filter by date range and other parameters.

    .PARAMETER DeviceId
        The unique identifier of the device. Accepts pipeline input and can also be specified as MachineId. Use this parameter set when identifying the device by ID.

    .PARAMETER MachineDnsName
        The DNS name of the machine. Use this parameter set when identifying the device by DNS name.

    .PARAMETER MarkedEventsOnly
        Only return events that have been marked in the timeline.

    .PARAMETER SenseClientVersion
        Optional. The version of the Sense client.

    .PARAMETER GenerateIdentityEvents
        Whether to generate identity events. Defaults to $true.

    .PARAMETER IncludeIdentityEvents
        Whether to include identity events. Defaults to $true.

    .PARAMETER SupportMdiOnlyEvents
        Whether to support MDI-only events. Defaults to $true.

    .PARAMETER FromDate
        The start date for the timeline. Defaults to 1 hour before current time.

    .PARAMETER ToDate
        The end date for the timeline. Defaults to current time.

    .PARAMETER LastNDays
        Specifies the number of days to look back. Overrides FromDate and ToDate if specified.

    .PARAMETER DoNotUseCache
        Whether to bypass the cache. Defaults to $false.

    .PARAMETER ForceUseCache
        Whether to force using the cache. Defaults to $false.

    .PARAMETER PageSize
        The number of events to return per page. Defaults to 200.

    .PARAMETER IncludeSentinelEvents
        Whether to include Sentinel events. Defaults to $false.

    .EXAMPLE
        Get-XdrEndpointDeviceTimeline -DeviceId "2bec169acc9def3ebd0bf8cdcbd9d16eb37e50e2"
        Retrieves the last hour of timeline events for the specified device.

    .EXAMPLE
        Get-XdrEndpointDeviceTimeline -MachineDnsName "computer.contoso.com"
        Retrieves the last hour of timeline events using the machine DNS name.

    .EXAMPLE
        Get-XdrEndpointDeviceTimeline -DeviceId "2bec169acc9def3ebd0bf8cdcbd9d16eb37e50e2" -FromDate (Get-Date).AddDays(-7) -ToDate (Get-Date)
        Retrieves timeline events for the last 7 days.

    .EXAMPLE
        "2bec169acc9def3ebd0bf8cdcbd9d16eb37e50e2" | Get-XdrEndpointDeviceTimeline
        Retrieves timeline events using pipeline input.
    #>

    [OutputType([System.Object[]])]
    [CmdletBinding(DefaultParameterSetName = 'ByDeviceId')]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'ByDeviceId')]
        [Alias('MachineId')]
        [string]$DeviceId,

        [Parameter(Mandatory, ParameterSetName = 'ByMachineDnsName')]
        [string]$MachineDnsName,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [switch]$MarkedEventsOnly,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [string]$SenseClientVersion,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [bool]$GenerateIdentityEvents = $true,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [bool]$IncludeIdentityEvents = $true,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [bool]$SupportMdiOnlyEvents = $true,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [datetime]$FromDate = ((Get-Date).AddHours(-1)),

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [datetime]$ToDate = (Get-Date),

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [int]$LastNDays,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [bool]$DoNotUseCache = $false,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [bool]$ForceUseCache = $false,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [int]$PageSize = 200,

        [Parameter(ParameterSetName = 'ByDeviceId')]
        [Parameter(ParameterSetName = 'ByMachineDnsName')]
        [bool]$IncludeSentinelEvents = $false
    )

    begin {
        Update-XdrConnectionSettings
    }

    process {
        if ($PSBoundParameters.ContainsKey('LastNDays')) {
            $ToDate = Get-Date
            $FromDate = $ToDate.AddDays(-$LastNDays)
        }

        if (($ToDate - $FromDate).TotalDays -gt 30) {
            throw "The time range between FromDate and ToDate cannot exceed 30 days."
        }

        # Generate a new correlation ID
        $correlationId = [guid]::NewGuid().ToString()

        # Build the URI with query parameters
        $queryParams = @(
            "generateIdentityEvents=$($GenerateIdentityEvents.ToString().ToLower())"
            "includeIdentityEvents=$($IncludeIdentityEvents.ToString().ToLower())"
            "supportMdiOnlyEvents=$($SupportMdiOnlyEvents.ToString().ToLower())"
            "fromDate=$([System.Uri]::EscapeDataString($FromDate.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')))"
            "toDate=$([System.Uri]::EscapeDataString($ToDate.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')))"
            "correlationId=$correlationId"
            "doNotUseCache=$($DoNotUseCache.ToString().ToLower())"
            "forceUseCache=$($ForceUseCache.ToString().ToLower())"
            "pageSize=$PageSize"
            "includeSentinelEvents=$($IncludeSentinelEvents.ToString().ToLower())"
        )

        if ($PSBoundParameters.ContainsKey('MachineDnsName')) {
            $queryParams = @("machineDnsName=$([System.Uri]::EscapeDataString($MachineDnsName))") + $queryParams
        }

        if ($PSBoundParameters.ContainsKey('SenseClientVersion')) {
            $queryParams = @("SenseClientVersion=$([System.Uri]::EscapeDataString($SenseClientVersion))") + $queryParams
        }

        if ($MarkedEventsOnly) {
            $queryParams = @("markedEventsOnly=true") + $queryParams
        }

        # Determine the device identifier to use in the URI
        $deviceIdentifier = if ($PSCmdlet.ParameterSetName -eq 'ByDeviceId') { $DeviceId } else { (Get-XdrEndpointDevice -MachineSearchPrefix $MachineDnsName).MachineId }

        try {
            $Uri = "https://security.microsoft.com/apiproxy/mtp/mdeTimelineExperience/machines/$deviceIdentifier/events/?$($queryParams -join '&')"

            Write-Verbose "Retrieving XDR Endpoint device timeline for device $deviceIdentifier (From: $FromDate, To: $ToDate, CorrelationId: $correlationId)"
            $response = Invoke-RestMethod -Uri $Uri -ContentType "application/json" -WebSession $script:session -Headers $script:headers

            if ($response -and $response.Items) {
                return $response.Items
            } else {
                Write-Verbose "No timeline events found for device $deviceIdentifier."
                return @()
            }
        } catch {
            Write-Error "Failed to retrieve endpoint device timeline: $_"
        }
    }

    end {
    }
}