top

YAML-NUSS

Dank der Arbeit, die ich in den letzten zwei Einträgen ausgeführt habe, wurde NUSS zwar systematischer und einfacher anzupassen. Das Ziel, dass NUSS auch für Nutzer ohne Programmierkenntnisse anpassbar sein sollte, habe ich aber dennoch verfehlt. Deshalb vereinbarten mein Chef und ich, dass ich nun an einer weiteren Version arbeiten würde, deren Hauptziel es ist, eine YAML-Datei einzulesen, in der alle Konfiguration erfolgt. Dies soll sicherstellen, dass auch Nutzer ohne jegliche PowerShell- oder allgemeine Programmierkenntnisse dazu in der Lage sind, Prozeduren anzupassen oder neue Use Cases abzudecken - ganz nach dem Grundsatz humand-readable und -writable. Die Datei soll dann sämtliche Information beinhalten, die für eine Prozedur konfiguriert werde muss. Davon ausgenommen ist, was in autounattend.xml für das Windows-Setup selbst gesetzt werden muss.

Im Gegensatz zu den vorherigen Einträgen wird sich dieser Eintrag insbesondere auf die technische Seite fokussieren.

Die Struktur des YAML-Files

Die Struktur des YAML-Files bildet in erster Linie die logische Abfolge eines Setup-Prozesses ab. Der Prozess ist in acht stages unterteilt, die wiederum bestimmte steps enthalten. Zu durchlaufende steps werden über ein key-value pair mit dem key des Arbeitsschrittes und dem Wert true definiert; andere (z.B. packages) werden als Listen definiert. Einige keys dienen nur der weiteren Unterteilung, z.B. check in der start stage.

In einem ersten Schritt habe ich sämtliche stages und steps sowie ihr behavior in der Dokumentation definiert und danach erst implementiert. Hier zur Veranschaulichung eine YAML-Datei, welche unsere typische Support-Prozedur abbildet:

                
---
info:
name: 'YAML-NUSS Experimental'
version: 'Pre-1'
edition: 'Pro'
maintainer: 'Patrick Côté'
splashscreen: true

start:
check:
check_power: true
enforce_power: true
check_internet: true
enforce_internet: true

prepare:
install_winget: true
install_pswu: true

updates:
update_windows: true
repeat_until_done: true
update_manufacturer: true
hpia_bios_pw_path: "nep-bios-pw.bin"
hpia_url:

resources:
copy_resource:
- name: Wallpaper
from: '\usb\NepResources\wallpaper.png'
to: '$home\Documents'

packages:
install_manufacturer_software: true
detect_touch: true
winget_packages_as_job: true
winget_packages:
- human_name: Google Chrome
winget_id: Google.Chrome
touch: false
- human_name: Mozilla Firefox
winget_id: Mozilla.Firefox
touch: false
- human_name: Mozilla Thunderbird
winget_id: Mozilla.Thunderbird
touch: false
- human_name: 7-Zip
winget_id: 7zip.7zip
touch: false
- human_name: Foxit PDF Reader
winget_id: Foxit.FoxitReader
touch: false
- human_name: LibreOffice
winget_id: TheDocumentFoundation.LibreOffice
touch: false
- human_name: French local experience pack
winget_id: 9NHMG4BJKMDG
touch: false
- human_name: US English local experience pack
winget_id: 9PDSCC711RVF
touch: false
- human_name: Drawboard PDF
winget_id: 9WZDNCRFHWQT
touch: true

customization:
set_wallpaper: $HOME\Documents\Wallpaper.png
disable_bitlocker: true
activate_windows: true

audit:
check_packages: true
check_activation: true
check_manufacturer_software: true

PowerShell und YAML

PowerShell verfügt über keine native YAML-Unterstützung. Zum Einlesen gibt es zwei Möglichkeiten:

  • YAML-Datei manuell parsen und in entsprechende Datenstrukturen übersetzen.
    Dies wäre im Falle von YAML nicht sehr schwierig, aber mühsam und zeitraubend.
  • Die Verwendung eines fixfertigen Modules wie powershell-yaml. Ich habe mich für diesen Weg entschieden, da er am schnellsten und am zuverlässigsten ist.

Mit powershell-yaml kann die Datei config.yaml einfach eingelesen werden:

                
# YAML einlesen
$yaml = Get-Content ./config.yaml | ConvertFrom-Yaml
                
# Inhalt von $yaml:
PS /> $yaml

