Dans cette deuxième partie de la série, nous allons séparer l’interface des données. Cette approche vous permet de créer des tableaux de bord élégants et puissants avec quelques lignes de code seulement, tout en tirant parti des services Web pour fournir les données nécessaires.
Comme vous le verrez à la fin, cette approche offre une flexibilité considérable et vous permet de relever facilement des défis qui, autrement, vous donneraient bien du fil à retordre.
Bref rappel : les applications PSU
Si vous avez suivi la partie 1, vous apprécierez immédiatement le premier avantage majeur que PSU offre : il agit comme un système de gestion et un dépôt pour votre code, ce qui vous permet de réutiliser facilement vos travaux antérieurs.
Par exemple, la « liste de services » créée dans la partie 1 est toujours disponible dans PSU. Chaque fois que vous avez besoin d’informations supplémentaires sur vos services, vous pouvez y accéder depuis le tableau de bord principal de PSU :
- Cliquez sur Apps dans la barre de menu verticale à gauche.
- Vous verrez toutes vos applications. Repérez le « Service Dashboard » créé dans la partie 1.
- Cliquez sur l’icône en forme de globe pour ouvrir l’application. Elle affichera vos services ainsi que les détails supplémentaires que nous avons mis en œuvre, comme le nom du groupe de services et le nom du processus sous-jacent.
N’oubliez pas que chaque application PSU est directement accessible via son URL unique; vous pouvez l’ouvrir en naviguant vers son lien, par exemple : http://localhost:5000/list-services/Home
Création d’un tableau de bord d’actions
Nous allons créer un tableau de bord central avec des boutons de navigation. Comme tout ce qui concerne une interface, les tableaux de bord sont simplement des applications PSU. Nous reprenons donc là où nous nous étions arrêtés :
- Cliquez sur « Apps » dans le menu vertical principal à gauche, puis cliquez sur le sous-élément « Apps ». Vous verrez maintenant la liste de vos applications existantes.
- Cliquez sur le bouton bleu « Create App » en haut pour ajouter une nouvelle application. Choisissez « Dashboard1 » comme nom et entrez « dashboard1 » comme URL. Cliquez ensuite sur OK.
- Cliquez sur l’icône en forme de crayon pour ouvrir l’éditeur de code. Remplacez le code d’exemple par le code suivant :
# 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
- Cliquez sur l’icône Enregistrer (avec le symbole de disquette). Cette étape est cruciale : vous devez enregistrer manuellement toute modification, sinon PSU ignorera vos changements.
- Cliquez sur le bouton « View App » pour l’exécuter. Vous verrez maintenant votre interface de tableau de bord, composée de trois boutons et d’un menu de navigation généré automatiquement dans le coin supérieur gauche.
Cet exemple illustre quelques concepts fondamentaux. Explorons d’abord l’interface, puis examinons les détails :
- Lorsque vous cliquez sur « Services », vous verrez l’application de liste de services créée dans la partie 1. Si vous obtenez un message d’erreur, c’est peut-être parce que vous avez sauté la partie 1 et que l’application de liste de services n’existe pas.
- Lorsque vous cliquez sur « Processes » ou « Something else », vous naviguez vers des pages qui font partie de votre application tableau de bord. Ces pages ne contiennent pas encore de contenu utile.
Appel d’applications externes
Le bouton « Services » montre comment intégrer des applications externes dans votre tableau de bord. En utilisant Invoke-UDRedirect '/list-services' avec le commutateur -Native, vous pouvez spécifier des URL relatives à la racine PSU principale et accéder à toute autre application créée précédemment.
Bien qu’il soit possible d’incorporer des applications externes de cette façon, ce n’est pas considéré comme une bonne pratique, car cela a un coût : vous perdez la navigation automatique. Le menu de navigation dans le coin supérieur gauche n’est plus disponible dans l’application externe. C’est normal, puisque vous naviguez effectivement vers une application complètement différente.
Appel de sous-pages
Les deux autres boutons fonctionnent différemment : ils naviguent vers des sous-pages de l’application tableau de bord. Ils utilisent le même applet de commande UDRedirect, mais sans le commutateur -Native, de sorte que l’URL fait référence à une sous-page interne créée dans votre tableau de bord à l’aide de New-UDPage.
Comme ces sous-pages font partie de votre application tableau de bord, la navigation automatique fonctionne comme prévu et le menu de navigation reste visible quelle que soit la page affichée.
Puisque vous définissez ces sous-pages dans votre application tableau de bord, vous avez un contrôle total et pouvez facilement ajouter des fonctionnalités d’interface de façon cohérente, par exemple en ajoutant un bouton « Retour à l’accueil » bien visible.
Voici un exemple. Remplacez simplement votre code (et n’oubliez pas de cliquer sur le bouton Enregistrer avant de visualiser l’application) :
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
Sur les sous-pages, vous trouverez maintenant un bouton « Home » qui vous ramène commodément à la racine du tableau de bord.

