Skip to main content
Automate the creation, scheduling, and export of reports from Falcon data using PSFalcon.

Scheduled Reports

List Scheduled Reports

# Get all scheduled reports
$Reports = Get-FalconScheduledReport -Detailed -All

# Filter by status
$ActiveReports = Get-FalconScheduledReport -Filter "status:'enabled'" -Detailed -All

# Get reports by type
$VulnReports = Get-FalconScheduledReport -Filter "type:'vulnerability'" -Detailed -All

# Display report details
$Reports | Select-Object id, name, type, status, schedule | Format-Table -AutoSize

Download Latest Report Results

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

# Get most recent successful report executions
$Executions = Get-FalconScheduledReport -Filter "last_execution.status:'DONE'" -Detailed -All |
  Select-Object -ExpandProperty last_execution | 
  Where-Object { $_.status -eq 'DONE' -and $_.result_metadata }

if ($Executions) {
  foreach ($Execution in $Executions) {
    # Download report file
    $OutputPath = Join-Path (Get-Location).Path $Execution.result_metadata.report_file_name
    
    Receive-FalconScheduledReport -Id $Execution.id -Path $OutputPath
    
    if (Test-Path $OutputPath) {
      Write-Host "Downloaded: $($Execution.result_metadata.report_file_name)"
      Get-ChildItem $OutputPath | Select-Object FullName, Length, LastWriteTime
    }
  }
} else {
  Write-Host "No completed report executions found"
}

Execute Scheduled Report On-Demand

# Get report by name/filter
$Report = Get-FalconScheduledReport -Filter "name:*'Daily Detection Summary'*" -Detailed | 
  Select-Object -First 1

if ($Report) {
  # Execute report immediately
  Invoke-FalconScheduledReport -Id $Report.id
  
  Write-Host "Scheduled report execution requested: $($Report.name)"
  Write-Host "Check execution status with: Get-FalconScheduledReport -Id $($Report.id) -Execution"
}

Custom Host Reports

Generate Host Inventory Report

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

param(
  [Parameter()]
  [string]$Filter,
  
  [Parameter()]
  [switch]$IncludeLoginInfo
)

# Output file
$OutputFile = Join-Path (Get-Location).Path "host_inventory_$(Get-Date -Format FileDateTime).csv"

Write-Host "Generating host inventory report..."

# Get all hosts
$Param = @{
  Detailed = $true
  All = $true
}

if ($Filter) {
  $Param['Filter'] = $Filter
}

if ($IncludeLoginInfo) {
  $Param['Login'] = $true
}

$Hosts = Get-FalconHost @Param

if (!$Hosts) {
  Write-Host "No hosts found"
  return
}

Write-Host "Found $($Hosts.Count) hosts"

# Get policy information for enrichment
$Policies = @{
  prevention = @(Get-FalconPreventionPolicy -Detailed -All | Select-Object id, name)
  sensor_update = @(Get-FalconSensorUpdatePolicy -Detailed -All | Select-Object id, name)
  response = @(Get-FalconResponsePolicy -Detailed -All | Select-Object id, name)
}

# Get host group information
$HostGroups = @(Get-FalconHostGroup -Detailed -All | Select-Object id, name)

# Build report
$Report = @($Hosts).foreach{
  $Host = $_
  
  # Get policy names
  $PreventionPolicy = $Policies.prevention | Where-Object { 
    $_.id -eq $Host.device_policies.prevention.policy_id 
  } | Select-Object -ExpandProperty name
  
  $SensorUpdatePolicy = $Policies.sensor_update | Where-Object { 
    $_.id -eq $Host.device_policies.sensor_update.policy_id 
  } | Select-Object -ExpandProperty name
  
  $ResponsePolicy = $Policies.response | Where-Object { 
    $_.id -eq $Host.device_policies.response.policy_id 
  } | Select-Object -ExpandProperty name
  
  # Get host group names
  $Groups = @($Host.groups).foreach{
    $GroupId = $_
    $HostGroups | Where-Object { $_.id -eq $GroupId } | Select-Object -ExpandProperty name
  } | Where-Object { $_ }
  
  $Entry = [PSCustomObject]@{
    hostname = $Host.hostname
    device_id = $Host.device_id
    platform = $Host.platform_name
    os_version = $Host.os_version
    os_build = $Host.os_build
    agent_version = $Host.agent_version
    first_seen = $Host.first_seen
    last_seen = $Host.last_seen
    status = $Host.status
    groups = $Groups -join '; '
    prevention_policy = $PreventionPolicy
    sensor_update_policy = $SensorUpdatePolicy
    response_policy = $ResponsePolicy
    local_ip = $Host.local_ip
    external_ip = $Host.external_ip
    mac_address = $Host.mac_address
    serial_number = $Host.serial_number
    system_manufacturer = $Host.system_manufacturer
    system_product_name = $Host.system_product_name
  }
  
  # Add login info if requested
  if ($IncludeLoginInfo -and $Host.recent_logins) {
    $Entry | Add-Member -NotePropertyName 'last_login_user' -NotePropertyValue $Host.recent_logins[0].user_name
    $Entry | Add-Member -NotePropertyName 'last_login_time' -NotePropertyValue $Host.recent_logins[0].login_time
  }
  
  $Entry
}

