Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsStudyTraining
ProjectsChecklistsAI RankingsNewsletterStatusTagsAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Study
Training
Projects
Checklists
AI Rankings
Newsletter
Status
Tags
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

429+ Articles
114+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. HOWTOs
  3. SentinelOne PowerShell API Automation
SentinelOne PowerShell API Automation
HOWTOAdvanced

SentinelOne PowerShell API Automation

The SentinelOne Management Console REST API enables automation of administrative tasks, reporting, threat response, and integration with existing security...

Dylan H.

Security Operations

February 11, 2026
15 min read

SCENARIO

The SentinelOne Management Console REST API enables automation of administrative tasks, reporting, threat response, and integration with existing security orchestration platforms. PowerShell provides a powerful scripting environment for Windows-centric organizations to automate SentinelOne operations without writing complex applications.

Use this method when:

  • Automating daily operational tasks (reporting, threat triage, agent management)
  • Integrating SentinelOne with SIEM, ticketing systems, or security orchestration platforms
  • Building custom dashboards or executive reports
  • Performing bulk operations (agent upgrades, policy changes, threat remediation)
  • Scheduled automation via Task Scheduler or Azure Automation
  • Custom compliance reporting or audit workflows

REQUIREMENTS & ASSUMPTIONS

Prerequisites:

  • SentinelOne Management Console with API access enabled
  • API token with appropriate permissions (Admin, SOC, or custom role)
  • PowerShell 5.1+ (Windows) or PowerShell 7+ (cross-platform)
  • Network connectivity from script execution host to SentinelOne console (HTTPS/443)
  • Understanding of REST API concepts (GET, POST, PUT, DELETE methods)
  • Familiarity with JSON data structures

Assumed Environment:

  • PowerShell execution policy allows script execution (Set-ExecutionPolicy RemoteSigned)
  • Internet proxy configured if required (via $PSDefaultParameterValues)
  • Secure credential storage (Azure Key Vault, Windows Credential Manager, encrypted files)
  • Logging directory available for script output (C:\BIN\LOGS)

Required Information:

  • SentinelOne Console URL: https://yourtenant.sentinelone.net
  • API Token: Generated from console with appropriate permissions
  • API Documentation: https://yourtenant.sentinelone.net/api-doc/overview

PROCESS

Step 1: Generate API token

  1. Log in to the SentinelOne Management Console
  2. Navigate to Settings → Users
  3. Locate your user account and click on it
  4. Under API Token, click Generate (or Regenerate if token exists)
  5. Copy the token immediately (it's only shown once)
  6. Store securely in password manager or secure credential storage

Token format:

  • Length: Variable (typically 64+ characters)
  • Format: Alphanumeric string
  • Example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... (JWT format)

Security best practices:

  • Never hardcode tokens in scripts (use secure retrieval methods)
  • Generate service account-specific tokens (not personal user tokens)
  • Rotate tokens quarterly or per security policy
  • Limit token permissions to minimum required (principle of least privilege)
  • Store in Azure Key Vault, AWS Secrets Manager, or encrypted configuration files

Step 2: Set up PowerShell environment

Install required modules (optional but recommended):

# Install PowerShell Gallery modules for enhanced API interaction
Install-Module -Name PowerShellGet -Force -AllowClobber
Install-Module -Name PSFramework -Scope CurrentUser  # Enhanced logging

Create reusable API connection module:

# Save as: C:\Scripts\SentinelOne-API-Module.psm1
 
$script:S1ApiToken = $null
$script:S1ConsoleUrl = $null
 
function Connect-S1Api {
    <#
    .SYNOPSIS
        Establishes connection parameters for SentinelOne API
    .PARAMETER ApiToken
        SentinelOne API token (securely retrieved)
    .PARAMETER ConsoleUrl
        SentinelOne console URL (e.g., https://yourtenant.sentinelone.net)
    .EXAMPLE
        $token = Get-Secret -Name "SentinelOne-API-Token" -AsPlainText
        Connect-S1Api -ApiToken $token -ConsoleUrl "https://yourtenant.sentinelone.net"
    #>
    param(
        [Parameter(Mandatory=$true)]
        [string]$ApiToken,
 
        [Parameter(Mandatory=$true)]
        [ValidatePattern('^https://.*\.sentinelone\.net$')]
        [string]$ConsoleUrl
    )
 
    $script:S1ApiToken = $ApiToken
    $script:S1ConsoleUrl = $ConsoleUrl.TrimEnd('/')
 
    Write-Host "[SUCCESS] Connected to SentinelOne API: $ConsoleUrl" -ForegroundColor Green
}
 
function Invoke-S1ApiRequest {
    <#
    .SYNOPSIS
        Generic wrapper for SentinelOne API requests
    .PARAMETER Endpoint
        API endpoint path (e.g., "/web/api/v2.1/agents")
    .PARAMETER Method
        HTTP method (GET, POST, PUT, DELETE)
    .PARAMETER Body
        Request body for POST/PUT requests (hashtable, auto-converted to JSON)
    .EXAMPLE
        Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents" -Method GET
    #>
    param(
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
 
        [Parameter(Mandatory=$false)]
        [ValidateSet('GET','POST','PUT','DELETE')]
        [string]$Method = 'GET',
 
        [Parameter(Mandatory=$false)]
        [hashtable]$Body = @{}
    )
 
    if (-not $script:S1ApiToken -or -not $script:S1ConsoleUrl) {
        throw "Not connected to SentinelOne API. Run Connect-S1Api first."
    }
 
    $headers = @{
        "Authorization" = "ApiToken $script:S1ApiToken"
        "Content-Type" = "application/json"
    }
 
    $uri = "$script:S1ConsoleUrl$Endpoint"
 
    $params = @{
        Uri = $uri
        Method = $Method
        Headers = $headers
        ErrorAction = 'Stop'
    }
 
    if ($Method -in @('POST', 'PUT') -and $Body.Count -gt 0) {
        $params.Body = ($Body | ConvertTo-Json -Depth 10)
    }
 
    try {
        $response = Invoke-RestMethod @params
        return $response
    }
    catch {
        Write-Host "[ERROR] API request failed: $($_.Exception.Message)" -ForegroundColor Red
        Write-Host "  URI: $uri" -ForegroundColor Yellow
        Write-Host "  Method: $Method" -ForegroundColor Yellow
 
        if ($_.Exception.Response) {
            $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
            $responseBody = $reader.ReadToEnd()
            Write-Host "  Response: $responseBody" -ForegroundColor Yellow
        }
 
        throw
    }
}
 
Export-ModuleMember -Function Connect-S1Api, Invoke-S1ApiRequest

Import the module in your scripts:

# Import custom module
Import-Module "C:\Scripts\SentinelOne-API-Module.psm1" -Force
 
# Retrieve API token securely (example using Windows Credential Manager)
$token = (Get-StoredCredential -Target "SentinelOne-API").GetNetworkCredential().Password
 
# Connect to API
Connect-S1Api -ApiToken $token -ConsoleUrl "https://yourtenant.sentinelone.net"

Step 3: Common API operations - Agent management

Retrieve all agents:

# Get all agents with basic filtering
$agents = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents" -Method GET
 
Write-Host "Total agents: $($agents.pagination.totalItems)"
Write-Host "Online agents: $(($agents.data | Where-Object {$_.isActive -eq $true}).Count)"
 
# Display summary
$agents.data | Select-Object computerName, osType, agentVersion, isActive, lastActiveDate | Format-Table -AutoSize

Filter agents by specific criteria:

# Build query parameters
$agentFilter = @{
    isActive = $true
    osTypes = "windows"
    agentVersions = "23.4.2.487"
    siteIds = "1234567890"
}
 
# Construct query string
$queryString = ($agentFilter.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&"
$endpoint = "/web/api/v2.1/agents?$queryString"
 
$filteredAgents = Invoke-S1ApiRequest -Endpoint $endpoint -Method GET
 
Write-Host "Filtered agents count: $($filteredAgents.pagination.totalItems)"

Get detailed information for specific agent:

# Search by computer name
$computerName = "WKS-001"
$agents = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents?computerName=$computerName" -Method GET
 
if ($agents.data.Count -gt 0) {
    $agent = $agents.data[0]
 
    Write-Host "Agent Details:" -ForegroundColor Cyan
    Write-Host "  Computer Name: $($agent.computerName)"
    Write-Host "  Agent ID: $($agent.id)"
    Write-Host "  Agent Version: $($agent.agentVersion)"
    Write-Host "  OS: $($agent.osName) $($agent.osRevision)"
    Write-Host "  Domain: $($agent.domain)"
    Write-Host "  Last Active: $($agent.lastActiveDate)"
    Write-Host "  Network Status: $($agent.networkStatus)"
    Write-Host "  Infected: $($agent.infected)"
} else {
    Write-Host "[WARN] Agent not found: $computerName" -ForegroundColor Yellow
}

Initiate full disk scan on agent:

# Trigger full scan on specific agent
$agentId = "1234567890123456789"
 
$scanBody = @{
    data = @{
        ids = @($agentId)
    }
}
 
$scanResult = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents/actions/initiate-scan" -Method POST -Body $scanBody
 
if ($scanResult.data.affected -gt 0) {
    Write-Host "[SUCCESS] Full scan initiated on $($scanResult.data.affected) agent(s)" -ForegroundColor Green
} else {
    Write-Host "[WARN] Scan not initiated - verify agent ID and connectivity" -ForegroundColor Yellow
}

Bulk agent operations (restart, update, etc.):

# Example: Restart SentinelOne agent service on multiple computers
$computerNames = @("WKS-001", "WKS-002", "WKS-003")
 
# Get agent IDs for these computers
$agentIds = foreach ($computerName in $computerNames) {
    $agent = (Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents?computerName=$computerName" -Method GET).data
    if ($agent) { $agent[0].id }
}
 
# Restart agents
$restartBody = @{
    data = @{
        ids = $agentIds
    }
}
 
$result = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents/actions/restart-machine" -Method POST -Body $restartBody
 
Write-Host "Restart initiated on $($result.data.affected) agent(s)"

Step 4: Common API operations - Threat management

Retrieve unresolved threats:

# Get all unresolved threats from last 7 days
$dateFilter = (Get-Date).AddDays(-7).ToString("yyyy-MM-ddT00:00:00Z")
$endpoint = "/web/api/v2.1/threats?resolved=false&createdAt__gte=$dateFilter"
 
$threats = Invoke-S1ApiRequest -Endpoint $endpoint -Method GET
 
Write-Host "Unresolved threats: $($threats.pagination.totalItems)" -ForegroundColor Yellow
 
# Display summary
$threats.data | Select-Object @{Name="ThreatID";Expression={$_.id}}, @{Name="ThreatName";Expression={$_.threatInfo.threatName}}, @{Name="Computer";Expression={$_.agentRealtimeInfo.computerName}}, @{Name="DetectedAt";Expression={$_.threatInfo.createdAt}} | Format-Table -AutoSize

Retrieve threat details including storyline:

# Get specific threat details
$threatId = "1234567890"
 
$threat = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats/$threatId" -Method GET
 
Write-Host "Threat Details:" -ForegroundColor Cyan
Write-Host "  Threat Name: $($threat.data.threatInfo.threatName)"
Write-Host "  Classification: $($threat.data.threatInfo.classification)"
Write-Host "  File Path: $($threat.data.threatInfo.filePath)"
Write-Host "  SHA256: $($threat.data.threatInfo.sha256)"
Write-Host "  Computer: $($threat.data.agentRealtimeInfo.computerName)"
Write-Host "  Mitigation Status: $($threat.data.threatInfo.mitigationStatus)"
 
# Get threat timeline/storyline
$timeline = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats/$threatId/timeline" -Method GET
 
Write-Host "`nThreat Timeline Events: $($timeline.data.Count)" -ForegroundColor Cyan
$timeline.data | Select-Object -First 10 | ForEach-Object {
    Write-Host "  - $($_.eventType): $($_.processName) at $($_.createdAt)"
}

Bulk resolve threats:

# Mark multiple threats as resolved
$threatIds = @("1234567890", "1234567891", "1234567892")
 
$resolveBody = @{
    data = @{
        threatIds = $threatIds
        resolution = "confirmed_threat"  # Options: confirmed_threat, false_positive, pua, suspicious
        analystNote = "Bulk resolution - remediated via automated response playbook"
    }
}
 
$result = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats/mark-as-resolved" -Method POST -Body $resolveBody
 
Write-Host "[SUCCESS] Resolved $($result.data.affected) threat(s)" -ForegroundColor Green

Quarantine or remediate threats:

# Quarantine specific threat
$threatId = "1234567890"
 
$quarantineBody = @{
    data = @{
        ids = @($threatId)
    }
}
 
$result = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats/actions/quarantine" -Method POST -Body $quarantineBody
 
Write-Host "Quarantine action initiated on $($result.data.affected) threat(s)"

Step 5: Common API operations - Reporting and analytics

Generate agent deployment status report:

# Retrieve all agents and generate CSV report
$allAgents = @()
$cursor = $null
 
do {
    $endpoint = if ($cursor) {
        "/web/api/v2.1/agents?cursor=$cursor&limit=1000"
    } else {
        "/web/api/v2.1/agents?limit=1000"
    }
 
    $response = Invoke-S1ApiRequest -Endpoint $endpoint -Method GET
 
    $allAgents += $response.data
    $cursor = $response.pagination.nextCursor
 
    Write-Host "Retrieved $($allAgents.Count) agents so far..."
} while ($cursor)
 
# Generate report
$report = $allAgents | Select-Object `
    computerName,
    osType,
    osName,
    osRevision,
    agentVersion,
    siteName,
    isActive,
    lastActiveDate,
    domain,
    infected,
    networkStatus
 
# Export to CSV
$reportPath = "C:\BIN\LOGS\SentinelOne-Agent-Report-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
$report | Export-Csv -Path $reportPath -NoTypeInformation
 
Write-Host "[SUCCESS] Agent report generated: $reportPath" -ForegroundColor Green
Write-Host "Total agents: $($allAgents.Count)"
Write-Host "Active agents: $(($allAgents | Where-Object {$_.isActive}).Count)"
Write-Host "Infected agents: $(($allAgents | Where-Object {$_.infected}).Count)"

Generate daily threat summary report:

# Get threats from last 24 hours
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-ddT00:00:00Z")
$threats = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats?createdAt__gte=$yesterday" -Method GET
 
# Analyze threat data
$totalThreats = $threats.pagination.totalItems
$resolvedThreats = ($threats.data | Where-Object {$_.threatInfo.resolved}).Count
$unresolvedThreats = $totalThreats - $resolvedThreats
 
$threatsByClassification = $threats.data | Group-Object -Property {$_.threatInfo.classification} | Select-Object Name, Count
 
# Generate HTML report
$htmlReport = @"
<!DOCTYPE html>
\<html\>
\<head\>
    \<title\>SentinelOne Daily Threat Summary - $(Get-Date -Format 'yyyy-MM-dd')</title>
    \<style\>
        body { font-family: Arial, sans-serif; margin: 20px; }
        h1 { color: #333; }
        table { border-collapse: collapse; width: 100%; margin-top: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #4CAF50; color: white; }
        .summary { background-color: #f0f0f0; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
    </style>
</head>
\<body\>
    <h1>SentinelOne Daily Threat Summary</h1>
    <div class="summary">
        \<p\>\<strong\>Report Date:</strong> $(Get-Date -Format 'yyyy-MM-dd HH:mm')</p>
        \<p\>\<strong\>Total Threats (24h):</strong> $totalThreats</p>
        \<p\>\<strong\>Resolved:</strong> $resolvedThreats</p>
        \<p\>\<strong\>Unresolved:</strong> $unresolvedThreats</p>
    </div>
 
    <h2>Threats by Classification</h2>
    \<table\>
        \<tr\>\<th\>Classification</th>\<th\>Count</th></tr>
"@
 
foreach ($classification in $threatsByClassification) {
    $htmlReport += "\<tr\>\<td\>$($classification.Name)</td>\<td\>$($classification.Count)</td></tr>"
}
 
$htmlReport += @"
    </table>
 
    <h2>Recent Unresolved Threats</h2>
    \<table\>
        \<tr\>\<th\>Threat Name</th>\<th\>Computer</th>\<th\>Detected At</th>\<th\>Confidence</th></tr>
"@
 
$unresolvedThreatList = $threats.data | Where-Object {-not $_.threatInfo.resolved} | Select-Object -First 20
foreach ($threat in $unresolvedThreatList) {
    $htmlReport += "\<tr\>\<td\>$($threat.threatInfo.threatName)</td>\<td\>$($threat.agentRealtimeInfo.computerName)</td>\<td\>$($threat.threatInfo.createdAt)</td>\<td\>$($threat.threatInfo.confidenceLevel)</td></tr>"
}
 
$htmlReport += @"
    </table>
</body>
</html>
"@
 
# Save report
$htmlPath = "C:\BIN\LOGS\SentinelOne-Daily-Threat-Report-$(Get-Date -Format 'yyyyMMdd').html"
$htmlReport | Out-File -FilePath $htmlPath -Encoding UTF8
 
Write-Host "[SUCCESS] Threat report generated: $htmlPath" -ForegroundColor Green
 
# Email report (optional - requires configured SMTP)
# Send-MailMessage -From "sentinelone@company.com" -To "security-team@company.com" -Subject "SentinelOne Daily Threat Summary" -BodyAsHtml -Body $htmlReport -SmtpServer "smtp.company.com"

Step 6: Common API operations - Policy and exclusion management

Retrieve and export current policies:

# Get all sites
$sites = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/sites" -Method GET
 
# For each site, get policy configuration
$policyReport = foreach ($site in $sites.data) {
    Write-Host "Retrieving policy for site: $($site.name)"
 
    # Note: Policy retrieval varies by version, this is a generalized example
    $policy = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/sites/$($site.id)" -Method GET
 
    [PSCustomObject]@{
        SiteName = $site.name
        SiteId = $site.id
        AgentCount = $site.agentCount
        # Add specific policy settings as available in API response
    }
}
 
$policyReport | Export-Csv -Path "C:\BIN\LOGS\SentinelOne-Policy-Audit-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation

Create exclusion via API:

# Create path exclusion
$exclusionBody = @{
    data = @{
        type = "path"
        value = "C:\Program Files\Custom App\**"
        osType = "windows"
        mode = @("static", "interoperability")
        description = "Custom business application - approved by CISO ticket #12345"
        scope = "site"
        siteIds = @("1234567890")
    }
}
 
$result = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/exclusions" -Method POST -Body $exclusionBody
 
if ($result.data.id) {
    Write-Host "[SUCCESS] Exclusion created with ID: $($result.data.id)" -ForegroundColor Green
} else {
    Write-Host "[ERROR] Failed to create exclusion" -ForegroundColor Red
}

Bulk create exclusions from CSV:

# Import exclusions from CSV file
# CSV format: Type,Value,OSType,Mode,Description,Scope,SiteId
$exclusionsToCreate = Import-Csv -Path "C:\Temp\exclusions-to-create.csv"
 
foreach ($exclusion in $exclusionsToCreate) {
    Write-Host "Creating exclusion: $($exclusion.Value)"
 
    $exclusionBody = @{
        data = @{
            type = $exclusion.Type.ToLower()
            value = $exclusion.Value
            osType = $exclusion.OSType.ToLower()
            mode = $exclusion.Mode.Split('|')
            description = $exclusion.Description
        }
    }
 
    if ($exclusion.Scope -eq "Site") {
        $exclusionBody.data.scope = "site"
        $exclusionBody.data.siteIds = @($exclusion.SiteId)
    } else {
        $exclusionBody.data.scope = "account"
    }
 
    try {
        $result = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/exclusions" -Method POST -Body $exclusionBody
        Write-Host "  [SUCCESS] Exclusion ID: $($result.data.id)" -ForegroundColor Green
    }
    catch {
        Write-Host "  [FAILED] $($_.Exception.Message)" -ForegroundColor Red
    }
}

Step 7: Advanced automation - SIEM integration

Send SentinelOne alerts to Splunk/Syslog:

# Retrieve new threats and forward to Splunk HEC (HTTP Event Collector)
$splunkHecUrl = "https://splunk.company.com:8088/services/collector/event"
$splunkToken = "your-splunk-hec-token"
 
$lastCheckFile = "C:\BIN\LOGS\sentinelone-last-check.txt"
$lastCheckTime = if (Test-Path $lastCheckFile) {
    Get-Content $lastCheckFile
} else {
    (Get-Date).AddHours(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
}
 
# Get threats since last check
$threats = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats?createdAt__gte=$lastCheckTime" -Method GET
 
Write-Host "Found $($threats.pagination.totalItems) new threat(s) since $lastCheckTime"
 
# Send each threat to Splunk
foreach ($threat in $threats.data) {
    $splunkEvent = @{
        event = @{
            threatId = $threat.id
            threatName = $threat.threatInfo.threatName
            classification = $threat.threatInfo.classification
            confidenceLevel = $threat.threatInfo.confidenceLevel
            computerName = $threat.agentRealtimeInfo.computerName
            filePath = $threat.threatInfo.filePath
            sha256 = $threat.threatInfo.sha256
            detectedAt = $threat.threatInfo.createdAt
            mitigationStatus = $threat.threatInfo.mitigationStatus
        }
        sourcetype = "sentinelone:threat"
        index = "security"
    } | ConvertTo-Json -Depth 10
 
    $splunkHeaders = @{
        "Authorization" = "Splunk $splunkToken"
        "Content-Type" = "application/json"
    }
 
    try {
        Invoke-RestMethod -Uri $splunkHecUrl -Method POST -Headers $splunkHeaders -Body $splunkEvent
        Write-Host "  [SUCCESS] Sent threat $($threat.id) to Splunk" -ForegroundColor Green
    }
    catch {
        Write-Host "  [FAILED] Failed to send threat to Splunk: $($_.Exception.Message)" -ForegroundColor Red
    }
}
 
# Update last check time
(Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ") | Out-File -FilePath $lastCheckFile -Force

Step 8: Scheduled automation with Task Scheduler

Create scheduled task to run daily threat report:

# Define the script to run
$scriptPath = "C:\Scripts\SentinelOne-Daily-Threat-Report.ps1"
$scriptContent = @'
# SentinelOne Daily Threat Report Script
Import-Module "C:\Scripts\SentinelOne-API-Module.psm1" -Force
 
$token = (Get-StoredCredential -Target "SentinelOne-API").GetNetworkCredential().Password
Connect-S1Api -ApiToken $token -ConsoleUrl "https://yourtenant.sentinelone.net"
 
# [Add report generation code from Step 5]
 
# Email report
$htmlPath = "C:\BIN\LOGS\SentinelOne-Daily-Threat-Report-$(Get-Date -Format 'yyyyMMdd').html"
Send-MailMessage -From "sentinelone@company.com" -To "security-team@company.com" -Subject "SentinelOne Daily Threat Summary - $(Get-Date -Format 'yyyy-MM-dd')" -Body (Get-Content $htmlPath -Raw) -BodyAsHtml -SmtpServer "smtp.company.com"
'@
 
$scriptContent | Out-File -FilePath $scriptPath -Encoding UTF8
 
# Create scheduled task
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00AM"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
 
Register-ScheduledTask -TaskName "SentinelOne-Daily-Threat-Report" -Action $action -Trigger $trigger -Principal $principal -Description "Daily SentinelOne threat summary report"
 
Write-Host "[SUCCESS] Scheduled task created: SentinelOne-Daily-Threat-Report" -ForegroundColor Green

VERIFICATION

Test API connectivity:

# Simple API connectivity test
try {
    $response = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/system/info" -Method GET
    Write-Host "[SUCCESS] API connectivity verified" -ForegroundColor Green
    Write-Host "  Console Version: $($response.data.consoleVersion)"
    Write-Host "  API Version: $($response.data.apiVersion)"
}
catch {
    Write-Host "[FAILED] API connectivity test failed" -ForegroundColor Red
    Write-Host "  Error: $($_.Exception.Message)"
}

Verify API token permissions:

# Check token scope and permissions
$tokenInfo = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/users/api-token-details" -Method GET
 
Write-Host "API Token Details:" -ForegroundColor Cyan
Write-Host "  User: $($tokenInfo.data.user.fullName)"
Write-Host "  Role: $($tokenInfo.data.user.role)"
Write-Host "  Expiration: $($tokenInfo.data.expirationDate)"
Write-Host "  Scopes: $($tokenInfo.data.scopes -join ', ')"

Test API rate limits:

# Monitor API rate limit headers
$response = Invoke-WebRequest -Uri "$ConsoleUrl/web/api/v2.1/agents?limit=1" -Headers @{"Authorization"="ApiToken $ApiToken"} -Method GET
 
Write-Host "API Rate Limit Status:" -ForegroundColor Cyan
Write-Host "  Limit: $($response.Headers['X-RateLimit-Limit'])"
Write-Host "  Remaining: $($response.Headers['X-RateLimit-Remaining'])"
Write-Host "  Reset: $(([DateTimeOffset]::FromUnixTimeSeconds([int]$response.Headers['X-RateLimit-Reset'])).LocalDateTime)"

TROUBLESHOOTING

Issue: Authentication failure (401 Unauthorized)

Solutions:

  • Verify API token is correct and not expired
  • Check token was copied completely (no truncation)
  • Regenerate token from console if necessary
  • Verify user account is not disabled
  • Ensure ApiToken prefix is used in Authorization header

Issue: Permission denied (403 Forbidden)

Solutions:

  • Verify user role has required API permissions
  • Check endpoint requires Admin/SOC role vs. Analyst role
  • Review API documentation for endpoint-specific permission requirements
  • Generate new token from service account with higher privileges

Issue: Rate limit exceeded (429 Too Many Requests)

Solutions:

  • Implement rate limit handling in scripts:
function Invoke-S1ApiRequestWithRetry {
    param($Endpoint, $Method = 'GET', $Body = @{}, $MaxRetries = 3)
 
    $retryCount = 0
 
    while ($retryCount -lt $MaxRetries) {
        try {
            return Invoke-S1ApiRequest -Endpoint $Endpoint -Method $Method -Body $Body
        }
        catch {
            if ($_.Exception.Response.StatusCode -eq 429) {
                $retryAfter = $_.Exception.Response.Headers['Retry-After']
                Write-Host "[WARN] Rate limit exceeded, retrying after $retryAfter seconds..." -ForegroundColor Yellow
                Start-Sleep -Seconds ([int]$retryAfter + 1)
                $retryCount++
            } else {
                throw
            }
        }
    }
 
    throw "Max retries exceeded due to rate limiting"
}
  • Reduce request frequency (add delays between calls)
  • Use pagination with smaller page sizes instead of retrieving all data at once

Issue: Pagination handling for large datasets

Solutions:

# Proper pagination handling using cursor
function Get-AllS1Agents {
    $allAgents = @()
    $cursor = $null
 
    do {
        $endpoint = if ($cursor) {
            "/web/api/v2.1/agents?cursor=$cursor&limit=1000"
        } else {
            "/web/api/v2.1/agents?limit=1000"
        }
 
        $response = Invoke-S1ApiRequest -Endpoint $endpoint -Method GET
        $allAgents += $response.data
        $cursor = $response.pagination.nextCursor
 
        Write-Host "Retrieved $($allAgents.Count) agents so far..."
    } while ($cursor)
 
    return $allAgents
}

Issue: Proxy configuration required

Solutions:

# Configure PowerShell to use corporate proxy
$proxy = "http://proxy.company.com:8080"
$proxyCredential = Get-Credential -Message "Enter proxy credentials"
 
$PSDefaultParameterValues = @{
    'Invoke-RestMethod:Proxy' = $proxy
    'Invoke-RestMethod:ProxyCredential' = $proxyCredential
    'Invoke-WebRequest:Proxy' = $proxy
    'Invoke-WebRequest:ProxyCredential' = $proxyCredential
}
 
# Or configure system-wide proxy
netsh winhttp set proxy proxy-server="proxy.company.com:8080"

COMMANDS/SCRIPTS

Complete SentinelOne automation framework:

# Save as: C:\Scripts\SentinelOne-Automation-Framework.ps1
 
# Module import and connection (see Step 2 for module code)
Import-Module "C:\Scripts\SentinelOne-API-Module.psm1" -Force
 
# Secure token retrieval (Azure Key Vault example)
# Requires: Install-Module Az.KeyVault
$vaultName = "CompanySecrets"
$secretName = "SentinelOne-API-Token"
$token = (Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -AsPlainText)
 
Connect-S1Api -ApiToken $token -ConsoleUrl "https://yourtenant.sentinelone.net"
 
# === Automation Functions ===
 
function Get-S1UnresolvedThreats {
    <#
    .SYNOPSIS
        Retrieves all unresolved threats from last N days
    #>
    param([int]$DaysBack = 7)
 
    $dateFilter = (Get-Date).AddDays(-$DaysBack).ToString("yyyy-MM-ddT00:00:00Z")
    $endpoint = "/web/api/v2.1/threats?resolved=false&createdAt__gte=$dateFilter"
 
    $threats = Invoke-S1ApiRequest -Endpoint $endpoint -Method GET
    return $threats.data
}
 
function Invoke-S1ThreatTriage {
    <#
    .SYNOPSIS
        Automated threat triage - marks low-severity PUA as resolved
    #>
    $puaThreats = Get-S1UnresolvedThreats -DaysBack 1 | Where-Object {$_.threatInfo.classification -eq "pua"}
 
    Write-Host "Found $($puaThreats.Count) PUA threats for auto-resolution"
 
    foreach ($threat in $puaThreats) {
        $resolveBody = @{
            data = @{
                threatIds = @($threat.id)
                resolution = "pua"
                analystNote = "Auto-resolved PUA - low severity, automated triage $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
            }
        }
 
        try {
            Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats/mark-as-resolved" -Method POST -Body $resolveBody
            Write-Host "  [SUCCESS] Resolved threat: $($threat.id) - $($threat.threatInfo.threatName)" -ForegroundColor Green
        }
        catch {
            Write-Host "  [FAILED] $($threat.id): $($_.Exception.Message)" -ForegroundColor Red
        }
    }
}
 
function Send-S1DailyReport {
    <#
    .SYNOPSIS
        Generates and emails daily operational report
    #>
    param(
        [string]$EmailTo = "security-team@company.com",
        [string]$EmailFrom = "sentinelone@company.com",
        [string]$SmtpServer = "smtp.company.com"
    )
 
    # Get statistics
    $allAgents = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/agents" -Method GET
    $activeAgents = ($allAgents.data | Where-Object {$_.isActive}).Count
    $infectedAgents = ($allAgents.data | Where-Object {$_.infected}).Count
 
    $yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-ddT00:00:00Z")
    $threats = Invoke-S1ApiRequest -Endpoint "/web/api/v2.1/threats?createdAt__gte=$yesterday" -Method GET
    $unresolvedThreats = ($threats.data | Where-Object {-not $_.threatInfo.resolved}).Count
 
    # Build HTML report
    $htmlBody = @"
\<html\>
\<body\>
<h2>SentinelOne Daily Report - $(Get-Date -Format 'yyyy-MM-dd')</h2>
<h3>Agent Status</h3>
\<ul\>
    \<li\>Total Agents: $($allAgents.pagination.totalItems)</li>
    \<li\>Active Agents: $activeAgents</li>
    \<li\>Infected Agents: $infectedAgents</li>
</ul>
<h3>Threat Summary (24h)</h3>
\<ul\>
    \<li\>Total Threats: $($threats.pagination.totalItems)</li>
    \<li\>Unresolved: $unresolvedThreats</li>
</ul>
\<p\>\<em\>Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</em></p>
</body>
</html>
"@
 
    # Send email
    Send-MailMessage -From $EmailFrom -To $EmailTo -Subject "SentinelOne Daily Report - $(Get-Date -Format 'yyyy-MM-dd')" -Body $htmlBody -BodyAsHtml -SmtpServer $SmtpServer
 
    Write-Host "[SUCCESS] Daily report sent to $EmailTo" -ForegroundColor Green
}
 
# === Main Execution ===
Write-Host "=== SentinelOne Daily Automation ===" -ForegroundColor Cyan
 
# Run automated triage
Invoke-S1ThreatTriage
 
# Generate and send daily report
Send-S1DailyReport
 
Write-Host "`n[COMPLETE] Automation tasks finished" -ForegroundColor Green

Related Reading

  • SentinelOne Health Check: Agent Status Monitoring and
  • Deploy SentinelOne Policy
  • SentinelOne Application Control Policies
#sentinelone#edr#Security#threat-hunting#deployment#policy#automation#api#detection-rules

Related Articles

SentinelOne Health Check: Agent Status Monitoring and

Organizations deploying SentinelOne endpoint protection require continuous monitoring of agent health to ensure comprehensive threat coverage across their...

17 min read

Deploy SentinelOne Policy

Deploy, manage, and validate SentinelOne security policies across your endpoint estate using the SentinelOne Management API. This automated workflow supports:

25 min read

SentinelOne Application Control Policies

Organizations face security risks from unauthorized applications, malware disguised as legitimate software, and shadow IT installations that bypass...

15 min read
Back to all HOWTOs