Anleitungen

Bitwarden/Vaultwarden


Die Zwei-Faktor-Authentifizierung (2FA) mit Vaultwarden einrichten

Hier erfahrt ihr, wie man die neue, erzwungene Zwei-Faktor-Authentifizierung von Microsoft mit Vaultwarden erledigen könnt ohne euch eine zusätzliche Authenticator-App zu installieren.

Die Zwei-Faktor-Authentifizierung (2FA) mit Vaultwarden einrichten

Die böse Microsoft-Anmeldung

Ihr kennt es alle. Man möchte sich nichtsahnend in seinen Microsoft-Account einloggen, weil Outlook mal wieder zickt und schon kommt diese geheimnisvolle Meldung, in der Microsoft mitteilt, dass es hier nicht weiter geht, bevor man nicht einige zusätzliche Schritte getan hat.

Was genau Microsoft von euch will und wie ihr das mit Hilfe von Vaultwarden erledigen könnt, ist hier nachzulesen. Auch als Bilderbuch.

image.png

Bis dahin kommen wir ja noch ganz normal. Wir klicken auf anmelden und jetzt das...

Bildschirmfoto 2024-07-30 um 13.06.39.png

Wir denken uns nichts dabei und klicken auf WeiterAber oh Graus, was ist das?

image.png

Und hier geht unsere Anleitung dann so richtig los.

Wir klicken NICHT auf Weiter sondern auf Ich möchte eine andere Authentifikator-App verwenden, was uns dann hierhin führt:

image.png

Hier klicken wir natürlich auf Weiter , was uns dann hierhin führt:

image.png

Den QR-Code scannen wir NICHT sondern klicken auf Das Bild wird nicht gescannt?

image.png

Wir klicken in diesem Bild dann auf dasimage.pngSymbol. Das kopiert uns den geheimen Schlüssel in die Zwischenablage. (Zum Glück, sonst müssten wir das Kauderwelsch auch noch abtippen.)

Jetzt öffnen wir ein neues Browserfenster oder einen neuen Tab und öffnen unseren Vaultwarden unter https://pwd.schlichter.biz

Bildschirmfoto 2024-07-30 um 13.08.38.png

Hier geben wir natürlich unsere -Mail-Adresse an

image.png

gefolgt von unserem Passwort und schon sind wir drin...

Wir suchen uns den Eintrag von Microsoft Office und klicken drauf

image.png

In das Feld Authentifizierungsschlüssel (TOTP) fügen wir den kopierten geheimen Schlüssel ein. Entweder mit Strg+V oder per Rechtsklick in das Feld und dann Einfügen. Anschliessend klicken wir auf Speichern

image.png

Jetzt sehen wir in dem Feld dahinter einen sechsstelligen Zahlen-Code den wir uns kopieren. Ab jetzt müssen wir uns etwas beeilen, da sich der Code alle 30 Sekunden ändert.

image.pngJetzt nochmal auf Weiter geklickt...

image.png

Jetzt ein letztes Mal auf Fertig klicken und ihr seid drin. War doch gar nicht so schlimm...

Und nein, das müsst ihr nicht jedes Mal machen. Nach dieser Einrichtung meldet ihr euch wie gewohnt an, und wenn der Code abgefragt wird kopiert ihr nur den sechstelligen Zahlencode und fügt den dann ein. 

Dieser Code gilt für alle Microsoft-Apps und auch ie Webseiten office.com und teams.com.

Auch hier gilt: Einloggen mit E-Mail und Kennwort und bei Abfrage den Code auf den Vaultwarden.

Viel Erfolg!

E-Mail für dich

Hier wird kurz beschrieben, wie man eine E-Mail verfasst und die Felder richtig benutzt.

E-Mail für dich

Eine Mail verfassen - Am Beispiel von Outlook

IT-Support und wie Ihr unterstützen könnt

Eine Anleitung zur Erstellung korrekter Supportanfragen per Mail

IT-Support und wie Ihr unterstützen könnt

