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 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 verfügt über keine native YAML-Unterstützung. Zum Einlesen gibt es zwei Möglichkeiten:
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…
Die Inhalte der Datei können nun verwendet werden, um Funktionen aufzurufen und ihre Parameter zu definieren.
Dies geschieht auf folgende Arten:
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 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
}
}
Für diese Aufgabe waren folgende Kompetenzen relevant: