Harden BizTalk maintenance workflow and add installer
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{md,json}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -1,2 +1 @@
|
|||||||
|
|
||||||
* text=auto eol=crlf
|
* text=auto eol=crlf
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ bin/
|
|||||||
obj/
|
obj/
|
||||||
|
|
||||||
# Logs / Outputs
|
# Logs / Outputs
|
||||||
|
artifacts/
|
||||||
*.log
|
*.log
|
||||||
*.json
|
*.json
|
||||||
*.html
|
*.html
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ VisualStudioVersion = 15.0.28307.168
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkPlatformManagementTool", "src\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj", "{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkPlatformManagementTool", "src\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj", "{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkPlatformManagementTool.Tests", "tests\BizTalkPlatformManagementTool.Tests\BizTalkPlatformManagementTool.Tests.csproj", "{B2AD340B-859F-413B-8811-7FBA4BD2BC02}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkPlatformManagementTool.Setup", "src\BizTalkPlatformManagementTool.Setup\BizTalkPlatformManagementTool.Setup.csproj", "{38E61630-11AB-4F8D-B421-F8A88831C4BD}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkPlatformManagementTool.Packager", "src\BizTalkPlatformManagementTool.Packager\BizTalkPlatformManagementTool.Packager.csproj", "{AF67FD76-8F5D-42DB-9D52-20EF6332D1F9}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -14,6 +20,18 @@ Global
|
|||||||
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B2AD340B-859F-413B-8811-7FBA4BD2BC02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2AD340B-859F-413B-8811-7FBA4BD2BC02}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B2AD340B-859F-413B-8811-7FBA4BD2BC02}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B2AD340B-859F-413B-8811-7FBA4BD2BC02}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{38E61630-11AB-4F8D-B421-F8A88831C4BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{38E61630-11AB-4F8D-B421-F8A88831C4BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{38E61630-11AB-4F8D-B421-F8A88831C4BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{38E61630-11AB-4F8D-B421-F8A88831C4BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AF67FD76-8F5D-42DB-9D52-20EF6332D1F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AF67FD76-8F5D-42DB-9D52-20EF6332D1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AF67FD76-8F5D-42DB-9D52-20EF6332D1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AF67FD76-8F5D-42DB-9D52-20EF6332D1F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
+37
-9
@@ -1,27 +1,55 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased] - 2026-04-27
|
## [Unreleased] - 2026-08-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Überarbeitete deutschsprachige GUI mit einheitlichem Workflow und DPI-Skalierung.
|
||||||
|
- .NET-Installer mit sicherem Update, Rollback und Deinstallation.
|
||||||
|
- Release-Packager für ZIP, Base64-TXT und SHA-256.
|
||||||
|
- Regressionstestprojekt für Plan-, Vergleichs-, Validierungs- und Dateilogik.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Restore startet ausschließlich Artefakte, die im Vorher-Snapshot aktiv waren.
|
||||||
|
- Vorhandene Vorher-Snapshots werden beim Herunterfahren wiederverwendet.
|
||||||
|
- WMI-Auflösung nutzt zusätzliche Qualifier und bricht bei Mehrdeutigkeit ab.
|
||||||
|
- Snapshot-Vergleich verwendet zusammengesetzte Artefaktidentitäten.
|
||||||
|
- JSON-Dateien werden atomar und größenbegrenzt geschrieben.
|
||||||
|
- Logs und Wartungsdateien liegen unter `%ProgramData%` und überleben Updates.
|
||||||
|
- Transport-URIs werden nicht mehr in neuen Snapshots persistiert.
|
||||||
|
|
||||||
|
## [2.0.0] - 2026-04-27
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
- Projekt auf `BizTalkPlatformManagementTool` umbenannt.
|
- Projekt auf `BizTalkPlatformManagementTool` umbenannt.
|
||||||
- Solution, Projektdatei, Namespace, Assemblyname und Standard-Ausgabepfad angepasst.
|
- Solution, Projektdatei, Namespace, Assemblyname und Standard-Ausgabepfad angepasst.
|
||||||
- Dokumentation aktualisiert und `Installation.md` ergänzt.
|
- Dokumentation aktualisiert und `Installation.md` ergänzt.
|
||||||
|
|
||||||
## [1.3.0] - 2026-02-02
|
## [1.3.0] - 2026-02-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- HTML Report mit farblicher Kennzeichnung (grün/rot)
|
|
||||||
- Farbige Statusausgabe auf STDOUT
|
- HTML-Report mit farblicher Kennzeichnung
|
||||||
- Remote‑Server Parameter (-Server) und Ausgabeverzeichnis (-OutDir)
|
- farbige Statusausgabe
|
||||||
- Verbesserte Diff‑Erkennung (neu/entfernt)
|
- Remote-Server-Parameter und Ausgabeverzeichnis
|
||||||
|
- verbesserte Diff-Erkennung für neue und entfernte Artefakte
|
||||||
|
|
||||||
## [1.2.0] - 2026-02-02
|
## [1.2.0] - 2026-02-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Remote‑fähige Snapshot‑Funktion, Logging (Konsole + Datei)
|
|
||||||
|
- Remote-fähige Snapshot-Funktion und Logging
|
||||||
|
|
||||||
## [1.1.0] - 2026-02-02
|
## [1.1.0] - 2026-02-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Textuelle Diff‑Ausgabe auf STDOUT + HTML Report
|
|
||||||
|
- textuelle Diff-Ausgabe und HTML-Report
|
||||||
|
|
||||||
## [1.0.0] - 2026-02-02
|
## [1.0.0] - 2026-02-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Grundfunktion: Snapshot & Diff (Receive Locations, Send Ports, Orchestrations)
|
|
||||||
|
- Snapshot und Diff für Receive Locations, Send Ports und Orchestrierungen
|
||||||
|
|||||||
+133
-79
@@ -1,105 +1,159 @@
|
|||||||
# BizTalk Platform Management Tool Dokumentation
|
# Technische Dokumentation
|
||||||
|
|
||||||
**Stand:** 2026-04-27
|
**Stand:** 2026-08-06
|
||||||
**Implementierung:** C# WinForms, .NET Framework 4.6.1
|
|
||||||
**Archivierte PowerShell-Version:** `archive/powershell/BizTalkPlatformManagementTool.ps1`
|
|
||||||
|
|
||||||
## Zweck
|
**Version:** 2.1.0
|
||||||
|
|
||||||
Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster für Microsoft BizTalk Server 2020:
|
**Technik:** C# WinForms, .NET Framework 4.6.1
|
||||||
|
|
||||||
- Exakten Laufzeitstatus vor einer Wartung erfassen.
|
## Ziel und Sicherheitsprinzip
|
||||||
- Receive Locations, Orchestrations, Send Ports und Host Instances in definierter Reihenfolge herunterfahren.
|
|
||||||
- Den Laufzeitstatus aus einer gespeicherten `before.json` wiederherstellen.
|
|
||||||
- Den Status nach der Wartung erfassen und beide Snapshots vergleichen.
|
|
||||||
- Plan- und Ergebnisdateien für Review, Audit und Nachvollziehbarkeit schreiben.
|
|
||||||
|
|
||||||
## Projektstruktur
|
Das Tool unterstützt geplante BizTalk-Wartungen, ohne den fachlich gewünschten
|
||||||
|
Ausgangszustand zu verlieren. Der Vorher-Snapshot ist die einzige Quelle für
|
||||||
|
den Restore. Das bedeutet insbesondere:
|
||||||
|
|
||||||
- Solution: `BizTalkPlatformManagementTool.sln`
|
- Nur Artefakte, die im Snapshot aktiv waren, werden heruntergefahren.
|
||||||
- Projekt: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
|
- Nur Artefakte, die im Snapshot aktiv waren, werden wieder gestartet.
|
||||||
- UI: `src/BizTalkPlatformManagementTool/Ui/MainForm.cs`
|
- Zuvor gestoppte, gebundene oder deaktivierte Artefakte werden beim Restore nicht verändert.
|
||||||
- WMI-Zugriff: `src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs`
|
- Ein vorhandener Vorher-Snapshot wird durch **Herunterfahren** nicht ersetzt.
|
||||||
- Operationslogik: `src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs`
|
- Snapshot und Zielserver müssen übereinstimmen; Kurzname und FQDN desselben Servers werden akzeptiert.
|
||||||
- PowerShell-Archiv: `archive/powershell/BizTalkPlatformManagementTool.ps1`
|
- Bei mehrdeutiger WMI-Auflösung wird abgebrochen, statt möglicherweise das falsche Objekt zu verändern.
|
||||||
|
|
||||||
## UI Workflow
|
## Oberfläche
|
||||||
|
|
||||||
1. Anwendung mit Administratorrechten starten.
|
Die Oberfläche führt von links nach rechts durch sechs Schritte:
|
||||||
2. BizTalk-Servernamen eintragen.
|
|
||||||
3. Ausgabeverzeichnis wählen.
|
|
||||||
4. **Dry run** aktiviert lassen.
|
|
||||||
5. Mit **Diagnose** den Zugriff auf `root\MicrosoftBizTalkServer` prüfen.
|
|
||||||
6. Mit **Snapshot Before** die Datei `before.json` erzeugen.
|
|
||||||
7. Mit **Shutdown** den Plan `shutdown-plan.json` erzeugen und prüfen.
|
|
||||||
8. **Dry run** erst deaktivieren, wenn der Plan korrekt ist.
|
|
||||||
9. Nach der Wartung mit **Restore** aus `before.json` wiederherstellen.
|
|
||||||
10. Mit **Snapshot After** und **Compare** die Umgebung validieren.
|
|
||||||
|
|
||||||
Die Statusanzeige rechts im Kopfbereich bewertet die Host-Instance-Zustaende des letzten Snapshots und zeigt `Started`, `Stopped`, `Partial` oder `Unknown`. **Clear** leert die sichtbaren Ergebnis- und Log-Grids, loescht aber keine Dateien. **Close** beendet die Anwendung.
|
1. **Verbindung prüfen**
|
||||||
|
2. **Zustand erfassen**
|
||||||
|
3. **Herunterfahren**
|
||||||
|
4. **Wiederherstellen**
|
||||||
|
5. **Nachher erfassen**
|
||||||
|
6. **Vergleichen**
|
||||||
|
|
||||||
## Sicherheitsdesign
|
Kopfbereich, Status-Badge, einheitliche Eingabefelder, gleich große
|
||||||
|
Workflow-Schaltflächen, farblich getrennte destruktive und wiederherstellende
|
||||||
|
Aktionen sowie responsive Ergebnis- und Logtabellen sorgen für eine klare
|
||||||
|
Bedienung auch bei kleineren Bildschirmgrößen und DPI-Skalierung.
|
||||||
|
|
||||||
- Dry-run ist standardmäßig aktiviert.
|
Der Dry-run ist standardmäßig aktiviert. Bei echten Änderungen zeigt die
|
||||||
- Beim Start wird geprüft, ob die Anwendung mit Administratorrechten läuft. Ohne erhöhte Rechte wird eine Fehlermeldung angezeigt und die Anwendung beendet.
|
Statusleiste einen roten Hinweis und jede Änderung verlangt eine zusätzliche
|
||||||
- Echte Shutdown-/Restore-Aktionen verlangen bei deaktiviertem Dry-run eine zusätzliche Bestätigung.
|
Bestätigung mit Zielserver und verwendeter Zustandsdatei. Während einer
|
||||||
- Jede Operation schreibt Einträge in das sichtbare Operation Log.
|
Operation sind Eingaben gesperrt; das Fenster kann nicht versehentlich
|
||||||
- Zusätzlich wird neben der EXE eine tägliche Logdatei `BizTalkPlatformManagementTool-yyyy-MM-dd.log` geschrieben.
|
geschlossen werden.
|
||||||
- Logdateien werden rollierend für den aktuellen Tag plus vier vorherige Tage vorgehalten.
|
|
||||||
- Der Kopfbereich zeigt den zuletzt erkannten Umgebungsstatus aus den Host Instances.
|
|
||||||
- Operationspläne werden vor Laufzeitänderungen gespeichert.
|
|
||||||
- WMI-Methodenrückgaben werden geprüft.
|
|
||||||
- Wartezeiten nutzen konfigurierbare Timeout- und Polling-Werte.
|
|
||||||
- Host Instances auf anderen Servern werden übersprungen und als Warnung protokolliert.
|
|
||||||
- Echte Shutdown-/Restore-Schritte protokollieren WMI-Klasse, Schlüssel, Zielobjekt und Methode, damit Fehler wie WMI-Query- oder Methodenfehler eindeutig zugeordnet werden können.
|
|
||||||
|
|
||||||
## Shutdown-Reihenfolge
|
## Erfasster Zustand
|
||||||
|
|
||||||
1. Receive Locations deaktivieren, die im Snapshot aktiviert waren.
|
Über `root\MicrosoftBizTalkServer` werden gelesen:
|
||||||
2. Orchestrations stoppen, die gestartet waren.
|
|
||||||
3. Send Ports stoppen, die gestartet waren.
|
|
||||||
4. Host Instances stoppen, die auf dem ausgewählten Server gestartet waren.
|
|
||||||
|
|
||||||
## Restore-Reihenfolge
|
| WMI-Klasse | Relevanter Zustand |
|
||||||
|
| --- | --- |
|
||||||
|
| `MSBTS_ReceiveLocation` | `IsDisabled`, Name, Receive Port, Adapter |
|
||||||
|
| `MSBTS_SendPort` | `Status`, Name, Adaptertyp |
|
||||||
|
| `MSBTS_Orchestration` | `OrchestrationStatus`, Name und Assembly-Identität |
|
||||||
|
| `MSBTS_HostInstance` | `ServiceState`, Hostname und ausführender Server |
|
||||||
|
|
||||||
1. Host Instances starten, die auf dem ausgewählten Server zuvor gestartet waren.
|
Transportadressen werden bewusst nicht persistiert. Sie sind für Start/Stop
|
||||||
2. Send Ports wiederherstellen.
|
nicht erforderlich und können Benutzerinformationen, Kennwörter oder andere
|
||||||
3. Orchestrations wiederherstellen, soweit dies sicher möglich ist.
|
sensible Bestandteile enthalten.
|
||||||
4. Receive Locations zuletzt wiederherstellen.
|
|
||||||
|
|
||||||
## Restore-Grenzen
|
## Zustandswerte
|
||||||
|
|
||||||
- Send Ports werden auf `Started`, `Stopped` oder `Bound` zurückgesetzt.
|
- Send Port: `1=Bound`, `2=Stopped`, `3=Started`
|
||||||
- Orchestrations mit Status `Started`, `Stopped` oder `Unbound` werden direkt behandelt.
|
- Orchestrierung: `1=Unbound`, `2=Bound`, `3=Stopped`, `4=Started`
|
||||||
- Orchestrations mit Status `Bound` bleiben unverändert und werden zur manuellen Prüfung markiert, weil ein blindes Unenlist zu `Unbound` führen kann.
|
- Hostinstanz: `1=Stopped`, `2=StartPending`, `3=StopPending`, `4=Running`
|
||||||
- Receive Locations werden zuletzt aktiviert, damit eingehender Verkehr erst nach Host Instances und abhängigen Artefakten wieder aufgenommen wird.
|
- Receive Location: `Enabled = !IsDisabled`
|
||||||
|
|
||||||
## Ausgabedateien
|
Weitere Host-Servicezustände werden als unbekannt angezeigt und nicht
|
||||||
|
automatisch als gestartet behandelt.
|
||||||
|
|
||||||
- Snapshots: `before.json`, `after.json`
|
## Shutdown-Plan
|
||||||
- Operationspläne: `shutdown-plan.json`, `restore-plan.json`
|
|
||||||
- Nachher-Snapshots: `shutdown-after.json`, `restore-after.json`
|
|
||||||
- Diff: `diff.json`, `diff.csv`, `diff.html`
|
|
||||||
- Snapshot-Reports: `*.csv`, `*.hosts.csv`, `*.html`
|
|
||||||
- Laufzeitlogs neben der EXE: `BizTalkPlatformManagementTool-yyyy-MM-dd.log`
|
|
||||||
|
|
||||||
## Fehleranalyse
|
1. Alle im Snapshot aktivierten Receive Locations deaktivieren.
|
||||||
|
2. Alle im Snapshot gestarteten Orchestrierungen kontrolliert stoppen.
|
||||||
|
3. Alle im Snapshot gestarteten Send Ports stoppen.
|
||||||
|
4. Alle im Snapshot gestarteten Hostinstanzen des ausgewählten Servers stoppen.
|
||||||
|
|
||||||
Bei echten Shutdown- und Restore-Aktionen wird jeder Schritt vor der Ausführung mit Artefakttyp, WMI-Klasse, Schlüsselproperty, Schlüsselwert und Methodenname protokolliert. Die Objektauflösung verwendet eine breite `SELECT * FROM <class>`-Abfrage und filtert danach im Prozess auf den Schlüsselwert. Dadurch können Host-Instance-Namen und andere BizTalk-Namen mit Sonderzeichen keine ungültige WMI-WQL-`WHERE`-Query mehr erzeugen.
|
Hostinstanzen anderer BizTalk-Knoten erscheinen als übersprungene Hinweise.
|
||||||
|
Damit kann derselbe Gruppen-Snapshot nachvollziehbar bleiben, ohne remote
|
||||||
|
Hostinstanzen versehentlich vom falschen Knoten aus zu verändern.
|
||||||
|
|
||||||
Snapshot- und Plan-JSON-Dateien werden als UTF-8 ohne BOM geschrieben. Beim Laden werden vorhandene Dateien mit UTF-8-BOM oder durch Encoding-Konvertierung sichtbar gewordenem BOM-Marker toleriert.
|
## Restore-Plan
|
||||||
|
|
||||||
## Status Mapping
|
1. Zuvor gestartete Hostinstanzen starten.
|
||||||
|
2. Zuvor gestartete Send Ports starten.
|
||||||
|
3. Zuvor gestartete Orchestrierungen starten.
|
||||||
|
4. Zuvor aktivierte Receive Locations zuletzt aktivieren.
|
||||||
|
|
||||||
- `MSBTS_SendPort.Status`: `1=Bound`, `2=Stopped`, `3=Started`
|
Gestoppte oder gebundene Send Ports, gestoppte/gebundene/ungebundene
|
||||||
- `MSBTS_Orchestration.OrchestrationStatus`: `1=Unbound`, `2=Bound`, `3=Stopped`, `4=Started`
|
Orchestrierungen, deaktivierte Receive Locations und gestoppte Hostinstanzen
|
||||||
- `MSBTS_ReceiveLocation`: `Enabled = !IsDisabled`
|
erzeugen keine Restore-Aktion.
|
||||||
- `MSBTS_HostInstance.ServiceState`: `1=Stopped`, `2=StartPending`, `3=StopPending`, `4=Started`
|
|
||||||
|
|
||||||
## Anforderungen
|
## Eindeutige WMI-Auflösung
|
||||||
|
|
||||||
- Windows Server oder Administrationshost mit .NET Framework 4.6.1
|
WMI-Abfragen werden ohne dynamisch zusammengesetzte WQL-`WHERE`-Klauseln
|
||||||
- BizTalk Server 2020 oder installierte BizTalk Administration Tools
|
ausgeführt. Die Filterung erfolgt im Prozess und behandelt Sonderzeichen in
|
||||||
- Berechtigungen auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
Namen sicher. Zusätzlich werden verwendet:
|
||||||
- Lokale Administratorrechte und Start der Anwendung mit **Als Administrator ausführen**
|
|
||||||
- Visual Studio mit .NET Framework 4.6.1 Developer Pack für Builds
|
- Receive Location: `Name` plus `ReceivePortName`
|
||||||
|
- Orchestrierung: `Name` plus `AssemblyName`
|
||||||
|
- Hostinstanz: `InstanceName` plus `RunningServer`
|
||||||
|
|
||||||
|
Die vollständige Assembly-Identität einer Orchestrierung wird im Snapshot und
|
||||||
|
beim Vergleich berücksichtigt. Liefert WMI trotz der Qualifier mehrere
|
||||||
|
Treffer, wird die Operation mit einer eindeutigen Fehlermeldung abgebrochen.
|
||||||
|
Jeder Schritt prüft vor dem Methodenaufruf den aktuellen Zustand. Bereits
|
||||||
|
erreichte Zielzustände werden protokolliert und übersprungen; dadurch können
|
||||||
|
teilweise ausgeführte Pläne nach einer Fehlerkorrektur sicher erneut gestartet
|
||||||
|
werden.
|
||||||
|
|
||||||
|
## Dateisicherheit
|
||||||
|
|
||||||
|
JSON-Dateien werden zunächst vollständig in eine temporäre Datei geschrieben,
|
||||||
|
auf den Datenträger gespült und anschließend atomar ersetzt. Leere Dateien und
|
||||||
|
Dateien über 16 MiB werden verworfen. UTF-8 mit und ohne BOM sowie der sichtbare
|
||||||
|
BOM-Marker älterer Konvertierungen werden beim Lesen toleriert.
|
||||||
|
|
||||||
|
Vergleiche verwenden zusammengesetzte Identitäten. Gleichnamige Artefakte in
|
||||||
|
unterschiedlichen Anwendungen, Receive Ports oder Assembly-Versionen werden
|
||||||
|
nicht miteinander vermischt.
|
||||||
|
|
||||||
|
## Verzeichnisse
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramFiles%\BizTalkPlatformManagementTool\
|
||||||
|
BizTalkPlatformManagementTool.exe
|
||||||
|
BizTalkPlatformManagementTool.exe.config
|
||||||
|
|
||||||
|
%ProgramData%\BizTalkPlatformManagementTool\
|
||||||
|
Wartungen\
|
||||||
|
Logs\
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Trennung verhindert, dass ein Update Zustandsdateien überschreibt, und
|
||||||
|
ermöglicht normale Logrotation außerhalb des geschützten Programmverzeichnisses.
|
||||||
|
Logs werden für den aktuellen Tag und vier vorherige Tage aufbewahrt.
|
||||||
|
|
||||||
|
## Installer und Rollback
|
||||||
|
|
||||||
|
Der .NET-Installer benötigt keine PowerShell. Er validiert die Assembly, baut
|
||||||
|
ein Staging-Verzeichnis auf, verschiebt eine bestehende Installation in ein
|
||||||
|
Backup und aktiviert anschließend die neue Version. Bei einem Fehler wird das
|
||||||
|
Backup zurückverschoben. Startmenü- und optionale Desktop-Verknüpfung werden
|
||||||
|
systemweit angelegt.
|
||||||
|
|
||||||
|
Der Release-Packager erzeugt den Installationsordner, ein ZIP, eine
|
||||||
|
Base64-Transportdatei und eine SHA-256-Datei.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Die Regressionstests prüfen:
|
||||||
|
|
||||||
|
- Restore enthält ausschließlich zuvor aktive Artefakte.
|
||||||
|
- Shutdown enthält ausschließlich zuvor aktive Artefakte.
|
||||||
|
- Receive Locations erhalten ihren zusätzlichen WMI-Qualifier.
|
||||||
|
- Vergleiche trennen gleichnamige Artefakte verschiedener Anwendungen.
|
||||||
|
- fremde Server-Snapshots werden abgewiesen.
|
||||||
|
- atomarer JSON-Ersatz hinterlässt keine temporären Dateien.
|
||||||
|
|
||||||
|
Ein echter WMI-Integrationstest muss auf einem BizTalk-2020-System im Dry-run
|
||||||
|
und anschließend in einem freigegebenen Wartungsfenster erfolgen.
|
||||||
|
|||||||
+49
-30
@@ -1,40 +1,59 @@
|
|||||||
# Installation
|
# Installation und Update
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
- Windows Server 2019/2022 oder ein Windows-Administrationshost
|
- Windows Server 2019/2022 oder Administrationshost
|
||||||
- Microsoft BizTalk Server 2020 oder BizTalk Administration Tools
|
- Microsoft BizTalk Server 2020 oder installierte BizTalk Administration Tools
|
||||||
- .NET Framework 4.6.1 Runtime
|
- .NET Framework 4.6.1 oder neuer
|
||||||
- Für Builds: Visual Studio mit .NET Framework 4.6.1 Developer Pack
|
- lokales Administratorkonto
|
||||||
- Zugriff auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
- Mitgliedschaft in der Gruppe **BizTalk Server Administrators**
|
||||||
- Ausreichende Rechte zum Lesen und Ändern von BizTalk-Artefakten
|
- WMI-Zugriff auf `root\MicrosoftBizTalkServer`
|
||||||
- Lokale Administratorrechte und Start der EXE mit **Als Administrator ausführen**
|
|
||||||
- Schreibrechte im Verzeichnis der EXE für die tägliche Logdatei
|
|
||||||
|
|
||||||
## Build
|
## Installation
|
||||||
|
|
||||||
1. Repository öffnen.
|
1. `BizTalkPlatformManagementTool-Setup.zip` vollständig entpacken.
|
||||||
2. `BizTalkPlatformManagementTool.sln` in Visual Studio öffnen.
|
2. `Setup.exe` starten; die UAC-Anforderung bestätigen.
|
||||||
3. Konfiguration `Release|Any CPU` auswählen.
|
3. Optional die systemweite Desktop-Verknüpfung auswählen.
|
||||||
4. Solution bauen.
|
4. **Installieren / aktualisieren** wählen.
|
||||||
5. Das Ergebnis liegt unter `src\BizTalkPlatformManagementTool\bin\Release\`.
|
|
||||||
|
|
||||||
## Deployment
|
Installiert wird nach `%ProgramFiles%\BizTalkPlatformManagementTool`.
|
||||||
|
Persistente Wartungszustände und Logs liegen getrennt unter
|
||||||
|
`%ProgramData%\BizTalkPlatformManagementTool`.
|
||||||
|
|
||||||
1. Den Release-Ordner auf einen BizTalk-Server oder einen Administrationshost kopieren.
|
## Sicheres Update
|
||||||
2. Sicherstellen, dass der ausführende Benutzer WMI-Zugriff auf `root\MicrosoftBizTalkServer` hat.
|
|
||||||
3. `BizTalkPlatformManagementTool.exe` mit **Als Administrator ausführen** starten.
|
Der Installer validiert das Paket, kopiert die neue Version zunächst in ein
|
||||||
4. Als Ausgabeverzeichnis einen Ordner wählen, in dem Plan-, Snapshot- und Report-Dateien abgelegt werden dürfen.
|
Staging-Verzeichnis und aktiviert sie erst danach. Eine vorhandene Installation
|
||||||
5. Prüfen, dass im EXE-Verzeichnis `BizTalkPlatformManagementTool-yyyy-MM-dd.log` geschrieben werden kann. Logs werden für maximal fünf Tage vorgehalten.
|
wird vorübergehend umbenannt. Schlägt das Update fehl, wird die vorherige
|
||||||
|
Programmversion wiederhergestellt. Wartungszustände und Logs unter
|
||||||
|
`%ProgramData%` bleiben unberührt.
|
||||||
|
|
||||||
|
Vor dem Update muss das Management Tool geschlossen sein. Laufende
|
||||||
|
Wartungsoperationen dürfen nicht durch ein Update unterbrochen werden.
|
||||||
|
|
||||||
|
## Deinstallation
|
||||||
|
|
||||||
|
Im Setup kann die Anwendung wieder deinstalliert werden. Standardmäßig bleiben
|
||||||
|
Wartungszustände und Logs erhalten. Die Option kann deaktiviert werden, wenn
|
||||||
|
auch diese Daten bewusst entfernt werden sollen.
|
||||||
|
|
||||||
|
## Transport-TXT zurückwandeln
|
||||||
|
|
||||||
|
Die Datei `BizTalkPlatformManagementTool-Setup.zip.b64.txt` kann auf dem
|
||||||
|
Zielsystem beispielsweise mit `certutil.exe` zurückgewandelt werden:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
certutil.exe -decode BizTalkPlatformManagementTool-Setup.zip.b64.txt BizTalkPlatformManagementTool-Setup.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Anschließend die SHA-256-Prüfsumme mit
|
||||||
|
`BizTalkPlatformManagementTool-Setup.zip.sha256.txt` vergleichen.
|
||||||
|
|
||||||
## Erster Funktionstest
|
## Erster Funktionstest
|
||||||
|
|
||||||
1. Anwendung mit **Als Administrator ausführen** starten.
|
1. Tool über Startmenü oder Desktop-Verknüpfung starten.
|
||||||
2. Zielserver eintragen oder den vorgeschlagenen lokalen Server verwenden.
|
2. Dry-run aktiviert lassen.
|
||||||
3. **Dry run** aktiviert lassen.
|
3. **Verbindung prüfen** ausführen.
|
||||||
4. **Diagnose** ausführen.
|
4. **Zustand erfassen** ausführen.
|
||||||
5. **Snapshot Before** ausführen und prüfen, ob `before.json` sowie CSV/HTML-Reports erzeugt wurden.
|
5. JSON-, CSV- und HTML-Dateien im Arbeitsordner prüfen.
|
||||||
|
6. **Herunterfahren** im Dry-run ausführen und den Plan kontrollieren.
|
||||||
## Produktive Nutzung
|
|
||||||
|
|
||||||
Vor produktiven Änderungen immer zuerst einen Dry-run ausführen und die erzeugten `shutdown-plan.json` beziehungsweise `restore-plan.json` prüfen. Dry-run erst deaktivieren, wenn der Plan fachlich und technisch korrekt ist.
|
|
||||||
|
|||||||
@@ -1,87 +1,105 @@
|
|||||||
# BizTalk Platform Management Tool
|
# BizTalk Platform Management Tool
|
||||||
|
|
||||||
WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations during maintenance windows. The tool captures runtime snapshots, creates shutdown and restore plans, executes those plans in a safe order, and writes diff reports for validation.
|
Grafisches .NET-Werkzeug für kontrollierte Wartungsfenster einer Microsoft
|
||||||
|
BizTalk-Server-2020-Umgebung. Es erfasst den Laufzeitzustand, fährt ausschließlich
|
||||||
|
die zuvor aktiven Artefakte in sicherer Reihenfolge herunter und startet nach
|
||||||
|
dem Patchen ausschließlich diese Artefakte wieder.
|
||||||
|
|
||||||
## Current State
|
## Wichtigste Eigenschaften
|
||||||
|
|
||||||
- Application: C# WinForms
|
- übersichtliche deutschsprachige WinForms-Oberfläche mit klarer Sechs-Schritt-Führung
|
||||||
- Target framework: .NET Framework 4.6.1
|
- Dry-run standardmäßig aktiv
|
||||||
- Solution: `BizTalkPlatformManagementTool.sln`
|
- Vorher-Snapshot als verbindliche Wiederherstellungsgrundlage
|
||||||
- Project: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
|
- vorhandener Vorher-Snapshot wird beim Herunterfahren nicht überschrieben
|
||||||
- Primary namespace and assembly: `BizTalkPlatformManagementTool`
|
- servergebundene Validierung verhindert die Verwendung einer Zustandsdatei für den falschen BizTalk-Server
|
||||||
- Legacy PowerShell archive: `archive/powershell/BizTalkPlatformManagementTool.ps1`
|
- Receive Locations, Send Ports, Orchestrierungen und Hostinstanzen
|
||||||
|
- kontrollierte Reihenfolge mit Zustandsprüfung und konfigurierbarem Timeout
|
||||||
|
- nur vorher aktive Artefakte werden heruntergefahren und wieder gestartet
|
||||||
|
- WMI-Objekte werden eindeutig aufgelöst; mehrdeutige Treffer führen sicher zum Abbruch
|
||||||
|
- wiederholbare Ausführung: bereits erreichte Zielzustände werden übersprungen
|
||||||
|
- atomar geschriebene JSON-Dateien mit Größenbegrenzung
|
||||||
|
- keine Transport-URIs, Benutzernamen oder Kennwörter im Snapshot
|
||||||
|
- CSV-/HTML-Berichte und Vorher-/Nachher-Vergleich
|
||||||
|
- tägliche Logs unter `%ProgramData%\BizTalkPlatformManagementTool\Logs`
|
||||||
|
- eigenständiger .NET-Installer für Installation, Update und Deinstallation
|
||||||
|
- ZIP-, SHA-256- und Base64-TXT-Paket werden beim Release-Build erzeugt
|
||||||
|
|
||||||
## Features
|
## Sicherer Wartungsablauf
|
||||||
|
|
||||||
- Snapshot before and after downtime as JSON, CSV and HTML
|
1. Tool als Administrator starten.
|
||||||
- Diff between `before.json` and `after.json`
|
2. Zielserver und Arbeitsordner prüfen.
|
||||||
- Controlled shutdown from the current runtime state
|
3. **Verbindung prüfen** ausführen.
|
||||||
- Controlled restore from `before.json`
|
4. Mit aktiviertem Dry-run **Zustand erfassen**.
|
||||||
- Host instance handling for the selected BizTalk server
|
5. **Herunterfahren** im Dry-run ausführen und `shutdown-plan.json` prüfen.
|
||||||
- Dry-run mode enabled by default
|
6. Dry-run deaktivieren und **Herunterfahren** bestätigen.
|
||||||
- WMI access through `root\MicrosoftBizTalkServer`
|
7. Server manuell patchen.
|
||||||
- Startup check for administrator rights
|
8. Dieselbe Vorher-Zustandsdatei auswählen.
|
||||||
- Detailed operation logging in the GUI and daily rolling log files next to the executable
|
9. **Wiederherstellen** zunächst im Dry-run prüfen und danach real ausführen.
|
||||||
- Environment status indicator based on host instance state
|
10. **Nachher erfassen** und **Vergleichen**.
|
||||||
- Clear and Close actions in the main toolbar
|
|
||||||
- No compile-time dependency on BizTalk ExplorerOM assemblies
|
|
||||||
|
|
||||||
## Safe Usage
|
Die Zustandsdatei ist sicherheitskritisch: Sie definiert exakt, welche
|
||||||
|
Artefakte wieder gestartet werden. Bewusst gestoppte, gebundene oder
|
||||||
|
deaktivierte Artefakte bleiben beim Restore unangetastet.
|
||||||
|
|
||||||
1. Start the app with **Run as administrator** on a BizTalk server or a management host with WMI access.
|
## Reihenfolge
|
||||||
2. Keep **Dry run** enabled.
|
|
||||||
3. Click **Diagnose** to verify WMI access.
|
|
||||||
4. Click **Snapshot Before**.
|
|
||||||
5. Click **Shutdown** and review `shutdown-plan.json`.
|
|
||||||
6. Disable **Dry run** only when the plan is correct.
|
|
||||||
7. After maintenance, click **Restore** using the saved `before.json`.
|
|
||||||
8. Click **Snapshot After** and **Compare**.
|
|
||||||
|
|
||||||
The environment indicator shows `Started`, `Stopped`, `Partial` or `Unknown` from the most recent snapshot. `Clear` removes the visible status and operation log grids; it does not delete files.
|
Herunterfahren:
|
||||||
|
|
||||||
The application checks administrator rights during startup. If it is not elevated, it shows an error message and exits because BizTalk WMI operations require an elevated administrator process.
|
1. zuvor aktivierte Receive Locations deaktivieren
|
||||||
|
2. zuvor gestartete Orchestrierungen stoppen
|
||||||
|
3. zuvor gestartete Send Ports stoppen
|
||||||
|
4. zuvor gestartete Hostinstanzen des ausgewählten Servers stoppen
|
||||||
|
|
||||||
## Operation Order
|
Wiederherstellen:
|
||||||
|
|
||||||
Shutdown:
|
1. zuvor gestartete Hostinstanzen des ausgewählten Servers starten
|
||||||
- Disable receive locations that were enabled.
|
2. zuvor gestartete Send Ports starten
|
||||||
- Stop orchestrations that were started.
|
3. zuvor gestartete Orchestrierungen starten
|
||||||
- Stop send ports that were started.
|
4. zuvor aktivierte Receive Locations zuletzt aktivieren
|
||||||
- Stop host instances that were started on the selected server.
|
|
||||||
|
|
||||||
Restore:
|
## Dateien
|
||||||
- Start host instances that were previously started.
|
|
||||||
- Restore send ports to Started, Stopped or Bound.
|
|
||||||
- Restore orchestrations where safe.
|
|
||||||
- Restore receive locations last.
|
|
||||||
|
|
||||||
Orchestrations that were `Bound` are deliberately left unchanged during restore to avoid accidentally making them `Unbound`.
|
Standard-Arbeitsbereich:
|
||||||
|
|
||||||
## Outputs
|
```text
|
||||||
|
%ProgramData%\BizTalkPlatformManagementTool\
|
||||||
|
Wartungen\
|
||||||
|
Wartungsstatus-YYYYMMDD-HHmmss.json
|
||||||
|
shutdown-plan.json
|
||||||
|
shutdown-after.json
|
||||||
|
restore-plan.json
|
||||||
|
restore-after.json
|
||||||
|
after.json
|
||||||
|
diff.json / diff.csv / diff.html
|
||||||
|
Logs\
|
||||||
|
BizTalkPlatformManagementTool-YYYY-MM-DD.log
|
||||||
|
```
|
||||||
|
|
||||||
- `before.json`, `after.json`
|
Zu jedem Snapshot entstehen CSV- und HTML-Begleitberichte. Vollständige
|
||||||
- `shutdown-plan.json`, `restore-plan.json`
|
Transportadressen werden nicht gespeichert, weil sie Zugangsdaten oder andere
|
||||||
- `shutdown-after.json`, `restore-after.json`
|
sensible URI-Bestandteile enthalten können und für die Wiederherstellung nicht
|
||||||
- `diff.json`, `diff.csv`, `diff.html`
|
benötigt werden.
|
||||||
- Snapshot sidecars: `*.csv`, `*.hosts.csv`, `*.html`
|
|
||||||
- Runtime logs next to the executable: `BizTalkPlatformManagementTool-yyyy-MM-dd.log`
|
|
||||||
|
|
||||||
Log files are retained for the current day plus the previous four days. Older `BizTalkPlatformManagementTool-*.log` files are removed on startup.
|
## Build und Tests
|
||||||
|
|
||||||
## Troubleshooting
|
Voraussetzung ist das .NET Framework 4.6.1 Developer Pack:
|
||||||
|
|
||||||
The Operation Log shows the WMI class, key property, key value and method for real shutdown and restore steps. WMI objects are resolved with a broad `SELECT * FROM <class>` query and a client-side key filter so names containing special characters do not break the WMI query parser.
|
```cmd
|
||||||
|
msbuild BizTalkPlatformManagementTool.sln /t:Rebuild /p:Configuration=Release /p:Platform="Any CPU"
|
||||||
|
tests\BizTalkPlatformManagementTool.Tests\bin\Release\BizTalkPlatformManagementTool.Tests.exe
|
||||||
|
```
|
||||||
|
|
||||||
Snapshot and plan JSON files are written as UTF-8 without BOM. Loading is tolerant of existing files that contain a UTF-8 BOM or a visible BOM marker from previous encoding conversions.
|
Der Release-Build erzeugt automatisch:
|
||||||
|
|
||||||
## Build
|
```text
|
||||||
|
artifacts\BizTalkPlatformManagementTool-Setup\
|
||||||
|
artifacts\BizTalkPlatformManagementTool-Setup.zip
|
||||||
|
artifacts\BizTalkPlatformManagementTool-Setup.zip.b64.txt
|
||||||
|
artifacts\BizTalkPlatformManagementTool-Setup.zip.sha256.txt
|
||||||
|
```
|
||||||
|
|
||||||
Open `BizTalkPlatformManagementTool.sln` in Visual Studio on Windows with the .NET Framework 4.6.1 Developer Pack installed, then build the `Release|Any CPU` configuration.
|
## Dokumentation
|
||||||
|
|
||||||
The app targets .NET Framework 4.6.1 for compatibility with customer environments that do not have newer .NET Framework developer packs installed.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [Installation](Installation.md)
|
- [Installation](Installation.md)
|
||||||
- [Dokumentation](Dokumentation.md)
|
- [Technische Dokumentation](Dokumentation.md)
|
||||||
- [References](REFERENCES.md)
|
- [Microsoft-Referenzen](REFERENCES.md)
|
||||||
|
|||||||
@@ -12,8 +12,11 @@
|
|||||||
- MSBTS_Orchestration.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-stop-method-wmi
|
- MSBTS_Orchestration.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-stop-method-wmi
|
||||||
- MSBTS_Orchestration.Unenlist (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-unenlist-method-wmi
|
- MSBTS_Orchestration.Unenlist (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-unenlist-method-wmi
|
||||||
- MSBTS_ReceiveLocation (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-wmi
|
- MSBTS_ReceiveLocation (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-wmi
|
||||||
|
- MSBTS_ReceiveLocation.Name / zusammengesetzter Schlüssel: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-name-property-wmi
|
||||||
- MSBTS_ReceiveLocation.Enable (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-enable-method-wmi
|
- MSBTS_ReceiveLocation.Enable (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-enable-method-wmi
|
||||||
- MSBTS_ReceiveLocation.Disable (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-disable-method-wmi
|
- MSBTS_ReceiveLocation.Disable (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-disable-method-wmi
|
||||||
- MSBTS_HostInstance (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-wmi
|
- MSBTS_HostInstance (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-wmi
|
||||||
- MSBTS_HostInstance.Start (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-start-method-wmi
|
- MSBTS_HostInstance.Start (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-start-method-wmi
|
||||||
- MSBTS_HostInstance.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-stop-method-wmi
|
- MSBTS_HostInstance.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-stop-method-wmi
|
||||||
|
- MSBTS_HostInstance.ServiceState (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-servicestate-property-wmi
|
||||||
|
- MSBTS_Orchestration.AssemblyName / zusammengesetzter Schlüssel: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-assemblyname-property-wmi
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{AF67FD76-8F5D-42DB-9D52-20EF6332D1F9}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>BizTalkPlatformManagementTool.Packager</RootNamespace>
|
||||||
|
<AssemblyName>BizTalkPlatformManagementTool.Packager</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols><DebugType>full</DebugType><Optimize>false</Optimize><OutputPath>bin\Debug\</OutputPath><DefineConstants>DEBUG;TRACE</DefineConstants><WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType><Optimize>true</Optimize><OutputPath>bin\Release\</OutputPath><DefineConstants>TRACE</DefineConstants><WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" /><Reference Include="System.Core" /><Reference Include="System.IO.Compression" /><Reference Include="System.IO.Compression.FileSystem" /><Reference Include="System.Security" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup><Compile Include="Program.cs" /></ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj"><Project>{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}</Project><Name>BizTalkPlatformManagementTool</Name><ReferenceOutputAssembly>false</ReferenceOutputAssembly></ProjectReference>
|
||||||
|
<ProjectReference Include="..\BizTalkPlatformManagementTool.Setup\BizTalkPlatformManagementTool.Setup.csproj"><Project>{38E61630-11AB-4F8D-B421-F8A88831C4BD}</Project><Name>BizTalkPlatformManagementTool.Setup</Name><ReferenceOutputAssembly>false</ReferenceOutputAssembly></ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Target Name="CreateInstallPackage" AfterTargets="Build" Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<Exec Command=""$(TargetPath)" "$(MSBuildProjectDirectory)\..\.." "$(Configuration)"" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizTalkPlatformManagementTool.Packager
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static int Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (args.Length != 2) throw new ArgumentException("Usage: Packager.exe <repository-root> <configuration>");
|
||||||
|
var root = Path.GetFullPath(args[0]);
|
||||||
|
var configuration = args[1];
|
||||||
|
var artifacts = Path.Combine(root, "artifacts");
|
||||||
|
var package = Path.Combine(artifacts, "BizTalkPlatformManagementTool-Setup");
|
||||||
|
var zip = package + ".zip";
|
||||||
|
var base64 = zip + ".b64.txt";
|
||||||
|
var checksum = zip + ".sha256.txt";
|
||||||
|
RecreateDirectory(package);
|
||||||
|
Directory.CreateDirectory(Path.Combine(package, "application"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(package, "documentation"));
|
||||||
|
|
||||||
|
var applicationOutput = Path.Combine(root, "src", "BizTalkPlatformManagementTool", "bin", configuration);
|
||||||
|
var setupOutput = Path.Combine(root, "src", "BizTalkPlatformManagementTool.Setup", "bin", configuration);
|
||||||
|
CopyRequired(Path.Combine(applicationOutput, "BizTalkPlatformManagementTool.exe"), Path.Combine(package, "application", "BizTalkPlatformManagementTool.exe"));
|
||||||
|
CopyRequired(Path.Combine(applicationOutput, "BizTalkPlatformManagementTool.exe.config"), Path.Combine(package, "application", "BizTalkPlatformManagementTool.exe.config"));
|
||||||
|
CopyRequired(Path.Combine(setupOutput, "BizTalkPlatformManagementTool.Setup.exe"), Path.Combine(package, "Setup.exe"));
|
||||||
|
CopyRequired(Path.Combine(root, "README.md"), Path.Combine(package, "documentation", "README.md"));
|
||||||
|
CopyRequired(Path.Combine(root, "Dokumentation.md"), Path.Combine(package, "documentation", "Dokumentation.md"));
|
||||||
|
CopyRequired(Path.Combine(root, "Installation.md"), Path.Combine(package, "documentation", "Installation.md"));
|
||||||
|
|
||||||
|
DeleteIfExists(zip);
|
||||||
|
DeleteIfExists(base64);
|
||||||
|
DeleteIfExists(checksum);
|
||||||
|
ZipFile.CreateFromDirectory(package, zip, CompressionLevel.Optimal, false);
|
||||||
|
WriteBase64(zip, base64);
|
||||||
|
var hash = ComputeSha256(zip);
|
||||||
|
File.WriteAllText(checksum, hash + " " + Path.GetFileName(zip) + "\r\n", new UTF8Encoding(false));
|
||||||
|
Console.WriteLine("Installationsordner: " + package);
|
||||||
|
Console.WriteLine("Installations-ZIP: " + zip);
|
||||||
|
Console.WriteLine("Transport-TXT: " + base64);
|
||||||
|
Console.WriteLine("SHA-256: " + hash);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Packaging fehlgeschlagen: " + ex.Message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteBase64(string source, string target)
|
||||||
|
{
|
||||||
|
var encoded = Convert.ToBase64String(File.ReadAllBytes(source));
|
||||||
|
using (var writer = new StreamWriter(target, false, new UTF8Encoding(false)))
|
||||||
|
{
|
||||||
|
for (var offset = 0; offset < encoded.Length; offset += 120)
|
||||||
|
writer.WriteLine(encoded.Substring(offset, Math.Min(120, encoded.Length - offset)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ComputeSha256(string path)
|
||||||
|
{
|
||||||
|
using (var algorithm = SHA256.Create())
|
||||||
|
using (var stream = File.OpenRead(path))
|
||||||
|
{
|
||||||
|
return BitConverter.ToString(algorithm.ComputeHash(stream)).Replace("-", string.Empty).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyRequired(string source, string destination)
|
||||||
|
{
|
||||||
|
if (!File.Exists(source)) throw new FileNotFoundException("Datei fehlt: " + source, source);
|
||||||
|
File.Copy(source, destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RecreateDirectory(string path)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(path)) Directory.Delete(path, true);
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteIfExists(string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path)) File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{38E61630-11AB-4F8D-B421-F8A88831C4BD}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>BizTalkPlatformManagementTool.Setup</RootNamespace>
|
||||||
|
<AssemblyName>BizTalkPlatformManagementTool.Setup</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols><DebugType>full</DebugType><Optimize>false</Optimize><OutputPath>bin\Debug\</OutputPath><DefineConstants>DEBUG;TRACE</DefineConstants><WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType><Optimize>true</Optimize><OutputPath>bin\Release\</OutputPath><DefineConstants>TRACE</DefineConstants><WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" /><Reference Include="System.Core" /><Reference Include="System.Drawing" /><Reference Include="System.Windows.Forms" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="InstallerEngine.cs" /><Compile Include="MainForm.cs" /><Compile Include="Program.cs" /><None Include="app.manifest" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj"><Project>{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}</Project><Name>BizTalkPlatformManagementTool</Name><ReferenceOutputAssembly>false</ReferenceOutputAssembly></ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizTalkPlatformManagementTool.Setup
|
||||||
|
{
|
||||||
|
internal sealed class InstallerEngine
|
||||||
|
{
|
||||||
|
private const string ProductName = "BizTalkPlatformManagementTool";
|
||||||
|
private readonly string _packageDirectory;
|
||||||
|
private readonly string _installDirectory;
|
||||||
|
private readonly string _runtimeDirectory;
|
||||||
|
|
||||||
|
public InstallerEngine(string packageDirectory)
|
||||||
|
{
|
||||||
|
_packageDirectory = Path.GetFullPath(packageDirectory);
|
||||||
|
_installDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), ProductName);
|
||||||
|
_runtimeDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), ProductName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInstalled
|
||||||
|
{
|
||||||
|
get { return File.Exists(Path.Combine(_installDirectory, "BizTalkPlatformManagementTool.exe")); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Install(bool createDesktopShortcut, Action<string> report)
|
||||||
|
{
|
||||||
|
report = report ?? delegate { };
|
||||||
|
var source = Path.Combine(_packageDirectory, "application");
|
||||||
|
var sourceExe = Path.Combine(source, "BizTalkPlatformManagementTool.exe");
|
||||||
|
RequireFile(sourceExe);
|
||||||
|
RequireFile(sourceExe + ".config");
|
||||||
|
var assembly = AssemblyName.GetAssemblyName(sourceExe);
|
||||||
|
report("Paket geprüft: Version " + assembly.Version + ".");
|
||||||
|
|
||||||
|
var staging = _installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
||||||
|
var backup = _installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
||||||
|
var hadInstallation = Directory.Exists(_installDirectory);
|
||||||
|
var backupCreated = false;
|
||||||
|
var activated = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CopyDirectory(source, staging);
|
||||||
|
AssemblyName.GetAssemblyName(Path.Combine(staging, "BizTalkPlatformManagementTool.exe"));
|
||||||
|
report("Neue Version im Staging-Verzeichnis validiert.");
|
||||||
|
|
||||||
|
if (hadInstallation)
|
||||||
|
{
|
||||||
|
Directory.Move(_installDirectory, backup);
|
||||||
|
backupCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.Move(staging, _installDirectory);
|
||||||
|
activated = true;
|
||||||
|
Directory.CreateDirectory(Path.Combine(_runtimeDirectory, "Wartungen"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(_runtimeDirectory, "Logs"));
|
||||||
|
CreateShortcuts(createDesktopShortcut);
|
||||||
|
report(hadInstallation ? "Programmdateien sicher aktualisiert." : "Programmdateien installiert.");
|
||||||
|
report("Arbeitsdaten bleiben unter " + _runtimeDirectory + ".");
|
||||||
|
TryDeleteDirectory(backup);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var rollbackErrors = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (activated && Directory.Exists(_installDirectory)) Directory.Delete(_installDirectory, true);
|
||||||
|
if (backupCreated && Directory.Exists(backup)) Directory.Move(backup, _installDirectory);
|
||||||
|
}
|
||||||
|
catch (Exception rollback)
|
||||||
|
{
|
||||||
|
rollbackErrors.Add(rollback.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Installation/Update fehlgeschlagen. "
|
||||||
|
+ (rollbackErrors.Count == 0 ? "Die vorherige Version wurde wiederhergestellt. " : "Rollbackfehler: " + string.Join(" | ", rollbackErrors) + ". ")
|
||||||
|
+ "Ursache: " + ex.Message,
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TryDeleteDirectory(staging);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Uninstall(bool keepRuntimeData, Action<string> report)
|
||||||
|
{
|
||||||
|
report = report ?? delegate { };
|
||||||
|
DeleteShortcuts();
|
||||||
|
if (Directory.Exists(_installDirectory)) Directory.Delete(_installDirectory, true);
|
||||||
|
if (!keepRuntimeData && Directory.Exists(_runtimeDirectory)) Directory.Delete(_runtimeDirectory, true);
|
||||||
|
report(keepRuntimeData
|
||||||
|
? "Programm entfernt; Wartungszustände und Logs wurden beibehalten."
|
||||||
|
: "Programm und Arbeitsdaten wurden entfernt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateShortcuts(bool desktop)
|
||||||
|
{
|
||||||
|
var target = Path.Combine(_installDirectory, "BizTalkPlatformManagementTool.exe");
|
||||||
|
var programs = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), "JR IT Services");
|
||||||
|
Directory.CreateDirectory(programs);
|
||||||
|
WriteInternetShortcut(Path.Combine(programs, "BizTalk Platform Management Tool.url"), target);
|
||||||
|
var desktopPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory), "BizTalk Platform Management Tool.url");
|
||||||
|
if (desktop) WriteInternetShortcut(desktopPath, target); else if (File.Exists(desktopPath)) File.Delete(desktopPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteInternetShortcut(string path, string target)
|
||||||
|
{
|
||||||
|
var uri = new Uri(target).AbsoluteUri;
|
||||||
|
var content = "[InternetShortcut]\r\nURL=" + uri + "\r\nIconFile=" + target + "\r\nIconIndex=0\r\n";
|
||||||
|
File.WriteAllText(path, content, new UTF8Encoding(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteShortcuts()
|
||||||
|
{
|
||||||
|
var start = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), "JR IT Services", "BizTalk Platform Management Tool.url");
|
||||||
|
var desktop = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory), "BizTalk Platform Management Tool.url");
|
||||||
|
if (File.Exists(start)) File.Delete(start);
|
||||||
|
if (File.Exists(desktop)) File.Delete(desktop);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyDirectory(string source, string destination)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destination);
|
||||||
|
foreach (var file in Directory.GetFiles(source))
|
||||||
|
File.Copy(file, Path.Combine(destination, Path.GetFileName(file)), false);
|
||||||
|
foreach (var directory in Directory.GetDirectories(source))
|
||||||
|
CopyDirectory(directory, Path.Combine(destination, Path.GetFileName(directory)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RequireFile(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path)) throw new FileNotFoundException("Installationspaket ist unvollständig. Datei fehlt: " + path, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryDeleteDirectory(string path)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(path)) return;
|
||||||
|
try { Directory.Delete(path, true); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizTalkPlatformManagementTool.Setup
|
||||||
|
{
|
||||||
|
internal sealed class MainForm : Form
|
||||||
|
{
|
||||||
|
private readonly InstallerEngine _engine;
|
||||||
|
private readonly CheckBox _desktop = new CheckBox();
|
||||||
|
private readonly CheckBox _keepData = new CheckBox();
|
||||||
|
private readonly Button _install = new Button();
|
||||||
|
private readonly Button _uninstall = new Button();
|
||||||
|
private readonly TextBox _status = new TextBox();
|
||||||
|
|
||||||
|
public MainForm(InstallerEngine engine)
|
||||||
|
{
|
||||||
|
_engine = engine;
|
||||||
|
Text = "BizTalk Platform Management Tool – Setup";
|
||||||
|
ClientSize = new Size(680, 500);
|
||||||
|
MinimumSize = new Size(696, 539);
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
AutoScaleMode = AutoScaleMode.Dpi;
|
||||||
|
Font = new Font("Segoe UI", 9F);
|
||||||
|
BackColor = Color.White;
|
||||||
|
BuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildUi()
|
||||||
|
{
|
||||||
|
var header = new Panel { Dock = DockStyle.Top, Height = 112, BackColor = Color.FromArgb(28, 67, 102) };
|
||||||
|
header.Controls.Add(new Label { Text = "BizTalk Platform Management Tool", Location = new Point(24, 20), Size = new Size(620, 34), ForeColor = Color.White, Font = new Font(Font.FontFamily, 17F, FontStyle.Bold) });
|
||||||
|
header.Controls.Add(new Label { Text = "Installieren oder sicher auf eine neue Version aktualisieren", Location = new Point(26, 62), Size = new Size(620, 25), ForeColor = Color.FromArgb(220, 232, 242) });
|
||||||
|
Controls.Add(header);
|
||||||
|
|
||||||
|
var body = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(24, 18, 24, 18), ColumnCount = 2, RowCount = 5 };
|
||||||
|
body.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
|
||||||
|
body.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
|
||||||
|
body.RowStyles.Add(new RowStyle(SizeType.Absolute, 48));
|
||||||
|
body.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
|
||||||
|
body.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
|
||||||
|
body.RowStyles.Add(new RowStyle(SizeType.Absolute, 54));
|
||||||
|
body.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||||
|
Controls.Add(body);
|
||||||
|
|
||||||
|
body.Controls.Add(new Label
|
||||||
|
{
|
||||||
|
Text = _engine.IsInstalled
|
||||||
|
? "Eine vorhandene Installation wurde erkannt. Arbeitsdaten bleiben beim Update erhalten."
|
||||||
|
: "Bereit zur Installation. Setup muss aus dem vollständig entpackten Paket gestartet werden.",
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
TextAlign = ContentAlignment.MiddleLeft
|
||||||
|
}, 0, 0);
|
||||||
|
body.SetColumnSpan(body.GetControlFromPosition(0, 0), 2);
|
||||||
|
|
||||||
|
_desktop.Text = "Desktop-Verknüpfung erstellen";
|
||||||
|
_desktop.Checked = true;
|
||||||
|
_desktop.Dock = DockStyle.Fill;
|
||||||
|
body.Controls.Add(_desktop, 0, 1);
|
||||||
|
body.SetColumnSpan(_desktop, 2);
|
||||||
|
_keepData.Text = "Wartungszustände und Logs bei Deinstallation behalten";
|
||||||
|
_keepData.Checked = true;
|
||||||
|
_keepData.Dock = DockStyle.Fill;
|
||||||
|
body.Controls.Add(_keepData, 0, 2);
|
||||||
|
body.SetColumnSpan(_keepData, 2);
|
||||||
|
|
||||||
|
ConfigureButton(_install, "Installieren / aktualisieren", Color.FromArgb(31, 111, 181));
|
||||||
|
ConfigureButton(_uninstall, "Deinstallieren", Color.FromArgb(120, 55, 55));
|
||||||
|
_install.Click += async delegate { await InstallAsync(); };
|
||||||
|
_uninstall.Click += async delegate { await UninstallAsync(); };
|
||||||
|
body.Controls.Add(_install, 0, 3);
|
||||||
|
body.Controls.Add(_uninstall, 1, 3);
|
||||||
|
|
||||||
|
_status.Dock = DockStyle.Fill;
|
||||||
|
_status.Multiline = true;
|
||||||
|
_status.ReadOnly = true;
|
||||||
|
_status.ScrollBars = ScrollBars.Vertical;
|
||||||
|
_status.BackColor = Color.FromArgb(247, 249, 252);
|
||||||
|
_status.BorderStyle = BorderStyle.FixedSingle;
|
||||||
|
body.Controls.Add(_status, 0, 4);
|
||||||
|
body.SetColumnSpan(_status, 2);
|
||||||
|
_uninstall.Enabled = _engine.IsInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InstallAsync()
|
||||||
|
{
|
||||||
|
SetBusy(true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() => _engine.Install(_desktop.Checked, Report));
|
||||||
|
Report("Installation erfolgreich abgeschlossen.");
|
||||||
|
MessageBox.Show(this, "Installation/Update erfolgreich abgeschlossen.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Report("FEHLER: " + ex.Message);
|
||||||
|
MessageBox.Show(this, ex.Message, "Installation fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
finally { SetBusy(false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UninstallAsync()
|
||||||
|
{
|
||||||
|
if (MessageBox.Show(this, "BizTalk Platform Management Tool wirklich deinstallieren?", Text, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) != DialogResult.Yes) return;
|
||||||
|
SetBusy(true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() => _engine.Uninstall(_keepData.Checked, Report));
|
||||||
|
MessageBox.Show(this, "Deinstallation abgeschlossen.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Report("FEHLER: " + ex.Message);
|
||||||
|
MessageBox.Show(this, ex.Message, "Deinstallation fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
finally { SetBusy(false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Report(string message)
|
||||||
|
{
|
||||||
|
if (InvokeRequired) { BeginInvoke(new Action<string>(Report), message); return; }
|
||||||
|
_status.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] " + message + Environment.NewLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBusy(bool busy)
|
||||||
|
{
|
||||||
|
if (InvokeRequired) { BeginInvoke(new Action<bool>(SetBusy), busy); return; }
|
||||||
|
_install.Enabled = !busy;
|
||||||
|
_uninstall.Enabled = !busy && _engine.IsInstalled;
|
||||||
|
_desktop.Enabled = !busy;
|
||||||
|
_keepData.Enabled = !busy;
|
||||||
|
UseWaitCursor = busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureButton(Button button, string text, Color color)
|
||||||
|
{
|
||||||
|
button.Text = text;
|
||||||
|
button.Dock = DockStyle.Fill;
|
||||||
|
button.Margin = new Padding(4, 7, 4, 7);
|
||||||
|
button.FlatStyle = FlatStyle.Flat;
|
||||||
|
button.FlatAppearance.BorderSize = 0;
|
||||||
|
button.BackColor = color;
|
||||||
|
button.ForeColor = Color.White;
|
||||||
|
button.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizTalkPlatformManagementTool.Setup
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
private static void Main()
|
||||||
|
{
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.Run(new MainForm(new InstallerEngine(AppDomain.CurrentDomain.BaseDirectory)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="BizTalkPlatformManagementTool.Setup" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@@ -46,11 +47,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Models\ArtifactStates.cs" />
|
<Compile Include="Models\ArtifactStates.cs" />
|
||||||
<Compile Include="Models\BizTalkSnapshot.cs" />
|
<Compile Include="Models\BizTalkSnapshot.cs" />
|
||||||
<Compile Include="Models\DiffModels.cs" />
|
<Compile Include="Models\DiffModels.cs" />
|
||||||
<Compile Include="Models\OperationModels.cs" />
|
<Compile Include="Models\OperationModels.cs" />
|
||||||
<Compile Include="Services\BizTalkWmiClient.cs" />
|
<Compile Include="Services\BizTalkWmiClient.cs" />
|
||||||
|
<Compile Include="Services\AppPaths.cs" />
|
||||||
<Compile Include="Services\CsvWriter.cs" />
|
<Compile Include="Services\CsvWriter.cs" />
|
||||||
<Compile Include="Services\HtmlReportWriter.cs" />
|
<Compile Include="Services\HtmlReportWriter.cs" />
|
||||||
<Compile Include="Services\JsonFileStore.cs" />
|
<Compile Include="Services\JsonFileStore.cs" />
|
||||||
@@ -62,6 +65,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="App.config" />
|
<None Include="App.config" />
|
||||||
|
<None Include="app.manifest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ namespace BizTalkPlatformManagementTool.Models
|
|||||||
|
|
||||||
public static string FormatReceiveLocation(bool enabled)
|
public static string FormatReceiveLocation(bool enabled)
|
||||||
{
|
{
|
||||||
return enabled ? "Enabled" : "Disabled";
|
return enabled ? "Aktiviert" : "Deaktiviert";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatSendPort(int status)
|
public static string FormatSendPort(int status)
|
||||||
{
|
{
|
||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case SendPortStarted: return "Started";
|
case SendPortStarted: return "Gestartet";
|
||||||
case SendPortStopped: return "Stopped";
|
case SendPortStopped: return "Gestoppt";
|
||||||
case SendPortBound: return "Bound";
|
case SendPortBound: return "Gebunden";
|
||||||
default: return "Unknown (" + status + ")";
|
default: return "Unbekannt (" + status + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ namespace BizTalkPlatformManagementTool.Models
|
|||||||
{
|
{
|
||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case OrchestrationStarted: return "Started";
|
case OrchestrationStarted: return "Gestartet";
|
||||||
case OrchestrationStopped: return "Stopped";
|
case OrchestrationStopped: return "Gestoppt";
|
||||||
case OrchestrationBound: return "Bound";
|
case OrchestrationBound: return "Gebunden";
|
||||||
case OrchestrationUnbound: return "Unbound";
|
case OrchestrationUnbound: return "Nicht gebunden";
|
||||||
default: return "Unknown (" + status + ")";
|
default: return "Unbekannt (" + status + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,11 +48,11 @@ namespace BizTalkPlatformManagementTool.Models
|
|||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case HostStarted: return "Started";
|
case HostStarted: return "Gestartet";
|
||||||
case HostStartPending: return "StartPending";
|
case HostStartPending: return "Wird gestartet";
|
||||||
case HostStopPending: return "StopPending";
|
case HostStopPending: return "Wird gestoppt";
|
||||||
case HostStopped: return "Stopped";
|
case HostStopped: return "Gestoppt";
|
||||||
default: return "Unknown (" + state + ")";
|
default: return "Unbekannt (" + state + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,18 @@ namespace BizTalkPlatformManagementTool.Models
|
|||||||
|
|
||||||
[DataMember(Order = 3)]
|
[DataMember(Order = 3)]
|
||||||
public int OrchestrationStatus { get; set; }
|
public int OrchestrationStatus { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 4, EmitDefaultValue = false)]
|
||||||
|
public string AssemblyName { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 5, EmitDefaultValue = false)]
|
||||||
|
public string AssemblyVersion { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 6, EmitDefaultValue = false)]
|
||||||
|
public string AssemblyCulture { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 7, EmitDefaultValue = false)]
|
||||||
|
public string AssemblyPublicKeyToken { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ namespace BizTalkPlatformManagementTool.Models
|
|||||||
|
|
||||||
[DataMember(Order = 13)]
|
[DataMember(Order = 13)]
|
||||||
public string Warning { get; set; }
|
public string Warning { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 14, EmitDefaultValue = false)]
|
||||||
|
public string QualifierProperty { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 15, EmitDefaultValue = false)]
|
||||||
|
public string QualifierValue { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class OperationOptions
|
public sealed class OperationOptions
|
||||||
|
|||||||
@@ -12,14 +12,19 @@ namespace BizTalkPlatformManagementTool
|
|||||||
{
|
{
|
||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
Application.SetCompatibleTextRenderingDefault(false);
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||||
|
Application.ThreadException += delegate(object sender, System.Threading.ThreadExceptionEventArgs args)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Unerwarteter Fehler: " + args.Exception.Message, "BizTalk Platform Management Tool", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
};
|
||||||
|
|
||||||
if (!IsRunningAsAdministrator())
|
if (!IsRunningAsAdministrator())
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
"BizTalk Platform Management Tool must be started with administrator rights.\r\n\r\n" +
|
"Das BizTalk Platform Management Tool benötigt Administratorrechte.\r\n\r\n" +
|
||||||
"Please close the application and start it again with 'Run as administrator'. " +
|
"Bitte starten Sie die Anwendung über ‚Als Administrator ausführen‘ erneut. " +
|
||||||
"Without elevated rights, BizTalk WMI operations in root\\MicrosoftBizTalkServer will fail.",
|
"Ohne erhöhte Rechte sind BizTalk-WMI-Operationen nicht zuverlässig möglich.",
|
||||||
"Administrator Rights Required",
|
"Administratorrechte erforderlich",
|
||||||
MessageBoxButtons.OK,
|
MessageBoxButtons.OK,
|
||||||
MessageBoxIcon.Error);
|
MessageBoxIcon.Error);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("BizTalk Platform Management Tool")]
|
||||||
|
[assembly: AssemblyDescription("Kontrollierte BizTalk-Wartung mit Zustandswiederherstellung")]
|
||||||
|
[assembly: AssemblyCompany("JR IT Services")]
|
||||||
|
[assembly: AssemblyProduct("BizTalk Platform Management Tool")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
[assembly: Guid("DA63FBDA-4B7F-4D93-B63E-652F26A94796")]
|
||||||
|
[assembly: AssemblyVersion("2.1.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("2.1.0.0")]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace BizTalkPlatformManagementTool.Services
|
||||||
|
{
|
||||||
|
public static class AppPaths
|
||||||
|
{
|
||||||
|
public const string ProductName = "BizTalkPlatformManagementTool";
|
||||||
|
|
||||||
|
public static string RuntimeDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||||
|
ProductName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string WorkspaceDirectory
|
||||||
|
{
|
||||||
|
get { return Path.Combine(RuntimeDirectory, "Wartungen"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LogDirectory
|
||||||
|
{
|
||||||
|
get { return Path.Combine(RuntimeDirectory, "Logs"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
public sealed class BizTalkOperationService
|
public sealed class BizTalkOperationService
|
||||||
{
|
{
|
||||||
public const string Version = "2.0.0-net461";
|
public const string Version = "2.1.0-net461";
|
||||||
private const string UnknownApplication = "(Unknown Application)";
|
private const string UnknownApplication = "(Unbekannte Anwendung)";
|
||||||
|
|
||||||
private readonly OperationLogger _logger;
|
private readonly OperationLogger _logger;
|
||||||
|
|
||||||
@@ -24,7 +24,14 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
using (var client = CreateClient(server))
|
using (var client = CreateClient(server))
|
||||||
{
|
{
|
||||||
var ports = client.Query("MSBTS_SendPort");
|
var ports = client.Query("MSBTS_SendPort");
|
||||||
_logger.Success("WMI/CIM diagnostic succeeded. Send ports visible: " + ports.Count);
|
try
|
||||||
|
{
|
||||||
|
_logger.Success("WMI-Diagnose erfolgreich. Sichtbare Send Ports: " + ports.Count);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
foreach (var port in ports) port.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,56 +49,74 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
var apps = new Dictionary<string, ApplicationSnapshot>(StringComparer.OrdinalIgnoreCase);
|
var apps = new Dictionary<string, ApplicationSnapshot>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var item in client.Query("MSBTS_ReceiveLocation"))
|
foreach (var item in client.Query("MSBTS_ReceiveLocation"))
|
||||||
{
|
{
|
||||||
var appName = GetApplicationName(item);
|
using (item)
|
||||||
var app = GetApplication(apps, appName);
|
|
||||||
app.ReceiveLocations.Add(new ReceiveLocationState
|
|
||||||
{
|
{
|
||||||
Application = app.Application,
|
var appName = GetApplicationName(item);
|
||||||
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
var app = GetApplication(apps, appName);
|
||||||
ReceivePortName = BizTalkWmiClient.SafeGetString(item, "ReceivePortName", string.Empty),
|
app.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
Enabled = !BizTalkWmiClient.SafeGetBoolean(item, "IsDisabled", true),
|
{
|
||||||
AdapterName = BizTalkWmiClient.SafeGetString(item, "AdapterName", string.Empty),
|
Application = app.Application,
|
||||||
Address = BizTalkWmiClient.SafeGetString(item, "InboundTransportURL", string.Empty)
|
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
||||||
});
|
ReceivePortName = BizTalkWmiClient.SafeGetString(item, "ReceivePortName", string.Empty),
|
||||||
|
Enabled = !BizTalkWmiClient.SafeGetBoolean(item, "IsDisabled", true),
|
||||||
|
AdapterName = BizTalkWmiClient.SafeGetString(item, "AdapterName", string.Empty),
|
||||||
|
// Transport-URIs koennen Zugangsdaten enthalten und werden fuer die
|
||||||
|
// Zustandswiederherstellung nicht benoetigt. Daher nicht persistieren.
|
||||||
|
Address = string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in client.Query("MSBTS_SendPort"))
|
foreach (var item in client.Query("MSBTS_SendPort"))
|
||||||
{
|
{
|
||||||
var appName = GetApplicationName(item);
|
using (item)
|
||||||
var app = GetApplication(apps, appName);
|
|
||||||
app.SendPorts.Add(new SendPortState
|
|
||||||
{
|
{
|
||||||
Application = app.Application,
|
var appName = GetApplicationName(item);
|
||||||
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
var app = GetApplication(apps, appName);
|
||||||
Status = BizTalkWmiClient.SafeGetInt32(item, "Status", 0),
|
app.SendPorts.Add(new SendPortState
|
||||||
PrimaryTransportType = BizTalkWmiClient.SafeGetString(item, "PTTransportType", string.Empty),
|
{
|
||||||
PrimaryTransportAddress = BizTalkWmiClient.SafeGetString(item, "PTAddress", string.Empty)
|
Application = app.Application,
|
||||||
});
|
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
||||||
|
Status = BizTalkWmiClient.SafeGetInt32(item, "Status", 0),
|
||||||
|
PrimaryTransportType = BizTalkWmiClient.SafeGetString(item, "PTTransportType", string.Empty),
|
||||||
|
PrimaryTransportAddress = string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in client.Query("MSBTS_Orchestration"))
|
foreach (var item in client.Query("MSBTS_Orchestration"))
|
||||||
{
|
{
|
||||||
var appName = GetApplicationName(item);
|
using (item)
|
||||||
var app = GetApplication(apps, appName);
|
|
||||||
app.Orchestrations.Add(new OrchestrationState
|
|
||||||
{
|
{
|
||||||
Application = app.Application,
|
var appName = GetApplicationName(item);
|
||||||
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
var app = GetApplication(apps, appName);
|
||||||
OrchestrationStatus = BizTalkWmiClient.SafeGetInt32(item, "OrchestrationStatus", 0)
|
app.Orchestrations.Add(new OrchestrationState
|
||||||
});
|
{
|
||||||
|
Application = app.Application,
|
||||||
|
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
||||||
|
OrchestrationStatus = BizTalkWmiClient.SafeGetInt32(item, "OrchestrationStatus", 0),
|
||||||
|
AssemblyName = BizTalkWmiClient.SafeGetString(item, "AssemblyName", string.Empty),
|
||||||
|
AssemblyVersion = BizTalkWmiClient.SafeGetString(item, "AssemblyVersion", string.Empty),
|
||||||
|
AssemblyCulture = BizTalkWmiClient.SafeGetString(item, "AssemblyCulture", string.Empty),
|
||||||
|
AssemblyPublicKeyToken = BizTalkWmiClient.SafeGetString(item, "AssemblyPublicKeyToken", string.Empty)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in client.Query("MSBTS_HostInstance"))
|
foreach (var item in client.Query("MSBTS_HostInstance"))
|
||||||
{
|
{
|
||||||
var state = BizTalkWmiClient.SafeGetInt32(item, "ServiceState", 0);
|
using (item)
|
||||||
snapshot.HostInstances.Add(new HostInstanceState
|
|
||||||
{
|
{
|
||||||
InstanceName = BizTalkWmiClient.SafeGetString(item, "InstanceName", BizTalkWmiClient.SafeGetString(item, "Name", string.Empty)),
|
var state = BizTalkWmiClient.SafeGetInt32(item, "ServiceState", 0);
|
||||||
HostName = BizTalkWmiClient.SafeGetString(item, "HostName", string.Empty),
|
snapshot.HostInstances.Add(new HostInstanceState
|
||||||
Server = BizTalkWmiClient.SafeGetString(item, "RunningServer", string.Empty),
|
{
|
||||||
RawState = state,
|
InstanceName = BizTalkWmiClient.SafeGetString(item, "InstanceName", BizTalkWmiClient.SafeGetString(item, "Name", string.Empty)),
|
||||||
StateText = ArtifactStates.FormatHostInstance(state)
|
HostName = BizTalkWmiClient.SafeGetString(item, "HostName", string.Empty),
|
||||||
});
|
Server = BizTalkWmiClient.SafeGetString(item, "RunningServer", string.Empty),
|
||||||
|
RawState = state,
|
||||||
|
StateText = ArtifactStates.FormatHostInstance(state)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var app in apps.Values.OrderBy(a => a.Application))
|
foreach (var app in apps.Values.OrderBy(a => a.Application))
|
||||||
@@ -103,7 +128,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
}
|
}
|
||||||
snapshot.HostInstances = snapshot.HostInstances.OrderBy(x => x.Server).ThenBy(x => x.InstanceName).ToList();
|
snapshot.HostInstances = snapshot.HostInstances.OrderBy(x => x.Server).ThenBy(x => x.InstanceName).ToList();
|
||||||
|
|
||||||
_logger.Success("Snapshot created. Applications: " + snapshot.Applications.Count + ", host instances: " + snapshot.HostInstances.Count);
|
_logger.Success("Snapshot erstellt. Anwendungen: " + snapshot.Applications.Count + ", Hostinstanzen: " + snapshot.HostInstances.Count);
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,27 +141,32 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
foreach (var item in app.ReceiveLocations.Where(x => x.Enabled))
|
foreach (var item in app.ReceiveLocations.Where(x => x.Enabled))
|
||||||
{
|
{
|
||||||
plan.Steps.Add(Step("ReceiveLocation", app.Application, item.Name, null, "Disable receive location", "MSBTS_ReceiveLocation", "Name", item.Name, "Disable", null, null));
|
var step = Step("ReceiveLocation", app.Application, item.Name, null, "Receive Location deaktivieren", "MSBTS_ReceiveLocation", "Name", item.Name, "Disable", null, null);
|
||||||
|
SetQualifier(step, "ReceivePortName", item.ReceivePortName);
|
||||||
|
plan.Steps.Add(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in app.Orchestrations.Where(x => x.OrchestrationStatus == ArtifactStates.OrchestrationStarted))
|
foreach (var item in app.Orchestrations.Where(x => x.OrchestrationStatus == ArtifactStates.OrchestrationStarted))
|
||||||
{
|
{
|
||||||
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Stop orchestration", "MSBTS_Orchestration", "Name", item.Name, "Stop", new[] { 1, 1 }, ArtifactStates.OrchestrationStopped));
|
var step = Step("Orchestration", app.Application, item.Name, null, "Orchestrierung stoppen", "MSBTS_Orchestration", "Name", item.Name, "Stop", new[] { 1, 1 }, ArtifactStates.OrchestrationStopped);
|
||||||
|
SetQualifier(step, "AssemblyName", item.AssemblyName);
|
||||||
|
plan.Steps.Add(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in app.SendPorts.Where(x => x.Status == ArtifactStates.SendPortStarted))
|
foreach (var item in app.SendPorts.Where(x => x.Status == ArtifactStates.SendPortStarted))
|
||||||
{
|
{
|
||||||
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Stop send port", "MSBTS_SendPort", "Name", item.Name, "Stop", null, ArtifactStates.SendPortStopped));
|
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Send Port stoppen", "MSBTS_SendPort", "Name", item.Name, "Stop", null, ArtifactStates.SendPortStopped));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted))
|
foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted))
|
||||||
{
|
{
|
||||||
var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Stop host instance", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Stop", null, ArtifactStates.HostStopped);
|
var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Hostinstanz stoppen", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Stop", null, ArtifactStates.HostStopped);
|
||||||
if (!string.IsNullOrEmpty(item.Server) && !string.Equals(item.Server, server, StringComparison.OrdinalIgnoreCase))
|
SetQualifier(step, "RunningServer", item.Server);
|
||||||
|
if (!string.IsNullOrEmpty(item.Server) && !SameServer(item.Server, server))
|
||||||
{
|
{
|
||||||
step.Execute = false;
|
step.Execute = false;
|
||||||
step.Warning = "Host instance belongs to server '" + item.Server + "'. Run this action on that server.";
|
step.Warning = "Die Hostinstanz gehört zum Server '" + item.Server + "'. Aktion dort ausführen.";
|
||||||
}
|
}
|
||||||
plan.Steps.Add(step);
|
plan.Steps.Add(step);
|
||||||
}
|
}
|
||||||
@@ -150,64 +180,37 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted))
|
foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted))
|
||||||
{
|
{
|
||||||
var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Start host instance", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Start", null, ArtifactStates.HostStarted);
|
var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Hostinstanz starten", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Start", null, ArtifactStates.HostStarted);
|
||||||
if (!string.IsNullOrEmpty(item.Server) && !string.Equals(item.Server, server, StringComparison.OrdinalIgnoreCase))
|
SetQualifier(step, "RunningServer", item.Server);
|
||||||
|
if (!string.IsNullOrEmpty(item.Server) && !SameServer(item.Server, server))
|
||||||
{
|
{
|
||||||
step.Execute = false;
|
step.Execute = false;
|
||||||
step.Warning = "Host instance belongs to server '" + item.Server + "'. Run this action on that server.";
|
step.Warning = "Die Hostinstanz gehört zum Server '" + item.Server + "'. Aktion dort ausführen.";
|
||||||
}
|
}
|
||||||
plan.Steps.Add(step);
|
plan.Steps.Add(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var app in snapshot.Applications)
|
foreach (var app in snapshot.Applications)
|
||||||
{
|
{
|
||||||
foreach (var item in app.SendPorts)
|
// Restore startet ausschliesslich Artefakte, die im Vorher-Snapshot aktiv waren.
|
||||||
|
// Bewusst gestoppte/gebundene Artefakte bleiben unangetastet.
|
||||||
|
foreach (var item in app.SendPorts.Where(x => x.Status == ArtifactStates.SendPortStarted))
|
||||||
{
|
{
|
||||||
if (item.Status == ArtifactStates.SendPortStarted)
|
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Send Port starten", "MSBTS_SendPort", "Name", item.Name, "Start", null, ArtifactStates.SendPortStarted));
|
||||||
{
|
|
||||||
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Start send port", "MSBTS_SendPort", "Name", item.Name, "Start", null, ArtifactStates.SendPortStarted));
|
|
||||||
}
|
|
||||||
else if (item.Status == ArtifactStates.SendPortStopped)
|
|
||||||
{
|
|
||||||
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Ensure send port is stopped", "MSBTS_SendPort", "Name", item.Name, "StopOrEnlist", null, ArtifactStates.SendPortStopped));
|
|
||||||
}
|
|
||||||
else if (item.Status == ArtifactStates.SendPortBound)
|
|
||||||
{
|
|
||||||
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Ensure send port is bound", "MSBTS_SendPort", "Name", item.Name, "UnEnlist", null, ArtifactStates.SendPortBound));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in app.Orchestrations)
|
foreach (var item in app.Orchestrations.Where(x => x.OrchestrationStatus == ArtifactStates.OrchestrationStarted))
|
||||||
{
|
{
|
||||||
if (item.OrchestrationStatus == ArtifactStates.OrchestrationStarted)
|
var step = Step("Orchestration", app.Application, item.Name, null, "Orchestrierung starten", "MSBTS_Orchestration", "Name", item.Name, "Start", new[] { 1, 1, 1 }, ArtifactStates.OrchestrationStarted);
|
||||||
{
|
SetQualifier(step, "AssemblyName", item.AssemblyName);
|
||||||
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Start orchestration", "MSBTS_Orchestration", "Name", item.Name, "Start", new[] { 1, 1, 1 }, ArtifactStates.OrchestrationStarted));
|
plan.Steps.Add(step);
|
||||||
}
|
|
||||||
else if (item.OrchestrationStatus == ArtifactStates.OrchestrationStopped)
|
|
||||||
{
|
|
||||||
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Ensure orchestration is stopped", "MSBTS_Orchestration", "Name", item.Name, "StopIfStarted", new[] { 1, 1 }, ArtifactStates.OrchestrationStopped));
|
|
||||||
}
|
|
||||||
else if (item.OrchestrationStatus == ArtifactStates.OrchestrationBound)
|
|
||||||
{
|
|
||||||
plan.Steps.Add(new OperationStep
|
|
||||||
{
|
|
||||||
Kind = "Note",
|
|
||||||
Application = app.Application,
|
|
||||||
Name = item.Name,
|
|
||||||
Action = "Orchestration was Bound in the snapshot; leaving unchanged to avoid making it Unbound.",
|
|
||||||
Execute = false,
|
|
||||||
Warning = "Manual check recommended."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (item.OrchestrationStatus == ArtifactStates.OrchestrationUnbound)
|
|
||||||
{
|
|
||||||
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Unenlist orchestration", "MSBTS_Orchestration", "Name", item.Name, "UnenlistService", new[] { 1 }, ArtifactStates.OrchestrationUnbound));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in app.ReceiveLocations)
|
foreach (var item in app.ReceiveLocations.Where(x => x.Enabled))
|
||||||
{
|
{
|
||||||
plan.Steps.Add(Step("ReceiveLocation", app.Application, item.Name, null, item.Enabled ? "Enable receive location" : "Disable receive location", "MSBTS_ReceiveLocation", "Name", item.Name, item.Enabled ? "Enable" : "Disable", null, null));
|
var step = Step("ReceiveLocation", app.Application, item.Name, null, "Receive Location aktivieren", "MSBTS_ReceiveLocation", "Name", item.Name, "Enable", null, null);
|
||||||
|
SetQualifier(step, "ReceivePortName", item.ReceivePortName);
|
||||||
|
plan.Steps.Add(step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +238,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Info("Executing step: " + DescribeStep(step));
|
_logger.Info("Executing step: " + DescribeStep(step));
|
||||||
using (var instance = client.FindByProperty(step.WmiClass, step.KeyProperty, step.KeyValue))
|
using (var instance = client.FindByProperties(step.WmiClass, step.KeyProperty, step.KeyValue, step.QualifierProperty, step.QualifierValue))
|
||||||
{
|
{
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
{
|
{
|
||||||
@@ -262,6 +265,31 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string SaveSnapshot(string path, BizTalkSnapshot snapshot)
|
||||||
|
{
|
||||||
|
SnapshotStore.SaveSnapshotSet(path, snapshot);
|
||||||
|
_logger.Success("Zustand gespeichert: " + path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ValidateSnapshot(BizTalkSnapshot snapshot, string expectedServer)
|
||||||
|
{
|
||||||
|
if (snapshot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Die Zustandsdatei enthält keinen Snapshot.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.Applications == null || snapshot.HostInstances == null)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Die Zustandsdatei ist unvollständig.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SameServer(snapshot.Server, expectedServer))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Die Zustandsdatei gehört zum Server '" + snapshot.Server + "', ausgewählt ist jedoch '" + expectedServer + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string SavePlan(string outputDirectory, string fileName, OperationPlan plan)
|
public string SavePlan(string outputDirectory, string fileName, OperationPlan plan)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(outputDirectory);
|
Directory.CreateDirectory(outputDirectory);
|
||||||
@@ -282,6 +310,12 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
private void ExecuteStep(BizTalkWmiClient client, ManagementObject instance, OperationStep step, OperationOptions options)
|
private void ExecuteStep(BizTalkWmiClient client, ManagementObject instance, OperationStep step, OperationOptions options)
|
||||||
{
|
{
|
||||||
|
if (IsTargetReached(instance, step))
|
||||||
|
{
|
||||||
|
_logger.Success("Bereits im Zielzustand: " + step.Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(step.MethodName, "StopOrEnlist", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(step.MethodName, "StopOrEnlist", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var status = BizTalkWmiClient.SafeGetInt32(instance, "Status", 0);
|
var status = BizTalkWmiClient.SafeGetInt32(instance, "Status", 0);
|
||||||
@@ -318,6 +352,33 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
WaitForTarget(client, step, options);
|
WaitForTarget(client, step, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetReached(ManagementObject instance, OperationStep step)
|
||||||
|
{
|
||||||
|
if (instance == null || step == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.Kind == "ReceiveLocation")
|
||||||
|
{
|
||||||
|
var shouldBeEnabled = string.Equals(step.MethodName, "Enable", StringComparison.OrdinalIgnoreCase);
|
||||||
|
return !BizTalkWmiClient.SafeGetBoolean(instance, "IsDisabled", true) == shouldBeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!step.TargetState.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var property = step.Kind == "SendPort"
|
||||||
|
? "Status"
|
||||||
|
: step.Kind == "Orchestration"
|
||||||
|
? "OrchestrationStatus"
|
||||||
|
: step.Kind == "HostInstance" ? "ServiceState" : string.Empty;
|
||||||
|
return property.Length > 0
|
||||||
|
&& BizTalkWmiClient.SafeGetInt32(instance, property, 0) == step.TargetState.Value;
|
||||||
|
}
|
||||||
|
|
||||||
private void WaitForTarget(BizTalkWmiClient client, OperationStep step, OperationOptions options)
|
private void WaitForTarget(BizTalkWmiClient client, OperationStep step, OperationOptions options)
|
||||||
{
|
{
|
||||||
if (!step.TargetState.HasValue)
|
if (!step.TargetState.HasValue)
|
||||||
@@ -325,22 +386,22 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
if (step.Kind == "ReceiveLocation")
|
if (step.Kind == "ReceiveLocation")
|
||||||
{
|
{
|
||||||
var shouldBeEnabled = string.Equals(step.MethodName, "Enable", StringComparison.OrdinalIgnoreCase);
|
var shouldBeEnabled = string.Equals(step.MethodName, "Enable", StringComparison.OrdinalIgnoreCase);
|
||||||
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => !BizTalkWmiClient.SafeGetBoolean(o, "IsDisabled", true) == shouldBeEnabled, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, step.QualifierProperty, step.QualifierValue, o => !BizTalkWmiClient.SafeGetBoolean(o, "IsDisabled", true) == shouldBeEnabled, step.Action + " abgeschlossen", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step.Kind == "SendPort")
|
if (step.Kind == "SendPort")
|
||||||
{
|
{
|
||||||
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => BizTalkWmiClient.SafeGetInt32(o, "Status", 0) == step.TargetState.Value, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, step.QualifierProperty, step.QualifierValue, o => BizTalkWmiClient.SafeGetInt32(o, "Status", 0) == step.TargetState.Value, step.Action + " abgeschlossen", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
||||||
}
|
}
|
||||||
else if (step.Kind == "Orchestration")
|
else if (step.Kind == "Orchestration")
|
||||||
{
|
{
|
||||||
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => BizTalkWmiClient.SafeGetInt32(o, "OrchestrationStatus", 0) == step.TargetState.Value, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, step.QualifierProperty, step.QualifierValue, o => BizTalkWmiClient.SafeGetInt32(o, "OrchestrationStatus", 0) == step.TargetState.Value, step.Action + " abgeschlossen", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
||||||
}
|
}
|
||||||
else if (step.Kind == "HostInstance")
|
else if (step.Kind == "HostInstance")
|
||||||
{
|
{
|
||||||
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => BizTalkWmiClient.SafeGetInt32(o, "ServiceState", 0) == step.TargetState.Value, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, step.QualifierProperty, step.QualifierValue, o => BizTalkWmiClient.SafeGetInt32(o, "ServiceState", 0) == step.TargetState.Value, step.Action + " abgeschlossen", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +441,24 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetQualifier(OperationStep step, string property, string value)
|
||||||
|
{
|
||||||
|
if (step == null || string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
step.QualifierProperty = property;
|
||||||
|
step.QualifierValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SameServer(string left, string right)
|
||||||
|
{
|
||||||
|
var first = (left ?? string.Empty).Trim().TrimStart('\\').Split('.')[0];
|
||||||
|
var second = (right ?? string.Empty).Trim().TrimStart('\\').Split('.')[0];
|
||||||
|
return first.Length > 0 && string.Equals(first, second, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private static object[] ToObjects(int[] values)
|
private static object[] ToObjects(int[] values)
|
||||||
{
|
{
|
||||||
if (values == null || values.Length == 0)
|
if (values == null || values.Length == 0)
|
||||||
|
|||||||
@@ -71,10 +71,15 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
public ManagementObject FindByProperty(string className, string propertyName, string value)
|
public ManagementObject FindByProperty(string className, string propertyName, string value)
|
||||||
{
|
{
|
||||||
return FindByProperty(className, propertyName, value, true);
|
return FindByProperties(className, propertyName, value, null, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ManagementObject FindByProperty(string className, string propertyName, string value, bool logLookup)
|
public ManagementObject FindByProperties(string className, string propertyName, string value, string qualifierProperty, string qualifierValue)
|
||||||
|
{
|
||||||
|
return FindByProperties(className, propertyName, value, qualifierProperty, qualifierValue, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ManagementObject FindByProperties(string className, string propertyName, string value, string qualifierProperty, string qualifierValue, bool logLookup)
|
||||||
{
|
{
|
||||||
EnsureConnected();
|
EnsureConnected();
|
||||||
|
|
||||||
@@ -84,6 +89,14 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
"{0} = \"{1}\"",
|
"{0} = \"{1}\"",
|
||||||
propertyName,
|
propertyName,
|
||||||
value ?? string.Empty);
|
value ?? string.Empty);
|
||||||
|
if (!string.IsNullOrWhiteSpace(qualifierProperty))
|
||||||
|
{
|
||||||
|
filter += string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
" AND {0} = \"{1}\"",
|
||||||
|
qualifierProperty,
|
||||||
|
qualifierValue ?? string.Empty);
|
||||||
|
}
|
||||||
if (logLookup)
|
if (logLookup)
|
||||||
{
|
{
|
||||||
_logger.Info("Resolving WMI object: " + queryText + " with client-side filter " + filter);
|
_logger.Info("Resolving WMI object: " + queryText + " with client-side filter " + filter);
|
||||||
@@ -91,26 +104,37 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ManagementObject match = null;
|
|
||||||
var items = Query(className, false);
|
var items = Query(className, false);
|
||||||
|
var matches = new List<ManagementObject>();
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
if (MatchesProperty(item, className, propertyName, value))
|
if (MatchesProperty(item, className, propertyName, value)
|
||||||
|
&& (string.IsNullOrWhiteSpace(qualifierProperty)
|
||||||
|
|| MatchesProperty(item, className, qualifierProperty, qualifierValue)))
|
||||||
{
|
{
|
||||||
match = item;
|
matches.Add(item);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
if (!object.ReferenceEquals(item, match))
|
if (!matches.Contains(item))
|
||||||
{
|
{
|
||||||
item.Dispose();
|
item.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return match;
|
if (matches.Count > 1)
|
||||||
|
{
|
||||||
|
foreach (var item in matches)
|
||||||
|
{
|
||||||
|
item.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("WMI lookup is ambiguous. Class: " + className + ", filter: " + filter + ", matches: " + matches.Count + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches.Count == 0 ? null : matches[0];
|
||||||
}
|
}
|
||||||
catch (ManagementException ex)
|
catch (ManagementException ex)
|
||||||
{
|
{
|
||||||
@@ -147,14 +171,14 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
return returnCode;
|
return returnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WaitForState(string className, string keyProperty, string keyValue, Func<ManagementObject, bool> isReached, string description, int timeoutSeconds, int pollIntervalSeconds)
|
public void WaitForState(string className, string keyProperty, string keyValue, string qualifierProperty, string qualifierValue, Func<ManagementObject, bool> isReached, string description, int timeoutSeconds, int pollIntervalSeconds)
|
||||||
{
|
{
|
||||||
var deadline = DateTime.UtcNow.AddSeconds(Math.Max(1, timeoutSeconds));
|
var deadline = DateTime.UtcNow.AddSeconds(Math.Max(1, timeoutSeconds));
|
||||||
var delay = Math.Max(1, pollIntervalSeconds);
|
var delay = Math.Max(1, pollIntervalSeconds);
|
||||||
|
|
||||||
while (DateTime.UtcNow <= deadline)
|
while (DateTime.UtcNow <= deadline)
|
||||||
{
|
{
|
||||||
using (var current = FindByProperty(className, keyProperty, keyValue, false))
|
using (var current = FindByProperties(className, keyProperty, keyValue, qualifierProperty, qualifierValue, false))
|
||||||
{
|
{
|
||||||
if (current != null && isReached(current))
|
if (current != null && isReached(current))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
public static class JsonFileStore
|
public static class JsonFileStore
|
||||||
{
|
{
|
||||||
|
private const long MaximumJsonBytes = 16L * 1024L * 1024L;
|
||||||
private static readonly Encoding Utf8NoBom = new UTF8Encoding(false);
|
private static readonly Encoding Utf8NoBom = new UTF8Encoding(false);
|
||||||
|
|
||||||
public static void Save<T>(string path, T value)
|
public static void Save<T>(string path, T value)
|
||||||
@@ -17,11 +18,41 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
UseSimpleDictionaryFormat = true
|
UseSimpleDictionaryFormat = true
|
||||||
});
|
});
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
var fullPath = Path.GetFullPath(path);
|
||||||
|
var temporaryPath = fullPath + ".tmp." + System.Guid.NewGuid().ToString("N");
|
||||||
|
try
|
||||||
{
|
{
|
||||||
serializer.WriteObject(stream, value);
|
using (var stream = new MemoryStream())
|
||||||
var json = Utf8NoBom.GetString(stream.ToArray());
|
{
|
||||||
File.WriteAllText(path, json, Utf8NoBom);
|
serializer.WriteObject(stream, value);
|
||||||
|
var payload = stream.ToArray();
|
||||||
|
if (payload.LongLength > MaximumJsonBytes)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Die JSON-Ausgabe überschreitet die Sicherheitsgrenze von 16 MiB.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var file = new FileStream(temporaryPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
|
||||||
|
{
|
||||||
|
file.Write(payload, 0, payload.Length);
|
||||||
|
file.Flush(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
File.Replace(temporaryPath, fullPath, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.Move(temporaryPath, fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (File.Exists(temporaryPath))
|
||||||
|
{
|
||||||
|
File.Delete(temporaryPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +63,16 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
UseSimpleDictionaryFormat = true
|
UseSimpleDictionaryFormat = true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var information = new FileInfo(path);
|
||||||
|
if (!information.Exists)
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Die JSON-Datei wurde nicht gefunden.", path);
|
||||||
|
}
|
||||||
|
if (information.Length == 0 || information.Length > MaximumJsonBytes)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Die JSON-Datei ist leer oder überschreitet die Sicherheitsgrenze von 16 MiB: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
var json = NormalizeJson(File.ReadAllText(path, Encoding.UTF8));
|
var json = NormalizeJson(File.ReadAllText(path, Encoding.UTF8));
|
||||||
using (var stream = new MemoryStream(Utf8NoBom.GetBytes(json)))
|
using (var stream = new MemoryStream(Utf8NoBom.GetBytes(json)))
|
||||||
{
|
{
|
||||||
@@ -41,7 +82,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
}
|
}
|
||||||
catch (SerializationException ex)
|
catch (SerializationException ex)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException("Failed to load JSON file '" + path + "'. " + DescribeJsonStart(json), ex);
|
throw new InvalidDataException("Die JSON-Datei '" + path + "' konnte nicht geladen werden. " + DescribeJsonStart(json), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,17 +111,17 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
{
|
{
|
||||||
return "The file is empty or contains only whitespace.";
|
return "Die Datei ist leer oder enthält nur Leerzeichen.";
|
||||||
}
|
}
|
||||||
|
|
||||||
var trimmed = json.TrimStart();
|
var trimmed = json.TrimStart();
|
||||||
var first = trimmed.Length == 0 ? '\0' : trimmed[0];
|
var first = trimmed.Length == 0 ? '\0' : trimmed[0];
|
||||||
if (first != '{' && first != '[')
|
if (first != '{' && first != '[')
|
||||||
{
|
{
|
||||||
return "The first JSON character is '" + first + "' instead of '{' or '['.";
|
return "Das erste JSON-Zeichen ist '" + first + "' statt '{' oder '['.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "The file starts with valid JSON syntax but could not be deserialized into the expected model.";
|
return "Die Datei beginnt mit gültiger JSON-Syntax, passt aber nicht zum erwarteten Datenmodell.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
public OperationLogger(Action<LogEntry> sink)
|
public OperationLogger(Action<LogEntry> sink)
|
||||||
{
|
{
|
||||||
_sink = sink;
|
_sink = sink;
|
||||||
_logDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
_logDirectory = ResolveLogDirectory();
|
||||||
CleanupOldLogs();
|
CleanupOldLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,5 +132,30 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
// Log retention cleanup is best-effort.
|
// Log retention cleanup is best-effort.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ResolveLogDirectory()
|
||||||
|
{
|
||||||
|
var candidates = new[]
|
||||||
|
{
|
||||||
|
AppPaths.LogDirectory,
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppPaths.ProductName, "Logs"),
|
||||||
|
AppDomain.CurrentDomain.BaseDirectory
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var candidate in candidates)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(candidate);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Das Logging darf die Bedienoberflaeche nicht am Start hindern.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
public static SnapshotDiff Compare(BizTalkSnapshot before, BizTalkSnapshot after)
|
public static SnapshotDiff Compare(BizTalkSnapshot before, BizTalkSnapshot after)
|
||||||
{
|
{
|
||||||
|
if (before == null) throw new ArgumentNullException("before");
|
||||||
|
if (after == null) throw new ArgumentNullException("after");
|
||||||
var diff = new SnapshotDiff();
|
var diff = new SnapshotDiff();
|
||||||
|
|
||||||
CompareReceiveLocations(diff, FlattenReceiveLocations(before), FlattenReceiveLocations(after));
|
CompareReceiveLocations(diff, FlattenReceiveLocations(before), FlattenReceiveLocations(after));
|
||||||
@@ -97,11 +99,11 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
foreach (var item in before)
|
foreach (var item in before)
|
||||||
{
|
{
|
||||||
beforeMap[item.InstanceName ?? string.Empty] = item;
|
beforeMap[Key(item.Server, item.InstanceName)] = item;
|
||||||
}
|
}
|
||||||
foreach (var item in after)
|
foreach (var item in after)
|
||||||
{
|
{
|
||||||
afterMap[item.InstanceName ?? string.Empty] = item;
|
afterMap[Key(item.Server, item.InstanceName)] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var pair in afterMap)
|
foreach (var pair in afterMap)
|
||||||
@@ -145,7 +147,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
foreach (var item in app.ReceiveLocations)
|
foreach (var item in app.ReceiveLocations)
|
||||||
{
|
{
|
||||||
map[item.Name ?? string.Empty] = item;
|
map[Key(item.Application, item.ReceivePortName, item.Name)] = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
@@ -158,7 +160,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
foreach (var item in app.SendPorts)
|
foreach (var item in app.SendPorts)
|
||||||
{
|
{
|
||||||
map[item.Name ?? string.Empty] = item;
|
map[Key(item.Application, item.Name)] = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
@@ -171,10 +173,15 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
foreach (var item in app.Orchestrations)
|
foreach (var item in app.Orchestrations)
|
||||||
{
|
{
|
||||||
map[item.Name ?? string.Empty] = item;
|
map[Key(item.Application, item.Name, item.AssemblyName, item.AssemblyVersion, item.AssemblyCulture, item.AssemblyPublicKeyToken)] = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string Key(params string[] parts)
|
||||||
|
{
|
||||||
|
return string.Join("|", Array.ConvertAll(parts, value => (value ?? string.Empty).Trim().ToUpperInvariant()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
{
|
{
|
||||||
public sealed class MainForm : Form
|
public sealed class MainForm : Form
|
||||||
{
|
{
|
||||||
|
private static readonly Color BrandColor = Color.FromArgb(28, 67, 102);
|
||||||
|
private static readonly Color AccentColor = Color.FromArgb(31, 111, 181);
|
||||||
|
private static readonly Color DangerColor = Color.FromArgb(178, 45, 45);
|
||||||
|
private static readonly Color SuccessColor = Color.FromArgb(32, 122, 72);
|
||||||
|
|
||||||
private readonly TextBox _serverTextBox = new TextBox();
|
private readonly TextBox _serverTextBox = new TextBox();
|
||||||
private readonly TextBox _outputTextBox = new TextBox();
|
private readonly TextBox _outputTextBox = new TextBox();
|
||||||
private readonly TextBox _stateFileTextBox = new TextBox();
|
private readonly TextBox _stateFileTextBox = new TextBox();
|
||||||
@@ -19,33 +24,39 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
private readonly CheckBox _dryRunCheckBox = new CheckBox();
|
private readonly CheckBox _dryRunCheckBox = new CheckBox();
|
||||||
private readonly DataGridView _statusGrid = new DataGridView();
|
private readonly DataGridView _statusGrid = new DataGridView();
|
||||||
private readonly DataGridView _logGrid = new DataGridView();
|
private readonly DataGridView _logGrid = new DataGridView();
|
||||||
private readonly StatusStrip _statusStrip = new StatusStrip();
|
|
||||||
private readonly ToolStripStatusLabel _statusLabel = new ToolStripStatusLabel();
|
private readonly ToolStripStatusLabel _statusLabel = new ToolStripStatusLabel();
|
||||||
|
private readonly ToolStripStatusLabel _modeLabel = new ToolStripStatusLabel();
|
||||||
private readonly Label _environmentStatusLabel = new Label();
|
private readonly Label _environmentStatusLabel = new Label();
|
||||||
|
private readonly ProgressBar _progressBar = new ProgressBar();
|
||||||
|
private readonly ToolTip _toolTip = new ToolTip();
|
||||||
private readonly OperationLogger _logger;
|
private readonly OperationLogger _logger;
|
||||||
private readonly BizTalkOperationService _service;
|
private readonly BizTalkOperationService _service;
|
||||||
|
|
||||||
private Button _diagnoseButton;
|
private Button _diagnoseButton;
|
||||||
private Button _beforeButton;
|
private Button _beforeButton;
|
||||||
private Button _afterButton;
|
|
||||||
private Button _compareButton;
|
|
||||||
private Button _shutdownButton;
|
private Button _shutdownButton;
|
||||||
private Button _restoreButton;
|
private Button _restoreButton;
|
||||||
|
private Button _afterButton;
|
||||||
|
private Button _compareButton;
|
||||||
private Button _clearButton;
|
private Button _clearButton;
|
||||||
private Button _closeButton;
|
private Button _closeButton;
|
||||||
|
private bool _busy;
|
||||||
|
|
||||||
public MainForm()
|
public MainForm()
|
||||||
{
|
{
|
||||||
Text = "BizTalk Platform Management Tool";
|
Text = "BizTalk Platform Management Tool";
|
||||||
Width = 1180;
|
ClientSize = new Size(1240, 800);
|
||||||
Height = 760;
|
MinimumSize = new Size(1040, 700);
|
||||||
MinimumSize = new Size(980, 640);
|
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
AutoScaleMode = AutoScaleMode.Dpi;
|
||||||
|
Font = new Font("Segoe UI", 9F);
|
||||||
|
BackColor = Color.FromArgb(245, 247, 250);
|
||||||
|
|
||||||
_logger = new OperationLogger(AppendLog);
|
_logger = new OperationLogger(AppendLog);
|
||||||
_service = new BizTalkOperationService(_logger);
|
_service = new BizTalkOperationService(_logger);
|
||||||
BuildUi();
|
BuildUi();
|
||||||
_logger.Info("Log file: " + _logger.LogFilePath);
|
FormClosing += MainFormClosing;
|
||||||
|
_logger.Info("Protokolldatei: " + _logger.LogFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildUi()
|
private void BuildUi()
|
||||||
@@ -54,134 +65,215 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
{
|
{
|
||||||
Dock = DockStyle.Fill,
|
Dock = DockStyle.Fill,
|
||||||
ColumnCount = 1,
|
ColumnCount = 1,
|
||||||
RowCount = 4,
|
RowCount = 5,
|
||||||
Padding = new Padding(10)
|
Padding = new Padding(0),
|
||||||
|
BackColor = BackColor
|
||||||
};
|
};
|
||||||
|
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 86));
|
||||||
|
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 176));
|
||||||
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 92));
|
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 92));
|
||||||
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 54));
|
|
||||||
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||||
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
|
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
|
||||||
Controls.Add(root);
|
Controls.Add(root);
|
||||||
|
|
||||||
root.Controls.Add(BuildSettingsPanel(), 0, 0);
|
root.Controls.Add(BuildHeader(), 0, 0);
|
||||||
root.Controls.Add(BuildActionPanel(), 0, 1);
|
root.Controls.Add(BuildSettingsPanel(), 0, 1);
|
||||||
root.Controls.Add(BuildTabs(), 0, 2);
|
root.Controls.Add(BuildWorkflowPanel(), 0, 2);
|
||||||
|
root.Controls.Add(BuildTabs(), 0, 3);
|
||||||
|
root.Controls.Add(BuildStatusStrip(), 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
_statusStrip.Items.Add(_statusLabel);
|
private Control BuildHeader()
|
||||||
_statusLabel.Text = "Ready. Dry run is enabled.";
|
{
|
||||||
root.Controls.Add(_statusStrip, 0, 3);
|
var header = new TableLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ColumnCount = 2,
|
||||||
|
RowCount = 2,
|
||||||
|
Padding = new Padding(24, 14, 24, 12),
|
||||||
|
BackColor = BrandColor
|
||||||
|
};
|
||||||
|
header.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||||
|
header.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 250));
|
||||||
|
header.RowStyles.Add(new RowStyle(SizeType.Absolute, 34));
|
||||||
|
header.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
|
||||||
|
|
||||||
|
var title = new Label
|
||||||
|
{
|
||||||
|
Text = "BizTalk-Wartung sicher steuern",
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
Font = new Font(Font.FontFamily, 17F, FontStyle.Bold),
|
||||||
|
ForeColor = Color.White,
|
||||||
|
TextAlign = ContentAlignment.MiddleLeft
|
||||||
|
};
|
||||||
|
var subtitle = new Label
|
||||||
|
{
|
||||||
|
Text = "Zustand erfassen · kontrolliert herunterfahren · exakt wiederherstellen",
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ForeColor = Color.FromArgb(220, 232, 242),
|
||||||
|
TextAlign = ContentAlignment.MiddleLeft
|
||||||
|
};
|
||||||
|
|
||||||
|
_environmentStatusLabel.Dock = DockStyle.Fill;
|
||||||
|
_environmentStatusLabel.Margin = new Padding(16, 4, 0, 4);
|
||||||
|
_environmentStatusLabel.Padding = new Padding(8, 0, 8, 0);
|
||||||
|
_environmentStatusLabel.Font = new Font(Font, FontStyle.Bold);
|
||||||
|
_environmentStatusLabel.TextAlign = ContentAlignment.MiddleCenter;
|
||||||
|
UpdateEnvironmentStatus(null);
|
||||||
|
|
||||||
|
header.Controls.Add(title, 0, 0);
|
||||||
|
header.Controls.Add(subtitle, 0, 1);
|
||||||
|
header.Controls.Add(_environmentStatusLabel, 1, 0);
|
||||||
|
header.SetRowSpan(_environmentStatusLabel, 2);
|
||||||
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Control BuildSettingsPanel()
|
private Control BuildSettingsPanel()
|
||||||
{
|
{
|
||||||
var panel = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 10, RowCount = 2 };
|
var group = new GroupBox
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 64));
|
{
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30));
|
Text = "Ziel und Wartungsdateien",
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 64));
|
Dock = DockStyle.Fill,
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 45));
|
Margin = new Padding(18, 14, 18, 6),
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 96));
|
Padding = new Padding(12, 10, 12, 10),
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 70));
|
BackColor = Color.White
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 76));
|
};
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 72));
|
var panel = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 6, RowCount = 3 };
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 76));
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 118));
|
||||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 220));
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 48));
|
||||||
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106));
|
||||||
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 118));
|
||||||
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 52));
|
||||||
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106));
|
||||||
|
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
|
||||||
|
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
|
||||||
|
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
|
||||||
|
group.Controls.Add(panel);
|
||||||
|
|
||||||
_serverTextBox.Text = Environment.MachineName;
|
_serverTextBox.Text = Environment.MachineName;
|
||||||
ConfigureInput(_serverTextBox);
|
ConfigureInput(_serverTextBox);
|
||||||
_outputTextBox.Text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "BizTalkPlatformManagementTool");
|
_outputTextBox.Text = AppPaths.WorkspaceDirectory;
|
||||||
ConfigureInput(_outputTextBox);
|
ConfigureInput(_outputTextBox);
|
||||||
_stateFileTextBox.Text = "before.json";
|
_stateFileTextBox.Text = "Wartungsstatus-" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".json";
|
||||||
ConfigureInput(_stateFileTextBox);
|
ConfigureInput(_stateFileTextBox);
|
||||||
|
|
||||||
_timeoutInput.Minimum = 30;
|
_timeoutInput.Minimum = 30;
|
||||||
_timeoutInput.Maximum = 3600;
|
_timeoutInput.Maximum = 3600;
|
||||||
_timeoutInput.Value = 300;
|
_timeoutInput.Value = 300;
|
||||||
ConfigureInput(_timeoutInput);
|
ConfigureInput(_timeoutInput);
|
||||||
|
|
||||||
_pollInput.Minimum = 1;
|
_pollInput.Minimum = 1;
|
||||||
_pollInput.Maximum = 60;
|
_pollInput.Maximum = 60;
|
||||||
_pollInput.Value = 5;
|
_pollInput.Value = 5;
|
||||||
ConfigureInput(_pollInput);
|
ConfigureInput(_pollInput);
|
||||||
|
|
||||||
_dryRunCheckBox.Text = "Dry run";
|
_dryRunCheckBox.Text = "Sicherheitsmodus: nur planen (Dry-run)";
|
||||||
_dryRunCheckBox.Checked = true;
|
_dryRunCheckBox.Checked = true;
|
||||||
_dryRunCheckBox.Dock = DockStyle.Fill;
|
_dryRunCheckBox.Dock = DockStyle.Fill;
|
||||||
_dryRunCheckBox.Margin = new Padding(8, 7, 8, 7);
|
_dryRunCheckBox.Margin = new Padding(5, 8, 8, 6);
|
||||||
_dryRunCheckBox.TextAlign = ContentAlignment.MiddleLeft;
|
_dryRunCheckBox.Font = new Font(Font, FontStyle.Bold);
|
||||||
|
_dryRunCheckBox.ForeColor = AccentColor;
|
||||||
|
_dryRunCheckBox.CheckedChanged += delegate { UpdateModeIndicator(); };
|
||||||
|
|
||||||
var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) };
|
var browseOutput = SmallButton("Ordner …", BrowseOutputClick);
|
||||||
browseButton.Click += BrowseButtonClick;
|
var browseState = SmallButton("Datei …", BrowseStateClick);
|
||||||
|
|
||||||
_environmentStatusLabel.Dock = DockStyle.Fill;
|
panel.Controls.Add(FieldLabel("BizTalk-Server"), 0, 0);
|
||||||
_environmentStatusLabel.Margin = new Padding(12, 5, 0, 5);
|
|
||||||
_environmentStatusLabel.TextAlign = ContentAlignment.MiddleCenter;
|
|
||||||
_environmentStatusLabel.BorderStyle = BorderStyle.FixedSingle;
|
|
||||||
UpdateEnvironmentStatus(null);
|
|
||||||
|
|
||||||
panel.Controls.Add(Label("Server"), 0, 0);
|
|
||||||
panel.Controls.Add(_serverTextBox, 1, 0);
|
panel.Controls.Add(_serverTextBox, 1, 0);
|
||||||
panel.Controls.Add(Label("Output"), 2, 0);
|
panel.Controls.Add(FieldLabel("Timeout (Sek.)"), 3, 0);
|
||||||
panel.Controls.Add(_outputTextBox, 3, 0);
|
panel.Controls.Add(_timeoutInput, 4, 0);
|
||||||
panel.Controls.Add(browseButton, 4, 0);
|
|
||||||
panel.Controls.Add(Label("Timeout"), 5, 0);
|
|
||||||
panel.Controls.Add(_timeoutInput, 6, 0);
|
|
||||||
|
|
||||||
panel.Controls.Add(Label("State"), 0, 1);
|
panel.Controls.Add(FieldLabel("Arbeitsordner"), 0, 1);
|
||||||
panel.Controls.Add(_stateFileTextBox, 1, 1);
|
panel.Controls.Add(_outputTextBox, 1, 1);
|
||||||
panel.SetColumnSpan(_stateFileTextBox, 2);
|
panel.Controls.Add(browseOutput, 2, 1);
|
||||||
panel.Controls.Add(_dryRunCheckBox, 3, 1);
|
panel.Controls.Add(FieldLabel("Prüfintervall (Sek.)"), 3, 1);
|
||||||
panel.Controls.Add(Label("Poll sec."), 7, 1);
|
panel.Controls.Add(_pollInput, 4, 1);
|
||||||
panel.Controls.Add(_pollInput, 8, 1);
|
|
||||||
panel.Controls.Add(_environmentStatusLabel, 9, 0);
|
|
||||||
panel.SetRowSpan(_environmentStatusLabel, 2);
|
|
||||||
|
|
||||||
return panel;
|
panel.Controls.Add(FieldLabel("Vorher-Zustand"), 0, 2);
|
||||||
|
panel.Controls.Add(_stateFileTextBox, 1, 2);
|
||||||
|
panel.Controls.Add(browseState, 2, 2);
|
||||||
|
panel.Controls.Add(_dryRunCheckBox, 3, 2);
|
||||||
|
panel.SetColumnSpan(_dryRunCheckBox, 3);
|
||||||
|
|
||||||
|
_toolTip.SetToolTip(_stateFileTextBox, "Diese Datei bestimmt exakt, welche Artefakte später wieder gestartet werden.");
|
||||||
|
_toolTip.SetToolTip(_dryRunCheckBox, "Im Dry-run werden Pläne und Protokolle erzeugt, aber keine BizTalk-Zustände geändert.");
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Control BuildActionPanel()
|
private Control BuildWorkflowPanel()
|
||||||
{
|
{
|
||||||
var panel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, Padding = new Padding(0, 8, 0, 0), WrapContents = false };
|
var outer = new TableLayoutPanel
|
||||||
_diagnoseButton = ActionButton("Diagnose", DiagnoseClick);
|
{
|
||||||
_beforeButton = ActionButton("Snapshot Before", BeforeClick);
|
Dock = DockStyle.Fill,
|
||||||
_afterButton = ActionButton("Snapshot After", AfterClick);
|
ColumnCount = 2,
|
||||||
_compareButton = ActionButton("Compare", CompareClick);
|
RowCount = 1,
|
||||||
_shutdownButton = ActionButton("Shutdown", ShutdownClick);
|
Margin = new Padding(18, 4, 18, 8),
|
||||||
_restoreButton = ActionButton("Restore", RestoreClick);
|
Padding = new Padding(18, 4, 18, 8)
|
||||||
_clearButton = ActionButton("Clear", ClearClick);
|
};
|
||||||
_closeButton = ActionButton("Close", CloseClick);
|
outer.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||||
|
outer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 190));
|
||||||
|
|
||||||
panel.Controls.Add(_diagnoseButton);
|
var workflow = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 6, RowCount = 2 };
|
||||||
panel.Controls.Add(_beforeButton);
|
for (var i = 0; i < 6; i++) workflow.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 16.666F));
|
||||||
panel.Controls.Add(_afterButton);
|
workflow.RowStyles.Add(new RowStyle(SizeType.Absolute, 22));
|
||||||
panel.Controls.Add(_compareButton);
|
workflow.RowStyles.Add(new RowStyle(SizeType.Absolute, 48));
|
||||||
panel.Controls.Add(_shutdownButton);
|
|
||||||
panel.Controls.Add(_restoreButton);
|
var captions = new[] { "Vorprüfung", "Vor Wartung", "Wartung", "Nach Wartung", "Kontrolle", "Abschluss" };
|
||||||
panel.Controls.Add(_clearButton);
|
foreach (var caption in captions.Select((value, index) => new { value, index }))
|
||||||
panel.Controls.Add(_closeButton);
|
{
|
||||||
return panel;
|
workflow.Controls.Add(new Label
|
||||||
|
{
|
||||||
|
Text = caption.value,
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ForeColor = Color.DimGray,
|
||||||
|
TextAlign = ContentAlignment.MiddleCenter
|
||||||
|
}, caption.index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_diagnoseButton = WorkflowButton("1 Verbindung prüfen", AccentColor, DiagnoseClick);
|
||||||
|
_beforeButton = WorkflowButton("2 Zustand erfassen", AccentColor, BeforeClick);
|
||||||
|
_shutdownButton = WorkflowButton("3 Herunterfahren", DangerColor, ShutdownClick);
|
||||||
|
_restoreButton = WorkflowButton("4 Wiederherstellen", SuccessColor, RestoreClick);
|
||||||
|
_afterButton = WorkflowButton("5 Nachher erfassen", AccentColor, AfterClick);
|
||||||
|
_compareButton = WorkflowButton("6 Vergleichen", AccentColor, CompareClick);
|
||||||
|
workflow.Controls.Add(_diagnoseButton, 0, 1);
|
||||||
|
workflow.Controls.Add(_beforeButton, 1, 1);
|
||||||
|
workflow.Controls.Add(_shutdownButton, 2, 1);
|
||||||
|
workflow.Controls.Add(_restoreButton, 3, 1);
|
||||||
|
workflow.Controls.Add(_afterButton, 4, 1);
|
||||||
|
workflow.Controls.Add(_compareButton, 5, 1);
|
||||||
|
|
||||||
|
var utility = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, RowCount = 1, Padding = new Padding(12, 22, 0, 0) };
|
||||||
|
utility.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
|
||||||
|
utility.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
|
||||||
|
_clearButton = SmallButton("Leeren", ClearClick);
|
||||||
|
_closeButton = SmallButton("Schließen", CloseClick);
|
||||||
|
utility.Controls.Add(_clearButton, 0, 0);
|
||||||
|
utility.Controls.Add(_closeButton, 1, 0);
|
||||||
|
|
||||||
|
outer.Controls.Add(workflow, 0, 0);
|
||||||
|
outer.Controls.Add(utility, 1, 0);
|
||||||
|
return outer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Control BuildTabs()
|
private Control BuildTabs()
|
||||||
{
|
{
|
||||||
var tabs = new TabControl { Dock = DockStyle.Fill };
|
var tabs = new TabControl { Dock = DockStyle.Fill, Margin = new Padding(18, 0, 18, 8), Padding = new Point(14, 5) };
|
||||||
var statusPage = new TabPage("Status / Results");
|
var statusPage = new TabPage("Status und Ausführungsplan") { Padding = new Padding(4) };
|
||||||
var logPage = new TabPage("Operation Log");
|
var logPage = new TabPage("Aktivitätsprotokoll") { Padding = new Padding(4) };
|
||||||
|
|
||||||
ConfigureGrid(_statusGrid);
|
ConfigureGrid(_statusGrid);
|
||||||
_statusGrid.Columns.Add("Scope", "Scope");
|
AddGridColumn(_statusGrid, "Scope", "Bereich", 130, false);
|
||||||
_statusGrid.Columns.Add("Application", "Application");
|
AddGridColumn(_statusGrid, "Application", "Anwendung", 190, false);
|
||||||
_statusGrid.Columns.Add("Name", "Name");
|
AddGridColumn(_statusGrid, "Name", "Name", 250, true);
|
||||||
_statusGrid.Columns.Add("Status", "Status");
|
AddGridColumn(_statusGrid, "Status", "Status / Aktion", 180, false);
|
||||||
_statusGrid.Columns.Add("Details", "Details");
|
AddGridColumn(_statusGrid, "Details", "Details", 300, true);
|
||||||
|
_statusGrid.CellFormatting += StatusGridCellFormatting;
|
||||||
statusPage.Controls.Add(_statusGrid);
|
statusPage.Controls.Add(_statusGrid);
|
||||||
|
|
||||||
ConfigureGrid(_logGrid);
|
ConfigureGrid(_logGrid);
|
||||||
_logGrid.Columns.Add("Time", "Time");
|
AddGridColumn(_logGrid, "Time", "Zeit", 72, false);
|
||||||
_logGrid.Columns.Add("Level", "Level");
|
AddGridColumn(_logGrid, "Level", "Stufe", 85, false);
|
||||||
_logGrid.Columns.Add("Message", "Message");
|
AddGridColumn(_logGrid, "Message", "Meldung", 600, true);
|
||||||
_logGrid.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
|
||||||
logPage.Controls.Add(_logGrid);
|
logPage.Controls.Add(_logGrid);
|
||||||
|
|
||||||
tabs.TabPages.Add(statusPage);
|
tabs.TabPages.Add(statusPage);
|
||||||
@@ -189,19 +281,38 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Control BuildStatusStrip()
|
||||||
|
{
|
||||||
|
var strip = new StatusStrip { SizingGrip = false, BackColor = Color.White };
|
||||||
|
_statusLabel.Text = "Bereit.";
|
||||||
|
_statusLabel.Spring = true;
|
||||||
|
_statusLabel.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
_progressBar.Style = ProgressBarStyle.Marquee;
|
||||||
|
_progressBar.MarqueeAnimationSpeed = 25;
|
||||||
|
_progressBar.Visible = false;
|
||||||
|
var progressHost = new ToolStripControlHost(_progressBar) { AutoSize = false, Width = 120, Height = 16 };
|
||||||
|
strip.Items.Add(_statusLabel);
|
||||||
|
strip.Items.Add(progressHost);
|
||||||
|
strip.Items.Add(_modeLabel);
|
||||||
|
UpdateModeIndicator();
|
||||||
|
return strip;
|
||||||
|
}
|
||||||
|
|
||||||
private void DiagnoseClick(object sender, EventArgs e)
|
private void DiagnoseClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var server = _serverTextBox.Text.Trim();
|
var server = GetOptions().Server;
|
||||||
RunAsync("Diagnosing WMI access...", () => _service.Diagnose(server));
|
RunAsync("WMI-Zugriff wird geprüft …", () => _service.Diagnose(server));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BeforeClick(object sender, EventArgs e)
|
private void BeforeClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var options = GetOptions();
|
var options = GetOptions();
|
||||||
RunAsync("Creating before snapshot...", () =>
|
var path = ResolveStateFile(options);
|
||||||
|
if (File.Exists(path) && !ConfirmOverwrite(path)) return;
|
||||||
|
RunAsync("Vorher-Zustand wird erfasst …", () =>
|
||||||
{
|
{
|
||||||
var snapshot = _service.CreateSnapshot(options.Server);
|
var snapshot = _service.CreateSnapshot(options.Server);
|
||||||
_service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot);
|
_service.SaveSnapshot(path, snapshot);
|
||||||
ShowSnapshot(snapshot);
|
ShowSnapshot(snapshot);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -209,7 +320,7 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
private void AfterClick(object sender, EventArgs e)
|
private void AfterClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var options = GetOptions();
|
var options = GetOptions();
|
||||||
RunAsync("Creating after snapshot...", () =>
|
RunAsync("Nachher-Zustand wird erfasst …", () =>
|
||||||
{
|
{
|
||||||
var snapshot = _service.CreateSnapshot(options.Server);
|
var snapshot = _service.CreateSnapshot(options.Server);
|
||||||
_service.SaveSnapshot(options.OutputDirectory, "after.json", snapshot);
|
_service.SaveSnapshot(options.OutputDirectory, "after.json", snapshot);
|
||||||
@@ -220,10 +331,10 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
private void CompareClick(object sender, EventArgs e)
|
private void CompareClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var options = GetOptions();
|
var options = GetOptions();
|
||||||
RunAsync("Comparing before and after snapshots...", () =>
|
RunAsync("Vorher- und Nachher-Zustand werden verglichen …", () =>
|
||||||
{
|
{
|
||||||
var before = JsonFileStore.Load<BizTalkSnapshot>(Path.Combine(options.OutputDirectory, "before.json"));
|
var before = LoadAndValidateSnapshot(ResolveStateFile(options), options.Server);
|
||||||
var after = JsonFileStore.Load<BizTalkSnapshot>(Path.Combine(options.OutputDirectory, "after.json"));
|
var after = LoadAndValidateSnapshot(Path.Combine(options.OutputDirectory, "after.json"), options.Server);
|
||||||
var diff = SnapshotComparer.Compare(before, after);
|
var diff = SnapshotComparer.Compare(before, after);
|
||||||
_service.SaveDiff(options.OutputDirectory, "diff.json", diff);
|
_service.SaveDiff(options.OutputDirectory, "diff.json", diff);
|
||||||
ShowDiff(diff);
|
ShowDiff(diff);
|
||||||
@@ -232,16 +343,23 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
|
|
||||||
private void ShutdownClick(object sender, EventArgs e)
|
private void ShutdownClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!ConfirmDangerousAction("Shutdown"))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = GetOptions();
|
var options = GetOptions();
|
||||||
RunAsync("Preparing shutdown...", () =>
|
if (!ConfirmDangerousAction("herunterfahren", options)) return;
|
||||||
|
RunAsync("Herunterfahren wird vorbereitet …", () =>
|
||||||
{
|
{
|
||||||
var snapshot = _service.CreateSnapshot(options.Server);
|
var statePath = ResolveStateFile(options);
|
||||||
_service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot);
|
BizTalkSnapshot snapshot;
|
||||||
|
if (File.Exists(statePath))
|
||||||
|
{
|
||||||
|
snapshot = LoadAndValidateSnapshot(statePath, options.Server);
|
||||||
|
_logger.Info("Vorhandener Vorher-Zustand wird unverändert wiederverwendet: " + statePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snapshot = _service.CreateSnapshot(options.Server);
|
||||||
|
_service.SaveSnapshot(statePath, snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
var plan = _service.CreateShutdownPlan(snapshot, options.Server);
|
var plan = _service.CreateShutdownPlan(snapshot, options.Server);
|
||||||
_service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan);
|
_service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan);
|
||||||
ShowPlan(plan);
|
ShowPlan(plan);
|
||||||
@@ -257,15 +375,11 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
|
|
||||||
private void RestoreClick(object sender, EventArgs e)
|
private void RestoreClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!ConfirmDangerousAction("Restore"))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = GetOptions();
|
var options = GetOptions();
|
||||||
RunAsync("Preparing restore...", () =>
|
if (!ConfirmDangerousAction("wiederherstellen", options)) return;
|
||||||
|
RunAsync("Wiederherstellung wird vorbereitet …", () =>
|
||||||
{
|
{
|
||||||
var snapshot = JsonFileStore.Load<BizTalkSnapshot>(ResolveStateFile(options));
|
var snapshot = LoadAndValidateSnapshot(ResolveStateFile(options), options.Server);
|
||||||
var plan = _service.CreateRestorePlan(snapshot, options.Server);
|
var plan = _service.CreateRestorePlan(snapshot, options.Server);
|
||||||
_service.SavePlan(options.OutputDirectory, "restore-plan.json", plan);
|
_service.SavePlan(options.OutputDirectory, "restore-plan.json", plan);
|
||||||
ShowPlan(plan);
|
ShowPlan(plan);
|
||||||
@@ -284,7 +398,7 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
_statusGrid.Rows.Clear();
|
_statusGrid.Rows.Clear();
|
||||||
_logGrid.Rows.Clear();
|
_logGrid.Rows.Clear();
|
||||||
UpdateEnvironmentStatus(null);
|
UpdateEnvironmentStatus(null);
|
||||||
_statusLabel.Text = "Ready.";
|
_statusLabel.Text = "Bereit.";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseClick(object sender, EventArgs e)
|
private void CloseClick(object sender, EventArgs e)
|
||||||
@@ -300,13 +414,14 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
work();
|
work();
|
||||||
_logger.Success("Operation completed.");
|
_logger.Success("Vorgang abgeschlossen.");
|
||||||
SetBusy(false, "Ready.");
|
SetBusy(false, "Bereit.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(FormatException(ex));
|
_logger.Error(FormatException(ex));
|
||||||
SetBusy(false, "Failed: " + ex.Message);
|
SetBusy(false, "Fehler: " + ex.Message);
|
||||||
|
InvokeIfRequired(() => MessageBox.Show(this, FormatException(ex), "Vorgang fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -314,11 +429,8 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
private OperationOptions GetOptions()
|
private OperationOptions GetOptions()
|
||||||
{
|
{
|
||||||
var server = string.IsNullOrWhiteSpace(_serverTextBox.Text) ? Environment.MachineName : _serverTextBox.Text.Trim();
|
var server = string.IsNullOrWhiteSpace(_serverTextBox.Text) ? Environment.MachineName : _serverTextBox.Text.Trim();
|
||||||
var output = string.IsNullOrWhiteSpace(_outputTextBox.Text)
|
var output = string.IsNullOrWhiteSpace(_outputTextBox.Text) ? AppPaths.WorkspaceDirectory : _outputTextBox.Text.Trim();
|
||||||
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "BizTalkPlatformManagementTool")
|
var stateFile = string.IsNullOrWhiteSpace(_stateFileTextBox.Text) ? "Wartungsstatus.json" : _stateFileTextBox.Text.Trim();
|
||||||
: _outputTextBox.Text.Trim();
|
|
||||||
var stateFile = string.IsNullOrWhiteSpace(_stateFileTextBox.Text) ? "before.json" : _stateFileTextBox.Text.Trim();
|
|
||||||
|
|
||||||
return new OperationOptions
|
return new OperationOptions
|
||||||
{
|
{
|
||||||
Server = server,
|
Server = server,
|
||||||
@@ -330,25 +442,34 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveStateFile(OperationOptions options)
|
private static BizTalkSnapshot LoadAndValidateSnapshot(string path, string server)
|
||||||
|
{
|
||||||
|
var snapshot = JsonFileStore.Load<BizTalkSnapshot>(path);
|
||||||
|
BizTalkOperationService.ValidateSnapshot(snapshot, server);
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveStateFile(OperationOptions options)
|
||||||
{
|
{
|
||||||
return Path.IsPathRooted(options.StateFile) ? options.StateFile : Path.Combine(options.OutputDirectory, options.StateFile);
|
return Path.IsPathRooted(options.StateFile) ? options.StateFile : Path.Combine(options.OutputDirectory, options.StateFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ConfirmDangerousAction(string actionName)
|
private bool ConfirmDangerousAction(string verb, OperationOptions options)
|
||||||
{
|
{
|
||||||
if (_dryRunCheckBox.Checked)
|
if (options.DryRun) return true;
|
||||||
{
|
var message = "Die BizTalk-Umgebung auf '" + options.Server + "' wird jetzt tatsächlich " + verb + ".\r\n\r\n"
|
||||||
return true;
|
+ "Maßgeblicher Vorher-Zustand:\r\n" + ResolveStateFile(options) + "\r\n\r\nFortfahren?";
|
||||||
}
|
return MessageBox.Show(this, message, "BizTalk-Laufzeit ändern", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
var result = MessageBox.Show(
|
private bool ConfirmOverwrite(string path)
|
||||||
actionName + " will change the BizTalk runtime state on server '" + _serverTextBox.Text + "'. Continue?",
|
{
|
||||||
"Confirm BizTalk Runtime Change",
|
return MessageBox.Show(this,
|
||||||
|
"Die Zustandsdatei existiert bereits und ist die Grundlage einer möglichen Wiederherstellung:\r\n\r\n" + path + "\r\n\r\nWirklich überschreiben?",
|
||||||
|
"Vorher-Zustand schützen",
|
||||||
MessageBoxButtons.YesNo,
|
MessageBoxButtons.YesNo,
|
||||||
MessageBoxIcon.Warning,
|
MessageBoxIcon.Warning,
|
||||||
MessageBoxDefaultButton.Button2);
|
MessageBoxDefaultButton.Button2) == DialogResult.Yes;
|
||||||
return result == DialogResult.Yes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowSnapshot(BizTalkSnapshot snapshot)
|
private void ShowSnapshot(BizTalkSnapshot snapshot)
|
||||||
@@ -359,22 +480,14 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
foreach (var app in snapshot.Applications)
|
foreach (var app in snapshot.Applications)
|
||||||
{
|
{
|
||||||
foreach (var item in app.ReceiveLocations)
|
foreach (var item in app.ReceiveLocations)
|
||||||
{
|
_statusGrid.Rows.Add("Receive Location", app.Application, item.Name, ArtifactStates.FormatReceiveLocation(item.Enabled), item.AdapterName);
|
||||||
_statusGrid.Rows.Add("ReceiveLocation", app.Application, item.Name, ArtifactStates.FormatReceiveLocation(item.Enabled), item.AdapterName + " " + item.Address);
|
|
||||||
}
|
|
||||||
foreach (var item in app.SendPorts)
|
foreach (var item in app.SendPorts)
|
||||||
{
|
_statusGrid.Rows.Add("Send Port", app.Application, item.Name, ArtifactStates.FormatSendPort(item.Status), item.PrimaryTransportType);
|
||||||
_statusGrid.Rows.Add("SendPort", app.Application, item.Name, ArtifactStates.FormatSendPort(item.Status), item.PrimaryTransportType + " " + item.PrimaryTransportAddress);
|
|
||||||
}
|
|
||||||
foreach (var item in app.Orchestrations)
|
foreach (var item in app.Orchestrations)
|
||||||
{
|
_statusGrid.Rows.Add("Orchestrierung", app.Application, item.Name, ArtifactStates.FormatOrchestration(item.OrchestrationStatus), item.AssemblyName);
|
||||||
_statusGrid.Rows.Add("Orchestration", app.Application, item.Name, ArtifactStates.FormatOrchestration(item.OrchestrationStatus), string.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
foreach (var item in snapshot.HostInstances)
|
foreach (var item in snapshot.HostInstances)
|
||||||
{
|
_statusGrid.Rows.Add("Hostinstanz", string.Empty, item.InstanceName, item.StateText, item.HostName + " auf " + item.Server);
|
||||||
_statusGrid.Rows.Add("HostInstance", string.Empty, item.InstanceName, item.StateText, item.HostName + " on " + item.Server);
|
|
||||||
}
|
|
||||||
UpdateEnvironmentStatus(snapshot);
|
UpdateEnvironmentStatus(snapshot);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -385,13 +498,11 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
{
|
{
|
||||||
_statusGrid.Rows.Clear();
|
_statusGrid.Rows.Clear();
|
||||||
foreach (var item in diff.ArtifactDifferences)
|
foreach (var item in diff.ArtifactDifferences)
|
||||||
{
|
_statusGrid.Rows.Add("Abweichung", item.Application, item.Name, item.Before + " → " + item.After, item.ArtifactType);
|
||||||
_statusGrid.Rows.Add("Diff", item.Application, item.Name, item.Before + " -> " + item.After, item.ArtifactType);
|
|
||||||
}
|
|
||||||
foreach (var item in diff.HostInstanceDifferences)
|
foreach (var item in diff.HostInstanceDifferences)
|
||||||
{
|
_statusGrid.Rows.Add("Host-Abweichung", string.Empty, item.InstanceName, item.Before + " → " + item.After, item.Server);
|
||||||
_statusGrid.Rows.Add("HostDiff", string.Empty, item.InstanceName, item.Before + " -> " + item.After, item.Server);
|
if (diff.ArtifactDifferences.Count == 0 && diff.HostInstanceDifferences.Count == 0)
|
||||||
}
|
_statusGrid.Rows.Add("Vergleich", string.Empty, "Keine Abweichungen", "OK", "Vorher- und Nachher-Zustand stimmen überein.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,9 +512,7 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
{
|
{
|
||||||
_statusGrid.Rows.Clear();
|
_statusGrid.Rows.Clear();
|
||||||
foreach (var step in plan.Steps)
|
foreach (var step in plan.Steps)
|
||||||
{
|
_statusGrid.Rows.Add(step.Kind, step.Application, step.Name, step.Execute ? step.Action : "Übersprungen", step.Warning ?? step.MethodName);
|
||||||
_statusGrid.Rows.Add(step.Kind, step.Application, step.Name, step.Execute ? step.Action : "Skipped", step.Warning ?? step.MethodName);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,19 +522,10 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
{
|
{
|
||||||
var index = _logGrid.Rows.Add(entry.Timestamp.ToString("HH:mm:ss"), entry.Level.ToString(), entry.Message);
|
var index = _logGrid.Rows.Add(entry.Timestamp.ToString("HH:mm:ss"), entry.Level.ToString(), entry.Message);
|
||||||
var row = _logGrid.Rows[index];
|
var row = _logGrid.Rows[index];
|
||||||
if (entry.Level == LogLevel.Error)
|
if (entry.Level == LogLevel.Error) row.DefaultCellStyle.ForeColor = DangerColor;
|
||||||
{
|
else if (entry.Level == LogLevel.Warning) row.DefaultCellStyle.ForeColor = Color.DarkOrange;
|
||||||
row.DefaultCellStyle.ForeColor = Color.DarkRed;
|
else if (entry.Level == LogLevel.Success) row.DefaultCellStyle.ForeColor = SuccessColor;
|
||||||
}
|
if (_logGrid.Rows.Count > 0) _logGrid.FirstDisplayedScrollingRowIndex = _logGrid.Rows.Count - 1;
|
||||||
else if (entry.Level == LogLevel.Warning)
|
|
||||||
{
|
|
||||||
row.DefaultCellStyle.ForeColor = Color.DarkOrange;
|
|
||||||
}
|
|
||||||
else if (entry.Level == LogLevel.Success)
|
|
||||||
{
|
|
||||||
row.DefaultCellStyle.ForeColor = Color.DarkGreen;
|
|
||||||
}
|
|
||||||
_logGrid.FirstDisplayedScrollingRowIndex = Math.Max(0, _logGrid.Rows.Count - 1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,55 +533,121 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
{
|
{
|
||||||
InvokeIfRequired(() =>
|
InvokeIfRequired(() =>
|
||||||
{
|
{
|
||||||
_diagnoseButton.Enabled = !busy;
|
_busy = busy;
|
||||||
_beforeButton.Enabled = !busy;
|
foreach (var button in new[] { _diagnoseButton, _beforeButton, _shutdownButton, _restoreButton, _afterButton, _compareButton, _clearButton, _closeButton })
|
||||||
_afterButton.Enabled = !busy;
|
button.Enabled = !busy;
|
||||||
_compareButton.Enabled = !busy;
|
_serverTextBox.Enabled = !busy;
|
||||||
_shutdownButton.Enabled = !busy;
|
_outputTextBox.Enabled = !busy;
|
||||||
_restoreButton.Enabled = !busy;
|
_stateFileTextBox.Enabled = !busy;
|
||||||
_clearButton.Enabled = !busy;
|
_dryRunCheckBox.Enabled = !busy;
|
||||||
_closeButton.Enabled = !busy;
|
_progressBar.Visible = busy;
|
||||||
_statusLabel.Text = status;
|
_statusLabel.Text = status;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BrowseButtonClick(object sender, EventArgs e)
|
private void BrowseOutputClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var dialog = new FolderBrowserDialog())
|
using (var dialog = new FolderBrowserDialog())
|
||||||
{
|
{
|
||||||
dialog.SelectedPath = Directory.Exists(_outputTextBox.Text) ? _outputTextBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
dialog.Description = "Arbeitsordner für Snapshots, Pläne und Berichte wählen";
|
||||||
if (dialog.ShowDialog(this) == DialogResult.OK)
|
dialog.SelectedPath = Directory.Exists(_outputTextBox.Text) ? _outputTextBox.Text : AppPaths.WorkspaceDirectory;
|
||||||
{
|
if (dialog.ShowDialog(this) == DialogResult.OK) _outputTextBox.Text = dialog.SelectedPath;
|
||||||
_outputTextBox.Text = dialog.SelectedPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BrowseStateClick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using (var dialog = new OpenFileDialog())
|
||||||
|
{
|
||||||
|
dialog.Title = "Vorher-Zustand auswählen";
|
||||||
|
dialog.Filter = "BizTalk-Zustand (*.json)|*.json|Alle Dateien (*.*)|*.*";
|
||||||
|
dialog.CheckFileExists = true;
|
||||||
|
dialog.InitialDirectory = Directory.Exists(_outputTextBox.Text) ? _outputTextBox.Text : AppPaths.WorkspaceDirectory;
|
||||||
|
if (dialog.ShowDialog(this) == DialogResult.OK) _stateFileTextBox.Text = dialog.FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateEnvironmentStatus(BizTalkSnapshot snapshot)
|
||||||
|
{
|
||||||
|
if (snapshot == null || snapshot.HostInstances == null || snapshot.HostInstances.Count == 0)
|
||||||
|
{
|
||||||
|
SetEnvironmentBadge("STATUS UNBEKANNT", Color.FromArgb(225, 230, 236), BrandColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = snapshot.HostInstances.Count;
|
||||||
|
var started = snapshot.HostInstances.Count(x => x.RawState == ArtifactStates.HostStarted);
|
||||||
|
var pending = snapshot.HostInstances.Count(x => x.RawState == ArtifactStates.HostStartPending || x.RawState == ArtifactStates.HostStopPending);
|
||||||
|
if (started == total) SetEnvironmentBadge("GESTARTET " + started + "/" + total, Color.FromArgb(214, 242, 225), Color.FromArgb(20, 92, 52));
|
||||||
|
else if (started == 0 && pending == 0) SetEnvironmentBadge("GESTOPPT 0/" + total, Color.FromArgb(250, 220, 220), Color.FromArgb(130, 24, 24));
|
||||||
|
else SetEnvironmentBadge("TEILWEISE " + started + "/" + total, Color.FromArgb(255, 237, 194), Color.FromArgb(128, 80, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetEnvironmentBadge(string text, Color backColor, Color foreColor)
|
||||||
|
{
|
||||||
|
_environmentStatusLabel.Text = text;
|
||||||
|
_environmentStatusLabel.BackColor = backColor;
|
||||||
|
_environmentStatusLabel.ForeColor = foreColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateModeIndicator()
|
||||||
|
{
|
||||||
|
_modeLabel.Text = _dryRunCheckBox.Checked ? " DRY-RUN AKTIV " : " ECHTE ÄNDERUNGEN ";
|
||||||
|
_modeLabel.BackColor = _dryRunCheckBox.Checked ? Color.FromArgb(221, 237, 251) : Color.FromArgb(250, 220, 220);
|
||||||
|
_modeLabel.ForeColor = _dryRunCheckBox.Checked ? BrandColor : DangerColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StatusGridCellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.RowIndex < 0 || e.ColumnIndex != 3 || e.Value == null) return;
|
||||||
|
var value = Convert.ToString(e.Value);
|
||||||
|
if (value.IndexOf("Gestartet", StringComparison.OrdinalIgnoreCase) >= 0 || value.IndexOf("Aktiviert", StringComparison.OrdinalIgnoreCase) >= 0 || value == "OK")
|
||||||
|
_statusGrid.Rows[e.RowIndex].DefaultCellStyle.ForeColor = SuccessColor;
|
||||||
|
else if (value.IndexOf("Gestoppt", StringComparison.OrdinalIgnoreCase) >= 0 || value.IndexOf("Deaktiviert", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
_statusGrid.Rows[e.RowIndex].DefaultCellStyle.ForeColor = Color.DimGray;
|
||||||
|
else if (value.IndexOf("→", StringComparison.Ordinal) >= 0)
|
||||||
|
_statusGrid.Rows[e.RowIndex].DefaultCellStyle.ForeColor = DangerColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainFormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_busy) return;
|
||||||
|
e.Cancel = true;
|
||||||
|
MessageBox.Show(this, "Ein Vorgang läuft noch. Bitte warten Sie bis zum Abschluss.", "Vorgang läuft", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
|
||||||
private void InvokeIfRequired(Action action)
|
private void InvokeIfRequired(Action action)
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (IsDisposed || Disposing) return;
|
||||||
{
|
if (InvokeRequired) BeginInvoke(action); else action();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InvokeRequired)
|
|
||||||
{
|
|
||||||
BeginInvoke(action);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Label Label(string text)
|
private static Label FieldLabel(string text)
|
||||||
{
|
{
|
||||||
return new Label { Text = text, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleRight, Margin = new Padding(0, 5, 4, 5) };
|
return new Label { Text = text, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleRight, Margin = new Padding(0, 6, 8, 6), ForeColor = Color.FromArgb(55, 65, 75) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Button ActionButton(string text, EventHandler handler)
|
private static Button WorkflowButton(string text, Color color, EventHandler handler)
|
||||||
{
|
{
|
||||||
var button = new Button { Text = text, Width = 112, Height = 34, Margin = new Padding(0, 0, 8, 0) };
|
var button = new Button
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
Margin = new Padding(4, 2, 4, 4),
|
||||||
|
FlatStyle = FlatStyle.Flat,
|
||||||
|
BackColor = color,
|
||||||
|
ForeColor = Color.White,
|
||||||
|
Font = new Font("Segoe UI", 9F, FontStyle.Bold),
|
||||||
|
Cursor = Cursors.Hand
|
||||||
|
};
|
||||||
|
button.FlatAppearance.BorderSize = 0;
|
||||||
|
button.Click += handler;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Button SmallButton(string text, EventHandler handler)
|
||||||
|
{
|
||||||
|
var button = new Button { Text = text, Dock = DockStyle.Fill, Margin = new Padding(5, 6, 5, 6), FlatStyle = FlatStyle.System };
|
||||||
button.Click += handler;
|
button.Click += handler;
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
@@ -489,41 +655,7 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
private static void ConfigureInput(Control control)
|
private static void ConfigureInput(Control control)
|
||||||
{
|
{
|
||||||
control.Dock = DockStyle.Fill;
|
control.Dock = DockStyle.Fill;
|
||||||
control.Margin = new Padding(4, 6, 8, 6);
|
control.Margin = new Padding(4, 7, 8, 7);
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateEnvironmentStatus(BizTalkSnapshot snapshot)
|
|
||||||
{
|
|
||||||
if (snapshot == null || snapshot.HostInstances == null || snapshot.HostInstances.Count == 0)
|
|
||||||
{
|
|
||||||
_environmentStatusLabel.Text = "Environment: Unknown";
|
|
||||||
_environmentStatusLabel.BackColor = SystemColors.Control;
|
|
||||||
_environmentStatusLabel.ForeColor = SystemColors.ControlText;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var total = snapshot.HostInstances.Count;
|
|
||||||
var started = snapshot.HostInstances.Count(x => x.RawState == ArtifactStates.HostStarted);
|
|
||||||
var pending = snapshot.HostInstances.Count(x => x.RawState == ArtifactStates.HostStartPending || x.RawState == ArtifactStates.HostStopPending);
|
|
||||||
|
|
||||||
if (started == total)
|
|
||||||
{
|
|
||||||
_environmentStatusLabel.Text = "Environment: Started (" + started + "/" + total + ")";
|
|
||||||
_environmentStatusLabel.BackColor = Color.FromArgb(230, 245, 233);
|
|
||||||
_environmentStatusLabel.ForeColor = Color.DarkGreen;
|
|
||||||
}
|
|
||||||
else if (started == 0 && pending == 0)
|
|
||||||
{
|
|
||||||
_environmentStatusLabel.Text = "Environment: Stopped (0/" + total + ")";
|
|
||||||
_environmentStatusLabel.BackColor = Color.FromArgb(252, 232, 230);
|
|
||||||
_environmentStatusLabel.ForeColor = Color.DarkRed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_environmentStatusLabel.Text = "Environment: Partial (" + started + "/" + total + ")";
|
|
||||||
_environmentStatusLabel.BackColor = Color.FromArgb(255, 244, 214);
|
|
||||||
_environmentStatusLabel.ForeColor = Color.DarkOrange;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureGrid(DataGridView grid)
|
private static void ConfigureGrid(DataGridView grid)
|
||||||
@@ -531,32 +663,42 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
grid.Dock = DockStyle.Fill;
|
grid.Dock = DockStyle.Fill;
|
||||||
grid.AllowUserToAddRows = false;
|
grid.AllowUserToAddRows = false;
|
||||||
grid.AllowUserToDeleteRows = false;
|
grid.AllowUserToDeleteRows = false;
|
||||||
|
grid.AllowUserToResizeRows = false;
|
||||||
grid.ReadOnly = true;
|
grid.ReadOnly = true;
|
||||||
grid.RowHeadersVisible = false;
|
grid.RowHeadersVisible = false;
|
||||||
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||||
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
grid.MultiSelect = false;
|
||||||
grid.BackgroundColor = SystemColors.Window;
|
grid.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders;
|
||||||
|
grid.BackgroundColor = Color.White;
|
||||||
|
grid.BorderStyle = BorderStyle.None;
|
||||||
|
grid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
|
||||||
|
grid.GridColor = Color.FromArgb(228, 232, 238);
|
||||||
|
grid.EnableHeadersVisualStyles = false;
|
||||||
|
grid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(235, 239, 244);
|
||||||
|
grid.ColumnHeadersDefaultCellStyle.ForeColor = BrandColor;
|
||||||
|
grid.ColumnHeadersDefaultCellStyle.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||||
|
grid.ColumnHeadersHeight = 34;
|
||||||
|
grid.RowTemplate.Height = 30;
|
||||||
|
grid.DefaultCellStyle.Padding = new Padding(4, 3, 4, 3);
|
||||||
|
grid.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 252);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddGridColumn(DataGridView grid, string name, string title, int width, bool fill)
|
||||||
|
{
|
||||||
|
var column = new DataGridViewTextBoxColumn { Name = name, HeaderText = title, Width = width, SortMode = DataGridViewColumnSortMode.Automatic };
|
||||||
|
if (fill) column.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||||
|
grid.Columns.Add(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatException(Exception ex)
|
private static string FormatException(Exception ex)
|
||||||
{
|
{
|
||||||
if (ex == null)
|
if (ex == null) return "Vorgang fehlgeschlagen.";
|
||||||
{
|
|
||||||
return "Operation failed.";
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = ex.Message;
|
var message = ex.Message;
|
||||||
var inner = ex.InnerException;
|
for (var inner = ex.InnerException; inner != null; inner = inner.InnerException)
|
||||||
while (inner != null)
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(inner.Message) && message.IndexOf(inner.Message, StringComparison.OrdinalIgnoreCase) < 0)
|
if (!string.IsNullOrWhiteSpace(inner.Message) && message.IndexOf(inner.Message, StringComparison.OrdinalIgnoreCase) < 0)
|
||||||
{
|
message += " Ursache: " + inner.Message;
|
||||||
message += " Inner error: " + inner.Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = inner.InnerException;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="BizTalkPlatformManagementTool.app" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<supportedOS Id="{4f476546-937d-4f22-9fc8-4b416b2f8f9f}" />
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{B2AD340B-859F-413B-8811-7FBA4BD2BC02}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>BizTalkPlatformManagementTool.Tests</RootNamespace>
|
||||||
|
<AssemblyName>BizTalkPlatformManagementTool.Tests</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj">
|
||||||
|
<Project>{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}</Project>
|
||||||
|
<Name>BizTalkPlatformManagementTool</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using BizTalkPlatformManagementTool.Models;
|
||||||
|
using BizTalkPlatformManagementTool.Services;
|
||||||
|
|
||||||
|
namespace BizTalkPlatformManagementTool.Tests
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static int _failed;
|
||||||
|
|
||||||
|
private static int Main()
|
||||||
|
{
|
||||||
|
Run("RestoreStartsOnlyPreviouslyActiveArtifacts", RestoreStartsOnlyPreviouslyActiveArtifacts);
|
||||||
|
Run("ShutdownStopsOnlyPreviouslyActiveArtifacts", ShutdownStopsOnlyPreviouslyActiveArtifacts);
|
||||||
|
Run("SnapshotComparisonUsesCompositeIdentity", SnapshotComparisonUsesCompositeIdentity);
|
||||||
|
Run("SnapshotServerValidationIsSafe", SnapshotServerValidationIsSafe);
|
||||||
|
Run("JsonStoreReplacesAtomically", JsonStoreReplacesAtomically);
|
||||||
|
Console.WriteLine(_failed == 0 ? "PASS: 5 tests" : "FAIL: " + _failed + " test(s)");
|
||||||
|
return _failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RestoreStartsOnlyPreviouslyActiveArtifacts()
|
||||||
|
{
|
||||||
|
var snapshot = FullSnapshot();
|
||||||
|
var plan = new BizTalkOperationService(null).CreateRestorePlan(snapshot, "BIZ01");
|
||||||
|
AssertEqual(4, plan.Steps.Count(x => x.Execute), "restore executable step count");
|
||||||
|
Assert(plan.Steps.Any(x => x.Kind == "HostInstance" && x.Name == "HostStarted"), "started host missing");
|
||||||
|
Assert(plan.Steps.Any(x => x.Kind == "SendPort" && x.Name == "SendStarted"), "started send port missing");
|
||||||
|
Assert(plan.Steps.Any(x => x.Kind == "Orchestration" && x.Name == "OrchStarted"), "started orchestration missing");
|
||||||
|
AssertEqual("AssemblyName", plan.Steps.Single(x => x.Kind == "Orchestration").QualifierProperty, "orchestration qualifier property");
|
||||||
|
var receive = plan.Steps.Single(x => x.Kind == "ReceiveLocation");
|
||||||
|
AssertEqual("ReceivePortName", receive.QualifierProperty, "receive qualifier property");
|
||||||
|
AssertEqual("RP1", receive.QualifierValue, "receive qualifier value");
|
||||||
|
Assert(!plan.Steps.Any(x => x.Name == "SendStopped" || x.Name == "SendBound" || x.Name == "ReceiveDisabled"), "inactive artifacts must remain untouched");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShutdownStopsOnlyPreviouslyActiveArtifacts()
|
||||||
|
{
|
||||||
|
var plan = new BizTalkOperationService(null).CreateShutdownPlan(FullSnapshot(), "BIZ01");
|
||||||
|
AssertEqual(4, plan.Steps.Count(x => x.Execute), "shutdown executable step count");
|
||||||
|
Assert(!plan.Steps.Any(x => x.Name == "SendStopped" || x.Name == "SendBound" || x.Name == "ReceiveDisabled"), "inactive artifacts must not be stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotComparisonUsesCompositeIdentity()
|
||||||
|
{
|
||||||
|
var before = EmptySnapshot("BIZ01");
|
||||||
|
var after = EmptySnapshot("BIZ01");
|
||||||
|
before.Applications.Add(App("AppA", ArtifactStates.SendPortStarted));
|
||||||
|
before.Applications.Add(App("AppB", ArtifactStates.SendPortStopped));
|
||||||
|
after.Applications.Add(App("AppA", ArtifactStates.SendPortStopped));
|
||||||
|
after.Applications.Add(App("AppB", ArtifactStates.SendPortStopped));
|
||||||
|
var diff = SnapshotComparer.Compare(before, after);
|
||||||
|
AssertEqual(1, diff.ArtifactDifferences.Count, "composite send port comparison");
|
||||||
|
AssertEqual("AppA", diff.ArtifactDifferences[0].Application, "changed application");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotServerValidationIsSafe()
|
||||||
|
{
|
||||||
|
BizTalkOperationService.ValidateSnapshot(EmptySnapshot("biz01.domain.local"), "BIZ01");
|
||||||
|
AssertThrows<InvalidDataException>(() => BizTalkOperationService.ValidateSnapshot(EmptySnapshot("BIZ02"), "BIZ01"), "foreign server must be rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void JsonStoreReplacesAtomically()
|
||||||
|
{
|
||||||
|
var directory = Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool.Tests." + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = Path.Combine(directory, "state.json");
|
||||||
|
JsonFileStore.Save(path, EmptySnapshot("BIZ01"));
|
||||||
|
JsonFileStore.Save(path, EmptySnapshot("BIZ02"));
|
||||||
|
var loaded = JsonFileStore.Load<BizTalkSnapshot>(path);
|
||||||
|
AssertEqual("BIZ02", loaded.Server, "atomic replacement payload");
|
||||||
|
AssertEqual(0, Directory.GetFiles(directory, "*.tmp.*").Length, "temporary files");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(directory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BizTalkSnapshot FullSnapshot()
|
||||||
|
{
|
||||||
|
var snapshot = EmptySnapshot("BIZ01");
|
||||||
|
var app = new ApplicationSnapshot { Application = "Orders" };
|
||||||
|
app.ReceiveLocations.Add(new ReceiveLocationState { Application = "Orders", Name = "ReceiveEnabled", ReceivePortName = "RP1", Enabled = true });
|
||||||
|
app.ReceiveLocations.Add(new ReceiveLocationState { Application = "Orders", Name = "ReceiveDisabled", ReceivePortName = "RP1", Enabled = false });
|
||||||
|
app.SendPorts.Add(new SendPortState { Application = "Orders", Name = "SendStarted", Status = ArtifactStates.SendPortStarted });
|
||||||
|
app.SendPorts.Add(new SendPortState { Application = "Orders", Name = "SendStopped", Status = ArtifactStates.SendPortStopped });
|
||||||
|
app.SendPorts.Add(new SendPortState { Application = "Orders", Name = "SendBound", Status = ArtifactStates.SendPortBound });
|
||||||
|
app.Orchestrations.Add(new OrchestrationState { Application = "Orders", Name = "OrchStarted", OrchestrationStatus = ArtifactStates.OrchestrationStarted, AssemblyName = "Orders.Processes" });
|
||||||
|
app.Orchestrations.Add(new OrchestrationState { Application = "Orders", Name = "OrchStopped", OrchestrationStatus = ArtifactStates.OrchestrationStopped });
|
||||||
|
snapshot.Applications.Add(app);
|
||||||
|
snapshot.HostInstances.Add(new HostInstanceState { InstanceName = "HostStarted", Server = "BIZ01", RawState = ArtifactStates.HostStarted });
|
||||||
|
snapshot.HostInstances.Add(new HostInstanceState { InstanceName = "HostStopped", Server = "BIZ01", RawState = ArtifactStates.HostStopped });
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BizTalkSnapshot EmptySnapshot(string server)
|
||||||
|
{
|
||||||
|
return new BizTalkSnapshot { Server = server, CreatedAt = DateTime.Now.ToString("s"), ToolVersion = BizTalkOperationService.Version };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApplicationSnapshot App(string name, int sendStatus)
|
||||||
|
{
|
||||||
|
var app = new ApplicationSnapshot { Application = name };
|
||||||
|
app.SendPorts.Add(new SendPortState { Application = name, Name = "SharedName", Status = sendStatus });
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Run(string name, Action test)
|
||||||
|
{
|
||||||
|
try { test(); Console.WriteLine("PASS: " + name); }
|
||||||
|
catch (Exception ex) { _failed++; Console.WriteLine("FAIL: " + name + " - " + ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Assert(bool condition, string message)
|
||||||
|
{
|
||||||
|
if (!condition) throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertEqual<T>(T expected, T actual, string label)
|
||||||
|
{
|
||||||
|
if (!object.Equals(expected, actual)) throw new InvalidOperationException(label + ": expected=" + expected + ", actual=" + actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertThrows<T>(Action action, string label) where T : Exception
|
||||||
|
{
|
||||||
|
try { action(); }
|
||||||
|
catch (T) { return; }
|
||||||
|
throw new InvalidOperationException(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user