top

Ziel und Scope

Bei Projekt Neptun haben wir viele Supporgeräte, die wir bei Garantiefällen oder zur Überbrückung von Lieferzeiten an Kunden ausleihen. Wenn wir dieser zurückerhalten, müssen wir sie zurücksetzen. Die aktuelle Prozedur basiert auf Images, die mit Clonezilla/Parted Magic auf einen Server gespeichert bzw. gelesen werden.

Der Prozess war früher sinnvoll, hat aber mehrere Probleme. Das Image muss auf das Gerät aufgespielt werden, und nachdem Updates getätigt worden sind, wird das Gerät wiederum auf den Server geimaged. Das kann gerade bei älteren Geräten länger dauern. Fehlerquellen beinhalten die Qualität der Verbindung und die Image-Dateinamen, welche von Hand gesetzt werden. Schliesslich ist relativ viel Handarbeit notwendig. Der Prozess zeigte sich ausser Lage, den stetig wachsenden Berg an zurückzusetzenden Geräten mit der begrenzten Zeit der Supportmitarbeiter zu bewältigen.

Demgegenüber dauert ein Windows-Clean Install mit modernen Flash-Speichermedien weniger als 15 Minuten. Wird generell mit Clean Installs gearbeitet, entfällt auch das Imagen auf den Server, was Zeit spart, und die entsprechende Infrastruktur muss nicht mehr unterhalten werden. Mit Powershell und Windows Unattended Install-Konfigurationen kann ein guter Teil der manuellen Arbeit automatisiert werden.

Deshalb war es meine Aufgabe, eine neue Prozedur für das Zurück- und Aufsetzen unserer Supportgeräte zu entwerfen, die auf Powershell und dem Windows Unattended Install basiert. In einem zweiten Schritt habe ich sie implementiert und dokumentiert. Ausserdem sollte ich nach IPERKA vorgehen, um eine strukturierte Herangehensweise zu üben. Während des gesamten Projektes habe ich mit einem Spreadsheet gearbeitet, um die Schritte zu organisieren und zu visualisieren.

I - Informieren

Planungsgrundlage waren die Schritte des bestehenden Prozesses. Als Werkzeuge bereits definiert waren der Windows Unattended Install und Powershell. Vorab abgeklärt mussten zwei Aspekte:

  1. Welche Schritte müssen überhaupt übernommen werden, welche entfallen?
  2. Was sind die Möglichkeiten der beiden Werkzeuge, wo kommt welches zum Zug? Wie können sie integriert werden?

Da der Support-Chef abwesend war und ich in Gang kommen wollte, ging ich zu Beginn davon aus, dass wir die Schritte 1:1 in die neue Prozedur übernehmen, das Imagen ausgenommen. Zu Punkt 2 verfolgte ich den Grundsatz, dass so viel wie möglich im Unattended-Verfahren gelöst werden sollte. Was dann übrig bleibt, wird soweit wie möglich via Powershell bearbeitet. Nur, was nicht oder nur sehr aufwändig automatisiert werden kann, sollte dann von Hand besorgt werden müssen.

Auszug aus den ersten Recherchearbeiten

Die Ergebnisse der Recherche habe ich in einer Präsentation gesammelt und vorgetragen.

P - Planung

Bei der Planung traf ich auf Schwierigkeiten:

  • Erstens empfand ich, dass ich auch nach den Recherchearbeiten im Schritt I zu wenig Wissen und Erfahrung hatte, um die Arbeit sinnvoll zu planen. Es war notwendig, gewisse Dinge einfach mal auszuprobieren, um ihre Tauglichkeit und Funktionsweise zu eruieren.
  • Zweitens war ich aber auch ungeduldig und wollte vor allem einfach mal loslegen. Dies führte mittelfristig aber zu einer eher planlosen Herangehensweise, die zu Verzögerungen führte.

Die Planung vermengte sich also teilweise mit der Implementation, was wie gesagt teils notwendig, teils übereilt war.

