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. Deploy SentinelOne Policy
Deploy SentinelOne Policy
HOWTOAdvanced

Deploy SentinelOne Policy

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

Dylan H.

Security Operations

February 11, 2026
25 min read

SCENARIO

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

  • Policy deployment to sites, groups, or entire accounts with pre-deployment validation
  • Bulk operations across multiple endpoint groups simultaneously with agent count verification
  • Policy drift detection to identify configuration inconsistencies across organizational units
  • Baseline validation against corporate security standards with violation reporting
  • Rollback capabilities with automated backup metadata before deployment
  • Template-based cloning for standardized policy creation without manual console configuration
  • Scheduled deployment with maintenance window support for off-hours changes

When to use this:

  • Rolling out new security policies (threat protection, firewall, data control, network quarantine) to production endpoints
  • Standardizing configurations across multiple site locations or business units
  • Auditing policy compliance against baseline security requirements
  • Recovering from failed deployments using rollback metadata and backup policies
  • Creating policy variations using templates without manual console configuration
  • Exporting policies for version control, disaster recovery, or compliance documentation

REQUIREMENTS & ASSUMPTIONS

Prerequisites:

  • PowerShell 5.1 or higher installed on the management workstation
  • SentinelOne management console access with API capabilities enabled
  • API token with Policy Management permissions (Account > Settings > API Tokens in console)
  • Specific required permissions: Policy Read, Policy Write, Group Read, Agent Read
  • Network connectivity to SentinelOne API endpoints (HTTPS/443)
  • Administrator privileges on the local workstation to create directories in C:\BIN\

Assumptions:

  • SentinelOne agents are deployed and reporting to the management console
  • Sites, groups, and accounts are already configured in the SentinelOne console
  • Policy IDs, Group IDs, Site IDs, or Account IDs are known (use -Action List to discover)
  • C:\BIN\ directory exists or script has permissions to create it
  • For scheduled deployments, system remains powered on and connected during maintenance windows
  • Template JSON files follow the SentinelOne policy schema structure (obtained via Export action)
  • Baseline validation files contain only the policy settings to be enforced (not full policy objects)

Security Considerations:

  • Never hardcode API tokens in scripts or commit them to version control systems
  • Store API tokens in environment variables ($env:S1_API_TOKEN, $env:S1_API_URL) or secure credential stores
  • Use service accounts with minimum necessary permissions for automation workflows
  • Review deployment backups in C:\BIN\S1-Policies\rollback\ for audit compliance and forensics
  • Validate policies in dev/test environments before production deployment
  • Always use -DryRun parameter to test deployment parameters before production execution
  • Maintain backup exports of critical policies before making changes
  • Verify rollback files are created successfully after each deployment operation
  • API tokens are logged in debug mode - review log file permissions appropriately

PROCESS

1. Configure API credentials

Set environment variables for SentinelOne API authentication:

# Set API credentials (session-based - expires when PowerShell closes)
$env:S1_API_URL = "https://console.sentinelone.net"
$env:S1_API_TOKEN = "your-api-token-here"
 
# For persistent credentials (recommended for scheduled tasks)
[System.Environment]::SetEnvironmentVariable('S1_API_URL', 'https://console.sentinelone.net', 'User')
[System.Environment]::SetEnvironmentVariable('S1_API_TOKEN', 'your-api-token-here', 'User')

Verify connectivity to the SentinelOne API:

.\Deploy-SentinelOne-Policy.ps1 -Action List -PolicyType All

Expected output: Connection confirmation followed by list of available policies.

2. List available policies

Retrieve all policies or filter by type to identify the policy ID for deployment:

# List all policies
.\Deploy-SentinelOne-Policy.ps1 -Action List -PolicyType All
 
# List only threat protection policies
.\Deploy-SentinelOne-Policy.ps1 -Action List -PolicyType ThreatProtection
 
# Other policy types: DataControl, Firewall, NetworkQuarantine

Output includes: Policy ID, Name, Type, Scope (Site/Account), Creation Date

Note the Policy ID from the output for use in subsequent operations (typically 13-16 digit numeric string).

3. Export existing policy for backup

Before deployment, export current policies for versioning, rollback, and compliance documentation:

# Export specific policy by ID
.\Deploy-SentinelOne-Policy.ps1 -Action Export -PolicyId "1234567890123456" -OutputPath "C:\Backup\S1-Policies"

Output files created:

  • Policy JSON: C:\Backup\S1-Policies\<PolicyName>_<PolicyId>.json
  • Metadata file: C:\Backup\S1-Policies\<PolicyName>_<PolicyId>.json.meta

Metadata includes export date, policy details, executor username, and computer name for audit trails.

4. Test deployment with dry run

Validate deployment parameters without applying changes to production endpoints:

# Dry run deployment to a group
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Deploy `
    -PolicyId "1234567890123456" `
    -TargetScope Group `
    -TargetId "9876543210987654" `
    -DryRun

Verify output shows:

  • Target group name and affected agent count
  • Policy name and ID being deployed
  • "DRY RUN - No changes applied" confirmation
  • No rollback files created (dry run only simulates)

Always review dry run output before proceeding to actual deployment.

5. Deploy policy to target scope

Execute actual deployment to Group, Site, or Account scope:

# Deploy to specific group (most granular control)
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Deploy `
    -PolicyId "1234567890123456" `
    -TargetScope Group `
    -TargetId "9876543210987654"
 
# Deploy to entire site (affects all groups within site)
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Deploy `
    -PolicyId "1234567890123456" `
    -TargetScope Site `
    -TargetId "5555555555555555"
 