# Export to CSV
$Report | Export-Csv -Path $OutputFile -NoTypeInformation

Write-Host "Report generated with $($Report.Count) hosts"
Get-ChildItem $OutputFile | Select-Object FullName, Length, LastWriteTime

Generate Stale Host Report

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

param(
  [Parameter()]
  [int]$DaysOffline = 30
)

# Output file
$OutputFile = Join-Path (Get-Location).Path "stale_hosts_$(Get-Date -Format FileDateTime).csv"

# Find hosts not seen in X days
$Filter = "last_seen:<'now-$($DaysOffline)d'"
$StaleHosts = Get-FalconHost -Filter $Filter -Detailed -All

if (!$StaleHosts) {
  Write-Host "No hosts found offline for $DaysOffline+ days"
  return
}

Write-Host "Found $($StaleHosts.Count) stale hosts"

# Calculate days offline
$Report = @($StaleHosts).foreach{
  $LastSeen = [datetime]$_.last_seen
  $DaysOffline = [Math]::Floor((Get-Date) - $LastSeen).TotalDays
  
  [PSCustomObject]@{
    hostname = $_.hostname
    device_id = $_.device_id
    platform = $_.platform_name
    os_version = $_.os_version
    agent_version = $_.agent_version
    first_seen = $_.first_seen
    last_seen = $_.last_seen
    days_offline = $DaysOffline
    status = $_.status
  }
}

# Sort by days offline
$Report = $Report | Sort-Object -Property days_offline -Descending

# Export
$Report | Export-Csv -Path $OutputFile -NoTypeInformation

Write-Host "`nStale Host Summary:"
Write-Host "  Total: $($Report.Count)"
Write-Host "  30-60 days: $(($Report | Where-Object { $_.days_offline -ge 30 -and $_.days_offline -lt 60 }).Count)"
Write-Host "  60-90 days: $(($Report | Where-Object { $_.days_offline -ge 60 -and $_.days_offline -lt 90 }).Count)"
Write-Host "  90+ days: $(($Report | Where-Object { $_.days_offline -ge 90 }).Count)"

Get-ChildItem $OutputFile | Select-Object FullName, Length, LastWriteTime

Detection Reports

Daily Detection Summary Report

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

param(
  [Parameter()]
  [int]$DaysBack = 1
)

# Output file
$OutputFile = Join-Path (Get-Location).Path "detection_summary_$(Get-Date -Format FileDateTime).csv"

# Get detections from last X days
$Filter = "first_behavior:>'now-$($DaysBack)d'"
$Detections = Get-FalconDetection -Filter $Filter -Detailed -All

if (!$Detections) {
  Write-Host "No detections in the last $DaysBack day(s)"
  return
}

Write-Host "Found $($Detections.Count) detections"

# Build report
$Report = @($Detections).foreach{
  [PSCustomObject]@{
    detection_id = $_.detection_id
    hostname = $_.device.hostname
    platform = $_.device.platform_name
    severity = $_.max_severity_displayname
    status = $_.status
    first_behavior = $_.first_behavior
    last_behavior = $_.last_behavior
    assigned_to = $_.assigned_to_name
    tactic = ($_.behaviors.tactic | Select-Object -Unique) -join '; '
    technique = ($_.behaviors.technique | Select-Object -Unique) -join '; '
    behavior_count = $_.behaviors.Count
  }
}