Ich führte aus, mit welchen Werkzeugen die Schritte bewältigt werden können, und verlinkte auf die entsprechenden Stellen in der WSIM- bzw. in der Powershell-Dokumentation. So schuf ich eine Entscheidungsgrundlage beim Testen und genauerer Konsultuierung der Dokumentation, und ich würde mich in Zukunft vermehrt auf das Schreiben des Skripts konzentrieren können. Ausserdem konkretisierte ich die zukünftige Prozedur: Phase I mit Unattended Install, Phase II mit Powershell-Skript (das automatisch beim ersten Start ausgeführt wird), und Phase III mit Handarbeit - vorher war provisorisch ein Ablauf mit zwei Skriptphasen geplant.

Die Planugsmatrix zu Beginn des Arbeitsschritts...

...und gegen Ende des Schritts, mit nur mehr drei Phasen.

E - Entscheidung

Auch beim Entscheiden stiess ich auf ähnliche Problematiken wie beim Planen: Gewisse Entscheidungen (z.B. ob ein Schritt mit zumutbarem Aufwand in Powershell lösbar ist; oder ob ein gefundener Lösungsansatz noch aktuell ist) konnten erst getroffen werden, nachdem ihre Umsetzung zumindest im Ansatz erfolgt war. Auch hier vermengten sich also Entscheidungen und Realisierung.

Es wurden einige Schritte entweder von Phase I nach Phase II oder umgekehrt verschoben, sie wurden in die Phase III verschoben, oder aber sie mussten ganz aus der Prozedur fallen, weil sie mittlerweile ohnehin schon Standard waren oder unter Windows 11 nicht mehr zutrafen.

Ein wichtiges Beispiel für eine Entscheidung, die später wieder revidiert wurde, waren die Firmware- und Treiberupdates für HP-Geräte. Zu diesem Zeitpunkt hatte ich mich auf die Lösung festgelegt, auf automatischem Wege den HP Support Assistant zu installieren und die Updates später in Handarbeit durchzuführen, da die Client Management Script Library von HP nicht richtig zu funktonieren schien. Durch einen Tip des Supportchefs gelang es mir aber später (schon bei Schritt RK), einen grossen Teil dieser Updates dennoch in Phase II mit einem anderen HP-Tool durchzuführen und die Handarbeitsphase somit zu verkürzen.

RK - Realisieren & Kontrollieren

Ich habe hier die Schritte «Realisieren» und «Kontrollieren» zusammengenommen, da ich mich hier (wie bei Entwicklungsarbeiten wohl üblich) in einem konstanten Kreislauf befand: Implementationen mussten laufend getestet, dann angepasst und/oder gefixt werden. Dies geschah teilweise in PowerShell ISE, teilweise musste der gesamte Installationsprozess mit einem Testgerät durchlaufen werden.

Den Fortschritt hielt ich laufend in einer neuen Tabelle fest, an der ich kontrollieren und nachführen konnte, was schon implementiert wurde:

Stand der Tabelle nach der Ausführung

Bei den Testläufen traten zahlreiche Fehler auf und es dauerte eine ganze Weile, bis das Script in einem ersten brauchbaren Zustand war. Dies war sicher meiner mangelnden Erfahrung mit Powershell geschuldet, zudem begann ich erst nach einer ganzen Weile, überhaupt einen Linter zu verwenden. Umso grösser war dann das Erfolgserlebnis, nachdem der Prozess erfolgreich abschloss.

Einerseits konnte mich die IPERKA-Methode, bzw. das Planen überhaupt, sehr überzeugen: Ich konnte mich konzentriert und zielgerichtet an die Arbeit machen und eine Pendenz nach der anderen abhaken, was sehr befriedigend war. Anderserseits zeigte sich wieder einmal, dass ich mehr vorausdenken und mir den späteren Rewrite hätte ersparen können - wobei man hier einräumen muss, dass die erste Implementation trotzdem sehr nützlich war, um erste Kenntnisse mit Powershell zu sammeln; ich war da immerhin Laie.

In der ersten Version der Implementierung basierte die ganze Phase II auf einem einzelnen Powershell-Skript, welches alles aufs Mal erledigte. Es verfügte über keine Funktionsdefinitionen und war dadurch unübersichtlich. Modifikationen waren relativ aufwendig vorzunehmen und konnten somit auch Fehler mit sich ziehen. Auch die Dokumentation führte ich nur mit einiger Verspätung.

