Búsqueda de sitios web

Obtenga enlaces de políticas de grupo con PowerShell


Recientemente estuve conversando con mi amiga Gladys Kravitz sobre informes de políticas de grupo. y la discusión me llevó a desempolvar un código antiguo que tenía para obtener enlaces de políticas de grupo usando PowerShell. El módulo GroupPolicy tiene un comando Set-GPLink, pero nada que le muestre fácilmente qué GPO están vinculados a su sitio, dominio y unidades organizativas. Aunque es posible que no necesite dicho informe, espero que haya algo en mis técnicas de secuencias de comandos que le resulte útil.

Comience con un comando central

Como ocurre con cualquier proyecto de PowerShell, debes comenzar desde la consola. ¿Puedes ejecutar un comando de forma interactiva que te proporcione la esencia de tu objetivo? En mi caso, este es el cmdlet Get-GPInheritance del módulo GroupPolicy. Cuento contigo para leer ayuda y ejemplos. Así es como me muestra lo que está vinculado a nivel de dominio.

Al observar el resultado, veo una propiedad que probablemente tenga la información que busco.

Esto parece prometedor. Puedo verificar que también funciona con unidades organizativas.

(Get-ADOrganizationalUnit -filter * | Get-GPInheritance).GpoLinks | 
 Select-Object -Property Target,DisplayName,Enabled,Enforced,Order |
 Format-Table

Esto se ve bastante bien y se acerca a lo que esperaba lograr.

Consulta de enlaces a sitios

¿Qué pasa con los sitios? Suponiendo que no conozco los nombres distintivos de todos mis sitios, quiero una solución programática. Puedo utilizar el módulo ActiveDirectory para descubrir sitios.

$getADO = @{
     LDAPFilter = "(Objectclass=site)"
     properties = "Name"
     SearchBase = (Get-ADRootDSE).ConfigurationNamingContext
 }
 $sites = Get-ADObject @getADO

Solo tengo un sitio, pero desafortunadamente Get-GPInheritance no puede usar sitios. Hay formas de explorar Active Directory y analizar enlaces a sitios, pero decidí tomar una forma más fácil y recurrir al objeto COM heredado, GPMGMT.GPM. Así es cómo.

Primero, necesito una referencia de objeto. También obtendré todas las constantes que requiere el objeto.

$gpm = New-Object -ComObject "GPMGMT.GPM"
$gpmConstants = $gpm.GetConstants()

Voy a necesitar referencias al dominio y al bosque.

$gpmdomain = $gpm.GetDomain("company.pri", "", $gpmConstants.UseAnyDC)
$SiteContainer = $gpm.GetSitesContainer("company.pri", "company.pri", $null, $gpmConstants.UseAnyDC)

Mi dominio y mi bosque se llaman Company.pri.

El objeto $SiteContainer tiene un método GetSite(), pero necesita el nombre de un sitio. Pero eso lo entendí antes.

Este nuevo objeto tiene un método llamado GetGPOLinks().

Eso es bastante bueno. Lo único que me falta es el nombre del GPO.

$site.GetGPOLinks() | Select GPOId,@{Name="DisplayName";Expression = {$gpmdomain.GetGPO($_.gpoid).Displayname}},Enabled,Enforced,SOMLinkOrder

Como tengo el GPOId, puedo obtener el GPO. Este código utiliza el objeto COM. Podría haber usado el cmdlet, pero necesitaría analizar los caracteres {}.

$site.GetGPOLinks() | Select GPOId,@{Name="DisplayName";Expression = {(Get-GPO -id ($_.gpoid.replace("{|",""))).Displayname}},Enabled,Enforced,SOMLinkOrder

Con estas piezas en su lugar, puedo crear una herramienta PowerShell en torno a estos fragmentos de código.

Obtener-GPLink

Escribí una función llamada Get-GPLink. Dado que estoy usando los cmdlets GroupPolicy y ActiveDirectory, y admiten la especificación de un servidor y/o dominio, quería admitir eso también. Inicialmente, intenté usar splatting y asignar dinámicamente valores de parámetros. Pero esto rápidamente se volvió difícil de manejar. En su lugar, defino una versión de alcance de script de los valores de $PSDefaultParameter.

if ($Server) {
    $script:PSDefaultParameterValues["Get-AD*:Server"] = $server
    $script:PSDefaultParameterValues["Get-GP*:Server"] = $Server
}

