diff --git a/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1 b/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1 new file mode 100644 index 0000000..78a9790 --- /dev/null +++ b/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1 @@ -0,0 +1,452 @@ +#Requires -Version 5.1 + +param( + [switch]$SkipSelfUpdateCheck +) + +<# +.SYNOPSIS + KANBAN-Rational-Druck mit Self-Update, Private-Config-Ladung, SQL-Abfrage und n8n-Webhook. + +.DESCRIPTION + Ablauf: + 1. Optional Self-Update aus dem Public-Repo + 2. AZ_GITHUB_TOKEN prüfen + 3. Private Konfiguration aus dem Private-Repo laden + 4. Genau eine Datei aus Input-Ordner lesen + 5. Dateiname ohne Endung als Belegnummer verwenden + 6. SQL-Abfrage ausführen + 7. Ergebnis an n8n senden + 8. Logging in Datei + +.NOTES + Repo: AZ-PowerShell-Pub +#> + +$ErrorActionPreference = 'Stop' +$script:ScriptName = 'KANBAN-Rational-Druck' +$script:PublicRawUrl = 'https://git.az-gruppe.com/AZ-Intec-GmbH/AZ-PowerShell-Pub/raw/branch/main/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1' +$script:PrivateRawUrl = 'https://git.az-gruppe.com/AZ-Intec-GmbH/AZ-PowerShell-Prv/raw/branch/main/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1' +$script:LogFilePath = $null + +#region Hilfsfunktionen + +function Write-ConsoleLog +{ + param( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet('INFO', 'WARN', 'ERROR', 'SUCCESS')] + [string]$Level = 'INFO' + ) + + $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $line = "[$timestamp] [$Level] $Message" + Write-Host $line +} + +function Write-Log +{ + param( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet('INFO', 'WARN', 'ERROR', 'SUCCESS')] + [string]$Level = 'INFO' + ) + + $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $line = "[$timestamp] [$Level] $Message" + + Write-Host $line + + if ($script:LogFilePath) + { + Add-Content -Path $script:LogFilePath -Value $line -Encoding UTF8 + } +} + +function Get-StringHashSha256 +{ + param( + [Parameter(Mandatory = $true)] + [string]$Text + ) + + $sha = [System.Security.Cryptography.SHA256]::Create() + try + { + $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text) + $hashBytes = $sha.ComputeHash($bytes) + return ([System.BitConverter]::ToString($hashBytes)).Replace('-', '') + } finally + { + $sha.Dispose() + } +} + +function Test-AzGithubToken +{ + if ([string]::IsNullOrWhiteSpace($env:AZ_GITHUB_TOKEN)) + { + throw "❌ Umgebungsvariable AZ_GITHUB_TOKEN ist nicht gesetzt." + } +} + +function Start-SelfUpdateIfNeeded +{ + param( + [Parameter(Mandatory = $true)] + [string]$RemoteScriptUrl + ) + + if ($SkipSelfUpdateCheck) + { + Write-ConsoleLog "Self-Update-Prüfung wurde übersprungen (Restart nach Update)." "INFO" + return + } + + $localScriptPath = $MyInvocation.MyCommand.Path + if ([string]::IsNullOrWhiteSpace($localScriptPath)) + { + throw "❌ Lokaler Skriptpfad konnte nicht ermittelt werden." + } + + Write-ConsoleLog "Prüfe auf neue Public-Version aus Git..." "INFO" + + try + { + $remoteResponse = Invoke-WebRequest -Uri $RemoteScriptUrl -UseBasicParsing + $remoteContent = $remoteResponse.Content + + if ([string]::IsNullOrWhiteSpace($remoteContent)) + { + throw "Remote-Skriptinhalt ist leer." + } + + $localContent = Get-Content -Path $localScriptPath -Raw -Encoding UTF8 + + $localHash = Get-StringHashSha256 -Text $localContent + $remoteHash = Get-StringHashSha256 -Text $remoteContent + + if ($localHash -eq $remoteHash) + { + Write-ConsoleLog "✓ Public-Skript ist bereits aktuell." "SUCCESS" + return + } + + Write-ConsoleLog "⚠ Neue Public-Version gefunden. Aktualisiere lokales Skript..." "WARN" + + $tempUpdatedScript = Join-Path $env:TEMP ("{0}-{1}.ps1" -f $script:ScriptName, [guid]::NewGuid().ToString()) + Set-Content -Path $tempUpdatedScript -Value $remoteContent -Encoding UTF8 + + Copy-Item -Path $tempUpdatedScript -Destination $localScriptPath -Force + + Write-ConsoleLog "✓ Lokales Public-Skript wurde aktualisiert." "SUCCESS" + Write-ConsoleLog "Starte aktualisierte Version neu..." "INFO" + + $argumentList = @( + '-ExecutionPolicy', 'Bypass', + '-File', ('"{0}"' -f $localScriptPath), + '-SkipSelfUpdateCheck' + ) + + Start-Process -FilePath 'powershell.exe' -ArgumentList $argumentList | Out-Null + + Write-ConsoleLog "Aktuelle Instanz wird beendet, damit die neue Version ausgeführt wird." "INFO" + exit 0 + } catch + { + throw "❌ Fehler bei der Self-Update-Prüfung des Public-Skripts: $($_.Exception.Message)" + } +} + +function Get-PrivateConfig +{ + param( + [Parameter(Mandatory = $true)] + [string]$RawConfigUrl + ) + + $token = $env:AZ_GITHUB_TOKEN + $headers = @{ + Authorization = "token $token" + } + + Write-ConsoleLog "Lade private Konfiguration aus dem Private-Repo..." "INFO" + + try + { + $response = Invoke-WebRequest -Uri $RawConfigUrl -Headers $headers -UseBasicParsing + $content = $response.Content + + $tempFile = Join-Path $env:TEMP ("KANBAN-Rational-Druck-private-{0}.ps1" -f ([guid]::NewGuid().ToString())) + Set-Content -Path $tempFile -Value $content -Encoding UTF8 + + . $tempFile + + Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue + + if (-not $script:PrivateConfig) + { + throw "Private Konfiguration wurde geladen, aber `$script:PrivateConfig ist nicht gesetzt." + } + + return $script:PrivateConfig + } catch + { + throw "❌ Fehler beim Laden der privaten Konfiguration: $($_.Exception.Message)" + } +} + +function Test-RequiredConfig +{ + param( + [Parameter(Mandatory = $true)] + [hashtable]$Config + ) + + $requiredKeys = @( + 'InputFolder', + 'LogFolder', + 'WebhookUrl', + 'SqlServer', + 'SqlDatabase', + 'SqlUser', + 'SqlPassword', + 'SqlQuery', + 'SqlParameterName' + ) + + foreach ($key in $requiredKeys) + { + if (-not $Config.ContainsKey($key) -or [string]::IsNullOrWhiteSpace([string]$Config[$key])) + { + throw "❌ Fehlende Konfiguration: '$key'" + } + } +} + +function Get-SingleInputFile +{ + param( + [Parameter(Mandatory = $true)] + [string]$InputFolder + ) + + if (-not (Test-Path -Path $InputFolder)) + { + throw "❌ Input-Ordner existiert nicht: $InputFolder" + } + + $files = Get-ChildItem -Path $InputFolder -File + + if ($files.Count -eq 0) + { + throw "❌ Keine Datei im Input-Ordner gefunden: $InputFolder" + } + + if ($files.Count -gt 1) + { + throw "❌ Es wurden mehrere Dateien gefunden. Erwartet wird genau eine Datei im Ordner: $InputFolder" + } + + return $files[0] +} + +$SqlPasswordEncode = [System.Text.Encoding]::UTF8.GetString( + [System.Convert]::FromBase64String($Config.SqlPassword) +) + +function Invoke-SqlTopOneQuery +{ + param( + [Parameter(Mandatory = $true)] + [hashtable]$Config, + + [Parameter(Mandatory = $true)] + [string]$Belegnummer + ) + + Write-Log "Starte SQL-Abfrage mit Parameter $($Config.SqlParameterName) = '$Belegnummer'..." "INFO" + + $connectionString = "Server={0};Database={1};User ID={2};Password={3};" -f ` + $Config.SqlServer, + $Config.SqlDatabase, + $Config.SqlUser, + $SqlPasswordEncode + + $connection = New-Object System.Data.SqlClient.SqlConnection + $connection.ConnectionString = $connectionString + + try + { + $connection.Open() + + $command = $connection.CreateCommand() + $command.CommandText = $Config.SqlQuery + $command.CommandTimeout = 60 + + [void]$command.Parameters.AddWithValue($Config.SqlParameterName, $Belegnummer) + + $adapter = New-Object System.Data.SqlClient.SqlDataAdapter $command + $dataTable = New-Object System.Data.DataTable + [void]$adapter.Fill($dataTable) + + if ($dataTable.Rows.Count -eq 0) + { + Write-Log "⚠ Kein SQL-Datensatz gefunden." "WARN" + return $null + } + + $row = $dataTable.Rows[0] + $result = [ordered]@{} + + foreach ($column in $dataTable.Columns) + { + $value = $row[$column.ColumnName] + if ($value -eq [System.DBNull]::Value) + { + $result[$column.ColumnName] = $null + } else + { + $result[$column.ColumnName] = $value + } + } + + Write-Log "✓ SQL-Datensatz erfolgreich gelesen." "SUCCESS" + return [pscustomobject]$result + } catch + { + throw "❌ Fehler bei SQL-Abfrage: $($_.Exception.Message)" + } finally + { + if ($connection.State -ne [System.Data.ConnectionState]::Closed) + { + $connection.Close() + } + $connection.Dispose() + } +} + +function Send-N8nWebhook +{ + param( + [Parameter(Mandatory = $true)] + [string]$WebhookUrl, + + [Parameter(Mandatory = $true)] + [hashtable]$Payload + ) + + Write-Log "Sende Payload an n8n-Webhook..." "INFO" + + try + { + $json = $Payload | ConvertTo-Json -Depth 10 + $null = Invoke-RestMethod -Uri $WebhookUrl -Method Post -ContentType 'application/json' -Body $json + Write-Log "✓ Webhook erfolgreich gesendet." "SUCCESS" + } catch + { + throw "❌ Fehler beim Senden des Webhooks: $($_.Exception.Message)" + } +} + +#endregion + +#region Hauptlogik + +try +{ + Start-SelfUpdateIfNeeded -RemoteScriptUrl $script:PublicRawUrl + + Test-AzGithubToken + + $config = Get-PrivateConfig -RawConfigUrl $script:PrivateRawUrl + Test-RequiredConfig -Config $config + + if (-not (Test-Path -Path $config.LogFolder)) + { + New-Item -Path $config.LogFolder -ItemType Directory -Force | Out-Null + } + + $script:LogFilePath = Join-Path $config.LogFolder ("KANBAN-Rational-Druck_{0}.log" -f (Get-Date -Format 'yyyy-MM-dd')) + + Write-Log "Starte Skriptausführung KANBAN-Rational-Druck..." "INFO" + + $inputFile = Get-SingleInputFile -InputFolder $config.InputFolder + + $fileName = $inputFile.Name + $fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($inputFile.Name) + $fileFullPath = $inputFile.FullName + $fileExtension = $inputFile.Extension + + Write-Log "Gefundene Datei: $fileName" "INFO" + Write-Log "Extrahierte Belegnummer: $fileBaseName" "INFO" + + $sqlResult = $null + $sqlFound = $false + $status = 'success' + $message = 'SQL-Datensatz gefunden' + + try + { + $sqlResult = Invoke-SqlTopOneQuery -Config $config -Belegnummer $fileBaseName + + if ($null -eq $sqlResult) + { + $sqlFound = $false + $status = 'warning' + $message = 'Kein SQL-Datensatz gefunden' + } else + { + $sqlFound = $true + } + } catch + { + Write-Log $_.Exception.Message "ERROR" + $sqlFound = $false + $status = 'error' + $message = $_.Exception.Message + } + + $payload = [ordered]@{ + status = $status + message = $message + fileName = $fileName + fileBaseName = $fileBaseName + fileFullPath = $fileFullPath + fileExtension = $fileExtension + processedAt = (Get-Date).ToString('s') + sqlFound = $sqlFound + sqlResult = $sqlResult + } + + Send-N8nWebhook -WebhookUrl $config.WebhookUrl -Payload $payload + + Write-Log "Skriptausführung abgeschlossen." "SUCCESS" +} catch +{ + try + { + if (-not $script:LogFilePath) + { + $fallbackFolder = Join-Path $env:TEMP 'KANBAN-Rational-Druck' + if (-not (Test-Path $fallbackFolder)) + { + New-Item -Path $fallbackFolder -ItemType Directory -Force | Out-Null + } + $script:LogFilePath = Join-Path $fallbackFolder ("KANBAN-Rational-Druck_{0}.log" -f (Get-Date -Format 'yyyy-MM-dd')) + } + + Write-Log $_.Exception.Message "ERROR" + } catch + { + Write-Host "❌ Kritischer Fehler: $($_.Exception.Message)" + } + + exit 1 +} + +#endregion diff --git a/scripts/KANBAN-Rational-Druck/_README.md b/scripts/KANBAN-Rational-Druck/_README.md new file mode 100644 index 0000000..181f1a6 --- /dev/null +++ b/scripts/KANBAN-Rational-Druck/_README.md @@ -0,0 +1,73 @@ +# KANBAN-Rational-Druck + +## Zweck + +Dieses Skript verarbeitet genau eine Datei aus einem definierten Eingangsordner und nutzt den Dateinamen ohne Endung als Suchwert für eine MS-SQL-Abfrage. + +Beispiel: + +- Datei: `2026-1234567.txt` +- Suchwert für SQL: `2026-1234567` + +Anschließend wird das Ergebnis per HTTP POST an einen n8n-Webhook gesendet. + +--- + +## Funktionsweise + +1. Prüft, ob `AZ_GITHUB_TOKEN` gesetzt ist +2. Lädt die private Konfiguration aus dem Repo `AZ-PowerShell-Prv` +3. Liest genau eine Datei aus dem Input-Ordner +4. Extrahiert den Dateinamen ohne Endung +5. Führt eine parametrisierte SQL-Abfrage aus +6. Sendet die Daten an n8n +7. Schreibt Logs in eine Logdatei + +--- + +## Repo-Pfade + +### Public +`AZ-PowerShell-Pub/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1` + +### Private +`AZ-PowerShell-Prv/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1` + +Raw-URL für Private-Datei: + +`https://git.az-gruppe.com/AZ-Intec-GmbH/AZ-PowerShell-Prv/raw/branch/main/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1` + +--- + +## Voraussetzungen + +- Windows +- PowerShell 5.1 +- Zugriff auf den Gitea-Server +- `AZ_GITHUB_TOKEN` lokal als Umgebungsvariable gesetzt +- Netzwerkzugriff auf: + - Gitea + - MS-SQL Server + - n8n Webhook + +--- + +## Benötigte Konfiguration im Private-Repo + +Folgende Werte müssen in der privaten Datei gepflegt werden: + +- `InputFolder` +- `LogFolder` +- `WebhookUrl` +- `SqlServer` +- `SqlDatabase` +- `SqlUser` +- `SqlPassword` +- `SqlQuery` +- `SqlParameterName` + +--- + +## Beispiel-Aufruf + +powershell.exe -ExecutionPolicy Bypass -File .\KANBAN-Rational-Druck.ps1