Public/Export-KrbEtypeReport.ps1
|
#Requires -Version 7.4 function Export-KrbEtypeReport { <# .EXTERNALHELP KrbEtypeInsight-Help.xml .SYNOPSIS Writes a Kerberos encryption type risk assessment to HTML, CSV or JSON #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [object[]]$Risk, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter()] [ValidateSet('Html', 'Csv', 'Json')] [string]$Format = 'Html', [Parameter()] [ValidateNotNullOrEmpty()] [string]$Title, [Parameter()] [object]$DomainContext, [Parameter()] [switch]$PassThru ) begin { $correlationId = [System.Guid]::NewGuid() Write-Verbose "Starting $($MyInvocation.MyCommand.Name) - CorrelationId: $correlationId" if (-not $Title) { $Title = "Kerberos Encryption Type Risk Assessment - $(Get-Date -Format 'yyyy-MM-dd')" } # Resolve-Path fails on a path that does not exist yet, which is every output file, so # the parent is resolved instead and the leaf appended. This also normalises a # relative path against the caller's location rather than the process working # directory, which are not the same thing in PowerShell. $parent = Split-Path -Path $Path -Parent if (-not $parent) { $parent = '.' } $resolvedParent = Resolve-Path -Path $parent -ErrorAction SilentlyContinue if (-not $resolvedParent) { Write-Error "Output directory does not exist: $parent" -ErrorAction Stop return } $outputPath = Join-Path -Path $resolvedParent.Path -ChildPath (Split-Path -Path $Path -Leaf) $collected = [System.Collections.Generic.List[object]]::new() } process { foreach ($item in $Risk) { if ($item) { $collected.Add($item) } } } end { if ($collected.Count -eq 0) { Write-Warning ('No risk objects were supplied, so no report was written. An empty ' + 'assessment means nothing was measured, not that nothing is at risk.') return } if (-not $PSCmdlet.ShouldProcess($outputPath, "Write $Format assessment report")) { return } Write-Verbose "Writing $($collected.Count) risk object(s) as $Format to $outputPath" try { switch ($Format) { 'Json' { # Depth 8 reaches through Risk to Findings to Evidence and into the # nested arrays inside Evidence. The default of 2 truncates at Findings # and produces a file whose evidence is the string # 'System.Collections.Hashtable'. $collected | ConvertTo-Json -Depth 8 | Set-Content -Path $outputPath -Encoding utf8 -ErrorAction Stop } 'Csv' { # One row per finding. A principal with four findings is four rows, which # is what makes the file pivotable; one row per principal forces the # findings into a single joined cell and defeats the purpose of using a # spreadsheet. $rows = foreach ($item in $collected) { foreach ($finding in @($item.Findings)) { [PSCustomObject]@{ PrincipalName = $item.PrincipalName Roles = @($item.Roles) -join ';' RiskLevel = $item.RiskLevel RiskScore = $item.RiskScore WillBreak = $item.WillBreakOnHardening FindingCode = $finding.Code Severity = $finding.Severity FindingTitle = $finding.Title FindingDetail = $finding.Detail RecommendedAction = $finding.RecommendedAction RequestCount = $item.RequestCount FailureCount = $item.FailureCount ClientCount = $item.ClientCount ClientsWithoutAes = @($item.ClientsWithoutAesSupport) -join ';' ObservedEtypes = @($item.ObservedTicketEtypeNames) -join ';' AvailableKeys = @($item.AvailableKeys) -join ';' ConfiguredEtypes = if ($item.ConfiguredEncryptionTypes) { $item.ConfiguredEncryptionTypes.EffectiveHex } else { $null } FirstSeen = $item.FirstSeen LastSeen = $item.LastSeen SchemaVersion2 = $item.EventSchemaVersion2 } } } $rows | Export-Csv -Path $outputPath -NoTypeInformation -Encoding utf8 -ErrorAction Stop } 'Html' { $html = New-KrbHtmlReport -Risk $collected.ToArray() -Title $Title ` -DomainContext $DomainContext -CorrelationId $correlationId $html | Set-Content -Path $outputPath -Encoding utf8 -ErrorAction Stop } } } catch { $errorDetails = @{ CorrelationId = $correlationId Function = $MyInvocation.MyCommand.Name OutputPath = $outputPath Format = $Format ErrorMessage = $_.Exception.Message Line = $_.InvocationInfo.ScriptLineNumber } Write-Verbose ('Report write failure detail: ' + ($errorDetails | ConvertTo-Json -Compress)) Write-Error "Failed to write report to '$outputPath': $($_.Exception.Message)" -ErrorAction Stop return } Write-Verbose "Completed $($MyInvocation.MyCommand.Name) - CorrelationId: $correlationId" if ($PassThru) { Get-Item -Path $outputPath } } } |