Dateien nach "/" hochladen
This commit is contained in:
@@ -1,66 +1,133 @@
|
||||
# SMTPGraphRelay V1.2 – Queue & Retry
|
||||
# SMTPGraphRelay V1.3 – parallele SMTP-Verbindungen
|
||||
|
||||
Diese Version ersetzt nur `SMTPGraphRelay.ps1`. Die bestehende `config.json`, Entra-App,
|
||||
das Zertifikat und Exchange Application RBAC bleiben unverändert.
|
||||
V1.3 baut auf V1.2 auf. Entra-App, Zertifikat, Exchange Application RBAC und
|
||||
`config.json` können unverändert weiterverwendet werden.
|
||||
|
||||
## Neue Queue-Struktur
|
||||
## Neu
|
||||
|
||||
```text
|
||||
queue\
|
||||
├── incoming\
|
||||
├── pending\
|
||||
└── processing\
|
||||
- bis zu 20 parallele SMTP-Verbindungen standardmäßig
|
||||
- konfigurierbares Parallel-Limit über `Smtp.MaxConcurrentClients`
|
||||
- SMTP-Clients laufen in einem .NET Runspace Pool
|
||||
- Queue-/Graph-Versand läuft in einem eigenen Runspace
|
||||
- ein langsamer Graph-Aufruf blockiert die SMTP-Annahme nicht mehr
|
||||
- ein langsamer/hängender SMTP-Client blockiert andere Clients nicht mehr
|
||||
- bei erreichtem Parallel-Limit erhält ein neuer Client:
|
||||
`421 4.3.2 SMTPGraphRelay busy, try again later`
|
||||
- Logging ist mit einem benannten Mutex gegen parallele Schreibzugriffe geschützt
|
||||
- Queue-/Retry-Logik aus V1.2 bleibt erhalten
|
||||
|
||||
failed\
|
||||
## Optional: Parallel-Limit konfigurieren
|
||||
|
||||
Bestehende `config.json` muss nicht geändert werden. Ohne Eintrag gilt `20`.
|
||||
|
||||
Optional:
|
||||
|
||||
```json
|
||||
"Smtp": {
|
||||
"ListenAddress": "0.0.0.0",
|
||||
"Port": 2525,
|
||||
"Hostname": "SMTPRELAY01",
|
||||
"AllowedNetworks": [
|
||||
"127.0.0.1/32",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"MaxMessageSizeMB": 25,
|
||||
"ClientTimeoutSeconds": 120,
|
||||
"MaxConcurrentClients": 20
|
||||
}
|
||||
```
|
||||
|
||||
- `incoming`: Mail wird gerade atomar geschrieben.
|
||||
- `pending`: vollständig angenommene und sendbare Mail.
|
||||
- `processing`: aktuell durch den Worker bearbeitet.
|
||||
- `failed`: permanent fehlgeschlagene oder nach MaxRetries aufgegebene Mail.
|
||||
Erlaubter Bereich: 1 bis 200. Ungültige Werte fallen auf 20 zurück.
|
||||
|
||||
Beim Start werden alte V1-Mails direkt aus `queue\` nach `pending\` migriert.
|
||||
Mails, die nach einem Absturz noch in `processing\` liegen, werden nach `pending\`
|
||||
zurückgestellt.
|
||||
|
||||
## Retry
|
||||
|
||||
- HTTP 429: `Retry-After` wird verwendet; fehlt es, exponentielles Backoff.
|
||||
- HTTP 408 und 5xx: Retry nach `RetryMinutes` aus `config.json`.
|
||||
- Netzwerk-/Transportfehler ohne HTTP-Code: Retry.
|
||||
- typische permanente 4xx wie 400/401/403/404/413/415/422: direkt nach `failed`.
|
||||
- nach `MaxRetries`: nach `failed`.
|
||||
|
||||
## Installation über bestehende Version
|
||||
|
||||
1. Scheduled Task stoppen:
|
||||
## Update
|
||||
|
||||
```powershell
|
||||
Stop-ScheduledTask -TaskName "SMTPGraphRelay"
|
||||
|
||||
Copy-Item `
|
||||
"C:\Program Files\SMTPGraphRelay\SMTPGraphRelay.ps1" `
|
||||
"C:\Program Files\SMTPGraphRelay\SMTPGraphRelay-V1.2-backup.ps1"
|
||||
```
|
||||
|
||||
2. Bestehendes `SMTPGraphRelay.ps1` sichern.
|
||||
3. `SMTPGraphRelay-V1.2.ps1` als `SMTPGraphRelay.ps1` in den Programmordner kopieren.
|
||||
4. Task starten:
|
||||
Dann `SMTPGraphRelay-V1.3.ps1` als
|
||||
|
||||
```text
|
||||
C:\Program Files\SMTPGraphRelay\SMTPGraphRelay.ps1
|
||||
```
|
||||
|
||||
ablegen und starten:
|
||||
|
||||
```powershell
|
||||
Start-ScheduledTask -TaskName "SMTPGraphRelay"
|
||||
```
|
||||
|
||||
5. Log prüfen:
|
||||
Log:
|
||||
|
||||
```powershell
|
||||
Get-Content "C:\Program Files\SMTPGraphRelay\logs\SMTPGraphRelay.log" -Tail 100
|
||||
Get-Content `
|
||||
"C:\Program Files\SMTPGraphRelay\logs\SMTPGraphRelay.log" `
|
||||
-Tail 100
|
||||
```
|
||||
|
||||
## Hinweis zu 202 Accepted
|
||||
Beim Start sollten u. a. erscheinen:
|
||||
|
||||
Microsoft Graph `sendMail` liefert bei erfolgreicher Annahme `202 Accepted`. Das bedeutet,
|
||||
dass Graph die Nachricht angenommen hat, aber nicht, dass die endgültige Zustellung bereits
|
||||
abgeschlossen ist.
|
||||
```text
|
||||
SMTPGraphRelay V1.3 gestartet ...
|
||||
Maximale parallele SMTP-Verbindungen: 20
|
||||
Queue-/Graph-Worker läuft separat vom SMTP-Listener.
|
||||
```
|
||||
|
||||
Eine absolut garantierte Exactly-Once-Zustellung kann ein SMTP→Graph-Gateway nicht
|
||||
sicherstellen: Falls Graph die Mail bereits angenommen hat und der lokale Prozess exakt
|
||||
vor dem Löschen der Queue-Datei abstürzt, kann ein erneuter Versuch theoretisch ein Duplikat
|
||||
erzeugen. V1.2 reduziert dieses Risiko durch die Processing-Queue, kann es aber nicht
|
||||
vollständig eliminieren.
|
||||
## Test für Parallelität
|
||||
|
||||
In mehreren PowerShell-Fenstern gleichzeitig:
|
||||
|
||||
```powershell
|
||||
Send-MailMessage `
|
||||
-SmtpServer 127.0.0.1 `
|
||||
-Port 2525 `
|
||||
-From "info@maieredv.de" `
|
||||
-To "manuel.maier@maieredv.de" `
|
||||
-Subject "Parallel-Test" `
|
||||
-Body "SMTPGraphRelay V1.3"
|
||||
```
|
||||
|
||||
Für einen härteren Test können mehrere Jobs gleichzeitig gestartet werden:
|
||||
|
||||
```powershell
|
||||
1..10 | ForEach-Object {
|
||||
Start-Job -ArgumentList $_ -ScriptBlock {
|
||||
param($n)
|
||||
Send-MailMessage `
|
||||
-SmtpServer 127.0.0.1 `
|
||||
-Port 2525 `
|
||||
-From "info@maieredv.de" `
|
||||
-To "manuel.maier@maieredv.de" `
|
||||
-Subject "Parallel-Test $n" `
|
||||
-Body "Nachricht $n"
|
||||
}
|
||||
}
|
||||
|
||||
Get-Job | Wait-Job | Receive-Job
|
||||
Get-Job | Remove-Job
|
||||
```
|
||||
|
||||
## Architektur
|
||||
|
||||
```text
|
||||
+--> SMTP Worker 1 --+
|
||||
SMTP Listener -----------+--> SMTP Worker 2 --+--> pending\
|
||||
+--> SMTP Worker N --+
|
||||
Runspace Pool
|
||||
|
|
||||
| unabhängig
|
||||
v
|
||||
Queue / Graph Worker
|
||||
|
|
||||
v
|
||||
Microsoft Graph
|
||||
```
|
||||
|
||||
Der SMTP-Listener nimmt dadurch weiter neue Verbindungen an, während andere Clients
|
||||
noch `DATA` übertragen oder Microsoft Graph gerade langsam antwortet.
|
||||
|
||||
+283
-14
@@ -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.2: robuste Queue, statuscodeabhängiger Graph-Retry, EHLO/HELO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT
|
||||
V1.3: parallele SMTP-Clients, separater Queue-Worker, robuste Queue, statuscodeabhängiger Graph-Retry
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
@@ -33,7 +33,24 @@ function Write-Log {
|
||||
$logDir = $script:Config.Paths.Logs
|
||||
if (-not [IO.Path]::IsPathRooted($logDir)) { $logDir = Join-Path $PSScriptRoot $logDir }
|
||||
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||||
Add-Content -LiteralPath (Join-Path $logDir "SMTPGraphRelay.log") -Value $line -Encoding UTF8
|
||||
$logFile = Join-Path $logDir "SMTPGraphRelay.log"
|
||||
|
||||
# Mehrere SMTP-Runspaces und der Queue-Worker können gleichzeitig loggen.
|
||||
# Ein benannter Mutex verhindert kollidierende Schreibzugriffe.
|
||||
$mutex = New-Object System.Threading.Mutex($false, "Local\SMTPGraphRelay-Log")
|
||||
$lockTaken = $false
|
||||
try {
|
||||
$lockTaken = $mutex.WaitOne(5000)
|
||||
if ($lockTaken) {
|
||||
Add-Content -LiteralPath $logFile -Value $line -Encoding UTF8
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($lockTaken) {
|
||||
try { $mutex.ReleaseMutex() } catch {}
|
||||
}
|
||||
$mutex.Dispose()
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
@@ -785,6 +802,120 @@ function Handle-SmtpClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-FunctionBootstrap {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string[]]$FunctionNames
|
||||
)
|
||||
|
||||
$parts = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($name in $FunctionNames) {
|
||||
$item = Get-Item -LiteralPath ("Function:\{0}" -f $name) -ErrorAction Stop
|
||||
$parts.Add(("function {0} {{`r`n{1}`r`n}}" -f $name, $item.Definition))
|
||||
}
|
||||
|
||||
return ($parts -join "`r`n`r`n")
|
||||
}
|
||||
|
||||
function New-WorkerConfig {
|
||||
# Runspaces haben keinen verlässlichen $PSScriptRoot des Hauptskripts.
|
||||
# Deshalb werden alle Pfade für Worker einmal absolut aufgelöst.
|
||||
$copy = $script:Config | ConvertTo-Json -Depth 20 | ConvertFrom-Json
|
||||
|
||||
$copy.Paths.Queue = Resolve-PathFromConfig $script:Config.Paths.Queue
|
||||
$copy.Paths.Failed = Resolve-PathFromConfig $script:Config.Paths.Failed
|
||||
$copy.Paths.Logs = Resolve-PathFromConfig $script:Config.Paths.Logs
|
||||
|
||||
return $copy
|
||||
}
|
||||
|
||||
function Send-ServiceBusyAndClose {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[System.Net.Sockets.TcpClient]$Client
|
||||
)
|
||||
|
||||
try {
|
||||
$stream = $Client.GetStream()
|
||||
$writer = New-Object System.IO.StreamWriter($stream, [System.Text.Encoding]::ASCII, 1024, $true)
|
||||
$writer.NewLine = "`r`n"
|
||||
$writer.AutoFlush = $true
|
||||
$writer.WriteLine("421 4.3.2 SMTPGraphRelay busy, try again later")
|
||||
$writer.Flush()
|
||||
$writer.Dispose()
|
||||
$stream.Dispose()
|
||||
}
|
||||
catch {}
|
||||
finally {
|
||||
try { $Client.Close() } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-CompletedSmtpWorkers {
|
||||
for ($i = $script:ActiveSmtpWorkers.Count - 1; $i -ge 0; $i--) {
|
||||
$worker = $script:ActiveSmtpWorkers[$i]
|
||||
|
||||
if ($worker.AsyncResult.IsCompleted) {
|
||||
try {
|
||||
[void]$worker.PowerShell.EndInvoke($worker.AsyncResult)
|
||||
}
|
||||
catch {
|
||||
Write-Log ("SMTP-Worker für {0} wurde mit Fehler beendet: {1}" -f $worker.RemoteAddress, $_.Exception.Message) "WARN"
|
||||
}
|
||||
finally {
|
||||
try { $worker.PowerShell.Dispose() } catch {}
|
||||
$script:ActiveSmtpWorkers.RemoveAt($i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Start-SmtpClientWorker {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[System.Net.Sockets.TcpClient]$Client
|
||||
)
|
||||
|
||||
Remove-CompletedSmtpWorkers
|
||||
|
||||
$remoteAddress = "unknown"
|
||||
try {
|
||||
$remoteAddress = ([System.Net.IPEndPoint]$Client.Client.RemoteEndPoint).Address.ToString()
|
||||
} catch {}
|
||||
|
||||
if ($script:ActiveSmtpWorkers.Count -ge $script:MaxConcurrentClients) {
|
||||
Write-Log ("SMTP-Verbindung von {0} abgewiesen: Parallel-Limit {1} erreicht." -f $remoteAddress, $script:MaxConcurrentClients) "WARN"
|
||||
Send-ServiceBusyAndClose -Client $Client
|
||||
return
|
||||
}
|
||||
|
||||
$ps = [System.Management.Automation.PowerShell]::Create()
|
||||
$ps.RunspacePool = $script:SmtpRunspacePool
|
||||
|
||||
[void]$ps.AddScript($script:SmtpWorkerScript)
|
||||
[void]$ps.AddArgument($Client)
|
||||
[void]$ps.AddArgument($script:WorkerConfig)
|
||||
|
||||
try {
|
||||
$async = $ps.BeginInvoke()
|
||||
|
||||
[void]$script:ActiveSmtpWorkers.Add([pscustomobject]@{
|
||||
PowerShell = $ps
|
||||
AsyncResult = $async
|
||||
Client = $Client
|
||||
RemoteAddress = $remoteAddress
|
||||
StartedUtc = [DateTime]::UtcNow
|
||||
})
|
||||
}
|
||||
catch {
|
||||
try { $ps.Dispose() } catch {}
|
||||
try { $Client.Close() } catch {}
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ConfigPath)) {
|
||||
throw "Konfiguration nicht gefunden: $ConfigPath. Bitte zuerst Setup-SMTPGraphRelay.ps1 ausführen."
|
||||
}
|
||||
@@ -794,33 +925,171 @@ $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
|
||||
|
||||
# MaxConcurrentClients ist optional, damit bestehende config.json-Dateien unverändert weiterlaufen.
|
||||
$script:MaxConcurrentClients = 20
|
||||
if ($script:Config.Smtp.PSObject.Properties.Name -contains "MaxConcurrentClients") {
|
||||
try {
|
||||
$configuredMax = [int]$script:Config.Smtp.MaxConcurrentClients
|
||||
if ($configuredMax -ge 1 -and $configuredMax -le 200) {
|
||||
$script:MaxConcurrentClients = $configuredMax
|
||||
}
|
||||
else {
|
||||
Write-Log "Ungültiges Smtp.MaxConcurrentClients; verwende Standard 20." "WARN"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "Smtp.MaxConcurrentClients konnte nicht gelesen werden; verwende Standard 20." "WARN"
|
||||
}
|
||||
}
|
||||
|
||||
$script:WorkerConfig = New-WorkerConfig
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SMTP Runspace Pool
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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 = @(
|
||||
"Write-Log",
|
||||
"Resolve-PathFromConfig",
|
||||
"Test-IPv4InCidr",
|
||||
"Test-ClientAllowed",
|
||||
"Get-QueueDirectories",
|
||||
"Save-SmtpMessage",
|
||||
"Write-SmtpLine",
|
||||
"Handle-SmtpClient"
|
||||
)
|
||||
|
||||
$smtpBootstrap = Get-FunctionBootstrap -FunctionNames $smtpFunctionNames
|
||||
$script:SmtpWorkerScript = @"
|
||||
param(`$Client, `$Config)
|
||||
`$script:Config = `$Config
|
||||
|
||||
$smtpBootstrap
|
||||
|
||||
Handle-SmtpClient -Client `$Client
|
||||
"@
|
||||
|
||||
$script:SmtpRunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(
|
||||
1,
|
||||
$script:MaxConcurrentClients
|
||||
)
|
||||
$script:SmtpRunspacePool.Open()
|
||||
$script:ActiveSmtpWorkers = New-Object System.Collections.ArrayList
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Separater Queue-/Graph-Worker
|
||||
# ---------------------------------------------------------------------------
|
||||
# Damit ein langsamer Graph-Aufruf nicht mehr die Annahme neuer SMTP-Verbindungen
|
||||
# blockiert, läuft Process-Queue dauerhaft in einem eigenen Runspace.
|
||||
$queueFunctionNames = @(
|
||||
"Write-Log",
|
||||
"Resolve-PathFromConfig",
|
||||
"Get-GraphConnection",
|
||||
"Set-MimeSender",
|
||||
"Get-QueueDirectories",
|
||||
"Get-GraphFailureInfo",
|
||||
"Get-RetryDecision",
|
||||
"Move-QueueItem",
|
||||
"Send-QueuedMail",
|
||||
"Process-Queue"
|
||||
)
|
||||
|
||||
$queueBootstrap = Get-FunctionBootstrap -FunctionNames $queueFunctionNames
|
||||
$queueWorkerScript = @"
|
||||
param(`$Config)
|
||||
`$script:Config = `$Config
|
||||
|
||||
$queueBootstrap
|
||||
|
||||
try {
|
||||
while (`$true) {
|
||||
try {
|
||||
Process-Queue
|
||||
}
|
||||
catch {
|
||||
Write-Log ("Queue-Worker: {0}" -f `$_.Exception.Message) "ERROR"
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds ([int]`$script:Config.Queue.PollSeconds)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
try { Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||
}
|
||||
"@
|
||||
|
||||
$script:QueuePowerShell = [System.Management.Automation.PowerShell]::Create()
|
||||
[void]$script:QueuePowerShell.AddScript($queueWorkerScript)
|
||||
[void]$script:QueuePowerShell.AddArgument($script:WorkerConfig)
|
||||
$script:QueueAsyncResult = $script:QueuePowerShell.BeginInvoke()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Listener
|
||||
# ---------------------------------------------------------------------------
|
||||
$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 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
|
||||
Write-Log "SMTPGraphRelay V1.3 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
|
||||
Write-Log "Graph-Absender: $($script:Config.Graph.SenderMailbox)"
|
||||
|
||||
$lastQueueRun = [DateTime]::MinValue
|
||||
Write-Log "Maximale parallele SMTP-Verbindungen: $script:MaxConcurrentClients"
|
||||
Write-Log "Queue-/Graph-Worker läuft separat vom SMTP-Listener."
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
if ((Get-Date) -gt $lastQueueRun.AddSeconds([int]$script:Config.Queue.PollSeconds)) {
|
||||
try { Process-Queue } catch { Write-Log "Queue-Worker: $($_.Exception.Message)" "ERROR" }
|
||||
$lastQueueRun = Get-Date
|
||||
Remove-CompletedSmtpWorkers
|
||||
|
||||
# Sollte der Queue-Worker unerwartet beendet werden, Relay nicht still
|
||||
# ohne Versand weiterlaufen lassen.
|
||||
if ($script:QueueAsyncResult.IsCompleted) {
|
||||
try {
|
||||
[void]$script:QueuePowerShell.EndInvoke($script:QueueAsyncResult)
|
||||
throw "Queue-Worker wurde unerwartet beendet."
|
||||
}
|
||||
catch {
|
||||
throw "Queue-Worker wurde unerwartet beendet: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($listener.Pending()) {
|
||||
$client = $listener.AcceptTcpClient()
|
||||
# V1 verarbeitet Clients seriell. Für typische Geräte-/Monitoring-Relays bewusst simpel.
|
||||
Handle-SmtpClient -Client $client
|
||||
} else {
|
||||
Start-Sleep -Milliseconds 200
|
||||
Start-SmtpClientWorker -Client $client
|
||||
}
|
||||
else {
|
||||
Start-Sleep -Milliseconds 100
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$listener.Stop()
|
||||
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
|
||||
Write-Log "SMTPGraphRelay wird beendet..." "INFO"
|
||||
|
||||
try { $listener.Stop() } catch {}
|
||||
|
||||
# Neue Clients werden nicht mehr angenommen; bestehende Sessions schließen.
|
||||
foreach ($worker in @($script:ActiveSmtpWorkers)) {
|
||||
try { $worker.Client.Close() } catch {}
|
||||
try { $worker.PowerShell.Stop() } catch {}
|
||||
try {
|
||||
if ($worker.AsyncResult) {
|
||||
[void]$worker.PowerShell.EndInvoke($worker.AsyncResult)
|
||||
}
|
||||
} catch {}
|
||||
try { $worker.PowerShell.Dispose() } catch {}
|
||||
}
|
||||
$script:ActiveSmtpWorkers.Clear()
|
||||
|
||||
try { $script:SmtpRunspacePool.Close() } catch {}
|
||||
try { $script:SmtpRunspacePool.Dispose() } catch {}
|
||||
|
||||
try { $script:QueuePowerShell.Stop() } catch {}
|
||||
try {
|
||||
if ($script:QueueAsyncResult) {
|
||||
[void]$script:QueuePowerShell.EndInvoke($script:QueueAsyncResult)
|
||||
}
|
||||
} catch {}
|
||||
try { $script:QueuePowerShell.Dispose() } catch {}
|
||||
|
||||
try { Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||
Write-Log "SMTPGraphRelay beendet."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user