# Deploy to account (all sites/groups - broadest impact)
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Deploy `
    -PolicyId "1234567890123456" `
    -TargetScope Account `
    -TargetId "1111111111111111"

Automated actions during deployment:

  1. Validates policy exists and retrieves policy name
  2. Calculates affected agent count across target scope (aggregates all groups)
  3. Creates rollback backup in C:\BIN\S1-Policies\rollback\deployment-\<timestamp\>.json
  4. Applies policy via API (groups/set-policy endpoint with scope filter)
  5. Verifies affected group count matches expectations
  6. Generates deployment report in C:\BIN\S1-Policies\deployment-report-\<timestamp\>.txt

Deployment report includes: Policy details, target scope, affected agents/groups, rollback command.

6. Schedule deployment for maintenance window

Deploy during off-hours using scheduled time parameter:

# Schedule deployment for 2:00 AM maintenance window
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Deploy `
    -PolicyId "1234567890123456" `
    -TargetScope Site `
    -TargetId "5555555555555555" `
    -ScheduleTime "2025-11-27 02:00"

Important notes:

  • The script waits (blocks) until the scheduled time before executing deployment
  • For unattended execution, run in a background PowerShell session or Windows Task Scheduler
  • Keep the PowerShell window open or the scheduled task will terminate
  • For maintenance window validation, consider using the MaintenanceWindow.ps1 helper script

7. Verify deployment and detect drift

Compare policies across groups within a scope to identify configuration drift:

# Check for policy inconsistencies across site
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Compare `
    -TargetScope Site `
    -TargetId "5555555555555555"
 
# Compare across entire account
.\Deploy-SentinelOne-Policy.ps1 `
    -Action Compare `
    -TargetScope Account `
    -TargetId "1111111111111111"

Output includes:

  • List of all unique policies in use within the scope
  • Group assignments for each policy (showing which groups use which policy)
  • Drift detection warning if multiple policies are detected
  • Comparison report saved to: C:\BIN\S1-Policies\policy-comparison-<deployment-id>.json

Drift remediation: If drift detected, standardize by deploying the correct policy to affected groups using step 5.

8. Validate policy against baseline

Ensure deployed policy meets corporate security standards:

.\Deploy-SentinelOne-Policy.ps1 `
    -Action Validate `
    -PolicyId "1234567890123456" `
    -BaselinePath "C:\Standards\corporate-baseline.json"

Baseline file format (JSON with required settings only):

{
  "antiTamperingEnabled": true,
  "agentUiEnabled": false,
  "scanNewAgentFilesEnabled": true,
  "networkQuarantineEnabled": true,
  "exploitProtectionEnabled": true,
  "behavioralIndicatorsEnabled": true
}

Output format: Lists violations with setting name, expected value, and actual value for non-compliant settings.

Remediation: If violations found, clone the policy with corrected settings (step 9) or manually update through console.

9. Clone policy with template modifications

Create policy variations from existing policies using template-based modifications:

.\Deploy-SentinelOne-Policy.ps1 `
    -Action Clone `
    -PolicyId "1234567890123456" `
    -TemplatePath "C:\Templates\strict-policy-mods.json"

Template file contains only the settings to override (merged with source policy):

{
  "name": "High Security Workstations",
  "description": "Enhanced security for production servers",
  "antiTamperingEnabled": true,
  "agentUiEnabled": false,
  "exploitProtectionEnabled": true,
  "behavioralIndicatorsEnabled": true
}

Output: New policy ID and auto-generated name with timestamp suffix (e.g., "Original Policy - Cloned 2025-11-26 1430").

Template processing details:

  • Template properties override source policy settings
  • Read-only fields excluded: id, createdAt, updatedAt, accountId, siteId
  • Timestamp automatically appended to policy name to prevent conflicts
  • Cloned policy created via POST /policies endpoint

10. Rollback failed deployment

Retrieve rollback information and guidance for manual policy restoration:

.\Deploy-SentinelOne-Policy.ps1 `
    -Action Rollback `
    -RollbackId "deployment-20251126-143022"

Rollback output includes:

  • Original deployment details (policy, scope, timestamp, executor)
  • Affected agent count and target name
  • Location of rollback metadata file
  • Guidance for manual policy restoration

Note: The rollback action displays deployment metadata and rollback file location. Manual policy reapplication is required using the backup files in C:\BIN\S1-Policies\ or exported policy JSONs from step 3.

Complete rollback workflow:

  1. Run rollback command to retrieve deployment details and affected scope
  2. Locate previous policy backup JSON from step 3 (exported before deployment)
  3. Use -Action Deploy with the original policy ID to restore previous configuration
  4. Verify restoration using -Action Compare to confirm no drift remains

Important limitation: Current implementation stores deployment metadata but does not capture previous policy state. For true automated rollback, export policies before deployment (step 3 is critical).

11. Review deployment logs and reports

All operations are logged with timestamps, severity levels, and detailed results:

# View deployment log for today
Get-Content "C:\BIN\LOGS-$(Get-Date -Format 'yyyyMMdd')-S1-PolicyDeploy.log"
 
# View last 50 lines of log
Get-Content "C:\BIN\LOGS-$(Get-Date -Format 'yyyyMMdd')-S1-PolicyDeploy.log" -Tail 50
 
# Review deployment report (human-readable summary)
Get-Content "C:\BIN\S1-Policies\deployment-report-deployment-*.txt"
 
# List all deployment reports
Get-ChildItem "C:\BIN\S1-Policies\deployment-report-*.txt" | Sort-Object LastWriteTime -Descending

Log location: C:\BIN\LOGS-\<YYYYMMDD\>-S1-PolicyDeploy.log (date-stamped, new file daily)

Log levels: INFO (standard operations), SUCCESS (completed tasks), WARNING (drift/violations), ERROR (failures), DEBUG (API requests in debug mode)

SCRIPT DETAIL

Core functionality

API Integration:

  • Uses SentinelOne Management API v2.1 with bearer token authentication (ApiToken <token>)
  • All requests handled through Invoke-S1ApiRequest function with centralized error handling and retries
  • API connectivity validation occurs at script startup by retrieving account information
  • Supports filtering by policy type with type mapping: ThreatProtection → threat-protection, DataControl → data-control, Firewall → firewall-control, NetworkQuarantine → network-quarantine
  • API base URL format: https://<console-url>/web/api/v2.1/<endpoint>

