Subido por Guillermo Otero Matey

PowerShell-Core-y-Windows-PowerShell-Los-fundamentos-del-lenguaje

Anuncio
d e s c a r g a do en: e y b o oks. c o m
PowerShell Core y Windows PowerShell
Los fundamentos del lenguaje (2a edición)
Este libro sobre los fundamentos de Windows PowerShell (todas las versiones) y de PowerShell Core
(versión multiplataforma y Open Source) lo han escrito dos de los fundadores de la comunidad
PowerShell francófona (www.powershell­scripting.com). Se dirige a los profesionales del sector
informático que deseen iniciarse en las técnicas de scripting. Este libro propone un acercamiento
progresivo y didáctico para que los debutantes de verdad, es decir los que nunca han utilizado
PowerShell, puedan iniciar suavemente el aprendizaje y adquirir bases sólidas que les permitirán ser
autónomos. Los "falsos debutantes" también encontrarán el libro interesante ya que podrán perfeccionar
sus conocimientos a través de numerosas buenas prácticas que los autores han depurado a lo largo de los
capítulos.
Este libro habla de las bases del lenguaje y por lo tanto no depende de una determinada versión de
PowerShell. Cuando existen diferencias de implementación, estas se ponen de manifiesto explícitamente
con el objetivo de que los scripts puedan ser compatibles con versiones anteriores.
En los nueve primeros capítulos, el lector descubrirá las nociones esenciales para arrancar con fuerza,
tales como: la manipulación de los objetos, los tipos de variables, los operadores, los arrays, los bucles
y las estructuras condicionales, las funciones, los perfiles, etc. A continuación, el capítulo dedicado a los
snap­ins, los módulos y el acceso a la PowerShell Gallery explicará cómo enriquecer PowerShell con
comandos adicionales. Un capítulo importante trata sobre la gestión de errores y la depuración. Este
dará al lector las técnicas adecuadas así como los trucos que debe conocer para conseguir que sus scripts
sean robustos. Siendo la seguridad una preocupación permanente, la detallaremos en un capítulo
específico. El capítulo sobre el Framework .NET y .NET Core mostrará que las capacidades de PowerShell
son prácticamente ilimitadas. Se consagra un capítulo a las tecnologías llamadas de remoting, que
permiten ejecutar comandos y scripts PowerShell de manera remota: este capítulo explicará cómo
abordar estas tecnologías de la manera adecuada, preocupándose especialmente del aspecto de la
parametrización (tanto con WinRM como sobre SSH) y de las trampas a evitar (rebote, etc.).
Para finalizar, los últimos capítulos permitirán al lector utilizar PowerShell en el mundo empresarial con la
ayuda de numerosos casos prácticos, y descubrir el ecosistema PowerShell con la presentación de
herramientas de terceros y de los principales actores del mercado.
Existen contenidos complementarios al libro que se pueden descargar en esta página y del sitio web de la
comunidad PowerShell francófona: PowerShell­Scripting.com. d e s c a r g a do en: e y b o oks. c o m
Los capítulos del libro:
Prólogo – Introducción – Descubrimiento de PowerShell – Manipulación de objetos – Variables y tipos de
datos – Operadores – Arrays – Bucles y condiciones – Funciones y scripts – Gestión de archivos y fechas –
Perfiles PowerShell – Snap­ins, módulos y PowerShell Gallery – Gestión de errores y depuración –
Seguridad – Framework .NET y .NET Core – CIM/WMI – Ejecución remota – Casos de estudio – Recursos
adicionales – Conclusión – Anexos
Robin LEMESLE ­ Arnaud PETITJEAN
Robin LEMESLE es Ingeniero de Sistemas, experto en Virtualización y Scripting en entornos Windows
Server, para la compañía La Française des Jeux (loterías francesas).
Arnaud PETITJEAN es Ingeniero de Sistemas DevOps, especialista en infraestructuras Microsoft para una
gran empresa francesa de investigación. Gracias a su experiencia y sus numerosas contribuciones a la
comunidad, Microsoft le otorgó el título MVP (Most Valuable Professional) Cloud and Datacenter
Management en el año 2007. Gracias a esto, está en contacto directo con el equipo PowerShell de
Microsoft Corp. en Redmond.
Arnaud y Robin son los cofundadores de la comunidad PowerShell francófona (www.powershell­
scripting.com). Reconocidos profesionales de IT, trabajan a diario en vastos entornos distribuidos y
comparten encantados a lo largo de estas páginas su rica experiencia práctica y sus conocimientos
expertos sobre PowerShell.
2
Prólogo
3
Sobre PowerShell
Microsoft ha conseguido imponer la línea de comandos a los administradores de sistemas Windows aun
siendo la interfaz gráfica la que ha permitido el éxito de los productos de la firma de Redmond. No ha sido
fácil, ha tomado su tiempo, pero ¡Microsoft lo ha conseguido!
Jeffrey Snover, el inventor de PowerShell, recuerda constantemente que no hay que enfrentar PowerShell a
las interfaces gráficas, sino que ambos son complementarios. Su visión consiste en que la interfaz gráfica
tiene total cabida en los ordenadores personales pero mucho menos en los servidores... Es la razón por la
cual Windows Server en modo Core, o Nano Server, representa la tendencia en Microsoft. Esto es mucho
más cierto desde que Jeffrey Snover se sitúa al frente del departamento de Windows Server. En la
actualidad trabaja como arquitecto de la división Azure Stack.
Debemos reconocerlo, PowerShell no es un shell como los demás. Y es seguramente gracias a ello por lo
que ha podido convencer rápidamente ­ hasta a los más reacios ­ del interés existente del uso de la línea
de comandos. En efecto, aunque su nombre lo deja entrever, PowerShell es extremadamente potente (¡es
una realidad!), es también eficiente y extensible, pero es sobre todo fácil de aprender. Los diferentes
profesionales IT que han utilizado VBScript en el pasado le pueden convencer de ello...
A lo largo de los años y de las sucesivas versiones de PowerShell, hemos constatado cómo Microsoft se ha
esforzado en respetar los estándares del mercado. No podemos sino alegrarnos de este cambio de
orientación, y descubrirá a lo largo de los capítulos de este libro con qué facilidad PowerShell es capaz de
comunicarse con todos los estándares.
La última versión a la hora de escribir estas líneas, la versión 6, es el perfecto ejemplo del cambio de
mentalidad ocurrido en Microsoft; puede que no lo sepa, pero PowerShell es, en la actualidad, open source
y multiplataforma. Y, efectivamente, todo puede pasar. Satya Nadella, el nuevo CEO de Microsoft, no es
ajeno a este cambio de paradigma. Tan solo podemos alegrarnos, pues podremos aprovechar nuestro
conocimiento en PowerShell para administrar otros sistemas operativos diferentes a los de Microsoft.
Sobre este libro
Este libro no pretende hacer que se convierta en un experto PowerShell. Nuestro objetivo es cubrir las
funcionalidades principales del lenguaje con el fin de darle una visión general completa, pero también de
convertirlo en alguien autónomo capaz de escribir scripts sencillos sin tener que recurrir a Google. No
entraremos voluntariamente en los detalles técnicos, para preservar el rumbo y no perder de vista nuestro
objetivo.
En el momento de escribir estas líneas, acaba de salir a la luz PowerShell 6, también llamado PowerShell
Core. Como en el mundo de la empresa hay que saber hacer malabarismos entre las diferentes versiones
de PowerShell, hemos querido en este libro enseñarle técnicas universales que se pueden aplicar a todas
las versiones.
Hemos escrito este libro con PowerShell 5 y PowerShell 6 instalados en nuestras máquinas. Sin embargo, no
nos olvidamos de los IT Pro que cuentan con scripts que desean ejecutar en plataformas más antiguas. Por
este motivo, nos esforzaremos por proponer técnicas de compatibilidad hacia atrás, siempre que existan.
Dicho esto, podríamos decir a simple vista que más del 90 % del contenido de esta obra funcionará
perfectamente a partir de PowerShell versión 3 (e incluso menos). En efecto, conviene saber que las
versiones siguientes no revolucionan nuestra shell favorita, aportan «solamente» nuevas funcionalidades,
así como algunos nuevos comandos, de modo que el corazón y la filosofía general del producto quedan
perfectamente idénticos. que las versiones 4 y 5 no revolucionan nuestra shell favorita, aportan
«solamente» nuevas funcionalidades así como algunos nuevos comandos, quedando el corazón y la filosofía
general del producto perfectamente idénticos.
4
Otro punto que merece ser subrayado concierne el idioma de PowerShell. Hemos adoptado la postura de
utilizar plataformas de servidores en sus versiones US y no la versión española como en nuestros
anteriores libros, y eso por varias razones. La primera surge al constatar que muchas empresas, incluidas
las empresas españolas, instalan esencialmente versiones US (a las cuales añaden eventualmente el
paquete de idioma ES). La segunda razón es que Microsoft ya no ofrece, por desgracia (por razones
económicas), actualizaciones de la ayuda en español. Por último, pensamos que no será demasiado duro
convencerles de que es más complicado encontrar en Internet ayuda sobre un mensaje de error en español
que en inglés, la comunidad PowerShell es evidentemente más anglófona que hispanohablante, y eso
aunque esta última sea activa.
Sobre los autores
Arnaud Petitjean trabaja como ingeniero de sistemas DevOps en una gran empresa francesa de
investigación. A este respecto, maneja PowerShell de manera cotidiana para hacerse la vida más fácil y
también, en ocasiones, para resolver situaciones complejas... Le encanta decir que PowerShell le
proporciona superpoderes; poderes que le permiten hacer prácticamente de todo y sin duda le ayudan a
estar más seguro de sí mismo. Arnaud, por su experiencia y su contribución a la comunidad, ha recibido el
título de Most Valuable Professional (MVP) por parte de Microsoft desde el año 2007. Ha sido el primer MVP
francés en PowerShell.
Robin Lemesle, por su parte, trabaja también como ingeniero de sistemas para el grupo Française des Jeux.
Es experto en virtualización y en automatización de tareas.
Más allá de su experiencia profesional, donde practican con PowerShell a diario, los autores animan la
comunidad francófona de PowerShell y aprovechan gracias a ella muchas experiencias relativas a su uso.
Ambos son autores de numerosos libros sobre PowerShell y siguen esta maravillosa herramienta desde la
aparición de la primera versión beta, liberada en 2006.
5
Introducción
6
¿Para qué utilizar los scripts?
Desde siempre, los administradores de sistemas utilizan scripts para automatizar las tareas pesadas. En
efecto, no existe mayor aburrimiento que repetir operaciones de administración tales como la creación de
cuentas de usuarios, por citar solamente la más clásica. Sin embargo, ¿qué hay más gratificante y
enriquecedor a nivel personal que escribir un script que realice estas tareas para nosotros?
Podríamos imaginar que el tiempo invertido en desarrollar un script solo es rentable si pasamos menos
tiempo haciendo el script que realizando la tarea manualmente, pero esto es una visión algo limitada de las
cosas. Y va a entender la razón…
Retomando el ejemplo citado anteriormente, imaginemos que tuviéramos que crear 200 cuentas de usuario.
Si pasamos de media cinco minutos por cuenta (no siendo molestado por el teléfono, ni los usuarios, ni por
una reunión súper importante planificada en el último minuto), necesitaremos aproximadamente de dos a
tres días a tiempo completo para realizar este trabajo, sin contar todos los errores que se habrán producido
(nombre mal deletreado, espacio del home directory o de la cuenta de correo olvidado, permisos mal
otorgados, etc.). Imaginemos ahora que decidimos escribir EL script «que va bien». Supongamos que somos
principiantes y que, en el peor de los casos, tardamos cuatro días en acabarlo. Aun así durante estos cuatro
días habremos aprendido muchísimas cosas y tendremos la posibilidad de reutilizar este script en una
próxima creación de usuarios.
Además, desde el momento en que empezamos a reutilizar los scripts ganamos tiempo; y este tiempo
podremos dedicarlo a otras cosas. Lo más importante es sobre todo que habremos mejorado nuestro nivel
de «scripting». Podremos por lo tanto aprovechar nuestros nuevos conocimientos para la realización de
otras tareas de administración de sistemas aún más complejas. Esto hace que el trabajo del administrador
de sistemas sea netamente más interesante, ¡hasta lúdico!
Por otra parte, el scripting tiene una vertiente muchas veces ignorada por los responsables informáticos: la
de la calidad. Calidad, que es la palabra clave en la mayoría de las grandes empresas donde una de las
principales preocupaciones es el aumento constante de la calidad de servicio a través de buenas prácticas
mencionadas por ITIL (Information Technology Infrastructure Library). Aunque el scripting no pretende
solucionarlo todo, aporta sin embargo una base estable e importante. En efecto, su principal interés reside
en la automatización de tareas eliminando errores inducidos por un tratamiento manual.
Por último, la importancia de la automatización en el Cloud Computing resulta evidente. ¡Está en el centro
del mismo! Sin automatización, simplemente no existiría el Cloud. PowerShell representa la última
generación de los lenguajes de scripts; y gracias a eso ha sabido elegir lo mejor de cada uno de ellos. No
cabe duda de que, aprendiendo el uso de PowerShell, adquirirá competencias de fuerte valor añadido,
competencias que podrá poner en valor a muy corto plazo. Entonces, ¿a qué espera?... ¿preparado?... ¡Ya!
Histórico de los lenguajes de script
En los inicios de la informática, existía Unix… Este sistema operativo apareció en 1968, y con él aparecieron
los primeros entornos de ejecución de scripts llamados «shells» (o «conchas» en español). Vamos a citar
algunos. Por citar los más conocidos (en orden cronológico): Bourne shell (sh), C shell (csh), Korn shell (ksh),
Bash shell (la shell bash es un proyecto GNU iniciado por la Free Software Foundation). Llamamos «scripts
shell» a los scripts desarrollados para estos entornos. En la actualidad, se utilizan ampliamente en la
administración de sistemas Unix/Linux.
Mientras tanto, Bill Gates desarrollaba las principientes versiones de DOS en Bellevue en un barrio de
Seattle cuya primerísima versión se llamó Q­DOS por Quick and Dirty Operating System, sistema operativo
realizado a todo correr. En 1981 MS­DOS hizo su aparición en su versión 1.0 con los primeros scripts batch
(ficheros con extensión .bat). Estos últimos eran (y siguen siendo) muy limitados en términos de
funcionalidad, pero siguen siendo utilizados por los administradores de sistemas Windows para realizar
tareas sencillas.
7
1987 es el año de la aparición del lenguaje PERL (Practical Extraction and Report Language), desarrollado por
Larry Wall, en su versión 1.0, para las plataformas Unix. Este lenguaje ­ cuyo funcionamiento se basó en los
scripts shell y el lenguaje C ­ tiene como objetivo la manipulación de archivos, datos y procesos. Hizo falta
esperar todavía diez años para ver aparecer PERL en el mundo Windows, en 1997 con el kit de
recursos técnicos de Windows NT4.0.
Existen todavía muchos lenguajes de scripts orientados a sistemas, que no detallaremos, ya que este no es
el objetivo de este libro, pero que merecen sin embargo ser citados. Se trata de Rexx, Python, S­Lang,
Tcl/tk, Ruby, Rebol, etc.
Otros dos lenguajes de scripts muy extendidos en el desarrollo web son JavaScript y VBScript. El primero fue
desarrollado por Netscape y hecho público en 1995 (en Navigator 2.0) con el fin de hacer los sitios web más
dinámicos y más vivos, así como conseguir un mayor nivel de interacción en las páginas HTML (HyperText
Markup Language). La respuesta de Microsoft no se hizo esperar cuando sacó el lenguaje JScript (en Internet
Explorer 3.0), lenguaje muy similar al de Netscape. Para poner a todo el mundo de acuerdo, el ECMA
(organismo internacional de normalización de sistemas de información y comunicación) normaliza estos dos
lenguajes en 1997 tomando lo mejor de cada uno para realizar el ECMAScript (ISO/IEC 16262). Es también
con Internet Explorer 3 cuando VBScript hizo su aparición. VBScript recoge la integridad de las
funcionalidades de JavaScript pero con una sintaxis cercana al Basic, mientras que JavaScript se parece más
a Java o a C.
Volviendo al desarrollo de scripts para sistemas, antes de la aparición del kit de recursos técnicos de NT4.0,
los administradores de sistemas Windows no disponían de otra opción que utilizar los scripts batch MS­DOS.
Estos últimos eran sencillos de realizar cuando se trataba de realizar tareas triviales (como por ejemplo
montar una unidad de red), pero podían volverse muy complicados en cuanto deseábamos analizar un
archivo, o realizar un simple bucle. Menos mal que Microsoft tuvo la buena idea de incluir en el kit de
recursos, además de Perl, un intérprete de script KiXtart. KiXtart extendía de manera significativa los scripts
batch aportando numerosas funcionalidades de red, así como un potente lenguaje de script realmente
pensado para la administración de sistemas. En resumen, KiXtart era una alternativa real a los viejos scripts
batch. Solo existía un pequeño «pero», aunque KiXtart fue desarrollado por miembros de Microsoft no
estaba soportado oficialmente por la empresa de Redmond.
Entonces apareció discretamente con Windows 98 y el Option Pack de NT4.0 una fabulosa herramienta de
administración llamada WSH (Windows Script Host). Este último ofrecía un potente entorno de ejecución de
scripts VBScript, pero era muy poco conocido en sus inicios. Por este motivo, en detrimento de los usuarios,
numerosos virus usaron nuevas funcionalidades ofrecidas por WSH, poniendo en evidencia sus
capacidades…
Sin embargo, hasta estos últimos años, WSH/VBScript fueron la pareja más extendida para la realización de
tareas de administración de sistemas en el mundo Windows. Sin embargo, esto ha cambiado desde la
aparición de PowerShell...
¿Y PowerShell en todo esto?
Microsoft, hacia los años 2004­2005, tomó conciencia de las limitaciones de su sistema operativo basado
casi exclusivamente en una interfaz gráfica y las dificultades encontradas por los usuarios para automatizar
tareas repetitivas. Por este motivo, Bill Gates en persona reclutará, de la empresa Digital Equipment
Coporartion (DEC), a un brillante ingeniero llamado Jeffrey Snover para resolver esta laguna. Este último
pasará algunos meses trabajando en el asunto, presentará una primera versión embrionaria pero
convincente a la dirección de Microsoft y obtendrá, a cambio, un equipo de desarrolladores y el presupuesto
necesario. Es así como nace el proyecto Monad, que más tarde se convertiría en Windows PowerShell,
cuando se lanzó la primera versión al público a finales de 2006.
Tras el nacimiento de PowerShell, la firma de Redmond cambió radicalmente su visión de las cosas y adoptó
un giro de 180º (y no de 360º). La nueva estrategia de Microsoft respecto a la administración de todos sus
8
productos se vuelve ahora orientada a la línea de comandos, mientras que, desde la creación de Windows, la
interfaz gráfica era la reina. La idea no era hacer desaparecer las interfaces gráficas, sino más bien dar
prioridad a PowerShell para la realización de tareas de administración, preservando las herramientas gráficas
que han contribuido al éxito de Windows.
Esta estrategia ha dado sus frutos, ya que diez años después no existe ningún producto de la gama
System Center que no pueda administrarse íntegramente con PowerShell, relegando las interfaces gráficas
a los usuarios más novatos.
Hoy en día, PowerShell está profundamente anclado en los sistemas Windows tanto en su versión cliente
como servidor, si bien desde Windows Server 2008 R2 cada nuevo rol o característica instalada conlleva la
disponibilidad de nuevos comandos. Encontramos comandos para gestionar el directorio Active Directory, las
directivas de grupo, el clustering, las trasferencias inteligentes de ficheros BITS, IIS, Hyper­V, etc. En
resumen, la lista es ahora demasiado larga para que la enumeremos.
Y cuando vemos el fervor con esta shell por parte de terceros como VMware, Citrix, HP, IBM, NetApp y otros,
todos proponen módulos o snap­ins para gestionar sus aplicaciones o sus equipos a través de PowerShell.
Este fenómeno está lejos de detenerse, y es mejor, ya que deseamos que siga.
Once años después de la versión 1 de PowerShell (y de las versiones que han ido apareciendo cada dos o
tres años), Microsoft ha liberado el código fuente y lo ha situado bajo licencia MIT. PowerShell 6.0 se ha
convertido en la primera versión multiplataforma (es posible instalar PowerShell en Linux ­todas las
distribuciones principales están soportadas­ y Mac OS). Esta hazaña es posible gracias a la «portabilidad»
del Framework .NET en una versión también open source llamada .NET Core. Descubrirá a lo largo de este
libro que PowerShell es realmente una revolución en sí mismo, y que modifica profundamente la manera de
administrar los sistemas Windows (¡y no solo Windows!) de la presente década y de la que viene.
Interés de los scripts frente a los lenguajes de programación
Un script es un simple fichero de texto en el cual se suceden todas las instrucciones que lo componen, de
manera similar a cualquier código fuente. La diferencia entre un lenguaje de script y un lenguaje de
programación propiamente dicho (como C/C++, C#, Visual Basic, o Delphi) es que un script no se compila, es
decir que no se transforma en un archivo binario que puede ejecutar directamente la máquina, sino que
hace falta obligatoriamente un intérprete de comandos llamado «anfitrión de script» o «shell» para
ejecutarlo.
Uno de los principales intereses de los scripts respecto a los lenguajes de programación clásicos es que
hace falta pocas instrucciones para llegar a hacer la misma cosa u operaciones similares. La sintaxis está
generalmente simplificada y la programación es menos restrictiva (no hace falta declarar las variables,
existen pocos tipos de datos, etc.). Además, el código fuente de un script es siempre accesible.
En resumen…
Los scripts permiten:
Un ahorro de tiempo: son capaces de realizar tareas complejas y ser ejecutados automáticamente
por el sistema, sin intervención humana. Gracias a su simplicidad, las tareas de administración se
realizan más rápidamente. Los scripts hacen por lo tanto ganar un tiempo considerable a los
administradores.
Limitar los errores: un script solo necesita escribirse una vez y puede utilizarse un número
indefinido de veces. Por lo tanto, los riesgos de cometer errores vinculados a la realización manual de
una tarea se ven, en gran medida, reducidos. Los scripts son por lo tanto sinónimo de
reproducibilidad y de calidad.
Más flexibilidad: los scripts pueden adaptarse a todas las situaciones integrando un mínimo de
lógica.
9
Descubrimiento
de PowerShell
10
Presentación de PowerShell
PowerShell es a la vez un intérprete de comandos y un potente lenguaje de scripts. Saca su potencia
gracias en gran parte al Framework .NET en el que se apoya. Conocido por los desarrolladores, el
Framework .NET lo es menos por parte de los administradores de sistemas y del resto de desarrolladores
de scripts; lo cual es normal... Para entenderlo en pocas palabras, el Framework .NET es una biblioteca de
clases inmensa a partir de la cual crearemos objetos; objetos que nos permitirán actuar sobre el conjunto
del sistema operativo con un mínimo esfuerzo. Todos los que han probado la potencia del Framework .NET
no escatimarán en halagos sobre él. Es por lo tanto gracias a este último como PowerShell desarrolla toda
su inteligencia y su orientación a objetos. Y es esta característica de manipular objetos la que hace de
PowerShell ¡una shell de excepción!
Con PowerShell no trabajará únicamente con texto, como es el caso con la mayoría de las demás shells,
sino con objetos; y esto sin apenas darse cuenta. Por ejemplo, cuando use una pipe «|» para pasar datos a
un comando, no transmitirá texto, sino un objeto con todo lo que lo caracteriza (sus propiedades y sus
métodos). Gracias a esto es cómo los scripts PowerShell son, por lo general, más concisos que en los demás
lenguajes tales como VBScript por citar solo uno.
Por otra parte, PowerShell posee un juego de comandos extremadamente rico y que no deja de crecer con
cada nueva versión. Desde la versión 3 contábamos ya con aproximadamente más de doce veces el número
de comandos del intérprete CMD.exe.
Dicho esto, los comandos CMD pueden usarse desde PowerShell, si existe la necesidad. Los comandos
PowerShell poseen la inmensa ventaja de estar todos diseñados en base al mismo modelo (heredan todos
del mismo tipo de objetos). Tienen nombres fáciles de retener, y es fácil adivinar los nombres de comandos
que no conocemos. Cada uno de ellos posee además una (o varias) serie de parámetros, parámetros que
memorizaremos gracias a la coherencia existente en conjunto.
Por último, para terminar esta presentación, sepa que existe una ayuda en línea integrada en la consola
que puede consultar en cualquier momento y que es muy completa. Esta ayuda recordará algunas cosas tal
vez a los administradores de sistemas OpenVMS que reconocerán de paso la estructura sintáctica. La ayuda
en línea da acceso a tres niveles de explicaciones: estándar, detallada, o completa.
Todos los puntos que expondremos hacen de PowerShell un lenguaje de script muy potente y sobre todo
fácil de aprender, hasta para los que no han probado nunca la programación.
Histórico de versiones
En los años 2003­2004, la firma de Redmond comprende que le falta un lenguaje de script de verdad,
homogéneo pero sobre todo que ya era hora que todos sus productos de la gama System Center pudieran
administrarse completamente por línea de comandos.
Por este motivo, el proyecto Monad vio la luz, proyecto que producirá a finales del 2006 la primera versión
de PowerShell. Hasta la versión 3, el ritmo de aparición de versiones se realizaba cada tres años, que se
correspondía con la aparición de las versiones de Windows. Desde 2012, el ritmo se ha acelerado
claramente y el ciclo se ha reducido a la mitad, lo que supone contar con una nueva versión cada dieciocho
meses. Este ritmo sostenido no es producto del azar; es el fruto de la adopción de un enfoque Agile dentro
de Microsoft, que consiste en publicar productos más a menudo, aunque con menos nuevas funcionalidades.
Este enfoque se contrapone al utilizado hasta el momento, que consistía en publicar una nueva versión
principal con muchísimas evoluciones pero a un ritmo mucho más lento. Este nuevo paradigma se enmarca
de lleno en el enfoque DevOps, del que tendremos ocasión de hablar más detenidamente... Monad.
11
Histórico de la aparición de versiones
En el mundo de PowerShell, 2016 es una fecha clave, pues ese año apareció la primera versión de
PowerShell, llamada «PowerShell Core». En lo sucesivo, ya no se basa en el Framework .NET, sino en .NET
Core (tendremos la ocasión de hablar más detenidamente en el capítulo Framework .NET y .NET Core). .NET
Core es la versión open source del Framework .NET. PowerShell Core aparece, oficialmente, en la versión
5.1 y forma parte de Windows Server Nano, también llamado «Nano Server». En paralelo, algunos meses
antes, Microsoft publicó en Github.com el código fuente de PowerShell y lo liberó bajo licencia MIT.
PowerShell se convierte, entonces, en open source y multiplataforma de manera oficial gracias a la
portabilidad intrínseca de .NET Core, también multiplataforma.
La aparición de Windows Server 2016 es una fecha importante en el ecosistema de PowerShell, pues es
también sinónimo de ruptura. En efecto, encontramos en lo sucesivo dos versiones distintas de PowerShell,
que son «Windows PowerShell 5.1» y «PowerShell Core 5.1». Esto quiere decir que esta versión no es
multiplataforma, a diferencia de «PowerShell Core». En el momento de escribir estas líneas, Microsoft ha
decidido que Windows PowerShell 5.1 será LA última versión de PowerShell basada en el Framework .NET y
que no la hará evolucionar (salvo correcciones vinculadas a la seguridad). Los esfuerzos de desarrollo se
centrarán, en lo sucesivo, únicamente en PowerShell Core, versión que pasará a la versión 6.0 final en
2018.
Es importante subrayar el hecho de que pueden coexistir Windows PowerShell 5.1 y PowerShell Core, así
como varias versiones de PowerShell Core una junto a otra.
Diferencias entre Windows PowerShell y PowerShell Core
La principal diferencia radica en que PowerShell Core es multiplataforma, contrariamente a Windows
PowerShell, que funciona solo bajo Windows. Es posible, por tanto, ejecutar PowerShell Core en Mac OS y
en las principales distribuciones de Linux, como RedHat, Suse, Debian, etc.
12
En términos de funcionalidades, por sorprendente que pueda parecer, PowerShell Core 6.0 no cubre el 100
% de la superficie funcional de Windows PowerShell 5.1, sino que aporta otras funcionalidades que no
existirán en Windows PowerShell, puesto que, como indicábamos, Windows PowerShell 5.1 será la última
versión de Windows PowerShell. Descubriremos las nuevas funcionalidades de PowerShell Core 6.0 en la
siguiente sección.
En cuanto a funcionalidades que faltan cuando se trabaja con PowerShell Core, podemos destacar
principalmente:
Las interfaces gráficas Windows Forms y WPF.
Los workflows PowerShell.
Dado que la vocación principal de un lenguaje de script no es la creación de aplicaciones, podemos aceptar
la desaparición de las interfaces gráficas, sobre todo en la era DevOps en la que vivimos.
En cuanto a los workflows de PowerShell, estos no han tenido un éxito unánime entre la comunidad
PowerShell debido a su complejidad y a sus implementaciones extrañas. Por este motivo, muy pocos
profesionales los utilizan, de modo que Microsoft ha decidido dejarlos a un lado de momento. Dicho esto, la
verdadera razón de la «no compatibilidad» de los workflows se produce porque Microsoft se basa en la API
workflow del Framework .NET, que tampoco está presente en .NET Core.
Principales evoluciones de las distintas versiones
Desde un punto de vista cuantitativo, el número de comandos ha aumentado de forma espectacular. Hemos
pasado de 129 comandos en la versión inicial de PowerShell a más del doble en la segunda versión, hasta
alcanzar un número incalculable en las siguientes versiones.
En términos de funcionalidades importantes aparecidas a lo largo de las versiones, podemos citar:
Versión 1 : funcionalidades iniciales.
Versión 2 : funciones avanzadas, ayuda integrada, módulos, remoting WinRM, jobs, PowerShell ISE.
Versión 3 : delegated endpoints, scheduled jobs, workflows, PowerShell ISE mejorado (snippets,
IntelliSense, etc.), autocompletado mejorado (propone los valores de los parámetros), ayuda no
incluida pero descargable, mejoras del rendimiento, sintaxis simplificada, redirección de flujos,
expansión automática de las propiedades de objetos contenidos en un array, Select­Object
optimizado (­first), PowerShell Web Access, etc.
Versión 4 : gestión de configuraciones (DSC), sistema de actualización de la ayuda mejorado, Get­
Process mejorado (­IncludeUserName), Get­FileHash, PowerShell ISE mejorado (workflow debugging,
bugfix), etc.
Versión 5.x: mejora de DSC, gestión de paquetes para las aplicaciones (OneGet, acceso a la
PowerShell Gallery y/o a repositorios privados de módulos PowerShell (PowerShellGet), transcripción
mejorada, scripblock logging, comandos CMS (Cryptographic Message Syntax), etc.
Versión 6 : soporte multiplataforma, instalación simultánea de varias versiones de PowerShell,
rendimientos mejorados, una mejor codificación de archivos de texto, mejora de las interacciones con
los servicios web, etc.
13
Plataformas soportadas
1. Plataformas cliente
Windows 7
SP1
Versiones soportadas
Windows 8.1
Windows 10
V3 / Frarmework .NET 4.0
A instalar
V4 / Frarmework .NET 4.5
A instalar
Nativo
V5.1 / Frarmework .NET 4.5.2
A instalar
A instalar
Nativo
V6.0 (Core) / .NET Core 2.0
A instalar
A instalar
A instalar
Las celdas vacías significan que la configuración no está soportada. La mención «A instalar» significa que
es posible instalar PowerShell (en la versión precisada en la línea correspondiente) sobre esta plataforma.
«Nativo» indica que PowerShell está instalado de manera nativa.
Cabe destacar que Windows PowerShell 2.0 ya no está soportado por Microsoft desde agosto de 2017.
2. Plataformas servidor
Versiones soportadas
Windows
Server
2008 R2
Windows
Server
2012/R2
Windows
Server
2016
V3 / Frarmework
4.0
.NET
A instalar
V4 / Frarmework
4.5
.NET
A instalar
Nativo
V5.1 / Frarmework
4.5.2
.NET
A instalar
A instalar
Nativo
V6.0 (Core) / .NET Core
2.0
A instalar
A instalar
A instalar
Mac OS
Linux
A instalar
A instalar
Antes de utilizar PowerShell en las versiones mencionadas arriba, tendrá que realizar dos operaciones. La
primera, obligatoria, es verificar la versión del Framework .NET instalada, y realizar una actualización de la
versión si lo necesita. En efecto, como mencionamos en la introducción, PowerShell obtiene su
«inteligencia» de .NET (en sentido general), de modo que este es un requisito previo.
La segunda operación es, efectivamente, instalar PowerShell. Para ello, si desea instalar Windows
PowerShell, tendrá que instalar Windows Management Framework. En caso contrario, si se trata de la
versión Core de PowerShell, tendrá que descargarla desde GitHub, en la siguiente dirección:
http://github.com/PowerShell/PowerShell.
Windows
PowerShell
se
instala
siempre
en
la
ruta
C:\Windows\System32\WindowsPowerShell\v1.0. El número de versión en la ruta no hace
referencia a la versión de PowerShell, sino a la versión del motor de ejecución de PowerShell que, en
este caso, sigue siendo la versión 1.0.
14
Comenzando con PowerShell
Además de la consola por línea de comandos, existe desde la versión 2.0 un excelente entorno de
desarrollo de scripts PowerShell en modo gráfico llamado PowerShell ISE. La ventaja de este es que se
instala de base en todas las versiones de Windows. Su «inconveniente» es que solo es compatible con
Windows PowerShell. Por consiguiente, deja de lado a PowerShell Core.
Por este motivo, desde hace poco, Microsoft da preferencia a un ordenador personal de desarrollo
multiplataforma llamado Visual Studio Code. Este entorno de desarrollo es polivalente, pues es compatible
con todas las ediciones de PowerShell y está disponible para todas las plataformas (Windows, Mac, Linux).
En un primer momento, nos interesaremos en la consola «clásica», consola que podríamos calificar de
«histórica» por el hecho de que existe desde la primera versión.
1. Consola Windows PowerShell clásica
a. Arranque de la consola
Cuando arrancamos la consola PowerShell, debemos saber que esta se ejecuta con permisos simples de
usuario y por lo tanto limitados; y eso aunque abramos nuestra sesión con una cuenta de administrador.
No se sorprenda por lo tanto si se le deniega el acceso a ciertos directorios, a ciertas claves de registro o
a ciertos comandos. Esto es debido a un conocido mecanismo de seguridad llamado «User Access
Control» (UAC), en castellano «control de cuentas de usuario»
Para abrir una consola clásica o gráfica (ISE) con permisos de Administrador, debe sistemáticamente
hacer clic con el botón derecho en el icono de PowerShell (o PowerShell ISE) y elegir Ejecutar como
Administrador (o Run as Administrator en un sistema en inglés) como sigue.
Ejecutar como Administrador ­ Menú contextual
Verá la diferencia entre las consolas PowerShell abiertas como administrador y las que no lo son
observando el título de la ventana arriba a la izquierda de estas, como se muestra aquí:
15
Ejecutar como Administrador ­ título de la ventana
b. Descubrir la consola
A simple vista, no es posible distinguir una ventana «consola PowerShell» de una ventana «línea de
comandos CMD.exe», salvo por el color de fondo, que difiere (azul para una y negro para la otra).
Consola Windows PowerShell «clásica» al arrancar
El año indicado en el Copyright puede ser diferente en su sistema. No es el mismo en función de las
versiones de PowerShell. Aquí se trata de PowerShell versión 3.
Para determinar la versión de PowerShell instalada en su ordenador personal o servidor, existe un
pequeño truco que consiste en visualizar el contenido de la variable $PSVersionTable.
16
$PSVersionTable con Windows PowerShell
$PSVersionTable con PowerShell Core
El valor de la propiedad PSVersion indica la versión instalada. La propiedad PSEdition indica, por su
parte, la edición de PowerShell. El valor Desktop representa a Windows PowerShell, mientras que el
valor Core representa a PowerShell Core.
A continuación presentamos las teclas y las combinaciones de teclas que permiten «navegar» en la
consola:
17
Teclas
Descripción
[Tab]/[Mayús][Tab]
Realiza el autocompletado de una ruta, nombre de
comando, parámetro o valor del parámetro (si el tipo es un
valor Enum).
[Esc]
Borra la línea de comando actual.
[Flecha
arriba]/[Flecha
abajo]
hacia
hacia
Permite ver el histórico de comandos ejecutados.
[Flecha
hacia
la
derecha]/[Flecha hacia la
izquierda]
Desplaza el cursor sobre la línea de comandos actual.
[Ctrl][Flecha
derecha]
hacia
la
Desplaza el cursor hacia la derecha saltando de palabra en
palabra en la línea de comandos.
[Ctrl][Flecha
izquierda]
hacia
la
Desplaza el cursor hacia la izquierda saltando de palabra en
palabra en la línea de comandos.
[Inicio]
Devuelve el cursor al inicio de la línea de comandos.
[Fin]
Devuelve el cursor al final de la línea de comandos.
[Ctrl] C
Pone fin a la ejecución del comando actual.
[Ctrl][Pausa]
Termina la ejecución de la consola.
A partir de PowerShell 5.1 (todas las versiones), aparece el módulo PSReadline y aporta una serie de
nuevas funcionalidades a la consola así como nuevas combinaciones de teclas, he aquí un extracto:
Teclas
Descripción
Clear­Host).
[Ctrl] L
Borra la pantalla (equivalente al comando
[Ctrl] r
Busca hacia arriba en la historia.
[Ctrl] s
Busca hacia abajo en la historia.
[Ctrl] a
Selecciona la línea de comando en curso.
[Ctrl] c
Copia el texto seleccionado en el portapapeles. Si no hay
ningún texto seleccionado, se interrumpe la ejecución en
curso.
[Ctrl] C
Copia el teto seleccionado en el portapapeles. Si no hay
ningún texto seleccionado, copia toda la línea de comandos.
[Ctrl][Espacio]
Completa la entrada en caso de fin único. Si no, presenta
todas las posibilidades en un menú de selección.
[Ctrl][Page Up]
Mueve la visualización una pantalla hacia arriba.
[Ctrl][Page Down]
Mueve la visualización una pantalla hacia abajo
La funcionalidad principal de PSReadline es, indiscutiblemente, la coloración sintáctica en la consola, así
como el hecho de que el histórico no se pierda de una consola a otra. ¡Se aproxima seriamente a las
funcionalidades de la aplicación Terminal de Linux! Tan solo faltaría la transparencia y el
redimensionamiento dinámico de la consola (arrastrando la esquina inferior derecha de la consola) y
sería ideal. ¡Efectivamente, sepa que todo esto es posible en Windows 10!
18
Por último, sepa que existen otras combinaciones de teclas aportadas por PSReadline y que todo es
bastante parametrizable. Para descubrir estos detalles, le invitamos a probar los comandos Get­
PSReadlineKeyHandler y Get­PSReadlineKeyHandler.
2. El entorno integrado de escritura de scripts (ISE)
PowerShell ISE es un editor de scripts en modo gráfico disponible de forma nativa en todas las
plataformas Windows, lo cual lo convierte en algo muy práctico y también, de algún modo, imprescindible.
Gracias a él, adiós al viejo Bloc de notas y viva el resaltado sintáctico, el número de líneas, el depurador, la
ejecución paso a paso, la ayuda en línea en modo gráfico, etc.
Desde la versión 3 de PowerShell, el editor ISE se ha simplificado y es más funcional. Resulta mucho más
sencillo porque ISE está dotado de una verdadera consola embebida, similar a la consola clásica, pero
mejorada (cuando no se disponía del módulo PSReadline...).
Disponemos también de la posibilidad de abrir una consola PowerShell sobre una o varias máquinas
remotas directamente en el editor. En fin, ¡todo un placer! Sin embargo, conviene saber que Microsoft ha
decidido, como con Windows PowerShell, no hacer evolucionar ISE en beneficio de Visual Studio Code,
que, por su parte, es multiplataforma. En efecto, ISE se basa en la tecnología WPF, tecnología que no
puede portarse a .NET Core.
Aquí tiene la interfaz de ISE en todo su esplendor:
Consola PowerShell ISE ­ Pantalla por defecto
19
En la pestaña superior, encontramos el editor de scripts, en el cual vemos las pestañas. Esto permite
trabajar sobre varios scripts al mismo tiempo. El panel inferior permite insertar directamente comandos de
manera interactiva, como en la consola original.
En la categoría de funcionalidades interesantes encontramos la función IntelliSense, muy apreciada. Para
activarla, basta con empezar a teclear el comienzo del comando para que se realice una propuesta
indicando la sintaxis. Después debe elegir la que le interesa usando la tecla [Tab] o [Entrar].
Función IntelliSense de PowerShell ISE
La funcionalidad IntelliSense permite gestionar un conjunto de propuestas que se muestran cuando se
usan los siguientes caracteres:
Guión ­ detrás del verbo de un comando como Get­ o antes de insertar el nombre de un
parámetro del comando, como en Get­Command ­.
Punto. después de un nombre de variable, como por ejemplo
$profile..
Doble dos­puntos:: después de un tipo de objeto, como por ejemplo
Backslash\ detrás de cada provider, como por ejemplo
[int]::.
C:\.
En el caso en el que IntelliSense no aparezca, es posible forzarlo gracias a la combinación de
teclas [Ctrl][Espacio].
Por último, terminaremos con otra funcionalidad muy práctica; hablamos de los snippets. Un snippet es un
extracto de código o una estructura de código lista para su uso que se insertará directamente en el editor.
El objetivo es hacer ganar tiempo al desarrollador de script. Para acceder a esta funcionalidad basta con
utilizar la combinación de teclas [Ctrl] J o ir al menú Edit ­ Start Snippets para que aparezca la lista que
contiene todos los snippets.
20
Selección de un snippet en la consola ISE
Una vez seleccionado con la tecla [Entrar], el código PowerShell aparece directamente en la ventana de
script.
Inserción de un snippet
Los snippets son muy prácticos ya que permiten acceder directamente a la estructura del código sin tener
que consultar la ayuda. Para los desarrolladores de scripts más aguerridos, los snippets hacen ganar un
tiempo precioso, sobre todo cuando escriben funciones avanzadas con ayuda integrada, por ejemplo.
Además se pueden crear snippets propios gracias al comando
New­IseSnippet.
21
3. Visual Studio Code
Visual Studio Code, desarrollado por Microsoft, es el editor que tiene el viento a favor, en este momento,
en la comunidad de los programadores de scripts y desarrolladores de cualquier tipo. Este, en efecto,
además de ser gratuito y multiplataforma (por si fuera poco), tiene la particularidad de soportar no solo
PowerShell, sino también muchos otros lenguajes de programación. Contamos, entre ellos, con C++, C#,
CSS, DockerFile, Go, HTML, Java, JavaScript, JSON, Less, Markdown, PHP, Python, Sass, T­SQL,
TypeScript, etc.
Además, su sólida integración con el gestor de código fuente Git hace de él una herramienta
imprescindible. Está lejos de ser tan potente como el Visual Studio clásico, pero para usos modestos se
sobra y se basta. En cualquier caso, para nosotros, programadores de scripts PowerShell, será perfecto.
Además, retoma el conjunto de las funcionalidades de PowerShell ISE. De modo que no podemos más
que enfatizar el uso de Visual Studio Code en lugar de ISE. Sobre todo porque, como dijimos en la
presentación de ISE, este no va a evolucionar más.
Cabe destacar que Visual Studio Code se basa en el framework Electron, proyecto open source que
permite la generación de interfaces gráficas multiplataforma (implementando JavaScript, HTML y CSS).
Electron se basa en tecnologías conocidas, como Node.js y Chromium. Existe, por tanto, un vínculo de
parentesco evidente entre el editor Atom de GitHub y Visual Studio Code, pues ambos editores están
basados en Electron.
Para que se haga una idea de cómo es Visual Studio Code, he aquí una captura de pantalla:
Si los colores oscuros por defecto no le convienen, sepa que puede cambiar de tema. Existen muchos,
incluso uno llamado PowerShell ISE que recupera el mismo código de colores del editor homónimo.
22
Un poco a la manera de ISE, la pantalla se divide en dos partes horizontales con, en la parte superior, el
código y, en la parte inferior, una consola PowerShell interactiva. Esta última permite visualizar el
resultado de la ejecución de un script.
Para sacar el máximo provecho de Visual Studio Code con PowerShell, habrá que instalar la extensión
PowerShell. Para abrir el menú de las extensiones, vaya al menú View ­ Extensions (o pulse [Ctrl][Mayús]
X), o bien haga clic en el icono
de la barra vertical situada en la parte izquierda de la pantalla.
Una vez que se muestre el menú de extensiones, hay que buscar Powershell en la zona de texto y, a
continuación, hacer clic en el botón Install cuando aparezca la extensión PowerShell. Realizadas estas
operaciones, la extensión se descarga y se instala. Bastará entonces con hacer clic en el botón Reload
para que Visual Studio Code tenga en cuenta la extensión.
La codificación de los archivos de texto producidos por defecto con Visual Studio Code es UTF­8 (No
BOM), lo cual plantea un problema para la visualización de los caracteres acentuados en la consola
Windows PowerShell clásica.
Para evitar estos problemas, recomendamos guardar los archivos seleccionando alguno de los
formatos siguientes: UTF­8 with BOM, ISO 8859­1 o incluso Windows 1252.
23
Volveremos a hablar de la problemática ligada a los formatos de codificación cuando abordemos la
manipulación de archivos de texto.
Visual Studio Code es muy configurable, hasta el punto de que en ocasiones resulta difícil situarse entre la
cantidad de configuraciones que se nos proponen.
Para comprender bien su filosofía de funcionamiento, conviene saber que:
La paleta de comandos juega un rol central, pues es una especie de motor de búsqueda de
funcionalidades y de parámetros. Para activarla, lo más sencillo es utilizar la combinación de teclas
[Ctrl][Shift] P o bien ir al menú Ver ­ Command Palette.
La personalización de los distintos parámetros se realiza sobrecargando el archivo de configuración
por defecto. Dicho de otro modo, crearemos un archivo de configuración específico que se cargará
junto al archivo por defecto. Tendrá en cuenta sus cambios frente a este último. Los archivos de
configuración son archivos de texto en formato JSON. El acceso a los parámetros se realiza a través
del menú File ­ Preferences ­ Settings o a través de la combinación de teclas [Ctrl] , (coma).
Como le hemos indicado, nuestro nuevo editor codifica sus archivos por defecto en UTF­8, lo cual plantea
un problema de visualización de caracteres acentuados cuando ejecutamos los scripts en la consola
PowerShell clásica. Vamos a modificar este comportamiento por defecto para seleccionar otro formato de
codificación. Abra, por tanto, el archivo de parámetros como se ha indicado anteriormente.
Visual Studio Code muestra las preferencias de usuario
Una vez abiertas las preferencias como se muestra en la imagen anterior, busque la palabra «encoding»
en el campo de búsqueda previsto para ello y, a continuación, duplique la fila "files.encoding":
"utf8" en la ventana de la derecha. Esta contiene todas las preferencias personales.
24
Una vez realizado esto, cambie el valor «utf8» por «utf8bom» y, a continuación, guarde los cambios con la
combinación de teclas [Ctrl] S.
Su
archivo
de
preferencias
de
usuario
se
almacena
en
la
siguiente
ubicación:
$env:APPDATA\code\User\settings.json
Como habrá visto, en este mismo archivo se almacenarán todas las demás preferencias, tales como
el tema de color, el tamaño de letra por defecto, etc.
Una transición suave con el pasado
Los scripts son retro compatibles, es decir que, por ejemplo, los scripts escritos para la versión 1 de
PowerShell funcionarán con la versión 5.x. Al menos en principio... ya que cada nueva versión aporta
potencialmente nuevas palabras claves, comandos y variables y por lo tanto si por mala suerte los ha
empleado como nombres de variables o funciones podría entonces encontrar algunos problemas... Pero no
se preocupe ya que en más de un 99% de los casos no existe ningún problema, o es que no ha tenido
suerte.
Sin embargo, conviene matizar esto un poco, pues desde PowerShell 6 las cosas han cambiado ligeramente.
En efecto, como dijimos al principio del capítulo, PowerShell 6 representa en cierto modo una renovación
dentro del ecosistema PowerShell debido a que actualmente es open source y multiplataforma. Microsoft ha
autorizado numerosos «breaking changes», entendiéndolos como cambios que podrían producir un mal
funcionamiento en scripts existentes.
En cuanto a los incondicionales de CMD que todavía no se han convertido a PowerShell, que no se
preocupen: PowerShell no elimina lo del pasado. Prácticamente todos los comandos que se encontraban en
CMD lo están también en PowerShell; algunos se encuentran en forma de alias, de funciones, o de archivos
externos. Estos últimos son entonces los comandos originales.
Tomemos, por ejemplo, el comando
PowerShell y observemos el resultado:
dir que todo el mundo conoce perfectamente. Probémoslo en
25
dir en PowerShell
Y ahora
dir en la línea de comandos CMD:
26
Constatará por sí mismo que a primera vista la diferencia no es grande, si no es por el color de fondo que
cambia así como por el tamaño de la ventana, más grande en PowerShell. Para las tareas ordinarias, como
la navegación en carpetas y archivos, no tendrá necesidad por lo tanto de conocer los verdaderos
comandos PowerShell que se esconden detrás de los alias. Porque, en efecto, dir es aquí un alias. Dicho
esto, algunos comandos heredados pueden ser también retomados en forma de funciones.
A continuación puede ver una lista no exhaustiva de antiguos comandos que podrá reutilizar en PowerShell:
dir, md, cd, rd, move, ren, cls, copy.
Los usuarios de Unix tampoco se perderán ya que la mayoría de los comandos Unix funcionan también,
como: ls, mkdir, cp, mv, pwd, cat, mount, lp, ps, etc.
Para conocer la lista completa de alias disponibles, teclee el comando
teclee Get­Command ­CommandType function.
Get­Alias. Y para las funciones,
Le invitamos desde este instante a no utilizar nunca más la shell CMD, incluso para realizar tareas básicas.
Así se familiarizará rápidamente con PowerShell y aprenderá poco a poco la serie de comandos disponibles.
Obtendrá rápidamente competencias y eficacia. No pierda de vista que el sistema de ayuda integrada del
cual hablaremos a continuación es un verdadero triunfo en comparación a CMD. ¡Por lo tanto sería de tontos
no usarlo!
Sistema de ayuda integrado
Desde PowerShell 3.0, la filosofía de Microsoft en cuanto al sistema de ayuda integrado ha cambiado mucho.
En las anteriores versiones, la ayuda venía instalada de forma estándar y estaba disponible en español. Ya
no es el caso desde la versión 3...
Así para poder aprovechar el sistema de ayuda, muy completo por otro lado (pero exclusivamente en
inglés), debe descargar la ayuda. Aunque esto parezca una limitación a primera vista, nos garantiza
disponer de las últimas versiones de los archivos de ayuda disponibles. En efecto, Microsoft actualiza la
ayuda con regularidad y es posible obtener dichas actualizaciones, cosa que no era posible anteriormente.
Solo existe un pequeño inconveniente: la máquina sobre la que queremos descargar la ayuda, debe
disponer de acceso directo a Internet, es decir sin pasar por un proxy, o que un administrador la haya
descargado con antelación y la comparta por red. Además, debe ser administrador de su equipo para poder
actualizar la ayuda.
Pero no se preocupe demasiado ya que PowerShell proporciona todos los comandos para disponer de la
ayuda en un entorno empresarial.
Si la ayuda no está instalada en su sistema, se dará cuenta rápidamente ya que propone únicamente la
ayuda sintáctica. Dicho de otro modo, solo tendrá acceso a las proposiciones de los nombres de los
parámetros sin más explicación.
Ejemplo
Petición de ayuda de un comando cuando esta no se encuentra instalada.
27
PS > help update­help
NAME
Update­Help
SYNTAX
Update­Help [[­Module] <string[]>] [[­SourcePath] <string[]>]
[[­UICultur...
Update­Help [[­Module] <string[]>] [[­UICulture] <cultureinfo[]>]
[­Liter...
ALIASES
None
REMARKS
Get­Help cannot find the Help files for this cmdlet on this computer.
It is displaying only partial help.
­­ To download and install Help files for the module that includes
this cmdlet, use Update­Help.
­­ To view the Help topic for this cmdlet online, type: "Get­Help
Update­Help ­Online" or go to http://go.microsoft.com/fwlink/?LinkID=210614.
La sección de ayuda que contiene los comentarios muestra claramente que los archivos de ayuda no se
pueden encontrar para este comando y que esta es por lo tanto incompleta.
1. Actualización de los archivos de ayuda
Para actualizar la ayuda es necesario arrancar la consola como administrador haciendo clic con el botón
derecho y después elegir Run as Administrator (o Ejecutar como administrador en español) y teclear el
comando Update­Help.
Descarga de la ayuda
Utilice el parámetro
­Verbose para ver más detalles sobre las secciones actualizadas.
Sin embargo debe estar atento ya que el comando Update­Help realiza únicamente la actualización de
la ayuda para los comandos presentes en los módulos instalados en su ordenador.
Aunque todavía no hayamos tratado la noción de módulos en este libro, quédese con la idea de que un
módulo es un contenedor de un número de comandos relativos a un servicio o una funcionalidad
específica. Por ejemplo, el módulo ActiveDirectory engloba todos los comandos que se encargan de la
administración del directorio de empresa Active Directory.
28
Es posible realizar la actualización de uno o varios módulos en particular especificando sus nombres al
parámetro ­Module, como vemos a continuación:
Ejemplo
Actualización de la ayuda para los módulos Activ eDirectory e Hyper­ V.
PS > Update­Help ­Module ActiveDirectory,Hyper­V
Por defecto, cada máquina solo puede realizar una actualización a la vez cada 24h. Esta regla ha
sido impuesta por Microsoft con el fin de evitar la sobrecarga de los servidores de descargas. Sin
embargo, el parámetro ­Force permite evitar este límite.
2. Configuración del sistema de ayuda en empresa
En empresa, no todas las máquinas poseen obligatoriamente acceso a Internet y tampoco es deseable
que puedan descargarse cualquier cosa sin control. Por consiguiente, es preferible en este entorno dejar
la ayuda descargada en una carpeta compartida en red y pedir a PowerShell que se actualice sobre dicha
carpeta con el comando Update­Help.
Observe que también se puede dejar la ayuda en una llave USB e importarla en una máquina
conectada a la red. El procedimiento es el mismo que el explicado a continuación. La única diferencia
es que la actualización de la ayuda se realizará localmente y no a través de una carpeta compartida de
red.
a. Copiar la ayuda en una carpeta compartida en red
La primera etapa consiste en descargarse la ayuda en una máquina (que tendrá el rol de servidor de
ayuda) que tenga acceso a Internet con el comando Save­Help y especificar la ruta donde se
descargarán los archivos:
PS > Save­Help ­DestinationPath D:\HelpReposity ­Force
Hace falta sin embargo saber que, por defecto, la ayuda descargada no se corresponde con la integridad
del sistema de ayuda disponible en Microsoft sino que descarga únicamente los módulos presentes en la
máquina que la descarga.
Evidentemente es posible evitar esta limitación; se le facilitan varios modos de operación en la
sección
de
ayuda
about_Updatable_Help
accesible
tecleando
el
comando
help
about_Updatable_Help.
Si miramos más detenidamente, después de la descarga encontramos en el directorio parejas de
archivos CAB/XML que representan la ayuda de los comandos de cada módulo.
29
PS > Get­ChildItem D:\HelpReposity | Format­Table Name
Name
­­­­
ActiveDirectory_43c15630­959c­49e4­a977­758c5cc93408_en­US_HelpContent.cab
ActiveDirectory_43c15630­959c­49e4­a977­758c5cc93408_HelpInfo.xml
AppBackgroundTask_eb40bd55­3bab­4fa6­88ee­0dcf3cad5a25_en­US_HelpContent.cab
AppBackgroundTask_eb40bd55­3bab­4fa6­88ee­0dcf3cad5a25_HelpInfo.xml
AppLocker_9dafd409­67de­4108­8ee9­73cd61f5b7bf_en­US_HelpContent.cab
AppLocker_9dafd409­67de­4108­8ee9­73cd61f5b7bf_HelpInfo.xml
AppvClient_596d7b43­928b­44d4­89e7­17d34740ecc2_en­US_HelpContent.cab
AppvClient_596d7b43­928b­44d4­89e7­17d34740ecc2_HelpInfo.xml
Appx_aeef2bef­eba9­4a1d­a3d2­d0b52df76deb_en­US_HelpContent.cab
Appx_aeef2bef­eba9­4a1d­a3d2­d0b52df76deb_HelpInfo.xml
...
Como ocurre con la actualización, solo es posible realizar la copia una vez al día. El parámetro
­Force permite evitar esta limitación.
b. Actualización de la ayuda desde una carpeta de red compartida
La segunda etapa es utilizar
archivos de ayuda.
Update­Help con el parámetro ­SourcePath indicando la ruta de los
PS > Update­Help ­SourcePath "\\SRV2K1\HelpRepository" ­force
Si desea actualizar la ayuda y su sistema se encuentra en la DMZ, acuérdese de abrir los puertos
TCP correspondientes al protocolo SMB (Server Message Block) para que pueda actualizar el
contenido de la ayuda en línea.
c. Forzar Update­Help para que utilice la ubicación de red
Puede forzar el comando
Update­Help a actualizarse desde un servidor interno de la empresa
definiendo una directiva de grupo (GPO) Set the Default Source Path for Update­Help.
Se trata en efecto de una de las tres directivas de grupo que permiten configurar PowerShell. Para
obtener más detalle sobre las GPO aplicables a PowerShell, consulte la sección de ayuda
about_Group_Policy_Settings.
Esta directiva de grupo aparece debajo de Configuración del equipo y Configuración de usuario, pero
solo la Configuración del equipo es efectiva, la parte relativa al usuario se ignora ya que se trata de un
parámetro de equipo únicamente.
En Windows Server 2012 y en versiones posteriores, la ruta exacta es la siguiente:
En un sistema español: Configuración del equipo ­ Directivas ­ Plantillas administrativas ­
Componentes de Windows ­ Windows PowerShell ­ Set the Default Source Path for Update­
Help.
En un sistema en inglés: Computer Configuration ­ Policies ­ Administrative Templates ­
Windows Components ­ Windows PowerShell ­ Set the Default Source Path for Update­Help.
30
Comandos básicos
Antes de todo, PowerShell es un entorno en línea de comandos al servicio del sistema operativo pero
también y sobre todo al servicio de los usuarios. Y como tal, se instala con una serie de comandos que debe
conocer, o por lo menos saber cómo encontrarlos cuando falla la memoria...
1. Estructura de los comandos
Los comandos PowerShell con llamados cmdlets (por command­applets), aunque la mayor parte del
tiempo diremos, simplemente, comandos. Están estructurados de la siguiente manera: un verbo seguido
de un nombre separado por un guión (­): verbo­nombre. Por ejemplo, Get­Command.
El verbo (evidentemente en inglés) describe la acción a realizar sobre el nombre. En el anterior ejemplo,
recuperamos (Get) los comandos (Command).
Con PowerShell encontramos numerosos verbos genéricos tales como Get, Set, Add, Remove, etc. que
se combinan con diferentes nombres como Path, Variable, Item, Object, Computer, etc.
Los nombres que constituyen los comandos están siempre en singular; y esto es válido también para los
parámetros.
Por lo tanto, es posible, mezclando verbos y nombres, acordarse fácilmente de un buen número de
comandos. Tenga en cuenta que los comandos, así como sus parámetros asociados, pueden escribirse
indistintamente en mayúsculas o en minúsculas. El analizador sintáctico PowerShell no es case sensitive
(salvo en el caso de PowerShell 6 para Mac OS o Linux).
Encuentre la lista completa de verbos oficiales y sus grupos de aplicación usando el comando
Get­Verb.
Es habitual, y es siempre una buena práctica, elegir siempre un verbo aprobado cuando creamos
funciones o scripts que vayan a usar otros usuarios, y incluso con el fin de preservar una cierta
homogeneidad global entre sus propios comandos y los de Microsoft.
2. Get­Command
Si solo fuera a quedarse con uno, entonces quédese con este: Get­Command. Este comando permite
descubrir todos los comandos PowerShell. Sin precisar parámetro alguno. Get­Command devuelve
también los alias y las funciones de la sesión. De momento, nos interesaremos solamente en los
comandos, y para ello añadimos el parámetro ­CommandType seguido del tipo de comando elegido, a
saber cmdlet.
31
PS > Get­Command ­CommandType cmdlet
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
...
Name
­­­­
Add­BitsFile
Add­Computer
Add­Content
Add­History
Add­JobTrigger
Add­Member
Add­PSSnapin
Add­Type
Checkpoint­Computer
Clear­Content
Clear­DnsCache
Clear­EventLog
Clear­History
Clear­Item
Clear­ItemProperty
Clear­Variable
Compare­Object
Complete­BitsTransfer
Complete­Transaction
Connect­PSSession
Connect­WSMan
ConvertFrom­Csv
ConvertFrom­Json
ConvertFrom­SecureString
ConvertFrom­StringData
Convert­Path
ConvertTo­Csv
ConvertTo­Html
ModuleName
­­­­­­­­­­
BitsTransfer
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
PSScheduledJob
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
DnsShell
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
BitsTransfer
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.WSMan...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
Microsoft.PowerShell...
En la primera versión de PowerShell los comandos básicos eran 129. En la actualidad su número difiere en
función del tipo de sistema (cliente o servidor), del número de roles, de las funcionalidades instaladas, y
puede llegar a superar rápidamente los 2000.
A decir verdad, hay tantos que se ha vuelto muy difícil conocer el número exacto. Para contarlos puede
teclear:
PS > Get­Command ­CommandType cmdlet,function | Measure­Object
Count
: 2137
Average :
Sum
:
Maximum :
Minimum :
Property:
Este resultado es el obtenido en un sistema Windows Server 2016 Standard instalado con el rol Hyper­V.
Si su resultado es diferente, quiere decir que ha instalado comandos adicionales bien mediante snap­ins,
módulos (volveremos a ellos más adelante en el libro), roles o funcionalidades.
Get­Command posee el parámetro ­verb y este último permite conocer todos los comandos que
empiezan por un verbo concreto. Veamos el resultado de la línea de comandos siguiente:
32
PS > Get­Command ­Verb write
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
Write­Debug
Write­Error
Write­EventLog
Write­Host
Write­Output
Write­Progress
Write­Verbose
Write­Warning
ModuleName
­­­­­­­­­­
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Acabamos de conseguir todos los comandos cuyo verbo es
Write.
De la misma manera, podemos obtener los comandos que se aplican a los objetos, es decir aquellos en los
que una parte del nombre es object:
PS > Get­Command ­Noun object
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
Compare­Object
ForEach­Object
Group­Object
Measure­Object
New­Object
Select­Object
Sort­Object
Tee­Object
Where­Object
ModuleName
­­­­­­­­­­
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Core
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Core
Podemos también obtener los comandos de un tipo determinado, de los cuales los más utilizados son:
alias, function, cmdlet, externalscript, application.
Ejemplo
PS > Get­Command ­Commandtype alias
CommandType
­­­­­­­­­­­
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
Alias
...
Name
­­­­
% ­> ForEach­Object
? ­> Where­Object
ac ­> Add­Content
asnp ­> Add­PSSnapin
cat ­> Get­Content
cd ­> Set­Location
chdir ­> Set­Location
clc ­> Clear­Content
clear ­> Clear­Host
clhy ­> Clear­History
cli ­> Clear­Item
clp ­> Clear­ItemProperty
cls ­> Clear­Host
clv ­> Clear­Variable
cnsn ­> Connect­PSSession
ModuleName
­­­­­­­­­­
33
Si está buscando un comando del cual no sabe el nombre, pero sí sabe (o presupone) que el comando que
busca debe devolverle información, existe una alta probabilidad de que empiece por el verbo Get. En
estas condiciones, puede utilizar lo siguiente: Get­Command Get* o Get­Command Get­*.
PS > Get­Command Get­*
CommandType
­­­­­­­­­­­
Script
Cmdlet, Script
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
Get­LogProperties
Get­Verb
Get­Acl
Get­Alias
Get­AppLockerFileInformation
Get­AppLockerPolicy
Get­AuthenticodeSignature
Get­BitsTransfer
Get­ChildItem
Get­CimAssociatedInstance
Get­CimClass
Get­CimInstance
Get­CimSession
Get­Command
Get­ComputerRestorePoint
Get­Content
ModuleName
­­­­­­­­­­
PSDiagnostics
Microsoft.PowerShell.Security
Microsoft.PowerShell.Utility
AppLocker
AppLocker
Microsoft.PowerShell.Security
BitsTransfer
Microsoft.PowerShell.Management
CimCmdlets
CimCmdlets
CimCmdlets
CimCmdlets
Microsoft.PowerShell.Core
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
De la misma manera, si sabe que el comando se aplica a equipos, puede probar esto:
PS > Get­Command *­computer
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
Add­Computer
Checkpoint­Computer
Remove­Computer
Rename­Computer
Restart­Computer
Restore­Computer
Stop­Computer
ModuleName
­­­­­­­­­­
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
3. Get­Help
IMPORTANTE: es necesario actualizar la ayuda antes de seguir. Si no sabe cómo hacerlo, lea el
apartado Sistema de ayuda integrado de este capítulo.
Este comando permite, como su nombre indica, obtener ayuda sobre cualquier comando, y también cosas.
Su uso es muy sencillo y le aconsejamos usarlo y abusar de él.
a. Ayuda sobre los comandos
Puede obtener ayuda acerca de un comando de distintas maneras:
Get­Help miComando
Help miComando
34
miComando ­?
Get­Help miComando devuelve la ayuda estándar de un comando, es decir la mínima posible.
Conviene saber que la ayuda de PowerShell ofrece tres niveles de detalle:
la ayuda estándar,
la ayuda detallada,
la ayuda completa.
Para acceder a la ayuda detallada, añada el parámetro ­Detailed, o sea Get­Help miComando ­
detailed. Y para obtener la ayuda completa, especifique el parámetro ­Full, o sea Get­
Help miComando ­full.
Le recomendamos mostrar sistemáticamente la ayuda detallada de los comandos, pues la ayuda
estándar no muestra ningún ejemplo, lo cual es una pena teniendo en cuenta la riqueza de explicaciones
que aportan. ¡Sería una pena omitirlos! La ayuda detallada proporciona, además, explicaciones acerca
del funcionamiento de cada parámetro, lo cual resulta muy apreciado.
En cuanto a la ayuda completa, está muy bien, aunque puede resultar demasiado rica en detalles y, por
consiguiente, aturdir a un programador de scripts debutante. Por este motivo pensamos que la ayuda
detallada es el nivel que Microsoft tendría que haber propuesto por defecto.
Cuando teclea miComando
al nivel estándar.
­?, no puede especificar el nivel de detalle. La ayuda devuelta corresponde
Veamos un ejemplo para entender cómo se devuelve la ayuda:
PS > Help Get­Item
NAME
Get­Item
SYNOPSIS
Gets files and folders.
SYNTAX
Get­Item [­Path] <String[]> [­Credential <PSCredential>]
[­Exclude <String[]>] [­Filter <String>] [­Force] [­Include
<String[]>] [­UseTransaction [<SwitchParameter>]]
[<CommonParameters>]
Get­Item [­Credential <PSCredential>] [­Exclude <String[]>]
[­Filter <String>] [­Force] [­Include <String[]>] ­LiteralPath
<String[]> [­UseTransaction [<SwitchParameter>]]
[<CommonParameters>]
Get­Item [­Stream <string>] [<CommonParameters>]
DESCRIPTION
The Get­Item cmdlet gets the item at the specified location. It
does not get the contents of the item at the location unless you
use a wildcard character (*) to request all the contents of the
item.
35
The Get­Item cmdlet is used by Windows PowerShell providers to
enable you to navigate through different types of data stores.
In the file system, the Get­Item cmdlet gets files and folders.
Note: This custom cmdlet help file explains how the Get­Item
cmdlet works in a file system drive. For information about the
Get­Item cmdlet in all drives, type "Get­Help Get­Item ­Path
$null" or see Get­Item at
http://go.microsoft.com/fwlink/?LinkID=113319.
RELATED LINKS
Online version:
http://technet.microsoft.com/library/jj628239(v=wps.630).aspx
Get­Item (generic);
http://go.microsoft.com/fwlink/?LinkID=113319
FileSystem Provider
Add­Content
Clear­Content
Get­Content
Get­ChildItem
Get­Content
Get­Item
Remove­Item
Set­Content
Test­Path
REMARKS
To see the examples, type: "get­help Get­Item ­examples".
For more information, type: "get­help Get­Item ­detailed".
For technical information, type: "get­help Get­Item ­full".
For online help, type: "get­help Get­Item ­online"
Le recomendamos dar preferencia al uso del comando Help en lugar de Get­Help, seguido del
nivel de detalle deseado (­detailed o ­full), pues esto ofrece dos ventajas interesantes: la
primera es que resulta más corto de escribir y la segunda es que la ayuda se mostrará paginada.
Help es, en realidad, una función que permite mostrar el contenido paginado de la ayuda.
Si tiene que escribir ­detailed cada vez que utiliza la ayuda, puede hacer que Get­Help
invoque sistemáticamente este parámetro. Para ello, introduzca la siguiente línea en la
consola o, mejor, inclúyala en su perfil PowerShell para que el cambio sea persistente:
$PSDefaultParameterValues=@{"get­help:detailed"=$true}
Get­Help, sino que puede aplicarse a cualquier comando
PowerShell para pasar valores por defecto a los parámetros.
Este truco no solo funciona para
Para terminar, anote la existencia del parámetro
­Online que abre directamente el navegador y se
conecta al sitio Technet de Microsoft con el fin de mostrar la ayuda en línea más reciente del comando
elegido. Esta opción puede resultar muy útil en el caso de que no desee actualizar la ayuda en alguna
máquina.
Por ejemplo:
PS > Get­Help Get­Command ­Online
36
Los parámetros como
­Detailed, ­Full u ­Online, que pueden escribirse o no, se llaman, en
realidad, conmutadores (o switches, en inglés). Son parámetros algo particulares, pues no reciben
ningún valor.
b. Ayuda conceptual
No escatimaremos sobre la calidad de la ayuda de PowerShell. En efecto, esta es particularmente rica, y
además contiene ayuda sobre la utilización de los comandos. Proporciona también información práctica
sobre los propios conceptos del lenguaje tales como el uso de tablas, operadores de comparación,
bucles, pipeline, funciones, etc. Las secciones son realmente numerosas y bien detalladas.
Para descubrir todas las secciones de ayuda accesibles, teclee: help
about_*
Esta ayuda le será de gran utilidad cuando desarrolle un script y se haya olvidado de llevar consigo el
magnífico libro que tiene entre sus manos...
PS > Help about_*
Name
­­­­
about_Aliases
about_Arithmetic_Operators
about_Arrays
about_Assignment_Operators
about_Automatic_Variables
about_Break
about_Command_Precedence
...
Category
­­­­­­­­
HelpFile
HelpFile
HelpFile
HelpFile
HelpFile
HelpFile
HelpFile
Module
­­­­­­
Synopsis
­­­­­­­­
Describes
Describes
Describes
Describes
Describes
Describes
Describes
how to use...
the operat...
arrays, wh...
how to use...
variables ...
a statemen...
how Window...
Existen más de un centenar de secciones de ayuda conceptuales, es decir suficientes para profundizar
en sus conocimientos sobre numerosos temas.
4. Get­Member
Get­Member es probablemente el comando más interesante de todos ya que devuelve todas las
propiedades y métodos de un objeto así como su tipo.
No es necesario cuando debutamos con PowerShell dominar este comando. En efecto, pone de
manifiesto el concepto de orientación a objetos, que trataremos con más detalle en el siguiente
capítulo.
Gracias a Get­Member, podrá dejar pasmados a sus compañeros de trabajo ya que ganará un tiempo
considerable en la escritura de sus scripts.
PS > $miVariable = ’¡Buenos días a todos!’
Acabamos de crear la variable $miVariable y darle un valor de tipo cadena (String). Observará que
no hemos necesitado declararla ya que PowerShell asocia directamente un tipo en función del contenido.
Una variable empieza siempre por el carácter dólar. Veremos con detalle las variables en el capítulo
Variables y tipos de datos.
37
Ahora, imaginemos que queremos realizar acciones sobre ella, como por ejemplo convertirla en letras
mayúsculas o contar el número de caracteres que contiene.
Para hacerlo, generalmente en todos los lenguajes de scripts o de programación debemos consultar la
documentación para saber los comandos que permiten la manipulación de las cadenas de caracteres.
Evidentemente en PowerShell podemos hacer lo mismo, pero es ahora cuando el comando Get­Member
adquiere todo su sentido y va a entender porqué...
Teclee ahora:
PS > $miVariable | Get­Member
TypeName: System.String
Name
­­­­
Clone
CompareTo
Contains
CopyTo
EndsWith
Equals
GetEnumerator
GetHashCode
GetType
GetTypeCode
IndexOf
IndexOfAny
Insert
IsNormalized
LastIndexOf
LastIndexOfAny
Normalize
PadLeft
PadRight
Remove
Replace
Split
StartsWith
Substring
ToBoolean
ToByte
ToChar
ToCharArray
ToDateTime
ToDecimal
ToDouble
ToInt16
ToInt32
ToInt64
ToLower
ToLowerInvariant
ToSByte
ToSingle
ToString
ToType
ToUInt16
ToUInt32
ToUInt64
ToUpper
MemberType
­­­­­­­­­­
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Definition
­­­­­­­­­­
System.Object Clone()
int CompareTo(System.Object valu...
bool Contains(string value)
System.Void CopyTo(int sourceInd...
bool EndsWith(string value), boo...
bool Equals(System.Object obj), ...
System.CharEnumerator GetEnumera...
int GetHashCode()
type GetType()
System.TypeCode GetTypeCode()
int IndexOf(char value), int Ind...
int IndexOfAny(char[] anyOf), in...
string Insert(int startIndex, st...
bool IsNormalized(), bool IsNorm...
int LastIndexOf(char value), int...
int LastIndexOfAny(char[] anyOf)...
string Normalize(), string Norma...
string PadLeft(int totalWidth), ...
string PadRight(int totalWidth),...
string Remove(int startIndex, in...
string Replace(char oldChar, cha...
string[] Split(Params char[] sep...
bool StartsWith(string value), b...
string Substring(int startIndex)...
bool ToBoolean(System.IFormatProvi...
byte ToByte(System.IFormatProvider...
char ToChar(System.IFormatProvider...
char[] ToCharArray(), char[] ToC...
System.DateTime ToDateTime(System...
decimal ToDecimal(System.IFormat...
double ToDouble(System.IFormat...
System.Int16 ToInt16(System.IFormat...
System.UInt32 ToUInt32(System.IForm...
System.UInt64 ToUInt64(System.IFor...
string ToLower(), string ToLower...
string ToLowerInvariant()
System.SByte ToSByte(System.IFor...
float ToSingle(System.IFor...
string ToString(), string ToStri...
System.Object ToType(type conversi...
System.UInt16 ToUInt16(System.IFor...
System.UInt32 ToUInt32(System.IFor...
System.UInt64 ToUInt64(System.IFor...
string ToUpper(), string ToUpper...
38
ToUpperInvariant
Trim
TrimEnd
TrimStart
Chars
Length
Method
Method
Method
Method
ParameterizedProperty
Property
string ToUpperInvariant()
string Trim(Params char[] trimCh...
string TrimEnd(Params char[] tri...
string TrimStart(Params char[] t...
char Chars(int index) {get;}
int Length {get;}
Vemos aparecer varios elementos particularmente interesantes:
El campo
TypeName indica el tipo de variable. En este caso, como lo suponíamos, el tipo String.
Una lista con los nombres de los métodos, las propiedades y su definición asociada.
Sin gran esfuerzo podemos por lo tanto imaginar que el método ToUpper nos permitirá devolver la
cadena contenida en $miVariable en mayúsculas. Probémoslo para ver qué nos devuelve:
PS > $miVariable.ToUpper()
¡BUENOS DÍAS A TODOS!
De la misma manera, podemos intuir que la propiedad
contenidos en nuestra cadena.
Length nos devolverá el número de caracteres
PS > $miVariable.Length
24
En resumen, este comando es realmente indispensable. Nos devuelve información acerca de los objetos
que manipulamos, ¡pues con PowerShell todo es un objeto! Resulta, por tanto, fundamental conocer el
tipo de objetos que manipulamos, así como sus propiedades y métodos.
Un error clásico cuando se empieza con PowerShell es olvidar los paréntesis del final cuando se
invoca a los métodos. Por ejemplo, si teclea $miVariable.ToUpper, no obtendrá ningún
resultado ya que PowerShell mostrará la definición del método. Preste atención por lo tanto a este
tema.
Gestión de carpetas y archivos
Hemos visto que podíamos utilizar los viejos comandos DOS/CMD para desplazarnos en la jerarquía de
carpetas. Aunque se pueda hacer todavía y permita ganar tiempo a quienes los utilizan, esto no le quita
nivel a sus conocimientos de PowerShell.
Cuando teclea DIR en PowerShell, en realidad llama a un alias. El alias ejecuta el comando
ChildItem. Para verificarlo, teclee el siguiente comando:
Get­
PS > Get­Alias dir
CommandType
­­­­­­­­­­­
Alias
Name
­­­­
dir ­> Get­ChildItem
ModuleName
­­­­­­­­­­
Debajo presentamos una tabla con los principales comandos CMD y sus equivalentes en PowerShell.
39
DOS/CMD
Equivalente
PowerShell
(alias)
dir
dir,
ls
cd
Cmdlet PowerShell
Descripción
Get­ChildItem
Muestra el
carpeta.
cd, chdir,
sl
Set­Location
Cambia de carpeta.
md
md, ni
New­Item
Crea un archivo/carpeta.
rd
del, erase,
rd, ri, rm,
rmdir
Remove­Item
Suprime un archivo/carpeta.
move
mi,
mv
Move­Item
Mueve un archivo/carpeta.
ren
ren, rni
Rename­Item
Renombra un archivo/carpeta.
copy
copy,
cpi
Copy­Item
Copia un archivo/carpeta.
qci,
move,
cp,
contenido
de
una
Como el objetivo de este libro es el aprendizaje de PowerShell, nos esforzaremos en no utilizar los antiguos
comandos CMD, ¡y animamos a que haga lo mismo!
Nos damos cuenta de que existen muchos alias vinculados al mundo Unix, tales como ls, rm, cp.
Estos se han creado para facilitar el acceso a PowerShell a aquellas personas que provengan de estos
entornos. Cabe destacar que estos alias solo existen en PowerShell para Windows. En efecto, se han
eliminado en PowerShell 6 únicamente sobre las plataformas Mac OS y Linux (y solo en estas plataformas)
para dejar la posibilidad de invocar los verdaderos comandos nativos.
1. Get­ChildItem (alias: gci, ls, dir)
Este comando permite obtener los archivos y carpetas presentes en el sistema de archivos.
Por ejemplo, observemos el resultado del siguiente comando:
PS > gci C:\
Directory : C:\
Mode
­­­­
d­­­­
d­r­­
d­­­­
d­­­­
d­r­­
d­­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
26/07/2012
09:44
28/10/2012
13:19
21/11/2012
23:40
12/01/2013
22:33
08/09/2012
16:09
28/10/2012
13:16
09/09/2012
15:45
Length Name
­­­­­­ ­­­­
PerfLogs
Program Files
Program Files (x86)
Temp
Users
Windows
823 Mi_Certificado.cer
A primera vista, lo que llama la atención, es el nombre dado a cada columna; podemos también observar la
columna Mode.
40
Esta indica la naturaleza de los objetos en el interior del sistema de archivos. Los posibles valores son:
d: para un directorio.
a: para un archivo.
r: para un objeto de solo lectura.
h: para un objeto oculto.
s: para un objeto de sistema.
Para mostrar los archivos ocultos, agregue al comando
Force.
Get­Childitem el parámetro ­
Ejemplo:
PS > gci C:\ ­Force
Directory: C:\
Mode
­­­­
d­­hs
d­­hs
d­­­­
d­r­­
d­­­­
d­­h­
d­­hs
d­­­­
d­r­­
d­­­­
­arhs
­a­hs
­a­­­
­a­hs
LastWriteTime
Length
­­­­­­­­­­­­­
­­­­­­
28/10/2014
13:19
26/07/2014
09:14
26/07/2014
09:44
28/10/2014
13:19
21/11/2014
23:40
25/11/2014
17:19
09/09/2014
15:54
12/01/2014
22:33
08/09/2014
16:09
28/10/2014
13:16
26/07/2014
05:44
398156
02/06/2014
16:30
1
09/09/2014
15:45
823
12/01/2014
21:26 335544320
Name
­­­­
$Recycle.Bin
Documents and...
PerfLogs
Program Files
Program Files...
ProgramData
System Volume...
Temp
Users
Windows
bootmgr
BOOTNXT
Mi_Certificado.cer
pagefile.sys
Volviendo a los nombres de las columnas, estos indican en realidad el nombre de alguna propiedad del
archivo o de la carpeta. Hemos explicado que PowerShell está basado en objetos (a diferencia de la línea
de comandos), pues ¡va a poder juzgar por usted mismo!
Aquí tiene algunos ejemplos:
Mostrar (recursivamente) todos los archivos con extensión .log contenidos en el interior de una
jerarquía:
PS > Get­ChildItem C:\Temp\* ­Include *.log ­Recurse
Obtener el nombre de los archivos cuyo tamaño es superior a 32 KB:
PS > Get­ChildItem | Where­Object {$_.Length ­gt 32KB}
Obtener los archivos cuya fecha de última modificación sea posterior al 01/01/2018:
Descargado en: eybooks.com
41
PS > Get­ChildItem | Where­Object {$_.LastWriteTime ­gt ’01/01/2018’}
Preste atención: la fecha está siempre en formato americano, o sea MM/DD/AAAA.
Algunas explicaciones:
El pipe «|» permite pasar uno o varios objetos al comando siguiente. En nuestros ejemplos pasamos cada
objeto al comando Where­Object. Este último va a analizar las propiedades Length o
LastWriteTime (según el ejemplo), compararlas y devolver los objetos que satisfagan la condición
verdadera. El «$_» indica que tratamos el objeto actual.
Hemos utilizado en nuestro primer ejemplo un cuantificador de bytes (32KB). PowerShell integra de
forma nativa estos cuantificadores de bytes para simplificar la escritura del tamaño en memoria. Son
los siguientes: KB , MB , GB , TB y PB. ¡No olvide que 1KB equivale a 1024 bytes y no a 1000 como se ve
algunas (demasiadas) veces!
Debe saber también que desde PowerShell 3.0 existen cinco atributos adicionales. Estos últimos permiten
obtener archivos o carpetas en función de la naturaleza de los objetos buscados: carpeta, archivo, solo
lectura, objeto oculto y objeto de sistema.
Estos atributos se utilizan de la siguiente manera:
atributo elegido:
Get­ChildItem ­Attributes seguido del
Directory: para una carpeta.
Archive: para un archivo.
Hidden: para un objeto oculto.
ReadOnly: para un objeto de solo lectura.
System: para un objeto de sistema.
Veamos por ejemplo cómo mostrar todos los archivos o carpetas ocultos de la raíz de la partición de
sistema:
PS > Get­ChildItem C:\ ­Attributes Hidden
Directory: C:\
Mode
­­­­
d­­hs
d­­hs
d­­h­
d­­hs
­arhs
­a­hs
­a­hs
LastWriteTime
Length
­­­­­­­­­­­­­
­­­­­­
28/10/2014
13:19
26/07/2014
09:14
25/11/2014
17:19
09/09/2014
15:54
26/07/2014
05:44
398156
02/06/2014
16:30
1
12/01/2014
21:26 335544320
Name
­­­­
$Recycle.Bin
Documents and Settings
ProgramData
System Volume Information
bootmgr
BOOTNXT
pagefile.sys
Podemos evidentemente asociar combinaciones de atributos con los operadores siguientes:
+: para representar un Y lógico.
,: para representar un O lógico.
42
!: para representar un NO lógico.
Por ejemplo, si tomamos el comando anterior pero queremos filtrar únicamente los archivos ocultos y
aquellos que no son carpetas, basta con combinar los atributos «oculto» y «no es una carpeta».
PS > Get­ChildItem C:\ ­Attributes Hidden+!Directory
Directory: C:\
Mode
LastWriteTime
Length Name
­­­­
­­­­­­­­­­­­­
­­­­­­ ­­­­
­arhs
26/07/2014
05:44
398156 bootmgr
­a­hs
02/06/2014
16:30
1 BOOTNXT
­a­hs
12/01/2014
21:26 335544320 pagefile.sys
Los operadores lógicos se utilizan con frecuencia con el lenguaje de PowerShell. Volveremos a ellos
con más atención en el capítulo donde se abordan los operadores.
2. Set­Location (alias: sl, cd, chdir)
No hay mucho que decir acerca de este comando salvo que nos permite desplazarnos en una jerarquía de
directorios. Por ejemplo:
PS > Set­Location D:\
Al igual que en CMD, podemos utilizar rutas relativas así como los atajos «..» y «\» para designar
respectivamente a la carpeta superior y a la carpeta raíz del disco actual.
3. Get­Location (alias: gl, pwd)
Este comando devuelve la carpeta actual en el interior de una jerarquía de archivos.
Aquí puede ver el resultado de la ejecución de
Get­Location:
PS > Get­Location
Path
­­­­
C:\Users
Y ahora cómo hacer para recuperar en una variable la ruta (path) del directorio actual en una sola línea.
PS > $RutaActual = (Get­Location).Path
Acabamos de almacenar el valor de la ruta actual, en este caso C:\Users, en la variable
Ahora para poder mostrarla por pantalla, nada más sencillo que teclear:
$RutaActual.
PS > $RutaActual
C:\Users
43
4. New­Item (alias: ni, md)
Este comando permite crear carpetas, en detrimento del comando
md de CMD, y también archivos.
Veamos con detalle algunos de sus parámetros:
Parámetro
Descripción
­Path
Ruta de acceso del elemento a crear (p.e.: C:\Temp).
­Itemtype
Tipo de elemento a crear: file para un archivo,
carpeta.
­Name
Nombre del nuevo elemento a crear.
­Value
Contenido del elemento a crear (p.e.: "¡hola!" en el caso de un archivo
de texto).
directory para una
a. Crear una carpeta
La creación de una carpeta se realiza indicando la palabra clave
ItemType.
directory con el parámetro ­
PS > New­Item ­ItemType directory ­Name Temp
Directory: C:\
Mode
­­­­
d­­­­
LastWriteTime
­­­­­­­­­­­­­
06/09/2014
17:37
Length Name
­­­­­­ ­­­­
Temp
Si deseamos dar a nuestra nueva carpeta un nombre que contenga un espacio, debemos poner el
nombre entre comillas después del parámetro ­Name. Por ejemplo:
PS > New­Item ­Name ’Directorio Test’ ­ItemType directory
b. Crear un archivo
Imaginemos que queremos crear un archivo llamado
PowerShell!». El comando sería el siguiente:
miArchivo.txt que contenga la frase «¡Viva
PS > New­Item ­Name miArchivo.txt ­ItemType file ­Value ’¡Viva
PowerShell!’
Directory: C:\
Mode
­­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
06/09/2014
17:39
Length Name
­­­­­­ ­­­­
15 miArchivo.txt
Descubrirá en el capítulo Gestión de archivos y fechas que existen otros métodos, aún más
prácticos, para crear archivos. Sepa que los operadores de redirección «>» y «>>» funcionan
también muy bien con PowerShell.
44
5. Remove­Item (alias: ri, rm, rmdir, rd, erase, del)
El comando
Remove­Item, como su nombre indica, permite eliminar archivos o carpetas.
Podemos usarlo de distintas maneras:
PS > Remove­Item C:\Temp\*.log
En este ejemplo, acabamos de eliminar todos los archivos
.log contenidos en la carpeta C:\Temp.
PS > Get­ChildItem C:\Temp\* ­Include *.txt ­Recurse | Remove­Item
Aquí, eliminamos selectivamente todos los archivos contenidos en la jerarquía de archivos cuya extensión
es .txt. Esta sintaxis es muy práctica ya que podemos construirla poco a poco; primero enumeramos los
archivos que queremos suprimir, para después pasarlos con un pipe al comando Remove­item.
Para suprimir un archivo del sistema, oculto o de solo lectura, basta simplemente con utilizar el parámetro
­force, como muestra el siguiente ejemplo:
PS > Remove­Item archivoASuprimir.txt ­Force
Remove­Item posee también el parámetro ­WhatIf; este indica lo que hará el comando pero sin
ejecutarlo. Es de algún modo un modo de simulación. Otro parámetro interesante es ­Confirm.
Gracias a él, PowerShell pide confirmación para cada archivo a suprimir, lo que no ocurre por defecto.
Los parámetros ­WhatIf y ­Confirm forman parte de los «parámetros comunes» de PowerShell. Es
decir, que pueden aplicarse a cualquier comando PowerShell.
6. Move­Item (alias: mi, move, mv)
Este comando permite mover un archivo o una carpeta de un sitio a otro. En el caso de una carpeta, el
contenido también se mueve. Move­Item permite, entre otras cosas, renombrar el objeto manipulado.
a. Mover archivos
Ejemplo:
Mover archivos *.jpg desde la carpeta actual hasta la carpeta «Mis fotos».
PS > Move­Item ­Path *.jpg ­Destination ’C:\Temp\Mis fotos’
O:
PS > Move­Item *.jpg ’C:\Temp\Mis fotos’
En el primer ejemplo, hemos especificado explícitamente todos los parámetros, mientras que en el
segundo nos hemos contentado con el mínimo. Sin embargo, el resultado es el mismo. Esto es posible
porque el intérprete de comandos PowerShell es relativamente «inteligente». Cuando analiza un
45
comando y los nombres de los parámetros no vienen indicados, mira la definición del comando y pasa al
primer parámetro el primer valor, y después el segundo valor al segundo parámetro, y así sucesivamente
hasta que ya no se hayan indicado más parámetros a transmitir.
Para que el ejemplo de arriba funcione, tendríamos que haber creado antes la carpeta
C:\Temp\Mis fotos. Dicho esto, es posible forzar la creación de la carpeta de destino usando
el parámetro ­force.
b. Mover carpetas
El desplazamiento de carpetas es similar al de los archivos.
Tomemos el ejemplo siguiente:
PS > Move­Item ’Mis fotos’ ’Mis nuevas fotos’
Acabamos de mover la integridad de la carpeta
haríamos ahora para renombrar la carpeta Mis
Mis fotos a la carpeta Mis nuevas fotos. ¿Como
fotos por Mis nuevas fotos?
Respuesta:
PS > Move­Item ’Mis fotos’ ’Mis nuevas fotos’
Resulta muy curioso, se preguntará, que se trate de la misma línea de comandos. Tiene toda la razón,
pero se trata de un funcionamiento normal. La única diferencia viene del hecho de que según el
resultado deseado, deberá crear o no previamente la carpeta de destino. Si no lo hace, renombrará la
carpeta.
7. Rename­Item (alias: ren, rni)
El objetivo de este comando es renombrar un archivo o una carpeta. Este comando es medianamente útil
en la medida en el que repite la función del comando Move­Item. Dicho esto, puede haber casos, que no
conocemos aún, en los que pueda ser de utilidad... ¿Igual para no confundir el renombrar con mover como
en el ejemplo anterior?
a. Renombrar un archivo
Ejemplo:
Renombrar el archivo
miArchivoDeLog.txt en archlog.txt.
PS > Rename­Item ­Path C:\Temp\miArchivoDeLog.txt ­Newname archlog.txt
O:
PS > Rename­Item C:\Temp\miArchivoDeLog.txt archlog.txt
46
b. Renombrar una carpeta
Ejemplo:
Renombrar la carpeta
miCarpeta1 en miCarpeta2.
PS > Rename­Item ­Path C:\Temp\MiCarpeta1 ­Newname MiCarpeta2
O:
PS > Rename­Item C:\Temp\MiCarpeta1 MiCarpeta2
8. Copy­Item (alias: cpi, cp, copy)
Gracias a este comando, podemos copiar archivos o carpetas, o incluso ambos a la vez.
Algunos ejemplos:
Copiar un archivo desde una carpeta de origen hasta otra carpeta de destino:
PS > Copy­Item ­Path C:\Temp\ficLog.txt ­Destination D:\Logs
O:
PS > Copy­Item C:\Temp\ficLog.txt D:\Logs
Copiar una jerarquía de carpetas (es decir todas las subcarpetas y archivos):
PS > Copy­Item ­Path DirSource ­Destination DirDest ­Recurse
Copy­Item crea automáticamente la carpeta de destino si no existe.
Proveedores PowerShell
Ahora que está familiarizado con la serie de comandos que permiten navegar y gestionar una jerarquía de
archivos y carpetas, podemos confesar que estos comandos permiten realizar muchas otras cosas...
Todos los comandos que hemos visto anteriormente (familias
*­Item y *­Location) permiten la
manipulación no solo del sistema de archivos, sino también:
de la base de registro,
de las variables,
de las variables de entorno,
de los alias,
de la base de certificados X.509 de su equipo,
de las funciones,
y de la continuación WSMan (útil para la funcionalidad Remote PowerShell).
47
Un certificado X.509 permite firmar y/o cifrar datos.
Esto explica la generalización en los nombres de los comando *­Item, en la medida en la que un «item»
puede representar por ejemplo un archivo, una carpeta, una clave de registro, una variable o cualquier otra
cosa.
Todas las fuentes de datos que acabamos de enumerar antes son accesibles por medio de lo que llamamos
en la jerga PowerShell los proveedores (encontramos también con bastante frecuencia los términos
Provider o PSProvider). Son dieciocho, aunque por defecto solo se muestran seis. Para obtener la lista y
los detalles asociados, teclee el comando Get­PSProvider.
PS > Get­PSProvider
Name
­­­­
Alias
Environment
FileSystem
Function
Registry
Variable
WSMan
Certificate
Capabilities
­­­­­­­­­­­­
ShouldProcess
ShouldProcess
Filter, ShouldProcess, Credentials
ShouldProcess
ShouldProcess, Transactions
ShouldProcess
Credentials
ShouldProcess
Drives
­­­­­­
{Alias}
{Env}
{C, E, D, F}
{Function}
{HKLM, HKCU}
{Variable}
{WSMan}
{Cert}
A partir de la versión 3 de PowerShell, solo se muestran seis providers al arrancar la sesión. Esto es
debido a que desde ahora PowerShell carga los componentes bajo demanda, pero no es razón para
que los que falten no se puedan usar directamente.
El acceso al contenido de los proveedores se realiza mediante un lector. Veamos la lista de lectores
integrados: Alias, Env, A, B, C, D, E,..., Z, Function, HKLM, HKCU, Variable, Cert, WSMan.
El número de lectores que se pueden usar de tipo FileSystem depende de cada equipo.
La navegación en el interior de estos lectores se hace exactamente igual que para navegar en el sistema de
archivos de un disco duro. Para utilizarlos, nada más simple que usar la sintaxis siguiente: Get­
ChildItem Lector_del_proveedor:.
Por ejemplo:
Get­ChildItem Alias:
Get­ChildItem Env:
Get­ChildItem C:
Get­ChildItem Function:
Get­ChildItem HKLM:
Get­ChildItem Variable:
Get­ChildItem Cert:
Get­ChildItem WSMan:
48
Estos ejemplos permiten obtener los objetos contenidos en cada proveedor. Dicho esto, como con cualquier
letra de unidad, podemos entrar en ellos y explorar el contenido. Para ello, probemos los comandos
siguientes:
PS > Get­ChildItem Env:
Name
­­­­
ALLUSERSPROFILE
APPDATA
CommonProgramFiles
CommonProgramFiles(x86)
CommonProgramW6432
COMPUTERNAME
ComSpec
FP_NO_HOST_CHECK
HOMEDRIVE
HOMEPATH
LOCALAPPDATA
LOGONSERVER
NUMBER_OF_PROCESSORS
OS
Path
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
ProgramData
ProgramFiles
ProgramFiles(x86)
ProgramW6432
PSModulePath
PUBLIC
SESSIONNAME
SystemDrive
SystemRoot
TEMP
TMP
USERDOMAIN
USERNAME
USERPROFILE
windir
Value
­­­­­
C:\ProgramData
C:\Users\Administrator\AppData\Roaming
C:\Program Files\Common Files
C:\Program Files (x86)\Common Files
C:\Program Files\Common Files
W2K2016
C:\Windows\system32\cmd.exe
NO
C:
\Users\Administrator
C:\Users\Administrator\AppData\Local
\\W2K2016
4
Windows_NT
%SystemRoot%\system32\WindowsPowerShell\v1...
.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WS...
AMD64
Intel64 Family 6 Model 37 Stepping 5, Gen...
6
2505
C:\ProgramData
C:\Program Files
C:\Program Files (x86)
C:\Program Files
C:\Users\Administrator\Documents\WindowsPo...
C:\Users\Public
RDP­Tcp#0
C:
C:\Windows
C:\Users\ADMINI~1\AppData\Local\Temp\2
C:\Users\ADMINI~1\AppData\Local\Temp\2
PS­SCRIPTING
Administrator
C:\Users\Administrator
C:\Windows
Una vez en el interior de un proveedor podemos usar los comandos vistos anteriormente tales como:
Item, Remove­Item, Copy­Item, Rename­Item, etc.
New­
En el ejemplo anterior, si nos hubiésemos posicionado en el interior del proveedor Environment (con el
comando cd Env:), el uso de New­Item nos permitiría crear una nueva variable de entorno, y al
contrario, Remove­Item nos permitiría eliminar una.
Por ejemplo, para crear la variable
varTest:
49
PS > Set­Location Env:
PS > New­Item ­Path . ­Name varTest ­Value ’Variable de test’
Name
­­­­
varTest
Value
­­­­­
Variable de test
Hubiésemos podido también usar el siguiente comando:
New­Item
­path env: ­Name varTest ­Value ’Variable de test’
Y para eliminar la variable
varTest:
PS > Remove­Item Env:varTest
Para mostrar simplemente el contenido de una variable, haga como si estuviese en el proveedor de
variables de entorno: Get­Content miVariable.
Si no, haga lo siguiente: Get­Content
Env:miVariable
Ejemplo:
PS > Get­Content Env:windir
C:\Windows
Para hacer persistente la adición o eliminación de una variable de entorno, hay que manipular el
registro. En caso contrario, el efecto durará únicamente el tiempo de la sesión PowerShell en curso.
Ya está, acabamos de terminar la introducción sobre los proveedores; los volverá a encontrar a lo largo del
libro ya que su uso es muy frecuente. Simplifican considerablemente la vida en la escritura de scripts.
Observe que ciertos módulos o snap­ins pueden aportar proveedores suplementarios (además de
comandos). Es el caso, por ejemplo, del módulo ActiveDirectory. Gracias a él puede, si lo desea,
explorar el AD como si explorara un sistema de archivos.
Para obtener ayuda muy detallada sobre el funcionamiento de cada proveedor, puede utilizar el
comando help nombre_del_proveedor.
Ejemplo:
PS > help environment
o:
PS > help wsman
Para terminar, no dude en consultar la sección de ayuda especializada acerca de los proveedores usando el
comando help about_providers.
50
Manipulación
de objetos
51
¿Qué es la noción de objeto?
Sin entrar en detalle en la programación orientada a objetos, vamos en esta sección a explicar con palabras
algunas nociones esenciales que debe conocer cuando utiliza objetos… Para empezar, definamos lo que es
un objeto.
En primer lugar, debe saber que no existe UNA definición única de lo que es un objeto. Diremos simplemente
que se trata de un concepto informático abstracto para tratar de representar objetos del mundo real. Lo va
a entender…
Un objeto posee características, llamados en la jerga «propiedades». También es posible interactuar con un
objeto mediante «métodos».
Veamos un ejemplo concreto con una moto, la cual apreciamos particularmente
.
Una moto posee, entre otras cosas, las características siguientes:
Modelo.
Cilindrada.
Potencia.
Color.
Peso.
Etc.
Si una moto fuese un objeto informático, acabaríamos de enumerar las propiedades del objeto.
Todo esto está muy bien, pero ¿qué acciones podemos realizar con nuestra moto?
Pues entre otras cosas, podemos:
Arrancar/parar el motor.
Acelerar/frenar.
Girar (a la derecha o a la izquierda).
Hacer un caballito/un stoppie.
Etc.
Lo ha entendido, todo esto representaría los métodos de nuestro objeto.
Ahora que está familiarizado con los términos «propiedad» y «método», introduciremos el término
«miembro». Un miembro es el término genérico que representa indistintamente una propiedad o un método
del objeto. Tendremos la oportunidad de volver a hablar de ello, pero ¿igual ya se ha encontrado con el
comando Get­Member? Este permite, como su nombre indica, devolver los miembros de un objeto,
ayudando así a la comprensión de la estructura de un objeto.
La capacidad para un lenguaje informático de poder auto examinarse se denomina «reflexión».
PowerShell es por lo tanto gracias al framework .NET un lenguaje reflexivo.
En el mundo informático, y en particular en PowerShell, accedemos a los miembros de un objeto con la
notación llamada «puntuada»; es decir que utilizamos el carácter punto («.») para acceder a los miembros
de un objeto.
52
Por ejemplo, en un objeto llamado «miMoto», para recuperar el color usaremos la sintaxis siguiente:
PS > $miMoto.color
Y para arrancar «miMoto», la sintaxis que sigue:
PS > $miMoto.arrancar()
Es muy importante subrayar que la llamada a un método se realiza siempre con la ayuda de paréntesis. Si
los olvida, PowerShell no obtendrá el resultado esperado y eso provocará probablemente la aparición de
bugs.
Sepa que, por defecto, PowerShell no devuelve errores (en este caso, PowerShell devolverá el valor
$null) si intenta llamar a una propiedad que no existe. No es el caso si llama a un método que no existe.
Pero esté tranquilo, es posible cambiar este comportamiento por defecto con el fin de hacer que PowerShell
sea menos permisivo gracias al comando Set­StrictMode.
Es posible pasar valores a nuestros miembros para modificar las características de nuestro objeto o para
actuar sobre este último.
Por ejemplo, para cambiar el color de nuestra moto podríamos teclear lo siguiente:
PS > $miMoto.color = ’negro’
De la misma manera que para actuar sobre nuestra máquina, podríamos realizar lo siguiente:
# Girar a la derecha
PS > $miMoto.girar(’derecha’)
# Acelerar a 50 km/h
PS > $miMoto.acelerar(50)
Ahora que hemos aclarado el tema, debemos introducir una última noción que es la «clase de objetos».
Para hacerlo simple, una clase de objetos es la representación abstracta de un objeto. En otros términos,
nuestro objeto «miMoto» es en realidad un objeto creado a partir de la clase «moto» (la cual podría haber
sido creada a partir de la clase «vehículo»).
Decimos también que «miMoto» es una instancia de la clase «moto».
Así si hacemos la analogía con PowerShell (pero también con otros lenguajes .NET), el framework .NET pone
a nuestra disposición un conjunto de clases, las cuales instanciaremos (ya sea implícitamente o
explícitamente) con el fin de «fabricar» objetos. Como desarrolladores de scripts PowerShell pasamos la
mayor parte de nuestro tiempo manipulando estos objetos (¡y no las clases!).
Hay que saber que una clase puede también llamarse «tipo». Por consiguiente, cuando abordaremos la
noción de tipo de variables, acuérdese de esto. Por ejemplo, una variable de tipo string es en realidad un
objeto creado a partir de la clase «string», o más precisamente una instancia de la clase system.string.
Dicho esto, es frecuente decir que se trata de un objeto de tipo string.
Para terminar y completar este descubrimiento de los objetos, descubrirá que podemos también crear
nuestros propios objetos… Pero cada cosa a su tiempo…
53
Manipulación de objetos
1. Las colecciones
Antes de empezar a manipular objetos individuales es importante saber que la mayor parte del tiempo
tendremos primero que manipular una colección de objetos (una colección representa varios objetos, se
trata también de otro término para designar una tabla).
En efecto, cuando ejecutamos un comando PowerShell, este devuelve muchas veces varios resultados.
Tomemos el ejemplo siguiente:
PS > $result = Get­ChildItem C:\Windows
Hemos asignado el resultado del comando Get­ChildItem (equivalente a dir en CMD) a la variable
$result. Esta última representa ahora una colección de objetos ya que el directorio C:\Windows
contiene muchas carpetas y archivos. Los objetos contenidos en
diferentes.
$result tienen por lo tantos tipos
Para convencernos de ello, en modo interactivo, es decir desde la consola, aplicamos el comando
Member a nuestra variable:
Get­
PS > $result | Get­Member
TypeName: System.IO.DirectoryInfo
Name
­­­­
Mode
Create
CreateObjRef
CreateSubdirectory
Delete
...
MemberType
­­­­­­­­­­
CodeProperty
Method
Method
Method
Method
Definition
­­­­­­­­­­
System.String Mode{get=Mode;}
void Create(), void Create...
System.Runtime.Remoting.Ob...
System.IO.DirectoryInfo Cr...
void Delete(), void Delete...
TypeName: System.IO.FileInfo
Name
­­­­
Mode
AppendText
CopyTo
Create
CreateObjRef
...
MemberType
­­­­­­­­­­
CodeProperty
Method
Method
Method
Method
Definition
­­­­­­­­­­
System.String Mode{get=Mode;}
System.IO.StreamWriter Appe...
System.IO.FileInfo CopyTo(s...
System.IO.FileStream Create()
System.Runtime.Remoting.Obj...
Hemos truncado voluntariamente el resultado a cinco resultados únicamente por objeto ya que la lista es
larga y esta hubiese ocupado demasiado espacio en su impresión.
Lo importante aquí es la propiedad TypeName así como los diferentes valores que contiene. Los tipos de
la colección son por lo tanto los siguientes:
System.IO.DirectoryInfo
System.IO.FileInfo
54
El primer tipo representa una carpeta y el segundo un archivo. Esto significa que la variable
contiene únicamente objetos de estos dos tipos.
$result
Sin embargo, si ha entendido bien hasta aquí, se preguntará: ¿cómo es posible que Get­Member nos
devuelve este tipo de resultado? En efecto, mencionamos al principio de esta sección que el resultado de
un comando PowerShell producía generalmente una colección de objetos. Pero entonces ¿cómo es posible
que Get­Member no nos haya devuelto un resultado de tipo array?
Pues como todo lenguaje, PowerShell posee algunas pequeñas sutilidades y nos encontramos aquí con
una de ellas…
Debe saber que Get­Member, cuando se aplica sobre una variable que contiene objetos, devuelve los
miembros de los objetos contenidos en la colección (uno por tipo). Get­Member se aplica por lo tanto en
este contexto sobre el contenido. Para que
Get­Member se aplique sobre el continente, debe utilizarlo
de la siguiente manera:
PS > Get­Member ­InputObject $result
TypeName: System.Object[]
Name
­­­­
Count
Add
Address
Clear
Clone
...
MemberType
­­­­­­­­­­
AliasProperty
Method
Method
Method
Method
Definition
­­­­­­­­­­
Count = Length
int Ilist.Add(System.Objec...
System.Object&, mscorlib, ...
void Ilist.Clear()
System.Object Clone(), Sys...
Podemos observar que el tipo devuelto esta vez es System.Object[], es decir que se trata de una
tabla de objetos. Y recuerde, hemos averiguado antes el tipo de los objetos contenidos en el interior de
esta tabla gracias a la otra forma de usar el comando Get­Member.
Observe que los corchetes indican que la variable es de tipo array.
Tendremos la oportunidad de hablar de nuevo de los arrays con más detalle en el capítulo Arrays,
enteramente dedicado a este tema. De momento, vamos a introducir algunas pequeñas nociones que le
serán de utilidad para comprender bien lo que sigue.
Un array posee siempre la propiedad Length, útil para calcular su tamaño. Observe que gracias a Get­
Member podemos ver que existe también la propiedad Count y que esta última es en realidad un alias
de la propiedad Length. Podemos por lo tanto utilizar una u otra indistintamente y obtendremos el
mismo resultado.
PS > $result.length
110
Tenemos por lo tanto 110 archivos y carpetas en la carpeta C:\Windows. Nuestra colección tiene por lo
tanto referencias a 110 elementos.
Como con cualquier array, podemos usar un índice para acceder a los elementos del mismo.
Así, para recuperar el primer elemento, solicitaremos el elemento 0 como sigue:
55
PS > $result[0]
Directory: C:\Windows
Mode
­­­­
d­­­­
LastWriteTime
­­­­­­­­­­­­­
22/08/2013
17:36
Length Name
­­­­­­ ­­­­
addins
El resultado obtenido expone las propiedades Mode, LastWriteTime, Length y Name del objeto con
índice 0 de la colección. Observando el valor de la propiedad Mode, podemos deducir que se trata en esta
ocasión de una carpeta.
Para obtener una propiedad específica de un objeto, tal como la fecha de la última modificación, basta con
usar la notación con el punto y precisar el nombre de la propiedad deseada como sigue:
PS > $result[0].LastWriteTime
Jueves 22 de agosto de 2013 17:36:36
Aunque parezca evidente que nuestro objeto solo posea cinco propiedades, ya que PowerShell solo nos
muestra estas, representaría una visión sesgada de la realidad. En efecto, si PowerShell solo nos muestra
un subconjunto de propiedades, es simplemente para que no nos ahoguemos en una cantidad excesiva
de información no siempre útil. Tendremos la ocasión de volver a tratar esto en la parte dedicada al
formateo de los objetos de este capítulo. Hasta entonces, recuerde lo siguiente: el hecho de que
PowerShell no nos muestre ciertas propiedades no quiere decir que estas no existan en memoria.
En efecto, si aplicamos
Get­Member sobre $result[0], las verá todas en pantalla:
PS > $result[0] | Get­Member
No mostraremos el resultado de este comando puesto que ya ha tenido una muestra de ello cuando
hemos aplicado Get­Member a $result.
2. Diferencias de comportamiento entre versiones de PowerShell
La versión 3 de PowerShell ha introducido algunos cambios sutiles en el lenguaje que puede resultar
interesante conocer cuando se escriben scripts retrocompatibles.
a. Colecciones de objetos
Existe una diferencia de comportamiento importante que debe conocer entre las versiones de PowerShell
cuando intentamos manipular una colección que solo contiene un objeto. Pensará «pero… ¿una colección
existe siempre que exista más de un objeto? Si no, no sería una colección sino ¡un valor escalar!».
¡Efectivamente! Tiene toda la razón ya que de esta forma razonaban PowerShell v1 y v2. Pero ya no es
el caso con PowerShell v3 y versiones posteriores. En efecto, estas últimas consideran, para nuestro
beneficio, que una colección puede contener un solo objeto.
Esto tiene su importancia ya que imagínese, por ejemplo, recuperar los nombre de archivos presentes en
una carpeta y que esta solo contenga uno. En este caso, intentará recuperar el valor contenido en la
propiedad count, y como el objeto recuperado no es un array sino un escalar, entonces la propiedad
count no existirá y su script no funcionará como estaba previsto…
56
Este comportamiento se modificó desde la versión 3 de PowerShell ya que esto provocaba numerosos
dolores de cabeza a los principiantes de versiones anteriores. Si se lo mencionamos, es porque es
posible que tenga, por razones de compatibilidad, que desarrollar scripts para plataformas que solo
ejecuten PowerShell v2 como máximo.
Entenderá mejor lo explicado si lo explicamos mediante un ejemplo.
Consideremos la carpeta C:\Temp, que contiene un único archivo.
Puede ver el resultado de la ejecución en PowerShell versión 1 y 2:
PS > $result = Get­ChildItem C:\temp
PS > $result.count
No hay resultado, ya que en esta ocasión
$result.count vale null.
Veamos ahora lo que devuelve con PowerShell 3 y posteriores:
PS > $result = Get­ChildItem C:\temp
PS > $result.count
1
Aquí, el resultado vale 1 y no null como en las versiones 1 y 2, lo que resulta más lógico, o por lo
menos más intuitivo. En efecto, sea cual sea el número de objetos que contenga una carpeta, es lógico
utilizar el mismo código.
Así que para obtener un resultado coherente y homogéneo, y esto sea cual sea la versión de
PowerShell, existe un pequeño truco… Consiste en forzar a que el resultado devuelto sea una colección
englobando la expresión con @(), como puede ver a continuación:
PowerShell cualquier versión:
PS > $result = @(Get­ChildItem C:\temp)
PS > $result.count
1
En resumen, si no quiere tener sorpresas durante la ejecución, cuando sepa que va a recuperar una
colección de objetos, acuérdese de usar siempre @() en sus expresiones. Así se asegurará de la
compatibilidad de sus scripts con versiones más antiguas.
b. Expansión automática de las propiedades de los elementos de una colección
Antes de la versión 3 de PowerShell, no era posible escribir lo siguiente:
PS > (Get­Process).Name
En realidad, si era posible escribirlo, pero el resultado obtenido no habría sido el esperado; lo cual es
lógico si se piensa... Para que funcione, se debería haber escrito:
PS > Get­Process | Foreach­Object {$_.Name}
o bien:
57
PS > Get­Process | Select­Object ­ExpandProperty Name
En este ejemplo, tratamos de recuperar la propiedad Name del objeto devuelto por el comando Get­
Process, o más bien deberíamos decir: intentamos recuperar la propiedad Name de la colección de
objetos devuelta por Get­Process. Sin embargo, la propiedad Name no existe en la colección, sino
que está presente sobre los objetos que componen la colección.
Desde PowerShell 3, las cosas han cambiado y PowerShell se ha vuelto un poco más «inteligente», en el
sentido de que esta escritura funciona. En la actualidad, PowerShell intenta en primer lugar devolver la
propiedad solicitada sobre la colección. Si no la encuentra, entonces PowerShell inspecciona cada objeto
de la colección y devuelve esta propiedad si existe. Cabe destacar que ocurre lo mismo con los métodos.
Tenga precaución, pues conviene saber que PowerShell no devuelve ningún error si intenta invocar
una propiedad que no existe en un objeto. Al contrario, el valor devuelto valdrá Null.
Por ejemplo:
PS > (Get­Process).propriedadInexistente
PS > (Get­Process).propriedadInexistente ­eq $null
True
En cambio, la llamada a un método que no existe siempre devolverá un error.
Es posible modificar el comportamiento por defecto para que PowerShell devuelva un error en
caso de invocar una propiedad que no existe. Para ello, es necesario al menos escoger el
«modo strict versión 2» con el siguiente comando:
PS > Set­StrictMode ­Version 2
Por ejemplo:
PS > (Get­Process).propiedadNoExiste
The property ’propiedadNoExiste’ cannot be found on this object.
Verify that the property exists.
c. Where­Object y Foreach­Object simplificados
Desde la versión 3 de PowerShell, el uso de los comandos Where­Object y Foreach­Object se ha
simplificado, en el sentido de que ahora ya no es necesario utilizar un bloque de script junto a la variable
automática $_.
Ejemplo ­ Recuperar los procesos cuyo número de id es mayor de 1000:
# Forma clásica
PS > Get­Process | Where { $_.id ­gt 1000 }
sería equivalente a:
# Forma simplificada
PS > Get­Process | Where id ­gt 1000
58
Para que la magia opere, Microsoft ha modificado en profundidad el código de estos comandos
aportándole nuevos juegos de parámetros suplementarios. En la actualidad, todo lo que no es un bloque
de script (entienda, como ello, toda expresión que no esté delimitada por {}) se considera como un valor
de propiedad. Además, los operadores, por su parte, no son realmente operadores, puesto que se
interpretan como parámetros. ¡Es ingenioso!
De este modo, la forma simplificada no autoriza la escritura de filtros complejos que contengan varias
condiciones, y su ejecución resulta más lenta. Por todos estos motivos, nosotros, como autores,
preferimos seguir utilizando la forma tradicional.
Ejemplo ­ Recuperar los procesos cuyo id es mayor de 1000 que empiecen por la letra A:
# Forma clásica
PS > Get­Process | Where { $_.id ­gt 1000 ­and $_.name ­like ’a*’}
Se podría (equivocadamente) pensar que esto funciona:
# Forma simplificada
PS > Get­Process | Where id ­gt 1000 ­and name ­like ’a*
¡Pero no ocurre así! De hecho, para que funcione este ejemplo, habría que encadenar dos comandos
Where­Object como se muestra a continuación:
PS > Get­Process | Where id ­gt 1000 | Where name ­like ’a*’
Encontrará explicaciones adicionales sobre
Where­Object y Foreach­Object más adelante en este
capítulo.
3. Selección/recuperación de resultados
La recuperación de resultados contenidos en una colección de objetos pasa en muchas ocasiones por usar
el comando Select­Object. Este posee más de un as en su manga y lo usaremos muy a menudo a lo
largo de este libro, así como en la vida real.
Veamos los diferentes parámetros que tiene en cuenta este comando:
Descripción
Parámetro
­Property <Object[]>
Indica las propiedades a seleccionar.
­ExcludeProperty <string[]>
Suprime las
selección.
­ExpandProperty <string>
Especifica una propiedad a seleccionar e
indica una tentativa para desarrollar esta
propiedad.
­First <int>
Especifica el numero de objetos a seleccionar
desde el principio del array de entrada.
­Index <Int32[]>
Selecciona los objetos de un array en función
de su índice. Teclee los índices en una lista
separada por comas.
propiedades
indicadas
de
la
59
Parámetro
Descripción
­Last <int>
Especifica el número de objetos a seleccionar
desde el final del array de entrada.
­Skip <int>
Ignora el numero especificado de elementos
(empieza a contar desde 1).
­Unique <Switch>
Especifica que, si un subconjunto de objetos
de entrada contiene objetos que tengan
todas sus propiedades y valores idénticos, un
solo miembro de este subconjunto será
seleccionado.
a. Recuperación de los n primeros objetos
Veamos cómo recuperar únicamente los cinco primeros objetos de una colección. Vamos esta vez a
recuperar una colección de procesos gracias al comando Get­Process, pero como la lista es muy larga
nos interesaremos en un pequeño subconjunto.
PS > $processes = Get­Process
PS > $processes | Select­Object ­first 5
Handles
­­­­­­­
856
1736
318
212
1097
NPM(K)
­­­­­­
42
86
28
28
72
PM(K)
­­­­­
34260
49588
5828
54732
103000
WS(K) VM(M)
­­­­­ ­­­­­
29476
173
16988
538
4452
107
69992
345
154416
394
CPU(s)
­­­­­­
1,61
9,48
124,58
Id
­­
1012
1884
3752
708
828
ProcessName
­­­­­­­­­­­
CcmExec
ccSvcHst
ccSvcHst
chrome
chrome
Hemos tenido suerte, ya que los objetos recuperados se ordenan alfabéticamente por su nombre. En
efecto, con los conocimientos adquiridos hasta ahora, no sabemos cómo realizar una ordenación. Dicho
esto, veremos esto un poco más adelante, en la sección Ordenación de objetos.
b. Recuperación de los n últimos objetos
Para recuperar los n últimos objetos, lo habrá adivinado, vamos a usar el parámetro
ejemplo que sigue:
­Last, como en el
PS > Get­Process | Select­Object ­Last 5
Handles
­­­­­­­
127
714
365
135
178
NPM(K)
­­­­­­
11
88
20
13
18
PM(K)
­­­­­
3384
65436
14892
6568
16280
WS(K) VM(M)
­­­­­ ­­­­­
2896
59
133340
456
11264
79
3740
49
7048
86
CPU(s)
­­­­­­
88,44
Id
­­
560
3604
2844
3028
3492
ProcessName
­­­­­­­­­­­
winlogon
WINWORD
WmiPrvSE
WmiPrvSE
WmiPrvSE
60
c. Recuperación de objetos únicos
Como habrá observado en los resultados del ejemplo anterior, hemos recuperado varias veces el mismo
nombre de proceso. Esto es un funcionamiento normal en la medida en que podemos iniciar varias veces
un mismo proceso. Muy bien, pero ¿cómo hacer para recuperar una sola instancia de un proceso? Pues
simplemente usando el conmutador ­Unique (un conmutador es un parámetro que no acepta ningún
valor).
PS > Get­Process | Select­Object ­Unique | Select­Object ­First 5
Handles
­­­­­­­
869
1721
212
57
777
NPM(K)
­­­­­­
42
86
28
8
13
PM(K)
­­­­­
34336
47356
56728
2788
2632
WS(K) VM(M)
­­­­­ ­­­­­
29584
174
16392
538
70296
345
9228
65
2268
50
CPU(s)
­­­­­­
11,26
0,55
Id
­­
1012
1884
708
5332
444
ProcessName
­­­­­­­­­­­
CcmExec
ccSvcHst
chrome
conhost
csrss
Como puede constatar, no tenemos esta vez la repetición de los procesos ccSvcHst y chrome.
Aunque podríamos haber agrupado los parámetro
­Unique y ­First en el mismo comando, este
Select­Object aplica primero el
no hubiese devuelto el resultado esperado. En efecto
parámetro ­First, y después el conmutador ­Unique.
d. Recuperación de una propiedad determinada
El comando
Get­Process es maravilloso gracias a la información precisa que devuelve; dicho esto, en
ciertas ocasiones no necesitamos tanta… Imaginemos por ejemplo que queremos recuperar únicamente
los nombres de los procesos para escribirlos en un archivo de log. En este caso, usaremos el parámetro
­Property al que indicaremos la propiedad ProcessName. El uso explícito de este parámetro es
opcional ya que se trata del parámetro por defecto.
PS > Get­Process | Select­Object ProcessName
ProcessName
­­­­­­­­­­­
CcmExec
ccSvcHst
ccSvcHst
chrome
chrome
chrome
conhost
csrss
dwm
explorer
lsass
notepad
...
Podríamos especificar varios nombre de propiedades. Así, para recuperar el nombre de los procesos así
como el id asociado, podríamos haber escrito:
61
PS > Get­Process | Select­Object ProcessName,ID
ProcessName
­­­­­­­­­­­
CcmExec
ccSvcHst
ccSvcHst
chrome
...
Id
­­
1012
1884
3752
708
Pero volvamos a nuestros procesos… Cuando utilizamos el comando Select­Object como acabamos
de hacer, este devuelve siempre el nombre de la o las propiedades seleccionadas. Esto es normal ya que
el objeto devuelto ha sido «amputado» de sus otras propiedades. Para verificarlo, apliquemos el
comando Get­Member como se muestra a continuación:
PS > Get­Process | Select­Object ProcessName | Get­Member
TypeName: Selected.System.Diagnostics.Process
Name
­­­­
Equals
GetHashCode
GetType
ToString
ProcessName
MemberType
­­­­­­­­­­
Method
Method
Method
Method
NoteProperty
Definition
­­­­­­­­­­
bool Equals(System.Object obj)
int GetHashCode()
type GetType()
string ToString()
System.String ProcessName=CcmExec
Podemos en efecto observar que queda una sola propiedad a nuestro objeto y que su tipo empieza por
Selected.xxx, lo que significa que hemos aplicado una selección. Esta ha tenido lugar sobre un
objeto del tipo
System.Diagnostics.Process.
Para recuperar solamente el contenido de nuestra selección, tenemos que utilizar el parámetro
ExpandProperty, de la siguiente manera:
­
PS > Get­Process | Select­Object ­ExpandProperty ProcessName
CcmExec
ccSvcHst
ccSvcHst
chrome
chrome
chrome
conhost
csrss
dwm
explorer
lsass
notepad
...
Esta vez, el resultado obtenido es una tabla de cadenas de caracteres. Debe saber que en cada
aplicación de un comando PowerShell el tipo de dato devuelto puede cambiar y es importante saber
determinarlo. Get­Member es un valioso aliado para conseguirlo.
62
e. Selección de objetos de un array basado en el valor del índice
Ya lo ha comprendido, cuando un comando PowerShell nos devuelve varios resultados, recuperamos en
realidad una colección (un array) de objetos.
Hemos visto que los arrays están indexados y que su contenido es accesible mediante el valor de su
índice. Así, para recuperar el quinto elemento de una tabla, vamos a indicar el índice numero 4, estando
el primer elemento situado en la posición cero:
PS > $processes = Get­Process
PS > $processes[4]
Handles
­­­­­­­
1097
NPM(K)
­­­­­­
72
PM(K)
­­­­­
103000
Gracias al parámetro
WS(K) VM(M)
­­­­­ ­­­­­
154416
394
CPU(s)
­­­­­­
142,02
Id ProcessName
­­ ­­­­­­­­­­­
828 chrome
­Index podemos hacer lo mismo con la línea de comandos siguiente:
PS > $processes | Select­Object ­Index 4
También podemos especificar varios índices:
PS > $processes | Select­Object ­Index 4,18,25
Handles
­­­­­­­
1097
773
103
NPM(K)
­­­­­­
72
54
9
PM(K)
­­­­­
103000
27032
2248
WS(K) VM(M)
­­­­­ ­­­­­
154416
394
35112
275
980
54
CPU(s)
­­­­­­
142,07
8,30
Id ProcessName
­­ ­­­­­­­­­­­
828 chrome
1828 explorer
792 nvvsvc
f. Examen de todos los objetos de una colección
El comando Foreach­Object (alias:
objeto actual de una colección.
foreach o %) permite ejecutar un bloque de script para cada
Ejemplo:
PS > Get­Service | ForEach­Object {$_.DisplayName.toUpper()} |
Select ­First 5
ACTIVE DIRECTORY WEB SERVICES
APPLICATION EXPERIENCE
APPLICATION LAYER GATEWAY SERVICE
APPLICATION HOST HELPER SERVICE
APPLICATION IDENTITY
Una vez más, utilizamos la variable automática $_. Así, hemos recuperado para cada objeto la propiedad
DisplayName y aplicado el método toUpper() que permite devolver la cadena de caracteres en
mayúsculas.
Forma simplificada
La forma simplificada del comando
Foreach­Object permite escribir:
63
PS > Get­Service | ForEach­Object DisplayName
No obstante, la limitación de esta forma es que no podemos aplicar métodos como habríamos hecho con
la forma clásica. De este modo, habría que modificar el código de la siguiente manera:
PS > (Get­Service | ForEach­Object DisplayName).ToUpper() |
Select­Object ­first 5
¡Lo dejamos a su elección!
Para descubrir numerosos ejemplos acerca del uso del comando
Bucles y condiciones.
Foreach­Object, consulte el capítulo
g. Agrupación de objetos
El comando Group­Object permite agrupar objetos en base a una o varias propiedades. Detrás de su
apariencia anodina, este comando es muy potente y puede prestarnos muchos servicios...
Veamos los diferentes parámetros del comando
Parámetro
Group­Object:
Descripción
­Property <Object[]>
Especifica las propiedades de agrupación. Los objetos
se organizan en grupos dependiendo del valor de la
propiedad indicada.
El valor del parámetro ­Property puede ser otra
propiedad calculada. Para crear una propiedad
calculada, cree una tabla de hash con una clave
Expression especificando un valor de cadena o de
bloque de script.
­AsHashTable <Switch>
Devuelve el grupo bajo la forma de una tabla de hash.
Las claves de la tabla de hash son los valores de la
propiedad según se han ido agrupando los objetos. Los
valores de la tabla de hash son los objetos que tengan
un valor para esta propiedad.
­AsString <Switch>
Convierte las claves de la tabla de hash en cadenas.
Por defecto, las claves de la tabla de hash son
instancias del objeto agrupado. Este parámetro es
válido únicamente cuando se utiliza con el parámetro ­
AsHashTable.
­CaseSensitive
<string>
Hace que la agrupación sea case sensitive. Sin este
parámetro, los valores de propiedad de los objetos de
un grupo pueden tener caracteres de distintos tipos.
­Culture <string>
Especifica la cultura a usar en la comparación.
­NoElement <Switch>
Indica no devolver los grupos de resultados.
Veamos cómo agrupar los servicios por su estado (parado, arrancado u otros):
64
PS > Get­Service | Group­Object ­Property status
Count
­­­­­
96
68
Name
­­­­
Stopped
Running
Group
­­­­­
{System.ServiceProcess.ServiceController, Syst...
{System.ServiceProcess.ServiceController, Syst...
Una vez más, como el resultado contiene varias líneas, tenemos que utilizar un array. Así, podríamos
fácilmente conseguir el resultado del primer grupo de la siguiente manera:
PS > (Get­Service | Group­Object ­Property status)[0].Group
Status
­­­­­­
Stopped
Stopped
Stopped
Stopped
Stopped
Stopped
Name
­­­­
AdobeFlashPlaye...
AeLookupSvc
ALG
Appinfo
AppMgmt
aspnet_state
DisplayName
­­­­­­­­­­­
Adobe Flash Player Update Service
Experiencia de aplicación
Servicio de la pasarela de la capa...
Informaciones de aplicación
Gestión de aplicaciones
Servicio de estado ASP.NET
Observe que existen muchas otras maneras de conseguir el mismo resultado.
Veamos el impacto de la opción
­NoElement:
PS > Get­Service | Group­Object ­Property status ­NoElement
Count
­­­­­
96
68
Name
­­­­
Stopped
Running
En ciertos casos, este tipo de resultado es suficiente ya que solo nos interesa el nombre de los objetos
de cada grupo.
Otra manera, muy práctica y profesional, de acceder a los resultados de
usar el conmutador ­AsHashtable. Ahora entenderá…
Group­Object consiste en
PS > $r = Get­Service | Group­Object ­Property status `
­AsHashTable ­AsString
PS > $r.running
Status
­­­­­­
Running
Running
Running
Running
Running
...
Name
­­­­
AppIDSvc
AudioEndpointBu...
AudioSrv
BFE
BITS
DisplayName
­­­­­­­­­­­
Identidad de la aplicación
Generador de puntos de finalización...
Audio Windows
Motor de filtrado de base
Servicio de transferencia inteligente en...
No olvide agregar la opción ­AsString con
caracteres de modo que esto funcione.
­AsHashTable para convertir en cadenas de
Aprenderá más cosas sobre las tablas de hash en el capítulo Arrays.
65
h. Ordenación de objetos
La ordenación es una operación habitual cuando se manipula una colección de objetos. De esta
necesidad nació el comando Sort­Object.
Veamos los parámetros del comando
Sort­Object:
Descripción
Parámetro
­Property <Object[]>
Especifica las propiedades a usar en la ordenación.
Los objetos se ordenan en función de los valores de
estas propiedades. Los caracteres genéricos están
permitidos.
Si especifica varias propiedades, los objetos se
ordenan en primer lugar por su primera propiedad.
Si varios objetos tienen el mismo valor para la primera
propiedad, se ordenan en función de la segunda
propiedad. Este proceso continua hasta que no haya
ninguna propiedad especificada o ningún grupo de
objetos.
­CaseSensitive <string>
Indica que la ordenación debe respetar las
mayúsculas. Por defecto, la ordenación no es case
sensitive.
­Culture <string>
Especifica la configuración cultural a utilizar en la
ordenación.
­Descending <Switch>
Ordena los objetos en orden descendente. El valor
por defecto es ascendente.
El parámetro ­Descending se aplica a todas las
propiedades. Para ordenar algunas propiedades
ascendentemente y otras descendentemente debe
especificar los valores de la propiedad con la ayuda de
una tabla hash.
­Unique <Switch>
Elimina las repeticiones y devuelve solamente los
miembros únicos de la colección. Puede usar este
parámetro en vez del comando Get­Unique.
Cuando recuperamos los procesos devueltos por
Get­Process, podemos apreciar que están
ordenados por nombre y en orden alfabético.
Veamos cómo podríamos hacer para ordenarlos por su número de proceso.
PS > Get­Process | Sort­Object ­Property id
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s)
Id
­­­­­­­ ­­­­­­ ­­­­­ ­­­­­ ­­­­­ ­­­­­­
­­
0
0
0
24
0
0
741
0
264
1064
11
4
418
35
9880
8364
114
284
30
1
420
212
4
328
2055
60 96128 44184
687
372
797
13
2736
2516
50
444
...
ProcessName
­­­­­­­­­­­
Idle
System
svchost
smss
svchost
csrss
66
Por defecto, la ordenación se realiza en orden ascendente. Para obtenerlo de manera descendente,
vamos a usar antes ­Descending, como sigue:
PS > Get­Process | Sort­Object ­Property id ­Descending
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s)
Id ProcessName
­­­­­­­ ­­­­­­ ­­­­­ ­­­­­ ­­­­­ ­­­­­­
­­ ­­­­­­­­­­­
163
17
27596 78004
158 206,87 5836 dwm
57
8
2884
6396
66
7,78 5332 conhost
86
9
2384
6632
55
0,05 4864 taskeng
219
33 104516 124700
309 16,47 4856 chrome
177
26
55952 71884
249
4,29 4704 chrome
...
Para terminar con
Sort­Object, vamos a ver cómo suprimir los valores duplicados. Los procesos se
prestan especialmente bien a este tipo de ejercicio en la medida en que encontramos bastantes
repetidos.
A continuación se muestra cómo recuperar la lista de procesos (únicos) en ejecución en nuestro sistema:
PS > Get­Process | Select­Object ­ExpandProperty ProcessName |
Sort­Object ­Unique
i. Enumeración/cantidad de objetos
Determinar el número de objetos de una colección es una operación muy habitual en todo lenguaje
informático.
Ya hemos visto este tema al principio del capítulo, pero para los lectores que cojan el tren en marcha,
dado que una colección de objetos no es más que un array de objetos, podemos utilizar la propiedad
length (o count que es su alias).
Así la línea de comandos siguiente nos devolverá el número de procesos en ejecución de nuestro
sistema:
PS > $processes = Get­Process
PS > $processes.length
59
PS > $processes.count
59
Podemos también ahorrar una variable tecleando directamente:
PS > (Get­Process).length
59
Podríamos también usar el comando
Measure­Object para contar el número de objetos de una
colección, pero sería como matar moscas a cañonazos
En efecto
.
Measure­Object es un comando muy potente y por desgracia bastante desconocido por los
desarrolladores de scripts PowerShell.
En su forma de uso más simple, puede ver cómo usarlo para contar objetos de una colección:
67
PS > Get­Process | Measure­Object
Count
Average
Sum
Maximum
Minimum
Property
: 59
:
:
:
:
:
Este comando no devuelve directamente un resultado, sino que devuelve un objeto que contiene el
resultado. Así, para extraer el valor deseado, hubiésemos tenido que escribir:
PS > Get­Process | Measure­Object | Select­Object ­ExpandProperty
Count
O simplemente:
PS > (Get­Process | Measure­Object).count
Measure­Object posee dos series de parámetros. Uno permite medir texto, el otro medir números.
Dicho de otro modo, con este potente comando es posible calcular una media, un máximo y un mínimo a
partir de una colección de valores numéricos.
Measure­Object permite también sobre un archivo de texto contar las palabras, caracteres, líneas,
etc.
Aplicación de Measure­Object sobre una colección numérica
Sin más explicación, determinemos el tamaño mínimo, máximo y medio de los archivos presentes en una
carpeta, por ejemplo:
PS > $r = Get­ChildItem C:\Windows
PS > $r | Measure­Object ­Property length ­Average ­Maximum ­Minimum ­Sum
Count
Average
Sum
Maximum
Minimum
Property
:
:
:
:
:
:
41
224568,43902439
9207306
2871808
0
length
El resultado se explica de la siguiente manera. Tenemos 4 archivos existentes en la carpeta
C:\Windows. Su tamaño medio es aproximadamente 224 568 bytes. El tamaño mínimo de un archivo es
de cero bytes y el máximo de 2 871 808 bytes, para sumar 9 207 306 bytes. Práctico, ¿verdad?
Aplicación de Measure­Object sobre una colección de cadenas de caracteres
Get­
Content. Este devuelve un array de cadenas de caracteres que pasamos a Measure­Object para
En el ejemplo siguiente, recuperamos el contenido de un archivo con la ayuda del comando
contar el número de líneas, palabras y caracteres.
68
PS > Get­Content .\fic.txt | Measure­Object ­Line ­Word ­Character
Lines Words Characters Property
­­­­­ ­­­­­ ­­­­­­­­­­ ­­­­­­­­
5
122
726
j. Comparación de objetos
Otro comando muy potente para la manipulación de objetos, o deberíamos decir para la comparación de
colecciones de objetos. Como vemos, ¡PowerShell no tiene límites de recursos!
El comando Compare­Object recibe como entrada dos colecciones. La primera es considerada la de
referencia. La segunda por lo tanto es considerada como la de diferencia. Podemos especificar si
deseamos obtener los objetos diferentes o idénticos. Para finalizar, Compare­Object nos mostrará
mediante los símbolos
<= y => si los objetos se encuentran en la colección de referencia o en la otra.
Ejemplo de comparación de archivos de texto
PS > $fic1 = Get­Content .\fic1.txt
PS > $fic2 = Get­Content .\fic2.txt
PS > Compare­Object ReferenceObject $fic1 ­DifferenceObject $fic2
InputObject
­­­­­­­­­­­
Esta frase solo existe en fic2.txt.
SideIndicator
­­­­­­­­­­­­­
=>
El objeto obtenido representa la diferencia entre fic1.txt y fic2.txt. Por este motivo que la flecha apunta
hacia la derecha.
Veamos ahora el resultado si especificamos el conmutador
­includeEqual:
PS > Compare­Object $fic1 $fic2 ­includeEqual
InputObject
­­­­­­­­­­­
En una colección de objetos no hay, en general, qu...
Para llegar a aislar uno o varios objetos en parte...
Se trata del comando Where­Object (alias: Where).
Gracias a Where­Object una colección va a pasar a...
Podemos construir un filtro, para ello utilizaremos...
Esta frase solo existe en fic2.txt.
Es importante entender que
no solamente con texto.
SideIndicator
­­­­­­­­­­­­­
==
==
==
==
==
==
=>
Compare­Object puede aplicarse sobre cualquier tipo de objetos y
Ejemplo de comparación de objetos process
Terminaremos con un ejemplo de comparación de procesos ya que se trata de algo que podría llegar a
hacer en su trabajo de administrador de sistemas.
Vamos por lo tanto a recuperar los procesos en ejecución en un instante t. Arrancaremos un proceso, por
ejemplo la aplicación Internet Explorer, y después recuperaremos de nuevo los procesos. Estaremos por
lo tanto en el instante más algunos segundos.
69
PS
PS
PS
PS
>
>
>
>
$antes = Get­Process
Start­Process iexplore.exe
$despues = Get­Process
Compare­Object _ReferenceObject $antes ­DifferenceObject $despues
InputObject
SideIndicator
­­­­­­­­­­­
­­­­­­­­­­­­­
System.Diagnostics.Process (iexplore) =>
System.Diagnostics.Process (iexplore) =>
Observamos que cuando arranca Internet Explorer se crean dos procesos.
4. Filtrar los objetos
Dentro de una colección de objetos generalmente encontramos algunos que no nos interesan. Para llegar
a aislar uno o varios objetos determinados, PowerShell dispone de un comando adaptado a esta
necesidad. Se trata del comando Where­Object (alias: Where, ?).
Gracias a
Where­Object, será posible inspeccionar una colección meticulosamente y cada objeto será
evaluado con esmero. Un poco como un filtro de búsqueda.
Para construir un filtro usaremos los operadores de comparación. Sin embargo, no nos perderemos en los
detalles ya que estos últimos se tratan en el capítulo Operadores, dedicado a ellos. No hace falta decir
que ¡existe un gran número de ellos!
Con el objetivo de ilustrar nuestras palabras con ejemplos, usaremos esta vez una colección que contenga
objetos de tipo «service».
Empecemos primero por recuperar los servicios presentes en el equipo:
PS > $services = Get­Service
Veamos ahora el contenido de la variable
$services:
PS > $services
Status
­­­­­­
Running
Stopped
Stopped
Running
Stopped
...
Name
­­­­
ADWS
AeLookupSvc
ALG
AppHostSvc
AppIDSvc
DisplayName
­­­­­­­­­­­
Active Directory Web Services
Application Experience
Application Layer Gateway Service
Application Host Helper Service
Application Identity
Como hay un número importante de ellos, representamos aquí una pequeña muestra.
Aplicamos ahora un filtro para recuperar únicamente los servicios de Windows en ejecución de nuestro
equipo:
70
PS > $services | Where­Object {$_.Status ­eq ’running’}
Status
­­­­­­
Running
Running
Running
Running
Running
Running
...
Name
­­­­
ADWS
AppHostSvc
BFE
BITS
BrokerInfrastru...
CertPropSvc
DisplayName
­­­­­­­­­­­
Active Directory Web Services
Application Host Helper Service
Base Filtering Engine
Background Intelligent Transfer Ser...
Background Tasks Infrastructure Ser...
Certificate Propagation
El símbolo «$_» es en realidad una variable llamada « automática » que representa el objeto actual
recibido de Where­Object. Así, todos los objetos de la colección transitarán cada uno en su turno por
esta variable especial. Como esta contiene todas las propiedades (pero también los métodos) del objeto
actual, podemos acceder a cualquiera de ellas. Aquí recuperamos la propiedad Status y realizamos una
comparación de igualdad para determinar si esta es igual al valor
el objeto actual.
running. Si fuera el caso, se devolvería
Así, hubiésemos podido continuar la línea de comandos encadenando con otra comparación separadas por
un «pipe».
Como en el siguiente ejemplo:
PS > $services | Where {$_.Status ­eq ’running’} |
Select­Object ­Last 3
Status
­­­­­­
Running
Running
Running
Name
­­­­
WinRM
WsusService
wuauserv
DisplayName
­­­­­­­­­­­
Windows Remote Management (WS­Management)
WSUS Service
Windows Update
De esta manera, hemos recuperado los tres «últimos » servicios en ejecución en nuestro equipo.
Ahora, observemos otro ejemplo basado en archivos. En este ejemplo, recuperamos los archivos cuyo
tamaño es superior a 500 bytes. Para esto, usaremos un filtro basado en la propiedad length de cada
elemento devuelto por el cmdlet Get­ChildItem.
PS > Get­ChildItem | Where­Object {$_.length ­gt 500}
Directorio : C:\Temp
Mode
­­­­
­a­­­
­a­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
11/12/2014
09:57
11/12/2014
10:46
11/12/2014
10:49
Length
­­­­­­
9444
19968
9892
Name
­­­­
Fichero1.txt
Fichero2.txt
Fichero3.txt
71
Formateo de objetos para la visualización
Importante: los comandos de formateo han sido concebidos para mostrar los objetos en la consola de
PowerShell (ISE o consola clásica). ¡En ningún caso debe usarlos en un script! Si comete este error,
tendrá problemas para entender por qué su script no funciona correctamente. En efecto, los comandos de
formateo modifican los objetos originales para crear objetos que se puedan mostrar en la consola. No
debe por lo tanto confundir los comandos de formateo con el comando Select­Object.
Tratar una parte del tema de formateo de la visualización del resultado de comandos puede seguramente
sorprenderle, pero sepa que dado el aspecto de orientación a objetos de PowerShell, esto es indispensable
y comprenderá por qué.
Si también se pregunta por qué la ventana de la consola PowerShell es más generosa en términos de
dimensiones que la de CMD, entonces esta parte responderá a sus preguntas…
Dado que PowerShell posee la posibilidad intrínseca de manipular objetos, todo lo que aparece en pantalla
al ejecutar un comando no es sino una representación de algunas propiedades. En efecto, un objeto puede
tener hasta decenas de propiedades, de modo que habría sido complicado mostrar todas en pantalla, y eso
de manera inteligible. Así, la elección de propiedades, que llamaremos «propiedades por defecto», se realizó
de manera arbitraria por los creadores de PowerShell.
Debe saber que el número de propiedades a mostrar depende en parte del tamaño de la ventana
PowerShell. Este número depende también de lo que el usuario final está preparado para ver, ya que si
para el equivalente de un simple dir tiene como retorno quince propiedades para cada archivo, esto sería
rápidamente muy tedioso de leer e interpretar.
Quedémonos con el ejemplo de
dir o, mejor, de Get­ChildItem.
PS > Get­ChildItem C:\
Directory : C:\
Mode
­­­­
d­­­­­
d­r­­­
d­­­­­
d­r­­­
d­­­­­
LastWriteTime
­­­­­­­­­­­­­
11/12/2017 6:22 PM
11/5/2017
1:40 PM
11/3/2017
9:33 AM
11/14/2016 11:05 AM
11/3/2017
4:08 PM
Length Name
­­­­­­ ­­­­
Logs
Program Files
Program Files (x86)
Users
Windows
Observamos que este comando devuelve las siguientes propiedades:
Name.
Mode, LastWriteTime, Length y
Esta visualización es la visualización por defecto que obtenemos sin añadir ningún parámetro particular a
Get­ChildItem; se trata aquí de una visualización tabulada.
Sepa que con PowerShell dispone de comandos específicos para el formateo de la visualización. Tenemos 4
disponibles, pero solo hablaremos de los comandos más útiles, a saber Format­List y Format­Table.
72
Alias
Nombre
Descripción
Format­List
fl
Muestra las propiedades en forma de lista.
Format­Table
ft
Muestra las propiedades de forma tabulada.
Format­Wide
fw
Muestra una única propiedad en formato largo de tabla.
Format­Custom
fc
Visualización personalizada de propiedades definidas por el
usuario mediante un archivo PS1XML.
Debe saber que todos estos comandos se ejecutan principalmente a la salida de un pipeline para simplificar
su uso. Así, en una sola línea de comandos puede tratar su objeto y transmitirlo a un comando de formateo
mediante el pipe.
1. Format­List
Este comando de formateo permite mostrar las propiedades de objetos en forma de lista. Es decir que
cada propiedad de cada objeto se muestra en una nueva línea.
Continuemos con el ejemplo anterior, probando el siguiente comando:
Format­List
Get­ChildItem C:\ |
Directory: C:\
Name
CreationTime
LastWriteTime
LastAccessTime
Mode
LinkType
Target
:
:
:
:
:
:
:
Logs
9/12/2016 1:37:02 PM
11/12/2017 6:22:24 PM
9/12/2016 1:37:02 PM
d­­­­­
Name
CreationTime
LastWriteTime
LastAccessTime
Mode
LinkType
Target
:
:
:
:
:
:
:
Program Files
7/16/2016 8:04:24 AM
11/5/2017 1:40:44 PM
11/5/2017 1:40:44 PM
d­r­­­
:
:
:
:
:
:
:
Windows
7/16/2016 8:04:24 AM
11/3/2017 4:08:43 PM
11/3/2017 4:08:43 PM
d­­­­­
{C:\Logs}
{C:\Program Files}
...
Name
CreationTime
LastWriteTime
LastAccessTime
Mode
LinkType
Target
{C:\Windows}
Viendo atentamente el resultado de este comando, nos damos cuenta de que recuperamos propiedades
diferentes que cuando ejecutamos Get­ChildItem solo, es decir sin comando de formateo. En efecto,
hemos « perdido » la propiedad Length, y hemos conseguido algunas propiedades suplementarias, como
CreationTime, LastAccessTime y LastWriteTime.
73
Además, vemos que las propiedades se muestran unas debajo de las otras y que cada objeto está
separado del objeto que le precede por una línea en blanco.
a. Visualización selectiva de las propiedades de un objeto
El parámetro más utilizado con
Format­List es ­Property. Generalmente es un parámetro
olvidado, ya que se trata del parámetro por defecto de todos los comandos de formateo. Este permite
mostrar solamente algunas propiedades y por orden de aparición detrás de este parámetro.
Por ejemplo, para mostrar las propiedades Name y Length de los archivos contenidos en la carpeta
C:\Users\Administrator\Links, podríamos teclear lo siguiente:
PS > Get­ChildItem $Home\Links ­File | Format­List ­Property Name,Length
Name
: Desktop.lnk
Length : 462
Name
: Downloads.lnk
Length : 929
Name
: RecentPlaces.lnk
Length : 383
Otro ejemplo, para mostrar selectivamente algunas propiedades de los servicios de Windows:
PS > Get­Service | Format­List Name, Displayname, Status
Name
: ADWS
DisplayName : Active Directory Web Services
Status
: Running
Name
: AeLookupSvc
DisplayName : Application Experience
Status
: Stopped
Name
: ALG
DisplayName : Application Layer Gateway Service
Status
: Stopped
...
b. Visualización de todas las propiedades disponibles de un objeto
Ahora mostraremos todas las propiedades de un archivo (o más bien de un objeto de tipo archivo)
gracias al siguiente comando: Get­ChildItem Archivo| Format­List *
Gracias al uso del carácter comodín « * » enumeramos todas las propiedades de un objeto. Ya no
estamos por lo tanto limitados a las propiedades por defecto.
74
PS > Get­ChildItem MyFile.txt | Format­List *
PSPath
PSParentPath
PSChildName
PSDrive
PSProvider
PSIsContainer
VersionInfo
:
:
:
:
:
:
:
Microsoft.PowerShell.Core\FileSystem::C:\MyFile.txt
Microsoft.PowerShell.Core\FileSystem::C:\
MyFile.txt
C
Microsoft.PowerShell.Core\FileSystem
False
File:
C:\MyFile.txt
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug:
False
Patched:
False
PreRelease:
False
PrivateBuild:
False
SpecialBuild:
False
Language:
BaseName
Mode
Name
Length
DirectoryName
Directory
IsReadOnly
Exists
FullName
Extension
CreationTime
CreationTimeUtc
LastAccessTime
LastAccessTimeUtc
LastWriteTime
LastWriteTimeUtc
Attributes
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
MyFile
­a­­­
MyFile.txt
33
C:\
C:\
False
True
C:\MyFile.txt
.txt
07/12/2017 12:16:48
07/12/2017 11:16:48
07/12/2017 12:16:48
07/12/2017 11:16:48
07/12/2017 12:16:48
07/12/2017 11:16:48
Archive
La ventaja principal de usar el comando Format­List respecto a la visualización de un tipo tabla
(Format­Table), es que los valores de las propiedades disponen de más sitio en pantalla para
mostrarse, y por lo tanto no se truncan. Otro aspecto importante, y no de los menos interesantes, es
poder enumerar todas las propiedades de un objeto gracias al carácter comodín «*». También es
posible utilizar el comodín en una parte del nombre de las propiedades: Get­ChildItem |
Format­List Name, *time permite, además del nombre, mostrar todas las propiedades cuyo
nombre termina por «time».
Ejemplo
PS > Get­ChildItem MyFile.txt | Format­List Name,*time
Name
CreationTime
LastAccessTime
LastWriteTime
:
:
:
:
MyFile.txt
07/12/2017 12:16:48
07/12/2017 12:16:48
07/12/2017 12:16:48
75
2. Format­Table
El comando Format­Table permite mostrar las propiedades de objetos en forma de tabla. Este formato
es muy práctico ya que ofrece una visión más sintética que el formato de lista; seguramente no es casual
si la mayoría de los comandos PowerShell devuelven su resultado de esta forma.
Al igual que
Format­List, la ejecución de este comando sin especificar ningún parámetro devuelve una
lista de propiedades por defecto.
La lista de propiedades por defecto es distinta en función del tipo de objeto elegido.
Sigamos con el ejemplo anterior, probando el siguiente comando:
PS > Get­ChildItem C:\ | Format­Table
Directory : C:\
Mode
­­­­
d­­­­­
d­­­­­
d­r­­­
d­­­­­
d­­­­­
d­­­­­
d­r­­­
d­­­­­
­a­­­­
LastWriteTime
­­­­­­­­­­­­­
11/12/2017
6:22 PM
7/16/2016
3:23 PM
11/5/2017
1:40 PM
11/3/2017
9:33 AM
11/29/2016
5:40 PM
3/13/2017 11:39 PM
11/14/2016 11:05 AM
11/3/2017
4:08 PM
11/12/2017
6:42 PM
Vemos que
idéntico sin
Length Name
­­­­­­ ­­­­
Logs
PerfLogs
Program Files
Program Files (x86)
Sources
temp
Users
Windows
30 MyFile.txt
Format­Table no tiene efecto sobre nuestro comando Get­ChildItem; el resultado es
Format­Table.
Esto es normal ya que, por defecto, el resultado de
Get­ChildItem se hace siempre en este formato.
Acaba de descubrir que con PowerShell cada tipo de objeto posee una lista de propiedades mostradas por
defecto.
Nunca lo repetiremos bastante, pero recuerde que el hecho de que algunas propiedades no se muestren
por defecto en la consola no significa que el objeto no las posee.
A continuación los parámetros más corrientes utilizados con
Parámetro
Format­Table:
Descripción
­Property
Propiedad o lista de propiedades a mostrar.
­Autosize
Ajuste del tamaño de las columnas al número de caracteres a
mostrar.
­HideTableHeaders
Enmascara los encabezados de las columnas.
­GroupBy
Agrupa la visualización según una propiedad o un valor común.
A continuación veremos algunos ejemplos de uso de estos parámetros:
76
Ejemplo
Enumerar las propiedades personalizadas en una tabla.
PS > Get­ChildItem C:\ ­Force | Format­Table ­Property mode,
name,length,isreadonly,creationTime
Mode
­­­­
d­­hs
d­­hs
d­­­­
d­r­­
d­­­­
d­­h­
d­­hs
d­r­­
d­­­­
­arhs
­a­hs
­a­­­
­a­hs
Name
length
­­­­
­­­­­­
$Recycle.Bin
Documents and...
PerfLogs
Program Files
Program Files...
ProgramData
System Volum...
Users
Windows
bootmgr
398356
BOOTNXT
1
MyFile.txt
33
pagefile.sys
536870912
isreadonly
­­­­­­­­­­
True
False
False
False
Creati...
­­­­­­­
22/08/...
22/08/...
22/08/...
22/08/...
22/08/...
22/08/...
01/06/...
22/08/...
22/08/...
22/08/...
22/08/...
07/12/...
1/06/...
Name y
CreationTime. Esto significa que PowerShell ha truncado estos valores ya que no había suficiente
En este ejemplo, observará que existen puntos suspensivos para los valores de las propiedades
espacio para mostrarlos. Por defecto, la consola adapta la visualización al tamaño de la ventana, y para
ello ocupa todo el espacio (en horizontal) asignado y calcula el tamaño de las columnas en función de este
número. En este caso concreto, todas las columnas tienen el mismo tamaño; es la razón por la cual
podemos ver un gran número de espacios entre ciertas columnas mientras que otras no disponen de
suficiente sitio para mostrar sus datos (si el cálculo no es redondo, las primeras columnas (a la izquierda)
pueden tener uno o dos caracteres de más que los demás).
Para tratar de resolver este «problema» existe el parámetro
­Autosize.
a. Tamaño automático de una tabla
Probemos ahora la misma línea de comandos que antes pero añadiendo
­Autosize al final:
Ejemplo
Enumerar las propiedades personalizadas de una tabla con tamaño automático.
PS > Get­ChildItem C:\| Format­Table ­Property mode,name,length,
isreadonly,creationTime ­Autosize
Mode
­­­­
d­­hs
d­­hs
d­­­­
d­r­­
d­­­­
d­­h­
d­­hs
d­r­­
d­­­­
­arhs
Name
length
­­­­
­­­­­­
$Recycle.Bin
Documents and Settings
PerfLogs
Program Files
Program Files (x86)
ProgramData
System Volume Information
Users
Windows
bootmgr
398356
isreadonly CreationTime
­­­­­­­­­­ ­­­­­­­­­­­­
22/08/2013 17:39:31
22/08/2013 16:48:41
22/08/2013 17:39:30
22/08/2013 15:36:16
22/08/2013 15:36:16
22/08/2013 15:36:16
01/06/2014 11:27:18
22/08/2013 15:36:16
22/08/2013 15:36:16
True
22/08/2013 17:46:48
77
­a­hs BOOTNXT
­a­­­ MyFile.txt
­a­hs pagefile.sys
1
False
33
False
536870912 False
22/08/2013 17:46:48
07/12/2014 12:16:48
01/06/2014 11:27:19
¡Victoria! Nuestra información se muestra, ahora, correctamente y ningún dato está truncado. La
visualización parece ahora más equilibrada. PowerShell ha adaptado el tamaño de cada columna al
tamaño máximo de su contenido.
Cuando se usa el parámetro
­Autosize, PowerShell da prioridad a la visualización de las columnas de
la izquierda. PowerShell considera que la importancia de las columnas viene determinada por el orden en
el que se han especificado las propiedades en la línea de comandos.
Sepa que en las propiedades de la consola, en la pestaña Layout, es posible elegir el tamaño del
buffer así como el de la pantalla PowerShell. Jugando con estos parámetros le será posible
optimizar la visualización.
Creación de objetos personalizados
Resulta fundamental saber cómo devolver objetos y no texto bruto cuando se programa con PowerShell.
PowerShell es realmente un shell orientado a objetos. Todo comando produce objetos como retorno, de
modo que sería lógico que todos nuestros scripts y funciones devolvieran también objetos. Además de
respetar las buenas prácticas, esto nos facilita enormemente la vida. De ahí la importancia de este capítulo…
En esta sección veremos cómo enriquecer un objeto existente aportando nuevas propiedades o métodos.
Veremos también cómo crear un objeto desde la nada.
1. Transformación de un objeto existente
Aunque no hayamos todavía abordado CIM / WMI, vamos sin embargo a utilizar esta tecnología en los
próximos ejemplos con el fin de producir algo de acuerdo a la realidad. En consecuencia, le recomendamos
que lea el capítulo CIM/WMI si nunca ha oído hablar de estos términos.
En el siguiente ejemplo, recuperaremos la versión del sistema operativo de un conjunto de máquinas. Para
ello, efectuaremos una consulta WMI sobre la clase Win32_OperatingSystem. Esta devolverá un
objeto que contiene una serie de propiedades, cuyos nombres no son siempre evidentes. Vamos por lo
tanto, a partir del objeto devuelto, a crear un objeto con nuevas propiedades.
Veamos primero lo que devuelve la línea de comandos siguiente:
PS > Get­CimInstance ­ClassName Win32_OperatingSystem | Format­List *
Status
Name
2016 Standard|C:\Wi...
FreePhysicalMemory
FreeSpaceInPagingFiles
FreeVirtualMemory
Caption
2016 Standard
Description
InstallDate
CreationClassName
CSCreationClassName
: OK
: Microsoft Windows Server
:
:
:
:
394268
724536
1189144
Microsoft Windows Server
:
: 11/14/2016 11:05:01 AM
: Win32_OperatingSystem
: Win32_ComputerSystem
78
CSName
CurrentTimeZone
Distributed
LastBootUpTime
LocalDateTime
Version
...
:
:
:
:
:
:
WIN­MDN6O5ULNP4
60
False
11/3/2017 4:10:36 PM
11/12/2017 6:58:51 PM
10.0.14393
No hemos mostrado todo ya que la lista es muy larga, pero las propiedades que nos interesan están ahí.
Recuperemos ahora las propiedades
CSName, Caption y Version:
PS > Get­CimInstance Win32_OperatingSystem |
Select­Object CSName, Caption, Version |
Format­List
CSName : WIN­MDN6O5ULNP4
Caption : Microsoft Windows Server 2016 Standard
Version : 10.0.14393
Los datos recuperados están presentes pero el nombre de las propiedades deja mucho que desear…
Como podemos ver, Select­Object permite «truncar» un objeto de las propiedades no deseadas para
conservar únicamente las seleccionadas. En realidad, devuelve un objeto diferente del objeto original.
Para verificarlo, aplicamos el comando
TypeName:
Get­Member al resultado y observaremos el valor de la propiedad
PS > $obj = Get­CimInstance Win32_OperatingSystem |
Select­Object CSName, Caption, Version
PS > $obj | Get­Member
TypeName: Selected.Microsoft.Management.Infrastructure.CimInstance
Name
­­­­
Equals
GetHashCode
GetType
ToString
Caption
CSName
version
MemberType
­­­­­­­­­­
Method
Method
Method
Method
NoteProperty
NoteProperty
NoteProperty
Definition
­­­­­­­­­­
bool Equals(System.Object obj)
int GetHashCode()
type GetType()
string ToString()
string Caption=Microsoft Windows Server 2016 Standard
string CSName=WIN­MDN6O5ULNP4
string Version=10.0.14393
Podemos ver las siguientes cosas:
El
objeto
es
de
tipo
Selected.Microsoft.Management.Infrastructure.CimInstance.
El número de propiedades se ha reducido considerablemente. Ha pasado de una decena a tres.
Veamos ahora cómo modificar el nombre de las propiedades devueltas… Como le decíamos antes,
Select­Object es un comando muy sofisticado que tiene más de un as en la manga. No lo habíamos
mencionado hasta aquí, pero este acepta también como parámetro una tabla de hash en un formato
específico, como puede ver a continuación:
79
PS > Get­CimInstance Win32_OperatingSystem |
Select­Object @{name=’ComputerName’; expression={$_.CSName}},
@{n=’OS’; e={$_.Caption}},
Version
ComputerName
­­­­­­­­­­­­
WIN­MDN6O5ULNP4
OS
­­
Microsoft Windows Server 2016 Standard
Version
­­­­­­­
10.0.14393
¿No es mejor así?
Para limitar las pulsaciones de teclado, puede acortar el nombre de las claves de la tabla de hash.
Así, name y expression pueden transformarse respectivamente en n y e, tal como hemos hecho
para definir la propiedad OS.
Habrá observado el uso de las comillas simples para asignar la clave
expression. Esto significa
que esta última necesita que se le pase un bloque de script. En otras palabras, no estamos limitados
al uso de la variable $_ sino que podemos eventualmente efectuar procesamientos complejos en el
interior de un bloque de script si es necesario.
Para terminar este ejemplo, veamos cómo interrogar a varios equipos con el fin de recuperar un conjunto
de valores y ver cómo se devuelven. Para ello, el cmdlet Get­WMIObject tiene en cuenta el parámetro ­
ComputerName. Así, podremos aplicar la consulta WMI sobre uno o varios equipos remotos, como en el
ejemplo siguiente:
PS > $computers = ’.’, ’WIN8US­0’, ’WIN7US­0’
PS > Get­CimInstance Win32_OperatingSystem |
Select­Object @{name=’ComputerName’; expression={$_.CSName}},
@{n=’OS’; e={$_.Caption}},
Version
ComputerName
­­­­­­­­­­­­
WIN­MDN6O5ULNP4
WIN81US­0
WIN7US­0
OS
­­­­­­
Microsoft Windows Server 2016 Standard
Microsoft Windows 8.1 Enterprise
Microsoft Windows 7 Ultimate
Version
­­­­­­­­­­
10.0.14393
6.3.9600
6.1.7601
2. Creación de un objeto desde la nada
Antes hemos visto cómo usar Select­Object para crear un objeto personalizado a partir de un objeto
existente. Ahora, veamos cómo crear un objeto con todos sus componentes.
Es ahora cuando entra en escena el tipo
[PSCustomObject].
PS > $obj = [PSCustomObject] @{
Nombre
= ’Pedro Pérez’
Email
= ’pperez@ediciones­eni.com’
SitioWeb
= ’www.ediciones­eni.com’
Edad
= 43
CodigoPostal = 33000}
PS > $obj
80
Nombre
:
Email
:
SitioWeb
:
Edad
:
CodigoPostal:
Pedro Pérez
pperez@ediciones­eni.com
www.ediciones­eni.com
43
33000
Habrá observado que basta con pasar una tabla de hash al tipo
[PSCustomObject].
New­Object para crear un objeto personalizado; sin
PSCustomObject es que preserva el orden de las propiedades del objeto.
También habríamos podido usar el comando
embargo la ventaja del tipo
Ejemplo
PS > $obj = @{
Nombre = ’Pedro Pérez’
Email = ’pperez@ediciones­eni.com’
SitioWeb = ’www.ediciones­eni.com’
Edad = 43
CodigoPostal = 33000}
PS > New­Object ­TypeName PSObject ­Property $obj
Email
Nombre
SitioWeb
Edad
CodigoPostal
:
:
:
:
:
pperez@ediciones­eni.com
Pedro Pérez
www.ediciones­eni.com
43
33000
Conviene saber que PowerShell no preserva el orden de los datos en una tabla asociativa, por este
motivo nuestro objeto muestra sus propiedades desordenadas. Dicho esto, no es realmente grave, pues
en memoria el orden no importa. Para forzar a PowerShell a preservar el orden, hubiésemos tenido que
declarar nuestra tabla asociativa de tipo Ordered. Sin embargo el uso del tipo PSCustomObject
resulta mucho más simple ya que preserva el orden. Por esta razón recomendamos usarlo como
técnica para la creación de objetos personalizados.
Los tipos
PSCustomObject y Ordered aparecieron en PowerShell 3. De este modo, para que sus
New­Object de la
scripts sean compatibles con PowerShell 1 y 2, no tendrá otra opción salvo usar
siguiente manera:
New­Object ­TypeName PSObject ­Property $obj
No se aprovechará, tampoco, de la posibilidad de preservar el orden de las propiedades del objeto.
3. Añadir miembros
Es muy fácil añadir propiedades y métodos a un objeto existente, ya sea de tipo
PSObject u otro. En
general, los desarrolladores de scripts se contentan con añadir propiedades. Dicho esto, y los
desarrolladores se alegrarán, es posible agregar métodos, hablaremos un poquito de ello...
El comando clave para añadir miembros es
Add­Member.
Veremos diferentes técnicas ya que estas han evolucionado con las versiones de PowerShell.
81
a. Añadir una propiedad
Versiones PowerShell 1 y 2
Definamos un objeto simple de la siguiente manera:
PS > $hashTable = @{
Nombre
= ’Eleonor Pérez’
Profesión
= ’Hija de Geek’
Id
=
0
}
PS > $obj = New­Object ­TypeName PSObject ­Property $hashTable
Ahora, agreguemos la propiedad
FechaNacimiento:
PS > $obj | Add­Member ­MemberType NoteProperty `
­Name FechaNacimiento `
­Value ([DateTime]’08/26/2004’)
Verifiquemos ahora que a nuestro objeto se le ha añadido de una propiedad suplementaria:
PS > $obj | Format­List
Id
Profesión
Nombre
FechaNacimiento
:
:
:
:
0
Hija de Geek
Eleonor Pérez
26/08/2004 00:00:00
Al menos que utilicemos la propiedad
­PassThru, Add­Member no devuelve nada.
Veamos ahora cómo realizar la misma operación con una versión de PowerShell superior a la versión 2.
Comprobará que las cosas se han simplificado y que se preserva el orden de las propiedades del objeto.
Versiones PowerShell 3 y siguientes
PS > $obj = [PSCustomObject]@{
Nombre
= ’Eleonor Pérez’
Profesión
= ’Hija de Geek’
Id
=
0
}
PS > $obj |
Add­Member ­NotePropertyMembers @{FechaNacimiento=[DateTime]’08/26/2004’}
PS > $obj | Format­List
Nombre
Profesión
Id
FechaNacimiento
:
:
:
:
Eleonor Pérez
Hija de Geek
0
26/08/2004 00:00:00
82
El parámetro ­NotePropertyMembers es muy práctico. Permite pasar una tabla de hash que
contiene tantos pares propiedad/valor como son necesarios. Esto supone un ahorro de tiempo precioso
en relación al uso de los parámetros ­NotePropertyName y ­NotePropertyValue que deben
utilizarse con cada pareja clave/valor a añadir.
b. Añadir un método
La técnica mostrada aquí funciona cualquiera sea la versión de PowerShell.
Partiendo del objeto creado anteriormente, como disponemos de la fecha de nacimiento, ¿por qué no
crear un método GetUptime con el fin de determinar cuántos días han pasado desde su nacimiento?
Para conseguirlo, es necesario utilizar el comando Add­Member con su parámetro ­MemberType
seguido del valor
ScriptMethod, como muestra el siguiente ejemplo:
PS > $obj | Add­Member ­MemberType ScriptMethod ­Name GetUptime ­Value {
New­TimeSpan ­End (Get­Date) ­Start $this.FechaNacimiento |
Select­Object ­ExpandProperty Days
}
PS > $obj.GetUptime()
4826
Cuando estamos en el interior del bloque de script que define el cuerpo del método, la referencia al
objeto actual se realiza gracias a la variable especial $this.
4. Creación de una colección de objetos personalizados
En general, cuando decidimos crear un objeto personalizado, es porque tenemos una colección de ellos
que devolver.
Por ejemplo
PS > $obj1 = [PSCustomObject]@{
Nombre
= ’Eleonor Pérez’
Profesión
= ’Hija de Geek’
Id
=0
}
PS > $obj2 = [PSCustomObject]@{
Nombre
= ’Pedro Pérez’
Profesión
= ’Viejo Papá Geek’
Id
=1
}
PS > $obj3 = [PSCustomObject]@{
Nombre
= ’Roberto Fernández’
Profesión
= ’Joven Papá Geek’
Id
=2
}
PS > $objArray = @($obj1, $obj2, $obj3)
PS > $objArray
83
Nombre
­­­­­­
Eleonor Pérez
Pedro Pérez
Roberto Fernández
Profesión
­­­­­­­­­
Hija de Geek
Viejo Papá Geek
Joven Papá Geek
Id
­­­
0
1
2
Aunque sea más explícito crear un array de objetos antes de devolverla como acabamos de hacer,
PowerShell se encarga de agrupar automáticamente los objetos del mismo tipo antes de devolverlos. Así,
obtenemos el mismo resultado escribiendo lo siguiente:
PS > $obj1, $obj2, $obj3
Nombre
­­­­­­
Eleonor Pérez
Pedro Pérez
Roberto Fernández
Profesión
­­­­­­­­­
Hija de Geek
Viejo Papá Geek
Joven Papá Geek
Id
­­
0
1
2
Para que este ejemplo funcione con PowerShell 1 y 2, la técnica es la misma salvo que tendría que
crear los objetos con el comando New­Object (como hemos visto anteriormente), habiendo
aparecido el tipo PSCustomObject con la versión 3.
84
Variables y
tipos de datos
85
Las variables
1. Creación y asignación
La creación de variables en PowerShell es realmente una cosa fácil. A diferencia de los verdaderos
lenguajes de programación, PowerShell no es un lenguaje fuertemente tipado, es decir que las variables
no necesitan ser declaradas ni inicializadas antes de ser utilizadas. Así basta con asignar con el operador
= un valor a una variable y PowerShell se encarga del resto, a saber crearla en memoria y determinar su
tipo. Este es el funcionamiento por defecto, pero veremos a continuación que podemos hacer que
PowerShell sea mucho menos permisivo.
Ejemplo
$variable = valor de un tipo cualquiera
Tecleando
$var1 = 12 en la consola PowerShell, creamos una variable con el nombre var1 y le
asignamos el valor 12. PowerShell determinará a continuación que se trata de un valor entero y le
asignará automáticamente el tipo int (integer).
Tecleando $var2 = ’A’ realizamos la misma operación que antes exceptuando esta vez que
var2 será de string (cadena) aunque esta no contenga más que un carácter.
Aunque no lo recomendamos, es posible crear variables cuyo nombre contiene
caracteres especiales (&, @, %, ­, £, $, ., etc.). En este caso, es indispensable poner el nombre
de la variable entre llaves).
Ejemplo
${www.powershell­scripting.com} = 1
Esto asignará el valor
1 a la variable entre las llaves.
86
2. Determinar el tipo de una variable
Puede determinar el tipo de una variable aplicándole el método
retorno contenido en la propiedad
GetType() y observando el valor de
Name.
Ejemplo
PS > $var1 = 12
PS > $var1.GetType()
IsPublic IsSerial Name
­­­­­­­­ ­­­­­­­­ ­­­­
True
True
Int32
BaseType
­­­­­­­­
System.ValueType
3. Acceder al contenido de una variable
Para acceder al contenido de una variable, basta con teclear simplemente el nombre de la variable en la
consola o en un script.
Ejemplo
PS > $var1
12
PS > $var1 = $var1 + 5
PS > $var1
17
Las constantes
Las constantes son variables cuyo contenido no debe (y/o no puede) cambiar a lo largo del tiempo.
Aunque es posible contentarse con una simple asignación de la variable, esta técnica no impide una
eventual modificación del contenido de la variable. Por ello es preferible utilizar el comando New­Variable
así como el parámetro ­Option Constant, como en el siguiente ejemplo:
PS > New­Variable ­Name PI ­Value 3.14 ­Option Constant
De esta manera, estamos protegidos del riesgo de modificación del contenido. Para convencernos de ello,
probemos a ver qué pasa si asignamos un nuevo valor:
PS > $PI = 4
Cannot overwrite variable PI because it is read­only or constant.
At line:1 char:1
+ $PI=4
+ ~~~~~
+ CategoryInfo : WriteError: (PI:String) [],
SessionStateUnauthorized...
+ FullyQualifiedErrorId : VariableNotWritable
Es habitual, con el fin de distinguir fácilmente una constante de una variable en un programa,
poner su nombre en mayúsculas.
87
­Option también acepta otros valores, como: None, ReadOnly, Constant,
Private, AllScope, Unspecified. Estos pueden ser de gran utilidad para especificar la
El parámetro
visibilidad de una variable a través de los distintos alcances (ámbitos) de PowerShell.
Tipos de datos
Aunque en general, en los scripts de cada día, nos contentemos con los tipos de datos más básicos que
existen, es sin embargo posible utilizar un gran número de ellos. En efecto, como PowerShell obtiene toda
su potencia del framework .NET, todos los tipos .NET pueden potencialmente ser accedidos y utilizados.
Por el momento, nos contentaremos con describir los tipos comúnmente utilizados.
Descripción
Tipo
Int, Int32, Int64
Representa enteros en un rango comprendido
­2 147 483 648 y 2 147 483 647 incluidos.
El tipo de dato
bytes (32 bits).
Double
entre
int se almacena como un entero de cuatro
Representa números con coma tan grandes como 10 308
(positivos o negativos), con una precisión de 15 cifras y tan
pequeños como 10 ­323 (64 bits).
Char
Representa cualquiera de los 65 536 caracteres Unicode
contenidos en dos bytes (16 bits).
String
Representa una cadena bajo la forma de vector de caracteres
Unicode.
Boolean
Representa un valor booleano (true o
Array
Representa un array que contiene múltiples valores. Un array
puede contener datos de diferentes tipos.
Object, PSObject,
PSCustomObject
Representa un objeto generalmente personalizado.
false).
Asignación manual de tipos y tipado
Que los incondicionales del « tipado fuerte » estén tranquilos. Existe una alternativa al tipado automático
para ser dueño del tipado de las variables. Para ello, debe definir el tipo deseado entre corchetes y poner
este último delante de la variable como en el siguiente ejemplo:
PS > [int]$var = 12
Tecleando esto, tiene asegurado que la variable $var será de tipo entero. Pero, ¡no hay ninguna diferencia
entre $var = 12 e [int]$var = 12, se preguntará! Cierto es que por el momento el interés es mínimo
pero cuando sean grandes «powershellers» y sus scripts empiecen a tomar amplitud, el hecho de declarar
sus variables con un tipo determinado hará que su trabajo sea mucho más comprensible para los demás y
sobre todo impedirá que un valor de un tipo diferente le sea asignado por error.
88
Ejemplo
PS > [int]$numero = Read­Host ’Indique un número’
Indique un número: cien
Cannot convert value "cien" to type "System.Int32".
Error: "Input string was not in a correct format."
cien no ha sido reconocido como un número entero. El error es por lo tanto
detectado por el intérprete de comandos. Si no hubiésemos indicado el tipo [int] delante de la variable
$numero, el texto hubiese sido aceptado y su tratamiento posterior hubiese muy probablemente generado
un problema.
En este ejemplo, el texto
Si ahora tratamos de asignar un valor entero a un tipo
char:
PS > [char]$var=65
¿Qué pasará? PowerShell va simplemente a convertir el valor entero en un carácter, y no cualquiera, sino el
carácter cuyo código ASCII corresponde al valor entero. En nuestro ejemplo $var contendrá A ya que el
carácter A corresponde a 65 en código ASCII.
Probemos la operación inversa, es decir pasar del tipo
directamente:
string al tipo int. Esto no es posible
PS > [int]$var = ’A’
Cannot convert value "A" to type "System.Int32".
Error: "Input string was not in a correct format."
At line:1 char:1 [int]$var = ’A’
Sin embargo, es posible convertir una variable de tipo
char en tipo int:
PS > [int][char]$var = ’A’
PS > $var
65
El hecho de poder convertir únicamente variables de tipo
char se debe a que solo es posible
establecer una correspondencia entre un carácter y un código ASCII y no toda una cadena.
Veamos ahora qué pasa si asignamos un valor decimal de tipo
double a una variable de tipo int:
PS> $var1=10.5
PS> $var1
10,5
PS> [int]$var2=$var1
PS> $var2
10
89
$var2 se redondea a la parte entera más cercana, ya que una variable de tipo
entero solo acepta valores enteros del rango comprendido entre ­2 147 483 648 y 2 147 483 647
En todo lógica, la variable
incluidos.
Pero si intentamos convertir un valor mucho más grande que el rango de los enteros, esto es lo que pasa:
PS > $var1=1e27
PS > [int]$var2=$var1
Cannot convert value "1E+27" to type "System.Int32".
Error: "Value was either too large or too small for an Int32."
PowerShell devuelve un error que nos indica que no ha podido convertir un valor tan grande en una variable
de tipo entero.
Evidentemente, la asignación de variables no se limita al sistema decimal, podemos también convertir
valores decimales en valores hexadecimales y almacenarlos en una variable.
1. Conversión de un número decimal en hexadecimal
Para realizar este tipo de operación, PowerShell se puede apoyar en los formatos de visualización de
cadenas de caracteres (operador ­f) del framework .NET. Como no hemos hablado aún ni de las cadenas
de caracteres, ni de los métodos del framework .NET, solo presentamos los comandos que permiten
realizar la conversión.
PS > $dec = 1234
PS > $hex = "{0:X}" ­f $dec
PS > $hex
4D2
Preste atención, el uso del operador de formateo de cadenas asigna a la variable
Para comprobarlo, teclee: $hex.GetType()
$hex el tipo string.
2. Conversión de un número decimal en octal (base 8)
Gracias a una llamada directa al framework .NET y en particular al método ToString() de la clase
System.Convert, podemos convertir cualquier número decimal en un número de la base deseada.
Para hacer esto, utilizaremos el comando siguiente: [System.Convert]::ToString(<valor>,
<base>) donde valor corresponde al número decimal (en base 10) a convertir y base la nueva base
del número.
Ejemplo
Conversión de un número decimal en base 8.
PS > $Nb = [System.Convert]::ToString(1234,8)
PS > $Nb
2322
90
El método ToString() utilizado aquí se denomina «estático». Observe el uso de los dobles
para realizar la llamada a este último.
::
3. Conversión de un número decimal en binario (base 2)
De la misma manera que en el ejemplo anterior, basta con modificar la base de conversión y el truco está
hecho. Una vez más, tenga cuidado ya que el tipo devuelto, contrariamente a las apariencias, no es un
tipo binario sino un tipo cadena.
Conversión de un número decimal en base 2.
PS > $Nb = [System.Convert]::ToString(1234,2)
PS > $Nb
10011010010
Hacer obligatoria la declaración e inicialización de variables
Una buena práctica sería obligar a declarar todas nuestras variables en un script, una función o un bloque
de script. En efecto, ¿a quién no le ha ocurrido buscar un bug en un programa durante horas por culpa de
un simple error tipográfico en el nombre de alguna variable?
Aunque este no es el comportamiento por defecto, es posible imponer este modo de funcionamiento gracias
al uso del comando Set­StrictMode.
A continuación vemos la sintaxis de este comando:
PS > Get­Command Set­StrictMode ­Syntax
Set­StrictMode ­Version <Version> [<CommonParameters>]
Set­StrictMode ­Off [<CommonParameters>]
El parámetro
­Version permite los siguientes valores:
1.0:
En este modo, cualquier referencia a una variable no inicializada provocará un error (salvo dentro
de una cadena de caracteres).
2.0:
Todo referencia a una variable no inicializada provocará un error (también en el interior de una
cadena).
Queda prohibida cualquier referencia a una propiedad de un objeto que no exista.
Quedan prohibidas las llamadas a funciones que usen la misma sintaxis (firma) que la llamada a
métodos.
Queda prohibido el uso de variables sin nombres (${}).
91
Latest:
Modo más estricto. Este valor permite asegurar que
conservadoras de la última versión de PowerShell.
se
aplican
las
restricciones
más
Le recomendamos encarecidamente utilizar el modo estricto cada vez que escriba un script.
Muchas veces nos convencemos de que nuestro script solo contendrá algunas líneas y que no
merece la pena. Pero en la práctica, las necesidades evolucionan sin parar. Un pequeño script en
apariencia modesto se volverá rápidamente grande, y comprenderá entonces por qué esta buena
práctica le hará ganar mucho tiempo en múltiples niveles. Recomendamos utilizar el valor 2.0.
Variables predefinidas
1. Variables automáticas
PowerShell dispone de dos tipos de variables predefinidas. Las variables automáticas que almacenan
información de procesamiento, y las variables de configuración que sirven para definir el comportamiento
de PowerShell.
Empecemos por las variables automáticas:
Variable
Descripción
$$
Variable (de uso poco frecuente) que contiene el último valor
del comando escrito en la consola.
$?
Variable que contiene
éxito o
true si la última operación ha tenido
false en el caso contrario.
$^
Variable (de uso poco frecuente) que contiene la primera
parte de la última línea recibida por el entorno (es decir la
primera palabra del último comando tecleado en la consola).
$_
Variable que contiene el objeto actual transmitido mediante
una pipe |.
$AllNodes
Variable utilizada por DSC. Contiene los datos de
configuración que se pasan a través del parámetro ­
ConfigurationData.
$Args
Variable que contiene el array de argumentos pasados a
una función o a un script.
$ConsoleFileName
Variable que contiene la ruta de acceso al archivo de
consola (.psc1) que ha sido utilizado en la última sesión.
$Error
Variable de tipo array que contiene todos los errores
encontrados desde el inicio de la sesión PowerShell actual
(consulte el capítulo Gestión de errores y depuración).
$Event
Variable que contiene el evento actual tratado en el bloque
de script de un comando de grabación de evento,
típicamente como Register­ObjectEvent.
92
Descripción
Variable
$EventArgs
Variable que contiene los argumentos en relación con
$Event. Como esta última, $EventArgs se utiliza
únicamente en el bloque de scripts de un comando de
grabación de evento.
$EventSubscriber
Variable que contiene el subscriptor utilizado por
Como
esta
última,
$EventSubscriber
$Event.
se
utiliza
únicamente en el bloque de scripts de un comando de
grabación de evento.
$ExecutionContext
Variable que contiene un objeto EngineIntrinsics que
representa el contexto de ejecución del símbolo del sistema
Windows PowerShell.
$False
Variable que contiene el valor false. Esta variable es una
constante, y por consiguiente no es posible modificarla.
$Foreach
Variable
que
hace
referencia
al iterador de
un bucle
Foreach.
$Home
Variable que contiene la ruta (path) de la carpeta home del
usuario.
$Host
Variable que contiene informaciones del host (la consola
PowerShell).
$Input
Contiene un enumerador que permite enumerar los datos
de entrada que se pasan a una función. $Input solo
puede utilizarse dentro de una función (bloque Process) o
en un bloque de script.
$LastExitCode
Variable que contiene el código de salida de la última
ejecución de un fichero ejecutable Win32.
$Matches
Variable con los operadores que trabajan en las
expresiones regulares ­Match y ­NotMatch. $Matches
es un array que contiene el o los resultados de la
comparación.
$MyInvocation
Contiene informaciones sobre el script, la función o el bloque
de script actual, tales como su nombre, sus parámetros, su
ruta en el disco, etc.
Esta variable es particularmente útil para determinar el
nombre y la ruta actual del script en ejecución.
$NestedPromptlevel
Indica la profundidad del prompt actual. El valor 0 indica la
profundidad del prompt inicial. El valor se incrementa cuando
accede a un nivel anidado y se decrementa cuando sale de
él.
$NULL
Variable vacía o nula.
$PID
Variable que contiene un número que identifica el proceso
PowerShell en curso.
93
Variable
$Profile
Descripción
Variable que contiene las rutas (path) de los diferentes
perfiles de Windows PowerShell.
$PSBoundParameters
Variable que contiene un diccionario con los parámetros que
se pasan a un bloque Param en un script o función.
$PsCmdlet
Variable que contiene un objeto que representa el comando
o la función avanzada que está en ejecución.
$PsCommandPath
Variable que contiene la ruta completa del archivo o script
en ejecución.
$PSCulture
Variable que contiene el nombre de la región utilizada en el
sistema operativo (es­ES para el idioma español).
$PSDebugContext
Variable que contiene información relativa al entorno de
depuración. Contiene valores únicamente en un contexto de
depuración, es decir, cuando la ejecución de un script se
detiene en un «punto de ruptura».
$PsHome
Variable que contiene la ruta (path) donde está instalado
PowerShell.
$PSItem
Variable (de uso poco frecuente) que contiene el objeto
actual. Idéntico a $_.
$PSScriptRoot
Variable que contiene la carpeta desde donde se ejecuta el
script.
$PSSenderInfo
Variable que contiene información relativa al usuario que ha
arrancado la sesión remota: el nombre del usuario, su zona
horaria y el nombre del equipo origen. Tenga en cuenta que
la cantidad de información es parametrizable en las
opciones de configuración de las sesiones remotas.
$PSUICulture
Variable que contiene el nombre de la región de la interfaz
de usuario (UI) empleada actualmente.
$PSVersionTable
Variable que contiene un array de solo lectura que muestra
los detalles relativos a la versión de Windows PowerShell y
su entorno.
$Pwd
Variable que indica la ruta completa de la carpeta activa.
$Sender
Variable que contiene el objeto que ha generado el entorno.
Se relaciona con $Event. Como esta última, $Sender solo
está accesible en el bloque de script de un comando de
grabación de evento.
$ShellID
Variable que indica el identificador del shell.
$StackTrace
Variable que contiene información relativa a la arborescencia
de las llamadas a procedimientos detallados relativos al
último error.
$This
En el interior de un bloque de script, cuando se define una
propiedad o método suplementarios (por ejemplo, con Add­
Member), $this hace referencia al objeto en curso.
94
Descripción
Variable
$True
Variable que contiene el valor
true.
Nuevas variables automáticas incorporadas por PowerShell 6:
Variable
Descripción
Valor por defecto
$ISCoreCLR
Valor booleano.
Vale verdadero si PowerShell
se basa en .NET Core.
True
$IsLinux
Valor
booleano.Vale
verdadero si el OS que
ejecuta PowerShell funciona
bajo Linux.
Depende del OS en
ejecución
$IsMacOS
Valor
booleano.Vale
verdadero si el OS que
ejecuta PowerShell funciona
bajo Mac OS.
Depende del OS en
ejecución
$IsWindows
Valor booleano.
Vale verdadero si el OS que
ejecuta PowerShell funciona
bajo Windows.
Depende del OS en
ejecución
El contenido de las variables automáticas puede variar en función del contexto de ejecución, lo cual no
ocurre con las variables de configuración que veremos a continuación.
2. Variables de configuración
He aquí las variables de configuración de Windows PowerShell hasta la versión 5.1. PowerShell 6 parte de
ellas e introduce nuevas variables que veremos más adelante.
Variable
$ConfirmPreference
Descripción
Variable
que
permite
determinar si un comando
debe pedir confirmación del
usuario antes de ejecutarse.
Cuando
el
valor
de
Valor por defecto
High
$ConfirmPreference
(High, Medium, Low, None
[Elevada,
Media,
Débil,
Ninguna]) es superior o igual
al riesgo de la acción del
comando, PowerShell pide
confirmación
del
usuario
antes de ejecutar dicha
acción.
95
Descripción
Valor por defecto
$DebugPreference
Variable que contiene un
valor
específico
correspondiente
a
una
acción
preferida
a
establecer. Se utiliza con el
comando Write­Debug.
SilentlyContinue
$ErrorActionPreference
Variable que contiene un
valor
específico
correspondiente a la acción
preferida a realizar en caso
de error. Se utiliza, entre
otros,
con
el
comando
Write­Error.
Continue
$ErrorView
Variable que determina el
formato de visualización de
los mensajes de error en
PowerShell.
NormalView
$FormatEnumerationLimit
Variable que determina el
número
de
elementos
enumerados
durante
la
visualización.
4
$LogCommandHealthEvent
Variable que determina si los
errores
y
excepciones
contenidos
en
la
inicialización del comando se
registran o no.
False (no hay log)
$LogCommandLifecycle
Event
Variable que determina si las
paradas y rearranques de
los comandos se registran o
no.
False (no hay log)
$LogEngineHealthEvent
Variable que determina si los
errores de inicio de sesión
PowerShell se registran o
no.
True (log)
$LogEngineLifecycleEvent
Variable que determina si las
entradas y salidas de sesión
PowerShell se registran o
no.
True (log)
$LogProviderHealthEvent
Variable que determina si los
errores ligados al uso de
providers se registran o no.
True (log)
$LogProviderLifecycle
Event
Variable que determina si los
errores
de
creación
y
eliminación de providers se
registran o no.
True (log)
Variable
96
Variable
Descripción
Valor por defecto
$MaximumAliasCount
Variable que contiene el
número máximo de alias
posibles en una sesión.
4096
$MaximumDriveCount
Variable que contiene el
número máximo de lectores
posibles en una sesión (los
que son entregados por el
sistema no se tienen en
cuenta).
4096
$MaximumErrorCount
Variable que contiene el
número máximo de errores
guardados en el histórico de
errores
para
la
sesión
($error).
256
$MaximumFunctionCount
Variable que contiene el
número
máximo
de
funciones posibles en la
sesión.
4096
$MaximumHistoryCount
Variable que contiene el
número
máximo
de
comandos
que
pueden
guardarse en el histórico.
4096
$MaximumVariableCount
Variable que contiene el
número
máximo
de
variables posibles en la
sesión.
4096
$OFS
Variable que contiene el
separador de campos al
convertir una
tabla
en
cadena de caracteres.
Carácter espacio « »
$OutputEncoding
Variable que contiene el
método de codificación de
caracteres
usado
por
PowerShell cuando envía
texto a otras aplicaciones.
ASCII
$ProgressPreference
Variable que determina la
manera
como
responde
PowerShell
a
las
actualizaciones de progreso
generadas por un script, un
comando o un proveedor.
Continue
$PSDefaultParameterValues
Variable que contiene, en
forma de tabla asociativa,
los valores por defecto de
los comandos y funciones
avanzadas.
No inicializado
97
Variable
$PSEdition
Descripción
Valor por defecto
Variable que indica la
edición de PowerShell. El
valor Desktop representa
Depende de la edición
de PowerShell
instalada
a Windows PowerShell
basado en el Framework
.NET. El valor Core
representa por su parte a
PowerShell 6 y versiones
superiores basadas en.NET
Core.
$PSEmailServer
Variable que determina el
servidor de correo. Este
valor se utiliza con el
comando Send­
MailMessage para enviar
correos electrónicos.
No inicializado
$PSModuleAutoLoading
Preference
Variable que determina el
modo de carga de los
módulos. El valor None
desactiva
la
carga
automática de módulos.
No inicializado
$PSSessionApplicationName
Variable que contiene el
nombre de la aplicación
usada para el uso de
comandos
remotos.
La
aplicación por defecto del
sistema es WSMAN.
All
$PSSessionConfiguration
Name
Variable que contiene la
URL de la configuración de
sesión usada por defecto.
http://schemas.
microsoft.com/
PowerShell/microsoft.
PowerShell
$PSSessionOption
Variable que contiene los
valores por defecto en una
sesión remota.
Conjunto de
predefinidos
$VerbosePreference
Variable que contiene un
valor que corresponde a
una acción preferida. Se
utiliza con el comando
Write­Verbose.
SilentlyContinue
$WarningPreference
Variable que contiene un
valor que corresponde a
una acción preferida. Se
utiliza con el comando
Write­Warning.
Continue
valores
98
Variable
$WhatIfPreference
Descripción
Variable que determina si el
parámetro
­WhatIf se
activa automáticamente para
cada comando que la usa.
Valor por defecto
False
Ámbito de las variables
El ámbito (scope en inglés) determina la visibilidad de las variables (o de las funciones). En esta sección solo
mencionaremos brevemente el tema ya que es muy amplio y puede rápidamente volverse complejo. Para
obtener explicaciones más detalladas, le recomendamos consultar el tema de la ayuda About_Scopes.
La noción de ámbito es no obstante importante, ya que garantiza una independencia de variables, o por lo
menos un modo de funcionamiento que es útil conocer para evitar probables interferencias y otros efectos
no deseados.
Imagine por un momento que ejecuta un script y que una vez el script finalizado desea verificar un valor
tecleando su nombre en la consola. ¡Uy! No es posible (por defecto, en la consola clásica), ya que la variable
no está disponible; dicho esto es simplemente debido al ámbito de la variable.
PowerShell utiliza la noción de ámbito padre y de ámbito hijo. Un ámbito hijo es un ámbito creado en el
interior de un ámbito padre. Es decir que cada vez que ejecutemos una función, un script o un bloque de
instrucciones, se crea un nuevo ámbito. Y salvo que especifique lo contrario, PowerShell define que se
puede leer una variable en su propio ámbito así como en los ámbitos hijos. Pero solo puede ser modificada
en el ámbito donde se creó. Además los ámbitos padres no pueden ni leer ni modificar las variables
definidas en sus ámbitos hijos.
El siguiente esquema ilustra la accesibilidad de una variable en un ámbito hijo.
99
Supongamos que una variable
como sigue:
$var se inicializa con 0 en la consola. Después creamos la función Leer
function Leer
{
$valor
}
Hasta aquí todo va bien, podemos leer la variable $var cada vez que llamamos a la función Leer. Nos
encontramos en el caso de una variable creada en un ámbito padre accesible en solo lectura en un ámbito
hijo.
Ahora, creamos la función
la función:
Añadir que permite incrementar en 1 la variable $var cada vez que llamemos
function Añadir
{
$valor++
}
Si no se ha perdido hasta aquí, sabe que una vez finalizada la función nuestra variable no será
incrementada, y la variable $var valdrá todavía 0. En efecto, por defecto no disponemos de autorización
para modificar una variable definida en un ámbito padre desde un ámbito hijo.
PS > $var = 0
PS > Añadir
PS > $valor
0
Para resolver esto, hay que adaptar el ámbito de las variables según varios tipos:
El ámbito global.
El ámbito local.
El ámbito script.
El ámbito privado.
El ámbito using.
El ámbito workflow.
1. Ámbito global (global:)
El ámbito global es aquel que se aplica en el arranque de PowerShell, es decir que si en el arranque
inicializamos esta variable, su ámbito será global.
Las variables de ámbito global son accesibles en solo lectura en un ámbito hijo sin especificar nada en
particular; es el funcionamiento normal de PowerShell.
Sin embargo, para poder modificar una variable de ámbito global desde un ámbito hijo, tenemos que
especificar la palabra clave global: delante del nombre de la variable.
100
Por ejemplo
function Añadir
{
$global:valor++
}
Así, al volver al ámbito padre, la variable
$valor habrá sido modificada.
PS > $valor = 0
PS > Añadir
PS > $valor
1
2. Ámbito local (local:)
El ámbito local es aquel en el que estamos en un instante t (ámbito actual).
Se crea un nuevo ámbito local cada vez que ejecutamos una función, un script o un bloque de
instrucciones.
El ámbito local responde a las reglas básicas, a saber que una variable creada en un ámbito padre no
puede ser modificada en un ámbito hijo.
3. Ámbito script (script:)
El ámbito script es, como su nombre indica, un ámbito creado para el tiempo en que dura la ejecución del
script y deja de existir una vez termina dicha ejecución. Al igual de en la consola, un script puede verse
obligado a crear varios ámbitos hijos, que a su vez puede ser susceptibles de crear variables. El ámbito
script permite entonces acceder a estas variables, pero en el exterior de la función. Por ejemplo, veamos
qué ocurre en el siguiente script:
#script1.ps1
#Script para ilustrar el ámbito "script"
Function Nuevo_valor
{
$valor = 10
}
$valor = 0
"El valor inicial es
Nuevo_valor
"El nuevo valor es :
#inicialización de la variable
: $valor"
#Llamada de la función
$valor"
En este script, inicializamos una variable a 0, después llamamos a una función que asigna el valor
nuestra variable y para finalizar mostramos la variables.
10 a
Si no se especifica ningún ámbito, al ejecutar el script nos damos cuenta de que no se ha modificado la
variable:
101
PS > ./Script1.ps1
Valor inicial : 0
Nuevo valor : 0
Este funcionamiento es normal en la medida en que, por defecto, la variable
ámbito actual, o sea el de la función.
$valor no existe dentro del
Si ahora integramos la palabra clave script: delante del nombre de la variable en la función, la variable
$valor se identifica, entonces, como la declarada en el ámbito del script:
#script1.ps1
#Script para ilustrar el ámbito "script"
Function Nuevo_valor
{
$ script:valor = 10
}
$valor = 0
"El valor inicial es
Nuevo_valor
"El nuevo valor es :
#inicialización de la variable
: $valor"
#Llamada de la función
$valor"
El resultado no es por lo tanto el mismo. Esta vez se modifica la variable creada en el ámbito del script.
PS > ./Script1.ps1
Valor inicial : 0
Nuevo valor : 10
4. Ámbito privado (private:)
El ámbito privado se utiliza, en realidad, muy poco; permite hacer inaccesible el contenido de una variable
a los ámbitos padres e hijos.
5. Ámbito using (using:)
Este ámbito existe desde PowerShell 3.0. Es realmente eficaz cuando intentamos pasar una variable
declarada de manera local a un script que tiene que ejecutarse en un equipo remoto mediante los
mecanismos de comunicaciones remotos de PowerShell.
En las primeras versiones de PowerShell (v2 y v1), teníamos que lidiar con el comando Invoke­Command
y su parámetro ­ArgumentList para pasar valores a un script remoto. Quienes lo hayan probado
sabrán de qué hablamos… Esta situación ha cambiado, ¡y sólo podemos alegrarnos de ello!
6. Ámbito workflow (workflow:)
Se trata del ámbito propio de los workflows introducidos en PowerShell desde la versión 3.0. Se trata de
un ámbito propio del uso de workflows donde, por defecto, cada comando se ejecuta sin compartir el
«estado de los datos». Este ámbito permite entonces hacer referencia a variables que se encuentran
dentro del mismo workflow.
102
Cuantificadores de bytes
Una de las características de PowerShell es su capacidad de utilizar cuantificadores de bytes. Estos últimos
permiten manipular múltiplos de bytes sin tener que efectuar ninguna operación particular. Los
cuantificadores definidos son los siguientes:
Cuantificador
Valor
Descripción
Ejemplo de uso
KB
1024
Kilobytes
2 KB ­­> 2048
MB
1024²
Megabytes
1 MB ­­> 1048576, 2 MB ­­> 2097152
GB
1024 3
Gigabytes
1
GB
­­> 1073741824,
10737418240
TB
1024 4
Terabytes
1 TB ­­> 1099511627776
PB
1024 5
Petabytes
1 PB ­­> 1125899906842624
10
GB
­­>
Históricamente, los desarrolladores de scripts siempre hemos resuelto estas dificultades realizando
operaciones matemáticas: dividíamos nuestro número de bytes por 1024, lo que nos daba kilobytes.
Dividíamos después por 1024 para obtener megabytes, etc. Pero hay que reconocer que es mucho más
práctico cuando el intérprete de comandos gestiona esto nativamente.
El uso de cuantificadores está claramente ligado a los cálculos de tamaños, y eso ocurre a menudo cuando
trabajamos con la capacidad de un disco o de la memoria, del tamaño de los archivos o de las carpetas, etc.
Tomemos por ejemplo la consulta CIM/WMI siguiente que devuelve la capacidad de memoria de un equipo:
PS > $System = Get­CimInstance ­ClassName Win32_ComputerSystem
PS > $System.TotalPhysicalMemory
4217352192
Como puede imaginarse, el tamaño se expresa en bytes. Para convertirlo, sencillamente, basta con dividir el
valor por una vez el cuantificador de bytes elegido. Por ejemplo para obtener el resultado en megabytes:
PS > $System.TotalPhysicalMemory /1MB
4021,98046875
O en gigabytes:
PS > $System.TotalPhysicalMemory /1GB
3,92771530151367
Para terminar, sepa que se pueden utilizar varios cuantificadores de bytes en el mismo cálculo:
PS > 3547KB + 750MB + 256GB
275667971072
103
Operadores
104
Introducción
Existen numerosos tipos de operadores. Pero ya sean aritméticos, binarios, lógicos u otros, todos permiten
actuar sobre las variables. Tenga presente que conocer y dominar los diferentes operadores resulta
esencial en la elaboración de un buen script.
1. Operadores aritméticos
En lo que respecta a los operadores aritméticos, PowerShell trata las expresiones de izquierda a derecha
respetando las reglas matemáticas así como los paréntesis.
Ejemplo
PS > 2+4*3
14
PS > (2+4)*3
18
La lista de operadores aritméticos disponibles es la siguiente:
Símbolo
Significado
+
Suma
­
Resta
*
Multiplicación
/
División
%
Módulo
Los cuatro primeros operadores deben lógicamente resultarle familiares. En cuanto al último, el operador
módulo devuelve el resto de la división entera de a entre b.
Por ejemplo, tecleando 5%3 en la consola, obtendremos
el resto de la división vale 2.
2. Simplemente porque en 5 tenemos 1 veces 3 y
Constatará que las operaciones aritméticas se aplican también a las variables. Así, $var1 + $var2 le
devolverá la suma de las dos variables si contienen valores numéricos. En el caso de un tipo cadena, el
operador + tendrá como resultado concatenar el contenido de ambas variables.
Ejemplo del operador
+ sobre dos enteros
PS > $int1 = 10
PS > $int2 = 13
PS > $int2 + $int1
23
El operador suma se emplea también con cadenas (variables de tipo string). En estos casos, el operador
concatena las dos cadenas:
105
Introducción
Existen numerosos tipos de operadores. Pero ya sean aritméticos, binarios, lógicos u otros, todos permiten
actuar sobre las variables. Tenga presente que conocer y dominar los diferentes operadores resulta
esencial en la elaboración de un buen script.
1. Operadores aritméticos
En lo que respecta a los operadores aritméticos, PowerShell trata las expresiones de izquierda a derecha
respetando las reglas matemáticas así como los paréntesis.
Ejemplo
PS > 2+4*3
14
PS > (2+4)*3
18
La lista de operadores aritméticos disponibles es la siguiente:
Símbolo
Significado
+
Suma
­
Resta
*
Multiplicación
/
División
%
Módulo
Los cuatro primeros operadores deben lógicamente resultarle familiares. En cuanto al último, el operador
módulo devuelve el resto de la división entera de a entre b.
Por ejemplo, tecleando 5%3 en la consola, obtendremos
el resto de la división vale 2.
2. Simplemente porque en 5 tenemos 1 veces 3 y
Constatará que las operaciones aritméticas se aplican también a las variables. Así, $var1 + $var2 le
devolverá la suma de las dos variables si contienen valores numéricos. En el caso de un tipo cadena, el
operador + tendrá como resultado concatenar el contenido de ambas variables.
Ejemplo del operador
+ sobre dos enteros
PS > $int1 = 10
PS > $int2 = 13
PS > $int2 + $int1
23
El operador suma se emplea también con cadenas (variables de tipo string). En estos casos, el operador
concatena las dos cadenas:
106
PS > $cadena1 = ’A’
PS > $cadena2 = ’B’
PS > $cadena1 + $cadena2
AB
También con las cadenas, sepa que puede repetir el contenido de una cadena gracias al operador
multiplicación:
PS > $cadena1 = 10 * ’A’
PS > $cadena1
AAAAAAAAAA
PS > $cadena1 = ’ABC’ * 10
PS > $cadena1
ABCABCABCABCABCABCABCABCABCABC
Puede encontrar más operadores matemáticos como el seno, el coseno, la raíz cuadrada, etc.
utilizando la clase System.Math disponible en el framework .NET (consulte el capítulo
Framework .NET y .NET Core).
Operadores de comparación
Con un nombre tan evocador, resulta inútil precisar que los operadores de comparación permiten comparar
los datos entre ellos. A diferencia de otros lenguajes, PowerShell utiliza los mismos operadores ya sea para
comparar números, cadenas de caracteres, así como para efectuar búsquedas de elementos en un array.
Vea el conjunto de operadores de comparación:
Operador
Significado
­eq
Igual
­ne
No igual (distinto)
­gt
Estrictamente
superior
­ge
Superior o igual
­lt
Estrictamente inferior
­le
Inferior o igual
Importante: debe conocer una pequeña sutilidad del lenguaje que hace que los operadores no tengan el
mismo comportamiento cuando se aplican a un valor escalar o a un array. En efecto, aplicados a un escalar,
los operadores devuelven un valor booleano, lo que nos es cierto en el caso de un array.
1. Comparación sobre escalares
Un valor escalar es un valor atómico, como puede ser un número, un carácter o una cadena de caracteres.
Generalmente se oponen los arrays a los valores escalares. Habitualmente (pero no siempre) los arrays
contienen valores escalares.
107
Ejemplos
Operaciones sobre números
Operaciones sobre cadenas
PS > ’A’ ­eq ’a’
True
PS > 1 ­lt 2
True
PS > ’A’ ­ceq ’a’
False
PS > 2 ­lt 1
False
PS > ’A’ ­ceq ’A’
True
PS > 2 ­gt 1
True
PS > ’PowerShell’ ­eq ’powershell’
True
PS > 2 ­eq 2
True
Aplicados a las cadenas de caracteres, los operadores de comparación no son sensible a
mayúsculas, es decir que no distinguen entre minúsculas y mayúsculas. Para remediar esto,
simplemente preceda el nombre del operador con la letra c, como por ejemplo ­ceq.
2. Comparación sobre arrays
Aplicados sobre arrays, los operadores no devuelven un booleano como con los escalares, sino que
devuelven el o los valores encontrados. Usamos en general los operadores de igualdad con el fin de
verificar si un valor se encuentra o no dentro de un array.
Ejemplos
PS > "Angel","Roberto","Eleonor" ­eq "Eleonor"
Eleonor
PS > "Angel","Roberto","Eleonor" ­ne "Eleonor"
Angel
Roberto
PS > "Angel","Roberto","Eleonor" ­cne "eleonor"
Angel
Roberto
Eleonor
PS > 1,2,3,4,5 ­eq 4
4
PS > 1,2,3,4,5 ­ne 4
1
2
3
5
Operadores de comparación genéricos
Una expresión genérica es una expresión que contiene un carácter llamado «genérico». Por ejemplo, el
asterisco (*) reemplaza cualquier secuencia de caracteres y el signo de interrogación (?) solo substituye un
único carácter.
108
Como los operadores de comparación anteriores, los operadores genéricos no devuelven el mismo tipo de
dato en función de si se aplican a un valor escalar o un array.
Existen dos operadores de comparación que permiten comparar una cadena con una expresión genérica.
Operador
Significado
­like
Comparación de igualdad de expresiones genéricas.
­notlike
Comparación de desigualdad de expresiones genéricas.
Para entender mejor el uso de estos operadores, le presentamos algunos ejemplos de aplicación:
# Operaciones sobre escalares
PS > ’Powershell’ ­like ’*shell’
True
PS > ’powershell’ ­like
True
’power*’
PS > ’Powershell’ ­clike ’p*shell’
False
PS > ’powershell’ ­like ’*war*’
False
PS > ’powershell’ ­like ’*wer*’
True
PS > ’powershell’ ­like ’po?er*’
True
PS > ’power’ ­like ’po?er*’
True
PS > ’potter’ ­like ’po?er*’
False
PS > ’powershell’ ­like ’power?????’
True
# Operaciones sobre arrays
PS > "Angel","Roberto","Eleonor" ­like "*eonor"
Eleonor
PS > "Angel","Roberto","Eleonor","arturo" ­clike "A*"
Angel
PS > "Angel","Roberto","Eleonor","arturo" ­like "a*"
Angel
arturo
PS > "Angel","Roberto","Eleonor","arturo" ­notlike "a*"
Roberto
Eleonor
109
PS > "Angel","Roberto","Eleonor","arturo" ­cnotlike "*a*"
Roberto
Eleonor
PS > "Angel","Roberto","Eleonor","arturo" ­cnotlike "a*"
Angel
Roberto
Eleonor
Los operadores de comparación genéricos pueden (como los operadores de comparación) no respetar
las mayúsculas. Si desea que el operador respete las mayúsculas, preceda el nombre del operador con
la letra c, lo que nos da ­clike y ­cnotlike.
Operador de comparación de expresiones regulares
Las expresiones regulares (regex) son bien conocidas por los administradores de sistemas Unix/Linux, pero
generalmente mucho menos conocidas por los administradores Windows. La razón viene del hecho de que
los primeros están acostumbrados a manipular texto con herramientas por línea de comandos como grep,
awk, tail, etc.
Las expresiones regulares representan un metalenguaje extremadamente potente dedicado a la
manipulación de cadenas de caracteres. Las utilizamos para comparar cadenas y/o capturar cadenas de
caracteres que respondan a un modelo específico dentro de archivos de texto.
Las expresiones regulares surgieron, en los años 1950, de la mente de un matemático reconocido, y a
continuación se implementaron en los años 70 en las primeras versiones de Unix. Es probable que sea la
razón por la cual su sintaxis puede parece bastante rocambolesca a primera vista. ¡Pero no se deje
impresionar!
Le
invitamos
a
consultar
la
ayuda
en
línea
y
en
particular
la
sección
de
ayuda
about_Regular_Expression que constituye una introducción a la materia interesante.
PowerShell dispone de los dos operadores de comparación de expresiones regulares siguientes:
Operador
Significado
­match
Comparación de
expresión regular.
­notmatch
Comparación de desigualdad entre un pattern (patrón) y una expresión
regular.
igualdad
entre
un
pattern
(patrón)
y
una
El principio de las expresiones regulares es hacer corresponder un modelo (llamado «pattern» en inglés)
con una cadena de caracteres. Permiten analizar grandes cantidades de datos, de manera muy eficiente y
con un mínimo de esfuerzo (cuando las dominamos).
El principal inconveniente es que el modelo puede volverse rápidamente muy complejo, y será aún más
complicado de descifrar y mantener varios meses después de escribirlos; ¡sobre todo cuando no se
practican todos los días!
Las regex se utilizan muchas veces para validar datos introducidos, como por ejemplo una dirección IP o una
dirección de correo electrónico. En efecto en estos dos casos es fácil establecer un modelo. Si tomamos el
caso de una dirección IPv4, sabemos que está compuesta de cuatro grupos de cifras separados por el
carácter «.». Un modelo de regex puede fácilmente corresponder a este modelo…
Pero el uso más extendido es seguramente el análisis de archivos de logs. En efecto, se trata del dominio
predilecto de las regex. En este contexto, es fácil determinar si una información existe e igual de fácil resulta
capturarla.
110
No entraremos en más detalles ya que preferimos ilustrar con ejemplos la potencia de las regex. Una vez
más, el resultado de la comparación con una regex difiere según la realicemos sobre un valor escalar o
sobre un array.
1. Operaciones sobre escalares
Cuando comparamos una expresión regular con un escalar, el valor devuelto es de tipo booleano y la
variable automática $Matches contiene el resultado de la coincidencia (si hay coincidencia).
Ejemplos
# Operaciones sobre escalares
# Empieza por la letra p ?
PS > ’PowerShell’ ­match ’^p’
True
PS > $matches
Name
­­­­
0
Value
­­­­­
P
# Termina por la letra l ?
PS > ’Powershell’ ­match ’l$’
True
PS > $Matches
Name
­­­­
0
Value
­­­­­
l
# Acepta reemplazar la s por uno de los caracteres s, o, l
PS > ’Powershell’ ­match ’power[sol]hell’
True
PS > $matches
Name
­­­­
0
Value
­­­­­
Powershell
Ahora, veamos un ejemplo un poco más complejo. Creamos un modelo de regex que corresponde a la
cadena Date: 17/01/2018. Después capturaremos solamente la fecha y nos aseguraremos de que la
fecha capturada respecta un formato determinado (día y mes codificados con dos caracteres, una barra
como separador, año con 4 caracteres). Así una fecha que no respete este formato no se tomará en
consideración.
PS > ’Date: 17/01/2018’ ­match ’^Date:\s\d{2}/\d{2}/\d{4}$’
True
PS > $matches
Name
­­­­
0
El
Value
­­­­­
Date: 17/01/2018
^ indica que la cadena debe comenzar por el carácter que le sigue, en nuestro caso la letra d o D.
111
\s representa un carácter en blanco como un espacio, una tabulación o un retorno de carro.
\d representa un número comprendido entre 0 y 9.
{2} representa un cuantificador que indica que deseamos 2 ocurrencias del carácter que le precede.
\/ representa el carácter slash (/). El antislash (\) es el carácter de escape de las regex.
$ indica que la cadena debe terminar por el carácter que le precede, en nuestro caso un número.
Si no hubiésemos indicado los caracteres
^ y $, nuestra expresión regular se habría aplicado en toda la
cadena. Estos caracteres se aconsejan cuando trabajamos con las regex para evitar toda captura
inesperada. Para ilustrar estas líneas, observemos los ejemplos que siguen:
PS > ’### date: 17/01/2018 ###’ ­match ’Date:\s\d{2}/\d{2}/\d{4}’
True
PS > ’### date: 17/01/2018 ###’ ­match ’^Date:\s\d{2}/\d{2}/\d{4}$’
False
Ahora nos interesaremos en la captura observando el contenido de
únicamente cuando la comparación devuelve el valor
$Matches (que ha tenido lugar
True):
PS > $matches
Name
­­­­
0
Value
­­­­­
date: 17/01/2018
Dese cuenta que $Matches[0] contiene solamente el valor que corresponde con la expresión regular (y
no toda la cadena que contiene el valor). $Matches es en realidad una tabla asociativa. Contiene todas
las capturas realizadas por la expresión regular. Cabe
subexpresiones gracias a los paréntesis.
destacar que
podemos capturar varias
Ejemplos de capturas
# Captura simple
PS > ’Date: 17/01/2018’ ­match ’^Date:\s(\d{2}/\d{2}/\d{4})$’
True
PS > $matches
Name
­­­­
1
0
Value
­­­­­
17/01/2018
Date: 17/01/2018
# Captura múltiple
PS > ’Date: 17/01/2018’ ­match ’^Date:\s((\d{2})/(\d{2})/(\d{4}))$’
True
PS > $matches
Name
­­­­
Value
­­­­­
112
4
3
2
1
0
2018
01
17
17/01/2018
Date: 17/01/2018
Potente ¿verdad? Así, si solo nos interesa el año de la fecha, basta con recuperar el valor contenido en
$Matches[4].
Podemos profundizar un poco más dándole nombre a cada captura. Esta vez la regex puede volverse un
poco menos legible y parecer muy especial a ojos de un debutante:
PS > ’Date: 17/01/2018’ ­match
’^Date:\s(?<fecha>(?<dia>\d{2})/(?<mes>\d{2})/(?<anio>\d{4}))$’
True
PS > $matches
Name
­­­­
fecha
mes
día
anio
0
Value
­­­­­
17/01/2018
01
17
2018
Date: 17/01/2018
Cada grupo posee ahora un nombre. Así podemos acceder al valor de cada captura de la siguiente
manera: $Matches.<nombre captura>, como por ejemplo $Matches.anio.
2. Operaciones sobre arrays
Cuando aplicamos una expresión regular a un array, esta vez no hay booleano como valor devuelto ni
valores contenidos en la variable $Matches. Solo devuelve las cadenas que correspondan a la expresión
regular. Si ninguna cadena corresponde a la regex entonces no se devolverá nada.
Ejemplo
PS > ’boolean’,’bol’,’boca’,’baobab’,’bola’­match ’^b[ou]{2,}\w+$’
boolean
PS > ’boolean’,’bol’,’boca’,’baobab’,’bola’ ­match ’^c[ou]{2,}\w+$’
En el siguiente ejemplo, la regex corresponde a una cadena que:
Comienza por la letra b o B.
Seguida de 2 veces o más uno de los caracteres o o bien u.
Seguida de cualquier carácter, una vez o más.
Acaba por un carácter (que no sea un número o un espacio).
Los operadores de comparación de expresiones regulares pueden, como los operadores de
comparación, ser sensibles a las mayúsculas. Para conseguirlo, preceda el nombre del operador con
la letra c.
113
Operador de rango
El operador de rango se indica con .. (pronunciado «punto punto»). Permite, como su nombre indica, cubrir
un rango de valores. Si queremos cubrir el rango de valores de 1 a 10 (para realizar un bucle por ejemplo),
entonces basta con teclear la siguiente línea:
PS > 1..10
1
2
3
..
10
Podemos, de la misma manera, definir un rango dinámico utilizando variables. Nada le impide definir un
rango que vaya de $var1 hasta $var2 si estos valores son enteros.
Ejemplo
PS > $var1 = 5
PS > $var2 = 10
PS > $var1 .. $var2
5
6
7
8
9
10
Operadores de pertenencia
En PowerShell hablamos muchas veces de colecciones de objetos o de array de objetos. El operador de
pertenencia, introducido desde PowerShell 3.0, permite saber si un objeto se encuentra o no dentro de una
colección.
Operador
Significado
­in
Comprueba si un valor está dentro de una colección.
­notIn
Comprueba si un valor no está dentro de una colección.
Para comprender mejor el uso de estos operadores, le presentamos algunos ejemplos de aplicación:
PS > ’shell’ ­in ’Console’, ’shell’
True
PS > ’shell’ ­in ’Console’, ’powershell’
False
PS > 85 ­in 1..99
True
El operador primo hermano de ­in es el operador ­contains. En efecto, funciona como
manera inversa. Puede elegir el que corresponda mejor a la lógica de su script.
­in pero de
114
Significado
Operador
­contains
Comprueba si una colección contiene un valor en particular.
­notContains
Comprueba si una colección no contiene un determinado valor.
Para entender mejor el uso de estos operadores, a continuación verá algunos ejemplos de aplicación:
PS > ’Console’, ’shell’ ­contains ’shell’
True
PS > ’Console’, ’powershell’ ­contains ’shell’
False
PS > 1..99 ­contains 85
True
Operador de sustitución
El operador de sustitución ­replace es un operador muy potente. Probablemente hubiese merecido ser
llamado ­searchAndReplace por su eficacia, pero probablemente se consideraría su nombre demasiado
largo.
En su forma más simple
­replace puede aplicarse sobre una cadena simple (escalar), como en el ejemplo
siguiente:
PS > ’PowerShell’ ­replace ’Shell’, ’Jose’
PowerJose
Shell representa la cadena a buscar y Jose la cadena de sustitución.
Aunque esto funcione perfectamente, este caso de uso no ofrece mucha más funcionalidad que el método
Replace() aplicado a una cadena como sigue:
PS > $MiCadena = ’PowerShell’
PS > $MiCadena.Replace(’Shell’,’Jose’)
PowerJose
El mayor interés de este operador es que se puede aplicar a un array para buscar y reemplazar un valor por
otro. Mejor aún, ­replace acepta como entrada una expresión regular, lo que permite búsquedas
complejas y aún hay mucho más…
1. Sustitución con la ayuda de una expresión regular
Aunque usemos una cadena de caracteres simple para efectuar la búsqueda, el operador
considera como una expresión regular.
­replace la
En el ejemplo que sigue, creamos una expresión regular que corresponde a una fecha cualquiera con el
formato DD/MM/AAAA que reemplazaremos con la cadena Sábado:
115
PS > ’Llovió el 12/01/2018...’ ­replace ’\d{2}\/\d{2}\/\d{4}’,
’Sábado’
Llovió el Sábado...
Cuando utilizamos una regex con el operador
­replace, la primera cadena capturada se almacena en la
variable $1, la segunda en $2, la tercera en $3, etc. Así podemos reutilizar estas variables para crear un
resultado completamente diferente de la cadena de origen, como vemos debajo:
PS
PS
PS
**
> $str = "PowerShell está anticuado, ¡ VBScript es mejor !"
> $regex = "(PowerShell)( está anticuado, ¡ )(VBScript)( es mejor !)"
> $str ­replace $regex, ’** $3$2$1$4 **’
VBScript está anticuado, ¡ PowerShell es mejor ! **
Tenga en cuenta que los paréntesis se utilizan en las regex para indicar una captura.
2. Sustitución aplicada sobre un array
Es ahora cuando las cosas se ponen serias ya que
­replace es realmente eficaz cuando se aplica a una
tabla. Nos evitará tener que recorrer cada uno de los elementos para realizar una sustitución. En ciertos
casos, nos evitará también tener que escribir una algoritmo para reemplazar ciertas cadenas bien
definidas.
Definamos el siguiente array con los días de la semana:
PS > $tbDias = "Lunes Martes", "Miércoles Jueves", "Sábado Domingo"
Reemplacemos ahora la cadena
do por di de toda la tabla:
PS > $tbDias ­replace ’do’, ’di’
Sábadi dimingo
¡Perfecto! Bueno casi ya que la sustitución también se ha aplicado a Domingo que ha sido sustituido por
dimingo. Ahora, decidimos aplicar la sustitución solamente a la primera palabra de cada elemento del
array.
Para ello no necesitamos ningún algoritmo. Una pequeña regex nos permitirá realizar esta tarea, como
podemos ver a continuación:
PS > $tbDias ­replace ’^(\w*)do’, ’$1di’
Sábadi Domingo
¿Qué ha pasado? Pues simplemente que la regex ha capturado cada principio de cadena (gracias a ^) y
esto hasta do (o sea Sába) y lo almacena en $1. Después con $1di efectuamos una pequeña
concatenación. La sustitución se ha limitado a la expresión regular sin tocar el resto de la cadena.
Para escribir la regex, no olvide usar el operador
­match que le devolverá verdad ro o falso en función
del resultado. No olvide tampoco aplicarla a una cadena simple. Como por ejemplo:
116
PS > $tbDias[2] ­match ’^(\w*)do’
True
PS > $matches
Name
­­­­
1
0
Value
­­­­­
Sába
Sábado
El operador de sustitución puede (como los operadores de comparación) ser o no sensibles a las
mayúsculas. Para respetar el case sensitive, preceda su nombre con la letra c. De este modo, ­
replace se escribe ­creplace.
Operadores de tipo
Hasta ahora hemos hablado de cómo tipar su valor y también de cómo recuperar el tipo con el método
GetType. A continuación, vamos a descubrir cómo comprobar el tipo de una variable.
Estos operadores se utilizan relativamente poco en la medida en que, cuando definimos parámetros (en los
scripts o las funciones), les asignamos un tipo. De modo que no es realmente útil comprobar su tipo... Dicho
esto, como estos operadores existen, nos parece útil hablar de ellos.
Significado
Operador
­is
Comprueba si el objeto es del mismo tipo.
­isNot
Comprueba si el objeto no es del mismo
tipo.
Para ilustrar mejor el uso de estos operadores, presentamos algunos ejemplos de uso:
PS > ’Hola’ ­is [string]
True
PS > 20 ­is [int]
True
PS > ’B’ ­is [int]
False
Operadores lógicos
Los operadores lógicos permiten verificar varias comparaciones en una misma expresión. Por ejemplo:
($var1 ­eq $var2) ­and ($var3 ­eq $var4) devolverá el booleano true si $var1 es igual a
$var2 y $var3 es igual a $var4, en caso contrario el valor devuelto será false. Aquí tiene la lista de los
operadores lógicos disponibles:
117
Significado
Operador
­and
Y lógico
­or
O lógico
­not
NO lógico
!
NO lógico
­xor
O exclusivo
Para entender mejor el uso de estos operadores, veamos algunos ejemplos de aplicación:
PS > (5 ­eq 5) ­and (8 ­eq 9)
False
Falso ya que
5 es igual a 5, pero 8 no es igual a 9.
PS > (5 ­eq 5) ­or (8 ­eq 9)
True
Verdadero ya que una de las dos expresiones es verdadera,
5 es por supuesto igual a 5.
PS > ­not (8 ­eq 9)
True
PS > !(8 ­eq 9)
True
Verdadero ya que
8 no es igual a 9.
Operadores binarios
Los operadores binarios se utilizan para realizar operaciones entre números binarios. Como recordatorio, el
sistema binario es un sistema en base 2, a diferencia del sistema decimal que está en base 10. Es decir que
la notación se compone únicamente de 0 y 1.
Ejemplo de conversión de números decimales en base binaria
Decimal
0
1
2
3
4
5
Binario
0000
0001
0010
0011
0100
0101
Cuando llamamos a uno de los operadores binarios siguientes, los bits de los valores se comparan uno
detrás de otro, y si utilizamos un Y o un O obtenemos un resultado diferente.
118
Significado
Operador
­band
Operador Y
­bor
Operador O
­bnot
Operador NO
­bxor
Operador O exclusivo
­shr
Rellenado de bits por la derecha
­shl
Rellenado
izquierda
de
bits
por
la
El resultado devuelto después de una comparación binaria se convierte automáticamente en sistema
decimal y no en sistema binario.
Imaginemos que para una aplicación cualquiera deseamos saber si el bit con menor peso de una variable es
igual a 1. Tomemos como ejemplo el valor decimal 13, o sea 1101 en binario. Vemos claramente que el bit
con menor peso es igual a 1, pero para verificar esta afirmación con PowerShell utilizamos mejor nuestro
operador binario ­band.
Usando este operador realizamos lo que llamamos una máscara sobre el bit de menor peso. Si el resultado
es conforme a la máscara aplicada entonces el bit de menor peso tiene el valor 1. Veamos gráficamente
cómo sería la comparación:
Resultado
PS > $var = 13
PS > $var ­band 1
1
Operadores de asignación
Ahora sabe cómo asignar un valor a una variable y realizar una operación sobre esta última. A continuación
le mostraremos cómo hacer para realizar las dos operaciones al mismo tiempo en una operación.
Las operaciones descritas en la tabla siguiente devuelven estrictamente el mismo resultado.
119
Notación clásica
Notación acortada
$i=$i+8
$i+=8
$i=$i­8
$i­=8
$i=$i*8
$i*=8
$i=$i/8
$i/=8
$i=$i%8
$i%=8
$i=$i+1
$i++
$i=$i­1
$i­­
La notación $i++ o $i­­ es muy utilizada en las condiciones de los bucles de manera que se
incrementa o decrementa $i de 1 en cada paso.
Así, por ejemplo, vemos cómo añadir un valor con el operador de asignación
+=.
PS > $i = 0
PS > $i += 15
PS > $i
15
Si mostramos el valor de la variable
$i, obtenemos 15 ya que esta instrucción es equivalente a: $i = $i
+ 15.
Sigamos esta vez con el cálculo de factoriales de los números que van de
Para ello, creamos un bucle y, para cada valor de
1 a 10.
$i, multiplicaremos por el valor de $var antes de realizar
la asignación:
PS > $var = 1
PS > foreach($i in 1..10){$var *= $i ; $var}
1
2
6
24
120
720
5040
40320
362880
3628800
Como todavía no hemos abordado la noción de bucle foreach, no preste demasiada atención a este
ejemplo. Lo esencial es que haya comprendido que se trata de un bucle que va de 1 a 10, en el cual
para cada valor de i multiplicamos el valor de $i por el valor de $var y guardamos el resultado en
$var.
120
Operadores de redirección
Debe saber que los intérpretes de comandos tratan la información según una entrada, una salida y un error
estándar, estando cada elemento identificado por un descriptor de archivo.
Descriptor
Identificación
Explicación
0
Standard Input
Flujo de entrada por el cual las datos se introducen en
la shell. Por defecto, la entrada estándar es el teclado.
1
Standard Output
Flujo de salida por defecto por el cual se emiten los
datos. El flujo estándar es usado por la mayor parte de
los comandos que muestran un resultado en pantalla.
2
Error
Flujo de salida que permite al shell emitir los mensajes
de error.
Su visibilidad se gestiona mediante
variable $ErrorActionPreference.
3
Warning
la
Flujo de salida que permite al shell emitir los mensajes
de warning.
Son
visibles
si
la
variable
$WarningPreference vale Continue.
4
Verbose
Flujo de salida de mensajes detallados. Estos mensajes
son visibles cuando el mode verbose se habilita
mediante la variable $VerbosePreference.
5
Debug
Flujo de salida de mensajes de depuración. Estos
mensajes son visibles cuando el mode debug se
habilita mediante la variable $DebugPreference.
6
Information
Flujo de salida de los mensajes de información. Estos
serán visibles si la variable
$InformationPreference vale Continue.
*
Todos los flujos
El carácter asterisco representa todos los flujos.
Los flujos *,
PowerShell.
El flujo
3, 4 y 5 y sus variables de preferencia asociadas se han introducido con la versión 3 de
6 (information) se ha introducido con la versión 5.
Por defecto, la salida estándar es la consola para todos los flujos enumerados anteriormente. Pero para
redirigirlos con flexibilidad, PowerShell dispone de una batería de operadores.
Estos últimos son idénticos a los utilizados en la shell de Unix:
121
Operador
x> y
Significado
x hacia el archivo y. Si el archivo y ya existe, el contenido
2> error.log redirige el error
estándar hacia el archivo error.log.
Redirige el flujo
del archivo se reemplaza. Ejemplo
x>> y
x hacia el archivo y en modo añadir. Si el archivo y ya
Redirige el flujo
existe, el flujo se añade entonces al final del archivo.
x>&1
Redirige el flujo
x hacia la salida estándar. Ejemplo: 2>&1 redirige los
mensajes de error hacia la salida estándar.
x>&1 > y
Redirige el flujo
el archivo y.
x hacia la salida estándar y redirige la salida estándar hacia
Preste atención: cuando trabaje con los operadores de redirección de flujo no deje espacios entre el
número de flujo y el operador >&1, ya que generaría un error.
Supongamos que enviamos el resultado de un comando en un archivo de texto en vez de la consola. Para
ello usaremos el operador >:
PS > Get­Process > C:\Temp\process.txt
Obtenemos un archivo en
C:\Temp\process.txt que contiene todos los procesos en ejecución.
PS > Get­Content C:\Temp\process.txt
Handles NPM(K) PM(K)
­­­­­­­ ­­­­­ ­­­­­
66
4
1768
62
2
940
297
76
4832
851
14 13008
497
11
9528
34
2
796
582
5
1712
301
10
2732
202
6
2256
82
3
1388
678
27 27960
0
0
0
WS(K) VM(M)
­­­­­ ­­­­­
3520
57
2924
31
452
72
7532
105
5320
87
3464
36
3124
87
12728
135
5720
70
4436
45
41488
189
16
0
CPU(s)
­­­­­­
0,14
0,70
0,05
0,09
20,42
Id
­­
4200
1424
2096
548
1800
5152
768
4784
540
2636
368
0
ProcessName
­­­­­­­­­­­
acrotray
audiodg
ccApp
CcmExec
ccSvcHst
conime
csrss
csrss
DefWatch
dwm
explorer
Idle
Ahora si escribimos de nuevo un comando cuya salida se redirija en el mismo archivo con el operador >, los
datos serán eliminados. El contenido del archivo se borra y se reemplaza por la nueva salida. Para evitar
esto, debe utilizar el operador >> que indica a PowerShell que debe añadir la salida del comando al final del
archivo especificado.
Para redirigir un flujo hacia un archivo, puede también utilizar el comando Out­File en vez de
los operadores de redirección, sobre todo si desea usar parámetros tales como la codificación, el
número de caracteres en cada línea devuelta, etc.
Último ejemplo, la redirección del flujo de error estándar hacia un archivo. Para ello utilizamos un comando
susceptible de devolver un mensaje de error tal como pedir los archivos y las carpetas de una ubicación
inexistente. Después enviamos todo a un archivo con el operador 2>.
122
PS > Get­ChildItem C:\Temp\DirInexistente 2> .\err.txt
Ningún mensaje aparece en la consola. Pero recuperando el contenido del archivo
cuenta de que contiene el mensaje de error relativo al comando introducido.
err.txt, nos damos
PS > Get­Content .\err.txt
Get­ChildItem : Cannot find path ’C:\Temp\DirInexistente’ because it
does not exist...
Redirección aplicada a una función
Es posible utilizar la redirección de flujos hacia funciones. Tomemos como ejemplo la función definida antes:
function miFuncion
{
[CmdletBinding()]
Param ()
Write­Information "Esto es un mensaje de información"
Write­Verbose "Esto es un mensaje verboso"
}
Gracias a las instrucciones [CmdletBinding()] y Param(), habilitamos los parámetros comunes sobre
la función miFuncion. Los parámetros comunes que nos van a interesar específicamente aquí son ­
verbose e ­InformationAction. Serán más prácticos
$VerbosePreference e $InformationPreference.
de
manipular
que
las
variables
# Activación de los comandos Write­Information y Write­Verbose
PS > miFuncion ­Verbose ­InformationAction Continue
Esto es un mensaje de información
VERBOSE: Esto es un mensaje verboso
# Redirección de todos los flujos a un archivo de texto
PS > miFuncion ­Verbose ­InformationAction Continue *> C:\temp\log.txt
# Redirección del flujo verboso únicamente a un archivo de texto
PS > miFuncion ­Verbose ­InformationAction Continue 4> C:\temp\log.txt
Esto es un mensaje de información
En la primera llamada a la función, se muestran los mensajes de información y verboso de manera normal
en la consola.
En la segunda llamada, no se muestra nada en la consola y se crea un archivo log.txt. Este archivo contiene
los mensajes verboso y de información.
Por último, en la tercera llamada a la función, el archivo de texto contiene únicamente el mensaje verboso
(flujo 4), pues solo se redirige este. El mensaje de información se muestra, lógicamente, en la consola.
Operadores de partición y de concatenación
Los operadores de partición y de concatenación permiten combinar o fraccionar cadenas de caracteres a
voluntad.
123
Operador
Significado
­split
Fracciona
una
subcadenas.
­join
Concatena varias cadenas en una.
cadena
en
Así, por ejemplo, podemos partir una cadena utilizando el operador
­split al inicio de la línea.
PS > ­split ’PowerShell es sencillo’
PowerShell
es
sencillo
Por defecto, la división se realiza con el delimitador espacio « ». Para cambiar el delimitador, conviene situar
el operador al final de la línea y a continuación indicar el carácter deseado.
Ejemplo
PS > ’Apellido:Nombre:Dirección:Fecha’ ­split ’:’
Apellido
Nombre
Dirección
Fecha
El delimitador puede ser: un carácter único, una cadena de caracteres, una expresión regular o un bloque
de script (scriptblock).
Para obtener más detalle, le invitamos a consultar la sección de ayuda
completa.
El operador
about_Split, que es muy
­join permite concatenar varias cadenas de caracteres de un mismo array.
Ejemplo
PS > $array = ’Lunes’, ’Martes’, ’Miércoles’, ’Jueves’, ’Viernes’,
’Sábado’, ’Domingo’
PS > ­join $array
LunesMartesMiércolesJuevesViernesSábadoDomingo
PS > $array ­join ’, y ’
Lunes, y Martes, y Miércoles, y Jueves, y Viernes,
y Sábado, y Domingo
Para obtener más detalles, existe una sección de ayuda
about_Join.
124
Operador de formato ­f
1. Nociones básicas
El operador de formato ­f permite formatear cadenas de caracteres a la manera .NET, es decir
exactamente de la misma manera que podríamos hacerlo en VB.NET o C#.
El principio básico es muy simple; en efecto, se trata de reservar uno o varios emplazamientos
predefinidos en una cadena de caracteres y substituir estos últimos por un valor (o expresión). Basta
entonces con definir uno o varios nombres entre llaves (comenzando por cero) en una cadena, y
automáticamente estos últimos serán substituidos por un valor en tiempo de ejecución.
Ejemplo 1:
PS > $str = ’powershell’
PS > ’{0} es un potente lenguaje de scripts.’ ­f $str.ToUpper()
POWERSHELL es un potente lenguaje de scripts.
Evidentemente existen otras maneras de concatenar cadenas de caracteres, sin embargo nos gusta
particularmente el operador ­f ya que aporta cierta facilidad de escritura y una gran facilidad para la
comprensión del código. Esto es sobre todo cierto cuando tenemos que concatenar varios datos para
mostrarlos formateados en un archivo de texto.
Ejemplo 2:
PS > $str1 = ’powershell’
PS > $str2 = ’¡{0}, un concentrado de potencia en solo {1} caracteres !’ ­f
PS > $str2 ­f $str1.ToUpper(), $str1.length
¡POWERSHELL, un concentrado de potencia en solo 10 caracteres !
Así podemos, usando {0}, {1}, ..., {n}, reservar tantos espacios como sea necesario para crear una
cadena. En contrapartida, debe indicar después del operador ­f tantas expresiones como
emplazamientos haya reservado, todo separado por una coma.
Ejemplo 3:
PS > $tb = ’Charb’, ’Cabu’, ’Wolinski’, ’Tignous’
PS > $periodico = ’Charlie Hebdo’
PS > $str = ’{0}, {1}, {3}, y {2}, los {4} eran dibujantes de
{5}.’
PS > $str ­f
$tb[0],$tb[1],$tb[2],$tb[3],$tb.length,$periodico
Charb, Cabu, Tignous, y Wolinski, los 4 eran dibujantes
de Charlie Hebdo.
2. Ir más allá con los formatos de cadenas
Lo que acabamos de ver es el funcionamiento «básico» del operador de formateo. Es posible sacar mucho
partido asociando un símbolo de formato de cadena (FormatString) con un valor de alineación.
Aquí tiene la sintaxis de un emplazamiento reservado:
125
{<Index><,Alineamiento><:FS>}
Index = número de índice. Siempre empieza con el índice 0.
FS = símbolo de formato de cadena.
Un valor de alineación positivo alinea n caracteres hacia la derecha, mientras que un valor negativo alinea
n caracteres hacia la izquierda. Tenga en cuenta que es opcional definir un valor de alineación.
Si no se especifica ningún símbolo de formato de cadena, se utiliza el símbolo de formato general
(«G») para un tipo numérico, de fecha y hora o de enumeración.
Ejemplo de alineación:
PS > "{0,­25}{1,­25}`n{3,­25}{2,­25}" ­f $tb[0],$tb[1],$tb[2],$tb[3]
Charb
Cabu
Tignous
Wolinski
PS > "{0,25}{1,25}`n{3,25}{2,25}" ­f $tb[0],$tb[1],$tb[2],$tb[3]
Charb
Cabu
Tignous
Wolinski
El uso de un valor de alineación añade espacios en la cadena resultante.
Existe un gran número de formateadores de cadena agrupados por temática; en efecto existen algunos
para la visualización de números (enteros, decimales, hexadecimales, porcentajes, etc.), de cadenas de
caracteres, de fechas y horas, etc.
No los enumeramos aquí ya que son demasiados pero puede encontrarlos fácilmente en MSDN efectuando
una búsqueda sobre los términos «formateo compuesto».
Ejemplo de formateo de fecha:
PS > "Estamos en el {0:dd/MM/yyyy}." ­f (Get­Date)
Estamos en el 18/01/2018.
Ejemplo de formateo de moneda:
PS > ’El precio de la entrada de cine es de {0:c}.’ ­f 11.20
El precio de la entrada de cine es de 11,20 €.
En el siguiente ejemplo, pedimos explícitamente que el precio devuelto tenga un único decimal
c1:
PS > ’ El precio de la entrada de cine es de {0:c1}.’ ­f 11.20
El precio de la entrada de cine es de 11,2 €.
Resumen de operadores
En esta lista encontrará todos los operadores enunciados en este capítulo:
126
Significado
Operador
­eq
Igual.
­lt
Inferior a.
­gt
Superior a.
­le
Inferior o igual a.
­ge
Superior o igual a.
­ne
Diferente de.
­not
NO lógico.
!
NO lógico.
­match
Comparación de igualdad entre una expresión y una expresión regular.
­notMatch
Comparación de desigualdad entre una expresión y una expresión
regular.
­like
Comparación de igualdad para expresiones genéricas.
­notLike
Comparación de desigualdad para expresiones genéricas.
­replace
Operador de sustitución.
­and
Y lógico.
­or
O lógico.
­bor
Operador de bits O.
­band
Operador de bits Y.
­bxor
Operador de bits O exclusivo.
­shr
Desplazamiento de bits hacia la derecha.
­shl
Desplazamiento de bits hacia la izquierda.
­xor
O exclusivo.
­is
Operador de igualdad de tipo.
­isNot
Operador de desigualdad de tipo.
­in
Comprueba si un valor está en una colección.
­notIn
Comprueba si un valor no está en una colección.
­contains
Comprueba si una colección contiene un valor.
­notContains
Comprueba si una colección no contiene un valor.
­ceq
Igual (respeta el case sensitive).
­clt
Inferior a (respeta el case sensitive).
­cgt
Superior a (respeta el case sensitive).
­cle
Inferior o igual a (respeta el case sensitive).
­cge
Superior o igual a (respeta el case sensitive).
127
Significado
Operador
­cne
Diferente de (respeta el case sensitive).
­cmatch
Comparación de igualdad entre una expresión y una expresión regular
(respeta el case sensitive).
­cnotmatch
Comparación de desigualdad entre una expresión y una expresión
regular (respeta el case sensitive).
­clike
Comparación de igualdad de expresiones genéricas (respeta el case
sensitive).
­cnotlike
Comparación de desigualdad de expresiones genéricas (respeta el case
sensitive).
­creplace
Operador de sustitución (respeta el case sensitive).
x > y
Redirige el flujo x hacia un archivo, si el archivo
del archivo se reemplaza.
x >> y
Redirige el flujo x hacia un archivo, si el archivo
añade al final de archivo.
x>&1
Redirige el flujo
­split
Fracciona una cadena en subcadenas.
­csplit
Fracciona una cadena en subcadenas (respeta el case sensitive).
­join
Concatena varias cadenas en una sola.
­f
Operador de formateo de cadenas a la manera C# o VB.NET.
y ya existe, el contenido
y ya existe, el flujo se
x hacia la salida estándar.
128
Arrays
129
Introducción
La manipulación de arrays en PowerShell es una noción esencial que debe dominar. Aparte del hecho de
que su uso es bastante cómodo, conviene saber que, cuando el resultado de un comando devuelve varios
objetos, PowerShell devuelve un array de objetos. Veremos en este capítulo que existen diferentes tipos de
arrays y que estos son capaces de contener elementos de distintos tipos si es necesario.
Arrays de una dimensión
El array de una dimensión es el caso más simple. Los valores se almacenan unos a continuación de los
otros, y basta con indicar un número de índice (o index) para acceder al contenido. Un array de una
dimensión se denomina también lista o colección.
Por ejemplo aquí:
El valor
18 se encuentra en el array con el índice 0.
El valor
22 se encuentra en el array con el índice 1.
Los índices del array comienzan en 0 y no en 1 como en otros lenguajes.
1. Inicializar un array vacío
Existen muchas maneras de proceder para declarar un array, pero su expresión más sencilla, la
declaración de un array vacío, puede hacerse de la siguiente manera:
$miArray = @()
La variable
objetos.
$miArray es ahora un array vacío no tipado. Es decir que podrá contener todo tipo de
Restringir el tipo de array
Es posible restringir el tipo de datos que podrá contener un array. Par ello, basta con especificar el tipo
entre corchetes delante del nombre del array cuando lo definamos.
Por ejemplo:
[Int[]]$miArray = @()
[String[]]$miArray2 = @()
[Double[]]$miArray3 = @()
130
Preste atención de no olvidar la pareja de corchetes vacíos después del tipo, como Int[]. Si no lo
hace, PowerShell devolverá un error indicando que no puede convertir un array en un valor entero.
En efecto un tipo [Int] simple (sin el par de corchetes vacíos como sufijo) indica un objeto de tipo
entero y no un array de enteros.
¡Buena práctica!
Una buena práctica cuando conocemos de antemano (lo que generalmente ocurre) el tipo de datos que
debe contener un array es restringir sistemáticamente los arrays, pero también las variables. De esta
manera, cualquier error se detectará rápidamente evitándonos así largas sesiones de depuración.
Ejemplo: Tentativa de asignación de un valor no admitido en un array restringido
PS > $miArray1[0] = ’Hola’
Cannot convert value "Hola" to type "System.Int32". Error: "Input
string was not in a correct format."
...
El error se produce aquí porque el array $miArray1 solo puede contener valores enteros. Sin embargo,
no nos habríamos dado cuenta del error si no hubiésemos tipado la variable.
2. Inicializar un array con valores
Es posible declarar un array e inicializarlo al mismo tiempo. Para ello basta con asignarle varios valores
separados por una coma, siendo la coma el separador de valores cuando se trata de un array.
Ejemplo: Inicialización de un array con valores
PS > $tab = @(1,5,9,10,6)
o:
PS > $tab = 1,5,9,10,6
Tenga en cuenta que aquí hemos utilizado dos formas distintas para asignar valores a un array. Sepa que
ambas son perfectamente válidas e idénticas, siendo la primera forma más explícita que la segunda.
$tab define un array de enteros que contiene el valor 1 en el índice 0, después el valor 5 en el índice 1,
un 9 en el índice 2, etc.
Recuerde que, para un array no restringido, el tipo de valores contenidos en el array se asigna de manera
automática. Sin embargo, de la misma manera que para inicializar un array vacío, podemos restringir el
tipo de datos que puede contener.
Ejemplo
PS > [int[]]$tab = 1,2,3
[] inmediatamente después del nombre del tipo. Estos corchetes indican que se
trata de un array de valores del tipo en cuestión. En este ejemplo, el array $tab solo puede contener
valores enteros.
Observará los corchetes
131
También es posible inicializar un array con el operador de rango, por ejemplo: $tab
array de enteros que contiene todos los valores que van de 1 a 20.
= 1..20 es un
Un array puede sin embargo ser heterogéneo, en cuyo caso la asignación de tipo se hace valor por valor.
Ejemplo
PS > $tab = 1, 2.5, ’A’
3. Leer un array de una dimensión
Para leer un array de una dimensión, existen varios métodos.
El más sencillo es teclear su nombre en la consola y, a continuación, pulsar la tecla [Intro]. En este caso,
se devuelven todos los elementos del array. Para leer un valor en un índice preciso, basta con indicar
entre corchetes el índice deseado.
PS > $tab[0]
1
Para leer varios valores con índices precisos basta, esta vez, con indicar entre corchetes los índices
separados por comas.
PS > $tab[0,2]
1
3
Aquí solo se obtienen los valores con índice
0 y 2, y no el valor con el índice 1.
Puede también mostrar varios valores con el operador de rango. Por ejemplo:
mostrará los valores con los índices del 0 al 20.
$tab[0..20]
Ahora, supongamos que deseamos únicamente leer el valor contenido en el último índice. Existe un
método que permite saber cuántos valores contiene nuestro array. Esto se hace gracias a la propiedad
Length:
PS > $tab[$tab.Length­1]
3
Tenga en cuenta que quitamos una unidad a la propiedad
en 1.
Length ya que los índices comienzan en 0 y no
Pero existe otro método más sencillo: los índices negativos.
Cuando utiliza un índice negativo, hace referencia al número de índice desde el final del array.
Ejemplo
132
PS > $tab[­1]
3
PS > $tab[­3..­1]
1
2
3
El método más habitual para leer un array sigue siendo recorrer los arrays con bucles
Para saber más, consulte el capítulo Bucles y condiciones.
Foreach y For.
4. Concatenar dos arrays
Con PowerShell, la concatenación de arrays se hace con el operador
+. Supongamos que por un motivo
cualquiera necesitamos concatenar dos arrays (o más). Para ello, basta con sumar los arrays con el
operador +.
Ejemplo de suma de dos arrays de caracteres llamados
$cadena1 y $cadena2:
PS > $cadena1 = ’P’,’o’,’w’,’e’,’r’
PS > $cadena2 = ’S’,’h’,’e’,’l’,’l’
PS > $cadena1 + $cadena2
P
o
w
e
r
S
h
e
l
l
5. Añadir un elemento a un array
Añadir un valor a un array se hace con el operador
4 a nuestro array:
+=. Así tecleando la siguiente línea, añadimos el valor
PS > $tab= 1,2,3
PS > $tab += 4
PS > $tab
1
2
3
4
6. Modificar el valor de un elemento
La modificación de un elemento en un array consiste en realizar una asignación con el operador
=.
Por ejemplo, tecleando $tab[2]=1 modificamos el valor contenido en el índice 2 (el tercer valor por lo
tanto). En realidad, hemos realizado una nueva asignación y esta sustituirá el antiguo valor por el nuevo.
Ejemplo
133
PS > $tab = ’A’, ’B’
PS > $tab[0] = ’C’
PS > $tab
C
B
7. Eliminar un elemento
A menos que utilicemos un array de tipo
ArrayList (que no se aborda en este libro ya que se trata de
un tipo poco utilizado por los desarrolladores de scripts pero más por los programadores), no es posible
suprimir un elemento de un array. Sin embargo existe una solución alternativa que permite resolver este
problema. Esta consiste en efectuar una copia de un array excluyendo uno o varios índices.
Ejemplo
Supresión de elementos en un array
Tomemos el ejemplo de sus últimas notas del examen de fin de curso.
PS > $notas = 12, 18, 10, 14, 8, 11
No habiendo sobresalido en algoritmos y programación (8), decide suprimir esta nota que no le satisface
en absoluto.
Para ello, procedamos simplemente volviendo a copiar los elementos del array excepto el valor del índice
4:
PS > $notas = $notas[0..3 + 5]
PS > $notas
12
18
10
14
11
Si no conocemos los índices o el número de notas a eliminar es demasiado importante, podemos también
aplicar un filtro para obtener una copia del array con únicamente los valores mayores o iguales a 10.
PS > $notas = $notas | Where­Object {$_ ­ge 10}
PS > $notas
12
18
10
14
11
8. Determinar el número de elementos de un array
Para determinar el tamaño de un array, basta con utilizar la propiedad
siendo esta última un alias de la propiedad Length.
Length o la propiedad Count,
Ejemplo: cálculo del tamaño de un array
134
PS > $tab = @(’A’,24,12,’C’)
PS > $tab.length
4
9. Convertir en cadena el contenido de un array
A veces deseamos recuperar el contenido de un array en forma de cadena de caracteres.
Supongamos que tenemos el siguiente array:
PS > $tab = @(’A’,24,12,’C’,’XX’,65)
Para convertir su contenido en una cadena única, basta con realizar el tipado con el tipo
podemos ver a continuación:
[String], como
PS > [String]$tab
A 24 12 C XX 65
Debe saber que, por defecto, PowerShell utiliza el espacio como separador. Para cambiar este
comportamiento debe jugar con el contenido de la variable de opciones $OFS.
Así, veamos el efecto de la asignación del valor
+­­+ a la variable $OFS:
PS > $OFS = ’+­­+’
PS > [String]$tab
A+­­+24+­­+12+­­+C+­­+XX+­­+65
Arrays de varias dimensiones
Cuando hablamos de arrays de varias dimensiones, lo hacemos de arrays con varios índices, es más con
tantos índices como dimensiones. Así, para pasar de un array de una dimensión a dos dimensiones, basta
con añadir un índice que permita hacer referencia a él para esta nueva dimensión.
135
La lectura de arrays de varias dimensiones es similar a los de una dimensión. La única dificultad es jugar con
los índices. Tomemos el caso del array descrito más arriba.
La lectura del array con el índice
0 devolverá el primer elemento del array (aquí, otro array):
PS > $tab[0]
1
2
3
Para obtener un valor determinado, deberemos simplemente fijar el índice de la dimensión horizontal y el de
la dimensión vertical.
PS > $tab[0][2]
3
La mayoría de veces, un array con varias dimensiones es un array de arrays. Dicho de otro modo, se trata
de un array que contiene otros arrays. Es lo que vamos a comprobar en el ejemplo siguiente.
Definimos el siguiente array:
PS > $tabu = @(10..13),@(’Hola’,’Adios’),@(1.2,0.3,2.33)
Hemos definido un array que contiene otros tres arrays; respectivamente un array de enteros que va desde
los valores 10 a 13, un array de cadenas de caracteres y un array de números decimales (double).
Recuerde que para conocer el tipo de un objeto, puede usar el método Gettype(). Así, para
determinar el tipo del tercer array contenido en $tabu, podemos escribir lo siguiente:
PS > $tabu[2].Gettype()
Ahora observamos el contenido de nuestro array.
PS > $tabu
10
11
12
13
Hola
Adios
1,2
0,3
2,33
Ahora sacamos el número de elementos contenidos en
$tabu. Según usted, ¿cuántos deberíamos tener?
PS > $tabu.length
3
¡En efecto tres! Tenemos efectivamente tres arrays dentro de
$tabu.
Recuperemos el segundo array:
136
PS > $tabu[1]
Hola
Adios
Ahora recuperamos el segundo elemento del segundo array:
PS > $tabu[1][1]
Adios
Para finalizar, siendo una cadena de caracteres en realidad un array de caracteres (de tipo
[char]),
podemos también indexar el contenido de la cadena pudiendo acceder a cualquier carácter especificando su
índice. En el siguiente ejemplo, obtendremos el primer carácter de la cadena:
PS > $tabu[1][1][0]
A
Pero también podríamos recuperar los cinco primeros:
PS > $tabu[1][1][0..4]
A
d
i
o
s
Arrays asociativos
1. Arrays asociativos estándares
Un array asociativo, también llamado tabla de hash, es una tabla en la cual cada valor se referencia por
una clave y no por un índice. Hasta aquí hemos visto que en un array cada valor se indexaba
numéricamente. No obstante, en una tabla de hash, esta noción de indexación numérica ya no existe.
Utilizamos claves como identificadores. Por ejemplo, veamos una tabla de hash en la que cada elemento
(clave) posee un valor siendo en este caso la representación de un precio.
Clave
Valor
Proyector
1600
Television
1400
Consola_de_juegos
400
Con las tablas de hash, igual que con los arrays clásicos, puede utilizar tipos de datos heterogéneos.
a. Declarar un array asociativo vacío
La declaración de un array asociativo vacío se realiza de la siguiente manera:
137
$hashtable = @{}
b. Inicializar un array asociativo con datos
Para inicializar un array asociativo que contenga valores, se utiliza la sintaxis siguiente:
$hashtable = @{<clave1 = valor>; <clave2 = valor>;...}
@ delante de la
{, separar todas las parejas clave/valor por un punto y coma y cerrar el conjunto con
Tenga en cuenta que la creación de un array asociativo necesita anteponer el símbolo
llave de apertura
una llave }.
Volvamos a nuestro ejemplo con los productos anteriormente descritos. Así debería ser la inicialización
de la tabla:
PS > $catalogo = @{ Proyector = 1600 ; Television = 1400 ;
Consola_de_juegos = 400}
Para después leer los valores contenidos en la tabla, escribimos simplemente el nombre de la tabla en la
consola:
PS > $catalogo
Name
­­­­
Consola_de_juegos
Proyector
Television
Value
­­­­­
400
1600
1400
O bien para recuperar los elementos uno a uno, en cuyo caso utilizamos la notación con puntos o
corchetes:
PS > $catalogo[’Consola_de_juegos’]
400
PS > $catalogo.Television
1400
PS > $catalogo.’Television’
1400
Si la clave contiene espacios, es necesario ponerla entre apóstrofes. Dicho esto, no le
recomendamos crear claves con caracteres especiales, como, por ejemplo, acentos o espacios.
c. Añadir datos a un array asociativo
Al igual que con un array clásico, usamos el operador
+= para añadir datos a un array asociativo.
Ejemplo
PS > $catalogo += @{Amplificador = 800}
138
Comprobemos ahora el contenido del array:
PS > $catalogo
Name
­­­­
Amplificador
Consola_de_juegos
Proyector
Television
Value
­­­­­
800
400
1600
1400
Debe saber que por defecto el framework .NET no conserva el orden de introducción de los parejas
clave/valor en un array asociativo. Estando PowerShell basado en .NET, no escapa a esta regla…
d. Recorrer un array asociativo
El recorrido de una tabla de hash se realiza de manera diferente al de un array clásico. En efecto, debe
conocer el nombre de la clave para obtener su valor. Para recorrer integramente una tabla de hash, lo
primero es recuperar todas las claves (su nombre) y después cada valor de las mismas.
Recuperar el nombre de las claves
Una tabla de hash posee la propiedad
tabla.
keys. Esta última contiene el nombre de todas las claves de la
Ejemplo
PS > $catalogo.keys
Amplificador
Consola_de_juegos
Proyector
Television
Recuperar los valores
Ahora basta con realizar un simple bucle, por ejemplo con el comando
Foreach­Object y ya está:
PS > $catalogo.keys | Foreach­Object { $catalogo.$_}
800
1400
1600
400
Pero podemos hacerlo aún más sencillo utilizando la propiedad
values, como mostramos abajo:
PS > $catalogo.values
139
2. Arrays asociativos ordenados
Desde la versión 3, es posible forzar a PowerShell para preservar el orden en el que se declaran las
parejas clave/valor. Para ello, basta con prefijar el array con el tipo [ordered].
Ejemplo
PS > $catalogo = [ordered]@{ Proyector
Television = 1400 ;
Consola_de_juegos = 400}
PS > $catalogo
Name
­­­­
Proyector
Television
Consola_de_juego
= 1600 ;
Value
­­­­­
1600
1400
400
140
Bucles y
condiciones
141
Los bucles
Un bucle es una estructura repetitiva que permite ejecutar varias veces las instrucciones que se encuentran
en el bloque de instrucciones. Es importante dominar el concepto de los bucles, pues lo encontramos en
todo lenguaje informático y es la base de todo programa, sea cual sea el lenguaje.
1. Bucle While
Las instrucciones del bloque de instrucciones de este bucle se repiten mientras se satisfaga la condición
del mismo (es verdad). Dicho de otro modo, cuando la condición evaluada tenga el valor $false, se
saldrá del bucle.
La sintaxis de un bucle
While es la siguiente:
While (<condición>)
{
#bloque de instrucciones
}
Su funcionamiento es el siguiente:
1. El bucle evalúa la condición.
2. Si la condición es falsa, no se ejecuta el bloque de instrucciones y se finaliza el bucle.
3. Si la condición es verdadera, entonces se ejecuta el bloque de instrucciones.
4. Vuelta al paso 1.
Veamos un ejemplo básico de un bucle While que enumera los valores contenidos en un array. En este
bucle, mientras el valor $numero es estrictamente inferior al tamaño del array, el bloque de instrucciones
devuelve el valor del array para el índice $numero.
$numero = 0
$tab = 0..99
While($numero ­lt $tab.Length)
{
$tab[$numero]
$numero++
}
2. Bucle Do­While
El bucle Do­While se parece al bucle While, con la diferencia de que la condición se evalúa al final. El
bucle Do­While tiene la siguiente estructura:
Do
{
#bloque de instrucciones
}
While (<condición>)
142
Estando la evaluación al final, el bloque de instrucciones se ejecuta al menos una vez aunque la condición
sea falsa. Por ejemplo, con el siguiente bucle, se le pide al usuario introducir un número entre 0 y 10 en
primer lugar. Si el número introducido no está entre estos valores, entonces el bloque de instrucciones se
ejecuta de nuevo.
Do
{
[int]$var = Read­Host ’Introduzca un valor entre 0 y 10’
}
While( ($var ­lt 0 ) ­or ($var ­gt 10) )
3. Bucle Do­Until
Este bucle es gemelo del bucle Do­While. En español, significa literalmente «bucle hasta que...». En
efecto, con esta estructura, se repite el bucle hasta que la condición de salida se evalúe como verdadera
($true). Dicho de otro modo, mientras sea falsa, se continuará ejecutando el bloque de script. La
condición de salida es, por tanto, inversa respecto al bucle
Do­While.
Do
{
[int]$var = Read­Host ’Introduzca un valor entre 0 y 10’
}
Until( ($var ­ge 0 ) ­and ($var ­le 10) )
4. Bucle For
El bucle
For permite ejecutar un número determinado de veces un bloque de instrucciones.
Cuando utilizamos un bucle For, indicamos un valor de inicio, una condición de repetición del bucle así
como el incremento, es decir el valor con el cual se incrementa en cada iteración.
La sintaxis del bucle
For es la siguiente:
For (<inicial> ;<condición> ;<incremento>)
{
#bloque de instrucciones
}
Su funcionamiento es el siguiente:
1. Se evalúa la expresión inicial. En general se trata de una asignación que inicializa una variable.
2. Se evalúa la condición de repetición.
3. Si la condición es falsa, la instrucción
For se termina.
4. Si la condición es verdadera, se ejecuta el bloque de instrucciones.
5. Se incrementa la expresión en función del incremento elegido y la ejecución vuelve al paso 2.
Retomemos el ejemplo del recorrido del array, pero esta vez con un bucle
For.
143
$tab = 0..99
For($i=0 ;$i ­le 99 ;$i++)
{
$tab[$i]
}
5. Bucle Foreach
El bucle
Foreach, aunque de apariencia simple, es probablemente en que da más dolores de cabeza a
los debutantes. Y, no porque sea complicado, sino porque se puede utilizar de diferentes maneras. Cada
manera tiene algunas especificadades diferentes que debemos conocer…
Este tipo de tratamiento, cuando se domina, es de lejos el más práctico para recorrer una colección de
datos. A diferencia del bucle For, no es necesario determinar el avance de los elementos de la colección;
lo que contribuye a evitar los errores y permite ahorrar tiempo.
a. Primera técnica
Aunque se puede usar en línea de comandos, es decir directamente en la consola, encontramos esta
forma más frecuentemente en scripts. En efecto, se parece mucho al bucle Foreach de C#. Bajo esta
forma, no nos encontramos con un comando sino con una palabra clave.
La sintaxis es la siguiente:
Foreach (<elemento> in <colección>)
{
#bloque de instrucciones
}
El principio consiste en utilizar una variable arbitraria que solo existirá en el interior del bloque Foreach.
Esta variable recibirá en cada iteración un elemento de una colección, hasta que se recorra toda la
colección. Así, en el bloque de instrucciones, utilizaremos esta variable exactamente como cualquier otra.
Observemos el ejemplo siguiente:
Foreach ($elemento in Get­Process) {
’{0} arrancado el : {1}’ ­f $elemento.Name, $elemento.StartTime
}
Resultado
...
Notepad
arrancado el : 2/02/2018 09:57:00
Powershell arrancado el : 2/02/2018 09:09:24
WINWORD
arrancado el : 2/02/2018 08:57:40
...
Seguramente se ha dado cuenta, no es necesario almacenar el resultado del comando
Get­
Process en una variable. En la ejecución, PowerShell empieza evaluando Get­Process, después
almacena en memoria toda la colección de objetos. Si el consumo de memoria es un problema porque
dispone de un gran volumen de datos a procesar, entonces conviene mejor utilizar la segunda forma de
uso de Foreach.
144
Para volver a nuestro ejemplo, en la primera iteración la variable $elemento contiene el primer objeto
devuelto por Get­Process. Siendo cada elemento de la colección un objeto, podemos utilizar sus
propiedades y sus métodos. En la iteración siguiente $elemento contiene el segundo objeto de la
colección y así hasta recorrer todos los objetos.
Esta forma de uso de Foreach es la que más memoria viva consume ya que carga toda la
colección antes de empezar a procesarla; sin embargo es extremadamente eficiente.
b. Segunda técnica
Foreach con una pipeline. Aquí por lo
tanto no es la palabra clave Foreach la que se utiliza sino el comando Foreach­Object. Sin
embargo es posible utilizar el alias Foreach para ahorrar algunas pulsaciones de teclas. En general
Con esta técnica, se pasa la colección de objetos a procesar al
encontramos más esta forma en la consola que en los scripts. Esto es debido probablemente al hecho de
que el uso de pipe es muy natural en modo «interactivo» y lo es un poco menos cuando escribimos un
script en un editor.
Veamos en el siguiente ejemplo cuyo resultado es el mismo que en el ejemplo anterior:
Get­Process | Foreach­Object {
’{0} arrancado el : {1}’ ­f $_.Name, $_.StartTime
}
Esta vez ya no es necesario definir una variable arbitraria que contenga cada objeto de la colección, sino
usar la variable automática $_. Esta variable contiene el objeto actual que transita por la pipeline en un
instante t.
Esta forma de uso del bucle Foreach es la que menos memoria viva consume ya que no
almacena la colección antes de empezar a procesarla, pero trata los objetos de la colección
uno a uno. La ventaja por lo tanto es el uso mínimo de memoria. Sin embargo, como no puede
haber solo ventajas, lo pagamos en rendimiento. En efecto, cada vez que llamamos a la pipeline
ralentizamos la ejecución. Tendrá que elegir si prefiere eficiencia o minimizar el consumo de
memoria. Para ayudarle a realizar esta elección, no dude en apoyarse en el comando Measure­
Command.
Foreach­object es que este comando autoriza la ejecución de un bloque de script
antes y después del procesamiento principal gracias a los parámetros ­begin y ­end. En efecto, sin
saberlo llamamos al parámetro ­process cuando utilizamos este comando en su forma más simple.
Otra ventaja de
Ejemplo
Get­Process | Foreach­Object ­Begin {’* Inicio del procesamiento *’} `
­Process {’{0} arrancado el : {1}’ ­f $_.Name, $_.StartTime}`
­End {’* Fin del procesamiento *’}
Así, en este ejemplo, mostramos unas cadenas de texto al inicio y al final del procesamiento que
enmarcan las acciones realizadas, aunque hubiésemos podido hacer cualquier otra cosa.
Resultado
145
*Inicio del procesamiento
...
Notepad
arrancado el :
Powershell arrancado el :
WINWORD
arrancado el :
...
* Fin del procesamiento *
*
2/02/2018 09:57:00
2/02/2018 09:09:24
2/02/2018 08:57:40
Notación simplificada
Gracias a Where­Object, existe desde PowerShell 3 la posibilidad de usar una notación simplificada
(sin llaves y sin la variable $_ que representa el objeto en curso) para el comando Foreach­Object.
Así en vez de escribir el comando siguiente:
PS > Get­Process | Foreach­Object {$_.name}
Podemos escribir:
PS > Get­Process | Foreach­Object Name
Resultado
Armsvc
atieclxx
audiodg
Bootcamp
...
Estructura condicional If, Else, ElseIf
Una estructura condicional permite, mediante la evaluación de una condición, orientar la ejecución hacia un
bloque de instrucciones o hacia otro. La sintaxis de una estructura condicional es la siguiente:
If (condición)
{
#bloque de instrucciones
}
Para entender mejor el uso de una estructura condicional, veamos algunos ejemplos:
Imaginemos que queremos determinar si un valor introducido por el usuario es la letra A. Para ello,
utilizaremos una estructura condicional con una condición sobre el valor de la variable evaluada. Usando un
operador de comparación, la estructura resultante es la siguiente:
$var = Read­Host "Introduzca un carácter"
If ($var ­eq ’A’)
{
"El carácter introducido por el usuario es una ’A’"
}
Si la variable introducida por el usuario en una A, entonces se muestra una cadena de caracteres; en caso
contrario la ejecución seguirá su curso sin ejecutar el bloque de instrucciones que sigue al If.
146
Es posible asociar a la instrucción If la cláusula Else. Esta cláusula permite en el caso de devolver un
valor False orientar hacia un segundo bloque de instrucciones el procesamiento. Tomemos el siguiente
ejemplo:
If (($var1 ­eq 15) ­and ($var2 ­eq 18))
{
# Bloque de instrucciones 1
}
Else
{
# Bloque de instrucciones 2
}
En un primer momento, PowerShell evalúa la primera condición, a saber «la variable
Si se cumple, entonces la primera condición toma el valor true.
Después evalúa la segunda condición «la variable
toma también el valor
$var1 es igual a 15».
$var2 es igual a 18». Si se cumple, la segunda condición
true.
Si los dos valores son verdaderos, el operador lógico
­and de la condición devuelve el valor true (verdad Y
verdad = verdad), y así se ejecuta el bloque de instrucciones 1, en caso contrario, si la condición es falsa se
ejecuta el bloque de instrucciones 2.
En el mismo registro, aquí tiene otro ejemplo:
[int]$var1 = Read­Host ’Introduzca un número’
[int]$var2 = Read­Host ’ Introduzca un número ’
If ($var1 ­ge $var2)
{
"$var1 es más grande o igual que $var2"
}
Else
{
"$var1 es más pequeño que $var2"
}
En este segundo ejemplo, el usuario introduce dos números que se almacenan respectivamente en las
variables $var1 y $var2. PowerShell evalúa a continuación si la primera variable es mayor o igual que la
segunda. Si la condición se cumple, entonces devolvemos la cadena que indica que el primer valor es mayor
o igual que el segundo. Si la condición es falsa, se devuelve la cadena situada en el segundo bloque de
instrucciones de la cláusula Else.
Por último, para terminar con las estructuras condicionales, veamos cómo mejorarlas gracias a la instrucción
ElseIf. La instrucción ElseIf permite, si la condición anterior es falsa, evaluar otra condición. Así,
usando una estructura condicional con ElseIf, no nos limitamos a una orientación binaria, sino que
aumentamos las posibles orientaciones del flujo de ejecución.
Ejemplo
147
[int]$val = Read­Host ’Introduzca un número: 1,2 o 3’
If ($val ­eq 1)
{ ’El valor introducido es igual a 1’}
ElseIf($val ­eq 2)
{ ’El valor introducido es igual a 2’}
ElseIf($val ­eq 3)
{ ’El valor introducido es igual a 3’}
Else
{ "¡El valor introducido no es igual a 1 ni a 2 ni a 3!"}
De esta manera, hubiésemos podido crear tantos
ElseIf como fueran necesarios. Pero el uso intensivo de
ElseIf, aunque viable, no es una solución elegante. Al existir tantas condiciones como bloques de
instrucciones no hace el código flexible ni legible y preferiremos orientarnos hacia la instrucción Switch.
Switch
La instrucción Switch permite reemplazar ventajosamente toda una serie de If, ElseIf y Else. A
diferencia de las instrucciones If que, para una instrucción dada, orienta la ejecución siguiente hacia uno
de los dos bloques de instrucciones, la instrucción Switch orienta la ejecución hacia varios bloques de
instrucciones distintos. Y eso con una sola expresión. Esto le confiere un uso netamente más flexible.
Se puede construir un
Switch de varias maneras, en función de lo que se quiera comprobar.
1. Estructura simple
La sintaxis de
Switch es la siguiente:
Switch (<Expresión>)
{
<Valor_1> { bloque
<Valor_2> { bloque
<Valor_3> { bloque
Default
{ bloque
}
El valor
de
de
de
de
instrucciones
instrucciones
instrucciones
instrucciones
1; Break }
2; Break }
3; Break }
4}
Default es opcional. Su bloque de instrucciones solo se ejecuta cuando la expresión no
corresponde con ningún valor.
Es habitual utilizar la instrucción
Break dentro de bloques de instrucciones. Esto permite salir del
procesamiento del Switch en curso. En efecto, si se ejecuta un bloque de instrucciones, el valor evaluado
corresponde a la expresión. No hace falta comprobar los demás valores.
Tomemos como ejemplo de aplicación el caso básico donde el usuario introduce un número entre
PowerShell determina qué número se ha introducido. El código es el siguiente:
1y5y
148
[Int]$Numero = Read­Host ’Introduzca un número comprendido entre 1 y 5 ’
Switch($Numero)
{
1 { ’Ha introducido
2 { ’Ha introducido
3 { ’Ha introducido
4 { ’Ha introducido
5 { ’Ha introducido
Default { "¡ Número
}
el número 1
el número 2
el número 3
el número 4
el número 5
introducido
’; Break }
’; Break }
’; Break }
’; Break }
’; Break }
incorrecto !"}
2. Estructura a base de subexpresiones
También se puede utilizar la instrucción Switch con subexpresiones para obtener una mayor flexibilidad
en su uso. Esta forma se utiliza muy poco, pues no está documentada por Microsoft, lo cual es una lástima,
pues resulta realmente práctica. Reemplazará con éxito las comprobaciones espagueti intrínsecas a las
estructuras if/elseif/else.
Ejemplo:
[Int]$Numero = Read­Host ’Introduzca un número entero positivo’
Switch($Numero)
{
{$_ ­lt 10} { ’Número estrictamente inferior a 10 ’; Break}
{$_ ­ge 10 ­and $_ ­le 20} { ’Número entre 10 y 20’ ; Break}
Default { ’Número superior a 20’}
}
Cuando se utiliza
$_.
Switch con subexpresiones, el valor comprobado se asigna a la variable automática
3. Estructura a base de expresiones regulares
La instrucción
regex:
Switch acepta también expresiones regulares. Para ello, debe especificar el parámetro ­
$cadena = Read­Host ’Introduzca una cadena’
Switch ­regex ($cadena)
{
’^[aeiouy]’ { ’La cadena introducida empieza por una vocal’ ; break}
’^[^aeiouy]’ { ’La cadena introducida no empieza por una vocal’ ; break}
}
149
Funciones y
scripts
150
Funciones
1. Estructura de una función
En PowerShell, como en muchos lenguajes, una función es un conjunto de instrucciones a las que damos
un nombre. El principal interés de las funciones es que podemos llamarlas varias veces, sin tener que
volver a escribir las instrucciones en cada llamada. Una función está constituida de los siguientes
elementos:
un nombre;
un tipo de ámbito (opcional);
un bloque param (opcional);
un bloque de instrucciones.
En lo que se refiere al ámbito, abordaremos esta noción un poco más adelante en este capítulo. La
escritura de una función necesita la sintaxis siguiente:
Function [<ámbito> :] <nombre de función>
{
param (<lista de parámetros>)
# bloque de instrucciones
}
Tomemos por ejemplo la siguiente función:
Function Hola
{
$date = Get­Date
Write­Host "Hola, hoy es el $date" ­Foreground Yellow
}
Esta función es la más básica posible. En cada llamada, muestra un mensaje en amarillo en la consola.
Para llamar a una función, basta simplemente con escribir su nombre:
PS > Hola
Hola, hoy es el 13/01/2018 13:18:54
2. Uso de argumentos
Mencionamos los argumentos aquí para que el libro sea más completo, pero sepa que es mucho más
conveniente usar parámetros (que se abordan en la siguiente sección). En efecto, los parámetros
representan el medio para pasar valores a los scripts o funciones, desde la versión 2 de PowerShell.
Una noción interesante en el uso de funciones o de scripts es el paso de valores. Para ello, una técnica
consiste en usar argumentos. Los argumentos son los valores situados detrás del nombre de la función
cuando se la invoca. Aquí tiene la sintaxis de la llamada a una función con varios argumentos:
<Nombre de función> <Argumento1> <Argumento2> <ArgumentoN>
151
Por ejemplo
PS > Hola Pepe Paco
En este ejemplo, Pepe y Paco son los dos argumentos que se pasan a la función
Hola.
Imaginemos que acabamos de crear una función que muestra un mensaje en un cuadro de diálogo. La
pregunta es: ¿Cómo hacer para recuperar los argumentas de manera que los insertemos en un cuadro de
diálogo? Pues la respuesta es muy sencilla. Cuando pasamos argumentos a una función (o a un script),
todos se encentran almacenados en un array de argumentos llamado $args. Y es este array el que
usaremos en el cuerpo de la función o script.
segundo, etc.
$args[0] corresponde al primer argumento, $args[1] al
Tomemos como ejemplo una función capaz de mostrar una ventana pop­up que contiene un título y un
texto:
Function Show­Popup
{
$WshShell = New­Object ­ComObject wscript.Shell
$WshShell.Popup($args[0], 0, ’Popup PowerShell’)
}
Esta función llama a un objeto COM con el nombre
wscript.shell.
Cuando llamemos a esta función con uno o varios argumentos, tomaremos únicamente el primer
argumento y lo mostraremos en un cuadro de diálogo:
PS > Show­Popup "PowerShell es sencillo"
Observe que hemos codificado el título de la ventana pop­up «a fuego», pero podríamos haberlo hecho
fácilmente parametrizable con $args[1]. Dicho esto, el principal problema de la técnica del paso de
argumentos con $args es que no se sabe nunca el orden en que pasar los argumentos en la línea de
comandos. Todo esto se ha mejorado enormemente con la técnica de los parámetros, que veremos a
continuación.
152
3. Uso de parámetros
La segunda manera de transmitir variables a una función o script es usar parámetros. Le recomendamos
utilizar preferentemente los parámetros a los argumentos. La sintaxis de la llamada de una función con
parámetros es la siguiente:
<Nombre de función> ­<Parámetro> <Valor del parámetro>
Después, para que PowerShell los interprete, basta con especificar al principio de la función o del script los
parámetros de entrada gracias a la instrucción Param.
Por ejemplo
Function Show­Popup
{
Param([string]$mensaje, [string]$titulo)
$WshShell = New­Object ­ComObject wscript.Shell
$WshShell.Popup($mensaje, 0, $titulo)
}
Con este principio, a diferencia de lo que ocurre con los argumentos, el orden no tiene ninguna
importancia en el momento en el que especificamos el nombre del parámetro. Esto significa que las dos
expresiones siguientes darán el mismo resultado:
PS > Show­Popup ­titulo ’Mi título’ ­mensaje ’Hola’
PS > Show­Popup ­mensaje ’Hola’ ­titulo ’Mi título’
Si deseamos omitir los nombres de los parámetros en la llamada a la función, entonces tendremos que
tener en cuenta el orden en el cual han sido declarados en el bloque param. En efecto tenemos que
asegurarnos que los valores se asignan al parámetro correcto.
Así podemos llamar a la función
Show­Popup como sigue:
PS > Show­Popup "Mi título" "PowerShell es sencillo"
También podemos asignar valores por defecto a los parámetros como si se inicializara una variable.
Function Show­Popup
{
Param([string]$mensaje=’Mensaje...’, [string]$titulo=’Título’)
$WshShell = New­Object ­ComObject wscript.Shell
$WshShell.Popup($mensaje, 0, $titulo)
}
De este modo, cuando llamemos a la función, si los valores de los parámetros Titulo y
están indicados, la ejecución se realizará con los valores predefinidos en el bloque Param.
Mensaje no
Ejemplo
PS > Show­Popup
153
PowerShell permite también llamar a funciones, scripts o comandos nativos usando una parte del
nombre de un parámetro. De este modo es posible acortar los nombres de los parámetros tanto
como queramos en la medida en que no exista ambigüedad entre varios parámetros.
Por ejemplo
PS > Show­Popup ­t ’Mi título’ ­m "PowerShell es sencillo"
4. Retorno de valores
a. Devolver un valor escalar
Una función puede devolver todo tipo de objetos. Para ello, basta con insertar un objeto (o la variable
que representa un objeto) en cualquier sitio de la función (o el script) para que se devuelva su resultado.
Tomemos como ejemplo una función que calcula la media aritmética de dos números.
Function media
{
param ([double]$numero1, [double]$numero2)
($numero1 + $numero2) /2
}
Probemos a continuación nuestra función:
PS > media ­numero1 15 ­numero2 20
17,5
El resultado devuelto por la función
caracteres; ¡tenga esto presente!
media es un objeto de tipo double, y no una cadena de
Si bien la instrucción Return existe en PowerShell, es un falso amigo. Esta instrucción invita a
pensar que PowerShell devuelve únicamente lo que se pasa a esta función, lo cual es falso, pues
PowerShell devuelve todo valor (o todo objeto) no asignado a una variable. De modo que no resulta
una buena práctica utilizar Return (salvo en las clases, donde es obligatorio).
154
b. Devolver un objeto
Devolver un objeto por un script o una función es algo nuevo en una Shell. En efecto, esta facultad
estaba hasta ahora reservada a los lenguajes de alto nivel como C# y demás. Tendremos que
acostumbrarnos ya que se trata de la esencia misma de la filosofía de PowerShell. ¡Y también es lo que
lo hace grande!
¿Pero qué es lo que realmente pasa cuando ejecutamos las siguientes líneas de script?
Veamos qué ocurre cuando ejecutamos las líneas del siguiente script:
# Recupera todos los procesos en ejecución que empiezan por la letra a
PS > $processes = Get­Process a*
# Para los procesos recuperados anteriormente
PS > Stop­Process $processes
Sencillamente, Get­Process devuelve una colección de objetos de tipo process (todos aquellos cuyo
nombre empiece por la letra a). Esta colección se asigna a continuación a la variable $processes. Si
existen varios procesos que empiezan por la letra « a », entonces el tipo de esta variable será una tabla,
si no un escalar. Para terminar pasamos al comando Stop­Process la variable $processes para
parar los procesos contenidos en la colección. Habrá observado que en ningún momento se
recuperan cadenas de caracteres, ni se transmite texto.
Si viene de VBScript, tendrá que pensar de manera distinta ya que deberá evitar devolver «datos útiles»
sumergidos en una cadena de caracteres como "El tamaño total de los archivos es
15124 KB.". En efecto, devolviendo un valor de estas características le será muy complicado enlazar
con otros códigos.
La filosofía de PowerShell es devolver únicamente el valor
llamada Tamaño o Tamaño(KB) que contenga el valor:
15124, o un objeto que tenga una propiedad
Tamaño(KB)
­­­­­­­­­­
15124
No pierda de vista que a los desarrolladores PowerShell les encanta usar la pipeline para enlazar los
comandos con el objetivo de realizar un máximo de procesamiento en un mínimo de código y esfuerzo.
Para conseguir este objetivo, deberá esforzarse en devolver objetos existentes o sus propios objetos.
En el siguiente ejemplo, mostramos lo que no se debe hacer:
Function Get­FileInfo
{
Param([string]$Path)
$fichero = Get­Item $Path
"Nombre : $($fichero.FullName)"
"Fecha de creación : $($fichero.CreationTime)"
"Último acceso : $($fichero.LastAccessTime)"
}
Aunque no resulte muy útil, esta función con fines educativos devuelve algunas propiedades elegidas
como en el siguiente resultado:
155
PS > Get­FileInfo ­Path .\Abonados.txt
Nombre : C:\temp\Abonados.txt
Fecha de creación : 01/13/2018 16:57:20
Último acceso : 01/13/2018 16:57:20
Aunque nuestra función realiza su tarea, devuelve texto. Por lo tanto a parte de una finalidad informativa
para el usuario final o redirigir el resultado hacia un archivo de log, no permitirá enlazar otros
tratamientos.
La idea es por lo tanto que la función devuelva un objeto. Así será sencillo extraer los valores de sus
diferentes propiedades.
Aquí tiene el script final como es preferible escribirlo:
Function Get­FileInfo
{
Param([string]$Path)
$fichero = Get­Item $Path
$fichero | Select­Object FullName, CreationTime, LastAccessTime
}
Ahora probamos nuestra nueva función:
PS > Get­FileInfo ­Path .\Abonados.txt
FullName
­­­­­­­­
C:\temp\Abonados.txt
CreationTime
­­­­­­­­­­­­
01/13/2018 16:57:20
LastAccessTime
­­­­­­­­­­­­­­
01/13/2018 16:57:20
Mejor, ¿verdad? El resultado se parece por fin a lo que deberíamos esperar de una función PowerShell.
Además, contrariamente a la versión inicial de la función, podemos ahora enlazar el resultado como en el
ejemplo siguiente:
PS > Get­FileInfo ­Path C:\temp\abonados.txt | Reset­LastAccessTime
Aunque pueda apetecer (y se puede) querer españolizar a ultranza, como devolver los nombres de
las propiedades en castellano, le recomendamos ser coherente con el nombre de los comandos que
se encuentran en inglés. Esto permite evitar el « spanglish » y por lo tanto mantener una buena
homogeneidad global.
5. Introducción a las «funciones avanzadas»
Los términos «funciones avanzadas» han perdido importancia en la actualidad, pues esta noción apareció
con PowerShell 2.0, que se remonta a unos cuantos años... Así, actualmente, si queremos realizar scripts
de calidad profesional, estas funcionalidades llamadas «avanzadas» son el mínimo punto de partida.
Además, comprenderá rápidamente cómo le simplificarán la tarea y que, por tanto, su uso resulta
realmente ventajoso. ¡No deberíamos obviarlas!
Brevemente, diremos que las funciones avanzadas permiten realizar funciones que se comportan de
manera muy similar, por no decir idéntica, a los comandos PowerShell. Esta posibilidad permite a los
desarrolladores de scripts desarrollar tranquilamente nuevos comandos en PowerShell, aunque en
principio el desarrollo de comandos necesita del uso de un lenguaje .NET de alto nivel como C#.
156
Una función avanzada permite entre otras cosas:
Aceptar objetos de entrada por medio de una pipeline,
Implementar parámetros comunes: ­Verbose, ­WhatIf, ­ErrorAction, etc.
Validar los valores pasados como parámetros,
Hacer ciertos parámetros obligatorios,
Crear varios juegos de parámetros distintos,
Definir alias de los parámetros,
…
Además las funciones avanzadas constituyen la piedra angular en la realización del módulo PowerShell. En
efecto por razones de sencillez y rapidez de desarrollo, muchos de los comandos contenidos en los
módulos se escriben en PowerShell bajo la forma de funciones avanzadas.
a. Diferencias entre las funciones clásicas y las funciones avanzadas
Es fácil distinguir las funciones clásicas de las funciones avanzadas, pues estas últimas utilizan el
atributo CmdletBinding. Este atributo es rico en funcionalidades, pero no diremos más de momento,
pues resulta secundario respecto a las demás funcionalidades. Para obtener más información sobre este
atributo,
le
invitamos
a
consultar
la
sección
de
la
ayuda:
about_Functions_CmdletBindingAttribute.
He aquí la sintaxis de la definición de una función avanzada en su forma más sencilla:
Function <nombre de función>
{
[CmdletBinding()]
Param (<declaración de parámetros>)
Begin
{ # Bloque de instrucciones opcional utilizado una única vez }
Process { # Bloque de instrucciones }
End
{ # Bloque de instrucciones opcional utilizado una única vez }
}
Como un ejemplo es mejor que mil palabras, mostramos el aspecto de nuestra función anterior
Popup una vez transformada en función avanzada.
Show­
Function Show­Popup
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateLength(1,10)]
[String]$Titulo,
[Parameter(Mandatory=$false)]
[String]$Mensaje=’Mensaje...’
)
Process
{
$WshShell = New­Object ­ComObject wscript.Shell
$WshShell.Popup($Mensaje, 0, $Titulo)
157
}
}
Lo primero que podemos observar es el uso del atributo
[CmdletBinding()]. Después en el bloque
Param hemos «decorado» (es así como se dice en la jerga) cada parámetro de los atributos
[Parameter()].
Se trata de algo muy importante, pues gracias a este último se define la naturaleza del parámetro, es
decir, si el parámetro representa un carácter obligatorio u opcional, si debe estar comprendido entre
determinados valores, etc. En nuestro ejemplo, hemos hecho el parámetro ­titulo obligatorio.
Además, gracias al atributo [ValidateLength(1,10)], hemos conseguido que reciba únicamente
valores comprendidos entre uno y diez caracteres. Haciendo esto, nos aseguramos de que la función no
se ejecutará si el valor introducido no es correcto.
Insistimos en el hecho de que esta facilidad de validación sobre los valores de los parámetros es
realmente muy útil ya que permite que el desarrollador de script no tenga que preocuparse de los
valores introducidos. Así, podrá concentrarse todavía más en las acciones del script y no en los detalles
secundarios con poco valor añadido y un alto riesgo de error.
Para terminar, puede observar la presencia del bloque Process. Aquí, en nuestro ejemplo, habríamos
podido simplemente omitirlo, pues nuestra función no tiene en cuenta el pipeline. En efecto, los bloques
Begin y End solamente son útiles cuando una función o un script aceptan el paso de datos a través del
pipeline.
Para obtener más información acerca del atributo [Parameter()], le invitamos a consultar la sección
de ayuda about_Functions_Advanced_Parameters.
b. Atributos de validación de parámetros
Los atributos de validación de parámetros, también llamados «decoradores», son una gran fuerza para
las funciones avanzadas, pues permiten validar los valores introducidos. A modo de recordatorio, hemos
agrupado todos los decoradores en la siguiente tabla:
Atributo de validación de un
parámetro
[Alias(<String[]>)]
Descripción
Define uno o varios alias sobre un parámetro.
Sintaxis:
Param(
[Alias("CN","MachineName")]
[String]$ComputerName
)
[AllowNull()]
Indica que el parámetro recibe un valor nulo (lo
cual no ocurre por defecto). Este atributo está,
en
principio, asociado
a
un
parámetro
obligatorio.
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$Edad
)
158
Atributo de validación de un
parámetro
[AllowEmptyString()]
Descripción
Este
atributo
es
una
variante
del atributo
[AllowNull()]. Indica que se permite utilizar
una cadena vacía ("") como valor del
parámetro. Tiene sentido únicamente con un
parámetro obligatorio, pues un parámetro
obligatorio no autoriza el uso de cadenas
vacías.
Sintaxis:
Param (
[Parameter(Mandatory=$true)]
[AllowEmptyString()]
$Name
)
[AllowEmptyCollection()]
Variante
también
del
atributo
[AllowEmptyString()], este atributo indica
que se autoriza una colección vacía como valor
de parámetro. Solo tiene sentido con un
parámetro obligatorio, pues un parámetro
obligatorio no permite colecciones vacías.
Sintaxis:
Param (
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
$ComputerName
)
[ValidateNotNull()]
Permite especificar que el valor pasado como
argumento no debe ser nulo. Es el caso
contrario al atributo [AllowNull()].
Sintaxis:
Param (
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
$ComputerName
)
[ValidateNotNullOrEmpty()]
Especifica que el valor que se pasa como
argumento no debe ser nulo o vacío.
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$ComputerName
)
159
Atributo de validación de un
parámetro
[ValidateCount(<int>,<int>)]
Descripción
Indica un número mínimo y máximo de valores
que se pueden pasar como parámetro.
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[ValidateCount(1,5)]
[String[]]
$ComputerName
)
En este ejemplo, se aceptarán de uno a cinco
valores solamente.
[ValidateLength(<int>,<int>)]
Define la longitud mínima y máxima del valor que
se pasa como parámetro (número de caracteres,
por ejemplo).
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[ValidateLength(2,10)]
[String]
$ComputerName
)
En este ejemplo, un nombre de máquina deberá
tener de dos a diez caracteres.
[ValidatePattern(<Regex>)]
Permite asegurar que el valor que se pasa como
parámetro es conforme a un modelo establecido
con las expresiones regulares.
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[ValidatePattern(’^\w{6}\d{4}$’)]
[String[]]
$ComputerName
)
En este ejemplo, cada nombre de máquina que
se pase como parámetro deberá comenzar con
6 letras seguidas de 4 cifras, como por ejemplo:
WinSRV1234 o SrvXyz1234.
160
Atributo de validación de un
parámetro
[ValidateRange(<int>,<int>)]
Descripción
Permite asegurar que el valor que se pasa como
parámetro se ha seleccionado de entre un
rango de valores (mínimo y máximo).
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[ValidateRange(0,100)]
[Int]
$Num
)
En este ejemplo, el valor del parámetro deberá
estar comprendido entre 0 y 100.
[ValidateSet(<String[]>)]
Permite asegurar que el valor que se pasa como
parámetro se ha seleccionado de entre una lista
de valores predefinidos.
Sintaxis:
Param(
[Parameter(Mandatory=$true)]
[ValidateSet("Rojo", "Azul",
"Verde")]
[String]
$Color
)
En este ejemplo, el valor del parámetro debe
ser «rojo», «azul» o «verde» para ser aceptado.
[ValidateScript
({ScriptBlock})]
Se utilizará un bloque de script para validar el
valor que se pasa como parámetro. Para que el
valor sea aceptado, el bloque de script debe
devolver el valor $true.
Sintaxis:
Param(
[Parameter()]
[ValidateScript({$_ ­ge (Get­Date)})]
[DateTime]
$EventDate
)
En este ejemplo, para que el valor del
parámetro sea aceptado, tiene que: 1) ser de
tipo DateTime y 2) ser igual o superior a la
fecha en curso.
161
Scripts
Los scripts tienen un funcionamiento idéntico a las funciones en el sentido de que también pueden
aprovechar el atributo [CmdletBinding] y todo lo que caracteriza a las funciones avanzadas, utilizar
parámetros, proponer una ayuda integrada (como veremos a continuación), etc. La visión de Microsoft y en
particular del equipo de PowerShell es permitir a los desarrolladores PowerShell realizar scripts que se
comporten exactamente como comandos PowerShell nativos.
Por otra parte, los scripts PowerShell constituyen el entorno físico donde se almacenan generalmente las
variables así como un conjunto de funciones. Tienen la extensión .ps1.
Sea cual sea la versión de PowerShell, también con la versión 5, todos los scripts tienen la extensión
.ps1.
1. Estructuración de un script
Un script se estructura generalmente con las siguientes secciones:
# Nombre del script, autor, versión, etc.
Declaración de parámetros
...
Declaración de funciones
...
Cuerpo principal del script
...
Dicho esto, en su forma más minimalista, basta con que el script contenga el cuerpo principal, siendo el
resto opcional. Generalmente encontraremos en las primeras líneas del script algunos comentarios
describiendo brevemente:
El nombre del script,
Un número de versión,
La fecha de creación,
El nombre del autor.
Buena práctica: si desea aportar información adicional más precisa acerca del funcionamiento
del script, por ejemplo sobre el uso de los parámetros, así como eventualmente proporcionar
algún ejemplo de uso, le recomendamos facilitar una ayuda estructurada como se define en la
sección de ayuda integrada a los scripts y funciones. Así la ayuda estará disponible para todos sus
usuarios utilizando la línea de comandos siguiente: help MiScript.ps1. Todo script que se
precie, un entorno empresarial, debería proporcionar una ayuda integrada.
2. Comentarios
Seguramente ya lo sepa, mediante los diferentes ejemplos que le hemos suministrado, los comentarios
empiezan siempre por el carácter almohadilla «#». Un comentario puede estar situado en cualquier sitio en
un script.
162
Por ejemplo:
# MiScript.ps1
# Declaración de variables
[int]$numArch = 0 # número de objetos de la carpeta actual
# Principio del script
$numArch = (Get­Childitem).count
$numArch # devuelve el resultado encontrado
También es posible crear bloques de comentarios gracias a la sintaxis siguiente:
<#
Todo lo que se encuentra en el interior de un bloque
de comentarios se ignora
#>
Los bloques de comentarios son muy prácticos para comentar un gran número de líneas en el interior de
un script. Esto evita tener que anteponer una almohadilla delante de cada línea de comentario.
3. Ejecución de un script
Esto puede parecer sorprendente a primera vista, pero debe saber que la ejecución de scripts en
PowerShell está desactivada por defecto en todas las versiones cliente de Windows. Por el contrario, en
Windows Server, a partir de Windows Server 2012 R2, la ejecución está autorizada, pero solo bajo ciertas
condiciones.
Si no es el caso en su equipo, es porque el administrador de su empresa probablemente ha configurado
una directiva de grupo (GPO) para autorizar la ejecución de scripts.
Para saber si puede o no ejecutar scripts, puede:
Intentar ejecutar un script y ver el resultado,
Utilizar el comando
Get­ExecutionPolicy para determinar la estrategia de ejecución actual.
Si el resultado de la ejecución del comando
puede ejecutar scripts.
Get­ExecutionPolicy es Restricted, significa que no
La ejecución de comandos PowerShell tecleada directamente en la consola (modo interactivo) sí está
autorizada sea cuál sea la directiva de ejecución.
Para modificar la directiva de ejecución, debe:
Arrancar la consola en modo Administrador (botón derecho en el icono PowerShell, después Run
as Administrator),
Teclear el comando
La directiva
Set­ExecutionPolicy RemoteSigned.
RemoteSigned autoriza únicamente la ejecución de scripts en local; los scripts que
provienen de un servidor situado fuera de su zona de confianza intranet como los de Internet serán
bloqueados. Para saber más sobre seguridad en general, pero también sobre las demás directivas de
ejecución disponibles, consulte el capítulo Seguridad.
163
4. La directiva #Requires
La directiva
script.
#Requires permite asegurar la existencia de ciertos requisitos para permitir la ejecución del
Así es posible asegurarse de:
Que PowerShell se ejecuta en una versión mínima.
Que PowerShell se ejecute en una edición específica (Desktop o Core).
Que PowerShell se ejecuta en modo Administrador.
La presencia de ciertos módulos y snap­ins así como de los números de versiones.
Los diferentes casos de uso son los siguientes:
#Requires
#Requires
#Requires
#Requires
#Requires
#Requires
­Version <N>[.<n>]
­PSEdition [ Core | Desktop ]
­PSSnapin <PSSnapin­Name> [­Version <N>[.<n>]]
­Modules { <Module­Name> | <Hashtable> }
­ShellId <ShellId>
­RunAsAdministrator
Ejemplo:
Tomemos un script que comience así:
#Requires ­Version 5
#Requires ­Modules PSWorkflow,
@{ModuleName="DataOnTap";ModuleVersion=3.2.0.0}
#Requires ­PSSnapin DiskSnapin ­Version 1.2
#Requires ­RunAsAdministrator
Hemos forzado voluntariamente un poco las cosas ya que es raro verificar tantos requisitos a la vez. Sin
embargo sepa que puede especificar varias directivas #Requires en un mismo script. Las directivas
deben sistemáticamente aparecer al principio de línea y empezar con una almohadilla.
Los números de versión que hemos especificado, tanto para el snap­in como para el módulo, son
opcionales.
5. Toma de contacto del entorno de ejecución (contexto)
Cierta información puede resultar importante al ejecutar un script, como:
la ruta del script actual,
el nombre del script actual,
la línea de comandos completa que ha permitido ejecutar el script,
etc.
Cuando desarrollamos scripts de cierta entidad, es habitual tener que crear archivos de log y poder
«loguear» la línea de comandos completa (con los parámetros) que ha permitido la ejecución del script. Es
una información de importancia capital.
Para ello, PowerShell pone a nuestra disposición numerosas variables automáticas. Las resumimos en la
siguiente tabla:
164
Descripción
Variable
$PSCommandPath
Contiene la ruta completa del script actual.
$PSScriptRoot
Contiene la carpeta en la que se encuentra el
script actual.
$MyInvocation
Objeto que contiene la integridad
contexto de ejecución actual.
$MyInvocation.MyCommand.Path
Ídem $PSCommandPath.
$MyInvocation.MyCommand.line
Contiene la línea de comandos completa
indicada
para
la
ejecución
del script
(incluyendo todos los parámetros).
$MyInvocation.PSCommandPath
Contiene la ruta completa del script padre que
ha llamado al script hijo (este objeto solo
contiene un valor si la llamada al script hijo se
realiza a través de un script padre).
$MyInvocation.PSScriptRoot
Contiene la carpeta en la que se encuentra el
script padre que ha realizado la llamada al
script hijo (este objeto solo contiene un valor
si la llamada al script hijo se realiza a través
de un script padre).
del
Como puede ver, no es fácil encontrarse con tantas variables a nuestra disposición. Como los ejemplos
valen
más
que
mil
palabras,
creamos
el
pequeño
script
siguiente
que
llamaremos
testInvocacion.ps1 y que almacenaremos en la ruta C:\Temp:
# testInvocacion.ps1
Param ($a, $b, $c)
"`$PSCommandPath = $PSCommandPath"
"`$PSScriptRoot = $PSScriptRoot"
"`$MyInvocation.MyCommand.Path = $($MyInvocation.MyCommand.Path)"
"`$MyInvocation.MyCommand.line = $($MyInvocation.line)"
"`$MyInvocation.PSCommandPath = $($MyInvocation.PSCommandPath)"
"`$MyInvocation.PSScriptRoot
= $($MyInvocation.PSScriptRoot)"
$MyInvocation | Format­List * ­force
Este script nos mostrará el contenido de cada una de estas variables y esto nos ayudará a comprender
mejor el rol de las mismas.
Ahora, nos situamos en otra carpeta diferente a aquella donde se encuentra el script, por ejemplo en la
carpeta C:\Program Files, y ejecutamos el script testInvocacion.ps1 de la siguiente manera:
PS > ..\Temp\testinvocacion.ps1 ­a ’Hola’ ­b 22
$PSCommandPath = C:\Temp\testInvocacion.ps1
$PSScriptRoot = C:\Temp
$MyInvocation.MyCommand.Path = C:\Temp\testInvocacion.ps1
$MyInvocation.MyCommand.line = ..\Temp\testinvocacion.ps1 ­a
’Hola’ ­b 22
$MyInvocation.PSCommandPath =
$MyInvocation.PSScriptRoot
=
165
MyCommand
BoundParameters
UnboundArguments
ScriptLineNumber
OffsetInLine
HistoryId
ScriptName
Line
PositionMessage
:
:
:
:
:
:
:
:
:
PSScriptRoot
PSCommandPath
InvocationName
PipelineLength
PipelinePosition
ExpectingInput
CommandOrigin
DisplayScriptPosition
:
:
:
:
:
:
:
:
testInvocacion.ps1
{[a, Hola], [b, 22]}
{}
1
1
194
..\Temp\testinvocacion.ps1 ­a ’Hola’ ­b 22
At line:1 char:1
+ ..\Temp\testinvocacion.ps1 ­a ’Hola’ ­b 22
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
..\Temp\testinvocacion.ps1
1
1
False
Runspace
Enseguida nos damos cuenta de que el objeto
$MyInvocation.MyCommand.Path contiene la ruta
absoluta completa del script actual y que, todavía mejor, el objeto $MyInvocation.MyCommand.line
contiene la línea de comandos que ha permitido la ejecución del script.
también
que
$MyInvocation.PSCommandPath
y
$MyInvocation.PSScriptRoot están vacías por el momento. Es normal en la medida en que la
Podemos
observar
llamada al script ha sido directa, es decir desde la línea de comandos.
Veamos
qué
ocurre
ahora
si
creamos
un
segundo
script
cuya
función
sea
ejecutar
testInvocacion.ps1. Llamaremos a este script lanzadera.ps1 y lo almacenaremos en la carpeta
Documents.
# lanzadera.ps1
& C:\Temp\testinvocacion.ps1 ­a 2 ­b 1
C:\
y
tecleamos
el
comando
C:\Users\Administrador\Documents\lanzadera.ps1 para ejecutar el script como se muestra
Ahora,
accedemos
a
la
raíz
de
disco
a continuación:
PS > C:\Users\Administrador\Documents\lanzadera.ps1
$PSCommandPath = C:\Temp\testinvocacion.ps1
$PSScriptRoot = C:\Temp
$MyInvocation.MyCommand.Path = C:\Temp\testinvocacion.ps1
$MyInvocation.MyCommand.line = & C:\Temp\testinvocacion.ps1
­a 2 ­b 1
$MyInvocation.PSCommandPath =
C:\Users\Administrador\Documents\lanzadera.ps1
$MyInvocation.PSScriptRoot = C:\Users\Administrador\Documents
MyCommand
BoundParameters
UnboundArguments
ScriptLineNumber
OffsetInLine
:
:
:
:
:
testinvocacion.ps1
{[a, 2], [b, 1]}
{}
2
1
166
HistoryId
ScriptName
Line
: 15
: C:\Users\Administrador\Documents\lanzadera.ps1
: & c:\Temp\testinvocacion.ps1 ­a 2 ­b 1
PositionMessage
lanzadera.ps1:2
: At C:\Users\Administrador\Documents\
PSScriptRoot
PSCommandPath
InvocationName
PipelineLength
PipelinePosition
ExpectingInput
CommandOrigin
DisplayScriptPosition
:
:
:
:
:
:
:
char:1
+ & c:\Temp\testinvocacion.ps1 ­a 2 ­b 1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\Users\Administrador\Documents
C:\Users\Administrador\Documents\lanzadera.ps1
&
1
: 1
False
Internal
$MyInvocation.PSCommandPath y $MyInvocation.PSScriptRoot están esta vez presentes y
contienen información acerca del script padre que permite la ejecución del script hijo.
6. Internacionalización
En la versión 1 de PowerShell, la edición de scripts destinados al público de distintas nacionalidades era
algo complicado ya que el desarrollador debía traducir manualmente todo el contenido de texto de sus
scripts. En PowerShell v2 apareció lo que llamamos « la internacionalización de script » y esta
funcionalidad no ha evolucionado con las versiones posteriores de PowerShell. El principio de
internacionalización es permitir la escritura de scripts en diferentes idiomas sin tener que modificar el
código contenido en los scripts. Por ejemplo, si crea un script y este lo deben ejecutar varias personas,
cada una utilizando una versión distinta de PowerShell en términos de idioma (variable $PSUICulture
diferente), el hecho de utilizar la internacionalización permitirá a los usuarios obtener toda la parte textual
del script en su idioma, sin tener que realizar manipulación alguna. Veamos ahora como funciona.
La primera precaución es, evidentemente, separar los datos (data) del código del script.
Para ello usaremos una sección Data. En el interior de esta sección usaremos
comando ConvertFrom­StringData, que creará una tabla de hash de cadenas de caracteres.
el
Ejemplo
$TextoScript = Data {
ConvertFrom­StringData @’
Mensaje_1 = Hola
Mensaje_2 = Introduzca un valor
Mensaje_3 = Introduzca otro valor
Mensaje_4 = El resultado de la suma de estos dos valores es:
’@
}
Tenga en cuenta que aquí utilizamos una Here­String. Una Here­String empieza con un separador
@’ y termina por ’@ (el último separador debe obligatoriamente estar precedido de un retorno de carro).
Todos los caracteres entre los delimitadores arrobas se consideran como texto.
Así permitimos la traducción de las secciones Data. Una traducción en diferentes idiomas debe
almacenarse en archivos .psd1 y con un árbol de carpetas particular.
167
Los archivos .psd1 se deben guardar en una subcarpeta con el formato <idioma>­<País>, como
indica la variable $PSUICulture. Así si el script principal llamado MiScript.ps1 se encuentra en la
carpeta
C:\Temp, los archivos MiScript.psd1 se encontrarán en las carpetas siguientes:
C:\Temp\MiScript.ps1
C:\Temp\en­US\MiScript.psd1
C:\Temp\es­ES\MiScript.psd1
...
# Script Principal
# Archivo de datos traducidos en inglés
# Archivo de datos traducidos en español
Ejemplo de contenido de los archivos .psd1
Archivo C:\Temp\en­US\MiScript.psd1:
ConvertFrom­StringData @’
Mensaje_1 = Hello
Mensaje_2 = Enter a value
Mensaje_3 = Enter a second value
Mensaje_4 = The result of the addition of these two values is:
’@
Archivo C:\Temp\es­ES\MiScript.psd1:
ConvertFrom­StringData @’
Mensaje_1 = Hola
Mensaje_2 = Introduzca un valor
Mensaje_3 = Introduzca otro valor
Mensaje_4 = El resultado de la suma de estos dos valores es:
’@
La última etapa es permitir la importación de las cadenas de caracteres en el idioma de la interfaz de
usuario con el comando Import­LocalizedData. El uso de este comando importa el archivo .psd1
correspondiente al idioma utilizado, y hace transparente las acciones de traducción de los elementos
textuales del script. El script completo se transforma en el siguiente:
$TextoScript = Data {
#Región es­ES
ConvertFrom­StringData @’
Mensaje_1 = Hola
Mensaje_2 = Introduzca un valor
Mensaje_3 = Introduzca otro valor
Mensaje_4 = El resultado de la suma de estos dos valores es:
’@
}
Import­LocalizedData TextoScript
# Principio del script
Write­Host
Write­Host
[int]$var1
Write­Host
[int]$var2
$resultado
Write­Host
$TextoScript.Mensaje_1
$TextoScript.Mensaje_2
= Read­host
$TextoScript.Mensaje_3
= Read­host
= $var1 + $var2
"$($TextoScript.Mensaje_4) $resultado"
168
# Fin del script
Ejecutando el anterior script en un equipo Windows en versión US, obtendremos el siguiente resultado:
PS > C:\Temp\MiScript.ps1
Hello
Enter a value
5
Enter a second value
6
The result of the addition of these two values is: 11
DotSourcing
Llamamos «DotSourcing» al procedimiento que consiste en anteponer un punto y un espacio al nombre de
un script (y su ruta completa o relativa, según el caso) o a un bloque de instrucciones. Esta técnica permite
ejecutar el contenido de un script en el contexto actual. De esta forma, toda variable o función puede
reutilizarse, a continuación, durante toda la vida del contexto. Tomemos como ejemplo el siguiente script
que solo contiene funciones.
# funciones.ps1
Function Invoke­WakeUp
{
Write­Host ’¡Hola y buenos días!’ ­f Yellow
}
Function Get­CTempFiles
{
Get­ChildItem ­Path C:\Temp
}
Function Get­CPUTime
{
Get­Process | Where­Object {$_.CPU ­gt 500}
}
Al ejecutar este script de manera clásica, ninguna de las tres funciones no sería reutilizable, simplemente
porque el contexto creado para la apertura del script se ha finalizado con él. Además este script no hace
nada porque no realiza llamadas a las funciones declaradas en él.
PS > ./funciones.ps1
PS > Invoke­WakeUp
Despertador : The term ’Invoke­WakeUp’ is not recognized as the name of
a cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is correct
and try again.
...
La función Invoke­WakeUp no se reconoce ya que se ejecutó en el ámbito del script. Como el ámbito del
script se ha destruido después de su ejecución, es normal que la función no exista en el contexto actual.
Sin embargo, si ahora llamamos a este script utilizando la técnica de DotSourcing, es decir con un punto
delante y un espacio, las funciones estarán esta vez disponibles después de la ejecución del script.
169
Ejemplo
PS > . ./funciones.ps1
PS > Invoke­WakeUp
¡Hola y buenos días!
La técnica del DotSourcing pierde terreno frente a los módulos. Se ha conservado por retro
compatibilidad. Sin embargo, la tendencia es ahora claramente desarrollar módulos. Estos tienen la
ventaja de agrupar un conjunto de funciones de una misma familia de manera estructurada. Además son
fácilmente distribuibles a otras personas.
Ayuda integrada a los scripts y funciones
Cuando desarrollamos funciones o scripts que deben utilizar otras personas, como por ejemplo en el marco
del desarrollo de un módulo agrupando un conjunto de funciones, puede ser útil facilitar una ayuda.
En vez de escribir toneladas de documentación en forma de documentos Word asociados a nuestros
módulos, scripts o funciones, PowerShell ofrece una funcionalidad extremadamente interesante: la
posibilidad de incluir la ayuda directamente en el código, o fuera de él en un archivo XML (respetando la
gramática de MAML).
La ventaja de suministrar la ayuda en nuestros scripts o funciones es que confiere un carácter profesional a
nuestro trabajo, con un mínimo de esfuerzo. En efecto los scripts o funciones, disponiendo de ayuda, se
comportan exactamente como los comandos nativos. Los usuarios dispondrán, por lo tanto, para cada una
de sus realizaciones tres niveles de detalle, a saber: la ayuda estándar, detallada o completa.
Volvamos a utilizar la función
Show­Popup estudiada anteriormente.
Function Show­Popup
{
param([string]$mensaje=’Mensaje...’, [string]$titulo=’Título’)
$WshShell = New­Object ­ComObject wscript.Shell
$WshShell.Popup($mensaje, 0, $titulo)
}
Para empezar, cargamos la función en nuestra sesión y pedimos la ayuda para ver qué nos devuelve
PowerShell.
170
Visualización de la ayuda de la función
Show­Popup
Podemos observar que PowerShell devuelve incluso cierta información interesante acerca de nuestra
función como por ejemplo su nombre así como sus parámetros y el tipo esperado de los mismos. Esto es
posible gracias a que PowerShell efectúa un análisis del bloque Param. Por lo tanto es interesante dar
sistemáticamente un tipo a cada parámetro (buena práctica recomendada).
Ahora transformamos ligeramente nuestra función
Show­Popup para incluir algo de ayuda.
Function Show­Popup
{
<#
.SYNOPSIS
Muestra un mensaje en una ventana "Popup".
.DESCRIPTION
La función Show­Popup se basa en el objeto COM wscript.shell
de Windows Script Host para interactuar con el usuario.
.PARAMETER Titulo
Título de la ventana Popup. Si este parámetro no es especificado,
entonces el título por defecto tomará el valor "Título".
.PARAMETER Mensaje
Mensaje que aparacera en el cuerpo principal del mensaje.
Si este parámetro no es especificado entonces el mensaje tomará
por defecto el valor "Mensaje".
.EXAMPLE
Show­Popup ­mensaje "¡ El abuso de PowerShell perjudica la salud !"
.EXAMPLE
Show­Popup ­titulo Hola ­mensaje "¡ El abuso de PowerShell perjudica
la salud !"
#>
param([string]$mensaje=’Mensaje...’, [string]$titulo=’Título’)
$WshShell = New­Object ­ComObject wscript.Shell
$WshShell.Popup($mensaje, 0, $titulo)
}
Así, si pedimos la ayuda estándar de nuestra función (después de volverla a cargar), obtenemos lo
siguiente:
171
Visualización de la ayuda estándar de la función
Show­Popup
Ahora la ayuda detallada:
172
Visualización de la ayuda detallada de la función
Como
habrá
comprendido,
bajo
cada
palabra
clave
que
Show­Popup
empieza
por
un
punto
(por
ejemplo
.SYNOPSIS) debemos indicar la información correspondiente, como el resumen, la descripción, el rol de
cada parámetro, etc. El conjunto de la ayuda está incluido dentro de un bloque de comentarios. Tenga
precaución, ¡pues cualquier fallo en la escritura de una palabra clave no se mostrará!
Sepa que es posible añadir incluso más detalle en la ayuda integrada, como el autor, la versión, etc. Toda
esta información complementaria se mostrará cuando se solicite la ayuda completa (parámetro ­Full de
Get­Help).
173
Para
obtener
más
información
about_Comment_Based_Help.
sobre
este
asunto,
consulte
la
sección
de
ayuda
Si prefiere usar archivos XML externos al sistema de ayuda integrada, sepa que existe una
herramienta llamada Cmdlet Help Editor que le facilitará enormemente la tarea. Se puede
descargar en la dirección siguiente: http://cmdlethelpeditor.codeplex.com
174
Gestión de
archivos y
fechas
175
La gestión de archivos
La gestión de archivos es muy fácil con PowerShell, a diferencia de lo que sucede con VBScript, para quien
haya tenido ocasión de practicar con este. En efecto, ya no es necesario instanciar objetos COM de tipo
filesystem, abrirlos especificando un modo de acceso (lectura o escritura), para después cerrarlos.
PowerShell dispone de un juego de comandos dedicados a la gestión de archivos y veremos que
representan una ganancia enorme de productividad en la escritura de scripts.
Dicho esto, hay un aspecto en particular al que se debería prestar atención y del que habría que saber que
se hace para evitar problemas. Se trata del formato de codificación de los archivos de texto. Son muchos y
no conviene equivocarse; detallaremos este punto en este capítulo.
En el capítulo Descubrimiento de PowerShell, nos interesamos en el continente, es decir en el archivo en sí,
y habíamos visto cómo crearlo, desplazarlo, renombrarlo, etc. Ahora nos interesamos en el contenido y
veremos entre otras cosas cómo generarlo y cómo leerlo.
1. Formatos de codificación de los archivos de texto
Antes de entrar de lleno en materia, nos parece necesario dedicar algunos minutos a introducir este tema
tan vasto e importante: la codificación de archivos de texto.
Sin entrar en los detalles de la prehistoria informática, comenzaremos hablando del estándar ASCII,
conocido por todos. Este, aparecido con los primeros ordenadores a finales de los años 60 en Estados
Unidos, codifica los caracteres en 7 bits (de la posición 0 a la 127), lo que da, un total de 128 caracteres
que se agrupan en una tabla que se ha vuelto célebre, la famosa tabla ASCII. El problema que presenta
esta norma para nosotros, castellanohablantes (y no solo para nosotros), es que no contiene nuestros
caracteres acentuados… Lo cual es lógico, puesto que ASCII se inventó por un organismo americano para
los americanos.
ASCII extendido (ANSI), una solución intermedia
Dado un problema, dada una solución; y en informática los procesadores trabajan sobre potencias de 2,
de modo que no es posible escribir un carácter salvo en 8 bits (1 byte). Esto significa que existía un bit
inutilizado en la codificación de caracteres en formato ASCII. Los informáticos de la época decidieron usar
este último bit para extender la tabla ASCII y así poder almacenar los caracteres acentuados en las
distintas lenguas. Esta técnica dio pie al formato ASCII extendido, también conocido como ANSI. De este
modo, se creó lo que conocemos como «páginas de códigos» para almacenar los caracteres acentuados
del español, el francés, el alemán y otros. Si bien resultó relativamente eficaz, esta solución incorporó
cierta complejidad de configuración puesto que, en función del idioma, había que escoger la página de
códigos adecuada.
El mundo funcionó así durante muchos años, hasta principios de la década de los 90 y la aparición de
Internet, que lo revolucionó todo al basarse en ordenadores compatibles con varios idiomas
simultáneamente. En efecto, ¿quién habría aceptado que los caracteres acentuados del español no se
mostraran correctamente en un sistema operativo inglés, francés o chino, y viceversa? Así es como hizo su
aparición el estándar Unicode, y la solución se hizo más robusta desde un punto de vista técnico...
Unicode y sus variantes
Unicode se ha impuesto rápidamente como una solución eficaz a los problemas de codificación. Gracias a
él, existen muchísimos formatos Unicode, así como muchísimas variantes. Hemos incluido en la siguiente
tabla los formatos más comunes que manipularemos con PowerShell:
176
Tipo de
codificación
Formato
Unicode
Peso de un carácter
UTF­8
8 bits
8 bits para un carácter «estándar», variable para los
caracteres acentuados.
UTF­16
16 bits
16 bits.
UTF­32
32 bits
32 bits.
Encontramos también UTF­7, pero este formato jamás ha formado parte oficialmente del estándar.
Además, para los castellanohablantes, no presenta ningún interés en la medida en que no podemos
utilizarlo para codificar nuestros acentos. En lo que respecta a UTF­16 y UTF­32, como habrá comprendido,
requieren respectivamente 2 y 4 bytes para almacenar un carácter, lo cual obliga necesariamente a
duplicar o cuadruplicar el tamaño de los archivos codificados en estos formatos respecto a los de ASCII
puro o ASCII extendido.
En cuanto a UTF­8, esta codificación es interesante, puesto que todos los caracteres estándar, es decir,
los de la tabla ASCII, se codifican con 8 bits, mientras que los caracteres especiales (todos los demás)
pueden codificarse sobre 2 o 4 bytes (lo que deja un margen). Este formato está, por tanto, equilibrado,
puesto que permite codificar la mayoría de los caracteres del planeta manteniendo un tamaño compacto.
¿Big Endian o Little Endian?
Dediquemos un momento a los formatos UTF­16 y UTF­32. Estos codifican los caracteres en varios bytes, lo
cual plantea inevitablemente la cuestión del orden en la disposición de los bytes. ¿Hay que escribir el byte
de mayor peso antes que el byte de menor peso o al revés? Es un poco como si todos los humanos del
planea hablaran un idioma común, pero algunos países escribieran este idioma de derecha a izquierda,
mientras que otros lo escribieran de izquierda a derecha. En informática, esta noción que define el orden
de codificación de los bytes se denomina endianness; no existe un término equivalente en español.
Se distinguen dos variantes de codificación Unicode, Little Endian y Big Endian. La mayoría de los OS
actuales (Windows, Linux, Mac OS) han adoptado la norma Little Endian, pero ciertos trabajan todavía con
Big Endian (HP­UX, AIX, iSeries, zSeries, etc.).
Byte Order Mark (BOM) de los archivos Unicode
Por último, para seguir con esta explicación acerca de los distintos formatos de codificación de archivos de
texto, nos queda por abordar la noción de Byte Order Mark (BOM), traducida a menudo por Marca de
orden de bytes.
El BOM es una serie de bytes que permiten indicar el formato de codificación de un archivo de texto. El
BOM, cuando está presente, se sitúa siempre al inicio de un archivo. Permite señalar a programas como los
editores de texto en qué formato se encuentra escrito un archivo para poder abrirlo correctamente.
Formato Unicode
Byte Order Mark
UTF­8
EF BB BF
UTF­16
FF FE
UTF­32
FF FE 00 00
Para simplificar la tabla, los BOM indicados aquí lo son para la variante Little Endian, por defecto para un
sistema Windows.
Recuerde que la noción de Endianness solo se aplica a los formatos Unicode UTF­16 y UTF­32.
177
Para visualizar el BOM de un archivo de texto, podemos utilizar el comando
Format­Hex.
Ejemplo: BOM de un archivo UTF­8
PS > ’É­ù­è­é’ | Out­file ./UTF8BOM.txt ­Encoding utf8BOM
PS > Format­Hex ­Path .\UTF8BOM.txt
Path: C:\temp\UTF8BOM.txt
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000
El comando
EF BB BF C3 89 2D C3 B9 2D C3 A8 2D C3 A9 0D 0A
ɭù­Ã¨­Ã©..
Format­Hex resulta muy práctico, pues permite leer el contenido de cualquier tipo de
archivo. Se comporta un poco como un editor hexadecimal, salvo que no se puede editar el contenido.
Podríamos asimilarlo, por tanto, a un visor de archivos hexadecimal.
Intentémoslo con otro tipo de archivo, como por ejemplo un archivo PDF.
PS > Format­Hex ­Path .\MANUEL_V2.pdf | Select­Object ­First 10
Path: C:\temp\MANUEL_V2.pdf
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000
00000010
00000020
00000030
00000040
00000050
00000060
00000070
00000080
00000090
25
34
6E
37
38
39
33
20
30
30
50
34
65
38
30
32
38
20
30
30
44
32
61
30
38
31
5D
0D
0D
30
46
36
72
31
38
35
3E
0A
0A
30
2D
20
69
2F
2F
2F
3E
78
30
20
31
30
7A
4F
4E
48
0D
72
30
6E
2E
20
65
20
20
20
65
65
30
0D
36
6F
64
34
31
5B
6E
66
30
0A
0D
62
20
34
33
20
64
0D
30
30
25
6A
31
33
38
34
6F
0A
30
30
E2
20
2F
31
2F
34
62
34
30
30
E3
3C
4C
2F
54
30
6A
34
30
30
CF
3C
20
45
20
36
0D
32
31
30
D3
2F
38
20
38
20
20
36
36
30
0D
4C
39
31
38
33
20
20
20
37
0A
69
36
31
37
32
20
32
30
38
%PDF­1.6.%âãÏÓ..
4426 0 obj <</Li
nearized 1/L 896
7801/O 4431/E 11
8088/N 138/T 887
9215/H [ 4406 32
38]>>.endobj.
..xref..4426 2
00..0000000016 0
0000 n..00000078
Observe que, para limitar el número de filas del resultado, hemos invocado el comando
Select­
Object.
Conviene saber que los primeros bytes de un archivo binario permiten siempre identificar el tipo
de archivo. Es su firma. También se conoce como «número mágico» o «magic number» en inglés.
En el caso de un archivo PDF, su firma es la serie de bytes 25, 50, 44, 46.
2. Diferencias entre Windows PowerShell 5.x / PowerShell Core
En el pasado, el equipo de desarrolladores de PowerShell no había sido muy riguroso en cuanto a la
codificación de los archivos generados por los distintos comandos PowerShell que producían archivos de
texto. Por ejemplo, de manera predeterminada con Windows PowerShell 5.x, el comando Out­File
codificaba los archivos en UTF­16, mientras que el comando Export­CSV los creaba en UTF­8 y el
comando Set­Content los generaba en ANSI. Esta disparidad en los formatos de codificación creó
bastante confusión y planteaba dificultades a muchos desarrolladores de scripts... Afortunadamente, esta
época ha pasado. En la actualidad, PowerShell Core corrige los errores del pasado y todos los archivos
generados por los distintos comandos están en Unicode UTF­8 sin BOM.
178
¿Por qué sin BOM, se preguntará? Pues simplemente porque la mayoría de las plataformas, además de
Windows, utilizan este formato de codificación por defecto. PowerShell Core, al ser multiplataforma, se ha
decidido por este formato. Además, la mayoría de las herramientas y aplicaciones de Windows migran
poco a poco a esta codificación.
Último punto de atención: tenga precaución cuando utilice el parámetro
­Encoding, pues los valores
pueden variar ligeramente de un comando a otro con Windows PowerShell. Además, entre PowerShell
Core y Windows PowerShell, para este mismo parámetro los valores no son exactamente los mismos.
Entramos aquí, por tanto, en una zona en la que pueden aparecer potencialmente problemas de
compatibilidad entre dos versiones de PowerShell. Esto significa que hay que ser prudente...
3. Buenas prácticas
Tenga precaución respecto al formato de codificación de sus archivos de texto. Este precioso consejo le
evitará bastantes dolores de cabeza. De forma general, cuando cree un archivo con cualquier comando, le
recomendamos indicar siempre su codificación mediante el parámetro ­Encoding. Este consejo adquiere
todo su sentido con Windows PowerShell (consulte la sección anterior).
En términos de escoger el formato de codificación, el formato UTF­8 constituye una opción interesante,
puesto que es compacto y permite codificar todos los caracteres especiales del español (y no solo estos).
4. Escritura de datos en un archivo
Existen esencialmente dos formas de escribir datos en un archivo. Podemos utilizar
Out­File.
Set­Content u
Aunque estos dos comandos realizan la misma acción: crear archivos y datos, existe sin embargo una
diferencia que no se encuentra fácilmente.
Cuando utilizamos Out­File, este comando intenta, como los demás comandos
flujo antes de escribirlo en el archivo.
En cuanto a
Out­*, formatear el
Set­Content, no formatea el flujo sino que solamente le aplica el método ToString()
para asegurarse la escritura de los caracteres. Esta es la principal diferencia. Sin embargo, aunque pueda
parecer anodina, tendrá sorpresas si intenta escribir un objeto en un archivo con Set­Content sin
haberlo formateado antes.
Por ejemplo, el resultado de este comando escribe en el archivo el nombre del tipo de objeto en vez de su
contenido:
PS > Get­Process powershell | Set­Content .\MiArchivo.txt
PS > Get­Content .\MiArchivo.txt
System.Diagnostics.Process (powershell)
Mientras que con el siguiente comando, obtenemos el resultado deseado:
PS > Get­Process powershell | Out­File .\MiArchivo.txt
PS > Get­Content .\MiArchivo.txt
Handles
­­­­­­­
533
NPM(K) PM(K) WS(K)
­­­­­­ ­­­­­ ­­­­­
13 64608 65376
VM(M)
­­­­­
219
CPU(s)
Id
­­­­­­ ­­­­
38,39
2080
ProcessName
­­­­­­­­­­­
powershell
179
Para obtener el mismo resultado con
Set­Content, hubiésemos tenido que realizar una conversión de
tipos sobre el objeto antes de escribirlo. Esto se realiza de la siguiente manera:
PS > Get­Process powershell | Out­String ­Stream |
Set­Content ./MiArchivo.txt
Out­String permite convertir los objetos emitidos representándolos en forma de cadena de caracteres.
El parámetro ­Stream permite enviar al pipeline tantas cadenas como objetos recibidos, en vez de enviar
una cadena única que contiene la representación de todos los objetos.
Si deseamos personalizar el resultado, podríamos escribir lo siguiente:
PS > Get­Process powershell | Format­Table id, processname |
Out­String ­Stream | Set­Content MiArchivo.txt
Otra diferencia es que Out­File formatea la salida tal y como se visualiza en la consola. Es decir que
Out­File inserta retornos de carro y saltos de línea en función del ancho de la ventana PowerShell. No
es el caso con Set­Content.
Set­Content permite escribir directamente a nivel de bytes en un archivo gracias al parámetro ­
Encoding Byte. El valor Byte de este parámetro es propio de Set­Content. No es posible utilizar
este valor de codificación con Out­File. Esto permite manipular archivos que no sean de texto
escribiendo directamente los bytes del mismo.
En resumen, utilizaremos preferentemente
Out­File con los archivos de texto, y Set­Content con los
archivos binarios.
a. Archivos de texto con Out­File
Este potente comando permite crear archivos con contenido. Aparentemente lleva a cabo las mismas
operaciones que los operadores de redirección, salvo que podemos especificar a Out­File un número
de parámetros, cosa que no podemos hacer con los operadores de redirección.
A continuación, presentamos la lista de parámetros del comando utilizados con mayor frecuencia:
Parámetro
Descripción
FilePath <String>
Archivo de destino.
Encoding <String>
Tipo de codificación (por defecto: unicode).
Append <Switch>
Añade el contenido a un archivo existente.
NoClobber <Switch>
Indica que no se debe machacar un archivo existente.
Los posibles valores para el parámetro de codificación
­Encoding son los siguientes:
180
Nombre
Descripción
Ascii
Fuerza la codificación ASCII básica (juego de caracteres 0 a
127, 7 bits). Preste atención, este tipo no contiene los
caracteres con acentos. Para estos caracteres es mejor utilizar
el valor Default o un formato Unicode.
UTF7
Fuerza la codificación Unicode
UTF7.
UTF8
Fuerza la codificación Unicode
UTF8.
UTF8BOM*
Ídem UTF8.
UTF8NoBOM*
Fuerza la codificación en Unicode
Unicode
Fuerza la codificación Unicode
UTF16 LittleEndian.
BigEndianUnicode
Fuerza la codificación Unicode
UTF16 BigEndian.
UTF32
Fuerza la codificación Unicode
UTF32 LittleEndian.
Default
Utiliza la codificación
extendido).
Oem
Utiliza la codificación del fabricante del equipo OEM (Original
Equipment Manufacturer) actual para el sistema operativo.
UTF8 sin BOM.
ANSI actual del sistema
(ASCII
El asterisco significa que este valor solo está presente con PowerShell Core.
Ejemplo
Creación de un archivo
ASCII que contiene información sobre un proceso del sistema.
PS > Get­Process powershell |
Out­File C:\Temp\Process.txt ­Encoding ascii
Este comando crea el archivo ASCII Process.txt en la carpeta C:\Temp. Este archivo contiene el
resultado de la ejecución del comando anterior pasado a través de una pipeline.
Ejemplo 2
Añadir datos a un archivo existente.
PS > Get­Date | Out­File C:\Temp\Process.txt ­Append ­Encoding ascii
En este ejemplo, añadimos datos al archivo que hemos creado en el ejemplo anterior. Tenga mucho
cuidado de siempre especificar el mismo formato de codificación cuando añade datos a un archivo.
PowerShell no le avisará, pero si los formatos de sus datos difieren su archivo no se podrá leer.
Cuando añade datos a un archivo de texto, no olvide nunca tener en cuenta la codificación de este,
por el peligro de hacer que sus archivos no sean accesibles. Una manera simple, cuando no conoce
el origen del archivo y desea añadir datos, es abrirlo con el bloc de notas y hacer como si desease
guardarlo con la opción Guardar como. Así en la parte inferior de la ventana podrá ver una lista
desplegable llamada Codificación que le permitirá elegir la codificación deseada, sabiendo que la
opción propuesta por defecto es la del archivo que ha abierto.
181
Visual Studio Code muestra también en la barra de estado (la inferior) el formato de codificación del
archivo en curso.
b. Redirección del flujo estándar
Creación de archivos
Hemos visto en el capítulo sobre los operadores que existía un operador de redirección: el operador
mayor que >. Este operador representa la forma más simple de crear un archivo. Funciona de forma
idéntica al presente en CMD.exe (salvo por que el tipo de codificación por defecto es Unicode). Por lo
tanto cuando se utiliza de forma clásica, solo se redirige el flujo de salida en el archivo.
Ejemplo
PS > Get­ChildItem C:\Temp > dir.txt
Esta línea de comandos enumera los archivos y carpetas que se encuentran en la carpeta
guarda dicho listado en el archivo
C:\Temp y
dir.txt.
Ningún cambio por lo tanto para los habituales de CMD.exe sobre el funcionamiento de este operador.
Añadir datos a un archivo
Tampoco hay que reseñar ningún cambio para añadir datos ya que se realiza con el operador de
redirección >>. Así gracias a este operador podemos añadir contenido al final de un archivo existente.
Ejemplo
PS > Get­Date >> dir.txt
Esta línea de comandos tiene como resultado añadir la fecha al final del archivo
el contenido anterior del archivo.
dir.txt, preservando
Los operadores de redirección de flujo > y >> llaman en realidad al comando Out­File. Para
comprobarlo, llamaremos al comando Trace­Command (que detallaremos en un próximo capítulo)
para tratar de descubrir qué encontramos en el interior de estos operadores...
Probemos lo siguiente:
PS > Trace­Command ­Name CommandDiscovery ­Expression
{’ab’ > test.txt} ­PSHost
DEBUG : CommandDiscovery Information: 0 : Looking up command: out­file
DEBUG : CommandDiscovery Information: 0 : Cmdlet found: Out­File
...
Vemos aparecer en la última línea Cmdlet found: Out­File, ¡ C.Q.D. ! Pero aún podemos
hacerlo mejor viendo los valores que PowerShell asigna a los diferentes parámetros de Out­File.
182
c. Creación de archivos binarios con Set­Content
Al contrario que con
Out­File, este comando escribe los datos tales como los recibe. La gran fuerza de
Set­Content es poder escribir directamente bytes en un archivo, sea cuál sea el contenido del archivo
(texto o binario). Pero cuidado, Set­Content machaca el contenido del archivo de destino ya que no
posee ninguna opción ­Append como Out­File.
No olvide que
Set­Content pertenece a la familia de comandos *­Content, que contiene:
Add­Content: añade datos a un archivo existente.
Clear­Content: borra los datos presentes en un archivo pero no borra el archivo.
Get­Content: lee el contenido de un archivo. Estudiaremos este comando con detalle un poco
más adelante.
Los parámetros de
Set­Content son:
Descripción
Parámetro
­Path <String[]>
Archivo de destino que recibe los datos.
­Value <Object[]>
Datos a escribir.
­Include <String[]>
Modifica únicamente los elementos especificados.
­Exclude <String[]>
Omite los elementos especificados.
­Filter <String>
Especifica un filtro en el formato o lenguaje del proveedor.
­PassThru <Switch>
Transmite el objeto tratado por este comando al pipeline
después de su ejecución.
­Force <Switch>
Fuerza el comando a ejecutarse sin comprometer la
seguridad, por ejemplo creando una carpeta si no existe.
­Credential
<PSCredential>
Utiliza informaciones de identificación para validar el acceso
al archivo.
­Encoding <String>
Tipo de codificación (valor por defecto:
ANSI).
Los valores posibles para el parámetro de codificación
Nombre
ASCII
default, o sea
­Encoding son los siguientes:
Descripción
Fuerza la codificación
ASCII básica (juego de caracteres 0 a
127, 7 bits).
UTF7
Fuerza la codificación Unicode
UTF7.
UTF8
Fuerza la codificación Unicode
UTF8.
UTF8BOM*
Ídem UTF8.
UTF8NoBOM*
Fuerza la codificación a Unicode
Unicode
Fuerza la codificación Unicode
UTF16 LittleEndian.
BigEndianUnicode
Fuerza la codificación Unicode
UTF16 BigEndian.
UTF8 sin BOM
183
Nombre
Descripción
Byte**
Fuerza la codificación en byte.
String
Utiliza la codificación
Unknown
Ídem Unicode.
ANSI actual del sistema.
El asterisco significa que este valor solo está presente en PowerShell Core.
Dos asteriscos significan que este valor no está disponible con PowerShell Core.
IMPORTANTE: tenga cuidado, ya que los valores de este parámetro no son de hecho los mismos
valores que para el comando Out­File.
Aunque sea posible escribir datos de texto con Set­Content, lo más interesante es la posibilidad de
escribir directamente bytes en un archivo sea cual sea su tipo.
Si envía datos de tipo String a un archivo sin especificar explícitamente la codificación deseada, el
archivo resultante será un archivo ANSI. Es decir un archivo ASCII extendido con la página de
códigos actual para tener en cuenta los caracteres acentuados.
Ejemplo
Envío de datos de texto en un archivo.
PS > ’AAéBB’ | Set­Content ­Path ./test.txt
Esta línea de comandos crea el archivo
tiene este archivo:
test.txt con el formato ANSI. Veamos ahora qué tamaño
PS > Get­Item ­Path ./test.txt
Directory : C:\Temp
Mode
­­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
20/01/2018
12:15
Length Name
­­­­­­ ­­­­
7 test.txt
¿Por qué tenemos un archivo de 7 bytes cuando solo hemos enviado cinco caracteres en su interior?
Llamamos
Format­Hex al recurso para saber con exactitud el contenido de nuestro archivo.
PS > Format­Hex ­Path ./test.txt
Path: C:\Users\Administrator\test.txt
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000
41 41 E9 42 42 0D 0A
AAéBB..
184
41, 42 y E9 son respectivamente los códigos ASCII en notación hexadecimal de los caracteres A, B y
é; hasta aquí todo es normal. Pero podemos constatar que tenemos dos bytes suplementarios al final
del archivo (materializado por los dos puntos) que se han agregado automáticamente. Estos bytes 0D y
0A en hexadecimal o 13, 10 en decimal corresponden a los caracteres CR (Carriage Return)y LF (Line
Feed). Dicho de otro modo, un retorno de carro y un retorno de línea.
Esto es totalmente normal, puesto que bajo el entorno Windows (ya era el caso bajo DOS) cada línea de
un archivo termina con CR y LF. Mientras que en Unix (y derivados), una línea termina únicamente por
LF. Es lo que explica por qué existen problemas de formateo cuando intercambiamos archivos entre
estos dos entornos...
Escribir una serie de bytes «brutos» en un archivo
En el ejemplo siguiente, vamos a intentar escribir una cadena de caracteres en un archivo, pero esta vez
haremos que los caracteres de control CR y LF no se agreguen al final de línea. Para ello, enviaremos los
bytes correspondientes a los códigos ASCII de la cadena a escribir; después especificaremos la
codificación byte para Set­Content.
Ejemplo
Escritura de un flujo de bytes en un archivo sin CR LF.
PS > [byte[]][char[]]’AAéBB’ | Set­Content test.txt ­Encoding byte
AAéBB en un array de caracteres, que convertimos a su vez en un
array de bytes. A continuación pasaremos el resultado a Set­Content donde tenemos cuidado de
añadir el parámetro ­Encoding byte.
Haciendo esto, convertimos la cadena
Conversión de archivos Unix a archivos Windows (Unix2DOS)
En el último ejemplo, crearemos un pequeño convertidor de archivos de texto en formato Unix al formato
DOS/Windows.
Ejemplo
Convertir un archivo de texto Unix en DOS.
# Convert­Unix2Dos.ps1
[CmdletBinding()]
Param (
[parameter(Mandatory=$true)]
[string]$path,
[parameter(Mandatory=$false)]
[string]$destino=$path
)
$tab = Get­Content $path ­Encoding byte
for ($i=0;$i ­lt $tab.length; $i++)
{
if ($tab[$i] ­eq 10)
{
$tab = $tab[0..$($i­1)] + [byte]13 + $tab[$i..$tab.length]
$i++
}
}
$tab | Set­Content ­Path $desting ­Encoding Byte
185
Este pequeño script añade el carácter de control CR (13 decimal) delante de cada carácter LF (10
decimal). De este modo,
la sucesión de bytes siguiente: 68
se transformará en: 68
74 57 98 102 10 65 66 48 10 125 139 78
74 57 98 102 13 10 65 66 48 13 10 125 139 78
Almacenamos el contenido del archivo de origen bajo la forma de una sucesión de bytes en el array
$tab. Después, recorremos la totalidad del array $tab buscando el carácter LF. Cuando encontramos
uno, concatenamos al principio de nuestro array el carácter CR y también al final del mismo. Después
insertamos el nuevo contenido en el array $tab. En definitiva, eliminamos en cada iteración el contenido
de
$tab por un contenido nuevo modificado. Lo hacemos así porque no existe ningún método para
insertar un elemento en una ubicación determinada dentro de un array. Al final incrementamos nuestra
variable índice en una posición ya que hemos añadido un elemento en $tab; con lo que la condición
siempre sería verdadera y entraríamos en un bucle infinito. Para terminar, para escribirlo, transferimos
nuestro array de bytes mediante una pipeline a Set­Content sin olvidar especificar el tipo de
codificación
byte.
5. Lectura de datos con Get­Content
Como se puede imaginar y como su nombre indica Get­Content nos permite leer el contenido de un
archivo. Este último puede ser de tipo texto o de tipo binario. Get­Content se fija en ello en el momento
en el que indicamos la codificación correspondiente. Por defecto este comando espera leer archivos de
texto.
Los parámetros de
Get­Content son los siguientes:
Descripción
Parámetro
­Path <String[]>
Archivo de origen que contiene los datos a leer.
­TotalCount <Int64>
Número de líneas a leer desde el principio del
archivo. Por defecto se leen todas las líneas (valor
­1).
Alias: First,
Head
­*Tail <Int32>
Número de líneas a leer empezando por el final del
archivo.
Alias: Last
­ReadCount <Int64>
Número
de
líneas
del contenido
enviado
simultáneamente al pipeline, Por defecto se envían
una a una (valor 1). Un valor 0 indica que
queremos enviar todas las líneas de golpe.
­*Delimiter <String>
Especifica el delimitador usado para dividir el
archivo en objetos durante la lectura. Por defecto
se utiliza el carácter de escape `n.
­*Raw <Switch>
Ignora el delimitador estándar y devuelve el
archivo en un solo bloque (en un solo objeto) en
vez de devolver un array de cadenas de
caracteres.
186
Parámetro
Descripción
­*Stream <String>
Recupera el contenido del Alternate Data Stream
especificado (solo se aplica con el formato de
archivo NTFS). Para obtener más detalles, consulte
el capítulo Seguridad.
­Wait <Switch>
Recupera las nuevas líneas de texto añadidas al
archivo (lo verifica cada segundo) y espera
indefinidamente hasta que el usuario presione
[Ctrl] C.
­Include <String[]>
Recupera únicamente los elementos especificados.
­Exclude <String[]>
Omite los elementos especificados.
­Filter <String>
Especifica un filtro en el formato o el idioma del
proveedor.
­Force <Switch>
Fuerza la ejecución del comando sin comprometer
la seguridad.
­Credential <PSCredential>
Usa información de autentificación para validar el
acceso al archivo.
­Encoding <String>
Especifica el tipo de codificación del archivo.
Los posibles valores para la parametrización de la codificación son los siguientes:
Nombre
Descripción
ASCII
Fuerza la codificación ASCII básico (juego de caracteres 0 a
127, 7 bits). Los caracteres acentuados no pertenecen a este
juego de caracteres.
UTF7
Fuerza la codificación Unicode
UTF7.
UTF8
Fuerza la codificación Unicode
UTF8.
UTF8BOM*
Ídem UTF8.
UTF8NoBOM*
Fuerza la codificación a Unicode
Unicode
Fuerza la codificación Unicode
UTF16 LittleEndian.
BigEndianUnicode
Fuerza la codificación Unicode
UTF16 BigEndian.
Byte**
Fuerza la codificación de tipo byte.
String
Utiliza la codificación
Unknown
Ídem Unicode.
UTF8 sin BOM.
ANSI actual del sistema.
El asterisco significa que este valor solo está presente en PowerShell Core.
Dos asteriscos significan que este valor no está disponible con PowerShell Core.
Ejemplo
Funcionalidades básicas.
187
PS > Get­Date > ./misProcesos.txt
PS > Get­Process >> ./misProcesos.txt
PS > Get­Content ./misProcesos.txt ­Totalcount 10
lunes 5 de enero 2015 21:38:09
Handles
­­­­­­­
46
156
137
NPM(K)
­­­­­­
7
10
11
PM(K)
­­­­­
1868
1236
1400
WS(K) VM(M)
­­­­­ ­­­­­
8152
60
3468
46
14840
54
CPU(s)
­­­­­­
0,45
0,23
0,84
Id
­­
2072
328
388
ProcessName
­­­­­­­­­­­
conhost
csrss
csrss
En este ejemplo, creamos un archivo de texto con el operador de redirección «mayor que» (Unicode, por
lo tanto) que contiene la fecha y la hora así como la lista de procesos en ejecución. Después llamamos
Get­Content para leer las diez primeras líneas del archivo.
Manipular un archivo como un array
Cuando se utiliza Get­Content, obtenemos como salida un array de cadenas de caracteres. Como se
trata de un array, resulta muy sencillo manipular su contenido o leer una línea en concreto.
Ejemplo: lectura de la 15ª línea de un archivo
PS > $fic = Get­Content FabulaLaFontaine.txt
PS > $fic[14]
A la hormiga no le gusta compartir ;
Recibimos el contenido del archivo en la variable
$fic y, a continuación recuperamos la línea situada en el
índice 14 del array (en realidad la 15ª línea del archivo ya que recuerde que los índices de los arrays
empiezan en cero).
Además, como una cadena es también un array de caracteres, podemos leer cualquier carácter usando la
sintaxis de los arrays de dos dimensiones. Por ejemplo, la i de la palabra hormiga que se encuentra en el
índice 9:
PS > $fic[14][9]
i
Para finalizar con este ejemplo, si pedimos la propiedad
Length a nuestro array $fic, obtenemos el
número de elementos que lo componen, o sea el número total de líneas de nuestro archivo de texto.
PS > $fic.Length
22
22 es el número de líneas de nuestro archivo.
Lectura de un archivo en modo « texto bruto »
Como le mencionamos en la introducción de este comando, Get­Content sabe leer bytes. Esta
funcionalidad es particularmente interesante para obtener el contenido real de los archivos, sea cual sea
el modo de acceso a bajo nivel del contenido.
188
En efecto, ¿qué diferencia un archivo de texto de un archivo binario? La respuesta es simple: el contenido
o la interpretación de este. En ambos casos, un archivo posee atributos que le caracterizan tales como un
nombre, una extensión, un tamaño, una fecha de creación, etc.
Un archivo de texto contiene, como su homólogo binario, una sucesión de bytes con una estructura
determinada.
Intentemos leer un modo texto bruto un archivo de texto
Unicode, pero antes creemos uno nuevo:
PS > ’PowerShell’ | Out­File .\test.txt ­Encoding unicode
PS > Get­Content .\test.txt ­Encoding byte
255 254 80 0 111 0 119 0 101 0 114 0 83 0 104 0 101 0 108 0 108 0 13 0 10 0
En realidad se muestran los bytes verticalmente, pero para facilitar la lectura y la comprensión del
ejemplo los hemos puesto horizontalmente.
Para mostrar el resultado en una única línea, habríamos podido escribir:
PS > (Get­Content .\test.txt ­Encoding byte) ­join ’ ’
En este contexto, el operador
­join concatena los elementos del array con el carácter espacio.
Un ojo experto en archivos de texto observaría dos cosas:
El archivo empieza por dos bytes a tener en cuenta: 255 y
254.
Todos los caracteres están codificados con dos bytes siendo uno de ellos cero.
Habrá observado también la presencia de los bytes
LF (como hemos visto más arriba en este capítulo).
La presencia de los bytes
13 y 10 al final de línea que corresponden con CR y
255 y 254 se explica por el hecho que todo archivo Unicode empieza por un
encabezado (el BOM del que hemos hablado al principio de este capítulo) cuyo tamaño varía entre 2 y 4
bytes. Esto depende de la codificación elegida (UTF8, UTF16, UTF32).
En este caso, 255
codificado en UTF16
254 (FF FE en notación hexadecimal) significa que tenemos delante un archivo
Little Endian.
La presencia de los ceros se explica por el hecho de que en un archivo
UFT16 todos los caracteres se
codifican con dos bytes.
Ejemplo: Determinar el tipo de codificación de un archivo
La pregunta que nos hacíamos desde hace varias páginas, a saber: «¿cómo reconocer el tipo de
codificación de un archivo de texto?» ha encontrado, por fin, respuesta en el ejemplo anterior. Los
primeros bytes de un archivo nos dan su codificación.
Construyamos por lo tanto un pequeño script como herramienta que nos dirá el tipo de codificación de un
archivo en función de sus primeros bytes.
189
# Get­FileTypeEncoding.ps1
[CmdletBinding()]Param (
[parameter(Mandatory=$true)]
[string]$path
)
# definición
$ANSI=0
Set­Variable
Set­Variable
Set­Variable
Set­Variable
Set­Variable
de variables y constantes
­Name
­Name
­Name
­Name
­Name
UTF8BOM
UTF16LE
UTF16BE
UTF32LE
UTF32BE
­Value
­Value
­Value
­Value
­Value
’EFBBBF’
’FFFE’
’FEFF’
’FFFE0000’
’0000FEFF’
­Option
­Option
­Option
­Option
­Option
constant
constant
constant
constant
constant
$fic = Get­Content ­Path $path ­Encoding byte ­First 4
# Formateo de los bytes leidos sobre 2 car. y conversión en Hexa
# ej : 0 ­> 00, o 10 ­> 0A en vez de A
# y concatenación de los bytes en una cadena para efectuar la
# comparación
[string]$strLeido = [string](’{0:x}’ ­f $fic[0]).PadLeft(2, ’0’) +
[string](’{0:x}’ ­f $fic[1]).PadLeft(2, ’0’) +
[string](’{0:x}’ ­f $fic[2]).PadLeft(2, ’0’) +
[string](’{0:x}’ ­f $fic[3]).PadLeft(2, ’0’)
Switch ­regex ($strLeido){
"^$UTF32LE" {’Unicode UTF32LE’; break}
"^$UTF32BE" {’Unicode UTF32BE’; break}
"^$UTF8BOM" {’Unicode UTF8 with BOM’; break}
"^$UTF16LE" {’Unicode UTF16LE’; break}
"^$UTF16BE" {’Unicode UTF16BE’; break}
default # el archivo no está codificado en Unicode,
{
# profundizamos en el exámen del archivo...
# Búsqueda de un byte cuyo valor es > 127
$fic = Get­Content ­Path $path ­Encoding byte
if ($fic ­gt 127) {
’ANSI’
}
else {
’ASCII’
}
} #fin default
} #fin switch
Este script lee los cuatro primeros bytes del archivo, los formatea (conversión hexadecimal al formato
texto) y los compara a la firma Unicode para determinar el tipo de codificación. Si no lo encontramos
entre los presentes, es que el archivo es de tipo ASCII puro (caracteres US de 0 a 127), o de tipo ANSI
(ASCII extendido, o ANSI+ página de códigos para gestionar los caracteres acentuados).
Este ejemplo no es perfecto, pues no es compatible con los archivos codificados en UTF­8 sin BOM. Para
ello, habíamos podido agregar una comprobación complementaria, una especie de análisis heurístico del
contenido, pues el hecho de no haber BOM significa que no hay un encabezado, de modo que resulta difícil
determinar con exactitud el formato de codificación de un archivo.
El formato UTF8NoBOM es, no obstante, el formato preferente en PowerShell Core, por motivos de
interoperabilidad con las demás plataformas y, por tanto, de homogeneidad de funcionamiento.
190
6. Búsqueda en el contenido de un archivo con Select­String
Gracias a
Select­String podemos mirar a través del contenido de una cadena de caracteres, de un
archivo, o de un gran número de archivo buscando una cadena de caracteres con formato de expresión
regular. Los "Unixianos", que conocen el comando Grep, no estarán sorprendidos ya que Select­
String pretende ser más o menos su equivalente en Windows.
Veamos los parámetros de
Select­String:
Parámetro
Descripción
­Pattern <String[]>
Cadena simple o expresión regular de búsqueda.
­Path <String[]>
Objetivo de la búsqueda: cadena(s) o archivo(s).
­InputObject <PSObject>
Acepta un objeto como entrada.
­Include <String[]>
Recupera únicamente los elementos especificados.
­Exclude <String[]>
Omite los elementos especificados.
­SimpleMatch <Switch>
Especifica que se debe utilizar una correspondencia
simple, mejor que una de expresión regular.
­CaseSensitive <Switch>
Hace que las correspondencias sean sensibles a las
mayúsculas.
­Quiet <Switch>
Reemplaza el resultado del comando por un valor
booleano.
­List <Switch>
Especifica
que
se
debe
devolver
solo
correspondencia para cada archivo de entrada.
­AllMatches <Switch>
Busca varias correspondencias en cada línea de texto.
Sin
este
parámetro,
Select­String busca
únicamente la primera ocurrencia en cada línea de
texto.
­Context <Int32>
Permite seleccionar un número específico de líneas
antes y después de la línea que incluye la
correspondencia (permite así ver el contenido buscado
en su contexto).
­Encoding <String>
Indica la codificación del flujo de texto al que
­NotMatch <Switch>
Indica qué modelo no devuelve la búsqueda. Este
parámetro es muy útil para realizar una búsqueda
inversa (al no seleccionar las líneas basadas en el
modelo) Equivalente a Grep ­v.
una
Select­
String se aplica. Los valores puede ser: UTF7,
UTF8,
UTF32,
Ascii,
Unicode,
BigEndianUnicode, Default o OEM.
Los caracteres acentuados no se tienen en cuenta en las búsquedas en el interior de archivos ANSI.
Por el contrario, todo funciona correctamente con los archivos Unicode.
Ejemplo
Búsqueda simple.
191
PS > Select­String ­Path C:\Temp\*.txt ­Pattern ’hormi’
C:\Temp\CigarraHormiga.txt:8:En casa de la hormiga su vecina,
C:\Temp\ CigarraHormiga.txt:15: A la hormiga no le gusta compartir;
C:\Temp\HormigasUtiles.txt:1:Las hormigas son muy útiles.
En este ejemplo, buscamos la cadena « hormi » entre todos los archivos de texto de la carpeta
C:\Temp.
Como valor devuelto, obtenemos el nombre de los archivos (o del archivo si solo hubiese uno) que
contienen la cadena buscada. Los valores 8, 15 y 1 corresponden al número de línea en el archivo donde
se encuentra una correspondencia.
Algunas veces, cuando los resultados son muchos, es interesante utilizar el conmutador
­List para
especificar al comando que solo devuelva el primer resultado encontrado en el archivo.
Veamos cuál sería el resultado con
­List:
PS > Select­String ­Path C:\Temp\*.txt ­Pattern ’hormi’ ­List
C:\Temp\CigarraHormiga.txt:8:En casa de la hormiga su vecina,
C:\Temp\HormigasUtiles.txt:1:Las hormigas son muy útiles.
Los resultados obtenidos son de tipo Microsoft.PowerShell.Commands.MatchInfo. Así es
posible obtener y manipular un cierto número de informaciones:
PS > $var = Select­String ­Path C:\Temp\*.txt ­Pattern ’hormiga’
PS > $var | Get­Member ­Membertype property
TypeName: Microsoft.PowerShell.Commands.MatchInfo
Name
­­­­
Context
Filename
IgnoreCase
Line
LineNumber
Matches
Path
Pattern
MemberType
­­­­­­­­­­
Property
Property
Property
Property
Property
Property
Property
Property
Definition
­­­­­­­­­­
Microsoft.PowerShell.Commands.MatchInfoContext ...
System.String Filename {get;}
System.Boolean IgnoreCase {get;set;}
System.String Line {get;set;}
System.Int32 LineNumber {get;set;}
System.Text.RegularExpressions.Match[] Matches ...
System.String Path {get;set;}
System.String Pattern {get;set;}
Ahora, intentamos forzar que la visualización se haga en forma de lista:
PS > $var | Format­List
IgnoreCase
LineNumber
Line
Filename
Path
Pattern
Context
Matches
:
:
:
:
:
:
:
:
True
8
En casa de la hormiga su vecina,
CigarraHormiga.txt
C:\Temp\CigarraHormiga.txt
hormiga
{Hormiga}
192
IgnoreCase
LineNumber
Line
Filename
Path
Pattern
Context
Matches
:
:
:
:
:
:
:
:
True
15
A la hormiga no le gusta compartir ;
CigarraHormiga.txt
C:\Temp\CigarraHormiga.txt
hormiga
IgnoreCase
LineNumber
Line
Filename
Path
Pattern
Context
Matches
:
:
:
:
:
:
:
:
True
1
Las hormigas son muy útiles.
hormigasUtiles.txt
C:\Temp\HormigasUtiles.txt
hormiga
{Hormiga}
{Hormiga}
Así podemos pedir el número de línea de la primera coincidencia:
PS > $Var[0].Linenumber
8
Ejemplo
Otra búsqueda simple.
Podemos también utilizar
sigue:
Select­String pasándole los datos buscados a través de una pipeline como
PS > Get­Item C:\Temp\*.txt | Select­String ­Pattern ’hormiga’
Los resultados obtenidos son los mismos que en el ejemplo anterior.
¡No se equivoque! Utilice Get­Item o Get­ChildItem y no Get­Content ya que aunque
parezca que funciona, el resultado no es generalmente el esperado. En efecto pasaría al pipeline el
contenido de los archivos y no los archivos en sí y el contenido es de alguna forma concatenado. Lo que
tendría como consecuencia falsear el valor de la propiedad LineNumber.
Ejemplo
PS > $var = Get­Content C:\Temp\*.txt | Select­String ­Pattern ’hormiga’
PS > $Var | Format­List
IgnoreCase
LineNumber
Line
Filename
Path
Pattern
Context
Matches
:
:
:
:
:
:
:
:
True
8
En casa de la hormiga su vecina,
InputStream
InputStream
hormiga
{Hormiga}
193
IgnoreCase
LineNumber
Line
Filename
Path
Pattern
Context
Matches
:
:
:
:
:
:
:
:
True
15
A la hormiga no le gusta compartir ;
InputStream
InputStream
hormiga
IgnoreCase
LineNumber
Line
Filename
Path
Pattern
Context
Matches
:
:
:
:
:
:
:
:
True
23
Las hormigas son muy útiles.
InputStream
InputStream
hormiga
{Hormiga}
{Hormiga}
En este ejemplo, observaremos que:
El nombre del archivo ha desaparecido de los resultados para ser reemplazado por un
«InputStream» que indica el origen de los datos.
Como el enlace con el archivo origen ha desaparecido, el número de línea es relativo al conjunto del
flujo, lo que da un resultado potencialmente erróneo si esperamos obtener la posición en el archivo
(observe la tercera y última coincidencia del ejemplo).
Ejemplo 3
Búsqueda en base a una expresión regular.
PS > Get­item $pshome/en­US/*.txt | Select­String ­Pattern ’World$’
C:\...\about_Preference_Variables.help.txt:293:
C:\...\about_Preference_Variables.help.txt:313:
C:\...\about_Preference_Variables.help.txt:403:
DEBUG: Hello, World
DEBUG: Hello, World
...:continue:Hello,World
...
Esta línea de comandos explora todos los archivos con extensión
termine por World.
.txt buscando una cadena que se
El ejemplo anterior se basa en una expresión regular. Para que Select­String realice una
búsqueda con una expresión literal en vez de una regular, debe emplear el parámetro ­
SimpleMatch. Este conmutador es necesario cuando la cadena de búsqueda contiene caracteres
especiales que tienen un significado en el lenguaje de expresiones regulares tales como el dólar, el
punto, el asterisco, etc.
Ejemplo
Búsqueda donde el resultado es un booleano.
194
PS > Select­String CigarraHormiga.txt ­Pattern ’hormiga’ ­Quiet
True
PS > Select­String CigarraHormiga.txt ­Pattern ’elefante’ ­Quiet
False
Ejemplo
Búsqueda de una cadena mostrando su contexto (2 líneas antes y 2 líneas después).
PS > Select­String CigarraHormiga.txt ­Pattern ’Agosto’ ­Context 2
CigarraHormiga.txt:11:Hasta la estación nueva
CigarraHormiga.txt:12:"Le pagaré, le dice,
CigarraHormiga.txt:13:Antes de agosto, palabra de animal,
CigarraHormiga.txt:14:Interes y principal."
CigarraHormiga.txt:15:A la hormiga no le gusta compartir ;
Hubiese podido añadir el conmutador ­SimpleMatch para precisar que nuestra búsqueda se realiza
sobre una cadena literal y no sobre una expresión regular. Esto funciona correctamente ya que no hay
caracteres especiales en nuestra cadena de búsqueda.
7. Gestión de archivos CSV
a. Importación/exportación de datos
Los archivos CSV (Comma­Separated Values) son archivos de texto cuyos valores están separados por un
delimitador. Generalmente, en la primera línea de estos archivos se encuentra los encabezados. Estos
incluyen el nombre de cada «columna» de datos o «campos». El nombre de los campos así como los
valores están separados por un delimitador como la coma en un entorno anglo­sajón y el punto y coma
en un entorno hispano.
Seguidamente presentamos un ejemplo de archivo CSV muy simple:
Sexo;Nombre;Año_de_nacimiento
F;Gabriela;1972
M;Jose;2008
F;Eleonor;2004
PowerShell incluyen un juego de dos comandos para gestionar estos archivos:
un archivo CSV, Import­CSV para leer un archivo CSV.
Los parámetros de
Export­CSV para crear
Export­CSV son los siguientes:
Descripción
Parámetro
­Path <String>
Ruta del archivo de destino.
­Append <Switch>
Escribe valores
existente.
­InputObject <PSObject>
Acepta un objeto como entrada.
a
continuación
de
un
archivo
195
Descripción
Parámetro
­Force <Switch>
Reemplaza el archivo especificado si el destino ya
existe.
­Encoding <String>
Tipo de codificación del archivo a crear (consulte
Out­File para la lista de valores posibles).
ASCII es el tipo por defecto con Windows
PowerShell.
­NoTypeInformation
<Switch>
Por defecto, se escribe una línea que contiene el
tipo de datos (empezando por #TYPE) antes del
encabezado. Si se incluye esta opción, la línea no se
escribirá.
­NoClobber <Switch>
No sobreescribir el archivo si este ya existe.
­Delimiter <Char>
Muy útil, este parámetro permite especificar un
carácter delimitador para separar los valores de
propiedad. El valor por defecto es la coma.
­UseCulture <Switch>
En vez del parámetro ­Delimiter, puede
también usar ­UseCulture. Especificando una
región determinada, PowerShell adaptará el
delimitador (el delimitador para la región es­ES es el
punto y coma). Para verificarlo, pruebe: (Get­
Culture).TextInfo.ListSeparator.
Y ahora los de
Import­CSV:
Parámetro
Descripción
­Path <String[]>
Ruta del archivo de origen.
­Delimiter <Char>
Muy útil, este parámetro permite especificar un carácter
delimitador para separar los valores de propiedad. El
valor por defecto es la coma.
­UseCulture <Switch>
En vez del parámetro ­Delimiter, puede también usar
­UseCulture. Especificando una región determinada,
PowerShell adaptará el delimitador (el delimitador para la
región es­ES es el punto y coma). Para verificarlo, pruebe:
(Get­Culture).TextInfo.ListSeparator.
­Header <String[]>
Permite especificar otra línea de encabezado diferente de
la contenida en el archivo importado. Permite también
especificar un encabezado de archivo a los archivos CSV
que no tienen.
Ejemplo: Export­CSV
196
PS > Get­Eventlog Application ­Newest 5 |
Select­Object TimeGenerated, EntryType, Source, EventID |
Export­CSV C:\Temp\EventLog.csv ­Encoding Unicode
PS > Get­Content C:\Temp\EventLog.csv
#TYPE Selected.System.Diagnostics.EventLogEntry
"TimeGenerated","EntryType","Source","EventID"
"05/01/2018 20:46:26","Information","Windows Reporting","1001"
"05/01/2018 20:44:48","Information","ESENT","327"
"05/01/2018 20:44:48","Information","ESENT","326"
"05/01/2018 20:41:04","Information","Wlclntfy","6000"
"05/01/2018 20:41:02","Information","Wlclntfy","6003"
Acabamos de crear un archivo de texto
Unicode llamado EventLog.csv. Como no hemos
especificado un delimitador, se utiliza la coma (valor por defecto). El archivo contiene las propiedades
TimeGenerated, Entrytype, Source, EventID de un objeto de tipo registro de Aplicación. Tenga
en cuenta que la primera línea del archivo empieza por #TYPE seguida del tipo de objeto contenido en
nuestro archivo; en otras palabras, se trata del tipo generado por el comando Get­EventLog.
Tenga cuidado ya que por defecto Export­CSV genera archivos de tipo ASCII, es decir sin
caracteres acentuados. Es por lo tanto importante no olvidarse del tipo de codificación deseado
cuando trabajamos en castellano.
Ejemplo: Import­CSV
PS > $journal = Import­Csv C:\Temp\EventLog.csv
PS > $journal
TimeGenerated
­­­­­­­­­­­­­
05/01/2018 20:46:26
05/01/2018 20:44:48
05/01/2018 20:44:48
05/01/2018 20:41:04
05/01/2018 20:41:02
EntryType
­­­­­­­­­
Information
Information
Information
Information
Information
Source
­­­­­­
Windows Re...
ESENT
ESENT
Wlclntfy
Wlclntfy
EventID
­­­­­­­
1001
327
326
6000
6003
Ahora, observemos las propiedades y métodos del objeto contenido en
$journal:
PS > $journal | Get­Member
TypeName: CSV:Selected.System.Diagnostics.EventLogEntry
Name
­­­­
Equals
GetHashCode
GetType
ToString
EntryType
EventID
Source
TimeGenerated
MemberType
­­­­­­­­­­
Method
Method
Method
Method
NoteProperty
NoteProperty
NoteProperty
NoteProperty
Definition
­­­­­­­­­­
bool Equals(System.Object obj)
int GetHashCode()
type GetType()
string ToString()
System.String EntryType=Information
System.String EventID=1001
System.String Source=Windows Reporting
System.String TimeGenerated=05/01/2018 20:46:26
197
Podemos ver que tenemos propiedades que corresponden al nombre de nuestro encabezado; ¡lo que
será muy útil para recuperar los valores!
Por ejemplo:
PS > $journal[0].EventID
1001
Ejemplo 2: Export­CSV y
Import­CSV
Imaginemos ahora que necesitamos realizar una búsqueda de resultados en un archivo CSV y en sus
modificaciones. Tomemos como ejemplo el siguiente archivo:
Apellido;Nombre;Dominio;Última_Conexión
Pérez;Roberto;powershell­scripting.com;20/03/2018
Fernández;Angel;powershell­scripting.com;19/09/2017
Teixeira;Jose;;02/02/2018
En este archivo se identifican tres personas. Entre ellas, una no está identificada como perteneciente al
dominio powershell­scripting.com, y debemos corregir esta anomalía.
En primer lugar, importamos el archivo.
PS > $usuarios = Import­Csv ./usuarios.csv ­UseCulture
PS > $usuarios
Apellido
­­­­­­­­
Pérez
Fernandez
Teixeira
Nombre
­­­­­­
Roberto
Angel
Jose
Dominio
­­­­­­­
powershell­scripting.com
powershell­scripting.com
Ultima_Conexión
­­­­­­­­­­­­­­­
20/03/2018
19/09/2017
02/02/2018
Dominio
­­­­­­­
powershell­scripting.com
Ultima_Conexión
­­­­­­­­­­­­­­­
20/03/2018
PS > $usuarios[0]
Apellido
­­­­­­­­
Pérez
Nombre
­­­­­­
Roberto
Como podemos ver arriba, la variable $usuarios se comporta como un array (es normal, puesto que lo
es) en el que cada línea corresponde a un número de índice. Además cada objeto definido en el array
dispone de las propiedades Apellido, Nombre, Dominio, Ultima_Conexión, correspondiente a
los nombres de las columnas de nuestro archivo CSV.
PS > $usuarios[0].Apellido
Pérez
PS > $usuarios[0].Dominio
powershell­scripting.com
Evidentemente, el array se puede recorrer con un bucle y cada uno de los valores puede modificarse; es
el caso más abajo. Para cada usuario, si no está especificado un dominio entonces añadimos el valor
PowerShell­scripting.com.
198
PS > Foreach($usuario in $usuarios){
if(($usuario.Dominio) ­eq ’’){
$usuario.Dominio = ’PowerShell­scripting.com’
"el usuario $($usuario.Apellido) ha sido añadido al dominio"
}
}
el usuario Teixeira ha sido añadido al dominio
PS > $usuarios
Apellido
­­­­­­­­
Pérez
Fernandez
Teixeira
Nombre
­­­­­­
Roberto
Angel
Jose
Dominio
­­­­­­­
powershell­scripting.com
powershell­scripting.com
powershell­scripting.com
Ultima_Conexión
­­­­­­­­­­­­­­­
20/03/2018
19/09/2017
02/02/2018
Para finalizar, para tener en cuenta los cambios realizados, se almacena la variable
archivo usuarios.csv mediante el comando Export­Csv.
$usuarios en el
PS > $usuarios | Export­Csv usuarios.csv `
­Encoding Unicode ­UseCulture
La ventaja de los archivos CSV delimitados por punto y coma es que los reconoce de manera
nativa Microsoft Excel. Así cuando hace doble clic sobre el archivo CSV, Excel lo debería
automáticamente convertir y mostrar.
b. Conversión de datos al formato CSV
Es posible gracias a ConvertTo­CSV transformar un array de objetos en un array de cadenas de
caracteres con formato CSV. ConvertTo­CSV realiza a grandes rasgos la misma función que Export­
CSV (de hecho acepta prácticamente los mismos parámetros) salvo que realiza la conversión en memoria
y no hacia un archivo de texto.
Esto permite tener flexibilidad cuando deseamos transformar los datos antes de escribirlos en disco.
Ejemplo 1: Conversión de un único objeto
PS
> $obj = [PSCustomObject] @{
Nombre = ’Angel Fernández’;
Email = ’[email protected]’;
sitioWeb = ’www.psh.local’;
codigoPostal = 33950}
PS > $obj | ConvertTo­Csv ­UseCulture ­NoTypeInformation
"Nombre";"Email";"sitioWeb";"codigoPostal"
"Angel Fernández";"[email protected]";"www.psh.local";"33950"
Ejemplo 2: Conversión de una colección de objetos
199
PS > Get­Verb | Select­Object ­First 5 | ConvertTo­CSV
#TYPE Selected.System.Management.Automation.PSCustomObject
"Verb","Group"
"Add","Common"
"Clear","Common"
"Close","Common"
"Copy","Common"
"Enter","Common"
El comando Get­Verb devuelve la lista de verbos aprobados por Microsoft relativos a la
nomenclatura de funciones y, en particular, de funciones avanzadas cuando están incluidas en
un módulo. Si una función exportada por un módulo tiene un verbo no aprobado, entonces se
muestra un mensaje de alerta al cargar el módulo.
c. Conversión de datos a partir del formato CSV
Anteriormente hemos visto cómo convertir un objeto en un array de cadenas de caracteres con el
formato CSV. Ahora realizaremos la operación inversa, es decir convertir datos con formato CSV en
objetos.
Aunque los pasos pueden parecer curiosos a primera vista, comprenderá rápidamente, a través de los
ejemplos que siguen, los casos de uso interesantes del comando ConvertFrom­CSV.
Primero debe saber que ConvertFrom­CSV funciona perfectamente con su comando homólogo
ConvertTo­CSV; sin embargo no hemos encontrado interés en convertir un objeto nativo en CSV para
realizar la operación inversa después. Por el contrario, allí donde ConvertFrom­CSV toma sentido, es
cuando trabajamos con comandos heredados del sistema operativo que generan salidas de datos con
formato CSV.
Tomemos como ejemplo el caso del comando Whoami.exe. Este da bastante información acerca del
usuario conectado, su pertenencia a grupos, su SID, así como mucha información adicional.
Probemos el comando siguiente que devuelve el SID del usuario conectado:
PS > whoami.exe /user
USER INFORMATION
­­­­­­­­­­­­­­­­
User Name
SID
=================== ===========================================
adps1\administrator S­1­5­21­3471033758­2576861177­32356578­500
El resultado de este comando es muy interesante pero ¿cómo recuperar el valor del SID para explotarlo
en un script? La primera idea que viene a la mente consiste en analizar el flujo de texto para extraer el
SID. ¿Por qué no? Dicho esto, esta manera de proceder es algunas veces arriesgada ya que si un
carácter o una línea difiere con respecto al resultado esperado, entonces nos arriesgamos a no extraer
un valor bueno. Por consiguiente, la idea aquí es explotar la posibilidad de este comando para generar
un resultado con formato CSV. Así añadiendo la opción /FO CSV a la línea de comando, le pediremos
que nos suministre un flujo de texto con formato CSV.
Probemos de nuevo añadiendo
/FO CSV:
200
PS > whoami.exe /user /FO CSV
"User Name","SID"
"adps1\administrator","S­1­5­21­3471033758­2576861177­32356578­500"
Esto está mejor. Se parece mucho al formato CSV. Veamos ahora el resultado si pasamos el resultado de
esta línea de comandos a ConvertFrom­CSV:
PS > whoami.exe /user /FO CSV | ConvertFrom­Csv
User Name
­­­­­­­­­
adps1\administrator
SID
­­­
S­1­5­21­3471033758­2576861177­32356...
Esta vez el resultado es un bonito objeto PowerShell con las propiedades
fácil recuperar el SID del usuario actual:
User Name y SID. Ahora es
PS > $user = whoami.exe /user /FO CSV | ConvertFrom­Csv
PS > $user.SID
S­1­5­21­3471033758­2576861177­32356578­500
Esto no es la mejor forma de recuperar el SID del usuario actual. Este ejemplo solo pretende
ilustrar lo fácil que resulta convertir un flujo de texto con formato CSV que provenga de un tercero y
convertirlo en un objeto PowerShell.
También con el comando Whoami.exe es posible conocer la pertenencia a grupos del usuario actual.
Veamos cómo convertir el resultado de manera que obtengamos al final un objeto PowerShell.
PS > whoami.exe /groups /FO CSV | ConvertFrom­CSV
Group Name
­­­­­­­­­­
Everyone
BUILTIN\Administ...
BUILTIN\Users
BUILTIN\Certific...
BUILTIN\Pre­Wind...
...
Type
­­­­
Well­known group
Alias
Alias
Alias
Alias
SID
­­­
S­1­1­0
S­1­5­32­544
S­1­5­32­545
S­1­5­32­574
S­1­5­32­554
En resumen, aunque de apariencia anodina, el comando
Attributes
­­­­­­­­­­
Mandatory group,...
Mandatory group,...
Mandatory group,...
Mandatory group,...
Mandatory group,...
ConvertFrom­CSV es muy potente y puede
serle útil en muchas ocasiones. No dude en abusar de él por lo tanto cuando tenga la oportunidad ya
que le será más sencillo que « parsear » los resultados con expresiones regulares complejas.
8. Gestión de archivos XML
XML (Extensible Markup Language) es un lenguaje basado en una jerarquización de datos con forma de
marcado. Para saber qué aspecto tiene el XML, lo mejor es seguramente ver un ejemplo.
201
<Libro>
<Titulo>Windows PowerShell</Titulo>
<SubTitulo>Los principios</SubTitulo>
<Autor>
<Nombre>Arnaud Petitjean</Nombre>
<AnioDeNacimiento>1974</AnioDeNacimiento>
<Distincion>MVP PowerShell</Distincion>
</Autor>
<Autor>
<Nombre>Robin Lemesle
</Nombre>
<AnioDeNacimiento>1985</AnioDeNacimiento>
<Distincion>MVP PowerShell</Distincion>
</Autor>
<Capitulo>
<Nombre>Introduccion</Nombre>
<NumeroPaginas>100</NumeroPaginas>
</Capitulo>
<Capitulo>
<Nombre>Descubrimiento de PowerShell</Nombre>
<NumeroPaginas>120</NumeroPaginas>
</Capitulo>
</Libro>
Para conocer el conjunto de comandos ligados al uso de archivos XML, teclee el siguiente comando:
PS > Get­Command ­Type cmdlet *XML*
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
ConvertTo­Xml
Export­Clixml
Import­Clixml
Select­Xml
Definition
­­­­­­­­­­
ConvertTo­Xml [­InputO...
Export­Clixml [­Path]...
Import­Clixml [­Path]...
Select­Xml [­XPath] <S...
Comando
Descripción
ConvertTo­Xml
Crea una representación XML (en memoria) a partir de objetos .NET.
Export­Clixml
Exporta uno o varios objeto(s) en una representación XML en un
archivo. Este mecanismo se denomina «serialización de objetos».
Import­Clixml
Importa un archivo XML exclusivamente generado por
Export­
CliXML y convierte el resultado en representación de objeto. Este
mecanismo se denomina «deserialización de objetos».
Select­Xml
Permite efectuar consultas de búsqueda con formato
XPath dentro
de un documento XML.
Import­Clixml permite únicamente la importación de archivos XML generados por el comando
Export­Clixml.
202
a. Carga de un archivo XML
Para importar nuestro archivo de ejemplo, basta con realizar una conversión de tipo sobre un array de
cadenas de caracteres devueltas por Get­Content. El archivo XML importado en el ejemplo siguiente
es el presentado en el archivo Libro.xml.
PS > $Libro = [xml](Get­Content ./Libro.xml)
PS > $Libro
Libro
­­­­­
Libro
b. Gestión del contenido
Ahora que hemos importado el archivo XML en memoria en la variable $Libro, nos resulta muy sencillo
recorrerlo. Cada marca que lo compone está ahora representada bajo la forma de propiedad.
Ejemplos
PS > $Libro.Libro
Titulo
­­­­­
Windows PowerShell
SubTitulo
­­­­­­­­­
Los...
Autor
Capitulo
­­­­­­
­­­­­­­­
{Autor... {Capitulo, Capitulo}
PS > $Libro.Libro.Titulo
Windows PowerShell
PS > $libro.Libro.autor
Nombre
­­­­­­
Arnaud Petitjean
Robin Lemesle
AnioDeNacimiento
­­­­­­­­­­­­­­­­
1974
1985
Distincion
­­­­­­­­­­
MVP PowerShell
MVP PowerShell
Podemos realizar modificaciones en memoria. Para ello, basta con indicar qué nuevo valor debe tomar la
marca en cuestión.
PS > $libro.Libro.Titulo = ’El libro Windows PowerShell’
PS > $Libro.Libro
Titulo
­­­­­­
El libro Windows PowerShell
SubTitulo
­­­­­­­­­
Los...
Autor
­­­­­
{Autor..
Capitulo
­­­­­­­­
{Capitulo...
Acabamos de ver como explorar un archivo XML personalizado, lo que resulta muy sencillo y práctico.
c. Exportar objetos en formato XML
ConvertTo­XML, que permite convertir uno o varios objetos en
una representación XML. Cabe destacar que ConvertTo­XML no crea un archivo, sino que esta acción
PowerShell está dotado del comando
corre de nuestra cuenta.
203
Para comprender bien el funcionamiento de este comando, veremos un pequeño ejemplo a continuación,
donde tenemos un array de objetos personalizado; aquí, coches. Seguidamente, exportaremos este
array en una representación XML que enviaremos después a un disco.
Definamos el array de objetos:
$coches = @(
[PSCustomObject]@{
Marca = ’AUDI’; Modelo=’A4 Avant’; Potencia = 177
},
[PSCustomObject]@{
Marca = ’BMW’; Modelo=’320i’; Potencia = 200
})
Resultado antes de la conversión:
PS > $coches
Marca Modelo
Potencia
­­­­­­ ­­­­­­
­­­­­­­­­
AUDI
A4 Avant
177
BMW
320i
200
Conversión del array en un flujo de datos de texto en formato XML:
$XmlFile = $coches | ConvertTo­Xml ­as String
Resultado tras la conversión:
PS > $XmlFile
<?xml version="1.0" encoding="utf­8"?>
<Objects>
<Object Type="System.Management.Automation.PSCustomObject">
<Property Name="Marca" Type="System.String">AUDI</Property>
<Property Name="Modelo" Type="System.String">A4 Avant</Property>
<Property Name="Potencia" Type="System.Int32">177</Property>
</Object>
<Object Type="System.Management.Automation.PSCustomObject">
<Property Name="Marca" Type="System.String">BMW</Property>
<Property Name="Modelo" Type="System.String">320i</Property>
<Property Name="Potencia" Type="System.Int32">200</Property>
</Object>
</Objects>
Envío del resultado a un archivo de texto:
PS > $XmlFile | Out­File .\coches.xml ­encoding UTF8
d. Serialización/deserialización con los comandos *­CliXML
Los comandos PowerShell para la manipulación del contenido XML tienen como principal objetivo
almacenar información, en particular almacenar objetos. Los objetos creados por PowerShell son en
realidad los del framework .NET. Este mecanismo llamado serialización/deserialización es muy conocido
por nuestros amigos los desarrolladores.
204
Para entenderlo mejor, tomemos un ejemplo concreto. Existen casos donde deseamos guardar en un
archivo el estado de un objeto y, en particular, todas sus propiedades y valores asociados. El objetivo es
poder recrear fielmente este objeto posteriormente. Es lo que ocurre cuando recuperamos información
desde un equipo remoto con los mecanismos de comunicación remota de PowerShell. En efecto, para
transitar por los protocolos HTTP/HTTPS, los datos (que son en PowerShell siempre objetos)
necesitan ser convertidos en flujo de texto. Tendremos la oportunidad de volver a hablar de estos
mecanismos posteriormente en este libro.
Por el momento, contentémonos con exportar los procesos en ejecución en nuestro equipo. Vamos de
alguna manera a tomar una instantánea de nuestro sistema. Podremos así reimportar este «estado del
sistema» después y compararlo con los procesos en ejecución en otro momento del día. Esto permitirá
por ejemplo identificar los procesos que faltan o los idénticos en diferentes instantes.
Ejemplo
Serialización de procesos en ejecución.
PS > Get­Process | Export­Clixml ./Processes.clixml
Veamos qué aspecto tiene nuestro archivo XML, al menos las primeras líneas ya que es muy voluminoso
(del orden de varios MB):
PS > Get­Content ./Processes.clixml ­First 15
<Objs Version="1.1.0.1"
xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>System.Diagnostics.Process</T>
<T>System.ComponentModel.Component</T>
<T>System.MarshalByRefObject</T>
<T>System.Object</T>
</TN>
<ToString>System.Diagnostics.Process (AppleOSSMgr)</ToString>
<Props>
<I32 N="BasePriority">8</I32>
<B N="HasExited">false</B>
<Obj N="Handle" RefId="1">
<TN RefId="1">
<T>System.IntPtr</T>
...
La gramática (el esquema) utilizado aquí para la serialización de objetos es propia de PowerShell.
Podemos
verlo
gracias
a
la
marca
<Objs
Version="1.1.0.1"
xmlns="http://schemas.microsoft.com/powershell/2004/04">
situada
automáticamente al inicio del archivo.
Ahora volvamos a importar la instantánea del estado de los procesos tomada anteriormente y
comparémosla con los procesos actualmente en ejecución para ver la diferencia.
Ejemplo
Deserialización de los procesos exportados anteriormente.
PS > $Antes = Import­Clixml .\Processes.clixml
205
$Antes contiene ahora los procesos que se ejecutaban en el preciso momento que hemos
tomado la instantánea. Dicho de otro modo, contiene el resultado del comando Get­Process con la
La variable
diferencia de que un objeto deserializado pierde todos los métodos del objeto origen (pero no las
propiedades).
Podemos ahora comparar los nombres de los procesos (los que se ejecutaban anteriormente con los que
se están ejecutando ahora mismo) gracias a Compare­Object:
PS > $Ahora = Get­Process
PS > Compare­Object $Antes $Ahora ­Property Name
Name
­­­­
chrome
PaintDotNet
SideIndicator
­­­­­­­­­­­­­
<=
<=
El resultado de la comparación efectuada con Compare­Object nos indica que los procesos chrome y
PaintDotNet estaban en ejecución anteriormente.
9. Importar/exportar datos en formato JSON
El formato JSON es muy popular en la actualidad. Es el que devuelven con mayor frecuencia los servicios
REST en la Web. Es fácil manipular archivos JSON gracias al conjunto de comandos ConvertTo­Json y
ConvertFrom­Json.
a. Exportar datos
Tomemos, por ejemplo, un array de objetos personalizados que queremos convertir a formato JSON.
Recuperaremos nuestro array de coches, ya utilizado antes con ConvertTo­XML.
$coches = @(
[PSCustomObject]@{
Marca = ’AUDI’; Modelo=’A4 Avant’; Potencia = 177
},
[PSCustomObject]@{
Marca = ’BMW’; Modelo=’320i’; Potencia = 200
})
Resultado antes de la conversión:
PS > $coches
Marca Modelo
Potencia
­­­­­­ ­­­­­­
­­­­­­­­­
AUDI
A4 Avant
177
BMW
320i
200
Resultado convertido a JSON:
206
PS > $coches | ConvertTo­Json
[
{
"Marca": "AUDI",
"Modelo": "A4 Avant",
"Potencia": 177
},
{
"Marca": "BMW",
"Modelo": "320i",
"Potencia": 200
}
]
ConvertTo­Json posee el parámetro ­Compress que permite devolver una especie de resultado
más compacto, sin saltos de línea ni indentación.
El resultado convertido en JSON compacto sería:
PS > $coches | ConvertTo­Json ­Compress
[{"Marca":"AUDI","Modelo":"A4 Avant","Potencia":177},
{"Marca":"BMW","Modelo":"320i","Potencia":200}]
b. Importar datos
Es posible convertir fácilmente datos en formato JSON en un array de objetos PowerShell gracias al
comando ConvertFrom­Json.
Como decíamos en la introducción, el formato JSON se utiliza a menudo como formato de devolución de
datos cuando se consumen servicios REST en Internet.
En el siguiente ejemplo, recuperaremos la cotización del BitCoin (en dólares EUA) desde a plataforma de
tipos de cambio Poloniex.
PS > $URI = ’https://poloniex.com/public?command=returnTicker’
PS > $res = (Invoke­WebRequest ­Uri $URI).content | ConvertFrom­Json
PS > $res.USDT_BTC
id
last
lowestAsk
highestBid
percentChange
baseVolume
quoteVolume
isFrozen
high24hr
low24hr
:
:
:
:
:
:
:
:
:
:
121
13447.20814455
13447.20814458
13447.20814455
­0.05872196
64652043.59451559
4780.90035758
0
14449.02739320
12853.99999974
La propiedad que contiene la cotización es, claramente, la propiedad
a PowerShell que devuelva únicamente esta propiedad.
last. Podemos, entonces, pedirle
PS > $res.USDT_BTC.last
13447.20814455
207
El valor de un BitCoin en el momento de escribir estas líneas es de 13447 $.
Lo que es impresionante aquí no es el valor del BitCoin, sino el hecho de que ConvertFrom­Json
haya convertido automáticamente de manera casi mágica los datos en formato JSON devueltos por
Invoke­WebRequest en un array de hash que contiene un array de valores.
10. Exportar datos como página HTML
Si la creación de páginas HTML le agrada para presentar algunos informes estratégicos o por cualquier
otra razón, ¡entonces el comando ConvertTo­HTML está pensado para usted! En efecto gracias a este
comando la generación de páginas web se vuelve casi un juego de niños si hacemos abstracción de la
representación visual.
Aquí tiene los parámetros disponibles para
ConvertTo­HTML:
Descripción
Parámetro
Property <Object[]>
Propiedades del objeto indicado como parámetro que
se deben escribir en la página HTML.
InputObject <PSObject>
Acepta un objeto como entrada.
Body <String[]>
Especifica el texto a insertar en el elemento
<body>.
Head <String[]>
Especifica el texto a insertar en el elemento
<head>.
Title <String>
Especifica el texto a insertar en el elemento
<title>.
Un poco a la manera del comando
columna del archivo HTML.
Export­CSV, el nombre de las propiedades servirá de título para cada
Ejemplo
Listado de los servicios del sistema.
PS > Get­Service | ConvertTo­HTML ­Property name, displayname,
status ­Title ’Servicios del sistema’ | Out­File Servicios.htm
Este ejemplo nos permite crear una página HTML que contiene un listado de los servicios, sus nombres así
como sus estados. Hemos especificado el parámetro ­Title para que la ventana tenga un título distinto
al que se pone por defecto. Pasamos el conjunto a Out­File que crea el archivo Servicios.htm.
Si no especificamos propiedades en particular, se escribirán todas las del objeto; el parámetro
­
Property juega por lo tanto un rol de filtro.
Por otra parte, a diferencia de Export­CSV, ConvertTo­HTML necesita para funcionar correctamente
que le añadamos un comando para escribir el flujo de texto de generación de la página en un archivo. Por
consiguiente, no olvide prestar atención a la codificación del archivo resultante.
208
Para abrir este archivo directamente en Internet Explorer, basta con teclear el siguiente
comando: ./servicios.htm o Invoke­Item ./servicios.htm.
Como la extensión .htm es conocida por Windows, este abre el archivo con la aplicación asociada
(por defecto, Internet Explorer).
Gracias al parámetro
­Body, podemos especificar contenido adicional que aparecerá en el cuerpo de la
página, justo antes de los datos del objeto.
Ejemplo
Listado de los servicios del sistema con BODY.
PS > Get­Service | ConvertTo­HTML ­Property `
name,displayname,status ­Title ’Servicios del sistema’ `
­body ’<CENTER><H2>Estado de los servicios del sistema</H2></CENTER>’ |
Out­File ./Servicios.htm
209
Ejemplo
Listado de los servicios del sistema formateado con CSS.
Mejor aún, vamos esta vez a crear un marco para nuestra tabla gracias a las hojas de estilo en cascada
(Cascading Style Sheets). Para ello, creamos la hoja de estilo siguiente, que llamaremos Style.css:
<style type=’text/css’>
table {
border: medium solid #000000;
border­collapse: collapse ;
}
td, th {
border: thin solid #6495ed;
}
</style>
Tenemos que incluir este archivo en el elemento
HEAD de nuestro archivo HTML, de la siguiente manera:
210
PS > $CSS = Get­Content Style.css
PS > Get­Service | ConvertTo­HTML ­Property `
name,displayname,status ­Title ’Servicios del sistema’ `
­Head $CSS ­Body `
’<CENTER><H2>Estado de los servicios del sistema</H2></CENTER>’ |
Out­File ./Servicios.htm
Un último ejemplo para terminar con este comando podría ser dar color a cada fila de nuestra tabla.
Podríamos así diferenciar los servicios en ejecución de los demás.
Ejemplo
Listado de los servicios del sistema con análisis del contenido.
PS > Get­Service |
ConvertTo­Html ­Property name,displayname,status `
­Title ’Servicios del sistema’ ­Body `
’<CENTER><H2>Estado de los servicios del sistema</H2></CENTER>’ |
foreach {
if($_ ­match ’<td>Running</td>’)
{
$_ ­replace ’<tr>’, ’<tr bgcolor=#DDDDDD>’
}
211
elseif($_ ­match ’<td>Stopped</td>’)
{
$_ ­replace ’<tr>’, ’<tr bgcolor= #6699FF>’
}
else
{
$_
}
} | Out­File ./Servicios.htm
En este ejemplo, cuando un servicio se está ejecutando, reemplazamos la marca
TR (que indica una fila)
TR incluyendo además la instrucción que permite mostrar un fondo de color
bgcolor=#codigoColor.
por la misma marca
11. Exportar datos con Out­GridView
Disponible únicamente en Windows PowerShell, el comando Out­GridView permite mostrar datos de
manera gráfica (a la imagen de lo que hemos hecho anteriormente con una página HTML, pero sin
esfuerzo) y de manera interactiva. La interactividad de la tabla se encuentra en la posibilidad de hacer clic
sobre el título de las columnas para realizar una ordenación de los datos por orden alfabético. Otra forma
de interacción es una función de búsqueda integrada en la tabla. Esta resulta práctica cuando los datos
son numerosos y buscamos uno en particular. Sepa que es posible utilizar Out­GridView en la mitad de
212
una línea de comandos, así los objetos seleccionados serán transmitidos al pipeline.
Ejemplo 1
Listado de los servicios en ejecución.
PS > Get­Service | Out­GridView
Aquí hemos hecho clic en la columna Status para ordenar los resultados según su estado (Running,
Stopped, etc.). Tenga en cuenta que encima de las columnas se encuentra una zona de búsqueda en la
cual por defecto se encuentra el texto Filter.
Out­GridView puede eventualmente substituir un filtro Where­Object simplificado.
Ejemplo 2
Filtrado de los servicios y exportación CliXML.
PS > Get­Service | Out­GridView ­Passthru |
Export­CliXML ./Servicios.clixml
213
Utilización de
Out­GridView en modo filtro gracias al parámetro ­Passthru
Gracias al parámetro ­Passthru la selección en modo gráfico se transmite al pipeline. En nuestro
ejemplo los objetos seleccionados serán serializados.
Fechas
Con PowerShell, la obtención de un objeto que representa la fecha y la hora actual se realiza mediante el
comando Get­Date. Aunque un simple Get­Date en la consola devuelve la hora y la fecha actual, este
fecha puede declinarse en numerosos formatos.
Una variable que contenga una fecha es de tipo
DateTime. Para comprobarlo por usted mismo,
teclee el siguiente comando:
PS > (Get­Date).GetType()
IsPublic IsSerial Name
­­­­­­­­ ­­­­­­­­ ­­­­
True
True
DateTime
BaseType
­­­­­­­­
System.ValueType
Los objetos de tipo DateTime poseen numerosas propiedades y métodos interesantes como, entre otros,
el método IsDaylightSavingTime, que indica si la hora actual está ajustada al horario de verano o de
invierno.
214
Para darse cuenta de las posibilidades existentes en el tratamiento de las fechas, lo mejor es una vez más
utilizar Get­Member sobre el objeto devuelto por Get­Date:
PS > Get­Date | Get­Member
Cuando hablamos de fecha o de tiempo, es necesario definir lo que llamamos unidad de tiempo. Y la unidad
más elemental del sistema se llama «tick». Un tick es una medida de tiempo. Corresponde a un latido de
corazón del ordenador, es decir a un periodo del Timer (el Timer es el componente electrónico que gestiona
el tiempo). Este valor actualmente es del orden de diez millonésimas de segundo.
Para conocer el número de ticks
siguiente: $((Get­Date).ticks) ­
presentes
en
un
segundo,
teclee
el
comando
$((Get­Date).Addseconds(­1).ticks)
Por lo tanto aproximadamente 10 millones de media para el tratamiento del comando.
1. Manipulación de los objetos DateTime
El comando Get­Member aplicado a un objeto de tipo DateTime devuelve una lista considerable de
métodos y propiedades. La siguiente tabla expone los métodos más utilizados y da una breve descripción.
Descripción
Método
Add
AddDays
AddHours
AddMilliseconds
AddMinutes
AddMonths
AddSeconds
AddTicks
AddYears
Añade o sustrae una o varias unidades de tiempo del
objeto fecha. Añade si el valor pasado como
argumento es positivo, sustrae si el valor pasado es
negativo.
CompareTo
Compara una fecha con otra. Los valores devueltos
son:
­1: si la fecha es anterior a la que la comparamos.
1: si es posterior.
0: si son iguales.
Equals
Devuelve un indicador booleano de comparación.
True: si las dos fechas son idénticas.
False: en caso contrario.
GetDateTimeFormats
Devuelve todos los formatos disponibles para el
objeto DateTime.
215
Método
Descripción
Get_Date
Get_Day
Get_DayOfWeek
Get_DayOfYear
Get_HourGet_Millisecond
Get_Minute
Get_Month
Get_Second
Get_Ticks
Get_TimeOfDay
Get_Year
Los métodos que empiezan por
IsDayLightSavingTime
Devuelve un valor booleano que indica si la hora
actual está ajustada a la hora de invierno o de
verano.
Subtract
Sustrae una fecha del objeto.
ToFileTime
Devuelve el valor del objeto
hora de archivo Windows.
ToFileTimeUtc
Devuelve el valor del objeto DateTime actual, en
hora de archivo Windows (Hora Universal).
ToLocalTime
Devuelve el valor del objeto
Get_* devuelven el
parámetro de la fecha en cuestión. Ejemplo: el
método Get_DayOfWeek devuelve el día de la
semana correspondiente.
DateTime actual, en
DateTime actual, en
hora local.
ToLongDateString
Devuelve una cadena de caracteres que contiene la
fecha en formato largo.
ToLongTimeString
Devuelve una cadena de caracteres que contiene la
hora en formato largo.
ToOADate
Devuelve la fecha en formato OLE (Object Linking and
Embedding) automation (número flotante). El formato
OLE automation corresponde al número de días
desde el 30 de diciembre de 1899 a media noche.
ToShortDateString
Devuelve una cadena de caracteres que contiene la
fecha en formato corto.
ToShortTimeString
Devuelve una cadena de caracteres que contiene la
hora en formato corto.
ToString
Devuelve una cadena de caracteres que contiene la
fecha y la hora en formato estándar.
ToUniversalTime
Devuelve la fecha y la hora en formato estándar.
Aquí vemos cómo conseguir la lista de propiedades disponibles de un objeto
datetime:
PS > Get­Date | Format­List *
DisplayHint
DateTime
Date
Day
:
:
:
:
DateTime
lunes 5 de enero de 2015 23:07:44
05/01/2018 00:00:00
5
216
DayOfWeek
DayOfYear
Hour
Kind
Millisecond
Minute
Month
Second
Ticks
TimeOfDay
Year
:
:
:
:
:
:
:
:
:
:
:
Monday
5
23
Local
926
7
1
44
635560960649267742
23:07:44.9267742
2015
Ejemplo
Obtener los minutos de la hora actual.
PS > (Get­Date).Minute
8
2. Formateo de fechas
En cuanto hablamos de formatos o formateo, debe saber que no vamos a devolver un objeto de tipo
datetime, sino una cadena (tipo string). En efecto, un formato es una representación textual de un
objeto.
La elección de un formato no es generalmente una cosa sencilla, sobre todo cuando existen unos sesenta
llamados «estándares».
Pues sí, existen numerosas maneras de representar una fecha. Para darse cuenta de ello, probemos el
siguiente comando:
PS > (Get­Date).GetDateTimeFormats() | Sort­Object ­Unique
01.06.15
01.06.15 21.18
01.06.15 21.18.17
01.06.15 21:18
01.06.15 21:18:17
01.06.15 21H18
01.06.15 21H18.17
01/06/15
01/06/15 21.18
01/06/15 21.18.17
01/06/15 21:18
01/06/15 21:18:17
01/06/15 21H18
01/06/15 21H18.17
01/06/2015
01/06/2015 21.18
01/06/2015 21.18.17
01/06/2015 21:18
01/06/2015 21:18:17
01/06/2015 21H18
01/06/2015 21H18.17
01­06­15
01­06­15 21.18
01­06­15 21.18.17
01­06­15 21:18
217
01­06­15 21:18:17
01­06­15 21H18
01­06­15 21H18.17
1 de junio
1 de junio de 2015
1 de junio de 2015 19.18.17
1 de junio de 2015 19:18:17
1 de junio de 2015 19H18.17
1 de junio de 2015 21.18
1 de junio de 2015 21.18.17
1 de junio de 2015 21:18
1 de junio de 2015 21:18:17
1 de junio de 2015 21H18
1 de junio de 2015 21H18.17
1/06/15
1/06/15 21.18
1/06/15 21.18.17
1/06/15 21:18
1/06/15 21:18:17
1/06/15 21H18
1/06/15 21H18.17
1/6/15
1/6/15 21.18
1/6/15 21.18.17
1/6/15 21:18
1/6/15 21:18:17
1/6/15 21H18
1/6/15 21H18.17
1­6­15
1­6­15 21.18
1­6­15 21.18.17
1­6­15 21:18
1­6­15 21:18:17
1­6­15 21H18
1­6­15 21H18.17
2015­06­01
2015­06­01 21.18
2015­06­01 21.18.17
2015­06­01 21:18
2015­06­01 21:18:17
2015­06­01 21:18:17Z
2015­06­01 21H18
2015­06­01 21H18.17
2015­06­01T21:18:17
2015­06­01T21:18:17.5076053+02:00
21.18
21.18.17
21:18
21:18:17
21H18
21H18.17
junio de 2015
lunes 1 de junio de 2015
lunes 1 de junio de 2015 19.18.17
lunes 1 de junio de 2015 19:18:17
lunes 1 de junio de 2015 19H18.17
lunes 1 de junio de 2015 21.18
lunes 1 de junio de 2015 21.18.17
lunes 1 de junio de 2015 21:18
lunes 1 de junio de 2015 21:18:17
218
lunes 1 de junio de 2015 21H18
lunes 1 de junio de 2015 21H18.17
lunes, 1 de junio de 2015
lunes, 1 de junio de 2015 19.18.17
lunes, 1 de junio de 2015 19:18:17
lunes, 1 de junio de 2015 19H18.17
lunes, 1 de junio de 2015 21.18
lunes, 1 de junio de 2015 21.18.17
lunes, 1 de junio de 2015 21:18
lunes, 1 de junio de 2015 21:18:17
lunes, 1 de junio de 2015 21H18
lunes, 1 de junio de 2015 21H18.17
Mon, 01 Jun 2015 21:18:17 GMT
a. Formatos estándares
Para ayudarle en la elección del formato, la siguiente tabla muestra un listado de los formatos
estándares aplicables a los objetos DateTime.
Descripción
Formato
d
Formato de fecha corta.
D
Formato de fecha larga.
f
Formato de fecha larga y hora abreviada.
F
Formato de fecha larga y hora completa.
g
Formato de fecha corta y hora abreviada.
G
Formato de fecha corta y hora completa.
m,M
Formato mes y día: "dd MMMM".
r,R
Formato de fecha y hora basado en la especificación RFC 1123.
s
Formato de fecha y hora ordenada.
t
Formato de hora abreviada.
T
Formato de hora completa.
u
Formato de fecha y hora universal (indicador de tiempo universal: "Z").
U
Formato de fecha larga y hora completa con tiempo universal.
y,Y
Formato año y mes.
A continuación le presentamos algunos ejemplos de aplicación de diferentes formatos.
Ejemplos
Fecha con formato estándar definido según la RFC 1123.
PS > Get­Date ­Format r
Mon, 05 Jan 2015 23:12:00 GMT
Fecha con formato fecha corta y hora completa como se define en la RFC 1123.
PS > Get­Date ­Format G
05/01/2018 23:12:19
219
Fecha con formato fecha larga y hora completa como se define en la RFC 1123.
PS > Get­Date ­Format F
lunes 5 enero de 2015 23:12:40
b. Formatos personalizados
Evidentemente la visualización de una fecha no se limita a los formatos estándares. Es posible usar
formatos personalizados y constituyen la técnica más simple y rápida para crear el formato deseado.
Para ello, debe asociar el parámetro ­Format con los especificadores de formato. La diferencia con los
formatos estándares enunciados anteriormente se encuentra en que los formatos personalizados son
elementos que se pueden combinar en una cadena de caracteres por ejemplo, y sin embargo los
formatos estándares solo tienen sentido si no se modifican.
A continuación presentamos una lista no exhaustiva de los formatos personalizados:
Formato
Descripción
d
Representación del día por un número comprendido entre: 1­31.
dd
Representación del día por un número comprendido ente: 01­31. La
diferencia con el formato «d» es la inserción de un cero no significativo para
los números que van de 1 a 9.
ddd
Representación del día en forma de su nombre abreviado. Ejemplo: Lun.,
Mar., Mie., etc.
dddd
Representación del día en forma de su nombre completo.
f
Representación de la cifra más significativo de la fracción de segundos.
ff
Representación de las dos cifras más significativas de la fracción de
segundo.
fff
Representación de las tres cifras más significativas de la fracción de
segundo.
ffff
Representación de las cuatro cifras más significativas de la fracción de
segundo.
h
Representación de la hora por un número. Números comprendidos entre: 1­
12.
hh
Representación de la hora por un número con inserción de un cero no
significativo para los números que van de 1 a 9. Números comprendidos
entre: 01­12.
H
Representación de la hora por un número. Números comprendidos entre: 0­
23.
HH
Representación de la hora por un número con inserción de un cero no
significativo para los números que van de 0 a 9. Números comprendidos
entre: 00­23.
m
Representación de los minutos por un número. Números comprendidos
entre: 0­59.
mm
Representación de los minutos por un número con inserción de un cero no
significativo para los números que van de 0 a 9. Números comprendidos
entre: 00­59.
220
Formato
Descripción
M
Representación del mes por un número. Números comprendidos entre: 1­12.
MM
Representación del mes por un número con inserción de un cero no
significativo para los números que van de 1 a 9. Números comprendidos
entre: 01­12.
MMM
Representación del mes en forma de nombre abreviado.
MMMM
Representación del mes en forma de nombre completo.
y
Representación del año en forma de número de dos cifras, como mucho. Si el
año se compone de más de dos cifras, solo las dos cifras de menor peso
aparecen en el resultado y si se compone de menos, solo la o las cifras (sin
cero significativo) aparecen.
yy
Ídem que en el caso anterior con la diferencia que si el año se compone de
menos de dos cifras, se rellena el número con ceros no significativos para
alcanzar dos cifras.
yyy
Representación del año en forma de número de tres cifras. Si el año se
compone de más de tres cifras, solo las tres cifras de menor peso aparecen
en el resultado. Si el año se compone de menos de tres cifras, se rellena el
número con ceros no significativos para alcanzar tres cifras.
yyyy
Representación del año en forma de número de cuatro cifras. Si el año se
compone de más de cuatro cifras, solo las cuatro cifras de menor peso
aparecen en el resultado. Si el año se compone de menos de cuatro cifras,
se rellena el número con ceros no significativos para alcanzar cuatro cifras.
Para obtener la lista completa de los especificadores de formato, visite el sitio web MSDN de
Microsoft: http://msdn2.microsoft.com/es­es/library/8kb3ddd4(VS.80).aspx
Ejemplo
<Nombre del
día> <Número del día> de <Mes> del <Año> ­­­­ <Hora>:<Minuto>:<Segundos>
En este primer ejemplo, deseamos simplemente mostrar la fecha con el siguiente formato:
PS > Get­Date ­Format ’dddd dd MMMM yyyy ­­­­ HH:mm:ss’
lunes 05 de enero del 2015 ­­­­ 23:18:56
Ejemplo
Imaginemos que tiene que generar informes en cuyo nombre de archivo debe aparecer la fecha en la que se
ha generado.
Para ello, nada más fácil...
PS > New­Item ­Type file ­Name `
"Informe_$((Get­Date ­Format ’dd­MM­yyyy’)).txt"
Resultado
Mode
­­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
05/01/2018
23:19
Length Name
­­­­­­ ­­­
0 Informe_05­01­2015.txt
221
Existe un último modo de visualización. Este último se llama visualización en modo Unix.
Como puede imaginarse, este modo utiliza los especificadores de formato Unix. A continuación puede ver
los especificadores fundamentales:
Descripción
Formato
%m
Mes del año (01­12).
%d
Día del mes (01­31).
%y
Año, únicamente las dos últimas cifras (00­99).
%Y
Año con cuatro cifras.
%D
Visualización con el formato mm/dd/yy.
%H
Horas (00­23).
%M
Minutos (00­59).
%S
Segundos (00­59).
%T
Hora con el formato HH:MM:SS.
%J
Día del año (1­366).
%w
Día de la semana (0­6) con Sábado = 0.
%a
Abreviación del día (lun. , mar. , etc.).
%h
Abreviación del mes (Feb., Jul. , etc.).
%r
Hora con formato HH:MM:SS con HH (0­12).
%n
Nueva línea.
%t
Tabulación.
Ejemplo
Visualización con formato Unix de la fecha actual.
PS > Get­Date ­Uformat
’Estamos a %a %d de %h del %Y, y son las %T’
Estamos a lun. 05 de ene. del 2015, y son las 23:21:19
3. Manipulación de fechas
a. Crear una fecha
Existen varias maneras de crear una fecha en PowerShell. La más habitual consiste en utilizar el
comando Get­Date. Usado sin parámetros, este comando devuelve un objeto que representa la fecha
y la hora actual. Si deseamos crear un objeto DateTime que contenga la fecha que queramos,
podemos especificarla gracias a los parámetros: ­Year, ­Month, ­Day, ­Hour, ­Minute, ­Second.
Ejemplo
Si deseamos definir una variable que contenga la fecha del 1 de febrero del 2015, el comando sería el
siguiente:
222
PS > $Date = Get­Date ­Year 2015 ­Month 2 ­Day 1
Tenga en cuenta que cualquier parámetro que no se haya especificado un valor corresponde a la fecha
actual.
b. Modificar una fecha
Al ejecutar un script o para una aplicación de terceros, podemos llegar a modificar una fecha dada. Para
responder a esto, debemos usar la familia de métodos Add*.
Los métodos Add permiten añadir una cantidad entera relativa de días con AddDays, de horas con
AddHours, de milisegundos con AddMilliseconds, de meses con AddMonth, de segundos con
AddSeconds, de años con AddYears y de ticks con AddTicks.
Los enteros relativos son el conjunto de enteros (0,1,2,3...) positivos y negativos (0,­1,­2,­3...).
Por ejemplo, para saber qué día de la semana será el mismo día que hoy pero dentro de un año, basta
con añadir un año a la fecha actual y recuperar el día de la semana correspondiente.
PS > $date = Get­Date
PS > $date.AddYears(1).DayOfWeek
Monday
De la misma manera, es fácil encontrar el día de su nacimiento.
Ejemplo
PS > $date = [DateTime]’10/03/1974’
PS > $date.DayOfWeek
Thursday
Cuando especificamos una fecha como en el ejemplo anterior, el formato esperado es el formato
anglosajón, a saber: mes/día/año.
c. Comparar fechas
Existen varios tipos de comparaciones de fechas siendo la más simple la que se realiza con el método
CompareTo. Aplicado a la variable de tipo DateTime, este método permite una comparación rápida y
devuelve los valores siguientes:
Descripción
Valor devuelto
­1
Si la fecha es anterior a la que la comparamos.
1
Si es posterior.
0
Si son iguales.
223
Ejemplo
Comparación de las fechas de dos archivos.
Para ello, basta con recuperar una a una las fechas de creación y compararlas con el método
CompareTo.
PS > $Date_archivo_1 = (Get­item Archivo_1.txt).Get_CreationTime()
PS > $Date_archivo_2 = (Get­item Archivo_2.txt).Get_CreationTime()
PS > $Date_archivo_1.CompareTo($date_archivo_2)
­1
d. Calcular un intervalo entre dos fechas
Es muy fácil calcular el tiempo trascurrido entre dos fechas. Esta operación se realiza gracias al comando
New­TimeSpan. El resultado de este comando no devuelve un objeto de tipo DateTime sino un
objeto
TimeSpan.
Ejemplo
Cálculo del tiempo transcurrido desde su nacimiento.
Para determinar el número de segundos que han transcurrido desde su nacimiento debe, en primer
lugar, calcular el tiempo transcurrido gracias al comando New­TimeSpan. Después, debe recuperar la
propiedad TotalSeconds, como se muestra más abajo.
Construimos primero la variable
$FechaNacim que contendrá nuestra fecha de nacimiento:
PS > $FechaNacim = Get­Date ­Year 1985 ­Month 10 ­Day 6 ­Hour 8
­Minute 30
Podemos también construirla de la siguiente manera. Es su elección:
PS > $FechaNacim = [datetime]’1985/10/06 08:30’
Después llamamos a New­TimeSpan y le pasamos la variable con nuestra fecha de nacimiento así como
la fecha y hora actual:
PS > New­TimeSpan ­Start $FechaNacim ­End (Get­Date)
Days
Hours
Minutes
Seconds
Milliseconds
Ticks
TotalDays
TotalHours
TotalMinutes
TotalSeconds
TotalMilliseconds
:
:
:
:
:
:
:
:
:
:
:
10683
14
54
10
333
9230648503339945
10683,6209529397
256406,902870554
15384414,1722332
923064850,333994
923064850333,994
Ahora, solo queda seleccionar la propiedad
TotalSeconds así:
224
PS > (New­TimeSpan ­Start $FechaNacim ­End (Get­Date)).TotalSeconds
923064911,014729
El comando New­TimeSpan devuelve un objeto de tipo TimeSpan. Para conocer todos los
métodos aplicables al tipo TimeSpan, teclee el siguiente comando: New­Timespan | Get­
Member.
e. Conversión de una fecha expresada en ticks
Frecuentemente ocurre que recibimos valores enteros largos para expresar fechas como por ejemplo un
valor del tipo «130088918630153620». Un valor de estas características corresponde al número de ticks
ocurridos desde el 1 de enero del año 1601 a las 0h00.
Es el caso por ejemplo de las fechas devueltas por el API ADSI cuando manipulamos el Active Directory
Domain Services pero es también el caso de ciertas clases .NET.
En este caso, ¡no se estrese! Aunque PowerShell no dispone de comandos para la conversión de una
fecha de este tipo, podemos aun así salir del atolladero utilizando directamente el framework .NET.
En efecto la clase
lo siguiente:
DateTime permite nativamente entender este tipo de formato, así podemos escribir
PS > [DateTime]130088918630153620
miércoles 27 de marzo del 0413 21:35:00
Aunque el resultado parezca correcto, existe sin embargo un problema. El problema es que la fecha
devuelta está en formato UTC (Coordinated Universal Time). Es decir que primero empieza por el año 1 y
segundo no tiene en cuenta la zona horaria en la que estamos. Así, nos encontramos con una hora de
diferencia si no tenemos en cuenta este detalle.
Para «aumentar» una fecha, basta con añadirle años gracias el método
el resultado debemos añadir 1600 años:
AddYears(). Así, para ajustar
PS > ([DateTime]130088918630153620).AddYears(1600)
miércoles 27 de marzo del 2013 21:35:00
A continuación, tenemos que ajustar la fecha UTC para convertirla en una fecha local, es decir en un
formato que tenga en cuenta la zona horaria en la que nos encontramos.
Para recuperar la zona horaria, usaremos la clase
Local, como podemos ver a continuación:
TimeZoneInfo y en particular su propiedad estática
PS > [TimeZoneInfo]::Local
Id
DisplayName
StandardName
DaylightName
BaseUtcOffset
SupportsDaylightSavingTime
:
:
:
:
:
:
Romance Standard Time
(UTC+01:00) Bruxelles, Copenhague, Madrid, Paris
Paris, Madrid
Paris, Madrid (hora de verano)
01:00:00
True
225
La última etapa consiste en modificar nuestra fecha aplicándole la zona horaria. Para ello utilizaremos el
método estático ConvertTimeFromUtc(), también de la clase TimeZoneInfo:
PS > $myUTCTime = ([DateTime]130088918630153620).AddYears(1600)
PS > [TimeZoneInfo]::ConvertTimeFromUtc($myUTCTime, [TimeZoneInfo]::Local)
miércoles 27 de marzo del 2013 22:35:00
Para terminar esta parte, transformamos esta pequeña pieza de script tan útil en una función ya que la
utilizaremos más adelante en este libro.
Por lo tanto podemos escribir la siguiente función:
function ConvertTo­LocalTime
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[Int64]$LongDate
)
$myUTCTime = ([DateTime]$LongDate).AddYears(1600)
[TimeZoneInfo]::ConvertTimeFromUtc($myUTCTime, [TimeZoneInfo]::Local)
}
Ahora probamos nuestra función:
PS > ConvertTo­LocalTime ­LongDate 130088918630153620
miércoles 27 de marzo del 2013 22:35:00
226
Perfiles
PowerShell
227
Introducción
La noción de perfil es una noción familiar para los profesionales IT como nosotros ya que existen desde
hace mucho tiempo en Windows con, entre otros, el famoso «perfil Windows» (que puede ser local o
remoto), así como el perfil de Outlook. Un perfil es simplemente un archivo (o conjunto de archivos) que
contiene las preferencias del usuario.
Tendrá a partir de ahora que componer perfiles adicionales, los de PowerShell. Y pueden ser numerosos, ya
que ¡podemos crear hasta una decena diferentes si así lo deseamos! Los perfiles PowerShell son
sencillamente scripts ejecutados automáticamente al arrancar la consola PowerShell, y eso en un orden
determinado. Permiten configurar nuestro entorno PowerShell para, por ejemplo, cargar automáticamente
funciones o asignar ciertas variables. Veremos varios casos de uso en este capítulo.
Una noción importante que debe conocer asociada a los perfiles PowerShell es la de «símbolo del sistema».
El símbolo del sistema es un entorno de ejecución PowerShell.
De partida, conocemos dos símbolos del sistema que son la consola clásica y la consola gráfica ISE, aunque
existen, sin embargo, muchos otros, como Visual Studio Code, PowerGUI, etc. Cada uno de estos símbolos
del sistema puede, si lo desea, tener en cuenta los perfiles que le son propios.
Perfiles disponibles
Podemos determinar la lista de perfiles que se pueden utilizar en el símbolo del sistema en ejecución ­ aquí
la consola Windows PowerShell estándar ­ gracias al siguiente comando:
PS > $profile | Format­List * ­force
AllUsersAllHosts
:
C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost
: C:\Windows\System32\WindowsPowerShell\v1.0\
Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts
: C:\Users\Arnaud\Documents\
WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\Arnaud\Documents\
WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Length
: 80
Podemos observar que se nos ofrecen cuatro perfiles diferentes. Veremos más adelante a qué se
corresponde cada uno de estos perfiles. Observe que, si bien están referenciados en la variable
$profile, ninguno de estos archivos está creado. Es algo que debe hacer usted.
La misma línea de comando aplicada al entorno ISE nos devolverá un resultado ligeramente distinto:
PS > $profile | Format­List * ­force
AllUsersAllHosts
: C:\Windows\System32\WindowsPowerShell\v1.0\
profile.ps1
AllUsersCurrentHost
: C:\Windows\System32\WindowsPowerShell\v1.0\
Microsoft.PowerShellISE_profile.ps1
CurrentUserAllHosts
: C:\Users\Arnaud\Documents\
WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\Arnaud\Documents\
WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1
Length
: 83
Observemos ahora las rutas de los perfiles en PowerShell Core:
228
AllUsersAllHosts
AllUsersCurrentHost
: C:\Program Files\PowerShell\6.0.0\profile.ps1
: C:\Program Files\PowerShell\6.0.0\
Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts
: C:\Users\Arnaud\Documents\PowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\Arnaud\Documents\PowerShell\
Microsoft.PowerShell_profile.ps1
Como habrá comprendido, no va a ser preciso complicarse con los perfiles, pues, según el entorno de
ejecución de PowerShell, se almacenan en lugares distintos. Así, jugando con el nombre del archivo del perfil
podemos crear un perfil específico para cada símbolo del sistema.
$Profile es una variable automática que hace referencia a la ruta de todos los perfiles. Es un
poco especial ya que sus propiedades no son visibles si no tenemos a bien aplicar el
conmutador­force al comando de formateo Format­List.
Propiedades de $Profile
Descripción
CurrentUserCurrentHost
Este perfil es específico de la consola actual. Se aplica
únicamente al usuario actual.
CurrentUserAllHosts
Este perfil se aplica a todas las consolas, pero únicamente
para el usuario actual.
AllUsersCurrentHost
Este perfil es específico de la consola actual pero se aplica
a todos los usuarios del equipo. Es un perfil
llamado « máquina ».
AllUsersAllHosts
Este perfil es común a todas las consolas y a todos los
usuarios del equipo actual. Es un perfil llamado
« maquina ».
Es mejor manipular preferentemente los perfiles de los usuarios en vez de los perfiles del equipo ya
que los primeros pueden acompañarle si utiliza los perfiles Windows remotos o si ha puesto en marcha
alguna directiva de grupo que redirige la carpeta Mis documentos hacia una carpeta compartida. Si se
encuentra en este último caso, y utiliza PowerShell en un servidor, no olvide desactivar la configuración de
seguridad avanzada de Internet Explorer. Sin esto dependiendo de su directiva de ejecución de script
actual, PowerShell puede impedirle ejecutar su perfil.
Incluso aunque es posible tener un perfil común entre Windows PowerShell y PowerShell ISE, no es
posible compartir los perfiles entre PowerShell Core y Windows PowerShell. En efecto, estos no se
almacenan en la misma ruta de disco.
Orden de aplicación de perfiles
El orden de aplicación de perfiles es importante. PowerShell los aplica en el siguiente orden:
1.
AllUsersAllHosts
2.
AllUsersCurrentHost
3.
CurrentUserAllHosts
4.
CurrentUserCurrentHost
229
Como de costumbre son los parámetros los más cercanos al usuario los prioritarios y por lo tanto se aplican
en último lugar. Por ejemplo, si define varias veces la misma variable en sus perfiles, será la última definición
la que tendrá la última palabra.
Creación de un perfil
Por defecto, ningún perfil está creado. El método más simple para crear su perfil consiste en apoyarse en la
variable $profile. Efectivamente, como hemos visto anteriormente, esta variable contiene en sus
propiedades las rutas hacia los distintos perfiles que PowerShell tiene en cuenta.
Ahora vamos a crear un perfil que será común a todas las consolas (o símbolos del sistema) pero que se
aplicará solo a nosotros. Para ello, debemos elegir la propiedad CurrentUserAllHosts de la variable
$profile.
Verifiquemos primero hacia qué carpeta apunta esta propiedad:
PS > $profile.CurrentUserAllHosts
C:\Users\Arnaud\Documents\WindowsPowerShell\profile.ps1
Para crear su perfil, teclee el comando:
PS > New­Item ­Path $profile.CurrentUserAllHosts ­ItemType file ­Force
­force permite aquí crear la jerarquía de carpetas si esta no existe al crear el archivo.
Enhorabuena, su perfil se ha creado pero ocupa cero bytes ya que está vacío. Para modificarlo con el editor
gráfico ISE, teclee el siguiente comando:
PS > ise $profile.CurrentUserAllHosts
Puede hacer lo mismo con Visual Studio Code escribiendo el siguiente comando:
PS > code $profile.CurrentUserAllHosts
Ahora está preparado para personalizar su entorno preferido. Podría por ejemplo cambiar el color de fondo
de la ventana, su tamaño, el color de las caracteres, añadir nuevos alias o nuevas funciones, mostrar un
mensaje personalizado según el modo de acceso de la consola, etc.
En la siguiente parte veremos un abanico de lo que podemos hacer para personalizar nuestra consola
PowerShell.
Personalización del entorno
Todo lo que vamos a ver ahora es para que lo incluya en su perfil. Deberá elegir lo que más se adecue a sus
necesidades.
1. Modificación del prompt
El prompt es el conjunto de caracteres que indican que el equipo está listo para que los comandos se
puedan introducir.
230
Por defecto, tiene el siguiente aspecto: PS
RUTA_ACTUAL>
Se encontrará al arrancar PowerShell en el directorio raíz de su perfil Windows, o sea en Windows 10 y
Windows Server 2016: C:\Users\NombreDelUsuario.
Aquí tiene el prompt por defecto:
Prompt por defecto al arrancar la consola
PowerShell Core le sitúa por defecto en la carpeta de instalación, que en Windows es C:\Program
Files\PowerShell\6.0.0.
Para modificar el prompt, basta con redefinir la función Prompt de PowerShell. Veamos primero qué
contiene en su configuración de origen. Para visualizar su contenido, vamos a invocar al proveedor
Function gracias a su lector function: como se muestra a continuación:
PS > Get­Content function:prompt
"PS $($executionContext.SessionState.Path.CurrentLocation)`
$(’>’ * ($nestedPromptLevel + 1)) "
# .Link
# http://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll­help.xml
La función Prompt llama a la variable automática $executionContext que representa el contexto de
ejecución del símbolo del sistema actual. Mirándola de cerca, podemos comprender las siguientes cosas:
Recupera la ruta actual
(0).
Duplica el carácter > tantas veces como niveles tiene el prompt actual
(1).
Concatena las cadenas anteriores para obtener una cadena como sigue: PS
(0)(1)
$nestedpromptlevel indica si nos encontramos en un entorno embebido. Si esta variable contiene un
número superior a cero, significa que nos encontramos en un entorno embebido (caso particular cuando se
está depurando).
Resulta muy fácil redefinir la función Prompt, así podemos por ejemplo decidir eliminar del prompt la ruta
actual ya que, muchas veces debido a esto, el prompt es infinitamente largo. Por lo tanto cuando
navegamos por una jerarquía de directorios profunda, nuestra línea de comandos no cabe en una sola
línea haciendo incómoda la lectura. Sin embargo, para no privarnos de esta información interesante que
231
representa la ruta actual, la mostraremos en el título de la ventana, en vez del título habitual «Usuario:
Windows PowerShell».
Aquí tiene nuestra nueva función
Prompt:
function prompt
{
’PS > ’
$host.UI.RawUI.Set_WindowTitle((Get­Location))
}
Observará que el título de la ventana se refresca cada vez que cambiamos el directorio actual. La realidad
es un poco diferente ya que la función Prompt se evalúa cada vez que PowerShell nos devuelve el foco
para introducir una nueva línea de comandos.
$host hace referencia al objeto correspondiente a nuestro entorno. Posee un gran número de
propiedades y métodos que pueden servir para personalizar la ventana PowerShell.
a. Un prompt con mucho color
Para dar un pequeño toque de fantasía a nuestro prompt, podemos añadirle un poco de color, como por
ejemplo:
function prompt
{
Write­Host (’PS ’ + (Get­Location) +’>’) `
­NoNewLine ­Foreground yellow
’ ’
}
Haciendo esto, mostramos una cadena de caracteres en color con el comando Write­Host, a la cual le
decimos que no realice un retorno de línea con la opción ­NoNewLine. Después redefinimos nuestro
prompt con la expresión más sencilla: un espacio. Es obligatorio que la función prompt devuelva una
cadena de caracteres al flujo estándar. Sin eso el prompt por defecto «PS>» aparece. En vez de escribir
«’ ’» en la función, lo que puede parecer un poco raro, hubiésemos podido perfectamente escribir
Write­Output ’ ’, que representa lo mismo.
b. Un prompt siempre en hora
¿Podría querer mostrar la fecha y la hora en vez de la ruta actual?
Nada más sencillo. Probemos esto:
function prompt
{
Write­Host (’PS ’ + (Get­Date) + ’>’) ­NoNewLine ­Foreground yellow
’ ’
}
PS 12/02/2018 22:23:38>
Puede hacer muchas cosas en la función prompt, pero acuérdese de que esta función debe
devolver siempre un valor de tipo String. En caso contrario PowerShell mostrará el prompt por
defecto " PS>".
232
2. Modificación del tamaño de la ventana
Puede actuar sobre la ventana de la consola para modificar su tamaño, su color, su título, su posición, etc.
PowerShell permite actuar sobre la consola a través del objeto
host.ui.RawUI. Veamos sus
propiedades para ver sobre cuales podemos actuar:
PS > $host.UI.RawUI
ForegroundColor
BackgroundColor
CursorPosition
WindowPosition
CursorSize
BufferSize
WindowSize
MaxWindowSize
MaxPhysicalWindowSize
KeyAvailable
WindowTitle
:
:
:
:
:
:
:
:
:
:
:
DarkYellow
DarkMagenta
0,4
0,0
25
120,3000
120,50
120,87
192,87
False
Windows PowerShell
Si queremos ajustar horizontalmente nuestra ventana, tendremos que modificar a la vez su tamaño y el
tamaño del buffer asociado. Esto se hace de la siguiente manera:
PS
PS
PS
PS
PS
PS
PS
PS
>
>
>
>
>
>
>
>
$buff = $host.ui.RawUI.BufferSize
$buff.width = 150
$buff.Height = 3000
$host.ui.RawUI.BufferSize = $buff
$tamanio = $host.ui.RawUI.WindowSize
$tamanio.Width = $buff.width
$tamanio.Height = 60
$host.ui.RawUI.WindowSize = $tamanio
# init. de la variable $buff
# núm. de car. por línea
# núm. de líneas verticales
# inicializamos la variable
# ajuste de la ventana/buffer
# número de líneas verticales
El tamaño del buffer y de la ventana deben ser estrictamente iguales si no desea tener scroll
horizontal.
3. Modificación de los colores
Puede elegir los colores para su entorno preferido, tanto para los caracteres como para el color de fondo
de la ventana.
Aquí tiene la lista de colores posibles:
Black
Blue
Cyan
DarkBlue
DarkCyan
DarkGray
DarkGreen
DarkMagenta
DarkRed
DarkYellow
Gray
Green
Magenta
Red
White
Yellow
Se puede obtener también la lista de colores de la siguiente manera:
PS > [Enum]::GetValues([System.ConsoleColor])
233
Observe que esta técnica funciona con todos los tipos Enumeración.
Para asignar los colores a las propiedades, haga lo siguiente:
PS > $host.ui.RawUI.ForeGroundColor = ’White’ # Color del texto
PS > $host.ui.RawUI.BackGroundColor = ’Black’ # Color del fondo
$host.UI.RawUI.BackGroundColor,
es preferible limpiar a continuación la pantalla con un Clear­Host. Si no lo hace, solo se aplicará el
Cuando cambiamos el color de fondo de la ventana con
color de fondo a los nuevos caracteres; lo que no resulta ser el mejor efecto visual. Puede también
asignar colores diferentes a los definidos por defecto para los mensajes de error y de depuración. Para
consultarlos, teclee $host.privatedata.
A continuación presentamos la lista de propiedades y colores por defecto:
PS > $host.privatedata
ErrorForegroundColor
ErrorBackgroundColor
WarningForegroundColor
WarningBackgroundColor
DebugForegroundColor
DebugBackgroundColor
VerboseForegroundColor
VerboseBackgroundColor
ProgressForegroundColor
ProgressBackgroundColor
:
:
:
:
:
:
:
:
:
:
Red
Black
Yellow
Black
Yellow
Black
Yellow
Black
Yellow
DarkCyan
4. Modificación del título de la ventana
El título de la ventana se modifica gracias a la propiedad
WindowTitle, de la siguiente manera:
PS > $host.ui.RawUI.WindowTitle = ’www.PowerShell­Scripting.com’
Observe que esta propiedad no es dinámica. No podrá por lo tanto mostrar la hora del sistema y verla
refrescarse en tiempo real. Sin embargo, puede utilizar la función prompt que se encargará de actualizar
el título de la ventana con frecuencia.
En el siguiente ejemplo, en función del usuario conectado, mostramos en la barra de título si la consola se
ejecuta en modo administrador (UAC) o no. En efecto, conviene saber que, cuando abre una consola
PowerShell por defecto, esta no posee todos los privilegios a causa del UAC. De modo que puede ser
interesante saberlo dando un vistazo a la barra de título de la ventana de la consola.
He aquí el fragmento de código que tiene que introducir en su perfil PowerShell para que se ejecute al
inicio de la consola.
function IsAdmin
{
$CurrentUser =
[System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal =
New­Object System.Security.principal.windowsprincipal($CurrentUser)
$principal.IsInRole( `
[System.Security.Principal.WindowsBuiltInRole]::Administrator)
234
}
if (isAdmin)
{
$ElevationMode = ’* Modo administrador *’
}
else
{
$ElevationMode = ’* Mode usuario simple *’
}
$host.ui.RawUI.WindowTitle = $ElevationMode
Modificación del título de la consola
Para ejecutar PowerShell en modo administrador, debe hacer clic con el botón derecho sobre el icono
PowerShell y elegir Run as Administrator.
5. Ejemplo de perfil completo
A continuación puede ver cómo quedaría nuestro perfil reuniendo todos los pequeños fragmentos de
scripts que hemos visto anteriormente.
# Definición de un alias personalizado
Set­alias ­name grep ­value Select­String
# Definición de las funciones
function IsAdmin
{
$CurrentUser =
[System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal =
New­Object System.Security.principal.windowsprincipal($CurrentUser)
$principal.IsInRole( `
[System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
# Modificación de las variables de preferencia
$VerbosePreference = ’Continue’
$DebugPreference
= ’Continue’
235
# Mensaje de bienvenida personalizado
#
if (isAdmin) {
$ElevationMode = ’Administrador’
$host.UI.RawUI.BackGroundColor = ’DarkRed’
Clear­Host
}
else {
$ElevationMode = ’No Administrador’
$host.UI.RawUI.BackGroundColor = ’DarkMagenta’
Clear­Host
}
$CurrentUser =
[System.Security.Principal.WindowsIdentity]::GetCurrent()
Write­Host ’+­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­+’
Write­Host ("+­ Hola {0} " ­f ($CurrentUser.Name).split(’\’)[1])
Write­Host ’+­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­+’
# Modificación del color del prompt en amarillo, reemplazo
# del Prompt por PS > y mostrar la ruta en curso en la barra de título
# de la ventana de la consola
function prompt
{
Write­Host (’PS ’ + ’>’) ­NoNewline ­ForegroundColor yellow
$host.UI.RawUI.Set_windowtitle("$(Get­Location) [$ElevationMode]")
’ ’
}
Aquí tiene el resultado de la ejecución de nuestro perfil:
Arranque de la consola con un perfil personalizado
Cuando abre la consola, puede que en ocasiones vea aparecer el siguiente mensaje: « Loading
personal and system profiles took 1709ms » o similar.
Este mensaje se utiliza para informarle de que el tiempo de inicio de la consola se ha extendido
debido al tiempo necesario para cargar su perfil.
236
Ejecutar PowerShell sin perfil
En ciertos casos, es necesario lanzar PowerShell sin perfil para no alargar inútilmente el tiempo de apertura
de la consola y/o para estar seguro de no molestar la correcta ejecución de un script. Esto resulta muy útil
para ejecutar scripts PowerShell mediante una tarea programada.
Los ejecutables PowerShell.exe (Windows PowerShell) y pswh.exe (PowerShell Core) poseen para ello el
parámetro ­Noprofile.
Así podemos escribir, por ejemplo, la línea de comandos siguiente para iniciar PowerShell ignorando
cualquier perfil y ejecutar miScript.ps1:
PS > pwsh.exe ­noprofile ­file ./miScript.ps1
Existen
numerosos
parámetros
disponibles.
Para
obtener
una
lista
exhaustiva,
teclee
powershell.exe /? o pwsh;exe / ? .
237
Snap-ins,
módulos y
PowerShell
Gallery
238
Introducción
Allí donde coinciden PowerShell y la célebre firma de la manzana es en torno al famoso eslogan «hay una
aplicación para ello». En efecto, en el ecosistema PowerShell podríamos establecer un paralelismo diciendo
que «hay un comando para ello». Esto es todavía más cierto después de que Microsoft haya creado la
PowerShell Gallery (de la que hablaremos al final de este capítulo) podríamos compararla con un almacén de
aplicaciones. Salvo que, en vez de encontrar aplicaciones, encontramos numerosos comandos adicionales
empaquetados en módulos. Como comprenderá, estos comandos adicionales van a permitir extender hasta
el infinito las funcionalidades básicas que ofrece PowerShell.
Por otro lado, PowerShell ha suscitado el interés de muchos fabricantes, como, por ejemplo, VMWare,
Amazon, Google, Citrix, HP, Hitachi, EMC, F5 Networks, etc. Todos proponen snap­ins o módulos para
gestionar sus aplicaciones o equipos. No parece que vaya a detenerse, lo cual está muy bien, ya que cada
vez pedimos más.
Los snap­ins
La noción de «snap­in» concierne a Windows PowerShell. Podríamos decir que el snap­in es el ancestro del
módulo. Por este motivo, Microsoft los ha retirado completamente de PowerShell Core en beneficio de los
módulos.
Con Windows PowerShell 1.0, las nuevas funcionalidades y comandos adicionales se realizaban únicamente
mediante snap­ins. Por razones de compatibilidad, Windows PowerShell sigue soportando los snap­ins,
pero, a partir de PowerShell 2.0, han cedido su sitio a los módulos, mucho más prácticos y fáciles de usar.
En efecto, para instalar un snap­in, hay que poseer permisos de administrador; los snap­ins se
proporcionan como archivos MSI, pues deben registrar sus DLL en el registro. Por el contrario, para instalar
un módulo, basta con un simple copiar/pegar y puede hacerse con permisos de usuario sencillo. Por último,
como guinda, para desarrollar snap­ins hay que hacerlo necesariamente con un lenguaje .NET compilado
(como, por ejemplo, C# o VB.NET), mientras que los módulos pueden desarrollarse en PowerShell, lo cual
hace su desarrollo mucho más compatible.
1. Enumerar los snap­ins instalados
Para conocer la lista de snap­ins presentes en su equipo e importados en la sesión actual, teclee el
comando Get­PSSnapin. Por defecto, en Windows PowerShell solo existe uno: el snap­in
Microsoft.PowerShell.Core.
PS > Get­PSSnapin
Name
: Microsoft.PowerShell.Core
PSVersion
: 5.1.14393.1944
Description : This Windows PowerShell snap­in contains cmdlets used
to manage components of Windows PowerShell.
Get­PSSnapin posee también un conmutador ­Registred. Este, cuando se especifica, permite
enumerar los snap­ins instalados en nuestro sistema pero que no han sido importados en la sesión actual.
El resultado del comando no engloba a los snap­ins necesarios para el funcionamiento de PowerShell.
Aquí tiene por ejemplo los snap­ins disponibles cuando instalamos el Toolkit PowerCLI puesto a
disposición por VMware para administrar máquinas virtuales. En efecto, este se ha resistido, pues no se
convirtió en módulo hasta finales de 2016. Y, en el momento de escribir estas líneas, los snap­ins no se
utilizan prácticamente nada…
239
PS > Get­PSSnapin ­Registered
Name
: VMware.DeployAutomation
PSVersion
: 2.0
Description : Cmdlets for Rule­Based­Deployment
Name
:
PSVersion
:
Description :
Image Builder
VMware.ImageBuilder
2.0
This Windows PowerShell snap­in contains VMware ESXi
cmdlets used to generate custom images.
Name
: VMware.VimAutomation.Storage
PSVersion
: 2.0
Description : This Windows PowerShell snap­in contains cmdlets that
let you manage vSphere policy based storage.
Name
: VMware.VimAutomation.Vds
PSVersion
: 2.0
Description : This Windows PowerShell snap­in contains cmdlets that
let you manage vSphere Distributed Switches.
Este comando devuelve los snap­ins instalados en el equipo pero no importados en la sesión.
2. Importar un snap­in
En cuanto a la importación, esta se realiza mediante el comando Add­PSSnapin. Retomemos el ejemplo
el snap­in puesto a nuestra disposición por el fabricante VMware. Disponible en el sitio web de VMware
con el nombre vSphere PowerCLI, esta caja de herramientas es un conjunto de archivos DLL que, una
vez instalados, se pueden importar como snap­in con el comando Add­PSSnapin:
PS > Add­PSSnapin ­Name VMware.VimAutomation.Core
Una vez cargado el snap­in, si sabemos que el nuevo conjunto de comandos contiene las letras «VM»,
podemos ver los comandos de la siguiente manera:
PS > Get­Command ­Type cmdlet ­Name *VM*
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
...
Name
­­­­
Add­VMHost
Add­VmHostNtpServer
Get­VM
Get­VMGuest
Get­VMGuestNetworkInterface
Get­VMHost
ModuleName
­­­­­­­­­­
VMware.VimAutomation.Core
VMware.VimAutomation.Core
VMware.VimAutomation.Core
VMware.VimAutomation.Core
VMware.VimAutomation.Core
VMware.VimAutomation.Core
Pero podemos hacer cosas más chulas aún ya que si el comando no contiene los caracteres «VM» no lo
veremos de esta manera.
240
3. Enumerar los comandos de un snap­in
Como habíamos visto en el capítulo Descubrimiento de PowerShell, por defecto el comando Get­
Command devuelve para cada comando su módulo de origen. Debe saber que esta propiedad es válida
tanto para los comandos que vienen de un módulo como de un snap­in.
Así, para enumerar los comandos de nuestro snap­in, basta con teclear el comando
Module Nombre_del_Snap­in.
Get­Command ­
Ejemplo
Listado de comandos disponibles para el snap­in
Microsoft.PowerShell.Core
PS > Get­Command ­Module Microsoft.PowerShell.Core
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
...
Name
­­­­
Add­History
Add­PSSnapin
Clear­History
Connect­PSSession
Disable­PSRemoting
Disable­PSSessionConfiguration
Disconnect­PSSession
Enable­PSRemoting
Enable­PSSessionConfiguration
Enter­PSSession
Exit­PSSession
Export­Console
Export­ModuleMember
ForEach­Object
Get­Command
Get­Help
Get­History
ModuleName
­­­­­­­­­­
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
4. Descargar un snap­in
Cuando el snap­in no tiene ya sentido, puede querer suprimirlo de la sesión actual (no del sistema) para
liberar memoria usando el comando Remove­PSSnapin.
PS > Remove­PSSnapin ­Name VMware.VimAutomation.Core
Es raro que llegue a utilizar este comando ya que en general basta con cerrar la consola PowerShell
cuando hemos acabado nuestro trabajo.
Los módulos
Como hemos dicho antes, los módulos han aparecido con la versión 2.0 de PowerShell. Un módulo es un
contenedor (package) que agrupa comandos, pero también scripts, variables, alias y funciones.
Comparándolo con un snap­in, la ventaja de un módulo es que es fácilmente transportable (basta con una
simple copia del archivo) y por lo tanto también fácil de compartir para que otros usuarios puedan
disfrutarlo. También es bastante fácil crearlo uno mismo, aunque esto excede los objetivos de este libro...
La idea del equipo de PowerShell de Microsoft es crear una gran comunidad de usuarios y hacer que esta
pueda intercambiar o compartir módulos a la imagen de la comunidad CPAN (Comprehensive Perl Archive
Network) que conocen bien los usuarios del lenguaje PERL.
241
Con este objetivo nació en 2015 el sitio web www.powershellgallery.com. La «PowerShell Gallery» pretende
ser un repositorio central de módulos PowerShell dirigido por Microsoft para la comunidad internacional de
usuarios PowerShell. Este repositorio web es iniciativa de Microsoft. Necesitará como mínimo la versión 5 de
PowerShell para poder aprovechar comandos que permiten interactuar con los módulos presentes en este
repositorio.
Una de las grandes ventajas de los módulos respecto de los snap­ins es que no es necesario ser
administrador del equipo para instalarlos y usarlos.
1. Instalar un módulo
Con Windows Server, cada rol instalado da también derecho a un módulo que se instala automáticamente.
Sin embargo, puede también recibir el módulo de un tercero. En este caso, basta con copiarlo en alguna
de las ubicaciones previstas para alojar los módulos y ¡ya está! Un módulo se presenta bajo la forma de
una carpeta que contiene uno o varios archivos. Para que un módulo sea válido, debe contener como
mínimo un archivo con la extensión .psd1, así como un archivo con la extensión .psm1. Estos dos
archivos tienen el mismo nombre que la carpeta del módulo.
El primer archivo (.psd1) contiene los metadatos del módulo (versión, nombre del autor, funciones y
variables exportadas, etc.). El segundo archivo (.psm1) contiene, por su parte, el código de las funciones.
Tenga precaución, pues las ubicaciones para almacenar los módulos difieren entre Windows PowerShell y
PowerShell Core. Sea la que sea, está referenciada en la variable de entorno PSModulePath.
Contenido de la variable PSModulePath en Windows PowerShell:
PS > $env:PSModulePath ­split ’;’
C:\Users\Administrator\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules
Contenido de la variable PSModulePath en PowerShell Core:
PS > $env:PSModulePath ­split ’;’
C:\Users\Administrator\Documents\PowerShell\Modules
C:\Program Files\PowerShell\Modules
c:\program files\powershell\6.0.0\Modules
Utilizamos el operador split para dividir el contenido de la variable PSModulePath con el
carácter punto y coma. Esto nos permite obtener un resultado más comprensible en lugar de
obtenerlo todo en la misma línea.
Con Windows PowerShell, las ubicaciones de los módulos son las siguientes:
Ruta
Utilización
%UserProfile%\Documents\
WindowsPowerShell\Modules
Esta ubicación contiene los módulos del usuario
en curso.
C:\Program Files\
WindowsPowerShell\Modules
Ubicación común a todos los usuarios de la
máquina.
242
Utilización
Ruta
%Windir%\System32\
WindowsPowerShell\v1.0\
Modules
Esta ubicación contiene todos los módulos
proporcionados por Microsoft. Se comparten
entre todos los usuarios de una máquina. Aquí
es donde encontraremos los módulos instalados
por defecto o aquellos instalados tras instalar un
rol o una funcionalidad de Windows.
Si añadimos una carpeta a la variable de entorno PSModulePath, PowerShell la analizará
para determinar la presencia de módulos. Esto da flexibilidad para la organización de los
módulos.
Observe que por defecto la carpeta
Modules del «Home del usuario» no existe. Tenemos por lo tanto
que crearla manualmente.
Si instala un módulo a través de la PowerShell Gallery utilizando el comando Install­Module
seguido del parámetro ­scope CurrentUser, la carpeta «WindowsPowerShell» o «PowerShell»
(según la versión de PowerShell utilizada) se creará automáticamente.
Buenas prácticas acerca de las ubicaciones
De forma general, evite instalar sus módulos en la carpeta de instalación de PowerShell, es decir
C:\Windows\system32\WindowsPowerShell\v1.0\Modules en Windows PowerShell o
c:\program files\powershell\6.0.0\Modules con PowerShell Core.
Si el módulo que instala solo le afecta a usted, entonces sitúelo en su perfil de Windows, es decir, en
C:\Users\Administrator\Documents\WindowsPowerShell\Modules
o
C:\Users\Administrator\Documents\PowerShell\
versión de PowerShell utilizada.
Modules
en
función
de
la
De manera inversa, si desea que un módulo esté disponible para todos los usuarios de una máquina (en
el caso de un servidor, por ejemplo), la ubicación correcta es la carpeta C:\Program
Files\WindowsPowerShell o C:\Program Files\PowerShell\Modules.
2. Mostrar los módulos disponibles
Para conocer los módulos disponibles, así como los módulos ya importados, PowerShell dispone del
comando Get­Module.
Get­Module posee varios parámetros de los cuales presentamos los más utilizados:
Descripción
Parámetro
­All <Switch>
Permite obtener todos los módulos importados así
como los objetos de tipo «módulo» dependientes. Sin
este parámetro, se enumera un único objeto por
módulo (es decir sin las dependencias).
­ListAvailable <Switch>
Permite obtener todos los módulos instalados pero no
importados en la sesión.
­Name <String[]>
Permite obtener
especificados.
únicamente
el
o
los
módulos
243
Descripción
Parámetro
­Refresh <Switch>
Permite refrescar la caché de la sesión actual para
obtener los comandos pertenecientes a los módulos.
Este parámetro es sobre todo útil cuando se modifica
un módulo durante la sesión de usuario. Recuerde
siempre que este parámetro debe usarse en
combinación con ­ListAvailable.
­PSSession <PSSession>
Permite indicar una sesión remota (WinRM) para
ejecutar el comando sobre un sistema remoto.
Utilizado sin argumentos,
Get­Module devuelve los módulos importados en la sesión actual:
PS > Get­Module
Para enumerar los módulos instalados (en las rutas definidas en la variable $Env:PSModulePath) pero
no obligatoriamente importados, debe utilizar el parámetro ­listAvailable.
Como habrá adivinado, no obtendrá el mismo resultado en función de la edición de PowerShell que utilice,
pues las ubicaciones de los módulos son diferentes.
Por ejemplo, he aquí lo que nos devuelve la siguiente línea de comandos sobre Windows Server 2016 con
PowerShell Core:
PS > Get­Module ­listAvailable
Directory: C:\Program Files\PowerShell\Modules
ModuleType Version
­­­­­­­­­­ ­­­­­­­
Script
1.6.4
Name
­­­­
EZLog
ExportedCommands
­­­­­­­­­­­­­­­­
{Write­EZLog, Conve...
Directory: C:\program files\powershell\6.0.0\Modules
ModuleType Version
­­­­­­­­­­ ­­­­­­­
Manifest
1.0.0.0
Manifest
1.1.0.0
Manifest
3.0.0.0
Manifest
3.0.0.0
Manifest
3.1.0.0
Manifest
3.0.0.0
Manifest
3.1.0.0
Manifest
3.0.0.0
Script
1.1.7.0
Script
1.6.0
Script
0.0
Script
1.0.0.0
Script
1.2
Name
­­­­
CimCmdlets
Microsoft.PowerShell.Archive
Microsoft.PowerShell.Diagnostics
Microsoft.PowerShell.Host
Microsoft.PowerShell.Management
Microsoft.PowerShell.Security
Microsoft.PowerShell.Utility
Microsoft.WSMan.Management
PackageManagement
PowerShellGet
PSDesiredStateConfiguration
PSDiagnostics
PSReadLine
ExportedCommands
­­­­­­­­­­­­­­­­
{Get­CimAssociatedI...
{Compress­Archive, ...
{Get­WinEvent, New­...
{Start­Transcript, ...
{Add­Content, Clear...
{Get­Acl, Set­Acl, ...
{Format­List, Forma...
{Disable­WSManCredS...
{Find­Package, Get­...
{Install­Module, Fi...
{ThrowError, Get­PS...
{Disable­PSTrace, D...
{Get­PSReadlineKeyH...
Constatamos que tenemos módulos situados en varias carpetas, y un módulo externo llamado EZLOG
instalado en C:\Program Files\PowerShell\Modules.
244
3. Cargar/importar un módulo
Desde PowerShell versión 3 no tenemos que importar manualmente los módulos para usar los comandos
que contienen. PowerShell importa automáticamente un módulo cuando utiliza un comando. Por este
motivo Get­Command devuelve todos los comandos instalados en el sistema, aunque no se encuentren
en la sesión actual.
Aquí lo puede comprobar. En nuestra sesión actual, solo están cargados dos módulos :
PS > Get­Module
ModuleType Version
­­­­­­­­­­ ­­­­­­­
Manifest
3.1.0.0
Manifest
3.1.0.0
Name
­­­­
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
ExportedCommands
­­­­­­­­­­­­­­­­
{Add­Computer,...
{Add­Member, Add­Type...
Sin embargo, podemos enumerar comandos pertenecientes a un módulo no cargado.
PS > Get­Command ­Module
CommandType
­­­­­­­­­­­
Alias
Function
Function
Function
Function
Function
Function
Function
Function
Function
...
DnsServer
Name
­­­­
Export­DnsServerTrustAnchor
Add­DnsServerConditionalForwarderZone
Add­DnsServerDirectoryPartition
Add­DnsServerForwarder
Add­DnsServerPrimaryZone
Add­DnsServerResourceRecord
Add­DnsServerResourceRecordDS
Add­DnsServerResourceRecordMX
Add­DnsServerResourceRecordPtr
Get­DnsServerDiagnostics
ModuleName
­­­­­­­­­­
DNSserver
DNSserver
DNSserver
DNSserver
DNSserver
DNSserver
DNSserver
DNSserver
DNSserver
DNSserver
Podemos también usarlos sin tener que cargar el módulo.
PS > Get­DnsServer
ServerSetting:
==============
EnableOnlineSigning
TcpReceivePacketSize
WriteAuthorityNs
SocketPoolSize
AppendMsZoneTransferTag
NameCheckFlag
UpdateOptions
MaximumTrustAnchorActiveRefreshInterval
EnableIPv6
RpcProtocol
ForestDirectoryPartitionBaseName
AutoCreateDelegation
EnableDirectoryPartitions
SelfTest
DsAvailable
EnableSendErrorSuppression
SilentlyIgnoreCNameUpdateConflicts
True
65536
False
2500
False
2
783
15.00:00:00
True
5
ForestDnsZones
2
True
4294967295
True
True
False
245
EnableDuplicateQuerySuppression
DomainDirectoryPartitionBaseName
ReloadException
AdminConfigured
StrictFileParsing
AllowCNameAtNs
MaximumSignatureScanPeriod
IsReadOnlyDC
DisableAutoReverseZone
...
True
DomainDnsZones
False
True
False
True
2.00:00:00
False
False
Nos damos cuenta de que se ha importado el módulo automáticamente.
PS > Get­Module
ModuleType
­­­­­­­­­­
Manifest
Manifest
Manifest
Version
­­­­­­­
2.0.0.0
3.1.0.0
3.1.0.0
Name
­­­­
DnsServer
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
ExportedCommands
­­­­­­­­­­­­­­­­
{Add­DnsServer...
{Add­Computer,...
{Add­Member, Add­Type...
Evidentemente, podemos configurar la manera de funcionar de la carga automática. Esto se realiza
usando la variable $PSModuleAutoloadingPreference. Los valores para esta variable son los
siguientes:
Valor
Descripción
All
Importar automáticamente
necesidad.
ModuleQualified
Se importan los módulos automáticamente únicamente cuando
el usuario especifica el nombre del módulo al usar uno de sus
comandos.
Por
ejemplo,
si
el
usuario
teclea
"MiModulo\MiComando", Windows PowerShell importa el módulo
MiModulo.
None
Ninguna importación automática de módulo incluyendo los
módulos Built­in PowerShell:
los
módulos
en
función
de
la
Microsoft.PowerShell.Utility
Microsoft.PowerShell.Diagnostics
Microsoft.PowerShell.Host
Microsoft.PowerShell.Management
Microsoft.PowerShell.Security
Microsoft.PowerShell.Utility
Debemos subrayar que algunos comandos considerados como
básicos (como por ejemplo Read­Host o Get­Acl) no
funcionarán si elige este valor.
Si ha elegido no importar los módulos automáticamente, (o el módulo no se encuentra en el Path),
entonces tendrá que realizar la operación de carga manualmente con el comando Import­Module
<nombre módulo>.
Ejemplo
PS > Import­Module Storage
246
Una vez importado el módulo, el comando
Get­Module visto anteriormente confirma que el módulo está
cargado en memoria.
PS > Get­Module
ModuleType
­­­­­­­­­­
Manifest
Manifest
Manifest
Manifest
Version
­­­­­­­
2.0.0.0
3.1.0.0
3.1.0.0
2.0.0.0
El parámetro
módulo.
Name
­­­­
DnsServer
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
Storage
ExportedCommands
­­­­­­­­­­­­­­­­
{Add­DnsServer...
{Add­Computer,...
{Add­Member,...
{Add­Initiator...
verbose nos devuelve información sobre el contenido cargado cuando importamos un
PS > Import­Module Storage ­Verbose
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
...
Importing
Importing
Importing
Importing
Importing
Importing
Importing
Importing
Importing
Importing
function
function
function
function
function
function
function
function
function
function
’Add­InitiatorIdToMaskingSet’.
’Add­PartitionAccessPath’.
’Add­PhysicalDisk’.
’Add­TargetPortToMaskingSet’.
’Add­VirtualDiskToMaskingSet’.
’Clear­Disk’.
’Clear­FileStorageTier’.
’Connect­VirtualDisk’.
’Disable­PhysicalDiskIndication’.
’Disconnect­VirtualDisk’.
4. Prefijar los comandos de un módulo
Podemos prefijar el nombre de los comandos exportados. Esto permite identificar fácilmente los comandos
de un módulo y evitar así colisiones de nombres entre varios comandos. Tomemos el ejemplo del módulo
siguiente en el que se presentan dos comandos.
PS > Get­Module MiModuloPerso
ModuleType Name
­­­­­­­­­­ ­­­­
Script
MiModuloPerso
ExportedCommands
­­­­­­­­­­­­­­­­
{List­Archive, Get­Archive}
Usando el parámetro ­Prefix al usar Import­Module, importamos los comandos pero añadiendo el
prefijo de nuestra elección delante del nombre de cada comando.
PS > Import­Module MiModuloPerso ­Prefix My
Y aquí tiene el resultado. Una vez realizado, podemos filtrar los comandos con el prefijo elegido.
PS > Get­Command *My*
CommandType
­­­­­­­­­­
Cmdlet
Cmdlet
Name
­­­­
List­MyArchive
Get­MyArchive
ModuleName
­­­­­­­­­­
MiModuloPerso
MiModuloPerso
247
5. Enumerar los comandos de un módulo
Como hemos visto un poco más arriba (con los snap­ins) en este capítulo, por defecto el comando
Get­
Command devuelve para cada comando su módulo de origen.
Así, una vez identificado el nombre del módulo, basta con pasarlo al parámetro
­Module como en el
siguiente ejemplo:
PS > Get­Command ­Module Microsoft.PowerShell.Core
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
...
Name
­­­­
Add­History
Add­PSSnapin
Clear­History
Connect­PSSession
Disable­PSRemoting
Disable­PSSessionConfiguration
Disconnect­PSSession
Enable­PSRemoting
Enable­PSSessionConfiguration
Enter­PSSession
Exit­PSSession
Export­Console
Export­ModuleMember
ForEach­Object
Get­Command
Get­Help
Get­History
ModuleName
­­­­­­­­­­
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
Microsoft.PowerShell.Core
6. Seguir el uso de los módulos
Es posible hacer un seguimiento, en el visor de eventos, de la actividad de los comandos contenidos en los
módulos. Para ello, debe activar la directiva Administrative Template\Windows Components\Windows
PowerShell\Turn on Module Logging.
248
Directiva para el seguimiento del uso de módulos 1/2
Esta directiva permite filtrar, con el botón Show…, los módulos que serán auditados.
249
Directiva para el seguimiento del uso de módulos 2/2
Por ejemplo, para auditar el uso del módulo PSCX que hemos descargado, teclee «PSCX».
El valor indicado acepta el carácter de substitución «*». Así para elegir todos los módulos built­
in, podemos introducir el siguiente valor: Microsoft.PowerShell.*
Observemos ahora el resultado para la ejecución de un comando que pertenece al módulo PSCX.
PS > Get­DomainController
SiteName
CurrentTime
­­­­­­­­
­­­­­­­­­­­
Default­First­Sit... 07/01/2018 21:55:29
Name
­­­­
WIN2K12R2.ADPS1.local
De un rápido vistazo mediante el comando Get­EventLog, nos asegura que el último evento en el log
Windows PowerShell está referido a un comando del módulo auditado.
PS > Get­EventLog ­Log "Windows PowerShell" ­Newest 1 |
format­Table ­Property Source,InstanceID,Message
Source
­­­­­­
PowerShell
InstanceID
­­­­­­­­­­
800
Message
­­­­­­­
Details ... command : Get­Domain...
7. Descargar un módulo
El comando
Remove­Module permite descargar el módulo de memoria.
PS > Remove­Module EZLog
250
Es bastante poco frecuente tener que descargar un módulo. En general, cuando se ha terminado de
trabajar con él, simplemente se cierra la consola PowerShell.
No se preocupe, este comando no elimina el módulo del disco, pues, como con la instalación, esta
operación corre de su cargo (a menos que utilice la PowerShell Gallery a través de los comandos
específicos).
PowerShell Gallery
1. ¿Qué es?
La PowerShell Gallery es un repositorio de scripts y de módulos (es decir, de numerosos recursos DSC).
Está alojado y gestionado por Microsoft y todo el mundo está invitado a contribuir con sus creaciones, y
también a consumirlas. Como comprenderá, la idea de la galería PowerShell es favorecer la compartición
para evitar tener que reinventar la rueda.
Microsoft es, evidentemente, un contribuyente importante de la PowerShell Gallery, pero no es el único.
Los autores somos también contribuyentes, aunque de manera modesta, al enriquecimiento de este
repositorio.
En el momento de escribir estas líneas, en la galería existen más de 2750 elementos únicos publicados
(scripts y módulos) para un conjunto de más de 15 000 elementos si se contabilizan todas las versiones.
Conviene saber que los módulos representan, por su parte, más del 85 % de los elementos presentes en
la biblioteca. Estos, a diferencia de los scripts, presentan la ventaja de que están versionados
rigurosamente y poseen muchos metadatos. La galería PowerShell es, ante todo, un repositorio de
módulos.
2. ¿Quid de la seguridad?
Como se trata de un repositorio público, esta cuestión nos viene inmediatamente a la mente. Es del todo
normal. Y mucho mejor plantearse la pregunta, pues de lo contrario podríamos sufrir graves
consecuencias.
Conviene saber que, cuando alguien quiere publicar un módulo en la galería, Microsoft utiliza un robot que
realiza las siguientes acciones:
1. Instalación del módulo.
2. Escaneo con el antivirus para detectar eventuales malwares.
3. Aplicación del módulo «PowerShell Script Analyzer».
El PowerShell Script Analyzer (presente en la propia galería) es un excelente módulo que permite
comprobar la calidad del código de un módulo o de un script PowerShell. Sirve para verificar ­ a través de
un cierto número de reglas predefinidas ­ que se respetan las buenas prácticas. Como, por ejemplo:
Que no se recupera ni almacena ninguna contraseña sin cifrar.
Si se deben utilizar credenciales en una función, debe hacerse de una manera segura.
No debe almacenarse directamente ningún nombre de máquina.
Etc.
251
Si un módulo se considera como no conforme, es decir, si no satisface alguno de los tres criterios
anteriores (instalación correcta, escaneo de antimalware y Script Analyzer), se notifica por correo
electrónico al propietario del módulo. Si pasadas unas semanas el módulo no se ha corregido, se retira de
la galería. Vemos que existen, claramente, ciertos riesgos.
Un consejo: tenga precaución con lo que instala. Le recomendamos instalar o bien únicamente módulos
creados por personas de su confianza, o bien, antes de realizar cualquier instalación, estudiar el código
fuente si tiene la capacidad. Veremos, a través de esta sección, cómo visualizar el código fuente de los
módulos incluso sin tener que instalarlos, pero también cómo descargar un módulo sin realmente
instalarlo.
3. ¿Cómo acceder a la galería?
Para acceder (entendemos por ello consumir los elementos que allí se encuentran), podemos bien ir
directamente al sitio web www.powershellgallery.com para explorar su contenido, o bien utilizar
PowerShell mediante la línea de comandos. Veremos ambos métodos.
a. El sitio web www.powershellgallery.com
Sobre la página de inicio de la PowerShell Gallery, encontrará en la sección superior derecha un campo
de búsqueda que le permite ir directamente «a la caza» de los módulos escribiendo palabras claves, con
un funcionamiento similar al de los motores de búsqueda en Internet.
d e s c a r g a do en: e y b o oks. c o m
252
Página de inicio de la galería PowerShell
Una vez introducidas las palabras clave en la zona de búsqueda, obtendrá una lista de resultados. No
tiene más que seleccionar el elemento que le parezca más adecuado.
Por ejemplo, supongamos que estamos buscando una función o un módulo que permita generar
fácilmente archivos de log en formato de texto bien estructurados. Intentemos realizar una búsqueda
con las palabras clave «easy logging» y veamos qué obtenemos.
253
Ejemplo de búsqueda en la PowerShell Gallery
Vemos cómo aparece una lista compuesta por 78 objetos. Cada uno de los elementos posee un nombre,
una descripción breve, un número de versión, un autor, así como el número de veces que se ha
descargado. Esta última información nos da cierta idea acerca de la popularidad de un módulo. En cuanto
al número de versión, es también un indicador interesante para evaluar el grado de madurez de un
elemento de la galería.
Observamos, de paso, que el autor principal de este libro es el autor del módulo EZLog, que nos servirá
como ejemplo para ilustrar esta sección.
«EZ» es un juego de palabra; pronunciado en inglés, equivale a «easy», que significa «fácil».
Hagamos clic ahora en el módulo EZLog y veamos qué obtenemos...
254
El módulo EZLog visto desde el sitio web de la galería PowerShell
255
En esta pantalla podemos observar ciertos metadatos interesantes, como, por ejemplo, qué funciones
componen el módulo, las eventuales dependencias con otros módulos, la versión mínima de PowerShell
exigida, así como las distintas versiones pasadas y su fecha de aparición.
Adicionalmente, si hace clic en Show, dentro del apartado FileList, puede ver el código fuente de los
archivos que componen el módulo. Por último, en la columna de la izquierda, tiene la posibilidad de
contactar con el autor y promocionar un módulo en redes sociales como Facebook, LinkedIn y Twitter.
El último apartado importante que conviene conocer es el llamado Project Site. Lleva, generalmente, al
repositorio GitHub del módulo, pues, como le decíamos, todo objeto de la PowerShell Gallery tiene
licencia open source. La mayoría de los desarrolladores utilizan GitHub, puesto que esta plataforma,
además de ser particularmente eficaz, es gratuita para proyectos open source. A partir de GitHub o de
Git es posible, a continuación, hacer un «fork» (clonar) un módulo para, eventualmente, colaborar a su
desarrollo.
Como resumen de nuestra presentación sobre www.powershellgallery.com, cabe recordar que este sitio
resulta extremadamente práctico para explorar la galería y para obtener información precisa acerca de
un módulo. Sin embargo, habrá que utilizar PowerShell para poder instalar o publicar un módulo en la
galería. Es lo que vamos a ver a continuación.
b. El módulo PowerShellGet
Este módulo contiene los comandos que permiten interactuar con la galería PowerShell. Como siempre,
para mostrar los comandos presentes en un módulo, utilizamos el siguiente comando:
PS > Get­Command ­Module PowerShellGet
CommandType
­­­­­­­­­­­
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Name
­­­­
Find­Command
Find­DscResource
Find­Module
Find­RoleCapability
Find­Script
Get­InstalledModule
Get­InstalledScript
Get­PSRepository
Install­Module
Install­Script
New­ScriptFileInfo
Publish­Module
Publish­Script
Register­PSRepository
Save­Module
Save­Script
Set­PSRepository
Test­ScriptFileInfo
Uninstall­Module
Uninstall­Script
Unregister­PSRepository
Update­Module
Update­ModuleManifest
Update­Script
Update­ScriptFileInfo
Version
­­­­­­­
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
1.0.0.1
Source
­­­­­­
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
PowerShellGet
Los comandos que se utilizan más frecuentemente se detallan en la siguiente tabla:
256
Descripción
Comando
Find­Module
Buscar un módulo.
Install­Module
Instalar un módulo.
Save­Module
Descargar un módulo para estudiarlo antes de instalarlo.
Update­Module
Actualizar todos los módulos instalados.
Publish­Module
Publicar un módulo en la PowerShell Gallery.
Uninstall­Module
Desinstalar un módulo.
4. Buscar un módulo
El comando Find­Module busca los módulos presentes en la galería PowerShell (u otra, si ha definido
alguna otra). Devuelve un objeto de tipo PSGetItemInfo por cada módulo encontrado. Así, es fácil
pasar estos resultados al comando
Si no se precisa el parámetro
encontrada.
Install­Module mediante el pipeline para instalarlos.
­RequiredVersion, entonces Find­Module devuelve la última versión
Find­Module posee varios parámetros; he aquí los que se utilizan con mayor frecuencia:
Parámetro
Descripción
­AllVersions <Switch>
Recupera todas las versiones de un módulo.
­Command <String[]>
Especifica un array de comandos que se han de
encontrar en los módulos.
­DSCResource <String[]>
Especifica un nombre o una parte de nombre
de módulo que contiene recursos DSC.
­MaximumVersion <Version>
Especifica una versión máxima de un módulo
para incluir en los resultados de la búsqueda.
Este parámetro no es compatible con ­
RequiredVersion.
­MinimumVersion <Version>
Especifica una versión mínima de un módulo
para incluir en los resultados de la búsqueda.
Este parámetro no es compatible con ­
RequiredVersion.
­Name <String[]>
Especifica los nombres de uno o varios módulos
que se han de buscar. Este parámetro acepta
el carácter «*». Si no se especifica, entonces
se realiza una búsqueda exacta.
­Repository <String[]>
Especifica el nombre de un repositorio
alternativo registrado mediante el comando
Register­PSRepository.
­RequiredVersion <Version>
Especifica un número de versión específico de
módulos que se han de buscar. Este parámetro
es incompatible con ­Minimum Version y ­
MaximumVersion.
Supongamos que estamos buscando un módulo ideal que permita generar archivos de log bien
estructurados.
257
Buscar módulos con el carácter genérico «*»
Dicho módulo tendría, probablemente, la palabra «log» en su nombre. Intentemos ejecutar el siguiente
comando para encontrar todos los módulos cuyo nombre termina por «log»:
PS > Find­Module ­Name *log
Version
­­­­­­­
1.1.0.0
3.2.1
2.0.0
1.0.2
0.1.3
4.7.2.2
1.0
0.1
1.0
1.1.1
1.0.0.1
1.6.4
1.0
0.2.2.0
Name
­­­­
xWinEventLog
Posh­SYSLOG
PSMultiLog
Log
Posh­WriteLog
PSLog
Get­TsLog
GCDialog
PSRotateLog
SimplyLog
xEventlog
EZLog
FC_Log
uLog
Repository
­­­­­­­­­­
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
PSGallery
Podríamos omitir el parámetro
implícito.
Description
­­­­­­­­­­­
Configure Windows Event Logs
Send SYSLOG messages from PowerShell
The PSMultiLog module is designed to ...
A logging module for PowerShell
Posh­WriteLog imports a Write­Log cmd...
Redirects standard Write­* cmdlets to...
Allows a user with a Tableau Server l...
User interface components for buildin...
Remove or compress old IIS logs
Simple Module for logging.
create eventlog and eventsource
A very easy and pragmatic log module...
A Logging utility
Easy log management for scripts...
­Name para abreviar nuestra línea de comandos, pues está
Como puede observar, existen varios módulos de logs...
Obtener información complementaria acerca de un módulo
Imaginemos ahora que su atención se dirige al módulo
dada la calidad de este módulo ;­).
EZLog, lo cual es perfectamente comprensible
Para obtener información complementaria acerca de un objeto se utiliza, como de costumbre, el comando
de formateo Format­List:
PS > Find­Module ­Name EZLog | Format­List *
Name
Version
Type
Description
:
:
:
:
EZLog
1.6.4
Module
A very easy and pragmatic log module for
admins in a hurry. See project site on
Github for more info.
Arnaud PETITJEAN
Arnaud_Petitjean_MVP
(c) 2017 Arnaud PETITJEAN. All rights
reserved.
11/28/2017 3:41:51 PM
Author
CompanyName
Copyright
:
:
:
PublishedDate
InstalledDate
UpdatedDate
LicenseUri
ProjectUri
IconUri
Tags
Includes
:
:
:
:
: http://github.com/apetitjean/ezlog
:
: {write­log, log, logging, easy...}
: {Function, RoleCapability, Command,
DscResource...}
258
PowerShellGetFormatVersion
ReleaseNotes
Dependencies
RepositorySourceLocation
Repository
PackageManagementProvider
AdditionalMetadata
:
:
:
:
:
:
:
{}
https://www.powershellgallery.com/api/v2/
PSGallery
NuGet
{versionDownloadCount, ItemType, copyright,
CompanyName...}
Obtener el histórico de versiones de un módulo
Podemos obtener la lista de versiones disponibles de un módulo presente en la galería con el siguiente
comando:
PS > Find­Module EZLog ­AllVersions | Format­Table Version, PublishedDate
Version
­­­­­­­
1.6.4
1.6.3
1.6.1
1.6.0
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.0
PublishedDate
­­­­­­­­­­­­­
11/28/2017 3:41:51 PM
11/28/2017 3:18:41 PM
9/22/2017 11:16:24 AM
2/2/2017 9:45:50 PM
11/16/2016 7:50:07 PM
11/16/2016 7:26:42 PM
11/16/2016 4:32:19 PM
11/15/2016 11:51:31 PM
10/17/2016 10:26:29 PM
9/21/2016 2:40:32 PM
9/14/2016 9:59:20 PM
9/14/2016 9:39:28 PM
9/14/2016 8:03:46 PM
9/12/2016 12:52:20 PM
8/25/2016 9:58:11 PM
Antes de instalar un módulo, le aconsejamos visitar el sitio web del proyecto indicado en la propiedad
ProjectURI cuando esté informada. En efecto, resulta bastante difícil hacerse una idea de lo que
hace un módulo y de cómo utilizarlo únicamente con los comandos de PowerShell Gallery. Los proyectos
hospedados en la plataforma GitHub disponen, generalmente, de un archivo Readme que se muestra
en la página de bienvenida de cada proyecto y que ofrece información acerca de los objetivos, así como
ejemplos de uso.
5. Guardar un módulo
Antes de instalar un módulo, puede resultar interesante consultar su código fuente. En efecto, incluso
aunque Microsoft ha tomado ciertas precauciones para evitarnos descargar código malicioso, siempre es
conveniente no confiar al 100 % en el contenido de la galería. Nada reemplaza el ojo crítico del ser
humano, al menos de momento...
Hemos visto cómo es posible visualizar este código de distintas maneras (a través del sitio web
www.powershellgallery.com y sobre GitHub). Dicho esto, el comando Save­Module nos va a permitir
descargar un módulo en la ruta que queramos. La principal diferencia con el comando
es, esencialmente, la ruta de descarga, que difiere.
Install­Module
259
Save­Module comparte la mayoría de los parámetros del comando Find­Module y posee además el
parámetro ­Path. Gracias a este parámetro podremos indicarle dónde guardar el módulo descargado.
En el siguiente ejemplo, las dos líneas de comando hacen exactamente lo mismo:
PS > Find­Module EZLog | Save­Module ­Path C:\temp\cuarentena
PS > Save­Module EZLog ­Path C:\temp\cuarentena ­force
Dado que no hemos precisado la versión deseada, instalaremos la última versión del módulo
carpeta llamada «cuarentena». Observe la opción
EZLog en la
­force, que permite crear automáticamente la carpeta
de destino si esta no existe.
Como el módulo descargado no está ubicado en el Path, no se corre el riesgo de cargarlo por error o
inadvertidamente.
6. Instalar un módulo
Si ha leído con atención la sección anterior que define el comando
Save­Module, probablemente habrá
comprendido que la diferencia principal con Install­Module reside en la ruta de descarga de los
módulos. En efecto, el rol del comando Install­Module consiste en registrar un módulo en una de las
carpetas referenciadas por la variable $PSModulePath, cuyo efecto inmediato será que los comandos
del módulo estarán accesibles inmediatamente.
El comando
Install­Module posee varios parámetros; he aquí los utilizados con mayor frecuencia:
Parámetro
Descripción
­Force <Switch>
Fuerza la instalación de un módulo. Si ya existe algún módulo
con el mismo nombre y el mismo número de versión en la
máquina, este se borrará si se especifica esta opción.
­MaximumVersion
<Version>
Especifica una versión máxima de un módulo para instalar. Este
parámetro no es compatible con ­RequiredVersion.
­MinimumVersion
<Version>
Especifica una versión mínima de un módulo para incluir en los
resultados de la búsqueda. Este parámetro no es compatible
con ­RequiredVersion.
­Name <String[]>
Especifica los nombres de uno o varios módulos para instalar.
Este parámetro no acepta el carácter «*».
­Repository
<String[]>
Especifica el nombre de un repositorio alternativo registrado
mediante el comando Register­PSRepository.
­RequiredVersion
<Version>
Especifica un número de versión específica de módulos para
buscar.
Este
parámetro
es
incompatible
con
­
MinimumVersion y ­MaximumVersion.
­Scope <String>
Especifica el ámbito de instalación. Los posibles valores son
AllUsers y CurrentUser. Si no se especifica este
parámetro, el valor de instalación por defecto es
El parámetro más importante de este comando es, sin duda, el parámetro
AllUsers.
­Scope. En efecto, gracias a
este parámetro el módulo instalado estará disponible para todos los usuarios de una máquina, o bien
para el usuario en curso únicamente.
260
Preste atención, las rutas de instalación difieren según la edición de PowerShell (PowerShell Core o
Windows PowerShell).
Instalación de un módulo para el usuario en curso de una máquina
En el siguiente ejemplo, las dos líneas de comando hacen exactamente lo mismo:
PS > Find­Module EZLog | Install­Module ­Scope CurrentUser
PS > Install­Module EZLog ­Scope CurrentUser
Instalación de un módulo para todos los usuarios de una máquina
Puede escoger indistintamente una u otra línea de comando:
PS > Find­Module EZLog | Install­Module ­Scope AllUsers
PS > Install­Module EZLog ­Scope AllUsers
Cuando no especifica el ámbito, el resultado será el mismo siempre que AllUsers sea la ubicación
por defecto, de modo que le recomendamos ser siempre lo más explícito posible. Esto elimina toda
ambigüedad. Además, si los valores por defecto cambiaran algún día en una versión posterior de
PowerShell, no afectaría al correcto funcionamiento de su script.
Observe que, como no hemos indicado una versión específica para instalar, PowerShell instala la versión
del módulo más reciente de la galería.
7. Desinstalar un módulo
La desinstalación de un módulo hace que se elimine el módulo de la máquina local.
El comando
Uninstall­Module posee varios parámetros; he aquí los que se utilizan con mayor
frecuencia:
Parámetro
Descripción
­AllVersions
<Switch>
Desinstala todas las versiones de un módulo.
­Force <Switch>
Fuerza la desinstalación de un módulo sin pedir confirmación.
­MaximumVersion
<Version>
Especifica una versión máxima de un módulo para desinstalar.
Este parámetro no es compatible con ­RequiredVersion.
­MinimumVersion
<Version>
Especifica una versión mínima de un módulo para desinstalar.
Este parámetro no es compatible con ­RequiredVersion.
­Name <String[]>
Especifica los nombres de uno o varios módulos
desinstalar. Este parámetro no admite el carácter «*».
­RequiredVersion
<Version>
Especifica un número de versión específica de módulos para
desinstalar. Este
parámetro
es
incompatible
con ­
MinimumVersion y ­MaximumVersion.
para
261
Eliminar todas las versiones de un módulo
Gracias a la siguiente línea de comandos podemos eliminar todas las versiones del módulo EZLog
presentes en nuestra máquina local:
PS > Uninstall­Module EZLog ­AllVersions
Eliminar una versión específica de un módulo
El parámetro
­RequiredVersion permite especificar una versión específica para desinstalar:
PS > Uninstall­Module EZLog ­RequiredVersion 1.6.4
No está obligado a especificar el número de versión del módulo que va a desinstalar si solo existe
una versión instalada.
8. Recuperar la lista de módulos instalados
En lugar de tener que mirar en las múltiples rutas referenciadas por la variable
$env:PSModulePath
para encontrar los módulos instalados en la máquina, existe un comando que nos va a facilitar la tarea. Se
trata del comando Get­InstalledModule.
Sí, pero ¿no hace el comando
Get­Module ­ListAvailable exactamente lo mismo?, se preguntará.
Efectivamente, tenemos su respuesta: «no exactamente».
En efecto, la diferencia entre Get­Module ­ListAvailable y Get­InstalledModule es que
Get­InstalledModule devuelve únicamente los módulos que se han instalado con el módulo
PowerShellGet. Esta diferencia es pequeña pero importante, pues, si ha instalado de manera manual
un módulo por algún otro método distinto a Install­Module, solo lo verá con Get­Module, que, por
su parte, devuelve todos los módulos, sea cual sea el método de instalación.
Get­InstalledModule es interesante, pues nos permite conocer de un vistazo los módulos
agregados a una máquina. En efecto, no es fácil distinguir entre los módulos nativos de PowerShell y los
módulos agregados por un usuario.
Get­InstalledModule posee varios parámetros, de los que mostramos los utilizados con mayor
frecuencia:
Parámetro
Descripción
­AllVersions
<Switch>
Indica que se quiere recuperar todas las versiones de un
módulo. Si no se especifica, el comando devuelve únicamente la
versión más reciente de un módulo.
­MaximumVersion
<Version>
Especifica una versión máxima de un módulo para recuperar.
Este parámetro no es compatible con ­RequiredVersion.
­MinimumVersion
<Version>
Especifica una versión mínima de un módulo para recuperar.
Este parámetro no es compatible con ­RequiredVersion.
­Name <String[]>
Especifica los nombres de uno o varios módulos para recuperar.
Este parámetro no admite el carácter «*».
262
Parámetro
Descripción
­RequiredVersion
<Version>
Especifica un número de versión específica de módulos para
recuperar.
Este
parámetro
es
incompatible
con
­
MinimumVersion y ­MaximumVersion.
Mostrar los módulos descargados desde la PowerShell Gallery
En nuestra máquina, hemos instalado el módulo PSScriptAnalyzer, así como el módulo EZLog en versión
1.6.3 y 1.6.4. He aquí lo que nos devuelve Get­InstalledModule cuando no se utiliza ningún
parámetro:
PS > Get­InstalledModule
Version
­­­­­­­
1.6.4
1.16.1
Name
­­­­
EZLog
PSScriptAnalyzer
Repository
­­­­­­­­­­
PSGallery
PSGallery
Description
­­­­­­­­­­­
A very easy and pragmatic log modul...
PSScriptAnalyzer provides script ana...
No vemos la versión 1.6.3 del módulo EZLog. En efecto, Get­InstalledModule nos devuelve
únicamente las versiones más recientes de los módulos instalados.
Recuperar todas las versiones de un módulo descargado desde la PowerShell Gallery
Para obtener todas las versiones de un módulo determinado, hay que pedirlo explícitamente de la
siguiente manera:
PS > Get­InstalledModule ­Name EZLog ­AllVersions
Version
­­­­­­­
1.6.3
1.6.4
Name
­­­­
EZLog
EZLog
Repository
­­­­­­­­­­
PSGallery
PSGallery
9. Actualizar un módulo
El comando Update­Module descarga y actualiza uno o varios módulos instalados en el equipo local.
Puede indicar el nombre del módulo para actualizar únicamente dicho módulo. Si no especifica ningún
nombre de módulo, PowerShell actualizará todos los módulos presentes en la variable
$env:PSModulePath.
Observe que, para que Update­Module funcione correctamente, la instalación de los módulos debe
haberse llevado a cabo con el módulo PowerShellGet.
Ejemplo: Instalación y actualización posterior de un módulo
En primer lugar, instalaremos una versión antigua del módulo EZLog. Por ejemplo, la versión 1.6.0.
PS > Install­Module ­Name EZLog ­RequiredVersion 1.6.0 ­Scope CurrentUser
A continuación, actualizaremos el módulo a su última versión:
PS >Update­Module ­Name EZLog
263
Por último, comprobaremos que la actualización ha producido el efecto deseado:
PS >Get­InstalledModule ­AllVersions ­Name EZLog
Version
­­­­­­­
1.6.0
1.6.4
Name Repository Description
­­­­ ­­­­­­­­­­ ­­­­­­­­­­­
EZLogPSGallery Averyeasy and pragmatic log module for admins in a hurry...
EZLogPSGallery Averyeasy and pragmatic log module for admins in a hurry...
También habríamos podido utilizar el comando
Update­Module sin indicar el nombre del módulo.
Esto habría tenido como efecto actualizar todos los módulos instalados previamente mediante el
módulo PowerShellGet.
10. Publicar un módulo
La publicación de un módulo en la PowerShell Gallery excede el objetivo de este libro en la medida en que
no hemos abordado la creación de módulos. A pesar de ello, vamos a repasar los conceptos principales;
tenga por seguro de que no se trata de algo complicado.
El comando que permite realizar la publicación es Publish­Module. Este módulo posee numerosos
parámetros que no detallaremos, pues la mayoría de ellos son opcionales. Lo importante sería:
1.
Poseer una clave de API para autenticarse en la PowerShell Gallery.
2.
Disponer de un manifiesto de módulo válido que contenga una sección PrivateData.
Esta sección contiene los metadatos que ve en cada módulo cuando explora la galería.
a. Obtener la clave de API
Para obtener una clave de API por parte de la PowerShell Gallery, debe autenticarse en el sitio web
www.powershellgallery.com con su cuenta de Microsoft. Para ello, haga clic en la parte superior derecha
del enlace Register y, a continuación, siga las indicaciones para autenticarse.
Una vez superada esta etapa, haga clic en su nombre de usuario (siempre en la parte superior derecha
de la pantalla) y, a continuación, acceda a su clave de API. ¡Eso es todo!
Esta clave debe permanecer secreta. No debe divulgarla a nadie, pues es la que le identificará en lo
sucesivo cuando publique un módulo.
264
Recuperar la clave de API en la PowerShell Gallery
b. Crear el manifiesto del módulo
Cuando cree un módulo, debe tener obligatoriamente un manifiesto asociado (se trata de un archivo con
la extensión .psd1); en caso contrario, su módulo no será válido.
Para que su módulo sea aceptado en la galería, debe haber informado la sección PrivateData, como
muestra el siguiente extracto:
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery
in online galleries.
Tags = ’write­log’, ’log’, ’logging’, ’easy’, ’simple’
# A URL to the license for this module.
# LicenseUri = ’’
# A URL to the main website for this project.
ProjectUri = ’http://github.com/apetitjean/ezlog’
# A URL to an icon representing this module.
# IconUri = ’’
# ReleaseNotes of this module
# ReleaseNotes = ’Minor bug fix.’
} # End of PSData hashtable
} # End of PrivateData hashtable
265
Este extracto se ha obtenido del manifiesto del módulo EZLog. Puede acceder a él en la carpeta de
instalación del módulo actual o, si no desea instalarlo, consultando el siguiente enlace que le dirigirá a
GitHub: https://github.com/apetitjean/EZLog/blob/master/src/EZLog.psd1
c. Publicar el módulo en la galería
Ahora que posee su clave de API y que el módulo que desea publicar posee un manifiesto válido, puede
utilizar la siguiente línea de comando para publicar su módulo:
PS > $PSGalleryAPIKey = ’0fb33a8a­b0db­9876­1234­12345678cc92’
PS > Publish­Module ­Name MyModule ­NuGetApiKey $PSGalleryAPIKey
Asegúrese de que su módulo se encuentra en una ruta prevista para este efecto, es decir,
referenciada por $env:PSModulePath.
¡Ya está, hecho! No tiene más que esperar unos pocos minutos antes de ver su obra publicada en
Internet y accesible al gran público.
266
Gestión de
errores y
depuración
267
Introducción a la gestión de errores y a la depuración
En vuestra vida de desarrollador de scripts, tarde o temprano deberá enfrentarse a errores
precisamente a la gestión de los errores dentro de sus scripts. ¿Qué da más rabia que un script
cuelga en plena ejecución? Cuando esto ocurre, es preferible y mucho más elegante interceptar los
para mostrar un bonito mensaje personalizado en vez de dejar que PowerShell muestre sus
mensajes.
o más
que se
errores
propios
Por otra parte, puede ser interesante tratar de anticipar los errores para actuar en consecuencia. Por
ejemplo, si intenta suprimir una jerarquía completa de archivos y por alguna razón cualquiera esta contiene
un archivo sobre el cual no dispone de permisos adecuados, entonces se generará un error.
Gracias a lo que va a aprender en este capítulo, podrá decidir cómo debe comportarse PowerShell frente a
los errores. ¿Deberá interrumpir la ejecución del script o continuar? Y si continua ¿deberá o no mostrar un
mensaje de error? Si sí, ¿qué tipo de mensaje? ¿El mensaje configurado por defecto o un mensaje que
habrá definido usted mismo?
La otra parte de la gestión de los errores tiene que ver con la depuración. Cuando un script cuenta con
algunos cientos de líneas de código, la depuración puede resultar una tarea compleja que consume mucho
tiempo. Menos mal que descubrirá que PowerShell posee algunos mecanismos muy útiles que podrán
salvarle y hacerle ganar un tiempo precioso.
La gestión de los errores
Para abordar correctamente este tema, primero tenemos que diferenciar dos tipos de errores: los errores
críticos («Terminating errors» es el término inglés correspondiente) y los errores no críticos («Non­
Terminating errors» en inglés).
Los primeros son considerados como graves, y cuando ocurren se interrumpe la ejecución del comando o del
script. Los errores críticos se encuentran en general en errores sintácticos, en una división por cero, etc.
Los segundos, los errores no críticos, son más bien considerados como avisos; la mayor parte de los errores
son, generalmente, de este tipo. En este caso, la ejecución del script continúa y ­ salvo indicación contraria ­
los errores se muestran por pantalla. Podemos encontrarnos este tipo de errores, por ejemplo, al suprimir
un archivo si los permisos de acceso son insuficientes o si intentamos mover un archivo que no existe.
Veremos que el comportamiento por defecto, que consiste en continuar la ejecución del script al encontrarse
con un error no crítico, se puede modificar. En algunos casos, es preferible parar el desarrollo de un script
en vez de dejar que continúe por el peligro de generar errores en cascada que podrían poner en peligro en
el mejor de los casos algunos archivos, en el peor nuestro sistema.
Empecemos por interesarnos en los errores no críticos ya que generalmente son los que encontramos más
habitualmente.
Los errores no críticos
Debe saber que PowerShell permite definir su comportamiento frente a los errores de varias maneras:
Globalmente: es decir para todo el script o para la extensión en ejecución (consulte el capítulo
Variables y tipos de datos) gracias a la variable $ErrorActionPreference.
Selectivamente: es decir que para cada comando el comportamiento puede ser diferente. Esto se
hace gracias al uso de un parámetro que es común a todos los comandos. Este parámetro se llama
ErrorAction.
268
1. Variable de opciones: $ErrorActionPreference
Interesémonos ahora en la « variable de opciones » (así es como llamamos a las variables que almacenan
las preferencias de los usuarios) $ErrorActionPreference.
Puede tomar los siguientes valores:
Descripción
Valor
SilentlyContinue
El script se ejecuta sin mostrar errores aunque los encuentre.
Continue
El script continua si encuentra un error y lo muestra (valor por
defecto).
Stop
El script se interrumpe si encuentra un error. En este caso todos
los errores toman el carácter crítico.
Inquire
Cuando ocurre un error, un prompt solicita al usuario qué debe
hacer (continuar, continuar en modo silencioso, parar o
suspender).
Ignore
Mismo comportamiento que SilentlyContinue salvo que en
caso de error este no se almacena en la variable automática
$Error.
Como por defecto $ErrorActionPreference contiene el valor Continue, los errores no críticos no
bloquean la ejecución; PowerShell simplemente los almacena y los muestra por pantalla.
Cuando $ErrorActionPreference toma el valor Stop, todos los errores encontrados se
vuelven errores críticos. Así un error no crítico que surja durante la ejecución de un script interrumpirá
este
último
al
igual
que
lo
hace
un
error
crítico.
Para
modificar
el
valor
de
$ErrorActionPreference, puede hacer lo siguiente $ErrorActionPreference = ’Stop’
(¡no olvide las comillas simples!) o Set­Variable ­Name ErrorActionPreference ­value
’Stop’.
Ejemplo
Aplicación del comando
Get­ChildItem en una carpeta inexistente.
PS > $ErrorActionPreference
Continue
PS > Get­ChildItem ’C:\NoExiste’
Get­ChildItem : Cannot find path ’C:\NoExiste’ because it does not
exist.
...
Ahora, modifiquemos la variable $ErrorActionPreference con el valor
volvamos a ejecutar los comandos:
SilentlyContinue y
PS > $ErrorActionPreference = ’SilentlyContinue’
PS > Get­ChildItem ’C:\NoExiste’
Esta vez, aunque el error esté aún presente, no se muestra. Es más, los errores dejarán de mostrarse,
independientemente del comando introducido, mientras no volvamos al modo Continue.
269
PS > $ErrorActionPreference = ’SilentlyContinue’
PS > Get­ChildItem ’C:\NoExiste’
PS > Get­ComandoQueNoExiste
En vez de utilizar sin parar la sesión actual, con el riesgo de no acordarnos en qué modo nos
encontramos, podríamos usar un bloque de script para realizar las pruebas. En efecto, el ámbito de
$ErrorActionPreference como para todas las demás variables es propio a cada bloque de script.
Para retomar nuestro último ejemplo, podríamos escribir lo siguiente:
PS
>>
>>
>>
>>
>>
> &{
$ErrorActionPreference = ’SilentlyContinue’
Get­ChildItem ’C:\documento privado’
Get­ComandoQueNoExiste
}
2. Parámetro ­ErrorAction y los parámetros comunes
Otra técnica consiste en utilizar los parámetros «comunes» de los comandos. Encontramos casi siempre el
término inglés «common parameters» para designarlos.
Estos constituyen una de las grandes fuerzas de PowerShell: la homogeneidad. En efecto estos
parámetros están presentes para todos los comandos de PowerShell.
Estos parámetros son los siguientes:
Posible valor
Descripción
­ErrorAction (­ea)
SilentlyContinue
Continue
Inquire
Stop
Ignore
Determina
el
comportamiento
del
comando en caso de error.
­ErrorVariable (­ev)
<Nombre
variable>
Se almacena el error
resultante en la variable
pasada como parámetro,
además de $Error.
­Debug (­db)
$True
$False
Indica al comando
pase
al
modo
depuración.
­Verbose (­vb)
$True
$False
Indica al comando de
pasar al modo detallado.
­OutVariable (­ov)
<Nombre
variable>
La salida resultante del
comando en curso se
almacena en la variable
pasada como parámetro.
­OutBuffer (­ob)
<Int32>
Determina el nombre de
los objetos en memoria
caché
antes
de
transmitirlos al pipeline.
Parámetro (alias)
que
de
270
Posible valor
Parámetro (alias)
Descripción
­WarningAction (­wa)
SilentlyContinue
Continue
Inquire
Stop
Ignore
Determina
comportamiento
comando en caso
recibir avisos.
­WarningVariable (­wv)
<Nombre
variable>
El aviso resultante se
almacena en la variable
pasada como parámetro.
­InformationAction (­ia)
SilentlyContinue
Continue
Inquire
Stop
Ignore
Sobrescribe el valor de la
variable $information
<Nombre de
variable>
Permite almacenar
mensajes informativos en
una variable.
­InformationVariable
iv)
(­
el
del
de
Preference.
Permite mostrar o no los
mensajes informativos.
Para dar un poco más de flexibilidad a nuestros scripts, más granularidad a nuestros comandos y así evitar
jugar continuamente con la variable $ErrorActionPreference, es posible utilizar el parámetro ­
ErrorAction. Este parámetro permite actuar sobre el comportamiento de cada comando y no a nivel
global del script como antes.
Ejemplo
PS > $ErrorActionPreference = ’Continue’
PS > Get­ChildItem ’C:\NoExiste’
Get­ChildItem : Cannot find path ’C:\NoExiste’ because it does not
exist.
PS > Get­ChildItem ’C:\NoExiste’ ­ErrorAction ’SilentlyContinue’
Hemos utilizado el alias gci de Get­ChildItem para conseguir que el comando entre en una sola
línea, buscando una mejor comprensión. Hubiésemos podido también usar el parámetro corto ­EA en
vez de ­ErrorAction. Gracias al uso de ­ErrorAction ’SilentlyContinue’ hemos impedido
la visualización de un mensaje de error aunque $ErrorActionPreference tiene configurado el
modo «Continue».
de
ilustrar
los
valores
Continue
y
SilentlyContinue
$ErrorActionPreference, pero todavía no hemos mencionado Stop, Inquire e Ignore.
Acabamos
de
Stop permite interrumpir la ejecución del script cuando se encuentra con un error aunque sea un error no
crítico. Es una manera de asegurarse que un script no podrá acabar su ejecución si surge un error
cualquiera.
En cuanto a
ejemplo:
Inquire, indica a PowerShell solicitar al usuario la acción a realizar, como en el siguiente
271
PS > Get­ChildItem ’C:\NoExiste’ ­ErrorAction ’Inquire’
Confirm
Cannot find path ’C:\NoExiste’ because it does not exist.
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help
(default is "Y"):
Para pasar un valor a un parámetro podemos actuar, a nuestra elección de las siguientes maneras:
Get­ChildItem
’C:\NoExiste’
­ErrorAction
’Inquire’
o
Get­
ChildItem’C:\NoExiste’ ­ErrorAction:Inquire. Las dos sintaxis son posibles.
Para terminar, el valor Ignore tiene el mismo comportamiento que SilentlyContinue, a saber no
mostrar los errores. La diferencia es que SilentlyContinue almacena los errores en la variable
$Error y el valor Ignore tiene como efecto ignorar completamente los errores, como si no hubiesen
ocurrido.
3. Almacenamiento de errores
Sea cual sea el modo en el que nos encontramos (Continue, SilentlyContinue, Stop, Inquire o
Ignore), existe una variable «automática» llamada $Error que contiene, bajo la forma de array (o más
precisamente de ArrayList, se trata de un array de objetos de tipo ErrorRecord), los últimos 256
mensajes de error encontrados siendo el 256 correspondiente al valor de la variable
$MaximumErrorCount. Si por alguna razón necesita almacenar más errores, puede modificar este
número.
El último error se encuentra siempre en
$Error[0], el penúltimo en $Error[1] y así sucesivamente...
Por ejemplo
PS > $ErrorActionPreference = ’Continue’
PS > Get­ChildItem ’C:\NoExiste’ ­ErrorAction ’SilentlyContinue’
Gracias al parámetro
­ErrorAction hemos podido impedir la visualización de un mensaje de error con
el nivel de script configurado en modo Continue.
Veamos ahora el contenido de la variable
$Error[0]:
PS > $Error[0]
Get­ChildItem : Cannot find path ’C:\NoExiste’ because it does not
exist.
...
Existe otra manera para recuperar un mensaje de error además de usar $Error[0]. Aunque
$Error[0] sea muy práctico, con su uso se dará cuenta de que en ciertos casos obtenemos siempre el
error esperado. Imaginemos que tenemos algunas líneas de código que se suceden las unas a las otras y
que después llega nuestro test de error con $Error[0].
La variable $Error[0] contiene suficiente información para que podamos identificar fácilmente la línea
que ha provocado el error. El problema, en este contexto, es que es posible dejar escapar un error y
almacenar el error generado por otro comando, más adelante en el script. Tenemos por lo tanto que
recorrer «manualmente» la tabla $Error hasta encontrar la línea que nos interesa. Esto hace que sea
bastante tedioso y puede complicar la automatización del tratamiento de los errores.
272
Por eso la ventaja de «fijar» el almacenamiento del error a nivel del mismo comando.
Gracias al parámetro
­ErrorVariable podemos almacenar el mensaje de error en una variable elegida
por nosotros mismos y para cada comando o solamente para los que nos interesan; exactamente igual
que con el parámetro ­ErrorAction.
En el siguiente ejemplo, enviamos el error en la variable $MiVariable. Observe que no es necesario
poner el signo del dólar delante del nombre de la variable con ­ErrorVariable.
PS > $ErrorActionPreference = ’SilentlyContinue’
PS > Get­ChildItem ’C:\NoExiste’ ­ErrorVariable MiVariable
PS >
PS > $MiVariable
Get­ChildItem : Cannot find path ’C:\NoExiste’ because it does not exist.
...
Para concatenar los errores y así evitar que el contenido de
$MiVariable se sobreescriba con
cada iteración, debe prefijar el nombre de la variable con el signo «+». Así podríamos escribir lo
siguiente: Get­ChildItem ’C:\NoExiste’ ­ErrorVariable +MiVariable.
4. El tipo ErrorRecord
Examinemos más de cerca nuestra variable
Teclee:
$MiVariable ya que resulta particularmente interesante.
PS > $MiVariable | Get­Member ­Force
TypeName: System.Management.Automation.ErrorRecord
Name
­­­­
pstypenames
psadapted
psbase
psextended
psobject
Equals
GetHashCode
GetObjectData
GetType
get_CategoryInfo
get_ErrorDetails
get_Exception
get_FullyQualifiedErrorId
get_InvocationInfo
get_PipelineIterationInfo
get_ScriptStackTrace
get_TargetObject
set_ErrorDetails
ToString
CategoryInfo
ErrorDetails
Exception
FullyQualifiedErrorId
MemberType
­­­­­­­­­­
CodeProperty
MemberSet
MemberSet
MemberSet
MemberSet
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Property
Property
Property
Property
Definition
­­­­­­­­­­
System.Collections.Obj...
psadapted {Exception, ...
psbase {Exception, Tar...
psextended {PSMessageD...
psobject {BaseObject, ...
bool Equals(System.Obj...
int GetHashCode()
void GetObjectData(Sys...
type GetType()
System.Management.Auto...
System.Management.Auto...
System.Exception get_E...
string get_FullyQualif...
System.Management.Auto...
System.Collections.Obj...
string get_ScriptStack...
System.Object get_Targ...
void set_ErrorDetails(...
string ToString()
System.Management.Auto...
System.Management.Auto...
System.Exception Excep...
string FullyQualifiedE...
273
InvocationInfo
PipelineIterationInfo
ScriptStackTrace
TargetObject
PSMessageDetails
Property
Property
Property
Property
ScriptProperty
System.Management.Auto...
System.Collections.Obj...
string ScriptStackTrac...
System.Object TargetOb...
System.Object PSMessag...
ErrorRecord, o sea el mismo que el de los objetos contenidos en
$Error. Este tipo posee las siguientes propiedades:
Nos damos cuenta de que el tipo es
la tabla
Descripción
Propiedad
Exception
Se trata del mensaje de error tal y como se muestra
por pantalla. Es en realidad un poco más complejo que
esto ya que esta propiedad devuelve en realidad un
objeto cuyo tipo varía en función del error. Para
verificarlo, basta con observar el tipo y los miembros
de $Error[0] y de $Error[1]. Es probable que se
sorprenda.
ErrorDetails
Contiene información adicional acerca del error
encontrado. Esta propiedad puede ser nula. Si no es
nula,
es
preferible
mostrar
ErrorDetails.message
en
vez
de
Exception.message ya que el mensaje es mucho
más preciso.
FullyQualifiedErrorId
Esta propiedad identifica el error de la manera más
precisa que existe. La usaremos para filtrar y encontrar
un error determinado.
CategoryInfo
Devuelve la categoría del error.
TargetObject
Objeto que ha provocado el error. Esta propiedad
puede ser nula.
PipelineIterationInfo
Devuelve el estado del pipeline cuando se crea un
error.
InvocationInfo
Devuelve el contexto en el que se ha producido el
error, como la posición y el número de línea.
ScriptStackTrace
Devuelve la pila de depuración al ocurrir el error.
Observemos ahora las propiedades de nuestro error:
PS > $MiVariable | Format­List ­Force
Exception
: System.Management.Automation.ItemNotFoundException:
Cannot find path ’C:\NoExiste’ because it does
not exist...
TargetObject
: C:\NoExiste
CategoryInfo
: ObjectNotFound: (C:\NoExiste:String) [Get­...
FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands...
ErrorDetails
:
InvocationInfo
: System.Management.Automation.InvocationInfo
ScriptStackTrace
: at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {0, 1}
PSMessageDetails
:
274
Para mostrar todas las propiedades, estamos obligados a usar el parámetro ­Force del comando
Format­List. Esto es debido a la visualización personalizada definida por los creadores de
PowerShell. Han hecho que solo se muestre lo estrictamente necesario para no inundar al usuario de
información superflua.
5. Redirección de la visualización de los mensajes de error
Acabamos de ver que para redirigir los mensajes de error, podemos hacerlo de manera global modificando
el valor de la variable $ErrorActionPreference o bien de manera selectiva, comando a comando,
jugando con el parámetro común
­ErrorAction.
Existe, sin embargo, otro método que consiste en redirigir el flujo de escritura de los mensajes de error,
no ya al flujo de error, sino hacia el flujo de visualización estándar. Hace por lo tanto posible enviar los
errores en un archivo de texto o en una variable.
PowerShell ofrece una gran permisividad a nivel de las redirecciones de mensajes ya sean de
error o de aviso. Para saber más acerca de las redirecciones de flujo, le invitamos a consultar
los operadores de redirección en el capítulo Operadores.
a. Redirección a un archivo de texto
Esto se realiza gracias al operador
2>.
Ejemplo
Redirección de errores a un archivo de log.
PS > Get­ChildItem ’C:\NoExiste’ 2> Error.log
Haciendo esto, habremos creado un archivo. Sin embargo, tenga cuidado ya que si existe un archivo con
el mismo nombre, su contenido será machacado.
Si queremos añadir contenido a un archivo, debemos esta vez usar el operador
2>>.
b. Redirección a una variable
Hemos visto anteriormente que esto era posible empleando el parámetro
esto, existe una segunda posibilidad gracias al operador
­ErrorVariable. Dicho
2>&1.
Este operador indica al intérprete que envíe los errores hacia el flujo estándar, por lo tanto la pantalla, y
no hacia el flujo de error. Así, debemos usar una variable para almacenar nuestro error.
Empleamos voluntariamente los términos «almacenar nuestro error» en vez de «almacenar nuestro
mensaje de error» ya que la variable de error es de tipo ErrorRecord y no de tipo String.
Ejemplo
Redirección de errores a una variable.
275
PS > $Var = Get­ChildItem ’C:\NoExiste’ 2>&1
Si el color rojo de los mensajes de error no le gusta, puede cambiarlo, por ejemplo, por verde:
$host.PrivateData.set_ErrorForegroundColor(’green’).
c. Redirección de errores hacia $null
La redirección de mensajes se puede hacer hacia
esté siempre almacenado en
$null utilizando la sintaxis 2>$null. Aunque el error
$Error, el flujo de error se redirige y no se muestra por pantalla.
Ejemplo
Redirección hacia
$null.
PS > Get­ChildItem ’C:\documento privado’ 2>$null
6. Intercepción de errores no críticos
a. Caso general
Existen varias maneras de interceptar los errores en un script. La más sencilla consiste en comprobar el
resultado booleano almacenado en la variable $?. Esta variable contiene el resultado de la ejecución de
la última operación.
Para entender bien su funcionamiento, tomemos el siguiente ejemplo:
PS > Get­ChildItem ’c:\NoExiste’ ­ErrorAction ’SilentlyContinue’
PS > $?
False
En el ejemplo, $? contiene el valor $false ya que se ha producido un error, como es natural puesto
que intentamos recuperar el contenido de una carpeta que no existe, lo que no ocurre sin generar un
problema.
b. Caso de ejecutables externos
Existe un medio de saber si un programa externo a PowerShell se ha ejecutado normalmente con
$LASTEXITCODE. Esta variable contiene el código de error del último comando ejecutado pero solo se
aplica a archivos ejecutables externos a PowerShell. En otros términos. no se puede usar con comandos
PowerShell. En Windows, cuando un proceso Win32 acaba, devuelve siempre un valor entero como
código de salida. Por convención este código vale cero si el proceso ha acabado sin errores y otro valor
en caso contrario.
Ejemplo
PS > ping miEquipo.dominio
Ping request could not find host miEquipo.dominio. Please check
the name and try again.
PS > $?
False
PS > $LASTEXITCODE
1
276
PS > ping 127.0.0.1 ­n 1
Pinging 127.0.0.1 with 32 bytes of data:
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Ping statistics for 127.0.0.1:
Packets: Sent = 1, Received = 1, Lost = 0 (0% loss),
Approximate round trip times in milli­seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
PS > $LASTEXITCODE
0
PS > $?
True
ping un ejecutable externo (ping.exe), la variable $LASTEXITCODE contiene un
valor al acabar su ejecución. Fíjese que, incluso en esta situación, $? puede sernos útil. De hecho el
principio de $? consiste en verificar que el último código de salida de un ejecutable ($LastExitCode)
Siendo el comando
sea 0 o no.
Los errores críticos
Vayamos ahora a la caza de los errores críticos, llamados habitualmente «excepciones» por los
desarrolladores. Por lo tanto es así como los llamaremos para distinguirlos de los errores no críticos.
Gracias a lo que descubriremos en esta sección, tendremos todavía más control sobre nuestros scripts. Así,
en lugar de detener repentinamente un script a causa de una excepción, podremos actuar en consecuencia
y tomar las medidas (correctivas o alternativas) que se imponen. En efecto, a menudo resulta indispensable
saber si todo se ejecuta normalmente. Pero para esto debemos intentar prever los errores antes de que se
produzcan...
1. Intercepción de errores críticos con Try­Catch­Finally
Como decíamos antes, cuando un error crítico ocurre, la ejecución se interrumpe bruscamente. Para evitar
tener que gestionar los errores comando a comando, es posible utilizar bloques de ejecución Try, Catch
y Finally. Estas instrucciones son bien conocidas, pues están presentes en muchos lenguajes de
programación.
El bloque Try contiene el código que puede provocar la excepción. Se ejecuta hasta que ocurra una
excepción (por ejemplo: división por cero) o hasta su éxito total. Si se produce un error crítico durante la
ejecución de instrucciones, PowerShell pasa el objeto de error del bloque Try al bloque Catch.
La sintaxis del bloque
Try es la siguiente:
Try {<bloque de instrucciones>}
La cláusula Catch contiene el bloque de ejecución asociado opcionalmente al tipo de error que se ha de
interceptar. Cuando se utiliza Catch sin precisar ningún tipo, se intercepta cualquier excepción. La
sintaxis del bloque Catch es la siguiente:
Catch [<tipo de error a interceptar>] {<bloque de instrucciones>}
Consulte la sección Determinar el tipo de errores críticos de este capítulo para saber cómo
conocer los tipos de errores críticos de un comando.
277
Opcionalmente, se utiliza
Finally seguido de un bloque de script. Este último se ejecuta cada vez que
se ejecuta el script, aunque tenga lugar o no un error.
La sintaxis del bloque
Finally es la siguiente:
Finally {<bloque de instrucciones>}
Ilustremos todo esto con una función que provoca una división por cero. Este tipo de error se considera
como crítico desde el principio de los tiempos en todos los lenguajes informáticos; ¡no nos pregunte por
qué!:
He aquí la función:
$ErrorActionPreference = ’Stop’
Function Bucle1
{
For ($i=­2 ;$i ­le 2;$i++) {
100/$1
}
’Resto del script...’
}
PS > bucle1
­50
­100
Attempted to divide by zero.
At line:5 char:8
+
100/$i
+
~~~~~~
+ CategoryInfo
: NotSpecified: (:) [],
ParentContainsErrorRecordException
+ FullyQualifiedErrorId : RuntimeException
Podemos observar que la ejecución de la función se detiene en el momento de la división por cero, pues la
cadena «Resto del script» no se muestra.
Esto se debe al hecho de que hemos modificado el valor de la variable $ErrorActionPreference con
el valor Stop en lugar del valor Continue por defecto. Resulta muy importante empezar siempre los
scripts con esta instrucción cuando trata de interceptar posibles errores.
MUY
IMPORTANTE:
Cuando
realice
la
gestión
de
errores,
no
olvide
asignar
a
la
variable
$ErrorActionPreference el valor Stop.
Ahora, agreguemos las instrucciones Try,
error y hacer que la ejecución continúe.
Catch a nuestra función para interceptar cualquier tipo de
$ErrorActionPreference = ’Stop’
Function Bucle2
{
Try {
For ($i=­2 ;$i ­le 2;$i++) {
100/$i
}
}
Catch {
"¡Se ha producido un error!"
278
}
’Resto del script...’
}
Observemos ahora el resultado:
PS > bucle2
­50
­100
¡Se ha producido un error!
Resto del script...
Function Bucle
{
For ($i=­2 ;$i ­le 2;$i++)
{
Try { 100/$i }
Catch { Write­Host ’ ¡Error en el script!’}
Finally {"..."}
}
}
¡Perfecto! Esta vez, la ejecución de la función ha seguido su curso. En efecto, vemos que se muestra la
frase «Resto del script...». Además, la ejecución del bloque Catch también ha tenido lugar, puesto que se
ha mostrado el mensaje «¡Se ha producido un error!». Evidentemente, esto no resulta útil, pero en la vida
real habríamos podido ejecutar algún procesamiento alternativo.
Por último, como le decíamos, un bloque Catch puede estar tipado. Es decir, puede desencadenarse en
función del tipo de una excepción en particular. En el siguiente ejemplo seremos capaces de ejecutar un
bloque de script únicamente si se produce el tipo de excepción «División por cero»:
$ErrorActionPreference = ’Stop’
Function Bucle3
{
Try {
For ($i=­2 ;$i ­le 2;$i++) {
100/$i
}
}
Catch [System.DivideByZeroException] {
Write­Host "¡Se ha producido una división por cero!"
}
Catch {
Write­Host "¡Se ha producido un error!"
}
’Resto del script...’
}
He aquí el resultado:
PS > bucle3
­50
­100
¡Se ha producido una división por cero!
Resto del script...
279
Dado el mensaje explícito que acabamos de recibir, nos indica que se ha ejecutado el bloque Catch
específico (el primero). Como comprenderá, un bloque Catch no tipado intercepta cualquier error,
independientemente de su tipo.
2. Determinar el tipo de errores críticos
Para conocer el tipo de una excepción, lo más sencillo es provocarla y ver a continuación su tipo en las
propiedades de la variable $Error[0] (o $Error[1] en algunos casos).
Tomemos por ejemplo la división entre cero:
PS > 1/0
PS > $Error[0] | Format­List * ­Force
ErrorRecord
WasThrownFromThrowStatement
Message
Data
InnerException
TargetSite
StackTrace
Runspaces
:
:
:
:
Attempted to divide by zero.
False
Attempted to divide by zero.
{System.Management.Automation.Interpreter.
In terpretedFrameInfo}
: System.DivideByZeroException:
Attempted to divide by zero.
: System.Collections.ObjectModel.Collection`1
[System.Management.Automation.
PSObject]
Invoke(System.Collections.IEnumerable)
:
at System.Management.Automation.
.PipelineBase.Invoke(IEnumerable input)
at System.Management.Automation.
Runspaces.Pipeline.Invoke()
HelpLink
Source
HResult
:
: System.Management.Automation
: ­2146233087
Acabamos de provocar la excepción que nos interesa, y ahora gracias al contenido de
determinado que su tipo es System.DivideByZeroException.
$Error[0] hemos
Desgraciadamente, no hemos encontrado otro modo menos empírico que proponerle para determinar el
tipo de un error crítico.
3. Generar excepciones personalizadas
Gracias a la instrucción
throw, es posible generar errores críticos de cualquier naturaleza.
La sintaxis es la siguiente:
Throw ["Texto a mostrar cuando ocurre la excepción"]
Sepa que no está obligado a especificar una cadena de caracteres después de la instrucción
puede utilizarse sola en su expresión más sencilla.
throw. Esta
280
Ejemplo
function test
{
Try {
Throw ’ErrorGrave’
}
Catch {
"Se ha producido el siguiente error: $_"
}
}
PS > test
Se ha producido el siguiente error: ErrorGrave
En un bloque Catch, puede invocar la variable
interior del bloque Try, lo cual puede resultar útil.
$_. Esta contiene la excepción generada en el
Para obtener más detalles acerca del funcionamiento de la instrucción Throw, puede consultar el
apartado de la ayuda ad hoc mediante el comando Help about_Throw. Ya que está, le invitamos
también a profundizar en sus conocimientos sobre Try, Catch, Finally mediante Help
about_Try_Catch_Finally.
La depuración
En lo relativo a la depuración, PowerShell está dotado de ricas funcionalidades en comparación con su primo
hermano VBScript. Es tan cierto que incluso integra la noción de breakpoint y de ejecución paso a paso
tanto en modo consola como de manera gráfica.
Hay mil y una formas de depurar un programa; podría parecerse a un arte, pues resulta algo complejo y, por
consiguiente, consume tiempo. A menudo, las técnicas difieren de una persona a otra. Sin embargo, las
bases son comunes... Una técnica clásica consiste en intercalar en un script mensajes para mostrar el
contenido de las variables o mensajes de aviso de paso para tratar de detectar el o los bugs. Veremos que
con PowerShell es posible hacerlo todavía mejor (en lugar de definir Write­Host de depuración sin ton ni
son) para no «contaminar» nuestro código.
Mostraremos también en este apartado cómo realizar una ejecución paso a paso, a menudo indispensable
para encontrar el origen de nuestros errores.
Por último, para evitar perder tiempo depurando, lo mejor es evitar crear bugs. Qué fácil resulta decirlo,
¿verdad? Pero verá que, esforzándose en respetar algunas buenas prácticas, tales como declarar variables
y asignarles un tipo, ahorrará un tiempo precioso. Hablaremos de esto también.
1. Mostrar información en tiempo de ejecución
PowerShell está dotado de varios flujos de mensajes que no están activos por defecto. Estos tipos de
mensajes pueden resultar muy útiles cuando se está depurando (pero no solo aquí), pues es posible
habilitarlos bajo demanda. Podríamos dejar estos comandos en nuestro código, pues no hacen nada
hasta que se activan.
En efecto, en otros lenguajes diferentes a PowerShell, cuando se realiza la depuración mediante
visualización (de mensajes o variables), nos vemos obligados a volver sobre el código una vez que ha
terminado la sesión de depuración para comentar cada línea. Esto ya no será necesario con los comandos
281
que le vamos a presentar.
He aquí los distintos flujos a nuestra disposición, así como los comandos asociados para utilizarlos:
Flujo
Número
de flujo
Comandos
Variable de preferencia asociada
Warning
3
Write­Warning
$WarningPreference
Verbose
4
Write­Verbose
$VerbosePreference
Debug
5
Write­Debug
$DebugPreference
Information
6
Write­Information
$InformationPreference
No pierda de vista que el flujo número 1 es el flujo llamado «estándar». Es el que se utilizará por el
comando Write­Output. Pero este comando se omite la mayoría de las veces, pues está implícito cada
vez que PowerShell emite un objeto (una cadena, el contenido de una variable, etc.).
En cuanto al flujo número 2, se trata del flujo de error. PowerShell utiliza este flujo cada vez que debe
devolverse un error al usuario. Sin embargo, este último también tiene la posibilidad de generarlo con el
comando Write­Error y con la instrucción Throw.
En el capítulo Operadores, en la sección Operadores de redirección, hemos explicado cómo redirigir
estos flujos gracias a los operadores de redirección > y >>. Hemos indicado en la tabla anterior los
números de flujo para que no pierda de vista que es posible redirigir cada flujo independientemente uno
de otro. No olvide que también puede redirigir todos los flujos gracias al carácter «*».
Cada flujo posee, a su vez, un parámetro común que permite asignar las variables de preferencia.
Para obtener más información acerca de los parámetros comunes, consulte la sección Parámetro ­
ErrorAction y los parámetros comunes de este capítulo o consulte la sección de ayuda
about_CommonParameters.
a. Mostrar mensajes en modo verbose
Existe en PowerShell un modo llamado detallado (verbose, en inglés), que podemos activar o desactivar
según nos convenga. Este permite a los scripts o a los comandos mostrar la información adicional que
poseen.
Para escribir cierta información solo visible en «modo verbose», debe utilizar el comando Write­
Verbose. Esto es la primera cosa que debe hacer pero no es suficiente ya que para mostrar por
consola su información, debe ajustar el valor de la variable $VerbosePreference, que por defecto
tiene el valor SilentlyContinue. Esto significa que, por defecto, no se mostrará la información
adicional. Los demás valores son los mismos que para la variable $ErrorActionPreference, a
saber Continue, Stop, e Inquire.
Ejemplo
Intentemos escribir cierta información en el modo por defecto.
PS > $VerbosePreference
SilentlyContinue
PS > Write­Verbose ’¡Esto es un test!’
PS >
282
Como esperábamos, no pasa nada. Veamos ahora qué pasa si se asigna el valor
variable
Continue a la
$VerbosePreference.
PS > $VerbosePreference = ’continue’
PS > Write­Verbose ’¡Esto es un test!’
VERBOSE: ¡Esto es un test!
Observamos que nuestra cadena de caracteres empieza por «VERBOSE:» y sobre todo que se muestra
en amarillo, resultando perfectamente visible en un entorno de visualización estándar.
Lo que es evidente es que el amarillo no sobresale muy bien en una impresión en blanco y negro.
Es la razón por la cual hemos utilizado la negrita para lo que verá normalmente en amarillo en la
pantalla.
En modo Stop, el mensaje se muestra pero finaliza la ejecución ya que se ha interceptado una
excepción; mientras que en modo Inquire se presenta el menú de confirmación habitual.
Como comprenderá, jugando con el valor de la variable $VerbosePreference podemos activar el
modo detallado; dicho de otra forma, activa las instrucciones Write­Verbose. Este mecanismo es
idéntico para los comandos Write­Debug y Write­Warning que vamos a ver ahora.
b. Visualización de mensajes en modo debug
Para la visualización de mensajes el modo
Verbose, con ciertas diferencias:
debug funciona exactamente igual que el comando Write­
La escritura de mensajes de depuración se realiza con
La variable de opciones a ajustar es
Write­Debug.
$DebugPreference.
El mensaje mostrado empieza por «DEBUG:».
Ejemplo
PS > $DebugPreference = ’continue’
PS > Write­Debug ’Esto es una información de depuración.’
DEBUG: Esto es una información de depuración.
PS >
PS > $DebugPreference = ’stop’
PS > Write­Debug ’Esto es una información de depuración.’
DEBUG: Esto es una información de depuración.
Write­Debug: The running command stopped because the preference
variable "DebugPreference" or common parameter is set to Stop:
c. Visualización de mensajes en modo warning
El último modo posible de visualización para mensajes de estado es el modo warning. Funciona de la
misma manera que los dos anteriores (verbose y debug), con algunas diferencias:
La escritura del mensaje de advertencia se realiza con
La variable de opciones a ajustar es
Write­Warning.
$WarningPreference.
El mensaje mostrado empieza por «WARNING:».
283
En vez de manipular las variables de opciones que acabamos de ver, es posible, para cada comando,
utilizar los parámetros comunes siguientes:
Verbose: para mostrar información adicional, si la hay.
Debug: para mostrar un menú de confirmación de tipo «Inquire» cuando se produce alguna de
las situaciones siguientes: visualización de un mensaje de depuración o de advertencia, error no
crítico.
Confirm: pide al usuario una confirmación antes de ejecutar un comando que modifica el estado
del sistema.
d. Mostrar mensajes de información
El flujo «information» ha aparecido con la versión 5 de PowerShell. Se trata de un flujo de datos
suplementario que puede utilizarse si no encuentra el resultado deseado con los comandos Write­
Verbose, Write­Warning y Write­Debug.
Este flujo permite recibir también los datos emitidos por el comando
posible redirigirlo, cosa que no ocurría en el pasado.
Write­Host, y de este modo es
Cabe destacar, sin embargo, que el comando Write­Host, a diferencia de Write­Information,
no se ve impactado ni por la variable de preferencia $InformationPreference, ni por el parámetro
común ­InformationAction.
2. Forzar la declaración de variables
PowerShell no le obliga a declarar sus variables. Una simple asignación de valor basta para declarar una
variable, PowerShell se encarga del resto.
Esta aparente facilidad puede, no obstante, volverse en su contra.
Aquí tiene un caso de error clásico donde provocamos un error escribiendo el nombre de una variable:
PS > $MiVariable = 25
PS > $Total = $miVaraible * 12
Esto producirá, como podemos imaginar, un resultado del todo imprevisible pero sobre todo imprevisto. Lo
peor de todo es que PowerShell no devolverá ningún error, pues sus mecanismos de conversión por
defecto hacen que se ejecute la operación.
En efecto, conviene saber que una variable no inicializada tendrá el valor $null. Y un valor nulo
convertido en un entero por PowerShell dará el valor cero. De modo que cero multiplicado por doce valdrá,
siempre, cero...
Para evitar este tipo de errores, podemos hacer que PowerShell nos obligue a declarar todas las variables
antes de su uso. Para ello, esto y forzar la declaración de todas las variables, PowerShell pone a nuestra
disposición el comando Set­PSDebug seguido del parámetro ­Strict.
Set­PSDebug ­Strict corresponde en esencia a «Option Explicit» de VBScript pero en una
versión menos restrictiva.
Retomemos nuestro ejemplo para ver los cambios:
284
PS > Set­PSDebug ­Strict
PS > $miVariable = 25
PS > $Total = $miVaraible * 12
The variable ’$miVaraible’ cannot be retrieved because it has not been
set.
El mensaje es muy claro: no hemos declarado
$miVaraible; lo que es normal dado que hemos
cometido un error tipográfico. Con una indicación así, es complicado no encontrar nuestro error.
Pero PowerShell va aún más lejos en la declaración de variables gracias al comando Set­StrictMode.
Muy cercano al funcionamiento de Set­PSDebug­Strict, Set­StrictMode permite no solo
arreglar los errores de variables no declaradas sino que también permite arreglar los errores provocados
por propiedades inexistentes. En realidad Set­StrictMode permite explotar diferentes niveles de
versión definidos a continuación:
Versión
Definición
1.0
Prohíbe las referencias a variables no declaradas, a excepción de las
presentadas en cadenas.
2.0
Prohíbe las referencias a variables no declaradas (sobre todo las variables no
declaradas presentes en cadenas).
Prohíbe las referencias a propiedades inexistentes de un objeto.
Prohíbe las llamadas a funciones que usan la sintaxis de llamada a métodos.
Prohíbe una variable sin nombre (${}).
Latest
Selecciona la versión más reciente (la más estricta) disponible.
Por ejemplo, tomemos el caso típico de una propiedad que no existe. Incluso activando el modo
PSDebug ­Strict, no se genera ningún error.
Set­
PS > Set­PSDebug ­Strict
PS > $miVariable = 25
PS > $mivariable.utilizounapropriedadquenoexiste
Ahora si utilizamos el comando Set­StrictMode en su versión 2.0 (que prohíbe las referencias a
propiedades inexistentes), se generará esta vez un error.
PS > Set­StrictMode ­Version 2.0
PS > $miVariable = 25
PS > $mivariable.utilizounapropriedadquenoxiste
The property ’utilizounapropriedadquenoxiste’ cannot be found
on this object. Verify that the property exists.
El mensaje es una vez más muy claro: la propiedad solicitada no existe y por lo tanto se detiene la
ejecución del script.
Aquí tiene una buena costumbre para ganar tiempo en el desarrollo de sus scripts ya que la depuración
puede tomar mucho tiempo y volverse muy ardua.
Buena práctica: piense siempre en declarar sus variables antes de utilizarlas y asígneles un
tipo. Para estar seguro de no olvidarse de ninguna variable, esfuércese siempre por comenzar
sus scripts con la instrucción Set­StrictMode ­Version 2.0 o, al menos, 1.0.
285
3. Ejecución paso a paso
Ejecutar un script paso a paso, poner breakpoints, inspeccionar variables durante la ejecución de un
script; todas estas cosas pertenecen a los sueños de cualquier desarrollador de script que ha probado
alguna vez un lenguaje de desarrollo de alto nivel como Visual Basic o C++. Pues sepa que todo esto ya
no es un sueño sino una auténtica realidad con PowerShell.
a. En la consola PowerShell clásica
Para entrar en el modo de ejecución paso a paso, debe usar el comando
Set­PSDebug ­Step.
La ejecución paso a paso le permitirá ejecutar un script línea a línea y para cada línea deberá indicar al
intérprete de comandos lo que debe hacer. Dispondrá de las siguientes posibilidades:
Yes (tecla «Y» o «Enter»): ejecuta el comando.
Yes to All (tecla «A»): sale del modo paso a paso y ejecuta el script hasta el final.
No (tecla «N»): impide la ejecución del comando actual.
No to All (tecla «L»): impide la ejecución de todos los comandos hasta el final del script.
Suspend (tecla «S»): suspende la ejecución del script en curso y entra en el intérprete de
comandos embebido.
Tomemos el ejemplo siguiente:
PS > Set­PSDebug ­Step
PS > For ($i=1 ; $i ­le 5; $i++) {Write­Host "Hola $i"}
Resultado:
Continue with this operation?
1+ For ( >>>> $i=1 ; $i ­le 5; $i++) {Write­Host "Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend
[?] Help (default is "Y"):
Contestamos «Yes» tres veces seguidas y vemos lo que pasa.
PS > Set­PSDebug ­Step
PS > For ($i=1 ; $i ­le 5; $i++) {Write­Host "Hola $i"}
Continue with this operation?
1+ For ( >>>> $i=1 ; $i ­le 5; $i++) {Write­Host " Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend
[?] Help (default is "Y"):
DEBUG: 1+ For ($i=1 ; $i ­le 5; $i++) {Write­Host " Hola $i"}
Continue with this operation?
1+ For ($i=1 ; >>>>$i ­le 5; $i++) {Write­Host " Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend
[?] Help (default is "Y"):
DEBUG: 1+ For ($i=1 ; $i ­le 5; $i++) {Write­Host " Hola $i"}
Continue with this operation?
1+ For ($i=1 ; $i ­le 5; $i++) {>>>>Write­Host " Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend
286
[?] Help (default is "Y"):
DEBUG: 1+ For ($i=1 ; $i ­le 5; $i++) {Write­Host " Hola $i"}
Hola 1
Continue with this operation?
1+ For ( >>>> $i=1 ; $i ­le 5; $i++) {Write­Host " Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend
[?] Help (default is "Y"):
For. Observará que en cada iteración tenemos derecho al
posicionamiento de la etapa de debug materializada por " >>>>".
Validamos cada etapa del bucle
Ahora entramos en modo depuración eligiendo suspender la ejecución del script utilizando la tecla «S».
Haciendo esto, entramos en un «subshell» o shell embebido. A partir de este momento, estaremos en
una nueva instancia de PowerShell y podremos examinar el contenido de las variables en ejecución y
hasta modificarlas.
Continue with this operation?
1+ For ($i=1 ; $i ­le 5; >>>> $i++) {Write­Host " Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):S
PS >>>
PS >>> $i
1
PS >>> $i=­2
PS >>> exit
Continue with this operation?
1+ For ($i=1 ; $i ­le 5; $i++) { >>>> Write­Host " Hola $i"}
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):
DEBUG: 1+ For ($i=1 ; $i ­le 5; $i++) { >>>> Write­Host " Hola $i"}
Hola ­2
...
Al entrar en un shell embebido, constatará que el prompt cambia ligeramente (doble signo «mayor que»
además del existente «>>»).
Hemos solicitado el valor de $i (que vale 1), y lo hemos modificado asignándole el valor ­2. También
hubiésemos podido realizar otra cosa completamente distinta como ejecutar comandos o scripts.
Después hemos salido del subshell gracias al comando exit, y el modo paso a paso ha retomado su
curso como si no hubiese pasado nada aun cuando hemos modificado $i.
¡Aquí tiene toda la potencia de PowerShell! Poder depurar un script PowerShell con él mismo, es
sorprendente ¿no le parece?
Debe saber que en cualquier momento puede entrar en un shell embebido en cuanto utiliza el
comando $host.EnterNestedPrompt(). Para saber si se encuentra en el shell principal o
en uno embebido, verifique el valor de la variable $NestedPromptLevel. Si es distinto de cero,
es porque se encuentra en un shell embebido.
El hecho de que el símbolo de sistema de PowerShell se transforme («>>») es debido a la
definición de la función Prompt. Esta está definida por defecto. Si modifica la función Prompt,
puede llegar a obtener una visualización completamente distinta.
Para volver al modo de ejecución normal y desactivar el modo paso a paso, debe introducir el
comando Set­PSDebug ­Off.
287
Gestión de los breakpoints
Quien dice ejecución paso a paso dice también breakpoint. En efecto, existen al menos seis comandos
para gestionar los «BreakPoint».
Descripción
Comando
Set­PsBreakpoint
Permite definir un breakpoint.
Get­PsBreakpoint
Permite enumerar los breakpoints definidos.
Disable­PsBreakpoint
Permite desactivar los breakpoints.
Enable­PsBreakpoint
Permite activar los breakpoints.
Remove­PsBreakpoint
Permite suprimir los breakpoints.
Get­PsCallStack
Permite mostrar la pila de llamadas.
Ejemplo de uso
Tomemos por ejemplo la función siguiente que devuelve el tamaño libre de los discos locales.
Function Get­DiskFreeSpace {
$Disks = Get­CimInstance Win32_LogicalDisk |
Where {$_.DriveType ­eq 3}
Foreach($disk in $Disks)
{
$prop = [Ordered]@{
’ID’ = $disk.DeviceID
’FreeSpace(GB)’ = [Math]::Round(($disk.FreeSpace)/1GB,3)
’FreeSpace(%)’ =
[Math]::Round(($disk.FreeSpace)*100/($disk.Size),3)
}
New­Object ­TypeName PSObject ­Property $prop
}
}
Pongamos ahora un breakpoint en la entrada de la función:
PS > Set­PsBreakpoint ­Command Get­DiskFreeSpace
ID Script
Line Command
Variable
­­ ­­­­­­
­­­­ ­­­­­­­
­­­­­­­­
0
Get­DiskFreeSpace
Action
­­­­­­
Al ejecutar la función, el modo de depuración se activa:
Entering debug mode. Use h or ? for help.
Hit Command breakpoint on ’Get­DiskFreeSpace’
At line:1 char:28
+ Function Get­DiskFreeSpace {
Cuando el prompt PowerShell muestra
[DBG], significa que se encuentra en el entorno de depuración
de PowerShell. Para navegar en el depurador PowerShell, debe utilizar los siguientes comandos:
288
Descripción
Comando depurador
S« Step­Into »
Ejecuta la siguiente instrucción y se para.
V« Step­Over »
Ejecuta la siguiente instrucción, pero ignora las funciones y
las llamadas.
O« Step­Out »
Realiza un paso a paso fuera de la función actual subiendo
un nivel si esta es embebida. Si se encuentra en el cuerpo
principal, la ejecución continúa hasta el final o hasta el
breakpoint siguiente.
C« Continue »
Continúa la ejecución hasta que el script se termina o
hasta alcanzar el siguiente breakpoint.
L« List »
Muestra la parte del script que se ejecuta. Por defecto, el
comando muestra la línea actual, las cinco líneas
anteriores y las diez siguientes. Para continuar mostrando
el script, use la tecla [Enter].
L<x> « List »
Muestra 16 líneas del inicio del script con el número de
línea especificado con el valor <x>.
L<x> <n> « List »
Muestra <n> líneas del script empezando por el número de
línea especificado con <x>.
G« Stop »
Para la ejecución del script y sale del depurador.
K« Get­PsCallStack »
Muestra la pila de llamadas.
[Enter]
Repite el último comando si se trata de Step(s), Step­
Over(v) o List(1). En los demás casos, representa una
acción de envío.
?, h
Muestra la ayuda de los comandos del depurador.
Ejemplo
PS > Get­DiskFreeSpace
Hit Command breakpoint on ’Get­DiskFreeSpace’
At line:1 char:28
+ Function Get­DiskFreeSpace {~
[DBG]: PS >> V
At line:2 char:4
+
$Disks = Get­CimInstance Win32_LogicalDisk |
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS >> v
At line:4 char:21
+
Foreach($disk in $Disks)
+
~~~~~~
[DBG]: PS >> k
Command
­­­­­­­
Get­DiskFreeSpace
<ScriptBlock>
Arguments
­­­­­­­­­
{}
{}
Location
­­­­­­­­
<No file>
<No file>
289
[DBG]: PS >> c
ID
­­
C:
D:
FreeSpace(GB)
­­­­­­­­­­­­­
16,197
30,25
FreeSpace(%)
­­­­­­­­­­­­
49,752
60,024
Para eliminar los breakpoints, debe utilizar el comando
Remove­PSbreakpoint con el nombre o el ID
del breakpoint como argumento. Aquí tiene un ejemplo con el breakpoint que tiene un ID igual a 0:
PS > Remove­PSbreakpoint ­ID 0
Así es como puede navegar en el depurador en modo consola. Sin embargo, con PowerShell ISE la
depuración puede realizarse también gráficamente.
b. En la consola PowerShell ISE
La depuración de un script con la ejecución paso a paso está claramente simplificada en la consola ISE.
Generalmente, se trata de la consola preferida por los desarrolladores de scripts para realizar esta
delicada operación.
En ISE, se define un breakpoint seleccionando la línea, y después eligiendo con el botón derecho la
opción Toggle Breakpoint, o presionando la tecla [F9].
Definición de un breakpoint desde la consola ISE
Para poder habilitar el depurador en ISE a fin de definir puntos de ruptura, su script debe haber
sido guardado.
Para poder situar puntos de ruptura, PowrShell exige que el script en edición esté guardado.
Se pueden crear varios breakpoints dentro del mismo script.
290
Definición de varios breakpoints desde la consola ISE
Después se realiza la ejecución presionando la tecla [F5] o bien eligiendo Run/Continue desde el menú
Debug.
Al ejecutar el script, el estado de cada variable es visible situando el puntero del ratón encima de la
misma.
4. Modo traza de Set­PSDebug
El modo «traza» permite comprender cómo PowerShell interpreta un script; veremos así el resultado de la
ejecución de cada operación. Esto nos permitirá, por ejemplo, descubrir más rápidamente el origen de un
bug.
La activación del modo «traza» se hace de la siguiente manera:
Set­PSDebug ­Trace [1 | 2]
Existen dos modos de traza: el primero ­trace 1 es el modo básico que muestra solamente las
operaciones, el segundo ­trace 2 es el modo detallado que muestra además de las operaciones, todas
las llamadas a scripts o a funciones. Encontramos también los términos «niveles de seguimiento» para
designar estos modos.
Retomemos el ejemplo siguiente que devuelve el tamaño libre de los discos.
# Get­DiskFreeSpace.ps1
# Script que calcula la memoria libre de los discos lógicos locales
$Disks = Get­CimInstance Win32_LogicalDisk |
Where {$_.DriveType ­eq 3}
Foreach($disk in $Disks)
{
$prop = [Ordered]@{
’ID’ = $disk.DeviceID
’FreeSpace(GB)’ = [Math]::Round(($disk.FreeSpace)/1GB,3)
291
’FreeSpace(%)’ =
[Math]::Round(($disk.FreeSpace)*100/($disk.Size),3)
}
New­Object ­TypeName PSObject ­Property $prop
}
Veamos el resultado con el primer modo de traza:
PS > Set­PSDebug ­Trace 1
PS > .\Get­DiskFreeSpace.ps1
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
DEBUG:
ID
­­
C:
D:
DEBUG:
1+ >>>> .\Get­DiskFreeSpace.ps1
4+ >>>> $Disks = Get­CimInstance Win32_LogicalDisk |
5+
Where >>>> {$_.DriveType ­eq 3}
5+
Where { >>>> $_.DriveType ­eq 3}
5+
Where {$_.DriveType ­eq 3 >>>> }
5+
Where >>>> {$_.DriveType ­eq 3}
5+
Where { >>>> $_.DriveType ­eq 3}
5+
Where {$_.DriveType ­eq 3 >>>> }
5+
Where >>>> {$_.DriveType ­eq 3}
5+
Where { >>>> $_.DriveType ­eq 3}
5+
Where {$_.DriveType ­eq 3 >>>> }
6+ Foreach($disk in >>>> $Disks)
6+ Foreach( >>>> $disk in $Disks)
8+
>>>> $prop = [Ordered]@{
14+
>>>> New­Object ­TypeName PSObject ­Property $prop
FreeSpace(GB)
FreeSpace(%)
­­­­­­­­­­­­­
­­­­­­­­­­­­
16,197
49,752
30,25
60,024
6+ Foreach( >>>> $disk in $Disks)
En la consola, comprobamos que se muestran todas las operaciones en amarillo y como mensaje de
depuración. Además se muestra un número seguido del símbolo «+» delante de cada operación. Este
número corresponde al número de línea del script en ejecución.
Veamos ahora qué pasa si definimos el nivel de traza a 2:
PS > Set­PSDebug ­Trace 2
PS > .\Get­DiskFreeSpace.ps1
DEBUG:
1+ >>>> .\Get­DiskFreeSpace.ps1
DEBUG:
! CALL function ’<ScriptBlock>’
DEBUG:
4+ >>>> $Disks = Get­CimInstance Win32_LogicalDisk |
DEBUG:
! CALL function ’<ScriptBlock>’ (defined in file
’C:\Temp\Get­DiskFreeSpace.ps1’)
DEBUG:
5+
Where >>>> {$_.DriveType ­eq 3}
DEBUG:
! CALL function ’<ScriptBlock>’ (defined in file
’C:\Temp\Get­DiskFreeSpace.ps1’)
DEBUG:
5+
Where { >>>> $_.DriveType ­eq 3}
DEBUG:
5+
Where {$_.DriveType ­eq 3 >>>> }
DEBUG:
5+
Where >>>> {$_.DriveType ­eq 3}
DEBUG:
! CALL function ’<ScriptBlock>’ (defined in file
’C:\Temp\Get­DiskFreeSpace.ps1’)
DEBUG:
5+
Where { >>>> $_.DriveType ­eq 3}
DEBUG:
5+
Where {$_.DriveType ­eq 3 >>>> }
DEBUG:
5+
Where >>>> {$_.DriveType ­eq 3}
DEBUG:
! CALL function ’<ScriptBlock>’ (defined in file
292
’C:\Temp\Get­DiskFreeSpace.ps1’)
DEBUG:
5+
Where { >>>> $_.DriveType ­eq 3}
DEBUG:
5+
Where {$_.DriveType ­eq 3 >>>> }
DEBUG:
! SET $Disks = ’\\WIN2K12R2\root\cimv2:Win32_LogicalDisk...
DEBUG:
6+ Foreach($disk in >>>> $Disks)
DEBUG:
! SET $foreach = ’IEnumerator’.
DEBUG:
6+ Foreach( >>>> $disk in $Disks)
DEBUG:
! SET $disk = ’\\WIN2K12R2\root\cimv2:Win32_LogicalDisk...
DEBUG:
8+
>>>> $prop = [Ordered]@{
DEBUG:
! SET $prop =
’System.Collections.Specialized.OrderedDiction...
DEBUG:
14+
>>>> New­Object ­TypeName PSObject ­Property $prop
ID
­­
C:
D:
FreeSpace(GB)
­­­­­­­­­­­­­
16,197
30,25
DEBUG:
DEBUG:
FreeSpace(%)
­­­­­­­­­­­­
49,752
60,024
6+ Foreach( >>>> $disk in $Disks)
! SET $foreach = ’’.
En este modo además vemos aparecer la llamada de nuestro script, las distintas asignaciones de variables
y sus valores asociados. En ciertos casos, podemos también ver las llamadas a los métodos estáticos del
framework .NET.
5. Trace­Command
Este comando permite conseguir trazas de muy bajo nivel. Fue inicialmente concebido por (y para) los
empleados de Microsoft que se encargaban del desarrollo de PowerShell pero también para los que se
encargaban de dar soporte a los usuarios. Su uso e interpretación, algo complejos, hacen que sea más
adecuado para los desarrolladores experimentados que para los usuarios finales de PowerShell. En
efecto, apenas existe una poca documentación sobre Trace­Command.
Para lo que viene a continuación, puede ser útil saber que el mecanismo de trazado de este comando es
el del framework .NET.
Veamos algunos parámetros de
Trace­Command:
Parámetro
Descripción
­Name
Nombre de las fuentes de la traza. Es decir la información que nos
interesa trazar. Por ejemplo, podemos estar interesados en los
mensajes que intercambia PowerShell cuando se asigna una
variable o cuando se asignan parámetros en la llamada a un script
o a un comando. Las fuentes de traza son numerosas; para
conocerlas, use el comando: Get­TraceSource.
­Expression
Bloque de scripts a trazar. Especificamos en este parámetro un
bloque de script entre llaves. Por ejemplo: {./miScript.ps1}.
­Command
Nombre del comando que se ejecutará durante la traza.
­Option
Tipo de eventos trazados,
­FilePath
Envío de la traza a un archivo. Cuando la información es
abundante, conviene redirigirla a un archivo. Observe que se
puede utilizar esta opción conjuntamente con ­PSHost.
All es el valor por defecto.
293
Descripción
Parámetro
­Debugger
Envío de la traza en un depurador externo como Visual Studio u
otros.
­PSHost
Envío de la traza en pantalla.
­ListenerOption
Nivel de detalle de cada línea de traza.
Las fuentes de traza son numerosas. Para obtener la lista completa utilice el comando
Get­
TraceSource. Encontrará la lista completa en el anexo Lista de las fuentes de traza.
Aquí tiene una descripción de algunas de esas fuentes:
Fuente
Descripción
TypeConversion
Traza la mecánica interna de conversión de tipo. Por ejemplo,
en una asignación de variable.
CommandDiscovery
Permite observar cómo funciona el intérprete de comandos
para encontrar un comando o un script.
ParameterBinding
Traza la asociación de parámetros entre la llamada a un script
o una función y el intérprete de comandos.
FormatViewBinding
Permite saber si una vista predefinida existe o no.
Ejemplo: fuente de traza
TypeConversion
Tomemos un ejemplo simple donde definimos una variable forzando su tipo:
PS > [char]$var=65
Asignamos a una variable de tipo
decir «A».
Gracias a
char el valor «65», para obtener su carácter ASCII correspondiente, es
Trace­Command, entenderemos mejor lo que pasa.
Probemos la siguiente línea de comandos:
PS > Trace­Command ­Name TypeConversion ­Expression {
[char]$var=65} ­Pshost
Aquí tiene el resultado obtenido:
PS > Trace­Command ­Name TypeConversion ­Expression {[char]$var=65} ­Pshost
DEBUG : TypeConvers…: Converting "System.Object[]" to "System.Object".
DEBUG : TypeConvers…: Result type is assignable from value to convert’s type
DEBUG : TypeConvers…: Converting "" to "System.String".
DEBUG : TypeConvers…: Result type is assignable from value to convert’s type
DEBUG : TypeConvers…: Converting "" to "System.String".
DEBUG : TypeConvers…: Result type is assignable from value to convert’s type
DEBUG : TypeConvers…: Converting
"System.Management.Automation.InvocationInfo" to
"System.Management.Automation.InvocationInfo".
DEBUG : TypeConvers…: Result type is assignable from value to convert’s
DEBUG : TypeConvers…: Converting "System.Object[]" to "System.Object[]".
294
DEBUG : TypeConvers…: Result type is assignable from value to convert’s type
DEBUG : TypeConvers…: Converting "65" to "System.Char".
DEBUG : TypeConvers…: Conversion using IConvertible succeeded.
Ejemplo: fuente de traza
CommandDiscovery
En este ejemplo, intentamos ejecutar un script que no existe y así observar el comportamiento del
intérprete de comandos.
Probemos la siguiente línea de comandos:
PS > Trace­Command ­Name CommandDiscovery ­Expression {c:\miScript.ps1}
­Pshost
Aquí tiene el resultado obtenido:
PS > Trace­Command ­Name CommandDiscovery ­Expression
{c:\miScript.ps1} ­Pshost
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
filter: c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
path: c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
PSPath
DEBUG: CommandDiscovery Information: 0
not be found: c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
the lookup in the specified directory:
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
DEBUG: CommandDiscovery Information: 0
found, trying again with get­prepended
DEBUG: CommandDiscovery Information: 0
get­c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
filter: get­c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
path: get­c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
PSPath
DEBUG: CommandDiscovery Information: 0
for the path: get­c:\miScript.ps1
DEBUG: CommandDiscovery Information: 0
get­c
DEBUG: CommandDiscovery Information: 0
: Looking up command: c:\miScript.ps1
: Attempting to resolve function or
: The name appears to be a qualified
: Trying to resolve the path as an
: ERROR: The path could
: The path is
c:\
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: Looking for
: The command
rooted, so only doing
miScript.ps1 in c:\
miScript.ps1.ps1 in c:\
miScript.ps1.COM in c:\
miScript.ps1.EXE in c:\
miScript.ps1.BAT in c:\
miScript.ps1.CMD in c:\
miScript.ps1.VBS in c:\
miScript.ps1.VBE in c:\
miScript.ps1.JS in c:\
miScript.ps1.JSE in c:\
miScript.ps1.WSF in c:\
miScript.ps1.WSH in c:\
miScript.ps1.MSC in c:\
[c:\miScript.ps1] was not
: Looking up command:
: Attempting to resolve function or
: The name appears to be a qualified
: Trying to resolve the path as an
: ERROR: A drive could not be found
: ERROR: The drive does not exist:
: The path is relative, so only doing
295
the lookup in the specified directory:
DEBUG: CommandDiscovery Information: 0 : ERROR: ’get­c:\miScript.ps1’ is not
recognized as a cmdlet, function,operable program or script file.
c:\miScript.ps1 : The term ’c:\miScript.ps1’ is not recognized as the name of
a cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and try again.
At line:1 char:51
+ Trace­Command ­Name CommandDiscovery ­Expression {c:\miScript.ps1}
­PSHost
Confirmamos cómo PowerShell empieza primero buscando una función o un filtro con el nombre indicado
c:\miScript.ps1. Después, como no lo encuentra, determina que se trata de una ruta hacia un
archivo. Entonces busca el archivo miScript.ps1 en la carpeta C:\. No pudiendo encontrar este
archivo, repasa todas las extensiones contenidas en la variable de entorno PATHEXT para intentar
encontrar un archivo que ejecutar. Para terminar, como la búsqueda no ha obtenido buenos
resultados hasta el momento, el intérprete busca un comando de tipo «Get» añadiendo el prefijo «Get­»
a «C:\miScript.ps1», es decir «Get­c:\miScript.ps1». Por último, cuando todas las soluciones
están agotadas, PowerShell genera un error.
Interesante ¿verdad? Resulta difícil imaginar todo lo que ocurre por detrás para una operación tan simple
como la ejecución de un script.
Ejemplo: fuente de traza
FormatViewBinding
Esta fuente de traza nos permite saber si el resultado de un comando mostrado en pantalla ha utilizado
algún formateo por parte de PowerShell. En efecto, un gran número de tipos de objetos aprovechan el
formato por defecto, el cual está descrito en los archivos .ps1xml contenidos en la carpeta de instalación
$PSHome,
C:\Windows\System32\WindowsPowerShell\ v1.0).
de
PowerShell
(en
la
variable
o
sea
generalmente
Probemos la siguiente línea de comandos:
PS > Trace­Command ­Name FormatViewBinding ­Expression {Get­Process
notepad | Out­Host} ­PSHost
Aquí tiene el resultado obtenido:
PS > Notepad.exe
PS > Trace­Command ­Name FormatViewBinding ­Expression {Get­Process
notepad | Out­Host} ­PSHost
DEBUG: FormatViewBindin Information: 0 : FINDING VIEW TYPE:
System.Diagnostics.Process
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:
ThumbprintTable TYPE:
System.Security.Cryptography.X509Certificates.X509Certificate2
DEBUG: FormatViewBindin Information: 0 : NOT MATCH List NAME:
ThumbprintList GROUP: CertificateProviderTypes
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Wide NAME:
ThumbprintWide GROUP: CertificateProviderTypes
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:
PSThumbprintTable TYPE: System.Management.Automation.Signature
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Wide NAME:
PSThumbprintWide TYPE: System.Management.Automation.Signature
DEBUG: FormatViewBindin Information: 0 : NOT MATCH List NAME:
PathOnly GROUP: CertificateProviderTypes
296
DEBUG: FormatViewBindin Information: 0 :
NOT MATCH Table NAME:
System.Security.Cryptography.X509Certificates.X509CertificateEx TYPE:
System.Security.Cryptography.X509Certificates.X509CertificateEx
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:
System.Reflection.Assembly TYPE: System.Reflection.Assembly
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:
System.Reflection.AssemblyName TYPE: System.Reflection.AssemblyName
DEBUG: FormatViewBindin Information: 0 :
NOT MATCH Table NAME:
System.Globalization.CultureInfo TYPE: System.Globalization.CultureInfo
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:System.
Diagnostics.FileVersion Info TYPE: System.Diagnostics.FileVersionInfo
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:
System.Diagnostics.EventLogEntry TYPE:
System.Diagnostics.EventLogEntry
DEBUG: FormatViewBindin Information: 0 :
NOT MATCH Table NAME:
System.Diagnostics.EventLog TYPE: System.Diagnostics.EventLog
DEBUG: FormatViewBindin Information: 0 :
NOT MATCH Table NAME:
System.Version TYPE: System.Version
DEBUG: FormatViewBindin Information: 0 : NOT MATCH Table NAME:
System.Drawing.Printing.PrintDocument TYPE: System.Drawing.Printing.
PrintDocument
DEBUG: FormatViewBindin Information: 0 :
NOT MATCH Table NAME:
Dictionary TYPE: System.Collections.DictionaryEntry
DEBUG: FormatViewBindin Information: 0 :
NOT MATCH Table NAME:
ProcessModule TYPE: System.Diagnostics.ProcessModule
DEBUG: FormatViewBindin Information: 0 :
MATCH FOUND Table NAME:
process TYPE: System.Diagnostics.Process
DEBUG: FormatViewBindin Information: 0 :
MATCH FOUND Table NAME:
process TYPE: Deserialized.System.Diagnostics.Process
DEBUG: FormatViewBindin Information: 0 : An applicable
view has been found
Handles
­­­­­­­
53
NPM(K)
­­­­­­
3
PM(K)
­­­­­
1444
WS(K) VM(M)
­­­­­ ­­­­­
5564
53
CPU(s)
­­­­­­
0,41
Id ProcessName
­­ ­­­­­­­­­­­
2888 notepad
ver
en
todas
las
últimas
líneas
la
siguiente
información:
DEBUG:
FormatViewBindin Information: 0: An applicable view has been found. Esto
Podemos
significa que se ha encontrado una vista.
DEBUG: FormatViewBindin Information: 0: FINDING VIEW
TYPE: System.Diagnostics.Process, esta es interesante ya que indica precisamente el nombre
En cuanto a la primera línea
del tipo de vista.
Si no se hubiese encontrado una vista para este tipo, hubiésemos visto el mensaje siguiente en la última
línea: DEBUG: FormatViewBindin Information: 0: No applicable view has been
found.
297
Seguridad
298
La seguridad: ¿para quién? ¿Por qué?
La aparición de las redes locales y de Internet ha cambiado muchas cosas en la manera de proteger su PC.
Ya no basta con encadenar el disco duro a la mesa y cerrar la puerta del despacho por la noche para que no
le roben o pirateen sus datos. Ahora, proteger su puesto de trabajo se ha convertido en algo esencial para
no tener que remediar intrusiones o malas intenciones.
Pero entonces ¿contra quién prevenir? Pues contra todo lo que se mueve… y también los que no se
mueven. En efecto, ya sean programas malintencionados, usuarios con malas intenciones, o hasta usuarios
novatos, todos están considerados una amenaza. Por este motivo debe asegurar su sistema estableciendo
reglas de seguridad, aplicándolas y asegurándose de que los demás hacen lo mismo.
Los riesgos vinculados al scripting
Adivinará rápidamente que lo que hace la fuerza del scripting se convierte en su debilidad. La facilidad con la
que puede hacer todo, ya sea haciendo clic en un script o ejecutándolo desde la ventana de comandos,
puede meterle en problemas si no presta atención.
Imagine un script de inicio de sesión que en la apertura de la sesión ¡la bloquea enseguida! Claro, es
divertido entre amigos, pero en una empresa, dudamos que esto sea bueno. Todavía peor un script de una
persona malintencionada o realmente novata en PowerShell (en este caso, le aconsejamos comprarle un
ejemplar de este libro…) puede perfectamente bloquear cuentas de usuarios en Active Directory, formatear
el disco, reiniciar el sistema en un bucle infinito… Lo ha entendido, un script puede hacer de todo. Aunque a
día de hoy se notifica al usuario con alertas para prevenirle de la ejecución de un script, estas no son
capaces de determinar si un script es nocivo para el correcto funcionamiento del sistema.
Los riesgos vinculados al scripting se resumen a una historia de compromisos: o bien impide toda ejecución
de scripts, es decir asumir el riesgo de «amargarle la vida» teniendo que hacer y rehacer tareas básicas y
muy ingratas, o bien elige abrir su sistema a PowerShell, teniendo cuidado de tomar las precauciones que
se imponen.
Pero no se deje desanimar ya que aunque la ejecución de scripts le expone a ciertos problemas de
seguridad, PowerShell está dotado de ciertos conceptos que hacen de él uno de los lenguajes de script más
seguros. No hay que olvidar que en caso de problemas de seguridad, es la imagen de Microsoft en su
conjunto la que sufre…
Optimizar la seguridad de PowerShell
1. La seguridad de PowerShell por defecto
Lo ha entendido, la seguridad es una área muy importante, sobre todo en el dominio del scripting. Por
este motivo los creadores de PowerShell han incluido dos reglas de seguridad por defecto.
Los archivos ps1 asociados al bloc de notas
La extensión «.ps1» de los scripts PowerShell está por defecto asociada al bloc de notas (o Notepad).
Este procedimiento permite evitar ejecutar automáticamente scripts potencialmente peligrosos por una
mala manipulación. El bloc de notas es, realmente, un editor un poco clásico, pero tiene la doble ventaja
de ser inofensivo y de no bloquear la ejecución de un script cuando este está abierto con el editor.
Observará sin embargo que la edición (clic con el botón derecho + Modificar) de archivos .ps1 está
asociada al editor ISE.
Este tipo de seguridad no existía con los scripts VBS cuya apertura estaba directamente asociada a
Windows Script Host.
299
Una directiva de ejecución restringida
La segunda barrera de seguridad es la aplicación de la directiva de ejecución
para los puestos clientes y
R2).
Restricted por defecto
RemoteSigned por defecto para los servidores (desde Windows Server 2012
La directiva Restricted es la más restrictiva. Es decir que bloquea sistemáticamente la ejecución de
todos los scripts. Solo se ejecutan los comandos tecleados en la shell. Para remediar a esta situación,
PowerShell requiere que el usuario cambie el modo de ejecución con el comando Set­
ExecutionPolicy <modo de ejecución>. Pero para ello debe ser administrador del equipo.
Igual comprende mejor por qué el uso de PowerShell en sus equipos no constituye un aumento del
riesgo, en la medida en que se respetan ciertas reglas.
2. Las directivas de ejecución
PowerShell integra un concepto de seguridad que llamamos directivas de ejecución (execution policies)
para que un script no autorizado no pueda ejecutarse en contra de la voluntad del usuario. Existen siete
configuraciones posibles: Restricted, RemoteSigned, AllSigned, UnRestricted, Bypass,
Default y Undefined. A cada una de ellas le corresponde un nivel de autorización de ejecución de
determinados scripts. Podrá llegar a cambiar en función de la directiva que desee aplicar.
a. Las diferentes directivas de ejecución
Restricted: es la directiva más restrictiva y también la directiva por defecto en los puestos cliente (de
Windows 7 a Windows 10). No permite la ejecución de scripts pero autoriza las instrucciones por la línea
de comandos tecleadas en la consola (modo interactivo). Esta directiva puede considerarse como la más
radical dado que protege frente a la ejecución de archivos .ps1.
Con esta directiva, al tratar de ejecutar un script, se muestra un mensaje de este tipo en la consola:
.\lanzadera.ps1 : File C:\temp\lanzadera.ps1 cannot be loaded because running
scripts is disabled on this system. For more information, see
about_Execution_Policies at https://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1 *
+ .\lanzadera.ps1
+ ~~~~~~~~~~~~~
+ CategoryInfo
: SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
Si esta es la directiva definida por defecto en la instalación de PowerShell, tendrá que cambiarla para la
ejecución de su primer script.
Para poder modificar la directiva de ejecución PowerShell, deberá ser administrador local de la
máquina.
AllSigned: es la directiva más «segura» que permite la ejecución de scripts. Autoriza únicamente la
ejecución de scripts firmados. Un script firmado es un script que contiene una firma digital como la que se
muestra en la figura de abajo.
300
Ejemplo de script firmado
Con la directiva AllSigned, la ejecución de scripts firmados necesita que esté en posesión de los
certificados correspondientes (consulte la sección Firma de scripts).
RemoteSigned: esta directiva es similar a AllSigned con la diferencia de que solo los scripts que
tienen un origen distinto al local necesitan una firma. Por consiguiente, todos sus scripts creados
localmente pueden ser ejecutados sin necesidad de firma.
A partir de Windows Server 2012 R2, PowerShell se ejecuta con esta directiva de ejecución por defecto,
lo que no era el caso en las versiones anteriores de Windows Server.
Si intenta ejecutar un script descargado de Internet sin estar firmado, obtendrá el siguiente mensaje de
error.
.\Get­Script.ps1: File C:\Temp\Script.ps1 cannot be loaded.
The file C:\Temp\Script.ps1 is not digitally signed.
Seguramente se pregunte ¿cómo hace PowerShell para saber que nuestro script tiene su origen en
Internet? Respuesta: Gracias a los «Alternate Data Streams» que se implementan con la forma de
flujos cachés desde las aplicaciones de comunicación tales como Microsoft Outlook, Internet Explorer,
Outlook Express y Windows Messenger (consulte la sección dedicada a los Alternate Data Streams). En
resumen, cuando descarga un script desde un cliente Microsoft, se le adjunta su origen.
301
Unrestricted: con esta directiva todos los scripts, sea cual sea su origen, se ejecutan sin solicitar
una firma.
Esta directiva muestra aun así un aviso cuando intenta ejecutar algún script descargado de Internet.
PS > .\script.ps1
Security warning
Run only scripts that you trust. While scripts from the internet can
be useful, this script can potentially harm your computer. If you trust
this script, use the Unblock­File cmdlet to allow the script to run
without this warning message. Do you want to run C:\Temp\script.ps1?
[D] Do not run [R] Run once [S] Suspend [?] Help (default is "D"):
Bypass: es la directiva menos restrictiva, y por lo tanto la menos segura. No se bloquea nada y no se
muestra ningún mensaje de aviso. Es por lo tanto la directiva con el riesgo más elevado de ejecutar
scripts malintencionados.
Undefined: no se ha definido ninguna directiva para el ámbito actual. Si no se definen directivas de
ejecución en ningún ámbito entonces la directiva efectiva será la directiva Restricted.
Default: carga la directiva por defecto, a saber Restricted.
Microsoft ha puesto en marcha mecanismos con el fin de intentar limitar los riesgos vinculados a la
ejecución de scripts que provengan del exterior de la empresa y por lo tanto potencialmente
peligrosos. La configuración por defecto permite alcanzar este objetivo pero no garantiza en ningún
caso una seguridad perfecta.
b. Los ámbitos de las directivas de ejecución
PowerShell permite gestionar el ámbito de las directivas. El orden de aplicación es el siguiente:
Ámbito Process: la directiva de ejecución solo afecta a la sesión actual (proceso Windows
PowerShell). El valor asignado al ámbito Process se almacena únicamente en memoria; por lo
tanto no se conserva al cerrar la sesión de PowerShell.
Ámbito CurrentUser: la directiva de ejecución aplicada al ámbito CurrentUser solo afecta al
usuario actual. Se almacena el tipo de directiva de manera permanente dentro de la clave de
registro HKEY_CURRENT_USER.
Ámbito LocalMachine: la directiva de ejecución aplicada al ámbito LocalMachine afecta a
todos los usuarios del equipo. Se almacena el tipo de directiva de manera permanente dentro de
la clave de registro HKEY_LOCAL_MACHINE.
La directiva con prioridad 1 tiene preferencia sobre la que tenga prioridad 3. Por lo tanto si el ámbito
LocalMachine es más restrictivo que el ámbito Process, la directiva aplicada será aun así la del
ámbito Undefined en cuyo caso PowerShell aplicará la directiva del ámbito
intentará aplicar la directiva LocalMachine.
Recuerde que el ámbito
CurrentUser y después
LocalMachine es el definido por defecto cuando aplicamos una directiva de
ejecución sin precisar un ámbito particular.
302
c. Identificar la directiva de ejecución actual
La directiva de ejecución actual se obtiene con el comando
Get­ExecutionPolicy.
Ejemplo
PS > Get­ExecutionPolicy
Restricted
Con este comando, tenemos la opción
­List. Gracias a ella, sabremos qué directivas se aplican en cada
ámbito.
Por ejemplo
PS > Get­ExecutionPolicy ­List
Scope ExecutionPolicy
­­­­­ ­­­­­­­­­­­­­­­
MachinePolicy
Undefined
UserPolicy
Undefined
Process
Undefined
CurrentUser
AllSigned
LocalMachine
Restricted
En este ejemplo, vemos que en el ámbito CurrentUser hemos aplicado la directiva AllSigned,
mientras que en el ámbito LocalMachine hemos asignado la directiva Restricted (valor por
defecto). Si ha seguido bien hasta aquí, ¿cuál es según usted la directiva que se aplica a nuestra sesión
PowerShell actual?
Para saberlo, preguntémosle a
Get­ExecutionPolicy:
PS > Get­ExecutionPolicy
AllSigned
Pues sí, se trata de la directiva
ámbito LocalMachine.
AllSigned ya que el ámbito CurrentUser es prioritario frente al
MachinePolicy y
UserPolicy. Estos ámbitos corresponden respectivamente a los ámbitos LocalMachine y
CurrentUser cuando se usan las directivas de grupo (GPO) para parametrizar el comportamiento de
Observe que en la lista de los ámbitos devueltos encontramos los ámbitos
PowerShell en los equipos de un dominio.
Get­
ExecutionPolicy ­List. Debe saber que las directivas de ejecución definidas GPO son prioritarias
El orden de aplicación de las directivas de ejecución es el devuelto por el comando
respecto a las demás.
d. Aplicar una directiva de ejecución
La directiva Restricted es la directiva aplicada por defecto en el entorno PowerShell sobre un puesto
de trabajo. Esta no es la adecuada ya que no permite la ejecución de scripts. Solo podemos por lo tanto
aconsejarle la elección de otra más flexible, de manera que pueda gozar de las numerosas ventajas que
ofrece el scripting con PowerShell.
303
El contexto de seguridad de su empresa le guiará en la elección de su directiva de ejecución. Dicho esto,
aunque estemos tentados de elegir la directiva AllSigned tanto en los puestos cliente como en los
servidores, debe saber que se trata del modo de seguridad más restrictivo. En efecto, después de cada
modificación de un script (perfil incluido), deberá firmarlo de nuevo, lo cual resulta muy pesado en el uso
diario.
Para
aportar
un
mínimo
de
seguridad,
le
recomendamos
la
directiva
de
ejecución
RemoteSigned. Después de haberlo investigado por nosotros mismos, es la directiva elegida por
Microsoft a nivel interno para la gestión de sus equipos. Es también la que utiliza la mayoría de
empresas.
El cambio de directiva se realiza con el comando
Set­ExecutionPolicy seguido del modo elegido.
Por ejemplo: Set­ExecutionPolicy RemoteSigned tendrá como resultado aplicar la directiva de
ejecución RemoteSigned al ámbito LocalMachine.
Para aplicar una directiva a otro ámbito diferente del ámbito
parámetro ­Scope seguido del nombre del ámbito.
LocalMachine, tiene que utilizar el
Ejemplo
Aplicación de la directiva
RemoteSigned al ámbito Process.
PS > Set­ExecutionPolicy ­ExecutionPolicy RemoteSigned ­Scope Process
Execution Policy Change
The execution policy helps protect you from scripts that you do not trust.
Changing the execution policy might expose you to the security risks
described in the about_Execution_Policies help topic at
http://go.microsoft.com/fwlink/?LinkID=135170. Do you want to change
the execution policy?
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"):
Cuando realizamos un cambio de directiva de ejecución, se muestra sistemáticamente un mensaje de
aviso solicitándolo confirmar la acción.
Verifiquemos ahora el resultado:
PS > Get­ExecutionPolicy ­List
Scope ExecutionPolicy
­­­­­ ­­­­­­­­­­­­­­­
MachinePolicy
Undefined
UserPolicy
Undefined
Process
RemoteSigned
CurrentUser
AllSigned
LocalMachine
Restricted
PS > Get­ExecutionPolicy
RemoteSigned
Solo es posible modificar la directiva de ejecución del ámbito LocalMachine con permisos de
administrador. Por lo tanto, arranque PowerShell como administrador (clic con el botón
derecho + Run as Administrator).
304
clave de registro correspondiente al ámbito LocalMachine es la siguiente:
HKEY_Local_Machine\SOFTWARE\Microsoft\PowerShell\1\ShellIds\
Microsoft.Powershell\ExecutionPolicy
La
Otra manera de configurar la directiva de ejecución de los equipos consiste en usar GPO (Group
Policy Objects). Para ello Microsoft genera un archivo .admx (consulte más adelante en este
capítulo).
3. Scripts descargados de Internet
Si ha leído la sección anterior, sabe que los scripts creados localmente no están sometidos a las mismas
obligaciones que los descargados de Internet.
En primer lugar, con la directiva
RemoteSigned no le será posible ejecutar scripts descargados de
Internet si estos no están firmados o desbloqueados. Dicho esto, incluso estando firmados, para poder
ejecutarlos sin problemas, los scripts deben provenir de una entidad aprobada.
¡Intentemos verlo un poco más claro! Por ejemplo, cuando descarga un script desde Internet pasando por
las herramientas de comunicación Microsoft (Outlook, Internet Explorer, Outlook Express o Windows Live
Messenger), estos asocian en su script un flujo de datos adicional llamado Alternate Data Stream
(consulte la sección dedicada a los Alternate Data Streams) permitiendo a PowerShell identificar el origen
del script. Y si el script no tiene un origen local, entonces pueden presentarse dos casos.
Caso de un script firmado
Si el script está firmado digitalmente, es decir si contiene una firma que permita a la vez identificar el editor
y garantizar la integridad del script, entonces PowerShell le pedirá si desea aprobar este editor (consulte
al final del capítulo la sección Ejecutar scripts firmados).
Tenga en cuenta que para ejecutar un script firmado, debe poseer necesariamente un certificado de
autoridad raíz de confianza correspondiente.
El primer reflejo que debe tener cuando recupera un script de Internet o de otra parte, es verificar su
contenido. Un ojo crítico disminuirá rápidamente los potenciales riesgos, si el código no es muy largo.
Y si finalmente después de haber inspeccionado el código determina que no presenta riesgo alguno
para su sistema, entonces puede ejecutarlo. Preste una particular atención a los comandos de tipo Set.
Caso de un script no firmado
Si intenta ejecutar un script no firmado que proviene de un equipo remoto y que se encuentra en modo
RemoteSigned, PowerShell le responderá:
.\Script.ps1 : File C:\Temp\Script.ps1 cannot be loaded. The file
C:\Temp\Script.ps1 is not digitally signed. You cannot run this script
on the current system.
Sin embargo también es posible ejecutar scripts no firmados. Para ello debe hacer lo que llamamos
«desbloquear» el script. Desbloquear un script equivale a eliminar el Alternate Data Stream que contiene
la información sobre su origen. Una vez desbloqueado el script, puede ejecutarlo como si hubiese sido
creado localmente.
305
Es evidente que en estos casos, más que en ningún otro, debe inspeccionar el script. No olvide que un
script descargado de Internet de un sitio web desconocido es potencialmente peligroso.
Para desbloquear un script descargado de Internet, use el comando
siguiente ejemplo:
Unblock­File como muestra el
PS > Unblock­File ./Group_Members_Modified.ps1
El comando
Unblock­File apareció con PowerShell 3.0.
Si prefiere usar la interfaz gráfica, entonces siga los pasos siguientes:
Arranque el explorador de Windows y localice el script.
Haga clic con el botón derecho sobre el script y elija Properties. Después, en la pestaña General, en
la parte inferior de la ventana, elija Unblock.
Ventana Properties que permite desbloquear un script
El script está ahora desbloqueado. Ya no es posible conocer su origen.
306
4. Los Alternate Data Streams (ADS)
a. Los origines
Desconocidos para muchos informáticos, los Alternate Data Streams (en español: flujos de datos
adicionales) no son de ayer. En efecto, los ADS aparecieron con el sistema de archivos NTFS (New
Technology File System) usado por la familia Windows desde principios de los años 90 con la aparición de
Windows NT 3.1.
El principio de los ADS, que no ha evolucionado desde su creación, es insertar flujos de datos
suplementarios en un archivo. Hasta aquí nada sorprendente. Sí pero, algo más sorprendente, es que el
contenido así como el tamaño de los flujos son invisibles. Es decir que puede «esconder» un ejecutable
de varios megabytes en un archivo de texto de pocos bytes sin que el tamaño del archivo, visible por el
usuario desde la pestaña Properties, indique la presencia de los bytes ocupados por el ejecutable.
Poco documentados y explicados de forma poco clara, entenderá seguramente por qué los ADS son
todavía hoy el instrumento de numerosos virus. La pregunta es por lo tanto ¿porqué estos flujos de
datos son invisibles?
Lo que hay que saber es que el uso de los ADS hoy se ha desviado de su función original. En sus
orígenes, los ADS se integraron en los sistemas operativos Windows para permitir la compatibilidad con
el sistema de archivos Macintosh: el Hierarchical File System (HFS). Tal vez sin saber que los archivos
Macintosh (en los SO anteriores a MAC OSX) son el resultado de la asociación de dos componentes: el
Data Fork y el Resource Fork. Como su nombre indica, el Resource Fork contiene los recursos usados por
una aplicación. Encontraremos por ejemplo elementos de la interfaz gráfica (menús, ventanas, mensajes,
etc.) y otros elementos ligados a la traducción de la aplicación en varios idiomas. Y por oposición, el
DataFork contiene el código binario de la aplicación, que en principio es inmutable. Sin embargo, en el
caso de aplicaciones compatibles con PowerPC y Motorola, el Data Fork contiene dos versiones de
código.
Es por lo tanto por una cuestión de interoperabilidad entre sistemas por lo que NTFS integró los data
streams. Estos últimos juegan el rol del «Resource Fork» en versión Windows. Sin embargo, en la
práctica, esta voluntad de interoperabilidad no se materializó en ninguna aplicación concreta. Si bien hoy
en día los ADS sirven principalmente al sistema NTFS para insertar información en los archivos (de alguna
manera como metadatos). Por ejemplo, cuando descarga un script PowerShell desde Internet Explorer,
este último crea y añade un Alternate Data Stream a su script para especificar su origen. Es así como
PowerShell determina si el script que intenta ejecutar se ha creado localmente o no.
Tenga en cuenta que solo se evalúa el tamaño del flujo principal de los archivos por el
administrador de cuotas, el de los ADS se ignora.
b. Crear y leer los ADS
En PowerShell 3.0 apareció el parámetro ­Stream que, en varios comandos de gestión de archivos, se
encarga de los flujos de datos alternativos. Aquí tiene un escenario de gestión para que aprenda todos
los principios.
Empecemos por crear un archivo de texto vacío gracias al comando siguiente:
PS > New­Item ­Name .\MiArchivo.txt ­Type file
Directory: C:\Temp
Mode
­­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
13/01/2015
22:55
Length Name
­­­­­­ ­­­­
0 MiArchivo.txt
307
El resultado del comando nos permite comprobar que el tamaño de nuestro archivo es nulo.
Agreguemos ahora un flujo ADS con el comando
Set­Content y el parámetro ­Stream. Sepa que un
flujo de datos ADS está compuesto de un nombre y un contenido.
PS > Set­Content .\MiFichero.txt ­Stream ’Flujo ADS’ ­Value (Get­Service)
Verifiquemos ahora el tamaño del archivo con un simple
Get­ChildItem. No ha cambiado.
PS > Get­ChildItem .\MiArchivo.txt
Directory: C:\Temp
Mode
­­­­
­a­­­
LastWriteTime
­­­­­­­­­­­­­
13/01/2015
22:55
Length Name
­­­­­­ ­­­­
0 MiArchivo.txt
Y sin embargo existe un contenido asociado a este archivo. Tecleando Get­Item seguido del parámetro
­Stream *, vemos que existen dos flujos de datos: un flujo estándar llamado $DATA que contiene el
contenido del archivo y un segundo flujo llamado alternativo que acabamos de crear. Observaremos que
su tamaño no es cero ya que hemos insertado el resultado de Get­Service.
PS > Get­Item .\MiArchivo.txt ­Stream *
FileName : C:\Temp\MiArchivo.txt
Stream
­­­­­­
:$DATA
Flujo ADS
Length
­­­­­­
0
1658
Para mostrar el contenido del flujo alternativo, basta con utilizar el comando
parámetro ­Stream.
Get­Content seguido del
PS > Get­Content .\MiArchivo.txt ­Stream ’Flujo ADS’
AeLookupSvc
ALG
AllUserInstallAgent
Appinfo
AppMgmt
AudioEndpointBuilder
...
Por lo tanto, en resumen, sepa que se puede esconder muchísimo contenido con los Alternate Data
Stream sin que el tamaño del archivo cambie.
Los ADS dependen del sistema de archivos NTFS, así que se perderán si transfiere un archivo con
contenido ADS mediante un llave USB con formato FAT32 o si envía el archivo por correo electrónico.
La ejecución de archivos ejecutables disimulados en un ADS no funciona desde Windows 7 y
Windows Server 2008 R2.
308
Para conocer todos los comandos que se encargan de los Alternate Data Streams, teclee el
siguiente comando: Get­Command ­ParameterName Stream
c. Observar y comprender los ADS de sus archivos .ps1
Si ha estado atento, ya sabe que el modo de ejecución
RemoteSigned reconoce el origen de los
scripts gracias a los ADS. Y vamos a ver exactamente qué ADS se crean y qué contienen.
Evidentemente, inspeccionar los ADS asociados a un script supone que este último tenga su origen en
una herramienta de comunicación Microsoft como Internet Explorer, Outlook, etc.
Tomemos por ejemplo el script
list­group.ps1 descargado desde el sitio web www.powershell­
scripting.com. A continuación enumeramos los ADS usando el siguiente comando:
PS > Get­Item List­Group.ps1 ­Stream *
FileName : C:\Temp\List­Group.ps1
Stream
Length
­­­­­­
­­­­­­
:$DATA
936
Zone.Identifier
26
Observamos claramente que se ha detectado un ADS con el nombre
Zone.Identifier.
Si nos tomamos la molestia de mirar lo que contiene, encontramos lo siguiente:
PS > Get­Content List­Group.ps1 ­Stream Zone.Identifier
[ZoneTransfer]
ZoneId=3
Realmente, cuando descarga un script desde una herramienta de comunicación Microsoft, este último
crea un Data Stream llamado «Zone.Identifier». Se traduce el origen en un identificador de zona
(ZoneID) en función de la seguridad elegida a nivel de Internet Explorer. En efecto, la noción de Zona
Internet es la utilizada por el navegador de Microsoft. Las modificaciones aportadas en las opciones de
seguridad con sobre todo la inserción de sitios/servidores de confianza tienen un impacto directo sobre
el ZoneID y por consiguiente sobre lo que PowerShell considera como local o remoto.
Zona Internet
Valor
NoZone
­1
Sí
MyComputer
0
Sí
Intranet
1
Sí
Trusted
2
Sí
Internet
3
No
Untrusted
4
No
Considerado como local
d. Modificar el ZoneId o cómo transformar un script remoto en un script local
El identificador de zona es una información asociada al script que permite al sistema operativo saber su
origen.
Solamente ahora que está familiarizado con los ADS y sobre todo con el creado por las herramientas de
comunicación Microsoft, veremos cómo hacer creer a PowerShell que un script es un script local.
309
Para ello, existe dos técnicas:
La primera consiste, como hemos enunciado anteriormente en este capítulo, en suprimir el ADS
utilizando el comando Unblock­File o haciéndolo por la interfaz gráfica (clic con el botón
derecho sobre el script ­Properties­Unblock).
La segunda es un poco más larga, pero igualmente eficaz, y consiste en cambiar el ZoneID. Para
modificar el identificador de zona desde la shell, basta con fijar nosotros mismos el contenido del
ADS con el comando Set­Content seguido del parámetro ­Stream.
PS > Set­Content .\List­Group.ps1 ­Stream "Zone.Identifier" `
­Value "[ZoneTransfer]", "ZoneId=2"
5. Cadenas securizadas
Saber ofuscar los datos sensibles contenidos en los scripts debería formar parte de las tareas habituales.
Decimos «debería» ya que aún hoy en día, muchos son los scripts donde los datos confidenciales están en
claro. Existe numerosas técnicas para disimular cadenas de caracteres en un script, pero la más eficaz es
la securización de cadenas que incluye el framework .NET.
Con PowerShell, tenemos que disociar claramente una cadena cifrada de una cadena securizada.
Hablamos de cadena cifrada cuando su contenido se vuelve incomprensible para toda persona que no
disponga de una clave de descifrado (consulte la sección dedicada al cifrado de cadenas), y hablamos de
cadenas securizadas cuando tienen:
Un contenido cifrado: el contenido de las cadenas securizadas está cifrado carácter a carácter y
después almacenadas en memoria. El cifrado del contenido no necesita de ninguna clave, el
framework .NET cifra él mismo los datos.
Un acceso en escritura controlado: una vez creada, una cadena securizada puede ver cómo le
añaden texto únicamente carácter a carácter. Sin olvidar que un método propio al tipo
SecureString permite configurar un acceso de solo lectura, lo que impide cualquier modificación
de la cadena.
No duplicación del contenido en memoria: PowerShell pertenece a los lenguajes orientados a
objetos, normalmente llamados lenguajes de alto nivel. Este tipo de lenguajes posee la
particularidad de no aburrir al desarrollador con ciertos detalles de programación como la reserva o
liberación de la memoria. Estas operaciones se delegan en el Garbage Collector (GC) o recolector
de basura en castellano. Aunque muy práctico, ocurre que el Garbage Collector puede realizar
varias copias en memoria para optimizar la reserva dinámica de variables. Es lo que llamamos el
«Mark and Compact». Así para paliar este problema de seguridad, una cadena SecureString se
almacena en un espacio de memoria no gestionado por el GC, y nunca se duplica en memoria. Una
vez eliminada la variable, el espacio atribuido se borra inmediatamente de la memoria y no deja
ninguna traza.
a. Securizar una cadena
Para securizar una cadena con PowerShell, existen dos técnicas.
La primera consiste en utilizar el comando
ConvertTo­SecureString cuyos parámetros son:
310
Descripción
Parámetro
­String
Este parámetro permite determinar la cadena a descifrar.
­SecureKey
Este parámetro permite usar una cadena securizada como valor de
clave. En realidad, el valor de la cadena securizada se convierte en
un array de bytes y puede así utilizarse como clave.
­Key
Este parámetro determina la clave a utilizar. Como recordatorio, la
clave debe ser de 128, 192 o 256 bits. Es decir que si utiliza un
array de enteros como clave, sabiendo que un entero está
codificado con 8 bits, puede usar arrays de 16, 24 o 32 enteros.
­AsPlainText
No se utiliza este parámetro en el ámbito del descifrado. Sirve
únicamente cuando se utiliza un comando para transcribir una
cadena en una cadena securizada.
­Force
Se
usa
este
parámetro
como
complemento
al parámetro
­
AsPlainText para especificar que deseamos realmente securizar
la cadena mediante ­AsPlainText.
Así, asociando al comando ConvertTo­SecureString los parámetros
securizamos una cadena de texto bruto, por ejemplo aquí "Hola":
­AsPlainText y ­Force,
PS > $cadena = ConvertTo­SecureString ’Hola’ ­AsPlainText ­Force
PS > $cadena
System.Security.SecureString
Observará que cuando tratamos de leer este valor, el resultado no se muestra sino que solo se ve el
tipo.
El segundo método consiste en introducir un texto en la consola con el comando
convertir este texto en una cadena securizada con el parámetro ­AsSecureString.
Read­Host y
PS > $cadena = Read­Host ­AsSecureString
*****
PS > $cadena
System.Security.SecureString
En ambos casos, el objeto devuelto es de tipo
SecureString, y no se puede leer directamente.
Para saber lo que se puede hacer con la cadena securizada que hemos creado, eche un rápido vistazo a
la siguiente tabla que enumera los diferentes métodos asociado al objeto SecureString.
Descripción
Método
AppendChar
Permite añadir un carácter al final de la cadena securizada.
Clear
Elimina la cadena.
Copy
Crea una copia del valor almacenado.
Dispose
Libera todos los recursos empleados por el objeto
GetHashCode
Recupera bajo la forma de un entero de 32 bits el código hash.
GetType
Identifica el tipo: SystemString.
Secure­String.
311
Método
Descripción
get_Length
Devuelve bajo la forma de un entero de 32 bits el tamaño de la
cadena.
InsertAt
Permite insertar un carácter en un índice determinado de la cadena
securizada.
IsReadOnly
Devuelve el valor booleano
True si la cadena es de solo lectura y
False si no lo es.
MakeReadOnly
Hace que el contenido de la cadena sea inalterable. Esta operación es
irreversible.
RemoveAt
Permite eliminar un carácter en un índice determinado de la cadena
securizada.
SetAt
Permite reemplazar un carácter por otro en un índice determinado.
Listando los métodos de un objeto SecureString con el comando que utiliza desde el principio del
libro (a saber Get­Member), un observador atento habrá notado la ausencia de dos métodos
omnipresentes con los objetos encontrados hasta ahora: Equals y
ToString.
Esto no representa un olvido por nuestra parte sino más bien una voluntad por parte de Microsoft de no
permitir estos métodos con un objeto del tipo SecureString, lo que constituye evidentemente un
problema de seguridad. El método Equals permite evaluar si dos objetos son idénticos: si la igualdad
se respeta, entonces devuelve el booleano
true, en caso contrario se devuelve el valor false.
Sin embargo este método aplicado a un objeto de tipo
SecureString siempre devolverá el valor
false aunque las dos cadenas securizadas sean idénticas.
Ejemplo
PS > $cadena1 = Read­Host ­AsSecureString
*****
PS > $cadena2 = $cadena1.Copy()
PS > $cadena1.Equals($cadena2)
False
Esta seguridad permite así evitar que se descubran las cadenas por métodos de automatización de tests
sucesivos, llamados de «fuerza bruta».
El método ToString en sí mismo devuelve únicamente el tipo de objeto
String ya que en caso contrario devolvería la cadena en claro.
System.Security.Secure
Veamos con más detalle qué pasa cuando utilizamos algunos métodos definidos en la anterior tabla.
Primero, creamos una cadena securizada con el comando
Read­Host y asignamos una palabra de
cuatro letras:
PS > $cadena = Read­Host ­AsSecureString
****
Verificamos después su tamaño con el siguiente comando:
PS > $cadena.Length
4
312
La consola muestra lógicamente el número 4.
Intentemos ahora añadir un carácter:
PS > $cadena.AppendChar(’P’)
PS > $cadena.Length
5
El tamaño de la cadena se ha incrementado, efectivamente, en un carácter.
Si ahora queremos insertar varios caracteres, no se nos permite hacerlo de forma directa:
PS > $cadena.AppendChar(’Hola’)
Cannot convert argument "c", with value: "Hola", for "AppendChar"
to type "System.Char": "Cannot convert value "Hola" to type
"System.Char". Error: "String must be exactly one character long.""
Pero como nada es imposible, aquí tiene cómo evitar este problema:
PS > $insert = ’Hola’
PS > For ($i=0;$i ­lt $insert.length;$i++){
$cadena.AppendChar($insert[$i])}
Verifiquemos si la cadena se ha incrementado:
PS > $cadena.Length
12
Hagamos ahora que esta cadena sea accesible en modo de solo lectura:
PS > $cadena.MakeReadOnly()
Intentemos añadirle un carácter:
PS > $cadena.AppendChar(’P’)
Exception calling "AppendChar" with "1" argument(s): "Instance is
read­only."
Con toda lógica, PowerShell genera un mensaje de error ya que el objeto
accesible en lectura y no en escritura.
SecureString solo es
b. Leer una cadena securizada
No se sorprenderá si le decimos que no existe un método propiamente dicho para convertir una cadena
securizada en una cadena clásica. Esto es fácilmente comprensible para respetar ciertos puntos de
seguridad enunciados más arriba. En efecto, si el contenido de una cadena securizada se copia en una
cadena estándar, se copiará entonces en memoria por el Garbage Collector, y la información no será ya
por lo tanto confidencial.
Pero ya está acostumbrado. Disponemos una vez más de una solución ya que existe con el framework
.NET una clase llamada Runtime.InteropServices.Marshal que proporciona dos métodos:
313
SecureStringToBSTR que nos permite reservar memoria no gestionada por el Garbage
Collector para copiar el contenido de la cadena securizada.
PtrToStringUni que copia el contenido de la cadena almacenada en una parte de la memoria
no gestionada hacia un objeto de tipo String en Unicode, gestionado por el Garbage Collector.
Ejemplo
Leer una cadena securizada.
Primero creamos una cadena securizada con el comando
cuatro letras:
Read­Host y asignamos una palabra de
PS > $cadenaSec = Read­Host ­assecurestring
****
Leemos ahora este
SecureString usando los métodos de la clase Marshal:
PS > $ptr = [System.Runtime.InteropServices.Marshal]
::SecureStringToBSTR($cadenaSec)
PS > $cadenaEnClaro = [System.Runtime.InteropServices.Marshal]::
PtrToStringUni($ptr)
Tenga en cuenta que la variable
$cadenaEnClaro es de tipo String y contiene el valor de la cadena
securizada.
Sin embargo, una vez haya recuperado su cadena en claro, es mejor eliminarla enseguida de la zona de
memoria para limitar los riesgos. Esto puede parecer radical, pero numerosas herramientas de hacking
se basan en la lectura de zonas de memoria. Para ello, procedamos primero a liberar el puntero de la
cadena no gestionada por el Garbage Collector. Después reemplazamos el contenido de la variable
$cadenaEnClaro por un valor cualquiera y al final forzamos el Garbage Collector para que se ejecute.
Traducido en PowerShell, esto da lugar al siguiente código:
# Libera el puntero de la cadena
PS > [System.Runtime.InteropServices.Marshal]::
ZeroFreeCoTaskMemUnicode($ptr)
# Modificación del contenido de la variable $cadena por 40 estrellas
PS > $cadenaEnClaro = ’*’ * 40
# Llamada al Garbage Collector
PS > [System.GC]::Collect()
6. Cifrado
El cifrado es una técnica vieja de más de treinta siglos que consiste en transformar una información clara
(inteligible) en una información que solo puede ser comprendida por una persona autorizada. Esta
transformación se realizaba generalmente, mediante permutaciones de letras del mensaje (transposición)
o reemplazando una o varias letras por otras (substitución).
El esquema siguiente pone en escena los tradicionales personajes Alice y Bob que buscan comunicarse por
medio de un canal de transmisión público como la red Internet.
314
Envío de un mensaje confidencial
En el esquema de anterior, las operaciones de cifrado y descifrado están representados por claves y los
envíos/recepciones de mensaje por flechas.
En esta escena, Alice trasforma su mensaje editado en claro en un mensaje cifrado. Después lo trasmite a
Bob que realizará la trasformación inversa, a saber descifrar el mensaje. Así Alice y Bob hacen que su
mensaje sea incomprensible para Oscar, quien lo hubiese podido interceptar durante el intercambio. En
efecto, Oscar no dispone de la clave: no sabe cómo Alice ha cifrado el mensaje ni cómo Bob va a
descifrarlo.
Los primeros sistemas de cifrado estaban esencialmente basados en el alfabeto, como el famoso código
llamado «César», donde cada letra a cifrar se reemplazaba por otra letra situada en la enésima posición
siguiente en el alfabeto. Así, si el desfase era de 1, la A valía B y la B valía C. Por ejemplo, tomemos un
desfase de 3.
alfabeto original: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
alfabeto codificado: D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
Lo que daría:
Mensaje original = PowerShell es facil
Mensaje cifrado = SrzhuVkhoo hv idflo
La ventaja de utilizar una cadena cifrada es que puede guardarla en un archivo para eventualmente
reutilizarla después, lo que no es posible con una cadena securizada.
Noción de clave de cifrado
Un cifrado está generalmente compuesto por un algoritmo fijo que tiene asociada una clave que es
variable. En el caso del código César, el algoritmo es el desfase de letras en el alfabeto y la clave es el
número de letras que se deben desfasar. Por ejemplo, si le dan el siguiente mensaje: «Eudyr yrxv hwhv
wuhv iruw» y le dicen que ha sido cifrado con el código César, no podrá descifrarlo. Se debe transmitir por
lo tanto el valor de la clave que corresponde al número de letras que hay que desfasar. Ahora si le
decimos que la clave utilizada para cifrar es el 3 (por lo tanto ­3 para descifrar). Puede decodificar el
mensaje.
315
Ejemplo
Cifrar un mensaje con el código César.
Cómo puede observar, el script necesita los parámetros
texto a cifrar y la clave utilizada.
­texto y ­clave que contienen respectivamente el
# Script Cesar.ps1
# Cifrado de un mensaje gracias al código César
Param (
[parameter(Mandatory=$true)]
[string]$Texto,
[parameter(Mandatory=$false)]
[int]$Clave = 10
)
$mensaje_origen = $texto
$alfabeto_MAY=’ABCDEFGHIJKLMNOPQRSTUVWXYZ’
$alfabeto_MIN=’abcdefghijklmnopqrstuvwxyz’
for($i=0;$i ­lt $mensaje_origen.length;$i++)
{
$encontrado = 0
for($j=0;$j ­lt $alfabeto_MAY.length;$j++)
{
$tmp = $clave
While($($j+$tmp) ­ge $alfabeto_MAY.length)
{
$tmp ­= 26
}
If($mensaje_origen[$i] ­ceq $alfabeto_MAY[$j])
{
$mensaje_modif += $alfabeto_MAY[$j+$tmp]
$encontrado = 1
}
Elseif($mensaje_origen[$i] ­ceq $alfabeto_MIN[$j])
{
$mensaje_modif += $alfabeto_MIN[$j+$tmp]
$encontrado = 1
}
}
if(!$encontrado) {$mensaje_modif += $mensaje_origen[$i]}
}
$Resultado = New­Object PSObject
$Resultado |
Add­Member Noteproperty ­Name ’Mensaje Origen’ ­value $mensaje_origen
$Resultado |
Add­Member Noteproperty ­Name ’Mensaje Modificado’ ­value $mensaje_modif
$Resultado
Resultado en la consola PowerShell:
PS > ./cesar.ps1 ­texto "PowerShell es facil" ­clave 14
Mensaje Origen
­­­­­­­­­­­­­­
PowerShell es facil
Mensaje Modificado
­­­­­­­­­­­­­­­­­­
DcksfGvszz sg toqwz
316
El cifrado con clave simétrica
También llamado cifrado de una clave, es el sistema utilizado por PowerShell para cifrar un mensaje. Su
principal característica es que el emisor y el receptor utilizan ambos la misma clave para cifrar y descifrar el
mensaje. En el ejemplo del código César, si el emisor cifra su mensaje con la clave «13», el receptor debe
obligatoriamente usar el mismo valor para realizar la rotación en sentido contrario y poder así descifrar el
mensaje. Por lo tanto se trata de un sistema simétrico.
Evidentemente este principio necesita que la clave sea secreta durante toda la transacción.
El cifrado de cadenas en PowerShell se basa en el algoritmo de Rijndael en su versión AES (Advanced
Encryption Standard ­ estándar de cifrado avanzado).
Este sistema, creado a finales de los años 90 por dos investigadores belgas, utiliza el principio de cifrado
con clave simétrica de tamaño 128, 192 o 256 bits. Por lo tanto, al cifrar sus mensajes, debe tener en
cuenta anotar correctamente su clave.
El sistema de clave simétrica encuentra sus límites cuando varias personas buscan transmitir
mensajes cifrados entre ellos. Si cinco personas constituyen una red de intercambio de mensajes
secretos, cada una de ellas debe conocer la clave secreta de cuatro personas. Esto constituye ya un
buen número de claves.
a. Cifrar una cadena
El cifrado de cadenas con PowerShell se realiza mediante el comando
ConvertFrom­SecureString.
El nombre explícito del comando («convertir desde una cadena securizada») deja entrever que para cifrar
una cadena debemos antes asegurarnos de que la cadena es securizada, es decir de tipo
SecureString. Es tarea suya por lo tanto transformar una cadena de caracteres en una cadena
securizada y después usar ConvertFrom­SecureString.
Este comando dispone de tres parámetros (sin contar los parámetros comunes):
Parámetro
Descripción
­SecureString
Este parámetro permite determinar la cadena a cifrar. Acuérdese de
que esta cadena debe ser de tipo SecureString.
­Key
Este parámetro determina la clave a utilizar. Para su información, la
clave debe tener un tamaño de 128, 192 o 256 bits. Es decir que si
utiliza un array de enteros como clave y un entero está codificado
con 8 bits, puede utilizar arrays de 16, 24 o 32 enteros.
­SecureKey
Este parámetro permite usar una cadena securizada como valor de la
clave. En realidad, el valor de la cadena securizada se convierte en
un array de bytes y así puede usarse como clave.
Si no se especifica una clave, PowerShell utiliza la API Win32 DPAPI (Data Protection API) para cifrar
y descifrar los datos.
Para entender mejor cómo cifrar un texto, aquí tiene ejemplos de aplicación.
Ejemplo: cifrar una cadena sin clave
Crearemos, en primer lugar, una cadena securizada que contendrá nuestra información confidencial:
317
PS > $secure_string_pwd = ConvertTo­SecureString `
"Código de entrada edificio : 101985" ­AsPlainText ­Force
después la cadena securizada en una cadena cifrada con ConvertFrom­
SecureString sin especificar ninguna clave y redirigimos el resultado a un archivo de texto:
Convertimos
PS > ConvertFrom­SecureString $secure_string_pwd >
Recuperamos el contenido del archivo con
información cifrada.
.\cadena_c1.txt
Get­Content y vemos que este contiene efectivamente
PS > Get­Content .\cadena_c1.txt
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000013a9c2458a11d74c9ca6b1733bb
1f56a000000000200000000001066000000010000200000001d30422f3578b382080d683464
9580e1ae4fd4eb505c0e435c2f726db68e714f000000000e8000000002000020000000345bc
0fa9b3b2168780b47a3cc82a8dadd7969d259a607266404958bd121198f50000000eba4bf80
ed9f9b685286c5c8aa5717bd0225788c09725e1fa2c43df80d9619f52a07d1e6c506c04a9c7
9a6ec6e736ac596d43ba8d4f6f3c0bad9b40b89c2d5a7132c9460297750d01003e5e0d2116d
4740000000ef97e9fe911a49f897aca0d839b478d51e393c48f81f5a6196f11622e9a54c45f
ac4076b84419110759ebe80026274240e2cd5fb66f0efda9a398de2f6fda23e
Ejemplo: cifrar una cadena con una clave de 256 bits
Veamos ahora con más detalle cómo cifrar una cadena usando una clave de 256 bits, es decir de 32
bytes (32 x 8 = 256). Empecemos una vez más creando una cadena securizada:
PS > $secure_string_pwd = ConvertTo­SecureString `
"Código de entrada edificio : 101985" ­AsPlainText ­Force
Después creamos nuestra clave de 32 bytes especificando valores inferiores a 256 y la asignamos al
comando ConvertFrom­Securestring con el parámetro ­Key, para al final devolver todo en un
archivo de texto:
PS > $clave = (6,10,19,85,4,7,19,87,13,3,20,13,3,6,34,43,56,34,
23,14,87,56,34,23,12,65,89,8,5,9,15,17)
PS > ConvertFrom­SecureString ­secureString $secure_string_pwd `
­key $clave > .\cadena_c2.txt
El archivo muestra efectivamente información cifrada, pero esta vez se ha cifrado con una clave de 256
bits que nosotros mismos hemos especificado.
PS > Get­Content .\cadena_c2.txt
76492d1116743f0423413b16050a5345MgB8ADIAcwBGAFoAdQAyAFIAdgBhAHEASQBaADIARwB
EAGgAVgB2AGsAcQBPAGcAPQA9AHwANwA5ADkANgAyAGUANwBmADYAMgBjADcAOAA3ADQAZQA1AD
cAZQBmAGEANgAwAGQAMQBlAGYAYwAwADMAYgA0ADYAYwBjADIAZAAzADcAMgBiAGEAOABiADAAY
gBkAGYAYwBjAGUAOAA3ADkAZQBhADAANwAxAGEANQA5AGQAMgAwADMAMAAxADUANAA1ADEAYwA2
ADgAZgBmADgAMQA5ADkAZgBlADYAMwBjAGMAYwBjAGIAOQA1AGEAOABhAGUAOAAwADQAOQBmADM
AMABlAGUAZgAwAGEANABkAGIANwAxADYAZgBiADcAMwA4ADcANgBiAGMAYQA1AGMAMwA2ADMAYw
AxADkAMgBlADcANwA2AGQANAA1ADcAOAA2ADMAMgAyADEAOABmAGQAOQA2AGEAYwBmAGYAZAA5A
GEAYgA=
318
Ejemplo: cifrar un texto con una cadena securizada
Para terminar, intentemos ahora cifrar texto con una clave de 128 bits usando una cadena securizada
como clave, lo que resulta mucho más práctico que memorizar aunque sean solo 16 números.
Sabemos que una variable de tipo «char» puede representar cualquiera de los 65 536 caracteres
Unicode en dos bytes (o sea 16 bits). Por lo tanto debemos usar 8 caracteres (128/16=8) para alcanzar
128 bits. Para poder utilizar una cadena como clave, esta debe necesariamente estar securizada.
Crearemos en primer lugar una cadena securizada que nos servirá de clave (de 8 caracteres
exactamente):
PS > $clave = ConvertTo­SecureString ’millave’ ­AsPlainText ­Force
Después, una vez más, securizamos la cadena que contendrá nuestra información confidencial:
PS > $secure_string_pwd = ConvertTo­SecureString `
"Código de entrada edificio : 101985" ­AsPlainText ­Force
Para acabar, ciframos la cadena con el comando ConvertFrom­SecureString, pero esta vez
especificando el parámetro ­SecureKey asociado a la cadena securizada que nos sirve de clave:
PS > ConvertFrom­SecureString ­SecureString `
$secure_string_pwd ­SecureKey $clave > .\cadena_c3.txt
Y si observamos el contenido del archivo, veremos evidentemente una cadena cifrada, pero que lo habrá
sido con una clave de cifrado de tipo cadena securizada.
PS > Get­Content .\cadena_c3.txt
76492d1116743f0423413b16050a5345MgB8AFIARgBVAHoAOQBrAHkAdAA0AFcAcQBmAEUAUAB
IAGsARQB2ADUATwAvAEEAPQA9AHwAZQBlAGUANgA4ADEAOQA1AGQAMwA3AGYAOQBiADYAMgBhAD
MAYQA1AGMAZgA0ADcANQAxAGMANABjAGQAYwA0ADMAMwA2AGMAYgAzAGIAZQA5ADQAYwA2AGQAZ
gAwAGYAZgAyADcAYgBmAGMAOABjAGIANQBhADgAZABhAGIAYgBkADEAMQA4ADYANgBkADUAYQAw
ADYANAA3ADkANABiADEAMAAxADcAMQAwADEAMQBkAGUANgAwAGEAOABmAGIAOAA1ADcAMQBiADQ
AOAA4AGMAMgBiADYAYwA5ADkAOQA5ADIAMgBmAGIANgA0ADUAZgAyAGIANgAwADgANwBhADgAZg
BhADgAZAA2ADMAYQBkAGEAOABkADkAOQA5AGEAMABjAGUAOQA3AGQAZAAzAGMAMwBjADMAZAAzA
GQANgA=
b. Descifrar una cadena
Retomamos el texto (cifrado con la clave de 256 bits) de la sección anterior. Para realizar la operación
inversa, es decir para descifrarlo, utilizamos ConvertTo­SecureString con la clave
correspondiente:
PS > $cadena_cifrada = Get­Content .\cadena_c2.txt
PS > $cadena_original = ConvertTo­SecureString ­Key $clave ­String
$cadena_cifrada
Una vez ejecutado el comando, la variable
texto en forma de cadena securizada.
$cadena_original no contiene el texto en claro, sino el
Aquí tiene lo que PowerShell nos muestra al leer la variable
$cadena_original en la consola:
319
PS > $cadena_original
System.Security.SecureString
En la mayoría de los casos basta con recuperar una cadena de tipo
SecureString ya que todos
los comandos relativos a la seguridad necesitan este tipo de objeto.
Si aun así debe recuperar el contenido de una cadena securizada, aquí tiene un truco (extraído
directamente del framework .NET) para conseguirlo:
PS > $ptr = [System.Runtime.InteropServices.Marshal]::
SecureStringToBSTR($cadena_original)
PS > [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
Y claro está, el resultado corresponde al texto inicialmente introducido.
Código de entrada edificio: 101985
7. Gestión de credenciales
En el arduo mundo de la seguridad informática, la primera manera de minimizar los riesgos es la de no
trabajar a diario con una cuenta de administrador. Por este motivo se utiliza el comando Get­
Credential, que permite obtener las «credenciales», es decir la pareja usuario/contraseña de un
usuario. Gracias a este mecanismo, un usuario puede autenticarse con otra cuenta y por lo tanto realizar
tareas con otra identidad.
Utilizado simplemente en la consola, Get­Credential muestra una interfaz gráfica que permite
introducir un nombre de usuario así como la contraseña asociada. Devuelve bajo la forma de un objeto
PSCredential el nombre de usuario sin cifrar así como la contraseña como una cadena securizada.
Interfaz gráfica del comando
Get­Credential
Ejemplo
Valor devuelto para el nombre de usuario «Administrator».
320
UserName
­­­­­­­­
\Administrator
Password
­­­­­­­­
System.Security.SecureString
Este comando dispone también del parámetro opcional
­Credential que permite indicar el nombre de
usuario directamente por línea de comandos. De esta manera, tan solo queda introducir la contraseña.
Veamos otro ejemplo con el borrado de un archivo mediante el comando
Remove­Item. Si ejecuta el
comando con una cuenta limitada con la que no dispone de permisos sobre este archivo, PowerShell le
devolverá el siguiente mensaje:
Remove­Item : Cannot remove item C:\Temp\Error.log: Access to the path
’C:\Temp\Error.log’ is denied.
Ahora, apliquemos a este mismo comando los permisos de un usuario autorizado a borrar este tipo de
archivos. Para ello, basta con añadir al parámetro ­Credential el valor devuelto por el comando Get­
Credential.
Ejemplo
PS > Remove­Item FicheroABorrar ­Credential (get­credential)
Así, al ejecutarlo, la ventana asociada al comando Get­Credential le pide que indique la información
de autenticación de un usuario con los permisos adecuados y transmite esta información al primer
comando que entonces puede ejecutarse con el usuario indicado. A continuación presentamos un conjunto
de comandos para los cuales se pueden pasar credenciales, o dicho de otra manera cuentan con ­
Credential entre sus parámetros.
PS > Get­Command ­ParameterName Credential | Format­Table ­auto
CommandType
­­­­­­­­­­­
Function
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
mkdir
Add­Computer
Add­Content
Clear­Content
Clear­Item
Clear­ItemProperty
Connect­PSSession
Copy­Item
Copy­ItemProperty
Enter­PSSession
Get­Content
Get­Credential
Get­HotFix
Get­Item
Get­ItemProperty
Get­PSSession
Get­WmiObject
Invoke­Command
Invoke­Item
Invoke­RestMethod
Invoke­WebRequest
Invoke­WmiMethod
Join­Path
Move­Item
ModuleName
­­­­­­­­­­
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
321
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Cmdlet
Move­ItemProperty
New­Item
New­ItemProperty
New­PSDrive
New­PSSession
New­Service
New­WebServiceProxy
Receive­PSSession
Register­WmiEvent
Remove­Computer
Remove­Item
Remove­ItemProperty
Remove­WmiObject
Rename­Item
Rename­ItemProperty
Reset­ComputerMachinePassword
Resolve­Path
Restart­Computer
Save­Help
Send­MailMessage
Set­Content
Set­Item
Set­ItemProperty
Set­WmiInstance
Split­Path
Start­Job
Start­Process
Stop­Computer
Test­ComputerSecureChannel
Test­Connection
Test­Path
Update­Help
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Microsoft.PowerShell....
Ejemplo
Lista de comandos que disponen del parámetro
­Credential.
Sepa que es posible mediante el comando Invoke­Command ejecutar un bloque de script que
contenga cualquier comando PowerShell con credenciales alternativas; incluyendo comandos que no
tienen entre sus parámetros ­Credential. La restricción, en este caso, es la de activar «la
comunicación remota de PowerShell» en su equipo.
Tomemos una situación habitual: la creación de un archivo. Pero para que esta operación se lleve a cabo,
debe disponer de los permisos adecuados. Veamos qué pasa cuando intenta crear un archivo sin tener los
permisos suficientes para ello.
PS > New­Item ­Name .\prueba.txt ­Type file
New­Item : Access to the path ’C:\Temp\prueba.txt’ is denied.
Probemos ahora a usar el mismo comando pero añadiendo el parámetro
­Credential. Para ello, teclee
este comando:
PS > New­Item ­Name .\prueba.txt ­Type file ­Credential `
(Get­Credential ­Credential Administrator)
322
Se abre el cuadro de diálogo que pide la información de autenticación, e introduciendo la pareja
usuario/contraseña con los permisos de acceso adecuados ya puede realizar la operación.
8. Solicitar la introducción de una contraseña de forma segura
En Francia existe la expresión: «¡Para vivir feliz, vivamos escondidos!». Esta máxima se aplica también a
las contraseñas. La contraseña es un elemento crítico de la cadena de «seguridad informática» y es un
error introducirla sin cifrar en un archivo. Es parecido a escribir su código bancario en su tarjeta de crédito.
Antes de pensar en enmascarar la introducción de la contraseña, mire en primer lugar si el uso de
soluciones como las credenciales o también la ejecución de scripts con una cuenta normal no bastaría para
ello.
Y si existe una verdadera necesidad de pedir al usuario que teclee una contraseña durante la ejecución
del script, le proponemos varios métodos para hacer esta operación lo más confidencial posible.
a. Uso del comando Read­Host
Como hemos visto en la sección que trata de las cadenas securizadas, el comando Read­Host
asociado al parámetro ­AsSecureString permite hacer confidencial la introducción de una
contraseña. Tecleando el siguiente comando, cada carácter introducido en la shell se traducirá con la
visualización del carácter asterisco (*), y cuando termine la introducción de la contraseña, la variable
$psw recibe un objeto de tipo SecureString.
PS > $psw = Read­Host ’Contraseña PVP’ ­AsSecureString
****
El uso del comando
Read­Host es simple y eficaz.
b. Uso del comando Get­Credential
Aunque el objetivo de este comando sea recuperar información vinculada a otra cuenta distinta de la del
usuario actual, también puede servir para recuperar una contraseña.
Get­Credential, se abre una interfaz gráfica que le invita a introducir una
contraseña. En el ejemplo siguiente, almacenamos la contraseña en la variable $password. Observe
que el campo «nombre de usuario» ya está informado gracias al parámetro ­Credential.
Tecleando el comando
Ejemplo
PS > $password = (Get­Credential ­Credential ’user’).password
323
Interfaz gráfica
Get­Credential completada previamente
Si solo le interesa la contraseña, el nombre de usuario no tiene ningún interés y solo utilizará la
propiedad password del objeto PSCredential. Sin embargo, si desea también recuperar el
nombre de usuario, puede usar la propiedad UserName. Por ejemplo: $User = (Get­
Credential).Username
Firma de scripts
1. Las firmas digitales
Una firma digital es un «proceso» que permite identificar al firmante y que garantiza la integridad del
documento. Las firmas digitales, llamadas también firmas electrónicas, se usan en PowerShell para
gestionar la ejecución de scripts según la directiva de ejecución elegida.
Desde un punto de vista conceptual, una firma digital corresponde generalmente al cifrado, con la ayuda
de una clave privada, de una forma abreviada del mensaje llamada «huella». La huella de un mensaje es
el resultado obtenido después de aplicar una función de hash al contenido del documento.
Al otro lado de la cadena, el destinatario se asegura de que los datos no han sido modificados durante la
transmisión efectuando una comparación entre la huella que acompaña al documento descifrado gracias a
la clave pública y la huella que él mismo ha calculado. Si ambas huellas son idénticas entonces significa
que los datos son íntegros.
2. Los certificados
Un certificado es indisociable de una clave pública. Es el elemento que va a permitir asociar la clave a su
propietario. Es decir que cuando desciframos una firma con la clave pública, no es necesario verificar que
esta clave es del firmante. Al igual que con un documento oficial, un certificado contiene información acerca
de la identidad del propietario. Añadir una firma a un script significa que debe estar en posesión de un
certificado electrónico de firma, que permite identificar de manera segura a la persona que firma el script.
Este certificado se puede obtener de diferentes maneras: o bien decide comprar un certificado de firma a
una autoridad de certificación reconocida, o bien crea su propio certificado (certificado «auto firmado»).
324
a. Comprar un certificado
Para comprar un certificado, debe pasar por un organismo de certificación (Certificate Authority o CA) que
entrega certificados de clase 1, 2 o 3, definidos según un uso y un nivel de protección de los datos. Estos
certificados están también asociados a un tiempo de vida y a cierta garantía en función del precio que
puede ir desde algunas decenas a varias centenas de euros. Algunos organismos se reconocen de
manera nativa en Windows, lo que permite desplegar scripts firmados sin ninguna manipulación.
b. Crear un certificado auto firmado
Crear un certificado auto firmado es, como podemos imaginar, la solución más barata. En realidad es
gratuita. Esta solución es preferible para los que dispongan de un presupuesto apretado y/o que tengan
que crear una plataforma de test o de formación. Se desaconseja encarecidamente utilizar un certificado
autofirmado en producción, por motivos de seguridad evidentes.
Una de las cosas importantes que debe saber cuando crea usted mismo un certificado es que el equipo
en el que se crea este certificado se convierte en una «autoridad de certificación», y esta autoridad
deberá ser aprobada por todos los equipos que ejecuten los scripts que ha firmado.
Para crear un certificado auto firmado, debe primero descargar e instalar el SDK (Software Development
Kit) del framework .NET, que está disponible en el sitio web de Microsoft.
El SDK incluye numerosas herramientas interesantes, pero la que nos interesa por ahora es la
herramienta makecert.exe que, como su propio nombre sugiere, sirve para crear certificados.
Antes de usar el ejecutable makecert.exe, le proponemos que se familiarice con la MMC (Microsoft
Management Console) de manera que pueda ver rápidamente los editores ya aprobados en su equipo.
Para abrirla, basta con teclear
mmc.exe desde PowerShell.
PS > mmc.exe
Al abrirse la consola está vacía y es usted el que tiene que añadir lo que llamamos snap­
ins (complementos empotrables).
325
Para ello, haga clic en File y Add / Remove Snap­in...
Haga clic en el botón Add y después, en la nueva ventana, elija el complemento Certificates.
Finalice la selección eligiendo My user account en la pregunta formulada.
326
Ahora se encuentra con la consola bien configurada para observar sus certificados. Haga clic en el
botón OK para acceder a la siguiente pantalla.
En esta consola, nos interesaremos esencialmente en los certificados personales, en las autoridades de
certificación raíz de confianza y en los editores aprobados de manera que podamos ver su evolución a
través del tiempo.
327
No olvide refrescar la consola con el icono apropiado, o usando la tecla [F5], para ver aparecer las
modificaciones que vamos a realizar.
Tenga en cuenta que hubiésemos podido simplemente usar la línea de comandos siguiente para obtener
el mismo resultado:
PS > Get­ChildItem Cert:\CurrentUser\Root
Ahora que está listo, pasemos a las cosas serias.
El primer comando se debe ejecutar en modo administrador en el símbolo del sistema del kit de
desarrollo:
PS C:\Program Files (x86)\Windows Kits\8.1\bin\x64> .\makecert.exe ­n
"CN=Certificado Raiz PowerShell" ­a sha1 ­eku 1.3.6.1.5.5.7.3.3 ­r
­sv c:\Temp\root.pvk c:\temp\root.cer ­ss Root ­sr localMachine
La línea de comandos anterior llama a la herramienta makecert.exe para crear un certificado de
autoridad de certificación raíz en su máquina. Para entender mejor este comando, detallamos las
opciones utilizadas:
Opción
Descripción
­n
Define el nombre del publicador de certificados.
­a
Define el algoritmo de cifrado utilizado.
­r
Indica la creación de un certificado auto firmado.
­sv
Define el archivo .pvk de clave privada asociado al certificado .cer.
328
Opción
Descripción
­eku
Especifica un Object Identifier (OID) que sirve para precisar si el certificado será
usado por una aplicación. Por ejemplo, 1.3.6.1.5.5.7.3.3 indica que el certificado
podrá utilizarse para firmar scripts. Para obtener más informaciones, una
búsqueda en MSDN con las palabras IX509ExtensionMSApplicationPolicies le
indicará los diferentes OID utilizables en el ámbito de la creación de
certificados.
­ss
Define el nombre del almacén de certificados que contendrá el certificado
creado.
­sr
Define dónde, en la base de registro, se guarda el almacén de certificados.
Una vez ejecutado el comando, se le invita a introducir la contraseña de su clave privada, clave que
será indispensable en la creación del certificado.
Después de hacer clic en OK, se le pedirá una vez más que introduzca la contraseña de su clave
privada.
Si refresca su consola de gestión, confirmará que la nueva autoridad de certificación que acabamos
de crear está presente en el almacén Autoridades de certificación raíz.
Si no desea usar la consola grafica, puede usar la línea de comandos siguiente:
329
PS > Get­ChildItem Cert:\CurrentUser\Root | `
Where {$_.subject ­like "*Powershell*"}
Directory: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root
Thumbprint
­­­­­­­­­­
C95AD30D67D76D8FF35666A952BDE4F59396C8CA
Subject
­­­­­­­
CN=Certificado Raiz PowerShell
Ahora que su equipo es una autoridad de certificación, podemos entonces crear certificados
personales entregados por esta misma autoridad de certificación. Y para ello debemos utilizar el
siguiente comando:
PS C:\Program Files (x86)\Windows Kits\8.1\bin\x64> .\makecert.exe ­pe ­n
"CN=Mi Empresa" ­ss MY ­a sha1 ­eku 1.3.6.1.5.5.7.3.3 ­iv
c:\temp\root.pvk ­ic c:\temp\root.cer
Succeeded
Este comando utiliza nuevas opciones cuyos detalles son:
Opción
Descripción
­pe
Permite incluir la clave privada en el certificado.
­iv
Especifica el archivo de clave privada .pvk.
­ic
Especifica el archivo del certificado .cer raíz.
En la interfaz, introduzca la contraseña de su clave privada (la misma que antes) y haga clic en OK.
Ya ha finalizado la operación y ha creado un certificado que le permitirá después firmar sus scripts.
Para verificar la creación, vuelva a la consola de gestión de certificados y refrésquela. Seleccione
después en la ventana de la izquierda la opción Personal y después Certificates o teclee la
siguiente línea de comandos:
330
PS > Get­ChildItem Cert:\CurrentUser\My
Directory: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
Thumbprint
­­­­­­­­­­
FBCFADDF647D8A886F3071DF58168BE0E6E24D6A
E1952825C7653F867C1868B45C8E0434382E2850
0F1FEB3699EA69A9FC0D399FCDE07F5769E1EA1E
Subject
­­­­­­­
CN=Windows Azure Tools
CN=Mi Empresa
CN=4353bed24c9a6a67
Puede observar que disponemos ahora de un certificado personal llamado «Mi Empresa»; por lo tanto
todo ha funcionado de maravilla, como habíamos previsto.
3. Firmar su primer script
Para firmar un script, primero hay que estar en posesión de un certificado adecuado. Para verificar que
efectivamente disponemos de un certificado contenido en el lector cert: que sirve para firmar, utilizamos
el comando: Get­ChildItem cert: ­r ­codesign.
Lógicamente, si ha seguido adecuadamente los pasos de creación de un certificado auto firmado, debería
ver su certificado de firma, así como otros certificados:
PS > Get­ChildItem cert: ­r ­codesign
Directory: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
Thumbprint
­­­­­­­­­­
FBCFADDF647D8A886F3071DF58168BE0E6E24D6A
E1952825C7653F867C1868B45C8E0434382E2850
Subject
­­­­­­­
CN=Windows Azure Tools
CN=Mi Empresa
La aplicación de una firma digital a un script utiliza el comando
Set­AuthenticodeSignature al cual
debemos pasar el certificado de firma a utilizar.
Debemos por lo tanto realizar un filtro para seleccionar solamente el certificado que nos interesa:
PS > $cert = Get­ChildItem cert: ­r ­codesign |
where {$_.subject ­like "*mi empresa*"}
PS > $miScript = ’C:\Temp\MiScript.ps1’
PS > Set­AuthenticodeSignature ­FilePath $miScript ­Certificate $cert
Directory: C:\temp
FirmarCertificate
­­­­­­­­­­­­­­­­­
E1952825C7653F867C1868B45C8E0434382E2850
Status
­­­­­
Valid
Path
­­­­
MiScript.ps1
Después de la firma del script, pude abrirlo con su editor preferido y observar su firma al final del archivo.
331
Ejemplo de script PowerShell firmado
Acaba de firmar su primer script.
4. Ejecutar scripts firmados
Cuando ejecuta por primera vez un script firmado en un equipo distinto al que ha utilizado para firmarlo,
PowerShell muestra el siguiente mensaje de error:
PS > .\MiScript.ps1
File C:\temp\MiScript.ps1 cannot be loaded. A certificate chain could
not be built to a trusted root authority.
Para ejecutar un script en modo AllSigned, no basta con que el script esté firmado sino que debe estar
en posesión del certificado raíz Y que el editor de este script esté aprobado. Si ya está en posesión del
certificado raíz o lo ha importado, entonces debe aprobar el editor, en caso contrario obtendrá el siguiente
mensaje en PowerShell:
332
PS > .\MiScript.ps1
Do you want to run software from this untrusted publisher?
File C:\temp\MiScript.ps1 is published by CN=Mi Empresa and is
not trusted on your system. Only run scripts from trusted publishers.
[V] Never run [D] Do not run
Help (default is "D"):
[R] Run once
[A] Always run
[?]
Contestando al mensaje con Always run, el editor de script se define como editor aprobado.
Haciendo esto, PowerShell almacenará automáticamente el certificado raíz en el almacén de editores
aprobados. Dicho esto, para evitar la visualización de este mensaje de aviso, debería anticipar la
realización de esta acción.
Preste atención y defina el modo de ejecución a
AllSigned para verificar sus scripts firmados.
Gestionar las directivas de ejecución de PowerShell mediante
las directivas de grupo
Controlar la directiva de ejecución de PowerShell en el seno de un grupo de equipos no es una tarea fácil,
sobre todo cuando se trata de un parque de varias decenas de puestos informáticos. Si en el pasado era
necesario instalar un modelo de administración (archivo ADM, Administration Model) de manera que se
pudiese aplicar la directiva de ejecución mediante directivas de grupo (GPO en inglés, Group Policy Object),
hoy en día no resulta necesario. La aplicación de una directiva de seguridad mediante GPO es en realidad
muy sencilla de implementar.
Ejecute GPMC.msc.
Seleccione la OU (unidad organizativa) correspondiente y haga clic con el botón derecho para
seleccionar Create a GPO in this domain, and Link it here….
333
Defina un nombre para la nueva GPO.
Haga clic con el botón derecho y seleccione Edit en la GPO que acabamos de crear.
334
Vaya en la jerarquía de la izquierda a Computer Configuration ­ Policies ­ Administrative Templates ­
Windows Components ­ Windows PowerShell.
335
Haga doble clic en el parámetro Turn on Script Execution y seleccione la directiva de ejecución
deseada.
Cierre el editor de GPO.
La directiva de ejecución está ahora desplegada en todos los equipos que pertenezcan a la unidad
organizativa correspondiente.
Para aplicar inmediatamente una GPO en un cliente, ejecute el comando
Gpupdate /force.
336
Framework
.NET y .NET
Core
337
Introducción a .NET
Le venimos diciendo desde el comienzo de este libro que PowerShell obtiene su potencia del Framework
.NET. Por fin ha llegado el momento de entrar en detalle.
Para hacerlo sencillo, cuando Jeffrey Snover diseñó PowerShell, el Framework .NET acababa de nacer y su
popularidad aumentaba entre la comunidad de desarrolladores. En efecto, este framework ofrece un gran
número de funciones listas para su uso, que permiten aumentar significativamente la productividad de los
desarrolladores. Estas funciones, listas para su uso ­ llamadas «clases», en la jerga ­ evitan al desarrollador
tener que reinventar la rueda, de modo que pueden centrarse en el código útil de su programa.
Snover sucumbió a los cantos de sirena del Framework .NET, para nuestro bien, y no nos quejamos por ello,
¡al contrario!
De modo que, cuando utilizamos PowerShell, estamos utilizando de manera indirecta el Framework .NET. Por
ejemplo, cuando declaramos una variable, esta usa un tipo del Framework .NET y no un tipo específico de
PowerShell (si bien pueden existir algunos).
En ocasiones, cuando PowerShell no posee el comando que deseamos, podemos invocar directamente
ciertas clases del Framework .NET para alcanzar nuestro fin. Por ejemplo, veremos al final del capítulo cómo
comprimir archivos en formato ZIP. En efecto, no hemos esperado a la versión 5 de PowerShell para crear
archivos comprimidos (a través del nuevo comando Compress­Archive). El Framework .NET ofrece una
clase para ello (desde su versión 2.0), de modo que hace tiempo que hemos creado un equivalente a este
comando...
En resumen, todo esto para decirle que, dado que PowerShell se basa para sus propios objetivos en el
Framework .NET, también nosotros podemos utilizar cualquier clase del framework. Es lo que le
mostraremos a través de este capítulo. Pero antes, vamos a hacer un rápido resumen para que comprenda
cómo se sitúa PowerShell respecto a las diferentes versiones del Framework que se han sucedido. Esto
tiene su importancia, sobre todo tras la aparición de PowerShell Core.
El framework .NET
Conocido por muchos desarrolladores, el framework .NET es un componente de Windows que apareció en
su versión final en 2002. Indispensable para la instalación de PowerShell, el framework está ahora instalado
de forma nativa en los sistemas Windows. Destinado a facilitar el desarrollo de aplicaciones informáticas, el
framework proporciona una inmensa biblioteca de clases sobre la cual se apoyan ciertos lenguajes como
C#, VB.NET, J#, y por supuesto PowerShell.
Arquitectura software
338
Composición del Framework .NET
Sin entrar en detalles muy técnicos que no resultan realmente útiles para la comprensión, acuérdese
simplemente de que el Framework .NET está compuesto por dos elementos principales:
La CLR (Common Language Runtime), entorno de ejecución compatible con todos los lenguajes de
programación que respetan la CLS (Common Language Specification). CLR es el equivalente a la
máquina virtual de Java; es quien interpreta el bytecode resultado de la compilación de un programa
.NET.
Las bibliotecas de clases contienen todos los tipos que podemos encontrar en el Framework .NET.
Cada clase se encuentra catalogada en un espacio de nombres.
Sucesivas versiones del Framework .NET
Con el paso de los años, el Framework .NET ha mejorado y tomado la dirección correcta. Las siguientes
figuras muestran las nuevas API que han enriquecido el Framework a lo largo del tiempo.
Evolución del Framework .NET de la v2 a la v3.5
339
Evolución del Framework .NET de la v4 a la v4.5
Omitiremos el detalle de cada API, pero podemos destacar la presencia de WinForms y WPF. Estos dos
bloques de software permiten crear interfaces gráficas. No diremos más, por ahora, sobre esto.
En el momento de escribir estas líneas, la versión en curso es la versión 4.7.1, que salió el 17 de octubre de
2017.
.NET Core
A diferencia del Framework .NET, que solo funciona sobre la plataforma Windows, .NET Core ha nacido
multiplataforma.
.NET Core es la última versión del Framework .NET, aunque es open source. Sí, ha leído bien. .NET Core es la
reescritura completa del Framework .NET pero en versión open source. ¿Imagina el trabajo astronómico que
ha hecho falta para pasar de una versión a otra? Es realmente colosal, pero no tenemos duda de que el
objetivo se alcanzará en algunos años. Además, gracias a que .NET Core es open source, significa que todo
el mundo puede contribuir en su desarrollo, lo cual no puede sino mejorar su portabilidad así como potenciar
sus funcionalidades.
Esta portabilidad ha forzado a Microsoft a revisar su código y optimizarlo. Además, aunque .NET Core parte
de una página en blanco, aprovecha más de quince años de experiencia adquirida por Microsoft durante el
desarrollo de su hermano mayor. Con toda probabilidad, es por ello por lo que .NET Core es mucho más
ligero y realmente más veloz que su hermano mayor.
La versión estable actual de .NET Core (en el momento de escribir estas líneas) es la versión 2.1.4.
¿Qué significa esto para nosotros, desarrolladores de scripts PowerShell?
En primer lugar, como comprenderá, estamos viviendo un periodo de cambio en el ciclo de vida de
PowerShell. En efecto, hasta la versión 5.1, PowerShell, o deberíamos decir «Windows PowerShell» para ser
más precisos, se basa en el Framework .NET. Por consiguiente, PowerShell solo puede funcionar sobre la
plataforma Windows.
Actualmente, tenemos .NET Core y PowerShell Core, que se basa sobre él, lo que permite aprovechar
instantáneamente la portabilidad intrínseca de .NET Core. PowerShell es capaz, por tanto, de funcionar no
solo sobre Windows, sino también sobre Linux, Mac OS y, por qué no, ¡sobre Raspberry! Y, como guinda,
¡todo ello con pleno soporte por parte de Microsoft! ¡Qué buena noticia!
Además, PowerShell Core es rápido, más rápido que Windows PowerShell. Es normal, pensará, pues .NET
Core es mucho más rápido que su hermano mayor...
340
Desafortunadamente, conviene ser cautos en nuestros propósitos (aunque tampoco en exceso). En efecto,
.NET Core es reciente, de modo que todavía no posee el conjunto de funcionalidades del Framework .NET.
Además, ciertas funcionalidades tales como WinForms y WPF ­ necesarias para el desarrollo de interfaces
gráficas ­ no podrán migrarse, pues están demasiado vinculadas a Windows para poder ser multiplataforma.
Esto no es más que un ejemplo concreto; existen muchos otros, aunque de momento es el único que nos ha
impactado de verdad. Por fortuna, la creación de interfaces gráficas en PowerShell no es realmente su
vocación, de modo que no es, al fin y al cabo, una gran pérdida. Cabe esperar que Microsoft nos
proporcione, en poco tiempo, alguna alternativa multiplataforma a estas dos API que son WinForms y WPF,
cada vez más envejecidas.
¿Hay que instalar .NET Core para poder utilizar PowerShell Core?
No, no merece la pena preocuparse, .NET Core, además de ser extremadamente compacto respecto al
Framework .NET, forma parte del paquete de instalación de PowerShell Core.
Además, varias versiones de .NET Core (y también de PowerShell Core) pueden coexistir en una misma
máquina. En efecto, puede instalarse en cualquier carpeta. Esta es una gran ventaja respecto a su
predecesor, que, por su parte, se instala en la carpeta de instalación de Windows. Por este motivo no es
posible, en parte, hacer coexistir varias versiones de Windows PowerShell.
PowerShell Core frente a Windows PowerShell, ¿cuál elegir?
Dejando las cosas claras, Microsoft dedica todo su esfuerzo y energía a .NET Core, así como a PowerShell
Core. Si bien Windows PowerShell y el Framework .NET siguen teniendo soporte, evidentemente, estos dos
componentes mayores del ecosistema Microsoft ya no evolucionarán más. No habrá nuevas funcionalidades,
solamente parches de seguridad. Sí, es chocante, pero nos tenemos que hacer a la idea.
La versión 5.1 será, por tanto, la última versión de Windows PowerShell. El futuro está encaminado, por
tanto, a buscar un mundo más abierto con PowerShell Core (versión 6). La transición puede llegar a ser
dolorosa, ya que va a ser necesario probar todos nuestros scripts y, forzosamente, adaptar más de uno,
pues supone un gran cambio. Dicho esto, todavía tenemos tiempo, pues PowerShell Core acaba de
aparecer y Windows PowerShell va a contar con soporte todavía durante algunos años.
¿Qué versión de PowerShell escoger?
No tenemos otra opción que acompañar el cambio. Es la ley de la evolución, adaptarse o morir.
Afortunadamente, este cambio no es tan dramático como parece, aunque sí ineludible. De modo que, desde
nuestro punto de vista como autores y también como profesionales de la informática, le recomendamos, si
nos lo permite, que escriba en lo sucesivo todos sus scripts para PowerShell Core, es decir, para la versión
6.
Si, por el contrario, desea seguir trabajando con Windows PowerShell, le recomendamos comprobar que sus
scripts funcionan también con PowerShell 6. Esto le ahorrará un tiempo precioso cuando se vea abocado a
la transición.
Utilizar objetos .NET con PowerShell
A partir de ahora, y hasta el final del capítulo, no distinguiremos entre el Framework .NET y .NET Core, pues
todo lo que vamos a ver a continuación se aplica a ambos frameworks.
En esta sección le explicaremos qué es el Framework .NET, qué contiene, cómo buscar las clases que nos
pueden interesar, cómo crear objetos, y cómo enumerar sus miembros.
Hablaremos indistintamente de clase .NET o de tipo .NET, pues ambos términos hacen referencia a la misma
cosa.
341
Antes que nada, sepa que, en el entorno .NET, todo tiene un tipo. Hasta ahora, sin realmente prestar
atención, hemos manipulado numerosos objetos que poseían cada uno un tipo bien definido en la biblioteca
del Framework. Tomemos por ejemplo el caso del objeto devuelto por el comando Get­Date.
PS > $Date = Get­Date
PS > $Date.GetType()
IsPublic IsSerial Name
­­­­­­­­ ­­­­­­­­ ­­­­
True
True
DateTime
Aplicando el método
BaseType
­­­­­­­­
System.ValueType
GetType del objeto representado por $Date, podemos ver que el tipo es DateTime
System. Por lo tanto el nombre completo es: System.DateTime.
y que su espacio de nombres es
Llamamos espacio de nombres (o namespace en inglés) a lo que precede al nombre de una o varias
clases .NET o WMI. Se utilizan con el objetivo de organizar los objetos y así evitar confundir clases que
podrían eventualmente poseer el mismo nombre.
Para obtener más información, sepa que todos los tipos (se trate de clases, estructuras, enumeraciones,
delegados o interfaces) definidos en la bibliotecas de clase del Framework están detallados en el sitio web
de Microsoft MSDN.
Descripción de una clase del Framework .NET en MSDN
342
Al igual que los tipos encontrados hasta ahora, cada tipo .NET que utilice posee uno o varios miembros que
se pueden obtener mediante el comando Get­Member. Para ver los métodos y propiedades del tipo
DateTime, teclee simplemente:
PS > Get­Date | Get­Member
TypeName: System.DateTime
Name
­­­­
Add
AddDays
AddHours
AddMilliseconds
AddMinutes
AddMonths
AddSeconds
MemberType
­­­­­­­­­­
Method
Method
Method
Method
Method
Method
Method
Definition
­­­­­­­­­­
datetime Add(timespan value)
datetime AddDays(double value)
datetime AddHours(double value)
datetime AddMilliseconds(dou...
datetime AddMinutes(double v...
datetime AddMonths(int months)
datetime AddSeconds(double v...
Entre todos los miembros que podemos encontrar en un tipo .NET, existen los miembros estáticos. Estos,
parecidos a los demás, se diferencian por el hecho de que se deben llamar sin instanciar antes la clase a la
cual pertenecen. Por ejemplo, el tipo DateTime dispone, según la información ofrecida por el sitio web
MSDN, de ciertos miembros estáticos como Now, Today, UtcNow, etc.
Y para utilizar un método o propiedad «estática» de una clase del Framework, basta con usar la sintaxis
siguiente: [<Espacio de nombres>.<Tipo .NET>]::<Miembro­estático>
Ejemplo
PS > [System.DateTime]::Now
Sunday, January 28, 2018 10:16:20 PM
Para
hacer
referencia
[System.DateTime]
a
un
tipo,
especificamos
su
nombre
De paso vemos que se trata del mismo resultado devuelto por el comando
entre
corchetes.
Ejemplo:
Get­Date:
PS > Get­Date
Sunday, January 28, 2018 10:16:30 PM
Existen clases formadas únicamente por miembros estáticos. Estas clases se denominan «estáticas».
Es decir que no pueden ser instanciadas con el comando New­Object.
Para conocer los miembros estáticos de un tipo, existen dos posibilidades:
Ir a la página de MSDN correspondiente al tipo buscado y observar todos los miembros definidos con
el icono
que significa «static».
Utilizar el parámetro
­static con el comando Get­Member.
343
Ejemplo
PS > Get­Date | Get­Member ­static
TypeName : System.DateTime
Name
­­­­
Compare
DaysInMonth
Equals
FromBinary
FromFileTime
FromFileTimeUtc
FromOADate
IsLeapYear
Parse
ParseExact
ReferenceEquals
SpecifyKind
TryParse
TryParseExact
MaxValue
MinValue
Now
Today
UtcNow
MemberType
­­­­­­­­­­
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Method
Property
Property
Property
Property
Property
Definition
­­­­­­­­­­
static int Compare(datetime t1, datet...
static int DaysInMonth(int year, int ...
static bool Equals(datetime t1, datet...
static datetime FromBinary(long dateD...
static datetime FromFileTime(long fil...
static datetime FromFileTimeUtc(long ...
static datetime FromOADate(double d)
static bool IsLeapYear(int year)
static datetime Parse(string s), stat...
static datetime ParseExact(string s, ...
static bool ReferenceEquals(System.Ob...
static datetime SpecifyKind(datetime ...
static bool TryParse(string s, [ref] ...
static bool TryParseExact(string s, s...
static datetime MaxValue {get;}
static datetime MinValue {get;}
datetime Now {get;}
datetime Today {get;}
datetime UtcNow {get;}
1. Crear una instancia de clase (Objeto)
Como en todos los lenguajes orientados a objetos, un objeto no es más que una instanciación de una
clase (o de un tipo). Con PowerShell, la instanciación de una clase .NET se realiza con el comando New­
Object.
Cada vez que instanciamos una clase utilizando New­Object, llamamos a un constructor. Un constructor
es un método que tiene el mismo nombre que la clase y que permite generalmente inicializar las variables.
Cada clase posee al menos un constructor. Y hablamos de sobrecarga del constructor cuando existen
varios constructores que utilizan distintos argumentos.
Para conocer la lista de sobrecargas de un constructor, lo más sencillo es ir al sitio web de MSDN para ver
las características de la clase estudiada.
Como veremos un poco más adelante en esta sección, hablamos de constructor por defecto cuando
este no necesita ningún parámetro para instanciar la clase. Pero preste atención, ya que, incluso con
este nombre, no todas las clases poseen un constructor por defecto.
En el ámbito de la instanciación de una clase .NET, el comando
New­Object posee tres parámetros (sin
contar los parámetros comunes), cuyo detalle vemos a continuación:
344
Descripción
Parámetro
­TypeName
Especifica el nombre completo de la clase .NET, es decir el espacio de
nombres más la clase.
­ArgumentList
Especifica una lista de argumentos que se pasan al constructor de la
clase .NET.
­Property
Define los valores de las propiedades y llama los métodos del nuevo
objeto.
Es posible omitir « System » en la descripción de los tipos que se encuentran en el espacio de
nombres « System », siendo este último el espacio de nombres por defecto.
Ejemplo
New­Object ­TypeName System.DateTime
es equivalente a:
New­Object ­TypeName DateTime
Ejemplo: Creación de una fecha con la clase DateTime
creamos un objeto de tipo DateTime, es decir una instancia
System.DateTime, para después verificar su valor y su tipo (con el método GetType).
En
este
ejemplo,
del
tipo
PS > $var = New­Object ­TypeName DateTime
PS > $var
lunes 1 de enero de 0001 00:00:00
PS > $var.GetType()
IsPublic IsSerial Name
­­­­­­­­ ­­­­­­­­ ­­­­
True
True
DateTime
BaseType
­­­­­­­­
System.ValueType
Observe que invocamos al constructor por defecto ya que no hemos indicado ningún parámetro. Por lo
tanto, nuestro objeto tiene como valor la fecha del 1 de enero del año 01 a las 0h00.
Intentemos ahora crear este objeto con la ayuda de la siguiente sobrecarga del constructor:
(Int32, Int32, Int32)
Este constructor inicializa una nueva instancia de la estructura
especificados.
DateTime
DateTime con el año, el mes y el día
Utilizando este constructor, y añadiendo los siguientes valores, obtenemos un objeto fecha que tiene
como valor la fecha del 14 de febrero del año 2018 a las 0h00:
PS > $var = New­Object ­TypeName DateTime ­ArgumentList 2018, 2, 14
PS > $var
Wednesday, February 14, 2018 12:00:00 AM
Para todos los tipos definidos por defecto en PowerShell, el uso del comando New­Object no
resulta necesario. Basta con especificar el tipo entre corchetes para invocar al constructor.
345
Ejemplo
PS > [System.DateTime]’2/14/2018’
Wednesday, February 14, 2018 12:00:00 AM
O bien:
PS > [System.DateTime]$Date = ’2/14/2018’
PS > $Date
Wednesday, February 14, 2018 12:00:00 AM
Ejemplo: Creación de una cadena con el objeto .NET String
Aquí tiene un ejemplo que pone de manifiesto la importancia de los constructores. Intentamos ahora crear
un objeto de tipo String. Para ello, instanciamos la clase System.String con New­Object, pero sin
precisar argumentos.
Aquí tiene el resultado:
PS > $Cadena = New­Object ­TypeName System.String
New­Object : A constructor was not found. Cannot find an appropriate
constructor for type System.String.
PowerShell nos informa de que no encuentra un constructor apropiado, y por lo tanto no puede crear el
objeto. Para instanciar esta clase es necesario indicar uno o varios argumentos al constructor.
Ejemplo
PS > $Cadena = New­Object ­TypeName System.string ­ArgumentList ’Hola’
PS > $Cadena
Hola
Observe que podemos también no utilizar
­TypeName ni ­ArgumentList y escribir:
PS > $Cadena = New­Object System.String ’Hola’
Para quienes estén acostumbrados a la programación orientada a objetos, sepa que es posible
especificar la lista de argumentos de un constructor entre paréntesis.
Ejemplo
PS > $Cadena = New­Object ­TypeName System.String ­ArgumentList ’Hola’
es equivalente a:
PS > $Cadena = New­Object System.String(’Hola’)
346
Ejemplo: Prueba de creación de un Windows Form (ventana gráfica)
A continuación intentamos instanciar el tipo System.Windows.Forms.Form para crear una Windows
Form. Para ello, utilizamos una vez más el comando New­Object:
PS > $var = New­Object ­typeName System.Windows.Forms.Form
New­Object : Cannot find type [System.Windows.Forms.Form]: verify that
the assembly containing this type is loaded.
Se muestra un mensaje de error de este tipo cada vez que intentamos instanciar un tipo del Framework
.NET que no está cargado en memoria mediante un «assembly» en PowerShell.
2. Los assemblies
Elemento imprescindible del Framework .NET, un assembly puede considerarse como un conjunto de tipos
.NET que constituyen una unidad lógica ejecutable por el CLR (Common Language Runtime). Sin embargo,
para la mayoría de los usuario PowerShell, el término de biblioteca de clases .NET bastará.
Aunque pueda resultar similar a una DLL (Dynamic Link Library), un assembly no es lo mismo ya que está
compuesto de un ejecutable y de varias DLL indisociables las unas de las otras, así como un manifiesto
que garantiza las versiones de las DLL cargadas.
Elemento indispensable para el buen funcionamiento de PowerShell, se cargan ciertos assemblies del
Framework al arrancar PowerShell, de manera que se garantiza el uso inmediato de cierta cantidad de
tipos de objetos.
Para conocer los assemblies cargados por PowerShell, teclee el comando siguiente:
PS > [AppDomain]::CurrentDomain.GetAssemblies()
GAC
Version
Location
­­­
­­­­­­­
­­­­­­­­
True v4.0.30319 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Management...
True v4.0
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Management...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Numerics\v...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.Managem...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Management...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.DirectoryS...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4...
False v4.0.30319
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configurat...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\System.Transactions...
True v4.0
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\mscorlib.resource...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configurat...
False v4.0.30319
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.CSharp\...
True v4.0.30319 C :\Windows\Microsoft.Net\assembly\GAC_64\System.Data\v4.0_4. ...
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
rue v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
True v4.0
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
347
True
True
True
True
v4.0.30319
v4.0
v4.0.30319
v4.0
Para
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Dynamic\v4...
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerSh...
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.WSMan.M...
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.WSMan.M...
saber
el
número
de
assemblies
cargados,
teclee
[AppDomain]::CurrentDomain.GetAssemblies().Count
el
comando
siguiente:
Cada assembly devuelto por el comando [AppDomain]::Current Domain.Get Assemblies() es
de tipo System.Reflection.Assembly y posee por lo tanto una paleta de métodos, entre otros
GetExportedTypes que permite enumerar todos los tipos contenidos en un assembly.
Ejemplo
PS > $assemblies = [AppDomain]::CurrentDomain.GetAssemblies()
PS > $assemblies[0]
GAC
­­­
True
Version
­­­­­­­
v4.0.30319
Location
­­­­­­­­
C:\Windows\...\v4.0.30319\mscorlib.dll
PS > $assemblies[0].GetExportedTypes()
IsPublic
­­­­­­­­
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
...
IsSerial
­­­­­­­­
True
False
False
True
True
False
False
False
True
True
False
True
True
True
True
True
True
Name
­­­­
Object
ISerializable
_Exception
Exception
ValueType
IComparable
IFormattable
IConvertible
Enum
AggregateException
ICloneable
Delegate
MulticastDelegate
Action`1
Action
Action`2
Action`3
BaseType
­­­­­­­­
System.Object
System.Object
System.ValueType
System.Exception
System.Object
System.Delegate
System.MulticastDelegate
System.MulticastDelegate
System.MulticastDelegate
System.MulticastDelegate
Para conocer el detalle de todos los assemblies cargados, utilice el siguiente comando:
[AppDomain]::CurrentDomain.GetAssemblies() | format­list *
Aquí tiene el detalle del primer assembly cargado:
348
CodeBase
FullName
EntryPoint
DefinedTypes
Evidence
PermissionSet
SecurityRuleSet
ManifestModule
ReflectionOnly
Location
ImageRuntimeVersion
GlobalAssemblyCache
...
:
:
:
:
:
:
:
:
:
:
:
:
file:///C:/Windows/Microsoft.NET/Framework64/...
mscorlib, Version=4.0.0.0, Culture=neutral,...
{System.Object, System.Runtime.Serialization...
{<System.Security.Policy.GacInstalled...
{}
Level2
CommonLanguageRuntimeLibrary
False
C:\Windows\Microsoft.NET\Framework64...
v4.0.30319
True
3. Cargar un assembly
Desde el principio de este libro, solo hemos utilizado tipos «integrados» gracias a los assemblies cargados
en el arranque de PowerShell. Pero muchos otros tipos están disponibles, y para importarlos debe cargar
los assemblies correspondientes. Un ejemplo concreto podría ser el uso de los Windows Forms. El uso de
objetos gráficos no está disponible en PowerShell si no cargamos previamente el assembly
System.Windows.Forms.
La técnica más simple para cargar un assembly es el uso del comando
cargamos el assembly
Add­Type. En el siguiente ejemplo
System.Windows.Forms:
PS > Add­Type ­AssemblyName System.Windows.Forms
La otra técnica para cargar assemblies es usar directamente el framework .NET.
Ejemplo
PS > [System.Reflection.Assembly]::LoadWithPartialName(’System.Windows.Forms’)
GAC
­­­
True
Version
Location
­­­­­­­
­­­­­­­­
v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows...
El método
LoadWithPartialName no es el único que permite cargar un assembly: con los métodos
Load o LoadFrom obtenemos exactamente el mismo resultado. La diferencia reside en el hecho de que
en un caso utilizamos el nombre corto del assembly y en el otro el nombre largo (o strong name en inglés).
Un nombre largo se constituye de cuatro partes: el nombre del assembly, la versión, la región y el token
público (versión comprimida de la clave pública del assembly).
Ejemplo
PS > [System.Reflection.Assembly]::Load(’System.Windows.Forms,
version=4.0.0.0, culture=neutral,’+’PublicKeyToken=b77a5c561934e089’)
GAC
­­­
True
Version
­­­­­­­
v4.0.30319
Location
­­­­­­­­
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows...
Aunque el método
LoadWithPartialName sea más simple de usar, no permite verificar que
cargamos la versión correcta del assembly. Por este motivo, este método está destinado a
desaparecer en versiones posteriores del Framework.
349
El assembly System.Windows.Forms no está disponible con PowerShell Core, pues los Windows
Forms no están soportados en .NET Core.
4. Enumerar los tipos contenidos en los assemblies
Cuando prevemos usar un tipo determinado del framework, resulta interesante saber si este tipo ya se
encuentra cargado o no para evitar una carga del assembly que resultaría inútil. Para ello existe una
técnica que consiste en crear un comando capaz de recuperar todos los tipos disponibles con los
assemblies cargados en memoria y después ordenarlos por su espacio de nombres.
Por ejemplo, para obtener el contenido del assembly
comandos bastan:
System.Windows.Forms, las dos líneas de
PS > $FormAssembly = [AppDomain]::CurrentDomain.GetAssemblies() |
Where­Object {$_.Fullname ­match ’System.windows.forms’}
PS > $FormAssembly.GetExportedTypes().fullname
...
System.Windows.Forms.CaptionButton
System.Windows.Forms.CharacterCasing
System.Windows.Forms.CheckBox
System.Windows.Forms.CheckBox+CheckBoxAccessibleObject
System.Windows.Forms.CheckBoxRenderer
System.Windows.Forms.ListControl
System.Windows.Forms.ListBox
System.Windows.Forms.ListBox+ObjectCollection
System.Windows.Forms.ListBox+IntegerCollection
System.Windows.Forms.ListBox+SelectedIndexCollection
System.Windows.Forms.ListBox+SelectedObjectCollection
System.Windows.Forms.CheckedListBox
System.Windows.Forms.CheckedListBox+ObjectCollection
System.Windows.Forms.CheckedListBox+CheckedIndexCollection
System.Windows.Forms.CheckedListBox+CheckedItemCollection
System.Windows.Forms.CheckState
System.Windows.Forms.Clipboard
...
ListBox, TextBox, Form, son conocidos por
todos aquellos que hayan tenido que desarrollar alguna vez una pequeña interfaz gráfica.
O sea más de mil tipos, entre los cuales algunos, como
Aunque el método de búsqueda de un tipo no sea tan complicado, siempre resulta interesante
automatizar este tipo de tarea. Para ello, podemos crear una función de búsqueda sobre los tipos
disponibles en los assemblies cargados por PowerShell.
PS > Function Get­TypeName ($TypeName = ’.’) {
[AppDomain]::CurrentDomain.GetAssemblies() |
Where­Object
{$_.location ­ne $null} |
Foreach­Object {$_.GetExportedTypes() } |
Where­Object
{$_.Fullname ­match $TypeName} |
Foreach­Object {$_.Fullname} }
En esta función, compuesta por cuatro pipelines, recuperamos en primer lugar mediante el método
CurrentDomain.GetAssemblies, todos los assemblies cargados por PowerShell.
El
resultado
pasa
después
por
el
primer
pipeline
para
finalmente
aplicarse
al
método
GetExportedTypes, que permite enumerar el contenido de cada assembly.
350
El resto de la función permite, gracias a
Where­Object, realizar una ordenación sobre los nombres de
los tipos pasados por el segundo pipeline y mostrar su nombre completo.
Así, usando esta función, podemos, con un simple comando, encontrar cualquier tipo que contenga la
palabra clave que haya definido.
Ejemplo con la palabra clave Zip
PS > Get­TypeName Zip
System.IO.Compression.ZipArchive
System.IO.Compression.ZipArchiveEntry
System.IO.Compression.ZipArchiveMode
System.IO.Compression.GZipStream
System.IO.Compression.ZipFile
System.IO.Compression.ZipFileExtensions
La función devuelve todos los tipos que contengan en su nombre la palabra
Zip.
Para reutilizar esta función más adelante, le aconsejamos incluirla en su perfil o en un módulo
«herramientas».
Sacar partido de la potencia de .NET
El uso de .NET da a PowerShell una apertura sobre miles de clases listas para su uso. Por lo tanto,
manipular objetos .NET equivale a permitir una gran flexibilidad pero también sirve para aportar otras
perspectivas a nuestros scripts.
Para ilustrar este propósito vamos, en esta parte, a explicar paso a paso cómo sacar partido a algunas
clases a través de diferentes escenarios como:
El Wake­on­LAN (despertar en remoto) de un equipo.
La compresión de archivos.
La visualización de tooltips.
1. Wake­on­LAN
El Wake­on­LAN (WOL) es un proceso que permite encender un equipo apagado desde la red Ethernet con
una serie de bytes un poco particulares llamados «paquete mágico».
Hoy en día todas las placas base lo soportan, sin embargo puede ser que el Wake­on­LAN esté
desactivado en la BIOS de su máquina.
El paquete mágico que permite lanzar el WOL es una sucesión de 102 bytes cuyos 6 primeros toman el
valor hexadecimal FF y los 96 siguientes son 16 veces la repetición de la dirección MAC (Media Access
Control) de la tarjeta de red del ordenador remoto. Para crear este paquete, usaremos la tabla de bytes
siguiente:
PS > [byte[]]$Direccion_Mac = 0x00, 0x11, 0x43, 0x0E, 0x97, 0x4F
PS > [byte[]]$paquete = 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
PS > $paquete += $Direccion_Mac * 16
351
Una vez constituido el paquete, tenemos que enviarlo por la red. Y para ello, utilizaremos la clase
UdpClient (presente en el espacio de nombres System.Net.Sockets) que proporciona los servicios
de red necesarios para enviar el datagrama UDP (User Datagram Protocol):
PS > $UdpClient = New­Object System.Net.Sockets.UdpClient
Es gracias a esta clase y en particular al método Connect como podremos establecer una conexión con
un host remoto. Bastará, a continuación, con una simple llamada al método Send para finalizar el envío
del datagrama:
PS > $UdpClient.Connect(([System.Net.IPAddress]::Broadcast),1600)
PS > $UdpClient.Send($Paquete,$Paquete.Length)
Tenga en cuenta que para la llamada a la función Connect usamos a la vez una propiedad estática
Broadcast que devuelve la dirección IP de broadcast (255.255.255.255) de manera que garantizamos
una difusión por toda la red del datagrama, así como el número de puerto 1600.
Aquí tiene nuestro script de Wake­on­LAN completo:
Ejemplo
Script de Wake­on­LAN
# WOL.ps1
# Script que permite encender un equipo remoto
[byte[]]$Direccion_Mac = 0x00, 0x11, 0x43, 0x0E, 0x97, 0x4F
[byte[]]$Paquete = 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
$Paquete += $Direccion_Mac * 16
$UdpClient = New­Object System.Net.Sockets.UdpClient
$UdpClient.Connect(([System.Net.IPAddress]::Broadcast),1600)
$UdpClient.Send($Paquete,$Paquete.Length)
Por lo tanto, si el equipo remoto está correctamente conectado a la red, y con la condición de que su placa
base esté bien configurada para tener en cuenta el WOL, tendrá la agradable sorpresa de despertar su
equipo en pleno sueño.
2. Comprimir/descomprimir una carpeta
Pasemos a otro ejemplo. Veamos ahora cómo comprimir y descomprimir una carpeta con archivos, sin
tener que utilizar ni una herramienta de terceros ni el comando Compress­Archive aparecido
recientemente en PowerShell (v5.0).
La clase .NET que nos va a permitir llevar a cabo esta operación se llama ZipFile. Está ubicada en el
espacio de nombres System.IO.Compression. Apareció con el Framework 4.5. También está
disponible en .NET Core.
Dado que los métodos de la clase
ZipFile son estáticos (como indica el
representa un método), no debemos utilizar el comando
«::», pues en caso contrario no funcionará.
amarillo junto al logo que
New­Object como en el ejemplo anterior, sino
He aquí una captura de pantalla de la definición de la clase ZipFile tal y como se define en el sitio de
referencia msdn.microsoft.com para mostrarle todas las posibilidades ofrecidas por esta última:
352
Documentación de la clase ZipFile (MSDN)
Como habrá observado probablemente, el método estático que debemos utilizar para comprimir una
carpeta es el método CreateFromDirectory(). Y para descomprimir un archivo comprimido, su clase
hermana es, en efecto, la clase llamada ExtractToDirectory(). Podemos observar también que
estas clases se definen varias veces. Esto es así porque aceptan varios valores según el caso de uso. Se
dice, en la jerga, que están «sobrecargadas» o, dicho de otro modo, que poseen varios constructores.
En primer lugar, tenemos que cargar el assembly que contiene nuestra clase mediante el comando
Type, como se muestra a continuación:
Add­
PS > Add­Type ­Assembly System.IO.Compression.FileSystem
Este requisito previo se menciona en la página MSDN de la clase.
A continuación, para crear un archivo comprimido, podemos escribir las siguientes líneas de comando:
353
PS > $SourceDirectory = ’C:\CarpetaAComprimir’
PS > $ArchiveFileName = ’C:\miArchivo.zip’
PS > [System.IO.Compression.ZipFile]::CreateFromDirectory(
$SourceDirectory,$ArchiveFileName)
Y para descomprimir un archivo comprimido:
PS > $ArchiveFileName = ’C:\miArchivo.zip’
PS > $DestinationDirectory = ’C:\CarpetaDestino’
PS > [System.IO.Compression.ZipFile]::ExtractToDirectory(
$ZipFile, $DestinationDirectory)
He aquí un ejemplo algo más elaborado y plenamente funcional:
Function Compress­Directory {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[String]$SourceDirectory,
[Parameter(Mandatory=$true)]
[String]$ArchiveFileName
)
Add­Type ­Assembly System.IO.Compression.FileSystem
$SourceDirectory = (Resolve­Path $SourceDirectory).Path
[System.IO.Compression.ZipFile]::CreateFromDirectory(
$SourceDirectory,$ArchiveFileName,’Optimal’,$false)
}
Function Expand­ZipFile {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[String]$ArchiveFileName,
[Parameter(Mandatory=$true)]
[String]$DestinationDirectory
)
Add­Type ­Assembly System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory(
$ArchiveFileName, $DestinationDirectory)
}
3. Crear un tooltip con información contextual (Balloon Tip)
Este ejemplo no puede aplicarse a PowerShell Core.
Una vez más, para mostrar lo que se puede hacer con el Framework .NET, nos interesamos en los tooltip
de información, también llamados Balloon Tip.
Sin embargo tenga cuidado, ya que este tipo de objetos .NET pertenece a un assembly que no está
cargado al arrancar PowerShell. Tendrá por lo tanto que tener la precaución de cargarlo con el comando
siguiente:
PS > Add­Type ­AssemblyName System.Windows.Forms
354
El assembly
System.Windows.Form se utiliza principalmente para todo lo que tiene que ver con
interfaces gráficas.
Sigamos con la creación del objeto .NET que constituye nuestra ayuda. Para ello, basta con instanciar la
clase NotifyIcon.
PS > $Ayuda = New­Object System.Windows.Forms.NotifyIcon
Un parámetro esencial para la creación de una ayuda contextual es el icono en la barra de tareas con el
que será presentado. Para nuestro ejemplo, recuperamos sencillamente el de PowerShell.
PS > $Path = ’C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe’
PS > $Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
PS > $Ayuda.Icon = $icon
Añadimos los parámetros que constituyen el contenido de la ayuda.
PS
PS
PS
PS
>
>
>
>
$Ayuda.BalloonTipIcon = ’Info’
$Ayuda.BalloonTipTitle = ’Hello World’
$Ayuda.BalloonTipText = ’Este mensaje viene de PowerShell’
$Ayuda.Visible = $true
Finalmente observamos el resultado con el método
ShowBallonTip. El parámetro permite definir el
tiempo de visualización (en segundos) de la información:
PS > $Ayuda.ShowBalloonTip(10)
Visualización de un tooltip con información contextual
Los valores mínimo y máximo de visualización aplicados por el sistema operativo son en general 10 y
30 segundos respectivamente. Sin embargo esto puede variar en función del sistema operativo. Los
valores de espera demasiado grandes o pequeños se ajustan automáticamente por Windows en
función del valor mínimo o máximo apropiados.
355
CIM/WMI
356
Introducción
El título de este capítulo probablemente no le diga gran cosa excepto que las tres letras WMI significan
Windows Management Instrumentation. WMI lo conocen bien los administradores de sistemas Microsoft ya
que esta tecnología permite obtener información acerca de la infraestructura pero también permite actuar
sobre una parte o la totalidad de un sistema operativo, así como en ciertas aplicaciones del mundo
Microsoft. Esta tecnología se concibió de manera que se pueda utilizar de manera local o remota con la
misma facilidad.
WMI ha sido la tecnología elegida durante muchos años para la gestión de hardware y software en red,
aunque cederá progresivamente su sitio a la tecnología CIM. Igual es una torpeza decir esto ya que WMI ya
se apoya en gran medida sobre CIM. CIM es más bien una norma (o estándar) abierto que una tecnología.
CIM es el acrónimo de Common Information Model.
Este capítulo le dará un pequeño vistazo de lo que se puede hacer en términos de gestión distribuida. Estas
tecnologías son tan amplias que han dado paso a algunos libros muy densos... A lo largo de este capítulo
nos esforzaremos en ir a lo esencial para que pueda debutar con rapidez en estas tecnologías. Sin embargo
hemos elegido hablar de CIM y WMI separándolos ya que existen dos conjuntos de comandos PowerShell
distintos; y estos son completamente independientes... Dicho esto, para cualquier desarrollo nuevo sobre la
plataforma PowerShell 3 y posteriores, le aconsejamos usar CIM.
Estándares y más estándares, pero ¿para hacer qué?
La obtención de datos para administrar un entorno determinado es importante, pero no constituye más que
una pequeña parte del problema de gestión más global en una empresa. Otro esfuerzo importante va
dirigido sobre la normalización y la organización de los datos. Por ejemplo, una persona sabe que «bueno»,
«funcional», «operacional» y «en marcha» son todos sinónimos de un estado «OK». Pero ¿cómo un
programa informático lo llega a saber? Peor aún, ¿cómo sabe el programa dónde se encuentra la
información que busca?
Sin embargo el problema no se termina aquí con la determinación del lugar y la semántica. En efecto existe
una necesidad creciente de gobierno del sistema de información en términos básicos. Así, por muy anodina
que parezca, la avería de un ventilador en un equipo (en un procesador por ejemplo) puede tener
consecuencias importantes. Por lo tanto es capital estar en disposición de determinar cuáles son los riesgos
en la operativa expresados en términos de nivel de servicio.
La totalidad de la gestión de múltiples componentes en un entorno distribuido es una realidad que se
convierte en requisito previo. Ya no basta con gestionar individualmente los puestos de trabajo, los
servidores, los elementos activos de la red, los armarios de almacenamiento, los hypervisores, etc. Todos
estos componentes interactúan entre sí para suministrar servicios globales. La información pasa a través de
todas estas capas. La gestión debe, por tanto, pasar también a través de todas estas capas. Aquí está el
quid de la normalización y es aquí donde reside también el objetivo del esquema CIM.
Por lo tanto toda la industria informática se ha reunido, al menos los nombres más importantes como: IBM,
DELL, Intel, AMD, Microsoft, VMware, NetApp, Citrix, etc., en el consorcio DMTF (Distributed Management Task
Force) para establecer normas comunes.
El diagrama de tecnologías del DMTF (que puede descargarse desde: http://dmtf.org/standards/stackmap)
sintetiza todos los esfuerzos de normalización realizados durante los últimos años.
357
Diagrama de tecnologías del DMTF
1. ¿Qué es WMI?
WMI es la implementación concreta de Microsoft de los trabajos del DMTF y en particular de la iniciativa
industrial llamada WBEM (Web­Based Enterprise Management). El rol de WBEM es codificar y transportar los
datos definidos según el estándar CIM.
La tecnología WMI está presente en Windows desde hace tiempo; desde Windows NT 4 para ser exactos.
WMI se apoya internamente en una mini base de datos de clases llamada «base CIM». Esta última está
estructurada según las normas dictadas por el consorcio DMTF.
2. ¿Qué es CIM?
CIM significa «modelo de datos unificado» (Common Information Model). Es un estándar abierto que define
cómo elementos administrados en un entorno informático pueden representarse como un conjunto de
objetos coherentes y un conjunto de relaciones entre estos objetos. El objetivo es disponer de una
administración de sistemas coherente y unificada de los elementos gestionados de manera totalmente
independiente de sus fabricantes o proveedores.
3. CIM frente a WMI
Aunque CIM es ante todo un estándar, es también el prefijo del nombre dado a numerosos comandos
PowerShell. En nuestro contexto, los comando CIM representan la evolución respecto a los comandos WMI
ya que:
Son más interoperables.
Sobrepasan tranquilamente las fronteras de la LAN gracias a su gramática XML (basado en SOAP)
utilizada por WS­Man además del transporte que se realiza ahora sobre HTTP/HTTPS.
Permiten el diálogo con antiguos sistemas operativos mediante RPC/DCOM.
358
Una diferencia importante se encuentra a nivel de los protocolos utilizados para estas dos tecnologías. En
efecto, los comandos PowerShell de la familia CIM permiten administrar cualquier hardware o software
remoto (llamado también «endpoint») compatible con la norma CIM + WS­Man. Lo cual resulta evidente
para cualquier equipo Windows en el que esté instalado el Windows Management Framework 3 como
mínimo. Estos comandos implementan también los protocolos DCOM/RPC para la administración de
sistemas heredados. Por lo tanto, ya no es necesario usar comandos de la familia WMI en un equipo en el
que está instalado PowerShell 3 como mínimo. Iremos más allá afirmando que es, actualmente, una buena
práctica no utilizarlos en beneficio de los comandos CIM. En efecto, han sido retirados de PowerShell 6.
4. Y concretamente, ¿qué podemos hacer?
a. Configuración de servidores DELL mediante iDRAC
Concretamente, resulta muy sencillo con PowerShell y los comandos CIM, por ejemplo, configurar
enteramente la parte hardware de los servidores DELL a través de la interfaz de acceso remoto iDRAC
(Integrated Dell Remote Access Controller). Evidentemente, si el servidor está apagado, es posible con
esta misma interfaz encenderlo. Encontramos en la literatura anglófona, para designarlo, los términos
«out­of­band management».
Es posible actualizar la BIOS, cambiar el orden de arranque del equipo, configurar los discos en RAID, y
evidentemente recopilar datos relativos al estado de salud de todos los componentes internos, etc.
b. Gestión de sistemas operativos Linux desde Windows
Existen distintos frameworks open source, que puede instalarse en sistemas de tipo Unix/Linux, como
Open Management Infrastructure (OMI) patrocinado por Microsoft y OpenPegasus, que aportan la
capa CIM necesaria al sistema operativo o a un equipo para que se pueda administrar mediante el
protocolo WS­Man. El primero, desarrollado por Microsoft y sus partners, se conocía antes con el nombre
NanoWBEM, mientras que el segundo está desarrollado por el OpenGroup, otro consorcio industrial
parecido al DMTF.
OMI puede instalarse, entre otros, sobre switchs de la marca Arista y próximamente sobre los de Cisco.
c. Gestión de Windows Server 2012/R2 desde Linux
Windows Server 2012 ­ y ocurrirá lo mismo con versiones posteriores ­ quiere ser el SO servidor más
respetuoso con los estándares jamás desarrollado por Microsoft. Por este motivo se puede administrar
remotamente en su totalidad mediante el modelo CIM. Dado que las consultas CIM se envían por HTTPS,
cualquier cliente CIM (de cualquier plataforma) puede enviar ordenes de gestión a un servidor Windows
Server 2012 (y versiones posteriores). Microsoft ha demostrado esto en varias ocasiones en los diversos
eventos técnicos TechEd y MMS durante el año 2012.
De este modo, cualquier consulta CIM se puede realizar desde entonces con Linux, como por ejemplo
recuperar información o actuar sobre parte del sistema operativo Microsoft.
5. Dificultades que superar
Como Jeffrey Snover, el padre de PowerShell, dice, «los profesionales IT aman y odian WMI. Les gusta
porque pueden hacer muchas cosas con él pero lo detestan porque resulta muy complicado descubrir lo
que tiene dentro».
Efectivamente, cuando realizamos un script apoyándonos en CIM/WMI, lo que más tiempo consume es
llegar a encontrar la información que buscamos. Pero una vez localizada, el resto es coser y cantar...
359
Arquitectura general y terminología
La arquitectura CIM/WMI se descompone en tres capas como en el siguiente esquema:
Arquitectura CIM/WMI
Consumidor CIM/WMI es el término genérico para designar una aplicación cliente que realiza llamadas
CIM/WMI. Puede ser simplemente un script o una herramienta de administración, o también una aplicación
empresarial como Microsoft System Center Configuration Manager.
Un recurso gestionado puede ser cualquier componente físico o lógico administrable mediante CIM/WMI.
Puede ser cualquier componente del sistema como el sistema de discos, el visor de eventos, el registro, los
servicios, los procesos, etc. La lista es realmente muy larga. ¡Es increíble todo lo que puede ser
administrado!
Un recurso gestionado dialoga con la infraestructura CIM/WMI exclusivamente a través de un proveedor.
Cada recurso o más bien cada clase de recurso está descrita en un archivo de texto con el formato MOF
(Managed Object Format). Este tipo de archivos contiene todas las propiedades, métodos y demás
información útil que describen todo lo que es posible hacer con un recurso gestionado a través de CIM/WMI.
Para que la infraestructura CIM/WMI pueda usarlo, un archivo MOF debe estar compilado. Después se carga
en la base CIM. Si conoce SNMP (Simple Network Management Protocol), sepa que un archivo MOF es a
CIM/WMI lo que un MIB (Management Information Base) es a SNMP.
La infraestructura CIM/WMI está compuesta de los tres componentes siguientes:
El CIMOM, que significa Common Information Model Object Manager, puede designar varias cosas:
El componente software que permite a un sistema ser gestionado con el estándar CIM.
Típicamente, va a ser OMI, OpenPegasus para los sistemas no Microsoft, o el Windows
Management Framework para los sistemas Windows. Para Windows 8/Server 2012 no tiene que
preocuparse por esto. El CIMOM forma parte del sistema operativo.
El servicio Windows que permite el diálogo entre todos los componentes. Para WMI, lleva el
nombre Infraestructura de gestión Windows/Windows Management Instrumentation
(winmgmt). Y para CIM, lleva el nombre Gestión remota de Windows (Gestion WSM)/Windows
Remote Management (WS­Management) (WinRM).
El nombre de batalla «CIMOM» viene directamente de la iniciativa WBEM.
360
Realiza el rol de intermediario entre el consumidor y el proveedor. Sin embargo no trata él mismo las
consultas emitidas por un consumidor, sino que las orienta hacia el proveedor apropiado. De este modo,
gracias a él un consumidor puede efectuar consultas en un formato homogéneo interrogando sin saberlo a
varios proveedores diferentes. El CIMOM sabe a qué proveedor interrogar ya que es él mismo el que se
encarga de almacenar las definiciones de recurso/proveedor en la base CIM. El CIMOM asegura también las
funciones de seguridad, que consulta WQL (WMI Query Language), mirando que las consultas se realicen con
los niveles de acceso correctos y de administración de eventos. Para terminar, gracias a CIMOM, un
consumidor WMI puede suscribirse a un evento a intervalos de tiempo definidos por el consumidor. El
CIMOM va a buscar la información en el proveedor correspondiente (un evento representa un cambio de
estado de un recurso gestionado).
Los eventos WMI son muy interesantes ya que permiten una vigilancia casi en tiempo real de la información
del sistema, además consumiendo pocos recursos.
La base CIM contiene el conjunto de clases correspondientes a los recursos gestionados. En el
interior de la base CIM, las clases se agrupan en espacios de nombres por motivos de organización.
En efecto, visto el número de clases a almacenar, ¡la organización es imperativa! Por ejemplo, el
espacio de nombres root/cimv2 incluye las clases que representan los recursos asociados
generalmente a un equipo y a su sistema operativo.
El proveedor es la capa software que dialoga con el CIMOM y los recursos gestionados. Los
proveedores dialogan con los recursos gestionados usando sus API nativas. Así, gracias a los
proveedores, no tenemos que conocer las diferentes API correspondientes a los diversos recursos.
¡Esta es precisamente la potencia de la estandarización!
El concepto de clase es aquí exactamente el mismo que el del Active Directory Domain Services (AD DS), por
ejemplo. Una clase no es más que una descripción abstracta de las propiedades y funcionalidades que un
determinado componente de software o hardware posee. Con respecto al AD DS, la diferencia reside en el
hecho de que no hay prácticamente ningún dato en la base CIM. En efecto, la información gestionada por la
infraestructura CIM/WMI es información dinámica (como por ejemplo la cantidad de memoria instalada, la
temperatura de las CPUs, la velocidad de los ventiladores, etc.) y no sería acertado guardarla en una base
de datos. Por lo tanto, en cada consulta emitida por un consumidor, se solicita a los proveedores.
Un ejemplo de clase podría ser la clase Win32_Service. Esta define lo que es un servicio en el sentido
más amplio del término. Posee entre otras las propiedades name, description y status, y los métodos
startService, stopService y pauseService.
Hablaremos también en muchas ocasiones de instancia de clase. Una instancia de clase es
tradicionalmente lo que llamamos un objeto. Utilizaremos independientemente uno o otro término (instancia
u objeto). Típicamente, todos los servicios Windows que tenemos en nuestro sistema operativo son
instancias de la clase Win32_Service. También podemos decir que los servicios Windows son «objetos
de tipo Win32_Service» u «objetos de la clase Win32_Service».
Comandos de la familia CIM
1. Conjunto de comandos
Introducidos en PowerShell 3.0, el conjunto de comandos de la familia CIM se indica en la siguiente tabla.
En PowerShell 6.0 es el único juego de comandos soportado para interactuar con CIM/WMI:
361
Comando
Descripción
Get­CimInstance
Recupera las instancias de una clase.
Set­CimInstance
Modifica una o varias instancias de una clase.
New­CimInstance
Crea una nueva instancia de clase.
Remove­CimInstance
Suprime una o varias instancias de una clase.
Get­CimAssociatedInstance
Recupera las instancias
instancia determinada.
Get­CimClass
Recupera el esquema de clase de una clase
CIM.
Invoke­CimMethod
Invoca una instancia o un método estático de
una clase.
New­CimSession
Crea una sesión CIM en el equipo local o en un
equipo remoto.
New­CimSessionOption
Crea un conjunto de opciones a utilizar cuando
se establece la sesión.
Get­CimSession
Recupera la lista de sesiones establecidas.
Remove­CimSession
Suprime las sesiones CIM establecidas en un
equipo.
Register­CimIndicationEvent
Se suscribe a un evento WMI/CMI.
asociadas
de
una
El comando que más usaremos es Get­CimInstance. Sin embargo, para utilizarlo, debemos determinar
la clase a partir de la cual queremos obtener instancias. Y ahora las cosas se ponen un poco más feas...
2. Descubrimiento de clases
Get­CimClass es EL comando elegido para descubrir todo lo que se esconde en una infraestructura
CIM/WMI ya que el descubrimiento era hasta ahora el gran punto débil de esta tecnología. En efecto, en
las versiones anteriores de PowerShell, teníamos que escribir nuestros propios scripts de búsqueda para
tener la esperanza de encontrar LA propiedad correcta o EL método necesario a la problemática del
momento. ¡Esto forma parte del pasado! Microsoft, bajo el impulso de numerosos comentarios por parte
de la comunidad MVP pero también de sus clientes, ha puesto los medios para colmar esta laguna. Es
precisamente lo que vamos a ver en esta sección.
Antes de empezar, debe saber que si no se precisa un espacio de nombres, root/cimv2 será el que se
considerará por defecto. Nada más normal cuando se sabe que el 90% de las clases CIM estándares se
encuentran en su interior. Sin embargo, también debe saber que por ejemplo las clases relativas a la
administración de un servidor Hyper­V se encuentran en un espacio de nombres propio, en su caso
root/virtualization.
a. Enumerar todas las clases
La lista de todas las clases situadas en el espacio de nombres
obtener de la siguiente manera:
root/cimv2 de un equipo se puede
PS > Get­CimClass ­Namespace root/cimv2
362
En el anterior ejemplo, hemos indicado el espacio de nombres
root/cimv2, pero hubiésemos podido
omitirlo ya que es el espacio de nombres por defecto. Era, simplemente, para mostrarle que el parámetro
­Namespace existe y permite indicar un espacio de nombres determinado.
Enumerar todas las clases de un sistema no presenta mucho interés en la medida en que el resultado
devuelto comporta una «infinidad» de resultados. En general, no realizaremos esta operación por placer
sino más bien porque buscamos una clase en particular.
Como decíamos más arriba, Get­CimClass se optimizó para la búsqueda. Así es fácil realizar la
búsqueda sobre un nombre de clase, de propiedad o de método; y el resultado es extremadamente
rápido.
b. Buscar clases con una determinada palabra
Es posible filtrar para mostrar únicamente las clases que contengan ciertas letras, como por ejemplo la
palabra «network»:
PS > Get­CimClass ­ClassName *network*
NameSpace: ROOT/cimv2
CimClassName
­­­­­­­­­­­­
CIM_NetworkAdapter
Win32_NetworkAdapter
Win32_NetworkConnection
Win32_NetworkProtocol
Win32_NetworkClient
Win32_NetworkLoginProfile
Win32_NetworkAdapterConfiguration
...
CimClassMethods
CimClassProperties
­­­­­­­­­­­­­­­
­­­­­­­­­­­­­­­­­­
{SetPowerState, R...{Caption, Description,
{SetPowerState, R...{Caption, Description,
{}
{Caption, Description,
{}
{Caption, Description,
{}
{Caption, Description,
{}
{Caption, Description,
{EnableDHCP, Rene...{Caption, Description,
Ins...
ns...
Ins...
Ins...
Ins...
Set...
Set...
El resultado devuelto es por lo tanto el conjunto de clases cuyo nombre contiene la palabra «network».
Observe que para cada una de ellas también devuelve las propiedades CimClassMethods y
CimClassProperties. Como sus nombres sugieren, estas últimas contienen respectivamente los
métodos y las propiedades de cada clase. Lo que constituye una información muy interesante...
Acabamos de ver cómo, con la línea de comandos, recuperar las clases de manera «clásica». Veamos
ahora cómo hacerlo gráficamente con PowerShell ISE.
El siguiente pantallazo muestra que es posible obtener « intellisense » incluidos los valores de los
parámetros, lo que en nuestro caso será la lista de clases. Esto es una funcionalidad importante de
PowerShell ISE que justificaría únicamente por ella sola el hecho de ¡solo utilizar esta consola para todo!
Para obtener el intellisense, justo después del nombre del parámetro, presione las teclas [Ctrl][Espacio]:
363
Funcionalidad Intellisense de PowerShell ISE sobre los valores de un parámetro de un comando
Una vez seleccionada la clase en la lista, presione [Enter] para que el nombre de esta se muestre en vez
de «*network*» en la línea de comandos de nuestro ejemplo.
Tenga en cuenta que si hacemos lo mismo en la consola PowerShell clásica, el resultado es
prácticamente similar salvo que la consola no mostrará una lista gráfica. En este caso hablamos de
«autocompletado». Para que se convenza de la utilidad de esta funcionalidad, pruebe esto en cualquier
consola:
PS > Get­CimClass ­ClassName *network*[TAB]
Como de costumbre, cada vez que presionamos la tecla [Tab] pasará al valor siguiente y [Mayús][Tab]
permite volver al valor anterior.
La funcionalidad de «autocompletado» está también disponible para cualquier nombre de clase o
espacio de nombres en ambas consolas.
3. Descubrimiento de los miembros de una clase
Cuando sabemos que una propiedad o un método está presente dentro de una clase, es fácil solicitar sus
miembros manipulando el objeto devuelto por Get­CimClass sobre una clase dada.
Dicho esto, a veces necesitamos realizar una búsqueda a lo largo de toda la infraestructura CIM/WMI. En
efecto, puede ocurrir que ya hayamos usado una propiedad o un método con un determinado nombre y
que no nos acordemos del nombre de esa clase. En ese caso, en vez de buscar en Google, podemos
realizar esta búsqueda nosotros mismo y eso, una vez más, gracias a Get­CimClass.
a. Enumerar los miembros de una clase
Como hemos comentado brevemente antes, el comando Get­CimClass devuelve la lista de clases que
coincidan con un determinado nombre o modelo de nombre. Pero Get­CimClass devuelve también las
propiedades y los métodos de cada clase.
En
el
ejemplo
anterior,
hemos
visto
que
existía
una
clase
llamada
Win32_NetworkAdapterConfiguration. Veamos ahora cuáles son sus miembros.
Probemos el siguiente comando para enumerar todas las propiedades de esta clase:
364
PS > $members = Get­CimClass ­ClassName Win32_NetworkAdapterConfiguration
PS > $members.CimClassProperties | Select­Object Name
Name
­­­­
Caption
Description
SettingID
ArpAlwaysSourceRoute
ArpUseEtherSNAP
DatabasePath
DeadGWDetectEnabled
DefaultIPGateway
DefaultTOS
DefaultTTL
DHCPEnabled
DHCPLeaseExpires
DHCPLeaseObtained
...
Ahora nos interesamos en los métodos de esta clase:
PS > $members = Get­CimClass ­ClassName Win32_NetworkAdapterConfiguration
PS > $members.CimClassMethods |
Select­Object Name
Name
­­­­
EnableDHCP
RenewDHCPLease
RenewDHCPLeaseAll
ReleaseDHCPLease
ReleaseDHCPLeaseAll
EnableStatic
SetGateways
EnableDNS
SetDNSDomain
SetDNSServerSearchOrder
SetDNSSuffixSearchOrder
SetDynamicDNSRegistration
SetIPConnectionMetric
SetWINSServer
EnableWINS
SetTcpipNetbios
EnableIPSec
DisableIPSec
...
Por lo tanto adivinamos que es posible realizar numerosas acciones sobre una instancia de esta clase.
b. Buscar miembros de una clase
Get­CimClass posee los parámetros ­PropertyName y ­MethodName entre muchos otros. Ambos
sirven para realizar búsquedas.
365
Búsqueda sobre el nombre de una propiedad
Ejemplo
Búsqueda sobre la propiedad speed.
PS > Get­CimClass ­PropertyName speed
NameSpace: ROOT/cimv2
CimClassName
­­­­­­­­­­­­
CIM_NetworkAdapter
Win32_NetworkAdapter
CIM_PhysicalMemory
Win32_PhysicalMemory
CimClassMethods
CimClassProperties
­­­­­­­­­­­­­­­
­­­­­­­­­­­­­­­­­­
{SetPowerState, R... {Caption, Description, I...
{SetPowerState, R... {Caption, Description, I...
{}
{Caption, Description, I...
{}
{Caption, Description, I...
Las clases devueltas en este ejemplo contienen todas una propiedad con el nombre «speed». Veremos
un poco más adelante cómo vamos a recuperar el valor.
Es posible usar el carácter asterisco (wildcard) en la cadena de búsqueda. Así, en el anterior
ejemplo, hubiésemos podido escribir «*speed*» para enumerar todas las clases que contienen una
propiedad cuyo nombre contiene la palabra « speed » como «Negotiated Speed» o «SpeedDuplex».
Búsqueda sobre el nombre de un método
Ejemplo
Búsqueda sobre el método reboot.
PS > Get­CimClass ­MethodName reboot
NameSpace: ROOT/cimv2
CimClassName
­­­­­­­­­­­­
CIM_OperatingSystem
Win32_OperatingSystem
CimClassMethods
­­­­­­­­­­­­­­­
{Reboot, Shutdown}
{Reboot, Shutdown...
CimClassProperties
­­­­­­­­­­­­­­­­­­
{Caption, Description, I...
{Caption, Description, I...
Inútil añadir más, vemos clarísimamente la presencia del método
en particular en la propiedad CimClassMethods.
Reboot en las dos clases devueltas,
4. Recuperar una o varias instancias
La recuperación de una instancia de clase (acuérdese, es un sinónimo de la palabra «objeto») se realiza
con el comando Get­CimInstance. Si está acostumbrado a trabajar con WMI, es el equivalente de
Get­WmiObject. Get­CimInstance se ha diseñado para no impactar en las costumbres de los
desarrolladores de scripts acostumbrados a usar
Get­WmiObject.
Una vez más, es mucho más cómodo utilizar la consola ISE en vez de la consola clásica para este tipo de
consulta, gracias la comodidad que proporciona la funcionalidad del Intellisense.
366
Ejemplo 1
Recuperación de la clase
Win32_BIOS.
PS > Get­CimInstance ­ClassName Win32_BIOS
SMBIOSBIOSVersion
Manufacturer
Name
SerialNumber
Version
:
:
:
:
:
090006
American Megatrends Inc.
BIOS Date: 05/23/12 17:15:53 Ver: 09.00.06
3837­7720­5993­2778­7920­6602­98
VRTUAL ­ 5001223
Evidentemente, como de costumbre, para recuperar únicamente una propiedad, como por ejemplo la
propiedad Manufacturer, basta con escribir:
PS > (Get­CimInstance ­ClassName Win32_BIOS).Manufacturer
American Megatrends Inc.
Ejemplo 2
Obtener la lista de máquinas virtuales en un servidor Hyper­V.
PS > Get­CimInstance ­Namespace root/virtualization ­class
Msvm_ComputerSystem |
Select­Object ElementName, status*, install* | Format­Table ­AutoSize
ElementName
­­­­­­­­­­­
WS2012US­0
WS2012FR­1
WS2012US­1
WS2012CUS­1
Win8US­0
Status
­­­­­­
OK
OK
OK
OK
OK
StatusDescriptions
­­­­­­­­­­­­­­­­­­
{OK}
{Operating normally}
{Operating normally}
{Operating normally}
{Operating normally}
InstallDate
­­­­­­­­­­­
10/20/2012 7:11:06 PM
11/4/2012 10:31:00 PM
10/21/2012 1:41:06 AM
10/21/2012 12:07:09 AM
Si no especificamos el parámetro ­ComputerName, ni el parámetro ­CimSession, entonces Get­
CimInstance se ejecutará en el equipo local usando la infraestructura WMI. Por el contrario, si se
indica uno de los dos parámetros, entonces Get­CimInstance usará la infraestructura del servidor
remoto.
5. Recuperar una o varias instancias con un filtro QL/CQL
Cuando una consulta CIM/WMI devuelve un gran número de objetos, es preferible utilizar un filtro lo más
cerca de la fuente de datos, en vez del lado cliente (con Where­Object). Esto permite evitar el tránsito
de datos en la red, pero también permite eventualmente mejorar el rendimiento de la consulta.
No existirían las consultas si no existiera un lenguaje para realizar las consultas. WMI adoptó el lenguaje
WQL (WMI Query Language). El WQL es un subconjunto simplificado de SQL (Structured Query Language)
ANSI conocido por los administradores de bases de datos. WQL solo permite recuperar información; no
posee funciones de modificación o de supresión de datos. No permite tampoco ordenar la información
devuelta. WQL implementa la norma CQL (CIM Query Language) propuesta por el DMTF, aunque WQL no la
respeta completamente...
367
Habiendo Microsoft más que nunca decidido plegarse a los estándares de la industria, implementó en
PowerShell 2 la verdadera norma CQL. Así al realizar un filtro, podemos elegir uno u otro lenguaje. Dicho
esto, en la práctica, WQL y CQL son tan parecidos que no sabemos realmente cuál de los dos utilizamos.
El parámetro
­QueryDialect, soportado por todos los comandos de la familia CIM y que hace
posible realizar consultas, permite indicar cuál de los dos lenguajes se utiliza. Acepta únicamente los
valores «WQL» y «CQL». WQL es el valor por defecto.
Encontrará el contenido de la norma CIM Query Language ­ DSP0202 en esta dirección:
http://www.dmtf.org/sites/default/files/standards/documents/DSP0202_1.0.0.pdf
De este modo, gracias a WQL, por ejemplo, podemos pedir una instancia específica que responde a ciertos
criterios, como en el siguiente ejemplo:
PS > Get­CimInstance ­Namespace root/virtualization ­class Msvm_ComputerSystem `
­Filter "ElementName like ’win8%’"
Caption
Description
ElementName
HealthState
InstallDate
Name
OperationalStatus
Status
StatusDescriptions
EnabledDefault
EnabledState
OtherEnabledState
RequestedState
TimeOfLastStateChange
CreationClassName
NameFormat
PrimaryOwnerContact
PrimaryOwnerName
Roles
Dedicated
IdentifyingDescriptions
OtherDedicatedDescriptions
OtherIdentifyingInfo
PowerManagementCapabilities
ResetCapability
AssignedNumaNodeList
OnTimeInMilliseconds
ProcessID
TimeOfLastConfigurationChange
PSComputerName
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
Virtual Machine
Microsoft Virtual Machine
Win8US­0
5
10/21/2012 12:07:09 AM
F20295FE­11B9­410A­B33D­70A4B6A22F63
{2}
OK
{Operating normally}
2
2
12
3/22/2013 3:43:33 AM
Msvm_ComputerSystem
1
{0}
225231775
2100
3/22/2013 3:43:33 AM
Acabamos de realizar un filtro sobre el nombre de una máquina virtual, pidiendo únicamente las instancias
que corresponden a nuestros criterios, en este caso todas las que tienen la propiedad ElementName
que empieza por «Win8». El símbolo % es el equivalente al carácter genérico *.
368
6. Invocar un método
Hemos visto cómo recuperar los métodos de una clase. Veamos ahora cómo utilizarlo de manera concreta.
Ejemplo
Hard Reset de una máquina virtual Hyper­v.
PS > $vm = Get­CimInstance ­Namespace root/virtualization ­class
Msvm_ComputerSystem `
­Filter "ElementName like ’win8%’"
PS > $vm | Invoke­CimMethod ­MethodName RequestStateChange `
­Arguments @{RequestedState=3}
Job
­­­
Msvm_ConcreteJob (InstanceID = "11E4...
El valor
ReturnValue PSComputerName
­­­­­­­­­­­ ­­­­­­­­­­­­­­
4096
3 asignado a la propiedad RequestedState indica que deseamos realizar un apagado en
caliente de la VM (lado hypervisor).
Comandos de la familia WMI
Se habrá dado cuenta, a lo largo de este capítulo, de que desde la aparición de PowerShell 3 el conjunto de
comandos WMI ha quedado en beneficio de los comandos de la familia CIM. Microsoft no lo evolucionará
más, y a lo mejor la mantendrá durante cierto tiempo. Los comandos de esta familia no han evolucionado
desde la anterior versión de PowerShell.
Como le decíamos, estos comandos no forman parte de PowerShell Core, de modo que, si los utiliza en un
script, sepa que este puede no ejecutarse correctamente en PowerShell Core.
La familia de comando WMI se resume en los cinco comandos siguientes:
Comando
Descripción
Equivalente CIM
Get­WmiObject
Recupera las instancias de
una clase.
Get­CimInstance
Invoke­WmiMethod
Invoca una instancia o un
método estático de una
clase.
Invoke­CimMethod
Register­
WmiEvent
Se suscribe a un evento
WMI/CIM.
Register­
CimIndicationEvent
Remove­WmiObject
Suprime una o varias
instancias de una clase.
Remove­CimInstance
Set­WmiInstance
Modifica
una
o
varias
instancias de una clase.
Set­CimInstance
Cada uno de estos comandos de la familia WMI posee un equivalente en la familia CIM. Hemos añadido,
como recordatorio, en la tabla anterior la columna Equivalente CIM para ayudarle en la transición si desea
convertir antiguos scripts.
El comando más utilizado de entre todos los que se han presentado es, sin duda, Get­WmiObject. Este,
a diferencia de su equivalencia CIM Get­CimInstance, devuelve objetos «vivos», es decir que poseen
propiedades y métodos, métodos sobre las instancias en las que podemos actuar.
369
Esto no es posible con el conjunto de comandos CIM, por la sencilla razón de que los objetos se serializan y
deserializan entre el cliente y el servidor (flujo XML) para atenerse a la norma CIM. Es la razón por la cual se
utiliza el comando Invoke­CimMethod con mayor frecuencia en el mundo CIM que el comando Invoke­
WmiMethod en el mundo WMI.
Aquí tiene los parámetros más habituales usados por
Get­WmiObject:
Descripción
Parámetro
Class <String>
Permite definir el nombre de la clase de la cual
deseamos recuperar las instancias.
Property <String>
Permite definir el nombre de la propiedad o conjunto
de propiedades a recuperar.
NameSpace <String>
Permite definir el espacio de nombres en el cual se
encuentra la clase. El valor por defecto es
root\cimv2.
Query <String>
Permite definir la consulta a ejecutar usando el
lenguaje de consultas WQL.
ComputerName <String>
Permite definir el nombre del equipo en el cual se
aplica el comando. Por defecto el valor es el equipo
local (valor «.»).
Filter <String>
Permite definir una cláusula «Where» con el formato
WQL.
Credential <PSCredential>
Permite indicar informaciones de autenticación si el
comando debe ejecutarse con otra cuenta distinta
de la cuenta actual.
List [<SwitchParameter>]
Permite enumerar las clases WMI. Esta propiedad
funciona con la complicidad de ­namespace. Si se
omite el namespace, el espacio de nombres por
defecto es root\cimv2.
1. Búsqueda de clases y miembros
Aunque la obtención de clases CIM/WMI disponibles en un sistema se puede realizar con Get­
WmiObject, resultan ser las mismas que con el comando Get­CimClass. Como este último comando
es mucho más intuitivo, práctico y rápido que Get­WmiObject, le recomendamos que utilice Get­
CimClass.
Sin embargo, como insiste, aquí tiene un ejemplo:
370
PS > Get­WmiObject ­list
Name
­­­­
Win32_CurrentTime
Win32_LocalTime
Win32_UTCTime
Win32_NTLogEvent
CIM_ManagedSystemElement
CIM_LogicalElement
CIM_OperatingSystem
Win32_OperatingSystem
CIM_Process
Win32_Process
CIM_System
CIM_ComputerSystem
CIM_UnitaryComputerSystem
Win32_ComputerSystem
CIM_ApplicationSystem
Win32_NTDomain
CIM_SoftwareElement
CIM_BIOSElement
...
Methods
­­­­­­­
{}
{}
{}
{}
{}
{}
{Reboot, Shutdown}
{Reboot, Shutdown...
{}
{Create, Terminat...
{}
{}
{SetPowerState}
{SetPowerState, R...
{}
{}
{}
{}
Properties
­­­­­­­­­­
{Day, DayOfWeek, Ho...
{Day, DayOfWeek, Ho...
{Day, DayOfWeek, Ho...
{Category, Category...
{Caption, Descripti...
{Caption, Descripti...
{Caption, CreationC...
{BootDevice, BuildN...
{Caption, CreationC...
{Caption, CommandLi...
{Caption, CreationC...
{Caption, CreationC...
{Caption, CreationC...
{AdminPasswordStatu...
{Caption, CreationC...
{Caption, ClientSit...
{BuildNumber, Capti...
{BuildNumber, Capti...
No mostramos todos los resultados ya que son demasiado numerosos. En efecto existen más de 1000
clases... De aquí la necesidad de estar preparado para llegar a determinar los tesoros encerrados en su
interior.
El número de clases WMI está en constante aumento. No conocemos su número exacto en los
diferentes sistemas operativos Microsoft, pero sepa que para cada nueva versión de SO el número
de clases aumenta. Esto demuestra, si es necesario mencionarlo, que WMI es una tecnología muy
importante que nos permite realizar cada vez más tareas.
2. Recuperar una o varias instancias
En el mundo WMI, es posible recuperar una instancia gracias al comando Get­WmiObject en conjunto
con la clase Win32_ComputerSystem, como se muestra en el siguiente ejemplo:
Ejemplo 1
Recuperar información del hardware
PS > Get­WmiObject Win32_ComputerSystem
Domain
Manufacturer
Model
Name
PrimaryOwnerName
TotalPhysicalMemory
:
:
:
:
:
:
WORKGROUP
Gigabyte Technology Co., Ltd.
G33M­DS2R
WS2012US­0
Windows User
4283936768
Obtenemos como resultado de este comando cierta información, aunque no es más que una muestra de
las propiedades de la clase Win32_ComputerSystem. En efecto esta posee más de cincuenta
propiedades. Entonces, ¿por qué no las vemos?
371
La respuesta cabe en estas pocas palabras: «archivos de definición de tipos».
Como de costumbre, para utilizar la visualización por defecto, debemos escribir el siguiente comando:
PS > Get­WmiObject Win32_ComputerSystem | Format­List *
PSComputerName
AdminPasswordStatus
BootupState
ChassisBootupState
KeyboardPasswordStatus
PowerOnPasswordStatus
PowerSupplyState
PowerState
FrontPanelResetStatus
ThermalState
Status
Name
PowerManagementCapabilities
PowerManagementSupported
__GENUS
__CLASS
__SUPERCLASS
__DYNASTY
__RELPATH
__PROPERTY_COUNT
__DERIVATION
__SERVER
__NAMESPACE
__PATH
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
WS2012US­0
3
Normal boot
2
3
3
2
0
3
2
OK
WS2012US­0
2
Win32_ComputerSystem
CIM_UnitaryComputerSystem
CIM_ManagedSystemElement
Win32_ComputerSystem.Name="WS2012US­0"
59
{CIM_UnitaryComputerSystem, CIM_ComputerSystem,
CIM_System, CIM_LogicalElement...}
: WS2012US­0
: root\cimv2
: \\WS2012US­0\root\cimv2:Win32_ComputerSystem.Name=
"WS2012US­0"
...
Las clases cuyos nombres empiezan por «__» son clases del sistema propias de WMI. Es muy raro
que las necesitemos. Además estas clases no tienen visibilidad cuando utilizamos el comando Get­
CimInstance.
Ejemplo 2
Recuperar información acerca del sistema operativo.
PS > Get­WmiObject Win32_OperatingSystem | Format­List *
PSComputerName
Status
FreePhysicalMemory
FreeSpaceInPagingFiles
FreeVirtualMemory
BuildNumber
BuildType
Caption
CodeSet
CountryCode
CSDVersion
CSName
:
:
:
:
:
:
:
:
:
:
:
:
WS2012US­0
OK
1665228
699288
2269816
9200
Multiprocessor Free
Microsoft Windows Server 2012 Standard
1252
1
WS2012US­0
372
CurrentTimeZone
Description
MUILanguages
NumberOfLicensedUsers
NumberOfProcesses
NumberOfUsers
OperatingSystemSKU
Organization
OSArchitecture
OSLanguage
OSProductSuite
OSType
OtherTypeDescription
PAEEnabled
PlusProductID
PlusVersionNumber
SerialNumber
ServicePackMajorVersion
ServicePackMinorVersion
SizeStoredInPagingFiles
Version
...
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
60
{en­US}
0
49
1
7
64­bit
1033
272
18
00183­80000­52486­AA060
0
0
720896
6.2.9200
El anterior ejemplo solo es una pequeña selección entre las numerosas Propiedades devueltas por la clase
Win32_OperatingSystem.
Las clases
Win32_ComputerSystem y Win32_OperatingSystem son dos clases que se
utilizan con mucha frecuencia. En efecto, contienen numerosa información acerca del hardware (como
el nombre y la velocidad de los procesadores, la cantidad de memoria instalada, etc.) y del software
(como la versión de Windows, la versión del Service Pack, etc.).
Establecer sesiones con equipos remotos
Mientras que con los comando WMI es posible comunicarse con máquinas remotas (mediante los protocolos
DCOM y RPC), la comunicación no está optimizada ya que en cada consulta se establece una sesión, y a
continuación se elimina después de haber devuelto los resultados al cliente. Además el diálogo con los
equipos remotos se realiza de manera secuencial.
Los comandos de la familia CIM mejoran enormemente esta comunicación aportando:
Una comunicación que permite eligir entre los protocolos HTTPS/WS­Man o DCOM/RPC.
La posibilidad de mantener una sesión entre el cliente y los servidores.
La posibilidad de enviar consultas de manera paralela y no secuencial.
Un mecanismo de « remoting » similar a las sesiones PowerShell remotas.
El «remoting» CIM es muy similar al funcionamiento del mecanismo de «comunicación remota» de PowerShell
(consulte el capítulo Ejecución remota).
Disponemos por lo tanto de la posibilidad de establecer una sesión temporal para una consulta con el
parámetro ­ComputerName, o utilizar una sesión CIM con el parámetro ­Session, para enviar
simultáneamente una consulta hacia varios equipos. Lo habrá comprendido, el uso de sesiones CIM es un
mecanismo eficaz ya que permite reutilizar conexiones y evita así tener que crear y eliminar sesiones.
373
1. Comando New­CimSession
Es posible establecer una sesión CIM mediante el comando
siguiente ejemplo:
New­CimSession, como podemos ver en el
$s = New­CimSession ­ComputerName localhost, ws2012us­1
Acabamos de establecer dos sesiones: una en nuestro equipo local y otra hacia una máquina llamada
«ws2012us­1». También hemos asignado el conjunto a la variable $s. Así será más fácil manipular las
sesiones creadas.
Si fuese necesario, hubiésemos podido pasar credenciales alternativas mediante el parámetro
­
Credential.
Una vez establecida la sesión, solo queda usar
$s como referencia.
Ejemplo
Recuperar información relativa al sistema en varios equipos.
PS > Get­CimInstance ­ClassName Win32_operatingSystem ­CimSession $s |
Select­Object PSComputerName, Installdate, Version, Caption
PSComputerName
­­­­­­­­­­­­­­
ws2012us­1
Standard
localhost
Standard
Installdate
Version Caption
­­­­­­­­­­­
­­­­­­­ ­­­­­­­
04/11/2012 23:30:25 6.2.9200 Microsoft Windows Server 2012
20/10/2012 22:40:39 6.2.9200 Microsoft Windows Server 2012
Aquí tiene, como información, el conjunto de parámetros de
New­CimSession:
New­CimSession [[­ComputerName] <String[]>] [[­Credential] <PSCredential>]
[­Authentication <PasswordAuthenticationMechanism>] [­Name <String>]
[­OperationTimeoutSec <UInt32>] [­Port <UInt32>] [­SessionOption
<CimSessionOptions>][­SkipTestConnection [<SwitchParameter>]]
[<CommonParameters>]
New­CimSession [[­ComputerName] <String[]>] [­CertificateThumbprint <String>]
[­Name <String>] [­OperationTimeoutSec <UInt32>] [­Port <UInt32>]
[­SessionOption <CimSessionOptions>] [­SkipTestConnection [<SwitchParameter>]]
[<CommonParameters>]
2. Comando New­CimSessionOption
Cuando creamos una sesión CIM, es posible especificar numerosas opciones que tienen como principal
característica la seguridad de la sesión, pero también algunas características técnicas de esta última.
Aquí tiene el conjunto de parámetros de
New­CimSessionOption:
374
New­CimSessionOption [­Protocol] <ProtocolType> [­Culture <CultureInfo>]
[­UICulture <CultureInfo>] [<CommonParameters>]
New­CimSessionOption [­Culture <CultureInfo>] [­EncodePortInServicePrincipalName
[<SwitchParameter>]] [­Encoding <PacketEncoding>] [­HttpPrefix <Uri>]
[­MaxEnvelopeSizeKB <UInt32>] [­NoEncryption [<SwitchParameter>]]
[­ProxyAuthentication <PasswordAuthenticationMechanism>]
[­ProxyCertificateThumbprint <String>] [­ProxyCredential <PSCredential>] [­roxyType
<ProxyType>] [­SkipCACheck [<SwitchParameter>]] [­SkipCNCheck [<SwitchParameter>]]
[­SkipRevocationCheck [<SwitchParameter>]] [­UICulture <CultureInfo>] [­UseSsl
[<SwitchParameter>]] [<CommonParameters>]
New­CimSessionOption [­Culture <CultureInfo>] [­Impersonation <ImpersonationType>]
[­PacketIntegrity [<SwitchParameter>]] [­PacketPrivacy [<SwitchParameter>]]
[­UICulture <CultureInfo>] [<CommonParameters>]
El último grupo de parámetros está dedicado a la configuración de una sesión DCOM.
3. Comando Get­CimSession
Debemos utilizar el comando
Get­CimSession para visualizar las sesiones CIM existentes.
Continuando con el ejemplo anterior, aquí tiene el resultado si pedimos las sesiones activas:
PS > Get­CimSession
Id
Name
InstanceId
ComputerName
Protocol
:
:
:
:
:
1
CimSession1
86062ba4­1d54­4510­9db1­3a01add2dc2d
ws2012us­1
WSMAN
Id
Name
InstanceId
ComputerName
Protocol
:
:
:
:
:
2
CimSession2
9114abcb­4634­464d­a29a­300413de6538
localhost
WSMAN
4. Comando Remove­CimSession
Para eliminar una sesión, podemos elegir entre indicar su número de identificador, su nombre, su
InstanceID o un nombre de equipo. Pero podemos también suprimirlas todas de golpe gracias a la
potencia del pipeline, como vemos a continuación:
PS > Get­CimSession | Remove­CimSession
Monitoring de los recursos con la gestión de eventos
Otra faceta de CIM/WMI desconocida pero sin embargo muy útil es la gestión de eventos (o events en
inglés). CIM permite vigilar o «monitorizar» eventos enviando una notificación. Después somos libres de
decidir las acciones a llevar a cabo según la recepción de uno u otro evento.
La gestión de eventos CIM/WMI puede revelarse como un formidable aliado para ayudarnos, a nosotros los
administradores de sistemas, a evitar transformarnos en auténticos bomberos. En efecto, gracias a este
mecanismo, estaremos prevenidos, por ejemplo, si un disco lógico de un equipo llega al 80% de ocupación.
375
«Estar prevenido» puede significar recibir un e­mail, un SMS o un pop­up; todo depende de su script y el
modo de notificación que haya elegido. Lo habrá entendido, es posible notificar de la aparición de un evento
sobre cualquier recurso gestionado por CIM/WMI. Podemos por lo tanto vigilar el buen funcionamiento de
los servicios en ciertos equipo remotos, la ejecución de un proceso determinado, o también monitorizar
ciertas claves de registro. Viendo todas las clases WMI disponibles, es muy posible que podamos
monitorizar EL recurso que le hacía tanta falta y que le permitirá en el futuro dormir a placer...
Para los que conocen SCOM (System Center Operations Manager), deben saber que el principio de
notificaciones de eventos es el mismo; y para aquellos que no lo conozcan, podrán hacer lo mismo que con
SCOM pero a una escala infinitamente más pequeña y con menor coste…
Si las notificaciones no existieran, y para intentar hacer lo mismo, estaríamos obligados a desarrollar scripts
que se ejecutasen a intervalos de tiempo constantes para vigilar ciertos recursos gestionados. Aunque esto
es posible, esta técnica puede llegar a consumir muchos recursos ya que el script debe ejecutarse con
mucha frecuencia, o incluso ejecutarse como tarea de fondo constantemente. Las notificaciones WMI se han
diseñado precisamente para ser la manera más eficiente de realizar estas tareas.
1. Vigilar la creación de un proceso local
Tomemos un ejemplo sencillo: la vigilancia del proceso MSPaint.exe que corresponde a la aplicación de
dibujo estándar de Windows. El objetivo es lanzar un evento cuando el sistema detecta la ejecución del
proceso MSPaint.exe. El evento se contentará simplemente con mostrar un mensaje de aviso
devolviendo la fecha y la hora de arranque del proceso vigilado.
Por lo tanto deberemos «subscribirnos» a un evento de creación de un proceso, y definir la acción a
realizar cuando el evento se produce. El comando PowerShell clave para llevar a cabo esta operación es
Register­CimIndicationEvent. Este es el equivalente a Register­WmiEvent para WMI. Sin
embargo, le recomendamos el primer comando al segundo, ya que los comandos de la familia WMI están
ahora un poco obsoletos.
A continuación presentamos el script de vigilancia del proceso
MSPaint.exe:
$accion = {
$msg = "El proceso ha sido arrancado el {0} !" ­f (ConvertTo­LocalTime `
­LongDate $event.SourceEventArgs.NewEvent.Time_Created)
Write­Warning $msg
}
$query = "SELECT * FROM __InstanceCreationEvent
WITHIN 3 WHERE Targetinstance ISA ’Win32_process’
AND TargetInstance.Name=’mspaint.exe’"
Register­CimIndicationEvent ­Query $query ­SourceIdentifier "PaintWatcher" `
­Action $accion
Hemos recuperado en este ejemplo la función ConvertTo­LocalTime definida en el capítulo Gestión
de archivos y fechas. Perdamos un poco de tiempo sobre la consulta WQL ya que es la más importante en
este ejemplo; y solo será ella la que modificaremos en el futuro para los demás ejemplo.
El principio de la consulta es similar a las que ya hemos realizado antes, a saber que está constituida de
SELECT y de FROM. Por el contrario, aquí indicamos que deseamos obtener eventos de tipo
__InstanceCreationEvent; a saber, como el nombre deja suponer, eventos de creación de objetos.
376
Después aparecen dos nuevas palabras clave: WITHIN e
ISA. La primera indica un intervalo en segundos
que determina la frecuencia de ejecución del administrador de eventos, y la segunda que la instancia a
monitorizar debe pertenecer a una cierta clase WMI. Después definimos el nombre de la instancia sobre la
cual llevamos nuestra atención con TargetInstance.Name=’NombreDelProceso’. Si no
hubiésemos precisado el nombre del proceso, el script hubiese devuelto la primera instancia de procesos
detectados.
Probemos nuestro script en la consola PowerShell ISE:
Subscripción a un evento de creación de un proceso
Para que el mensaje de aviso se produzca, basta simplemente con arrancar la aplicación Paint.
Lo que podemos ver es que el script no es en ningún momento bloqueante; más bien al contrario ya que
se ejecuta en un proceso en segundo plano.
2. Vigilar la creación de un proceso en un equipo remoto
Una de las grandes ventajas de CIM/WMI es que podemos subscribirnos a eventos que tienen lugar en
uno o varios equipos remotos. Para ello basta con añadir el parámetro ­ComputerName o ­Session al
comando Register­CimIndicationEvent, de la siguiente manera:
Register­CimIndicationEvent ­Query $query ­SourceIdentifier "RemotePaintWatcher" `
­Action $action ­ComputerName Win8US­0
377
Aunque el evento se desarrolle en un equipo remoto, el bloque de script del parámetro
­Action se
ejecuta en el equipo local en el que se ha realizado la subscripción.
Register­CimIndicationEvent posee los siguientes parámetros:
Descripción
Parámetro
Action <ScriptBlock>
Especifica los comandos que gestionan
los
eventos.
Los
comandos
especificados en el parámetro Action
se ejecutan cuando un evento tiene
lugar, en vez de enviar el evento a la
cola de espera de eventos. Coloque los
comandos entre llaves ( { } ) para
crear un bloque de script.
El valor del parámetro Action puede
incluir
las
variables
automáticas
$Event,
$EventSubscriber,
$Sender, $SourceEventArgs y
$SourceArgs, que dan información
acerca del evento al bloque de script
Action.
CimSession <CimSession>
Ejecuta el comando usando la sesión
CIM especificada.
ClassName <String>
Especifica el evento al cual desea
subscribirse. Indique la clase WMI que
genera los eventos.
ComputerName <String>
Especifica el nombre del equipo remoto
sobre el cual se vigila el evento. Indique
el nombre NetBIOS, una dirección IP o
un nombre FQN (Fully Qualified Name).
Forward [<SwitchParameter>]
Envía
los
subscripción
local. Utilice
subscriba a
remoto o en
MessageData <PSObject>
Especifica datos suplementarios para
esta subscripción de eventos. El valor
de este parámetro aparece en la
propiedad MessageData de todos los
eventos asociados a esta subscripción.
Namespace <String>
Especifica el espacio de nombres de la
clase WMI.
Query <String>
Especifica una consulta en el lenguaje
de consultas WQL o CQL indicado por el
parámetro ­QueryDialect.
QueryDialect <String>
Especifica el lenguaje de consultas
utilizado por el parámetro ­Query.
eventos
para
esta
a la sesión sobre el equipo
este parámetro cuando se
los eventos en un equipo
una sesión remota.
378
Parámetro
Descripción
SourceIdentifier <String>
Especifica
un
nombre
para
la
subscripción. El nombre indicado debe
ser único en la sesión activa. El valor
por defecto es el GUID asignado por
Windows PowerShell. El valor de este
parámetro aparece en el valor de la
propiedad SourceIdentifier del
objeto subscrito y de todos los objetos
de
eventos
asociados
a
esta
subscripción.
SupportEvent [<SwitchParameter>]
Enmascara
la
subscripción
a
los
eventos. Utilice este parámetro cuando
la subscripción actual pertenece a un
mecanismo de subscripción de eventos
más complejo y que no debe ser
descubierto.
OperationTimeoutSec <Int32>
Especifica el tiempo de espera para el
cual el comando espera una respuesta
del equipo.
El valor por defecto 0 (cero) indica al
comando que utilice el valor por defecto
de timeout del servidor.
3. Vigilar el espacio ocupado de un disco duro en un servidor remoto
Como decíamos antes, podemos vigilar el espacio en disco de un equipo local o remoto.
En este ejemplo, lanzamos una notificación cuando el espacio libre del disco C: de un equipo remoto es
inferior a 10 GB. La notificación se traducirá en el envío de un correo electrónico al administrador.
Basaremos nuestra consulta CIM/WMI en la clase Win32_LogicalDisk.
Aquí tiene el script resultante:
# CimEvents2.ps1
# Vigilar el espacio en disco restante de C:
$query = "SELECT * FROM __InstanceModificationEvent
WITHIN 60
WHERE Targetinstance ISA ’Win32_LogicalDisk’
AND TargetInstance.DeviceID = `"C:`"
AND TargetInstance.FreeSpace < 10737418240"
$action = {
$e = $Event.SourceEventArgs.NewEvent
$freeSpace = $e.TargetInstance.FreeSpace / 1GB
$freeSpace = [System.Math]::Round($freeSpace,2)
$message = "¡Umbral crítico alcanzado! Tamaño restante: $FreeSpace GB"
Send­MailMessage ­To ’[email protected]’ ­From ’[email protected]’ `
­Subject ’Espacio en disco bajo’ ­Body $message ­SMTPServer
`
mailsrv.miempresa.es
}
Register­CimIndicationEvent ­Query $query ­SourceID ’EspacioLibre’ ­Action
$action
379
Los
más
atentos
habrán observado
una
pequeña
sutilidad en la
asignación de
la
propiedad
TargetInstance.DeviceID a nivel de la consulta WQL. En efecto, hemos tenido que emplear el
carácter de escape «backtick» (`) delante de las comillas dobles. Esto es así ya que la consulta debe
obligatoriamente contener comillas dobles y si no ponemos los backticks, PowerShell considera que
cerramos la cadena anteriormente abierta. Esto provocaría por lo tanto un error. También hemos usado el
backtick en las líneas que siguen para cortar las líneas de código demasiado largas y que ocupen una sola
línea.
También hemos utilizado un método estático del framework .NET: el de la clase matemática (Math)
llamado Round(). Así redondeamos el resultado de la conversión de bytes a megabytes a dos decimales.
4. Monitorizar la supresión de archivos
Después de ilustrar el control en la creación y modificación de instancias, solo nos queda probar la
supresión de instancias. En este ejemplo, vigilaremos la carpeta C:\temp con el objetivo de detectar
cualquier eliminación de archivos en su interior.
Esta ruta corresponde a la ruta del archivo con formato WMI; tendremos después que extraer el nombre
de nuestro archivo.
# CimEvents3.ps1
# Vigilar la eliminación de archivos en C:\temp
$query = "SELECT * FROM __InstanceDeletionEvent
WITHIN 3
WHERE Targetinstance ISA ’CIM_DirectoryContainsFile’
AND TargetInstance.GroupComponent=’Win32_Directory.Name=`"C:\\\\temp`"’"
$action =
{
$e = $Event.SourceEventArgs.NewEvent
Write­Host ("¡El archivo {0} ha sido suprimido!" ­f
$e.TargetInstance.PartComponent.Name)
}
Register­CimIndicationEvent ­query $query ­sourceid ’supresion’ ­action
$action
El resultado de la ejecución de este script podría ser el siguiente:
¡El archivo C:\temp\ficTest.txt ha sido suprimido!
Así, en cada eliminación de un archivo, un mensaje nos advertirá en los 3 segundos siguientes como
máximo. Y eso mientras no se cierre la sesión PowerShell o se elimine esta tarea.
5. Algunas explicaciones complementarias
Habrá observado a través de estos pocos ejemplos que solo cambia la consulta WQL; el resto del script es
prácticamente idéntico. Un buen dominio de la gestión de eventos WMI pasa por lo tanto necesariamente
por una buena comprensión del lenguaje WQL y sobre todo del esquema de la base WMI.
Hemos utilizado las tres clases de eventos siguientes:
380
__InstanceCreationEvent
__InstanceModificationEvent
__InstanceDeletionEvent
Estas clases, llamadas también «clases intrínsecas» nos han permitido monitorizar instancias WMI, o dicho
de otro modo, objetos WMI de nuestro sistema operativo. Sepa sin embargo que existe otra categoría de
clases llamada «clase extrínseca». Esta última se apoya en las funcionalidades del proveedor WMI propio
a cada objeto del sistema controlado. El control del registro es por ejemplo una clase extrínseca. Dicho
esto, no vamos a extendernos más en este tema en la medida en que el principio es siempre el mismo.
El uso de eventos puede ser extremadamente práctico para la gestión diaria de un sistema de información
en el sentido amplio del término ya que en caso contrario se necesita un programa adicional. Todo está
integrado en Windows, ¡lo que resulta perfecto! Los eventos permiten una gestión activa y dinámica de
Windows de manera sencilla y rápida. Sin embargo son volátiles, es decir que debemos guardarlos
sistemáticamente (con Register­CimIndicationEvent) antes de poder utilizarlo ya que, de base,
no son persistentes.
Dicho esto, una funcionalidad muy poco conocida de WMI es la posibilidad de hacer que las notificaciones
de eventos sean persistentes. Aunque es posible, esta operación puede resultar más bien compleja. Por
este motivo, una persona sensata creó el módulo PowerShell PowerEvents y lo ha hecho público en el
sitio web de Codeplex (http://powerevents.codeplex.com). El autor de este módulo se llama Trevor
Sullivan.
Gestión basada en las URI (Uniform Resource Identifier)
El uso de URI (Uniform Resource Identifier) se hace necesario cuando debemos administrar sistemas que
disponen de un CIMOM, es decir administrables respetando el estándar CIM del DMTF, que no sean
sistemas operativos Microsoft. En efecto, para estos últimos, es mucho más práctico y natural usar las
clases CIM/WMI de las cuales ya hemos hablado. Sin embargo, lo uno no impide lo otro. Es decir que si
conoce perfectamente el esquema CIM, entonces puede, bajo su responsabilidad, usar las URI para
administrar también Windows. Debe saber que en realidad cuando hacemos una consulta CIM/WMI en un
equipo remoto, por ejemplo con el comando Get­CimInstance sobre una clase dada, PowerShell
transforma el nombre de la clase y la propiedad solicitada en una URI.
Por ejemplo, la siguiente línea de comandos:
PS > Get­CimInstance ­ComputerName WS2012fr­1 ­ClassName Win32_OperatingSystem
equivale a esta otra:
PS > $Uri = ’http://schemas.microsoft.com/wbem/wsman/1/wmi/root/
cimv2/Win32_OperatingSystem’
PS > Get­CimInstance ­ComputerName WS2012fr­1 ­ResourceUri $Uri
Los ejemplos que damos se han realizado con un equipo bajo Windows 8 x64 que sirve de cliente y
otro con Windows Server 2012 que sirve de equipo gestionado, pero hubiésemos podido
perfectamente hacerlos a la inversa. Estos dos equipos se encuentran dentro de un mismo dominio y el
cortafuegos está activado en cada uno de ellos. No hemos configurado reglas particulares en el
cortafuegos.
WS­Man responde a los estándares Web, y por lo tanto cualquier recurso gestionado debe conformarse con
un cierto formalismo. Cada recurso CIM está normalizado por el DMTF y por eso se puede representar por
una URI.
381
1. Anatomía de una URI
En la plataforma Microsoft, las URI permiten enlazar el protocolo WS­Management y las clases WMI. Las
URI WMI se han definido directamente en el esquema WS­Management.
Una URI es la concatenación de un prefijo y del espacio de nombres WMI, como vemos abajo:
El prefijo estándar contiene una URL que no es una verdadera URL válida en el sentido que suele darse en
Internet al término, pero representa la norma que define el esquema del recurso gestionado. Por ejemplo,
para gestionar una tarjeta iDRAC en un servidor DELL, es necesario usar la URI siguiente:
http://schemas.dell.com/wbem/wscim/1/cim­schema/2
Lo que debe tener en cuenta para la construcción de una URI:
Una
URI
empieza
siempre
por
una
URL
del
tipo:
http://schemas.microsoft.com/wbem/wsman/1
Después viene un espacio de nombres que para WMI es del tipo
wmi/root, wmi/root/cimv2
(en la mayoría de los casos), wmi/root/microsoft, wmi/root/directory, etc.
Y para terminar el nombre de una clase o el nombre de una instancia.
Ejemplos de URI
# WMI
http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service
http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Processor
# DELL iDRAC
http://schemas.dell.com/wbem/wscim/1/cim­schema/2/DCIM_BIOSService
http://schemas.dell.com/wbem/wscim/1/cim­schema/2/DCIM_ComputerSystem
La norma del DMTF que define las URI posee la referencia DSP0207. Se puede descargar
gratuitamente
en
la
siguiente
dirección:
http://www.dmtf.org/sites/default/files/standards/documents/DSP0207_1.0.0.pdf
2. Conjunto de comandos PowerShell
PowerShell ha sido históricamente dotado de un conjunto de comandos de la familia WSMan. Estos
permiten, desde PowerShell versión 2, enviar consultas CIM a recursos gestionables mediante URI o dicho
de otro modo respondiendo al estándar WBEM. Este juego de comandos está todavía soportado, pero
desde la versión 3 de PowerShell casi todas las funcionalidades del conjunto de comandos WSMan han
sido incluidas en el conjunto de comandos de la familia CIM.
Por lo tanto puede apostar a que los comandos WSMan tienen todas las papeletas para caer en desuso.
Los mencionaremos en esta sección por una inquietud de exhaustividad.
382
a. Conjunto de comandos de la familia WSMan
Podemos dividir el conjunto en dos categorías: una para realizar operaciones y la otra para la
configuración de sesiones WSMan.
Comandos WSMan orientados a las operaciones
Descripción
Comando
Test­WSMan
Evalúa si el
arrancado.
Get­WSManInstance
Muestra la información de gestión para una instancia de
un recurso especificado para una URI de recurso.
Set­WSManInstance
Modifica la información de gestión asociada a un
recurso.
New­WSManInstance
Crea una nueva instancia de un recurso de gestión.
Remove­WSManInstance
Suprime una instancia de un recurso de gestión.
Invoke­WSManAction
Llama una acción sobre un objeto especificado por la
URI de recurso y los selectores.
servicio
WinRM
está
efectivamente
Comandos WSMan orientados a la configuración
Comando
Descripción
Connect­WSMan
Se conecta al servicio WinRM en un equipo remoto.
Disconnect­WSMan
Desconecta el cliente del servicio WinRM en un equipo
remoto.
New­WSManSessionOption
Crea una tabla de hash de opciones de sesión WSMAN
para usarlos como parámetros de entrada para los
comandos:
Get­WSManInstance
Set­
WSManInstance
Connect­WSMan.
Invoke­WSManAction
Set­WSManQuickConfig
Configura el equipo local para la administración remota.
Get­WSManCredSSP
Obtiene la configuración CredSSP (Credential Security
Service Provider) del cliente.
Enable­WSManCredSSP
Activa la autenticación CredSSP en un equipo cliente.
Disable­WSManCredSSP
Desactiva
cliente.
la
autenticación CredSSP
en un equipo
b. Conjunto de comandos de la familia CIM
No recorreremos de nuevo el conjunto de comandos de la familia CIM ya que ya lo hemos hecho al
principio de este capítulo. Tenga en cuenta, simplemente, que todos estos comandos poseen el
parámetro ­ResourceURI que permite indicar la URI del recurso gestionado.
383
3. Prueba de la correcta configuración de un sistema
Antes de comenzar a enviar consultas CIM, conviene probar si el servicio WinRM del equipo remoto (pero
también eventualmente del equipo local) se está ejecutando y funciona correctamente. Para ello usaremos
el comando Test­WSMan:
PS > Test­WSMan ­Authentication default ­ComputerName WS2012
wsmid
ProtocolVersion
ProductVendor
ProductVersion
:
:
:
:
http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
Microsoft Corporation
OS: 6.2.9200 SP: 0.0 Stack: 3.0
El resultado indica que la versión instalada de WinRM es la versión 3.0.
Test­WSMan es un comando útil para realizar un diagnóstico rápido de conectividad WinRM. Esta
funcionalidad no existe en el conjunto de comandos de la familia CIM.
El remoting (comunicaciones remotas) PowerShell está activado por defecto en Windows Server 2012
pero no en Windows 8 ni en las versiones anteriores de Windows.
4. Envío de consultas CIM/WMI mediante una URI
a. Enumerar los servicios de un equipo remoto
Como el resultado es muy detallado, con la ayuda de Select­Object y del parámetro
nos contentaremos con recuperar el primer objeto únicamente.
­first 1,
PS > $URI =
’http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service’
PS > Get­WSManInstance ­ResourceURI $URI ­computer Win2k12 ­Enumerate |
Select­Object ­first 1
xsi
p
cim
type
lang
AcceptPause
AcceptStop
Caption
CheckPoint
CreationClassName
Description
: http://www.w3.org/2001/XMLSchema­instance
: http://schemas.microsoft.com/wbem/wsman/1/Wi
n32_Service
: http://schemas.dmtf.org/wbem/wscim/1/common
: p:Win32_Service_Type
: es­ES
: false
: true
: Servicios Web de Active Directory
: 0
: Win32_Service
: Este servicio proporciona una interfaz de servicio web
a las instancias del servicio de directorio (AD DS y
AD LDS) que se están ejecutando localmente en este
servidor. Si este servicio se detiene o deshabilita,
las aplicaciones cliente como Active Directory
PowerShell no podrán tener acceso a las instancias
del servicio de directorio que se estén ejecutando
384
localmente en este servidor ni tampoco podrán
administrarlas.
DesktopInteract
: false
DisplayName
: Servicios Web de Active Directory
ErrorControl
: Normal
ExitCode
: 0
InstallDate
: InstallDate
Name
: ADWS
PathName
: C:\Windows\ADWS\Microsoft.ActiveDirectory.Web
Services.exe
ProcessId
: 1360
ServiceSpecificExitCode : 0
ServiceType
: Own Process
Started
: true
StartMode
: Auto
StartName
: LocalSystem
State
: Running
Status
: OK
SystemCreationClassName : Win32_ComputerSystem
SystemName
: Win2k12
TagId
: 0
WaitHint
: 0
Hemos obtenido exactamente el mismo resultado que con el comando
Get­CimInstance siguiente:
PS > Get­CimInstance ­ResourceURI $URI ­computer Win2k12 |
Select­Object ­First 1
b. Determinar la fecha de instalación de un equipo remoto
En este ejemplo, vemos que el resultado de una consulta CIM difiere ligeramente según utilicemos la
familia de comandos WSMan o la familia CIM. El ejemplo que usaremos consiste en recuperar la fecha de
instalación de Windows en un equipo remoto.
Como de costumbre, lo primero que debemos hacer es crear la URI del recurso gestionado.
PS > $URI=
’http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_OperatingSystem’
PS > $WSManResult = Get­WSManInstance ­ResourceURI $URI ­computer Win2k12
PS > $WSManResult
Restringiremos
numerosas.
voluntariamente
...
Caption
CodeSet
CountryCode
CreationClassName
CSCreationClassName
CSDVersion
CSName
CurrentTimeZone
InstallDate
...
:
:
:
:
:
:
:
:
:
la
visualización
de
ciertas
propiedades
ya
que
son
realmente
Microsoft Windows Server 2012 Standard
1252
33
Win32_OperatingSystem
Win32_ComputerSystem
CSDVersion
Win2k12
60
InstallDate
385
InstallDate no es extrañamente visible... Veamos ahora lo que devuelve el comando
Get­CimInstance:
La propiedad
PS > $CimResult = Get­CimInstance ­ResourceUri $URI ­ComputerName WS2012es­1
PS > $CimResult
...
Caption
: Microsoft Windows Server 2012 Standard
CodeSet
: 1252
CountryCode
: 33
CreationClassName
: Win32_OperatingSystem
CSCreationClassName
: Win32_ComputerSystem
CSDVersion
: CSDVersion
CSName
: Win2k12
CurrentTimeZone
: 60
InstallDate
: 10/20/2012 10:40:39 PM
...
Y allí, ¡sorpresa! En la lista aparece claramente la fecha dentro de la propiedad
InstallDate.
Para intentar saber más cosas, probemos a recuperar únicamente la propiedad
InstallDate:
PS > $CimResult.InstallDate
Saturday, October 20, 2012 10:40:39 PM
PS > $CimResult.InstallDate.GetType().Name
DateTime
PS > $WSManResult.InstallDate
Datetime
­­­­­­­­
2012­10­20T22:40:39+02:00
PS > $WSManResult.InstallDate.GetType().Name
XmlElement
La explicación es relativamente simple. Cuando usamos los comandos de la familia WSMan, el resultado
devuelto es un objeto XML, mientras que con la familia CIM el resultado es un objeto deserializado. Aquí
nos damos realmente cuenta de que un objeto serializado y deserializado (aunque sea más liviano que
el objeto de salida, puesto que ha perdido sus métodos) es mucho más fácil y natural de manipular que
un objeto XML.
Esto demuestra una vez más que PowerShell ha progresado realmente con la versión 3 y que es
preferible utilizar los últimos comandos puestos a nuestra disposición.
Caja de herramientas gráfica para la exploración de la base
CIM/WMI
Si no se siente muy cómodo usando la línea de comandos para la exploración de la base CIM/WMI, sepa que
existen algunas herramientas gráficas de más o menos fácil acceso.
1. Tester WMI (Wbemtest.exe)
El tester WMI está presente por defecto en todas las versiones de Windows desde Windows NT4. Gracias
a él, puede explorar el esquema de la base CIM, examinar las definiciones de clases, visualizar y actuar
sobre instancias en ejecución. Es una herramienta interesante, pero por desgracia reservada a personas
386
experimentadas. Su mayor interés reside en el hecho de que está instalado en todos los equipos Windows
(cliente o servidor).
Aquí tiene una muestra de su interfaz gráfica:
Tester WMI (Wbemtest.exe)
2. CIM Studio
Preste atención: esta herramienta ya no funciona en Windows 8/Server 2012. Le hablamos de ella
puesto que sigue siendo una excelente herramienta para explorar gráficamente la infraestructura
WMI.
CIM Studio pertenece al kit de desarrollo WMI (WMI SDK); es una aplicación Web. Microsoft ofrece este
SDK gratuitamente. CIM Studio retoma lo esencial de las funcionalidades de Wbemtest pero aporta una
interfaz gráfica netamente más ergonómica con algunas funcionalidades adicionales como la búsqueda de
clases, de propiedades y métodos.
La exploración del esquema CIM se realiza bajo la forma de una jerarquía, lo cual resulta perfecto ya que
nos permite descubrir la jerarquía de clases. Por otra parte las casillas sombreadas junto al nombre de las
clases indican que estas poseen instancias en ejecución. Haciendo doble clic sobre ellas obtendrá la lista
de instancias de esta clase así como todas sus propiedades y métodos. ¡Una verdadera delicia!
Aquí tiene una captura de pantalla de esta fabulosa herramienta:
387
3. SAPIEN WMI Explorer 2015
Hubo una época (no tan lejana) donde el explorador WMI de la empresa SAPIEN Technologies Inc. era
gratuita; desgraciadamente ya no es el caso... Una versión de evaluación gratuita está sin embargo
disponible y plenamente funcional durante 45 días a partir de su instalación inicial. Después tendrá que
obtener una licencia de unos cuarenta euros; lo que es muy razonable dada su formidable eficacia.
Esta herramienta, simple y práctica, es innegablemente, y de lejos, la mejor herramienta gráfica que
podemos utilizar cuando tenemos que lidiar con las tecnologías WMI/CIM. Es inmejorable cuando se trata
de visualizar y buscar instancias, así como efectuar búsquedas sobre nombres de propiedades incluidas en
las clases WMI. Esta herramienta permite también generar código PowerShell correspondiente a cada
acción realizada en el interior de la consola. Esto permite acelerar en gran medida la puesto a punto de
scripts sobre todo cuando no tenemos una gran experiencia en WMI.
WMI
Explorer
2015
se
puede
http://www.sapien.com/software/wmiexplorer
descargar
en
la
siguiente
dirección:
388
WMI Explorer ­ Búsqueda de una clase que contiene la palabra «operating»
389
Ejecución
remota
390
Introducción
Hemos visto en el capítulo anterior que a través de CIM/WMI es posible realizar una serie de acciones sobre
máquinas remotas. En este capítulo le vamos a mostrar que existen otros medios de llevarlo a cabo.
La administración remota de equipos es un tema muy amplio y no todo el mundo tiene las mismas
expectativas. Sobre todo es muy amplio porque, en función del OS remoto (Windows Server o cliente
Windows) y de la versión de PowerShell instalada, no administraremos las máquinas de la misma manera.
Además, desde que PowerShell es multiplataforma, existen distintas maneras de proceder. En efecto, no se
gestiona de la misma manera un OS Windows que un OS Linux.
A continuación, en términos de expectativas, habrá quien simplemente quiera enviar un comando de manera
remota, como por ejemplo reiniciar una máquina, recuperar una lista de procesos en ejecución, reiniciar un
servicio detenido, etc., mientras que otras personas desearán, por su parte, ejecutar un script sobre uno o
varios equipos remotos.
Sin duda, todos estos escenarios son posibles con PowerShell. El objetivo de este capítulo es,
precisamente, explicarle cómo implementar todos estos escenarios de la manera mejor adaptada posible a
su contexto y a su entorno técnico.
Evolución de las tecnologías de acceso remoto
PowerShell existe ahora desde hace una decena de años. Ha evolucionado con el tiempo, si bien no
gestiona los equipos en la actualidad como lo hacía en el pasado. En efecto, los protocolos han
evolucionado considerablemente y, poco a poco, RPC ha ido cediendo su lugar a WinRM como protocolo de
acceso remoto, sin apenas darnos cuenta. Por este motivo existen diversas maneras de llevar a cabo las
acciones de administración remota. Vamos a ayudarle a verlo con mayor claridad.
Comenzaremos con una explicación sobre cómo enviar comandos sencillos de manera remota a una
máquina Windows, tal y como lo hacíamos con la primera versión de PowerShell. A continuación, veremos
cómo ejecutar comandos, y también scripts, de manera remota utilizando el protocolo WinRM. Veremos,
además, en esta sección cómo es posible delegar ciertos comandos específicos en modo «RunAS» a un
usuario que no sea administrador. Por último, le mostraremos cómo hacer lo mismo (salvo la parte «RunAS»)
sobre una máquina que ejecuta Linux con PowerShell 6 utilizando, esta vez, como protocolo de acceso
remoto no WinRM, sino SSH.
Protocolos de acceso remoto
Para comprender bien la evolución de PowerShell y de los protocolos de acceso remoto, nada mejor que un
pequeño esquema recapitulativo.
391
Pilas de protocolos utilizadas por PowerShell
Nos daremos cuenta de que existen tres pilas de protocolos principales que se basan en:
MS­RPCE, es decir, RPC,
HTTP/HTTPS,
SSH.
Acabamos de enumerarlos, del más antiguo al más reciente en la historia de PowerShell, si podemos
expresarnos así.
Por encima de los protocolos, en los bloques sombreados, hemos mencionado las familias de comandos que
se basan directamente en ellos. Como comprenderá, los comandos que se basan en RPC son principalmente
antiguos comandos PowerShell (de los que hablaremos con más detalle en la siguiente sección). Es habitual
decir que los comandos se basan en la «comunicación remota del Framework .NET».
La segunda categoría de comandos, que utilizan HTTP/HTTPS como protocolo de transporte, se basan en
WinRM. Se dice que estos comandos utilizan la «comunicación remota de Windows PowerShell». Esta
categoría de comandos ofrece una mayor riqueza funcional y es la recomendada por Microsoft para
administrar máquinas Windows a distancia.
Desde la aparición de PowerShell Core (versión 6), el escenario ha cambiado ligeramente, pues esta versión
es multiplataforma. Es preciso, por tanto, encontrar una forma común de comunicación a distancia
transversal. Se ha escogido, para ello, OpenSSH.
Dado que la integración de SSH con PowerShell es una novedad, subsisten ciertas limitaciones (hablaremos
de ellas al final de este capítulo). Dicho esto, se trata del camino hacia el futuro y podemos constatar sobre
el repositorio GitHub PowerShell que Microsoft se pone «las pilas» para migrar WinRM a SSH, con el objetivo
de ofrecer las mismas funcionalidades sea cual sea el protocolo de transporte utilizado y la plataforma de
destino administrada.
392
Comunicaciones remotas del Framework .NET
Esta sección solo se aplica a Windows PowerShell. En efecto, en PowerShell Core, con ánimo de
homogeneizar el funcionamiento multiplataforma, el parámetro ­ComputerName solo existe en los
comandos basados en WinRM.
PowerShell se apoya plenamente en el Framework .NET y por eso aprovecha las funcionalidades de
ejecución remota de este. Así es como algunos comandos han adquirido el parámetro ­ComputerName.
Estos comandos son capaces de ejecutarse sobre uno o varios equipos remotos que no es necesario
configurar. Funciona, simplemente (siempre que los distintos firewalls no impidan su funcionamiento).
Las comunicaciones remotas del Framework .NET representan la manera más sencilla de actuar sobre
equipos remotos, pero preste atención porque se apoyan en el protocolo RPC. Este protocolo utiliza puertos
dinámicos. Podría por lo tanto verse enfrentado rápidamente a problemas de seguridad de red. Es la razón
por la que su uso está sobre todo restringido a redes de tipo LAN. Si trabaja en entornos con cortafuegos y
reglas de filtrado instaladas, le aconsejamos que vaya directamente a la sección que habla de las
comunicaciones remotas WinRM.
Preste atención: a partir de Windows Server 2012/Windows 8, los protocolos RPC y DCOM están
bloqueados por el cortafuegos de Windows. Por lo tanto, a menos que añada excepciones en el
cortafuegos, no podrá usar la «comunicación remota del Framework .NET». Le recomendamos
enérgicamente, para estas plataformas, optar por el mecanismo de «comunicación remota de
PowerShell».
1. Requisitos
Ser miembro del grupo de Administradores del equipo remoto o ser miembro del grupo Administradores del
dominio.
2. Determinar los comandos remotos del Framework .NET
Desgraciadamente no existe un método sencillo que permita determinar, para un comando dado, sobre
qué mecanismo reposa su funcionalidad de ejecución remota. Lo más sencillo es leer la sección de ayuda
asociada al comando y prestar especial atención al parámetro ­ComputerName. Por lo tanto es lo que le
vamos a mostrar.
Lo primero que debemos hacer, si todavía no lo hemos realizado, es instalar la ayuda de PowerShell en su
equipo. Para ello, le invitamos a volver al capítulo Descubrimiento de PowerShell.
Después, para recuperar todos los comandos con el parámetro
­ComputerName, teclee:
PS > Get­Help * ­parameter ComputerName
Después, para asegurarse de que el comando se apoya en el mecanismo del Framework .NET,
simplemente debe comprobar, en la ayuda, que no se basa en la «comunicación remota de Windows
PowerShell» (veremos de qué se trata en la siguiente sección de este capítulo).
393
Ejemplo
PS > Get­Help Get­Process ­parameter ComputerName
­ComputerName <String[]>
Gets the processes running on the specified computers. The default is
the local computer.
Type the NetBIOS name, an IP address, or a fully qualified domain name of
one or more computers. To specify the local computer, type the computer
name, a dot (.), or "localhost".
This parameter does not rely on Windows PowerShell remoting. You can use
the ComputerName parameter of Get­Process even if your computer is not
configured to run remote commands.
Podemos leer en la ayuda que este comando «no se basa en la comunicación remota de Windows
PowerShell». Esto significa que se apoya en los mecanismos de comunicación remota del Framework .NET.
Aunque este trámite pueda parecer un poco particular para determinar cuáles son los comandos que se
apoyan en los mecanismos de comunicación remota del Framework .NET y cuáles se apoyan en los
mecanismos de comunicación remota de PowerShell, no existen otras soluciones.
La idea del «Team Microsoft PowerShell» es sin duda que el usuario no se cuestione todo esto. En efecto,
la finalidad es proponer el mismo parámetro a comandos diferentes sin preocuparse de la tecnología que
se esconde detrás. Conocer la tecnología subyacente puede tener su importancia, ya que, para que una
máquina acepte las comunicaciones remotas de PowerShell, debe estar configurada correctamente.
3. Juego de comandos
Veamos con un poco más de detalle lo que se puede realizar con cada uno de estos comandos.
Comando
Descripción
Add­Computer
Añade el equipo local o remoto a un dominio o grupo de trabajo.
Este comando permite también cambiar un equipo de dominio.
Clear­EventLog
Elimina todas las entradas de los logs de eventos especificados.
Get­Counter
Obtiene datos de contadores de rendimiento.
Get­EventLog
Obtiene los eventos del log de eventos o la lista de los logs de
eventos.
Get­HotFix
Obtiene los parches del sistema que han sido instalados.
Get­Process
Obtiene la lista de los procesos en ejecución.
Get­Service
Obtiene la lista de servicios.
Get­WinEvent
Obtiene los eventos de los logs de eventos y de los archivos de
logs de seguimiento de eventos.
Get­WmiObject
Obtiene instancias de clases WMI o información acerca de las
clases disponibles.
Limit­EventLog
Define las propiedades del log de eventos que limitan el tamaño
del mismo y la antigüedad de sus entradas.
394
Descripción
Comando
New­EventLog
Crea un log de eventos y una fuente de eventos.
Remove­Computer
Retira el equipo de un dominio de Active Directory.
Remove­EventLog
Elimina un log de eventos o anula la subscripción de una fuente
de eventos.
Remove­WmiObject
Elimina una instancia de una clase WMI existente.
Rename­Computer
Renombra el equipo local o remoto.
Restart­Computer
Reinicia el sistema operativo.
Set­Service
Arranca, para
propiedades.
Set­WmiInstance
Crea o actualiza una instancia de una clase WMI existente.
Show­EventLog
Muestra los logs de eventos.
Stop­Computer
Apaga el sistema operativo.
Write­EventLog
Escribe un evento en un log de eventos.
e
interrumpe
un
servicio
y
modifica
sus
4. Envío de comandos remotos
El envío de un comando de la lista anterior es realmente sencillo ya que el único requisito necesario es
estar conectado con una cuenta que como mínimo sea administrador del equipo remoto. En efecto, la
mayoría de los comandos no permiten el paso de credenciales alternativas (credentials).
Ejemplo 1
Parada/reinicio del servicio W32Time de un servidor remoto.
Parada del servicio:
PS > Get­Service ­ComputerName Win2k12 ­Name W32time |
Set­Service­Status stopped
Comprobación del estado del servicio:
PS > Get­Service ­ComputerName Win2k12 ­name W32time
Status
­­­­­­
Stopped
Name
­­­­
W32time
DisplayName
­­­­­­­­­­­
Windows Time
Arranque del servicio:
PS > Get­Service ­ComputerName Win2k12 ­name W32time |
Set­Service ­Status running
Ejemplo 2
Leer los logs de eventos de un equipo remoto.
PS > Get­EventLog ­ComputerName Win2k12 ­LogName system ­Newest 10
395
Esta línea de comandos recupera las 10 entradas más recientes del log del sistema de un equipo remoto.
PS > Get­EventLog ­ComputerName Win2k12 ­LogName system ­Newest 10
Index
­­­­­
9415
9414
9413
9412
9411
9410
9409
9408
9407
9406
Time
­­­­
marzo
marzo
marzo
marzo
marzo
marzo
marzo
marzo
marzo
marzo
21
21
21
21
21
21
21
21
21
21
EntryType
Source
InstanceID Message
­­­­­­­­­
­­­­­­
­­­­­­­­­­ ­­­­­­­
22:12 Information Service... 1073748860 El servicio Experienc...
22:04 Information Service... 1073748860 El servicio Programa ...
22:02 Information Service... 1073748860 El servicio Programa ...
22:02 Information Service... 1073748860 El servicio Experienc...
22:02 Information Service... 1073748860 El servicio Windows U...
22:01 Information Service... 1073748860 El servicio Gestión de..
22:01 Information Service... 1073748860 El servicio Gestión de..
22:01 Warning
WinRM
468906
La descripción del ...
22:01 Information WinRM
468900
La descripción del ...
22:01 Warning
WinRM
468901
La descripción del
...
Si queremos filtrar para mostrar únicamente los 10 últimos errores, es así de sencillo:
PS > Get­EventLog ­ComputerName Win2k12 ­LogName system |
Where {$_.EntryType ­eq ’Error’} | Select­Object ­First 10
Index Time
­­­­­ ­­­­
7672 febr.
7512 febr.
7358 febr.
7205 febr.
7071 febr.
7041 febr.
6890 febr.
6730 febr.
6407 febr.
6243 ener.
11
10
09
07
06
06
05
04
03
30
2...
1...
1...
2...
2...
2...
2...
2...
1...
2...
EntryType
­­­­­­­­­
Error
Error
Error
Error
Error
Error
Error
Error
Error
Error
Source
­­­­­­
Microsoft...
Microsoft...
Microsoft...
Microsoft...
DCOM
Microsoft...
Microsoft...
Microsoft...
Microsoft...
Microsoft...
InstanceID Message
­­­­­­­­­­ ­­­­­­­
20 Fallo de instalación...
20 Fallo de instalación...
20 Fallo de instalación...
20 Fallo de instalación...
10010 La descripción del ID...
20 Fallo de instalación...
20 Fallo de instalación...
20 Fallo de instalación...
20 Fallo de instalación...
20 Fallo de instalación...
Comunicaciones remotas de Windows PowerShell (WinRM)
Esta sección se aplica a Windows PowerShell y PowerShell Core, pero solo sobre plataformas Windows.
El mecanismo de comunicación remota de Windows PowerShell, presente desde la versión 2.0, se basa en
el protocolo WS­Man, llamado también «Gestión remota de Windows (WinRM)» o en inglés «Windows
Remote Management». WinRM es la implementación hecha por Microsoft del estándar WS­Man.
WinRM es una tecnología que emergió con Windows Server 2003 R2. WinRM, que significa Windows Remote
Management, permite enviar consultas de gestión a equipos de una red TCP/IP por los puertos 5985 (HTTP)
y 5986 (HTTPS). El protocolo que se esconde detrás de esta tecnología se llama WS­Management. Este
último es una especificación del DMTF (Distributed Management Task Force), que define un protocolo de
comunicación para la administración de servidores, equipamientos y aplicaciones basadas en los Web
Services (SOAP). WS­Management se ha convertido el 28 de enero de 2013 en un estándar oficial ISO
(International Organization for Standardization).
Al igual que WBEM, WS­Management aporta un estándar (esquema XML) orientado a la gestión del
hardware para facilitar la interoperabilidad entre sistemas heterogéneos en el seno de una infraestructura
IT.
396
1. Autenticación y cifrado de las comunicaciones
La seguridad de los intercambios entre dos ordenadores se basa en dos puntos esenciales, que son la
autenticación y el cifrado de los datos que transitan por la red.
Para que dos ordenadores confíen entre sí, se requiere necesariamente un tercero de confianza común a
ambas partes. En un dominio Active Directory, este tercero se materializa en los controladores de dominio.
Fuera del dominio, este tercero será una autoridad de certificación (PKI), de modo que dos máquinas que
dispongan cada una de un certificado de servidor entregado por una misma autoridad de certificación
confiarán mutuamente de manera natural. Esta autenticación mutua garantiza que un ataque de tipo
«man in the middle» no pueda producirse, pues el cliente está seguro de la identidad del servidor sobre el
que se conecta.
a. En un dominio Active Directory
Cuando se utiliza WinRM como protocolo de comunicación entre dos máquinas miembros de un dominio
Active Directory, la autenticación se realiza con Kerberos. A continuación, una vez realizada la
autenticación, las comunicaciones se cifran con una clave de cifrado simétrico de 256 bits. Incluso aunque
por defecto el tráfico de red transita a través de HTTP, este está cifrado, de modo que no hay ningún
problema de seguridad.
b. Fuera de un dominio Active Directory
Si las máquinas no son miembros de un dominio (como ocurre con servidores en la DMZ, por ejemplo),
entonces se utilizará el protocolo NTLM para realizar la autenticación y, a continuación, la comunicación
estará cifrada (como en el caso anterior) y se transmitirá por HTTP. La contraseña no se envía en ningún
momento sin cifrar por la red. NTLM es un protocolo conocido entre los administradores de sistemas y su
debilidad es que resulta imposible garantizar la identidad del servidor, de modo que es sensible a
ataques de tipo «man in the middle». Para reforzar la seguridad, en particular a nivel de autenticación,
puede utilizarse una autenticación por certificado y, de este modo, transitar a HTTPS. De esta manera,
las máquinas confiarán mutuamente entre sí y la autenticación NTLM estará cifrada. A continuación, la
comunicación estará doblemente cifrada, primero gracias a SSL y en segundo lugar gracias al mecanismo
intrínseco de WinRM. Es el único caso donde desplegar certificados para habilitar WinRM sobre HTTPS
tiene sentido.
c. Modificación del tipo de autenticación WinRM
Tenga precaución: no modifique los parámetros de autenticación, salvo si está seguro de lo que hace,
pues, una mala configuración de este parámetro puede tener un efecto devastador sobre la seguridad.
PowerShell soporta varios métodos de autenticación. Cada uno de ellos se explica en la siguiente tabla:
397
Método de
autenticación
Explicación
Activado
por
defecto
Cifrado de
credenciales
Basic
El nombre de usuario y la contraseña
se envían a la máquina. El transporte
de esta información se realiza sin
cifrar (salvo si el propio flujo está
cifrado con HTTPS). Este método es el
menos seguro de todos.
No
No (salvo si el
flujo lo está en
HTTPS)
Negotiate
El método Negotiate va a intentar
realizar
en
primer
lugar
una
autenticación
Kerberos
y,
a
continuación, si no es posible, lo
intentará con NTLM (autenticación en
formato
de
pregunta/respuesta
vinculada a un checksum MD5).
Sí
Sí
Kerberos
La autenticación Kerberos es la más
segura de todas. Está basada en un
mecanismo
de
claves
secretas
(cifrado simétrico) y el uso de tickets,
y no de contraseñas sin cifrar,
evitando así el riesgo de que se
intercepte de manera fraudulenta la
contraseña de los usuarios.
Para
utilizar
la
autenticación
Kerberos, las máquinas de origen y
de destino deben estar en el mismo
dominio AD.
Sí
Sí
Client Certificate­
Based
La autenticación se realiza mediante
la autenticación de un certificado
cliente.
No
Sí
CredSSP
La autenticación CredSSP permite al
usuario delegar sus credenciales.
Este mecanismo se implementa para
permitir escenarios de doble salto. Lo
veremos más adelante en este
capítulo.
No
No
Antes de modificar nada, puede resultar útil observar la configuración actual con el siguiente comando:
PS > Winrm get winrm/config/service/auth
Auth
Basic = false
Kerberos = true
Negotiate = true
Certificate = false
CredSSP = false
CbtHardeningLevel = Relaxed
Por defecto, se utiliza el modo Negotiate, es decir, WinRM intentará utilizar Kerberos si puede. En caso
contrario, intentará utilizar NTLM.
398
Para modificar los métodos de autenticación ofrecidos por un servidor WinRM, puede utilizarse el
comando Winrm. El siguiente ejemplo muestra cómo activarlo con el método Basic:
PS > Winrm set winrm/config/service/auth ’@{Basic="true"}’
Otro ejemplo para desactivar el método Kerberos, lo cual no le recomendamos hacer en un dominio; aquí
lo mostramos únicamente para ilustrar el comando.
PS > Winrm set winrm/config/service/auth ’@{Kerberos="false"}’
2. Requisitos
Los requisitos tanto para el equipo local como para los equipos remotos son los siguientes:
Windows PowerShell 2.0 o superior.
Windows Server 2008 R2 como mínimo / Windows 7 como mínimo.
Ser miembro del grupo «Administradores» del equipo remoto o «Usuarios de administración
remota» o miembro del grupo «Administradores de dominio».
3. WinRM Configuración manual del servicio en un entorno Active Directory
a. Configuración manual
En esta sección veremos cómo activar WinRM en un equipo dentro de un entorno Active Directory. Dado
que la «comunicación remota de PowerShell» está habilitada por defecto en los sistemas operativos
servidor desde Windows Server 2012 (cuando el equipo pertenece a un dominio), esta parte solo será
útil para sistemas operativos como:
Windows Server 2008/R2
Windows 7, 8 y 8.1
Este procedimiento puede también aplicarse a Windows Server 2012/2016 en el caso de que la
configuración de WinRM no funcione o si se ha deshabilitado mediante Disable­PSRemoting.
Sepa que las acciones realizadas en esta parte son evidentemente aplicables por GPO a la escala de un
dominio. Es lo que veremos en la próxima sección, Configuración del servicio WinRM por GPO. Dicho esto,
esta parte explica numerosos detalles que le serán útiles para la comprensión general de WinRM. Le
recomendamos por lo tanto leer esta parte con atención antes de pasar a la siguiente.
b. Activación del servicio WinRM
Para autorizar a un sistema a recibir comandos remotos, es necesario ejecutar el comando
Enable­
PSRemoting.
Observe que para enviar este comando esta etapa no es necesaria (salvo en el caso de una
comunicación entre máquinas fuera del dominio).
399
Enable­PSRemoting realiza las operaciones de configuración siguientes:
Arranca el servicio WinRM si no lo está.
Modifica el tipo de arranque del servicio WinRM a «automático».
Crea una escucha WinRM (llamada listener en inglés) para aceptar peticiones, sobre todas las
direcciones IP.
Activa una excepción en el cortafuegos para las comunicaciones WS­Man.
Activa todas las configuraciones de sesión inscritas para que se puedan recibir órdenes de un
equipo remoto.
Inscribe las configuraciones de sesión (veremos después lo que es una configuración de sesión).
Elimina la autorización Denegar Todos del descriptor de seguridad para todas las configuraciones
de sesión existentes.
Reinicia el servicio WinRM para aplicar los cambios.
La configuración de WinRM activa la regla del cortafuegos llamada «Windows Remote Management
(HTTP­in)» abriendo el puerto TCP 5985.
El comando Enable­PSRemoting debe ejecutarse en una consola PowerShell abierta con la
opción Ejecutar como administrador.
Si el sistema nunca ha sido configurado entonces deberá obtener algo parecido a:
PS > Enable­PSRemoting
Configuración rápida de WinRM
Se está ejecutando el comando "Set­WSManQuickConfig" para habilitar
este equipo para administración remota mediante el servicio WinRM.
Esto incluye:
1. Iniciar o reiniciar (si ya está iniciado) el servicio WinRM
2. Establecer el tipo del servicio WinRM en inicio automático
3. Crear una escucha para aceptar solicitudes en cualquier dirección IP
4. Habilitar una excepción de firewall para el tráfico
WS­Management (sólo para http).
¿Desea continuar?
[S] Sí [O] Sí a todo [N] No [T] No a todo [U] Suspender
[?] Ayuda (el valor predeterminado es "S"): S
WinRM está configurado para recibir solicitudes en este equipo.
WinRM se actualizó para administración remota.
Se creó una escucha WinRM en HTTP://* para aceptar solicitudes de
WS­Man en cualquier IP del equipo.
Excepción de firewall WinRM habilitada.
Una vez activado, es posible mostrar la configuración del servicio WinRM con el siguiente comando:
400
PS >
winrm get winrm/config
Config
MaxEnvelopeSizekb = 500
MaxTimeoutms = 60000
MaxBatchItems = 32000
MaxProviderRequests = 4294967295
Client
NetworkDelayms = 5000
URLPrefix = wsman
AllowUnencrypted = false
Auth
Basic = true
Digest = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = false
DefaultPorts
HTTP = 5985
HTTPS = 5986
TrustedHosts
Service
RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P...
MaxConcurrentOperations = 4294967295
MaxConcurrentOperationsPerUser = 1500
EnumerationTimeoutms = 240000
MaxConnections = 300
MaxPacketRetrievalTimeSeconds = 120
AllowUnencrypted = false
Auth
Basic = false
Kerberos = true
Negotiate = true
Certificate = false
CredSSP = false
CbtHardeningLevel = Relaxed
DefaultPorts
HTTP = 5985
HTTPS = 5986
IPv4Filter = *
IPv6Filter = *
EnableCompatibilityHttpListener = false
EnableCompatibilityHttpsListener = false
CertificateThumbprint
AllowRemoteAccess = true
Winrs
AllowRemoteShellAccess = true
IdleTimeout = 7200000
MaxConcurrentUsers = 10
MaxShellRunTime = 2147483647
MaxProcessesPerShell = 25
MaxMemoryPerShellMB = 1024
MaxShellsPerUser = 30
Evidentemente, puede configurar un número importante de parámetros. Para conocer los comandos
necesarios a la configuración de WinRM, teclee el comando winrm help config.
401
PS > winrm help config
Herramienta de la línea de comandos de Administración remota de Windows
La configuración de WinRM se administra con la línea de comandos de winrm o
GPO.
La configuración incluye la configuración global para cliente y servicio.
El servicio WinRM requiere al menos una escucha para indicar las direcciones
IP en las que acepta solicitudes de WS­Management. Por ejemplo, si el equipo
tiene varias tarjetas de red, se puede configurar WinRM para que solo las
acepte de una de las tarjetas de red.
Configuración global
winrm get winrm/config
winrm get winrm/config/client
winrm get winrm/config/service
winrm enumerate winrm/config/resource
winrm enumerate winrm/config/listener
winrm enumerate winrm/config/plugin
winrm enumerate winrm/config/service/certmapping
La funcionalidad de escucha de red requiere una o varias escuchas.
Las escuchas se identifican mediante dos selectores: Address y Transport.
Address debe
*
IP:1.2.3.4
MAC:...
ser una de
­ Escuchar
­ Escuchar
­ Escuchar
las siguientes:
en todas las direcciones IP del equipo
solo en la dirección IP especificada
solo en una dirección IP para MAC especificada
Nota: todas las escuchas están sujetas a IPv4Filter e IPv6Filter bajo
config/service.
Nota: la dirección IP puede ser de tipo IPv4 o IPv6.
El transporte debe ser uno de los siguientes:
HTTP ­ Escuchar solicitudes en HTTP (el puerto predeterminado es 5985)
HTTPS ­ Escuchar solicitudes en HTTPS (el puerto predeterminado es 5986)
Nota: de forma predeterminada, el tráfico de HTTP solo permite mensajes
cifrados con SSP Negotiate o Kerberos.
...
4. Configuración del servicio WinRM por GPO
La activación y configuración del servicio WinRM en un gran número de servidores o puestos de trabajo
puede realizarse con GPO. La aplicación de una directiva de seguridad por GPO es muy simple de poner en
marcha.
Ejecute GPMC.msc.
Seleccione la OU (unidad organizativa) deseada. Haga clic con el botón derecho para seleccionar
Crear un GPO en este dominio y vincularlo aquí....
402
Dé un nombre a la nueva GPO.
Haga clic con el botón derecho y seleccione Editar en la GPO que acaba de crear.
403
Vaya a Configuración del equipo ­ Directivas ­ Plantillas administrativas ­ Componentes de
Windows ­ Administración remota de Windows ­ Servicio WinRM y active el parámetro Permitir la
administración de servidores remotos a través de WinRM. Esto activará el servicio WinRM.
404
Computer
Para
los
sistemas
en
inglés,
el
parámetro
se
encuentra
en
Configuration\Administrative Templates\Windows Components\Windows Remote Management
(WinRM)\WinRM service.
Debe también, si lo necesita, pensar en añadir una GPO para gestionar las configuraciones del
firewall para autorizar las conexiones entrantes en los equipos para los cuales activamos WinRM.
Cuando está activado, el servicio WinRM se configura nativamente en automático en los
sistemas servidor pero no en los sistemas clientes.
5. WinRM en un entorno fuera de dominio
Si trabaja en un entorno de grupo de trabajo y no de Active Directory, es necesario activar WinRM en el
equipo remoto, aunque no es suficiente. Aquí tiene la prueba:
405
PS > Enter­PSSession Win8
Enter­PSSession : Error de conexión al servidor remoto. Mensaje de error:
El cliente WinRM no puede procesar la solicitud. Si el esquema de
autenticación es distinto de Kerberos o si el equipo cliente no está
unido a un dominio, se debe usar el transporte HTTPS o agregar el equipo
de destino al valor de configuración TrustedHosts. Use winrm.cmd para
configurar TrustedHosts. Tenga en cuenta que es posible que no se
autentiquen los equipos de la lista TrustedHosts. Para obtener más
información, ejecute el siguiente comando: winrm help config. Para obtener
más información, consulte el tema de la Ayuda about_Remote_Troubleshooting.
Como indica el mensaje de error, para permitir una conexión en este tipo de situación, es necesario
configurarla del lado cliente (y no del lado servidor) en lo que se denomina una lista de equipos de
confianza (trusted hosts list). Esto indica que tiene confianza explícita en ciertos equipos.
a. Configuración de la lista de equipos de confianza (trusted hosts list)
A diferencia de los equipos en un dominio, los accesos remotos en entornos workgroup necesitan que
WinRM esté también activado del lado cliente. En efecto la lista trusted hosts es editable a través del
provider WSMan que solo está disponible después de activar el servicio WinRM.
Aquí tiene el comando necesario para editar equipos de confianza.
PS > Set­Item WSMan:\localhost\Client\TrustedHosts ­Value <Nombre Equipo> ­Force
Debe saber que cualquier valor indicado reemplaza al anterior. Para asociar el antiguo y el nuevo valor,
debe utilizar el parámetro ­Concatenate.
PS > Set­Item WSMan:\localhost\Client\TrustedHosts ­Value <Nombre Equipo> ­Force `
­Concatenate
El hecho de añadir un equipo en la lista TrustedHosts no nos autenticará automáticamente en los
servidores remotos. Esto nos permite simplemente autorizar nuestro equipo para que se conecte al
servidor remoto. Es de alguna manera un mecanismo de «autoprotección» para que tengamos conciencia
de lo que hacemos. Debe ver esto como una capa de seguridad adicional desde el momento en que los
dos equipos no se encuentran en un dominio de seguridad Kerberos. Para mostrar los equipos de
confianza, podemos usar el comando winrm get winrm/config que nos devolverá mucha
información o simplemente nos permitirá recuperar un valor en el provider WSMan.
PS > Get­Item WSMan:\localhost\client\trustedhosts
WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client
Name
­­­­
TrustedHosts
Value
­­­­­
Win8
Type
­­­­
System.String
Observará que la configuración se realiza del lado localhost\Client y no del lado servidor
localhost\Service. La idea es proteger el equipo origen limitando el número de servidores a los
cuales se envían las credenciales por la red.
406
b. Desactivación de la UAC
Para
terminar,
última
etapa,
siempre
en
el
equipo
LocalAccountTokenFilterPolicy. En virtud de
cliente, debe configurar el parámetro
la UAC, las cuentas del grupo local
Administradores tienen dos tokens de acceso: uno con privilegios de usuario estándar y el otro con
privilegios de administrador. En el marco de una conexión remota en un equipo en workgroup, la UAC
hará que los permisos aplicados sean los del usuario estándar (lo que no es el caso en una conexión
remota entre dos equipos de un mismo dominio). Por este motivo, será necesario deshabilitar la UAC
para los modos remotos con el siguiente comando:
PS > Set­ItemProperty ­Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion
\Policies\System ­Name LocalAccountTokenFilterPolicy ­Value 1 ­Type DWord
Preste atención: asegúrese de que la contraseña de la cuenta Administrador no esté vacía (esto es
válido para los puestos de trabajo). Ya que en estos casos, le será imposible conectarse en el
equipo remoto.
c. Comunicarse con HTTPS
Como le decíamos en la introducción de esta sección, cuando estamos fuera del dominio, se utiliza el
protocolo NTLM para la autenticación entre una máquina cliente y una máquina servidor.
Si bien las credenciales no transitan sin cifrar por la red, NTLM no garantiza la identidad de la máquina de
destino, lo que podría permitir a un atacante situarse entre dos máquinas y observar las comunicaciones
(ataque conocido como «man in the middle»).
Para mitigar este riesgo, es posible configurar WinRM para utilizar HTTPS ­mediante la implementación de
certificados­ a fin de cifrar todo el diálogo entre un cliente y un servidor. Vamos a mostrarle, por tanto,
cómo activar este mecanismo.
Observe que incluso aunque no habilite HTTPS, el tráfico está cifrado mediante una clave de sesión
simétrica de 256 bits que se negocia durante la fase de autenticación.
Verificación de la configuración WinRM
Lo primero que debemos hacer es habilitar la comunicación remota WinRM mediante el comando
Enable­PSRemoting tal y como hemos hecho antes, sobre ambos equipos.
Puede ver la configuración de red WinRM mediante el siguiente comando:
PS > winrm enumerate winrm/config/listener
Listener
Address = *
Transport = http
Port = 5985
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint
ListeningOn = 192.168.0.14, 127.0.0.1, ::1
407
Por defecto, el servicio WinRM está configurado para utilizar el protocolo de transporte HTTP sobre el
puerto 5985 y escucha sobre todas las interfaces de red. Para forzar el uso de HTTPS y, de este modo,
cifrar las conexiones, hará falta en primer lugar un certificado depositado en el almacén Equipo/Personal.
El certificado debe cumplir los siguientes requisitos:
Un CN correspondiente al nombre de la máquina.
Un OID 1.3.6.1.5.5.7.3.1 ­ Autenticación servidor.
No estar expirado, revocado ni autofirmado.
Creación de un listener HTTPS
Una vez implementado el certificado, basta con crear un segundo listener con la precaución de precisar el
modo de transporte HTTPS.
PS > Winrm create winrm/config/Listener?Address=*+Transport=HTTPS
Un listener puede, como en IIS, estar en escucha sobre ciertas interfaces de red únicamente. Para
hacer esto, reemplace el carácter * por la cadena IP:xxx.xxx.xxx.xxx. Por ejemplo: Winrm
create winrm/config/Listener? Address=IP:192.168.0.14+Transport=HTTPS.
En el caso de que tenga múltiples certificados, puede precisar su firma.
PS > Winrm create winrm/config/Listener?Address=*+Transport=HTTPS
@{Hostname="Win2k12";
CertificateThumbprint="aae364037c03ca6f9fe213c0c652bb3e428806e1"}
Para fijar las direcciones IP sobre las que escucha el servicio, sustituya el carácter
deseadas.
* por las direcciones
PS > Winrm create winrm/config/Listener?Address=
192.168.0.14+Transport=HTTPS @{Hostname="Win2k12";
CertificateThumbprint="aae364037c03ca6f9fe213c0c652bb3e428806e1"}
Supresión del listener HTTP
Ahora puede suprimir el listener HTTP.
PS > Winrm delete winrm/config/Listener?Address=*+Transport=HTTP
Y mostrar de nuevo la configuración de red del servicio WinRM para comprobar el cambio.
PS > Winrm enumerate winrm/config/listener
Listener
Address = *
Transport = https
Port = 5986
Hostname = Win2k12.adps1.local
Enabled = true
URLPrefix = wsman
CertificateThumbprint = aa e3 640 37 c0 3c a6 f9 fe 21 3c 0c...
ListeningOn = 192.168.0.14
408
Las principales fuentes de error durante una conexión HTTPS pueden ser numerosas y los
mensajes que se muestran por consola son, desafortunadamente, poco explícitos. ¿Qué resulta
más molesto que un servicio bien configurado que no funciona? Por este motivo le informamos de un
bug que existe en ciertas plataformas. Este último impide una conexión WinRM en HTTPS, mientras que
las sesiones HTTP sí funcionan. Para saber más acerca de este bug y descargar su corrección, le
invitamos a consultar la página http://support.microsoft.com/kb/2402960.
Modificar los puertos de escucha
Por defecto, PowerShell utiliza los puertos de entrada 5985 y 5986 para recibir conexiones HTTP y HTTPS.
Es posible acceder a esta información directamente a través del siguiente comando:
PS > Get­ChildItem WSMan:\localhost\Service\DefaultPorts |
Format­Table Name,Value
Name
­­­­
HTTP
HTTPS
Value
­­­­­
5985
5986
Es posible modificar estos puertos, y existen dos técnicas para llevarlo a cabo. La primera consiste en
utilizar el comando Winrm. Con este comando, el puerto puede configurarse durante la creación de un
listener con la palabra clave create.
PS > Winrm create winrm/config/listener?Address=*+Transport=HTTP ’@{Port="8888"}’
O bien es posible modificar el puerto con la palabra clave
set.
PS > Winrm set winrm/config/listener?Address=*+Transport=HTTP
’@{Port="8888"}’
La segunda técnica consiste en utilizar el provider WSMan. Conviene saber que se crea un listener por
cada tipo de conexión y cada uno posee un nombre diferente. Para mostrarlos, introduzca el siguiente
comando:
PS > Get­ChildItem WSMan:\localhost\Listener
WSManConfig : Microsoft.WSMan.Management\WSMan::localhost\Listener
Type
­­­­
Container
Container
Keys
Name
­­­­
­­­­
{Transport=HTTP, Address=192.168.0.14} Listener_1084132640
{Transport=HTTPS, Address=192.168.0.14} Listener_1084132784
Mostremos los parámetros del listener HTTPS con el nombre
Listener_1084132640.
409
PS > Get­ChildItem WSMan:\localhost\Listener\Listener_1084132640
WSManConfig :
Microsoft.WSMan.Management\WSMan::localhost\Listener\Listener_1084132640
Type
­­­­
System.String
System.String
System.String
System.String
System.String
System.String
System.String
System.String
Name
­­­­
Address
Transport
Port
Hostname
Enabled
URLPrefix
CertificateThumbprint
ListeningOn_1773224830
Value
­­­­­
*
http
5985
true
wsman
192.168.0.14
La máquina escucha en el puerto 5985, como es normal. Cambiemos ahora la configuración fijando el
puerto 8888.
PS > Set­Item WSMan:\localhost\Listener\Listener_1084132640\Port
­value 8888
Definir el valor del elemento
Este comando define el valor de Item.
¿Desea continuar?
[S] Sí [N] No [S] Suspender
[?] Ayuda (el valor predeterminado es "S"): S
El listener HTTP está ahora configurado en modo escucha en el puerto 8888. Por último, una vez
realizada la modificación (poco importa la técnica utilizada), para establecer sesiones remotas sobre esta
máquina habrá que utilizar el puerto mediante el parámetro ­Port del comando New­PSSession.
Ejemplo
PS > $session = New­PSSession Win2k12.adps1.local ­Port 8888
6. Problemática del «doble salto» (noción de rebote)
Con la comunicación remota WinRM, el rebote de una máquina a otra (llamado double hop en inglés) es un
problema al que tarde o temprano tendrá que enfrentarse.
Tomemos un caso concreto: es el usuario «Juan» y está conectado en su puesto de trabajo. Abra una
sesión WinRM en el servidor A con sus credenciales. Una vez autenticado en el equipo A, intenta acceder a
un recurso específico sobre el servidor B. Es en este momento preciso, cuando se produce el «doble
salto», donde se rechaza el acceso al recurso, debido a que las credenciales no se transmiten al servidor
B.
El mecanismo se resume en el siguiente esquema.
410
Problemática clásica del doble salto
En realidad, se trata de una limitación voluntaria por parte de Microsoft, que vela por mejorar la seguridad
protegiendo nuestras credenciales. En efecto, el servidor A no está autorizado a transmitir nuestras
credenciales para autenticarnos en otro recurso de red. La seguridad está bien, aunque nos complica un
poco la existencia...
Además, no existe, desafortunadamente, ninguna técnica sencilla que nos permita resolver este delicado
problema. A pesar de ello, vamos a estudiar varias soluciones que permiten superar este problema de una
manera más o menos interesante.
a. CredSSP
Preste atención, a menos que utilice CredSPP junto con SSL (consulte la sección WinRM en un entorno
fuera del dominio), CredSPP presenta riesgos importantes de seguridad. Le desaconsejamos su
implementación.
Para modificar este comportamiento y por lo tanto permitir el rebote con la misma cuenta de usuario, es
posible utilizar un proveedor de soporte de seguridad de información, también conocido con el nombre
CredSSP. Se introdujo CredSSP con Windows Vista/Server 2008, y lo utiliza PowerShell. Al principio,
CredSSP se utilizaba en los entornos de Terminal Server para simplificar el SSO. Desde entonces se
utiliza también como mecanismo para realizar un doble salto.
Doble salto con uso de la delegación de permisos CredSSP
Para que este proceso funcione, es necesario que el cliente y el servidor activen el mecanismo CredSSP.
Para activarlo del lado cliente (puesto de trabajo de Juan en nuestro ejemplo), debe utilizar el siguiente
comando:
PS > Enable­WSManCredSSP ­Role client ­DelegateComputer ServidorA
Y para el lado servidor (servidor A en nuestro ejemplo):
411
PS > Enable­WSManCredSSP ­Role Server ­Force
Observe que estos comandos deben ejecutarse en una sesión iniciada como administrador. Finalmente,
para que la delegación pueda llevarse a cabo, debe usar el parámetro ­Authentication CredSSP y
especificar obligatoriamente las credenciales.
PS > $cred = Get­Credential
PS > Invoke­Command ­ComputerName ServidorA ­Credential $cred `
­Authentication CredSSP `
­ScriptBlock {Get­Service ­ComputerName ServidorB}
Ventajas
Fácil implementación.
Funciona con todos los servidores desde Windows Server 2008.
Inconvenientes
El login y la contraseña se envían sin cifrar por la red.
Requiere la configuración de los roles cliente y servidor.
b. Doble autenticación
El hecho de reautenticarse en el recurso remoto permite realizar un doble salto sin tener que configurar
nada especial, ni del lado del cliente ni del lado del servidor, todo ello sin degradar la seguridad.
Imaginemos que queremos recuperar los parches de seguridad instalados en el servidor B (llamado aquí
Win10­1, pero que en realidad es una máquina cliente que funciona con Windows 10). Como vamos a
pasar por una máquina de rebote, el famoso servidor A (llamado aquí Win2016­2), vamos a resultar
bloqueados. Lo veremos a continuación:
PS > $cmd = { Get­Hotfix ­Computername Win10­1 }
PS > Invoke­Command ­ComputerName win2016­2 ­ScriptBlock $cmd
Access is denied. (Exception
+ CategoryInfo
:
UnauthorizedAccess
+ FullyQualifiedErrorId :
+ PSComputerName
:
from HRESULT: 0x80070005 (E_ACCESSDENIED))
NotSpecified: (:) [Get­HotFix],
System.UnauthorizedAccessException,Microsoft.Po...
win2016­2
afortunadamente el comando Get­HotFix comprende, además del parámetro ­
ComputerName, el parámetro ­Credential, este nos va a permitir pasarle las credenciales y, de
este modo, resolver el famoso problema del doble salto. No obstante, vamos a utilizar un pequeño truco:
la variable $Using que permitirá a nuestro bloque de script acceder a una variable situada fuera de su
ámbito.
Como
PS > $cred = Get­Credential
PS > $cmd = { Get­Hotfix ­Computername Win10­1 ­Credential $using:cred}
PS > Invoke­Command ­ComputerName win2016­2 ­ScriptBlock $cmd ­Cred $cred
Source Description
PSComputerName
­­­­­­ ­­­­­­­­­­­
­­­­­­­­­­­­­­
HotFixID
InstalledBy
InstalledOn
­­­­­­­­
­­­­­­­­­­­
­­­­­­­­­­­
412
WIN10­1 Security Update KB4048951 NT AUTHORITY\SYSTEM 11/19/2017 12:00:00
AM win2016­2
WIN10­1 Security Update KB4049179 NT AUTHORITY\SYSTEM 11/5/2017 12:00:00
AM win2016­2
WIN10­1 Security Update KB4074595 NT AUTHORITY\SYSTEM 2/22/2018 12:00:00
AM win2016­2
El inconveniente de nuestro ejemplo es que, si el comando de nuestro bloque de script no tuviera el
parámetro ­Credential, nos habríamos visto bloqueados.
Preste atención: El parámetro ­ComputerName del comando Get­Hotfix no existe en
PowerShell 6. Nuestra línea de comandos solo funcionará con las versiones anteriores de
PowerShell.
Por ejemplo, el acceso a un recurso compartido no funcionará. En el siguiente ejemplo vamos a intentar,
siempre desde el servidor A, acceder a los archivos del servidor B.
PS > $cmd = { Get­ChildItem ­Path \\Win10­1\c$ }
PS > Invoke­Command ­ComputerName Win2016­2 ­ScriptBlock $cmd ­Cred $cred
Access is denied
Cannot find path ’\\Win10­1\c$’ because it does not exist.
Vamos a mostrarle cómo podemos anidar un segundo comando
primero y, así, poder invocar cualquier comando.
Invoke­Command en el interior del
PS > $cmd = { icm ­ComputerName Win10­1 ­Script {dir c:\} ­Cred $using:cred }
PS > Invoke­Command ­ComputerName Win2016­2 ­ScriptBlock $cmd ­Cred $cred
Directory: C:\
Mode
LastWriteTime Length Name
PSComputerName
­­­­
­­­­­­­­­­­­­ ­­­­­­ ­­­­
­­­­­­­­­­­­­­
d­­­­­ 2/19/2018
2:26 AM
PerfLogs
Win2016­2
d­r­­­ 11/24/2017
2:55 PM
Program Files
Win2016­2
d­r­­­ 11/19/2017
5:20 PM
Program Files (x86) Win2016­2
d­r­­­ 2/24/2018
3:45 PM
Users
Win2016­2
d­­­­­ 2/22/2018 11:37 AM
Windows
Win2016­2
Hemos utilizado el alias icm del comando Invoke­Command para ahorrar espacio y poder, así,
imprimirlo todo en una única línea. Ocurre lo mismo con el parámetro ­Credential, que hemos
reducido a ­Cred.
Ventajas
No requiere ninguna configuración, ni del lado cliente ni del lado servidor,
Funciona con todas las versiones de PowerShell.
Inconvenientes
Hay que tener las ideas claras para escribir el código, pues resulta complejo debido a que deben
transmitirse de nuevo las credenciales mediante la variable $using.
413
c. Punto de terminación delegado (modo RunAs)
Podemos resolver la problemática del doble salto estableciendo una conexión sobre un punto de
terminación remoto (remote endpoint) que se ejecuta en su propio contexto de seguridad.
Para conocer los detalles de la implementación, consulte la sección Creación de una configuración de
sesión delegada (RunAs), más adelante en este capítulo.
Ventajas
Funciona con todas las versiones de PowerShell.
Inconvenientes
La creación de un punto de terminación puede resultar compleja.
Hay que gestionar la contraseña de la cuenta que sirve para que funcione el punto de terminación
delegado.
Las acciones remotas se realizan bajo la identidad del punto de terminación y no sobre la
nuestra.
d. Delegación Kerberos restringida basada en un recurso
Si su dominio posee al menos un controlador de dominio que funciona con Windows Server 2012, puede
habilitar la delegación Kerberos restringida sobre un recurso.
Vamos a configurar nuestro servidor B para que acepte únicamente credenciales que provengan el
servidor A.
He aquí cómo realizar la implementación (debe realizarse sobre una máquina que posea el módulo Active
Directory instalado):
# Inicialiación de las variables que representan las máquinas protagonistas
PS > $ServerA = Get­ADComputer ­Identity ServidorA
PS > $ServerB = Get­ADComputer ­Identity ServidorB
# Activación de la delegación Kerberos restringida
sobre el recurso ServidorB
PS > Set­ADComputer ­Identity $ServerB `
­PrincipalsAllowedToDelegateToAccount $ServerA
¡Y está, hecho! Fácil, ¿verdad?
A continuación, para comprobar que nuestra acción ha tenido el efecto deseado sobre la cuenta del
equipo del servidor B, podemos ejecutar las siguientes líneas:
# Comprobación del valor del atributo de manera directa
PS > $x = Get­ADComputer ­Identity $ServerB `
­Properties msDS­AllowedToActOnBehalfOfOtherIdentity
PS > $x.’msDS­AllowedToActOnBehalfOfOtherIdentity’.Access
# Comprobación del valor del atributo de manera indirecta
PS > Get­ADComputer ­Identity $ServerB `
­Properties PrincipalsAllowedToDelegateToAccount
414
Si intenta realizar una conexión con el servidor B desde el servidor A y esta falla, tendrá que vaciar
la caché del KDC para evitar tener que esperar 15 minutos antes de que la configuración de la
delegación Kerberos se tenga en cuenta. El vaciado de la caché del servidor A puede hacerse de la
siguiente manera:
PS > Invoke­Command ­ComputerName $ServerA.Name ­Credential $cred ­ScriptBlock {
klist purge ­li 0x3e7
}
Ahora podemos comprobar que la delegación Kerberos funciona; por ejemplo, verificando que el acceso a
un recurso compartido se realiza en un contexto de doble salto:
PS
PS
PS
PS
>
>
>
>
$ServerA = ’Win2016­2’
$ServerB = ’Win10­1’
$cmd = { Get­ChildItem ­Path \\$using:ServerB\c$ }
Invoke­Command ­ComputerName $ServerA ­ScriptBlock $cmd
Directory: \\Win10­1\c$
Mode
LastWriteTime Length
­­­­
­­­­­­­­­­­­­ ­­­­­­
d­­­­­ 2/19/2018
2:26 AM
d­r­­­ 11/24/2017
2:55 PM
d­r­­­ 11/19/2017
5:20 PM
d­r­­­ 2/24/2018
3:45 PM
d­­­­­ 2/22/2018 11:37 AM
Name
PSComputerName
­­­­
­­­­­­­­­­­­­­
PerfLogs
Win2016­2
Program Files
Win2016­2
Program Files (x86) Win2016­2
Users
Win2016­2
Windows
Win2016­2
La delegación Kerberos funciona perfectamente, como puede comprobar, de modo que no necesitamos
tener que volver a pasar las credenciales. Observe que lo que acabamos de hacer mediante el comando
Invoke­Command podría haberse hecho también en una sesión interactiva a distancia abierta
mediante
Enter­PSSession.
Ventajas
Fácil de implementar.
Funciona dentro de un mismo dominio o entre dominios de un mismo bosque.
Todos los administradores autorizados a conectarse al servidor A tendrán permisos para
administrar el servidor B, pues la delegación se da a una cuenta de equipo.
Inconvenientes
Cada dominio debe poseer al menos un controlador de dominio con Windows Server 2012 como
mínimo.
7. Gestión de las configuraciones de sesiones
a. Generalidades
Como habrá podido observar, el comando Enable­PSRemoting realiza ciertas acciones como la
creación de los listeners WinRM, la configuración del cortafuegos y el arranque del servicio WinRM. ¡Pero
no es todo! Guarda configuraciones de sesiones en el equipo en el que se ejecuta. Una configuración de
415
sesión es un punto de acceso sobre el que un equipo o usuario remoto puede conectarse. El término
inglés para designar una configuración de sesión es endpoint.
Una configuración de sesión es un grupo de parámetros que define el entorno de la sesión PowerShell
cuando un usuario remoto se conecta.
Es posible crear configuraciones de sesión más o menos restrictivas en términos de seguridad según las
necesidades del momento. Podemos, por ejemplo, en una configuración de sesión definir el modo del
lenguaje (completo o restringido) autorizado, especificar los comandos, proveedores y funciones que
estarán disponibles en la sesión, limitar el volumen de datos intercambiados, etc.
Por defecto, solo los miembros del grupo Administradores y los miembros del grupo Usuarios
administración remota disponen de la autorización para conectarse remotamente.
Cuando abrimos una sesión PowerShell remota sin precisar ninguna configuración de sesión particular
entonces usamos la configuración por defecto llamada microsoft.powershell. Esta configuración
de sesión abre un proceso de 64 bits. Si necesita abrir un proceso de 32 bits, para por ejemplo cargar un
snap­in de 32 bits, entonces deberá conectarse de manera explícita a la configuración de sesión llamada
microsoft.powershell32.
Recordatorio: la comunicación remota de PowerShell está activada por defecto en Windows Server
2012/R2 cuando el equipo pertenece a un dominio Active Directory Domain Services.
A continuación puede ver el detalle de lo que ocurre cuando activamos manualmente la comunicación
remota de PowerShell en un equipo, por ejemplo un equipo con Windows 10.
PS > Enable­PSRemoting
... Confirmar
¿Está seguro de que desea realizar esta acción?
Se está realizando la operación « Set­PSSessionConfiguration » en el destino
« Nombre:
microsoft.powershell SDDL:
O:NSG:BAD:P(A;;GA;;;BA)(A;;GA;;;RM)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD).
Esto autorizará a los usuarios seleccionados para la ejecución remota
de comandos Windows PowerShell en este equipo ».
[S] Sí [O] Sí a todo [N] No [T] No a todo [U] Suspender
[?] Ayuda (el valor predeterminado es "S"):
Confirmar
¿Está seguro de que desea realizar esta acción?
Se está realizando la operación « Set­PSSessionConfiguration » en el destino
« Nombre: microsoft.powershell.workflow
SDDL :O:NSG:BAD:P(A;;GA;;;BA) (A;;GA;;;RM)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD).
Esto autorizará a los usuarios seleccionados para la ejecución remota
de comandos Windows PowerShell en este equipo ».
[S] Sí [O] Sí a todo [N] No [T] No a todo [U] Suspender
[?] Ayuda (el valor predeterminado es "S"):
Confirmar
¿Está seguro de que desea realizar esta acción?
Se está realizando la operación « Set­PSSessionConfiguration » en el destino
« Nombre: microsoft.powershell32
SDDL :O:NSG:BAD:P(A;;GA;;;BA)
(A;;GA;;;RM)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD).
Esto autorizará a los usuarios seleccionados para la ejecución remota
de comandos Windows PowerShell en este equipo ».
416
[S] Sí [O] Sí a todo [N] No [T] No a todo
[?] Ayuda (el valor predeterminado es "S"):
[U] Suspender
Confirmar
¿Está seguro de que desea realizar esta acción?
Se está realizando la operación « Set­PSSessionConfiguration » en el destino
« Nombre: microsoft.windows.servermanagerworkflows SDDL :
O:NSG:BAD:P(A;;GA;;;BA)(A;;GA;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD).
Esto autorizará a los usuarios seleccionados para la ejecución remota
de comandos Windows PowerShell en este equipo ».
[S] Sí [O] Sí a todo [N] No [T] No a todo [U] Suspender
[?] Ayuda (el valor predeterminado es "S"):
Enable­PSRemoting puede también utilizarse para recrear escenarios de configuración por
defecto. Así, si se ha equivocado en los permisos o si ha eliminado por error una configuración
de sesión por defecto, puede restablecer la configuración de origen gracias a este comando. Las
configuraciones de sesión de PowerShell se utilizan cuando usa un comando con el parámetro ­
Session, es decir con los siguientes comandos: Connect­PSSession, Disconnect­
PSSession, Enter­PSSession, Export­PSSession, Import­PSSession, Invoke­
Command, New­PSSession, Receive­Job, Receive­PSSession, Remove­PSSession.
Los comandos de gestión de configuraciones son los siguientes, se aplican siempre localmente:
Comando
Descripción
Register­PSSessionConfiguration
Registra una configuración de sesión
en el equipo local.
Unregister­PSSessionConfiguration
Elimina una configuración de sesión.
Get­PSSessionConfiguration
Obtiene las configuraciones de sesión
inscritas.
Set­PSSessionConfiguration
Modifica las propiedades de
configuración de sesión inscrita.
Enable­PSSessionConfiguration
Activa una configuración de sesión.
Disable­PSSessionConfiguration
Rechaza
el
acceso
configuración de sesión.
New­PSSessionConfigurationFile
Crea un archivo que representa una
configuración de sesión.
Test­PSSessionConfigurationFile
Verifica un archivo de configuración de
sesión.
a
una
una
b. Configuraciones de sesión por defecto
PowerShell incluye varias configuraciones de sesión que pueden diferir dependiendo del sistema
operativo y la versión de PowerShell utilizada.
417
Configuraciones de sesión por defecto con Windows PowerShell
Descripción
Configuración de sesión
microsoft.powershell
Sesión de configuración
por defecto.
microsoft.powershell32
Sesión de configuración de
32 bits. Esta sesión solo
está
presente
en los
equipos
con
sistemas
operativos de 64 bits.
microsoft.powershell.workflow
Sesión de configuración
usada por los workflows.
microsoft.windows.servermanagerworkflows
Sesión de configuración
usada
por
el
rol
«administrador
de
workflows». Este rol solo
está
presente
en
Windows
Server
2012/R2 y superior.
Aquí tiene el resultado del comando
Server 2016 instalado:
Get­PSSessionConfiguration en un equipo con Windows
PS > Get­PSSessionConfiguration
Name
: microsoft.powershell
PSVersion
: 5.1
StartupScript :
RunAsUser
:
Permission
: NT AUTHORITY\INTERACTIVE AccessAllowed,
BUILTIN\Administrators
AccessAllowed, BUILTIN\Remote Management Users AccessAllowed
Name
: microsoft.powershell.workflow
PSVersion
: 5.1
StartupScript :
RunAsUser
:
Permission
: BUILTIN\Administrators AccessAllowed, BUILTIN\
Remote Management
Users AccessAllowed
Name
: microsoft.powershell32
PSVersion
: 5.1
StartupScript :
RunAsUser
:
Permission
: NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\
Administrators
AccessAllowed, BUILTIN\Remote Management Users AccessAllowed
Name
: microsoft.windows.servermanagerworkflows
PSVersion
: 3.0
StartupScript :
RunAsUser
:
Permission
: NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\
Administrators
AccessAllowed
418
Configuraciones de sesión por defecto con PowerShell Core
PowerShell Core posee una única configuración de sesión por defecto.
Esta lleva en su nombre la versión en curso de PowerShell. En el siguiente ejemplo, como utilizamos la
versión 6.0.1, existe una configuración de sesión llamada «PowerShell.v6.0.1».
Es preciso ejecutar el comando Enable­PSRemoting antes de intentar mostrar las
configuraciones de sesión por defecto. En efecto, este será el comando que la creará, además de
hacer el resto (arrancar el servicio WinRM, etc.).
He aquí el resultado de la ejecución del comando
Windows Server 2016:
Get­PSSessionConfiguration sobre un OS
PS > Get­PSSessionConfiguration
Name
: PowerShell.v6.0.1
PSVersion
: 6.0
StartupScript :
RunAsUser
:
Permission
: NT AUTHORITY\INTERACTIVE AccessAllowed,
BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management
Users AccessAllowed
Variable $PSSessionConfigurationName
El
nombre
de
la
configuración
de
sesión
por
defecto
se
almacena
en
la
variable
$PSSessionConfigurationName.
c. Modificación de permisos
Por defecto, solo los miembros del grupo Administradores y los miembros del grupo Usuarios
administración remota pueden conectarse a los endpoints.
Dicho esto, podemos fácilmente modificar los permisos de un endpoint gracias al comando Set­
PSSessionConfiguration asociado con uno de sus parámetros ­SecurityDescriptorSddl o
­ShowSecurityDescriptorUI. Este último es muy práctico ya que muestra una ventana gráfica
para poder ver las ACL. Lo cual resulta muy práctico y sencillo; ¡parece que la interfaz gráfica algunas
veces sigue teniendo aspectos positivos!
Ejemplo
Modificación de las ACL de una configuración de sesión built­in.
PS > Set­PSSessionConfiguration ­Name microsoft.powershell ­ShowSecurityDescriptorUI
ADVERTENCIA: Set­PSSessionConfiguration reinicia el servicio WinRM y todos
los servicios dependientes.
Se desconectarán todas las sesiones de WinRM conectadas a configuraciones de
sesión de Windows PowerShell, como Microsoft.PowerShell y configuraciones
de sesión creadas con el cmdlet Register­PSSessionConfiguration.
419
Confirmar
¿Está seguro de que desea realizar esta acción?
Se está realizando la operación "Set­PSSessionConfiguration" en el destino
"Nombre: microsoft.powershell".
[S] Sí [O] Sí a todo [N] No [T] No a todo [U] Suspender [?] Ayuda
(el valor predeterminado es "S"):
Cuando modificamos los permisos de una configuración de sesión, Windows PowerShell debe reiniciar el
servicio WinRM para que las modificaciones sean efectivas.
Si elige continuar seleccionando «S» por Sí o validando simplemente con la tecla [Enter], llegamos a la
pantalla siguiente que permite configurar los permisos.
Modificación de las ACL de la sesión de configuración Microsoft PowerShell
Evidentemente, en este caso, en vez de modificar los permisos sería preferible añadir un miembro al
grupo Administradores o bien al grupo Usuarios de administración remota. Observe que este último se
introdujo con Windows Server 2012/Windows 8.
d. Creación de una configuración de sesión personalizada
Puede resultar interesante crear un endpoint en un servidor para, por ejemplo, limitar el acceso a ciertos
comandos a un grupo de usuarios como el proveedor externo a la empresa para limitar los riesgos. Así,
este último podrá verse atribuidos los permisos y comandos necesarios para la estricta realización de su
trabajo.
420
Para más detalles sobre este tema, consulte el capítulo Casos de estudio, sobre todo la
sección Delegar la gestión de un servidor.
Creación de un archivo de configuración
Lo primero que debemos hacer, para crear una configuración de sesión personalizada, es crear un
archivo de configuración, archivo que importaremos en una segunda fase para crear el endpoint.
La
creación
de
un
archivo
de
configuración
se
realiza
con
el
comando
New­
PSSessionConfigurationFile.
A continuación puede ver la sintaxis de este comando:
New­PSSessionConfigurationFile [­Path] <String> [­AliasDefinitions
<Hashtable[]>] [­AssembliesToLoad <String[]>] [­Author <String>]
[­CompanyName <String>] [­Copyright <String>] [­Description <String>]
[­EnvironmentVariables <Object>] [­ExecutionPolicy <ExecutionPolicy>]
[­FormatsToProcess <String[]>] [­FunctionDefinitions <Hashtable[]>]
[­Guid <Guid>] [­LanguageMode <PSLanguageMode>] [­ModulesToImport
<Object[]>] [­PowerShellVersion <Version>] [­SchemaVersion <Version>]
[­ScriptsToProcess <String[]>] [­SessionType <SessionType>]
[­TypesToProcess <String[]>] [­VariableDefinitions <Object>]
[­VisibleAliases <String[]>] [­VisibleCmdlets <String[]>]
[­VisibleFunctions <String[]>] [­VisibleProviders <String[]>]
[<CommonParameters>]
Este comando posee numerosos parámetros que permiten, entre otras cosas:
Cambiar de assemblies en el arranque o de módulos específicos para extender el conjunto de
comandos.
Configurar la visibilidad de ciertos elementos como los proveedores, los alias, las funciones y los
comandos.
Definir el nivel de funcionalidad deseado del lenguaje PowerShell y de la sesión (consulte la
siguiente tabla con los parámetros ­SessionType y ­LanguageMode).
Definir la estrategia de ejecución de la sesión.
Definir variables.
Definir ciertos metadatos como el nombre del autor de la configuración, su empresa así como un
copyright y una descripción.
Cargar scripts en el arranque de la sesión.
Esta última funcionalidad, la carga de scripts en el arranque de la sesión, es también un método muy
potente para configurar la sesión.
El parámetro
­SessionType acepta los siguientes valores:
421
Valor posible para el parámetro
­SessionType
Empty
Descripción
No se añade ningún módulo ni snap­in a la sesión.
Esta opción está pensada para limitar al máximo su
uso a los módulos, funciones y scripts que habrá
autorizado con los parámetros adecuados previstos a
este efecto.
Si no añadimos nada a una sesión vacía, un usuario
que
se
conecta
con
ella
no
podrá
hacer
absolutamente nada.
Default
Añade
a
la
sesión
el
snap­in
básico
Microsoft.PowerShell.core. Este último contiene los
comandos Import­Module y Add­PSSnapin, por
lo tanto un usuario puede utilizarlos para cargar otros
módulos.
Es posible sin embargo prohibir explícitamente el uso
de estos comandos.
RestrictedRemoteServer
Exit­
PSSession, Get­Command, Get­FormatData,
Get­Help, Measure­Object, Out­Default y
Select­Object.
Deberá
utilizar
otros
parámetros
de
New­
PSSessionConfigurationFile para añadir
Incluye únicamente los siguientes comandos:
módulos, funciones, scripts y otras funcionalidades a
la sesión.
El parámetro
­LanguageMode acepta los siguientes valores:
Valor posible para el
parámetro ­
Descripción
LanguageMode
FullLanguage
Todos los elementos del lenguaje están permitidos.
NoLanguage
Los usuarios pueden utilizar comandos y funciones pero no
están autorizados a usar elementos del lenguaje como
bloques de scripts, variables y operadores.
RestrictedLanguage
Los usuarios pueden utilizar comandos y funciones pero no
están autorizados a usar elementos del lenguaje como
bloques de script, variables salvo las variables siguientes:
$PSCulture, $PSUICulture, $True, $False y $Null.
Los usuarios no pueden utilizar los operadores simples que
siguen: ­eq, ­gt, ­lt. Las asignaciones, referencias de
propiedades y las llamadas a métodos no están autorizadas.
El valor por defecto del parámetro ­LanguageMode
depende del valor del parámetro ­SessionType:
+ Empty
+ RestrictedRemoteServer
+ Default
:
:
:
NoLanguage
NoLanguage
FullLanguage
422
En el ejemplo siguiente vamos a definir una configuración de sesión para el grupo de usuarios del
HelpDesk y solo le daremos permisos para utilizar algunos comandos, entre ellos los que permiten
arrancar un servicio. Con nuestra gran bondad, le daremos también acceso a Get­Service para
verificar los servicios en ejecución, así como a Get­Command para enumerar los comandos disponibles
de la sesión:
PS > New­PSSessionConfigurationFile ­Path $home\services.pssc `
­SessionType Default `
­VisibleCmdlets @(’Get­Service’,
’Start­Service’,
’Get­Command’)
Acabamos de crear el archivo correspondiente a nuestra configuración de sesión. Podemos abrirlo para
ver su contenido:
@{
# Número de versión del esquema utilizado para este archivo de configuración
SchemaVersion = ’1.0.0.0’
# ID utilizado para identificar de manera única esta configuración de sesión
GUID = ’ec02c5a4­2a95­4d34­9f9b­7d5bb412fb34’
# Especifica la estrategia de ejecución para esta configuración de sesión
ExecutionPolicy = ’Restricted’
# Especifica el modo de idioma de esta configuración de sesión
LanguageMode = ’FullLanguage’
# Estado inicial de esta configuración de sesión
SessionType = ’Default’
# Variables de entorno definidas en esta configuración de sesión
# EnvironmentVariables =
# Autor de esta configuración de sesión
Author = ’administrador’
# Company associated with this session configuration
CompanyName = ’Desconocido’
# Instrucción de copyright para esta configuración de sesión
Copyright = ’(c) 2013 administrador. Todos los derechos reservados.’
# Descripción de las funcionalidades provistas por esta configuración de sesión
# Description =
# Versión del motor Windows PowerShell utilizado para esta configuración de sesión
# PowerShellVersion =
# Módulos a importar
# ModulesToImport =
# Assemblys que se cargarán en esta configuración de sesión
# AssembliesToLoad =
# Alias visibles en esta configuración de sesión
# VisibleAliases =
423
# Applets de comando visibles en esta configuración de sesión
VisibleCmdlets = ’Get­Service’, ’Start­Service’, ’Get­Command’
# Funciones visibles en esta configuración de sesión
# VisibleFunctions =
# Proveedores visibles en esta configuración de sesión
# VisibleProviders =
# Alias definidos en esta configuración de sesión
# AliasDefinitions =
# Funciones definidas en esta configuración de sesión
# FunctionDefinitions =
# Variables definidas en esta configuración de sesión
# VariableDefinitions =
# Archivos de tipo (.ps1xml) que seran cargados en esta configuración de sesión
# TypesToProcess =
# Archivos con formato (.ps1xml) a cargar en esta configuración de sesión
# FormatsToProcess =
# Especifica los scripts a ejecutar una vez la sesión configurada
# ScriptsToProcess =
}
El archivo de configuración es en realidad una gran tabla de hash. La etapa siguiente consiste en
guardar este archivo en el sistema. Dicho de otro modo crear el endpoint. Daremos además un nombre a
este último al crearlo.
Creación del endpoint / Importación del archivo de configuración
Ahora, es tiempo de registrar la configuración de sesión. Esto se realiza gracias al comando Register­
PSSessionConfiguration. Es ahora cuando daremos un nombre a nuestro endpoint; le llamaremos
para el ejemplo
ServiceMgmt, como podemos ver a continuación:
PS > Register­PSSessionConfiguration ­Name ServiceMgmt ­Path $home\services.pssc
­force
WSManConfig : Microsoft.WSMan.Management\WSMan::localhost\Plugin
Type
­­­­
Container
Keys
­­­­
{Name=ServiceMgmt}
Name
­­­­
ServiceMgmt
Nuestra configuración de sesión está ahora creada, pero solo los Administradores y los miembros del
grupo Usuarios administración remota pueden conectarse por el momento. Verifiquémoslo con la línea
de comandos siguiente:
424
PS > Get­PSSessionConfiguration ­Name ServiceMgmt | Format­List *
Copyright
CompanyName
GUID
Author
VisibleCmdlets
SessionType
ExecutionPolicy
SchemaVersion
LanguageMode
Architecture
Filename
ResourceUri
MaxConcurrentCommandsPerShell
Capability
xmlns
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
MaxConcurrentUsers
Name
SupportsOptions
ProcessIdleTimeoutSec
ExactMatch
ConfigFilePath
:
:
:
:
:
:
(c) 2018 administrador. Todos los derechos reservados.
Desconocido
ec02c5a4­2a95­4d34­9f9b­7d5bb412fb34
administrador
{Get­Service, Start­Service, Get­Command}
Default
Restricted
1.0.0.0
FullLanguage
64
%windir%\system32\pwrshplugin.dll
http://schemas.microsoft.com/powershell/ServiceMgmt
1000
{Shell}
http://schemas.microsoft.com/wbem/wsman/1/config/
PluginConfiguration
5
ServiceMgmt
true
0
true
C:\Windows\System32\WindowsPowerShell\v1.0\Session
Config\ServiceMgmt_ec02c5a4­2a95­4d34­9f9b­
7d5bb412fb34.
pssc
RunAsUser
:
IdleTimeoutms
: 7200000
OutputBufferingMode
: Block
PSVersion
: 3.0
SecurityDescriptorSddl
:
O:NSG:BAD:P(A;;GA;;;BA)(A;;GA;;;RM)S:P(AU;FA;GA;;;WD) (AU;SA;GXGW;;;WD)
MaxShellsPerUser
: 25
AutoRestart
: false
MaxShells
: 25
MaxIdleTimeoutms
: 43200000
Uri
: http://schemas.microsoft.com/powershell/ServiceMgmt
SDKVersion
: 2
XmlRenderingType
: text
RunAsPassword
:
MaxProcessesPerShell
: 15
ParentResourceUri
: http://schemas.microsoft.com/powershell/ServiceMgmt
Enabled
: True
UseSharedProcess
: false
MaxMemoryPerShellMB
: 1024
lang
: fr­FR
Permission
: BUILTIN\Administradores AccessAllowed, BUILTIN\
Usuarios administración remota AccessAllowed
Configuración de los permisos
La última etapa antes de poder acceder al endpoint: la configuración de las ACL. La manera más sencilla
de configurarlos es mediante la interfaz gráfica; pero sepa que si debe automatizar esta tarea, el
parámetro ­SecurityDescriptorSddl es el más apropiado.
425
PS > Set­PSSessionConfiguration ­Name ServiceMgmt ­ShowSecurityDescriptorUI
Configuración de las ACL de un endpoint personalizado
Acabamos de dar control total al grupo HelpDesk. Esperemos que sepan dar buen uso a esta
configuración de sesión... Para llevar el ejemplo al límite, deberíamos procurar que los miembros de este
grupo no sean miembros del grupo Administradores del equipo y delegar la gestión de ciertos servicios
a este grupo. Esta última acción puede hacerse fácilmente con las GPO o con la herramienta
Subinacl.exe del Ressource Kit de Windows.
Solamente queda comprobar el correcto funcionamiento de nuestro endpoint.
Conexión al endpoint para verificar su buen funcionamiento
A continuación, iniciamos una sesión de Windows con un usuario miembro del grupo HelpDesk (desde un
equipo miembro del mismo dominio que el servidor en el que se ha creado el endpoint).
Después abrimos una consola PowerShell y probamos las siguientes líneas:
426
PS > $s = New­PSSession ­Computername ws2012es­2 ­ConfigurationName ServiceMgmt
PS > Invoke­Command $s ­ScriptBlock {Get­Command ­Type cmdlet}
CommandType
­­­­­­­­­­­
Cmdlet
Cmdlet
Cmdlet
Name
­­­­
Get­Command
Get­Service
Start­Service
ModuleName
­­­­­­­­­­
Microsoft.PowerShell.Core
Microsoft.PowerShell.Management
Microsoft.PowerShell.Management
PSComputerName
­­­­­­­­­­­­­­
ws2012es­2
ws2012es­2
ws2012es­2
Hemos creado una sesión PowerShell conectada a nuestra configuración de sesión llamada
ServiceMgmt, después hemos enviado el comando dentro de un bloque de script. Este es uno de los
tres comandos autorizados anteriormente; su rol es evidentemente recuperar la lista de todos los
comandos de la sesión. Como podemos ver, solo vemos los tres comandos; ¡lo cual resulta excelente!
Veamos qué pasa si intentamos enviar otro comando:
PS > Invoke­Command $s ­ScriptBlock {Get­Process}
Get­Process: El término ’ Get­Process ’ no se reconoce como nombre de
un cmdlet, función, archivo de script o programa ejecutable.
Compruebe si escribió correctamente el nombre o, si incluyó una ruta
de acceso, compruebe que dicha ruta es correcta e inténtelo de nuevo.
En línea: 1 Carácter: 1
+ Get­Process
+ ~~~
+ CategoryInfo
: ObjectNotFound: (hjk:String) [],
CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName
: ws2012es­2
Como podíamos esperar, no podemos ejecutar otros comandos salvo los definidos en la creación del
archivo de configuración de sesión.
e. Creación de una configuración de sesión delegada (RunAs)
Es posible definir una configuración de sesión delegada, es decir que se ejecuta con una cuenta distinta
a la utilizada para conectarse. Para ello basta, al almacenar la sesión, con utilizar el parámetro ­
RunAsCredential y especificar los credentials con los que el endpoint debe ejecutarse. Aquí tiene los
numerosos
parámetros
que
se
pueden
especificar
con
el
comando
Register­
PSSessionConfiguration:
Register­PSSessionConfiguration [­Name] <String> [­AccessMode
<PSSessionConfigurationAccessMode>] [­Force [<SwitchParameter>]]
[­MaximumReceivedDataSizePerCommandMB <Double>]
[­MaximumReceivedObjectSizeMB <Double>] [­NoServiceRestart
[<SwitchParameter>]] [­ProcessorArchitecture <String>]
[­RunAsCredential <PSCredential>] [­SecurityDescriptorSddl <String>]
[­ShowSecurityDescriptorUI [<SwitchParameter>]] [­StartupScript
<String>] [­ThreadApartmentState <ApartmentState>] [­ThreadOptions
<PSThreadOptions>] [­TransportOption <PSTransportOption>]
[­UseSharedProcess [<SwitchParameter>]] ­Path <String>
[­Confirm [<SwitchParameter>]] [­WhatIf [<SwitchParameter>]]
[<CommonParameters>]
427
Crearemos para el ejemplo un pequeño script de arranque que se ejecutará al iniciar la sesión. Este nos
deseará la bienvenida y nos dará cierta información relativa a la sesión cuando nos conectemos al
endpoint. Nos conectaremos desde un equipo Windows 8 a un servidor Windows Server 2012 con una
cuenta del dominio con privilegios de usuario simple.
Esto da como resultado el siguiente script:
# EndPointStartupScript.ps1
$msgInicio = @"
#*********************************************************************#
Hola y bienvenido a $($env:COMPUTERNAME)
Está conectado como: $($env:USERDOMAIN + ’\’ + $env:USERNAME)
#*********************************************************************#
"@
Write­Host $msgInicio ­ForegroundColor Yellow
Veamos un resumen de las acciones a realizar del lado servidor:
Guardar la configuración de la sesión delegada.
Configurar las ACL en la configuración de sesión.
# 1º etapa: guardar la configuración de sesión
#
e indicar las credenciales Administrador
PS > Register­PSSessionConfiguration ­Name DelegatedEndpoint `
­StartupScript $home\EndPointStartupScript.ps1 `
­RunAsCredential (Get­Credential)
# 2º etapa: configurar las ACL
PS > Set­PSSessionConfiguration ­Name DelegatedEndPoint ­ShowSecurityDescriptorUI
Como en la anterior sección, debemos dar permisos a un usuario para autorizar su conexión en la
configuración de sesión.
Los permisos mínimos y suficientes para permitir a un usuario conectarse a una configuración de
sesión son de lectura (get, enumerate, subscribe) y ejecución (invoke).
Ahora solo queda probar la conexión a nuestro endpoint delegado. Para ello, nos conectaremos desde
un equipo Windows 8 con la cuenta de usuario «Guttenberg»:
428
Conexión a una configuración de sesión que se ejecuta con credenciales alternativas
Podemos observar las siguientes cosas:
Nos hemos conectado con la cuenta de usuario «Guttenberg» para abrir una sesión PowerShell
en el equipo WS2012ES­1 especificando el endpoint «DelegatedEndpoint».
El script de arranque se ha ejecutado correctamente. Nos indica que nos hemos
conectado correctamente y que nuestra identidad es ahora la del administrador del dominio
EDICIONES­ENI.
Hemos enviado un comando remoto al equipo WS2012ES­2 sin ningún problema con la seguridad
ligada al doble salto.
En el caso de la creación de una configuración de sesión delegada, no nos atañe la problemática
del «doble salto». En efecto, es la cuenta que permite «ejecutar» el endpoint la que realiza la
autenticación en otro equipo de la red, así no necesitamos transmitir la integridad de nuestro ticket
Kerberos con el mecanismo CredSSP.
8. Sesiones remotas
Hemos visto anteriormente cómo conectarse a una sesión PowerShell remota, pero no hemos tomado el
tiempo necesario para explicar el funcionamiento. Aquí tiene por lo tanto algunas explicaciones
complementarias…
Antes de poder enviar órdenes a un equipo remoto, es necesario establecer una sesión remota.
Una sesión remota puede ser:
429
Temporal: una sesión temporal se establece justo durante el tiempo del envío de un comando con
Invoke­Command o Enter­PSSession. Tal es el caso cuando utilizamos el parámetro ­
ComputerName.
Permanente: una sesión «permanente» persiste en el tiempo de la sesión PowerShell. Una sesión
permanente es útil en los casos donde debemos ejecutar varios comandos que comparten datos
mediante variables o funciones. Creamos una sesión permanente cuando utilizamos el parámetro ­
Session de los comandos Invoke­Command o Enter­PSSession.
La creación de una conexión permanente a un equipo local o remoto se realiza con el comando
New­
PSSession. Observemos sus parámetros en la siguiente tabla:
Parámetro
Descripción
­AllowRedirection <Switch>
Autoriza la redirección de esta conexión hacia
otra URI (Uniform Resource Identifier).
­ApplicationName <String>
Especifica el segmento del nombre de aplicación
en la URI de conexión.
­Authentication
<AuthenticationMechanism>
Especifica el mecanismo que permite autenticar
la información de identificación del usuario
(credenciales).
­CertificateThumbprint
<String>
Especifica el certificado digital de clave pública
(X509) de una cuenta de usuario que posee la
autorización de ejecutar esta acción.
­ComputerName <String[]>
Crea una conexión
especificado.
­ConfigurationName <String>
Especifica la configuración de la sesión utilizada
para la nueva sesión PSSession.
­ConnectionURI <Uri[]>
Especifica una URI que define el punto de
finalización de la conexión.
­Credential <PSCredential>
Especifica una cuenta de usuario que posee la
autorización para ejecutar esta acción.
­EnableNetworkAccess
<Switch>
Configura un token de seguridad que, en el
marco de una sesión de bucle (o loopback),
permite ejecutar comandos que obtienen datos
de otros equipos. Este parámetro, introducido
con PowerShell 3.0, no tiene ningún impacto si
no se encuentra en el caso de una sesión de
bucle. Una sesión de bucle es una sesión que
empieza y termina en el mismo sistema. Es el
caso
cuando
el
parámetro
­
ComputerName tiene el valor . o Localhost.
­Name <String[]>
Especifica un nombre que se puede retener para
la sesión PSSession.
­Port <Int>
Especifica el puerto del equipo remoto usado
para este comando.
­Session <PSSession[]>
Utiliza la sesión PSSession especificada como
modelo para la nueva sesión PSSession.
permanente
al
equipo
430
Descripción
Parámetro
­SessionOption
<PSSessionOption>
Define opciones avanzadas para la sesión.
­ThrottleLimit <Int>
Especifica el número máximo de conexiones
simultáneas que se pueden establecer para
ejecutar este comando.
­UseSSL <Switch>
Utiliza el protocolo Secure Socket Layer con
HTTPS para establecer la conexión. Por defecto,
no se utiliza SSL.
Aquí tiene algunos ejemplos de uso de
New­PSSession:
Ejemplo 1
Creación de una sesión en el equipo local y almacenamiento de la referencia del objeto PSSession en la variable
$session.
PS > $session = New­PSSession
Ejemplo 2
Creación de una sesión en el equipo remoto «Win2k12» y almacenamiento de la referencia del objeto PSSession
en la variable $session.
PS > $session = New­PSSession ­ComputerName Win2k12
Ejemplo 3
Creación de una sesión en el equipo remoto «Win2k12» especificando credenciales
almacenamiento de la referencia del objeto PSSession en la variable $session.
alternativas
y
PS > $cred = Get­Credential
PS > $session = New­PSSession ­ComputerName Win2k12 ­Credential $cred
Ejemplo 4
Creación de múltiples sesiones en una lista de equipos remotos especificando un valor de conexiones máximas
simultáneas igual a 50.
PS > $machines = Get­Content C:\Temp\machines.txt
PS > $sessions = New­PSSession ­ComputerName $machines ­ThrottleLimit 50
9. Ejecución de comandos remotos
¡Ya entramos en el quid de la cuestión! El comando necesario para la ejecución de comandos remotos es
Invoke­Command. Este posee numerosos parámetros que abordaremos un poco más adelante.
Como decíamos en la sección anterior, una sesión puede ser temporal o permanente. Por lo tanto
podemos elegir entre una u otra en función de la necesidad del momento. Para el envío puntual de un
comando remoto es más sencillo usar una sesión temporal mientras que, si desea enviar una sucesión de
comandos, será más cómodo establecer una sesión permanente.
Para ilustrar estos propósitos, veamos algún caso de uso:
431
Ejemplo 1
Recuperación de una variable de entorno en un ordenador remoto.
PS > Invoke­Command ­ComputerName Win2k12 ­ScriptBlock { $env:PROCESSOR_IDENTIFIER }
Intel64 Family 6 Model 37 Stepping 5, GenuineIntel
Ninguna complicación particular, especificamos el nombre del equipo en el parámetro
ejecutamos el bloque de script pasado entre llaves al parámetro ­ScriptBlock.
­ComputerName y
Ejemplo 2
Enumerar los servicios suspendidos en varios equipos.
PS > $cmde = { Get­Service | Where {$_.Status ­eq ’paused’} }
PS > Invoke­Command ­ComputerName Win2k12, localhost ­ScriptBlock $cmde
Status
­­­­­­
Paused
Paused
Name
­­­­
vmictimesync
Winmgmt
DisplayName
­­­­­­­­­­­
Servicio Sincronización fecha/hora...
Infraestructura de gestión Windows
PSComputerName
­­­­­­­­­­­­­­
Win2k12
localhost
Vea con qué facilidad hemos podido ejecutar un pequeño bloque de script en una lista de equipos. Tenga
en cuenta el uso de la propiedad PSComputerName. Esta resulta muy práctica ya que nos indica el
nombre del equipo al cual pertenece el resultado.
Ahora estableceremos una sesión permanente en el equipo «Win2k12» y veremos qué más podemos
hacer con respecto a una sesión temporal.
Ejemplo 3
Cambiar el estado de un servicio de «Paused» al estado «Running».
Primero crearemos una sesión remota:
PS > $pssession = New­PSSession ­ComputerName Win2k12
PS > $pssession
Id Name
­­ ­­­­
1 Session1
ComputerName
­­­­­­­­­­­­
Win2k12
State
­­­­­
Opened
ConfigurationName
­­­­­­­­­­­­­­­­­
Microsoft.PowerShell
Availability
­­­­­­­­­­­­
Available
Después usaremos la sesión para conectarnos al equipo remoto:
PS > Invoke­Command ­Session $pssession ­ScriptBlock {$s = Get­Service Winmgmt}
Ya hemos recuperado en la variable
$s el objeto correspondiente al servicio «Winmgmt».
Probemos a manipular este servicio para sacarlo de su estado de pausa:
PS > Invoke­Command ­Session $pssession ­ScriptBlock {$s}
Status
­­­­­­
Paused
Name
­­­­
Winmgmt
DisplayName
­­­­­­­­­­­
Infraestructura de gestión Windows
PSComputerName
­­­­­­­­­­­­­­
Win2k12
432
Llamemos al método
Continue() para modificar el estado del servicio:
PS > Invoke­Command ­Session $pssession ­ScriptBlock {$s.continue()}
Después al método
Refresh() para ver si ha ocurrido algo:
PS > Invoke­Command ­Session $pssession ­ScriptBlock {$s.refresh()}
Volvemos a llamar la variable
Status
­­­­­­
Running
Name
­­­­
Winmgmt
$s para ver su contenido:
DisplayName
­­­­­­­­­­­
Infraestructura de gestión Windows
PSComputerName
­­­­­­­­­­­­­­
Win2k12
¡Perfecto ha funcionado!
Ahora probemos si podemos hacer lo mismo usando una sesión temporal, es decir sin precisar la sesión.
PS > Invoke­Command ­ComputerName Win2k12 ­ScriptBlock {$s = Get­Service Winmgmt }
PS > Invoke­Command ­ComputerName Win2k12 ­ScriptBlock {$s}
No pasa nada por la simple y buena razón de que no hay persistencia de datos entre las sesiones.
Cuando utilizamos una sesión temporal, es como si cada vez abriésemos y cerrásemos una consola
PowerShell remota. Por lo tanto, todas las variables se destruyen sistemáticamente al finalizar la sesión.
10. Sesiones WinRM en modo desconectado
Invoke­Command, es posible establecer una sesión remota utilizando el parámetro ­
InDisconnectedSession y volvernos a conectar más tarde para recuperar el resultado. Incluso desde
Con
otro equipo.
PS > Invoke­Command {Get­Service} ­ComputerName Win2k12 ­InDisconnectedSession `
­SessionName Services
Id Name
­­ ­­­­
17 Services
ComputerName
­­­­­­­­­­­­
Win2k12
State
­­­­­
Disconnected
ConfigurationName
­­­­­­­­­­­­­­­­­
Microsoft.PowerShell
Availability
­­­­­­­­­­­­
None
La anterior línea de comando nos devuelve la información de la sesión creada en el equipo remoto
(observe que el nombre de la sesión está personalizado gracias al parámetro ­SessionName). Ahora
solo nos queda mostrar el resultado con el comando Receive­PsSession. Este último permite, al igual
que los jobs, recuperar el resultado de los comandos en ejecución en una sesión remota. Y eso desde un
ordenador distinto al utilizado para abrir la sesión.
433
PS > Receive­PSSession ­Name Services ­ComputerName Win2k12
Status
­­­­­­
Running
Stopped
Stopped
Stopped
Stopped
Stopped
Stopped
Stopped
Stopped
...
Name
­­­­
ADWS
AeLookupSvc
ALG
AllUserInstallA...
AppIDSvc
Appinfo
AppMgmt
AudioEndpointBu...
Audiosrv
DisplayName
­­­­­­­­­­­
Services Web Active Directory
Experiencia de aplicación
Servicio de la pasarela de la capa ...
Agente de instalación para todos los..
Identidad de la aplicación
Informaciones de aplicación
Gestión de aplicaciones
Generador de puntos de finalización...
Audio Windows
PSComputerName
­­­­­­­­­­­­­­
Win2k12
Win2k12
Win2k12
Win2k12
Win2k12
Win2k12
Win2k12
Win2k12
Win2k12
Le recomendamos guardar el resultado en una variable. Ya que una vez obtenido el resultado, este
se borra de la sesión remota.
11. Ejecución de scripts remotos
La ejecución de scripts remotos se hace de la misma forma que la ejecución de un bloque de script salvo
que el parámetro que debe usar en vez de ­ScriptBlock es ­FilePath.
La ruta indicada por el parámetro ­FilePath es la del script situado en el equipo local. Con este
parámetro, PowerShell convierte el contenido del archivo de scripts en un bloque de script, transmite el
bloque al equipo remoto y lo ejecuta en el equipo remoto.
Tenga en cuenta que para especificar valores a los parámetros del script, debe utilizar además el
parámetro ­ArgumentList.
Como PowerShell efectúa una conversión script ­> bloque
el riesgo de que se rechace la ejecución del bloque
configuradas en los equipos remotos. En efecto, aunque
ejecución de comandos se lleva a cabo; no ocurre así con los
de script en los equipos remotos, no existe
de script por las directivas de ejecución
el equipo esté en modo «restricted», la
scripts ejecutados localmente.
Ejemplo 1
Ejecución de un script para obtener el espacio de disco libre.
Tenemos un pequeño script llamado Get­DiskFreeSpace.ps1 (consulte el capítulo Gestión de errores
y depuración) que, como su nombre indica, devuelve el espacio libre en disco de todas las unidades.
A continuación puede ver lo que devuelve su ejecución local:
PS > .\Get­DiskFreeSpace.ps1
ID FreeSpace(GB) FreeSpace(%)
­­ ­­­­­­­­­­­­­ ­­­­­­­­­­­­
C:
14,81
45,491
A continuación, ejecutamos el script en un equipo remoto y observamos el resultado:
434
PS > Invoke­Command ­ComputerName Win2k12 ­FilePath .\Get­DiskFreeSpace.ps1
ID
FreeSpace(GB)
FreeSpace(%)
PSComputerName
RunspaceId
:
:
:
:
:
C:
14,81
45,491
Win2k12
315e4eb8­1d96­45d0­bc56­2f5687ebf9c6
La visualización del resultado en modo lista en vez del modo tabla es normal en la medida en que hay más
de cuatro propiedades que mostrar (consulte el capítulo Manipulación de objetos ­ Formateo de objetos
para la visualización). Pero no es lo más importante…
En efecto, podemos ver la aparición de las propiedades adicionales PSComputerName, RunspaceID y
PSShowComputerName. En realidad estas propiedades están siempre presentes cuando trabajamos
con mecanismos de comunicación remota de PowerShell, pero no son siempre visibles.
RunspaceID corresponde a una especie de «burbuja» en la que se ejecuta la sesión remota de
PowerShell. Cuando trabajamos con sesiones temporales, el RunspaceID cambia en cada nuevo
comando invocado por Invoke­Command; no ocurre así cuando trabajamos con sesiones permanentes.
El
A continuación puede ver los parámetros del comando
Parámetro
Invoke­Command:
Descripción
­AllowRedirection <Switch>
Autoriza la redirección de esta conexión hacia
otra URI (Uniform Resource Identifier).
­ApplicationName <String>
Especifica el segmento del nombre de aplicación
en la URI de conexión.
­ArgumentList <Object[]>
Permite especificar los valores de las variables
locales en el comando. Las variables del
comando se reemplazan por estos valores
antes de la ejecución del comando en el equipo
remoto. Indique los valores en una lista
separada por comas. Los valores se asocian a
las variables en el orden de visualización.
­AsJob <Switch>
Ejecuta el comando como tarea en segundo
plano en un equipo remoto. Use este
parámetro para ejecutar comandos cuya
ejecución necesite mucho tiempo.
­Authentication
<AuthenticationMechanism>
Especifica el mecanismo que permite autenticar
la información de identificación del usuario.
­CertificateThumbprint
<String>
Especifica el certificado digital de clave pública
(X509) de una cuenta de usuario que dispone
de autorización para ejecutar esta acción.
­ComputerName <String[]>
Especifica los equipos en los cuales el comando
se ejecuta. El valor por defecto es el equipo
local.
­ConfigurationName <String>
Especifica la configuración de sesión usada
para la nueva sesión PSSession.
­ConnectionURI <Uri[]>
Especifica una URI que define el punto de
finalización de conexión.
435
Descripción
Parámetro
­Credential <PSCredential>
Especifica una cuenta de usuario que dispone
de la autorización para ejecutar esta acción.
­EnableNetworkAccess <Switch>
Configura el token de seguridad que, en el
marco de una sesión de bucle (o loopback),
permite ejecutar comandos que obtienen datos
que provienen de otros equipos. Este
parámetro introducido con PowerShell 3.0 no
tiene ningún impacto si no se encuentra en el
caso de una sesión de bucle. Una sesión de
bucle es una sesión que empieza y termina en
el mismo sistema. Es el caso cuando se
atribuye al parámetro ­ComputerName el
valor
. o Localhost. Este parámetro se
introdujo con PowerShell 3.0.
­FilePath <String[]>
Ruta local del script a ejecutar remotamente.
­HideComputerName <Switch>
Omite el nombre del equipo de cada objeto en
la
visualización
de
salida
(propiedad
PSComputerName). Por defecto, el nombre
del equipo que genera el objeto aparece en la
visualización.
­InDisconnectedSession
Permite crear una sesión persistente. Con este
parámetro, el comando continua su ejecución
en el servidor remoto y puede volverse a
conectar más tarde para obtener el resultado.
Este parámetro se introdujo con PowerShell
3.0.
­InputObject <psobject>
Especifica la entrada del comando. Indique una
variable que contenga objetos o teclee un
comando o una expresión que devuelva los
objetos.
­JobName <String>
Especifica un nombre que puede recordar para
la tarea en segundo plano. Por defecto las
tareas se llaman «Tarea<n>», donde <n> es
un número entero. Este parámetro es válido
únicamente con el parámetro AsJob.
­NoNewScope <Switch>
Ejecuta el comando en el ámbito actual. Este
parámetro se introdujo con PowerShell 3.0.
­Port <Int>
Especifica el puerto de red del equipo remoto
utilizado para este comando.
­ScriptBlock <scriptblock>
Especifica los comandos a ejecutar. Sitúe los
comandos entre llaves ( { } ) para crear un
bloque de script. Este parámetro es obligatorio.
­Session <PSSession[]>
Ejecuta el comando en las sesiones Windows
PowerShell especificadas (PSSession). Indique
una variable que contenga las sesiones
PSSession o un comando que cree o devuelva
sesiones PSSession, como el comando New­
PSSession o Get­PSSession.
436
Parámetro
Descripción
­SessionName <Switch>
Permite mostrar un nombre personalizado para
la sesión. Este parámetro se introdujo con
PowerShell 3.0.
­SessionOption
<PSSessionOption>
Define opciones avanzadas para la sesión.
­ThrottleLimit <Int>
Especifica el número máximo de conexiones
simultáneas que pueden establecerse para
ejecutar este comando. Valor por defecto: 32.
­UseSSL <Switch>
Utiliza el protocolo Secure Socket Layer con
HTTPS para establecer la conexión. Por defecto,
no se utiliza SSL.
12. Copia de archivos mediante una sesión de comunicación
A través de una sesión de comunicación WinRM es posible copiar archivos de una máquina a otra. Esto se
lleva a cabo gracias a los parámetros ­ToSession y ­FromSession del comando Copy­Item.
Por ejemplo, queremos copiar el archivo
una máquina remota.
C:\temp\miScript.ps1 desde nuestra máquina local hasta
Lo primero que tenemos que hacer es, como de costumbre, establecer una sesión de comunicación.
Partiremos del principio de que ya se ha establecido y está referenciada mediante la variable $s.
PS > Copy­Item ­Path C:\temp\miScript.ps1 ­Destination c:\temp
­ToSession $s
Para comprobar que se ha producido la copia, podemos utilizar el siguiente comando:
PS > Invoke­Command ­Script {Get­Item C:\temp\miScript.ps1}
­session $s
13. Apertura de una sesión remota interactiva PowerShell
Otro punto a abordar antes de terminar esta parte sobre las comunicaciones remotas de PowerShell
concierne la posibilidad de ejecutar comandos en modo interactivo en un equipo remoto; es decir que
todos los comandos tecleados en la consola se ejecutan en un equipo remoto. Se trata de un
funcionamiento similar al de SSH o a Telnet.
Para ello disponemos de dos posibilidades: la primera consiste en abrir una consola clásica con un
comando ad­hoc y la segunda se apoya en la consola PowerShell en modo gráfico (PowerShell ISE).
a. Enter­PSSession
El comando Enter­PSSession arranca una única sesión interactiva en un equipo remoto. Se puede
abrir una sola sesión interactiva a la vez. Los eventuales perfiles PowerShell presentes en el equipo
remoto no se cargan.
Una vez terminada la sesión, teclee Exit­PSSession para desconectarse. Para arrancar una sesión
PowerShell remota, debe ser miembro del grupo de Administradores del equipo remoto.
437
Generalmente se utiliza con Enter­PSSession el parámetro ­ComputerName para especificar el
nombre del equipo remoto pero puede también pasar, si lo desea, un objeto de tipo PSSession al
parámetro ­Session.
Ejemplo
PS > Enter­PSSession ­ComputerName Win2k12
El resultado de esta línea de comandos produce un prompt diferente. En este último se encuentra el
nombre del equipo remoto, como muestra la captura de pantalla siguiente.
Sesión remota interactiva en la consola con
Los parámetros de
Enter­PSSession
Enter­PSSession son los siguientes:
Parámetro
Descripción
­AllowRedirection
Autoriza la redirección de esta conexión hacia
otra URI (Uniform Resource Identifier).
­ApplicationName <String>
Especifica el segmento del nombre
aplicación en la URI de conexión.
­Authentication
<AuthenticationMechanism>
Especifica el mecanismo que permite autenticar
la información de identificación del usuario.
­CertificateThumbprint
<String>
Especifica el certificado digital de clave pública
(X509) de una cuenta de usuario que dispone
de autorización para ejecutar esta acción.
­ComputerName <String>
Especifica los equipos en los cuales el
comando se ejecuta. El valor por defecto es el
equipo local.
­ConfigurationName <String>
Especifica la configuración de sesión usada
para la nueva sesión PSSession.
­ConnectionURI <Uri[]>
Especifica una URI que define el destino de
una conexión.
­Credential <PSCredential>
Especifica una cuenta de usuario que dispone
de la autorización para ejecutar esta acción.
de
438
Parámetro
Descripción
­EnableNetworkAccess <Switch>
Configura el token de seguridad que, en el
marco de una sesión de bucle (o loopback),
permite ejecutar comandos que obtienen
datos que provienen de otros equipos. Este
parámetro introducido con PowerShell 3.0 no
tiene ningún impacto si no se encuentra en el
caso de una sesión de bucle. Una sesión de
bucle es una sesión que empieza y termina en
el mismo sistema. Es el caso cuando se
atribuye al parámetro ­ComputerName el
valor
. o Localhost. Este parámetro se
introdujo con PowerShell 3.0.
­Id <int>
Especifica
el ID de
una sesión existente.
utiliza
la
sesión
especificada para la sesión interactiva. Para
buscar el ID de una sesión, utilice el applet de
comando Get­PSSession.
Enter­PSSession
­InstanceId <Guid>
Especifica el ID de la instancia de una sesión
existente. Enter­PSSession utiliza
la
sesión especificada para la sesión interactiva.
­Name <String[]>
Especifica un nombre que puede recordar para
la sesión existente.
­Port <Int>
Especifica el puerto de red del equipo remoto
utilizado para este comando..
­Session <PSSession[]>
Especifica una sesión Windows PowerShell
(PSSession) que se utilizará en la sesión
interactiva. Este parámetro acepta un objeto
sesión. Puede también utilizar los parámetros
Name, InstanceID o ID para especificar
una sesión PSSession.
­SessionOption
<PSSessionOption>
Define opciones avanzadas para la sesión.
­UseSSL <Switch>
Utiliza el protocolo Secure Socket Layer con
HTTPS para establecer la conexión. Por
defecto, no se utiliza SSL.
b. PowerShell ISE (Integrated Scripting Environment)
Este potente editor de scripts posee una funcionalidad que permite abrir varias consolas PowerShell
remotas. Esta resulta particularmente práctica.
Las diferentes sesiones remotas se abren cada una en una pestaña distinta como puede ver a
continuación:
439
Sesión remota interactiva en una consola ISE
Para abrir una consola remota en ISE, use el menú Archivo ­ Nueva pestaña de PowerShell en
remoto.... Se le pedirá que se autentique y aparecerá una nueva pestaña. En el ejemplo de la imagen de
arriba, tenemos una consola PowerShell en remoto abierta en dos equipos que funcionan
respectivamente con sistemas operativos distintos: Windows Server 2012 y Windows 7. Cada pestaña
lleva el nombre del equipo remoto en el que se ha abierto la sesión remota interactiva.
14. Importación de comandos remotos
Una última funcionalidad que abordaremos en este capítulo permite importar comandos remotos. Se trata
de una funcionalidad poco conocida pero sin embargo ¡tremendamente útil!
Supongamos que en un equipo remoto tenemos snap­ins o módulos que nos gustaría usar en nuestro
equipo local. Hasta aquí podemos decirnos que basta con utilizar el comando visto anteriormente Enter­
PSSession. Es cierto que esto funciona... Sin embargo, Enter­PSSession no carga los perfiles
PowerShell que podrían encontrarse en el equipo remoto. ¿Qué hacer entonces si tenemos bastantes
funciones indispensables en el equipo remoto y deseamos usarlas en nuestro equipo local? Pues en este
caso lo más sencillo es importar en la sesión PowerShell actual los comandos del equipo remoto. Así nos
beneficiaremos de nuestro perfil y del conjunto de comandos extendidos del equipo remoto.
Por este motivo, el comando Import­PSSession justifica su existencia. Pero deben existir muchos
otros escenarios en los cuales no hemos pensado…
El comando
Import­PSSession posee los siguientes parámetros:
Parámetro
Descripción
­AllowClobber <Switch>
Importa los comandos especificados aunque
tengan el mismo nombre que comandos de la
sesión actual.
­ArgumentList <Object[]>
Importa la variante del comando que resulta
del uso de los argumentos especificados.
440
Descripción
Parámetro
­CommandName <String[]>
Importa únicamente los comandos que poseen
los modelos de nombre o los nombres
especificados. Los caracteres genéricos están
autorizados.
­CommandType <CommandTypes>
Importa únicamente los tipos de objetos del
comando especificado. El valor por defecto es
Cmdlet.
­DisableNameChecking <Switch>
Suprime el mensaje de alerta referido a la
importación de comandos o funciones para las
cuales el nombre no respeta las buenas
prácticas, a saber un verbo que provenga del
comando Get­Verb.
­FormatTypeName <String[]>
Importa las instrucciones de formateo de tipo
Microsoft .NET framework especificadas.
­Module <String[]>
Importa únicamente los comandos en los
componentes de software que pueden incluirse
(Snap­ins) y los módulos Windows PowerShell
especificados.
­Prefix <String>
Añade el prefijo especificado a los nombres de
los
comandos
importados.
Utilice
este
parámetro para evitar conflictos de nombres
que se pueden producir cuando diferentes
comandos de la sesión tienen el mismo nombre.
­Session <PSSession>
Especifica la sesión PSSession desde la que se
importan los comandos.
­Certificate
<X509Certificate2>
Especifica el certificado cliente utilizado para
firmar los archivos o scripts en los módulos
temporales que crea Import­PSSession.
Tomemos, por ejemplo, el caso de un servidor controlador de dominio Windows Server 2012 con el rol
«Active Directory Domain Services» instalado. Por lo tanto este último tiene la suerte de disponer del
módulo Active Directory. Este módulo aporta numerosos comandos para la gestión de objetos de usuario,
equipos, grupos, OUs, etc. Por lo tanto queremos importar este módulo en la sesión actual, o dicho de otra
manera en nuestra consola.
Lo primero que debemos hacer es establecer una sesión con el ordenador remoto de la siguiente manera:
PS > $s = New­PSSession ­ComputerName Win2k12
Ahora, es como si acabásemos de abrir una consola PowerShell en un equipo remoto. Por lo tanto
debemos, en esta última, cargar el módulo ActiveDirectory ya que por el momento solo posee los
comandos básicos.
PS > Invoke­Command ­Session $s ­ScriptBlock { Import­Module ActiveDirectory }
Observamos que este comando tarda algunos segundos en ejecutarse y que durante este lapso de
tiempo se muestra una barra de progreso en nuestra consola.
441
Ahora que el módulo está cargado en el equipo remoto, podemos importar los comandos que lo
componen:
PS > Import­PSSession ­Session $s ­Module ActiveDirectory
ModuleType Name
­­­­­­­­­­ ­­­­
Script
tmp_43lfdsr2.iqx
ExportedCommands
­­­­­­­­­­­­­­­­
{ Add­ADCentralAccessPolicyMember, Add­...
¡Y ya está hecho! Para verificar que los comandos están perfectamente disponibles en la sesión actual, si
conocemos su nombre podemos ejecutar el siguiente comando:
PS > Get­Command *­AD*
CommandType
­­­­­­­­­­­
Function
Function
Function
Function
Function
Function
Function
Function
Function
...
Name
­­­­
Add­ADCentralAccessPolicyMember
Add­ADComputerServiceAccount
Add­ADDomainControllerPasswordReplicat...
Add­ADFineGrainedPasswordPolicySubject
Add­ADGroupMember
Add­ADPrincipalGroupMembership
Add­ADResourcePropertyListMember
Clear­ADAccountExpiration
Clear­ADClaimTransformLink
ModuleName
­­­­­­­­­­
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
tmp_43lfdsr2.iqx
O este comando si no conocemos los comandos incluidos en el módulo:
PS > Get­Command ­Module tmp*
Este último comando devuelve los comandos del módulo llamado «tmp*». Cuando importamos un módulo
con Import­PSSession, se crea localmente un módulo temporal que contiene todos los comandos
importados. Con este comando, pedimos a Get­Command que nos muestre la lista de comandos del
módulo temporal que empiezan por «tmp».
Al importar comandos, estos últimos se transforman en funciones. Además tenga presente que
aunque los comandos importados parezcan locales por su comportamiento, se ejecutan en el equipo
remoto. Debemos por tanto tener cuidado de mantener la sesión PSSession viva el tiempo necesario.
Comunicación remota sobre SSH (PowerShell Core
únicamente)
Preste atención: en el momento de escribir estas líneas, la versión de OpenSSH para Windows está todavía en
versión beta, de modo que puede que haya sutiles diferencias con la versión final.
Desde la aparición de PowerShell Core, es posible utilizar SSH como protocolo de transporte en lugar de
WinRM. Esto abre escenarios que no era posible contemplar hasta el momento, tales como la administración
de OS Linux o Mac OS desde una máquina Windows, ¡y también a la inversa!
Para ello, en primer lugar, vamos a tener que instalar la capa SSH que provee OpenSSH. Esto aporta a la
vez un cliente SSH (ssh.exe) y también un servidor SSH (sshd.exe). Win32 OpenSSH es la versión OpenSSH
para OpenBSD.
442
1. Instalación de OpenSSH en Windows
Lo prime que tenemos que hacer es descargar la última versión desde la siguiente dirección:
https://github.com/PowerShell/Win32­OpenSSH/releases/. A continuación, ejecute escrupulosamente las
siguientes operaciones:
Descargue el archivo Zip de la versión 64 bits si va a realizar la instalación sobre un OS de tipo
Windows Server 2016, como haremos aquí.
Descomprima el archivo en la ruta
C:\Program Files\OpenSSH.
Sitúese en la ruta donde haya descomprimido los archivos y, a continuación, desde una consola
PowerShell abierta en modo Administrador, ejecute la siguiente línea de comando:
PS > powershell.exe ­ExecutionPolicy Bypass ­File install­sshd.ps1
A continuación, vamos a configurar el firewall a fin de abrir el puerto 22 para autorizar las conexiones SSH
entrantes:
PS > New­NetFirewallRule ­Name sshd `
­DisplayName ’OpenSSH Server (sshd)’ `
­Enabled True ­Direction Inbound ­Protocol TCP `
­Action Allow ­LocalPort 22
Arranque el servicio SSH:
PS > Start­Service sshd
El arranque del servicio va a crear un archivo de host SSH, así como el archivo de configuración
sshd_config en %programdata%\ssh (observe que se trata de una carpeta oculta).
Para que los servicios SSH arranquen de manera automática con Windows, escriba las siguientes
líneas:
PS > Set­Service sshd ­StartupType Automatic
PS > Set­Service ssh­agent ­StartupType Automatic
Edite el archivo
%programdata%\ssh\sshd_config y copie las siguientes líneas:
PasswordAuthentication yes
PubkeyAuthentication yes
Subsystem powershell c:/program files/powershell/6.0.1/pwsh.exe ­sshs
­NoLogo ­NoProfile
La primera opción autoriza la conexión SSH con una contraseña. La segunda permite realizar la
autenticación mediante una clave. Finalmente, la última opción indica a SSH que debe arrancar
PowerShell cuando reciba una conexión sobre el subsistema «powershell». Asegúrese de indicar
una ruta válida hacia el ejecutable pwsh.exe.
Edite la variable de entorno PATH para incluir la ruta en la que ha instalado OpenSSH, en nuestro
caso: C:\Program files\OpenSSH\.
Preste atención, no olvide la barra invertida al final, pues puede resultar importante.
443
Abra una nueva consola Powershell y compruebe que puede ejecutar sin error el cliente SSH
escribiendo simplemente:
PS > Ssh.exe
Por último, para validar que la instalación de OpenSSH se ha realizado correctamente, vamos a comprobar
la conexión sobre la máquina que acabamos de configurar. Lo ideal es realizar esta conexión a partir de
otra máquina de la red que posea el cliente SSH, pero, por defecto, de manera local puede escribir:
PS > Ssh [email protected]
Donde la dirección IP indicada es la vuestra y el nombre o la contraseña son también correctos.
Si se abre una shell, significa que ha configurado todo sin error y que está listo para seguir.
2. Instalación de OpenSSH en Linux
Vamos a indicar cómo es el procedimiento de instalación para la distribución Ubuntu; es muy similar en la
mayoría de los sistemas Linux.
Instalación de la parte servidor y cliente:
sudo apt install openssh­server
sudo apt install openssh­client
A continuación, viene la parte de configuración, que se realiza en el archivo
archivo:
sshd_config. Edite el
sudo gedit /etc/ssh/sshd_config
Agregue la siguiente línea en la sección «subsystem», hacia el final del archivo:
Subsystem powershell pwsh.exe ­sshs ­NoLogo ­NoProfile
Genere una clave de host mediante el siguiente comando:
sudo ssh­keygen ­A
A continuación, reinicie el servicio SSH:
sudo service ssh restart
Compruebe que la instalación y la configuración son correctas intentando realizar una conexión SSH
hacia su máquina Linux o, por defecto, de manera local escribiendo:
ssh root@localhost
Si obtiene una shell anidada, significa que todo es correcto y que está preparado para continuar.
444
3. Ejecución de comandos y scripts remotos
Ahora que hemos configurado SSH en todas nuestras máquinas, podemos enviar comandos remotos a
estas últimas.
a. Establecer una sesión de comunicación
La primera operación consiste en establecer una sesión de comunicación entre la máquina cliente y la
máquina servidor. En el siguiente ejemplo, vamos a conectarnos a una máquina Linux desde una
máquina Windows.
Escriba la siguiente línea de comandos y la contraseña del usuario de la máquina remota cuando se le
solicite:
PS C:\> $s = New­PSSession ­HostName linuxsrv1 ­UserName root
The authenticity of host ’linuxsrv1 (10.211.55.7)’ can’t be established.
ECDSA key fingerprint is SHA256:qkXEHVBtYMscjJx9DKXEigPLNqM5K02xQZCKZdtfFY8.
Are you sure you want to continue connecting (yes/no)?
Warning: Permanently added ’linuxsrv1’ (ECDSA) to the list of known hosts.
root@linuxsrv1’s password:
Durante la primera conexión SSH a una máquina remota, el cliente SSH indica que no conoce la
identidad del servidor remoto. Le pide una configuración antes de conectarse. Si acepta la
conexión, entonces se registra de manera local la clave pública SSH de la máquina remota y no se le
volverá a pedir en un futuro.
Observe que lo que permite distinguir una sesión WinRM de una sesión SSH con New­PSSession
es el parámetro ­HostName. Una sesión WinRM utiliza el parámetro ­ComputerName.
Comprobemos ahora el estado de la conexión:
PS > Get­PSSession
Id Name ComputerName ComputerType State ConfigurationName Availability
­­ ­­­­ ­­­­­­­­­­­­ ­­­­­­­­­­­­ ­­­­­ ­­­­­­­­­­­­­­­­­ ­­­­­­­­­­­­
1 SSH1 linuxsrv1
RemoteMachine Opened DefaultShell
Available
También habríamos podido mostrar el contenido de la variable
resultado.
$s, que habría dado el mismo
La conexión está ahora establecida con éxito y podemos enviar comandos a la máquina remota.
b. Envío de comandos remotos
Vamos a enviar algunos comandos para entretenernos un poco.
He aquí cómo recuperar el OS de la máquina remota:
PS > Invoke­Command ­Session $s ­ScriptBlock { $PSVersiontable.OS }
Linux 4.14.0­kali3­amd64 #1 SMP Debian 4.14.17­1kali1 (2018­02­16)
445
A continuación, veamos cuánto tiempo lleva encendida la máquina:
PS > Invoke­Command ­Session $s ­ScriptBlock { Get­Uptime }
Days
Hours
Minutes
Seconds
Milliseconds
Ticks
TotalDays
TotalHours
TotalMinutes
TotalSeconds
TotalMilliseconds
PSComputerName
:
:
:
:
:
:
:
:
:
:
:
:
0
0
53
4
0
31840000000
0.0368518518518519
0.884444444444444
53.0666666666667
3184
3184000
linuxsrv1
Vamos a mostrar cómo es posible combinar comandos nativos de Linux con comandos PowerShell. En el
siguiente ejemplo, gracias al comando du (disk usage), recuperamos los datos de ocupación de espacio
en disco y limitamos el número de resultados a los 10 primeros mediante el comando PowerShell
Select­Object.
PS > Invoke­Command $s ­Script {du ­h /etc | Select­Object ­First 10}
8,0K
12K
4,0K
24K
600K
588K
28K
4,0K
16K
4,0K
/etc/nfc
/etc/udev/rules.d
/etc/udev/hwdb.d
/etc/udev
/etc/unicornscan
/etc/apache2/mods­available
/etc/apache2/conf­available
/etc/apache2/mods­enabled
/etc/apache2/sites­available
/etc/apache2/conf­enabled
Como habrá observado, funciona exactamente igual que con las sesiones WinRM entre máquinas
Windows.
c. Ejecución remota de un script
En el siguiente ejemplo, vamos a partir de una máquina Windows para ejecutar un script de manera
remota sobre dos máquinas. Una funciona con Linux, la otra con Windows. El script recupera cierta
información técnica, como:
La cantidad de RAM instalada.
La versión del OS.
El tiempo transcurrido desde el arranque de la máquina (uptime).
Escribir un script PowerShell multiplataforma
Como el script debe ejecutarse de igual manera sobre Linux y sobre Windows, debemos tomar cierta
precaución en particular. En efecto, no podemos, por ejemplo, invocar al subsistema CIM/WMI sobre un
OS Linux.
Veamos cómo queda, al final, nuestro script:
446
# Get­ComputerStatistics.ps1
$UptimeHours = [Math]::Round( (Get­Uptime).TotalHours, 2 )
if ($isLinux) {
$TotalMemory = cat /proc/meminfo | awk ’/MemTotal\:/ { print $2 }’
}
elseif ($isWindows) {
$TotalMemory = Get­CimInstance ­ClassName Win32_ComputerSystem
$TotalMemory = $TotalMemory.TotalPhysicalMemory / 1KB
}
[PSCustomObject]@{
UptimeHours
= $UptimeHours
OSVersion
= $PSVersionTable.OS
TotalMemoryKB = $TotalMemory
}
La parte más interesante es, sin duda, la que en función de la versión del OS va a ejecutar una sección
del script u otra. En efecto, aquí nos basamos en las variables automáticas $isLinux e $isWindows
para determinar la versión del OS. A continuación, si se trata de Linux, se invoca el comando cat
/proc/meminfo para recuperar la cantidad de RAM instalada, mientras que si el OS es Windows,
entonces se utiliza CIM y la clase Win32_ComputerSystem. Para terminar, una vez recuperada toda
la información técnica, se agrega todo en un objeto personalizado.
Establecer sesiones de comunicación SSH
Como antes, durante el envío de comandos remotos, debemos abrir canales de comunicación hacia las
máquinas que se van a administrar:
PS > $s1 = New­PSSession ­HostName linuxsrv1 ­UserName root
root@linuxsrv1’s password: ******
PS > $s2 = New­PSSession ­HostName win2016­2 ­UserName admin
admin@win2016­2’s password: ******
Una buena práctica es comprobar el estado de la conexión:
PS > Get­PSSession
Id Name ComputerName
­­ ­­­­ ­­­­­­­­­­­­
1 SSH1 linuxsrv1
2 SSH2 win2016­2
ComputerType
­­­­­­­­­­­­
RemoteMachine
RemoteMachine
State
­­­­­
Opened
Opened
ConfigurationName Availability
­­­­­­­­­­­­­­­­­ ­­­­­­­­­­­­
DefaultShell
Available
DefaultShell
Available
También es posible mezclar comunicaciones remotas WinRM con comunicaciones remotas SSH.
Habríamos podido agregar una sesión WinRM suplementaria.
Ejecución remota de un script
Ahora que tenemos dos sesiones de comunicación SSH, vamos a poder ejecutar nuestro script
ComputerStatistics de la siguiente manera:
Get­
447
PS > Invoke­Command ­FilePath C:\temp\Get­ComputerStatistics.ps1
­Session $s1,$s2
UptimeHours
OSVersion
TotalMemoryKB
PSComputerName
RunspaceId
:
:
:
:
:
2.23
Microsoft Windows 10.0.14393
4171092
win2016­2
cc9a96e9­ab7f­43dd­bd57­3eb4a779a269
UptimeHours
OSVersion
TotalMemoryKB
PSComputerName
RunspaceId
:
:
:
:
:
2.49
Linux 4.14.0­kali3­amd64 #1 SMP Debian 4.14.17­1kali1...
2039784
linuxsrv1
6e7e0ecf­c2c7­4c64­bd42­dfcb513828a9
Ve cómo es de una facilidad sorprendente, ¿verdad? Se abre ante nosotros todo un universo de
posibilidades.
4. Inicio de una sesión interactiva remota
El inicio de una sesión PowerShell SSH interactiva se realiza exactamente de la misma manera que con
WinRM.
Si ya ha establecido una sesión de comunicación y esta está referenciada mediante la variable
$s1,
entonces puede hacer de la siguiente manera:
PS C:\> Enter­PSSession ­Session $s1
[linuxsrv1]: PS /root>
Si se ha modificado su prompt, como en nuestro ejemplo, significa que se ha establecido la sesión y puede
escribir sus comandos como si estuviera conectado físicamente a la máquina remota.
Si no hubiera establecido una sesión previa, entonces debe utilizar el parámetro
comando Enter­PSSession, como se muestra a continuación:
­HostName del
PS > Enter­PSSession ­HostName linuxsrv1 ­user root
root@linuxsrv1’s password:
[linuxsrv1]: PS /root>
5. Copia de archivos a través de una sesión de comunicación
Igual que con WinRM, a través de una sesión SSH podemos copiar archivos de una máquina a otra. Esto
se realiza gracias a los parámetros ­ToSession y ­FromSession del comando Copy­Item.
Por ejemplo, si queremos copiar el archivo C:\temp\miScript.ps1 desde nuestra máquina local hasta
una máquina remota, lo primero que tenemos que hacer, como siempre, es establecer una sesión de
comunicación. Partiremos del principio de que ya se ha realizado y está referenciada por la variable $s.
PS > Copy­Item ­Path C:\temp\miScript.ps1 ­Destination /tmp ­ToSession $s
Para comprobar que la copia se ha realizado correctamente, podemos utilizar el siguiente comando:
448
PS > Invoke­Command ­ScriptBlock {Get­Childitem /tmp/*.ps1} ­session $s
Directory: /tmp
Mode
­­­­
­­­­­­
LastWriteTime Length Name
PSComputerName
­­­­­­­­­­­­­ ­­­­­­ ­­­­
­­­­­­­­­­­­­­
2/23/2018 12:07 AM
470 miScript.ps1 linuxsrv1
Conclusión
Hemos visto a través de este capítulo que existen múltiples formas de administrar máquinas de manera
remota (RPC, WinRM, SSH) y que, en función de su contexto de seguridad y de los sistemas operativos que
tenga que administrar, no siempre procederá del mismo modo.
PowerShell 6 presenta una gran evolución ofreciéndonos la posibilidad de administrar máquinas Linux y Mac
OS con una facilidad apabullante, en particular desde la compatibilidad con SSH.
Dicho esto, no todo es de color de rosa, pues se habrá dado cuenta de que pasar de una máquina a otra
puede resultar, en ocasiones, un auténtico desafío. Esperemos que Microsoft, en el futuro, nos proporcione
soluciones más fáciles de implementar que las actuales.
En cualquier caso, el dilema no es sencillo, pues se contraponen de un lado la facilidad de la administración
y de otro la seguridad. Vasto asunto...
449
Casos de
estudio
450
Encontrar las cuentas de equipo caducadas dentro del AD DS
Requisitos
El equipo que ejecuta el script debe ser un controlador de dominio que funcione bajo Windows
Server 2008 R2 o Windows Server 2012/R2 como mínimo.
Microsoft Excel (opcional).
1. Problemática
Muchas veces existe un gran número de cuentas de equipo inútiles que están presentes en el servicio de
directorio Active Directory. La razón es simple: en muchas empresas falta un procedimiento para
deshacerse del hardware, o si bien existe para la gestión del hardware físico no existe en cambio para
eliminar las cuentas de los equipos. Así, al cabo de algunos años, no es raro tener un 50% de las cuentas
de equipos de más. Por lo tanto, puede volverse complicado para un administrador gestionar
adecuadamente su parque de equipos. Para intentar volver a poner un poco de orden en el AD DS,
desarrollaremos un script que se conectará a un controlador de dominio y recuperará la lista de cuentas
de equipo. Para cada cuenta de equipo, miraremos en qué fecha se realizó el último acceso o dicho de otra
manera la fecha del último inicio de sesión. Ya que efectivamente, ¡hasta las cuentas de equipo inician
sesiones! Una cuenta de equipo inicia una sesión en un dominio autenticándose en el controlador de
dominio, igual que un usuario. Con la diferencia de que las contraseñas de las cuentas de equipo se
autogeneran.
Se genera una contraseña aleatoria la primera vez cuando un equipo se integra en el dominio. Después
cambia cada treinta días.
2. Dificultades que superar
Las propiedades de las cuentas de equipo están disponibles en el controlador de dominio, pero estas
últimas ¡no contienen siempre la información actualizada! En efecto, la información del último inicio de
sesión no se replica; se guarda localmente en cada controlador de dominio. Para disponer de la última
información, hay que preguntar a todos los controladores y quedarse con los datos que sean más
actuales. ¡Hubiese sido demasiado fácil de otra forma! Esto constituye la primera dificultad.
La buena noticia es que desde un dominio Windows 2003 disponemos de un atributo llamado
LastLogonTimeStamp. Este se replica en todos los controladores de dominio, pero (¡ya que existe un
pero!) esta replicación solo tiene lugar cada catorce días. Para responder a nuestra problemática, no
estamos a dos semanas de diferencia; por lo tanto adoptaremos esta técnica. Hay que saber que esta
propiedad se introdujo precisamente para identificar las cuentas no utilizadas de un dominio, y no para
dar información en tiempo real para la trazabilidad.
La segunda dificultad proviene del valor del atributo
LastLogonTimeStamp ya que este valor es un
entero codificado con 64 bits y no una simple fecha del tipo «10/09/2018». Hablábamos de ello
precisamente en el capítulo Gestión de archivos y fechas. El valor devuelto es el número de intervalos en
millonésimas de segundo desde el 1 de enero de 1601 (expresado en ticks), ahí queda eso... Tendremos,
por lo tanto, que convertir este valor en fecha, pero eso ya lo hemos hecho.
Puede encontrar un excelente artículo sobre este apasionante tema en el Blog del equipo de Active
Directory. Explica la génesis de los atributos LastLogon y LastLogonTimeStamp, y para qué han sido
creados: http://blogs.technet.com/b/askds/archive/2009/04/15/the­lastlogontimestamp­attribute­what­
it­was­designed­for­and­how­it­works.aspx
451
3. Solución
# Get­ComputerAccountLastLogon.ps1
function ConvertTo­LocalTime
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[Int64]$LongDate
)
$myUTCTime = ([DateTime]$LongDate).AddYears(1600)
[TimeZoneInfo]::ConvertTimeFromUtc($myUTCTime, [TimeZoneInfo]::Local)
}
$cuentas = Get­ADComputer ­Filter * `
­Properties name,LastLogonTimeStamp,OperatingSystem,
OperatingSystemVersion,OperatingSystemServicePack,
OperatingSystemServicePack
$cuentas | Select­Object @{e={$_.Name};n=’ComputerName’},
@{e={ConvertTo­LocalTime
$_.LastLogonTimeStamp};n=’LastLogon’},
@{e={$_.OperatingSystem};n=’OSName’},
@{e={$_.OperatingSystemVersion};n=’OSVersion’},
@{e={$_.OperatingSystemServicePack};n=’OSServicePack’}
Ahora probemos el script:
PS > .\Get­ComputerAccountLastLogon.ps1 | Format­Table ­AutoSize
ComputerName
­­­­­­­­­­­­
WS2012FR­1
WIN8US­0
WS2012CUS­1
WS2012US­1
WS2012FR­2
LastLogon
­­­­­­­­­
05/04/2015
08/04/2015
10/11/2015
27/03/2015
11/04/2015
20:51:02
01:48:59
04:24:28
18:56:38
23:27:36
OSName
­­­­­­
Windows
Windows
Windows
Windows
Windows
Server 2012 Standard
8 Enterprise
Server 2012 Standard
Server 2012 Standard
Server 2012 Standard
OSVersion OSServicePack
­­­­­­­­­ ­­­­­­­­­­­­­
6.2 (9200)
6.2 (9200)
6.2 (9200)
6.2 (9200)
6.2 (9200)
La propiedad OSServicePack está vacía ya que no hemos instalado el Service Pack en ninguno de
nuestros equipos de prueba.
Explicaciones complementarias
Ya que el comando Get­ADComputer del módulo Active Directory nos devuelve una propiedad
expresada en ticks, debemos convertirla en un objeto DateTime para poder explotarla plenamente. Es la
razón por la que llamamos la función
capítulo Gestión de archivos y fechas.
ConvertTo­Localtime, función que creamos y explicamos en el
También utilizamos la posibilidad de «remapeo» y transformación de propiedades ofrecida por
Object para acortar los nombres de las propiedades y transformar la fecha.
Select­
452
Para mostrarle que existe una gran cantidad de información interesante además de la propiedad
LastLogonTimeStamp, hemos aprovechado el propio ejemplo para devolverlas. Estas pueden ser
de utilidad, por ejemplo, para hacer inventarios rápidos de los sistemas operativos instalados así como
las versiones de los Service Pack respectivos.
Enumerar las cuentas de usuario inactivas en el AD DS
Requisitos
El equipo que ejecuta el script debe ser un controlador de dominio que funcione bajo Windows
Server 2008 R2 o Windows Server 2012/R2.
Microsoft Excel (opcional).
1. Problemática
El resultado es similar al de las cuentas de equipo: los administradores están siempre al corriente cuando
se trata de crear una cuenta de usuario pero nunca cuando hay que eliminarla. Esto provoca
necesariamente desviaciones que pueden acabar costando caro. En efecto, el número de objetos en el
directorio Active Directory Domain Services no para de crecer y los recursos son monopolizados para nada
(espacio en disco que almacena los «home directories», cuentas de correo electrónico, etc.).
Por otra parte, una mala gestión de las cuentas de usuario puede causar problemas de seguridad ya que
todos sabemos que una contraseña que no cambia se puede fácilmente «romper»...
2. Solución: ¡hacer limpieza!
La intención es loable pero sin un script ¡imposible! En el estudio del caso anterior, focalizamos nuestra
atención sobre las cuentas de equipo. Pues sepa que la gestión de cuentas de usuario se realiza con el
mismo principio, y las explicaciones del atributo LastLogon y LastLogonTimeStamp siguen siendo
válidas. A saber que el atributo LastLogon no se replica entre los controladores de dominio y que
LastLogonTimeStamp sí se replica pero cada catorce días aproximadamente. Para simplificarnos la
vida, como para las cuentas de equipos, nos contentaremos con usar LastLogonTimeStamp; por lo
tanto podremos saber en el peor de los casos la realidad con una diferencia de catorce días con la
situación real de la empresa. Pero ¿es realmente importante?
Para conocer más sobre el atributo
LastLogonTimeStamp, lea el caso de estudio anterior.
El script que vamos a desarrollar juntos nos permitirá encontrar las cuentas inactivas desde un cierto
número de días. Después es libre para adaptarlo y que así corresponda mejor a sus necesidades. Podría
desactivar las cuentas y, por qué no, eliminarlas (aunque no sería muy prudente) o mejor archivar los
datos de los usuarios en primer lugar.
A continuación tiene el script que hemos imaginado para responder a este caso:
453
# Get­UserAccountLastLogon.ps1
function ConvertTo­LocalTime
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[Int64]$LongDate
)
$myUTCTime = ([DateTime]$LongDate).AddYears(1600)
[TimeZoneInfo]::ConvertTimeFromUtc($myUTCTime, [TimeZoneInfo]::Local)
}
$cuentas = Get­ADUser ­Filter * `
­Properties Name,WhenCreated,HomeDrive,HomeDirectory,LastLogonTimestamp
$cuentas | Select­Object Name, WhenCreated, HomeDrive, HomeDirectory,
@{e={ConvertTo­LocalTime $_.lastlogontimestamp};n=’LastLogon’}
Ahora probemos nuestro script:
PS > ./Get­UserAccountLastLogon.ps1 | Format­Table
Name
­­­­
Administrador
Invitado
krbtgt
Angel Perez
Guttenberg
Paulo Pichon
WhenCreated
HomeDrive HomeDirectory
­­­­­­­­­­­
­­­­­­­­­ ­­­­­­­­­­­­­
21/10/2012 00:04:30
21/10/2012 00:04:30
21/10/2012 00:05:59
01/04/2015 14:18:53
02/04/2015 00:13:04
13/04/2015 23:22:04 H:
\\W2K12SRV\users
LastLogon
­­­­­­­­­
12/04/2015
01/01/1601
01/01/1601
01/01/1601
02/04/2015
01/01/1601
23:50:44
01:00:00
01:00:00
01:00:00
00:19:50
01:00:00
Observe que hemos aprovechado para devolver cierta información que puede resultar útil como la fecha
de creación, la ruta del «home directory» así como la letra de la unidad asignada.
Explicaciones complementarias
Igual que en el caso anterior, llamamos al módulo Active Directory presente en cualquier servidor Windows
desde la versión 2008 R2. Hemos tenido que convertir la fecha devuelta por la propiedad
LastLogonTimeStamp, expresada en ticks.
Habrá comprendido que un valor «01/01/1601 01:00:00» para la propiedad LastLogon significa que el
usuario nunca ha iniciado sesión. Para evitar este tipo de valor devuelto tendríamos que modificar la
función ConvertTo­LocalTime para que devuelva el valor $null cuando reciba el valor 0 como
entrada, como es nuestro caso.
Generar una lista en un archivo CSV
Para exportar nuestros datos en un archivo CSV, nada más fácil. Es ahora cuando llamamos a
Export­
CSV para ayudarnos.
PS > .\Get­UserAccountLastLogon.ps1 | Export­Csv ­NoTypeInformation
­UseCulture ­Path ./UserAccounts.csv ­Encoding Unicode
454
La codificación Unicode es aquí primordial si queremos preservar los acentos en los valores exportados.
Aquí tiene el resultado en Excel:
Apertura en Excel del archivo CSV
Posible evolución
Para ir más lejos, podríamos tener ganas de añadir una funcionalidad a nuestro script para que devuelva
solo las cuentas de usuarios que no se han conectado desde n días.
Para ello, nada más fácil que añadir un filtro con la cláusula
Where­Object, calcular un intervalo de
tiempo y evaluar este último como puede ver en el ejemplo siguiente:
PS > ./Get­UserAccountLastLogon.ps1 | Where {
(New­TimeSpan ­Start $_.LastLogon ­End (Get­Date) ).TotalDays ­ge 10 }
Aquí, hemos elegido devolver todas las cuentas que no han iniciado sesión desde hace 10 días respecto a
la fecha actual.
Para evitar recuperar también las cuentas que nunca se han conectado, debemos excluir estas últimas. A
continuación le mostramos la línea de comandos modificada:
PS > ./Get­UserAccountLastLogon.ps1 | Where {
(New­TimeSpan ­Start $_.LastLogon ­End (Get­Date) ).TotalDays ­ge 10 ­and
$_.LastLogon ­ne [DateTime]’01/01/1601 01:00:00’}
Name
WhenCreated
HomeDrive
HomeDirectory
LastLogon
Ahora
le
: Guttenberg
: 02/04/2015 00:13:04
:
:
: 02/04/2015 00:19:50
mostramos
el script modificado
NotConnectedSinceNDays.
que
incluye
un
parámetro
adicional, el parámetro
­
455
# Get­UserAccountLastLogonv2.ps1
[CmdletBinding()]
Param (
[Parameter(Mandatory=$false)]
[int]$NotConnectedSinceNDays = 90
)
function ConvertTo­LocalTime
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[Int64]$LongDate
)
$myUTCTime = ([DateTime]$LongDate).AddYears(1600)
[TimeZoneInfo]::ConvertTimeFromUtc($myUTCTime, [TimeZoneInfo]::Local)
}
$cuentas = Get­ADUser ­Filter * `
­Properties Name,WhenCreated,HomeDrive,HomeDirectory,LastLogonTimestamp
$cuentas | Select­Object Name, WhenCreated, HomeDrive, HomeDirectory,
@{e={ConvertTo­LocalTime $_.lastlogontimestamp};n=’LastLogon’} |
Where {
(New­TimeSpan ­Start $_.LastLogon ­End (Get­Date) ).TotalDays `
­ge $NotConnectedSinceNDays ­and
$_.LastLogon ­ne [DateTime]’01/01/1601 01:00:00’}
Observe
que
definimos
un
NotConnectedSinceNDays.
valor
por
defecto
de
90
días
para
el
parámetro
­
Ahora probamos el script con un valor inferior, por ejemplo 10 días:
PS > .\Get­UserAccountLastLogonv2.ps1 ­NotConnectedSinceNDays 10
Name
WhenCreated
HomeDrive
HomeDirectory
LastLogon
: Guttenberg
: 02/04/2013 00:13:04
:
:
: 02/04/2013 00:19:50
Al terminar este capítulo, nos hemos dado cuenta de la presencia de una nueva propiedad, la
propiedad LastLogonDate. Después de una pequeña investigación, hemos visto que esta
contiene el mismo valor que LastLogonTimeStamp pero en forma de objeto DateTime,y por lo
tanto directamente explotable. En otros términos, esto significa que hubiésemos podido simplificar
aún más nuestros scripts evitando el uso de nuestra función de conversión ConvertTo­
LocalTime.
456
Cambiar la contraseña de Administrador local remotamente
Requisitos
Ninguno.
1. Problemática
Cuando disponemos de un importante parque de servidores a administrar y deseamos garantizar a
nuestra empresa un cierto grado de seguridad, debemos cambiar regularmente las contraseñas de las
cuentas con privilegios de administración. Cuando se trata de cambiar la contraseña de una cuenta de
dominio, es fácil ya que la cambiamos en un único sitio. Pero cuando se trata de cambiar la contraseña de
Administrador local de cada servidor miembro del dominio, ¡es otra historia!
Es verdad que desde Windows 2008 y la aparición de las GPO, es posible configurar la cuenta de
administrador local y sobre todo su contraseña. Sin embargo, le desaconsejamos esta técnica. Al
igual que Microsoft por cierto (http://blogs.technet.com/b/grouppolicy/archive/2009/04/22/passwords­in­
group­policy­preferences­updated.aspx).
En efecto, con una GPO la contraseña se debe almacenar con la estrategia en SYSVOL el cual es
accesible por todos los usuarios. Lo que desde un punto de vista de la seguridad es claramente
incoherente. Después debe saber que la contraseña no se encuentra almacenada en claro, pero lo
aparenta. En efecto esta se codifica en AES con una clave bien conocida ya que es pública (consúltelo
aquí:
http://msdn.microsoft.com.sabidi.urv.cat/en­us/library/2c15cbf0­f086­4c74­8b70­
1f2fa45dd4be.aspx).
Se imagina por lo tanto con qué facilidad es posible descodificar esta contraseña.
2. Dificultades que superar
Toda la dificultad aquí va a estar ligada al hecho de que en un parque heterogéneo de equipos
funcionando con distintas versiones de sistema operativo, no todas disponen de los mecanismos de
comunicación remota de PowerShell activadas; ¡y es una verdadera pena! Por lo tanto, tendremos que
jugar un poco, tanto con los protocolos RPC/DCOM para estos equipos y con WinRM/WSMan para las
demás.
Sería evidentemente demasiado simple tratar de nivelarlo todo para utilizar RPC/DCOM para todo el
mundo. Por desgracia, desde Windows Server 2012 y Windows 8, las comunicaciones RPC/DCOM no están
autorizadas en el cortafuegos. A menos que autoricemos excepciones para poder alcanzar estos equipos,
tendremos que trabajar con lo que hay y crearemos grupos de equipos. A saber, los gestionados
mediante RPC/DCOM y los gestionados por la «comunicación remota de PowerShell».
3. Solución 1: DCOM/RPC
Escribimos el script apoyándonos en la API ADSI que permite modificar la contraseña de la cuenta de
Administrador local. Este toma la forma de una pequeña función que comprenderá los siguientes
parámetros: ­ComputerName y ­Password.
El parámetro ­ComputerName es un array, y por lo tanto permite pasar simultáneamente un conjunto de
nombres de equipos.
457
Esta función se conecta a la base de cuentas locales de un equipo remoto y cambia su contraseña.
Aquí tiene la función que hemos imaginado:
Function Set­LocalAdminPassword
{
[CmdletBinding()]
Param (
[Alias("CN","MachineName")]
[Parameter(Mandatory=$true, ValueFromPipeline=$true,
HelpMessage="¡Nombre del equipo obligatorio!")]
[String[]]$ComputerName,
[Parameter(Mandatory=$true,
HelpMessage="¡Contraseña obligatoria!")]
[String]$Password
)
Process {
$ComputerName | ForEach {
$objAdminUser = [ADSI]"WinNT://$_/Administrador,user"
$objAdminUser.SetPassword($password)
if ($?)
{
Write­Verbose "$_ : Se ha cambiado la contraseña"
}
else
{
Write­Error "$_ : ¡Imposible cambiar la contraseña!"
}
}
}
}
Probemos la función:
PS > Set­LocalAdminPassword ­ComputerName WS2008R2FR­1,Win8FR­0 `
­Password ’P@ssw0rd1’ ­Verbose
COMENTARIOS : WS2008R2FR­1 : Se ha cambiado la contraseña
COMENTARIOS : Win8FR­0 : Se ha cambiado la contraseña
Como nuestra función acepta como entrada una pipeline (consulte la definición del parámetro
ComputerName), podemos también escribir la siguiente línea de comando:
­
PS > "WS2008R2FR­1","Win8FR­0" | Set­LocalAdminPassword ­Password
’P@ssw0rd1’ ­Verbose
Explicaciones complementarias
¿Igual se ha dado cuenta de que hemos dotado a nuestra función del modo «verbose»? Esto es
automático en cuanto utilizamos funciones avanzadas, es decir en cuanto lo indicamos
[CmdletBinding()] al principio del script. Así el comando Write­Verbose entra en acción en la
función cuando especificamos el conmutador ­Verbose en la llamada a la función avanzada.
También hemos tenido la precaución de crear un alias para el parámetro
en vez de indicar ­ComputerName utilizar ­CN o ­MachineName.
­ComputerName. Así podemos
458
Por último, también hemos añadido un mensaje de ayuda (mediante la palabra clave HelpMessage).
Este mensaje se mostrará si el usuario olvida especificar los parámetros obligatorios en la línea de
comandos al llamar a la función.
Este script funciona para sistemas operativos en español ya que hemos indicado el nombre de una
cuenta de usuario llamada «Administrador» en la consulta ADSI. Si tiene sistemas operativos
americanos, acuérdese de reemplazar «Administrador» por «Administrator».
4. Solución 2: WSMan/WinRM
Para los equipos que no disponen de la comunicación remota de Windows PowerShell, es mejor usar esta
solución. En efecto, esta es mucho más robusta y fiable por el hecho del establecimiento de sesiones
PowerShell remotas.
# Creación de una sesión PowerShell remota hacia varios equipos
PS > $s = New­PSSession ­ComputerName WS2012FR­1, WS2012FR­2, WS2012FR­3
# Importación de la función Set­LocalAdminPassword en la sesión actual
PS > Invoke­Command ­Session $s ­FilePath .\Set­LocalAdminPassword.ps1
# Llamada a la función
PS > Invoke­Command ­Session $s ­ScriptBlock {
Set­LocalAdminPassword ­ComputerName $env:COMPUTERNAME `
­password ’P@ssw0rd1’ ­Verbose }
COMENTARIOS : WS2012FR­1 : Se ha cambiado la contraseña
COMENTARIOS : WS2012FR­2 : Se ha cambiado la contraseña
COMENTARIOS : WS2012FR­3 : Se ha cambiado la contraseña
Puede ser factible con este script modificar también la contraseña de Administrador local de todos sus
puestos de trabajo clientes. Sin embargo, tendrá que lidiar con otra dificultad: los puestos pueden estar
apagados o haber desaparecido de la red (de ahí la necesidad de una buena gestión de cuentas de
equipo, consulte el caso de estudio n°1). No trataremos esta problemática en este caso de estudio, pero
no olvide la funcionalidad Wake­on­LAN que hemos visto como ejemplo en el capítulo Framework .NET y
.NET Core. Le puede ser de utilidad.
Vigilar el registro de un evento en el log
Requisito
Windows Server 2012 como mínimo.
1. Problemática
Tenemos insomnio porque problemas de seguridad informática nos impiden dormir, a veces las pesadillas
se apoderan de nuestro sueño y nos despiertan de repente. Si esto le ocurre también, entonces lea
atentamente lo que sigue...
Con la esperanza de detectar una intrusión en el sistema o simplemente para saber si se han nombrado
nuevos administradores de dominio sin su autorización, puede ser extremadamente interesante vigilar la
creación de cuentas en el grupo «Administradores del dominio». Deseamos por lo tanto estar advertidos
por correo electrónico en cuanto se añade este tipo de cuentas, y eso en la medida de lo posible ¡en
tiempo real o casi!
459
2. Solución
Escribir un script basado en los eventos CIM/WMI que funcione continuamente en segundo plano bajo la
forma de tarea planificada (scheduled job). Este vigila la aparición de un evento particular en los logs de
seguridad. Este evento es el evento de agregación de miembros en un grupo local de seguridad y lleva el
ID 4728. En efecto, cuando una modificación de un grupo de seguridad tiene lugar y si se ha activado la
directiva de auditoría, entonces se registran automáticamente eventos en el log de seguridad de los
controladores de dominio. Este script debe por lo tanto ejecutarse sobre un controlador de dominio.
Deberemos asegurarnos de que se envíe un correo electrónico en caso de agregación de miembros al
grupo «Administradores del dominio» y solo en este caso, pues en caso contrario nos ahogaremos en
mensajes.
Principio de implementación
En primer lugar es necesario activar la auditoría de seguridad mediante las directivas de grupo aplicadas a
los controladores de dominio. Después tendremos que hacer algunas pruebas manuales de agregación de
miembros al grupo «Administradores del dominio» y observar el número de ID atribuido al mensaje de
información correspondiente a la acción en el visor de eventos de seguridad de Windows.
Una vez escrito el script, lo almacenaremos en Windows como tarea planificada de PowerShell para que
este arranque en cada reinicio del equipo. Al final, solo queda esperar las notificaciones por e­mail.
Aquí tiene el script:
# Watch­DomainAdminsGroup.ps1
$action = {
$evt = Get­WinEvent ­FilterHashtable @{Logname="Security"; ID="4728"} ­MaxEvents 1
if ( $evt.message ­match ’Nombre del grupo\W\:\W+Administradores del dominio’)
{
$msg = @"
Evento detectado a : {0}
Fecha de entrada en el log : {1}
Contenido del evento : {2}
"@ ­f (ConvertTo­LocalTime `
­LongDate $event.SourceEventArgs.NewEvent.Time_Created),
$evt.TimeCreated,
$evt.Message
Send­MailMessage ­SmtpServer ’ExchangeSrv’ `
­From ’Alert@ps­scripting.com’ `
­To ’Admins@ps­scripting.com’ `
­Subject ’¡Agregación de un miembro en el grupo
Administradores del dominio!’ `
­Body $msg `
­Encoding Unicode
}
}
$query = "SELECT * FROM __InstanceCreationEvent
WITHIN 30
WHERE Targetinstance ISA ’Win32_NTLogEvent’
AND TargetInstance.EventCode = ’4728’"
Register­CimIndicationEvent ­Query $query ­SourceIdentifier "SpyWatcher" `
­Action $action
460
Explicaciones complementarias
En el script anterior, empezamos creando un bloque de script
$action que se ejecutará en cuanto se
produzca el evento. En el interior de este, recuperamos el último objeto de tipo «entrada de log de
eventos de seguridad» cuyo ID tiene el valor 4728. Esto nos permitirá extraer enseguida su contenido así
como algunas otras propiedades para preparar el cuerpo del mensaje que enviaremos. Realizamos
después una pequeña comparación del contenido del evento para asegurarnos de que este sea relativo al
grupo «Administradores del dominio». Esta evaluación permite evitar enviar correos cuando se modifican
otros grupos distintos al de «Administradores del dominio».
Después construimos una Here­String. Esta última contiene el cuerpo del mensaje que se pasa al comando
Send­MailMessage.
Después llega la consulta WMI relativa a la creación de eventos. Esta se ejecuta cada 30 segundos; se
nos advertirá, por lo tanto, muy rápidamente en el caso de que llegue un nuevo evento. Se pone un filtro
de manera que solo se nos informe de los eventos con ID 4728. Para terminar «pegamos los trozos»
juntos para almacenar un evento CIM/WMI en nuestro sistema, evento que llamaremos «SpyWatcher».
Aquí tiene el contenido de un evento 4728 en el log de seguridad:
Entrada del log de seguridad de un evento de agregación de miembros al grupo Administradores del dominio
461
Ahora, le recomendamos ejecutar el script para probar y ver si recibe bien las notificaciones por e­mail. En
efecto, es preferible avanzar paso a paso pero avanzar en vez de intentar ir demasiado deprisa y que al
final no funcione. Desgraciadamente, es raro que todo funcione a la primera.
Planificación de la tarea
Una vez validado que los correos llegan bien cuando añade un miembro al grupo «Administradores del
dominio», solo le queda planificar la ejecución del script con la ayuda de las tareas programadas de
PowerShell (scheduled jobs).
Para ello, aquí tiene algunos comandos útiles:
# Definición del disparador en el arranque del equipo
PS > $trigger = New­JobTrigger ­AtStartup
# Planificación de la tarea
PS > Register­ScheduledJob ­Name ’Vigilancia grupo Administradores del dominio’ `
­Trigger $trigger `
­FilePath C:\Scripts\Watch­DomainAdminsGroup.ps1 `
­Credential (Get­Credential)
Id
­­
1
Name
­­­­
Vigilancia...
JobTriggers
­­­­­­­­­­­
1
Command
­­­­­­­
C:\Scripts\Watch­DomainAdminsGroup.ps1
Enabled
­­­­
True
# Verificación de la correcta creación de la tarea planificada
PS > Get­ScheduledJob ­Name ’Vigilancia grupo Administradores del dominio’ |
Format­List
InvocationInfo
Definition
Options
Credential
JobTriggers
Id
GlobalId
Name
Command
ExecutionHistoryLength
Enabled
PSExecutionPath
PSExecutionArgs
:
:
:
:
:
:
:
:
:
:
:
:
:
Microsoft.PowerShell.ScheduledJob.ScheduledJobInvocationInfo
System.Management.Automation.JobDefinition
Microsoft.PowerShell.ScheduledJob.ScheduledJobOptions
{1}
2
7ef612f1­2ebd­4efa­bc45­6b4c4cddf2d9
Vigilancia grupo Administradores del dominio
C:\Scripts\Watch­DomainAdminsGroup.ps1
32
True
powershell.exe
­NoLogo ­NonInteractive ­WindowStyle Hidden ­Command
"Import­Module PSScheduledJob; $jobDef = [Microsoft.Power
Shell.ScheduledJob.ScheduledJobDefinition]::LoadFromStore
(’Vigilancia grupo Administradores del dominio ’, ’C:\Use
rs\Administrad or\AppData\Local\Microsoft\Windows\PowerSh
ell\ScheduledJobs’); $jobDef.Run()"
Al guardar la tarea planificada con el comando Register­ScheduledJob, es necesario indicar las
credentials de una cuenta de servicio que sea miembro del grupo administradores del dominio.
Ahora, su controlador de dominio puede reiniciarse tanto como quiera. Estará seguro de que su tarea
planificada se ejecutará y recibirá las notificaciones por correo electrónico en caso de que se agregue un
miembro a este grupo de seguridad muy reservado. Podrá por lo tanto volver a dormir a pierna suelta...
462
Crear cuentas de usuarios por lote
Requisitos
El equipo que ejecuta el script debe ser un controlador de dominio que funcione con Windows
Server 2008 R2 o Windows Server 2012/R2.
Microsoft Excel.
1. Problemática
Pronto será la vuelta al colegio, y ¡vuelta al colegio es sinónimo de jaleo! Como cada año, tendremos
varias centenas de cuentas de usuarios que crear para los estudiantes. Pero este año ya no será como
los anteriores, ya que ¡esta vez automatizaremos la tarea! Aunque tardemos mucho tiempo en poner a
punto el script, es muy probable que aun así ganemos tiempo en comparación con una operación manual.
Y aunque no fuese este el caso, al menos conseguiremos una cierta satisfacción personal e intelectual. Por
otra parte, estaremos seguros de que todas las cuentas se crearán exactamente de la misma forma, lo
que evitará un gran número de errores manuales potenciales. Además, podremos reutilizar este script el
siguiente año; lo que nos dejará tiempo para ir a la playa...
Lo ideal sería que a partir de un archivo de texto, podamos importar los usuarios así como todos los
parámetros asociados; es lo que intentaremos hacer.
2. Solución
Para responder a esta problemática, lo más sencilla será crear un archivo Excel en el que cada línea
contenga la descripción de una cuenta de usuario. Para ello, será necesario empezar nuestro archivo por
una fila de encabezado. Cada valor de esta línea deberá llevar el nombre exacto de la propiedad a crear
igual a como existe en Active Directory Domain Services.
Importaremos después el archivo así creado en PowerShell gracias al comando Import­CSV, y
pasaremos el objeto resultante por el comando New­ADUser y ¡colorín colorado este cuento se acabó!
Explicaciones complementarias
En nuestro ejemplo, utilizaremos para crear nuestro archivo los campos abajo mencionados, pero podría
según las necesidades añadir o suprimir algunos:
Name: nombre del objeto (se trata del nombre visible en la consola de gestión del Active Directory).
SAMAccountName: nombre de login.
Surname: apellidos del usuario.
GivenName: nombre del usuario.
Description: descripción de la cuenta.
ProfilePath: ruta a especificar en caso de perfiles itinerantes.
ScriptPath: script de logon.
HomeDrive: letra de creación del home directory.
HomeDirectory: ruta de red (con formato UNC) hacia una carpeta compartida en un servidor.
463
A continuación puede ver el resultado en una hoja Excel:
Creación de usuarios por lote a partir de un archivo generado en Excel
Una vez creado el archivo Excel, debemos guardarlo con formato CSV (Comma Separated Values), lo que
debería dar algo del estilo:
Name;SAMAccountName;givenName;surname;Description;profilePath;scriptPath;
Perez;Perez;Eduardo;Pérez;Cuenta de usuario;;login.vbs;L:;\\srvfic1\users
Larea;Larea;Pedro;Larea;Cuenta de usuario;;login.vbs;L:;\\srvfic1\users
Gonzalo;Gonzalo;Juan;Gonzalo;Cuenta de usuario;;login.vbs;L:;\\srvfic1\users
Arellano;Arellano;Marcelo;Arellano;Cuenta de usuario;;login.vbs;L:;\\srvfic1\users
Navarro;Navarro;Jorge;Navarro;Cuenta de usuario;;login.vbs;L:;\\srvfic1\users
El hecho de que Excel no sepa guardar archivos CSV con otro separador que no sea un punto y coma no
nos molesta ya que el comando Import­CSV dispone de un parámetro ­Delimiter; este permite
especificar cualquier separador de campo.
La siguiente etapa, aunque parezca anodina, es importante ya que permite validar el correcto formateo de
nuestra archivo CSV y también comprobar que la importación se realiza correctamente. El 90% de los
problemas de scripts basados en archivos CSV podrían evitarse si los usuarios tomasen el tiempo de
realizar esta etapa...
Usemos por lo tanto el comando
Import­CSV y veamos qué nos devuelve:
Import­CSV en acción... en la consola ISE
464
PS > Import­Csv .\ListaUsuarios.csv ­Delimiter ’;’
PowerShell genera un array de objetos personalizados y cada objeto posee las propiedades que se han
definido en el encabezado de nuestro archivo CSV. Es importante que su resultado se parezca al que
hemos obtenido. Si no es el caso, verifique el carácter que utiliza como delimitador.
Gracias al módulo Active Directory disponemos del comando
New­ADUser para crear objetos usuarios.
Tomemos algunos segundos para observar su sintaxis:
SINTAXIS
New­ADUser [­Name] <String> [­AccountExpirationDate <DateTime>]
[­AccountNotDelegated <Boolean>] [­AccountPassword <SecureString>]
[­AllowReversiblePasswordEncryption <Boolean>] [­AuthType <ADAuthType>]
[­CannotChangePassword <Boolean>] [­Certificates <X509Certificate[]>]
[­ChangePasswordAtLogon <Boolean>] [­City <String>] [­Company <String>]
[­CompoundIdentitySupported <Boolean>] [­Country <String>] [­Credential
<PSCredential>] [­Department <String>] [­Description <String>] [­DisplayName
<String>] [­Division <String>] [­EmailAddress <String>] [­EmployeeID <String>]
[­EmployeeNumber <String>] [­Enabled <Boolean>] [­Fax <String>] [­GivenName
<String>] [­HomeDirectory <String>] [­HomeDrive <String>] [­HomePage <String>]
[­HomePhone <String>] [­Initials <String>] [­Instance <ADUser>]
[­KerberosEncryptionType <ADKerberosEncryptionType>] [­LogonWorkstations
<String>] [­Manager <ADUser>] [­MobilePhone <String>] [­Office <String>]
[­OfficePhone <String>] [­Organization <String>] [­OtherAttributes <Hashtable>]
[­OtherName <String>] [­PassThru [<SwitchParameter>]] [­PasswordNeverExpires
<Boolean>] [­PasswordNotRequired <Boolean>] [­Path <String>] [­POBox <String>]
[­PostalCode <String>] [­PrincipalsAllowedToDelegateToAccount <ADPrincipal[]>]
[­ProfilePath <String>] [­SamAccountName <String>] [­ScriptPath <String>]
[­Server <String>] [­ServicePrincipalNames <String[]>] [­SmartcardLogonRequired
<Boolean>] [­State <String>] [­StreetAddress <String>] [­Surname <String>]
[­Title <String>] [­TrustedForDelegation <Boolean>] [­Type <String>]
[­UserPrincipalName <String>] [­Confirm [<SwitchParameter>]] [­WhatIf
[<SwitchParameter>]] [<CommonParameters>]
Si presta atención a los nombres de las propiedades de New­ADUser, comprobará que corresponden
exactamente a los nombres de las propiedades que definimos en el archivo CSV creado anteriormente. En
efecto, cuando utilicemos el pipeline para pasar el objeto resultante de la importación del archivo CSV,
New­ADUser «mapeará» automáticamente las propiedades entre ellas. Esto es así ya que cada
parámetro de este comando acepta la entrada del pipeline por nombre de propiedad
(ValueFromPipelinebyPropertyName).
Solo queda utilizar la «ridículamente corta» línea de comandos que sigue y habremos acabado:
PS > Import­Csv .\ListaUsuarios.csv ­Delimiter ’;’ | New­ADUser
Para finalizar, si deseamos crear usuarios en una unidad organizativa en particular, basta con indicar el
parámetro ­Path, como en el siguiente ejemplo:
PS > Import­Csv .\ListaUsuarios.csv ­Delimiter ’;’ |
New­ADUser ­Path ’OU=Espania,DC=PS­SCRIPTING,DC=COM’
465
Verificar la versión software de una aplicación remota
Requisitos
Comunicación Windows PowerShell (WinRM) activada en todos los equipos.
Módulo Active Directory en el equipo que ejecuta el script.
1. Problemática
Una mañana, su jefe viene a verle y le pide realizar un inventario de las diferentes versiones de una
aplicación desplegada en un importante número de equipos. Generalmente es en este momento cuando
se da cuenta de que no dispone de una herramienta de informes de aplicaciones, ni de un inventario de
despliegue del software actualizado.
2. Solución
Para tratar de responder a este problema, crearemos un script que, para cada cuenta de equipo existente
en Active Directory Domain Services, buscará en el registro el número de versión para una aplicación dada.
Para nuestro ejemplo, cogeremos como aplicación el lector Windows Media Player.
A continuación puede ver el script resultante:
# Get­MediaPlayerVersion.ps1
function Get­Key
{
#Acceso al registro
$Version = (Get­ItemProperty HKLM:\SOFTWARE\Microsoft\MediaPlayer\PlayerUpgrade `
­Name "PlayerVersion" ­ea silentlycontinue).PlayerVersion
if ($Version ­eq $null)
{
$Version="No Instalada"
}
return $Version
}
# Consulta Active Directory
$listaEquipos = Get­ADComputer ­Filter * | Foreach {$_.Name}
foreach ($Equipo in $listaEquipos)
{
if (Test­Connection $Equipo ­quiet ­count 1) # Envio de un solo ping
{
$Version = Invoke­Command $Equipo ­Scriptblock ${function:Get­Key}
}
else
{
$Version = ’Equipo inalcanzable’
}
[PSCustomObject]@{Equipo = $Equipo; Version = $Version}
}
466
Utilización
./Get­MediaPlayerVersion.ps1
Resultado
Equipo
­­­­­­
W2K12
WIN8
WIN7
WIN2K8X64
W2K3R2SRV
Version
­­­­­­­
No Instalada
12,0,9200,16420
Equipo Inalcanzable
No Instalada
10,0,0,3997
Explicaciones complementarias
En primer lugar, definimos la función llamada Get­Key capaz de devolver el valor de la clave
PlayerVersion presente en el registro local. Para ello, llamamos al comando Get­ItemProperty
asociado al provider HKLM:
function Get­Key
{
#Acceso al registro
$Version = (Get­ItemProperty HKLM:\SOFTWARE\Microsoft\MediaPlayer\PlayerUpgrade `
­Name "PlayerVersion" ­ea silentlycontinue).PlayerVersion
if ($Version ­eq $null)
{
$Version="No Instalada"
}
return $Version
}
Después llamamos a la función de una manera un poco particular... Al menos de una manera que no
hemos visto hasta ahora. Observemos por lo tanto la siguiente línea:
$Version = Invoke­Command $Machine ­Scriptblock ${function:Get­Key}
La sintaxis ${function:Get­Key} es en realidad una manera de acceder al contenido de la función.
Así es como si pasamos a Invoke­Command un bloque de script estándar. Además, aunque la función
hubiese tenido parámetros, no hubiese pasado nada ya que un bloque de script puede tener parámetros.
Esto es lo que devuelve esta sintaxis en la consola:
PS > ${function:Get­Key}
#Acceso al registro
$Version = (Get­ItemProperty HKLM:\SOFTWARE\Microsoft\MediaPlayer\PlayerUpgrade `
­Name "PlayerVersion" ­ea silentlycontinue).PlayerVersion
if ($Version ­eq $null)
{
$Version="No Instalada"
}
return $Version
467
Después realizamos una consulta Active Directory para recuperar la lista de equipos del dominio, seguida
de un bucle para aplicar el tratamiento a cada una de ellas. En el interior del bucle, empezamos primero
haciendo ping a los equipos gracias al comando Test­Connection y después, si el equipo responde,
llamamos la función
Get­Key remotamente con la forma de un bloque de script.
Al final devolvemos un objeto personalizado gracias al acelerador de tipo
asociamos con una tabla de hash.
[PSCustomObject], que
Actualizar la configuración de red de un conjunto de equipos
Requisitos
Equipo que ejecute el script: Windows Server 2008 R2 o Windows Server 2012/R2.
Módulo Active Directory.
Equipos remotos soportados: Windows Server 2003 y posteriores.
El cortafuegos de los equipos remotos no debe filtrar: ICMP, RPC/DCOM.
1. Problemática
Acabamos de realizar una migración de nuestros servidores DNS y hemos tenido que instalar nuevos
equipos para reemplazar los antiguos. Por lo tanto para finalizar esta migración, tenemos que actualizar la
configuración de red de todos nuestros servidores para que el cambio sea tomado en cuenta. Nuestros
servidores disponen de una configuración de red estática. Por lo tanto un script será bienvenido para
automatizar este cambio de configuración. Esto evitará tener que modificar a mano la configuración de
cada uno de nuestros servidores, y nos hará ganar un tiempo precioso. Es este caso de estudio,
supondremos que el cortafuegos de red y los de los equipos remotos dejan pasar el protocolo ICMP así
como las consultas WMI.
Aquí tiene los campos que debemos actualizar en la interfaz gráfica:
468
Parámetros de red a modificar
2. Solución
Usaremos WMI para interrogar y modificar remotamente los parámetros de la configuración de red.
En primer lugar, nos concentraremos en realizar una función que recupera la configuración DNS de un
equipo y que devuelve un objeto personalizado. La llamaremos Get­DNSConfiguration. Después
haremos una segunda función para modificar la configuración DNS y la llamaremos Set­
DNSConfiguration. Ambas tomarán como entrada un parámetro para indicar el nombre del equipo
sobre el que actuar.
Aquí tiene el trabajo hecho:
Función
Get­DNSConfiguration
469
function Get­DNSConfiguration
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[String[]]$ComputerName
)
Process
{
foreach ($computer in $ComputerName)
{
if (Test­Connection $Computer ­Quiet ­Count 1)
{
# recuperación de las interfaces de red activas
$cnxActives = Get­WmiObject Win32_NetworkAdapter ­Computer $Computer |
Where {$_.NetConnectionStatus ­eq 2}
# recuperación de la configuración de las interfaces de red activas
$confActive =
Get­WmiObject Win32_NetworkAdapterConfiguration ­Comp $Computer |
Where { ($_.index ­eq $($cnxActives.index)) }
$result = $confActive.DNSServerSearchOrder
$Ping = ’OK’
}
else
{
$result = $null
$Ping = ’NOK’
}
[PSCustomObject]@{ HostName = $computer
Ping
= $ping
DNS
= $result }
}
}
}
Probemos la función:
PS > Get­DNSConfiguration ­ComputerName WIN8
HostName
­­­­­­­­
WIN8
Ping
­­­­
OK
DNS
­­­
{192.168.0.14, 12.168.0.100}
Perfecto, ¡la función funciona correctamente!
Explicaciones complementarias
Param define la variable $ComputerName. Esta es de tipo array de cadena de caracteres. Contiene el o
los nombres de los equipos recuperados por la línea de comandos. Observe que esta propiedad acepta la
entrada del pipeline y que no es obligatoria.
Después llega la esencia misma de la función: la consulta WMI, o más bien las consultas deberíamos decir.
La primera enumera las interfaces de red con un estado de «conexión». La segunda utiliza otra clase WMI
para enumerar las configuraciones de red de las interfaces. Aplicamos un filtro para que solo devuelva la
configuración de las interfaces de red «activas» (conectadas) cuyo identificador se ha recuperado en la
primera consulta.
470
Pedimos la propiedad
activa.
DNSServerSearchOrder, que nos devolverá la configuración DNS de la red
Para terminar, creamos un objeto personalizado de tipo PSCustomObject y le añadimos algunas
propiedades. Esto permite a nuestra función devolver un objeto en vez de simplemente texto.
3. Prueba de la solución
Para probar el trabajo realizado sobre un número importante de equipos, lo ideal sería buscar los nombres
directamente en el Active Directory Domain Services o, más sencillo, en un archivo de texto.
Si no disponemos de una lista de equipos en un archivo de texto, podemos escribir lo siguiente:
PS > Get­Content .\listaEquipos.txt | Get­DNSConfiguration
O también esto:
PS > Get­DNSConfiguration ­ComputerName (Get­Content .\listaEquipos.txt)
Y si obtenemos los equipos desde el Active Directory:
PS > Get­ADComputer ­Filter * | Select­Object Name ­ExpandProperty Name |
Get­DNSConfiguration
Necesitamos obligatoriamente pasar un filtro de tipo Select­Object para recuperar solamente una
única propiedad del objeto: el nombre. En caso contrario, se pasan todas las propiedades de los objetos
al pipeline lo que tendría como consecuencia generar errores. Para tener en cuenta este caso, hubiésemos
tenido que cambiar ValueFromPipeline por ValueFromPipelineByPropertyName a nivel de la
declaración del parámetro ­ComputerName.
En ambos casos obtenemos:
HostName
­­­­­­­­
W2K12
WIN8
WIN7
WIN2K8X64
W2K3R2SRV
WIN2K8R2
Ping
­­­­
OK
OK
NOK
OK
OK
OK
DNS
­­­
{192.168.0.100}
{192.168.0.14, 12.168.0.100}
{192.168.0.14, 12.168.0.100}
{192.168.0.14}
{192.168.0.30}
¿Interesante verdad? Comprobamos de paso que el equipo llamado «WIN7» no ha respondido al test de
conexión y por lo tanto le corresponde el estado «NOK». Por lo tanto no hemos realizado las consultas
WMI en este para evitar un timeout, lo que nos hace ganar tiempo.
Los valores para la propiedad DNS aparecen entre llaves. Esto significa que el resultado obtenido es
de tipo array.
Volvamos a nuestro caso de estudio. Ahora debemos construir la función que realizará la modificación de la
configuración de red. Observémosla:
Función
Set­DNSConfiguration:
471
function Set­DNSConfiguration
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[String[]]$ComputerName,
[Parameter(Mandatory=$false)]
[String[]]$DNSServerList = @(’192.168.0.30’, ’192.168.0.40’)
)
Process
{
# recuperación de las interfaces de red activas
$cnxActives = Get­WmiObject Win32_NetworkAdapter ­Computer $ComputerName |
Where {$_.NetConnectionStatus ­eq 2}
# recuperación de la configuración de las interfaces de red activas
$confActive = Get­WmiObject Win32_NetworkAdapterConfiguration `
­Comp $ComputerName | Where { ($_.index ­eq $($cnxActives.index))
}
$result = $confActive.SetDNSServerSearchOrder($DNSServerList)
if ($result.returnValue ­eq 0)
{
Write­Verbose "$ComputerName : Actualización DNS conseguida"
}
else
{
Write­Error "$ComputerName : ¡Error en la actualización DNS!"
}
}
}
Nuestra función Set­DNSConfiguration es prácticamente idéntica a Get­DNSConfiguration
excepto
que
recupera
una
propiedad
aplicada
a
un
método. Se
trata
del método
SetDNSServerSearchOrder(). Esta recibe como parámetro un objeto de tipo array de cadena de
caracteres que contiene las direcciones IP de los nuevos servidores DNS.
Podemos también notar que tenemos un parámetro adicional: ­DNSServerList. Este último se inicializa
con valores; estos últimos constituyen entonces los valores por defecto.
Observemos nuestra función en acción:
PS > Set­DNSConfiguration ­ComputerName WIN8 ­Verbose
COMENTARIOS : WIN8 : Actualización DNS conseguida
Acabamos de aplicar la nueva configuración DNS con los valores por defecto al equipo WIN8. Verifiquemos
que se ha funcionado correctamente:
PS > Get­DNSConfiguration ­ComputerName WIN8
HostName
­­­­­­­­
WIN8
Ping
­­­­
OK
DNS
­­­
{192.168.1.30, 192.168.1.40}
472
¡Perfecto! ¡Ahora pasemos a la velocidad superior! Supongamos que queremos ser selectivos en la
manera de aplicar los cambios. Solo deseamos aplicar los nuevos parámetros a los equipos que tienen en
su configuración DNS la dirección IP 192.168.0.14.
Antes de modificar nada, veamos primero la configuración general de nuestro parque de equipos :
PS > Get­ADComputer ­Filter * | Select­Object ­Expand Name | Get­DNSConfiguration
HostName
­­­­­­­­
W2K12
WIN8
WIN7
WIN2K8X64
WIN2K8R2
Ping
­­­­
OK
OK
OK
OK
OK
DNS
­­­
{192.168.0.100}
{192.168.0.30, 12.168.0.40}
{192.168.0.14, 12.168.0.100}
{192.168.0.14}
{192.168.0.30}
Probemos con el siguiente filtro:
PS > Get­ADComputer ­Filter * | Select­Object ­Expand Name | Get­DNSConfiguration |
Where { ($_.DNS ­contains ’192.168.0.14’) }
HostName
­­­­­­­­
WIN7
WIN2K8X64
Ping
­­­­
OK
OK
DNS
­­­
{192.168.0.14, 12.168.0.100}
{192.168.0.14}
Hemos obtenido los equipos que tienen en su configuración de red un servidor DNS con la dirección
192.168.0.14. Ahora aplicaremos solamente a este grupo la nueva configuración DNS:
PS > $a = Get­ADComputer ­Filter * | Select­Object ­Expand Name |
Get­DNSConfiguration | Where { ($_.DNS ­contains ’192.168.0.14’) }
PS > $a | Select­Object ­Expand HostName |
Set­DNSConfiguration ­DNS @(’192.168.1.50’, ’192.168.1.60’)
Acabamos de modificar una configuración DNS en particular a nuestra selección de equipos. Lo que nos da
como resultado:
PS > Get­ADComputer ­Filter * | Select Name ­Expand Name | Get­DNSConfiguration
HostName
­­­­­­­­
W2K12
WIN8
WIN7
WIN2K8X64
WIN2K8R2
Ping
­­­­
OK
OK
OK
OK
OK
DNS
­­­
{192.168.0.100}
{192.168.0.30, 12.168.0.40}
{192.168.0.50, 12.168.0.60}
{192.168.0.50, 12.168.0.60
{192.168.0.30}
Todo esto no parece homogéneo... Realicemos un último esfuerzo para poner todos nuestros equipos con
la misma configuración dejando los valores por defecto de la función Set­DNSConfiguration:
473
PS > Get­ADComputer ­Filter * | Select Name ­Expand Name |
Set­DNSConfiguration ­verbose
COMENTARIOS:
COMENTARIOS:
COMENTARIOS:
COMENTARIOS:
COMENTARIOS:
W2K12 : Actualización DNS conseguida
WIN8 : Actualización DNS conseguida
WIN7 : Actualización DNS conseguida
WIN2K8X64 : Actualización DNS conseguida
WIN2K8R2 : Actualización DNS conseguida
Verificamos el trabajo realizado…
PS > Get­ADComputer ­Filter * | Select Name ­Expand Name | Get­DNSConfiguration
HostName
­­­­­­­­
W2K12
WIN8
WIN7
WIN2K8X64
WIN2K8R2
Ping
­­­­
OK
OK
OK
OK
OK
DNS
­­­
{192.168.0.30,
{192.168.0.30,
{192.168.0.30,
{192.168.0.30,
{192.168.0.30,
12.168.0.40}
12.168.0.40}
12.168.0.40}
12.168.0.40}
12.168.0.40}
Encontrar los certificados caducados
Requisitos
Equipo que ejecuta el script: Windows Server 2008 R2 o Windows Server 2012/R2.
Módulo Active Directory (opcional).
La comunicación remota de PowerShell debe estar activada en los equipos remotos.
1. Problemática
En la empresa, las aplicaciones que utilizan certificados para securizar las comunicaciones con numerosas.
Ya sean aplicaciones de trabajo (Web, etc.) o componentes de la infraestructura (agente de supervisión,
de despliegue, etc.), todos son susceptibles de estar en el origen de un despliegue de certificados en los
servidores. Quien dice certificado dice fecha de caducidad. Y evidentemente, darse cuenta demasiado
tarde de que un certificado ha caducado no da buena imagen, sobre todo si provoca una parada de
servicio. Por este motivo, le mostramos cómo PowerShell puede avisar de este tipo de desventura.
2. Solución 1: Tarea planificada local PowerShell
Para responder a esta problemática, podríamos imaginar un script ejecutado localmente en cada máquina
como una tarea planificada y que devuelve el resultado en el visor de eventos o por correo electrónico.
Empecemos creando nuestro script para recorrer el almacén de certificados personales del equipo local y
obtener la lista de los que han caducado.
Como ya hemos visto en este libro, existe un proveedor PowerShell para recorrer los almacenes de
certificados. Se trata del proveedor Cert:.
PS > Gci Cert:\LocalMachine\My
474
Nos queda obtener la lista de los que tienen fecha de caducidad inferior a un límite que fijaremos a 30
días. Para ello, usaremos la propiedad NotAfter de los certificados que corresponde a la fecha final de
validez
20/06/2015 en nuestro ejemplo.
Certificado con fecha de caducidad el 20/06/2015
Nuestro script empieza a tomar forma:
$FechaLimite = (Get­Date).AddDays(30)
Gci Cert:\LocalMachine\My | Where {$_.notafter ­lt $FechaLimite}
El resultado obtenido tiene la forma siguiente:
475
Directorio: Microsoft.PowerShell.Security\Certificate::LocalMachine\my
Thumbprint
Subject
­­­­­­­­­­
­­­­­­­
F898037CC34AD771A3D4A3EEDD4C6F8B121E7476 CN=Win8ES­0.ediciones­eni.local
BC55E36F73B733FAF1B4DE2C91E882A26550B59A CN=Win8ES­0.ediciones­eni.local
Creamos un resultado personalizado con el comando
Select­Object:
$FechaLimite = (Get­Date).AddDays(30)
Gci Cert:\LocalMachine\My | Where {$_.notafter ­lt $FechaLimite} |
Select Subject, NotAfter,
@{Label="Expires In (Days)";Expression={($_.NotAfter ­ (Get­Date)).Days}}
El resultado obtenido ahora es el siguiente:
Subject
­­­­­­­
CN=Win8ES­0.ediciones­eni.local
CN=Win8ES­0.ediciones­eni.local
NotAfter
­­­­­­­­
15/01/2015 23:00:00
29/06/2015 00:00:00
Expires In (Days)
­­­­­­­­­­­­­­­­­
­156
9
Ahora que la visualización del resultado nos satisface, podemos enviarlo por e­mail con el comando
Send­
MailMessage o bien dejar una traza en el visor de eventos (hará que pueda ser « capturada » por una
herramienta de supervisión). Elegiremos esta segunda opción. Para ello usaremos el comando Write­
EventLog.
$FechaLimite = (Get­Date).AddDays(30)
$Resultado = Gci Cert:\LocalMachine\My |
Where {$_.notafter ­lt $FechaLimite} |
Select­Object Subject, NotAfter,
@{Label="Expires In (Days)";Expression={($_.NotAfter ­ (Get­Date)).Days}} |
Out­String
If ($Resultado.count ­gt 0){
New­Eventlog ­LogName "Application" ­Source "PowerShell­Job" ­EA 0
Write­EventLog ­LogName "Application" `
­Source "PowerShell­Job" `
­EventId 1 `
­Message "Certificados que van a caducar: $Resultado" `
­EntryType Error
}
Aquí puede ver el resultado en el log:
476
Evento ligado a la tarea programada para detectar los certificados caducados
Tan solo nos queda planificar nuestro comando mediante un job. Como recordatorio, una tarea (o job)
planificada es simplemente un script o bloque de script que se lanza gracias a una tarea planificada. Las
tareas planificadas pueden consultarse, después, en el planificador de tareas de Windows.
PS > $s = {
$FechaLimite = (Get­Date).AddDays(30)
$Resultado = Gci Cert:\LocalMachine\My |
Where {$_.notafter ­lt $FechaLimite} |
Select­Object Subject, NotAfter,
@{Label="Expires In (Days)";Expression={($_.NotAfter ­ (Get­Date)).Days}} |
Out­String
If ($Resultado.count ­gt 0){
New­Eventlog ­LogName "Application" ­Source "PowerShell­Job" ­EA 0
Write­EventLog ­LogName "Application" `
­Source "PowerShell­Job" `
­EventId 1 `
­Message " Certificados que van a caducar: $Resultado" `
­EntryType Error
}
}
PS > Register­ScheduledJob ­Scriptblock $s ­Name ’Verificación de certificados’
Id
­­
1
Name
JobTriggers
­­­­
­­­­­­­­­­­
Verificación... 0
Command
­­­­­­­
...
Enabled
­­­­­
True
477
Acabamos de crear nuestra tarea. Sin embargo no se ejecutará automáticamente en la medida en que no
hemos definido un planificador. Creemos uno que se ejecutará de manera semanal, los lunes por ejemplo,
a las 8h00.
PS > $Trigger = New­JobTrigger ­Weekly ­At "08:00:00" ­DaysOfWeek Monday
Lo asociamos a nuestra tarea:
PS > Get­ScheduledJob ­Name ’Verificación de certificados’ |
Add­JobTrigger ­Trigger $trigger
Ya hemos acabado. Nuestra tarea está ahora activa en el servidor.
Job PowerShell planificado visto en la consola de gestión de tareas planificadas
3. Solución 2: Consulta desde un punto central
Otra solución consiste en abordar el problema desde un punto de vista radicalmente opuesto. En efecto,
ya no se trata de enviar información desde cada equipo sino consultar los equipos desde un punto central
de administración. La ventaja de esta solución es que puede ver enseguida los sistemas que responden y
los que no, así puede extraer sus conclusiones. Por el contrario, el inconveniente es que esto puede
consumir tiempo y ciertas máquinas pueden no estar disponibles, sobre todo si la extensión de los
servidores se cifra en centenas o miles.
Para poner en marcha esta solución, recuperaremos la lista de equipos que pertenecen al dominio Active
Directory con el comando Get­ADComputer, para después realizar simplemente un Invoke­Command
en el conjunto de esta lista.
478
# Get­ExpiredCertificate.ps1
$liste = Get­ADComputer ­Filter * | Foreach {$_.Name}
$Script = {
$FechaLimite = (Get­Date).AddDays(30);
Gci Cert:\LocalMachine\My | Where {$_.notafter ­lt $FechaLimite} |
Select @{Label="Hostname" ; Expression={$Env:ComputerName}},
Subject, NotAfter,
@{Label="Expires In (Days)"; Expression={($_.NotAfter ­
(Get­Date)).Days}}
}
Invoke­Command ­ComputerName $liste ­ScriptBlock $Script |
Format­Table Hostname,Subject,NotAfter,Expire*
Aquí tiene el resultado. Observará que hemos cambiado ligeramente nuestra consulta para que muestre el
nombre del equipo asociado a cada certificado.
Hostname
­­­­­­­­
WIN8ES
WIN8ES
WIN8
Subject
­­­­­­­
CN=WINES8­0.ediciones­eni.local
CN=WINES8­0.ediciones­eni.local
CN=Win8.ediciones­eni.local
NotAfter
­­­­­­­­
15/01/2015 23:00:00
29/06/2015 00:00:00
20/06/2015 00:00:00
Expires In (Days)
­­­­­­­­­­­­­­­­
­156
9
1
Delegar la gestión de un servidor (solamente algunos
comandos)
Requisitos
Equipo servidor: Windows Server 2012/R2.
Equipo cliente: cualquier sistema operativo que disponga de PowerShell versión 3.0.
1. Problemática
Imagine el escenario donde nos encontramos en un entorno muy restrictivo. En un entorno de este
calibre, deseamos delegar la gestión de algunos comandos PowerShell a una serie de personas, incluso a
un grupo restringido de ellas. En efecto, no queremos que esta persona sea administrador local ya que
podría acceder a datos sensibles y esto no es viable.
Para ilustrar concretamente el escenario, imaginaremos que disponemos de un servidor de impresión en el
que deseamos delegar la gestión de los trabajos de impresión y únicamente los trabajos de impresión.
¿Cómo hacer esto?
2. Solución
Debemos crear una sesión de configuración (endpoint) restringida que dará acceso a los comandos de la
familia PrintJob (Get­PrintJob, Remove­PrintJob, Restart­PrintJob, Resume­PrintJob,
Suspend­PrintJob).
479
Creación del archivo de configuración
Lo primero que debemos hacer, en el servidor de impresión, es crear una configuración de sesión. Para
ello, crearemos primero un archivo de configuración que importaremos en una segunda fase.
La
creación
de
un
archivo
de
configuración
se
realiza
con
el
comando:
New­
PSSessionConfigurationFile.
Aprovecharemos para indicar el autor de este archivo, una descripción, el nombre del módulo a cargar y la
lista de funciones avanzadas a las cuales podremos acceder. Para terminar, gracias al parámetro ­
SessionType indicaremos que deseamos limitar el conjunto de comandos PowerShell al mínimo.
PS > New­PSSessionConfigurationFile `
­Path $home\PrintMgmt.pssc `
­Author ’A. Petitjean’ `
­Description "Endpoint restringido para la gestión de los jobs de impresión" `
­CompanyName "www.PowerShell­Scripting.com" `
­ModulesToImport PrintManagement `
­VisibleFunctions *­PrintJob `
­SessionType RestrictedRemoteServer
Veamos lo que contiene el archivo
PrintMgmt.pssc creado:
@{
# Número de versión del esquema utilizado para este archivo de configuración
SchemaVersion = ’1.0.0.0’
# ID utilizado para identificar de manera única esta configuración de sesión
GUID = ’bfe14211­c144­43f9­9634­42c74e0e3ea0’
# Especifica la estrategia de ejecución para esta configuración de sesión
ExecutionPolicy = ’Restricted’
# Especifica el modo de idioma de esta configuración de sesión
LanguageMode = ’NoLanguage’
# Estado inicial de esta configuración de sesión
SessionType = ’RestrictedRemoteServer’
# Variables de entorno definidas en esta configuración de sesión
# EnvironmentVariables =
# Autor de esta configuración de sesión
Author = ’A. Petitjean’
# Company associated with this session configuration
CompanyName = ’www.PowerShell­Scripting.com’
# Instrucción de copyright para esta configuración de sesión
Copyright = ’(c) 2013 A. Petitjean. Todos los derechos reservados.’
# Descripción de las funcionalidades provistas por esta configuración de sesión
Description = ’Endpoint restringido para la gestión de jobs de impresión’
# Versión del motor Windows PowerShell utilizado para esta configuración de sesión
# PowerShellVersion =
480
# Módulos a importar
ModulesToImport = ’PrintManagement’
# Assemblys que serán cargados en esta configuración de sesión
# AssembliesToLoad =
# Alias visibles en esta configuración de sesión
# VisibleAliases =
# Applets de comando visibles en esta configuración de sesión
# VisibleCmdlets =
# Funciones visibles en esta configuración de sesión
VisibleFunctions = ’*­PrintJob’
# Proveedores visibles en esta configuración de sesión
# VisibleProviders =
# Alias definidos en esta configuración de sesión
# AliasDefinitions =
# Funciones definidas en esta configuración de sesión
# FunctionDefinitions =
# Variables definidas en esta configuración de sesión
# VariableDefinitions =
# Archivos de tipo (.ps1xml) que seran cargados en esta configuración de sesión
# TypesToProcess =
# Archivos con formato (.ps1xml) a cargar en esta configuración de sesión
# FormatsToProcess =
# Especifica los scripts a ejecutar una vez configurada la sesión
# ScriptsToProcess =
}
La siguiente etapa consiste en guardar este archivo en el sistema; dicho de otro modo crear el endpoint.
Creación del endpoint/Importación del archivo de configuración
Ahora, es momento de crear la configuración de sesión. Esta se lleva a cabo gracias al comando
Register­PSSessionConfiguration. Debemos dar un nombre a nuestro endpoint. Lo llamaremos
PrintMgmt, como sigue:
PS > Register­PSSessionConfiguration ­Path $home\PrintMgmt.pssc ­Name
PrintMgmt
Ahora hemos creado nuestra configuración de sesión, pero solo los Administradores y los miembros del
grupo Usuarios para gestión remota pueden conectarse de momento. Verifiquémoslo con la siguiente
línea de comandos:
481
PS > Get­PSSessionConfiguration ­Name PrintMgmt
Name
PSVersion
StartupScript
RunAsUser
Permission
: PrintMgmt
: 3.0
:
:
: BUILTIN\Administradores AccessAllowed, BUILTIN\
Usuarios para gestión remota AccessAllowed
Configuración de los permisos
La última etapa antes de poder acceder a nuestro endpoint: la puesta en marcha de las ACL. La manera
más sencilla de configurarlas es mediante la interfaz gráfica; pero sepa que si debe automatizar esta
tarea, el parámetro ­SecurityDescriptorSddl será más apropiado.
PS > Set­PSSessionConfiguration ­Name PrintMgmt ­ShowSecurityDescriptorUI
Puesta en macha de las ACL en un endpoint personalizado
Debemos dar control total al usuario
Guttenberg; sin embargo el permiso mínimo necesario es el de
Ejecución. Esperemos que sepa hacer buen uso de esta configuración de sesión…
Última comprobación de uso:
482
PS > Get­PSSessionConfiguration ­Name PrintMgmt
Name
PSVersion
StartupScript
RunAsUser
Permission
: PrintMgmt
: 3.0
:
:
: BUILTIN\Administradores AccessAllowed, BUILTIN\
Usuarios para gestión remota AccessAllowed,
PS­SCRIPTING\Guttenberg AccessAllowed
Todo ok, podemos continuar... Ahora solo queda probar el endpoint.
Conexión al endpoint para verificar el buen funcionamiento
Ahora, abrimos una sesión en la red con el usuario Guttenberg desde un equipo miembro del mismo
dominio que el servidor en el que se ha creado el endpoint.
Después abrimos una consola PowerShell y probamos las siguientes líneas:
PS > # Establecimiento de una sesión
PS > $s = New­PSSession ­ComputerName WS2012FR­2 ­ConfigurationName PrintMgmt
PS > # Apertura de la sesión PowerShell remota
PS > Enter­PSSession ­Session $s
[WS2012FR­2]: PS>
Hemos accedido a la sesión remota. Ahora verificamos el conjunto de comandos a nuestra disposición:
[WS2012FR­2]: PS> Get­Command
CommandType
­­­­­­­­­­­
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Function
Name
­­­­
Exit­PSSession
Get­Command
Get­FormatData
Get­Help
Get­PrintJob
Measure­Object
Out­Default
Remove­PrintJob
Restart­PrintJob
Resume­PrintJob
Select­Object
Suspend­PrintJob
ModuleName
­­­­­­­­­­
PrintManagement
PrintManagement
PrintManagement
PrintManagement
PrintManagement
¡Y ya está! Hemos conseguido limitar el conjunto de comandos de Guttenberg. Por lo tanto solo puede
gestionar los trabajos de impresión con los comandos de la familia PrintJob.
No olvide sin embargo otorgar el permiso Operador de impresión a este usuario para que pueda realizar
sus tareas de administración; sin esto se le mostrará un maravilloso mensaje del estilo «No dispone de los
privilegios necesarios para realizar la operación…».
483
Recursos
adicionales
484
Recursos web
En esta sección encontrará algunos recursos sobre información técnica relativa a PowerShell. A la vez útil y
práctica, le permitirá, si lo desea, aumentar sus conocimientos en la materia.
1. Sitios Web en español
Por desgracia, no existen comunidades dedicadas a PowerShell en español. Esperamos que estas líneas
den ideas a algún grupo de administradores de sistemas para ofrecer a través de una comunidad hispana
su experiencia en la materia que seguro que la hay en gran cantidad y calidad. Los únicos sitios que se
pueden encontrar poseen algún tutorial y alguna entrada en blogs de profesionales. Citaremos dos de
ellos para disponer de una referencia en español sobre PowerShell.
a. IT Pro.es: comunidad de profesionales de Infraestructura
IT Pro.es es la comunidad de profesionales de infraestructura más grande del mundo en español. Es una
comunidad que agrupa a los mejores profesionales de España y Latinoamérica y que comparten las
mismas inquietudes. Nuestra comunidad está compuesta por profesionales con el reconocimiento de
Microsoft como Most Value Professional (MVP), Microsoft Active Professional (MAP), Springboard Series
Technical Expert Program (STEP), Microsoft Community Contributor (MCC), Microsoft Regional Director
Programa (MRD) y algunos empleados de Microsoft.
El objetivo de la comunidad es compartir las novedades, problemas y trucos que van surgiendo en el
ámbito de la tecnología para que el resto de profesionales puedan aprender y conocer todos los trucos
para facilitarles el trabajo en su día a día.
485
http://www.itpro.es , la comunidad de profesionales de infraestructura en español
b. Aprende Informática Conmigo
En esta página podrá encontrar algún tutorial interesante sobre PowerShell.
486
http://www.aprendeinformaticaconmigo.com
2. Sitios Web en inglés
a. PowerShell Team Blog
El equipo de PowerShell mantiene un blog (https://blogs.msdn.microsoft.com/powershell) donde
descubrirá todas las novedades, cuando las haya, y sobre todo numerosos trucos o aclaraciones sobre
funciones no documentadas de PowerShell. También puede interactuar con los miembros del equipo
dejando sus comentarios en cada una de sus entradas.
487
El blog del equipo de Microsoft PowerShell
b. Repositorio GitHub PowerShell
PowerShell es, en adelante, un producto open source, de modo que puede acceder al código fuente,
descargar la última versión estable o la última «nightly build», participar en su desarrollo, reportar un
bug o proponer ideas.
Para ello, el sitio https://github.com/powershell/powershell es el lugar adonde debe dirigirse.
488
Repositorio PowerShell en GitHub (1/2)
489
Repositorio PowerShell en GitHub (2/2)
c. PowerShell Magazine
PowerShell Magazine se fundó como iniciativa de tres MVP PowerShell de renombre como son Ravikanth
Chaganti, Shay Levy y Aleksandar Nikolic. Este sitio web muy activo, con casi un truco por día, se
diferencia proponiendo una revista mensual descargable gratuitamente. Entre todos los sitios
comunitarios en inglés, este es con diferencia nuestro preferido.
490
www.powershellmagazine.com, el sitio de la comunidad internacional
Herramientas de terceros
En esta sección encontrará algunas herramientas que resultan muy prácticas y que aportan un entorno de
trabajo confortable cuando se utiliza PowerShell de manera intensiva.
1. PowerShell Plus
Cada vez menos utilizado, en beneficio de Visual Studio Code, PowerShell Plus merece todavía ser citado.
Efectivamente, se trata de otro entorno de desarrollo. Está desarrollado por la empresa Idera.
Inicialmente bajo licencia, ha pasado a ser gratuito. PowerShell Plus dispone también de una librería de
scripts muy rica sobre Active Directory, Exchange, IIS, Hyper­V.
491
PowerShell Plus
2. PowerShell Studio 2017
PowerShell Studio 2017 es mucho más que un simple editor de scripts: se trata de un verdadero entorno
de desarrollo que se sitúa a medio camino entre Visual Studio Code y Visual Studio. PowerShell Studio
es un producto sometido a licencia editado por SAPIEN Technologies, y como la mayor parte de las
herramientas de esta empresa, no muy caro. ¡Y es realmente muy potente!
Esta herramienta ofrece numerosas funcionalidades como la realización de interfaces gráficas Windows
Forms, la creación asistida de módulos, la conversión de funciones clásicas en funciones avanzadas, la
transformación de scripts en archivos ejecutables con la posibilidad de embeber credenciales alternativas,
etc.
492
Sapien PowerShell Studio 2017
3. PowerGadget
Software FX (www.softwarefx.com) es una empresa americana que desarrolla y comercializa gadgets
(www.softwarefx.com/sfxSqlProducts/powergadgets/) para el área de notificación de Windows que puede
programar o personalizar gracias a PowerShell. Estos gadgets son en realidad verdaderos instrumentos
de supervisión de su puesto de trabajo. En efecto, un PowerGadget es un gadget con un diseño
conseguido que puede ser de distintos tipos (indicador de nivel, plano, curvo o hasta gráfico).
4. PowerShell Universal Dashboard
Desarrollado por un apasionado americano muy activo llamado Adam Driscoll, que también es MVP Cloud
and Datacenter Management, PowerShell Universal Dahsboard es una formidable herramienta que permite
generar cuadros de mando web dinámicos, ¡y no solo eso!
Se trata de una herramienta desconcertante fácil de usar que produce resultados de calidad profesional.
Brevemente, diremos que le permitirá sorprender a sus colegas y dejarse querer dentro de su jerarquía,
gracias a sus estupendos cuadros de mando de todo tipo.
493
Además, usuando Universal Dashboard, también podrá crear, en pocas líneas, un Web Service REST con
PowerShell, una verdadera proeza técnica que esperábamos desde hace tiempo.
Universal Dashboard en acción...
Gracias a Universal Dashboard, podrá crear un sitio web completo con PowerShell publicando bonitas
curvas y gráficos (animados dinámicamente). Universal Dashboard ofrece también la posibilidad de crear
formularios que permiten a un usuario introducir datos. Es compatible asimismo con la API OAuth, que
permite a los usuarios identificarse con su cuenta de Google, Twitter, Facebook o Microsoft.
Es una verdadera joya a un precio irrisorio. Unos 20 € por una licencia de servidor. Puede evaluarlo
también de manera gratuita durante 30 días.
494
Sitio web de poshtools.com
Sitio de demostración live: http://universaldashboard.azurewebsites.net/Home
Sitio principal: https://poshtools.com
495
Conclusión
496
Conclusión
Ha hecho falta esperar once años de maduración para que PowerShell adquiriese su título nobiliario. Dicho
esto, no es grave, pues PowerShell supera, en la actualidad, todas nuestras expectativas. En efecto,
¡¿quién habría podido pensar que un día se convertiría en open source y que se utilizaría en otras
plataformas distintas a Microsoft?! Nosotros, desde luego, no, ¡ni tampoco Jeffrey Snover, su
creador!Esperamos que haya disfrutado tanto leyendo el libro como nosotros escribiéndolo. Somos
conscientes de que no le hemos convertido (todavía) en un experto, pero, si ha leído con atención todos los
capítulos, posee todas las bases para comenzar esta aventura. Confiamos que pueda ser autónomo para
profundizar en los temas usted mismo.Se habrá dado cuenta probablemente de que el acceso al Framework
.NET extiende considerablemente el campo de acción de PowerShell. Por lo tanto, según el grado de
complejidad de los scripts, la frontera entre el scripting y el desarrollo es cada vez más fina. Es, por cierto,
debido a esto por lo que el término «DevOps» emergió, como a Jeffrey Snover le gusta emplear con
frecuencia. DevOps es la contracción de las palabras «desarrollador» y «operacional» en inglés. Un Op es
una persona que trabaja en «operaciones», lo que llamamos en castellano un «administrador de sistema
miembro del equipo de producción del servicio informático». En fin, todo esto para decir que los dos mundos,
gracias a PowerShell, tienden a reunirse y eso para la mejora de las empresas. En efecto, estos dos
mundos tenían muchas veces una tendencia a enfrentarse: el uno queriendo siempre actualizar a la última
versión y el otro intentando hacer funcionar mejor o peor la versión en producción. PowerShell se sitúa en la
intersección entre ambos mundos, podrá sorprenderse al ir a charlar con miembros del equipo de desarrollo
de su empresa y viceversa.
Conocemos de hecho numerosos desarrolladores C# que se están volviendo regularmente hacia PowerShell
para probar ciertas clases.NET para realizar maquetas o simplemente para realizar scripts. Lo hacen así
porque PowerShell les hace ganar tiempo por el hecho de que es posible gracias a su intérprete de
comandos probar rápidamente trozos de código sin tener que compilar ni lanzar el entorno Visual Studio
que resulta muy pesado. Hay que confesar que la sintaxis de Windows PowerShell es bastante cercana a la
de C#. Exagerando un poco, podríamos casi asimilarlo a una versión simplificada de C#.
Así un IT Pro ducho en las técnicas de scripting PowerShell y a .NET podrá sin mucho esfuerzo intentar
realizar una incursión en el mundo del desarrollo de aplicaciones en C#. ¿Quizá para añadir funcionalidades
adicionales a PowerShell...?
497
Anexos
498
Sintaxis de las expresiones regulares
PowerShell utiliza los caracteres de expresiones regulares siguientes.
Formato
Lógica
Ejemplo
valor
Hace
corresponder
los
caracteres
exactos en cualquier sitio en el valor de
origen.
"look" ­match "oo"
.
Hace corresponder
cualquiera.
"copy" ­match "c..y"
[Valor]
Hace corresponder con al menos uno de
los caracteres entre los corchetes.
"big" ­match "b[iou]g"
[rango]
Hace corresponder con al menos uno de
los caracteres del rango. el uso del guión
(­) le permite especificar un carácter
adyacente.
"and" ­match "[a­e]nd"
[^]
Hace corresponder todos los caracteres
con excepción de los que se encuentra
entre corchetes.
"and" ­match "[^brt]nd"
^
Hace corresponder los caracteres del
inicio.
"book" ­match "^bo"
$
Hace corresponder los caracteres del
final.
"book" ­match "ok$"
*
Hace corresponder todas las instancias
del carácter anterior.
"baggy" ­match "g*"
?
Hace corresponder una
carácter anterior.
"baggy" ­match "g?"
\
Hace corresponder el carácter que sigue
como un carácter de escape.
con
un
carácter
instancia
del
"Try$" ­match "Try\$"
PowerShell utiliza también las clases de caracteres disponibles en las expresiones regulares de Microsoft
.NET Framework.
Formato
Lógica
Ejemplo
\p{name}
Hace corresponder todos los caracteres de la clase de
caracteres indicada por {name}. Los nombres tenidos
en cuenta son grupo Unicode y rangos de bloques, por
ejemplo Ll, Nd, Z, IsGreek e IsBoxDrawing.
"abcd defg"
match
"\p{Ll}+"
\P{name}
Hace corresponder el texto no incluido en los grupos y
rangos de bloques especificados en {name}.
1234
­match
"\P{Ll}+"
\w
Hace corresponder cualquier carácter de texto.
Equivalente a las categorías de caracteres Unicode
[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}].
Si
el
comportamiento conforme a ECMAScript se especifica
mediante la opción ECMAScript, \w es equivalente a
[a­zA­Z_0­9].
"abcd defg"
match "\w+"
­
(corresponde
abcd)
a
­
499
Formato
\W
Lógica
Ejemplo
Hace corresponder cualquier carácter no textual.
Equivalente
a
las
categorías
Unicode
[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}].
"abcd defg"
match "\W+"
­
(corresponde
espacio)
al
\s
Hace corresponder cualquier espacio. Equivalente a
las
categorías
de
caracteres
Unicode
[\f\n\r\t\v\x85\p{Z}].
"abcd defg"
match "\s+"
­
\S
Hace corresponder cualquier carácter distinto al
espacio. Equivalente a las categorías de caracteres
Unicode [^\f\n\r\t\v\x85\p{Z}].
"abcd defg"
match "\S+"
­
\d
Hace corresponder cualquier cifra decimal. Equivalente
a \p{Nd} para Unicode y a [0­9] para el
comportamiento no Unicode.
12345
"\d+"
­match
\D
Hace corresponder cualquier carácter que no
represente una cifra. Equivalente a \P{Nd} para
Unicode y a [^0­9] para el comportamiento no
Unicode.
"abcd"
"\D+"
­match
Para terminar, PowerShell tiene en cuenta también los cuantificadores disponibles en las expresiones
regulares .NET Framework. Los siguientes elementos son algunos ejemplos de cuantificadores.
Formato
Lógica
Ejemplo
*
Especifica cero o más correspondencias. Por
ejemplo \w * o (abc) *. Equivalente a {0,}.
"abc" ­match "\w*"
+
Hace corresponder instancias repetidas de
caracteres anteriores.
"xyxyxy" ­match "xy+"
?
Especifica cero o una correspondencia. Por
ejemplo \w? o (abc)?. Equivalente a {0,1}.
"abc" ­match "\w?"
{n}
Especifica exactamente n correspondencias.
Por ejemplo, (pizza){2}.
"abc" ­match "\w{2}"
{n,}
Especifica al menos n correspondencias. Por
ejemplo, (abc){2,}.
"abc" ­match "\w{2,}"
{n,m}
Especifica al menos n pero como mucho m
correspondencias.
"abc" ­match "\w{2,3}"
Tenga en cuenta que el carácter de escape de las expresiones regulares, el backslash (\), no es el mismo
que el de Windows PowerShell. El carácter de escape para PowerShell es el carácter backtick (`) (ASCII 96).
Lista de verbos aprobados (Get­Verb)
Verb
­­­­
Add
Clear
Close
Copy
Enter
Exit
AliasPrefix
­­­­­­­­­­­
a
cl
cs
cp
et
ex
Group
­­­­­
Common
Common
Common
Common
Common
Common
Description
­­­­­­­­­­­
Adds a resource to a container, o...
Removes all the resources from a ...
Changes the state of a resource t...
Copies a resource to another name...
Specifies an action that allows t...
Sets the current environment or c...
500
Find
fd
Format
f
Get
g
Hide
h
Join
j
Lock
lk
Move
m
New
n
Open
op
Optimize
om
Push
pu
Pop
pop
Redo
re
Remove
r
Rename
rn
Reset
rs
Resize
rz
Search
sr
Select
sc
Set
s
Show
sh
Skip
sk
Split
sl
Step
st
Switch
sw
Undo
un
Unlock
uk
Watch
wc
Connect
cc
Disconnect dc
Read
rd
Receive
rc
Send
sd
Write
wr
Backup
ba
Checkpoint ch
Compare
cr
Compress
cm
Convert
cv
ConvertFrom cf
ConvertTo
ct
Dismount
dm
Edit
ed
Expand
en
Export
ep
Group
gp
Import
ip
Initialize in
Limit
l
Merge
mg
Mount
mt
Out
o
Publish
pb
Restore
rr
Save
sv
Sync
sy
Unpublish
ub
Update
ud
Debug
db
Measure
ms
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Common
Communications
Communications
Communications
Communications
Communications
Communications
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Diagnostic
Diagnostic
Looks for an object in a containe...
Arranges objects in a specified f...
pecifies an action that retrieve...
Makes a resource undetectable
Combines resources into one resource
Secures a resource
Moves a resource from one locatio...
Creates a resource
Changes the state of a resource t...
Increases the effectiveness of a ...
Adds an item to the top of a stack
Removes an item from the top of a...
Resets a resource to the state th...
Deletes a resource from a container
Changes the name of a resource
Sets a resource back to its origi...
Changes the size of a resource
Creates a reference to a resource...
Locates a resource in a container
Replaces data on an existing reso...
Makes a resource visible to the user
Bypasses one or more resources or...
Separates parts of a resource
Moves to the next point or resour...
Specifies an action that alternat...
Sets a resource to its previous s...
Releases a resource that was locked
Continually inspects or monitors ...
Creates a link between a source a...
Breaks the link between a source ...
Acquires information from a source
Accepts information sent from a s...
Delivers information to a destina...
Adds information to a target
Stores data by replicating it
Creates a snapshot of the current...
Evaluates the data from one resou...
Compacts the data of a resource
Changes the data from one represe...
Converts one primary type of inpu...
Converts from one or more types o...
Detaches a named entity from a lo...
Modifies existing data by adding ...
Restores the data of a resource t...
Encapsulates the primary input in...
Arranges or associates one or mor...
Creates a resource from data that...
Prepares a resource for use, and ...
Applies constraints to a resource
Creates a single resource from mu...
Attaches a named entity to a loca...
Sends data out of the environment
Makes a resource available to others
Sets a resource to a predefined s...
Preserves data to avoid loss
Assures that two or more resource...
Makes a resource unavailable to o...
Brings a resource up­to­date to m...
Examines a resource to diagnose o...
Identifies resources that are con...
501
Ping
Repair
Resolve
Test
Trace
Approve
Assert
Build
Complete
Confirm
Deny
Deploy
Disable
Enable
Install
Invoke
Register
Request
Restart
Resume
Start
Stop
Submit
Suspend
Uninstall
Unregister
Wait
Use
Block
Grant
Protect
Revoke
Unblock
Unprotect
pi
rp
rv
t
tr
ap
as
bd
cmp
cn
dn
dp
d
e
is
i
rg
rq
rt
ru
sa
sp
sb
ss
us
ur
w
u
bl
gr
pt
rk
ul
up
Diagnostic
Diagnostic
Diagnostic
Diagnostic
Diagnostic
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Lifecycle
Other
Security
Security
Security
Security
Security
Security
Use the Test verb
Restores a resource to a usable c...
Maps a shorthand representation o...
Verifies the operation or consist...
Tracks the activities of a resource
Confirms or agrees to the status ...
Affirms the state of a resource
Creates an artifact (usually a bi...
Concludes an operation
Acknowledges, verifies, or valida...
Refuses, objects, blocks, or oppo...
Sends an application, website, or...
Configures a resource to an unava...
Configures a resource to an avail...
Places a resource in a location, ...
Performs an action, such as runni...
Creates an entry for a resource i...
Asks for a resource or asks for p...
Stops an operation and then start...
Starts an operation that has been...
Initiates an operation
Discontinues an activity
Presents a resource for approval
Pauses an activity
Removes a resource from an indica...
Removes the entry for a resource ...
Pauses an operation until a speci...
Uses or includes a resource to do...
Restricts access to a resource
Allows access to a resource
Safeguards a resource from attack...
Specifies an action that does not...
Removes restrictions to a resource
Removes safeguards from a resourc...
Lista de orígenes de trace (Get­TraceSource)
Name
­­­­
CmdletProviderClasses
CmdletProviderContext
CommandDiscovery
CommandSearch
ConsoleControl
ConsoleHost
ConsoleHostRunspaceInit
ConsoleHostUserInterface
ConsoleLineOutput
DisplayDataQuery
ETS
FileSystemProvider
FormatFileLoading
FormatInfoDataClassFactory
FormatViewBinding
InternalDeserializer
LocationGlobber
MemberResolution
Modules
MshSnapinLoadUnload
Description
­­­­­­­­­­­
The namespace provider base classes tracer
The context under which a core command is being run.
Traces the discovery of cmdlets, scripts, functions...
CommandSearch
Console control methods
ConsoleHost subclass of S.M.A.PSHost
Initialization code for ConsoleHost’s Runspace
Console host’s subclass of S.M.A.Host.Console
ConsoleLineOutput
DisplayDataQuery
Extended Type System
The namespace navigation provider for the file system
Loading format files
FormatInfoDataClassFactory
Format view binding
InternalDeserializer class
The location globber converts PowerShell paths...
Traces the resolution from member name to the...
Module loading and analysis
Loading and unloading mshsnapins
502
ParameterBinderBase
ParameterBinderController
ParameterBinding
PathResolution
PSDriveInfo
PSSnapInLoadUnload
RegistryProvider
RunspaceInit
SessionState
TypeConversion
TypeMatch
A abstract helper class for the CommandProcessor...
Controls the interaction between the command
Traces the process of binding the arguments to...
Traces the path resolution algorithm.
The namespace navigation tracer
Loading and unloading mshsnapins
The namespace navigation provider for the Windows...
Initialization code for Runspace
SessionState Class
Traces the type conversion algorithm
F&O TypeMatch
d e s c a r g a do en: e y b o oks. c o m
503
Descargar