Administración de Windows

Simplificación de la administración de directivas de grupo con Windows PowerShell

Thorbjörn Sjövold

 

Resumen:

  • ¿Qué es Windows PowerShell?
  • Cómo se administraban anteriormente los GPO
  • Migración de secuencias de comandos a Windows PowerShell

La tecnología de directivas de grupo de Microsoft no se estableció de la noche a la mañana, era algo difícil de entender y necesitaba la aplicación de Active Directory, un extraño nuevo servicio que no se parecía en nada a los dominios de cuentas y recursos que eran los estándares del momento. Hoy, las directivas de grupo

son la red troncal de administración de casi todas las organizaciones que cuentan con una infraestructura Windows®. Tengo la sensación de que precisamente lo mismo sucederá con Windows PowerShell™, la última tecnología de administración de Microsoft. De hecho, es probable que Windows PowerShell facilite notablemente su trabajo como administrador de directivas de grupo.

En este artículo, muestro cómo las API de la Consola de administración de directivas de grupo de Microsoft® (GPMC) escritas para lenguajes de Windows Scripting Host como VBScript (y en general, para lenguajes de secuencias de comandos basados en COM), se pueden emplear directamente en Windows PowerShell para simplificar la manera en que administra directivas de grupo en su entorno.

Tareas de directivas de grupo para secuencias de comandos

Cuando hace algunos años Microsoft lanzó al mercado GPMC, de pronto los administradores de directivas de grupo tuvieron varias características de gran utilidad a su disposición. En particular, el complemento de MMC centrado en directivas de grupo representó un importante adelanto para la administración de directivas de grupo, especialmente en comparación con usuarios y equipos de Active Directory®. Además, había una API completamente nueva que permitió que los administradores usaran lenguajes basados en COM, como por ejemplo VBScript, para realizar administración de directivas de grupo para tareas como copia de seguridad y restauración de objetos de directiva de grupo (GPO), migración de GPO entre dominios, configuración de valores de seguridad en GPO y vínculos y generación de informes.

Desafortunadamente, la GPMC no ofrecía la capacidad de editar los valores reales configurados dentro de los objetos de directiva de grupo. En otras palabras, podía realizar operaciones en el contenedor de GPO, como por ejemplo la lectura de versiones de GPO y fechas de modificación, la creación de nuevos GPO, la copia de seguridad y restauración o importación de GPO de diferentes dominios, etc., pero no podía agregar ni cambiar mediante programación el contenido del GPO; por ejemplo, agregando una nueva carpeta redirigida o un nuevo software de instalación. Lo que en su lugar hacía generalmente era crear el GPO y configurar todos los valores manualmente con el Editor de objetos de directiva de grupo, a continuación realizar una copia de seguridad e importarla en un entorno de pruebas. Una vez que todo se comprobaba y funcionaba correctamente, se importaba en el entorno de producción. A pesar de la característica ausente, el uso de secuencias de comandos en lugar de interacción manual con la API de GPMC tuvo como resultado un ahorro muy importante en cuanto al tiempo, esfuerzo y errores de la administración diaria de directivas de grupo.

El siguiente nivel

¿En qué se diferencia Windows PowerShell de los lenguajes de secuencias de comandos como VBScript? Para principiantes, Windows PowerShell es un shell y, al menos para nuestro propósito, puede considerar un shell como un intérprete de línea de comandos. Aunque VBScript se puede ejecutar desde la línea de comandos, un archivo VBScript no se puede ejecutar línea por línea. En cambio, una secuencia de comandos de Windows PowerShell se puede crear sobre la marcha como una serie de comandos individuales. Además, Windows PowerShell tiene funciones que trabajan en forma muy similar a las subrutinas de VBScript, y que se pueden crear en tiempo real en el símbolo del sistema de Windows PowerShell.

Aún mejor, Windows PowerShell se crea a partir de Microsoft .NET Framework, mientras que VBScript depende de la antigua tecnología COM. Esto significa que la inmensa cantidad de código .NET que se produce hoy se puede usar directamente desde Windows PowerShell.

Al final, el resultado es que con Windows PowerShell se obtiene compatibilidad absoluta con secuencias de comandos y el modo interactivo, todo en un mismo paquete. Todos los ejemplos que ofrezco aquí serán de entradas de línea de comandos que puede escribir mientras lee. Sin embargo, funcionarán igual si los pone en un archivo de secuencias de comandos de Windows PowerShell y lo ejecuta.

Recreación de secuencias de comandos antiguas con Windows PowerShell

