MAIN MENU
Blog di Devolutions

Annunci, aggiornamenti e approfondimenti di Devolutions.

Unlock powershell universal code snippets web apps devolutions blog

Sfruttare al massimo PowerShell Universal: dai frammenti di codice alle app web

Questo articolo illustra le principali tecniche di performance di PowerShell e mostra poi come metterle in pratica in PowerShell Universal. Alla fine, avrà a disposizione una dashboard web completamente funzionale per i suoi script, accessibile da qualsiasi browser.

Benvenuti nella nostra nuova serie su PowerShell, dedicata ai trucchi di PowerShell e a come integrarli in PowerShell Universal. Ecco come funziona questa serie:

  • La prima parte di ogni articolo tratta tecniche PowerShell “pure” e presenta trucchi sorprendenti ed esempi di codice che portano la sua conoscenza di PowerShell a un livello superiore.
  • La seconda parte utilizza il livello gratuito di PowerShell Universal per costruire la sua dashboard PowerShell personale, così può utilizzare facilmente il suo codice PowerShell senza dover cercare script ed eseguire console.

PowerShell Universal (PSU)

Se PowerShell Universal è una novità per Lei, questo è il punto di partenza perfetto. PowerShell Universal incorpora il codice PowerShell come server web e, con pochi clic, può iniziare a creare le proprie dashboard grafiche. Si integra perfettamente con la conoscenza di PowerShell che già possiede e funziona su più piattaforme, sia con Windows PowerShell che con PowerShell 7. Con esso, può creare dashboard e strumenti senza dover ricorrere a WPF, WinForms o altre tecniche grafiche complesse.

Scalare le funzioni PowerShell

Diamo prima un’occhiata a un po’ di codice PowerShell e applichiamo alcuni trucchi e tecniche per migliorarlo.

Ha mai voluto identificare quale processo ospita un determinato servizio di sistema, solo per rendersi conto che tutti compaiono come “svchost.exe” in Process Monitor o Process Explorer?

La soluzione coinvolge due funzioni PowerShell che recuperano gli ID di processo dei servizi e i nomi dei gruppi di servizi, tra molte altre cose. Funzionano magnificamente, ma sono lente su larga scala. Diamo un’occhiata:

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
        }
    }
}

Questa fantastica funzione riceve il nome di un processo e restituisce il suo ID di processo (se disponibile):

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

Poiché questa funzione è compatibile con la pipeline, può scalarla su molti processi:

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

In questa chiamata, Get-Service restituisce tutti i servizi, e Get-ServiceProcessId associa automaticamente la proprietà “Name” al suo parametro “Name” (ValueFromPipelineByPropertyName). Poiché il codice della funzione è incorporato in un blocco process {}, questo codice viene ripetuto per ogni elemento della pipeline. Scrivere funzioni di questo tipo, compatibili con la pipeline, non è affatto complicato: basta designare un parametro con ValueFromPipeline o ValueFromPipelineByPropertyName e incorporare il codice in process {} – fatto. La sua funzione ora scala automaticamente.

Tuttavia, la chiamata è terribilmente lenta: gli ID di processo arrivano uno alla volta con secondi di ritardo. Questo perché il codice della funzione interroga WMI per ogni singolo servizio, e questa query WMI richiede tempo.

Ottimizzare le performance

Il problema di performance che ha appena visto è comune in molti script. Ogni volta che interroga informazioni, sia che si tratti di WMI, Active Directory, database o altro, il codice non scala bene.

Un miglioramento semplice per questi scenari è creare tabelle di lookup. PowerShell offre già un cmdlet che lo fa per Lei. Diamo un’occhiata:

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 }
    }
}

Questa funzione è velocissima: quando interroga più servizi, la query WMI viene eseguita solo una volta. Dopo il ritardo iniziale causato dalla query, i PID richiesti vengono restituiti senza ulteriori ritardi.

Questo perché abbiamo aggiunto un blocco begin {} alla funzione. Questa parte viene eseguita solo una volta, come preparazione, prima che venga eseguito qualsiasi altro codice. In questo blocco, interroghiamo WMI per tutti i servizi, il che non richiede più tempo che chiedere informazioni su un solo servizio.

