TierModel Logging System

Overview

The TierModel module includes a comprehensive structured logging system designed for enterprise environments. Logging is controlled via the -Logging switch, which is supported by both Deploy-TierModel.ps1 and Audit-TierModel.ps1. When enabled, operations are logged with correlation IDs, security redaction, and multiple output formats.

Deployment Logging vs. Audit Reporting

Deployment Logging (Deploy-TierModel.ps1) - Controlled by the -Logging switch - Records deployment execution details, progress, and results - Uses Write-TierModelLog function for structured JSON logging - Logs stored in files specified by -LogPath parameter - Purpose: Track what changes were made during deployment

Audit Reporting (Audit-TierModel.ps1) - Compliance reports are controlled by the -OutputFormat parameter (Text, Json, Html, NUnitXml) - Generates compliance and drift detection reports - Also supports -Logging, which is a separate concern from the report: it records the audit run itself — execution details and every -Level Error raised inside the TierModel module — to a structured log file, rather than leaving those on the console alone - Reports and logs stored in the directory specified by -LogPath parameter - Purpose: Document current state compliance vs. desired configuration

The distinction to hold on to is that a report describes the environment, while a log describes the run that produced it. An audit produces both, and they answer different questions.

For audit reporting documentation, see Drift Detection Details.

Features

Structured Logging

  • JSON Format: Machine-readable log entries for analysis
  • Correlation IDs: Track operations across multiple function calls
  • Security Redaction: Automatically redact sensitive data (passwords, tokens, secrets)
  • Multiple Severity Levels: Debug, Info, Warning, Error
  • Consistent Timestamps: ISO 8601 format with millisecond precision

Output Destinations

  • Console: Human-readable format with color coding by severity
  • File: JSON format for log aggregation and analysis
  • CI/CD Integration: Automatic artifact publishing in pipelines

Diagnostic Output — Debug Logging

The -EnableVerbose and -EnableDebug switches provide detailed diagnostic output for troubleshooting deployment or audit failures:

  • -EnableVerbose: Enable verbose diagnostic output. Console output becomes considerably more detailed and interleaves with normal progress output. This switch automatically enables -Logging, so a diagnostic run always leaves a log file behind. If -OutputFileBase is not specified, it defaults to the script name (Deploy-TierModel or Audit-TierModel) without prompting, keeping the re-run command copy-pasteable.

  • -EnableDebug: Enable debug diagnostic output. Like -EnableVerbose, this also automatically enables -Logging and makes console output substantially more detailed. These switches change only what is recorded, never what is decided or written to Active Directory.

  • Combined: -EnableVerbose -EnableDebug: When both switches are supplied together, a PowerShell transcript (full console session capture) is automatically started in a Debug\ subfolder beneath the resolved log directory. The transcript continues for the entire PowerShell session.

⚠️ CRITICAL WARNING — Transcript Contains Unredacted Sensitive Information:

The transcript is NOT redacted and may contain: - Distinguished names (DNs) - Security identifiers (SIDs) - SDDL (Security Descriptor Definition Language) - Group memberships - Other sensitive Tier 0 environmental detail

You MUST review the transcript contents before sharing it with anyone, including Microsoft support. Do not post it to forums, email it broadly, or include it in documents without careful redaction. This is a Tier 0 security tool; the transcript captures the full console session.

If the PowerShell session is interrupted with Ctrl-C before normal completion, the transcript is left open and continues capturing until the console exits. In that case, run Stop-Transcript manually to close the transcript file.

Performance note: Diagnostic switches slow execution and generate substantial output. Use them only when diagnosing failures; they are not intended for routine runs.

Usage

Enabling Logging in Deployment

Logging is controlled by the Deploy-TierModel.ps1 script using the -Logging switch:

# Deploy with logging enabled to current directory
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -OuOnly -ConfirmApply -Logging

# Deploy with logging to specific directory
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -FullDeployment -ConfirmApply -Logging -LogPath "C:\Logs\TierModel"

