Skip to content

Exporting security event logs

Enclave records security events - such as authentication attempts, system enrolments, policy changes, and administrative actions - in the organisation's security log. This script exports the full security log for a given organisation to a CSV file, making it available for offline analysis, compliance reporting, or ingestion into a SIEM.

If you need to review audit trails, investigate incidents, or produce compliance evidence for a customer, this script gives you the complete log in a portable format.

Prerequisites

How it works

The script pages through the GET /org/{orgId}/logs endpoint, retrieving up to 200 log entries per request. Because the security log can be large, the script includes a short delay between API calls to stay within rate limits.

Each log entry includes a timestamp, severity level, source IP address, username, and event message. The script writes all entries to a CSV file. If the output file already exists, you'll be prompted before it's overwritten.

The script

Save the following script as a .ps1 file.

  • -orgId, the organisation to run queries against.
  • -apiKey, the Enclave API key. Set the ENCLAVE_API_KEY environment variable as an alternative.
  • -outfile, the path to the output CSV file. Defaults to output.csv in the current directory.
Param(
    [Parameter(Mandatory=$false)]
    [string]$orgId,

    [Parameter(Mandatory=$false)]
    [string]$apiKey,

    [Parameter(Mandatory=$false)]
    [string]$outfile
)

$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"

if (-not $apiKey) {
    $apiKey = if ($env:ENCLAVE_API_KEY) {
        $env:ENCLAVE_API_KEY
    } else {
        Read-Host "Please enter your Enclave Personal Access Token"
    }
}

if (-not $apiKey) {
    Write-Error "No API key provided; either specify the '-apiKey' argument, or set the ENCLAVE_API_KEY environment variable."
    return;
}

if (-not $orgId) {
    $orgId = Read-Host "Please enter your Enclave Organisation Id"
}

if (-not $outfile) {
    $outfile = "output.csv"
}

$headers = @{Authorization = "Bearer $apiKey"}
$contentType = "application/json"

# ------------

function Invoke-EnclaveApi {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Uri,

        [Parameter(Mandatory = $true)]
        [string]$Method,

        [Parameter(Mandatory = $false)]
        [object]$Body
    )

    try {
        if ($null -ne $Body) {
            return Invoke-RestMethod -ContentType $contentType -Method $Method -Uri $Uri -Headers $headers -Body ($Body | ConvertTo-Json -Depth 10)
        } else {
            return Invoke-RestMethod -ContentType $contentType -Method $Method -Uri $Uri -Headers $headers
        }
    } catch {
        throw "Request to $Uri failed with error: $($_.Exception.Message)"
    }
}

# ------------

function Get-HumanReadableTimeDifference {
    param ([datetime]$pastTime)

    $timeDifference = [datetime]::Now - $pastTime

    if ($timeDifference.TotalDays -ge 2) {
        return "{0} days ago" -f [math]::Floor($timeDifference.TotalDays)
    } elseif ($timeDifference.TotalHours -ge 2) {
        return "{0} hours ago" -f [math]::Floor($timeDifference.TotalHours)
    } elseif ($timeDifference.TotalMinutes -ge 2) {
        return "{0} minutes ago" -f [math]::Floor($timeDifference.TotalMinutes)
    } else {
        return "{0} seconds ago" -f [math]::Floor($timeDifference.TotalSeconds)
    }
}

function Format-DateTime {
    param (
        [datetime]$dateTime
    )
    return Get-Date $dateTime -Format "dd MMMM yyyy"
}

function Get-AllEnclaveLogs {
    param (
        [Parameter(Mandatory = $true)]
        [string]$InitialUri,

        [Parameter(Mandatory = $false)]
        [hashtable]$Headers
    )

    $allItems = @()
    $currentUri = $InitialUri
    $i = 0

    while ($null -ne $currentUri) {
        $i++
        $response = Invoke-EnclaveApi -Method Get -Uri $currentUri

        Write-Host "Requesting page $i/$($response.metadata.lastPage+1)"

        # Cloudflare rate limit is 1200 requests over 300 seconds cumulativley. Lowest this value can safely be is 250ms
        Start-Sleep -Milliseconds 300

        if ($response -and $response.items) {
            $allItems += $response.items
        }

        if ($response.links -and $response.links.next) {
            $currentUri = $response.links.next
        } else {
            $currentUri = $null
        }
    }

    return $allItems
}

if (Test-Path -Path $OutFile) {
    Write-Host "Warning: The file '$OutFile' already exists and will be overwritten."
    $confirmation = Read-Host "Do you want to proceed? (y/n)"
    if ($confirmation -ne 'y') {
        Write-Host "Operation cancelled by the user."
        return
    }
}

# Request the maximum number of logs available from the API per query
$allLogs = Get-AllEnclaveLogs -InitialUri "https://api.enclave.io/org/$orgId/logs?page=0&per_page=200" -Headers $headers

$allLogs | Select-Object timeStamp, level, ipAddress, userName, message | Export-Csv -Path $outfile -NoTypeInformation

Write-Output "$($allLogs.Count) log entries successfully written to $outfile`n"

Usage

Run the script interactively (you'll be prompted for credentials) and export to the default output.csv:

.\security-logs-report.ps1

Pass parameters directly and specify a custom output file:

.\security-logs-report.ps1 -orgId "b87f2e1a3d9f" -apiKey $env:ENCLAVE_API_KEY -outfile "trantor-security-logs.csv"

Scheduling

Security log exports are typically run ad-hoc for audits or incident investigation. If you need a recurring export for compliance purposes, you can schedule the script using Windows Task Scheduler, your RMM platform, or Azure Functions. Consider using a timestamped filename (e.g. logs-2026-07-31.csv) to avoid overwriting previous exports.