Dateien nach "/" hochladen
This commit is contained in:
@@ -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"
|
||||
Reference in New Issue
Block a user