# Deploy with custom log filename base
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -OuOnly -ConfirmApply -Logging -LogPath "C:\Logs" -OutputFileBase "Deploy-Aug2024"

The deployment script will log: - Deployment initiation and parameters - Prerequisites validation results - Per-component deployment progress (OUs, Groups, Users, etc.) - Action execution details and results - Summary statistics and completion status

Note: Audit-TierModel.ps1 supports -Logging as well. Use -OutputFormat to control the compliance report, and -Logging to capture a structured record of the audit run — including errors raised inside the TierModel module, which would otherwise only reach the console. Both are written to the directory given by -LogPath.

Enabling Diagnostic Output — Verbose and Debug Logging

Both Deploy-TierModel.ps1 and Audit-TierModel.ps1 support diagnostic switches for troubleshooting:

Verbose Diagnostic Output

Enable verbose output to see detailed progress messages:

# Deploy with verbose diagnostics (plan mode)
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -OuOnly -EnableVerbose

# Deploy with verbose diagnostics (apply mode) and custom log location
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -FullDeployment -ConfirmApply -EnableVerbose -LogPath "C:\Logs"

# Audit with verbose diagnostics
.\Audit-TierModel.ps1 -PreferredDc "DC01.contoso.com" -GposOnly -EnableVerbose

Debug Diagnostic Output

Enable debug output for the deepest level of troubleshooting:

# Deploy with debug diagnostics
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -FullDeployment -ConfirmApply -EnableDebug -LogPath "C:\Logs"

# Audit with debug diagnostics
.\Audit-TierModel.ps1 -PreferredDc "DC01.contoso.com" -FullDeployment -EnableDebug -LogPath "C:\Reports"

Debug Transcript (Verbose + Debug Combined)

Capture a full unredacted transcript for detailed failure analysis. Use this only when diagnosing a complex issue in a controlled environment:

# Deploy with both verbose and debug enabled (creates transcript in Debug\ subfolder)
.\Deploy-TierModel.ps1 -PreferredDc "DC01.contoso.com" -OuOnly -ConfirmApply -EnableVerbose -EnableDebug -LogPath "C:\Logs"
# Creates: C:\Logs\Debug\<transcript-file>

# Audit with both switches
.\Audit-TierModel.ps1 -PreferredDc "DC01.contoso.com" -FullDeployment -EnableVerbose -EnableDebug -LogPath "C:\Reports"
# Creates: C:\Reports\Debug\<transcript-file>

The transcript captures every command and output from the PowerShell console session. Review it carefully before sharing—it may contain sensitive object names, DNs, SIDs, SDDL, and group memberships.

Log File Retention — There Is None

Deploy-TierModel.ps1 and Audit-TierModel.ps1 apply no retention policy and keep no rolling history. Every run writes a new timestamped file and nothing is ever rotated, pruned or overwritten. Old files remain until someone removes them.

This is a deliberate difference from optional\Update-TierModelMembership.ps1, which does prune itself. That script bounds its debug output on three axes at once — files older than 7 days, more than 30 files, or a total exceeding 200 MB. The reason for the difference is how each script is used:

Deploy / Audit Update-TierModelMembership.ps1
How it runs Interactively, on demand Scheduled task, unattended
How often Occasionally, to make or confirm a change Frequently, on a timer
Log growth Bounded by how often an operator runs it Unbounded without pruning
Retention None — files kept until removed Bounded: 7 days / 30 files / 200 MB

A scheduled task left alone will fill a disk, so it has to prune itself. An operator-invoked run will not, and silently deleting the evidence of a deployment would be worse than keeping it — the log of a tier-model change is exactly the artifact you want to still have months later.

The practical consequence: if you run with -EnableDebug repeatedly while chasing a problem, the Debug\ subfolder will accumulate files, including any unredacted transcripts. Clean it out yourself once the investigation is finished, and treat the contents as sensitive until you do.

Direct Function Usage (Advanced)

For custom scripts or automation, you can call Write-TierModelLog directly:

# Import the module
Import-Module TierModel

# Basic log entry
Write-TierModelLog -Level Info -Message "Operation completed successfully"

