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