param( [Parameter(Mandatory)] [string] $InputPdf, [Parameter(Mandatory)] [string] $OutputPdf, [Parameter(Mandatory)] [string] $WatermarkPdf, [Parameter(Mandatory)] [string] $ITextDllDir, [Parameter(Mandatory)] [string] $LogFile, [Parameter(Mandatory)] [string] $AddDebugMarker ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $AddDebugMarkerBool = ($AddDebugMarker -eq "True") function Write-Log([string]$msg) { $line = "[{0}] {1}`r`n" -f (Get-Date), $msg try { [System.IO.File]::AppendAllText($LogFile, $line) } catch { # Fallback, falls LogFile kaputt/leer/nicht schreibbar ist $fallback = Join-Path $env:TEMP "pdf_worker_fallback.log" [System.IO.File]::AppendAllText($fallback, $line) } } function Import-IText7 { param([Parameter(Mandatory)] [string] $DllDir) if (-not (Test-Path $DllDir)) { throw "IText-DLL-Verzeichnis existiert nicht: $DllDir" } # PS7 / .NET: sauberes Laden über AssemblyLoadContext, ohne Resolve-Handler Add-Type -AssemblyName "System.Runtime.Loader" -ErrorAction Stop $alc = [System.Runtime.Loader.AssemblyLoadContext]::Default # Lade alle DLLs aus dem Ordner. Wichtig: erst commons/io, dann kernel/layout etc. $preferredOrder = @( "itext.commons.dll", "itext.io.dll", "itext.kernel.dll", "itext.layout.dll", "itext.barcodes.dll" ) # 1) bevorzugte zuerst (wenn vorhanden) foreach ($name in $preferredOrder) { $p = Join-Path $DllDir $name if (Test-Path $p) { $full = (Resolve-Path $p).Path [void]$alc.LoadFromAssemblyPath($full) } } # 2) dann alle übrigen DLLs (z.B. BouncyCastle Adapter/Abhängigkeiten), die noch nicht geladen sind $dlls = Get-ChildItem -LiteralPath $DllDir -Filter "*.dll" -File | Sort-Object Name foreach ($dll in $dlls) { try { $full = $dll.FullName # Wenn bereits geladen, skip (heuristisch über Name) $already = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -ieq [IO.Path]::GetFileNameWithoutExtension($dll.Name) } if ($already) { continue } [void]$alc.LoadFromAssemblyPath($full) } catch { # absichtlich leise: manche DLLs sind evtl. native/inkompatibel, wir wollen nicht hier abbrechen } } } function Add-StationeryWatermark { param( [Parameter(Mandatory)] [string] $InputPdf, [Parameter(Mandatory)] [string] $OutputPdf, [Parameter(Mandatory)] [string] $WatermarkPdf, [bool] $AddDebugMarker ) $PdfReaderType = [iText.Kernel.Pdf.PdfReader] $PdfWriterType = [iText.Kernel.Pdf.PdfWriter] $PdfDocType = [iText.Kernel.Pdf.PdfDocument] $PdfCanvasType = [iText.Kernel.Pdf.Canvas.PdfCanvas] $reader=$null; $writer=$null; $pdf=$null $wmReader=$null; $wmDoc=$null try { $reader = New-Object $PdfReaderType($InputPdf) $writer = New-Object $PdfWriterType($OutputPdf) $pdf = New-Object $PdfDocType($reader, $writer) $pages = $pdf.GetNumberOfPages() $wmReader = New-Object $PdfReaderType($WatermarkPdf) $wmDoc = New-Object $PdfDocType($wmReader) $wmPages = $wmDoc.GetNumberOfPages() Write-Log ("INFO InputPages={0} WatermarkPages={1} WM={2}" -f $pages, $wmPages, $WatermarkPdf) if ($wmPages -lt 1) { throw "Wasserzeichen-PDF hat keine Seiten: $WatermarkPdf" } $wmFirstXObj = $wmDoc.GetPage(1).CopyAsFormXObject($pdf) $wmNextXObj = if ($wmPages -ge 2) { $wmDoc.GetPage(2).CopyAsFormXObject($pdf) } else { $wmFirstXObj } for ($p=1; $p -le $pages; $p++) { $page = $pdf.GetPage($p) $canvas = New-Object $PdfCanvasType($page.NewContentStreamBefore(), $page.GetResources(), $pdf) $use = if ($pages -eq 1 -or $p -eq 1) { 1 } else { 2 } if ($use -eq 1) { $canvas.AddXObjectAt($wmFirstXObj, 0, 0) | Out-Null } else { $canvas.AddXObjectAt($wmNextXObj, 0, 0) | Out-Null } # Debug-Marker: macht sofort sichtbar, ob Seite 2 wirklich benutzt wird if ($AddDebugMarker) { $canvas2 = New-Object $PdfCanvasType($page.NewContentStreamAfter(), $page.GetResources(), $pdf) $canvas2.BeginText() | Out-Null $canvas2.SetFontAndSize([iText.Kernel.Font.PdfFontFactory]::CreateFont(), 8) | Out-Null $canvas2.MoveText(10, 10) | Out-Null $canvas2.ShowText(("WM{0}" -f $use)) | Out-Null $canvas2.EndText() | Out-Null $canvas2.Release() } $canvas.Release() } } finally { foreach ($o in @($wmDoc,$pdf,$wmReader,$reader,$writer)) { try { if ($null -ne $o) { $o.Close() } } catch {} } } } try { Write-Log ("START Input={0} Out={1}" -f $InputPdf, $OutputPdf) if (-not (Test-Path $InputPdf)) { throw "Input-PDF nicht gefunden: $InputPdf" } if (-not (Test-Path $WatermarkPdf)) { throw "Wasserzeichen nicht gefunden: $WatermarkPdf" } if (-not (Test-Path $ITextDllDir)) { throw "ITextDllDir nicht gefunden: $ITextDllDir" } Import-IText7 -DllDir $ITextDllDir Add-StationeryWatermark -InputPdf $InputPdf -OutputPdf $OutputPdf -WatermarkPdf $WatermarkPdf -AddDebugMarker:$AddDebugMarkerBool Write-Log "OK" exit 0 } catch { Write-Log ("FAIL :: {0}" -f $_.Exception.Message) $e = $_.Exception while ($e) { Write-Log ("EX: {0}: {1}" -f $e.GetType().FullName, $e.Message) $e = $e.InnerException } exit 2 }