Eine Support-E-Mail verfassen

Im Prinzip verhält es sich mit einer Support-Anfrage wie mit dem Anruf bei der Notrufzentrale. Je genauer eure Angaben sind, desto schneller kann euch geholfen werden.

Meldungen wie "Hier geht nix!" oder "Tut nich!" sind nicht unbedingt hilfreich.

AN:

Also fangen wir mal vorne an, mit der korrekten E-Mail-Adresse. Wenn ihr schnelle Hilfe wünscht, schickt eure Mail an administrator@schlichter.biz

in dieses Postfach gehen dann alle Anfragen gesammtelt ein und werden von allen Admins der Reihe nach gelesen und abgearbeitet. Niemand wird bevorzugt und auch niemand übergangen. Bei uns sind alle gleich.

CC: und BCC:

Diese Felder könnt ihr ignorieren. Wen auch immer ihr da mit hinzuziehen wollt, vorgezogen werdet ihr trotzdem nicht.

BETREFF: 

Hier wird's das erste Mal interessant. Im Betreff sollte schon der erste Hinweis auf euer Problem stehen. Mit diesen Hinweisen können wir eure Anfragen auch immer wiederfinden und auch feststellen, wie oft eine bestimmte Anfrage von verschiedenen Kollegen kommt. Und nein, auch hier kommt nicht "Tut nich" rein. Richtig wäre z.B.: "Outlook startet nicht", "Fehlermeldung in Unitrade" oder "Kennwort vergessen".

NACHRICHTENTEXT:

Hier beschreibt bitte ganz sachlich euer Problem, den Fehler oder euer Anliegen nach folgendem Schema:

Was ist passiert? Was hast Du gemacht bzw. wo hast du geklickt oder getippt, als der Fehler auftrat? Passiert das bei anderen Kollegen auch? 

Emotionen, Flüche und Verwünschungen gehören hier nicht rein. Stattdessen macht lieber einen Screenshot oder ein Screenrecording von dem Fehler. Screenshots sind hilfreich bei einer bestehenden Fehlermeldung, die ihr selbst nicht bestätigen oder wegklicken könnt. Screenrecordings sind hilfreich bei reproduzierbaren Fehlern, also Fehlern die bei den gleichen Tätigkeiten an der gleichen Stelle wieder auftreten.

Wenn dann alles passt, sollte das Ganze ungefähr so aussehen:

grafik.png

Auf Senden klicken und auf Support warten

Screenshot und Screenrecording

Screenshot und Screenrecording

Screenshot erstellen

Ein Screenshot ist ein wunderbares Hilfsmittel um einen Programmzustand festzuhalten z.B. im Falle einer Fehlermeldung. So etwas ist sehr Hilfreich um dem Support einen Zustand oder Umstand mitzuteilen, der sich für den normalen User nicht oder nur schwer in Worte fassen lässt. Getreu dem Motto: Ein Bild sagt mehr als tausend Worte!

Und so könnt ihr Screenshots erstellen

Windows

Unter Windows gibt es mehrere Arten um einen Screenshot zu erstellen.

  1. Ihr sucht auf eurer Tastatur die Taste Druck. Das löst automatisch einen Screenshot eures Bildschirms aus, Windows macht also quasi ein Foto und legt es in die Zwischenablage. Von hier kann es mit Str+V in die Mail eingefügt werden.
  2. Das geht auch noch differenzierter indem man die Tasten Shift + Druck betätigt. Damit wird dann nur das aktive Fenster geknipst. Der Rest funktioniert wie bei Punkt 1
  3. Das Snipping Tool. Zu finden im Startmenü unter Zubehör. Hier kann man einen Rahmen um den Bereich ziehen, den man knipsen möchte.

iPhone/iPad

Bei modernen iPhones und iPads macht man einen Screenshot indem man die Lauter-Taste zusammen mit der Power-Taste drückt. Man hört dann auch ein Auslösegeräusch wie bei einer Kamera. Das Bild wird in der Foto-App abgelegt und kann von da aus versendet werden.

