Hovedmenu
Devolutions-blog

Nyheder, opdateringer og indsigter fra Devolutions.

Unlock powershell universal code snippets web apps devolutions blog

Lås PowerShell Universal op: Fra kodestykker til webapps

Artiklen gennemgår centrale teknikker til ydelse i PowerShell og viser derefter, hvordan du bringer dem til live i PowerShell Universal. Til sidst har du et fuldt fungerende webdashboard til dine scripts — tilgængeligt fra enhver browser.

Velkommen til vores nye PowerShell-serie om PowerShell-tricks, og hvordan du indarbejder dem i PowerShell Universal. Sådan fungerer serien:

  • Første del af hver artikel handler om ren PowerShell og giver dig overraskende tricks og kodeeksempler, der løfter din PowerShell-viden.
  • Anden del bruger det gratis niveau af PowerShell Universal til at bygge dit personlige PowerShell-dashboard, så du nemt kan bruge din PowerShell-kode uden at skulle finde scripts og starte konsoller.

PowerShell Universal (PSU)

Hvis du er ny i PowerShell Universal, er det her det perfekte sted at starte. PowerShell Universal indlejrer PowerShell-kode som en webserver, og med få klik kan du begynde at bygge dine egne dashboards med brugerflade. Det supplerer din eksisterende PowerShell-viden og virker på tværs af platforme, både med Windows PowerShell og PowerShell 7. Med det bygger du dashboards og værktøjer uden kompleks WPF, WinForms eller andre teknikker til brugerflader.

Skaler PowerShell-funktioner

Lad os først se på noget PowerShell-kode og bruge et par tricks og teknikker til at forbedre den.

Har du nogensinde villet finde ud af, hvilken proces der er vært for en bestemt systemtjeneste, for så at opdage, at de alle vises som “svchost.exe” i Process Monitor eller Process Explorer?

Løsningen involverer to PowerShell-funktioner, der henter tjenesternes proces-id’er og navne på tjenestegrupper, blandt meget andet. De virker fremragende, men de er langsomme i stor skala. Se selv:

function Get-ServiceProcessId
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string] $Name
    )

    process
    {
        $svc = Get-CimInstance Win32_Service -Filter "Name='$Name'"
        if ($svc -and ($svc.ProcessId -ne 0))
        {
            $svc.ProcessId
        }
    }
}

Denne fremragende funktion tager et procesnavn og returnerer dets proces-id (hvis det findes):

PS C:\> Get-ServiceProcessId -Name spooler
4784

Da funktionen understøtter pipeline, kan du skalere den til mange processer:

PS C:\> Get-Service | Get-ServiceProcessId
5524
1960
(...)

I dette kald dumper Get-Service alle tjenester, og Get-ServiceProcessId binder automatisk egenskaben “Name” til parameteren “Name” (ValueFromPipelineByPropertyName). Da funktionskoden ligger i en process {}-blok, gentages koden for hvert pipeline-element. At skrive den slags pipeline-bevidste funktioner er ikke raketvidenskab: Markér en parameter med ValueFromPipeline eller ValueFromPipelineByPropertyName, og læg koden i process {} — færdig. Din funktion skalerer nu automatisk.

Kaldet er dog pinefuldt langsomt: Proces-id’erne kommer drypvis ind med sekunders forsinkelse. Det skyldes, at funktionskoden spørger WMI for hver eneste tjeneste, og den WMI-forespørgsel tager tid.

Optimér ydelsen

Det ydelsesproblem, du lige har set, er almindeligt i mange scripts. Hver gang du henter oplysninger, uanset om det er WMI, Active Directory, databaser eller noget andet, skalerer koden dårligt.

En nem forbedring i disse tilfælde er at oprette opslagstabeller. PowerShell har allerede en cmdlet, der gør det for dig. Se selv:

function Get-ServiceProcessId
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string] $Name
    )

    begin
    {
        $hash = Get-CimInstance Win32_Service -ErrorAction Ignore |
                Group-Object -Property Name -AsHashTable -AsString
    }

    process
    {
        $id = $hash.$Name.ProcessId
        if ($id) { $id }
    }
}