# Log with structured data
Write-TierModelLog -Level Warning -Message "Configuration validation warning" -Data @{
    ConfigPath = "C:\Config\tiermodel.json"
    WarningCount = 3
    ValidationTime = "2.5s"
}

# Error logging
Write-TierModelLog -Level Error -Message "Deployment failed" -Data @{
    ActionId = "CreateGroup-001"
    ErrorType = "AccessDenied"
    TargetDN = "CN=TierAdmins,OU=Groups,DC=contoso,DC=com"
}

# Specify custom log file path
Write-TierModelLog -Level Info -Message "Custom log location" -LogPath "C:\CustomLogs\debug.log"

Log Entry Format

Console Format

[2024-01-15T10:30:45.123Z] [Info] Starting TierModel plan generation | ConfigPath=config/tiermodel.json, ActionCount=15 [CID: a1b2c3d4]

File Format (JSON)

{
  "Timestamp": "2024-01-15T10:30:45.123Z",
  "Level": "Info", 
  "Message": "Starting TierModel plan generation",
  "Data": {
    "ConfigPath": "config/tiermodel.json",
    "ActionCount": 15,
    "CorrelationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  },
  "CorrelationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Security Features

Automatic Redaction

Sensitive data is automatically redacted from log entries:

$userData = @{
    Name = "ServiceAccount"
    Password = "SuperSecret123"  # Will be redacted
    Department = "IT"            # Will be preserved
    Token = "abc123xyz"          # Will be redacted
}

Write-TierModelLog -Level Info -Message "Creating user" -Data $userData
# Output: [Info] Creating user | Name=ServiceAccount, Password=[REDACTED], Department=IT, Token=[REDACTED]

Redacted Keys: Password, Secret, Token, Key, Credential (case-insensitive)

Correlation ID Tracking

Each operation gets a unique correlation ID that's included in all related log entries:

# Starting deployment
Get-TierModelPlan -Path "config/tiermodel.json"
# Logs: [CID: a1b2c3d4] Starting TierModel plan generation
# Logs: [CID: a1b2c3d4] Action plan generation completed

CI/CD Integration

GitHub Actions

The logging system integrates with CI pipelines to capture and publish logs as artifacts:

- name: Deploy TierModel with Logging
  run: |
    .\TierModel\Deploy-TierModel.ps1 `
      -PreferredDc "${{ secrets.DC_HOSTNAME }}" `
      -FullDeployment `
      -ConfirmApply `
      -Logging `
      -LogPath "ci-logs" `
      -OutputFileBase "tiermodel-deployment"

- name: Upload Deployment Logs
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: deployment-logs
    path: ci-logs/

Azure DevOps

- task: PowerShell@2
  displayName: 'Deploy TierModel with Logging'
  inputs:
    filePath: 'TierModel/Deploy-TierModel.ps1'
    arguments: >
      -PreferredDc "$(DC_HOSTNAME)"
      -FullDeployment
      -ConfirmApply
      -Logging
      -LogPath "$(Agent.TempDirectory)/tiermodel-logs"
      -OutputFileBase "tiermodel-deployment"

- task: PublishPipelineArtifact@1
  condition: always()
  displayName: 'Publish Deployment Logs'
  inputs:
    targetPath: '$(Agent.TempDirectory)/tiermodel-logs'
    artifact: 'TierModel-Deployment-Logs'

Log Analysis

PowerShell Analysis

# Read and parse log file
$logs = Get-Content "C:\Logs\TierModel\tiermodel.log" | ForEach-Object { $_ | ConvertFrom-Json }

# Filter by correlation ID
$operationLogs = $logs | Where-Object { $_.CorrelationId -eq "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }

# Group by log level
$logsByLevel = $logs | Group-Object Level

# Find errors in last 24 hours
$recentErrors = $logs | Where-Object { 
    $_.Level -eq "Error" -and 
    [DateTime]::Parse($_.Timestamp) -gt (Get-Date).AddDays(-1) 
}

Log Aggregation Tools

The JSON format is compatible with popular log aggregation systems:

  • ELK Stack: Logstash can parse the JSON format directly
  • Splunk: JSON format enables rich field extraction
  • Azure Monitor: Compatible with Log Analytics workspace ingestion
  • PowerBI: JSON format can be imported for dashboard creation

Troubleshooting

Common Issues

File Permission Errors

# Check if log directory is writable
$logDir = Split-Path $script:DefaultLogPath -Parent
Test-Path $logDir -PathType Container
(Get-Acl $logDir).Access | Where-Object { $_.IdentityReference -eq $env:USERNAME }

Large Log Files

# Rotate logs manually
$logFile = $script:DefaultLogPath
if ((Get-Item $logFile).Length -gt 10MB) {
    $rotatedFile = $logFile -replace '\.log$', "-$(Get-Date -Format 'yyyyMMdd').log"
    Move-Item $logFile $rotatedFile
}

Missing Correlation IDs Correlation IDs are automatically generated. If missing, check: - Module import completed successfully - Script-level variables are initialized - No conflicting module imports

Debug Logging

Enable verbose logging for troubleshooting:

$VerbosePreference = 'Continue'
$DebugPreference = 'Continue'

# All Write-TierModelLog calls with Debug level will now appear
Write-TierModelLog -Level Debug -Message "Detailed debug information"

Best Practices

Logging Guidelines

  1. Use appropriate log levels:
  2. Debug: Detailed diagnostic information
  3. Info: General information about operation progress
  4. Warning: Something unexpected but recoverable occurred
  5. Error: An error occurred that prevented operation completion

  6. Include context data: powershell Write-TierModelLog -Level Info -Message "Action executed" -Data @{ ActionType = "CreateGroup" Target = "CN=Admins,OU=Groups,DC=contoso,DC=com" ExecutionTime = "1.2s" }

  7. Avoid logging sensitive information:

  8. The system automatically redacts known sensitive keys
  9. Avoid including credentials in custom data fields
  10. Use generic identifiers rather than actual passwords/secrets

  11. Log operation boundaries: powershell Write-TierModelLog -Level Info -Message "Starting user creation batch" # ... user creation logic ... Write-TierModelLog -Level Info -Message "User creation batch completed"

Performance Considerations

  • File logging has minimal performance impact (< 1ms per entry)
  • JSON serialization is optimized for small to medium data objects
  • Log rotation should be implemented for long-running systems
  • Consider disabling Debug level logging in production environments

Configuration Reference

Deployment Script Parameters

The Deploy-TierModel.ps1 script provides the following logging-related parameters:

Parameter Type Description
-Logging Switch Enables file logging for the deployment operation
-LogPath String Directory path where log files will be created (default: current directory)
-OutputFileBase String Base filename for log files without extension or timestamp

Module Variables (Advanced)

For custom scripts calling Write-TierModelLog directly, these module-level variables can be configured:

# Enable/disable file logging (used by Write-TierModelLog)
$script:LoggingEnabled = $true/$false

# Default log file path (used when LogPath not specified)
$script:DefaultLogPath = "C:\Logs\TierModel\tiermodel.log"

# Session correlation ID (automatically generated)
$script:CorrelationId = "00000000-0000-0000-0000-000000000000"

Note: In typical usage, you should use the Deploy-TierModel.ps1 script's -Logging switch rather than directly manipulating these variables.

Log Levels

Level Usage Console Stream File Logging
Debug Diagnostic details Write-Debug Always logged
Info General information Write-Verbose Always logged
Warning Recoverable issues Write-Warning Always logged
Error Operation failures Write-Error Always logged

Redacted Data Keys

The following keys are automatically redacted (case-insensitive): - Password - Secret
- Token - Key - Credential

Additional keys can be added by modifying the $sensitiveKeys array in the Write-TierModelLog function.

For additional documentation, see: - Drift Detection Details - Audit reporting and compliance checking - Deployment Methodology - Comprehensive deployment strategy - CI/CD Integration - Automated testing and deployment pipelines