Skip to main content

PingCastle Enterprise Installation and Configuration

Description

PingCastle Enterprise is a tool designed to improve and follow the Active Directory overall security level. This software has been developed to be compatible with most of the possible existing configurations. The goal is to provide reliable data to present the situation to the management, enabling continuous improvement over time.

Requirements

System Specifications

PingCastle Enterprise requires:

  • Windows Server operating systems that support ASP.NET 8.0
    • Windows Server 2012 R2
    • Windows Server 2016
    • Windows Server 2019
    • Windows Server 2022 (recommended)
    • Windows Server 2025 (recommended)

For more information on Windows Server support lifecycles, refer to the Windows Lifecycle Fact Sheet.

For ASP.NET 8.0 operating system compatibility, refer to the .NET 8.0 supported OS documentation.

PingCastle.exe

The PingCastle.exe scanner has an embedded ASP.NET package and can run on any Windows operating system without additional dependencies.

Database

PingCastle Enterprise requires a Microsoft SQL Server database to store its data.

Supported database editions:

  • SQL Server Express: Suitable for testing and lite user environments
  • SQL Server Standard: Recommended for production environments as needed
  • SQL Server Enterprise: Supported for high-availability production environments

PingCastle Enterprise uses Entity Framework Core 2 for database operations. Partial support for PostgreSQL is provided on a best effort basis at this stage. Other database engines are not specifically supported.

External System Dependencies

PingCastle Enterprise requires:

  • ASP.NET 8.0 Hosting Bundle: Required for hosting the web application
  • IIS (Internet Information Services): Used as the web server with Windows Authentication enabled by default
info

The ASP.NET 8.0 Hosting Bundle should be installed before configuring IIS to ensure proper module registration. If installed in the wrong order, run a repair on the ASP.NET 8.0 Hosting Bundle to resolve any issues.

Logon Providers

PingCastle Enterprise supports multiple authentication methods:

  • Local Authentication: Built-in username and password authentication
  • Windows Authentication: Integrated Windows authentication using Active Directory credentials
  • OpenID Connect: Standards-based authentication with identity providers such as Entra ID, Okta, and others
  • SAML2: SAML 2.0 federation for enterprise single sign-on solutions

License

Licenses are issued per user with soft limits on domain and user counts. Contact your sales representative for details on the licensing structure.

Architecture

PingCastle Enterprise uses a distributed architecture where the scanner (PingCastle.exe) performs Active Directory assessments and sends reports to the central Enterprise server for analysis, storage, and visualization.

Architecture Overview

Key Components

PingCastle Enterprise Server

  • Hosted on IIS with Windows Authentication
  • Requires SQL Server database for data storage
  • Accessible via HTTP/HTTPS (ports 80/443)
  • Provides web interface for administrators and users
  • Built-in scheduler that utilized Windows Task Scheduler for automated scanning of local and trusted domains

PingCastle.exe Scanner

  • Standalone executable with embedded .NET runtime
  • Performs Active Directory security assessments
  • Generates reports in XML and HTML formats
  • Can run on any Windows system
  • Requires standard Active Directory ports (389, 636, 88, 9389, 53)

Report Upload Methods

API Upload (Connected Domains)

  • PingCastle.exe connects directly to Enterprise server via HTTPS (port 443)
  • Automated upload after scan completion
  • Requires API key configuration
  • Real-time data synchronization

Manual Transfer (Disconnected Domains)

  • Export XML reports from isolated environments
  • Transfer via USB drive, email, or secure file transfer
  • Import through Enterprise web interface
  • Suitable for air-gapped or highly secure networks

Network Ports

PingCastle Enterprise Server
ServicePortProtocolNotes
HTTP80TCPOptional, typically redirected to HTTPS
HTTPS443TCPRecommended
Active Directory Scanning
ServicePort(s)ProtocolNotes
LDAP389TCP/UDPLDAP - Fallback when ADWS is not present. Less performant
LDAPS636TCPChecks for LDAPS
Also you can run the entire scan with LDAPS using -port 636 in the command line
Kerberos88TCP/UDP
DNS53TCP/UDP
SMB445TCP
ADWS9389TCPActive Directory Web Services for performant scans

Quick Installation

Follow these steps for a production-ready installation of PingCastle Enterprise.

Prerequisites

  1. Download the PingCastle Enterprise MSI Installer
  2. Windows Server (see Requirements section)
  3. SQL Server (Express, Standard, or Enterprise)

Installation Steps

Step 1: Install IIS with Windows Authentication

Install the IIS Web Server Role with Windows Authentication feature:

dism /online /enable-feature /featurename:IIS-WebServerRole /featurename:IIS-WebServerManagementTools /featurename:IIS-ManagementConsole /featurename:IIS-WindowsAuthentication

Step 2: Install ASP.NET 8 Hosting Bundle

Download and install the ASP.NET 8 Hosting Bundle.

warning

IIS must be installed before the ASP.NET 8.0 Hosting Bundle. If installed in the wrong order, repair the Hosting Bundle installation to ensure proper module registration.

Step 3: Install SQL Server

Install SQL Server (Express, Standard, or Enterprise edition) based on your needs. See the Database section for guidance on which edition to choose.

For SQL Express, visit SQL Server Express Downloads.

Step 4: Run the MSI Installer

  1. Launch the PingCastle Enterprise MSI installer
  2. Accept the license terms
  3. Enter your license key (provided by PingCastle support or licensing teams)
info

If the license key is missing, contact PingCastle support or your account manager.

Step 5: Configure Database Connection

During installation, choose one of two database configuration options:

Option A: Let the installer create the database

  • Provide SQL Server connection details
  • Installer creates the database and grants permissions automatically

Option B: Provide a custom connection string

  • Use an existing database
  • Provide the complete connection string

The installer will automatically configure IIS, create the application pool, and set up database permissions.

note

When the software is uninstalled, the database is not automatically removed.

Remote SQL Server Setup

If you're configuring a remote SQL Server (not on the local machine), see the Remote Database Configuration section for detailed setup instructions including SQL Authentication and Windows Authentication options.

Post Installation - Common Steps

IIS Maximum Upload Configuration

The default IIS upload limit may need to be increased to accommodate large report files. While there are multiple methods to configure this setting (web.config, IIS Manager), the simplest approach is using PowerShell:

Import-Module WebAdministration

$siteName = "PingCastleEnterprise"
$newLimit = 1GB # Byte value - PowerShell converts this automatically

Set-WebConfigurationProperty `
-PSPath "IIS:\Sites\$siteName" `
-Filter "system.webServer/security/requestFiltering/requestLimits" `
-Name "maxAllowedContentLength" `
-Value $newLimit
info

For more information on alternative configuration methods, search for "maxAllowedContentLength" in IIS documentation.

Manual Report Import Size Limit

If you need to import reports larger than 200MB manually, you'll need to adjust the client-side file size limit:

  1. Open the JavaScript configuration file at:

    C:\Program Files\Netwrix\PingCastleEnterprise\wwwroot\js\Reports\import.js
  2. Locate the maxFilesize parameter and change it from 200 to 1024 (or your desired limit in MB):

    maxFilesize: 1024  // Changed from 200 to 1024 MB
warning

This setting only affects the client-side validation. Ensure your IIS upload limit (configured above) is set appropriately to handle files of this size.

note

This setting is only for the UI-Based imports.

Automatic Forest Exploration Setup

For large environments, you can simplify scan configuration by using automatic forest exploration. This allows PingCastle to discover and scan all domains within a forest automatically using a wildcard in the --server parameter.

Manual Execution

To manually scan all domains in a forest:

PingCastle.exe --healthcheck --server *.domain.fqdn --Level Full

Replace domain.fqdn with your actual forest root domain name.

Automated Execution (Agent Setup)

For scheduled scans with automatic upload to PingCastle Enterprise:

