Dateien nach "/" hochladen
This commit is contained in:
+140
-5
@@ -7,7 +7,7 @@
|
||||
Nimmt lokale SMTP-Mails an, speichert sie als .eml in einer Queue und sendet sie
|
||||
anschließend per Microsoft Graph sendMail mit App-only Zertifikatsauthentifizierung.
|
||||
|
||||
V1.4: Zertifikatsüberwachung, parallele SMTP-Clients, separater Queue-Worker, robuste Queue und Graph-Retry
|
||||
V1.5: Log-Rotation, Zertifikatsüberwachung, parallele SMTP-Clients, Queue-Worker und robuste Graph-Retry-Logik
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
@@ -18,6 +18,114 @@ param(
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
function Get-LogSettings {
|
||||
$maxSizeMB = 10
|
||||
$retentionDays = 30
|
||||
$cleanupHours = 12
|
||||
|
||||
try {
|
||||
if ($script:Config.Logging) {
|
||||
if ($script:Config.Logging.PSObject.Properties.Name -contains "MaxFileSizeMB") {
|
||||
$v = [int]$script:Config.Logging.MaxFileSizeMB
|
||||
if ($v -ge 1 -and $v -le 1024) { $maxSizeMB = $v }
|
||||
}
|
||||
|
||||
if ($script:Config.Logging.PSObject.Properties.Name -contains "RetentionDays") {
|
||||
$v = [int]$script:Config.Logging.RetentionDays
|
||||
if ($v -ge 1 -and $v -le 3650) { $retentionDays = $v }
|
||||
}
|
||||
|
||||
if ($script:Config.Logging.PSObject.Properties.Name -contains "CleanupHours") {
|
||||
$v = [int]$script:Config.Logging.CleanupHours
|
||||
if ($v -ge 1 -and $v -le 168) { $cleanupHours = $v }
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return [pscustomobject]@{
|
||||
MaxFileSizeMB = $maxSizeMB
|
||||
RetentionDays = $retentionDays
|
||||
CleanupHours = $cleanupHours
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-LogMaintenance {
|
||||
param(
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
if (-not $script:Config -or -not $script:Config.Paths.Logs) {
|
||||
return
|
||||
}
|
||||
|
||||
$settings = Get-LogSettings
|
||||
$logDir = $script:Config.Paths.Logs
|
||||
|
||||
if (-not [IO.Path]::IsPathRooted($logDir)) {
|
||||
if ($PSScriptRoot) {
|
||||
$logDir = Join-Path $PSScriptRoot $logDir
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||||
|
||||
if (-not $script:NextLogCleanup) {
|
||||
$script:NextLogCleanup = [DateTime]::MinValue
|
||||
}
|
||||
|
||||
if (-not $Force -and (Get-Date) -lt $script:NextLogCleanup) {
|
||||
return
|
||||
}
|
||||
|
||||
$cutoff = (Get-Date).AddDays(-1 * $settings.RetentionDays)
|
||||
|
||||
foreach ($file in Get-ChildItem -LiteralPath $logDir -File -Filter "SMTPGraphRelay-*.log" -ErrorAction SilentlyContinue) {
|
||||
if ($file.LastWriteTime -lt $cutoff) {
|
||||
try {
|
||||
Remove-Item -LiteralPath $file.FullName -Force -ErrorAction Stop
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
$script:NextLogCleanup = (Get-Date).AddHours($settings.CleanupHours)
|
||||
}
|
||||
|
||||
function Rotate-LogIfNeeded {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$LogFile
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $LogFile)) {
|
||||
return
|
||||
}
|
||||
|
||||
$settings = Get-LogSettings
|
||||
|
||||
try {
|
||||
$file = Get-Item -LiteralPath $LogFile -ErrorAction Stop
|
||||
$maxBytes = [int64]$settings.MaxFileSizeMB * 1024 * 1024
|
||||
|
||||
if ($file.Length -lt $maxBytes) {
|
||||
return
|
||||
}
|
||||
|
||||
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$dir = Split-Path $LogFile -Parent
|
||||
$archive = Join-Path $dir "SMTPGraphRelay-$stamp.log"
|
||||
|
||||
$counter = 1
|
||||
while (Test-Path -LiteralPath $archive) {
|
||||
$archive = Join-Path $dir ("SMTPGraphRelay-{0}-{1}.log" -f $stamp, $counter)
|
||||
$counter++
|
||||
}
|
||||
|
||||
Move-Item -LiteralPath $LogFile -Destination $archive -Force
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
function Write-Log {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Message,
|
||||
@@ -31,28 +139,42 @@ function Write-Log {
|
||||
try {
|
||||
if ($script:Config -and $script:Config.Paths.Logs) {
|
||||
$logDir = $script:Config.Paths.Logs
|
||||
if (-not [IO.Path]::IsPathRooted($logDir)) { $logDir = Join-Path $PSScriptRoot $logDir }
|
||||
|
||||
if (-not [IO.Path]::IsPathRooted($logDir)) {
|
||||
if ($PSScriptRoot) {
|
||||
$logDir = Join-Path $PSScriptRoot $logDir
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||||
$logFile = Join-Path $logDir "SMTPGraphRelay.log"
|
||||
|
||||
# Mehrere SMTP-Runspaces und der Queue-Worker können gleichzeitig loggen.
|
||||
# Ein benannter Mutex verhindert kollidierende Schreibzugriffe.
|
||||
# Ein benannter Mutex schützt Rotation und Schreibzugriff gemeinsam.
|
||||
$mutex = New-Object System.Threading.Mutex($false, "Local\SMTPGraphRelay-Log")
|
||||
$lockTaken = $false
|
||||
|
||||
try {
|
||||
$lockTaken = $mutex.WaitOne(5000)
|
||||
|
||||
if ($lockTaken) {
|
||||
Rotate-LogIfNeeded -LogFile $logFile
|
||||
Add-Content -LiteralPath $logFile -Value $line -Encoding UTF8
|
||||
Invoke-LogMaintenance
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($lockTaken) {
|
||||
try { $mutex.ReleaseMutex() } catch {}
|
||||
}
|
||||
|
||||
$mutex.Dispose()
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
function Resolve-PathFromConfig {
|
||||
@@ -999,6 +1121,9 @@ $script:Config = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | Conv
|
||||
New-Item -ItemType Directory -Path (Resolve-PathFromConfig $script:Config.Paths.Logs) -Force | Out-Null
|
||||
Initialize-QueueDirectories
|
||||
|
||||
# Alte Logarchive beim Start direkt bereinigen.
|
||||
Invoke-LogMaintenance -Force
|
||||
|
||||
# Zertifikat beim Start immer prüfen und Status protokollieren.
|
||||
[void](Test-RelayCertificateExpiry -ForceLog)
|
||||
|
||||
@@ -1039,6 +1164,9 @@ $script:WorkerConfig = New-WorkerConfig
|
||||
# Nur die Funktionen, die ein SMTP-Client wirklich benötigt, werden in die
|
||||
# Worker-Runspaces kopiert. Graph-/Queue-Versand bleibt in einem separaten Worker.
|
||||
$smtpFunctionNames = @(
|
||||
"Get-LogSettings",
|
||||
"Invoke-LogMaintenance",
|
||||
"Rotate-LogIfNeeded",
|
||||
"Write-Log",
|
||||
"Resolve-PathFromConfig",
|
||||
"Test-IPv4InCidr",
|
||||
@@ -1072,6 +1200,9 @@ $script:ActiveSmtpWorkers = New-Object System.Collections.ArrayList
|
||||
# Damit ein langsamer Graph-Aufruf nicht mehr die Annahme neuer SMTP-Verbindungen
|
||||
# blockiert, läuft Process-Queue dauerhaft in einem eigenen Runspace.
|
||||
$queueFunctionNames = @(
|
||||
"Get-LogSettings",
|
||||
"Invoke-LogMaintenance",
|
||||
"Rotate-LogIfNeeded",
|
||||
"Write-Log",
|
||||
"Resolve-PathFromConfig",
|
||||
"Get-GraphConnection",
|
||||
@@ -1120,11 +1251,15 @@ $listenIp = [System.Net.IPAddress]::Parse($script:Config.Smtp.ListenAddress)
|
||||
$listener = [System.Net.Sockets.TcpListener]::new($listenIp, [int]$script:Config.Smtp.Port)
|
||||
$listener.Start()
|
||||
|
||||
Write-Log "SMTPGraphRelay V1.3 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
|
||||
Write-Log "SMTPGraphRelay V1.5 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
|
||||
Write-Log "Graph-Absender: $($script:Config.Graph.SenderMailbox)"
|
||||
Write-Log "Maximale parallele SMTP-Verbindungen: $script:MaxConcurrentClients"
|
||||
Write-Log "Queue-/Graph-Worker läuft separat vom SMTP-Listener."
|
||||
|
||||
$logSettings = Get-LogSettings
|
||||
Write-Log ("Log-Rotation: max. {0} MB pro Datei, Aufbewahrung {1} Tage." -f `
|
||||
$logSettings.MaxFileSizeMB, $logSettings.RetentionDays)
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
Remove-CompletedSmtpWorkers
|
||||
|
||||
Reference in New Issue
Block a user