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 Listto 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
-DryRunparameter 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 AllExpected 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, NetworkQuarantineOutput 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" `
-DryRunVerify 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:
- Validates policy exists and retrieves policy name
- Calculates affected agent count across target scope (aggregates all groups)
- Creates rollback backup in
C:\BIN\S1-Policies\rollback\deployment-\<timestamp\>.json - Applies policy via API (
groups/set-policyendpoint with scope filter) - Verifies affected group count matches expectations
- 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.ps1helper 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 /policiesendpoint
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:
- Run rollback command to retrieve deployment details and affected scope
- Locate previous policy backup JSON from step 3 (exported before deployment)
- Use
-Action Deploywith the original policy ID to restore previous configuration - Verify restoration using
-Action Compareto 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 -DescendingLog 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-S1ApiRequestfunction 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):
- Pre-deployment validation: Confirms policy exists via
GET /policies/{id}and retrieves metadata - Impact assessment: Calculates affected agent count across all groups in scope using
GET /agents?countOnly=true - Target resolution: Retrieves target details (Group/Site/Account name) for deployment report
- Backup creation: Writes rollback JSON with deployment metadata to
rollback/directory including deployment ID, timestamp, policy details, scope information, executor username, and computer name - Policy application: Calls
POST /groups/set-policyendpoint with scope filter (groupIds, siteIds, or accountIds) - Result verification: Confirms affected group count matches expectations and logs success/failure
- 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.idproperty - 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 /policiesendpoint 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_URLfor 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.ps1helper 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 Allto 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
}