PingCastle.exe --healthcheck --server *.domain.fqdn --Level Full --api-endpoint https://pingcastle.yourdomain.fqdn --api-key <Key from Configuration -> Agents with upload permission> --out "SchedulerLogs\<fqdn>.txt"

Parameters:

  • *.domain.fqdn - Wildcard pattern to scan all domains in the forest
  • --api-endpoint - URL of your PingCastle Enterprise server
  • --api-key - API key created in Configuration → Agents with upload permission
  • --out - Log file path for the scan output
tip

The wildcard pattern *.domain.fqdn will automatically discover and scan all child domains within the specified forest, eliminating the need to configure individual domain scans.

Scheduler Configuration

PingCastle Enterprise includes a built-in scheduler to automate scans. This is particularly useful when the solution is installed in a central forest and needs to scan all child domains automatically.

Permission Requirements

To use the scheduler, the application pool identity must have local administrator permissions on the server. This is a Windows requirement for creating and managing tasks in the Microsoft Windows Task Scheduler.

By default, PingCastle runs as a limited user (ApplicationPoolIdentity), which cannot access the network or modify system settings. Choose one of the following configuration options:

If you want to minimize the permissions granted to the application pool identity, you can create scheduled tasks manually or through scripts, then grant the application pool identity only start and stop permissions (not edit permissions).

PingCastle uses a folder named "PingCastle" in the Windows Task Scheduler. The scripts below use the COM API to manage security descriptors, which is not available through the native PowerShell API.

Function: New-PingCastleHealthCheckScheduledTask

This function creates a new scheduled task for PingCastle health checks.

<#
.SYNOPSIS
Creates or updates a PingCastle HealthCheck scheduled task.

.DESCRIPTION
Creates (or updates) a scheduled task named:

PingCastle HealthCheck <DomainFqdn>

in the \PingCastle Task Scheduler folder.

The task:
- Runs PingCastle.exe with healthcheck parameters
- Uploads results to PingCastle Enterprise using API endpoint and key
- Runs weekly on a configurable day and time
- Runs as SYSTEM by default (or a specified service account)
- Runs whether the user is logged on or not
- Runs with highest privileges
- Is configured for Windows Server 2019 or later

The function is idempotent and safe to re-run.

.PARAMETER DomainFqdn
The domain FQDN being assessed.

Example:
corp.example.com

.PARAMETER PingCastleEnterpriseUrl
The PingCastle Enterprise API endpoint used for result upload.

Example:
https://pingcastle.domain.local

.PARAMETER AgentApiKey
The PingCastle Enterprise agent API key used for authentication.

.PARAMETER FolderPath
The Task Scheduler folder in which to create the task.

Defaults to:
\PingCastle

The folder is created automatically if it does not exist.

.PARAMETER ProgramPath
Full path to PingCastle.exe.

Default:
C:\Program Files\Netwrix\PingCastleEnterprise\PingCastle.exe

.PARAMETER DayOfWeek
Day of the week the task runs.

Default:
Sunday

.PARAMETER Time24h
Start time in 24-hour format (HH:mm).

Default:
21:00

.PARAMETER RunAsUser
Optional user account to run the task under.

If omitted, the task runs as:
SYSTEM

If specified, RunAsPassword must also be provided.

.PARAMETER RunAsPassword
SecureString password for the RunAsUser account.

Required when RunAsUser is specified.

.PARAMETER OutputFolder
Relative folder (from the PingCastle.exe directory) used to store output files.

Default:
SchedulerLogs

.PARAMETER OutputFileName
Optional override for the output file name.

Default:
<DomainFqdn>.txt

.EXAMPLE
New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "domain.local" `
-PingCastleEnterpriseUrl "https://pc.domain.local" `
-AgentApiKey "APIKEY"

Creates a weekly PingCastle health check task running as SYSTEM
every Sunday at 21:00.

.EXAMPLE
New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "corp.example.com" `
-PingCastleEnterpriseUrl "https://pc.corp.example.com" `
-AgentApiKey "APIKEY" `
-DayOfWeek Wednesday `
-Time24h 02:30

Creates a task with a custom weekly schedule.

.EXAMPLE
$pw = Read-Host "Password" -AsSecureString
New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "corp.example.com" `
-PingCastleEnterpriseUrl "https://pc.corp.example.com" `
-AgentApiKey "APIKEY" `
-RunAsUser "CORP\svc_pingcastle" `
-RunAsPassword $pw

Creates a task that runs under a service account instead of SYSTEM.

.EXAMPLE
New-PingCastleHealthCheckScheduledTask -WhatIf

Shows what would be created or updated without making changes.

.OUTPUTS
PSCustomObject

Returns a summary including:
- Task name and folder
- RunAs account
- Schedule
- Program and arguments
- Output file path

.NOTES
Requires administrative privileges.

Uses the Task Scheduler COM API (Schedule.Service).

Task compatibility is set to Windows Server 2019+ (Win10 scheduler compatibility).
#>

