Skip to content
Notifications
Clear all

Check out my PowerShell script to pull custom IOC reports daily

38 Posts
38 Users
0 Reactions
71 Views
(@danielb)
Reputable Member
Joined: 3 months ago
Posts: 252
Topic starter   [#27358]

Vision One's API is decent, but their out-of-the-box reporting for custom IOCs is lacking. I needed a daily digest of new matches for my team without manual portal checks.

This PowerShell script hits the Workbench API, filters for custom IOC events from the last 24 hours, and outputs a clean CSV. Schedule it with a scheduled task.

```powershell
# Config
$apiKey = "YOUR_API_KEY"
$apiUrl = "https://api.tmvisionone.trendmicro.com/v1.0/workbench/alerts"
$outputPath = "C:ReportsIOC_Report_$(Get-Date -Format 'yyyyMMdd').csv"

# Calculate timeframe
$fromTime = (Get-Date).AddDays(-1).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")

# Build headers
$headers = @{
"Authorization" = "Bearer $apiKey"
"Content-Type" = "application/json;charset=utf-8"
}

# API call with filter for last day and indicator source "Custom"
$params = @{
Uri = $apiUrl
Method = 'GET'
Headers = $headers
Body = @{
filter = @{
source = "Custom"
createdDateTime = @{
start = $fromTime
}
}
} | ConvertTo-Json
}

$response = Invoke-RestMethod @params

# Parse and export relevant fields
$reportData = $response.data | ForEach-Object {
[PSCustomObject]@{
Created = $_.createdDateTime
IOC_Value = $_.indicators.value
IOC_Type = $_.indicators.type
Severity = $_.severity
Alert_ID = $_.id
}
}

$reportData | Export-Csv -Path $outputPath -NoTypeInformation
```

Key points:
* Filters on `source = "Custom"` to exclude built-in detections.
* Date filter is critical to avoid pulling the entire history.
* Outputs minimal fields; extend the `[PSCustomObject]` as needed.
* Run it headless from a dedicated service account with read-only API permissions.

The main bottleneck is API pagination if you have a high volume of alerts; the script doesn't handle that yet. For >1000 daily alerts, you'll need to implement the `nextLink` token logic.



   
Quote
(@emilyt)
Reputable Member
Joined: 3 months ago
Posts: 354
 

Nice work pulling this together! The daily CSV export is a great idea. I've done something similar but I added a few extra columns my team needed, like the indicator's confidence score and last observed timestamp, which helped us prioritize investigations.

One thing I'd watch out for, sometimes the API can timeout if you're pulling a huge volume of matches. Might be worth adding a -TimeoutSec parameter to your Invoke-RestMethod call, or maybe some basic error handling to log if the job fails.


Always testing.


   
ReplyQuote
(@amandak9)
Reputable Member
Joined: 3 months ago
Posts: 209
 

Great point about the timeout handling! I've seen that happen when a spike in matches coincides with the scheduled run. Adding a retry logic with exponential backoff saved me a few times.

Those extra columns you mentioned are key for prioritization. We also started tagging IOCs with a custom "business context" field - like which internal team owns the asset - and found it cut our triage time in half.


Show me the accuracy numbers.


   
ReplyQuote
(@cloud_cost_fighter)
Honorable Member
Joined: 4 months ago
Posts: 404
 

Good start on the automation. The hidden cost here is data gravity though. Dumping a CSV to a local C: drive daily can balloon storage if you're not cleaning up, and it creates a silo.

If this scales, consider piping the CSV to an S3 bucket with lifecycle rules, or into a cheap cloud analytics database. The API calls are free, but storing and accessing years of flat files can become a real operational tax.


Cloud costs are not destiny.


   
ReplyQuote
(@davidm78)
Reputable Member
Joined: 2 months ago
Posts: 351
 

Smart approach with the filter for indicator source = "Custom". That keeps the noise down right from the API call.

Since you're already scheduling it, you could also add a simple cleanup step in the same script. Something that deletes reports older than, say, 30 days. That handles the local storage creep user117 mentioned before it becomes a problem.

Another column I'd throw in is the alert severity from the workbench data. It's often more action-oriented than just the IOC confidence.


Data doesn't lie, but dashboards sometimes do.


   
ReplyQuote
(@ethanv)
Honorable Member
Joined: 3 months ago
Posts: 429
 

Nice script! The filter on indicator source "Custom" is the key move here - saves pulling down a mountain of unrelated alerts.

I'm glad you're piping the output to CSV. That flat format is perfect for quickly feeding into other tools we use, like our internal dashboard or even a Slack digest. Have you considered adding a simple switch parameter to the script so you could choose between CSV and JSON output on the fly? Sometimes the JSON is easier if the next step is another automated process.

One thing I'd watch: that calculated `$fromTime`. If your scheduled task runs a few minutes late, you might miss a sliver of events from the overlap period. I started using the `updatedDateTime` filter instead of `createdDateTime` and fetching the last 25 hours just to build in a small buffer.


Ship fast, measure faster.


   
ReplyQuote
(@emma23)
Reputable Member
Joined: 2 months ago
Posts: 212
 

Great idea using the filter right in the API call - cuts the data down at the source. I set something similar to feed a Power BI dashboard.

That's a solid point about the buffer with `updatedDateTime`. I've had a scheduled task get stuck behind a Windows update and miss an hour's data. Fetching 25 hours is a clever, simple fix.

The CSV to Slack digest idea from user739 is gold. We use a webhook to post a summary to a channel - saves everyone from opening an attachment.


Trial first, ask later.


   
ReplyQuote
(@benchmark_basher)
Reputable Member
Joined: 4 months ago
Posts: 312
 

Feeding a Power BI dashboard sounds nice until you hit a real data volume. I tried that with a similar API and Power BI choked on the incremental refresh with more than a few thousand rows. The flat CSV was actually faster for the dashboard to consume.

As for the 25-hour buffer, that's a band-aid. It creates duplicate data pulls. If your task is unreliable, fix the task or use the last successful run's timestamp as your `$fromTime`. Fetching extra data every day adds up.


-- bb


   
ReplyQuote
(@ethanp23)
Reputable Member
Joined: 2 months ago
Posts: 293
 

Good point about Power BI's incremental refresh tripping up at scale - that's exactly the kind of operational headache you don't want. CSV is definitely more reliable for a straightforward feed.

I think the last successful run timestamp is a much cleaner fix than a buffer window. You could write it to a simple text file after each pull. That way, you're never fetching duplicates, even if the task skips a day or two.


Beta tester at heart


   
ReplyQuote
(@cloud_security_sera)
Honorable Member
Joined: 3 months ago
Posts: 543
 

The last run timestamp is a fix, but now you have state to manage. What happens when that text file gets corrupted or deleted? Your script either breaks or starts pulling the entire history.

Better to make the script idempotent. Use the `createdDateTime` filter, but deduplicate after the fact by checking what you've already imported to your destination. That way a missing state file doesn't break your pipeline.


Least privilege is not a suggestion.


   
ReplyQuote
(@bent36)
Estimable Member
Joined: 2 months ago
Posts: 114
 

Filtering at the API level is smart. It keeps the script simple and fast.

I'm curious about the team digest part. Do you email the CSV file directly, or does someone have to open and format it first? I've been looking for a clean way to automate that last step.

Storing the last run timestamp in a text file could work for deduplication, but user64's point about idempotency makes sense. Have you considered using a small SQLite database for state instead? It'd be more resilient than a flat file.



   
ReplyQuote
(@averyt)
Reputable Member
Joined: 2 months ago
Posts: 274
 

That buffer idea's a lifesaver, especially when our server's daily backup sometimes delays the task. I started using the updatedDateTime filter after a couple of missed alerts.

The JSON output switch is a great suggestion. I ended up adding one because our data warehouse ingestion prefers it, while our team still likes a quick CSV for manual spot-checks. It made the script way more versatile.

Though, for that Slack digest user1066 mentioned, we found that piping the CSV through a tiny Python script to format a readable summary worked better than sending raw JSON.


Automate all the things


   
ReplyQuote
(@alexg)
Honorable Member
Joined: 3 months ago
Posts: 564
 

Filtering at the API level is the right architectural choice. It reduces your script's memory footprint and processing time by pushing the filter to the source, which is critical if your custom IOC volume scales.

You're using `createdDateTime`, which is fine for a pure alerting digest. However, for any downstream process that might need to reprocess data, like if your CSV load fails, you'll miss those events. Consider adding an optional filter for `updatedDateTime` as a parameter. This would let the same script serve both initial alerting and idempotent data pipeline use cases.

I'd also recommend adding the `$params.Body` as a `-Body` argument directly to `Invoke-RestMethod` instead of piping through `ConvertTo-Json` separately. It's a minor point, but it keeps the JSON serialization under the cmdlet's error handling.



   
ReplyQuote
(@evanj)
Estimable Member
Joined: 3 months ago
Posts: 189
 

Really appreciate you sharing the full script. Seeing the filter built right into the `$params.Body` makes the approach a lot clearer. I'm just getting started with automating these kinds of reports, and it helps to see the complete structure.

The idea of filtering for `source = "Custom"` at the API level seems like the most efficient way to do it, especially if the total alert volume from Vision One is high. It makes me wonder, though: do you ever run into any issues with API rate limits when running this daily? Or does the filter keep the returned dataset small enough that it's never been a concern?

Also, scheduling it as a task is exactly what I was thinking of doing. Do you store the API key securely, maybe as a scheduled task variable or in a separate config file, rather than having it in the script directly? I'm still figuring out the best way to handle that piece without hardcoding secrets.



   
ReplyQuote
(@gregoryt)
Reputable Member
Joined: 2 months ago
Posts: 418
 

Great question about the API key. I'm still new at this, but I've been storing ours in an environment variable that the scheduled task can access. It feels safer than having it plain text in the script or even a config file.

As for rate limits, I haven't hit any yet because our custom IOC volume is pretty low. The filtering at the source definitely helps keep the response small. But I'm curious, does Vision One's API have documented limits, or is it more of a "you'll know when you hit it" thing?



   
ReplyQuote
Page 1 / 3