Funktionen er lynhurtig: Når du spørger på flere tjenester, kører din WMI-forespørgsel kun én gang. Efter den indledende forsinkelse fra forespørgslen dumpes de ønskede PID’er uden gentagne pauser.

Det skyldes, at vi har tilføjet en begin {}-blok til funktionen. Den del køres kun én gang, som forberedelse, før noget andet kører. I blokken spørger vi WMI om alle tjenester, og det tager reelt ikke længere tid end at spørge om en enkelt tjeneste.

For senere at finde oplysningerne om en bestemt tjeneste sendes forespørgselsresultaterne videre til Group-Object. Cmdletten er perfekt til at bygge opslagstabeller, fordi den understøtter parametrene -AsHashtable og -AsString, som automatisk opretter en hashtabel, der er indekseret efter den egenskab, du angav. Lyder det komplekst? Det er det ikke. Se selv:

PS C:\> $hashtable = Get-CimInstance Win32_Service | Group-Object -Property Name -AsHashTable -AsString
PS C:\> $hashtable.Spooler.processId
4784
PS C:\> $hashtable.WSearch.ProcessId
10152
PS C:\> $hashtable.WSearch
ProcessId Name    StartMode State   Status ExitCode
--------- ----    --------- -----   ------ --------
10152     WSearch Auto      Running OK     0

Med hashtabellen fra Group-Object er nøglenavnene nu tjenestenavnene. Når du angiver et bestemt tjenestenavn, får du tjenesteobjektet. Når du tilføjer en bestemt egenskab, altså .ProcessId, får du proces-id’et direkte.

Lad os tage et eksempel mere, så det sætter sig, før vi går videre:

PS C:\> $hashtable = Get-Service | Group-Object -Property Status -AsHashTable -AsString
PS C:\> $hashtable.Running.Count
156
PS C:\> $hashtable.Stopped.Count
170
PS C:\> $hashtable.Running
Status   Name               DisplayName
------   ----               -----------
Running  AdobeARMservice    Adobe Acrobat Update Service
Running  AppIDSvc           Application Identity
Running  Appinfo            Application Information
(...)

Denne gang grupperede vi efter egenskaben “Status”, og vi kan nu bruge enhver af Status-konstanterne til at slå op i hashtabellen. Da der er mere end én tjeneste med statussen “Running” eller “Stopped”, returnerer hver nøgle i hashtabellen denne gang mere end én tjeneste. Det er nu banalt at tælle antallet af kørende eller stoppede tjenester eller dumpe alle kørende tjenester. At gruppere med hashtabeller er meget fleksibelt og ekstremt stærkt.

Når du tænker over det et øjeblik, rejser det en anden udfordring: Nu hvor du kan skalere funktionen og hurtigt hente proces-id’er for mange tjenester, hvor meget er en liste af tal så værd, hvis det oprindelige tjenestenavn, som et bestemt proces-id hører til, er gået tabt?

Tilføj oplysninger

For at tilføje oplysninger i stedet for at erstatte dem har PowerShell endnu en cmdlet netop til det formål. Denne forbedrede version kan tilføje proces-id’et med den nye parameter -PassThru:

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

        }

    }

}

Du finder parameteren -PassThru på en del PowerShell-cmdlets, og når den er der, ved du, at du kan vælge enten at udskrive en nyberegnet værdi eller tilføje den nye værdi til det oprindelige input. Det forstås bedst, når du ser på resultaterne. Som standard er intet ændret, og du får stadig tjenesternes proces-id’er:

PS C:\> Get-Service | Get-ServiceProcessId
5524
1960
(...)

Hvis du angiver -PassThru, får du de oprindelige inputobjekter (de tjenester, du sendte gennem pipelinen):

PS C:\> Get-Service | Get-ServiceProcessId -PassThru

Status   Name               DisplayName
------   ----               -----------
Stopped  AarSvc_78962bb     Agent Activation Runtime_78962bb
Running  AdobeARMservice    Adobe Acrobat Update Service
Stopped  ADPSvc             Aggregated Data Platform Service
(...)

Objekterne har nu en ny egenskab ved navn ProcessId, som du kan bruge til sortering. For at gøre den synlig bruger du Select-Object:

