Hovedmenu
Devolutions-blog

Nyheder, opdateringer og indsigter fra Devolutions.

PowerShell Universal dashboard separating UI apps from data endpoints.

Adskil brugergrænseflade og data i PSU: Byg apps og endpoints på den rigtige måde

Den her anden artikel i PSU-serien viser, hvordan du adskiller brugergrænseflade fra data ved at kombinere Apps og Endpoints i PowerShell Universal. Lær, hvordan du bygger genbrugelige dashboards, eksponerer data via webtjenester og skaber renere, mere skalerbare automatiseringsarbejdsgange.

I den her anden del af serien adskiller vi brugergrænsefladen fra dataene. Det lader dig bygge elegante og stærke UI-dashboards med blot nogle få linjer kode, samtidig med at du bruger webtjenester til at levere de nødvendige data.

Som du kan se til sidst, giver den tilgang enorm fleksibilitet og lader dig tage udfordringer med ro — udfordringer, der ellers typisk giver mange hovedpiner.

Kort opsummering: PSU-apps

Hvis du fulgte del 1, sætter du straks pris på den første store fordel, som PowerShell Universal giver: den fungerer som et administrationssystem og et lager for din kode, så du nemt kan genbruge tidligere arbejde.

For eksempel findes den „tjenesteliste“, vi oprettede i del 1, stadig i PSU. Når du har brug for mere information om dine tjenester, kan du tilgå den via det centrale PSU-dashboard:

  • Klik på Apps i den lodrette menulinje til venstre.
  • Du kan se alle dine apps. Find „Service Dashboard“, som blev oprettet i del 1.
  • Klik på globusikonet for at åbne appen. Den viser dine tjenester sammen med de ekstra detaljer, vi implementerede, for eksempel Service Group Name og navnet på den underliggende proces.

Husk, at hver PSU-app kan tilgås direkte via sin unikke URL, så du kan åbne den ved at gå til linket, for eksempel: http://localhost:5000/list-services/Home

Byg et handlingsdashboard

Vi bygger et centralt dashboard med navigationsknapper. Som alt andet med en brugergrænseflade er dashboards bare PSU-apps, så vi starter der, hvor vi slap sidst:

  • Klik på „Apps“ i den lodrette hovedmenu til venstre, og klik derefter på underelementet „Apps“. Du kan nu se en liste over dine eksisterende apps.
  • Klik på den blå knap „Create App“ øverst for at tilføje en ny app. Vælg „Dashboard1“ som navn, og indtast „dashboard1“ som URL. Klik derefter på OK.
  • Klik på blyantikonet for at åbne kodeeditoren. Erstat eksempelkoden med følgende kode:
# 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
  • Klik på gem-ikonet (med diskette-symbolet). Det her trin er afgørende: du skal gemme alle rettelser manuelt, ellers ignorerer PSU dine ændringer.
  • Klik på knappen „View App“ for at køre den. Du kan nu se dashboardets brugergrænseflade, som består af tre knapper og en automatisk genereret hamburgermenu i øverste venstre hjørne.

Eksemplet illustrerer nogle kernebegreber, så lad os først udforske grænsefladen og derefter se på detaljerne:

  • Når du klikker på „Services“, ser du den tjenesteliste-app, vi oprettede i del 1. Hvis du får en fejlmeddelelse, har du måske sprunget del 1 over, så den app, der viser tjenester, findes ikke.
  • Når du klikker på „Processes“ eller „Something else“, navigerer du til sider, der er en del af din dashboard-app. De sider er endnu ikke fyldt med meningsfuldt indhold.

Kald af eksterne apps

Knappen „Services“ viser, hvordan du kan indlejre eksterne apps i dit dashboard. Ved at bruge Invoke-UDRedirect '/list-services' med parameteren -Native kan du angive URL’er relativt til PSU-roden og tilgå enhver anden app, du har oprettet tidligere.

Det er muligt at indarbejde eksterne apps på den måde, men det regnes ikke for god praksis, fordi det har en pris: du mister den automatiske navigation. Hamburgermenuen i øverste venstre hjørne er ikke længere tilgængelig i den eksterne app. Det er forventet, fordi du i praksis navigerer til en helt anden app.

Kald af undersider

De to andre knapper fungerer anderledes: de navigerer til undersider i dashboard-appen. De bruger den samme cmdlet UDRedirect, men uden parameteren -Native, så URL’en henviser til en intern underside, der er oprettet i dit dashboard med New-UDPage.

Fordi de undersider er en del af din dashboard-app, virker den automatiske navigation som forventet, og hamburgermenuen forbliver synlig, uanset hvilken side der vises.

Da du definerer de undersider i din dashboard-app, har du fuld kontrol og kan nemt tilføje UI-funktioner på en ensartet måde — for eksempel ved at tilføje en mere synlig knap „Back to Home“.

Her er et eksempel. Erstat blot din kode (og husk at klikke på knappen Save, før du får vist appen):

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