if ($domain) {
    $script:PSDefaultParameterValues["Get-AD*:Domain"] = $domain
    $script:PSDefaultParameterValues["Get-ADDomain:Identity"] = $domain
    $script:PSDefaultParameterValues["Get-GP*:Domain"] = $domain
}

Ahora no tengo que preocuparme por eso. Si ejecuto Get-ADDomain y el parámetro Servidor se usa desde mi función, el cmdlet Get-ADDomain lo usará automáticamente.

La función revisa y consulta el dominio y las unidades organizativas utilizando el módulo GroupPolicy. Los enlaces a nivel de sitio se recuperan mediante el objeto COM. Técnicamente, podría haber usado el objeto COM para todo.

La función recopila todos los enlaces, lo que me permite ofrecer filtrado por nombre de GPO o mostrar solo enlaces habilitados o deshabilitados.

Eso no es tan malo. Pero siempre quiero más. Por ejemplo, no puedo canalizar la salida de mi función a Get-GPO. Aunque puedo, si hago un pequeño ajuste.

Pero no quiero hacer eso todo el tiempo. También me gustaría una salida predeterminada de una tabla. Sé que puedo hacerlo con un archivo format.ps1xml personalizado y mi confiable comando New-PSFormatXML. Pero para hacer eso necesito un nombre de tipo único y personalizado.

En mi código, puedo insertar un nuevo nombre de tipo ya que no estoy creando un objeto personalizado desde cero como suelo hacer.

$results.GetEnumerator().ForEach( { $_.psobject.TypeNames.insert(0, "myGPOLink") })

Una vez que tenga un nuevo nombre de tipo, puedo definir extensiones de tipo.

Update-TypeData -MemberType AliasProperty -MemberName GUID -Value GPOId -TypeName myGPOLink -Force
Update-TypeData -MemberType AliasProperty -MemberName Name -Value DisplayName -TypeName myGPOLink -Force
Update-TypeData -MemberType AliasProperty -MemberName GPO -Value DisplayName -TypeName myGPOLink -Force
Update-TypeData -MemberType AliasProperty -MemberName Link -Value Target -TypeName myGPOLink -Force
Update-TypeData -MemberType AliasProperty -MemberName Domain -Value GpoDomainName -TypeName myGPOLink -Force
Update-TypeData -MemberType ScriptProperty -MemberName TargetType -Value {
    switch -regex ($this.target) {
        "^((ou)|(OU)=)" { "OU" }
        "^((dc)|(DC)=)" { "Domain" }
        "^((cn)|(CN)=)" { "Site" }
        Default { "Unknown"}
    }
} -TypeName myGPOLink -Force

Esto hace que ahora sea mucho más fácil trabajar con los enlaces de mi Política de grupo.

Formato personalizado

Por supuesto, como tengo un tipo personalizado, puedo crear un archivo format.ps1xml y hacerlo realmente elegante. Ahora puedo crear una vista de tabla predeterminada de los resultados. Y como siempre estoy buscando formas de agregar color a PowerShell, decidí resaltar los enlaces deshabilitados y aquellos que se aplican mediante secuencias de escape ANSI.

<TableColumnItem>
    <ScriptBlock>
    <!-- use ANSI formatting if using the console host-->
    if ($host.name -eq 'ConsoleHost') {
        if ($_.Enabled) {
        $_.Enabled
        }
        else {
        "$([char]0x1b)[1;91m$($_.enabled)$([char]0x1b)[0m"
        }
    }
    else {
        $_.Enabled
    }
    </ScriptBlock>
</TableColumnItem>

El bloque de script solo usa ANSI si PowerShell detecta un host de consola. Estoy usando una secuencia de escape que funcionará tanto en Windows PowerShell como en PowerShell 7.x.

El archivo de formato también incluye otra vista.

Resumen

Utilizo el flujo de trabajo que acabo de describir en gran parte de mi trabajo. Empiezo con los comandos principales, construyo un contenedor sólido alrededor de los comandos en forma de función, escribo un objeto en la canalización y, si es necesario, lo personalizo con extensiones de tipo y formato. El resultado final debería ser una herramienta de PowerShell útil y fácil de usar que se ejecute en proceso como cualquier otro comando de PowerShell.

Si quieres ver cómo funciona todo esto, publiqué el código como esencia de GitHub. Si desea probarlo, deberá guardar ambos archivos en el mismo directorio. Preste mucha atención al nombre del archivo de formato, ya que se carga cuando obtiene el archivo de función.

Si tiene alguna pregunta sobre lo que hice o por qué, no dude en dejar un comentario.

Artículos relacionados