Per poter successivamente trovare le informazioni relative a un particolare servizio, i risultati della query vengono passati a Group-Object. Questo cmdlet è perfetto per costruire tabelle di lookup perché supporta i parametri -AsHashtable -AsString, che creano automaticamente una hashtable indicizzata in base alla proprietà specificata. Sembra complicato? Non lo è. Diamo un’occhiata:

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

Con la hashtable generata da Group-Object, i nomi delle chiavi sono ora i nomi dei servizi. Quando specifica un nome di servizio particolare, ottiene l’oggetto del servizio. Quando aggiunge una proprietà specifica, per esempio .ProcessId, ottiene direttamente il suo ID di processo.

Facciamo un altro esempio, in modo che il concetto sia chiaro prima di andare avanti:

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
(...)

Questa volta abbiamo raggruppato in base alla proprietà “Status”, e ora possiamo utilizzare qualsiasi costante di Status per interrogare la hashtable. Poiché ci sono più servizi con stato “Running” o “Stopped”, questa volta ogni chiave della hashtable restituisce più di un servizio. Ora è banale contare il numero di servizi in esecuzione o interrotti, oppure elencare tutti i servizi in esecuzione. Il concetto di raggruppamento con le hashtable è estremamente flessibile e potente.

Tuttavia, se ci pensa un momento, ciò pone un’altra sfida: ora che può scalare la sua funzione e recuperare rapidamente gli ID di processo per molti servizi, quanto è utile un elenco di numeri se si perde il nome del servizio originale a cui appartiene un determinato ID di processo?

Aggiungere informazioni

Per aggiungere – piuttosto che sostituire – informazioni, PowerShell dispone di un altro cmdlet creato esattamente per questo scopo. Questa versione migliorata può aggiungere l’ID di processo con il suo nuovo parametro -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

        }

    }

}

Può trovare il parametro -PassThru in numerosi cmdlet PowerShell, e quando è presente, sa che ha la possibilità di restituire un valore appena calcolato oppure di aggiungere il nuovo valore all’input originale. Questo si comprende meglio osservando i risultati. Per impostazione predefinita, non cambia nulla, e continua a ottenere gli ID di processo dei servizi:

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

Se specifica -PassThru, ottiene gli oggetti di input originali (i servizi che ha inviato tramite pipeline):

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
(...)

Tuttavia, questi oggetti hanno ora una nuova proprietà chiamata ProcessId, che può utilizzare per l’ordinamento. Per renderla visibile, usi 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
(...)

Ed ecco come funziona:

Perché una funzione possa trasmettere le informazioni, deve avere accesso all’oggetto di input originale. Ecco perché la nuova funzione ora ha due parametri reciprocamente esclusivi: -Name (specifica un nome di servizio come “Spooler”) e -Service (fornisce un’istanza effettiva di un servizio). Internamente, la funzione rileva quale informazione ha fornito l’utente e calcola quella mancante.

Quando l’utente specifica -PassThru, l’oggetto del servizio viene inviato tramite pipeline ad Add-Member. Add-Member può aggiungere dinamicamente nuove proprietà a qualsiasi oggetto. In questo esempio, aggiunge una nuova “NoteProperty” (valore statico) chiamata ProcessId e memorizza in questa proprietà l’ID di processo appena recuperato. Tutto qui.

Una volta adottata questa tecnica, può progettare il suo codice in modo molto più modulare, come scopriremo di seguito.

Un sistema flessibile come i Lego

Per esercitarci, aggiungiamo altre due funzioni che individuano il nome del processo per un determinato ID di processo e il nome del gruppo di servizi. È lo stesso approccio di base, con gli stessi ingredienti appena discussi.

Aggiungere il nome del processo

Ecco una funzione PowerShell modulare che recupera il nome del processo in chiaro da qualsiasi oggetto che abbia una proprietà chiamata ProcessId o Id di tipo intero:

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

        }

    }

}