Nu finder du på undersiderne en knap „Home“, der bekvemt fører dig tilbage til dashboardets rod.


Hjem-knap

Hvad med genbrug af kode?

Der følger kompromiser med undersider: du kan ikke genbruge det arbejde, du har lagt i tidligere apps. For at undersider skal vise nyttigt indhold, skal du selv tilføje den relevante kode.

Det fører os til bedre designstrategier. I del 1 lagde vi kræfter i at lave omfattende tjenestelister og implementerede dem som én enkelt app. Appen virkede som forventet, men som du kan se nu, kan den ikke genbruges. En bedre tilgang ville have været at adskille data og brugergrænseflade i særskilte komponenter — og det er præcis det, vi gør i dag.

I del 1 introducerede vi PSU-apps som UI-laget (præsentationslaget). For at fuldende billedet introducerer vi nu PSU-endpoints som datalaget.

Opret et endpoint (også kaldet webtjeneste)

For at gøre tjenestelisteinformationen fra del 1 virkelig genbrugelig burde vi have implementeret den som en webtjeneste i stedet for en app. Lad os rette det nu:

  • I den lodrette menu i PSU-dashboardet klikker du på „APIs“ og derefter på „Endpoints“. Du kan nu se alle dine selvdefinerede webtjenester (til at begynde med er der ingen). Klik på „Create Endpoint“.
  • I feltet „URL“ indtaster du get-service og klikker derefter på OK. Webtjenesten vises på listen.
  • Klik på blyantikonet for at åbne kodeeditoren. Nu kan du adskille brugergrænseflade og data rent. Her er rådatakoden fra del 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
  • Klik på ikonet Save. Klik derefter øverst på „Test“. Klik derefter på „Invoke“ i højre side for at køre din webtjeneste og kontrollere, at den virker. Du bør se tjenesteoplysningerne vist i JSON-format.

Som standard er den webtjeneste, du har oprettet, sikret og kræver godkendelse. Vi gennemgår godkendelse og sikkerhedsindstillinger for sig, så indtil videre slår vi godkendelse fra:

  • På listen over endpoints i PSU-dashboardet klikker du på tandhjulsikonet for din webtjeneste.
  • I dialogboksen åbner du fanen „Security“ og fjerner markeringen i „Authentication“. Klik derefter på OK.

Din webtjeneste er nu tilgængelig for alle, også eksterne PowerShell-sessioner. Lad os teste den ved at åbne en almindelig PowerShell-konsol. Din webtjeneste har en unik URL — i vores eksempel: http://localhost:5000/get-service. Start blot en PowerShell-konsol, og kør:

PS> Invoke-RestMethod -Uri http://localhost:5000/get-service

Webtjenesten returnerer dataene til dig — missionen er fuldført: data og brugergrænseflade er nu adskilt.


Webtjeneste i PowerShell

Den endelige dashboardversion

Nu hvor du ved, hvordan du tydeligt adskiller data fra brugergrænseflade, kan vi vende tilbage til dashboard-appen og fokusere helt på brugergrænsefladen. Vi støtter os ikke længere til eksterne apps. I stedet holder vi alt fokuseret på brugergrænsefladen.

  • I hovedmenuen i PSU-dashboardet klikker du på „Apps“ og derefter på undermenuen „Apps“. Til højre kan du se dine PSU-apps.
  • Klik på blyantikonet i din app „Dashboard1“ for at åbne koden. Erstat den med den nye kode, og klik derefter på ikonet Save.
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

Klik på „View App“ for at prøvekøre den endelige version. Du ser de samme tre knapper, men den her gang, når du klikker på „Services“, navigerer du gnidningsfrit til en underside, der viser den udvidede tjenesteliste. På det tidspunkt er hamburgermenuen stadig tilgængelig, du har adgang til knappen „Home“, og al kompleksiteten ved at oprette tjenestelisten er nu lagt over i en webtjeneste.


Tjenester

Næste del

Nu hvor vi har afklaret, hvordan du rent adskiller data fra brugergrænseflade, har du set to af hovedfunktionerne i PSU: „Apps“, som fungerer som præsentationslag, og „Endpoints“, som i bund og grund er webtjenester, der leverer rådata.

I de kommende dele dykker vi dybere ned i de fordele, PSU kan give med den tilgang:

  • Indtil videre har vi slået godkendelse helt fra. I de kommende dele gennemgår vi alle dine sikkerhedsmuligheder:
    • hvordan du kan køre webtjenester under forskellige konti,
    • hvordan du implementerer rettighedsforhøjelse, og
    • hvordan du lader almindelige brugere køre privilegerede opgaver uden at give dem administratorrettigheder eller være afhængig af kluntede „secrets“.
  • Indtil videre har vi vist statiske data i vores UI-frontend. I de kommende dele ser vi på dynamiske brugergrænseflader, der kan opdatere sig selv, når værdier ændrer sig, og meget mere.

Klar til at følge med? Download PowerShell Universal.