- GUI zur Verwaltung von Implantbibliotheken - Automatischer Download von Planmeca - Integration der Original-Herstellerinstaller - Automatisches Romexis-Backup - Download-Cache und Updateerkennung - Fortschrittsanzeige und Protokollierung
1338 lines
44 KiB
PowerShell
1338 lines
44 KiB
PowerShell
<#
|
|
Romexis Implant Library Online Bulk Installer GUI
|
|
|
|
Funktion:
|
|
- Laedt die Planmeca Implant Library Webseite
|
|
- Parst die verfuegbaren Downloadlinks zu content.planmeca.com
|
|
- Zeigt die Bibliotheken als auswählbare Liste
|
|
- Laedt nur die ausgewaehlten ZIPs herunter
|
|
- Entpackt jede ZIP-Datei
|
|
- Prueft die mitgelieferten Installationsskripte per SHA256 gegen bekannte kompatible Versionen
|
|
- Ruft die originalen Planmeca/Hersteller-Batchdateien auf, statt deren SQL-Logik nachzubauen
|
|
|
|
Wichtig:
|
|
- Am Romexis-Server ausfuehren.
|
|
- Romexis 3D Implant Lizenz wird von Planmeca vorausgesetzt.
|
|
- Erstellt optional vor der Installation ein SQL-Backup der Romexis-Datenbank.
|
|
- Dieses Skript ist ein Orchestrator; die eigentliche Installation bleibt bei den Originalskripten.
|
|
#>
|
|
|
|
# WinForms reicht hier aus, WPF waere etwas zu gross dafuer
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
Add-Type -AssemblyName System.Drawing
|
|
Add-Type -AssemblyName System.Web
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
|
|
# bei Fehler erstmal hart abbrechen
|
|
$ErrorActionPreference = 'Stop'
|
|
try {
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
if ([enum]::GetNames([Net.SecurityProtocolType]) -contains 'Tls13') {
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls13
|
|
}
|
|
} catch {
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
}
|
|
|
|
# Standardwerte, koennen in der GUI noch angepasst werden
|
|
$DefaultLibraryUrl = 'https://www.planmeca.com/dental-software/planmeca-romexis/3d-implantology-software/implant-library/'
|
|
$DefaultCacheDir = Join-Path $env:ProgramData 'RomexisImplantLibraryCache'
|
|
|
|
# Hashes aus Bekannten install script, bei Änderungen muss wieder geprüft werden
|
|
$KnownHashes = @{
|
|
'Install_implant.bat' = @(
|
|
'8409D680D78A51C194CBCA7EC4B69F8EAC49E63B4BCF515C55D0A170BC385599'
|
|
)
|
|
'Install_script.bat' = @(
|
|
'1A7FD946D555620260D6D58FDFF0E3F623684AE836FA496F6E8E7482286D41E4'
|
|
)
|
|
}
|
|
|
|
# kleines Logfenster unten in der Maske
|
|
function Write-UiLog {
|
|
param([string]$Text)
|
|
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
|
$script:txtLog.AppendText("[$timestamp] $Text`r`n")
|
|
$script:txtLog.SelectionStart = $script:txtLog.Text.Length
|
|
$script:txtLog.ScrollToCaret()
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
}
|
|
|
|
function Select-Folder {
|
|
param([string]$Description)
|
|
$dlg = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
$dlg.Description = $Description
|
|
$dlg.ShowNewFolderButton = $true
|
|
if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return $dlg.SelectedPath }
|
|
return $null
|
|
}
|
|
|
|
function Clean-HtmlText {
|
|
param([string]$Html)
|
|
if ([string]::IsNullOrWhiteSpace($Html)) { return '' }
|
|
$text = [regex]::Replace($Html, '<[^>]+>', ' ')
|
|
$text = [System.Web.HttpUtility]::HtmlDecode($text)
|
|
$text = [regex]::Replace($text, '\s+', ' ').Trim()
|
|
return $text
|
|
}
|
|
|
|
function Resolve-Url {
|
|
param([string]$BaseUrl, [string]$Href)
|
|
if ($Href -match '^https?://') { return $Href }
|
|
$base = [Uri]$BaseUrl
|
|
return ([Uri]::new($base, $Href)).AbsoluteUri
|
|
}
|
|
|
|
# Seite auslesen und die Downloadlinks rausfischen
|
|
# nicht schoen, aber aktuell stabil genug
|
|
function Get-PlanmecaLibraries {
|
|
param(
|
|
[string]$Url
|
|
)
|
|
|
|
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing
|
|
$html = $response.Content
|
|
|
|
# Planmeca baut die Liste aktuell mit h3 + Download link auf
|
|
$pattern = '(?s)<h3>\s*(?<name>.*?)\s*</h3>.*?<a[^>]+href="(?<url>https://content\.planmeca\.com/implantlibrary/[^"]+?\.zip)"[^>]*>\s*Download library\s*</a>'
|
|
|
|
$items = @()
|
|
|
|
foreach ($m in [regex]::Matches($html, $pattern)) {
|
|
$name = [System.Net.WebUtility]::HtmlDecode($m.Groups['name'].Value).Trim()
|
|
$downloadUrl = [System.Net.WebUtility]::HtmlDecode($m.Groups['url'].Value).Trim()
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($name) -and
|
|
-not [string]::IsNullOrWhiteSpace($downloadUrl)) {
|
|
|
|
Write-UiLog "Gefunden: $name"
|
|
Write-UiLog "URL: $downloadUrl"
|
|
|
|
$items += [pscustomobject]@{
|
|
Name = $name
|
|
Url = $downloadUrl
|
|
}
|
|
}
|
|
}
|
|
|
|
$items |
|
|
Sort-Object Name -Unique
|
|
}
|
|
|
|
function Get-SafeFileName {
|
|
param([string]$Name)
|
|
$safe = $Name -replace '[\\/:*?"<>|]', '_'
|
|
$safe = $safe -replace '\s+', '_'
|
|
return $safe.Trim('_')
|
|
}
|
|
|
|
# fuer Cache/Update pruefung, wenn Planmeca die ZIP ersetzt
|
|
function Get-RemoteFileInfo {
|
|
param([string]$Url)
|
|
|
|
$request = [System.Net.HttpWebRequest]::Create($Url)
|
|
$request.Method = 'HEAD'
|
|
$request.UserAgent = 'Mozilla/5.0'
|
|
|
|
$response = $request.GetResponse()
|
|
try {
|
|
return [pscustomobject]@{
|
|
Url = $Url
|
|
ETag = $response.Headers['ETag']
|
|
LastModified = $response.LastModified.ToString('o')
|
|
ContentLength = [int64]$response.ContentLength
|
|
}
|
|
}
|
|
finally {
|
|
$response.Close()
|
|
}
|
|
}
|
|
|
|
function Get-CacheMetaPath {
|
|
param([string]$ZipPath)
|
|
return "$ZipPath.meta.json"
|
|
}
|
|
|
|
function Read-CacheMeta {
|
|
param([string]$ZipPath)
|
|
|
|
$metaPath = Get-CacheMetaPath -ZipPath $ZipPath
|
|
if (-not (Test-Path -LiteralPath $metaPath)) {
|
|
return $null
|
|
}
|
|
|
|
return Get-Content -LiteralPath $metaPath -Raw | ConvertFrom-Json
|
|
}
|
|
|
|
function Write-CacheMeta {
|
|
param(
|
|
[string]$ZipPath,
|
|
[object]$RemoteInfo
|
|
)
|
|
|
|
$metaPath = Get-CacheMetaPath -ZipPath $ZipPath
|
|
|
|
$meta = [pscustomobject]@{
|
|
Url = $RemoteInfo.Url
|
|
ETag = $RemoteInfo.ETag
|
|
LastModified = $RemoteInfo.LastModified
|
|
ContentLength = $RemoteInfo.ContentLength
|
|
DownloadedAt = (Get-Date).ToString('o')
|
|
Sha256 = (Get-FileHash -LiteralPath $ZipPath -Algorithm SHA256).Hash
|
|
}
|
|
|
|
$meta | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $metaPath -Encoding UTF8
|
|
}
|
|
|
|
# Cache nur verwenden wenn Groesse/Datum/ETag noch passen
|
|
function Test-CacheIsCurrent {
|
|
param(
|
|
[string]$ZipPath,
|
|
[object]$RemoteInfo
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $ZipPath)) {
|
|
return $false
|
|
}
|
|
|
|
$localFile = Get-Item -LiteralPath $ZipPath
|
|
if ($localFile.Length -le 0) {
|
|
return $false
|
|
}
|
|
|
|
$meta = Read-CacheMeta -ZipPath $ZipPath
|
|
if (-not $meta) {
|
|
return $false
|
|
}
|
|
|
|
if ($RemoteInfo.ContentLength -gt 0 -and [int64]$meta.ContentLength -ne [int64]$RemoteInfo.ContentLength) {
|
|
return $false
|
|
}
|
|
|
|
if ($RemoteInfo.ETag -and $meta.ETag -and $RemoteInfo.ETag -ne $meta.ETag) {
|
|
return $false
|
|
}
|
|
|
|
if ($RemoteInfo.LastModified -and $meta.LastModified -and $RemoteInfo.LastModified -ne $meta.LastModified) {
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
# eigener Download, damit der Balken in der GUI was tut
|
|
function Download-FileWithProgress {
|
|
param(
|
|
[string]$Url,
|
|
[string]$TargetFile,
|
|
[string]$DisplayName
|
|
)
|
|
|
|
$request = [System.Net.HttpWebRequest]::Create($Url)
|
|
$request.UserAgent = 'Mozilla/5.0'
|
|
$response = $request.GetResponse()
|
|
|
|
try {
|
|
$totalBytes = $response.ContentLength
|
|
$inputStream = $response.GetResponseStream()
|
|
$outputStream = [System.IO.File]::Create($TargetFile)
|
|
|
|
try {
|
|
$buffer = New-Object byte[] 1048576
|
|
$totalRead = 0
|
|
|
|
while (($read = $inputStream.Read($buffer, 0, $buffer.Length)) -gt 0) {
|
|
$outputStream.Write($buffer, 0, $read)
|
|
$totalRead += $read
|
|
|
|
if ($totalBytes -gt 0) {
|
|
$percent = [int](($totalRead / $totalBytes) * 100)
|
|
$script:progressBar.Value = [Math]::Min(100, $percent)
|
|
$script:lblProgress.Text = "Download $DisplayName - $percent% ($([math]::Round($totalRead / 1MB, 1)) / $([math]::Round($totalBytes / 1MB, 1)) MB)"
|
|
}
|
|
else {
|
|
$script:progressBar.Style = 'Marquee'
|
|
$script:lblProgress.Text = "Download $DisplayName - $([math]::Round($totalRead / 1MB, 1)) MB"
|
|
}
|
|
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
}
|
|
}
|
|
finally {
|
|
$outputStream.Close()
|
|
$inputStream.Close()
|
|
}
|
|
}
|
|
finally {
|
|
$response.Close()
|
|
$script:progressBar.Style = 'Blocks'
|
|
}
|
|
}
|
|
|
|
# Expand-Archive hat keine vernuenftige Anzeige, daher hier per ZIP API
|
|
function Expand-ZipWithProgress {
|
|
param(
|
|
[string]$ZipPath,
|
|
[string]$DestinationPath,
|
|
[string]$DisplayName
|
|
)
|
|
|
|
$zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
|
|
|
|
try {
|
|
$entries = @($zip.Entries)
|
|
$total = $entries.Count
|
|
$current = 0
|
|
|
|
foreach ($entry in $entries) {
|
|
$current++
|
|
$percent = [int](($current / $total) * 100)
|
|
|
|
$script:progressBar.Value = [Math]::Min(100, $percent)
|
|
$script:lblProgress.Text = "Entpacke $DisplayName - $percent% ($current / $total Dateien)"
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
|
|
$targetPath = Join-Path $DestinationPath $entry.FullName
|
|
$targetDir = Split-Path $targetPath -Parent
|
|
|
|
if (-not (Test-Path -LiteralPath $targetDir)) {
|
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
}
|
|
|
|
if ($entry.FullName.EndsWith('/')) {
|
|
continue
|
|
}
|
|
|
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $targetPath, $true)
|
|
}
|
|
}
|
|
finally {
|
|
$zip.Dispose()
|
|
}
|
|
}
|
|
|
|
# Download mit lokalem Cache
|
|
# alte ZIPs werden ersetzt sobald die Online Datei anders aussieht
|
|
function Download-LibraryZip {
|
|
param(
|
|
[pscustomobject]$Library,
|
|
[string]$CacheDir
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Library.Url)) {
|
|
throw "Keine Download-URL fuer Bibliothek '$($Library.Name)' vorhanden."
|
|
}
|
|
|
|
New-Item -ItemType Directory -Path $CacheDir -Force | Out-Null
|
|
|
|
$uri = [Uri]$Library.Url
|
|
$leaf = [System.IO.Path]::GetFileName($uri.AbsolutePath)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($leaf) -or $leaf -notmatch '\.zip$') {
|
|
$leaf = (Get-SafeFileName $Library.Name) + '.zip'
|
|
}
|
|
|
|
$target = Join-Path $CacheDir $leaf
|
|
$tmpFile = "$target.download"
|
|
|
|
Write-UiLog "Download: $($Library.Name)"
|
|
Write-UiLog "URL: $($Library.Url)"
|
|
Write-UiLog "Ziel: $target"
|
|
|
|
Write-UiLog "Prüfe Online-Version..."
|
|
$remoteInfo = Get-RemoteFileInfo -Url $Library.Url
|
|
|
|
Write-UiLog "Remote Größe: $($remoteInfo.ContentLength) Bytes"
|
|
Write-UiLog "Remote Last-Modified: $($remoteInfo.LastModified)"
|
|
if ($remoteInfo.ETag) {
|
|
Write-UiLog "Remote ETag: $($remoteInfo.ETag)"
|
|
}
|
|
|
|
if (Test-CacheIsCurrent -ZipPath $target -RemoteInfo $remoteInfo) {
|
|
$existingSize = (Get-Item -LiteralPath $target).Length
|
|
Write-UiLog "ZIP im Cache ist aktuell: $leaf ($existingSize Bytes)"
|
|
return $target
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $target) {
|
|
Write-UiLog "Cache veraltet oder ohne Metadaten, lade neu: $leaf"
|
|
Remove-Item -LiteralPath $target -Force
|
|
}
|
|
|
|
$oldMeta = Get-CacheMetaPath -ZipPath $target
|
|
if (Test-Path -LiteralPath $oldMeta) {
|
|
Remove-Item -LiteralPath $oldMeta -Force
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $tmpFile) {
|
|
Remove-Item -LiteralPath $tmpFile -Force
|
|
}
|
|
|
|
try {
|
|
Write-UiLog "Starte Download..."
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = "Download $($Library.Name) wird gestartet..."
|
|
|
|
Download-FileWithProgress `
|
|
-Url $Library.Url `
|
|
-TargetFile $tmpFile `
|
|
-DisplayName $Library.Name
|
|
|
|
Write-UiLog "Download abgeschlossen."
|
|
$script:progressBar.Value = 100
|
|
$script:lblProgress.Text = "Download $($Library.Name) abgeschlossen."
|
|
}
|
|
catch {
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = "Download fehlgeschlagen."
|
|
throw "Download fehlgeschlagen fuer '$($Library.Name)': $($_.Exception.Message)"
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $tmpFile)) {
|
|
throw "Download fehlgeschlagen. Temporäre Datei wurde nicht erzeugt: $tmpFile"
|
|
}
|
|
|
|
$fileSize = (Get-Item -LiteralPath $tmpFile).Length
|
|
|
|
if ($fileSize -le 0) {
|
|
Remove-Item -LiteralPath $tmpFile -Force -ErrorAction SilentlyContinue
|
|
throw "Download fehlgeschlagen. ZIP-Datei ist leer: $tmpFile"
|
|
}
|
|
|
|
Write-UiLog "Download abgeschlossen, verschiebe Datei in Cache..."
|
|
Move-Item -LiteralPath $tmpFile -Destination $target -Force
|
|
Write-UiLog "Datei im Cache gespeichert."
|
|
|
|
if (-not (Test-Path -LiteralPath $target)) {
|
|
throw "Download wurde nicht korrekt abgeschlossen. Datei fehlt: $target"
|
|
}
|
|
|
|
Write-CacheMeta -ZipPath $target -RemoteInfo $remoteInfo
|
|
|
|
$zipHash = (Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash
|
|
Write-UiLog "Download erfolgreich: $leaf ($fileSize Bytes)"
|
|
Write-UiLog "ZIP SHA256: $zipHash"
|
|
Write-UiLog "Cache-Metadaten gespeichert: $(Get-CacheMetaPath -ZipPath $target)"
|
|
|
|
return $target
|
|
}
|
|
|
|
function Get-Sha256 {
|
|
param([string]$Path)
|
|
if (-not (Test-Path -LiteralPath $Path)) { return $null }
|
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToUpperInvariant()
|
|
}
|
|
|
|
function Convert-ManufacturerNameFromFolder {
|
|
param([string]$FolderName, [string]$FallbackName)
|
|
if (-not [string]::IsNullOrWhiteSpace($FallbackName)) { return $FallbackName }
|
|
$name = $FolderName -replace '_', ' '
|
|
return (Get-Culture).TextInfo.ToTitleCase($name.ToLowerInvariant())
|
|
}
|
|
|
|
function Find-ImplantRoot {
|
|
param([string]$WorkDir)
|
|
return Get-ChildItem -LiteralPath $WorkDir -Directory -Recurse -Filter 'Implant_library_files' | Select-Object -First 1
|
|
}
|
|
|
|
function Get-SqlServerFiles {
|
|
param([string]$ManufacturerDir)
|
|
$sqlFiles = @(Get-ChildItem -LiteralPath $ManufacturerDir -Recurse -File -Filter '*sqlsrv.sql' | Select-Object -ExpandProperty FullName)
|
|
if ($sqlFiles.Count -eq 0) {
|
|
$sqlFiles = @(Get-ChildItem -LiteralPath $ManufacturerDir -Recurse -File -Filter '*.sql' | Where-Object { $_.Name -notmatch 'fb\.sql$' } | Select-Object -ExpandProperty FullName)
|
|
}
|
|
return @($sqlFiles)
|
|
}
|
|
|
|
# Romexis Installationspfad aus Registry holen
|
|
# die zweite GUID ist fuer alte Installationen drin geblieben
|
|
function Get-RomexisInstallDir {
|
|
$guids = @(
|
|
'{B77EAE13-6B8D-477C-93F1-9C9A9ABA4355}',
|
|
'{A9256EA8-FAD2-4B23-90A9-B78CD122C0BF}'
|
|
)
|
|
|
|
foreach ($guid in $guids) {
|
|
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$guid"
|
|
$keyWow = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$guid"
|
|
|
|
foreach ($path in @($key, $keyWow)) {
|
|
if (Test-Path $path) {
|
|
$installDir = (Get-ItemProperty $path).InstallLocation
|
|
if ($installDir -and (Test-Path $installDir)) {
|
|
return $installDir.TrimEnd('\')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
throw 'Romexis Installationsverzeichnis wurde nicht gefunden.'
|
|
}
|
|
|
|
# DB Daten aus Romexis Config lesen
|
|
# moeglichst nah an den original Batchscripten
|
|
function Get-RomexisSqlConfig {
|
|
param(
|
|
[string]$RomexisInstallDir
|
|
)
|
|
|
|
$propsFile = Join-Path $RomexisInstallDir 'sconfig\romexis_server.properties'
|
|
|
|
if (-not (Test-Path -LiteralPath $propsFile)) {
|
|
throw "Romexis Server-Konfiguration nicht gefunden: $propsFile"
|
|
}
|
|
|
|
$props = @{}
|
|
|
|
Get-Content -LiteralPath $propsFile | ForEach-Object {
|
|
$line = $_.Trim()
|
|
|
|
if ($line -eq '' -or $line.StartsWith('#')) {
|
|
return
|
|
}
|
|
|
|
$idx = $line.IndexOf('=')
|
|
if ($idx -lt 1) {
|
|
return
|
|
}
|
|
|
|
$key = $line.Substring(0, $idx).Trim()
|
|
$value = $line.Substring($idx + 1).Trim()
|
|
|
|
$props[$key] = $value
|
|
}
|
|
|
|
$dbType = $props['SERVER_DB']
|
|
$dbUrl = $props['SERVER_DB_URL']
|
|
$uid = $props['SERVER_DB_UID']
|
|
$pwd = $props['SERVER_DB_PWD']
|
|
|
|
if ([string]::IsNullOrWhiteSpace($pwd)) {
|
|
$pwd = 'romexis'
|
|
}
|
|
|
|
$instance = $null
|
|
$database = $null
|
|
|
|
if ($dbType -eq '3') {
|
|
# jTDS:
|
|
# jdbc:jtds:sqlserver://SERVER/DB;instance=ROMEXIS
|
|
if ($dbUrl -match 'sqlserver://([^/;]+)/([^;]+)') {
|
|
$server = $Matches[1]
|
|
$database = $Matches[2]
|
|
}
|
|
|
|
if ($dbUrl -match 'instance=([^;]+)') {
|
|
$instance = "$server\$($Matches[1])"
|
|
}
|
|
else {
|
|
$instance = $server
|
|
}
|
|
}
|
|
elseif ($dbType -eq '5') {
|
|
# Microsoft JDBC:
|
|
# jdbc:sqlserver://SERVER\INSTANCE;databaseName=romexis_db
|
|
if ($dbUrl -match 'sqlserver://([^;]+)') {
|
|
$instance = $Matches[1]
|
|
}
|
|
|
|
if ($dbUrl -match 'databaseName=([^;]+)') {
|
|
$database = $Matches[1]
|
|
}
|
|
}
|
|
else {
|
|
throw "Unbekannter Romexis SQL-Verbindungstyp SERVER_DB=$dbType"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($instance)) {
|
|
throw "SQL Server/Instanz konnte aus SERVER_DB_URL nicht gelesen werden: $dbUrl"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($database)) {
|
|
throw "SQL Datenbank konnte aus SERVER_DB_URL nicht gelesen werden: $dbUrl"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($uid)) {
|
|
throw "SQL Benutzer konnte nicht gelesen werden."
|
|
}
|
|
|
|
[pscustomobject]@{
|
|
Instance = $instance
|
|
Database = $database
|
|
User = $uid
|
|
Password = $pwd
|
|
DbType = $dbType
|
|
DbUrl = $dbUrl
|
|
}
|
|
}
|
|
|
|
|
|
# Java aus der Romexis Installation suchen
|
|
function Get-RomexisJavaPath {
|
|
param([string]$RomexisInstallDir)
|
|
|
|
$candidates = @()
|
|
if ($env:PROCESSOR_ARCHITECTURE -eq 'x86') {
|
|
$candidates += Join-Path $RomexisInstallDir 'tools\jre_x86\bin\java.exe'
|
|
}
|
|
else {
|
|
$candidates += Join-Path $RomexisInstallDir 'tools\jre_x64\bin\java.exe'
|
|
}
|
|
$candidates += Join-Path $RomexisInstallDir 'tools\jre\bin\java.exe'
|
|
|
|
foreach ($candidate in $candidates) {
|
|
if (Test-Path -LiteralPath $candidate) { return $candidate }
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Get-RomexisVersion {
|
|
param([string]$RomexisInstallDir)
|
|
|
|
$java = Get-RomexisJavaPath -RomexisInstallDir $RomexisInstallDir
|
|
$jar = Join-Path $RomexisInstallDir 'server\RomexisServer.jar'
|
|
|
|
if (-not $java -or -not (Test-Path -LiteralPath $jar)) {
|
|
return 'unknown'
|
|
}
|
|
|
|
try {
|
|
$old = Get-Location
|
|
Set-Location -LiteralPath (Split-Path $jar -Parent)
|
|
$out = & $java -jar 'RomexisServer.jar' -version 2>&1 | Out-String
|
|
Set-Location $old
|
|
|
|
if ($out -match '([0-9]+)\.([0-9]+)') {
|
|
return "$($Matches[1])$($Matches[2])"
|
|
}
|
|
}
|
|
catch {
|
|
try { Set-Location $old } catch {}
|
|
}
|
|
|
|
return 'unknown'
|
|
}
|
|
|
|
function Request-PasswordDialog {
|
|
param([string]$Title, [string]$Prompt)
|
|
|
|
$dlg = New-Object System.Windows.Forms.Form
|
|
$dlg.Text = $Title
|
|
$dlg.Size = New-Object System.Drawing.Size(430, 160)
|
|
$dlg.StartPosition = 'CenterParent'
|
|
$dlg.FormBorderStyle = 'FixedDialog'
|
|
$dlg.MaximizeBox = $false
|
|
$dlg.MinimizeBox = $false
|
|
|
|
$lbl = New-Object System.Windows.Forms.Label
|
|
$lbl.Text = $Prompt
|
|
$lbl.Location = New-Object System.Drawing.Point(12, 15)
|
|
$lbl.Size = New-Object System.Drawing.Size(390, 22)
|
|
$dlg.Controls.Add($lbl)
|
|
|
|
$txt = New-Object System.Windows.Forms.TextBox
|
|
$txt.Location = New-Object System.Drawing.Point(15, 43)
|
|
$txt.Size = New-Object System.Drawing.Size(385, 24)
|
|
$txt.UseSystemPasswordChar = $true
|
|
$dlg.Controls.Add($txt)
|
|
|
|
$ok = New-Object System.Windows.Forms.Button
|
|
$ok.Text = 'OK'
|
|
$ok.Location = New-Object System.Drawing.Point(244, 80)
|
|
$ok.Size = New-Object System.Drawing.Size(75, 28)
|
|
$ok.DialogResult = [System.Windows.Forms.DialogResult]::OK
|
|
$dlg.AcceptButton = $ok
|
|
$dlg.Controls.Add($ok)
|
|
|
|
$cancel = New-Object System.Windows.Forms.Button
|
|
$cancel.Text = 'Abbrechen'
|
|
$cancel.Location = New-Object System.Drawing.Point(325, 80)
|
|
$cancel.Size = New-Object System.Drawing.Size(75, 28)
|
|
$cancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
|
|
$dlg.CancelButton = $cancel
|
|
$dlg.Controls.Add($cancel)
|
|
|
|
if ($dlg.ShowDialog($form) -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
return $txt.Text
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Invoke-SqlcmdWithCredential {
|
|
param(
|
|
[object]$SqlConfig,
|
|
[object]$Credential,
|
|
[string]$Database,
|
|
[string]$Query
|
|
)
|
|
|
|
$args = @('-S', $SqlConfig.Instance)
|
|
$args += $Credential.Args
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($Database)) {
|
|
$args += @('-d', $Database)
|
|
}
|
|
|
|
# WICHTIG:
|
|
# Kein "-r 1", weil SQL Server auch Erfolgsmeldungen wie
|
|
# "Processed xxxx pages..." ausgibt. Mit "-r 1" landen diese im Fehlerstrom.
|
|
$args += @('-b', '-Q', $Query)
|
|
|
|
$oldErrorActionPreference = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
try {
|
|
$output = & sqlcmd @args 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
}
|
|
finally {
|
|
$ErrorActionPreference = $oldErrorActionPreference
|
|
}
|
|
|
|
[pscustomobject]@{
|
|
ExitCode = $exitCode
|
|
Output = ($output | Out-String)
|
|
}
|
|
}
|
|
|
|
function Get-SqlCredentialCandidates {
|
|
param([object]$SqlConfig, [string]$CustomSaPassword)
|
|
|
|
$candidates = @()
|
|
|
|
$candidates += [pscustomobject]@{
|
|
Name = 'Windows Authentifizierung'
|
|
Args = @('-E')
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($SqlConfig.User) -and -not [string]::IsNullOrWhiteSpace($SqlConfig.Password)) {
|
|
$candidates += [pscustomobject]@{
|
|
Name = 'Romexis DB Benutzer'
|
|
Args = @('-U', $SqlConfig.User, '-P', $SqlConfig.Password)
|
|
}
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($CustomSaPassword)) {
|
|
$candidates += [pscustomobject]@{
|
|
Name = 'SQL sa Benutzer (eingegeben)'
|
|
Args = @('-U', 'sa', '-P', $CustomSaPassword)
|
|
}
|
|
}
|
|
|
|
$candidates += [pscustomobject]@{
|
|
Name = 'SQL sa Standard 1'
|
|
Args = @('-U', 'sa', '-P', 'pwr0mex!s')
|
|
}
|
|
$candidates += [pscustomobject]@{
|
|
Name = 'SQL sa Standard 2'
|
|
Args = @('-U', 'sa', '-P', 'Pwr0mex!s!!!')
|
|
}
|
|
|
|
return @($candidates)
|
|
}
|
|
|
|
function Get-WorkingSqlCredential {
|
|
param([object]$SqlConfig)
|
|
|
|
$testQuery = "SET NOCOUNT ON;SELECT 'Database connection OK'"
|
|
foreach ($cred in (Get-SqlCredentialCandidates -SqlConfig $SqlConfig)) {
|
|
$res = Invoke-SqlcmdWithCredential -SqlConfig $SqlConfig -Credential $cred -Database $SqlConfig.Database -Query $testQuery
|
|
if ($res.ExitCode -eq 0) {
|
|
Write-UiLog "SQL-Zugriff OK mit: $($cred.Name)"
|
|
return $cred
|
|
}
|
|
}
|
|
|
|
$custom = Request-PasswordDialog -Title 'SQL sa Passwort' -Prompt 'SQL sa Passwort eingeben, falls Standardzugänge nicht funktionieren:'
|
|
if (-not [string]::IsNullOrWhiteSpace($custom)) {
|
|
foreach ($cred in (Get-SqlCredentialCandidates -SqlConfig $SqlConfig -CustomSaPassword $custom)) {
|
|
if ($cred.Name -ne 'SQL sa Benutzer (eingegeben)') { continue }
|
|
$res = Invoke-SqlcmdWithCredential -SqlConfig $SqlConfig -Credential $cred -Database $SqlConfig.Database -Query $testQuery
|
|
if ($res.ExitCode -eq 0) {
|
|
Write-UiLog "SQL-Zugriff OK mit: $($cred.Name)"
|
|
return $cred
|
|
}
|
|
}
|
|
}
|
|
|
|
throw 'Kein funktionierender SQL-Zugang gefunden. Backup wurde nicht erstellt.'
|
|
}
|
|
|
|
function Invoke-SqlScalar {
|
|
param(
|
|
[object]$SqlConfig,
|
|
[object]$Credential,
|
|
[string]$Database,
|
|
[string]$Query
|
|
)
|
|
|
|
$args = @('-S', $SqlConfig.Instance)
|
|
$args += $Credential.Args
|
|
if (-not [string]::IsNullOrWhiteSpace($Database)) { $args += @('-d', $Database) }
|
|
$args += @('-h', '-1', '-W', '-Q', $Query)
|
|
|
|
$output = & sqlcmd @args 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "SQL-Abfrage fehlgeschlagen: $($output | Out-String)"
|
|
}
|
|
|
|
foreach ($line in $output) {
|
|
$value = ($line.ToString()).Trim()
|
|
if ($value -and $value -notmatch '^\([0-9]+ rows? affected\)$') { return $value }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Escape-SqlLiteral {
|
|
param([string]$Value)
|
|
return $Value -replace "'", "''"
|
|
}
|
|
|
|
function Escape-SqlName {
|
|
param([string]$Value)
|
|
return $Value -replace ']', ']]'
|
|
}
|
|
|
|
# Sicherheitsbackup vor DB Aenderungen
|
|
# kein Ersatz fuer das normale Backupscript
|
|
function New-RomexisDatabaseBackup {
|
|
param(
|
|
[string]$RomexisInstallDir,
|
|
[object]$SqlConfig
|
|
)
|
|
|
|
if (-not (Get-Command sqlcmd -ErrorAction SilentlyContinue)) {
|
|
throw 'sqlcmd wurde nicht gefunden. Bitte SQL Server Command Line Tools installieren oder PATH prüfen.'
|
|
}
|
|
|
|
Write-UiLog 'Prüfe SQL-Zugriff für Backup...'
|
|
$cred = Get-WorkingSqlCredential -SqlConfig $SqlConfig
|
|
|
|
$romexisVersion = Get-RomexisVersion -RomexisInstallDir $RomexisInstallDir
|
|
Write-UiLog "Romexis Version fuer Backup-Dateiname: $romexisVersion"
|
|
|
|
$imageDir = Invoke-SqlScalar -SqlConfig $SqlConfig -Credential $cred -Database $SqlConfig.Database -Query 'SET NOCOUNT ON;SELECT param_value FROM RBA_Server_Param_S WHERE param_number=1'
|
|
if ([string]::IsNullOrWhiteSpace($imageDir)) {
|
|
throw 'Romexis Image-Verzeichnis konnte nicht aus der Datenbank gelesen werden.'
|
|
}
|
|
$imageDir = $imageDir -replace '/', '\'
|
|
Write-UiLog "Romexis Image Dir: $imageDir"
|
|
|
|
$backupDir = Join-Path $imageDir 'Backup'
|
|
if (-not (Test-Path -LiteralPath $backupDir)) {
|
|
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
|
|
}
|
|
|
|
$dateTime = Get-Date -Format 'yyyyMMddHHmmss'
|
|
$backupFile = Join-Path $backupDir ("$dateTime#$($SqlConfig.Database)#$romexisVersion#.BAK")
|
|
|
|
Write-UiLog "Erstelle Datenbankbackup: $backupFile"
|
|
$dbName = Escape-SqlName $SqlConfig.Database
|
|
$backupPath = Escape-SqlLiteral $backupFile
|
|
$query = "BACKUP DATABASE [$dbName] TO DISK=N'$backupPath';"
|
|
$res = Invoke-SqlcmdWithCredential -SqlConfig $SqlConfig -Credential $cred -Database '' -Query $query
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($res.Output)) {
|
|
Write-UiLog ($res.Output.Trim())
|
|
}
|
|
|
|
if ($res.ExitCode -ne 0) {
|
|
throw "Backup fehlgeschlagen. sqlcmd ExitCode: $($res.ExitCode)"
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $backupFile)) {
|
|
throw "Backup wurde laut sqlcmd ausgeführt, aber die Datei wurde nicht gefunden: $backupFile"
|
|
}
|
|
|
|
Write-UiLog "Backup erstellt: $backupFile"
|
|
|
|
return $backupFile
|
|
}
|
|
|
|
# Geometrie Dateien kopieren
|
|
# manche Hersteller haben implants und sleeves, andere nur eins davon
|
|
function Copy-LibraryFiles {
|
|
param(
|
|
[string]$ManufacturerDir,
|
|
[string]$RomexisInstallDir
|
|
)
|
|
|
|
$manufacturerFolder = Split-Path $ManufacturerDir -Leaf
|
|
|
|
$copyJobs = @(
|
|
[pscustomobject]@{
|
|
Name = 'Implantatdateien'
|
|
Source = Join-Path $ManufacturerDir 'implants\files'
|
|
Target = Join-Path $RomexisInstallDir "geometries\implants\$manufacturerFolder"
|
|
},
|
|
[pscustomobject]@{
|
|
Name = 'Sleeve-Dateien'
|
|
Source = Join-Path $ManufacturerDir 'sleeves\files'
|
|
Target = Join-Path $RomexisInstallDir "geometries\sleeves\$manufacturerFolder"
|
|
}
|
|
)
|
|
|
|
foreach ($job in $copyJobs) {
|
|
if (-not (Test-Path -LiteralPath $job.Source)) {
|
|
Write-UiLog "$($job.Name) nicht vorhanden, überspringe: $($job.Source)"
|
|
continue
|
|
}
|
|
|
|
Write-UiLog "Kopiere $($job.Name):"
|
|
Write-UiLog "Quelle: $($job.Source)"
|
|
Write-UiLog "Ziel: $($job.Target)"
|
|
|
|
New-Item -ItemType Directory -Path $job.Target -Force | Out-Null
|
|
Copy-Item -LiteralPath (Join-Path $job.Source '*') -Destination $job.Target -Recurse -Force
|
|
}
|
|
}
|
|
|
|
function Quote-CmdArg {
|
|
param([string]$Value)
|
|
if ($null -eq $Value) { return '""' }
|
|
return '"' + ($Value -replace '"', '\"') + '"'
|
|
}
|
|
|
|
# Wichtiger Teil:
|
|
# SQL Import nicht selbst nachbauen, sondern Hersteller Batch verwenden
|
|
function Invoke-OriginalInstaller {
|
|
param(
|
|
[string]$ImplantRoot,
|
|
[string]$ManufacturerDir,
|
|
[string]$ManufacturerName,
|
|
[string[]]$SqlFiles,
|
|
[string]$DbUser,
|
|
[string]$DbPassword,
|
|
[bool]$AllowUnknownScripts
|
|
)
|
|
|
|
$installImplant = Join-Path $ImplantRoot 'Install_implant.bat'
|
|
$installScript = Join-Path $ImplantRoot 'Install_script.bat'
|
|
|
|
if (-not (Test-Path -LiteralPath $installImplant)) {
|
|
throw 'Install_implant.bat fehlt.'
|
|
}
|
|
|
|
$hash1 = Get-Sha256 $installImplant
|
|
$hash2 = Get-Sha256 $installScript
|
|
|
|
$hashOk = (
|
|
($hash1 -and $KnownHashes['Install_implant.bat'] -contains $hash1) -or
|
|
($hash2 -and $KnownHashes['Install_script.bat'] -contains $hash2)
|
|
)
|
|
|
|
Write-UiLog "Install_implant.bat: $installImplant"
|
|
Write-UiLog "Install_implant SHA256: $hash1"
|
|
|
|
if ($hash2) {
|
|
Write-UiLog "Install_script.bat SHA256: $hash2"
|
|
}
|
|
|
|
if (-not $hashOk -and -not $AllowUnknownScripts) {
|
|
throw "Unbekannte Installer-Skriptversion. Hashes: Install_implant=$hash1 Install_script=$hash2"
|
|
}
|
|
|
|
if (-not $hashOk) {
|
|
Write-UiLog "WARNUNG: unbekannte Skript-Hashes erlaubt: $ManufacturerName"
|
|
}
|
|
|
|
if (-not $SqlFiles -or $SqlFiles.Count -eq 0) {
|
|
throw "Keine SQL-Server-SQL-Datei gefunden für $ManufacturerName."
|
|
}
|
|
|
|
Write-UiLog "Starte Originalskript fuer: $ManufacturerName"
|
|
Write-UiLog "SQL-Dateien fuer $ManufacturerName :"
|
|
|
|
foreach ($sqlFile in $SqlFiles) {
|
|
Write-UiLog " SQL: $sqlFile"
|
|
|
|
if (-not (Test-Path -LiteralPath $sqlFile)) {
|
|
throw "SQL-Datei existiert nicht: $sqlFile"
|
|
}
|
|
}
|
|
|
|
$cmdParts = @()
|
|
$cmdParts += 'call'
|
|
$cmdParts += (Quote-CmdArg $installImplant)
|
|
$cmdParts += (Quote-CmdArg $DbUser)
|
|
$cmdParts += (Quote-CmdArg $DbPassword)
|
|
$cmdParts += (Quote-CmdArg $ManufacturerName)
|
|
|
|
foreach ($sqlFile in $SqlFiles) {
|
|
$cmdParts += (Quote-CmdArg $sqlFile)
|
|
}
|
|
|
|
$cmdLine = $cmdParts -join ' '
|
|
|
|
Write-UiLog "Arbeitsverzeichnis: $ImplantRoot"
|
|
Write-UiLog "CMD: $cmdLine"
|
|
|
|
Push-Location -LiteralPath $ImplantRoot
|
|
try {
|
|
$output = & cmd.exe /d /s /c $cmdLine 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
|
|
if ($output) {
|
|
Write-UiLog "Ausgabe Originalskript:"
|
|
foreach ($line in $output) {
|
|
$text = $line.ToString().TrimEnd()
|
|
if (-not [string]::IsNullOrWhiteSpace($text)) {
|
|
Write-UiLog " $text"
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-UiLog "Originalskript ExitCode: $exitCode"
|
|
|
|
if ($exitCode -ne 0) {
|
|
throw "Originalskript beendet mit ExitCode $exitCode."
|
|
}
|
|
}
|
|
|
|
# Ablauf fuer eine Bibliothek: holen, entpacken, kopieren, SQL import
|
|
function Install-Library {
|
|
param(
|
|
[pscustomobject]$Library,
|
|
[string]$CacheDir,
|
|
[object]$SqlConfig,
|
|
[bool]$AllowUnknownScripts,
|
|
[bool]$CopyFiles
|
|
)
|
|
|
|
$zip = Download-LibraryZip -Library $Library -CacheDir $CacheDir
|
|
|
|
Write-UiLog "Verwende ZIP: $zip"
|
|
|
|
if (-not (Test-Path -LiteralPath $zip)) {
|
|
throw "ZIP-Datei wurde nicht gefunden: $zip"
|
|
}
|
|
|
|
$work = Join-Path $env:TEMP ('RomexisOnlineInstall_' + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Path $work | Out-Null
|
|
try {
|
|
Write-UiLog "Entpacke: $($Library.Name)"
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = "Entpacke $($Library.Name) ..."
|
|
|
|
Expand-ZipWithProgress `
|
|
-ZipPath $zip `
|
|
-DestinationPath $work `
|
|
-DisplayName $Library.Name
|
|
|
|
$script:progressBar.Value = 100
|
|
$script:lblProgress.Text = "Entpacken $($Library.Name) abgeschlossen."
|
|
$root = Find-ImplantRoot $work
|
|
if (-not $root) { throw 'Implant_library_files nicht gefunden.' }
|
|
|
|
$manufacturerDirs = @(Get-ChildItem -LiteralPath $root.FullName -Directory | Where-Object { $_.Name -notin @('scripts') })
|
|
if ($manufacturerDirs.Count -eq 0) { throw 'Kein Herstellerordner unter Implant_library_files gefunden.' }
|
|
|
|
foreach ($manufacturerDir in $manufacturerDirs) {
|
|
$manufacturerName = Convert-ManufacturerNameFromFolder -FolderName $manufacturerDir.Name -FallbackName $Library.Name
|
|
$sqlFiles = @(Get-SqlServerFiles -ManufacturerDir $manufacturerDir.FullName)
|
|
|
|
Write-UiLog "Herstellerordner: $($manufacturerDir.FullName)"
|
|
Write-UiLog "Gefundene SQL-Dateien: $($sqlFiles.Count)"
|
|
|
|
foreach ($sqlFile in $sqlFiles) {
|
|
Write-UiLog " gefunden: $sqlFile"
|
|
}
|
|
|
|
$romexisInstallDir = Get-RomexisInstallDir
|
|
if ($CopyFiles) {
|
|
Copy-LibraryFiles -ManufacturerDir $manufacturerDir.FullName -RomexisInstallDir $romexisInstallDir
|
|
}
|
|
Invoke-OriginalInstaller -ImplantRoot $root.FullName -ManufacturerDir $manufacturerDir.FullName -ManufacturerName $manufacturerName -SqlFiles $sqlFiles -DbUser $SqlConfig.User -DbPassword $SqlConfig.Password -AllowUnknownScripts $AllowUnknownScripts
|
|
}
|
|
Write-UiLog "OK: $($Library.Name)"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# ---------------- GUI ----------------
|
|
# gewachsene WinForms Maske, nicht huebsch aber ok
|
|
$form = New-Object System.Windows.Forms.Form
|
|
$form.Text = 'Romexis Implant Library Online Installer'
|
|
$form.Size = New-Object System.Drawing.Size(1080, 760)
|
|
$form.StartPosition = 'CenterScreen'
|
|
|
|
$lblUrl = New-Object System.Windows.Forms.Label
|
|
$lblUrl.Text = 'Planmeca URL:'
|
|
$lblUrl.Location = New-Object System.Drawing.Point(12, 15)
|
|
$lblUrl.Size = New-Object System.Drawing.Size(100, 22)
|
|
$form.Controls.Add($lblUrl)
|
|
|
|
$txtUrl = New-Object System.Windows.Forms.TextBox
|
|
$txtUrl.Text = $DefaultLibraryUrl
|
|
$txtUrl.Location = New-Object System.Drawing.Point(115, 12)
|
|
$txtUrl.Size = New-Object System.Drawing.Size(770, 24)
|
|
$form.Controls.Add($txtUrl)
|
|
|
|
$btnLoad = New-Object System.Windows.Forms.Button
|
|
$btnLoad.Text = 'Liste laden'
|
|
$btnLoad.Location = New-Object System.Drawing.Point(895, 10)
|
|
$btnLoad.Size = New-Object System.Drawing.Size(150, 28)
|
|
$form.Controls.Add($btnLoad)
|
|
|
|
$lblCache = New-Object System.Windows.Forms.Label
|
|
$lblCache.Text = 'Download-Cache:'
|
|
$lblCache.Location = New-Object System.Drawing.Point(12, 50)
|
|
$lblCache.Size = New-Object System.Drawing.Size(100, 22)
|
|
$form.Controls.Add($lblCache)
|
|
|
|
$txtCache = New-Object System.Windows.Forms.TextBox
|
|
$txtCache.Text = $DefaultCacheDir
|
|
$txtCache.Location = New-Object System.Drawing.Point(115, 47)
|
|
$txtCache.Size = New-Object System.Drawing.Size(770, 24)
|
|
$form.Controls.Add($txtCache)
|
|
|
|
$btnCache = New-Object System.Windows.Forms.Button
|
|
$btnCache.Text = 'Auswählen'
|
|
$btnCache.Location = New-Object System.Drawing.Point(895, 45)
|
|
$btnCache.Size = New-Object System.Drawing.Size(150, 28)
|
|
$form.Controls.Add($btnCache)
|
|
|
|
$list = New-Object System.Windows.Forms.CheckedListBox
|
|
$list.Location = New-Object System.Drawing.Point(12, 85)
|
|
$list.Size = New-Object System.Drawing.Size(1033, 300)
|
|
$list.CheckOnClick = $true
|
|
$form.Controls.Add($list)
|
|
|
|
$btnAll = New-Object System.Windows.Forms.Button
|
|
$btnAll.Text = 'Alle auswählen'
|
|
$btnAll.Location = New-Object System.Drawing.Point(12, 392)
|
|
$btnAll.Size = New-Object System.Drawing.Size(130, 28)
|
|
$form.Controls.Add($btnAll)
|
|
|
|
$btnNone = New-Object System.Windows.Forms.Button
|
|
$btnNone.Text = 'Alle abwählen'
|
|
$btnNone.Location = New-Object System.Drawing.Point(150, 392)
|
|
$btnNone.Size = New-Object System.Drawing.Size(130, 28)
|
|
$form.Controls.Add($btnNone)
|
|
|
|
$chkUnknown = New-Object System.Windows.Forms.CheckBox
|
|
$chkUnknown.Text = 'Unbekannte Skript-Hashes erlauben'
|
|
$chkUnknown.Location = New-Object System.Drawing.Point(12, 433)
|
|
$chkUnknown.Size = New-Object System.Drawing.Size(320, 22)
|
|
$form.Controls.Add($chkUnknown)
|
|
|
|
$chkCopy = New-Object System.Windows.Forms.CheckBox
|
|
$chkCopy.Text = 'Implantatdateien automatisch nach Romexis kopieren'
|
|
$chkCopy.Location = New-Object System.Drawing.Point(12, 470)
|
|
$chkCopy.Size = New-Object System.Drawing.Size(420, 22)
|
|
$chkCopy.Checked = $true
|
|
$form.Controls.Add($chkCopy)
|
|
|
|
$chkBackup = New-Object System.Windows.Forms.CheckBox
|
|
$chkBackup.Text = 'Vor Installation automatisch Romexis-Datenbankbackup erstellen'
|
|
$chkBackup.Location = New-Object System.Drawing.Point(455, 470)
|
|
$chkBackup.Size = New-Object System.Drawing.Size(420, 22)
|
|
$chkBackup.Checked = $true
|
|
$form.Controls.Add($chkBackup)
|
|
|
|
$btnInstall = New-Object System.Windows.Forms.Button
|
|
$btnInstall.Text = 'Ausgewählte downloaden und installieren'
|
|
$btnInstall.Location = New-Object System.Drawing.Point(12, 505)
|
|
$btnInstall.Size = New-Object System.Drawing.Size(250, 34)
|
|
$form.Controls.Add($btnInstall)
|
|
|
|
$btnDownloadOnly = New-Object System.Windows.Forms.Button
|
|
$btnDownloadOnly.Text = 'Ausgewählte nur downloaden'
|
|
$btnDownloadOnly.Location = New-Object System.Drawing.Point(270, 505)
|
|
$btnDownloadOnly.Size = New-Object System.Drawing.Size(130, 34)
|
|
$form.Controls.Add($btnDownloadOnly)
|
|
|
|
$progressBar = New-Object System.Windows.Forms.ProgressBar
|
|
$progressBar.Location = New-Object System.Drawing.Point(420, 510)
|
|
$progressBar.Size = New-Object System.Drawing.Size(575, 22)
|
|
$progressBar.Minimum = 0
|
|
$progressBar.Maximum = 100
|
|
$progressBar.Value = 0
|
|
$form.Controls.Add($progressBar)
|
|
$script:progressBar = $progressBar
|
|
|
|
$lblProgress = New-Object System.Windows.Forms.Label
|
|
$lblProgress.Text = 'Bereit.'
|
|
$lblProgress.Location = New-Object System.Drawing.Point(420, 535)
|
|
$lblProgress.Size = New-Object System.Drawing.Size(575, 18)
|
|
$form.Controls.Add($lblProgress)
|
|
$script:lblProgress = $lblProgress
|
|
|
|
$txtLog = New-Object System.Windows.Forms.TextBox
|
|
$txtLog.Location = New-Object System.Drawing.Point(12, 560)
|
|
$txtLog.Size = New-Object System.Drawing.Size(1033, 150)
|
|
$txtLog.Multiline = $true
|
|
$txtLog.ScrollBars = 'Vertical'
|
|
$txtLog.ReadOnly = $true
|
|
$form.Controls.Add($txtLog)
|
|
$script:txtLog = $txtLog
|
|
$script:Libraries = @()
|
|
|
|
$btnCache.Add_Click({
|
|
$p = Select-Folder 'Download-Cache auswählen'
|
|
if ($p) { $txtCache.Text = $p }
|
|
})
|
|
|
|
# Nur downloaden, z.B. fuer anderen Romexis Server mitnehmen
|
|
$btnDownloadOnly.Add_Click({
|
|
try {
|
|
$btnDownloadOnly.Enabled = $false
|
|
$btnInstall.Enabled = $false
|
|
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = 'Download wird vorbereitet...'
|
|
|
|
if ($script:Libraries.Count -eq 0) {
|
|
throw 'Bitte zuerst die Liste laden.'
|
|
}
|
|
|
|
$selected = @()
|
|
foreach ($idx in $list.CheckedIndices) {
|
|
$selected += $script:Libraries[[int]$idx]
|
|
}
|
|
|
|
if ($selected.Count -eq 0) {
|
|
throw 'Keine Bibliothek ausgewählt.'
|
|
}
|
|
|
|
Write-UiLog "Nur-Download gestartet: $($selected.Count) Bibliotheken"
|
|
|
|
foreach ($lib in $selected) {
|
|
$zip = Download-LibraryZip -Library $lib -CacheDir $txtCache.Text
|
|
Write-UiLog "Gespeichert: $zip"
|
|
}
|
|
|
|
$script:progressBar.Value = 100
|
|
$script:lblProgress.Text = 'Download abgeschlossen.'
|
|
Write-UiLog 'Nur-Download fertig.'
|
|
|
|
[System.Windows.Forms.MessageBox]::Show(
|
|
'Download abgeschlossen.',
|
|
'Fertig',
|
|
'OK',
|
|
'Information'
|
|
) | Out-Null
|
|
}
|
|
catch {
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = 'Fehler beim Download.'
|
|
Write-UiLog "FEHLER Nur-Download: $($_.Exception.Message)"
|
|
[System.Windows.Forms.MessageBox]::Show(
|
|
$_.Exception.Message,
|
|
'Fehler',
|
|
'OK',
|
|
'Error'
|
|
) | Out-Null
|
|
}
|
|
finally {
|
|
$btnDownloadOnly.Enabled = $true
|
|
$btnInstall.Enabled = $true
|
|
$form.Cursor = [System.Windows.Forms.Cursors]::Default
|
|
}
|
|
})
|
|
|
|
$btnLoad.Add_Click({
|
|
try {
|
|
$list.Items.Clear()
|
|
$script:Libraries = @(Get-PlanmecaLibraries -Url $txtUrl.Text)
|
|
foreach ($lib in $script:Libraries) {
|
|
$label = $lib.Name
|
|
if (-not [string]::IsNullOrWhiteSpace($lib.Contains)) { $label += " [$($lib.Contains)]" }
|
|
[void]$list.Items.Add($label, $false)
|
|
}
|
|
Write-UiLog "$($script:Libraries.Count) Bibliotheken gefunden."
|
|
|
|
foreach ($lib in $script:Libraries) {
|
|
Write-UiLog "$($lib.Name) => $($lib.Url)"
|
|
}
|
|
} catch {
|
|
Write-UiLog "FEHLER beim Laden: $($_.Exception.Message)"
|
|
[System.Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Fehler beim Laden', 'OK', 'Error') | Out-Null
|
|
}
|
|
})
|
|
|
|
$btnAll.Add_Click({ for ($i = 0; $i -lt $list.Items.Count; $i++) { $list.SetItemChecked($i, $true) } })
|
|
$btnNone.Add_Click({ for ($i = 0; $i -lt $list.Items.Count; $i++) { $list.SetItemChecked($i, $false) } })
|
|
|
|
# Hauptbutton: optional Backup, dann alle gewaehlten Bibliotheken installieren
|
|
$btnInstall.Add_Click({
|
|
try {
|
|
$btnInstall.Enabled = $false
|
|
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = 'Installation wird vorbereitet...'
|
|
|
|
if ($script:Libraries.Count -eq 0) { throw 'Bitte zuerst die Liste laden.' }
|
|
|
|
$romexisInstallDir = Get-RomexisInstallDir
|
|
$sqlConfig = Get-RomexisSqlConfig -RomexisInstallDir $romexisInstallDir
|
|
|
|
Write-UiLog "Romexis Install Dir: $romexisInstallDir"
|
|
Write-UiLog "Romexis SQL Instance: $($sqlConfig.Instance)"
|
|
Write-UiLog "Romexis SQL Database: $($sqlConfig.Database)"
|
|
Write-UiLog "Romexis SQL User: $($sqlConfig.User)"
|
|
|
|
$selected = @()
|
|
foreach ($idx in $list.CheckedIndices) { $selected += $script:Libraries[[int]$idx] }
|
|
if ($selected.Count -eq 0) { throw 'Keine Bibliothek ausgewählt.' }
|
|
|
|
$backupText = if ($chkBackup.Checked) { "Vorher wird automatisch ein Romexis-Datenbankbackup erstellt." } else { "ACHTUNG: Automatisches Backup ist deaktiviert." }
|
|
$confirm = [System.Windows.Forms.MessageBox]::Show(
|
|
"Es werden $($selected.Count) Bibliotheken heruntergeladen und am Romexis-Server installiert.`r`n`r`n$backupText`r`n`r`nFortfahren?",
|
|
'Installation starten', 'YesNo', 'Warning')
|
|
if ($confirm -ne [System.Windows.Forms.DialogResult]::Yes) { return }
|
|
|
|
if ($chkBackup.Checked) {
|
|
$backupFile = New-RomexisDatabaseBackup -RomexisInstallDir $romexisInstallDir -SqlConfig $sqlConfig
|
|
Write-UiLog "Backup vor Installation erfolgreich: $backupFile"
|
|
}
|
|
else {
|
|
Write-UiLog 'WARNUNG: Installation ohne automatisches Backup gestartet.'
|
|
}
|
|
|
|
foreach ($lib in $selected) {
|
|
Install-Library -Library $lib -CacheDir $txtCache.Text -SqlConfig $sqlConfig -AllowUnknownScripts:$chkUnknown.Checked -CopyFiles:$chkCopy.Checked
|
|
}
|
|
$script:progressBar.Value = 100
|
|
$script:lblProgress.Text = 'Installation abgeschlossen.'
|
|
Write-UiLog 'Fertig.'
|
|
[System.Windows.Forms.MessageBox]::Show('Installation abgeschlossen.', 'Fertig', 'OK', 'Information') | Out-Null
|
|
} catch {
|
|
$script:progressBar.Value = 0
|
|
$script:lblProgress.Text = 'Fehler.'
|
|
Write-UiLog "FEHLER: $($_.Exception.Message)"
|
|
[System.Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Fehler', 'OK', 'Error') | Out-Null
|
|
}
|
|
finally {
|
|
$btnInstall.Enabled = $true
|
|
$form.Cursor = [System.Windows.Forms.Cursors]::Default
|
|
}
|
|
})
|
|
|
|
# beim Start gleich versuchen die Romexis Daten und die Onlineliste zu laden
|
|
$form.Add_Shown({
|
|
try {
|
|
$romexisInstallDir = Get-RomexisInstallDir
|
|
$sqlConfig = Get-RomexisSqlConfig -RomexisInstallDir $romexisInstallDir
|
|
|
|
Write-UiLog "Romexis Install Dir: $romexisInstallDir"
|
|
Write-UiLog "Romexis SQL Instance: $($sqlConfig.Instance)"
|
|
Write-UiLog "Romexis SQL Database: $($sqlConfig.Database)"
|
|
Write-UiLog "Romexis SQL User: $($sqlConfig.User)"
|
|
}
|
|
catch {
|
|
Write-UiLog "WARNUNG: Romexis SQL-Konfiguration konnte nicht automatisch gelesen werden: $($_.Exception.Message)"
|
|
}
|
|
|
|
Write-UiLog 'Lade Bibliotheksliste automatisch...'
|
|
$btnLoad.PerformClick()
|
|
})
|
|
|
|
[void]$form.ShowDialog() |