PS C:\> Get-Service | Get-ServiceProcessId -PassThru | Sort-Object ProcessId -Descending | Select-Object -Property DisplayName, ProcessId

DisplayName                                           ProcessId
-----------                                           ---------
Web Threat Defense Service                                19084
Diagnostic System Host                                    18580
Benutzerdienst für die Plattform für verbundene Ge...     17608
WebClient                                                 16616
Quality Windows Audio Video Experience                    15328
(...)

Og sådan virker det:

For at en funktion kan sende oplysninger videre, skal den have adgang til det oprindelige inputobjekt. Derfor har den nye funktion nu to parametre, der udelukker hinanden: -Name (angiv et tjenestenavn som “Spooler”) og -Service (angiv en faktisk instans af en tjeneste). Internt finder funktionen ud af, hvilke oplysninger brugeren gav, og beregner det, der mangler.

Når brugeren angiver -PassThru, sendes tjenesteobjektet til Add-Member. Add-Member kan dynamisk tilføje nye egenskaber til ethvert objekt. I eksemplet tilføjer den en ny “NoteProperty” (statisk værdi) ved navn ProcessId og gemmer det nyhentede proces-id i egenskaben. Det er det hele.

Når du tager teknikken til dig, kan du designe koden langt mere modulært, som vi ser næste gang.

Fleksibelt system som Lego

Til øvelse tilføjer vi to funktioner mere, der finder procesnavnet for et givet proces-id og navnet på tjenestegruppen. Det er den samme grundlæggende tilgang med de samme ingredienser, vi lige har gennemgået.

Tilføj procesnavn

Her er en modulær PowerShell-funktion, der henter procesnavnet i klartekst fra ethvert objekt, der har en egenskab ved navn ProcessId eller Id af typen integer:

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

        }

    }

}

Lad os hurtigt gennemgå de PowerShell-kodetricks, der er brugt her:

  • param()-blokken refererer til inputobjektet to gange: Det ligger uændret i $InputObject (så det kan sendes videre senere, hvis der bliver bedt om det), og dets proces-id ligger i $ProcessId. PowerShell klarer det svære og finder den rigtige egenskab. Du angiver bare den ønskede type ([int] i eksemplet) og gyldige navne (ProcessId, Id). [Alias()] tager en kommasepareret liste, så du kan angive så mange egenskabsnavne, du vil. Alle er gyldige for parameteren, og den første vinder.
  • Hvis der blev fundet et proces-id i inputobjektet, ligger det i $ProcessId. Koden bruger et simpelt If til at oversætte det til procesnavnet. Hvis $ProcessId derimod er NULL (fordi egenskaben manglede på inputobjektet eller var tom), er $ProcessName NULL. Tricket er at tildele $ProcessName til If og ikke tildele den inde i If-sætningen, så den bliver nulstillet, når If-betingelsen ikke er opfyldt. Se, hvor smukt og generelt den nye funktion kan bruges. Du kan bruge den med ethvert inputobjekt, der har en egenskab ProcessId eller Id. Find for eksempel navnet på din aktuelle PowerShell-vært:
PS C:\> Get-Process -id $pid | Get-ProcessNameById
powershell_ise

Eller brug den på vores tjenester, og få en liste over tjenester, deres underliggende processer og proces-id’er:

PS C:\> Get-Service | Get-ServiceProcessId -PassThru | Get-ProcessNameById -PassThru | Where-Object ProcessId | Select-Object -Property Name, ProcessName, ProcessId | select -First 5

Name                 ProcessName ProcessId
----                 ----------- ---------
AdobeARMservice      armsvc           5524
AppIDSvc             svchost          1960
Appinfo              svchost         13852
AudioEndpointBuilder svchost          4324
Audiosrv             svchost          4456
(...)

Tjenestegrupper

Som du måske har lagt mærke til, deler mange tjenester den samme proces, og der er mange processer med identiske navne (“svchost”). Det er med vilje. I Windows kan mange tjenester køre inde i den samme DLL.

For at hente deres tjenestegruppe kan vi genbruge Get-ServiceProcessId. Denne gang læser vi ikke proces-id’et fra WMI-objekterne, men PathName:

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 parameter "-k" 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

        }

    }

}

