param( [Parameter(Mandatory = $true)][int]$CampaignId, [string]$ConfigPath = (Join-Path (Split-Path -Parent $PSScriptRoot) 'config.ini'), [string]$OutputRoot ) $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot 'campaign-common.ps1') if ($CampaignId -lt 1) { throw 'CampaignId must be positive' } $context = Get-MailContext $ConfigPath if (-not $OutputRoot) { $OutputRoot = if ($context.Settings['REPORT_DIR']) { $context.Settings['REPORT_DIR'] } else { 'reports' } } if (-not [System.IO.Path]::IsPathRooted($OutputRoot)) { $OutputRoot = Join-Path (Split-Path -Parent $ConfigPath) $OutputRoot } $OutputRoot = [System.IO.Path]::GetFullPath($OutputRoot) $null = New-Item -ItemType Directory -Path $OutputRoot -Force $db = Open-MailDatabase $context try { $command = New-DbCommand $db 'SELECT [Name] FROM [Campaign] WHERE [Id]=?' $null = $command.Parameters.Add('id', [System.Data.OleDb.OleDbType]::Integer) $command.Parameters[0].Value = $CampaignId $name = $command.ExecuteScalar() if ($null -eq $name -or $name -is [DBNull]) { throw 'Campaign not found' } $suppression = @{} $command = New-DbCommand $db 'SELECT [EmailKey],[Reason] FROM [Suppression]' $reader = $command.ExecuteReader() try { while ($reader.Read()) { $suppression[[string]$reader.GetValue(0)] = [string]$reader.GetValue(1) } } finally { $reader.Dispose() } $deliveries = New-Object System.Collections.ArrayList $command = New-DbCommand $db 'SELECT [Id],[EmailKey],[Status],[AttemptCount],[LastAttemptAtUtc],[LastErrorCode] FROM [Delivery] WHERE [CampaignId]=? ORDER BY [Id]' $null = $command.Parameters.Add('id', [System.Data.OleDb.OleDbType]::Integer) $command.Parameters[0].Value = $CampaignId $reader = $command.ExecuteReader() try { while ($reader.Read()) { $key = [string]$reader.GetValue(1) $at = if ($reader.IsDBNull(4)) { '' } else { ([DateTime]$reader.GetValue(4)).ToString('o') } $errorCode = if ($reader.IsDBNull(5)) { '' } else { [string]$reader.GetValue(5) } $reason = if ($suppression.ContainsKey($key)) { $suppression[$key] } else { '' } $null = $deliveries.Add([pscustomobject]@{ CampaignId = $CampaignId; CampaignName = [string]$name; DeliveryId = [int]$reader.GetValue(0); Email = $key; Status = [string]$reader.GetValue(2); AttemptCount = [int]$reader.GetValue(3); LastAttemptUtc = $at; LastErrorCode = $errorCode; SuppressionReason = $reason }) } } finally { $reader.Dispose() } $attempts = New-Object System.Collections.ArrayList $command = New-DbCommand $db 'SELECT a.[Id],a.[DeliveryId],a.[StartedAtUtc],a.[EndedAtUtc],a.[Outcome],a.[SanitizedError] FROM [Attempt] AS a INNER JOIN [Delivery] AS d ON a.[DeliveryId]=d.[Id] WHERE d.[CampaignId]=? ORDER BY a.[Id]' $null = $command.Parameters.Add('id', [System.Data.OleDb.OleDbType]::Integer) $command.Parameters[0].Value = $CampaignId $reader = $command.ExecuteReader() try { while ($reader.Read()) { $started = if ($reader.IsDBNull(2)) { '' } else { ([DateTime]$reader.GetValue(2)).ToString('o') } $ended = if ($reader.IsDBNull(3)) { '' } else { ([DateTime]$reader.GetValue(3)).ToString('o') } $errorText = if ($reader.IsDBNull(5)) { '' } else { [string]$reader.GetValue(5) } $null = $attempts.Add([pscustomobject]@{ CampaignId = $CampaignId; AttemptId = [int]$reader.GetValue(0); DeliveryId = [int]$reader.GetValue(1); StartedUtc = $started; EndedUtc = $ended; Outcome = [string]$reader.GetValue(4); SanitizedError = $errorText }) } } finally { $reader.Dispose() } $stamp = [DateTime]::UtcNow.ToString('yyyyMMddTHHmmssZ') + '-' + [guid]::NewGuid().ToString('N').Substring(0,8) $prefix = Join-Path $OutputRoot "campaign-$CampaignId-$stamp" $deliveryPath = $prefix + '-deliveries.csv' $attemptPath = $prefix + '-attempts.csv' $deliveries | Export-Csv -LiteralPath $deliveryPath -NoTypeInformation -Encoding UTF8 if ($attempts.Count) { $attempts | Export-Csv -LiteralPath $attemptPath -NoTypeInformation -Encoding UTF8 } else { 'CampaignId,AttemptId,DeliveryId,StartedUtc,EndedUtc,Outcome,SanitizedError' | Set-Content -LiteralPath $attemptPath -Encoding UTF8 } Write-Output "Delivery report: $deliveryPath" Write-Output "Attempt report: $attemptPath" } finally { $db.Dispose() }