Android

Machen wir nicht.... 

Screenshot und Screenrecording

Screenrecord erstellen

Ein Screenrecord ist die Videoversion des Screenshots. Die meisten aktuellen Windows-Versionen haben ein Tool integriert um einen solchen Screenrecord aufzuzeichnen. Das Tool wurde zwar eigentlich zur Aufzeichnung von Spielesitzungen entwickelt, erfüllt aber auch hier seinen Zweck.

Drückt einfach die Windows-Taste und G. Das startet die Gamebar und am rechten Bildschirmrand findet ihr einen Aufnahmeknopf. Wenn ihr fertig seid, stoppt ihr die Aufnahme über eben diesen Knopf und das Video wird auf der Festplatte unter Videos abgespeichert.

AddOne

AddOne

Nachunternehmer-Bescheinigungen & Ablage im Doku-Archiv

Nachunternehmer-Bescheinigungen sind Dokumente, die von Subunternehmern bereitgestellt werden, um ihre Eignung und Qualifikation für einen Auftrag nachzuweisen.

01 Rechtliche Informationen (vom 04.09.2024)

Karl Schlichter hat die wichtigsten Stellen des folgenden Schreibens markiert, es aber nochmal einfach und für alle verständlich zusammengefasst:

Um nicht in die Haftung für Nachunternehmer zu kommen, muss ein Auftraggeber Unbedenklichkeitsbescheinigungen der Krankenkassen und eine qualifizierte (= mit Angabe der geltenden Betriebsteile mit den zugehörigen Lohnsummen der Unfallversicherung) Unbedenklichektsbescheinigungen der Unfallversicherung (BG) alle 3 Monate vorliegen.

Für den Zeitraum der Beauftragung des NUs muss der Nachweis lückenlos sein!!

>> Haftung gilt ab einer Gesamtsumme der beauftragten NU-Leistungen eines Hauptauftrages > 275 TEUR

Die Haftung des Bauunternehmers für den Nachunternehmer - Teil1 (08/2024)

2024-09-23 Nachunternehmer-Bescheinigungen & Ablage im Doku Archiv_1.png

2024-09-23 Nachunternehmer-Bescheinigungen & Ablage im Doku Archiv_2.png

2024-09-23 Nachunternehmer-Bescheinigungen & Ablage im Doku Archiv_3.png

2024-09-23 Nachunternehmer-Bescheinigungen & Ablage im Doku Archiv_4.png

02 Hinweise zu Bescheinigungen und Gültigkeit

Nachfolgend die Informationen aus dem Merkblatt vom 10.07.2024, wie die Bescheinigungen im Programm AddOne abgelegt werden sollen:

01 Betriebshaftpflicht
     - Ohne Ausnahme Pflicht
     - Gültigkeit 3 Monate ab Ausstellungsdatum, sofern nicht anders beschrieben

02 Berufsgenossenschaft
     - Nicht notwenidig bei NU mit Sitz im Ausland
     - Nicht notwendig, wenn keine Mitarbeiter vorhanden
     - Gültigkeit steht auf der Bescheinigung (ca. 6 - 12 Monate)

03 Krankenkasse
     - Nicht notwendig, wenn keine Mitarbeiter vorhanden
     - NU mit Sitz im EU-Ausland müssen eine A1 Bescheinigung vorlegen (Gültigkeit A1: Dauer des Einsatzes, längstens 24 Monate)
     - Gültigkeit 3 Monate, wenn nicht anders beschrieben

04 Finanzamt / Bescheinigung in Steuersachen
     - Ohne Ausnahme Pfllicht
     - NU mit Sitz im Ausland haben zugewiesene Finanzämter siehe PDF
     - Gültigkeit 3 Monate

05 Handwerksrolle
     - Nicht notwendig, wenn Gewerbeanmeldung vorhanden
     - Gültigkeit für immer

