Merge origin/main into main

This commit is contained in:
2026-08-04 10:09:45 +02:00
19 changed files with 809 additions and 380 deletions
+12
View File
@@ -6,6 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse.Tests", "tests\BizTalkCheckmkPulse.Tests\BizTalkCheckmkPulse.Tests.csproj", "{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse.Setup", "src\BizTalkCheckmkPulse.Setup\BizTalkCheckmkPulse.Setup.csproj", "{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse.Packager", "src\BizTalkCheckmkPulse.Packager\BizTalkCheckmkPulse.Packager.csproj", "{B35A04E0-253B-4CEB-9033-F5109F3B9F82}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -20,6 +24,14 @@ Global
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Release|Any CPU.Build.0 = Release|Any CPU
{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}.Release|Any CPU.Build.0 = Release|Any CPU
{B35A04E0-253B-4CEB-9033-F5109F3B9F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B35A04E0-253B-4CEB-9033-F5109F3B9F82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B35A04E0-253B-4CEB-9033-F5109F3B9F82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B35A04E0-253B-4CEB-9033-F5109F3B9F82}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+26 -40
View File
@@ -16,7 +16,7 @@ Verbindliche Randbedingungen:
- Der Agentpfad fuehrt niemals WMI- oder SQL-Abfragen aus.
- Fehler muessen als gueltige Checkmk-`UNKNOWN`-Services sichtbar werden.
- Schreiben und Lesen duerfen nie einen halben Snapshot exponieren.
- Laufzeitcode benoetigt keine PowerShell und keine BizTalk-DLL-Referenz.
- Installation und Laufzeit benoetigen keine PowerShell und keine BizTalk-DLL-Referenz.
- Installation, ACL und Scheduled Task muessen reproduzierbar sein.
## 2. Ausloeser der Architekturanderung
@@ -377,29 +377,30 @@ scripts\test-release.cmd
scripts\package-release.cmd
```
Der Release-Build der Visual-Studio-2019-Solution baut Anwendung, grafischen
Installer und Packaging-Werkzeug. Danach liegen der Paketordner
`artifacts\BizTalkCheckmkPulse-Setup` und das direkt transportierbare
`artifacts\BizTalkCheckmkPulse-Setup.zip` bereit.
### 8.2 Normales Servicekonto
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
```
Das ZIP wird auf dem Zielserver vollstaendig entpackt. Danach `Setup.exe` als
Administrator starten, Konto im Format `DOMAIN\Benutzer` (zum Beispiel
`BEW\t231bizmon`), Kennwort und Umgebung eintragen und die Installation
starten.
Der Installer muss als lokaler Administrator laufen. Er vergibt keine
AD-/BizTalk-/SQL-Rechte; diese bleiben getrennte administrative Freigaben.
Der installierte Task selbst laeuft mit `RunLevel Limited`. Der Installer fragt
das Kennwort mit `Get-Credential` ab; es wird nicht in Config oder Log
Der installierte Task selbst laeuft mit `RunLevel Limited`. Der Installer
uebergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle;
es wird weder in einer Prozesskommandozeile noch in Config oder Log
geschrieben. Das Konto benoetigt `Log on as a batch job`.
### 8.3 Optionales gMSA
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
Im Installer die gMSA-Checkbox aktivieren und ein auf `$` endendes Konto wie
`BEW\svc_biztalk_cmk$` eingeben. Ein Kennwort wird dann nicht abgefragt.
### 8.4 Checkmk
@@ -451,12 +452,12 @@ aelter als `LogRetentionDays`.
### 10.1 Grundpruefung
```powershell
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
Get-Item "$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
Get-Content "$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" -Tail 100
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```cmd
schtasks.exe /query /tn "BizTalk Checkmk Pulse Provider" /v /fo list
dir "%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
type "%ProgramData%\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log"
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
### 10.2 Kein Snapshot
@@ -615,32 +616,17 @@ Erst nach ACC-Abnahme erfolgt der gestufte Rollout nach DEV/TST/PRD.
## 13. Deinstallation und Rollback
```powershell
.\Uninstall-BizTalkCheckmkPulse.ps1
```
`-KeepRuntimeData` erhaelt Logs und Snapshot. Das Skript entfernt Task,
Wrapper und Programmdateien, aber keine AD-Gruppenmitgliedschaft. Die
Berechtigung wird separat nach Change-Verfahren entzogen.
`Setup.exe` aus dem entpackten Paket erneut starten und **Deinstallieren**
waehlen. Der Installer entfernt Task, Wrapper und Programmdateien, behaelt
Logs und Snapshot aber absichtlich bei. Die AD-Gruppenmitgliedschaft wird
nicht geaendert und separat nach Change-Verfahren entzogen.
Rollback auf die alte direkte `LocalSystem`-Architektur ist nicht empfohlen.
Falls kurzfristig notwendig, muss Security die Maschinenkonto-Berechtigung
explizit freigeben; der Checkmk-Agentdienst selbst darf nicht auf das
Collector-Konto umgestellt werden.
## 14. Transport per certutil
Die Source-Uebergabe besteht aus ZIP plus Base64-TXT:
```cmd
certutil -decode <datei>.zip.b64.txt <datei>.zip
certutil -hashfile <datei>.zip SHA256
tar -xf <datei>.zip
```
ZIP und Base64 werden erst nach dem Git-Commit aus genau diesem Commit erzeugt.
## 15. Referenzen
## 14. Referenzen
- https://learn.microsoft.com/en-us/biztalk/core/managing-biztalk-server-security
- https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
+43 -79
View File
@@ -164,22 +164,29 @@ scripts\test-release.cmd
scripts\package-release.cmd
```
Das Paket wird unter `artifacts\BizTalkCheckmkPulse-deploy` erzeugt:
Jeder Release-Build der Solution erzeugt automatisch den fertigen Paketordner
und das transportierbare ZIP:
```text
BizTalkCheckmkPulse-deploy\
Install-BizTalkCheckmkPulse.ps1
Uninstall-BizTalkCheckmkPulse.ps1
biztalk_checkmk_pulse.cmd
application\
BizTalkCheckmkPulse.exe
BizTalkCheckmkPulse.exe.config
artifacts\
BizTalkCheckmkPulse-Setup.zip
BizTalkCheckmkPulse-Setup\
Setup.exe
INSTALLATION.md
biztalk_checkmk_pulse.cmd
application\
BizTalkCheckmkPulse.exe
BizTalkCheckmkPulse.exe.config
```
Das Packaging-Projekt ist Teil von `BizTalkCheckmkPulse.sln`. Ein normaler
`Release`-Build in Visual Studio 2019 genuegt daher; ein separates
PowerShell-/WiX-/MSI-Tooling wird nicht benoetigt.
Format-Self-Test ohne WMI, SQL oder Event Log:
```cmd
artifacts\BizTalkCheckmkPulse-deploy\application\BizTalkCheckmkPulse.exe --self-test
artifacts\BizTalkCheckmkPulse-Setup\application\BizTalkCheckmkPulse.exe --self-test
```
Erwartet werden exakt neun `OK`-Zeilen. Die Regressionstests pruefen
@@ -191,16 +198,7 @@ Windows-/BizTalk-Laufzeitvalidierung.
## Berechtigung vorbereiten
Die exakte Read-Only-Gruppe wird in der BizTalk Administration Console unter
den Eigenschaften der BizTalk-Gruppe abgelesen. Ein bereits berechtigtes Konto
kann sie alternativ ermitteln:
```powershell
Get-CimInstance `
-Namespace root/MicrosoftBizTalkServer `
-ClassName MSBTS_GroupSetting |
Select-Object Name, BizTalkReadOnlyUserGroup, BizTalkOperatorGroup,
MgmtDbServerName, MgmtDbName
```
den Eigenschaften der BizTalk-Gruppe abgelesen.
Ein AD-Administrator nimmt das neue Provider-Konto in
`BizTalkReadOnlyUserGroup` auf. Nach AD-Replikation muss das Servicekonto durch
@@ -215,22 +213,23 @@ fuer das Provider-Konto.
## Installation mit normalem Servicekonto
```powershell
Set-Location C:\Temp\BizTalkCheckmkPulse-deploy
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
```
1. `BizTalkCheckmkPulse-Setup.zip` auf dem Zielserver vollstaendig entpacken.
2. `Setup.exe` starten und die UAC-Abfrage bestaetigen.
3. Collector-Konto, zum Beispiel `BEW\t231bizmon`, Kennwort und Umgebung
eingeben.
4. **Installieren / aktualisieren** waehlen.
Der Installer fragt das Kennwort ueber `Get-Credential` ab und speichert es
durch die Windows-Aufgabenplanung. Das Kennwort steht weder in der
Konfigurationsdatei noch in den Logs.
Der Installer uebergibt das Kennwort direkt ueber die Windows-Task-Scheduler-
COM-Schnittstelle. Es steht weder in einer Prozesskommandozeile noch in der
Konfigurationsdatei oder in Logs. Der Backslash in `DOMAIN\Benutzer` wird als
normaler Bestandteil des Kontonamens verarbeitet.
Wenn das Kennwort rotiert oder ablaeuft, muss es im Scheduled Task aktualisiert
werden. Bis dahin wird der Snapshot nach 180 Sekunden stale und Checkmk zeigt
alle neun Services als `UNKNOWN`.
werden. Dazu `Setup.exe` mit dem neuen Kennwort erneut ausfuehren. Bis dahin
wird der Snapshot nach 180 Sekunden stale und Checkmk zeigt alle acht Services
als `UNKNOWN`.
Ein gMSA kann weiterhin optional mit `-Gmsa` installiert werden; die
Ein gMSA kann ueber die Checkbox ohne Kennworteingabe installiert werden; die
produktive Standardbeschreibung geht vom normalen Servicekonto aus.
Der Installer:
@@ -245,48 +244,34 @@ Der Installer:
`IgnoreNew`, fuenf Minuten Laufzeitlimit und zwei Wiederholungen,
6. fuehrt den Self-Test aus und startet den Provider einmalig.
PowerShell wird nur fuer Installation und Betriebsdiagnose verwendet. Der
minuetliche Provider und der Checkmk-Consumer sind .NET-/CMD-Laufzeitcode und
haengen nicht von der PowerShell Execution Policy ab.
Installation, Update, Deinstallation und Laufzeit verwenden keine PowerShell.
## Verifikation auf dem Server
Task und letzter Lauf:
```powershell
Get-ScheduledTask -TaskName 'BizTalk Checkmk Pulse Provider' |
Select-Object TaskName, State
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider' |
Select-Object LastRunTime, LastTaskResult, NextRunTime
```cmd
schtasks.exe /query /tn "BizTalk Checkmk Pulse Provider" /v /fo list
```
Provider-Log:
```powershell
Get-ChildItem "$env:ProgramData\BizTalkCheckmkPulse\logs" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 3 Name, Length, LastWriteTime
Get-Content `
"$env:ProgramData\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log" `
-Tail 100
```cmd
dir /o-d "%ProgramData%\BizTalkCheckmkPulse\logs"
type "%ProgramData%\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log"
```
Snapshot und Consumer:
```powershell
Get-Item `
"$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" |
Select-Object FullName, Length, LastWriteTimeUtc
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```cmd
dir "%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```
Verbindlicher Test im echten `LocalSystem`-Kontext:
```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String -Pattern "BizTalk|UNKNOWN|Snapshot|Permission" -Context 0,1
```cmd
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
Danach in Checkmk eine Service Discovery ausfuehren, die neun Services
@@ -386,33 +371,12 @@ Ausfuehrliche Betriebs- und Architekturdetails stehen in
## Deinstallation
```powershell
.\Uninstall-BizTalkCheckmkPulse.ps1
```
Das Skript fragt vor dem Entfernen. Mit `-KeepRuntimeData` bleiben Snapshot und
Logs erhalten:
```powershell
.\Uninstall-BizTalkCheckmkPulse.ps1 -KeepRuntimeData
```
`Setup.exe` erneut starten und **Deinstallieren** waehlen. Snapshot und Logs
bleiben dabei unter `%ProgramData%\BizTalkCheckmkPulse` erhalten.
Die AD-Gruppenmitgliedschaft des Provider-Kontos wird bewusst nicht automatisch
geaendert und muss separat durch AD-/BizTalk-Administration entfernt werden.
## Certutil-Transport
Zu jeder Uebergabe wird ein Source-ZIP und eine certutil-kompatible
Base64-Textdatei erzeugt. Auf Windows:
```cmd
certutil -decode biztalk-checkmk-pulse-source-<datum>-<commit>.zip.b64.txt biztalk-checkmk-pulse-source.zip
certutil -hashfile biztalk-checkmk-pulse-source.zip SHA256
tar -xf biztalk-checkmk-pulse-source.zip
```
Die konkrete Datei und SHA-256-Summe werden bei der Uebergabe genannt.
## Quellen
- Microsoft: BizTalk `MSBTS_GroupSetting.BizTalkReadOnlyUserGroup`
+21
View File
@@ -0,0 +1,21 @@
# Installation auf dem BizTalk-Server
1. `BizTalkCheckmkPulse-Setup.zip` vollstaendig in ein lokales Verzeichnis entpacken.
2. `Setup.exe` als lokaler Administrator starten und die UAC-Abfrage bestaetigen.
3. Das Collector-Konto im Format `DOMAIN\Benutzer` eingeben, zum Beispiel
`BEW\t231bizmon`.
4. Kennwort und Umgebung eingeben und **Installieren / aktualisieren** waehlen.
5. Im Aufgabenplaner den Task `BizTalk Checkmk Pulse Provider` und danach den
Checkmk-Agent-Dump kontrollieren.
PowerShell wird fuer Installation, Update, Deinstallation und Laufzeit nicht
benoetigt. Das Kennwort wird direkt an die Windows-Aufgabenplanung uebergeben
und weder in einer Datei noch in einer Prozesskommandozeile abgelegt.
Der Installer vergibt keine AD-, BizTalk- oder SQL-Berechtigungen. Das Konto
muss separat Mitglied der fuer die BizTalk-Gruppe konfigurierten Read-Only-
Gruppe sein und lokal das Recht `Log on as a batch job` besitzen.
Bei einer Deinstallation bleiben Snapshot und Logs absichtlich unter
`%ProgramData%\BizTalkCheckmkPulse` erhalten. Sie koennen nach der
Betriebsfreigabe manuell entfernt werden.
-171
View File
@@ -1,171 +0,0 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$CollectorAccount,
[switch]$Gmsa,
[ValidateSet("", "ACC", "DEV", "TST", "PRD")]
[string]$EnvironmentName = "",
[string]$InstallDirectory = "$env:ProgramFiles\BizTalkCheckmkPulse",
[string]$CheckmkLocalDirectory = "$env:ProgramData\checkmk\agent\local"
)
$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0
$taskName = "BizTalk Checkmk Pulse Provider"
$runtimeRoot = Join-Path $env:ProgramData "BizTalkCheckmkPulse"
$dataDirectory = Join-Path $runtimeRoot "data"
$logDirectory = Join-Path $runtimeRoot "logs"
$sourceApplication = Join-Path $PSScriptRoot "application"
$sourceWrapper = Join-Path $PSScriptRoot "biztalk_checkmk_pulse.cmd"
$targetExe = Join-Path $InstallDirectory "BizTalkCheckmkPulse.exe"
$targetConfig = "$targetExe.config"
$targetWrapper = Join-Path $CheckmkLocalDirectory "biztalk_checkmk_pulse.cmd"
function Assert-Administrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Die Installation muss in einer administrativen Windows PowerShell ausgefuehrt werden."
}
}
function Invoke-Icacls {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string[]]$Arguments
)
& icacls.exe $Path @Arguments | Out-Host
if ($LASTEXITCODE -ne 0) {
throw "icacls ist fuer '$Path' mit Exitcode $LASTEXITCODE fehlgeschlagen."
}
}
Assert-Administrator
if (-not (Test-Path -LiteralPath $sourceApplication -PathType Container)) {
throw "Quellverzeichnis fehlt: $sourceApplication"
}
if (-not (Test-Path -LiteralPath $sourceWrapper -PathType Leaf)) {
throw "Checkmk-Wrapper fehlt: $sourceWrapper"
}
if ($Gmsa -and -not $CollectorAccount.EndsWith('$')) {
throw "Ein gMSA-Kontoname muss mit '$' enden, z.B. DOMAIN\svc_biztalk_cmk$."
}
Write-Host "Installiere Programmdateien nach $InstallDirectory ..."
New-Item -ItemType Directory -Path $InstallDirectory -Force | Out-Null
Copy-Item -Path (Join-Path $sourceApplication "*") -Destination $InstallDirectory -Force
[xml]$configuration = Get-Content -LiteralPath $targetConfig
$environmentSetting = $configuration.configuration.appSettings.add |
Where-Object { $_.key -eq "EnvironmentName" } |
Select-Object -First 1
if ($null -eq $environmentSetting) {
throw "EnvironmentName fehlt in $targetConfig."
}
$environmentSetting.value = $EnvironmentName
$configuration.Save($targetConfig)
Write-Host "Erzeuge Runtime-Verzeichnisse und Least-Privilege-ACLs ..."
New-Item -ItemType Directory -Path $runtimeRoot, $dataDirectory, $logDirectory -Force | Out-Null
# Runtime-Root: Provider lesen, SYSTEM lesen, lokale Administratoren verwalten.
Invoke-Icacls -Path $runtimeRoot -Arguments @(
"/inheritance:r",
"/grant:r",
"*S-1-5-18:(OI)(CI)(RX)",
"*S-1-5-32-544:(OI)(CI)(F)",
"${CollectorAccount}:(OI)(CI)(RX)"
)
# Daten: ausschliesslich Provider schreibt, LocalSystem liest den Snapshot.
Invoke-Icacls -Path $dataDirectory -Arguments @(
"/inheritance:r",
"/grant:r",
"*S-1-5-18:(OI)(CI)(RX)",
"*S-1-5-32-544:(OI)(CI)(F)",
"${CollectorAccount}:(OI)(CI)(M)"
)
# Logs: Provider und LocalSystem-Consumer duerfen getrennte Diagnosezeilen anhaengen.
Invoke-Icacls -Path $logDirectory -Arguments @(
"/inheritance:r",
"/grant:r",
"*S-1-5-18:(OI)(CI)(M)",
"*S-1-5-32-544:(OI)(CI)(F)",
"${CollectorAccount}:(OI)(CI)(M)"
)
Write-Host "Installiere Checkmk Local Check nach $CheckmkLocalDirectory ..."
New-Item -ItemType Directory -Path $CheckmkLocalDirectory -Force | Out-Null
Copy-Item -LiteralPath $sourceWrapper -Destination $targetWrapper -Force
Write-Host "Registriere Scheduled Task '$taskName' ..."
$action = New-ScheduledTaskAction `
-Execute $targetExe `
-Argument "--collect" `
-WorkingDirectory $InstallDirectory
$trigger = New-ScheduledTaskTrigger `
-Once `
-At (Get-Date).AddMinutes(1) `
-RepetitionInterval (New-TimeSpan -Minutes 1)
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
-StartWhenAvailable `
-RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 1)
if ($Gmsa) {
$principal = New-ScheduledTaskPrincipal `
-UserId $CollectorAccount `
-LogonType Password `
-RunLevel Limited
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
else {
$credential = Get-Credential `
-UserName $CollectorAccount `
-Message "Kennwort fuer das dedizierte BizTalk-Monitoringkonto eingeben"
if ($credential.UserName -ne $CollectorAccount) {
throw "Das eingegebene Konto stimmt nicht mit CollectorAccount ueberein."
}
$principal = New-ScheduledTaskPrincipal `
-UserId $CollectorAccount `
-LogonType Password `
-RunLevel Limited
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask `
-TaskName $taskName `
-InputObject $task `
-User $CollectorAccount `
-Password $credential.GetNetworkCredential().Password `
-Force | Out-Null
}
Write-Host "Fuehre formatseitigen Self-Test aus ..."
$selfTest = & $targetExe --self-test
if ($LASTEXITCODE -ne 0 -or @($selfTest).Count -ne 9) {
throw "Self-Test fehlgeschlagen. Erwartet wurden neun Checkmk-Zeilen."
}
Write-Host "Starte den Provider einmalig ..."
Start-ScheduledTask -TaskName $taskName
Write-Host ""
Write-Host "Installation abgeschlossen."
Write-Host "Naechste Pruefungen:"
Write-Host " Get-ScheduledTaskInfo -TaskName '$taskName'"
Write-Host " Get-Content '$logDirectory\biztalk-checkmk-pulse-*.log' -Tail 50"
Write-Host " & '$targetExe' --consume"
Write-Host " & 'C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe' dump"
@@ -1,30 +0,0 @@
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
param(
[string]$InstallDirectory = "$env:ProgramFiles\BizTalkCheckmkPulse",
[string]$CheckmkLocalDirectory = "$env:ProgramData\checkmk\agent\local",
[switch]$KeepRuntimeData
)
$ErrorActionPreference = "Stop"
$taskName = "BizTalk Checkmk Pulse Provider"
$runtimeRoot = Join-Path $env:ProgramData "BizTalkCheckmkPulse"
$wrapper = Join-Path $CheckmkLocalDirectory "biztalk_checkmk_pulse.cmd"
if ($PSCmdlet.ShouldProcess($taskName, "Scheduled Task entfernen")) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
}
if (Test-Path -LiteralPath $wrapper) {
if ($PSCmdlet.ShouldProcess($wrapper, "Checkmk Local Check entfernen")) {
Remove-Item -LiteralPath $wrapper -Force
}
}
if (Test-Path -LiteralPath $InstallDirectory) {
if ($PSCmdlet.ShouldProcess($InstallDirectory, "Programmdateien entfernen")) {
Remove-Item -LiteralPath $InstallDirectory -Recurse -Force
}
}
if (-not $KeepRuntimeData -and (Test-Path -LiteralPath $runtimeRoot)) {
if ($PSCmdlet.ShouldProcess($runtimeRoot, "Snapshot und Logs entfernen")) {
Remove-Item -LiteralPath $runtimeRoot -Recurse -Force
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ set "BASE=%ProgramFiles%\BizTalkCheckmkPulse"
set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
if not exist "%EXE%" (
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Install-BizTalkCheckmkPulse.ps1 erneut als Administrator ausfuehren und danach cmk-agent-ctl.exe dump erneut ausfuehren.
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Setup.exe erneut als Administrator ausfuehren und danach cmk-agent-ctl.exe dump erneut ausfuehren.
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
+13 -27
View File
@@ -37,15 +37,10 @@ Bevorzugte Rolle fuer BizTalk Server 2020:
BizTalk Server Read Only Users
SQL-Datenbankrolle: BTS_READONLY_USERS
Die exakt konfigurierte Gruppe mit einem bereits berechtigten Konto ermitteln:
Get-CimInstance -Namespace root/MicrosoftBizTalkServer `
-ClassName MSBTS_GroupSetting |
Select-Object Name, BizTalkReadOnlyUserGroup, BizTalkOperatorGroup,
MgmtDbServerName, MgmtDbName
Das Provider-Konto durch AD-Administration in BizTalkReadOnlyUserGroup
aufnehmen. Nicht vom Standardnamen ausgehen.
Die exakt konfigurierte Gruppe in den Eigenschaften der BizTalk-Gruppe in der
BizTalk Administration Console ablesen. Das Provider-Konto durch
AD-Administration in BizTalkReadOnlyUserGroup aufnehmen. Nicht vom
Standardnamen ausgehen.
Die Operator-Gruppe ist nur eine begruendungspflichtige Eskalation, falls eine
konkret benoetigte WMI-Klasse trotz bestaetigter Read-Only-Zuordnung und neuem
@@ -64,28 +59,19 @@ NICHT DURCHFUEHREN
INSTALLATION
============
Normales Servicekonto:
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
BizTalkCheckmkPulse-Setup.zip vollstaendig entpacken und Setup.exe als
Administrator starten. Konto im Format DOMAIN\Benutzer (zum Beispiel
BEW\t231bizmon), Kennwort und Umgebung eingeben. PowerShell wird nicht
benoetigt.
VERIFIKATION
============
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
Get-Content `
"$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" `
-Tail 100
Get-Item `
"$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String -Pattern "BizTalk|UNKNOWN|Permission|snapshot" -Context 0,1
schtasks.exe /query /tn "BizTalk Checkmk Pulse Provider" /v /fo list
dir "%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
type "%ProgramData%\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log"
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
ERWARTET
========
+13 -17
View File
@@ -15,13 +15,10 @@ Provider-Konto und publiziert einen validierbaren Snapshot.
## Installation
Normales Servicekonto:
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
```
`BizTalkCheckmkPulse-Setup.zip` vollstaendig entpacken, `Setup.exe` als
Administrator starten und Konto, Kennwort sowie Umgebung eingeben. Konten wie
`BEW\t231bizmon` werden direkt im Windows-Format `DOMAIN\Benutzer` verarbeitet.
Der Installer benoetigt keine PowerShell.
Der Installer legt den Local Check hier ab:
@@ -44,15 +41,14 @@ Verzoegerung zum minuetlichen Provider.
Aktive Agentkonfiguration pruefen:
```powershell
& "C:\Program Files (x86)\checkmk\service\check_mk_agent.exe" showconfig local
```cmd
"C:\Program Files (x86)\checkmk\service\check_mk_agent.exe" showconfig local
```
Verbindlicher Agent-Dump:
```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String -Pattern "BizTalk|UNKNOWN|snapshot|Permission" -Context 0,1
```cmd
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
## Service Discovery
@@ -95,11 +91,11 @@ Installationsautomation die klarere Variante.
## Healthchecks
```powershell
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
Get-Item "$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
Get-Content "$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" -Tail 50
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```cmd
schtasks.exe /query /tn "BizTalk Checkmk Pulse Provider" /v /fo list
dir "%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
type "%ProgramData%\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log"
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```
Soll:
+1 -1
View File
@@ -7,5 +7,5 @@ if errorlevel 1 (
exit /b 1
)
msbuild BizTalkCheckmkPulse.sln /p:Configuration=Release /p:Platform="Any CPU" /m:1 /v:minimal
msbuild "%~dp0..\BizTalkCheckmkPulse.sln" /p:Configuration=Release /p:Platform="Any CPU" /m:1 /v:minimal
exit /b %ERRORLEVEL%
+2 -14
View File
@@ -4,18 +4,6 @@ setlocal
call "%~dp0build-release.cmd"
if errorlevel 1 exit /b 1
set "ROOT=%~dp0.."
set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy"
set "APP=%OUT%\application"
if exist "%OUT%" rmdir /s /q "%OUT%"
mkdir "%APP%"
copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul
copy "%ROOT%\deployment\Install-BizTalkCheckmkPulse.ps1" "%OUT%\" >nul
copy "%ROOT%\deployment\Uninstall-BizTalkCheckmkPulse.ps1" "%OUT%\" >nul
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul
echo Deployment package written to %OUT%
echo Install package written to artifacts\BizTalkCheckmkPulse-Setup
echo Install ZIP written to artifacts\BizTalkCheckmkPulse-Setup.zip
exit /b 0
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" Condition="false" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B35A04E0-253B-4CEB-9033-F5109F3B9F82}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>BizTalkCheckmkPulse.Packager</RootNamespace>
<AssemblyName>BizTalkCheckmkPulse.Packager</AssemblyName>
<TargetFrameworkVersion>v4.7.2</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" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj">
<Project>{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}</Project>
<Name>BizTalkCheckmkPulse</Name>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="..\BizTalkCheckmkPulse.Setup\BizTalkCheckmkPulse.Setup.csproj">
<Project>{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}</Project>
<Name>BizTalkCheckmkPulse.Setup</Name>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="CreateInstallPackage" AfterTargets="Build" Condition=" '$(Configuration)' == 'Release' ">
<Exec Command="&quot;$(TargetPath)&quot; &quot;$(MSBuildProjectDirectory)\..\..&quot; &quot;$(Configuration)&quot;" />
</Target>
</Project>
@@ -0,0 +1,59 @@
using System;
using System.IO;
using System.IO.Compression;
namespace BizTalkCheckmkPulse.Packager
{
internal static class Program
{
private static int Main(string[] args)
{
try
{
if (args.Length != 2) throw new ArgumentException("Usage: BizTalkCheckmkPulse.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, "BizTalkCheckmkPulse-Setup");
var application = Path.Combine(package, "application");
var zip = Path.Combine(artifacts, "BizTalkCheckmkPulse-Setup.zip");
if (Directory.Exists(package)) Directory.Delete(package, true);
Directory.CreateDirectory(application);
Copy(
Path.Combine(root, "src", "BizTalkCheckmkPulse.Setup", "bin", configuration, "BizTalkCheckmkPulse.Setup.exe"),
Path.Combine(package, "Setup.exe"));
Copy(
Path.Combine(root, "src", "BizTalkCheckmkPulse", "bin", configuration, "BizTalkCheckmkPulse.exe"),
Path.Combine(application, "BizTalkCheckmkPulse.exe"));
Copy(
Path.Combine(root, "src", "BizTalkCheckmkPulse", "bin", configuration, "BizTalkCheckmkPulse.exe.config"),
Path.Combine(application, "BizTalkCheckmkPulse.exe.config"));
Copy(
Path.Combine(root, "deployment", "checkmk", "biztalk_checkmk_pulse.cmd"),
Path.Combine(package, "biztalk_checkmk_pulse.cmd"));
Copy(
Path.Combine(root, "deployment", "INSTALLATION.md"),
Path.Combine(package, "INSTALLATION.md"));
if (File.Exists(zip)) File.Delete(zip);
ZipFile.CreateFromDirectory(package, zip, CompressionLevel.Optimal, false);
Console.WriteLine("Install package: " + package);
Console.WriteLine("Install ZIP: " + zip);
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine("Packaging failed: " + ex.Message);
return 1;
}
}
private static void Copy(string source, string target)
{
if (!File.Exists(source)) throw new FileNotFoundException("Required package file missing: " + source, source);
File.Copy(source, target, true);
}
}
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" Condition="false" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>BizTalkCheckmkPulse.Setup</RootNamespace>
<AssemblyName>BizTalkCheckmkPulse.Setup</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<ApplicationManifest>app.manifest</ApplicationManifest>
</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="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Security" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="InstallerEngine.cs" />
<Compile Include="MainForm.cs" />
<Compile Include="Program.cs" />
<Compile Include="TaskSchedulerService.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.manifest" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,176 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Xml;
namespace BizTalkCheckmkPulse.Setup
{
internal sealed class InstallerEngine
{
internal const string TaskName = "BizTalk Checkmk Pulse Provider";
private readonly string packageDirectory;
private readonly string installDirectory;
private readonly string runtimeDirectory;
private readonly string checkmkLocalDirectory;
public InstallerEngine(string packageDirectory)
{
this.packageDirectory = Path.GetFullPath(packageDirectory);
installDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "BizTalkCheckmkPulse");
runtimeDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "BizTalkCheckmkPulse");
checkmkLocalDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "checkmk", "agent", "local");
}
public void Install(string account, string password, bool isGmsa, string environmentName, Action<string> report)
{
Validate(account, password, isGmsa, environmentName);
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
var sourceApplication = Path.Combine(packageDirectory, "application");
var sourceExe = Path.Combine(sourceApplication, "BizTalkCheckmkPulse.exe");
var sourceConfig = sourceExe + ".config";
var sourceWrapper = Path.Combine(packageDirectory, "biztalk_checkmk_pulse.cmd");
RequireFile(sourceExe);
RequireFile(sourceConfig);
RequireFile(sourceWrapper);
// Ein laufender Provider kann die installierte EXE waehrend eines Updates sperren.
new TaskSchedulerService().DeleteIfExists(TaskName);
report("Vorhandener Scheduled Task angehalten beziehungsweise fuer das Update entfernt.");
Directory.CreateDirectory(installDirectory);
var targetExe = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe");
var targetConfig = targetExe + ".config";
File.Copy(sourceExe, targetExe, true);
File.Copy(sourceConfig, targetConfig, true);
SetEnvironment(targetConfig, environmentName);
report("Programmdateien installiert: " + installDirectory);
var dataDirectory = Path.Combine(runtimeDirectory, "data");
var logDirectory = Path.Combine(runtimeDirectory, "logs");
Directory.CreateDirectory(runtimeDirectory);
Directory.CreateDirectory(dataDirectory);
Directory.CreateDirectory(logDirectory);
ApplyDirectoryAcl(installDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
ApplyDirectoryAcl(runtimeDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
ApplyDirectoryAcl(dataDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.ReadAndExecute);
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
report("Least-Privilege-Verzeichnisrechte gesetzt.");
Directory.CreateDirectory(checkmkLocalDirectory);
File.Copy(sourceWrapper, Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd"), true);
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
RunSelfTest(targetExe);
report("Self-Test erfolgreich: acht Checkmk-Services.");
new TaskSchedulerService().RegisterAndStart(
TaskName,
targetExe,
installDirectory,
account,
isGmsa ? null : password,
isGmsa);
report("Scheduled Task registriert: " + TaskName);
}
public void Uninstall(bool keepRuntimeData, Action<string> report)
{
new TaskSchedulerService().DeleteIfExists(TaskName);
report("Scheduled Task entfernt.");
var wrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
if (File.Exists(wrapper)) File.Delete(wrapper);
if (Directory.Exists(installDirectory)) Directory.Delete(installDirectory, true);
if (!keepRuntimeData && Directory.Exists(runtimeDirectory)) Directory.Delete(runtimeDirectory, true);
}
private static void Validate(string account, string password, bool isGmsa, string environmentName)
{
if (string.IsNullOrWhiteSpace(account)) throw new ArgumentException("Collector-Konto fehlt.");
if (account.IndexOf('\\') <= 0 || account.EndsWith("\\", StringComparison.Ordinal))
throw new ArgumentException(@"Collector-Konto im Format DOMAIN\Benutzer eingeben, z.B. BEW\t231bizmon.");
if (isGmsa && !account.EndsWith("$", StringComparison.Ordinal))
throw new ArgumentException(@"Ein gMSA-Konto muss mit '$' enden, z.B. BEW\svc_biztalk_cmk$.");
if (!isGmsa && string.IsNullOrEmpty(password)) throw new ArgumentException("Kennwort fehlt.");
if (!new[] { "", "ACC", "DEV", "TST", "PRD" }.Contains(environmentName))
throw new ArgumentException("Ungueltige Umgebung.");
}
private static void RequireFile(string path)
{
if (!File.Exists(path))
throw new FileNotFoundException("Installationspaket ist unvollstaendig. Datei fehlt: " + path, path);
}
private static void SetEnvironment(string configPath, string environmentName)
{
var document = new XmlDocument { PreserveWhitespace = true };
document.Load(configPath);
var setting = document.SelectSingleNode("/configuration/appSettings/add[@key='EnvironmentName']") as XmlElement;
if (setting == null) throw new InvalidDataException("EnvironmentName fehlt in " + configPath + ".");
setting.SetAttribute("value", environmentName);
document.Save(configPath);
}
private static void ApplyDirectoryAcl(
string path,
SecurityIdentifier collectorSid,
FileSystemRights collectorRights,
FileSystemRights systemRights)
{
var inheritance = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
var security = new DirectorySecurity();
security.SetAccessRuleProtection(true, false);
security.AddAccessRule(new FileSystemAccessRule(
new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null),
FileSystemRights.FullControl,
inheritance,
PropagationFlags.None,
AccessControlType.Allow));
security.AddAccessRule(new FileSystemAccessRule(
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null),
systemRights,
inheritance,
PropagationFlags.None,
AccessControlType.Allow));
security.AddAccessRule(new FileSystemAccessRule(
collectorSid,
collectorRights,
inheritance,
PropagationFlags.None,
AccessControlType.Allow));
new DirectoryInfo(path).SetAccessControl(security);
}
private static void RunSelfTest(string executable)
{
var start = new ProcessStartInfo(executable, "--self-test")
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var process = Process.Start(start))
{
if (process == null) throw new InvalidOperationException("Self-Test konnte nicht gestartet werden.");
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
if (!process.WaitForExit(30000))
{
process.Kill();
throw new InvalidOperationException("Self-Test hat das Zeitlimit ueberschritten.");
}
var lines = output.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
if (process.ExitCode != 0 || lines.Length != 8 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
throw new InvalidOperationException("Self-Test fehlgeschlagen. Exitcode=" + process.ExitCode + ", Zeilen=" + lines.Length + ". " + error);
}
}
}
}
+181
View File
@@ -0,0 +1,181 @@
using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BizTalkCheckmkPulse.Setup
{
internal sealed class MainForm : Form
{
private readonly InstallerEngine engine;
private readonly TextBox account = new TextBox();
private readonly TextBox password = new TextBox();
private readonly ComboBox environment = new ComboBox();
private readonly CheckBox gmsa = new CheckBox();
private readonly Button install = new Button();
private readonly Button uninstall = new Button();
private readonly TextBox status = new TextBox();
public MainForm(InstallerEngine engine)
{
this.engine = engine;
Text = "BizTalk Checkmk Pulse Setup";
ClientSize = new Size(650, 485);
MinimumSize = new Size(666, 524);
StartPosition = FormStartPosition.CenterScreen;
Font = new Font("Segoe UI", 9F);
var title = new Label
{
AutoSize = true,
Font = new Font(Font.FontFamily, 15F, FontStyle.Bold),
Location = new Point(20, 18),
Text = "BizTalk Checkmk Pulse installieren"
};
var description = new Label
{
AutoSize = false,
Location = new Point(22, 58),
Size = new Size(605, 48),
Text = "Installiert Anwendung, Checkmk Local Check, sichere Verzeichnisrechte und den minuetlichen Scheduled Task. PowerShell wird nicht verwendet."
};
AddLabel("Collector-Konto", 22, 120);
account.Location = new Point(190, 116);
account.Size = new Size(420, 23);
account.Text = @"BEW\t231bizmon";
AddLabel("Kennwort", 22, 158);
password.Location = new Point(190, 154);
password.Size = new Size(420, 23);
password.UseSystemPasswordChar = true;
AddLabel("Umgebung", 22, 196);
environment.Location = new Point(190, 192);
environment.Size = new Size(160, 23);
environment.DropDownStyle = ComboBoxStyle.DropDownList;
environment.Items.AddRange(new object[] { "(keine)", "ACC", "DEV", "TST", "PRD" });
environment.SelectedIndex = 0;
gmsa.Location = new Point(190, 229);
gmsa.Size = new Size(420, 24);
gmsa.Text = "Konto ist ein gMSA (kein Kennwort erforderlich)";
gmsa.CheckedChanged += delegate
{
password.Enabled = !gmsa.Checked;
if (gmsa.Checked) password.Clear();
};
install.Location = new Point(190, 267);
install.Size = new Size(200, 34);
install.Text = "Installieren / aktualisieren";
install.Click += async delegate { await InstallAsync(); };
uninstall.Location = new Point(410, 267);
uninstall.Size = new Size(200, 34);
uninstall.Text = "Deinstallieren";
uninstall.Click += async delegate { await UninstallAsync(); };
status.Location = new Point(22, 320);
status.Size = new Size(588, 135);
status.Multiline = true;
status.ReadOnly = true;
status.ScrollBars = ScrollBars.Vertical;
status.Text = "Bereit. Setup.exe muss aus dem vollstaendig entpackten Installationspaket gestartet werden.";
Controls.AddRange(new Control[]
{
title, description, account, password, environment, gmsa, install, uninstall, status
});
AcceptButton = install;
}
private void AddLabel(string text, int left, int top)
{
Controls.Add(new Label { AutoSize = true, Location = new Point(left, top), Text = text });
}
private async Task InstallAsync()
{
var collectorAccount = account.Text.Trim();
var collectorPassword = password.Text;
var environmentName = environment.SelectedIndex <= 0 ? string.Empty : environment.SelectedItem.ToString();
var collectorIsGmsa = gmsa.Checked;
SetBusy(true, "Installation laeuft ...");
try
{
await Task.Run(() => engine.Install(
collectorAccount,
collectorPassword,
collectorIsGmsa,
environmentName,
Report));
password.Clear();
Report("Installation abgeschlossen. Der Provider-Task wurde einmalig gestartet.");
MessageBox.Show(this, "Installation erfolgreich abgeschlossen.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
password.Clear();
Report("FEHLER: " + ex.Message);
MessageBox.Show(this, ex.Message, "Installation fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
SetBusy(false, null);
}
}
private async Task UninstallAsync()
{
var result = MessageBox.Show(
this,
"Scheduled Task, Checkmk-Wrapper und Programmdateien entfernen? Snapshot und Logs bleiben erhalten.",
"Deinstallation bestaetigen",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (result != DialogResult.Yes) return;
SetBusy(true, "Deinstallation laeuft ...");
try
{
await Task.Run(() => engine.Uninstall(true, Report));
Report("Deinstallation abgeschlossen. Runtime-Daten wurden beibehalten.");
MessageBox.Show(this, "Deinstallation erfolgreich 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, null);
}
}
private void SetBusy(bool busy, string message)
{
install.Enabled = !busy;
uninstall.Enabled = !busy;
account.Enabled = !busy;
environment.Enabled = !busy;
gmsa.Enabled = !busy;
password.Enabled = !busy && !gmsa.Checked;
UseWaitCursor = busy;
if (message != null) Report(message);
}
private void Report(string message)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(Report), message);
return;
}
status.AppendText(Environment.NewLine + DateTime.Now.ToString("HH:mm:ss") + " " + message);
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using System;
using System.Windows.Forms;
namespace BizTalkCheckmkPulse.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,123 @@
using System;
using System.Runtime.InteropServices;
namespace BizTalkCheckmkPulse.Setup
{
internal sealed class TaskSchedulerService
{
private const int TaskCreateOrUpdate = 6;
private const int TaskLogonPassword = 1;
private const int TaskTriggerTime = 1;
private const int TaskActionExecute = 0;
private const int TaskInstancesIgnoreNew = 2;
public void RegisterAndStart(
string taskName,
string executable,
string workingDirectory,
string account,
string password,
bool isGmsa)
{
dynamic service = null;
dynamic root = null;
dynamic definition = null;
dynamic registeredTask = null;
try
{
var schedulerType = Type.GetTypeFromProgID("Schedule.Service", true);
service = Activator.CreateInstance(schedulerType);
service.Connect();
root = service.GetFolder("\\");
definition = service.NewTask(0);
definition.RegistrationInfo.Description = "Collects BizTalk monitoring data for the Checkmk local check.";
definition.Principal.UserId = account;
// Auch gMSA werden als Password-Logon registriert. Nur das Kennwort
// bleibt leer; TASK_LOGON_SERVICE_ACCOUNT ist ausschliesslich fuer
// LocalSystem, LocalService und NetworkService vorgesehen.
definition.Principal.LogonType = TaskLogonPassword;
definition.Principal.RunLevel = 0;
definition.Settings.Enabled = true;
definition.Settings.StartWhenAvailable = true;
definition.Settings.MultipleInstances = TaskInstancesIgnoreNew;
definition.Settings.ExecutionTimeLimit = "PT5M";
definition.Settings.RestartCount = 2;
definition.Settings.RestartInterval = "PT1M";
definition.Settings.DisallowStartIfOnBatteries = false;
definition.Settings.StopIfGoingOnBatteries = false;
dynamic trigger = definition.Triggers.Create(TaskTriggerTime);
trigger.StartBoundary = DateTime.Now.AddMinutes(1).ToString("s");
trigger.Enabled = true;
trigger.Repetition.Interval = "PT1M";
dynamic action = definition.Actions.Create(TaskActionExecute);
action.Path = executable;
action.Arguments = "--collect";
action.WorkingDirectory = workingDirectory;
registeredTask = root.RegisterTaskDefinition(
taskName,
definition,
TaskCreateOrUpdate,
account,
isGmsa ? null : password,
TaskLogonPassword,
null);
registeredTask.Run(null);
}
finally
{
Release(registeredTask);
Release(definition);
Release(root);
Release(service);
}
}
public void DeleteIfExists(string taskName)
{
dynamic service = null;
dynamic root = null;
dynamic task = null;
try
{
var schedulerType = Type.GetTypeFromProgID("Schedule.Service", true);
service = Activator.CreateInstance(schedulerType);
service.Connect();
root = service.GetFolder("\\");
try
{
task = root.GetTask(taskName);
try
{
task.Stop(0);
}
catch (COMException)
{
// Ein nicht laufender Task muss vor dem Loeschen nicht gestoppt werden.
}
root.DeleteTask(taskName, 0);
}
catch (COMException ex)
{
const int FileNotFound = unchecked((int)0x80070002);
if (ex.ErrorCode != FileNotFound) throw;
}
}
finally
{
Release(task);
Release(root);
Release(service);
}
}
private static void Release(object value)
{
if (value != null && Marshal.IsComObject(value)) Marshal.FinalReleaseComObject(value);
}
}
}
@@ -0,0 +1,16 @@
<?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="BizTalkCheckmkPulse.Setup" />
<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="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>