MAIN MENU
Blog de Devolutions

Anuncios, actualizaciones y análisis de Devolutions.

Unlock powershell universal code snippets web apps devolutions blog

Descubra PowerShell Universal: de fragmentos de código a aplicaciones web

Este artículo le guía a través de técnicas clave de rendimiento en PowerShell y, a continuación, le muestra cómo aplicarlas en PowerShell Universal. Al final, dispondrá de un panel web totalmente funcional para sus scripts, accesible desde cualquier navegador.

Bienvenido a nuestra nueva serie de PowerShell, dedicada a trucos de PowerShell y a cómo incorporarlos en PowerShell Universal. Así es como funciona esta serie:

  • La primera parte de cada artículo trata técnicas de PowerShell puro y le presenta trucos sorprendentes y ejemplos de código que elevan sus conocimientos de PowerShell.
  • La segunda parte utiliza el nivel gratuito de PowerShell Universal para construir su propio panel personal de PowerShell, de modo que pueda utilizar fácilmente su código de PowerShell sin necesidad de buscar scripts ni ejecutar consolas.

PowerShell Universal (PSU)

Si no conoce PowerShell Universal, este es el lugar perfecto para empezar. PowerShell Universal integra código de PowerShell como un servidor web y, con solo unos clics, puede empezar a crear sus propios paneles con interfaz gráfica. Complementa a la perfección sus conocimientos existentes de PowerShell y funciona en múltiples plataformas, tanto con Windows PowerShell como con PowerShell 7. Con él, puede crear paneles y herramientas sin recurrir a técnicas complejas como WPF, WinForms u otras interfaces gráficas.

Escalado de funciones de PowerShell

Veamos primero algo de código de PowerShell y apliquemos algunos trucos y técnicas para mejorarlo.

¿Alguna vez ha querido identificar qué proceso aloja un servicio del sistema concreto, solo para darse cuenta de que todos aparecen como «svchost.exe» en Process Monitor o Process Explorer?

La solución implica dos funciones de PowerShell que recuperan los identificadores de proceso de los servicios y los nombres de los grupos de servicios, entre otras muchas cosas. Funcionan de maravilla, pero son lentas a gran escala. Véalo usted mismo:

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

Esta magnífica función toma un nombre de proceso y devuelve su identificador de proceso (si está disponible):

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

Como esta función admite canalización (pipeline), puede escalarla a muchos procesos:

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

En esta llamada, Get-Service vuelca todos los servicios y Get-ServiceProcessId vincula automáticamente la propiedad «Name» a su parámetro «Name» (ValueFromPipelineByPropertyName). Como el código de la función está incluido en un bloque process {}, este código se repite para cada elemento de la canalización. Escribir funciones de este tipo, compatibles con la canalización, no tiene ningún misterio: designe un parámetro con ValueFromPipeline o ValueFromPipelineByPropertyName, e incluya su código en process {}; ya está. Ahora su función escala de forma automática.

Sin embargo, la llamada es exasperantemente lenta: los identificadores de proceso van llegando uno a uno con segundos de retraso. Esto se debe a que el código de la función consulta WMI para cada servicio, y esta consulta WMI lleva tiempo.

Optimización del rendimiento

El problema de rendimiento que acaba de ver es habitual en muchos scripts. Siempre que se consulta información, ya sea de WMI, Active Directory, bases de datos o cualquier otra fuente, el código no escala bien.

Una mejora sencilla para estos escenarios consiste en crear tablas de referencia (lookup tables). PowerShell ya proporciona un cmdlet que hace esto por usted. Véalo:

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

Esta función es extremadamente rápida: cuando consulta varios servicios, la consulta WMI se ejecuta una sola vez. Tras el retraso inicial provocado por la consulta, los identificadores de proceso solicitados se vuelcan sin retrasos adicionales.

Esto se debe a que hemos añadido un bloque begin {} a la función. Esta parte se ejecuta una única vez, como preparación, antes de que suceda cualquier otra cosa. En este bloque, consultamos WMI para todos los servicios, algo que en realidad no lleva mucho más tiempo que preguntar por un único servicio.