06 Gewerbeanmeldung
     - Ohne Ausnahme Pflicht
     - Gültigkeit für immer

07 Freistellungsbescheinigung zum Steuerabzug bei Bauleistung §48b
     - Ohne Ausnahme Pflicht
     - Gültigkeit steht auf der Bescheinigung (ca. 1 Jahr)

08 SOKA BAU
     - NU mit Sitz im Ausland: Abteilungsleiter fragen
     - Nicht notwendig, wenn keine Mitarbeiter vorhanden
     - Gültigkeit 1 Jahr, wenn nicht anders beschrieben

09 Diverse / Sonstige Bescheinigungen & Unterlagen
     - z. B. Nachweis Steuerschuldnerschaft §13 (Gültigkeit ca. 6 Monate)
     - Gewerbezantralregister / Gewerbekarte
     - Ausweise / Versichertenkarten
     - Eintragungen ins Handelsregister / HRA
     - Erteilung von Steuernummern
     - Führungszeugnis
     - Wartungsverträge
     Unter 09 darf alles abgelegt werden, was sonst noch unter Bescheinigungen abgelegt werden muss.

10 Selbstauskunft & Checkliste NU
     - gültig immer bis zum 31.12. des laufenden Jahres

03 Bearbeitung in AddOne

03.1 Ablage im Dokumenten-Archiv

Kategorie: 01.03.07.01: aktuelle Bescheinigungen

Ablage im Doku-Archiv.png

z. B.:

Name:

01 Betriebshaftpflicht bis 25.07.2024 (Datum = Gültigkeit)
(01 - 10: Nummernvergabe vom Merkblatt)

Kategorie:
01.03.07.01 Aktuelle Bescheinigungen
Datum:
1 Tag VOR Ablaufdatum
Kostenstelle:
4: Dummy für Bescheinigungen
Adresse:
Firmenname auswählen

Wichtig!! Wenn eine Bescheinigung abgelaufen ist, unbedingt in abgelaufene Bescheinigungen (Kategorie: 01.03.07.02) verschieben!

Sonst ist es üblich, dass bei Dokumenten-Ablage im Dokumenten-Archiv das Datum im amerikanischen Format davor geschrieben wird. Das muss in diesem Fall (ausnahmsweise) nicht davor geschrieben werden, da es hier irrelevant ist.

03.2 Eintragung unter NU-/Lieferantendaten

Nach erfolgter Ablage im Dokumentenarchiv werden die Daten im Adressstamm des NUs eingepflegt.

> Add One
> Adressen
> NU auswählen
> Adressen-Details
> 3. NU-/Lieferantendaten

Die Reihenfolge in AddOne folgt der Nummerierung von 01 bis 08, wie sie unter "02 Allgemeine Hinweise zu Bescheinigungen und Gültigkeit" angegeben ist. Für jede Bescheinigung werden das Beginn- und Enddatum der aktuellsten Version eingetragen. Bei Bescheinigungen, die kein Enddatum haben (wie beispielsweise bei einer Gewerbeanmeldung), wird der 01.01.2999 als Enddatum verwendet. In der Spalte "Herkunft" wird der Absender der Bescheinigung vermerkt.

NU = Nachunternehmer

Kohlhammer-Übertragung

Kohlhammer-Übertragung

GWS-Kohlhammer-Drucker einrichten

Um den PDF-Druck zu Kohlhammer mit Textextrahierbarer PDF zu realisieren muss ein Script auf dem jeweiligen Server eingerichtet werden.

Voraussetzungen installieren

.NET SDK

Zuerst muss das Microsoft .NET SDK installiert werden. Ob alles richtig installiert ist, kann man mit diesem Befehl prüfen:

dotnet --info

Powershell 7

Zur korrekten Ausführung benötigen wir auch Powershell 7, das man hier laden kann.

Ordnerstruktur anlegen

Danach legen wir die Ordnerstruktur an. Das kann automatisch mit diesen Befehlen erfolgen:

$folders = @(
    "C:\PDF\IN",
    "C:\PDF\OUT",
    "C:\PDF\ERROR",
    "C:\PDF\TEMP"
)

foreach ($folder in $folders) {
    New-Item -ItemType Directory -Path $folder -Force | Out-Null
}
$work = "C:\Tools\itext_bundle"
New-Item -ItemType Directory -Path $work -Force | Out-Null
Set-Location $work

Den Installationspfad kann man je nach Umgebung natürlich anpassen.

Mini-Projekt erstellen

Die folgenden Befehle führen wir alle in Poershell 7 aus.

dotnet new console -n ITextBundle
Set-Location "$work\ITextBundle"

iText + Adapter als NuGet-Pakete hinzufügen

dotnet add package itext --version 9.5.0
dotnet add package itext.bouncy-castle-adapter --version 9.5.0

jetzt müssen wir das Ganze noch in einen Publish-Ordner ausgeben

$pub = "$work\publish"
dotnet publish -c Release -o $pub

jetzt noch prüfen, ob die notwendigen Abhängigkeiten vorhanden sind:

Get-ChildItem $pub -Filter "Microsoft.Extensions.Logging.dll" | Select Name, FullName
Get-ChildItem $pub -Filter "itext*.dll" | Select Name
Get-ChildItem $pub -Filter "*BouncyCastle*.dll" | Select Name

Die eingentlichen Powershell-Scripte:

pdf_worker.ps1
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") #Dies setzt, den wieder aktiviert, einen Debug-Marker, der unten Links WM1 oder WM2 andruckt, damit man sehen kann, ob die richtigen Seiten des Briefpapiers genutzt werden.

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
}

Dazu brauchen wir dann noch das Ausführungs-Script.

run_once.ps1

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$InputDir        = "C:\PDF\IN"
$ExportDir       = "C:\PDF\OUT"
$ErrorDir        = "C:\PDF\ERROR"
$TempDir         = "C:\PDF\TEMP"

$WatermarkPdf    = "C:\PDF\briefpapier.pdf"      # MUSS 2 Seiten haben (1+2)
$ITextDllDir     = "C:\Tools\itext_bundle\publish"
$WorkerScript    = "C:\PDF\pdf_worker.ps1"

$DeleteOriginalAfterSuccess = $true
$AddDebugMarker = $true   # setzt klein "WM1"/"WM2" ins PDF, damit man sieht, ob die richtige Siete gewählt wurde. Auf $false setzen, wenn fertig

function Initialize-Directories {
  foreach ($d in @($InputDir, $ExportDir, $ErrorDir, $TempDir)) {
    if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d | Out-Null }
  }
}

function New-SafeOutputName {
  param([Parameter(Mandatory)] [string] $InputPath, [Parameter(Mandatory)] [string] $OutDir)
  $base = [IO.Path]::GetFileNameWithoutExtension($InputPath)
  $ext  = [IO.Path]::GetExtension($InputPath)
  $out  = Join-Path $OutDir ($base + $ext)
  if (-not (Test-Path $out)) { return $out }
  $ts = Get-Date -Format "yyyyMMdd_HHmmss_fff"
  return Join-Path $OutDir ("{0}_{1}{2}" -f $base, $ts, $ext)
}

function Quote-Arg([string]$s) {
  if ($null -eq $s) { return '""' }
  '"' + ($s -replace '"','\"') + '"'
}

Initialize-Directories

if (-not (Test-Path $WorkerScript)) { throw "Worker-Script fehlt: $WorkerScript" }
if (-not (Test-Path $WatermarkPdf)) { throw "Wasserzeichen-Datei fehlt: $WatermarkPdf" }
if (-not (Test-Path $ITextDllDir))  { throw "ITextDllDir fehlt: $ITextDllDir" }

$runLog = Join-Path $ErrorDir ("run_{0}.log" -f (Get-Date -Format "yyyyMMdd_HHmmss"))
# Logfile garantiert anlegen, damit “keine Log-Datei” ausgeschlossen ist
[System.IO.File]::WriteAllText($runLog, ("[{0}] RUN START`r`n" -f (Get-Date)))