nep_unattend.ps1 - die erste Fassung

                
# Disable progressbar because it obscures terminal output
$ProgressPreference = "SilentlyContinue"

# Read key from UEFI and activate Windows
Write-Output "Activating Windows..."
$service = get-wmiObject -query 'select * from SoftwareLicensingService'
$key = $service.OA3xOriginalProductKey
$service.InstallProductKey($key) >> "C:\Users\Neptun\Documents\unattend.log"

# Install winget (should already be installed according to MS, but isn't >:()
Write-Output "Installing winget package manager..."
Add-AppxPackage "D:\NepResources\winget.msixbundle" 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"

# Install NuGet - required for installing PS modules
Write-Output "Installing NuGet PowerShell module manager..."
Install-PackageProvider -Name NuGet -Force 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"

# "mount" HKEY_USERS as a PSDrive so we can work with it
# then add reg key for disabling sharing of updates over network
# will oftentimes fail as set by default
Write-Output "Changing registry setting for network-shared updates..."
New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
New-ItemProperty -Path "HKU:\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings\" -Name "DownloadMode" -PropertyType DWord -Value 0 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"

# copy wallpaper to documents and set wallpaper
Write-Output "Copying and changing wallpaper..."
Copy-Item D:\NepResources\wallpaper.png C:\Users\Neptun\Documents\ 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop\" -Name WallPaper -Value "C:\Users\Neptun\Documents\wallpaper.png" 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"

# Install auxilliary software
Write-Output "Installing Foxit Reader..."
winget install Foxit.FoxitReader -h --accept-package-agreements --accept-source-agreements 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Installing Thunderbird..."
winget install Mozilla.Thunderbird --accept-package-agreements --accept-source-agreements 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Installing Firefox..."
winget install Mozilla.Firefox -h --accept-package-agreements --accept-source-agreements 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Installing LibreOffice..."
winget install TheDocumentFoundation.LibreOffice -h --accept-package-agreements --accept-source-agreements 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Installing 7-Zip..."
winget install 7zip.7zip -h --accept-package-agreements --accept-source-agreements 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"

# Check if touch device, if yes, install Drawboard PDF
# NOTE: Depends on locale!
$sysinfo = Get-PnpDevice
if($sysinfo.FriendlyName.Contains("HID-konformer Touchscreen" -or "HID-conforming touchscreen" -or "Écran tactile compatible HID")) {
Write-Output "Touchscreen detected - installing Drawboard PDF..."
winget install "Drawboard PDF" -h --accept-package-agreements --accept-source-agreements 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
} else {
Write-Output "No touchscreen detected - skipping Drawboard PDF..."
}

# Execute MS Store updates
Write-Output "Updating MS Store applications..."
winget upgrade -h --all 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"

# find out if device is LENOVO or HP and install appropriate software
# NOTE: LCVa uses winget, HPSA reliant on .exe on install drive
Write-Output "Checking for manufacturer and installing firmware/driver update tools..."
$manufacturer = Get-ComputerInfo -Property "CsManufacturer"
switch($manufacturer) {
"LENOVO" {
Write-Output "Lenovo system detected."
Write-Output "Installing Lenovo Commercial Vantage..."
winget install --accept-source-agreements --accept-package-agreements "Commercial Vantage" 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
}

"HP" {
Write-Output "HP system detected."
Write-Output "Installing HP Support Assistant..."
Invoke-Expression -Command "D:\NepResources\HPSA.exe /s"
}

"DELL" {
Write-Output "Dell system detected."
Write-Output "Installing Dell Command | Update..."
winget install --accept-source-agreements --accept-package-agreements Dell.CommandUpdate.Universal 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
}

"Microsoft Corporation" {
Write-Output "Microsoft system detected. Will update via MS Store and Windows updates."
}

Default {
Write-Output "Manufacturer identification failed. No action taken."
}
}
# Install manufacturer updates
# NOTE: Only Lenovo automated.
# NOTE: Only Lenovo updates with unattended installation are installed
switch($manufacturer) {
"LENOVO" {
Write-Output "Installing LSUClient and getting Lenovo updates..."
Install-Module -Name "LSUClient" -Force 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Import-Module LSUClient
$updates = Get-LSUpdate | Where-Object { $_.Installer.Unattended }
Write-Output "Downloading Lenovo updates..."
$updates | Save-LSUpdate 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Installing Lenovo updates..."
$updates | Install-LSUpdate
}

"HP" {
Write-Output "Skipping HP updates - remember to check manually!"
}

"DELL" {
Write-Output "Skipping Dell updates - remember to check manually!"
}

"Microsoft Corporation" {
Write-Output "Microsoft system detected. Will update via MS Store and Windows updates."
}

Default {
Write-Output "Identification failed. No driver/firmware updates applied automatically."
}
}