# Export
$Report | Export-Csv -Path $OutputFile -NoTypeInformation

# Generate summary statistics
$Summary = [PSCustomObject]@{
  total_detections = $Detections.Count
  critical = ($Detections | Where-Object { $_.max_severity_displayname -eq 'Critical' }).Count
  high = ($Detections | Where-Object { $_.max_severity_displayname -eq 'High' }).Count
  medium = ($Detections | Where-Object { $_.max_severity_displayname -eq 'Medium' }).Count
  low = ($Detections | Where-Object { $_.max_severity_displayname -eq 'Low' }).Count
  new_status = ($Detections | Where-Object { $_.status -eq 'new' }).Count
  in_progress = ($Detections | Where-Object { $_.status -eq 'in_progress' }).Count
  assigned = ($Detections | Where-Object { $_.assigned_to_name }).Count
  unassigned = ($Detections | Where-Object { -not $_.assigned_to_name }).Count
}

Write-Host "`nDetection Summary (Last $DaysBack Day(s)):"
$Summary | Format-List

Get-ChildItem $OutputFile | Select-Object FullName, Length, LastWriteTime

Detection Metrics Over Time

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

param(
  [Parameter()]
  [int]$DaysBack = 30
)

# Output file
$OutputFile = Join-Path (Get-Location).Path "detection_metrics_$(Get-Date -Format FileDateTime).csv"

Write-Host "Collecting detection metrics for last $DaysBack days..."

$Metrics = @()

# Collect metrics for each day
for ($i = 0; $i -lt $DaysBack; $i++) {
  $StartDay = $DaysBack - $i
  $EndDay = $DaysBack - $i - 1
  
  $Filter = "first_behavior:>'now-$($StartDay)d'+first_behavior:<'now-$($EndDay)d'"
  
  # Get counts by severity
  $Total = Get-FalconDetection -Filter $Filter -Total
  $Critical = Get-FalconDetection -Filter "$Filter+max_severity_displayname:'Critical'" -Total
  $High = Get-FalconDetection -Filter "$Filter+max_severity_displayname:'High'" -Total
  $Medium = Get-FalconDetection -Filter "$Filter+max_severity_displayname:'Medium'" -Total
  $Low = Get-FalconDetection -Filter "$Filter+max_severity_displayname:'Low'" -Total
  
  $Date = (Get-Date).AddDays(-$StartDay).ToString('yyyy-MM-dd')
  
  $Metrics += [PSCustomObject]@{
    date = $Date
    total = $Total
    critical = $Critical
    high = $High
    medium = $Medium
    low = $Low
  }
  
  Write-Host "$Date: $Total detections"
  
  # Rate limiting
  Start-Sleep -Milliseconds 500
}

# Export
$Metrics | Export-Csv -Path $OutputFile -NoTypeInformation

Write-Host "`nMetrics collected for $DaysBack days"
Get-ChildItem $OutputFile | Select-Object FullName, Length, LastWriteTime

Vulnerability Reports

CVE by Host Report

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

param(
  [Parameter(Mandatory)]
  [string[]]$Cve
)

# Output file
$OutputFile = Join-Path (Get-Location).Path "cve_by_host_$(Get-Date -Format FileDate).csv"

Write-Host "Querying vulnerabilities for $($Cve.Count) CVE(s)..."