$files = @(Get-ChildItem -LiteralPath $InputDir -Filter "*.pdf" -File -Force | Sort-Object LastWriteTime)
if ($files.Count -eq 0) {
  Write-Host "Keine PDFs gefunden in: $InputDir"
  Write-Host "Log: $runLog"
  exit 0
}

Write-Host ("Gefunden: {0} PDF(s)" -f $files.Count)
Write-Host "Log: $runLog"

foreach ($file in $files) {
  $tempOut  = New-SafeOutputName -InputPath $file.FullName -OutDir $TempDir
  $finalOut = New-SafeOutputName -InputPath $file.FullName -OutDir $ExportDir

  Write-Host ("Worker startet: {0}" -f $file.Name)

  # WICHTIG: Argumente als EIN String mit sicherem Quoting -> keine Param-Zerlegung bei Leerzeichen
  $argString = @(
    "-NoProfile",
    "-ExecutionPolicy Bypass",
    "-File", (Quote-Arg $WorkerScript),
    "-InputPdf", (Quote-Arg $file.FullName),
    "-OutputPdf", (Quote-Arg $tempOut),
    "-WatermarkPdf", (Quote-Arg $WatermarkPdf),
    "-ITextDllDir", (Quote-Arg $ITextDllDir),
    "-LogFile", (Quote-Arg $runLog),
    "-AddDebugMarker", (Quote-Arg ($AddDebugMarker.ToString()))
  ) -join " "

  $p = Start-Process -FilePath "pwsh.exe" -ArgumentList $argString -Wait -PassThru -WindowStyle Hidden

  if ($p.ExitCode -eq 0 -and (Test-Path $tempOut)) {
    Move-Item -LiteralPath $tempOut -Destination $finalOut -Force
    if ($DeleteOriginalAfterSuccess) { Remove-Item -LiteralPath $file.FullName -Force }
    Write-Host ("OK: {0}" -f $file.Name)
  }
  else {
    if (Test-Path $tempOut) { Remove-Item -LiteralPath $tempOut -Force -ErrorAction SilentlyContinue }
    $errDest = New-SafeOutputName -InputPath $file.FullName -OutDir $ErrorDir
    try { Move-Item -LiteralPath $file.FullName -Destination $errDest -Force } catch {}
    Write-Host ("FAIL (ExitCode {0}): {1}" -f $p.ExitCode, $file.Name)
  }
}

Write-Host "Fertig."
Write-Host "Log: $runLog"

Hier können in den ersten Zeilen die Pfade angepasst werden.

Diese beiden Scripte müssen in den PDF-Ordner gelegt werden und run_once.ps1 zur gewünschten Zeit ausgeführt werden, am Besten per Taskplaner.

Das das Script etwas braucht, bis alle PDFs gelesen/geschrieben sind, werden, deshalb werden die Dateien etwas zeitversetzt durch ein separates Script in eine .zip-Datei verpackt und per FTP zu Kohlhammer übertragen.




Kohlhammer-Übertragung

komprimieren in .ZIP

Die Rechnungen, Gutschriften und Mahnungen müssen in einer ZIP-Datei zu Kohlhammer übertragen werden.

Dazu erfasst ein Script alle fertigen PDF-Dateien, egal ob Gutschrift oder Rechnung, und fügt sie in ein ZIP-Archiv mit dem Dateinamen <Date>_schlichter.zip ein. Das Datum wird als Präfix gesetzt, damit, wenn durch Urlaub, Feiertage usw. Rechnungen nicht unmittelbar gedruckt werden können, die noch nicht verarbeitete Datei durch eine neue ersetzt wird und die vorherigen Rechnungen dadurch verloren sind.

Zusätzlich werden die PDF-Dateien in ein separates Dokumentenarchiv gelesen und dann gelöscht.