mail-organizer/scripts/SharePointHelper.psm1

337 lines
9.8 KiB
PowerShell

# SharePointHelper.psm1 -- Modul fuer SharePoint-Dokumentbibliothek-Operationen via Graph API
function Resolve-SharePointSite {
<#
.SYNOPSIS
Loest eine SharePoint-Site-URL in eine Site-ID auf.
.PARAMETER Settings
Zugangsdaten-Hashtable (TenantId, ClientId, ClientSecret)
.PARAMETER SiteUrl
SharePoint-Site-URL (z.B. "krahgruppe.sharepoint.com" oder "krahgruppe.sharepoint.com:/sites/IT")
#>
param(
[Parameter(Mandatory)]
[hashtable]$Settings,
[Parameter(Mandatory)]
[string]$SiteUrl
)
# URL normalisieren
$siteUrl = $SiteUrl -replace '^https?://', ''
# Hostname und Pfad trennen
if ($siteUrl -match '^([^:/]+):?(.*)$') {
$hostname = $Matches[1].TrimEnd('/')
$sitePath = $Matches[2].TrimStart(':').TrimStart('/').TrimEnd('/')
}
if ($sitePath) {
$endpoint = "/sites/${hostname}:/${sitePath}"
} else {
$endpoint = "/sites/${hostname}"
}
$site = Invoke-GraphRequest -Endpoint $endpoint -Settings $Settings
Write-Log "SharePoint Site aufgeloest: $($site.displayName) (ID: $($site.id))" -Level Debug
return $site
}
function Get-DocumentLibrary {
<#
.SYNOPSIS
Findet eine Dokumentbibliothek (Drive) auf einer SharePoint-Site.
.PARAMETER LibraryName
Name oder relativer Pfad der Bibliothek
#>
param(
[Parameter(Mandatory)]
[hashtable]$Settings,
[Parameter(Mandatory)]
[string]$SiteId,
[string]$LibraryName = $null
)
$drives = Invoke-GraphRequest -Endpoint "/sites/$SiteId/drives" -Settings $Settings
if ($LibraryName) {
$drive = $drives.value | Where-Object {
$_.name -eq $LibraryName -or $_.webUrl -like "*$LibraryName*"
} | Select-Object -First 1
} else {
# Standard-Dokumentbibliothek
$drive = $drives.value | Select-Object -First 1
}
if (-not $drive) {
throw "Dokumentbibliothek '$LibraryName' nicht gefunden auf Site $SiteId"
}
Write-Log "Dokumentbibliothek gefunden: $($drive.name) (ID: $($drive.id))" -Level Debug
return $drive
}
function Upload-SharePointDocument {
<#
.SYNOPSIS
Laedt ein Dokument in eine SharePoint-Dokumentbibliothek hoch.
.PARAMETER DriveId
Die Drive-ID der Dokumentbibliothek
.PARAMETER TargetFolder
Zielordner innerhalb der Bibliothek (z.B. "2025/Wartung")
.PARAMETER FilePath
Lokaler Pfad zur Datei
.PARAMETER FileName
Optionaler Dateiname (Standard: Original-Dateiname)
.OUTPUTS
Das erstellte DriveItem-Objekt
#>
param(
[Parameter(Mandatory)]
[hashtable]$Settings,
[Parameter(Mandatory)]
[string]$DriveId,
[string]$TargetFolder = "",
[Parameter(Mandatory)]
[string]$FilePath,
[string]$FileName = $null
)
if (-not (Test-Path $FilePath)) {
throw "Datei nicht gefunden: $FilePath"
}
if (-not $FileName) {
$FileName = [IO.Path]::GetFileName($FilePath)
}
# Pfad zusammenbauen
$uploadPath = if ($TargetFolder) {
"$TargetFolder/$FileName"
} else {
$FileName
}
$uploadPath = $uploadPath -replace '\\', '/'
$endpoint = "/drives/$DriveId/root:/${uploadPath}:/content"
# Token holen
$token = Get-GraphToken -Settings $Settings
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/octet-stream"
}
$uri = "https://graph.microsoft.com/v1.0$endpoint"
$fileBytes = [IO.File]::ReadAllBytes($FilePath)
# Fuer Dateien > 4MB muesste ein Upload-Session verwendet werden
if ($fileBytes.Length -gt 4MB) {
Write-Log "Datei groesser als 4MB -- verwende Upload-Session" -Level Info
return Upload-LargeFile -Settings $Settings -DriveId $DriveId -UploadPath $uploadPath -FilePath $FilePath
}
try {
$response = Invoke-RestMethod -Uri $uri -Method PUT -Headers $headers -Body $fileBytes
Write-Log "Dokument hochgeladen: $uploadPath (ID: $($response.id))" -Level Info
return $response
}
catch {
Write-Log "Fehler beim Upload von '$FileName': $_" -Level Error
throw
}
}
function Upload-LargeFile {
<#
.SYNOPSIS
Laedt grosse Dateien (>4MB) per Upload-Session hoch.
#>
param(
[Parameter(Mandatory)]
[hashtable]$Settings,
[Parameter(Mandatory)]
[string]$DriveId,
[Parameter(Mandatory)]
[string]$UploadPath,
[Parameter(Mandatory)]
[string]$FilePath
)
# Upload-Session erstellen
$sessionEndpoint = "/drives/$DriveId/root:/${UploadPath}:/createUploadSession"
$session = Invoke-GraphRequest -Endpoint $sessionEndpoint -Method POST -Body @{
item = @{
"@microsoft.graph.conflictBehavior" = "rename"
}
} -Settings $Settings
$uploadUrl = $session.uploadUrl
$fileBytes = [IO.File]::ReadAllBytes($FilePath)
$fileSize = $fileBytes.Length
$chunkSize = 10MB
$offset = 0
while ($offset -lt $fileSize) {
$end = [Math]::Min($offset + $chunkSize - 1, $fileSize - 1)
$chunk = $fileBytes[$offset..$end]
$contentRange = "bytes $offset-$end/$fileSize"
$headers = @{
"Content-Range" = $contentRange
"Content-Length" = $chunk.Length
}
$response = Invoke-RestMethod -Uri $uploadUrl -Method PUT -Headers $headers -Body $chunk
$offset = $end + 1
}
Write-Log "Grosse Datei hochgeladen: $UploadPath" -Level Info
return $response
}
function Set-SharePointItemFields {
<#
.SYNOPSIS
Setzt Metadaten-Felder auf einem SharePoint-Listenelement / Dokument.
.PARAMETER SiteId
Die Site-ID
.PARAMETER ListId
Die Listen-/Bibliotheks-ID (kann auch der Drive-Name sein)
.PARAMETER ItemId
Die Item-ID (listItem.id vom DriveItem)
.PARAMETER Fields
Hashtable mit Feldnamen und Werten
.EXAMPLE
Set-SharePointItemFields -Settings $s -SiteId $sid -DriveId $did -DriveItemId $diid -Fields @{ Datum = "2025-11-28"; Kommentar = "Backup-Check" }
#>
param(
[Parameter(Mandatory)]
[hashtable]$Settings,
[Parameter(Mandatory)]
[string]$DriveId,
[Parameter(Mandatory)]
[string]$DriveItemId,
[Parameter(Mandatory)]
[hashtable]$Fields
)
$endpoint = "/drives/$DriveId/items/$DriveItemId/listItem/fields"
try {
$result = Invoke-GraphRequest -Endpoint $endpoint -Method PATCH -Body $Fields -Settings $Settings
Write-Log "Metadaten aktualisiert fuer Item $DriveItemId : $($Fields.Keys -join ', ')" -Level Info
return $result
}
catch {
Write-Log "Fehler beim Setzen der Metadaten: $_" -Level Error
throw
}
}
function Invoke-SharePointUpload {
<#
.SYNOPSIS
Hauptfunktion: Laedt eine Datei in eine SharePoint-Dokumentbibliothek
und setzt Metadaten anhand einer Target-Konfiguration.
.PARAMETER TargetConfig
Ein Target-Objekt aus sharepoint-targets.json
.PARAMETER FilePath
Lokaler Pfad zur Datei
.PARAMETER Metadata
Hashtable mit Metadaten-Werten (Keys = Feldnamen aus der Target-Config)
.PARAMETER SubFolder
Optionaler Unterordner (z.B. "{year}" wird aufgeloest)
#>
param(
[Parameter(Mandatory)]
[hashtable]$Settings,
[Parameter(Mandatory)]
$TargetConfig,
[Parameter(Mandatory)]
[string]$FilePath,
[hashtable]$Metadata = @{},
[string]$FileName = $null
)
# 1. Site aufloesen
$site = Resolve-SharePointSite -Settings $Settings -SiteUrl $TargetConfig.siteUrl
# 2. Dokumentbibliothek finden
$drive = Get-DocumentLibrary -Settings $Settings -SiteId $site.id -LibraryName $TargetConfig.libraryName
# 3. Zielordner bestimmen
$targetFolder = $TargetConfig.targetFolder
if ($targetFolder) {
# Platzhalter in Ordnerpfad aufloesen
$now = Get-Date
$targetFolder = $targetFolder `
-replace '\{year\}', $now.ToString("yyyy") `
-replace '\{month\}', $now.ToString("MM") `
-replace '\{date\}', $now.ToString("yyyy-MM-dd")
}
# 4. Datei hochladen
$driveItem = Upload-SharePointDocument `
-Settings $Settings `
-DriveId $drive.id `
-TargetFolder $targetFolder `
-FilePath $FilePath `
-FileName $FileName
# 5. Metadaten setzen (aus Config-Defaults + uebergebene Werte)
$allFields = @{}
# Defaults aus der Target-Config
if ($TargetConfig.defaultFields) {
foreach ($prop in $TargetConfig.defaultFields.PSObject.Properties) {
$value = $prop.Value
# Platzhalter ersetzen
$now = Get-Date
$value = $value `
-replace '\{today\}', $now.ToString("yyyy-MM-dd") `
-replace '\{now\}', $now.ToString("yyyy-MM-ddTHH:mm:ssZ") `
-replace '\{year\}', $now.ToString("yyyy") `
-replace '\{month\}', $now.ToString("MM") `
-replace '\{filename\}', [IO.Path]::GetFileNameWithoutExtension($FilePath)
$allFields[$prop.Name] = $value
}
}
# Uebergebene Metadaten (ueberschreiben Defaults)
foreach ($key in $Metadata.Keys) {
$allFields[$key] = $Metadata[$key]
}
if ($allFields.Count -gt 0) {
Set-SharePointItemFields `
-Settings $Settings `
-DriveId $drive.id `
-DriveItemId $driveItem.id `
-Fields $allFields
}
return @{
DriveItemId = $driveItem.id
WebUrl = $driveItem.webUrl
FileName = $driveItem.name
Fields = $allFields
}
}
Export-ModuleMember -Function Resolve-SharePointSite, Get-DocumentLibrary,
Upload-SharePointDocument, Set-SharePointItemFields, Invoke-SharePointUpload