Dateien nach "/" hochladen

This commit is contained in:
2026-08-13 22:25:52 +02:00
parent 4b4d2b7f64
commit b5c994d970
4 changed files with 1090 additions and 0 deletions
+334
View File
@@ -0,0 +1,334 @@
#Requires -Version 5.1
#Requires -RunAsAdministrator
<#
.SYNOPSIS
First-Run Setup für SMTPGraphRelay.
.DESCRIPTION
- Installiert notwendige Microsoft Graph PowerShell Module
- Meldet interaktiv einen Entra-Administrator an
- Erstellt ein selbstsigniertes Zertifikat in LocalMachine\My
- Erstellt eine Entra ID App Registration
- Fügt das Zertifikat als Credential hinzu
- Vergibt Microsoft Graph Application Permission Mail.Send
- Erteilt Admin Consent über AppRoleAssignment
- Schreibt config.json
- Legt Firewallregel und Scheduled Task an
#>
[CmdletBinding()]
param()
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# ------------------------------------------------------------------------------
# WICHTIG: Dieses Setup muss mit Windows PowerShell 5.1 gestartet werden.
# Der eigentliche SMTPGraphRelay Scheduled Task verwendet ebenfalls powershell.exe
# (Windows PowerShell 5.1). Wird das Setup stattdessen mit PowerShell 7 / pwsh.exe
# ausgeführt, können Microsoft.Graph-Module im falschen Modulpfad landen und sind
# später für den SYSTEM-Task unter Windows PowerShell 5.1 nicht sichtbar.
# ------------------------------------------------------------------------------
if ($PSVersionTable.PSEdition -ne "Desktop" -or $PSVersionTable.PSVersion.Major -ne 5) {
Write-Host ""
Write-Host "==========================================================" -ForegroundColor Red
Write-Host " FALSCHE POWERSHELL-VERSION" -ForegroundColor Red
Write-Host "==========================================================" -ForegroundColor Red
Write-Host ""
Write-Host "Dieses Setup muss mit Windows PowerShell 5.1 ausgefuehrt werden." -ForegroundColor Yellow
Write-Host ""
Write-Host "Aktuell erkannt:" -ForegroundColor Cyan
Write-Host " Edition: $($PSVersionTable.PSEdition)"
Write-Host " Version: $($PSVersionTable.PSVersion)"
Write-Host ""
Write-Host "Bitte eine klassische Windows PowerShell oeffnen:" -ForegroundColor Cyan
Write-Host " C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
Write-Host ""
Write-Host "Danach das Setup dort erneut starten." -ForegroundColor Yellow
Write-Host ""
Read-Host "Enter druecken zum Beenden"
exit 1
}
Write-Host "Windows PowerShell erkannt: $($PSVersionTable.PSVersion)" -ForegroundColor Green
Write-Host ""
function Read-Default {
param([string]$Prompt, [string]$Default)
$value = Read-Host "$Prompt [Standard: $Default]"
if ([string]::IsNullOrWhiteSpace($value)) { return $Default }
return $value
}
function Convert-CertToKeyCredential {
param([Parameter(Mandatory)][System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate)
return @{
Type = "AsymmetricX509Cert"
Usage = "Verify"
Key = $Certificate.GetRawCertData()
DisplayName = "SMTPGraphRelay Certificate"
StartDateTime = $Certificate.NotBefore.ToUniversalTime()
EndDateTime = $Certificate.NotAfter.ToUniversalTime()
}
}
Write-Host "==========================================================" -ForegroundColor Cyan
Write-Host " SMTPGraphRelay - First Run / Entra ID Setup" -ForegroundColor Cyan
Write-Host "==========================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Schritt 0: Voraussetzungen prüfen..." -ForegroundColor Cyan
$modules = @(
"Microsoft.Graph.Authentication",
"Microsoft.Graph.Applications"
)
foreach ($module in $modules) {
if (-not (Get-Module -ListAvailable -Name $module)) {
Write-Host "-> $module fehlt. Wird für alle Benutzer installiert..." -ForegroundColor Yellow
Install-Module $module -Scope AllUsers -Repository PSGallery -Force -AllowClobber
}
}
Import-Module Microsoft.Graph.Authentication -ErrorAction Stop
Import-Module Microsoft.Graph.Applications -ErrorAction Stop
Write-Host "-> Voraussetzungen erfüllt." -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 1: Relay-Konfiguration erfassen" -ForegroundColor Cyan
$AppName = Read-Default "-> Name der Entra App" "SMTPGraphRelay"
$SenderMailbox = Read-Host "-> M365-Absenderpostfach (z.B. smtp-relay@firma.de)"
while ([string]::IsNullOrWhiteSpace($SenderMailbox) -or $SenderMailbox -notmatch '^[^@\s]+@[^@\s]+\.[^@\s]+$') {
$SenderMailbox = Read-Host "-> Bitte eine gültige Mailadresse eingeben"
}
$ListenAddress = Read-Default "-> Lokale Listen-IP" "0.0.0.0"
$Port = [int](Read-Default "-> SMTP-Port" "2525")
$AllowedNetworksText = Read-Default "-> Erlaubte Netze, mit Komma getrennt" "127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
$AllowedNetworks = @($AllowedNetworksText -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
Write-Host ""
Write-Host "Schritt 2: Erzeuge Zertifikat für unbeaufsichtigte Graph-Anmeldung..." -ForegroundColor Cyan
$certSubject = "CN=SMTPGraphRelay-$env:COMPUTERNAME"
$cert = New-SelfSignedCertificate `
-Subject $certSubject `
-CertStoreLocation "Cert:\LocalMachine\My" `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-HashAlgorithm SHA256 `
-KeyExportPolicy NonExportable `
-KeySpec Signature `
-NotAfter (Get-Date).AddYears(2)
Write-Host "-> Zertifikat erstellt: $($cert.Thumbprint)" -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 3: Mit Microsoft Graph anmelden..." -ForegroundColor Cyan
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
# Application.ReadWrite.All: App + Service Principal erstellen
# AppRoleAssignment.ReadWrite.All: Mail.Send als AppRoleAssignment (Admin Consent) erteilen
Connect-MgGraph -Scopes "Application.ReadWrite.All","AppRoleAssignment.ReadWrite.All" -NoWelcome
$TenantId = (Get-MgContext).TenantId
Write-Host "-> Verbunden mit Tenant: $TenantId" -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 4: Microsoft Graph Mail.Send Application Permission ermitteln..." -ForegroundColor Cyan
$GraphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
if (-not $GraphSp) { throw "Microsoft Graph Service Principal wurde im Tenant nicht gefunden." }
$MailSend = $GraphSp.AppRoles | Where-Object {
$_.Value -eq "Mail.Send" -and $_.AllowedMemberTypes -contains "Application"
} | Select-Object -First 1
if (-not $MailSend) { throw "Graph Application Permission Mail.Send wurde nicht gefunden." }
Write-Host "-> Mail.Send AppRole-ID: $($MailSend.Id)" -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 5: Entra ID App Registration erstellen..." -ForegroundColor Cyan
$AppParams = @{
DisplayName = $AppName
SignInAudience = "AzureADMyOrg"
RequiredResourceAccess = @(
@{
ResourceAppId = "00000003-0000-0000-c000-000000000000"
ResourceAccess = @(
@{
Id = $MailSend.Id
Type = "Role"
}
)
}
)
KeyCredentials = @(
(Convert-CertToKeyCredential -Certificate $cert)
)
}
$App = New-MgApplication -BodyParameter $AppParams
Write-Host "-> App erstellt: $($App.AppId)" -ForegroundColor Green
$Sp = $null
for ($i = 0; $i -lt 10 -and -not $Sp; $i++) {
try {
$Sp = New-MgServicePrincipal -AppId $App.AppId
} catch {
Start-Sleep -Seconds 2
}
}
if (-not $Sp) { throw "Service Principal konnte nicht erstellt werden." }
Write-Host "-> Service Principal erstellt: $($Sp.Id)" -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 6: Admin Consent für Mail.Send erteilen..." -ForegroundColor Cyan
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $Sp.Id `
-PrincipalId $Sp.Id `
-ResourceId $GraphSp.Id `
-AppRoleId $MailSend.Id | Out-Null
Write-Host "-> Mail.Send wurde als Application Permission erteilt." -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 7: config.json schreiben..." -ForegroundColor Cyan
$config = [ordered]@{
Smtp = [ordered]@{
ListenAddress = $ListenAddress
Port = $Port
Hostname = $env:COMPUTERNAME
AllowedNetworks = $AllowedNetworks
MaxMessageSizeMB = 25
ClientTimeoutSeconds = 120
}
Graph = [ordered]@{
TenantId = $TenantId
ClientId = $App.AppId
CertificateThumbprint = $cert.Thumbprint
SenderMailbox = $SenderMailbox
ForceSender = $true
}
Queue = [ordered]@{
PollSeconds = 10
MaxRetries = 8
RetryMinutes = @(1,5,15,30,60,120,240,480)
}
Paths = [ordered]@{
Queue = "queue"
Failed = "failed"
Logs = "logs"
}
}
$configPath = Join-Path $PSScriptRoot "config.json"
$config | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $configPath -Encoding UTF8
foreach ($dir in @("queue","failed","logs")) {
New-Item -ItemType Directory -Path (Join-Path $PSScriptRoot $dir) -Force | Out-Null
}
Write-Host "-> Konfiguration: $configPath" -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 8: Windows Firewall konfigurieren..." -ForegroundColor Cyan
$ruleName = "SMTPGraphRelay TCP $Port"
Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule `
-DisplayName $ruleName `
-Direction Inbound `
-Action Allow `
-Protocol TCP `
-LocalPort $Port `
-Profile Any | Out-Null
Write-Host "-> Firewallregel erstellt." -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 9: Autostart als Scheduled Task anlegen..." -ForegroundColor Cyan
$taskName = "SMTPGraphRelay"
$scriptPath = Join-Path $PSScriptRoot "SMTPGraphRelay.ps1"
$psExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$action = New-ScheduledTaskAction `
-Execute $psExe `
-Argument "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 1) `
-ExecutionTimeLimit ([TimeSpan]::Zero)
Register-ScheduledTask `
-TaskName $taskName `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Lokaler SMTP Store-and-Forward Relay zu Microsoft 365 via Microsoft Graph" `
-Force | Out-Null
Write-Host "-> Scheduled Task '$taskName' erstellt." -ForegroundColor Green
Write-Host ""
Write-Host "Schritt 10: Teste App-only Graph-Anmeldung..." -ForegroundColor Cyan
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
Connect-MgGraph `
-TenantId $TenantId `
-ClientId $App.AppId `
-Certificate $cert `
-NoWelcome | Out-Null
$ctx = Get-MgContext
if ($ctx.AuthType -ne "AppOnly") {
throw "App-only Graph-Anmeldung konnte nicht bestätigt werden."
}
Write-Host "-> App-only Graph-Anmeldung funktioniert." -ForegroundColor Green
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
Write-Host ""
Write-Host "==========================================================" -ForegroundColor Green
Write-Host " SMTPGraphRelay wurde eingerichtet." -ForegroundColor Green
Write-Host "==========================================================" -ForegroundColor Green
Write-Host ""
Write-Host " Tenant ID: $TenantId" -ForegroundColor Yellow
Write-Host " Client ID: $($App.AppId)" -ForegroundColor Yellow
Write-Host " Zertifikat: $($cert.Thumbprint)" -ForegroundColor Yellow
Write-Host " Graph-Absender: $SenderMailbox" -ForegroundColor Yellow
Write-Host " SMTP Listener: $ListenAddress`:$Port" -ForegroundColor Yellow
Write-Host ""
Write-Host "WICHTIG:" -ForegroundColor Yellow
Write-Host "Mail.Send als Application Permission ist tenantweit mächtig."
Write-Host "Diese V1 erzwingt lokal SenderMailbox='$SenderMailbox', begrenzt die"
Write-Host "Graph-Berechtigung selbst aber noch nicht per Exchange Application RBAC."
Write-Host ""
Write-Host "Task starten mit:" -ForegroundColor Cyan
Write-Host " Start-ScheduledTask -TaskName `"$taskName`""
Write-Host ""
Write-Host "Logs:" -ForegroundColor Cyan
Write-Host " $(Join-Path $PSScriptRoot 'logs\SMTPGraphRelay.log')"
Write-Host ""
$startNow = Read-Host "Relay jetzt starten? [J/n]"
if ([string]::IsNullOrWhiteSpace($startNow) -or $startNow -match '^(?i)j|ja|y|yes$') {
Start-ScheduledTask -TaskName $taskName
Start-Sleep -Seconds 2
Write-Host "-> Task gestartet." -ForegroundColor Green
}
Read-Host "Enter drücken zum Beenden"