Im zweiten Teil dieser Serie trennen wir die Nutzeroberfläche von den Daten. So können Sie mit nur wenigen Codezeilen elegante und leistungsstarke Dashboards erstellen und gleichzeitig Webdienste nutzen, um die benötigten Daten bereitzustellen.
Wie Sie am Ende sehen werden, bietet dieser Ansatz enorme Flexibilität und ermöglicht es Ihnen, Herausforderungen mit Leichtigkeit zu bewältigen – Herausforderungen, die normalerweise viele Kopfschmerzen bereiten würden.
Kurze Zusammenfassung: PSU-Apps
Wenn Sie Teil 1 gelesen haben, werden Sie sofort den ersten wesentlichen Vorteil von PSU zu schätzen wissen: Es fungiert als Verwaltungssystem und Repository für Ihren Code und ermöglicht Ihnen so die einfache Wiederverwendung früherer Arbeiten.
Die in Teil 1 erstellte „Serviceliste“ ist beispielsweise weiterhin in PSU verfügbar. Wann immer Sie weitere Informationen zu Ihren Diensten benötigen, können Sie diese jederzeit über das PSU-Haupt-Dashboard abrufen:
- Klicken Sie in der linken vertikalen Menüleiste auf Apps.
- Sie sehen nun alle Ihre Apps. Suchen Sie das in Teil 1 erstellte „Service-Dashboard“.
- Klicken Sie auf das Globus-Symbol, um die App zu öffnen. Dort werden Ihre Dienste zusammen mit den von uns implementierten zusätzlichen Details angezeigt, wie z. B. dem Namen der Servicegruppe und dem Namen des zugrunde liegenden Prozesses.
Denken Sie daran, dass jede PSU-App direkt über ihre eindeutige URL erreichbar ist. Sie können sie also öffnen, indem Sie zu ihrer Verknüpfung navigieren, zum Beispiel: http://localhost:5000/list-services/Home
Erstellen eines Aktions-Dashboards
Wir erstellen ein zentrales Dashboard mit Navigationsschaltflächen. Wie bei allem, was eine Nutzeroberfläche hat, sind Dashboards einfach PSU-Apps, daher machen wir dort weiter, wo wir letztes Mal aufgehört haben:
- Klicken Sie im vertikalen Hauptmenü auf der linken Seite auf „Apps“ und dann auf den Unterpunkt „Apps“. Nun sehen Sie eine Liste Ihrer vorhandenen Apps.
- Klicken Sie oben auf die blaue Schaltfläche „App erstellen“, um eine neue App hinzuzufügen. Wählen Sie „Dashboard1“ als Namen und geben Sie „dashboard1“ als URL ein. Klicken Sie anschließend auf OK.
- Klicken Sie auf das Stiftsymbol, um den Code-Editor zu öffnen. Ersetzen Sie den Beispielcode durch folgenden Code:
# use a scriptblock to save the different pages to one variable
$Pages = & {
# FIRST PAGE (root page) defines your cockpit with all of your buttons
New-UDPage -Name 'Home' -Url '/' -Content {
# add a small header:
New-UDTypography -Text 'My personal Dashboard' -Variant h6
# add three buttons
New-UDButton -Text 'Services' -OnClick {
# tell the button what to do when clicked
# use -Native to open an external app (URL is relative to PSU root)
Invoke-UDRedirect '/list-services' -Native
}
New-UDButton -Text 'Processes' -OnClick {
# tell the button what to do when clicked
# DO NOT use -Native to open a child page within this app (URL is relative to this app)
Invoke-UDRedirect '/list-processes'
}
# third button, same as before
New-UDButton -Text 'Something else' -OnClick {
Invoke-UDRedirect '/list-more'
}
}
# add as many CHILD PAGES as you need. Their URL is relative to the root URL of this app
New-UDPage -Name 'Processes' -Url '/list-processes' -Content {
New-UDTypography -Text 'Here you could implement your own code to display something more useful.'
}
New-UDPage -Name 'More' -Url '/list-more' -Content {
New-UDTypography -Text 'Here you could implement your own code to display something more useful.'
}
}
New-UDApp -Title 'My Cockpit' -Pages $Pages
- Klicken Sie auf das Speichersymbol (mit dem Diskettensymbol). Dieser Schritt ist entscheidend: Sie müssen alle Änderungen manuell speichern, sonst ignoriert PSU Ihre Änderungen.
- Klicken Sie auf die Schaltfläche „App anzeigen“, um sie zu starten. Nun sehen Sie Ihre Dashboard-Nutzeroberfläche, die aus drei Schaltflächen und einem automatisch generierten Hamburger-Menü in der oberen linken Ecke besteht.
Dieses Beispiel veranschaulicht einige Kernkonzepte. Sehen wir uns zunächst die Nutzeroberfläche an und betrachten wir anschließend die Details:
- Wenn Sie auf „Dienste“ klicken, sehen Sie die Dienstlisten-App, die wir in Teil 1 erstellt haben. Sollten Sie eine Fehlermeldung erhalten, haben Sie möglicherweise Teil 1 übersprungen, sodass die Dienstlisten-App nicht existiert.
- Wenn Sie auf „Prozesse“ oder „Sonstiges“ klicken, gelangen Sie zu Seiten Ihrer Dashboard-App. Diese Seiten enthalten noch keine aussagekräftigen Inhalte.
Aufruf externer Apps
Die Schaltfläche „Dienste“ zeigt, wie Sie externe Apps in Ihr Dashboard einbetten können. Mit Invoke-UDRedirect '/list-services' und dem Zusatz -Native können Sie URLs relativ zum PSU-Stammverzeichnis angeben und auf alle zuvor erstellten Apps zugreifen.
Es ist zwar möglich, externe Apps auf diese Weise einzubinden, doch gilt dies nicht als bewährte Vorgehensweise, da es einen Nachteil mit sich bringt: Die automatische Navigation geht verloren. Das Hamburger-Menü in der oberen linken Ecke ist in der externen App nicht mehr verfügbar. Dies ist zu erwarten, da man im Prinzip zu einer völlig anderen App navigiert.
Aufruf von Unterseiten
Die beiden anderen Schaltflächen funktionieren anders: Sie navigieren zu Unterseiten der Dashboard-App. Sie verwenden dasselbe UDRedirect-Cmdlet, jedoch ohne den Zusatz -Native, sodass die URL auf eine interne Unterseite verweist, die innerhalb Ihres Dashboards mit New-UDPage erstellt wurde.
Da diese untergeordneten Seiten Teil Ihrer Dashboard-App sind, funktioniert die automatische Navigation wie erwartet und das Hamburger-Menü bleibt sichtbar, unabhängig davon, welche Seite gerade angezeigt wird.
Da Sie diese Unterseiten innerhalb Ihrer Dashboard-App definieren, haben Sie die volle Kontrolle und können ganz einfach und einheitlich neue UI-Funktionen hinzufügen – z. B. durch Einfügen einer besser sichtbaren Schaltfläche „Zurück zur Startseite“.
Hier ist ein Beispiel. Ersetzen Sie einfach Ihren Code (und vergessen Sie nicht, vor dem Anzeigen der App auf die Schaltfläche Speichern zu klicken):
function New-CockpitBackBar {
New-UDStack -Direction row -Spacing 2 -Children {
New-UDButton -Text '← Home' -OnClick {
Invoke-UDRedirect '/'
}
}
}
# use a scriptblock to save the different pages to one variable
$Pages = & {
# FIRST PAGE (root page) defines your cockpit with all of your buttons
New-UDPage -Name 'Home' -Url '/' -Content {
# add a small header
New-UDTypography -Text 'My personal Dashboard' -Variant h6
# add three buttons
New-UDButton -Text 'Services' -OnClick {
# tell the button what to do when clicked
# use -Native to open an external app (URL is relative to PSU root)
Invoke-UDRedirect '/list-services' -Native
}
New-UDButton -Text 'Processes' -OnClick {
# tell the button what to do when clicked
# DO NOT use -Native to open a child page within this app (URL is relative to this app)
Invoke-UDRedirect '/list-processes'
}
# third button, same as before
New-UDButton -Text 'Something else' -OnClick {
Invoke-UDRedirect '/list-more'
}
}
# add as many CHILD PAGES as you need. Their URL is relative to the root URL of this app
New-UDPage -Name 'Processes' -Url '/list-processes' -Content {
New-CockpitBackBar
New-UDTypography -Text 'Here you could implement your own code to display something more useful.'
}
New-UDPage -Name 'More' -Url '/list-more' -Content {
New-CockpitBackBar
New-UDTypography -Text 'Here you could implement your own code to display something more useful.'
}
}
New-UDApp -Title 'My Cockpit' -Pages $Pages
Auf den Unterseiten finden Sie nun eine Schaltfläche „Startseite“, die Sie bequem zurück zum Dashboard-Startbildschirm führt.

Wie sieht es mit der Wiederverwendung von Code aus?
Die Verwendung von Unterseiten bringt Nachteile mit sich: Die in vorigen Apps geleistete Arbeit kann nicht wiederverwendet werden. Damit Unterseiten nützliche Inhalte anzeigen, müssen Sie den entsprechenden Code selbst hinzufügen.
Dies führt uns zu besseren Designstrategien. In Teil 1 haben wir viel Aufwand in die Erstellung umfassender Dienstlisten investiert und diese in einer einzigen App implementiert. Die App funktionierte wie erwartet, ist aber, wie Sie jetzt sehen, nicht wiederverwendbar. Ein besserer Ansatz wäre gewesen, Daten und Nutzeroberfläche in separate Komponenten aufzuteilen – und genau das werden wir heute tun.
In Teil 1 haben wir PSU-Apps als UI-Ebene (Darstellungsebene) vorgestellt. Um das Bild zu vervollständigen, werden wir nun PSU-Endpunkte als Datenebene einführen.
Erstellung eines Endpunkts (auch Webdienst genannt)
Um die in Teil 1 entwickelten Informationen zur Dienstliste wirklich wiederverwendbar zu machen, hätten wir sie als Webdienst anstatt als App implementieren sollen. Das werden wir nun korrigieren:
- Klicken Sie im vertikalen PSU-Dashboard-Menü auf „APIs“ und anschließend auf „Endpunkte“. Nun werden alle Ihre selbstdefinierten Webdienste angezeigt (anfangs gibt es keine). Klicken Sie auf „Endpunkt erstellen“.
- Geben Sie im „URL“-Feld
get-serviceein und klicken Sie dann auf OK. Der Webdienst wird in der Liste angezeigt. - Klicken Sie auf das Stiftsymbol, um den Code-Editor zu öffnen. Sie können nun die Nutzeroberfläche sauber von den Daten trennen. Hier ist der Rohdatencode aus Teil 1:
function Get-ServiceProcessId {
[CmdletBinding()]
param
(
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='ServiceName')]
[string]
$Name,
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='ActualService')]
[System.ServiceProcess.ServiceController]
$Service,
[switch]
$PassThru
)
begin
{
$hash = Get-CimInstance Win32_Service -ErrorAction Ignore |
Group-Object -Property Name -AsHashTable -AsString
}
process
{
# since the user now has the option to either specify the service name
# or pipe in an actual service, let's look up the pendant so we always
# have both and can simplify the remaining code
if ($PSCmdlet.ParameterSetName -eq 'ServiceName')
{
$Service = Get-Service -Name $Name -ErrorAction Ignore
}
else
{
$Name = $Service.Name
}
# make sure "0" values are replaced by NULL
$id = $hash.$Name.ProcessId | Where-Object { $_ -gt 0 }
if ($PassThru)
{
# take the service and add the process ID
$Service |
Add-Member -MemberType NoteProperty -Name ProcessId -Value $id -PassThru
}
else
{
$id
}
}
}
# pipe in any object with a property "ProcessId" or "Id", and add a property with
# the actual process name
function Get-ProcessNameById {
[CmdletBinding()]
param
(
[Parameter(Mandatory, ValueFromPipeline)]
[Object]
$InputObject,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[int]
[Alias('Id')]
$ProcessId,
[switch]
$PassThru
)
process
{
$ProcessName = if ($ProcessId)
{
(Get-Process -Id $ProcessId).Name
}
if ($PassThru)
{
$InputObject |
Add-Member -MemberType NoteProperty -Name ProcessName -Value $ProcessName -PassThru
}
else
{
$ProcessName
}
}
}
# submit the name of a service, or pipe in services
# adds the "GroupName" property, exposing the name of the service group for
# the given service
function Get-ServiceGroupName
{
[CmdletBinding()]
param
(
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='ServiceName')]
[string]
$Name,
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='ActualService')]
[System.ServiceProcess.ServiceController]
$Service,
[switch]
$PassThru
)
begin
{
$hash = Get-CimInstance Win32_Service |
Group-Object -Property Name -AsHashTable -AsString
}
process
{
# since the user now has the option to either specify the service name
# or pipe in an actual service, let's look up the pendant so we always
# have both and can simplify the remaining code
if ($PSCmdlet.ParameterSetName -eq 'ServiceName')
{
$Service = Get-Service -Name $Name -ErrorAction Ignore
}
else
{
$Name = $Service.Name
}
# get the launch command for this service
$pathName = $hash.$Name.PathName
# look if the "-k" parameter was specified, followed by the service group name
# we are after (uses RegEx)
$groupName = if($pathName -match '-k\s+(\w+)')
{
$matches[1]
}
if ($PassThru)
{
# take the service and add the process ID
$Service |
Add-Member -MemberType NoteProperty -Name GroupName -Value $groupName -PassThru
}
else
{
$groupName
}
}
}
Get-Service -ErrorAction Ignore |
# ...add the service process ID...
Get-ServiceProcessId -PassThru |
# ...take only services that have a process ID...
Where-Object ProcessId |
# ...add the process name for the process ID...
Get-ProcessNameById -PassThru |
# ...add the service group name so we can differentiate svchost...
Get-ServiceGroupName -PassThru |
# ...sort by service displayname...
Sort-Object -Property DisplayName |
# select the properties to display in the table
Select-Object -Property DisplayName, Name, ProcessName, GroupName, ProcessId
- Klicken Sie auf das Speichersymbol. Klicken Sie anschließend oben auf „Testen“. Klicken Sie danach rechts auf „Aufrufen“, um Ihren Webdienst auszuführen und zu überprüfen, ob er funktioniert. Die Dienstinformationen sollten im JSON-Format angezeigt werden.
Standardmäßig ist Ihr erstellter Webdienst gesichert und erfordert eine Authentifizierung. Wir werden die Authentifizierungs- und Sicherheitsoptionen separat behandeln und deaktivieren die Authentifizierung deshalb zunächst:
- Klicken Sie in der Liste der Endpunkte im PSU-Dashboard auf das Zahnradsymbol Ihres Webdienstes.
- Öffnen Sie im Dialogfeld die Registerkarte „Sicherheit“ und deaktivieren Sie „Authentifizierung“. Klicken Sie anschließend auf OK.
Ihr Webdienst ist nun für alle zugänglich, auch für externe PowerShell-Sitzungen. Testen wir ihn, indem wir eine normale PowerShell-Konsole öffnen. Ihr Webdienst hat eine eindeutige URL – in unserem Beispiel: http://localhost:5000/get-service. Starten Sie einfach eine beliebige PowerShell-Konsole und führen Sie folgenden Befehl aus:
PS> Invoke-RestMethod -Uri http://localhost:5000/get-service
Der Webdienst gibt die Daten an Sie zurück – Mission erfüllt: Daten und Nutzeroberfläche sind nun getrennt.

