Files
AZ-PowerShell-Pub/scripts/KANBAN-Rational-Druck/KANBAN-Rational-Druck.ps1

463 lines
14 KiB
PowerShell

#Requires -Version 5.1
param(
[switch]$SkipSelfUpdateCheck
)
<#
.SYNOPSIS
KANBAN-Rational-Druck mit Versionspruefung, Self-Update, Private-Config-Ladung, SQL-Abfrage und n8n-Webhook.
.DESCRIPTION
Ablauf:
1. Optional Self-Update aus dem Public-Repo per Versionsvergleich
2. AZ_GITHUB_TOKEN pruefen
3. Private Konfiguration aus dem Private-Repo laden
4. Alle TXT-Dateien direkt im Input-Ordner lesen
5. Dateiname ohne Endung als Belegnummer verwenden
6. SQL-Abfrage ausfuehren
7. Ergebnis an n8n senden
8. Logging in Datei
.NOTES
Repo: AZ-PowerShell-Pub
#>
$ErrorActionPreference = 'Stop'
$script:ScriptName = 'KANBAN-Rational-Druck'
$script:ScriptVersion = '1.1.0'
$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 Test-AzGithubToken
{
if ([string]::IsNullOrWhiteSpace($env:AZ_GITHUB_TOKEN))
{
throw "ERROR: Umgebungsvariable AZ_GITHUB_TOKEN ist nicht gesetzt."
}
}
function Get-ScriptVersionFromContent
{
param(
[Parameter(Mandatory = $true)]
[string]$Content
)
$match = [regex]::Match($Content, "\`$script:ScriptVersion\s*=\s*'([^']+)'")
if (-not $match.Success)
{
throw "ERROR: ScriptVersion konnte aus dem Remote-Skript nicht gelesen werden."
}
return $match.Groups[1].Value
}
function Start-SelfUpdateIfNeeded
{
param(
[Parameter(Mandatory = $true)]
[string]$RemoteScriptUrl
)
if ($SkipSelfUpdateCheck)
{
Write-ConsoleLog "Self-Update-Pruefung wurde uebersprungen (Restart nach Update)." "INFO"
return
}
$localScriptPath = $PSCommandPath
if ([string]::IsNullOrWhiteSpace($localScriptPath))
{
throw "ERROR: Lokaler Skriptpfad konnte nicht ermittelt werden."
}
Write-ConsoleLog "Pruefe 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."
}
$remoteVersion = Get-ScriptVersionFromContent -Content $remoteContent
$localVersion = $script:ScriptVersion
Write-ConsoleLog "Lokale Version: $localVersion | Remote Version: $remoteVersion" "INFO"
if ($remoteVersion -eq $localVersion)
{
Write-ConsoleLog "OK: Public-Skript ist bereits aktuell." "SUCCESS"
return
}
Write-ConsoleLog "WARN: 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
Remove-Item -Path $tempUpdatedScript -Force -ErrorAction SilentlyContinue
Write-ConsoleLog "OK: Lokales Public-Skript wurde aktualisiert." "SUCCESS"
Write-ConsoleLog "Starte aktualisierte Version neu..." "INFO"
$argumentList = @(
'-ExecutionPolicy', 'Bypass',
'-File', $localScriptPath,
'-SkipSelfUpdateCheck'
)
Start-Process -FilePath 'powershell.exe' -ArgumentList $argumentList | Out-Null
Write-ConsoleLog "Aktuelle Instanz wird beendet, damit die neue Version ausgefuehrt wird." "INFO"
exit 0
} catch
{
throw "ERROR: Fehler bei der Self-Update-Pruefung 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 "ERROR: 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 "ERROR: Fehlende Konfiguration: '$key'"
}
}
}
function Get-InputFiles
{
param(
[Parameter(Mandatory = $true)]
[string]$InputFolder
)
if (-not (Test-Path -Path $InputFolder))
{
throw "ERROR: Input-Ordner existiert nicht: $InputFolder"
}
$files = Get-ChildItem -Path $InputFolder -File -Filter '*.txt'
return @($files)
}
function Invoke-SqlTopOneQuery
{
param(
[Parameter(Mandatory = $true)]
[hashtable]$Config,
[Parameter(Mandatory = $true)]
[string]$Belegnummer
)
$sqlPasswordPlain = [System.Text.Encoding]::UTF8.GetString(
[System.Convert]::FromBase64String($Config.SqlPassword)
)
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,
$sqlPasswordPlain
$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 "WARN: 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 "OK: SQL-Datensatz erfolgreich gelesen." "SUCCESS"
return [pscustomobject]$result
} catch
{
throw "ERROR: 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 "OK: Webhook erfolgreich gesendet." "SUCCESS"
} catch
{
throw "ERROR: 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 Skriptausfuehrung KANBAN-Rational-Druck..." "INFO"
$inputFiles = Get-InputFiles -InputFolder $config.InputFolder
if ($inputFiles.Count -eq 0)
{
Write-Log "WARN: Keine TXT-Dateien im Input-Ordner gefunden." "WARN"
exit 0
}
Write-Log "Anzahl gefundener TXT-Dateien: $($inputFiles.Count)" "INFO"
foreach ($inputFile in $inputFiles)
{
try
{
$fileName = $inputFile.Name
$fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($inputFile.Name)
$fileFullPath = $inputFile.FullName
$fileExtension = $inputFile.Extension
Write-Log "Verarbeite 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 "Datei erfolgreich verarbeitet: $fileName" "SUCCESS"
} catch
{
Write-Log "Fehler bei Verarbeitung von Datei '$($inputFile.FullName)': $($_.Exception.Message)" "ERROR"
}
}
Write-Log "Skriptausfuehrung 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 "ERROR: Kritischer Fehler: $($_.Exception.Message)"
}
exit 1
}
#endregion