# GraphHelper.psm1 -- Basis-Modul fuer Microsoft Graph API Zugriff # Verwendet Client Credentials Flow (Azure App Registration) $script:GraphToken = $null $script:TokenExpiry = [DateTime]::MinValue function Get-GraphToken { <# .SYNOPSIS Holt oder erneuert das OAuth2-Token fuer Microsoft Graph. .PARAMETER Settings Hashtable mit TenantId, ClientId, ClientSecret #> param( [Parameter(Mandatory)] [hashtable]$Settings ) # Token wiederverwenden wenn noch gueltig (5 Min Puffer) if ($script:GraphToken -and $script:TokenExpiry -gt (Get-Date).AddMinutes(5)) { return $script:GraphToken } $body = @{ grant_type = "client_credentials" client_id = $Settings.ClientId client_secret = $Settings.ClientSecret scope = "https://graph.microsoft.com/.default" } $uri = "https://login.microsoftonline.com/$($Settings.TenantId)/oauth2/v2.0/token" try { $response = Invoke-RestMethod -Uri $uri -Method POST -Body $body -ContentType "application/x-www-form-urlencoded" $script:GraphToken = $response.access_token $script:TokenExpiry = (Get-Date).AddSeconds($response.expires_in) Write-Log "Token erfolgreich geholt (gueltig bis $($script:TokenExpiry))" -Level Debug return $script:GraphToken } catch { Write-Log "FEHLER beim Token-Abruf: $_" -Level Error throw "Graph API Authentifizierung fehlgeschlagen: $_" } } function Invoke-GraphRequest { <# .SYNOPSIS Fuehrt einen authentifizierten Request gegen die Graph API aus. .PARAMETER Endpoint Relativer Pfad (z.B. "/users/user@domain.com/messages") .PARAMETER Method HTTP-Methode (GET, POST, PATCH, DELETE) .PARAMETER Body Optionaler Request-Body als Hashtable .PARAMETER Settings Zugangsdaten-Hashtable #> param( [Parameter(Mandatory)] [string]$Endpoint, [string]$Method = "GET", [hashtable]$Body = $null, [Parameter(Mandatory)] [hashtable]$Settings ) $token = Get-GraphToken -Settings $Settings $headers = @{ Authorization = "Bearer $token" "Content-Type" = "application/json" } $uri = "https://graph.microsoft.com/v1.0$Endpoint" $params = @{ Uri = $uri Method = $Method Headers = $headers } if ($Body) { $params.Body = ($Body | ConvertTo-Json -Depth 10) } try { $response = Invoke-RestMethod @params return $response } catch { $statusCode = $_.Exception.Response.StatusCode.value__ Write-Log "Graph API Fehler ($statusCode) bei $Method $Endpoint : $_" -Level Error throw } } function Get-MailMessages { <# .SYNOPSIS Liest E-Mails aus einer Mailbox, optional gefiltert. .PARAMETER Filter OData-Filter (z.B. "categories/any(c:c eq 'Wartung')") .PARAMETER Top Anzahl der E-Mails (Standard: 50) #> param( [Parameter(Mandatory)] [hashtable]$Settings, [string]$Filter = $null, [int]$Top = 50, [switch]$UnreadOnly ) $user = $Settings.MailboxUser $endpoint = "/users/$user/mailFolders/inbox/messages?`$top=$Top&`$orderby=receivedDateTime desc" $endpoint += "&`$select=id,subject,from,receivedDateTime,categories,hasAttachments,isRead,body" if ($UnreadOnly) { $filterParts = @("isRead eq false") } else { $filterParts = @() } if ($Filter) { $filterParts += $Filter } if ($filterParts.Count -gt 0) { $endpoint += "&`$filter=" + ($filterParts -join " and ") } return Invoke-GraphRequest -Endpoint $endpoint -Settings $Settings } function Get-MailAttachments { <# .SYNOPSIS Laedt Anhaenge einer bestimmten E-Mail herunter. .PARAMETER MessageId Die ID der E-Mail .PARAMETER TargetPath Zielverzeichnis fuer die Dateien #> param( [Parameter(Mandatory)] [hashtable]$Settings, [Parameter(Mandatory)] [string]$MessageId, [Parameter(Mandatory)] [string]$TargetPath ) $user = $Settings.MailboxUser $endpoint = "/users/$user/messages/$MessageId/attachments" $result = Invoke-GraphRequest -Endpoint $endpoint -Settings $Settings if (-not (Test-Path $TargetPath)) { New-Item -ItemType Directory -Path $TargetPath -Force | Out-Null } $savedFiles = @() foreach ($att in $result.value) { if ($att.'@odata.type' -eq '#microsoft.graph.fileAttachment') { $filePath = Join-Path $TargetPath $att.name $bytes = [Convert]::FromBase64String($att.contentBytes) [IO.File]::WriteAllBytes($filePath, $bytes) $savedFiles += $filePath Write-Log "Anhang gespeichert: $filePath" -Level Info } } return $savedFiles } function Send-NotificationMail { <# .SYNOPSIS Sendet eine Benachrichtigungs-E-Mail ueber Graph API. #> param( [Parameter(Mandatory)] [hashtable]$Settings, [Parameter(Mandatory)] [string]$To, [Parameter(Mandatory)] [string]$Subject, [Parameter(Mandatory)] [string]$Body ) $user = $Settings.MailboxUser $endpoint = "/users/$user/sendMail" $mailBody = @{ message = @{ subject = $Subject body = @{ contentType = "HTML" content = $Body } toRecipients = @( @{ emailAddress = @{ address = $To } } ) } saveToSentItems = $false } Invoke-GraphRequest -Endpoint $endpoint -Method POST -Body $mailBody -Settings $Settings Write-Log "Benachrichtigung gesendet an $To : $Subject" -Level Info } function Set-MessageRead { param( [Parameter(Mandatory)] [hashtable]$Settings, [Parameter(Mandatory)] [string]$MessageId ) $user = $Settings.MailboxUser $endpoint = "/users/$user/messages/$MessageId" Invoke-GraphRequest -Endpoint $endpoint -Method PATCH -Body @{ isRead = $true } -Settings $Settings | Out-Null } function Add-MessageCategory { param( [Parameter(Mandatory)] [hashtable]$Settings, [Parameter(Mandatory)] [string]$MessageId, [Parameter(Mandatory)] [string[]]$Categories ) $user = $Settings.MailboxUser $endpoint = "/users/$user/messages/$MessageId" # Bestehende Kategorien lesen und ergaenzen $msg = Invoke-GraphRequest -Endpoint $endpoint -Settings $Settings $existing = @($msg.categories) $merged = ($existing + $Categories) | Select-Object -Unique Invoke-GraphRequest -Endpoint $endpoint -Method PATCH -Body @{ categories = $merged } -Settings $Settings } function Move-Message { <# .SYNOPSIS Moves a message to a destination folder via Graph API. .PARAMETER DestinationFolder Well-known folder name (e.g. "archive", "inbox", "drafts") or folder ID. #> param( [Parameter(Mandatory)] [hashtable]$Settings, [Parameter(Mandatory)] [string]$MessageId, [Parameter(Mandatory)] [string]$DestinationFolder ) $user = $Settings.MailboxUser $endpoint = "/users/$user/messages/$MessageId/move" Invoke-GraphRequest -Endpoint $endpoint -Method POST -Body @{ destinationId = $DestinationFolder } -Settings $Settings | Out-Null Write-Log "Nachricht verschoben nach '$DestinationFolder'" -Level Info } Export-ModuleMember -Function Get-GraphToken, Invoke-GraphRequest, Get-MailMessages, Get-MailAttachments, Send-NotificationMail, Set-MessageRead, Add-MessageCategory, Move-Message