Lo último que uno desea al comenzar con una nueva tecnología es tener que descartar todo el trabajo anterior. Hay tres métodos que puede usar para tener acceso a objetos COM desde la API de GPMC, o básicamente para volver a usar cualquier secuencia de comandos VBScript antigua que ande por allí. Puede seleccionar una de tres opciones:

  • Crear un cmdlet de Windows PowerShell que usa un lenguaje de programación como C# o C++ administrado.
  • Usar Windows PowerShell para tener acceso a ScriptControl en MSScript.ocx para ajustar antiguas secuencias de comandos.
  • Ajustar las llamadas COM en funciones Windows PowerShell que se pueden volver a usar o llamar directamente a los objetos COM.

Me centraré principalmente en la tercera opción, pero veamos primero rápidamente todas las opciones.

Creación de un cmdlet de Windows PowerShell

Microsoft incluyó un gran número de cmdlets con Windows PowerShell que permiten copiar archivos, dar formato a los resultados, recuperar la fecha y hora, etc., pero también se pueden cmdlets propios. El proceso se documenta completamente en msdn2.microsoft.com/ms714598.aspx. En resumen, los pasos son los siguientes:

  • Crear una biblioteca de clases DLL en un lenguaje de programación .NET como C#.
  • Crear una nueva clase y heredar de la clase base cmdlet.
  • Establecer atributos que determinen el nombre, el uso, los parámetros de entrada, etc., y agregar el código.

Como Windows PowerShell se crea a partir de Microsoft .NET Framework, cualquier tipo, como por ejemplo, una cadena, objeto, etc., que se devuelva o pase como parámetro tiene exactamente el mismo código que en Windows PowerShell; no se necesita ningún tipo de conversión especial.

El verdadero poder de esta solución es que tiene a su disposición un lenguaje de programación completo.

Ajuste de antiguas secuencias de comandos con el objeto ScriptControl en MSScript.ocx

Evidentemente, se necesita el motor VBScript para ejecutar un archivo VBScript. Lo que no es tan obvio es que este motor es un objeto COM y, dado que puede usar objetos COM desde Windows PowerShell, puede invocar el motor VBScript. A continuación se muestra el aspecto de esto:

$scriptControl = New-Object -ComObject ScriptControl
$scriptControl.Language = ‘VBScript’
$scriptControl.AddCode(
    ‘Function ShowMessage(messageToDisplay)
    MsgBox messageToDisplay
    End Function’)
$scriptControl.ExecuteStatement(‘ShowMessage
    “Hello World”’)

Si escribe este código en la interfaz de línea de comandos (CLI) de Windows PowerShell, la función ShowMessage de VBScript se llama y ejecuta con un parámetro, y aparecerá un cuadro de mensaje con el texto Hello World.

Ahora alguno puede pensar que esto es estupendo. Que ahora domina el arte de usar COM en Windows PowerShell y puede dejar de leer este artículo y comenzar a llenar el objeto ScriptControl con su colección de antiguas secuencias de comandos de GPMC. Desafortunadamente, no es así. A medida que las secuencias de comandos se hace más grandes, esta técnica se vuelve rápidamente muy compleja y difícil de depurar.

Ajuste de objetos COM

Por lo tanto, la mejor opción es la tercera: ajustar las llamadas COM en funciones Windows PowerShell que se pueden volver a usar y que le permiten emplear los objetos COM en la API de GPMC. La línea siguiente muestra cómo crear un objeto .NET directamente en Windows PowerShell. En este caso, se trata de un objeto FileInfo que se puede usar para obtener el tamaño del archivo:

$netObject = New-Object System.IO.FileInfo(
“C:\boot.ini”) # Create an instance of FileInfo 
               # representing c:\boot.ini

Tenga en cuenta que # se usa en Windows PowerShell para comentarios en línea. Al usar este objeto FileInfo con su instancia recién creada, se puede obtener de forma sencilla el tamaño del archivo boot.ini simplemente escribiendo el siguiente código:

$netObject.Length # Display the size in bytes of the
                  # file in the command line interface

Un momento, ¿no se supone que hablaríamos de objetos COM y conversión de VBScript? Sí, pero observe el siguiente comando:

$comFileSystemObject = New-Object –ComObject Scripting.FileSystemObject

Observará que la sintaxis es básicamente la misma que usé antes para crear objetos nativos de .NET Framework, con dos diferencias. En primer lugar, agregué el modificador -ComObject que apunta Windows PowerShell hacia el mundo COM en vez del mundo .NET. En segundo lugar, uso un ProgID de COM en vez de un constructor de .NET; en este caso Scripting.FileSystemObject. El ProgID es el mismo nombre que usa siempre. En VBScript, el equivalente será:

