By Benedikt Gunzelmann on Wednesday, 15 November 2017
Category: PowerShell

Informationen von eigenen ODS Wartungsplänen per PowerShell auslesen

Seit geraumer Zeit besteht in DSM die Möglichkeit, eine ODS Variable vom Typ "Zeitplan" zu erstellen. Da diese Variable im eScript über die "If"-Anweisung "IsInTimeframe" geprüft werden kann, ergeben sich hieraus gerade für Installationen viele neue Möglichkeiten. Somit wäre es denkbar einen Softwarerollout in mehrere "dynamische Phasen" (führe Aktion 1 aus wenn innerhalb Wartungszeitfenster und Aktion 2 wenn nicht) zu unterteilen, ohne das der DSM Administrator weitere Änderungen am eScript oder der Policy vornehmen muss, wie es oft bei der Verteilung von Patchen oder z.B. auch einer schleichenden Windows 10 Migration gewünscht ist. Ein "Aufschieben" oder "Erweitern" des Zeitplanes greift bei Ausführung des eScripts sofort.

Möchte man nun im eScript den "Beginn" oder das "Ende" des Wartungszeitplans herausfinden, gibt es für den Builtin DSM Wartungszeitplan den eScript Befehl "GetMaintenanceTimes". Leider ist es über diesen Befehl aber zum jetzigen Zeitpunkt nicht möglich einen eigenen Wartungsplan auszulesen um die gewünschten Informationen zu erhalten. Weist man den Wartungsplan einer DSM Variablen zu und lässt sich den Inhalt ausgeben, erscheint die Interpretation dieses codierten Strings auf den ersten Blick relativ schwierig.

Hier ein Custom Wartungszeitplan und der Inhalt der ODS Variablen als String:

1;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP///z8AAAAA

In diesem Artikel möchte ich zeigen, wie der angezeigte String per PowerShell interpretiert und die darin enthaltene Information in DSM weiterverarbeitet werden kann. Dies würde sich sowohl mit als auch ohne PSX realisieren lassen.

Verwendetes Script:

Eine kopierbare Version des Scriptes befindet sich am Ende des Artikels.

Erklärung zum Script:

Im angezeigten Script gehe ich davon aus, dass die benötigten Informationen per PSX ausgelesen werden können. (Die entsprechende Zeile um ohne PSX auszukommen ist ebenfalls schon vorgesehen und muss lediglich "Entkommentiert" werden.) Vorteil der PSX Variante wäre ganz klar, dass sich die Ausführung nicht auf den Kontext der DSM beschränkt, sondern auch eine vom eScript unabhängige Verwendung denkbar wäre. Demnach wird zunächst der Verbindungsaufbau durchgeführt, anschließend ein definiertes Computerobjekt und der zugewiesene Wartungsplan mit Namen "TimeframeToPostpone" gesucht.

Lassen wir uns nun die in $customtimeframe enthaltene Information anzeigen, erhalten wir den bereits weiter oben gezeigten String.

Kuckt man sich diesen String genauer an fällt auf, dass dieser durch ";" in drei Teile aufgeteilt ist. Für uns entscheidend sind die beiden hinteren Teile bei denen es sich in der Mitte um die Stundengenaue und am Ende um die Tagesgenaue Angabe aus dem DSM Dialog handelt. Bei diesen beiden Substrings handelt es sich um Base64 codierte Strings, die zunächst in ein Array mit entsprechenden 8-Bit Ganzzahlen konvertiert werden. Da in unserem Beispiel eine Tagesgenaue Auswertung reicht, splitte ich $customtimeframe in die besagten drei Teile und verarbeite nur den hinteren Teil weiter.

Hierbei ist nun zu sehen, dass unser erzeugtes Array aus 48 Bytes besteht, was gerechnet auf das komplette Jahr 4 Bytes pro Monat bedeutet. Jedes Byte besteht aus 8 Bits, die wiederrum die einzelnen Tage im Monat beschreiben. Hieraus entstehen also 32 Bits, die jeweils nach Anzahl der Tage eines Monats gekürzt werden müssen. Mit der folgenden Grafik möchte ich dies noch etwas verdeutlichen.

