Forum Discussion

ddaniel's avatar
ddaniel
Icon for Staying Involved rankStaying Involved
20 days ago
Solved

I could use some help with a Powershell script for the events feed.

For some work I am doing trying to track DHCP events, I have been looking at the Events Feed API and am having trouble getting code working in Powershell.  I know my API key/Account ID are good because I have other scripts/tasks running daily, but I am struggling on the event feed.   I would rather avoid Python because I already have other things happening in Powershell and am not very comfortable in Python.  I think I have events enabled in my account correctly.

I asked ChatGPT to review my efforts and add debugging and this is my current script.  It returns 0 events.  you can toggle $DEBUG_MODE = $true/$false as needed

Can someone let me know if you return results with this code?  thanks.

# start script -----------------------------------------------------------------------------

# ====== CONFIGURATION ======
$API_URL    = "https://api.catonetworks.com/api/v1/graphql2"
$API_KEY    = "YOUR_CATO_API_KEY"
$ACCOUNT_ID = "YOUR_ACCOUNT_ID"

$DEBUG_MODE = $true
$MAX_LOOPS  = 3
# ===========================

$query = @"
query EventsFeed(`$accountIDs: [ID!], `$marker: String) {
  eventsFeed(accountIDs: `$accountIDs, marker: `$marker) {
    marker
    fetchedCount
    accounts {
      id
      records {
        time
        fieldsMap
      }
    }
  }
}
"@

function Write-DebugLog {
    param(
        [string]$Message,
        $Data = $null
    )

    if (-not $DEBUG_MODE) { return }

    Write-Output "[DEBUG] $Message"

    if ($null -ne $Data) {
        try {
            if ($Data -is [string]) {
                Write-Output $Data
            }
            else {
                $json = $Data | ConvertTo-Json -Depth 20
                Write-Output $json
            }
        }
        catch {
            Write-Output "[DEBUG] Could not serialize debug data."
            Write-Output ($Data | Out-String)
        }
    }

    Write-Output ("=" * 80)
}

function Print-Event {
    param($Record)

    $timeStr = $Record.time
    try {
        $dt = [datetime]::Parse($Record.time)
        $timeStr = $dt.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss 'UTC'")
    }
    catch {}

    $f = $Record.fieldsMap

    Write-Output "[$timeStr] $($f.event_type) / $($f.event_sub_type): $($f.message)"
    Write-Output "  User: $($f.user_display_name)"
    Write-Output "  App:  $($f.application)"
    Write-Output "  Src:  $($f.src_ip)"
    Write-Output "  Dst:  $($f.dest_ip)"
    Write-Output ("-" * 80)
}