Deployment workflow (Deploy-S1Policy function):

  1. Pre-deployment validation: Confirms policy exists via GET /policies/{id} and retrieves metadata
  2. Impact assessment: Calculates affected agent count across all groups in scope using GET /agents?countOnly=true
  3. Target resolution: Retrieves target details (Group/Site/Account name) for deployment report
  4. Backup creation: Writes rollback JSON with deployment metadata to rollback/ directory including deployment ID, timestamp, policy details, scope information, executor username, and computer name
  5. Policy application: Calls POST /groups/set-policy endpoint with scope filter (groupIds, siteIds, or accountIds)
  6. Result verification: Confirms affected group count matches expectations and logs success/failure
  7. Report generation: Creates human-readable deployment report with rollback command and deployment ID

Scope hierarchy and targeting:

  • Group scope: Applies policy directly to a specific group (most granular control, single API call)
  • Site scope: Applies policy to all groups within a site location (retrieves groups via GET /groups?siteIds={id})
  • Account scope: Applies policy to all groups across entire SentinelOne account (widest impact, retrieves groups via GET /groups?accountIds={id})
  • Agent count calculation aggregates across all affected groups within the target scope for impact assessment

Policy comparison logic (Compare-S1Policies function):

  • Retrieves all groups within specified scope using appropriate filter (siteIds or accountIds)
  • Maps groups to their assigned policy IDs by examining group.policy.id property
  • Detects drift when multiple unique policy IDs are found within the same scope
  • Exports policy distribution map as JSON for compliance reporting and audit trails
  • Drift detection flags inconsistencies requiring standardization

Validation against baselines (Test-S1PolicyBaseline function):

  • Loads baseline JSON containing required security settings (partial policy object)
  • Compares each baseline property against current policy configuration using property-by-property comparison
  • Reports violations with setting name, expected value, and actual value for non-compliant settings
  • Returns compliance boolean for automation workflows and integration with CI/CD pipelines
  • Baseline files contain only settings to validate (not full policy objects) for focused compliance checks

Template-based cloning (Clone-S1Policy function):

  • Retrieves source policy via GET /policies/{id} endpoint
  • Overlays template modifications by merging specified properties with source policy
  • Excludes read-only fields during merge: id, createdAt, updatedAt, accountId, siteId
  • Appends timestamp to policy name to prevent naming conflicts (format: "Original Name - Cloned YYYY-MM-DD HHmm")
  • Creates new policy via POST /policies endpoint and returns new policy ID
  • Template files need only contain properties to override (not full policy structure)

Error handling and logging:

  • API connectivity verification before executing any actions (fails fast if credentials invalid)
  • Parameter validation for required fields per action type (throws descriptive errors)
  • Try-catch blocks with detailed error logging including exception messages and stack traces
  • $ErrorActionPreference = 'Stop' ensures all errors are caught and logged appropriately
  • Rollback file creation even on deployment failures for forensic analysis and audit trail
  • All API requests logged in DEBUG level with method, URI, and response status

File structure and outputs:

  • Policies: C:\BIN\S1-Policies\<policy-name>_<policy-id>.json (exported policy configurations)
  • Metadata: C:\BIN\S1-Policies\<policy-name>_<policy-id>.json.meta (export audit trail with timestamp, user, computer)
  • Rollback: C:\BIN\S1-Policies\rollback\deployment-\<timestamp\>.json (deployment metadata for rollback operations)
  • Comparison reports: C:\BIN\S1-Policies\policy-comparison-<deployment-id>.json (drift analysis results)
  • Deployment reports: C:\BIN\S1-Policies\deployment-report-<deployment-id>.txt (human-readable deployment summary)
  • Logs: C:\BIN\LOGS-\<YYYYMMDD\>-S1-PolicyDeploy.log (date-stamped daily log files)

Deployment ID and audit trails:

  • Each script execution generates a unique deployment ID: deployment-<yyyyMMdd-HHmmss>
  • Deployment IDs tie together log files, rollback data, and deployment reports for full traceability
  • Reports include executor username ($env:USERNAME), computer name ($env:COMPUTERNAME), and timestamps for compliance auditing
  • Rollback metadata preserves full deployment context including affected agents, groups, and target details

Security notes

Credential management:

  • API token read from parameter or environment variable $env:S1_API_TOKEN (parameter takes precedence)
  • API tokens never logged in standard INFO/WARNING/SUCCESS/ERROR levels (only DEBUG mode)
  • Tokens not written to deployment reports or rollback files
  • API URL defaults to $env:S1_API_URL for centralized configuration management
  • Supports both session-based environment variables (expire on PowerShell close) and persistent user-level variables

Audit trail and compliance:

  • Every deployment creates rollback metadata with executor username and computer name
  • All actions logged to date-stamped files in C:\BIN\LOGS-* with timestamps and severity levels
  • Deployment reports include rollback commands for operational runbooks and incident response
  • Export metadata tracks who exported policies, when, and from which computer
  • Log files suitable for SIEM integration or compliance reporting

Least privilege principle:

  • Script requires API token with minimum Policy Management permissions only
  • No agent deployment, system modification, or account administration capabilities needed
  • Read-only operations (List, Export, Compare, Validate) require only Policy Read and Group Read permissions
  • Write operations (Deploy, Clone) require Policy Write permissions
  • Recommended practice: Use dedicated service account with minimum necessary permissions for automation

Limitations and considerations

Rollback behavior:

  • Current implementation creates rollback metadata but does not store previous policy state
  • Manual policy reapplication required using exported backups (step 3) or original policy IDs
  • For true automated rollback, export policies before deployment operations
  • Rollback action displays metadata and guidance but does not automatically restore previous configuration
  • Enhancement opportunity: Store previous policy ID per group in rollback file for automated restoration