Rivediamo rapidamente i trucchi di codifica PowerShell usati qui:

  • Il blocco param() fa riferimento due volte all’oggetto di input: appare inalterato in $InputObject (così può essere passato in seguito, se richiesto), e il suo ID di processo appare in $ProcessId. PowerShell si occupa della parte difficile e identifica la proprietà appropriata. Lei deve solo specificare il tipo desiderato ([int] in questo esempio) e i nomi validi (ProcessId, Id). [Alias()] accetta un elenco separato da virgole, quindi può specificare tutti i nomi di proprietà che desidera. Qualsiasi di essi è accettabile per questo parametro, e vince il primo.
  • Se nell’oggetto di input è stato trovato un ID di processo, allora si trova in $ProcessId. Il codice usa un semplice If per tradurlo nel nome del processo. Se invece $ProcessId è NULL (perché la proprietà mancava nell’oggetto di input o era vuota), $ProcessName è NULL. Il trucco qui è assegnare $ProcessName all’If, e non assegnarlo all’interno della clausola If, in modo che venga azzerato quando la condizione If non è soddisfatta. Guardi quanto elegantemente e in modo generico può essere usata la nuova funzione. Può usarla con qualsiasi oggetto di input che abbia una proprietà ProcessId o Id. Ad esempio, scopra il nome del suo host PowerShell attuale:
PS C:\> Get-Process -id $pid | Get-ProcessNameById
powershell_ise

Oppure, applichiamola ai nostri servizi e otteniamo un elenco di servizi, i processi sottostanti e gli ID di processo:

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
(...)

Gruppi di servizi

Come avrà notato, molti servizi condividono lo stesso processo, e ci sono molti processi con nomi identici (“svchost”). Questo è voluto: in Windows, molti servizi possono essere eseguiti all’interno della stessa DLL.

Per recuperare il loro gruppo di servizi, possiamo riutilizzare Get-ServiceProcessId. Questa volta, non leggiamo l’ID di processo dagli oggetti WMI, ma piuttosto 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

        }

    }

}

E poiché abbiamo usato uno scripting modulare e compatibile con la pipeline, otteniamo un altro pezzo di codice, come i Lego, che possiamo combinare con gli altri secondo necessità:

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 in soccorso

Forse ha investito un paio d’ore di lavoro creando ottimi script PowerShell che le forniscono le informazioni di cui ha bisogno. Ma se la ricorderà tutto questo la settimana prossima? Il mese prossimo? E se avesse bisogno di queste informazioni in produzione? O volesse che un collega del help desk le visualizzasse senza doverlo introdurre alle console e agli script PowerShell?

È qui che entra in gioco PowerShell Universal. È completamente gratuito per uso personale, e se desidera utilizzarlo commercialmente, la licenza è economica ed è calcolata per server, non per utente.

In breve, PowerShell Universal incorpora un host PowerShell in un web server. Questo Le consente di costruire in sicurezza dashboard ricche di elementi grafici, trasformare script in servizi e servizi web, e molto altro.

Per ora, vogliamo utilizzare l’edizione personale gratuita per costruire una dashboard grafica ricca in cui poter consultare le informazioni sui servizi che abbiamo appena recuperato. Tenga presente che questo era solo un esempio: ovviamente può sostituire il nostro codice con qualsiasi cosa sia più rilevante per Lei, e visualizzare server VMWare, raccolte di immagini o account utente allo stesso modo.

Configurare PSU

Ecco i pochi passaggi necessari per configurare PSU sulla propria macchina. Nessuna licenza, nessun costo, nessun privilegio di amministratore richiesto. Può usare il modulo PowerShell Universal per installare il server Universal. Per installare il modulo, usi Install-Module o Install-PSResource.

Install-Module Devolutions.PowerShellUniversal

Per installare il server Universal, può usare Install-PSUServer.

Install-PSUServer -LatestVersion

L’esecuzione di questo comando ha effetti diversi in base al sistema operativo:

  • Su Windows, crea e avvia un servizio Windows sulla sua macchina.
  • Su Linux, crea e avvia un servizio systemd.
  • Su macOS, scarica ed estrae il server PowerShell Universal. Per ulteriori opzioni di installazione, consulti la documentazione su Installation | PowerShell Universal.

Creare un’app PSU

Per aggiungere una GUI al nostro script, navighi su http://localhost:5000 per aprire la console di gestione di PSU. Dovrà accedere utilizzando l’account Devolutions creato in precedenza.

Sul lato sinistro, clicchi su “Apps”, poi su “App”. Sul lato destro, vedrà ora un elenco delle app create in precedenza. Inizialmente, questo elenco è vuoto.

Clicchi su “Create App”. Le assegni un nome descrittivo, ad esempio “Service Dashboard”. Nel campo sottostante, inserisca l’“URL” per la sua app. Deve essere un nome relativo, con il prefisso di una barra “/”, ad esempio “/list-services”. Può lasciare “Description” vuoto. Si assicuri che il cursore “Definition” mostri “Code” e non “Command”. Poi clicchi OK. La nuova app apparirà nel suo elenco di app.

Successivamente, clicchi sull’icona a forma di matita. Si aprirà l’editor PowerShell integrato. Il codice predefinito è questo:

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

Per testare questa app, clicchi sul pulsante “View App” in alto. Lo script PowerShell basato sul web viene eseguito, e vedrà un messaggio di benvenuto nel suo browser. Vedrà anche come accedere a questa app: il suo URL è http://localhost:5000/list-services/Home

Avvertenze

Prima di andare avanti, ecco due cose importanti da ricordare:

  • Il codice di esempio predefinito usa New-UDApp {} per mostrare un semplice messaggio. Questo cmdlet deve essere sempre presente, poiché crea l’app. Tutte le sue modifiche avvengono solo all’interno delle parentesi graffe.
  • Può sempre testare il suo codice cliccando sul pulsante “View App” in cima alla pagina dell’editor, ma deve prima cliccare sull’icona SALVA. “View App” non salva automaticamente le sue modifiche più recenti. Se si dimentica di cliccare SALVA, “View App” ignora le sue ultime modifiche al codice.

Visualizzare una tabella

Sostituisca il codice predefinito all’interno delle parentesi graffe con questo codice:

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

}

Il codice recupera alcune informazioni e le memorizza in $data. Poi, chiama uno dei tanti cmdlet UD… per visualizzare i dati nella sua app web. New-UDTable, ad esempio, crea automaticamente una tabella per Lei.

Una volta cliccato su “View App”, potrà visualizzare la tabella nel suo browser:

Forse avrà notato che abbiamo aggiunto -ErrorAction Ignore al nostro codice. Questo perché, per impostazione predefinita, PSU esegue il suo codice in PowerShell 7. PowerShell 7 si comporta in modo leggermente diverso rispetto a Windows PowerShell, e Get-Service potrebbe generare eccezioni su sistemi non in lingua inglese (US). Ogni volta che vengono generate eccezioni nel suo codice, l’app mostrerebbe questi messaggi di errore. Per evitarlo, si assicuri sempre che il suo codice non generi eccezioni non gestite.

Visualizzatore di servizi

Ora che ha visto quanto sia semplice aggiungere un’interfaccia utente grafica basata su browser al suo codice, riassumiamo tutto e creiamo il nostro visualizzatore estteso dei processi di servizio. Sostituisca il codice con quello che abbiamo creato in precedenza:

# 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

}

All’inizio, trova le tre funzioni che abbiamo creato in precedenza. In una parte successiva di questa serie, sposteremo queste funzioni in moduli esterni, ma per ora tutto è definito nel codice, così non abbiamo alcuna dipendenza.

Poi, i dati recuperati vengono visualizzati dalla stessa New-UDTable usata in precedenza. Quando clicca su “View App”, il suo codice viene eseguito, le informazioni sui servizi vengono recuperate, e una tabella presenta i dati.

Tutto ciò di cui ha bisogno è l’URL per questa app web: http://localhost:5000/list-services/Home Potrebbe salvarlo come link sul suo desktop o aggiungerlo ai segnalibri del browser. Ogni volta che avrà bisogno di informazioni sui servizi, non dovrà più cercare script ed eseguirli in una console.

E quando ci riflette, vedrà le enormi opportunità: può costruire le sue dashboard personali. Ma con una licenza aziendale e configurando PSU con privilegi di amministratore, potrebbe anche ospitare i suoi script come servizio, fornendo l’accesso basato su browser con un controllo di sicurezza granulare a chiunque nella sua organizzazione.

Arriveremo a questo nel corso di questa serie. Per ora, ha visto come configurare PSU e creare la sua prima app web PSU. Nella prossima parte, ripartiremo di nuovo dal codice PowerShell e poi estenderemo la dashboard con nuovi controlli e maggiori funzionalità. Spiegheremo anche perché la tabella dei servizi che ha appena creato mostra le colonne in un ordine un po’ inaspettato, e come correggerlo.