Logging, Transcription, AMSI, Script Block Logging, and Auditability
Design auditable PowerShell automation with transcripts, structured logs, redaction, platform-aware engine logging, Script Block/Module logging concepts, and defensive AMSI context.
Learning objectives
- Differentiate transcripts, structured application logs, and engine/security logging.
- Use Start-Transcript safely in a disposable workspace and recognize privacy risks.
- Build structured audit records with UTC timestamps, correlation IDs, levels, and outcomes.
- Redact sensitive values before they reach logs.
- Explain Script Block/Module logging across Windows and non-Windows at a practical level.
- Describe AMSI as one defensive Windows integration without evasion guidance.
1. Security logging must create evidence without becoming a leak
Automation that changes infrastructure should leave enough evidence to answer: who/what ran, when, against which target, what action was attempted, what outcome occurred, and which correlation identifier ties related records together. But logs are also durable copies of data. If secrets enter them, retention and aggregation amplify the exposure.
The correct goal is auditable behavior with intentional data minimization.
2. Transcription records console interaction and output
Start-Transcript creates a text record of commands typed and output shown in the host. It works across current PowerShell platforms. That makes it useful for troubleshooting and some audit scenarios—and dangerous if operators type secrets or commands echo secret values.
$logRoot = Join-Path ([IO.Path]::GetTempPath()) 'ps-academy-ch16-logs'
New-Item -ItemType Directory -Path $logRoot -Force | Out-Null
$transcript = Join-Path $logRoot 'training-transcript.txt'
Start-Transcript -LiteralPath $transcript -IncludeInvocationHeader | Out-Null
try {
Write-Output 'Training operation started; no real secrets are used.'
Get-Date | Select-Object DateTime,Kind
}
finally {
Stop-Transcript | Out-Null
}
Get-Item -LiteralPath $transcript | Select-Object FullName,Length3. A transcript is evidence, so protect it like evidence
Transcripts can reveal commands, paths, hostnames, usernames, responses, and output values. Store them where only the intended operators/security systems can read them; define retention; avoid predictable shared locations without access controls; and never assume that a masked UI input guarantees downstream commands will not print the resulting value.
# Inspect what was captured in the training transcript.
Get-Content -LiteralPath $transcript | Select-Object -First 30
# Do not do this with a real secret while transcription is active:
# Write-Output $token4. Structured application logs are different from transcripts
A transcript captures host interaction. A structured application log is designed by the script and emits deliberate fields. Structured records are easier for CI systems, JSON processors, and observability platforms to query without parsing prose.
function New-AuditRecord {
param(
[Parameter(Mandatory)][string]$CorrelationId,
[Parameter(Mandatory)][ValidateSet('Info','Warning','Error')][string]$Level,
[Parameter(Mandatory)][string]$Event,
[Parameter(Mandatory)][string]$Target,
[Parameter(Mandatory)][bool]$Success
)
[pscustomobject]@{
TimestampUtc = [datetime]::UtcNow.ToString('o')
CorrelationId = $CorrelationId
Level = $Level
Event = $Event
Target = $Target
Success = $Success
}
}
$cid = [guid]::NewGuid().ToString()
New-AuditRecord -CorrelationId $cid -Level Info -Event 'ValidationStarted' -Target 'training-app' -Success $true |
ConvertTo-Json -Compress5. Redact before serialization, not after ingestion
Once a JSON log reaches a file, transcript, stdout collector, or SIEM forwarder, the secret has already crossed the boundary. Build a record that contains only safe metadata.
function ConvertTo-SafeAuditContext {
param([hashtable]$Context)
$safe = [ordered]@{}
foreach ($key in $Context.Keys) {
if ($key -match '(?i)token|password|secret|authorization|apikey') {
$safe[$key] = '***REDACTED***'
} else {
$safe[$key] = $Context[$key]
}
}
[pscustomobject]$safe
}
ConvertTo-SafeAuditContext @{
Environment='test'; ApiToken='training-placeholder'; Action='deploy-plan'
} | ConvertTo-Json -CompressKey-name redaction is a last-resort guard, not a perfect detector. The stronger design is to never add secret fields to the log context at all.
6. PowerShell has engine-level logging in addition to your app logs
On Windows, PowerShell can log engine activity to Windows event logs. Module logging records pipeline execution details for selected modules. Script Block Logging records processed script blocks, functions, commands, and scripts. On Linux, PowerShell integrates with systemd journal/syslog; on macOS, it uses Apple's unified logging system. Configuration mechanisms differ by platform.
[pscustomobject]@{
Platform = $PSVersionTable.Platform
Config = if ($IsWindows) {
'Windows event logging / policy configuration'
} elseif ($IsLinux) {
'powershell.config.json + systemd journal/syslog'
} else {
'powershell.config.json + Apple unified logging'
}
}7. Script Block Logging increases visibility and sensitivity
Script Block Logging can capture the content of script blocks PowerShell processes. That is valuable during incident response because defenders can reconstruct behavior beyond a simple process start event. It also means sensitive literals or dynamically constructed data can become part of security logs. Microsoft recommends Protected Event Logging when Script Block Logging is used beyond diagnostics.
# Read-only capability/context check; policy changes are intentionally omitted.
if ($IsWindows) {
Get-WinEvent -ListLog 'Microsoft-Windows-PowerShell/Operational' -ErrorAction SilentlyContinue |
Select-Object LogName,RecordCount,IsEnabled
} else {
[pscustomobject]@{ Note='Use the platform logging system; do not assume Windows Event Log.' }
}8. AMSI is a defensive scanning integration on Windows
The Antimalware Scan Interface (AMSI) is a Windows API that lets applications submit content to an antimalware provider for inspection. PowerShell on supported Windows versions integrates with AMSI. Current Microsoft security guidance notes that PowerShell 7.3 expanded AMSI data to include .NET method invocations.
This course treats AMSI as a defensive telemetry/scanning layer. We do not disable, bypass, or weaken it. Like every control, it belongs in defense in depth alongside application control, endpoint protection, least privilege, and logging.
[pscustomobject]@{
WindowsHost = $IsWindows
Guidance = if ($IsWindows) {
'Keep AMSI/endpoint protection enabled; validate your automation with approved security tooling.'
} else {
'Use the platform endpoint/security controls; AMSI is a Windows interface.'
}
}9. History and transcripts can reveal operational intent
Command history may show targets, file paths, module names, debugging steps, and accidentally pasted credentials. Transcripts can contain even more. A defender can use that evidence to reconstruct an incident; an unauthorized reader can use the same evidence to understand the environment. Protect history and transcript files according to the sensitivity of the systems being administered.
Get-History |
Select-Object -Last 10 Id,CommandLine,ExecutionStatus,StartExecutionTime,EndExecutionTime
# Inspect PSReadLine history location without printing the history contents:
$historyCommand = Get-Command Get-PSReadLineOption -ErrorAction SilentlyContinue
if ($historyCommand) {
(Get-PSReadLineOption).HistorySavePath
}10. An auditable function separates result data from audit records
Business output belongs on the Success stream. Operational diagnostics can use Verbose/Warning/Error streams. Audit records can be written intentionally to a protected file or logging service. Keeping these channels separate preserves composability.
function Invoke-TrainingChangePlan {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Target,[Parameter(Mandatory)][string]$AuditPath)
$cid = [guid]::NewGuid().ToString()
$start = New-AuditRecord -CorrelationId $cid -Level Info -Event 'PlanStarted' -Target $Target -Success $true
$start | ConvertTo-Json -Compress | Add-Content -LiteralPath $AuditPath -Encoding utf8
try {
Write-Verbose "Planning change for $Target; correlation=$cid"
$result = [pscustomobject]@{ Target=$Target; Planned=$true; CorrelationId=$cid }
$result
New-AuditRecord -CorrelationId $cid -Level Info -Event 'PlanCompleted' -Target $Target -Success $true |
ConvertTo-Json -Compress | Add-Content -LiteralPath $AuditPath -Encoding utf8
} catch {
New-AuditRecord -CorrelationId $cid -Level Error -Event 'PlanFailed' -Target $Target -Success $false |
ConvertTo-Json -Compress | Add-Content -LiteralPath $AuditPath -Encoding utf8
throw
}
}11. Lab — produce a redacted, correlated audit trail
$audit = Join-Path $logRoot 'audit.jsonl'
Invoke-TrainingChangePlan -Target 'training-app' -AuditPath $audit -Verbose
Get-Content -LiteralPath $audit | ForEach-Object { $_ | ConvertFrom-Json } |
Select-Object TimestampUtc,CorrelationId,Level,Event,Target,Success
# Cleanup disposable evidence after review.
Remove-Item -LiteralPath $logRoot -Recurse -Force -ErrorAction SilentlyContinueIn production, “cleanup” means a retention policy, not immediate deletion. The temporary lab removes files only because they contain training data and the workspace is disposable.
12. Common logging mistakes
| Mistake | Consequence | Better pattern |
|---|---|---|
| Use Write-Host debugging as the only evidence | Unstructured output is hard to correlate/query and may disappear. | Structured records + appropriate PowerShell streams. |
| Log request headers wholesale | Authorization/cookie secrets can enter durable systems. | Allowlist safe fields and redact before serialization. |
| Enable deep logging without retention/access design | Sensitive operational data accumulates indefinitely. | Define access, retention, protection, and forwarding first. |
| Disable security telemetry because it is noisy | Incident visibility is reduced and controls may be weakened. | Tune/route approved defensive telemetry; do not bypass it. |
13. Verification checklist
- You distinguish host transcripts, application logs, and engine/security logs.
- You know transcripts can contain sensitive commands/output.
- You can create timestamped, correlated, structured audit records.
- You redact before writing to the log sink.
- You understand Windows Script Block/Module logging and non-Windows logging destinations at a high level.
- You can describe AMSI defensively without treating it as the only security layer.
14. Knowledge check
Question 1. Why can a transcript be a security liability?
Question 2. What is the advantage of a correlation ID?
Question 3. When should redaction occur?
Question 4. Where does PowerShell engine logging go on Linux/macOS?
Question 5. What is the course stance on AMSI?
15. Summary and next bridge
Auditability is deliberate evidence engineering: structured fields, timestamps, correlation IDs, protected retention, and aggressive secret minimization. Transcription and engine-level logging add broader visibility, while AMSI participates in Windows defensive scanning. The final lesson applies the chapter's trust-boundary model to privilege itself: how to expose only the administrative capability an automation identity actually needs.
16. Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.