Da es im Moment noch etwas schwierig ist mit dem Byte-Array weiter zu arbeiten, werden die Bytes nach Monaten aufgeteilt und in ihre einzelnen Bits umgewandelt. Zum Schluss wird dies aneinander gereiht und bildet einen String mit 365 Zeichen. (Schaltjahr 366)

Zu guter letzt muss innerhalb des Bit-Strings nur noch nach Wartungsfenstern gesucht werden, die sich jeweils durch die "1" auszeichnen. Die Stelle der "1" im String definiert den Tag im Jahr und kann relativ einfach ermittelt werden indem wir vom ersten Tag eines Jahres (01.01.yyyy) ausgehen und über die Get-Date Methode "AddDays" die Position einer "1" aus dem String übergeben. Neben dem Start- und Enddatum eines Wartungszeitfensters wird in dem erzeugten PSObject ebenfalls noch die Nummer das Start- und Endtages als auch die Dauer der Wartung in Tagen angezeigt.

Wird das Script über CallScript aus einem eScript heraus aufgerufen, können die ermittelten Werte nun natürlich noch über Set-NiVar zurück in die DSM gemeldet und dort weiter verarbeitet werden. Abschließend möchte ich das verwendete Script noch zum kopieren bereit stellen:

# Dieser Block wird nur bei Verwendung von PSX benötigt
Import-Module psx7 -DisableNameChecking
New-PSDrive -Name emdb -PSProvider BlsEmdb -Root \\bgusrv-dsm-01 -Scope script
emdb:

$pc = Get-EmdbComputer -Name "bguclt-win10" -Recurse
$customtimeframe = $pc.GetVariableValue("TimeframeToPostpone")

# Beispielhaftes Einlesen des Base64-Zeitplans ohne PSX
# $customtimeframe = Get-NiVar "_ZeitplanAuseScript"

$yearmaintenance = $customtimeframe.Split(";")
$bytes = [Convert]::FromBase64String($yearmaintenance[2])

$currentyear = Get-Date -Format yyyy
$month = 0
$yeardays = ""

for ($i=1; $i -le 12; $i++) {
$daysofmonth = [DateTime]::DaysInMonth($currentyear, $i) - 1
$integer = [BitConverter]::ToInt32($bytes,$month)
$string = [Convert]::ToString($integer,2).PadLeft(32,"0")
$reverse = ([regex]::Matches($string,'.','RightToLeft') | ForEach {$_.value}) -join ''
$days = $reverse[0..$daysofmonth] -join ''
$month = $month + 4

# Jahr als Bit-String (365 Tage)
$yeardays = $yeardays + $days
}

$matches = [regex]::Matches($yeardays, '1+') # alle Vorkommnisse von zusammenhängenden Einsern ermitteln
$MaintenanceTimeFrames = foreach ($match in $matches) {

New-Object -TypeName psobject -Property @{
MaintenanceStartDayNumber = $match.Index
MaintenanceEndDayNumber = ($match.Index + $match.Length - 1)
MaintenanceDuration = $match.Length
MaintenanceStartDay = (([datetime]"01/01/$((Get-Date).Year)").AddDays($match.Index)).ToString("dd.MM.yyyy")
MaintenanceEndDay = (([datetime]"01/01/$((Get-Date).Year)").AddDays($match.Index + $match.Length - 1)).ToString("dd.MM.yyyy")
}
}

$MaintenanceTimeFrames | Select MaintenanceStartDay,MaintenanceEndDay,MaintenanceDuration,MaintenanceStartDayNumber,MaintenanceEndDayNumber

# Beispielhaftes Zurückmelden des MaintenanceEndDays ins eScript
# Set-NiVar "_MaintenanceEndDay" $MaintenanceTimeFrames.MaintenanceEndDay

Related Posts

Leave Comments