function Fetch-Events {
    $headers = @{
        "x-api-key"    = $API_KEY
        "Content-Type" = "application/json"
    }

    $marker = ""
    $totalEvents = 0
    $loopCount = 0

    while ($true) {
        $loopCount++
        if ($loopCount -gt $MAX_LOOPS) {
            Write-Output "[INFO] Reached MAX_LOOPS limit ($MAX_LOOPS)."
            break
        }

        $variables = @{
            accountIDs = @($ACCOUNT_ID)
            marker     = $marker
        }

        $bodyObject = @{
            query     = $query
            variables = $variables
        }

        $body = $bodyObject | ConvertTo-Json -Depth 20

        try {
            Write-DebugLog "Request URI" $API_URL
            Write-DebugLog "Request headers" @{
                "x-api-key"    = "***REDACTED***"
                "Content-Type" = "application/json"
            }
            Write-DebugLog "Request variables" $variables
            Write-DebugLog "Request body object" $bodyObject
            Write-DebugLog "Request body JSON" $body

            $response = Invoke-RestMethod `
                -Uri $API_URL `
                -Method Post `
                -Headers $headers `
                -Body $body `
                -TimeoutSec 30

            Write-DebugLog "Parsed API response" $response
        }
        catch {
            Write-Error "[ERROR] API request failed: $($_.Exception.Message)"

            if ($_.ErrorDetails -and $_.ErrorDetails.Message) {
                Write-Output "[DEBUG] ErrorDetails:"
                Write-Output $_.ErrorDetails.Message
                Write-Output ("=" * 80)
            }

            break
        }

        if ($response.errors) {
            Write-Error "[ERROR] API returned GraphQL errors."
            Write-DebugLog "GraphQL errors" $response.errors
            break
        }

        if (-not $response.data) {
            Write-Output "[DEBUG] Response has no 'data' property."
            Write-DebugLog "Full parsed response" $response
            break
        }

        $feed = $response.data.eventsFeed
        if (-not $feed) {
            Write-Output "[DEBUG] Response has no 'data.eventsFeed'."
            Write-DebugLog "Full parsed response" $response
            break
        }

        Write-DebugLog "eventsFeed object" $feed

        $batchCount = 0

        if (-not $feed.accounts) {
            Write-Output "[DEBUG] eventsFeed.accounts is null or empty."
        }
        else {
            Write-Output "[DEBUG] Number of accounts returned: $($feed.accounts.Count)"
        }

        foreach ($account in $feed.accounts) {
            Write-Output "[DEBUG] Inspecting account id: $($account.id)"

            if (-not $account.records) {
                Write-Output "[DEBUG] No records returned for this account."
                continue
            }

            Write-Output "[DEBUG] Records returned for account $($account.id): $($account.records.Count)"

            foreach ($record in $account.records) {
                if ($DEBUG_MODE -and $batchCount -lt 3) {
                    Write-DebugLog "Sample record" $record

                    if ($record.fieldsMap) {
                        Write-DebugLog "Sample fieldsMap keys" ($record.fieldsMap.PSObject.Properties.Name)
                    }
                }

                Print-Event $record
                $totalEvents++
                $batchCount++
            }
        }

        Write-Output "[INFO] Batch fetched: $($feed.fetchedCount)"
        Write-Output "[DEBUG] Batch printed: $batchCount"
        Write-Output "[DEBUG] Next marker: $($feed.marker)"

        if (($feed.fetchedCount -eq 0) -or [string]::IsNullOrWhiteSpace($feed.marker)) {
            Write-Output "[INFO] Stopping because fetchedCount is 0 or marker is empty."
            break
        }

        $marker = $feed.marker
    }

    Write-Output "[INFO] Total events retrieved: $totalEvents"
}

Fetch-Events


# end script -----------------------------------------------------------------------------

  • Hi ddaniel​, the first result from the API with no marker specified is always an empty result set with a marker value so I think your logic on line 196 just needs to be amended to this:

    if (($feed.fetchedCount -eq 0 -and $batchCount -ne 0) -or [string]::IsNullOrWhiteSpace($feed.marker)) {

    That way, it will skip trying to break execution for the first result set and then start using the marker until you do get a true empty result set.

8 Replies

    • ddaniel's avatar
      ddaniel
      Icon for Staying Involved rankStaying Involved

      I am able to make a connection and do other queries - this is my first time trying the events feed and that is what I am having trouble with.  

      I am successfully using the accountsnapshot for several months now to graph the current number of SDP users in the office and remote via PRTG.

      I return a little JSON result every 5 minutes that I feed into PRTG for monitoring of users on our systems via
       Omnissa VDI   / Cato SDP (remote or in office)  / Azure AVD (FYI - I would REALLY like to see my always on/prelogin Cato connections as well but there does not seem to be a way) It helps me spot unusual events and patterns/trends.



       

    • ddaniel's avatar
      ddaniel
      Icon for Staying Involved rankStaying Involved

      (I thought I had responded to this but I hope this is not a duplicate)
      I have been using the API and specifically the account snapshot for many months.  I use it to pull user counts of SDP users that are in office, or remote and graph that information in PRTG.  I parse the account info and count by connection type and then pass the counts as JSON into PRTG. (I wish I could also get my always on/prelogin counts but unfortunately that does not seem possible.)

      I use PRTG to graph the SDP client connections as part of my remote users dashboard

       

    • ddaniel's avatar
      ddaniel
      Icon for Staying Involved rankStaying Involved

      I have tried 3 times to reply directly and finally emailed, but I will do it without images (maybe that was the issue?) to see if it sticks.

      Yes - I am able to use the  API for an account snapshot and return results.  I have been doing this for months, every 5 minutes to get a count of remote and in-office Cato users and graph that in our network monitoring software in a "remote users " dashboard.

  • JohnF's avatar
    JohnF
    Icon for Cato Professional Services rankCato Professional Services

    Hi ddaniel​, the first result from the API with no marker specified is always an empty result set with a marker value so I think your logic on line 196 just needs to be amended to this:

    if (($feed.fetchedCount -eq 0 -and $batchCount -ne 0) -or [string]::IsNullOrWhiteSpace($feed.marker)) {

    That way, it will skip trying to break execution for the first result set and then start using the marker until you do get a true empty result set.

    • ddaniel's avatar
      ddaniel
      Icon for Staying Involved rankStaying Involved

      JohnF - thank you!  I have marked that as a solution and my code is now working and returning results.  I have more reading and development to do but that gets me started nicely.  I am now retrieving events.

  • ddaniel's avatar
    ddaniel
    Icon for Staying Involved rankStaying Involved

    I believe this is the third time I have tried to leave a reply and it disappears so I also responded by message.  but here goes again:
    I have been successfully using the accountsnapshot API for close to a year to gather counts of remote and in-office cato users, summarize the counts in a JSON format, and pass it to our PRTG (PRTG Network Monitor: All-in-One Network Monitoring Software) monitoring software to be included in our remote users dashboard.  So I think my API key / tenant ID are correct I am just getting no results on the Events feed example I am trying.

    I currently run a powershell script that parses an account snapshot to return total users, office sessions, and RemoteSDPsessions in JSON format.  This runs every 5 minutes and feeds our PRTG monitoring tool.

    I then send that JSON info to PRTG for graphing and include it in my "remote use" dashboard (something we started at the beginning of Covid and increased remote work) not important to this discussion but I also graph our Omnissa VDI users and our MS AVD users (currently migrating to AVD) through the APIs for those tools..  We are primarily a laptop with SDP client shop and have very low VDI usage.

    I am only having trouble with the API for Cato events feed via powershell.  I can only find python examples.  I may need to try one of those to verify my account is configured properly.  I would also like to see if anyone has a sample for the CATO API Playground. (I think that is the name)

     

  • ddaniel's avatar
    ddaniel
    Icon for Staying Involved rankStaying Involved

    FYI - I am still looking for someone who is successfully accessing the events feed via API to test the Powershell code at the top of this thread.