function New-PingCastleHealthCheckScheduledTask {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
# Domain FQDN used in task name and --server (e.g. corp.example.com)
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$DomainFqdn,

# PingCastle Enterprise API endpoint (e.g. https://pc.domain.local)
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$PingCastleEnterpriseUrl,

# Agent API key used for --api-key
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$AgentApiKey,

# Task Scheduler folder (defaults to \PingCastle)
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$FolderPath = '\PingCastle',

# PingCastle.exe path
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$ProgramPath = 'C:\Program Files\Netwrix\PingCastleEnterprise\PingCastle.exe',

# Weekly schedule options (defaults: Sunday 21:00)
[Parameter()]
[ValidateSet('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday')]
[string]$DayOfWeek = 'Sunday',

[Parameter()]
[ValidatePattern('^\d{2}:\d{2}$')]
[string]$Time24h = '21:00',

# Run as SYSTEM by default. If provided, task will run as this account (prompt/secure string required).
[Parameter()]
[string]$RunAsUser,

[Parameter()]
[System.Security.SecureString]$RunAsPassword,

# Optional: override output folder (relative to working dir), and filename
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$OutputFolder = 'SchedulerLogs',

[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$OutputFileName
)

begin {
function Ensure-FolderExists {
param([Parameter(Mandatory)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Path $Path -Force | Out-Null
}
}

function Get-TaskFolder {
param(
[Parameter(Mandatory)]$Service,
[Parameter(Mandatory)][string]$Path
)

# Normalize: must start with "\" and not end with "\" unless root
if ($Path -notmatch '^\\') { $Path = "\" + $Path }
if ($Path.Length -gt 1) { $Path = $Path.TrimEnd('\') }

try {
return $Service.GetFolder($Path)
} catch {
# Create missing folders recursively
$parts = $Path.Trim('\').Split('\')
$currentPath = '\'
$folder = $Service.GetFolder('\')

foreach ($p in $parts) {
$nextPath = if ($currentPath -eq '\') { "\$p" } else { "$currentPath\$p" }
try {
$folder = $Service.GetFolder($nextPath)
} catch {
$folder.CreateFolder($p, $null) | Out-Null
$folder = $Service.GetFolder($nextPath)
}
$currentPath = $nextPath
}
return $Service.GetFolder($Path)
}
}

function Build-WeeklyTriggerBoundary {
param(
[Parameter(Mandatory)][string]$Time24h
)
# Create an ISO boundary like 2026-01-29T21:00:00 (today) - scheduler will align by Repetition/DaysOfWeek
$dt = [DateTime]::Today.Add([TimeSpan]::Parse($Time24h))
return $dt.ToString("yyyy-MM-dd'T'HH:mm:ss")
}

function DayOfWeek-ToTaskSchedulerBitmask {
param([Parameter(Mandatory)][string]$Day)
# Task Scheduler days bitmask:
# Sunday=0x1, Monday=0x2, Tuesday=0x4, Wednesday=0x8, Thursday=0x10, Friday=0x20, Saturday=0x40
switch ($Day) {
'Sunday' { 0x1 }
'Monday' { 0x2 }
'Tuesday' { 0x4 }
'Wednesday' { 0x8 }
'Thursday' { 0x10 }
'Friday' { 0x20 }
'Saturday' { 0x40 }
default { throw "Invalid DayOfWeek: $Day" }
}
}
}

process {
$taskName = "PingCastle HealthCheck $DomainFqdn"

# Output file naming default
if (-not $OutputFileName) {
$OutputFileName = "$DomainFqdn.txt"
}

# Ensure output folder exists next to PingCastle.exe by default
$programDir = Split-Path -Parent $ProgramPath
$outDirFull = Join-Path $programDir $OutputFolder
Ensure-FolderExists -Path $outDirFull

$outFileFull = Join-Path $outDirFull $OutputFileName

# Build arguments EXACTLY as desired (quotes only where needed)
$arguments = @(
'--healthcheck'
'--server', $DomainFqdn
'--api-endpoint', $PingCastleEnterpriseUrl
'--api-key', $AgentApiKey
'--out', ('"' + $outFileFull + '"')
) -join ' '

# ---- Task Scheduler COM API ----
$svc = $null
try {
$svc = New-Object -ComObject 'Schedule.Service'
$svc.Connect()

$folder = Get-TaskFolder -Service $svc -Path $FolderPath

$task = $svc.NewTask(0)

# Registration info
$task.RegistrationInfo.Description = "PingCastle weekly healthcheck upload for $DomainFqdn"
$task.RegistrationInfo.Author = $env:COMPUTERNAME

# Principal / Run level
$TASK_RUNLEVEL_HIGHEST = 1
$task.Principal.RunLevel = $TASK_RUNLEVEL_HIGHEST

# Settings: run whether user is logged on or not, start when available, etc.
$task.Settings.Enabled = $true
$task.Settings.StartWhenAvailable = $true
$task.Settings.Hidden = $false
$task.Settings.AllowHardTerminate = $true
$task.Settings.DisallowStartIfOnBatteries = $false
$task.Settings.StopIfGoingOnBatteries = $false
$task.Settings.MultipleInstances = 0 # IgnoreNew

# Configure for: Windows Server 2019 or later
# Task Scheduler uses "Compatibility" via Settings.Compatibility.
# 4 == Win8.1, 5 == Win10. Server 2019 maps well to Win10 compatibility.
$task.Settings.Compatibility = 5

# Trigger: weekly Sunday 21:00 by default
$TASK_TRIGGER_WEEKLY = 3
$trigger = $task.Triggers.Create($TASK_TRIGGER_WEEKLY)
$trigger.StartBoundary = Build-WeeklyTriggerBoundary -Time24h $Time24h
$trigger.Enabled = $true
$trigger.WeeksInterval = 1
$trigger.DaysOfWeek = DayOfWeek-ToTaskSchedulerBitmask -Day $DayOfWeek

# Action: Start a program
$TASK_ACTION_EXEC = 0
$action = $task.Actions.Create($TASK_ACTION_EXEC)
$action.Path = $ProgramPath
$action.Arguments = $arguments
$action.WorkingDirectory = $programDir

# Logon types:
# 5 = SERVICE_ACCOUNT (SYSTEM)
# 1 = PASSWORD (run whether user is logged on or not, with stored password)
$TASK_LOGON_SERVICE_ACCOUNT = 5
$TASK_LOGON_PASSWORD = 1

$userId = $null
$password = $null
$logonType = $TASK_LOGON_SERVICE_ACCOUNT

if ($RunAsUser) {
if (-not $RunAsPassword) {
throw "RunAsPassword is required when RunAsUser is specified."
}

# Convert SecureString -> plain for COM registration (unavoidable for RegisterTaskDefinition)
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($RunAsPassword)
try { $password = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }

$userId = $RunAsUser
$logonType = $TASK_LOGON_PASSWORD
}
else {
# SYSTEM
$task.Principal.UserId = 'SYSTEM'
$logonType = $TASK_LOGON_SERVICE_ACCOUNT
}

# Register / update task
$TASK_CREATE_OR_UPDATE = 6

if ($PSCmdlet.ShouldProcess("$FolderPath\$taskName", "Create/Update weekly PingCastle healthcheck task")) {
$registered = $folder.RegisterTaskDefinition(
$taskName,
$task,
$TASK_CREATE_OR_UPDATE,
$userId,
$password,
$logonType,
$null
)

# Return a useful object
[pscustomobject]@{
TaskName = $taskName
Folder = $FolderPath
RunAs = $(if ($RunAsUser) { $RunAsUser } else { 'SYSTEM' })
HighestPrivileges = $true
Compatibility = 'Windows Server 2019+ (Win10)'
Trigger = "Weekly $DayOfWeek $Time24h"
Program = $ProgramPath
Arguments = $arguments
WorkingDirectory = $programDir
OutputFile = $outFileFull
}
}
}
finally {
if ($svc) { try { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($svc) } catch {} }
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
}
}

Function: Grant-PingCastleTaskSchedulerAccess

This function grants the application pool identity permission to start and stop tasks without editing them.

<#
.SYNOPSIS
Grants a security principal access to the PingCastle Task Scheduler folder and tasks.

.DESCRIPTION
Adds an Access Control Entry (ACE) to the Task Scheduler security descriptor
for the \PingCastle folder and optionally all tasks within it.

The function:
- Resolves the provided identity (user or group) to a SID using NTAccount.Translate()
- Safely inserts the ACE into the DACL section of the SDDL
- Avoids duplicate ACEs (idempotent)
- Supports -WhatIf and -Confirm
- Can be run repeatedly without breaking ACLs

By default, the function targets the \PingCastle Task Scheduler folder and is
intended to grant access to the PingCastle Enterprise IIS application pool
identity.

.PARAMETER Identity
The user or group to grant access to.

Defaults to: IIS APPPOOL\PingCastleEnterprise

Examples:
- IIS APPPOOL\PingCastleEnterprise
- DOMAIN\User
- DOMAIN\Group
- BUILTIN\Administrators

.PARAMETER FolderPath
The Task Scheduler folder to update.

Defaults to:
\PingCastle

.PARAMETER Rights
The SDDL rights string to grant.

Default is:
FA (Full Access)

Advanced usage may replace this with more granular rights if required.

.PARAMETER IncludeTasks
If specified (default), the ACE is also applied to all tasks contained
within the target folder. This is required for PingCastle Enterprise to read the tasks.

If omitted, only the folder ACL is updated.

.EXAMPLE
Grant-PingCastleTaskSchedulerAccess

Grants access to the default identity on the \PingCastle folder and all tasks.

.EXAMPLE
Grant-PingCastleTaskSchedulerAccess -WhatIf

Shows what ACL changes would be made without applying them.

.EXAMPLE
Grant-PingCastleTaskSchedulerAccess -Identity "DOMAIN\svc_pingcastle" -Confirm:$false

Grants access to a custom service account without confirmation prompts.

.OUTPUTS
PSCustomObject

Returns one object per folder or task indicating:
- Target type (Folder or Task)
- Name
- Whether it was changed
- Identity and SID used
- Any error encountered

.NOTES
Requires administrative privileges.

Uses the Task Scheduler COM API (Schedule.Service).

Safe to run multiple times.
#>
function Grant-PingCastleTaskSchedulerAccess {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
# DOMAIN\User, DOMAIN\Group, .\LocalUser, BUILTIN\Administrators, etc.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$Identity = "IIS APPPOOL\PingCastleEnterprise",

# Task Scheduler folder path (defaults to \PingCastle as requested)
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$FolderPath = '\PingCastle',

# You can change to e.g. "GRGXGW" if you want narrower rights, but keep "FA" if unsure.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$Rights = 'FA',

# Apply to tasks inside the folder too
[Parameter()]
[switch]$IncludeTasks = $true
)

begin {
# Constants from TASK_SECURITY_INFORMATION enum:
# OWNER = 1, GROUP = 2, DACL = 4, SACL = 8, ALL = 15
$DACL_ONLY = 4

function Resolve-ToSid {
param([Parameter(Mandatory)][string]$Name)

try {
$nt = [System.Security.Principal.NTAccount]::new($Name)
$sid = $nt.Translate([System.Security.Principal.SecurityIdentifier])
return $sid.Value
}
catch {
throw "Failed to resolve identity '$Name' to a SID. Ensure it exists and is spelled correctly. Error: $($_.Exception.Message)"
}
}

function Add-AceToDaclSddl {
param(
[Parameter(Mandatory)][string]$Sddl,
[Parameter(Mandatory)][string]$Ace
)

# No-op if already present
if ($Sddl -like "*$Ace*") { return $Sddl }

# Insert the ACE immediately after D: and any DACL control flags (e.g. D:P, D:PAI, etc.)
# Example:
# O:...G:...D:P(A;;...)(A;;...) -> O:...G:...D:P$Ace(A;;...)(A;;...)
$pattern = 'D:([^()]*)'
$match = [regex]::Match($Sddl, $pattern)
if (-not $match.Success) {
throw "Unexpected SDDL format (no D: section found). SDDL: $Sddl"
}

return [regex]::Replace(
$Sddl,
$pattern,
{ param($m) "D:$($m.Groups[1].Value)$Ace" },
1
)
}
}

process {
$sid = Resolve-ToSid -Name $Identity
$ace = "(A;;$Rights;;;$sid)"

$svc = $null
try {
$svc = New-Object -ComObject Schedule.Service
$svc.Connect()

try {
$folder = $svc.GetFolder($FolderPath)
}
catch {
throw "Task Scheduler folder '$FolderPath' was not found. Error: $($_.Exception.Message)"
}

$results = New-Object System.Collections.Generic.List[object]

# ---- Folder permissions ----
try {
$folderSddl = $folder.GetSecurityDescriptor($DACL_ONLY)
$newFolderSddl = Add-AceToDaclSddl -Sddl $folderSddl -Ace $ace

if ($newFolderSddl -ne $folderSddl) {
if ($PSCmdlet.ShouldProcess("Task Scheduler folder $FolderPath", "Add ACE for $Identity ($sid) rights '$Rights'")) {
$folder.SetSecurityDescriptor($newFolderSddl, $null)
}
$results.Add([pscustomobject]@{
Type = 'Folder'
Name = $FolderPath
Changed = $true
Identity = $Identity
Sid = $sid
Rights = $Rights
Error = $null
})
}
else {
$results.Add([pscustomobject]@{
Type = 'Folder'
Name = $FolderPath
Changed = $false
Identity = $Identity
Sid = $sid
Rights = $Rights
Error = $null
})
}
}
catch {
$results.Add([pscustomobject]@{
Type = 'Folder'
Name = $FolderPath
Changed = $false
Identity = $Identity
Sid = $sid
Rights = $Rights
Error = $_.Exception.Message
})
}

# ---- Task permissions ----
if ($IncludeTasks) {
$tasks = $folder.GetTasks(1)
foreach ($task in $tasks) {
$taskName = $task.Name
try {
$taskSddl = $task.GetSecurityDescriptor($DACL_ONLY)
$newTaskSddl = Add-AceToDaclSddl -Sddl $taskSddl -Ace $ace

if ($newTaskSddl -ne $taskSddl) {
if ($PSCmdlet.ShouldProcess("Task '$FolderPath\$taskName'", "Add ACE for $Identity ($sid) rights '$Rights'")) {
$task.SetSecurityDescriptor($newTaskSddl, $null)
}
$results.Add([pscustomobject]@{
Type = 'Task'
Name = "$FolderPath\$taskName"
Changed = $true
Identity = $Identity
Sid = $sid
Rights = $Rights
Error = $null
})
}
else {
$results.Add([pscustomobject]@{
Type = 'Task'
Name = "$FolderPath\$taskName"
Changed = $false
Identity = $Identity
Sid = $sid
Rights = $Rights
Error = $null
})
}
}
catch {
$results.Add([pscustomobject]@{
Type = 'Task'
Name = "$FolderPath\$taskName"
Changed = $false
Identity = $Identity
Sid = $sid
Rights = $Rights
Error = $_.Exception.Message
})
}
}
}

# Emit results
$results
}
finally {
# Best-effort COM cleanup
if ($svc) {
try { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($svc) } catch {}
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
}
}

Usage Examples

Example 1: Standalone Task Creation for Custom Deployment

This example creates a scheduled task on a remote server (not the PingCastle Enterprise server). You need to copy PingCastle.exe to the target server first, then create the scheduled task to scan and upload results.

# Prerequisites:
# 1. Copy PingCastle.exe to the remote server (e.g., C:\Tools\PingCastle\PingCastle.exe)
# 2. Run this script on the remote server with administrative privileges

# Create a scheduled task on a standalone server
New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "corp.contoso.com" `
-PingCastleEnterpriseUrl "https://pingcastle.contoso.com" `
-AgentApiKey "your-api-key-here" `
-ProgramPath "C:\Tools\PingCastle\PingCastle.exe" `
-DayOfWeek Wednesday `
-Time24h "02:00"

# The task will run as SYSTEM by default and upload results to PingCastle Enterprise
tip

This approach is useful for distributed scanning where you have dedicated scanning servers in different locations or security zones. Each server can independently scan its local domain and upload results to the central PingCastle Enterprise instance.

Example 2: PingCastle Enterprise Least Privilege Task Setup

This example creates scheduled tasks on the PingCastle Enterprise server itself, then grants the application pool minimal permissions (start/stop only) to manage those tasks.

# Step 1: Create scheduled task(s) using default paths
# (Assumes PingCastle.exe is in the default location)

New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "corp.contoso.com" `
-PingCastleEnterpriseUrl "https://pingcastle.contoso.com" `
-AgentApiKey "your-api-key-here"

# If you have multiple domains, create additional tasks:
New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "emea.contoso.com" `
-PingCastleEnterpriseUrl "https://pingcastle.contoso.com" `
-AgentApiKey "your-api-key-here" `
-DayOfWeek Monday `
-Time24h "03:00"

New-PingCastleHealthCheckScheduledTask `
-DomainFqdn "apac.contoso.com" `
-PingCastleEnterpriseUrl "https://pingcastle.contoso.com" `
-AgentApiKey "your-api-key-here" `
-DayOfWeek Tuesday `
-Time24h "04:00"

# Step 2: Grant the application pool least-privileged access
# This allows the PingCastle Enterprise web application to start/stop tasks
# without being a local administrator

Grant-PingCastleTaskSchedulerAccess -Identity "IIS APPPOOL\PingCastleEnterprise"

Write-Host "`nSetup complete! The PingCastle Enterprise application can now start and stop these scheduled tasks." -ForegroundColor Green
note

The Grant-PingCastleTaskSchedulerAccess function grants full access (FA) by default, which allows the application pool to read, start, stop, and manage the tasks. This is required for PingCastle Enterprise to display and control the tasks in the web interface. If you created the tasks with an administrator account, the application pool needs these permissions to interact with them.

Example 3: Bulk Setup with Least Privilege Using CSV Import

This example demonstrates bulk task creation using a CSV file to define multiple domains with custom schedules, followed by granting least-privileged access to the application pool.

First, create a CSV file named PingCastleDomains.csv with the following format:

DomainFqdn,DayOfWeek,Time24h
corp.contoso.com,Sunday,21:00
emea.contoso.com,Monday,21:00
apac.contoso.com,Tuesday,21:00
dev.contoso.com,Wednesday,21:00
test.contoso.com,Thursday,21:00

Then run this PowerShell script:

# Import the CSV file
$domains = Import-Csv -Path "C:\PingCastleDomains.csv"

# Configuration
$pingCastleUrl = "https://pingcastle.contoso.com"
$apiKey = "your-api-key-here"

# Create scheduled tasks for each domain
foreach ($domain in $domains) {
Write-Host "Creating task for $($domain.DomainFqdn)..." -ForegroundColor Cyan

try {
New-PingCastleHealthCheckScheduledTask `
-DomainFqdn $domain.DomainFqdn `
-PingCastleEnterpriseUrl $pingCastleUrl `
-AgentApiKey $apiKey `
-DayOfWeek $domain.DayOfWeek `
-Time24h $domain.Time24h `
-Confirm:$false

Write-Host " Successfully created task for $($domain.DomainFqdn)" -ForegroundColor Green
}
catch {
Write-Host " Failed to create task for $($domain.DomainFqdn): $_" -ForegroundColor Red
}
}

# Grant the application pool least-privileged access to all created tasks
Write-Host "`nGranting permissions to IIS application pool..." -ForegroundColor Cyan
Grant-PingCastleTaskSchedulerAccess -Identity "IIS APPPOOL\PingCastleEnterprise" -Confirm:$false

Write-Host "`nBulk setup complete! Created $($domains.Count) tasks." -ForegroundColor Green
Write-Host "The PingCastle Enterprise application can now manage these scheduled tasks." -ForegroundColor Green
tip

You can also use wildcards in the CSV to scan entire forests:

DomainFqdn,DayOfWeek,Time24h
*.corp.contoso.com,Sunday,21:00
*.emea.contoso.com,Monday,21:00

This will automatically discover and scan all child domains within each forest.

Manual Installation (Without MSI Installer)

When to Use Manual Installation

This section is for advanced users who cannot use or prefer not to use the MSI Installer. Manual installation is typically required for:

  • Linux deployments with Nginx or Apache
  • Azure App Service deployments using az webapp deploy
  • Custom Windows configurations requiring non-standard setup
  • Environments where the MSI Installer is not available or cannot be used

For standard Windows Server deployments, the MSI Installer (described earlier in this document) is the recommended and supported installation method.

PingCastle Enterprise can be manually installed as a standard ASP.NET Core 8.0 application. Manual installation involves:

Windows Manual Installation:

  1. Extract the application ZIP file to a target directory
  2. Create an IIS website and application pool
  3. Disable the Default Web Site if it conflicts with PingCastle Enterprise
  4. Configure the application pool identity
  5. Grant SQL Server permissions to the application pool account

Linux Manual Installation:

  • Installation procedures for Linux are not fully documented
  • Requires configuration of Nginx or Apache as a reverse proxy
  • Requires PostgreSQL database setup
  • See Hosting section for Microsoft's official ASP.NET Core hosting documentation

Azure App Service Deployment:

  • Use az webapp deploy command to deploy the application package
  • Configure Azure Database for PostgreSQL as the backend
  • Use the same deployment method for initial installation and subsequent updates
Limited Support for Alternative Configurations

While PingCastle Enterprise can run on Linux with PostgreSQL or in Azure App Service environments, Netwrix does not fully support these configurations. These setups are possible but not guaranteed for future releases. Customer support for non-Windows/non-SQL Server configurations will be provided on a best-effort basis only.

The fully supported configuration is Windows Server with IIS and Microsoft SQL Server, installed via the MSI Installer.

Hosting

PingCastle Enterprise can run on any infrastructure that supports ASP.NET Core 8.0. When performing a manual installation, refer to Microsoft's documentation for hosting procedures:

Windows with IIS (Manual Installation)

Linux (Limited Support - Manual Installation)

Azure (Limited Support - Manual Installation)

  • See Azure Hosting section below for detailed guidance on deploying to Azure App Service
  • Requires Azure CLI and manual configuration
  • Not officially supported or tested by Netwrix
IIS Configuration

For IIS deployments, if the "Default Web Site" conflicts with PingCastle Enterprise, stop the default website and configure it to not start automatically.

Database Configuration (Manual Installation)

General Database Requirements

Database backups are the customer's responsibility.

PingCastle Enterprise requires a database user account with database owner permissions. The application automatically creates and updates database tables during initial setup and software updates.

MSI Installer Handles This Automatically

If using the MSI Installer, database setup is handled automatically. This section is only relevant for manual installations.

SQL Server Permissions for IIS

When manually installing on Windows with IIS, the application pool requires database access. The application pool uses a special Windows account for which permissions must be granted manually.

Grant permissions with the following SQL:

IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins
WHERE loginname = 'IIS APPPOOL\PingCastleEnterprise')
BEGIN
CREATE LOGIN [IIS APPPOOL\PingCastleEnterprise] FROM WINDOWS;
END

USE PingCastleEnterprise;
EXEC sp_addrolemember 'db_owner', 'IIS APPPOOL\PingCastleEnterprise';

Remote Database Configuration (Manual Installation)

  1. Create a local SQL Server account:
    • Use SQL Server authentication
    • Uncheck "User must change password at next login" (PingCastle Enterprise does not support automatic password rotation)
    • You can manually update the password later in the appsettings.production.json file

  1. Create a database and set the user you created as the owner.

  2. Verify the credentials and server connectivity before proceeding.

TCP/IP Configuration

A common configuration issue is TCP/IP connectivity. TCP/IP is disabled by default in SQL Server and must be enabled manually in SQL Server Configuration Manager.

  1. During installation, specify a custom connection string:
Server=tcp:server.fqdn.com;Database=PingCastle;User Id=pingcastle;Password=pingcastle;Trusted_Connection=True;MultipleActiveResultSets=true
note

The database schema is not created during installation. Any connection issues will appear on first run. Check the Windows Event Log for detailed error messages. You can update the connection string after installation by editing appsettings.production.json. Remember to escape special characters in JSON strings (e.g., \ becomes \\).

Application Configuration (Manual Installation)

For manual installations, configure the appsettings.json file in the application root directory.

MSI Installer Handles This Automatically

The MSI Installer configures these settings automatically. This section is only for manual installations.

Required Settings

Configure these three settings in appsettings.json:

1. Database Type

Set the database parameter:

  • sqlserver (recommended)
  • postgres (limited support)

2. Connection String

Set the DefaultConnection parameter with your database connection string.

important

Escape special characters in JSON strings:

  • Backslash: \ becomes \\
  • Double quotes: " becomes \"

3. License Key

Set the License parameter with your license key.

Connection String Examples

SQL Server Local DB

"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-PingCastleEnterprise-9521AD04-BA3A-41DC-A454-F2BD464E9391;Trusted_Connection=True;MultipleActiveResultSets=true"

PostgreSQL

"DefaultConnection": "Server=localhost;Username=pingcastle;Password=pingcastle;Database=pingcastle"

Azure hosting

PingCastle Enterprise can be deployed on Microsoft Azure, though this configuration is not officially supported or tested by Netwrix. The instructions provided in this section are for guidance only.

Important Notes
  • Netwrix does not test PingCastle Enterprise on Azure hosting platforms
  • Support is limited to application bug fixes only
  • Installation, configuration, and troubleshooting are the customer's responsibility
  • Customers must provide their own Azure architecture and deployment blueprint
  • Always test upgrades and updates in a non-production environment before deploying to production
  • An Azure architect or Azure expert is recommended for deployment and maintenance

Deployment overview

To deploy PingCastle Enterprise on Azure, you need to:

  1. Create a managed application in Azure
  2. Create and configure a database
  3. Replicate the application configuration into the Azure Configuration page

The minimum required configuration fields are:

  • Database connection
  • License information
  • Connection string (must be named "DefaultConnection")

Azure Configuration page showing required configuration fields

Deploying with Azure CLI

To deploy files to the web server, install the Azure CLI.

Use the following command to deploy:

az webapp deploy --resource-group <group-name> --name <app-name> --src-path <zip-package-path>

Use the az webapp command to view your created application. For more information, see the Azure App Service deployment documentation.

note

Additional steps may be required depending on your Azure configuration.

Creating the App Service

Below are the steps to create a running application in Azure.

First, create an App Service:

Azure App Service creation interface

You can use an Azure template to create both the web app and database simultaneously:

Azure template selection for web app and database

Azure template configuration details

Docker configuration

When Azure automatically creates a Docker file, the configuration settings (normally provided via appsettings and displayed as Environment variables) are not embedded into the image. You will need to edit these manually on the server side.

Docker environment variables configuration

Additional Azure configuration settings

Debugging startup issues

To debug application startup issues, enable App Service Logs:

Enabling App Service Logs in Azure

You can then view the log stream:

App Service log stream view

In the example below, the connectionString was not found because Docker does not forward it. This must be corrected before the application can start:

Connection string error displayed in log stream

Azure Active Directory integration

Since the May 2024 release of PingCastle Enterprise, the application can read user tokens, allowing Azure Active Directory (AAD) configuration:

Azure Active Directory authentication configuration

Authentication

PingCastle Enterprise supports multiple authentication methods that can work simultaneously. You can configure any combination of Local Authentication, Windows Authentication, OpenID Connect, Azure AD, Header Authentication, SAML2, and Client Certificate authentication, allowing users to choose their preferred login method.

Local authentication is enabled by default and uses username and password stored in the PingCastle Enterprise database. No additional configuration is required.

To hide the local authentication option when other authentication methods are configured, add this to appsettings.json:

"disablePasswordLogon": true
warning

When disablePasswordLogon is set to true and Windows Authentication is enabled, any account calling the API will need to be a member of the WindowsGroup that is configured for authentication.

Custom Login Message

You can display a custom message on the login page by adding the customLoginMessage setting to your appsettings.production.json configuration file.

Example Configuration:

"customLoginMessage": "<p>The PingCastle UK Instance for consto</p>"

After performing an iisreset, the custom message will appear on the login page:

The login screen showing the custom login message

Security Note

The customLoginMessage setting renders raw HTML without escaping. While this allows formatting flexibility using Bootstrap CSS styles, Content Security Policy (CSP) protections prevent injection of custom CSS or JavaScript.

Email

PingCastle requires a configuration to be able to send emails.

It is located in the appsettings.json file.

PingCastle Enterprise now supports two email providers:

  • SMTP: Traditional SMTP server configuration
  • Graph: Modern authentication using Microsoft Graph API for Office 365

The Email configuration section in appsettings.json supports both providers:

"Email": {
"Provider": "SMTP",
"Email": "pingcastle@your.domain.com",

// SMTP Configuration (used when Provider is "SMTP")
"Login": "",
"Password": "",
"Host": "localhost",
"Port": "25",

// Graph Configuration (used when Provider is "Graph")
"TenantId": "",
"ClientId": "",
"AuthenticationMethod": "",
"ClientSecret": "",
"FromDisplayName": "PingCastle",
"CertificateAuth": {
"Mode": "",
"File": {
"Path": "",
"Password": ""
},
"Store": {
"Thumbprint": "",
"StoreLocation": "",
"StoreName": ""
}
}
}

Configuration Parameters:

  • Provider: Email provider type - SMTP or Graph
  • Email: The From address of the emails sent by the application (optional for SMTP, mandatory for Graph)
  • FromDisplayName: Display name for the email sender

SMTP Provider Parameters:

  • Login: Login credentials for the SMTP server (leave empty if not required)
  • Password: Password for the SMTP server (leave empty if not required)
  • Host: FQDN or IP address of the SMTP server
  • Port: Port of the SMTP server (25 is default, 465 and 587 for TLS/SSL. Encryption will be enabled unless port is 25)

Graph Provider Parameters:

  • TenantId: Azure AD tenant ID (mandatory for Graph)
  • ClientId: Application (client) ID from Azure AD app registration (mandatory for Graph)
  • AuthenticationMethod: ClientSecret or Certificate
  • ClientSecret: Client secret value (mandatory if using ClientSecret authentication)
  • CertificateAuth.Mode: File or Store (certificate location mode)
  • CertificateAuth.File.Path: Path to certificate file (e.g., path/to/certificate.pfx)
  • CertificateAuth.File.Password: Certificate file password
  • CertificateAuth.Store.Thumbprint: Certificate thumbprint
  • CertificateAuth.Store.StoreLocation: LocalMachine or CurrentUser
  • CertificateAuth.Store.StoreName: Store name (e.g., My, Root)

The email functionality is used to send password reset request and send notification such as weekly reports.

For detailed instructions on configuring Modern Authentication with Office 365, see the section below.

Modern Authentication with Office 365 (Graph API)

PingCastle Enterprise supports sending emails using Microsoft Graph API with modern authentication. This method is recommended for Office 365 environments as it provides enhanced security through OAuth 2.0 authentication.

This configuration uses RBAC for Applications (Role-Based Access Control for Applications) in Exchange Online, which allows the application to send emails from a specific shared mailbox without requiring a user account with mailbox access permissions.

Prerequisites:

Before starting this configuration, ensure you have:

  • Global Administrator or Exchange Administrator permissions
  • Application Developer permissions in Azure AD
  • Exchange Online PowerShell module installed or use the Cloud Management Shell
  • Microsoft Graph PowerShell module installed (optional, for PowerShell automation)
note

"PingCastle-Email" is used throughout this configuration as an example name. This can be substituted with any name that fits your organization's naming conventions.

Create and Export Certificate (For Entra ID Certificate Authentication)

If you prefer certificate-based authentication instead of client secrets, use this PowerShell script to create and export a self-signed certificate:

# Create Self-Signed Certificate for use with Entra App Registration for dev environments.
$Name = "PingCastle-Email"
$password = "ENTER PASSWORD"

# Create a self-signed certificate
$cert = New-SelfSignedCertificate -Subject "CN=PingCastle-Email" -CertStoreLocation "Cert:\LocalMachine\My" -KeyExportPolicy Exportable

# Create a password for the PFX
$pwd = ConvertTo-SecureString -String $password -Force -AsPlainText

# Export the certificate as PFX
Export-PfxCertificate -Cert $cert -FilePath "$env:USERPROFILE\$Name.pfx" -Password $pwd

# Export the certificate as CER for Entra
Export-Certificate -Cert $cert -FilePath "$env:USERPROFILE\PingCastle-Email.cer" -Type CERT

Write-Output "Certificate exported to: $env:USERPROFILE\$Name.pfx"
warning

For production environments, use certificates issued by your organization's Certificate Authority (CA) instead of self-signed certificates.

Part 1: Create Azure AD App Registration

Step 1: Access Microsoft Entra Admin Center
  1. Open a web browser and navigate to https://entra.microsoft.com
  2. Sign in with your administrator account
  3. If you have access to multiple tenants, use the Settings gear icon in the top menu to switch to the correct tenant

Entra admin center homepage with Settings menu

Step 2: Navigate to App Registrations
  1. In the left navigation pane, expand Identity
  2. Click on Applications
  3. Select App registrations
  4. Click + New registration at the top of the page

App registrations page with New registration button

Step 3: Configure Application Registration
  1. In the Name field, enter: PingCastle-Email
  2. Under Supported account types, select Accounts in this organizational directory only
  3. Leave Redirect URI (optional) blank for now
  4. Click Register

Register an application form

Step 4: Create Client Secret
  1. In the left menu under Manage, click Certificates & secrets
  2. Click + New client secret
  3. Add a description: PingCastle-Email Secret
  4. Set expiration to 12 months (or as per your policy)
  5. Click Add
  6. Important: Copy the secret Value immediately - it won't be shown again
  7. Paste it in Notepad or a password manager for later use
warning

If you misplace your secret, you can return to this screen and generate a new one.

Client secrets page

Part 2: Create Shared Mailbox

Step 5: Access Exchange Admin Center
  1. Navigate to https://admin.exchange.microsoft.com
  2. Sign in with your Exchange administrator account
  3. In the left navigation, expand Recipients
  4. Click Mailboxes

Exchange Admin Center navigation

Step 6: Create Shared Mailbox
  1. Click + Add a shared mailbox
  2. Fill in the following details:
    • Display Name: PingCastle
    • Email Address: pingcastle (the domain should auto-populate with your domain)
    • Alias: pingcastle (optional)
  3. Click Create

Add a shared mailbox form

Step 7: Verify Shared Mailbox Creation
  1. Wait for the mailbox creation process to complete
  2. Verify the mailbox appears in the mailboxes list
  3. Note the full email address (e.g., pingcastle@yourdomain.com)

Mailboxes list showing the new shared mailbox

Step 8: Block Shared Mailbox Sign-in

This should be automatically configured, but verify it:

  1. Navigate to https://entra.microsoft.com/
  2. Go to Users > All Users
  3. Search for and select the user account corresponding to the shared mailbox
  4. Click Edit Properties
  5. Click on the Settings tab
  6. Ensure the Account Enabled checkbox is unchecked
  7. Click Save

User properties page with Account Enabled disabled

Part 3: Configure RBAC for Applications

Step 9: Connect to Exchange Online PowerShell

Open Windows PowerShell as Administrator and run the following commands:

# Install Exchange Online Management module if not already installed
Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber

# Import the module
Import-Module ExchangeOnlineManagement

# Connect to Exchange Online
Connect-ExchangeOnline
Step 10: Create Service Principal

Using the values from your app registration, create the service principal:

# Define variables (replace with your actual values)
$AppId = "YOUR_APPLICATION_CLIENT_ID"
$ObjectId = "YOUR_APPS_SERVICE_PRINCIPAL_OBJECT_ID" # Get this from the Enterprise Applications screen in Entra ID

# Create Service Principal
New-ServicePrincipal -AppId $AppId -ObjectId $ObjectId -DisplayName "PingCastle-Email"
note

The $ObjectId is the Service Principal Object ID from Enterprise Applications, not the Object ID from App Registrations.

Step 11: Create Management Scope

Create a management scope that restricts access to only the PingCastle shared mailbox:

# Create Management Scope
$EmailAddress = "pingcastle@yourdomain.com" # The email address of the shared mailbox
New-ManagementScope -Name "PingCastle-Email-Scope" -RecipientRestrictionFilter "EmailAddresses -eq '$EmailAddress'"
Step 12: Assign Application Role

Assign the Application Mail.Send role to the service principal with the custom scope:

# Create Role Assignment
$ObjectId = "" # The Exchange Service Principal Object Id (This is output in Step 10)
New-ManagementRoleAssignment -Role "Application Mail.Send" -App $ObjectId -CustomResourceScope "PingCastle-Email-Scope"

Part 4: Test Configuration

Step 13: Test Service Principal Authorization

Verify the configuration works correctly:

# Test Service Principal Authorization
$EmailAddress = "pingcastle@yourdomain.com" # The email address of the shared mailbox
$ObjectId = "" # The Exchange Service Principal Object Id (This is output in Step 10)

Test-ServicePrincipalAuthorization -Identity $ObjectId -Resource $EmailAddress

Expected Output:

  • RoleName: Application Mail.Send
  • InScope: True
Step 14: Verify Scope Restriction

Test that the service principal cannot access other mailboxes:

# Test with a different email address
$EmailAddress = "otheruser@yourdomain.com" # A random email that the application should not be able to send as
$ObjectId = "" # The Exchange Service Principal Object Id (This is output in Step 10)

Test-ServicePrincipalAuthorization -Identity $ObjectId -Resource $EmailAddress

Expected Output:

  • InScope: False

This confirms the application can only send from the designated shared mailbox.

Updating appsettings.json

After completing either the manual or PowerShell configuration, update your PingCastle Enterprise appsettings.json file:

Example configuration with Client Secret:

"Email": {
"Provider": "Graph",
"Email": "pingcastle@yourdomain.com",
"TenantId": "your-tenant-id-guid",
"ClientId": "your-application-client-id",
"AuthenticationMethod": "ClientSecret",
"ClientSecret": "your-client-secret-value",
"FromDisplayName": "PingCastle"
}

Example configuration with Certificate (File mode):

"Email": {
"Provider": "Graph",
"Email": "pingcastle@yourdomain.com",
"TenantId": "your-tenant-id-guid",
"ClientId": "your-application-client-id",
"AuthenticationMethod": "Certificate",
"FromDisplayName": "PingCastle",
"CertificateAuth": {
"Mode": "File",
"File": {
"Path": "C:\\Certificates\\pingcastle.pfx",
"Password": "your-certificate-password"
}
}
}

Example configuration with Certificate (Store mode):

"Email": {
"Provider": "Graph",
"Email": "pingcastle@yourdomain.com",
"TenantId": "your-tenant-id-guid",
"ClientId": "your-application-client-id",
"AuthenticationMethod": "Certificate",
"FromDisplayName": "PingCastle",
"CertificateAuth": {
"Mode": "Store",
"Store": {
"Thumbprint": "your-certificate-thumbprint",
"StoreLocation": "LocalMachine",
"StoreName": "My"
}
}
}

Initial startup

At the first run of the application, the database is created. If there is an error with the database (missing right, invalid connection string) or hosting, the next screen will not be displayed.

For security reasons, there is no default account or password.

When there is no user configured in the application, a special screen is shown to create the first user. This user is given the "Admin" role.

First user creation screen

Initial configuration

For more details please see the user documentation.

Entities

Entities are created at Configuration -> Entities and implement Role-Based Access Control (RBAC) by assigning permissions to users for groups of domains. This controls access for email notifications and enables targeted dashboards.

PingCastle configures a default entity named "Default" where auto-created domains are assigned. You can create additional entities and build a hierarchy by setting parent relationships.

For bulk configuration, use Configuration -> Interoperability to edit the entity hierarchy using an Excel file (compatible with the PingCastleReporting tool format).

Encryption

The default PingCastle decryption key is marked as insecure. Note that the default encryption key is no longer provided in newer versions of PingCastle as this was a security risk. You must generate your own key in the Enterprise UI at Configuration -> Decryption and use that in your PingCastle.exe's appsettings.console.json file.

Bulk Import of existing reports

You can import existing reports using the bulk import functionality in Configuration -> Interoperability.

You can also use PingCastle.exe --upload-all-reports --api-endpoint https://your.pingcastle.server --api-key XXXXXX to upload reports via the command line.

Agents

An "agent" in PingCastle Enterprise refers to the PingCastle.exe program running on a remote system that uploads scan reports to the central server via API.

To configure an agent:

  1. Create an API key with upload permissions in Configuration -> Agents
  2. Configure Windows Task Scheduler on the remote system to run PingCastle.exe with the appropriate parameters

Upload existing reports stored in the current directory:

.\pingcastle.exe --healthcheck --server your.domain --api-endpoint https://endpoint.com --api-key abdsnhvdsklLksf

PingCastle agent deployment

For security reasons, PingCastle scans are not executed from the web application. Instead, remote systems must push their scan results to PingCastle Enterprise using the agent configuration.

Program version

Use the latest official version of PingCastle.exe (included in the PingCastleEnterprise directory). The application supports reports from different PingCastle versions - newer features will only display after upgrading PingCastle Enterprise, but no data is lost.

Scheduling

Configure Windows Task Scheduler (or your organization's batch scheduler) to run scans weekly using a normal user account (non-privileged) from a batch server (not a Domain Controller).

Command line

Create an API key with upload permissions (Advanced -> Agent as admin) and test the command before scheduling:

.\PingCastle --healthcheck --level Full --api-endpoint https://yourservername --api-key yourapikey

Optionally specify a different domain:

.\PingCastle --healthcheck --server <other domain> --level Full --api-endpoint https://yourservername --api-key yourapikey

Common issues

If TLS 1.2 is enabled on the server, ensure the TLS 1.2 client package is installed on the system running the audit.

Synchronization feature

PingCastle Enterprise supports a synchronization mode to implement a security zone model (commonly used within Defense sectors). Only domains are synchronized (Azure AD is not supported).

PingCastle Enterprise high trust

PingCastle Enterprise high trust

PingCastle Enterprise low trust

PingCastle audits

This enables report consolidation while keeping report details limited to appropriate security zones.

The data synchronized between high trust and low trust instances includes:

  • The status of the domain (active, removed, etc.)

  • The content of the report, based on a level filter (Full = no filter; Normal = recomputed for Full report, as-is for normal report; Light = stripped from Normal and Full, etc.)

The following data is not synchronized: exceptions, action plans, maturity changes, etc.

Configuration

You need to configure an API key with synchronization rights on the server side.

Note that you must assign the Agent to an entity. You cannot assign it to a domain, as the entity will be used as the root to assign the newly forwarded domains.

API key configuration showing Agent assignment to entity with synchronization rights

On the client side, edit the appsettings file to specify the credentials and other required information.

Add a "Sync" section at the root of the file (remember to add a comma before or after this section as required for valid JSON formatting).

Specify the Uri as the FQDN of the recipient server and the API key.

{
"Sync": {
"Uri": "https://syncrecipient.pingcastle.com/",
"ApiKey": "aaaaaaaa",
"ExportLevel": "Normal"
}
}

The export level is the one defined in the classic PingCastle Agent configuration. If information needs to be removed, the data will be recomputed (this can result in information loss if the instance is processing a more recent report). If the level does not need to be restricted, the information will be forwarded as-is. If the report version is more recent, no information will be lost.

Available export levels:

  • Full - No filter applied, all data included
  • Normal - Standard level with moderate filtering
  • Light - Stripped down data from Normal and Full
  • Paranoid - Most restrictive level

Synchronization patterns

PingCastle Enterprise will attempt to retrieve the license from the higher instance at startup. If it cannot be retrieved, it will use the locally configured license.

PingCastle Enterprise will sync a domain when the domain is edited or when the sync button is pressed.

Domain sync button interface

The Sync button is shown if the sync link is configured AND if the user has permission to edit the domain. When a sync is performed, the domain properties (status, etc.) will be synchronized along with past reports.

To avoid loading older reports with each change, information about the latest audit is shared with the lower instance. The lower instance can choose to upload only missing reports.

If a domain is created by a user locally, it will be synchronized. However, if it is removed locally (which is allowed when no reports are present), the application will attempt to remove it from the higher instance. Note that removal cannot be completed if reports already exist, so the remove request may be denied silently.

You can also force synchronization of all domains from the Interoperability page.

Interoperability page with option to force synchronization of all domains

Synchronization patterns at import time

To ensure license enforcement, before importing a new report in the lower instance, the instance will contact the higher instance to verify that the report does not create domains beyond the license limit. If there is a temporary network issue, this check will not be performed. If the check denies the import, the report will not be imported and the error will be logged.

Once this check completes, the import is performed on the lower instance. Then the report is synchronized to the higher instance. If there is any network issue during this step, the error will be ignored (but logged if logging is enabled).

Connection tests

To verify the connection is properly configured, you can sync a domain using the button described above.

If there is an error, it will be displayed as an exception.

Note: The error details may be contained in an inner exception shown below. In this example, this is a DNS issue where the host cannot be found.

Troubleshooting

Viewing Application Logs and Errors

When troubleshooting issues with PingCastle Enterprise, you need to view error messages and logs to diagnose problems.

Enable persistent debug logging to capture detailed application behavior:

  1. Log in to the PingCastle Enterprise Server.

  2. Locate the appsettings.json file (usually at C:\PingCastleEnterprise).

  3. Edit the appsettings.json file so the Logging section looks like this:

"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
  1. From the same directory, open the web.config file and edit the aspNetCore tag to enable stdout logging:
<aspNetCore processPath="dotnet"
arguments=".\PingCastleEnterprise.dll" stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout" hostingModel="InProcess" />
  1. Open PowerShell as Administrator and run IISRESET to restart the web services.

  2. Log in and perform actions in the PingCastle Enterprise web portal. Check C:\PingCastleEnterprise\logs\ to review the logs.

Platform-Specific Logs

  • Event Viewer stores application errors and warnings
  • Debug logs are written to the logs directory when enabled (C:\PingCastleEnterprise\logs\)

Common Errors and Solutions

Here are common errors, their causes, and how to fix them.

Incorrect ASP.NET Core Middleware Version

These error messages appear when the wrong version of ASP.NET Core is installed:

ASP.NET Core error - HTTP 502.5

ASP.NET Core error in browser

ASP.NET Core error details

Event log ASP.NET Core error

Event viewer error message

Command line error messages:

Command line ASP.NET Core error

Missing KB error message

Solution:

Identify the correct version of the ASP.NET Core framework and install it. If deploying to IIS, install the ASP.NET Core Hosting Bundle.

note

The last error was related to the missing KB KB2533623

Missing web.config

If the web.config file is missing or doesn't load the .NET module correctly, the web server will treat the application as a static file and return a 404 error.

Missing web.config error in IIS

404 error from missing web.config

Solution:

Download the correct web.config file from the PingCastle website and replace the existing one.

Application Startup Errors

When the application fails to start, generic error messages appear in the browser and event logs:

HTTP Error 500.0 in browser

Event log error 1000

More detailed error messages can be found in the event log or by running the application manually:

Event log error 1026 - license invalid

Common causes:

  • Invalid license key in appsettings.json
  • Missing or misconfigured application settings
  • Runtime dependencies not installed

Solution:

Check the detailed error message and correct the issue. For license errors, verify and update the license key in appsettings.json.

Database Permissions

The application requires database permissions to create tables and modify data. If these permissions aren't granted, the application will fail to start.

SQL Server authentication error

Requirements:

  • Permission to create tables (required on first run)
  • Permission to insert, update, and delete records
  • TCP/IP connectivity enabled on SQL Server
  • Firewall configured to allow remote connections (if SQL Server is on a different machine)

Important: The application pool identity needs these permissions, not your user account. When running under IIS, the identity is typically IIS APPPool\AppName.

SQL Server TCP/IP disabled in configuration

Solution 1: Change Application Pool Identity

Configure the IIS application pool to run as an Active Directory user that has database permissions:

IIS Application Pool Advanced Settings

Application Pool Identity dialog

Custom account credentials dialog

Solution 2: Use SQL Server Authentication

Add SQL Server credentials to the connection string in appsettings.json:

;User ID=sa;Password=pass123

Connection string in appsettings.json

SQL Server connection with authentication

Alternative: If you prefer not to grant table creation permissions, contact support to obtain a SQL script that creates the required tables manually.

Emergency Procedures

Reset Administrator Password

If no administrators are available (password forgotten or the administrator has left the company), you can reset PingCastle to Initialization mode to create a new administrator account.

Steps:

  1. Open your database management tool and navigate to the AspNetUsers table.

  2. Locate the administrator account (use the email address to find it) and delete that row.

  1. Restart the PingCastle Enterprise application.

  2. On the next visit to the web portal, the application will detect that no administrator exists and automatically switch to initialization mode.

  3. Follow the prompts to create a new administrator account.

note

This procedure only removes the administrator account. All other data (users, domains, reports) remains intact in the database.