Qu’en est-il de la réutilisabilité du code?
L’utilisation des sous-pages implique des compromis : vous ne pouvez pas réutiliser le travail investi dans les applications précédentes. Pour que les sous-pages affichent du contenu utile, vous devez y ajouter vous-même le code approprié.
Cela nous amène à de meilleures stratégies de conception. Dans la partie 1, nous avons investi des efforts dans la création de listes de services complètes et les avons mises en œuvre comme une seule application. L’application fonctionnait comme prévu, mais comme vous pouvez le voir maintenant, elle n’est pas réutilisable. Une meilleure approche aurait été de séparer les données et l’interface en composants distincts — et c’est exactement ce que nous allons faire aujourd’hui.
Dans la partie 1, nous avons présenté les applications PSU comme la couche interface (présentation). Pour compléter le tableau, nous allons maintenant présenter les points de terminaison PSU comme la couche données.
Création d’un point de terminaison (alias service Web)
Pour que les informations sur la liste de services développées dans la partie 1 soient véritablement réutilisables, nous aurions dû les implémenter comme un service Web plutôt que comme une application. Corrigeons cela maintenant :
- Dans le menu vertical du tableau de bord PSU, cliquez sur « APIs », puis sur « Endpoints ». Vous verrez maintenant tous vos services Web personnalisés (initialement, il n’y en a aucun). Cliquez sur « Create Endpoint ».
- Dans le champ « URL », entrez
get-service, puis cliquez sur OK. Le service Web apparaîtra dans la liste. - Cliquez sur l’icône en forme de crayon pour ouvrir l’éditeur de code. Vous pouvez maintenant séparer clairement l’interface des données. Voici le code de données brutes de la partie 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 = (Get-Process -Id $ProcessId -ErrorAction Ignore).Name
if ($PassThru)
{
$InputObject |
Add-Member -MemberType NoteProperty -Name ProcessName -Value $processName -PassThru
}
else
{
$processName
}
}
}
# pipe in services and add a "GroupName" property that identifies the svchost group
function Get-ServiceGroupName {
[CmdletBinding()]
param
(
[Parameter(Mandatory, ValueFromPipeline)]
[System.ServiceProcess.ServiceController]
$Service,
[switch]
$PassThru
)
process
{
$groupName = (Get-CimInstance Win32_Service -Filter "Name='$($Service.Name)'" -ErrorAction Ignore).StartName
if ($PassThru)
{
$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
- Cliquez sur l’icône Enregistrer. Ensuite, en haut, cliquez sur « Test ». Cliquez ensuite sur « Invoke » à droite pour exécuter votre service Web et vérifier qu’il fonctionne. Vous devriez voir les informations sur les services affichées au format JSON.
Par défaut, le service Web que vous avez créé est sécurisé et nécessite une authentification. Nous aborderons les options d’authentification et de sécurité séparément; pour l’instant, nous allons désactiver l’authentification :
- Dans la liste des points de terminaison du tableau de bord PSU, cliquez sur l’icône engrenage de votre service Web.
- Dans la boîte de dialogue, ouvrez l’onglet « Security » et décochez « Authentication ». Cliquez ensuite sur OK.
Votre service Web est maintenant accessible à tous, y compris aux sessions PowerShell externes. Testons-le en ouvrant une console PowerShell ordinaire. Votre service Web possède une URL unique — dans notre exemple : http://localhost:5000/get-service. Lancez simplement n’importe quelle console PowerShell et exécutez :
PS> Invoke-RestMethod -Uri http://localhost:5000/get-service
Le service Web vous retourne les données — mission accomplie : les données et l’interface sont maintenant séparées.

Version finale du tableau de bord
Maintenant que vous savez comment séparer clairement les données de l’interface, nous pouvons revoir notre application tableau de bord en nous concentrant entièrement sur l’interface. Nous ne faisons plus appel à des applications externes; à la place, nous gardons tout axé sur l’interface.
- Dans le menu principal du tableau de bord PSU, cliquez sur « Apps », puis sur le sous-menu « Apps ». À droite, vous verrez vos applications PSU.
- Cliquez sur l’icône en forme de crayon dans votre application « Dashboard1 » pour ouvrir le code. Remplacez-le par le nouveau code, puis cliquez sur l’icône Enregistrer.
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
Cliquez sur « View App » pour tester cette version finale. Vous verrez les mêmes trois boutons, mais cette fois, lorsque vous cliquez sur « Services », vous naviguez de façon transparente vers une sous-page affichant la liste de services enrichie. À ce stade, le menu de navigation est toujours disponible, vous avez accès au bouton « Home » et toute la complexité de la création de la liste de services est maintenant déléguée à un service Web.

Prochaine partie
Maintenant que nous avons clarifié comment séparer proprement les données de l’interface, vous avez vu deux des principales fonctionnalités de PSU : les « Apps », qui servent de couche de présentation, et les « Endpoints », qui sont essentiellement des points de terminaison fournissant des données brutes sous forme de services Web.
Dans les prochaines parties, nous approfondirons les avantages que PSU peut offrir avec cette approche :
- Pour l’instant, nous avons entièrement désactivé l’authentification. Dans les prochaines parties, nous explorerons toutes vos options de sécurité :
- comment exécuter des services Web sous différents comptes,
- comment implémenter l’élévation de privilèges, et
- comment permettre aux utilisateurs ordinaires d’invoquer des tâches privilégiées sans leur accorder de droits d’administrateur ni avoir recours à des « secrets » maladroits.
- Pour l’instant, nous avons affiché des données statiques dans notre interface. Dans les prochaines parties, nous examinerons les interfaces dynamiques qui se mettent à jour automatiquement à mesure que les valeurs changent, et bien plus encore.

Tobias Weltner
Aleksandar Nikolić
Steven Lafortune