Para poder encontrar más adelante la información de un servicio concreto, los resultados de la consulta se pasan a Group-Object. Este cmdlet es perfecto para crear tablas de referencia porque admite los parámetros -AsHashtable y -AsString, que crean automáticamente una tabla hash indexada por la propiedad que haya especificado. ¿Suena complejo? No lo es. Véalo:

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 tabla hash generada por Group-Object, los nombres de las claves son ahora los nombres de los servicios. Cuando especifica el nombre de un servicio concreto, obtiene el objeto de servicio. Cuando añade una propiedad específica, por ejemplo .ProcessId, obtiene directamente su identificador de proceso.

Veamos otro ejemplo para que quede claro antes de continuar:

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

Esta vez hemos agrupado por la propiedad «Status», y ahora podemos usar cualquiera de las constantes de Status para consultar la tabla hash. Como hay más de un servicio con el estado «Running» o «Stopped», en este caso cada clave de la tabla hash devuelve varios servicios. Ahora resulta trivial contar el número de servicios en ejecución o detenidos, o volcar todos los servicios en ejecución. El concepto de agrupación con tablas hash es muy flexible y extremadamente potente.

Sin embargo, si se para a pensarlo un momento, esto plantea otro reto: ahora que puede escalar su función y recuperar rápidamente los identificadores de proceso de muchos servicios, ¿de qué sirve una lista de números si se pierde el nombre del servicio original al que pertenece un identificador de proceso concreto?

Añadir información

Para añadir información —en lugar de sustituirla—, PowerShell dispone de otro cmdlet pensado exactamente para este propósito. Esta versión mejorada puede añadir el identificador de proceso con su nuevo parámetro -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

        }

    }

}

Puede encontrar el parámetro -PassThru en bastantes cmdlets de PowerShell, y cuando está presente, sabe que tiene la opción de mostrar como salida un valor recién calculado, o añadir el nuevo valor a la entrada original. Esto se entiende mejor observando los resultados. Por defecto, no cambia nada y sigue obteniendo los identificadores de proceso de los servicios:

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

Si especifica -PassThru, obtiene los objetos de entrada originales (los servicios que ha canalizado):

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

Sin embargo, estos objetos ahora tienen una nueva propiedad llamada ProcessId que puede usar para ordenar. Para hacerla visible, use 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
(...)

Y así es como funciona:

Para que una función pueda transmitir información, necesita acceso al objeto de entrada original. Por eso la nueva función ahora tiene dos parámetros mutuamente excluyentes: -Name (especifique un nombre de servicio como «Spooler») y -Service (proporcione una instancia real de un servicio). Internamente, la función detecta qué información ha proporcionado el usuario y calcula la que falta.

Cuando el usuario especifica -PassThru, el objeto de servicio se canaliza a Add-Member. Add-Member puede añadir dinámicamente nuevas propiedades a cualquier objeto. En este ejemplo, añade una nueva «NoteProperty» (valor estático) llamada ProcessId y almacena en ella el identificador de proceso recién recuperado. Eso es todo.

Una vez que domine esta técnica, podrá diseñar su código de forma mucho más modular, como descubriremos a continuación.

Sistema flexible tipo Lego

Como práctica, añadamos dos funciones más que averigüen el nombre del proceso a partir de un identificador de proceso concreto, y el nombre del grupo de servicios. Es el mismo planteamiento básico, usando los mismos ingredientes que acabamos de comentar.

Añadir el nombre del proceso

Aquí tiene una función modular de PowerShell que recupera el nombre del proceso en texto claro a partir de cualquier objeto que tenga una propiedad llamada ProcessId o Id de tipo entero:

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

        }

    }

}