Set comFileSystemObject = CreateObject(
    “Scripting.FileSystemObject”)

Para obtener el tamaño del archivo con VBScript, agregue la línea anterior a un archivo, junto con el siguiente código:

Set comFileObject = comFileSystemObject.GetFile(
    “C:\Boot.ini”)
WScript.Echo comFileObject.Size

A continuación, ejecútelo mediante Cscript.exe, por ejemplo. En Windows PowerShell, lo haría (directamente desde la línea de comandos de Windows PowerShell si lo desea) de la siguiente manera:

$comFileObject = $comFileSystemObject.GetFile(
    “C:\boot.ini”)
$comFileObject.Size

Por supuesto, para convertir un VBScript que lee el tamaño de un archivo, podría haber usado el cmdlet de Windows PowerShell que administra objetos en unidades de disco, pero quise mostrarle qué fácil es tener acceso a COM desde Windows PowerShell. Observe que aunque le indiqué a Windows PowerShell que creara un objeto COM, el objeto que realmente se crea aquí, $comFileSystemObject, es un objeto .NET que ajusta el objeto COM y expone su interfaz. En realidad, esto no importa para el alcance de este artículo.

Windows PowerShell en acción

Ahora que ha visto cómo tener acceso a COM desde Windows PowerShell, concentrémonos en directivas de grupo. Los ejemplos mostrarán fragmentos cortos de código para darle una idea de cómo usar las API de GPMC desde Windows PowerShell. Puede obtener un conjunto completo de funciones de Windows PowerShell para administrar directivas de grupo en la descarga de código relacionada con este artículo, en línea en technetmagazine.com/code07.aspx. En la figura 1 se muestra una lista de las funciones incluidas en la descarga.

Figure 1 Funciones personalizadas en la descarga

Nombre de la función Descripción
BackupAllGpos Permite realizar una copia de seguridad de todos los GPO en un dominio
BackupGpo Permite realizar una copia de seguridad de un único GPO
RestoreAllGpos Permite restaurar en un dominio todos los GPO a partir de una copia de seguridad
RestoreGpo Permite restaurar un único GPO a partir de una copia de seguridad
GetAllBackedUpGpos Permite recuperar la última versión de las copias de seguridad de GPO desde una ruta de acceso en particular
CopyGpo Permite copiar la configuración de un GPO a otro
CreateGpo Permite crear un nuevo GPO vacío
DeleteGpo Permite eliminar un GPO
FindDisabledGpos Devuelve todos los GPO donde tanto la parte del usuario como del equipo están deshabilitadas
FindUnlinkedGpos Devuelve todos los GPO que no tienen vínculos
CreateReportForGpo Permite crear un informe XML para un GPO único en un dominio
CreateReportForAllGpos Permite crear un informe XML separado por cada GPO en un dominio
GetGpoByNameOrID Permite buscar un GPO por su nombre para mostrar o Id. de GPO
GetBackupByNameOrId Permite buscar una copia de seguridad de GPO por su nombre para mostrar o Id. de GPO
GetAllGposInDomain Devuelve todos los GPO en un dominio

Mientras lee esta sección, si lo desea puede iniciar la línea de comandos de Windows PowerShell y escribir los comandos. Sin embargo, recuerde que algunos comandos dependen de comandos anteriores. Es decir, algunos de los objetos creados inicialmente se usarán más tarde, de modo que debe permanecer en la misma sesión de Windows PowerShell. Si cierra la sesión, tendrá que comenzar de nuevo desde el principio, volviendo a escribir todos los comandos.

Creemos entonces un nuevo GPO que usa Windows PowerShell. El equipo de directivas de grupo de Microsoft incluyó con GPMC varias muestras de VBScript completamente operativas que puede aprovechar para acelerar las cosas. Están en el directorio %ProgramFiles%\GPMC\Scripts, donde también encontrará un archivo llamado gpmc.chm que contiene la documentación de la API de GPMC. Veamos la secuencia de comandos CreateGPO.wsf y analicemos la base de su funcionamiento.

Al principio verá esta línea:

Dim GPM
Set GPM = CreateObject(“GPMgmt.GPM”)

Básicamente, es el punto de partida de cualquier sesión de administración de directivas de grupo o secuencia de comandos porque crea una instancia de la clase GPMgmt.GPM que permite tener acceso a la mayor parte de la funcionalidad de GPMC. Sigamos adelante y hagamos esto desde Windows PowerShell en su lugar:

$gpm = New-Object -ComObject GPMgmt.GPM

Ahora que tenemos el punto de partida de la administración de directivas de grupo, el paso siguiente es resolver qué se puede hacer. Normalmente, con este tipo de información iría a la documentación, pero Windows PowerShell tiene una característica realmente increíble. Si escribe la siguiente línea, obtendrá el resultado que se indica en la figura 2:

Figura 2 Resultado de Get-Member

Figura 2** Resultado de Get-Member **(Hacer clic en la imagen para ampliarla)

$gpm | gm

Creo que esto es genial. Tenga en cuenta cómo el cmdlet Get-Member (o gm, obtener miembro) le permite consultar las propiedades y los métodos que admite el objeto desde la línea de comandos. Por supuesto, no es lo mismo que leer la documentación, pero facilita el uso de objetos con los que ya está familiarizado cuando no recuerda el número exacto de parámetros, el nombre exacto, etc. Un punto importante a tener en cuenta es que al mirar las listas de nodos de la documentación de GPMC, se observa que el objeto GPM y todas las otras clases están prefijadas con la letra I; esto se debe al funcionamiento interno de COM y aquí no le prestaremos atención; está pensado para programadores de C++ que escriben código COM nativo y denotan la diferencia entre una interfaz y la clase que implementa. Observe también que al usar las API de GPMC, hay un solo objeto que se debe crear de este modo, GPMgmt.GPM; todos los demás objetos se crean con métodos que comienzan con este objeto de GPM.

Continuemos ahora con la creación de un nuevo GPO.

En la figura 3 se ilustra lo sencillo que es crear un GPO. Tenga en cuenta que omití parte del código, incluido el control de errores (por ejemplo, lo que sucedería si no se le permite crear GPO), y codifiqué un nombre de dominio, pero puede captar la idea.

Figure 3 Creación de un GPO

$gpmConstants = $gpm.GetConstants() 
# This is the GPMC way to retrieve all 
# constants
$gpmDomain =$gpm.GetDomain(“Mydomain.local”, “”, $gpmConstants.UseAnyDC)
# Connects to the domain where the GPO should 
# be created, replace Mydomain.local with the 
# name of the domain to connect to.
$gpmNewGpo = $gpmDomain.CreateGPO() 
# Create the GPO
$gpmNewGpo.DisplayName = “My New Windows PowerShell GPO” 
# Set the name of the GPO

Ahora que sabe cómo crear un GPO, abramos en cambio uno existente. Todavía tiene la referencia al dominio, $gpmDomain, de modo que escriba lo siguiente:

$gpmExistingGpo = $gpmDomain.GetGPO(
  “{31B2F340-016D-11D2-945F-00C04FB984F9}”) 
# Open an existing GPO based on its GUID, 
# in this case the Default Domain Policy.
$gpmExistingGpo.DisplayName 
# Show the display name of the GPO, it 
# should say Default Domain Policy
$gpmExistingGpo.GenerateReportToFile($gpmConstants.ReportHTML, “.\DefaultDomainPolicyReport.html”

Esto le ofrece un informe HTML completo de la configuración en la directiva de dominio predeterminada, pero obviamente puede usar cualquiera de los métodos y propiedades, como por ejemplo ModificationTime que indica la última vez que se modificó el GPO, para resolver cuándo se modificaron cualquiera de los valores en el GPO.

Esto es de gran utilidad. Probablemente habrá estado en una situación en donde los teléfonos empiezan a sonar como locos con usuarios que se quejan de que sus equipos se comportan extrañamente. Sospecha que esto se relaciona con una configuración de GPO modificada, agregada o eliminada, pero no tiene un indicio de qué GPO mirar. ¡Windows PowerShell viene al rescate! Si escribe la secuencia de comandos mostrada en la figura 4 en la línea de comandos de Windows PowerShell, obtendrá todos los GPO modificados en las últimas 24 horas.

Figure 4 Descubrimiento de los GPO modificados

$gpmSearchCriteria = $gpm.CreateSearchCriteria() 
# We want all GPOs so no search criteria will be specified
$gpmAllGpos = $gpmDomain.SearchGPOs($gpmSearchCriteria) 
# Find all GPOs in the domain
foreach ($gpmGpo in $gpmAllGpos)
{
if ($gpmGpo.ModificationTime -ge (get-date).AddDays(-1)) {$gpmGpo.DisplayName}
# Check if the GPO has been modified less than 24 hours from now 
}

Observe el operador -ge, que significa mayor o igual que. Quizá parezca extraño si estaba acostumbrado a los operadores < y > de otras secuencias de comandos o lenguajes de programación. Pero estos operadores se usan para la redirección, por ejemplo para redireccionar el resultado a un archivo, y no se pueden usar como operadores de comparación en Windows PowerShell.

Conclusión

El código de la figura 5 incluye una lista completa de secuencias de comandos para copiar la configuración de un GPO a otro. Ahora debe tener una buena idea acerca de cómo usar esta nueva tecnología con directivas de grupo y cómo volver a usar cualquier objeto COM o código VBScript que emplea un objeto COM.

Figure 5 Copia de la configuración de un GPO a otro

###########################################################################
# Function  : CopyGpo
# Description: Copies the settings in a GPO to another GPO
# Parameters : $sourceGpo     - The GPO name or GPO ID of the GPO to copy
#           : $sourceDomain   - The dns name, such as microsoft.com, of the domain where the original GPO is located
#           : $targetGpo      - The GPO name of the GPO to add
#           : $targetDomain   - The dns name, such as microsoft.com, of the domain where the copy should be put
#           : $migrationTable - The path to an optional Migration table to use when copying the GPO
# Returns   : N/A
# Dependencies: Uses GetGpoByNameOrID, found in article download
###########################################################################
function CopyGpo(
 [string] $sourceGpo=$(throw ‘$sourceGpo is required’),
 [string] $sourceDomain=$(throw ‘$sourceDomain is required’),
 [string] $targetGpo=$(throw ‘$targetGpo is required’),
 [string] $targetDomain=$(throw ‘$targetDomain is required’),
 [string] $migrationTable=$(“”),
 [switch] $copyAcl)
{
 
 $gpm = New-Object -ComObject GPMgmt.GPM # Create the GPMC Main object
 $gpmConstants = $gpm.GetConstants() # Load the GPMC constants
 $gpmSourceDomain = $gpm.GetDomain($sourceDomain, “”, $gpmConstants.UseAnyDC) # Connect to the domain passed 
                                                                              # using any DC
 $gpmSourceGpo = GetGpoByNameOrID $sourceGpo $gpmSourceDomain
 # Handle situations where no or multiple GPOs was found
 switch ($gpmSourceGpo.Count)
 {
   {$_ -eq 0} {throw ‘No GPO named $gpoName found’; return}
   {$_ -gt 1} {throw ‘More than one GPO named $gpoName found’; return} 
 }
 if ($migrationTable)
 {
   $gpmMigrationTable = $gpm.GetMigrationTable($migrationTable)
 }

 $gpmTargetDomain = $gpm.GetDomain($targetDomain, “”, $gpmConstants.UseAnyDC) # Connect to the domain passed 
                                                                              # using any DC

 $copyFlags = 0
 if ($copyAcl)
 {
   $copyFlags = Constants.ProcessSecurity
 }
 $gpmResult = $gpmSourceGpo.CopyTo($copyFlags, $gpmTargetDomain, $targetGpo)
 [void] $gpmResult.OverallStatus
 
}

Windows PowerShell será, al igual que las directivas de grupo son, una parte natural de cualquier entorno de administración de Windows. Pero hay millones de líneas de VBScript por allí que deberán migrarse o mantenerse, y con suerte, este tutorial ayudará.

Hay varias fuentes que puede usar para mejorar la administración de directivas de grupo y otras áreas donde anteriormente usó VBScript, incluidas las funciones de Windows PowerShell en la descarga, así como una estupenda guía sobre la conversión de VBScript a Windows PowerShell en el sitio web de TechNet que le ofrece sugerencias sobre cómo realizar las tareas habituales en Windows PowerShell cuando ya conoce su equivalente en VBScript. Lo encontrará en microsoft.com/technet/scriptcenter/topics/winpsh/convert.

Además, la API de GPMC está totalmente documentada. Puede descargar la información del sitio de directivas de grupo en la dirección microsoft.com/grouppolicy.

Por último, pero no por ello menos importante, si aún no instaló Windows PowerShell, ¿qué espera? Descárguelo de microsoft.com/powershell. Diviértase.

Thorbjörn Sjövold es el director tecnológico y fundador de Special Operations Software (www.specopssoft.com), un proveedor de productos de extensión de administración y seguridad de sistemas basados en directivas de grupo. Puede ponerse en contacto con él en la dirección thorbjorn.sjovold@specopssoft.com.

© 2008 Microsoft Corporation and CMP Media, LLC. Reservados todos los derechos; queda prohibida la reproducción parcial o total sin previa autorización.