Least-privilege Active Directory setup
By default the SlashID Collector works with Domain Admins credentials, which grant more access than required.
For production workloads it is advised to create dedicated, least-privilege accounts using the scripts on this page.
Prerequisites
- A domain-joined Windows machine with the Active Directory PowerShell module installed
- A user account with permissions to create AD users, groups, and set ACLs
AD Snapshot mode
AD Snapshot mode performs periodic LDAP snapshots of Active Directory objects (users, groups, computers, etc.).
The script below creates:
- A service account
svc-slashid-reader - A security group
SlashID-Readers - Read-only AD permissions (ReadProperty, ListChildren, ReadControl, ListObject) on the domain root, inherited by all descendant objects
Where to run: any domain-joined machine with the AD PowerShell module.
# This script creates a dedicated service account and security group for SlashID
# Must be run from a domain-joined machine with the AD PowerShell module installed
# Run as a user with sufficient domain permissions to create users/groups and set permissions
$ErrorActionPreference = "Stop"
Import-Module ActiveDirectory
$DomainName = (Get-ADDomain).DistinguishedName
$ServiceAccountName = "svc-slashid-reader"
$GroupName = "SlashID-Readers"
$UserPrincipalName = "$ServiceAccountName@$((Get-ADDomain).DNSRoot)"
function Generate-Password {
$UpperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$LowerChars = 'abcdefghijklmnopqrstuvwxyz'
$Numbers = '0123456789'
$SpecialChars = '!@#$%^&*()_-+=[]{}|;:,.<>?'
$AllChars = $UpperChars + $LowerChars + $Numbers + $SpecialChars
do {
$Password = -join (1..32 | ForEach-Object { $AllChars[(Get-Random -Minimum 0 -Maximum $AllChars.Length)] })
# Check if it meets complexity requirements
$HasUpper = $Password -cmatch '[A-Z]'
$HasLower = $Password -cmatch '[a-z]'
$HasNumber = $Password -cmatch '[0-9]'
$HasSpecial = $Password -cmatch '[^a-zA-Z0-9]'
} until ($HasUpper -and $HasLower -and $HasNumber -and $HasSpecial)
return $Password
}
$ServiceAccountPassword = Generate-Password
$securePassword = ConvertTo-SecureString -String $ServiceAccountPassword -AsPlainText -Force
# Create service account
New-ADUser -Name $ServiceAccountName `
-GivenName "SlashID" `
-Surname "Reader" `
-SamAccountName $ServiceAccountName `
-UserPrincipalName $UserPrincipalName `
-Enabled $true `
-PasswordNeverExpires $true `
-AccountPassword $securePassword `
-Description "Service account for SlashID AD integration"
Write-Host "Created service account $ServiceAccountName" -ForegroundColor Green
# Create security group
New-ADGroup -Name $GroupName `
-SamAccountName $GroupName `
-GroupCategory Security `
-GroupScope DomainLocal `
-DisplayName $GroupName `
-Description "Group for SlashID AD integration (read-only)"
Write-Host "Created security group $GroupName" -ForegroundColor Green
Add-ADGroupMember -Identity $GroupName -Members $ServiceAccountName
Write-Host "Added $ServiceAccountName to $GroupName" -ForegroundColor Green
# Set permissions
$GroupSID = (Get-ADGroup $GroupName).SID
$ACL = Get-ACL "AD:$DomainName"
$ReadPropGUID = [GUID]"00000000-0000-0000-0000-000000000000" # All properties
$PropType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$GroupSID,
[System.DirectoryServices.ActiveDirectoryRights]::ReadProperty,
[System.Security.AccessControl.AccessControlType]::Allow,
$ReadPropGUID,
$PropType
)
$ACL.AddAccessRule($ACE)
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$GroupSID,
[System.DirectoryServices.ActiveDirectoryRights]::ListChildren,
[System.Security.AccessControl.AccessControlType]::Allow
)
$ACL.AddAccessRule($ACE)
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$GroupSID,
[System.DirectoryServices.ActiveDirectoryRights]::ReadControl,
[System.Security.AccessControl.AccessControlType]::Allow
)
$ACL.AddAccessRule($ACE)
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$GroupSID,
[System.DirectoryServices.ActiveDirectoryRights]::ListObject,
[System.Security.AccessControl.AccessControlType]::Allow,
$ReadPropGUID,
$PropType
)
$ACL.AddAccessRule($ACE)
Set-ACL -AclObject $ACL -Path "AD:$DomainName"
Write-Host "Permissions successfully applied for $GroupName on $DomainName" -ForegroundColor Green
Write-Host "`n============================= ACCOUNT INFORMATION ==============================" -ForegroundColor Yellow
Write-Host "Username: $ServiceAccountName" -ForegroundColor Cyan
Write-Host "Password: $ServiceAccountPassword" -ForegroundColor Cyan
Write-Host "Domain: $((Get-ADDomain).DNSRoot)" -ForegroundColor Cyan
Write-Host "================================================================================" -ForegroundColor Yellow
Write-Host "`nIMPORTANT: Password will not be displayed again" -ForegroundColor Red
After running the script, note the username, password, and domain printed at the end — these are the credentials you will use in the collector configuration. The password will not be displayed again.
WMI Streaming mode
WMI Streaming mode provides real-time event streaming from domain controller Security event logs via WMI (Windows Management Instrumentation).
The script below creates:
- A service account
svc-sid-wmi-reader - A security group
SlashID-WMI-Readers - Membership in Event Log Readers (Security event log access) and Distributed COM Users (machine-wide DCOM permissions)
- WMI namespace permissions on
Root\CIMV2(Enable Account, Execute Methods, Remote Enable, Read Security) - DCOM application-level Launch/Activation permissions for the WMI application
- Windows Firewall rules to allow WMI traffic
Where to run: directly on each Domain Controller that you want to stream events from. The script modifies local firewall rules, WMI namespace ACLs, and DCOM registry settings on the machine where it runs.
# Must be run on the Domain Controller with the AD PowerShell module installed
# Run as a user with sufficient domain permissions to create users/groups and set permissions
$ErrorActionPreference = "Stop"
Import-Module ActiveDirectory
$DomainName = (Get-ADDomain).DistinguishedName
$ServiceAccountName = "svc-sid-wmi-reader"
$GroupName = "SlashID-WMI-Readers"
$UserPrincipalName = "$ServiceAccountName@$((Get-ADDomain).DNSRoot)"
function Generate-Password {
$UpperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$LowerChars = 'abcdefghijklmnopqrstuvwxyz'
$Numbers = '0123456789'
$SpecialChars = '!@#$%^&*()_-+=[]{}|;:,.<>?'
$AllChars = $UpperChars + $LowerChars + $Numbers + $SpecialChars
do {
$Password = -join (1..32 | ForEach-Object { $AllChars[(Get-Random -Minimum 0 -Maximum $AllChars.Length)] })
$HasUpper = $Password -cmatch '[A-Z]'
$HasLower = $Password -cmatch '[a-z]'
$HasNumber = $Password -cmatch '[0-9]'
$HasSpecial = $Password -cmatch '[^a-zA-Z0-9]'
} until ($HasUpper -and $HasLower -and $HasNumber -and $HasSpecial)
return $Password
}
# Create service account (domain-wide, skipped if already exists from a previous DC run)
$createdNewAccount = $false
if (-not (Get-ADUser -Filter "SamAccountName -eq '$ServiceAccountName'" -ErrorAction SilentlyContinue)) {
$ServiceAccountPassword = Generate-Password
$securePassword = ConvertTo-SecureString -String $ServiceAccountPassword -AsPlainText -Force
New-ADUser -Name $ServiceAccountName `
-GivenName "SlashID" `
-Surname "Logger" `
-SamAccountName $ServiceAccountName `
-UserPrincipalName $UserPrincipalName `
-Enabled $true `
-PasswordNeverExpires $true `
-AccountPassword $securePassword `
-Description "Service account for SlashID AD integration"
$createdNewAccount = $true
Write-Host "Created service account $ServiceAccountName" -ForegroundColor Green
} else {
Write-Host "Service account $ServiceAccountName already exists, skipping creation" -ForegroundColor Yellow
}
# Create security group (domain-wide, skipped if already exists from a previous DC run)
if (-not (Get-ADGroup -Filter "SamAccountName -eq '$GroupName'" -ErrorAction SilentlyContinue)) {
New-ADGroup -Name $GroupName `
-SamAccountName $GroupName `
-GroupCategory Security `
-GroupScope DomainLocal `
-DisplayName $GroupName `
-Description "Group for SlashID AD WMI logging (read-only)"
Write-Host "Created security group $GroupName" -ForegroundColor Green
} else {
Write-Host "Security group $GroupName already exists, skipping creation" -ForegroundColor Yellow
}
# Update group memberships (least privilege - only what's needed for WMI event log reading)
# These are domain-wide but safe to re-run: Add-ADGroupMember is a no-op if already a member.
$memberships = @(
@{ Group = $GroupName; Member = $ServiceAccountName; Desc = "group $GroupName" },
@{ Group = "Event Log Readers"; Member = $ServiceAccountName; Desc = "Event Log Readers (Security event log read access)" },
@{ Group = "Distributed COM Users"; Member = $ServiceAccountName; Desc = "Distributed COM Users (machine-wide DCOM remote launch/activation/access)" }
)
foreach ($m in $memberships) {
$members = Get-ADGroupMember -Identity $m.Group | Select-Object -ExpandProperty SamAccountName
if ($members -contains $m.Member) {
Write-Host "$ServiceAccountName is already a member of $($m.Desc), skipping" -ForegroundColor Yellow
} else {
Add-ADGroupMember -Identity $m.Group -Members $m.Member
Write-Host "Added $ServiceAccountName to $($m.Desc)" -ForegroundColor Green
}
}
# Allow WMI traffic
Enable-NetFirewallRule -DisplayGroup "Windows Management Instrumentation (WMI)"
Write-Host "Allowed WMI traffic in Windows Firewall" -ForegroundColor Green
# Add security group to Security Descriptor of Root/CIMV2 namespace
$Namespace = "Root\CIMV2"
$GroupSID = (Get-ADGroup $GroupName).SID
# WMI namespace permission flags:
# WBEM_ENABLE = 0x1 (Enable Account)
# WBEM_METHOD_EXECUTE = 0x2 (Execute Methods)
# WBEM_REMOTE_ACCESS = 0x20 (Remote Enable)
# READ_CONTROL = 0x20000 (Read Security)
$Permissions = 0x1 -bor 0x2 -bor 0x20 -bor 0x20000
$Security = Get-WmiObject -Namespace $Namespace -Class __SystemSecurity
$BinarySD = $(Invoke-WmiMethod -InputObject $Security -Name GetSD).SD
$sd = New-Object System.Security.AccessControl.RawSecurityDescriptor($BinarySD, 0)
$sid = New-Object System.Security.Principal.SecurityIdentifier($GroupSID.Value)
if (-not $sd.DiscretionaryAcl) {
Write-Error "WMI namespace $Namespace has no DACL. Set WMI permissions manually via wmimgmt.msc."
return
}
# Check if sufficient permissions are already granted
$alreadyPresent = $false
foreach ($ace in $sd.DiscretionaryAcl) {
if ($ace.SecurityIdentifier -eq $sid -and
$ace.AceQualifier -eq [System.Security.AccessControl.AceQualifier]::AccessAllowed -and
($ace.AccessMask -band $Permissions) -eq $Permissions) {
$alreadyPresent = $true
break
}
}
if ($alreadyPresent) {
Write-Host "WMI permissions for $GroupName on namespace $Namespace already exist, skipping" -ForegroundColor Yellow
} else {
$newAce = New-Object System.Security.AccessControl.CommonAce(
[System.Security.AccessControl.AceFlags]::None,
[System.Security.AccessControl.AceQualifier]::AccessAllowed,
$Permissions,
$sid,
$false,
$null
)
# Insert at position 0 to match the original prepend behavior
$sd.DiscretionaryAcl.InsertAce(0, $newAce)
$NewBinarySD = New-Object byte[] $sd.BinaryLength
$sd.GetBinaryForm($NewBinarySD, 0)
$inParams = $Security.GetMethodParameters("SetSD")
$inParams["SD"] = [byte[]]$NewBinarySD
$result = $Security.InvokeMethod("SetSD", $inParams, $null)
if ($result["ReturnValue"] -eq 0) {
Write-Host "Set WMI permissions for $GroupName on namespace $Namespace" -ForegroundColor Green
} else {
Write-Error "Failed to set WMI permissions. Error code: $($result["ReturnValue"])"
return
}
}
# Set DCOM application permissions for WMI (Windows Management Instrumentation)
# This ensures Remote Launch and Remote Activation work even if the WMI DCOM app
# has custom security that overrides machine-wide defaults from Distributed COM Users.
$WmiAppId = "{8BC3F05E-D86B-11D0-A075-00C04FB68820}"
$DcomRegPath = "HKLM:\SOFTWARE\Classes\AppID\$WmiAppId"
# DCOM permission constants
$COM_RIGHTS_EXECUTE = 1
$COM_RIGHTS_EXECUTE_REMOTE = 4
$COM_RIGHTS_ACTIVATE_REMOTE = 16
$RemoteLaunchActivate = $COM_RIGHTS_EXECUTE -bor $COM_RIGHTS_EXECUTE_REMOTE -bor $COM_RIGHTS_ACTIVATE_REMOTE
# Get current LaunchPermission (binary SD) or fall back to machine default
$currentLP = (Get-ItemProperty -Path $DcomRegPath -Name "LaunchPermission" -ErrorAction SilentlyContinue).LaunchPermission
if (-not $currentLP) {
$currentLP = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Ole" -Name "DefaultLaunchPermission" -ErrorAction SilentlyContinue).DefaultLaunchPermission
}
if ($currentLP) {
$sd = New-Object System.Security.AccessControl.RawSecurityDescriptor($currentLP, 0)
$sid = New-Object System.Security.Principal.SecurityIdentifier($GroupSID.Value)
# Check if the required permissions are already granted by an existing ACE
$permissionsSufficient = $false
foreach ($ace in $sd.DiscretionaryAcl) {
if ($ace.SecurityIdentifier -eq $sid -and $ace.AceQualifier -eq [System.Security.AccessControl.AceQualifier]::AccessAllowed) {
if (($ace.AccessMask -band $RemoteLaunchActivate) -eq $RemoteLaunchActivate) {
$permissionsSufficient = $true
break
}
}
}
if (-not $permissionsSufficient) {
$newAce = New-Object System.Security.AccessControl.CommonAce(
[System.Security.AccessControl.AceFlags]::None,
[System.Security.AccessControl.AceQualifier]::AccessAllowed,
$RemoteLaunchActivate,
$sid,
$false,
$null
)
$sd.DiscretionaryAcl.InsertAce($sd.DiscretionaryAcl.Count, $newAce)
$newBinaryLP = New-Object byte[] $sd.BinaryLength
$sd.GetBinaryForm($newBinaryLP, 0)
Set-ItemProperty -Path $DcomRegPath -Name "LaunchPermission" -Value $newBinaryLP
Write-Host "Set DCOM Launch/Activation permissions for $GroupName on WMI application" -ForegroundColor Green
} else {
Write-Host "DCOM Launch/Activation permissions are already sufficient for $GroupName" -ForegroundColor Yellow
}
} else {
Write-Warning "Could not read DCOM LaunchPermission. Set DCOM permissions manually via dcomcnfg."
}
if ($createdNewAccount) {
Write-Host "`n============================= ACCOUNT INFORMATION ==============================" -ForegroundColor Yellow
Write-Host "Username: $ServiceAccountName" -ForegroundColor Cyan
Write-Host "Password: $ServiceAccountPassword" -ForegroundColor Cyan
Write-Host "Domain: $((Get-ADDomain).DNSRoot)" -ForegroundColor Cyan
Write-Host "================================================================================" -ForegroundColor Yellow
Write-Host "`nIMPORTANT: Password will not be displayed again" -ForegroundColor Red
} else {
Write-Host "`nLocal permissions configured. Service account $ServiceAccountName was already created on a previous run." -ForegroundColor Cyan
}
After running the script, note the username, password, and domain printed at the end. The password will not be displayed again.
After running the WMI setup script, you must restart the WMI service for the namespace permission changes to take effect:
Restart-Service WinMgmt -Force
This restarts WMI and all dependent services. Schedule this during a maintenance window to avoid disruption.
Using the credentials
Once you have created the service accounts, use them in your collector configuration instead of Domain Admin credentials:
- For AD Snapshot mode, set
AD_SNAPSHOT_N_USERNAMEandAD_SNAPSHOT_N_PASSWORDto thesvc-slashid-readercredentials - For WMI Streaming mode, set
WMI_N_USERNAMEandWMI_N_PASSWORDto thesvc-slashid-wmi-readercredentials
Refer to your deployment guide (On-premises, Google Cloud Run, Azure, or AWS CDK) for the full list of environment variables.