Finale Dashboard-Version
Nachdem Sie nun wissen, wie man Daten klar von der Nutzeroberfläche trennt, können wir uns erneut unserer Dashboard-App zuwenden und uns ganz auf die Nutzeroberfläche konzentrieren. Wir sind nun nicht mehr auf externe Apps angewiesen, sondern richten alles auf die Nutzeroberfläche aus.
- Klicken Sie im Hauptmenü des PSU-Dashboards auf „Apps“ und dann auf das Untermenü „Apps“. Auf der rechten Seite sehen Sie nun Ihre PSU-Apps.
- Klicken Sie auf das Stiftsymbol in Ihrer „Dashboard1“-App, um den Code zu öffnen. Ersetzen Sie ihn mit dem neuen Code und klicken Sie anschließend auf das Speichersymbol.
function New-CockpitBackBar {
New-UDStack -Direction row -Spacing 2 -Children {
New-UDButton -Text '← Home' -OnClick {
Invoke-UDRedirect '/'
}
}
}
$Pages = & {
New-UDPage -Name 'Home' -Url '/' -Content {
New-UDTypography -Text 'My personal Dashboard' -Variant h6
New-UDButton -Text 'Services' -OnClick {
# do NOT go to a separate App, handle everything WITHIN this App (do NOT use -Native)
Invoke-UDRedirect '/list-services'
}
New-UDButton -Text 'Processes' -OnClick {
Invoke-UDRedirect '/list-processes'
}
New-UDButton -Text 'Something else' -OnClick {
Invoke-UDRedirect '/list-more'
}
}
# handle service list in a CHILD PAGE
New-UDPage -Name 'Services' -Url '/list-services' -Content {
New-CockpitBackBar
# get the data from a web service (reusable, modular, flexible)
$data = Invoke-RestMethod -Uri http://localhost:5000/get-service
New-UDTable -Data $data
}
New-UDPage -Name 'Processes' -Url '/list-processes' -Content {
New-CockpitBackBar
New-UDTypography -Text 'Here you could implement your own code to display something more useful.'
}
New-UDPage -Name 'More' -Url '/list-more' -Content {
New-CockpitBackBar
New-UDTypography -Text 'Here you could implement your own code to display something more useful.'
}
}
New-UDApp -Title 'My Cockpit' -Pages $Pages
Klicken Sie auf „App anzeigen“, um diese finale Version zu testen. Sie werden dieselben drei Schaltflächen sehen, aber wenn Sie dieses Mal auf „Dienste“ klicken, gelangen Sie direkt zu einer Unterseite mit der erweiterten Dienstliste. Zu diesem Zeitpunkt ist das Hamburger-Menü weiterhin verfügbar, Sie haben Zugriff auf die Schaltfläche „Startseite“, und die gesamte Komplexität der Erstellung der Dienstliste wird nun an einen Webdienst ausgelagert.