# First we re-enable progress bar, as it comforts us
# Add service for optional updates
# Then install PSWindowsUpdate, import module and execute Windows updates
# Then reset execution policy and reboot
$ProgressPreference = "Continue"
Write-Output "Installing and importing PSWindowsUpdate..."
Install-Module -Name PSWindowsUpdate -Force 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Import-Module PSWindowsUpdate
Add-WUServiceManager -ServiceID "7971f918-a847-4430-9279-4a52d1efe18d" -confirm:$false 1>>"C:\Users\Neptun\Documents\unattend.log"

Write-Output "Querying Windows updates..."
Get-WindowsUpdate 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Downloading and installing Windows updates, this will take a while. System will autoreboot!"
Install-WindowsUpdate -AcceptAll -AutoReboot 1>>"C:\Users\Neptun\Documents\unattend.log" 2>>"C:\Users\Neptun\Documents\unattenderror.log"
Write-Output "Resetting Execution Policy..."
Set-ExecutionPolicy Default

A - Auswerten

Der Supportchef und mein Ausbildner waren noch immer in den Ferien oder krank, weshalb die Aufgabe grossteils mir allein zufiel. Ich stellte mehrere Problematiken mit meinem Script fest:

  • Wie zuvor schon erwähnt, war das Skript nicht prozedural, sondern rein imperativ geschrieben. Um Änderungen z.B. an der Reihenfolge der Schritte vorzunehmen, musste man relativ umfassend umschreiben. Gewisse Zeilen mussten mehrmals mit geringfügigen Änderungen wiederholt werden (z.B. beim Packages Installieren).
  • Der Code war dementsprechend nicht parametrisiert. Wollte man also z.B. ein weiteres Package installieren oder eines weglassen, müsste man hierfür Zeilen löschen oder wiederholen, wobei jedes Mal Fehler entstehen können.
  • Durch die oben genannten Gründe ist das Skript sehr auf den spezifischen use case (die Zurücksetzung unserer Supportgeräte) fokusiert und lässt sich nicht für sehr ähnliche use cases, etwa die Aufsetzung von Demogeräten und Mieten, verwenden.
  • In unserem Projekt Neptun-Kontext ebenfalls problematisch ist, dass das Skript in diesem Zustand nur von Powershell-Kundigen angepasst werden kann. Dies ist beim Supportchef und den Supportmitarbeitern nicht der Fall.

Durch die Entwicklung dieser ersten Version hatte ich Einblicke und Erfahrungen gewonnen, es war aber Zeit für einen grundlegenden Rewrite, um die oben genannten Probleme anzugehen. Für die nächte Revision sollten folgende Grundsätze gelten:

  • Fast alle Logik wird in ein Modulfile ausgelagert und in Funktionen verpackt, damit sie problemlos wiederholbar und modifizierbar wird.
  • Das ursprüngliche Skriptfile wird dadurch schlanker und beinhaltet nur noch Funktionsaufrufe, welche die Reihenfolge der Schritte logisch spiegeln, und Definitionen von Parametern für diese Aufrufe. Eine ganz kleine Ausnahme ist I/O, welche zu trivial ist, um eine eigene Funktion zu "verdienen".
  • Mit den obigen Änderungen sollte es auch für Laien bewältigbar sein, Modifikationen am Ablauf des Skripts und den zu installierenden Packages vorzunehmen.

Auf diese Prinzipien gestützt startete ich in die zweite Projektphase. Ihre Entwicklung wird im nächsten Eintrag besprochen.