Repasemos rápidamente los trucos de codificación de PowerShell utilizados aquí:

  • El bloque param() hace referencia al objeto de entrada dos veces: aparece sin cambios en $InputObject (para poder transmitirlo más adelante si se solicita), y su identificador de proceso aparece en $ProcessId. PowerShell hace la parte difícil e identifica la propiedad adecuada. Usted solo especifica el tipo deseado ([int] en este ejemplo) y los nombres válidos (ProcessId, Id). [Alias()] acepta una lista separada por comas, de modo que puede especificar tantos nombres de propiedad como desee. Cualquiera de ellos es válido para este parámetro, y el primero que coincida es el que se usa.
  • Si se encontró un identificador de proceso en el objeto de entrada, este aparece en $ProcessId. El código utiliza un simple If para traducirlo al nombre del proceso. Sin embargo, si $ProcessId es NULL (porque faltaba la propiedad en el objeto de entrada o estaba vacía), $ProcessName es NULL. El truco aquí consiste en asignar $ProcessName al resultado del If, y no asignarlo dentro de la cláusula If, de modo que quede en null cuando no se cumpla la condición del If. Observe qué elegante y genérica resulta la nueva función. Puede usarla con cualquier objeto de entrada que tenga una propiedad ProcessId o Id. Por ejemplo, averigüe el nombre de su host actual de PowerShell:
PS C:\> Get-Process -id $pid | Get-ProcessNameById
powershell_ise

O aplíquela a nuestros servicios, y obtenga una lista de servicios, sus procesos subyacentes e identificadores de proceso:

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

Grupos de servicios

Como habrá notado, muchos servicios comparten el mismo proceso, y hay muchos procesos con nombres idénticos («svchost»). Esto es intencionado: en Windows, muchos servicios pueden ejecutarse dentro de la misma DLL.

Para recuperar su grupo de servicios, podemos reutilizar Get-ServiceProcessId. Esta vez, no leemos el identificador de proceso de los objetos WMI, sino 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

        }

    }

}

Y como hemos usado un scripting modular compatible con canalización, obtenemos otra pieza de código tipo Lego que podemos combinar con las demás según sea necesario:

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 al rescate

Puede que haya invertido varias horas de trabajo y haya creado excelentes scripts de PowerShell que le proporcionan la información que necesita. Pero ¿se acordará de todo esto la semana que viene? ¿El mes que viene? ¿Qué ocurre si necesita esta información en producción? ¿O si quiere que un compañero de soporte técnico la consulte sin tener que enseñarle consolas y scripts de PowerShell?

Ahí es donde entra en juego PowerShell Universal. Es completamente gratuito para uso personal, y si desea usarlo comercialmente, tiene una licencia económica por servidor, no por puesto.

En resumen, PowerShell Universal integra un host de PowerShell en un servidor web. Esto le permite crear de forma segura paneles ricos en interfaz gráfica, convertir scripts en servicios y servicios web, y mucho más.

Por ahora, queremos usar la edición personal gratuita para crear un panel con interfaz gráfica rica donde podamos consultar la información de servicios que acabamos de recuperar. Tenga en cuenta que esto solo era un ejemplo; evidentemente, puede sustituir nuestro código por cualquier cosa que le resulte más relevante y mostrar servidores VMWare, colecciones de imágenes o cuentas de usuario de la misma manera.

Configuración de PSU

Estos son los pocos pasos necesarios para configurar PSU en su propio equipo. Sin licencia, sin coste, sin necesidad de privilegios de administrador. Puede usar el módulo PowerShell Universal para instalar el servidor Universal. Para instalar el módulo, use Install-Module o Install-PSResource.

Install-Module Devolutions.PowerShellUniversal

Para instalar el servidor Universal, puede usar Install-PSUServer.

Install-PSUServer -LatestVersion

Ejecutar este comando tiene efectos diferentes según su sistema operativo:

  • En Windows, crea e inicia un servicio de Windows en su equipo.
  • En Linux, crea e inicia un servicio systemd.
  • En macOS, descarga y extrae el servidor PowerShell Universal. Para conocer opciones de instalación adicionales, consulte la documentación en Installation | PowerShell Universal.

Creación de una aplicación PSU

Para añadir una interfaz gráfica a nuestro script, navegue a http://localhost:5000 para abrir la consola de administración de PSU. Debe iniciar sesión con la cuenta de Devolutions que creó anteriormente.

En el lado izquierdo, haga clic en «Apps» y luego en «App». En el lado derecho verá ahora una lista de las aplicaciones que ha creado anteriormente. Al principio, esta lista está vacía.