Scheduled deployments:

  • Script execution blocks (waits) until scheduled time (not true background scheduling)
  • For production automation, use Windows Task Scheduler or run in background PowerShell session using Start-Job
  • Maintenance window validation not included in this script
  • Consider using MaintenanceWindow.ps1 helper script for maintenance window enforcement
  • System must remain powered on and connected during wait period for scheduled deployments

API rate limiting:

  • No built-in rate limiting or throttling for bulk operations
  • For large-scale deployments (100+ groups), consider adding delays between API calls
  • SentinelOne API limits vary by license tier (consult documentation for specific limits)
  • Bulk operations may trigger API rate limiting resulting in HTTP 429 responses
  • Enhancement opportunity: Implement exponential backoff retry logic for rate limit handling

Dry run simulation:

  • Dry run mode calculates affected agents but does not validate actual policy compatibility with endpoints
  • Agent compatibility checks (OS version, agent version) not performed in dry run
  • Dry run does not detect potential conflicts with existing policy configurations
  • Always test in dev/test environment before production deployment even with dry run validation

Policy type support:

  • Supports four policy types: ThreatProtection, DataControl, Firewall, NetworkQuarantine
  • Other SentinelOne policy types may exist but are not explicitly mapped in type filter
  • Use -PolicyType All to list all policies regardless of type
  • Template cloning supports all policy types but template structure must match policy type schema

SCRIPT CONTENTS

<#
.SYNOPSIS
    Deploy and manage SentinelOne security policies across endpoints via API.
 
.DESCRIPTION
    Comprehensive SentinelOne policy management tool supporting:
    - Policy listing, filtering, and export for backup/versioning
    - Policy deployment to sites, groups, or accounts with scope validation
    - Policy cloning with modifications and template-based deployment
    - Baseline validation and drift detection across groups
    - Scheduled deployment with maintenance window support
    - Automatic rollback on deployment failures
    - Detailed logging and deployment reports
 
.PARAMETER Action
    Operation to perform: List, Export, Deploy, Clone, Compare, Validate, Rollback
 
.PARAMETER ApiUrl
    SentinelOne management console URL (e.g., https://console.sentinelone.net)
 
.PARAMETER ApiToken
    API token for authentication. If not provided, reads from environment variable S1_API_TOKEN
 
.PARAMETER PolicyId
    Policy ID for export, deploy, or clone operations
 
.PARAMETER PolicyType
    Filter policies by type: ThreatProtection, DataControl, Firewall, NetworkQuarantine, All
 
.PARAMETER TargetScope
    Deployment scope: Site, Group, Account
 
.PARAMETER TargetId
    Target site ID, group ID, or account ID for deployment
 
.PARAMETER TemplatePath
    Path to JSON policy template file for deployment
 
.PARAMETER OutputPath
    Directory for exported policies and reports (default: C:\BIN\S1-Policies)
 
.PARAMETER ScheduleTime
    Schedule deployment for specific time (format: 'yyyy-MM-dd HH:mm')
 
.PARAMETER DryRun
    Simulate deployment without applying changes
 
.PARAMETER BaselinePath
    Path to baseline policy JSON for validation
 
.PARAMETER RollbackId
    Deployment ID to rollback
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action List -ApiUrl "https://console.sentinelone.net" -PolicyType ThreatProtection
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action Export -PolicyId "1234567890" -OutputPath "C:\Backup\S1"
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action Deploy -PolicyId "1234567890" -TargetScope Group -TargetId "9876543210"
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action Clone -PolicyId "1234567890" -TemplatePath "C:\Templates\strict-policy.json"
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action Compare -TargetScope Account -TargetId "1111111111"
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action Validate -PolicyId "1234567890" -BaselinePath "C:\Baselines\corporate-standard.json"
 
.EXAMPLE
    .\Deploy-SentinelOne-Policy.ps1 -Action Rollback -RollbackId "deployment-20251106-143022"
 
.NOTES
    Requires: PowerShell 5.1+, SentinelOne API token with policy management permissions
    Log Location: C:\BIN\LOGS-\<date\>-S1-PolicyDeploy.log
    API Version: v2.1
#>
 
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidateSet('List', 'Export', 'Deploy', 'Clone', 'Compare', 'Validate', 'Rollback')]
    [string]$Action,
 
    [Parameter(Mandatory = $false)]
    [string]$ApiUrl = $env:S1_API_URL,
 
    [Parameter(Mandatory = $false)]
    [string]$ApiToken = $env:S1_API_TOKEN,
 
    [Parameter(Mandatory = $false)]
    [string]$PolicyId,
 
    [Parameter(Mandatory = $false)]
    [ValidateSet('ThreatProtection', 'DataControl', 'Firewall', 'NetworkQuarantine', 'All')]
    [string]$PolicyType = 'All',
 
    [Parameter(Mandatory = $false)]
    [ValidateSet('Site', 'Group', 'Account')]
    [string]$TargetScope,
 
    [Parameter(Mandatory = $false)]
    [string]$TargetId,
 
    [Parameter(Mandatory = $false)]
    [string]$TemplatePath,
 
    [Parameter(Mandatory = $false)]
    [string]$OutputPath = "C:\BIN\S1-Policies",
 
    [Parameter(Mandatory = $false)]
    [string]$ScheduleTime,
 
    [Parameter(Mandatory = $false)]
    [switch]$DryRun,
 
    [Parameter(Mandatory = $false)]
    [string]$BaselinePath,
 
    [Parameter(Mandatory = $false)]
    [string]$RollbackId
)
 
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
 
# Initialize logging
$timestamp = Get-Date -Format 'yyyyMMdd'
$logPath = "C:\BIN\LOGS-$timestamp-S1-PolicyDeploy.log"
$deploymentId = "deployment-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
 
if (-not (Test-Path "C:\BIN")) {
    New-Item -Path "C:\BIN" -ItemType Directory -Force | Out-Null
}
 
if (-not (Test-Path $OutputPath)) {
    New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
}
 