Name Value
---- -----
resources {[copy_resource, System.Collections.Generic.List`1[System.Object]]}
audit {[check_packages, True], [check_manufacturer_software, True], [check_activation, True]}
updates {[update_manufacturer, True], [hpia_url, ], [repeat_until_done, True], [update_windows, True]…}
customization {[activate_windows, True], [disable_bitlocker, True], [set_wallpaper, $HOME\Documents\Wallpaper.…
info {[name, YAML-NUSS Experimental], [maintainer, Patrick Côté], [edition, Pro], [version, Pre-1]…}
prepare {[install_winget, True], [install_pswu, True]}
start {[check, System.Collections.Hashtable]}
packages {[winget_packages_as_job, True], [install_manufacturer_software, True], [detect_touch, True], [w…

Interpretation der Datei

Die Inhalte der Datei können nun verwendet werden, um Funktionen aufzurufen und ihre Parameter zu definieren.

Dies geschieht auf folgende Arten:

  1. Mit der .ContainsKey()-Methode kann überprüft werden, ob ein Schlüssel überhaupt vorhanden ist. Dies verwende ich, um festzustellen, ob eine stage bzw. substage überhaupt definiert worden ist. Ausserdem verwende ich sie auch für Keys mit Werten wie Pfaden oder Dateinamen.
  2. Mit switch-statements werden die key-value-Paare abgearbeitet, die entweder auf true oder false lauten.
  3. Arrays wie winget_packages werden mit foreach-Loops und den beiden oberen Methoden verarbeitet.

Der Vorteil meiner Herangehensweise ist, dass die Syntax der YAML-Datei nicht kontrolliert werden muss. Die token-Namen, die Reihenfolge der Ausführung und die vertikale Struktur ist hardcoded. Die Fehlerquellen im YAML-File werden also stark reduziert:

  • Es ist unerheblich, in welcher Reihenfolge die stages und steps genannt werden, solange die Hierarchie korrekt ist. Die Kontrolle über die Reihenfolger der Ausführung liegt ganz beim Programmier und kann sinnvoll gestaltet werden.
  • Ein falsch geschriebenes Token wird gar nicht erkannt, führt also nicht zu Fehlern in der Ausführung.
  • Ist ein Token nicht gesetzt oder falsch geschrieben, wendet das Skript ganz von selbst das definierte default behavior an.

Es folgen Beispiele für die drei Interpretationsarten:

                
# Hier wird auf das stage-Token geprüft
if($yaml.ContainsKey('start')) {
Import-Module "$PSScriptRoot\modules\start.psm1"

# Hier wird auf das sub-stage-Token geprüft
if($yaml.start.ContainsKey("check")) {

# In einem switch-Statement mit dem Vergleichsausdruck $true kann überprüft werden, ob die Keys auf true gesetzt wurden.
# Falls ja, wird die entsprechende Funktion, ggf. mit Parametern, ausgeführt.
# Hier sehen wir auch, wie enforce_power check_power overriden kann.
# D.h. check_power wird, auch wenn definiert, nicht ausgeführt, wenn enforce_power ausgeführt werden soll.
switch($true) {
$yaml.start.check.enforce_power { enforce_power }
($yaml.start.check.check_power -and !$yaml.start.check.enforce_power) { check_power }
$yaml.start.check.enforce_internet { enforce_internet }
($yaml.start.check.check_internet -and !$yaml.start.check.enforce_internet) { check_internet }
}
}
}

[...]

# Hier wird ein Arrray aus $yaml an eine Funktion weitergegeben, wo er weiterverarbeitet wird:
$yaml.packages.ContainsKey('winget_packages') {
if(!$yaml.packages.winget_packages_as_job) {
winget_packages -packages $yaml.packages.winget_packages
}

if(!$yaml.packages.winget_touch_packages_as_job) {
winget_touch_packages -packages $yaml.packages.winget_packages
}
}

[...]

function winget_packages {
param(
$packages
)

# Die foreach-Schleife loopt durch die Elemente des YAML-definierten Arrays, bei denen $touch nicht wahr ist
# So stelle ich sicher, dass packages, bei denen der Key touch nicht gesetzt wurde, als nicht-touch-Packete gelten
foreach($package in ($packages | Where-Object { $_.touch -ne $true })) {
msg_installing -name $package.human_name
winget install $package.winget_id --accept-package-agreements --accept-source-agreements -h
msg_installed -name $package.human_name
}
}

Kompetenzen

Für diese Aufgabe waren folgende Kompetenzen relevant:

  • Wirtschaftliches Denken und Handeln:
    Die Umsattlung auf YAML-configs erlaubt es, in Zukunft viel einfacher und zuverlässiger Prozeduren anzupassen und zu erstellen. Das spart Arbeitszeit. Zudem ist dies mit der Dokumentation für alle möglich, es muss keine Zeit von Programmierern aufgewendet werden.
  • Prozessorientiertes, vernetztes Denken und Handeln:
    Die ältere Version von NUSS wurde schon eingeführt und wird produktiv eingesetzt. Deswegen ist es wichtig, dass auch bei einer neuen Version aus Supportmitarbeiter-Perspektive die Arbeitsschritte gleich bleiben und auch das Endresultat (konkret das zurückgesetzte Gerät) identisch ist. All dies muss ich bei der Entwicklung von YAML-NUSS berücksichtigen.
  • Lernstrategien:
    Ich habe mir selbstständig diverse Dokumentationen über das Format YAML angeschaut. Ich habe einige Probedateien geschrieben und mit powershell-yaml "untersucht", um ein wenig Erfahrung zu sammeln. Diese habe ich gleich in die Praxis umgesetzt, indem ich die YAML-Struktur und -Tokens für NUSS definiert habe.
  • Arbeitstechniken:
    Hier habe ich im Kleinformat Documentation Driven Development angewendet: Ich ging das Projekt aus der Perspektive des Endnutzers an, der anhand einer Dokumentation eine Prozedur warten oder neu erstellen muss. Ich habe die Dokumentation, mitsamt allen Tokens und ihrem Verhalten, für diesen Nutzer geschrieben, und habe die Dokumentation danach in Code umgesetzt.
  • Eigenverantwortliches Handeln:
    Ich habe die Information, Dokumentation, Implementierung und Testing grösstenteils selbstständig geplant und durchgeführt. Ich habe periodisch meinem Vorgesetzten Bericht erstattet.

Angewendete Kompetenzen

Wirtschaftliches Denken und Handeln