# Process CVEs in batches of 20
for ($i = 0; $i -lt $Cve.Count; $i += 20) {
  $Group = @($Cve[$i..([Math]::Min($i + 19, $Cve.Count - 1))]).foreach{ "'$_'" } -join ','
  
  $Param = @{
    Filter = "suppression_info.is_suppressed:False+status:!'closed'+cve.id:[$Group]"
    Facet = 'host_info'
    Detailed = $true
    All = $true
  }
  
  $Vulnerabilities = Get-FalconVulnerability @Param
  
  if ($Vulnerabilities) {
    # Get unique AIDs for login lookup
    $AidList = $Vulnerabilities.aid | Select-Object -Unique
    $LoginList = Get-FalconHost -Id $AidList -Login
    
    # Build output
    $Output = @($Vulnerabilities).foreach{
      $Vuln = $_
      $Login = @($LoginList).Where({ $_.device_id -eq $Vuln.aid }).recent_logins | Select-Object -First 1
      
      [PSCustomObject]@{
        vulnerability_id = $Vuln.vulnerability_id
        cve_id = $Vuln.cve.id
        hostname = $Vuln.host_info.hostname
        aid = $Vuln.aid
        status = $Vuln.status
        confidence = $Vuln.confidence
        created_timestamp = $Vuln.created_timestamp
        updated_timestamp = $Vuln.updated_timestamp
        last_login_user = if ($Login) { $Login.user_name } else { '' }
        last_login_time = if ($Login) { $Login.login_time } else { '' }
      }
    }
    
    # Export
    $Output | Export-Csv -Path $OutputFile -NoTypeInformation -Append
    
    Write-Host "Batch $([Math]::Floor($i/20) + 1): Found $($Output.Count) vulnerabilities"
  }
  
  # Rate limiting
  Start-Sleep -Seconds 2
}

if (Test-Path $OutputFile) {
  $Results = Import-Csv -Path $OutputFile
  Write-Host "`nTotal vulnerabilities: $($Results.Count)"
  Get-ChildItem $OutputFile | Select-Object FullName, Length, LastWriteTime
}

Automated Report Scheduling

Daily Report Generation Script

#Requires -Version 5.1
using module @{ModuleName='PSFalcon';ModuleVersion='2.2'}

<#
.SYNOPSIS
Daily automated report generation for Falcon environment
.DESCRIPTION
Generates daily reports for hosts, detections, and vulnerabilities.
Designed to run as a scheduled task.
#>

param(
  [Parameter(Mandatory)]
  [string]$ClientId,
  
  [Parameter(Mandatory)]
  [string]$ClientSecret,
  
  [Parameter()]
  [string]$OutputDirectory = (Get-Location).Path,
  
  [Parameter()]
  [string]$EmailTo,
  
  [Parameter()]
  [string]$SmtpServer
)

# Authenticate
try {
  Request-FalconToken -ClientId $ClientId -ClientSecret $ClientSecret
  
  if ((Test-FalconToken).Token -ne $true) {
    throw "Authentication failed"
  }
} catch {
  Write-Error "Failed to authenticate: $_"
  exit 1
}

# Create date-stamped directory
$DateStamp = Get-Date -Format 'yyyy-MM-dd'
$ReportDir = Join-Path $OutputDirectory "falcon_reports_$DateStamp"

if (!(Test-Path $ReportDir)) {
  New-Item -Path $ReportDir -ItemType Directory | Out-Null
}

Write-Host "Generating reports in: $ReportDir"

# Generate reports
$Reports = @()

