Wie im letzten Eintrag ausgangs erwähnt, war der Supportchef in den Ferien, während mein Ausbildner krank war und somit nur begrenzt zur Verfügung stand. Ich konnte also einige Schritte mit meinem Ausbildner zwar besprechen, war aber im Grossen und Ganzen auf mich allein gestellt. Wir kamen überein, dass es sinnvoll wäre, das Skript in einer zweiten Version anzupassen und zu verbessern. Geplant war Folgendes:
Zudem sollte der Supportchef, sobald er zurück war, stärker in die Entwicklung und Testung einbezogen werden.
Das bisherige monolithische ps1-File (Details im letzten Eintrag) wurde also in zwei Dateien aufgesplittet.
Der Grundgedanke war, dass es so einfacher werden würde, neue Funktionalität einzubauen bzw. Bestehendes zu ergänzen oder abzuändern. Zudem könnte ich neue Setup-Prozeduren relativ einfach zusammenstöpseln, indem ich neue Skriptfiles schreibe, die sich an den Funktionen im Modul bedienen. Nach aktuellem Stand hätte eine neue Prozedur nämlich bedeutet, grosse Teile des Skripts zu copy pasten, andere Teile neu zu schreiben und dann auf das Beste zu hoffen. Ausserdem würde ich so Erfahrung im Schreiben von Funktionen und in der Verwendung von Parametern gewinnen in Powershell.
Das erste Etappenziel war, im bestehenden PS1-File alle nicht-trivialen Abläufe (ausgeschlossen war also z.B. die Anpassung einer globalen Variable; oder natürlich die Zeile für den Import des Moduls) in Funktionen ins PSM-File auszulagern. Dabei sollte der Funktionsumfang derselbe bleiben und die aktuell resultierende Installation auf anderem Wege hinzukriegen.
Ich begann also damit, die Abläufe aus dem Skript in Funktionen zu verpacken. Als Grundlage dafür nahm ich den Ansatz, dass das Skript (fast) ausschliesslich Funktionsaufrufe aufweisen sollte, die einen aus NUSS-Endnutzersicht logischen Arbeitsschritt erledigen. So würde der Umfang an Funktionen im Modul zu einem Baukasten, aus dem man sich die erforderlichen Schritte zusammensuchen kann, um mit nicht allzu viel Aufwand eine neue Prozedur zu schreiben.
Mein erster Versuch, da auch ein offensichtlicher Kandidat für Parametrisierung, war die Stelle für das Installieren von Packages. Im alten Verfahren sah sie so aus:
# 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"
Die Probleme an dieser Stelle sind offensichtlich: Derselbe Code wird mehrmals wiederholt. winget wird mit denselben Argumenten aufgerufen, nur das zu installierende Package ändert sich. Möchte man die Liste erweitern, muss man copypasten und es wird schnell unübersichtlich.
Ich habe mich dazu entschlossen, die Package-Installation folgendermassen umzugestalten:
- Im Skript wird ein Array von Strings definiert, welche die winget-IDs der zu installierenden Pakete enthält. Diese soll sich im Skript befinden, damit sie je nach Prozedur einfach angepasst werden kann.
- Dann wird im Skript die Funktion installPackages mit dem Array als Parameter aufgerufen.
- Im Modul ist installPackages definiert. Die Funktion loopt durch die Elemente des Arrays und ruft winget mit der entsprechenden winget-ID und den Argumenten auf.
Das sieht dann so aus:
# nep_unattend.ps1
# Define packages to install.
# Must:
# - be available in winget
# NOTE: typecast to String[] to ensure proper array concatenation when added with $touchPackages
[String[]]$packages =
"Google.Chrome",
"Mozilla.Thunderbird",
"Mozilla.Firefox",
"7zip.7zip",
"Foxit.FoxitReader",
"TheDocumentFoundation.LibreOffice",
"9NHMG4BJKMDG", # FR-Fr local experience pack
"9PDSCC711RVF" # EN-US local experience pack
[...]
installPackages -packages $packages
# nep_unattend.psm1
# PARAMETERS:
# String[] $packages
#
# Loops through input array and installs given package.
# Packages should use "vendor.software" or MS Store ID notation for disambiguation.
function installPackages {
param(
$packages
)
foreach($package in $packages) {
Write-Host "Installing $package..."
winget install $package --accept-package-agreements --accept-source-agreements
}
Write-Host "Non-touch packages installed." -ForegroundColor Green
}
Wir sehen hier also gut, wie im Skript nur definiert und aufgerufen wird, während die Details der Implementation in das Modul verschoben wurden.
Natürlich war das Potential nicht bei allen Arbeitsschritten des Skripts so offensichtlich wie hier, insgesamt wurde es aber trotzdem erheblich kürzer und übersichtlicher: In der aktuellsten Version umfasst das modulare Skript 90 Zeilen Code, das alte 134 Zeilen - wobei das neue aber noch mehr Funktionalität bietet und besser kommentiert ist.
Nach der erfolreichen Modularisierung des Codes fasste ich die Implementation einiger neuer Features ins Auge.
Dazu gehörten:
- automatische Touch-Erkennung, um Packages zu installieren, die nur auf Touch-Geräte gehören
- HP-Geräteupdates neu automatisiert über den HP Image Assistant anstelle über manuelle Installation mit dem HP Support Assistant
- eine automatisierte Kontrolle, ob Programme korrekt installiert und Windows erfolgreich aktiviert wurde
Diese konnte ich erfolgreich umsetzen, wobei wieder ein wenig Recherche notwendig war. Ich merkte jedenfalls schnell, dass die Funktionalität dank Teilung der Dateien einfacher zu implementieren und reibungsloser in die bestehende Prozedur zu intergrieren war.
Hier die entsprechenden Stellen im Code:
# Funktion getTouch - verwendet Informationen über Windows-plug and play-Devices, um herauszufinden, ob es einen Touchscreen gibt.
# Setzt voraus, dass irgendwo im Namen des Geräts «touchscreen» vorkommt - ist bei den bei uns verwendeten Sprachen gegeben.
function getTouch {
$sysinfo = Get-PnpDevice
if($sysinfo.FriendlyName -like "*touchscreen*") {
return $true
} else {
return $false
}
}
[...]
function installTouchPackages {
param(
$packages,
$touch
)
if($touch) {
Write-Host "Touchscreen detected. Installing touch software..."
foreach($package in $packages) {
Write-Host "Installing $package..."
winget install $package --accept-package-agreements --accept-source-agreements 1>>$HOME\Documents\unattend.log
}
Write-Host "Touch packages installed." -ForegroundColor Green
} else {
Write-Host "No touchscreen detected - skipping touch software..."
}
}
$touch = getTouch;
installTouchPackages -packages $packages -touch $touch
# Die HPIA-relevanten Stellen im Code
# Milde interessant ist, dass HPIA während der Installation heruntergeladen wird
"HP" {
Write-Host "Downloading and installing HP Image assistant..."
$ProgressPreference = 'Continue'
Invoke-WebRequest -URI $HpiaUrl -OutFile $HOME\Downloads\HPIA.exe
$ProgressPreference = 'SilentlyContinue'
Start-Process -FilePath $HOME\Downloads\HPIA.exe -ArgumentList "/s /e" -Wait
Write-Host "HP Image Assistant Installed." -ForegroundColor Green
Remove-Item $HOME\Downloads\HPIA.exe
break
}
[...]
"HP" {
Write-Host "Downloading and installing HP updates..."
Start-Process "C:\SWSetup\SP143766\HPImageAssistant.exe" -ArgumentList "/Operation:Analyze /Action:Install /Category:BIOS,Drivers,Firmware /ReportFolder:C:\Users\Neptun\Documents /Debug /Silent" -Wait
Write-Host "HP updates done." -ForegroundColor Green
break
}
# Die Prüfung der installierten Packages und der Herstellersoftware
function checkInstalledSoftware {
param(
$checklist,
$manufacturer
)
[String[]]$installedPkgs = winget list
Write-Host "Software install report:"
foreach($checking in $checklist) {
if($installedPkgs -Match $checking) {
Write-Host "$checking installed correctly." -ForegroundColor Green
} else {
Write-Host "$checking not installed. Install manually." -ForegroundColor Red
}
}
switch($manufacturer) {
"LENOVO" {
if(Get-AppxPackage -Name "*.LenovoSettingsforEnterprise") {
Write-Host "Lenovo Commercial Vantage installed." -ForegroundColor Green
} else {
Write-Host "Lenovo Commercial Vantage not installed - install manually." -ForegroundColor Red
}
break
}
"HP" {
if(Test-Path "C:\Program Files (x86)\HP\HP Support Framework") {
Write-Host "HP Support Assistant installed." -ForegroundColor Green
} else {
Write-Host "HP Support Assistant not installed - install manually." -ForegroundColor Red
}
break
}
"Dell Inc." {
if(Test-Path "C:\Program Files (x86)\Dell\CommandUpdate") {
Write-Host "Dell Command | Update installed." -ForegroundColor Green
} else {
Write-Host "Dell Command | Update not installed - install manually." -ForegroundColor Red
}
break
}
"Microsoft Corporation" {
Write-Host "Updates for Microsoft device installed via Store and Windows updates." -ForegroundColor Green
}
Default {
Write-Host "Manufacturer identification failed (manufacturer string $manufacturer) Install driver software manually." -ForegroundColor Red
}
}
}
Schliesslich erfolgte eine mehrtägige Testingphase. Die groben Fehler im Skript fand und behob ich alleine. Danach stand mir der Supportchef zur Seite, der die Funktionalität des Skripts auf weiteren Geräten überprüfte und mir wertvolles Feedback zu versteckteren Bugs oder zusätzlichen Features gab, die ich dann implementierte. Dieser Austausch war für mich sehr wertvoll, da ich so eine weitere Perspektive auf die Aufgabe erhielt, und natürlich auch, weil ich dann viel weniger Zeit für das Testing aufwenden musste. Das Resultat dieses Prozesses war NUSS, das wir dann auch an unsere Help Points verteilten und jetzt zum Einsatz kommt. Über das Repository und den Release- und Distributionsprozess schreibe ich zu einem anderen Zeitpunkt mehr.
In diesem Projekt benötigte ich insbesondere folgende Kompetenzen: