En Powershell disponemos de diversas maneras de pasar datos entre un cmdlet que genera los mismos y otro que los recibe a través de un pipeline (tubería). En gran parte de las ocasiones deberemos buscar aquella que funcione en un caso determinado investigando de manera metódica que puede estar fallando en un comando determinado. En este post vamos a ver con claros ejemplos esas opciones.

powershell logo

 

Este paso de información entre cmdlets se puede realizar de las siguientes maneras y es importante este orden porque si funcionara con la primera opción, que sería pasar los datos por valor, esta sería la opción recomendable. Las opciones de las que disponemos serían:

  • ByValue (Por valor)
  • ByPropertyName (Por nombre de propiedad del objeto generado por el cmdlet)
  • Custom property (Customizando una de esas propiedades)
  • Script (Script cuando todo lo anterior falla)

ByValue

Este es el escenario más sencillo y habitual dentro de comandos no excesivamente complejos. Por ejemplo tenemos el comando:

get-service nombredeservicio | stop-service

Queremos obtener un servicio determinado y pararlo. El cmdlet receptor es stop-service y lo que este puede recibir dependerá de sus propiedades. Lo primero que tenemos que hacer es ver que tipo de objeto genera el cmdlet inicial, en este caso get-service. Para ello ejecutamos el get-member

get-service | get-member

Vemos que el tipo de objeto que se enviará a través de la tubería es un ServiceController. Ahora necesitamos ver que tipo de objeto es capaz de recibir stop-service

get-help stop-service -showwindow

*lo podeis ejecutar también con el parámetro -full en vez de -showwindow

Si revisamos los parámetros, podemos ver si existe alguno que acepte entrada por pipeline, esto estará marcado con un true. Recordamos también que existen dos maneras de que pueda recibir este input, por valor o por nombre de propiedad, esto también estará marcado en la misma linea, por ejemplo en el caso del cmdlet stop-service si miramos en la ayuda veremos que tenemos el siguiente parámetro:

-InputObject <servicecontroller[]>

Accept pipeline input? True (ByValue)

Como decíamos, lo primero por lo que tenemos que buscar es si existe algún parámetro que acepte input de pipeline, encontraremos entre ellos este parámetro que si acepta input por valor y que además, el tipo de input es un objeto ServiceController, exactamente lo mismo que genera el get-service, bingo! En este caso vemos como podemos ejecutar el comando get-service nombredeservicio | stop-service sin más problema. Sigamos.

ByPropertyName

Ahora vamos a ver el segundo caso, cuando no podemos pasar algo tan sencillamente, supongamos el siguiente ejemplo:

get-service | stop-process

En este caso podríamos pensar que esto no debería funcionar ya que los servicios son algo totalmente diferente que los procesos. Pero funciona y ahora vamos a ver porque. Siguiendo el ejemplo anterior vamos a ver si hay algun parámetro en el cmdlet de destino stop-process que acepte una entrada por pipeline de un objeto de tipo ServiceController. Para ello como antes ejecutamos el get-help

get-help stop-process -showwindow

buscamos parametros que acepten input de valor a través de la tubería. En este caso encontramos lo siguiente:

-InputObject <process[]>

Accept pipeline input? True (ByValue)

Aquí vemos una propiedad que acepta ese input, que lo acepta por valor, hasta aquí todo bien, pero si nos fijamos en el tipo de objecto que espera recibir, se trata de un Process que no tiene nada que ver con el ServiceController que genera el get-service, por lo que no funcionaría por valor, pero y por nombre de propiedad?

En la misma ayuda encontramos por ejemplo otras propiedades que si aceptan input de tubería ByPropertyName por ejemplo

-Name <string[]>
..
Accept pipeline input? True (ByPropertyName)

En este caso este parámetro aceptaría una entrada por tubería de un objeto que tuviera una propiedad que se llamara Name y que llegara en forma de string. Vamos a ver si este es el caso de get-service con

get-service | gm

Vemos que una de las propiedades de get-service es efectivamente Name. Por lo que esto funcionaría. Podemos ver los resultados que este comando tendría con un -whatif para no liarla

get-service | stop-process -whatif

En este caso hemos hecho algo un poco raro, pero que sirve perfectamente para demostrar el funcionamiento de esta segunda opción de pasar entrada a un cmdlet. A partir de la propiedad Name de get-service mandamos los nombres de servicios a un cmdlet totalmente diferente, stop-process pero que acepta como entrada esa propiedad de Name, por lo que si algún nombre de nuestros servicios es exactamente igual que un nombre de nuestros procesos, este se detendrá.

Otro ejemplo aún más sencillo de este envío por valor a un cmdlet sería el siguiente

get-process calc | dir

En este caso y teniendo en marcha la calculadora de Windows, vemos que estamos enviando un objeto de tipo Process a un cmdlet que no tiene ningún parámetro que espere esto. Lo que sucede en este caso si miramos de nuevo la ayuda del cmdlet receptor, en este caso dir, es que encontramos el siguiente parámetro

-Path <string[]>
..
Accept pipeline input? True (ByValue, ByPropertyName)

Vemos que “dir” aunque no espere un objeto Process si que funcionaría por ejemplo si recibiese un objecto que tuviera la propiedad “Path” tanto por valor como por nombre de propiedad que es lo que nos interesa en este ejemplo.

Ahora si comprobamos si esta propiedad existe, la podremos localizar facilmente con

get-process | gm

Por lo que en este caso el comando

get-process calc | dir

funciona ya que “dir” esta recibiendo por pipeline, un parámetro Path en forma de string y por nombre de parámetro. Por lo que el output del comando muestra la ruta en la cual se está ejecutando este proceso calc.

Custom Property

Vamos a ver un escenario en el que por ejemplo quisieramos ver el estado de un servicio en todas las máquinas de nuestro dominio.

get-adcomputer – filter * | get-service -name bits

Esto no funciona, pero vamos a ver porque. Primero como siempre vemos que tipo de objeto genera el primer cmdlet

get-adcomputer -filter * | gm

y vemos que genera un objeto de tipo ADComputer y ahora vamos a ver la ayuda de get-service

get-help get-service -showwindow

y buscamos hay algún parametro que acepta entrada de pipeline por valor y vemos que si.

-InputObject <servicecontroller[]>

Accept pipeline input? True (ByValue)

Pero en este caso ServiceController no es lo mismo que el objeto que genera el primer cmdlet ADComputer, por lo que no sería posible pasar por valor. Pero seguimos buscando a ver si hay alguna manera de pasar por nombre de propiedad y efectivamente la hay

-Name <string[]>
Specifies the service name of services to be retrieved, Wildcards are permitted. By default, Get-Service gets all of the services on the computer.
..
Accept pipeline input? True (ByPropertyName, ByValue)

Si tenemos en el cmdlet ADComputer alguna propiedad llamada “Name” podríamos poder ejecutar el comando. Vamos a comprobar eso de nuevo con el comando

get-adcomputer -filter * | gm

Vemos que existe esa propiedad pero, es realmente esto lo que queremos hacer? Si nos fijamos en la propiedad de get-service anterior, vemos que espera efectivamente el valor de una propiedad Name, pero que eso indica el nombre del servicio. La propiedad Name de get-adcomputer lo que contiene es el nombre de la máquina y lo que espera get-service es el nombre de un servicio...por lo que no nos serviría de mucho...

Ahora estamos en un punto en que hemos visto que no podemos pasar nada por valor o por nombre de propiedad de get-adcomputer a get-service para obtener los resultados que queremos. Pero no nos damos por vencidos. Si revisamos de nuevo los parámetros que aceptan entrada de get-service encontramos lo siguiente:

-ComputerName <string[]>
Gets the services running on the specified computers. The default is the local computer.
...
Accept pipeline input? True (ByPropertyName)

Este parámetro nos iría de maravilla, pero para ello necesitaríamos que get-adcomputer tuviera una propiedad que se llamara exactamente ComputerName y no es así. Necesitaríamos que la propiedad Name se pasara a llamar ComputerName y esto es realmente de lo que se trata este tercer método.

En Powershell podemos crear propiedades al vuelo

Lo vemos facilmente con el ejemplo siguiente, si lo ejecutamos de la siguiente manera

Get-Service | select -Property name,status

Obtenemos los nombres y el estatus de los servicios en nuestra máquina, pero si indicaramos dos propiedades inventadas

Get-Service | select -Property nameinventado, statusinventado

no tendríamos un error si no que los resultados serían en blanco y si hacemos un get-member de lo anterior vemos que esas dos nuevas propiedades inventadas aparecen como propiedades en la lista

Get-Service | select -Property nameinventado, statusinventado | gm

La idea es tomar la propiedad name del objeto que entra en el pipeline y transformarla en la propiedad ComputerName que es la que necesitamos para el cmdlet receptor en la tubería. Los podemos hacer con el comando:

get-adcomputer -filter * | select -property name, @{name='ComputerName';expression={$_.name}}