try {
  # 1. Host inventory
  Write-Host "Generating host inventory..."
  $HostFile = Join-Path $ReportDir 'host_inventory.csv'
  $Hosts = Get-FalconHost -Detailed -All
  $Hosts | Select-Object hostname, device_id, platform_name, os_version, agent_version, last_seen |
    Export-Csv -Path $HostFile -NoTypeInformation
  $Reports += "Host Inventory: $($Hosts.Count) hosts"
  
  # 2. Daily detections
  Write-Host "Generating detection summary..."
  $DetectionFile = Join-Path $ReportDir 'daily_detections.csv'
  $Detections = Get-FalconDetection -Filter "first_behavior:>'now-24h'" -Detailed -All
  if ($Detections) {
    $Detections | Select-Object detection_id, @{l='hostname';e={$_.device.hostname}}, 
      max_severity_displayname, status, first_behavior |
      Export-Csv -Path $DetectionFile -NoTypeInformation
    $Reports += "Daily Detections: $($Detections.Count) detections"
  } else {
    $Reports += "Daily Detections: 0 detections"
  }
  
  # 3. Stale hosts
  Write-Host "Generating stale host report..."
  $StaleFile = Join-Path $ReportDir 'stale_hosts.csv'
  $StaleHosts = Get-FalconHost -Filter "last_seen:<'now-30d'" -Detailed -All
  if ($StaleHosts) {
    $StaleHosts | Select-Object hostname, device_id, platform_name, last_seen |
      Export-Csv -Path $StaleFile -NoTypeInformation
    $Reports += "Stale Hosts (30+ days): $($StaleHosts.Count) hosts"
  } else {
    $Reports += "Stale Hosts: 0 hosts"
  }
  
  # 4. Summary statistics
  Write-Host "Generating summary..."
  $SummaryFile = Join-Path $ReportDir 'summary.txt'
  
  $Summary = @"
Falcon Daily Report - $DateStamp
================================

Host Statistics:
  Total Hosts: $(Get-FalconHost -Total)
  Online (24h): $(Get-FalconHost -Filter "last_seen:>'now-24h'" -Total)
  Stale (30d+): $($StaleHosts.Count)

Detection Statistics:
  Total (24h): $(if ($Detections) { $Detections.Count } else { 0 })
  Critical: $(Get-FalconDetection -Filter "first_behavior:>'now-24h'+max_severity_displayname:'Critical'" -Total)
  New: $(Get-FalconDetection -Filter "status:'new'+first_behavior:>'now-24h'" -Total)
  
Reports Generated:
$($Reports -join "`n")

Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"@
  
  $Summary | Out-File -FilePath $SummaryFile
  
  Write-Host "`n$Summary"
  
  # Send email if configured
  if ($EmailTo -and $SmtpServer) {
    Write-Host "Sending email report..."
    
    $EmailParams = @{
      To = $EmailTo
      From = '[email protected]'
      Subject = "Falcon Daily Report - $DateStamp"
      Body = $Summary
      SmtpServer = $SmtpServer
      Attachments = @($HostFile, $DetectionFile, $StaleFile, $SummaryFile)
    }
    
    Send-MailMessage @EmailParams
    Write-Host "Email sent to $EmailTo"
  }
  
} catch {
  Write-Error "Report generation failed: $_"
  exit 1
} finally {
  # Always revoke token
  if ((Test-FalconToken).Token -eq $true) {
    Revoke-FalconToken
  }
}

Write-Host "`nReport generation complete"
Get-ChildItem $ReportDir | Select-Object Name, Length, LastWriteTime | Format-Table -AutoSize

Schedule as Windows Task

# Create scheduled task to run daily reports
$Action = New-ScheduledTaskAction -Execute 'PowerShell.exe' -Argument @"
-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\DailyFalconReport.ps1" -ClientId "YOUR_CLIENT_ID" -ClientSecret "YOUR_CLIENT_SECRET" -OutputDirectory "C:\Reports" -EmailTo "[email protected]" -SmtpServer "smtp.company.com"
"@

$Trigger = New-ScheduledTaskTrigger -Daily -At 6:00AM

$Principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest

$Settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd

Register-ScheduledTask -TaskName "Falcon Daily Report" -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Description "Generate daily Falcon security reports"

Write-Host "Scheduled task 'Falcon Daily Report' created"
Store API credentials securely using Windows Credential Manager, Azure Key Vault, or encrypted files instead of hardcoding them in scripts.

Export Formats

Export to JSON

# Export detections with full details to JSON
$Detections = Get-FalconDetection -Filter "first_behavior:>'now-7d'" -Detailed -All
$OutputFile = 'detections.json'

$Detections | ConvertTo-Json -Depth 16 | Out-File -FilePath $OutputFile

Export to Excel (using ImportExcel module)

# Requires ImportExcel module: Install-Module ImportExcel
$Hosts = Get-FalconHost -Detailed -All
$Detections = Get-FalconDetection -Filter "first_behavior:>'now-7d'" -Detailed -All

$ExcelFile = 'falcon_report.xlsx'

# Export hosts to sheet 1
$Hosts | Select-Object hostname, platform_name, os_version, agent_version, last_seen |
  Export-Excel -Path $ExcelFile -WorksheetName 'Hosts' -AutoSize -FreezeTopRow

# Export detections to sheet 2
$Detections | Select-Object detection_id, @{l='hostname';e={$_.device.hostname}}, 
  max_severity_displayname, status, first_behavior |
  Export-Excel -Path $ExcelFile -WorksheetName 'Detections' -AutoSize -FreezeTopRow

Write-Host "Excel report generated: $ExcelFile"
Large exports can consume significant memory. For datasets over 50,000 records, use streaming techniques or export in batches.

Next Steps

Bulk Operations

Process large datasets efficiently

Detection Management

Query and manage detections

Build docs developers (and LLMs) love