Haga clic en «Create App». Asígnele un nombre descriptivo, por ejemplo, «Service Dashboard». En el campo situado debajo, introduzca la «URL» de su aplicación. Debe ser un nombre relativo, precedido de una barra diagonal «/», por ejemplo, «/list-services». Puede dejar «Description» vacío. Asegúrese de que el deslizador «Definition» muestre «Code», no «Command». A continuación, haga clic en OK. La nueva aplicación aparece en su lista de aplicaciones.

A continuación, haga clic en el icono del lápiz. Esto abre el editor de PowerShell integrado. El código predeterminado es este:

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

Para probar esta aplicación, haga clic en el botón «View App» situado en la parte superior. Su script de PowerShell basado en la web se ejecuta y verá un mensaje amistoso en su navegador. También verá cómo acceder a esta aplicación: su URL es http://localhost:5000/list-services/Home.

Advertencias

Antes de continuar, aquí tiene dos cosas importantes que recordar:

  • El código de ejemplo predeterminado usa New-UDApp {} para mostrar un mensaje sencillo. Este cmdlet siempre debe estar presente, ya que es lo que crea la aplicación. Todos sus ajustes se realizan únicamente dentro de las llaves.
  • Siempre puede probar su código haciendo clic en el botón «View App» en la parte superior de la página del editor, pero primero debe hacer clic en el icono GUARDAR. «View App» no guarda automáticamente sus últimas ediciones. Si olvida hacer clic en GUARDAR, «View App» ignorará sus últimos cambios en el código.

Mostrar una tabla

Sustituya el código predeterminado dentro de las llaves por este código:

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

}

El código recupera cierta información y la almacena en $data. A continuación, llama a uno de los muchos cmdlets UD… para mostrar los datos en su aplicación web. New-UDTable, por ejemplo, crea automáticamente una tabla por usted.

Una vez que haga clic en «View App», podrá mostrar la tabla en su navegador:

Puede que haya notado que hemos añadido -ErrorAction Ignore a nuestro código. Esto se debe a que, por defecto, PSU ejecuta su código en PowerShell 7. PowerShell 7 se comporta de forma ligeramente distinta a Windows PowerShell, y Get-Service puede generar excepciones en sistemas que no son de EE. UU. Siempre que se generen excepciones en su código, la aplicación mostrará estos mensajes de error. Para evitarlo, asegúrese siempre de que su código no genere excepciones sin controlar.

Visor de servicios

Ahora que ha visto lo sencillo que es añadir una interfaz de usuario gráfica basada en el navegador a su código, cerremos el círculo y creemos nuestro visor extendido de procesos de servicios. Sustituya el código por el que creamos anteriormente:

# 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

}

Al principio, encontrará las tres funciones que creamos antes. En una parte posterior de esta serie, moveremos estas funciones fuera del código principal y las colocaremos en módulos externos, pero por ahora, todo está definido en el propio código para no tener dependencias.

A continuación, los datos recuperados se muestran mediante el mismo New-UDTable que usó anteriormente. Cuando haga clic en «View App», su código se ejecuta, se recupera la información de servicios y se le presenta una tabla con los datos.

Todo lo que necesita es la URL de esta aplicación web: http://localhost:5000/list-services/Home. Podría guardarla como un enlace en su escritorio o añadirla a favoritos en su navegador. Siempre que necesite información de servicios, ya no tendrá que buscar scripts y ejecutarlos en una consola.

Y cuando asimile esto, verá las enormes posibilidades: puede crear sus propios paneles personales. Pero con una licencia corporativa y configurando PSU con privilegios de administrador, también podría alojar sus scripts como un servicio, ofreciendo acceso basado en navegador con control de seguridad granular a cualquier persona de su organización.

Llegaremos a eso a lo largo de esta serie. Por ahora, ha visto cómo configurar PSU y crear su primera aplicación web propia con PSU. En la siguiente parte, volveremos a empezar con código de PowerShell y luego ampliaremos el panel con nuevos controles y más funciones. También explicaremos por qué la tabla de servicios que acaba de crear muestra las columnas en un orden algo inesperado, y cómo corregirlo.

Más de PowerShell

Leer más artículos