*tanto “name=” como “expression=” podemos abreviarlo a “n=” y “e=”

En este comando cogemos “al vuelo” la propiedad name del objeto entrante en la tubería ($_.name) y la asignamos a la propiedad con nombre ComputerName. La salida de este comando es un select del get-adcomputer con dos columnas, name y ComputerName, pero lo importante es que ahora ya tenemos una salida de get-adcomputer con un parámetro llamado ComputerName que le podemos pasar a get-service. Por tanto el comando resultante sería por ejemplo

get-adcomputer -filter * | select -property @{name='ComputerName';expression={$_.name}} | get-service -name nombredeservicio

genial no? Pero aún hay más

Scripting

Supongamos que queremos ejecutar el siguiente comando

get-adcomputer -filter * | get-wmiobject -class win32_bios

El resultado que esperamos es por cada ordenador en nuestro dominio, obtener datos de su bios. Como en casos anteriores el primer paso es comprobar la ayuda del cmdlet de destino, get-wmiobject

get-help get-wmiobject -showwindow

buscamos que parametros aceptan entrada por pipeline, pero no encontramos ninguno, entonces como podemos obtener el resultado que queremos? Este cuarto supuesto trata de ir más allá de los parámetros que aceptan entrada por tubería aunque siempre es recomendable ceñirse a los 3 primeros en caso que sea posible para hacer las cosas bien.

Como hemos dicho, en la ayuda de get-wmiobject no hay ningún parámetro que acepte entrada por pipeline (existe un nuevo cmdlet Get-CimInstance que reemplaza a Get-WmiObject y que si acepta entrada pero no nos es util para este ejemplo por lo que lo obviaremos), pero si repasamos la sintaxis, un poco más arriba en la propia ayuda, vemos que tiene el parámetro -ComputerName <string[]> por lo que lo único que tenemos que tratar es de ser capaces de pasarle los nombres de ordenador de esa manera.

Podríamos pensar que con algo así funcionaría:

Get-wmiobject -class win32_bios -ComputerName (Get-Adcomputer -filter *)

*El lanzar un cmdlet entre paréntesis indica que primero de todo se ejecuta eso y luego el resto.

Pero el objeto que produce el cmdlet get-adcomputer es un ADComputer y no un string que es lo que necesitamos para el parámetro ComputerName del get-wmiobject.. Incluso aunque pensemos que con un Select podríamos aislar solo el nombre de ordenador y de esa manera solo tener un string en vez de un objeto ADComputer, podemos ver con el siguiente comando que seguimos teniendo como salida un objeto ADComputer:

Get-ADComputer -filter * | Select -Property name | gm

Lo que querríamos sería extraer esos nombres y meterlos en un vector de strings. Para ellos debemos “expandir” la propiedad con el parámetro ExpandProperty

Get-ADComputer -filter * | Select -ExpandProperty name

Con el comando anterior vemos que el resultado son unicamente los nombres (strings) de los ordenadores y si queremos comprobar el tipo de salida y ejecutamos

Get-ADComputer -filter * | Select -ExpandProperty name | gm

vemos que es un objeto de tipo String! bingo de nuevo. Ahora ya tenemos en un vector de strings los nombres de ordenador listos para pasárselos al cmdlet get-wmiobject tal y como queríamos

get-wmiobject -class win32_bios -computername (Get-ADComputer -filter * | Select -ExpandProperty name)

Entendiendo el ejemplo anterior, podemos escribirlo aún de otra manera como vemos en el siguiente ejemplo:

get-wmiobject -class win32_bios -computername (Get-ADComputer -filter *).name

Este mismo comando lo podríamos llevar a cabo también a través de un bloque de script como el siguiente en el que utilizamos de nuevo el $_ para indicar que la acción se hará con cada uno de los objetos que atraviesen la tubería y de una manera más lógica, recordais el primer comando de esta seríe?

get-adcomputer -filter * | get-wmiobject -class win32_bios

Quiero mis ordenadores y de ellos quiero saber la bios, pues bien, con un sencillo script que indicamos entre { }, podemos generar el comando siguiente

Get-ADComputer -filter * | get-wmiobjetc win32_bios -ComputerName {$_.Name}

Ya que de cada objeto ADComputer generado por el primer cmdlet, cogeremos su Name y lo pasaremos al parámetro ComputerName del segundo cmdlet.

Como hemos dicho este recurso sería nuestra última opción pero siempre hemos de hacerlo de manera correcta empleando en orden las anteriores opciones siempre que sea posible.

Fuente Powershell.org