Nächster Teil
Nachdem wir nun geklärt haben, wie man Daten sauber von der Nutzeroberfläche trennen kann, haben Sie zwei der Hauptfunktionen von PSU kennengelernt: „Apps“, die als Präsentationsebene dienen, und „Endpunkte“, bei denen es sich im Wesentlichen um Webdienste handelt, die Rohdaten bereitstellen.
In den nächsten Teilen werden wir näher auf die Vorteile eingehen, die PSU mit diesem Ansatz bieten kann:
- Im Moment haben wir die Authentifizierung vollständig deaktiviert. In den folgenden Teilen werden wir alle Ihre Sicherheitsoptionen näher betrachten:
- wie Sie Webdienste unter verschiedenen Konten ausführen können,
- wie Sie eine Rechteerweiterung implementieren können und
- wie Sie normalen Nutzern ermöglichen können, privilegierte Aufgaben auszuführen, ohne ihnen Administratorrechte zu gewähren oder auf umständliche „Geheimnisse“ angewiesen zu sein.
- Bisher haben wir in unserer Nutzeroberfläche statische Daten angezeigt. In den nächsten Teilen werden wir uns mit dynamischen Nutzeroberflächen beschäftigen, die sich bei Wertänderungen selbst aktualisieren können, und mit vielem mehr.

Tobias Weltner
Aleksandar Nikolić
Adam Driscoll