Og fordi vi brugte modulær, pipeline-bevidst kode, får vi endnu en Lego-klods, som vi kan kombinere med de andre efter behov:

PS C:\> Get-Service | Get-ServiceProcessId -PassThru | Get-ProcessNameById -PassThru | Get-ServiceGroupName -PassThru | Where-Object ProcessId | Select-Object -Property Name, ProcessName, GroupName, ProcessId

Name                 ProcessName GroupName                     ProcessId
----                 ----------- ---------                     ---------
AdobeARMservice      armsvc                                         5524
AppIDSvc             svchost     LocalServiceNetworkRestricted      1960
Appinfo              svchost     netsvcs                           13852
AudioEndpointBuilder svchost     LocalSystemNetworkRestricted       4324
Audiosrv             svchost     LocalServiceNetworkRestricted      4456
(...)

PowerShell Universal til undsætning

Du har måske brugt et par timers arbejde og skabt fremragende PowerShell-scripts, der giver dig de oplysninger, du har brug for. Men husker du det hele næste uge? Næste måned? Hvad hvis du bare skal bruge oplysningerne i produktion? Eller du vil have en kollega i helpdesk til at se dem uden at skulle introducere vedkommende til konsoller og PowerShell-scripts?

Det er her, PowerShell Universal kommer ind. Det er helt gratis til personlig brug, og hvis du vil bruge det kommercielt, licenseres det økonomisk pr. server, ikke pr. bruger.

Kort sagt indlejrer PowerShell Universal en PowerShell-vært i en webserver. Det gør det muligt at bygge dashboards med en rig brugerflade på en sikker måde, gøre scripts til tjenester og webtjenester og meget mere.

Indtil videre vil vi bruge den gratis personlige udgave til at bygge et rigt dashboard, hvor vi kan slå de tjenesteoplysninger op, vi lige har hentet. Husk, at det her bare var et eksempel. Du kan selvfølgelig erstatte vores kode med noget, der betyder mere for dig, og vise VMWare-servere, billedsamlinger eller brugerkonti på samme måde.

Sæt PSU op

Her er de få trin, det tager at sætte PSU op på din egen maskine. Ingen licens, ingen omkostning, ingen administratorrettigheder kræves. Du kan bruge PowerShell Universal-modulet til at installere Universal-serveren. For at installere modulet bruger du Install-Module eller Install-PSResource.

Install-Module Devolutions.PowerShellUniversal

For at installere Universal-serveren kan du bruge Install-PSUServer.

Install-PSUServer -LatestVersion

Når du kører kommandoen, er effekten forskellig alt efter operativsystemet:

  • På Windows opretter og starter den en Windows-tjeneste på din maskine.
  • På Linux opretter og starter den en systemd-tjeneste.
  • På macOS downloader og pakker den PowerShell Universal-serveren ud. For flere installationsmuligheder henviser vi til dokumentationen på Installation | PowerShell Universal .

Opret en PSU-app

For at give scriptet en brugerflade går du til http://localhost:5000 for at åbne PSU-administrationskonsollen. Du skal logge ind med den Devolutions-konto, du oprettede tidligere.

I venstre side klikker du på “Apps” og derefter “App”. I højre side ser du nu en liste over apps, du har oprettet tidligere. Til at begynde med er listen tom.

Klik på “Create App”. Giv den et venligt navn, for eksempel “Service Dashboard”. I feltet nedenunder angiver du “URL” til din app. Det skal være et relativt navn med en skråstreg “/” foran, for eksempel “/list-services”. Du kan lade “Description” stå tom. Sørg for, at skyderen “Definition” viser “Code” og ikke “Command”. Klik derefter på OK. Den nye app vises på din liste over apps.

Klik derefter på blyantsikonet. Det åbner den integrerede PowerShell-editor. Standardkoden er denne:

New-UDApp -Content { 'Hello, World!' }

For at prøvekøre appen klikker du på knappen “View App” øverst. Dit webbaserede PowerShell-script kører, og du ser en venlig besked i browseren. Du ser også, hvordan du kan tilgå appen: URL’en er http://localhost:5000/list-services/Home.

Forbehold

Før vi går videre, er der to vigtige ting at huske:

  • Standardeksemplet bruger New-UDApp {} til at vise en simpel besked. Cmdletten skal altid være der. Den opretter appen. Alle dine tilpasninger sker kun inde i tuborgklammerne.
  • Du kan altid prøvekøre koden ved at klikke på knappen “View App” øverst på editorsiden, men du skal klikke på SAVE-ikonet først. “View App” gemmer ikke automatisk dine seneste ændringer. Hvis du glemmer at klikke på SAVE, ignorerer “View App” dine seneste kodeændringer.

Vis en tabel

Erstat standardkoden inde i tuborgklammerne med denne kode:

New-UDApp -Content {

    # 1.) prepare the data to show

    # get all services...
    $data = Get-Service -ErrorAction Ignore  |
    Sort-Object -Property DisplayName |
    # ...select the properties to display in the table
    Select-Object -Property DisplayName, Name, StartMode, StartType

    # 2.) output it using one of the many available UI components, i.e. as a table
    New-UDTable -Data $data

}

Koden henter nogle oplysninger og gemmer dem i $data. Derefter kalder den en af de mange UD…-cmdlets for at vise dataene i din webapp. New-UDTable opretter for eksempel automatisk en tabel til dig.

Når du klikker på “View App”, kan du vise tabellen i browseren:

Du har måske lagt mærke til, at vi tilføjede -ErrorAction Ignore til koden. Det skyldes, at PSU som standard kører din kode i PowerShell 7. PowerShell 7 opfører sig en smule anderledes end Windows PowerShell, og Get-Service kan kaste undtagelser på systemer, der ikke er sat til US. Hver gang der rejses undtagelser i koden, viser appen fejlbeskederne. For at undgå det skal du altid sørge for, at koden ikke udsender ubehandlede undtagelser.

Tjenestevisning

Nu hvor du har set, hvor enkelt det er at tilføje en grafisk, browserbaseret brugerflade til koden, samler vi det hele og opretter vores udvidede visning af tjenesteprocesser. Erstat koden med den kode, vi oprettede tidligere:

# first, define the functions we created
# these functions could also be stored in external modules so they are
# out of the way, but for now we keep everything as a single script


# submit name of service or pipe in services
# adds the process ID of the process that executes a service
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 property "GroupName", 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 parameter "-k" 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

        }

    }

}


# NOW we are defining our web app
# thanks to our modular functions, this part is short and simple to
# understand and modify
New-UDApp -Content {

  # 1.) prepare the data to show

  # retrieve all services (make sure to catch any error)
  $data = 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

   # 2.) output it using one of the many available UI components, i.e. as a table
  New-UDTable -Data $data

}

I begyndelsen finder du de tre funktioner, vi oprettede tidligere. I en senere del af serien flytter vi funktionerne af vejen og lægger dem i eksterne moduler, men indtil videre er alt defineret i koden, så vi ikke har nogen afhængigheder.

Derefter vises de hentede data med den samme New-UDTable, du brugte tidligere. Når du klikker på “View App”, køres koden, tjenesteoplysningerne hentes, og en tabel præsenterer dataene for dig.

Det eneste, du behøver, er URL’en til webappen: http://localhost:5000/list-services/Home. Du kan gemme den som et link på skrivebordet eller bogmærke den i browseren. Hver gang du har brug for tjenesteoplysninger, behøver du ikke længere lede efter scripts og køre dem i en konsol.

Og når det synker ind, ser du de enorme muligheder: Du kan bygge dine personlige dashboards. Men med en virksomhedslicens og ved at sætte PSU op med administratorrettigheder kan du lige så godt hoste dine scripts som en tjeneste og give browserbaseret adgang med detaljeret sikkerhedsstyring til alle i organisationen.

Vi når derhen i løbet af serien. Indtil videre har du set, hvordan du sætter PSU op og opretter din første PSU-webapp. I næste del starter vi igen med PowerShell-kode og udvider derefter dashboardet med nye kontroller og flere muligheder. Vi forklarer også, hvorfor tjenestetabellen, du lige har oprettet, viser kolonnerne i en noget uventet rækkefølge, og hvordan du retter det.

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