function Write-Log {
    param([string]$Message, [string]$Level = 'INFO')
    $logMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$Level] $Message"
    Add-Content -Path $logPath -Value $logMessage
 
    switch ($Level) {
        'ERROR' { Write-Host $Message -ForegroundColor Red }
        'WARNING' { Write-Host $Message -ForegroundColor Yellow }
        'SUCCESS' { Write-Host $Message -ForegroundColor Green }
        default { Write-Host $Message }
    }
}
 
function Invoke-S1ApiRequest {
    param(
        [string]$Endpoint,
        [string]$Method = 'GET',
        [object]$Body = $null
    )
 
    try {
        $headers = @{
            'Authorization' = "ApiToken $ApiToken"
            'Content-Type' = 'application/json'
        }
 
        $uri = "$ApiUrl/web/api/v2.1/$Endpoint"
        Write-Log "API Request: $Method $uri" -Level 'DEBUG'
 
        $params = @{
            Uri = $uri
            Method = $Method
            Headers = $headers
        }
 
        if ($Body) {
            $params.Body = ($Body | ConvertTo-Json -Depth 10)
        }
 
        $response = Invoke-RestMethod @params
        return $response
    }
    catch {
        Write-Log "API request failed: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Get-S1Policies {
    param([string]$Type = 'All')
 
    Write-Log "Retrieving policies (Type: $Type)..."
 
    try {
        $endpoint = "policies"
 
        if ($Type -ne 'All') {
            $typeMap = @{
                'ThreatProtection' = 'threat-protection'
                'DataControl' = 'data-control'
                'Firewall' = 'firewall-control'
                'NetworkQuarantine' = 'network-quarantine'
            }
            $endpoint += "?type=$($typeMap[$Type])"
        }
 
        $response = Invoke-S1ApiRequest -Endpoint $endpoint
 
        if ($response.data) {
            Write-Log "Retrieved $($response.data.Count) policies" -Level 'SUCCESS'
            return $response.data
        }
        else {
            Write-Log "No policies found" -Level 'WARNING'
            return @()
        }
    }
    catch {
        Write-Log "Failed to retrieve policies: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Export-S1Policy {
    param(
        [string]$PolicyId,
        [string]$ExportPath
    )
 
    Write-Log "Exporting policy ID: $PolicyId..."
 
    try {
        $policy = Invoke-S1ApiRequest -Endpoint "policies/$PolicyId"
 
        if (-not $policy.data) {
            throw "Policy not found: $PolicyId"
        }
 
        $policyData = $policy.data
        $fileName = "$($policyData.name -replace '[^\w\-]', '_')_$PolicyId.json"
        $filePath = Join-Path $ExportPath $fileName
 
        $policyData | ConvertTo-Json -Depth 10 | Set-Content -Path $filePath
        Write-Log "Policy exported to: $filePath" -Level 'SUCCESS'
 
        # Create backup metadata
        $metadata = @{
            ExportDate = (Get-Date -Format 'o')
            PolicyId = $PolicyId
            PolicyName = $policyData.name
            PolicyType = $policyData.type
            ExportedBy = $env:USERNAME
            ComputerName = $env:COMPUTERNAME
        }
 
        $metadataPath = Join-Path $ExportPath "$fileName.meta"
        $metadata | ConvertTo-Json | Set-Content -Path $metadataPath
 
        return $filePath
    }
    catch {
        Write-Log "Failed to export policy: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Get-S1Groups {
    param([string]$ScopeLevel, [string]$ScopeId)
 
    Write-Log "Retrieving groups for $ScopeLevel : $ScopeId..."
 
    try {
        $endpoint = "groups"
 
        switch ($ScopeLevel) {
            'Site' { $endpoint += "?siteIds=$ScopeId" }
            'Account' { $endpoint += "?accountIds=$ScopeId" }
        }
 
        $response = Invoke-S1ApiRequest -Endpoint $endpoint
        return $response.data
    }
    catch {
        Write-Log "Failed to retrieve groups: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Get-S1AgentCount {
    param([string]$GroupId)
 
    try {
        $endpoint = "agents?groupIds=$GroupId&countOnly=true"
        $response = Invoke-S1ApiRequest -Endpoint $endpoint
        return $response.pagination.totalItems
    }
    catch {
        Write-Log "Failed to get agent count: $($_.Exception.Message)" -Level 'WARNING'
        return 0
    }
}
 
function Deploy-S1Policy {
    param(
        [string]$PolicyId,
        [string]$Scope,
        [string]$ScopeId,
        [bool]$IsDryRun = $false
    )
 
    Write-Log "Deploying policy $PolicyId to $Scope : $ScopeId (DryRun: $IsDryRun)..."
 
    try {
        # Validate policy exists
        $policy = Invoke-S1ApiRequest -Endpoint "policies/$PolicyId"
        if (-not $policy.data) {
            throw "Policy not found: $PolicyId"
        }
 
        $policyName = $policy.data.name
        Write-Log "Policy: $policyName"
 
        # Get affected agents
        $affectedAgents = 0
        $targetName = ""
 
        switch ($Scope) {
            'Group' {
                $group = Invoke-S1ApiRequest -Endpoint "groups/$ScopeId"
                $targetName = $group.data.name
                $affectedAgents = Get-S1AgentCount -GroupId $ScopeId
            }
            'Site' {
                $site = Invoke-S1ApiRequest -Endpoint "sites/$ScopeId"
                $targetName = $site.data.name
                $groups = Get-S1Groups -ScopeLevel 'Site' -ScopeId $ScopeId
                foreach ($grp in $groups) {
                    $affectedAgents += Get-S1AgentCount -GroupId $grp.id
                }
            }
            'Account' {
                $account = Invoke-S1ApiRequest -Endpoint "accounts/$ScopeId"
                $targetName = $account.data.name
                $groups = Get-S1Groups -ScopeLevel 'Account' -ScopeId $ScopeId
                foreach ($grp in $groups) {
                    $affectedAgents += Get-S1AgentCount -GroupId $grp.id
                }
            }
        }
 
        Write-Log "Target: $targetName (Affected agents: $affectedAgents)"
 
        if ($IsDryRun) {
            Write-Log "DRY RUN - No changes applied" -Level 'WARNING'
            return @{
                Success = $true
                DryRun = $true
                PolicyId = $PolicyId
                PolicyName = $policyName
                Scope = $Scope
                ScopeId = $ScopeId
                TargetName = $targetName
                AffectedAgents = $affectedAgents
            }
        }
 
        # Create deployment backup
        $backupPath = Join-Path $OutputPath "rollback"
        if (-not (Test-Path $backupPath)) {
            New-Item -Path $backupPath -ItemType Directory -Force | Out-Null
        }
 
        $rollbackData = @{
            DeploymentId = $deploymentId
            Timestamp = (Get-Date -Format 'o')
            PolicyId = $PolicyId
            PolicyName = $policyName
            Scope = $Scope
            ScopeId = $ScopeId
            TargetName = $targetName
            AffectedAgents = $affectedAgents
            ExecutedBy = $env:USERNAME
        }
 
        $rollbackFile = Join-Path $backupPath "$deploymentId.json"
        $rollbackData | ConvertTo-Json -Depth 10 | Set-Content -Path $rollbackFile
 
        # Apply policy based on scope
        $body = @{
            data = @{
                policyId = $PolicyId
            }
        }
 
        switch ($Scope) {
            'Group' {
                $body.filter = @{ groupIds = @($ScopeId) }
            }
            'Site' {
                $body.filter = @{ siteIds = @($ScopeId) }
            }
            'Account' {
                $body.filter = @{ accountIds = @($ScopeId) }
            }
        }
 
        $response = Invoke-S1ApiRequest -Endpoint "groups/set-policy" -Method 'POST' -Body $body
 
        if ($response.data.affected) {
            Write-Log "Policy deployed successfully to $($response.data.affected) groups" -Level 'SUCCESS'
 
            # Update rollback data with success
            $rollbackData.Success = $true
            $rollbackData.AffectedGroups = $response.data.affected
            $rollbackData | ConvertTo-Json -Depth 10 | Set-Content -Path $rollbackFile
 
            return @{
                Success = $true
                DeploymentId = $deploymentId
                PolicyId = $PolicyId
                PolicyName = $policyName
                Scope = $Scope
                ScopeId = $ScopeId
                TargetName = $targetName
                AffectedAgents = $affectedAgents
                AffectedGroups = $response.data.affected
                RollbackFile = $rollbackFile
            }
        }
        else {
            throw "Policy deployment returned no affected groups"
        }
    }
    catch {
        Write-Log "Failed to deploy policy: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Clone-S1Policy {
    param(
        [string]$SourcePolicyId,
        [string]$TemplateFile
    )
 
    Write-Log "Cloning policy $SourcePolicyId with template modifications..."
 
    try {
        # Get source policy
        $sourcePolicy = Invoke-S1ApiRequest -Endpoint "policies/$SourcePolicyId"
        if (-not $sourcePolicy.data) {
            throw "Source policy not found: $SourcePolicyId"
        }
 
        $policyData = $sourcePolicy.data
 
        # Apply template modifications if provided
        if ($TemplateFile -and (Test-Path $TemplateFile)) {
            Write-Log "Applying template modifications from: $TemplateFile"
            $template = Get-Content $TemplateFile | ConvertFrom-Json
 
            # Merge template settings
            foreach ($property in $template.PSObject.Properties) {
                if ($property.Name -ne 'id' -and $property.Name -ne 'createdAt' -and $property.Name -ne 'updatedAt') {
                    $policyData.$($property.Name) = $property.Value
                }
            }
        }
 
        # Remove read-only fields
        $policyData.PSObject.Properties.Remove('id')
        $policyData.PSObject.Properties.Remove('createdAt')
        $policyData.PSObject.Properties.Remove('updatedAt')
        $policyData.PSObject.Properties.Remove('accountId')
        $policyData.PSObject.Properties.Remove('siteId')
 
        # Set new name
        $policyData.name = "$($policyData.name) - Cloned $(Get-Date -Format 'yyyy-MM-dd HHmm')"
 
        # Create new policy
        $body = @{ data = $policyData }
        $response = Invoke-S1ApiRequest -Endpoint "policies" -Method 'POST' -Body $body
 
        if ($response.data.id) {
            Write-Log "Policy cloned successfully - New ID: $($response.data.id)" -Level 'SUCCESS'
            return $response.data
        }
        else {
            throw "Policy clone failed - no ID returned"
        }
    }
    catch {
        Write-Log "Failed to clone policy: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Compare-S1Policies {
    param([string]$Scope, [string]$ScopeId)
 
    Write-Log "Comparing policies across $Scope : $ScopeId..."
 
    try {
        $groups = Get-S1Groups -ScopeLevel $Scope -ScopeId $ScopeId
        $policyMap = @{}
        $driftDetected = $false
 
        foreach ($group in $groups) {
            if ($group.policy) {
                $policyId = $group.policy.id
                $policyName = $group.policy.name
 
                if (-not $policyMap.ContainsKey($policyId)) {
                    $policyMap[$policyId] = @{
                        Name = $policyName
                        Groups = @()
                    }
                }
 
                $policyMap[$policyId].Groups += $group.name
            }
        }
 
        Write-Log "`nPolicy Distribution:" -Level 'INFO'
        Write-Log ("=" * 80) -Level 'INFO'
 
        foreach ($policyId in $policyMap.Keys) {
            $policyInfo = $policyMap[$policyId]
            Write-Log "Policy: $($policyInfo.Name) (ID: $policyId)" -Level 'INFO'
            Write-Log "  Groups ($($policyInfo.Groups.Count)):" -Level 'INFO'
            foreach ($grp in $policyInfo.Groups) {
                Write-Log "    - $grp" -Level 'INFO'
            }
            Write-Log "" -Level 'INFO'
        }
 
        if ($policyMap.Keys.Count -gt 1) {
            Write-Log "DRIFT DETECTED: $($policyMap.Keys.Count) different policies in use" -Level 'WARNING'
            $driftDetected = $true
        }
        else {
            Write-Log "No policy drift detected - all groups use the same policy" -Level 'SUCCESS'
        }
 
        # Export comparison report
        $reportPath = Join-Path $OutputPath "policy-comparison-$deploymentId.json"
        $policyMap | ConvertTo-Json -Depth 10 | Set-Content -Path $reportPath
        Write-Log "Comparison report saved to: $reportPath" -Level 'SUCCESS'
 
        return @{
            DriftDetected = $driftDetected
            PolicyCount = $policyMap.Keys.Count
            Policies = $policyMap
            ReportPath = $reportPath
        }
    }
    catch {
        Write-Log "Failed to compare policies: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Test-S1PolicyBaseline {
    param(
        [string]$PolicyId,
        [string]$BaselineFile
    )
 
    Write-Log "Validating policy $PolicyId against baseline: $BaselineFile..."
 
    try {
        if (-not (Test-Path $BaselineFile)) {
            throw "Baseline file not found: $BaselineFile"
        }
 
        $baseline = Get-Content $BaselineFile | ConvertFrom-Json
        $policy = Invoke-S1ApiRequest -Endpoint "policies/$PolicyId"
 
        if (-not $policy.data) {
            throw "Policy not found: $PolicyId"
        }
 
        $policyData = $policy.data
        $violations = @()
 
        # Compare baseline requirements
        foreach ($property in $baseline.PSObject.Properties) {
            $baselineValue = $property.Value
            $policyValue = $policyData.$($property.Name)
 
            if ($policyValue -ne $baselineValue) {
                $violations += @{
                    Setting = $property.Name
                    Expected = $baselineValue
                    Actual = $policyValue
                }
            }
        }
 
        if ($violations.Count -eq 0) {
            Write-Log "Policy meets all baseline requirements" -Level 'SUCCESS'
            return @{
                Compliant = $true
                Violations = @()
            }
        }
        else {
            Write-Log "Policy has $($violations.Count) baseline violations:" -Level 'WARNING'
            foreach ($violation in $violations) {
                Write-Log "  - $($violation.Setting): Expected '$($violation.Expected)', Got '$($violation.Actual)'" -Level 'WARNING'
            }
 
            return @{
                Compliant = $false
                Violations = $violations
            }
        }
    }
    catch {
        Write-Log "Failed to validate policy: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function Invoke-S1PolicyRollback {
    param([string]$RollbackDeploymentId)
 
    Write-Log "Initiating rollback for deployment: $RollbackDeploymentId..."
 
    try {
        $rollbackPath = Join-Path $OutputPath "rollback"
        $rollbackFile = Join-Path $rollbackPath "$RollbackDeploymentId.json"
 
        if (-not (Test-Path $rollbackFile)) {
            throw "Rollback data not found: $rollbackFile"
        }
 
        $rollbackData = Get-Content $rollbackFile | ConvertFrom-Json
 
        Write-Log "Original deployment details:"
        Write-Log "  Policy: $($rollbackData.PolicyName) ($($rollbackData.PolicyId))"
        Write-Log "  Scope: $($rollbackData.Scope) - $($rollbackData.TargetName)"
        Write-Log "  Deployed: $($rollbackData.Timestamp)"
        Write-Log "  Affected agents: $($rollbackData.AffectedAgents)"
 
        # Note: Actual rollback would require storing previous policy state
        # For now, this creates an audit trail and displays rollback information
        Write-Log "Rollback requires manual policy reapplication or restore from backup" -Level 'WARNING'
        Write-Log "Check backup policies in: $OutputPath" -Level 'INFO'
 
        return @{
            Success = $true
            RollbackData = $rollbackData
            Message = "Rollback information retrieved - manual policy restore required"
        }
    }
    catch {
        Write-Log "Failed to rollback deployment: $($_.Exception.Message)" -Level 'ERROR'
        throw
    }
}
 
function New-DeploymentReport {
    param([object]$DeploymentResult)
 
    $reportPath = Join-Path $OutputPath "deployment-report-$deploymentId.txt"
 
    $report = @"
SentinelOne Policy Deployment Report
=====================================
Deployment ID: $deploymentId
Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Executed By: $env:USERNAME
Computer: $env:COMPUTERNAME
 
Policy Details:
  - Policy ID: $($DeploymentResult.PolicyId)
  - Policy Name: $($DeploymentResult.PolicyName)
 
Target Details:
  - Scope: $($DeploymentResult.Scope)
  - Target ID: $($DeploymentResult.ScopeId)
  - Target Name: $($DeploymentResult.TargetName)
 
Deployment Results:
  - Status: $(if ($DeploymentResult.Success) { 'SUCCESS' } else { 'FAILED' })
  - Affected Agents: $($DeploymentResult.AffectedAgents)
  - Affected Groups: $($DeploymentResult.AffectedGroups)
  - Dry Run: $(if ($DeploymentResult.DryRun) { 'Yes' } else { 'No' })
 
Rollback Information:
  - Rollback File: $($DeploymentResult.RollbackFile)
  - Rollback Command: .\Deploy-SentinelOne-Policy.ps1 -Action Rollback -RollbackId $deploymentId
 
Log File: $logPath
=====================================
"@
 
    $report | Set-Content -Path $reportPath
    Write-Log "Deployment report saved to: $reportPath" -Level 'SUCCESS'
 
    return $reportPath
}
 
# Main execution
try {
    Write-Log "Starting SentinelOne Policy Manager - Action: $Action"
    Write-Log "Deployment ID: $deploymentId"
 
    # Validate credentials
    if (-not $ApiUrl -or -not $ApiToken) {
        throw "API URL and API Token are required. Set via parameters or environment variables S1_API_URL and S1_API_TOKEN"
    }
 
    # Verify API connectivity
    try {
        $accountInfo = Invoke-S1ApiRequest -Endpoint "accounts"
        Write-Log "Connected to SentinelOne: $($accountInfo.data[0].name)" -Level 'SUCCESS'
    }
    catch {
        throw "Failed to connect to SentinelOne API. Verify ApiUrl and ApiToken are correct."
    }
 
    # Check for scheduled deployment
    if ($ScheduleTime -and $Action -eq 'Deploy') {
        $scheduledDateTime = [DateTime]::ParseExact($ScheduleTime, 'yyyy-MM-dd HH:mm', $null)
        $waitSeconds = ($scheduledDateTime - (Get-Date)).TotalSeconds
 
        if ($waitSeconds -gt 0) {
            Write-Log "Deployment scheduled for: $ScheduleTime" -Level 'WARNING'
            Write-Log "Waiting $([Math]::Round($waitSeconds / 60, 2)) minutes..."
            Start-Sleep -Seconds $waitSeconds
        }
    }
 
    # Execute action
    switch ($Action) {
        'List' {
            $policies = Get-S1Policies -Type $PolicyType
 
            Write-Log "`nAvailable Policies:" -Level 'INFO'
            Write-Log ("=" * 80) -Level 'INFO'
 
            foreach ($policy in $policies) {
                Write-Log "ID: $($policy.id)" -Level 'INFO'
                Write-Log "  Name: $($policy.name)" -Level 'INFO'
                Write-Log "  Type: $($policy.type)" -Level 'INFO'
                Write-Log "  Scope: $($policy.scopeName)" -Level 'INFO'
                Write-Log "  Created: $($policy.createdAt)" -Level 'INFO'
                Write-Log "" -Level 'INFO'
            }
 
            Write-Log "Total policies: $($policies.Count)" -Level 'SUCCESS'
        }
 
        'Export' {
            if (-not $PolicyId) {
                throw "PolicyId parameter is required for Export action"
            }
 
            $exportedFile = Export-S1Policy -PolicyId $PolicyId -ExportPath $OutputPath
            Write-Log "Policy exported successfully to: $exportedFile" -Level 'SUCCESS'
        }
 
        'Deploy' {
            if (-not $PolicyId -or -not $TargetScope -or -not $TargetId) {
                throw "PolicyId, TargetScope, and TargetId parameters are required for Deploy action"
            }
 
            $result = Deploy-S1Policy -PolicyId $PolicyId -Scope $TargetScope -ScopeId $TargetId -IsDryRun $DryRun.IsPresent
 
            if (-not $DryRun.IsPresent) {
                $reportPath = New-DeploymentReport -DeploymentResult $result
                Write-Log "`nDeployment report: $reportPath" -Level 'SUCCESS'
            }
        }
 
        'Clone' {
            if (-not $PolicyId) {
                throw "PolicyId parameter is required for Clone action"
            }
 
            $clonedPolicy = Clone-S1Policy -SourcePolicyId $PolicyId -TemplateFile $TemplatePath
            Write-Log "Cloned policy ID: $($clonedPolicy.id)" -Level 'SUCCESS'
            Write-Log "Cloned policy name: $($clonedPolicy.name)" -Level 'SUCCESS'
        }
 
        'Compare' {
            if (-not $TargetScope -or -not $TargetId) {
                throw "TargetScope and TargetId parameters are required for Compare action"
            }
 
            $comparison = Compare-S1Policies -Scope $TargetScope -ScopeId $TargetId
 
            if ($comparison.DriftDetected) {
                Write-Log "Policy drift analysis complete - see report for details" -Level 'WARNING'
            }
            else {
                Write-Log "Policy comparison complete - no drift detected" -Level 'SUCCESS'
            }
        }
 
        'Validate' {
            if (-not $PolicyId -or -not $BaselinePath) {
                throw "PolicyId and BaselinePath parameters are required for Validate action"
            }
 
            $validation = Test-S1PolicyBaseline -PolicyId $PolicyId -BaselineFile $BaselinePath
 
            if ($validation.Compliant) {
                Write-Log "Policy validation successful - compliant with baseline" -Level 'SUCCESS'
            }
            else {
                Write-Log "Policy validation failed - $($validation.Violations.Count) violations found" -Level 'ERROR'
            }
        }
 
        'Rollback' {
            if (-not $RollbackId) {
                throw "RollbackId parameter is required for Rollback action"
            }
 
            $rollback = Invoke-S1PolicyRollback -RollbackDeploymentId $RollbackId
            Write-Log $rollback.Message -Level 'WARNING'
        }
    }
 
    Write-Log "Operation completed successfully" -Level 'SUCCESS'
    Write-Log "Log file: $logPath"
}
catch {
    Write-Log "Script execution failed: $($_.Exception.Message)" -Level 'ERROR'
    Write-Log "Stack trace: $($_.ScriptStackTrace)" -Level 'ERROR'
    exit 1
}

Related Reading

  • SentinelOne Control vs Complete Feature Comparison
  • SentinelOne Deep Visibility Threat Hunting
  • SentinelOne Forensics Rollback and Remediation
#sentinelone#edr#Security#threat-hunting#deployment#policy#automation#forensics#api#detection-rules#firewall

Related Articles

SentinelOne Control vs Complete Feature Comparison

This document provides a comprehensive comparison between SentinelOne Singularity Control and Singularity Complete SKUs to help MSP teams understand the...

17 min read

SentinelOne Deep Visibility Threat Hunting

Deep Visibility is SentinelOne's EDR telemetry engine that provides comprehensive endpoint data collection for threat hunting, incident investigation, and...

22 min read

SentinelOne Forensics Rollback and Remediation

This document provides comprehensive procedures for forensic evidence collection, ransomware rollback, and threat remediation using SentinelOne Complete...

39 min read
Back to all HOWTOs