El Blog de Gualtrysoft

Windows 2000/2003/2008, Active Directory, VBScript, Hyper-V, PowerShell y todo aquello interesante a la hora de usar, configurar y administrar Windows Server. También tenemos longanizas…

PowerShell: Cmdlet para Obtener un Listado de las Membresías de Grupo de Un Usuario

Posted by urpiano en Miércoles 24 \24\UTC octubre \24\UTC 2012

Este Cmdlet permite listar los grupos a los que pertenece un usuario, así como información propia del usuario y del contenedor en el que está albergada su cuenta (OU). Las membresías que se obtienen son:

  • Membresías directas: el usuario es miembro de un grupo y aparece como tal en la lista de la pestaña "Miembros" de las propiedades del grupo.
  • Membresías heredadas: el usuario es miembro de un grupo A que es, a su vez, miembro de un grupo B; esto hace al usuario miembro del grupo B, aunque no aparezca en la lista de la pestaña "Miembros" de las propiedades del grupo B.
  • Membresías por ser el grupo primario del usuario: el usuario tiene como grupo primario (propiedad primaryGroupId) a determinado grupo (de forma predeterminada los usuarios de un dominio pertenecen al grupo "Usuarios del dominio"); en este caso, el usuario tampoco aparece en la lista de la pestaña "Miembros" de las propiedades del grupo que tiene como grupo primario.

Este Cmdlet está concebido para la obtención de listados de usuarios y sus membresías, dando muchas posibilidades sobre la información que se puede obtener, debido a que:

  • No se obtienen simplemente los nombres o datos sobre los grupos a los que pertenece el usuario, si no que además se obtienen los datos propios del usuario y del contenedor que lo alberga, pudiendose en un listado enumerar las propiedades que se necesiten del usuario, la OU y el grupo.
  • Para comodidad en la generación de los listados, la devolución es una colección objetos personalizados, uno por cada membresía, que tiene información "habitual" del usuario e información "esencial" de la OU y del grupo.
  • Para mayor extensión de la información que se puede obtener, el objeto personalizado incluye tres propiedades que son en realidad tres objetos DirectoryEntry, correspondientes al usuario, la OU y el grupo, lo que permite acceder a otras propiedades no incluídas directamente en el objeto personalizado de la devolución.

El Cmdlet recibe como parámetros:

  • El objeto DirectoryEntry del usuario que se desea listar (requerido).
  • Nombre de usuario con el que se realizará la conexión para consultar a Active Directory (opcional); si se omite, se usarán las credenciales del usuario que está lanzando el Cmdlet.
  • Contraseña del usuario de conexión (opcional); si se omite el nombre de usuario será ignorado este parámetro.

Este Cmdlet requiere para su funcionamiento estos otros Cmdlets míos:

El Cmdlet incluye ayuda, pego a continuación la ayuda con modificador -Detailed (esta ayuda se basa en la prestación de ayuda basada en comentarios de PowerShell 2.0 ¿Qué todavía tienes la versión 1.0 y no la 2.0? ¿A qué esperas para instalarla si es mucho mejor?):

NOMBRE
    Get-UserMembership
    
SINOPSIS
    Devuelve información de usuario, así como sus membresías.
    
SINTAXIS
    Get-UserMembership [[-UserDE] ] [[-UserName] ] [[-Password] ] []
    
DESCRIPCIÓN
    Esta función recibe un objeto DirectoryEntry de un usuario de dominio y
    devuelve una colección de objetos personalizados en el que hay propiedades del
    usuario, la del grupo del que es miembro (por membresía directa, heredada o ser
    el grupo primario), indica si es administrador del dominio o no, número de
    grupos de los que es miembro, el nombre cacnónico y distinguido del contenedor
    en que está ubicado y los objetos DirectoryEntry correspondientes al usuario, el
    contenedor en que está ubicado y el grupo. Es un objeto de devolución
    excesivamente "desnormalizado" (empleando terminología de base de datos), pero
    esto es así porque la intención es que sea muy cómodo a la hora de realizar
    listados (nuevamente en terminología de base de datos, está más pensado para
    Business Intelligence que para operacional); si se quiere hacer de manera más
    "normalizada", es fácil modificar el script para que devuelva un único objeto
    por usuario (ahora devuelve uno por cada membresía de cada usuario) en el que se
    incluya una colección con los datos de los grupos a los que pertenece.
    
    La función permite la recepción por canalización del objeto DirectoryEntry del
    usuario, lo que hace que se pueda recibir los objetos procedentes de una
    consulta y poder así realizar un listado (es interesante, por tanto el uso de mi
    otra función Get-LDAPSearch que encapsula al objeto DirectorySearcher y no
    devuelve objetos DirectorySearchResult, como hace DirectorySearcher, si no que
    devuelve objetos DirectoryEntry.
    
    La función admite establecer el usuario y la contraseña con la que se conectará
    a Active Directory para obtener la información.

PARÁMETROS
    -UserDE 
        Objeto DirectoryEntry del usuario a procesar. Se puede recibir por
        canalización. Admite el alias DE.
        
    -UserName 
        Usuario con el que se producirá la validación al conectar al dominio;
        en caso de no establecerse, la conexión se realizará validando con el usuario
        que lanza la función. Este parámetro admite, además, el alias U.
        
    -Password 
        Contraseña del usuario con el que se producirá la validación al conectar
        al dominio; en caso de no pasarse el parámetro UserName, la conexión se
        realizará validando con el usuario que lanza la función y si Password fuese
        pasado se ignorará este parámetro. Este parámetro admite, además, el alias P.
        
    
        Este cmdlet admite los parámetros comunes Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer y OutVariable. Para obtener más información, escriba:
        "get-help about_commonparameters".
    
    -------------------------- EJEMPLO 1 --------------------------
    
    PS C:\>$DN = "CN=Pi, Filemon,OU=Agentes,DC=Tia,DC=org"
    
    PS C:\> $Filemon = New-Object System.DirectoryServices.DirectoryEntry $DN
    PS C:\> Get-UserMembership -UserDE $Filemon
    
    Obtiene la información y membresías del usuario cuyo nombre distinguido ha sido
    establecido en la variable $DN
    
    -------------------------- EJEMPLO 2 --------------------------
    
    PS C:\>$DN = "CN=Pi, Filemon,OU=Agentes,DC=Tia,DC=org"
    
    PS C:\> $Bac = "AdminBacterio"
    PS C:\> $Pass = "OfeliaFOCA"
    PS C:\> $Filemon = New-Object System.DirectoryServices.DirectoryEntry $DN,$Bac,$Pass
    PS C:\> Get-UserMembership -DE $Filemon -U $Bac -P $Pass
    
    Obtiene la información y membresías del usuario cuyo nombre distinguido ha sido
    establecido en la variable $DN, validándose al dominio con el usuario
    AdminBacterio y contraseña OfeliaFOCA
     
    -------------------------- EJEMPLO 3 --------------------------
    
    PS C:\>$DN = "OU=Agentes,DC=Tia,DC=org"
    
    PS C:\> $Filtro = "(&(objectClass=user)"
    PS C:\> $Filtro = "$Filtro(objectCategory=person)"
    PS C:\> $Filtro = "$Filtro(!(objectClass=contact)))"
    PS C:\> Get-LDAPSearch -C $DN -F $Filtro -S OneLevel | `
    >>  Get-UserMembership -U $Bac -P $Pass
    
    Obtiene la información y membresías de los usuarios ubacdos en el contenedor
    cuyo nombre distinguido ha sido establecido en la variable $DN, validándose al
    dominio con el usuario AdminBacterio y contraseña OfeliaFOCA
    
    -------------------------- EJEMPLO 4 --------------------------
    
    PS C:\>$DN = "OU=Agentes,DC=Tia,DC=org"
    
    PS C:\> $Bac = "AdminBacterio"
    PS C:\> $Pass = "OfeliaFOCA"
    PS C:\> $Filtro = "(&(objectClass=user)"
    PS C:\> $Filtro = "$Filtro(objectCategory=person)"
    PS C:\> $Filtro = "$Filtro(!(objectClass=contact)))"
    PS C:\> Get-LDAPSearch -C $DN -F $Filtro -S Subtree -U $Bac -P $Pass | `
    >>  Get-UserMembership -U $Bac -P $Pass
    
    Obtiene la información y membresías de los usuarios contenidos en el sub-árbol
    cuyo contenedor raíz tiene como nombre distinguido el establecido en la variable
    $DN, validándose al dominio con el usuario AdminBacterio y contraseña OfeliaFOCA
    
NOTAS
    Para ver los ejemplos, escriba: "get-help Get-UserMembership -examples".
    Para obtener más información, escriba: "get-help Get-UserMembership -detailed".
    Para obtener información técnica, escriba: "get-help Get-UserMembership -full".

Veremos a continuación un ejemplo de uso de este Cmdlet. En él obtendremos un fichero con el listado, en valores separados por tabuladores, de las membresías de todos los usuarios del dominio desde el que está lanzado el script:

# Establecemos el filtro LDAP con el que obtendremos todos los usuarios del
# dominio (no queremos contactos, de ahí el incluir (!(objectClass=contact))
$Filtro = "(&(objectClass=user)"
$Filtro = "$Filtro(objectCategory=person)"
$Filtro = "$Filtro(!(objectClass=contact)))"

# Ejecutamos la consulta LDAP, obtenemos las membresías de los usuarios
# encontrados por la consulta, seleccionamos las propiedades que nos interesan,
# el resultado lo convertimos a CSV, poniendo como separador el tabulador,
# convertimos el resultado a cadena (muchas propiedades no aparecerían en el
# listado como el valor, si no como el tipo) y por fin mandamos la salida a un
# fichero (si existe será sobreescrito). Todo esto, gracias al encaminamiento,
# es realizado en una sola línea.
Get-LDAPSearch -Filter $Filtro | Get-UserMembership | `
    Select-Object -Property Name,sAMAccountName,DisplayName,UserPrincipalName, `
                            distinguishedName,SID,Administrator, `
                            ParentCN,Title,Department,whenCreated, `
                            AccountExpirationDate,GroupNT,GroupDN,GroupSID | `
    ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Out-String | `
    Set-Content \\bacterio-pc\listados\usuarios-membresias.tab -Force

Este es el código del Cmdlet (para mayor facilidad en el Copy/Paste, incluyo los otros Cmdlets requeridos, así no es necesario ir recopilándolos e incluyéndolos, basta con copiar y pegar todo el código que viene a continuación):

Function Get-DirectoryEntryByPath([String]$Path, `
                                  [String]$UserName, `
                                  [String]$Password)
{
    # Obtendremos el objeto DirectoryEntry del contenedor pasando o no usuario y
    # contraseña en función de lo recibido como parámetros
    If([System.String]::IsNullOrEmpty($UserName))
    {
        # Obtenemos el objeto DirectoryEntry del contenedor sin pasar usuario y
        # contraseña
        $Parent = New-Object System.DirectoryServices.DirectoryEntry $Path
    }
    Else
    {
        # Obtenemos el objeto DirectoryEntry del contenedor pasando usuario y
        # contraseña
        $Parent = New-Object System.DirectoryServices.DirectoryEntry `
                                $Path,$UserName,$Password
    }
    
    # Devolvemos el objeto DirectoryEntry del contenedor
    Return $Parent

<#
    .SYNOPSIS
        Devuelve el objeto DirectoryEntry correspondiente a una ruta LDAP.

    .DESCRIPTION
        Esta función recibe una ruta LDAP y devuelve el objeto DirectoryEntry
correspondiente a esa ruta. Puede recibir o no nombre de usuario y contraseña
con las que se accederá a Active Directory para obtener el objeto.
        
        Si bien puede tener más usos, está principalmente pensada para poder
obtener el objeto DirectoryEntry del contenedor en el que está otro objeto, pues
la propiedad Parent de un DirectoryEntry tiene la ruta LDAP del contenedor en el
que está ubicado.

    .PARAMETER  Path
        Ruta LDAP del objeto DirectoryEntry a obtener.

    .PARAMETER  UserName
        Nombre del usuario con el que se conecta a Active Directory para
obtener el objeto.

    .PARAMETER  Password
        Contraseña del usuario con el que se conecta a Active Directory para
obtener el objeto.

    .EXAMPLE
        PS C:\> $Path = "LDAP://cn=Mortadelo,ou=Agentes,dc=tia,dc=org"
        PS C:\> Get-DirectoryEntry -Path $Path

Devuelve el objeto DirectoryEntry cuya ruta LDAP es la pasada como Path.

    .EXAMPLE
        PS C:\> $Path = "LDAP://cn=Mortadelo,ou=Agentes,dc=tia,dc=org"
        PS C:\> $U = "bacterio"
        PS C:\> $P = "OfeliaFOCA"
        PS C:\> Get-DirectoryEntry -Path $Path -UserName $U -Password $P

Devuelve el objeto DirectoryEntry cuya ruta LDAP es la pasada como Path,
conectando a active directory con el usuario "bacterio" cuya contraseña es
"OfeliaFOCA".

    .EXAMPLE
        PS C:\> $Path = "LDAP://cn=Mortadelo,ou=Agentes,dc=tia,dc=org"
        PS C:\> $Mortadelo = Get-DirectoryEntry -Path $Path
        PS C:\> $MortadeloOU = Get-DirectoryEntry -Path $Mortadelo.Parent

Devuelve el objeto DirectoryEntry cuya ruta LDAP es la pasada como Path y lo
almacena en la variable $Mortadelo. A continuación se obtiene el objeto
DirectoryEntry del contenedor en el que está $Mortadelo y se almacena en la
variable $MortadeloOU.

    .INPUTS
        System.String,System.String,System.String

    .OUTPUTS
        System.DirectoryServices.DirectoryEntry

    .NOTES
        Fernando Reyes López © 2012.

    .LINK
        http://freyes.svetlian.com

    .LINK
        https://urpiano.wordpress.com

#>

}
Function Get-StringSID($SID)
{
    # Comprobamos si hemos recibido directamente la propiedad objectSID de un
    # objeto DirectoryEntry; en caso afirmativo cambiaremos el parámetro para
    # que sea el array de bytes con el SID en lugar de un objeto del tipo
    # PropertyValueCollection, tipo que tiene la propiedad objectSID de un
    # objeto DirectoryEntry
    If(($SID.GetType()).Name -eq "PropertyValueCollection")
    {
        # Cambiamos el parámetro a array de Bytes invocando la propiedad Value
        # del objeto PropertyValueCollection recibido
        $SID = $SID.Value
    }
    # Comprobamos que se ha recibido un array de Bytes
    ElseIf(($SID.GetType()).Name -ne "Byte[]")
    {
        # Mostramos un mensaje indicado que el tipo no es válido y salimos de la
        # función
        Write-Host "Tipo no valido`:$(($SID.GetType()).Name)"
        Return
    }
    # Comenzamos a montar la devolucióniniciando con "S-" y agregando el primer
    # Byte, que contiene la revisión de SID
    $Devolucion = "S-$($SID[0].ToString())-"
    
    # Ahora debemos montar la autoridad, que consiste de los siguientes 6 bytes.
    # Si el 5º ó 6º byte no son cero se montará en la cadena cada byte en
    # hexadecimal con dos dígitos (1,2,3,4,5,6: 010203040506)
    If($SID[5] -ne 0 -or $SID[6] -ne 0)
    {
        $Devolucion = "$Devolucion$("{0:x2}{1:x2}{2:x2}{3:x2}{4:x2}{5:x2}" -f $SID[1..6])"
    }
    # Si tanto el 5º como el 6º bytes son cero, se deberá sumar el valor de los
    # otros cuatro bytes, desplazando a la izquierda el segundo 8 bits, el
    # tercero 16 y el cuarto 24 (un 1 desplazado 8 bits se convierte en 256)
    Else
    {
        # Asignamos el primer byte al valor; se asigna tal cual, pues este no
        # tiene que tener desplazamiento a la izquierda.
        $Value = $SID[1]
        # En este bucle iremos sumando los restantes bytes (2º, 3º y 4º),
        # desplazando, como dijimos antes, el 2º 8 bits, el 3º 16 y el 4º 24.
        For($i = 1;$i -lt 4;$i++)
        {
            # Obtenemos el valor del Byte
            $Byte = $SID[($i + 1)]
            # Lo desplazamos lo que corresponda según sea el 2º. eñ 3º o el 4º
            (1..(8 * $i))|ForEach{$Byte = $Byte * 2}
            # Sumamos lo obtenido al valor
            $Value = $Value + [Int32]$Byte
        }
        # Agregamos el valor obtenido de autoridad
        $Devolucion = "$Devolucion$($Value.ToString())"
    }
    # A continuación vamos a concatenar las sub-autoridades que haya
    # Lo primero es averiguar cuántas subautoridades hay. Este dato está en el
    # 8º byte
    $NumSubAthorities = [Int32]$SID[7]
    
    # En este bucle iremos agregando cada una de las sub-autoridades. Cada
    # sub-autoridad consta de 4 bytes
    For($i = 0; $i -lt $NumSubAthorities;$i++)
    {
        # Obtenemos el índice en el array del primer byte de la sub-autoridad
        $Indice = 8 + ($i * 4)
        # Determinados SIDs especiales presentarán un número de subautoridades
        # que no tendrán los correspondientes bytes, de ahí el tener que salir
        # del bucle si el índice obtenido es superior al total de bytes del SID
        If($SID.Count -le $Indice){Break}
        # La subautoridad es un entero sin signo de 32 bits. Usamos el método
        # ToUInt32 del objeto BitConverter, al que se le pasa como primer
        # parámetro un array de Bytes y como segundo el índice del primer
        # elemento del entero resultante; la función devolverá un entero de 32
        # bits sin signo compuesto por el del índice pasado y los tres
        # siguientes
        [UInt32]$SubAuthority = [System.BitConverter]::ToUInt32($SID,$Indice)
        # Agregamos la sub-autoridad, previo guión parra separarla
        $Devolucion = "$Devolucion-$($SubAuthority.ToString())"
    }
    # Una vez montado el SID, la función termina devolviéndola
    Return [String] $Devolucion

<#
    .SYNOPSIS
        Esta función recibe un SID y lo devuelve como cadena de SID.

    .DESCRIPTION
        Esta función recibe un SID y lo devuelve como cadena de SID. Puede
        recibir tanto el propio SID (que es un array de bytes) como la propiedad
        objectSID de un objeto DirectoryEntry.

    .PARAMETER  SID
        SID a obtener su cadena SID. Puede ser tanto un array de Bytes como un
        objeto PropertyValueCollection correspondiente a la propiedad objectSID
        de un objeto DirectoryEntry; en este caso, la función obtendrá el array
        de bytes que compone el SID para obtener su cadena SID.

    .EXAMPLE
        PS C:\> Get-StringSID -SID $arrSID
        La función devolverá la cadena SID del array de bytes $arrSID

    .EXAMPLE
        PS C:\> Get-StringSID -SID $Usuario.objectSID
        La función devolverá la cadena SID correspondiente a la propiedad
        objectSID del objeto DirectoryEntry $Usuario

    .INPUTS
        System.Byte[],System.DirectoryServices.PropertyValueCollection

    .OUTPUTS
        System.String

    .NOTES
        Fernando Reyes López © 2012

    .LINK
        http://freyes.svetlian.com

    .LINK
        https://urpiano.wordpress.com

#>

}

Function Get-NB2DN([Alias("NB")][String]$NetBios, `
                   [Alias("DN")][String]$distinguishedName, `
                   [Alias("U")][String] $UserName, `
                   [Alias("P")][String] $Password)
{
    # Creamos un objeto NameTranslate
    $Trans = New-Object -ComObject "NameTranslate"
    # Obtenemos el tipo del objeto, lo que nos permitirá invocar sus métodos
    $Type = $Trans.GetType()
    # Si NetBios no esta vacío, significa que hay que traducir de NT a DN
    If([String]::IsNullOrEmpty($NetBios) -eq $false)
    {
        # Este es el tipo que se suministrará como origen
        $NameType = 3 # Valor de ADS_NAME_TYPE_NT4
        # Este es el tipo que se solicitará en la traducción
        $NewType = 1  # Valor de ADS_NAME_TYPE_1779
        # Este es el nombre que se suministrará
        $Name = $NetBios
    }
    # Si distinguishedName no está vacío significa que hay que traducir de DN a
    # NT
    ElseIf([String]::IsNullOrEmpty($distinguishedName) -eq $false)
    {
        # Este es el tipo que se suministrará como origen
        $NameType = 1 # Valor de ADS_NAME_TYPE_1779
        # Este es el tipo que se solicitará en la traducción
        $NewType = 3  # Valor de ADS_NAME_TYPE_NT4
        # Este es el nombre que se suministrará
        $Name = $distinguishedName
    }
    # Si no se ha pasado ni nombre distinguido ni nombre netbios, se muestra un
    # mensaje notificándolo y se devuelve cadena vacía
    Else
    {
        Write-Host "Get-NB2DN: No se ha pasado ningún nombre" `
                   -ForegroundColor Red
        Return ""
    }
    # Lo primero es iniciar el traductor, indicando que se usará catálogo global
    # (ese 3 significa ADS_NAME_INITTYPE_GC).
    # La iniciación es diferente si pasamos o no credenciales. En primer lugar
    # lo haremos si no se han pasado credenciales
    If([String]::IsNullOrEmpty($UserName))
    {
        $Type.InvokeMember("Init", "InvokeMethod", $Null, $Trans, `
                                                          (3, $Null)) | Out-Null
    }
    # Ahora lo haremos si se han pasado credenciales
    Else
    {
        # Miraremos ahora si se ha recibido el nombre de dominio
        # (DOMINIO\Usuario)
        If($UserName -match "\\")
        {
            $Dominio = ($UserName -split "\\")[0].ToString()
            $Usuario = ($UserName -split "\\")[1].ToString()
        }
        # No se ha recibido el nombre del dominio (usuario), deberemos
        # averiguarlo
        Else
        {
            # Almacenamos el nombre de usuario
            $Usuario = $UserName
            # Obtenemos la RaizDSE
            $RaizDSE = New-Object System.DirectoryServices.DirectoryEntry `
                                        "LDAP://RootDSE",$UserName,$Password
            # Obtenemos el contenedor de particiones
            $Particiones = New-Object System.DirectoryServices.DirectoryEntry `
      "LDAP://CN=Partitions,CN=Configuration,$($RaizDSE.DefaultNamingContext)", `
                                      $UserName,$Password
            # Recorremos las particiones
            ForEach($Particion In $Particiones.Children)
            {
                # Si la partición actual es la correspondiente al dominio
                If($Particion.nCName -eq $RaizDSE.DefaultNamingContext)
                {
                    # Obtenemos el nombre NetBios
                    $Dominio = $Particion.NetBiosName.ToString()
                    # Terminamos el bucle
                    Break
                }
            }
        }
        # Ya tenemos todos los datos para poder iniciar le objeto NameTranslate
        # pasando usuario y contraseña
        $Type.InvokeMember("InitEx", "InvokeMethod", $Null, $Trans, `
                              (3, $Null,$Usuario,$Dominio,$Password)) | Out-Null
    }
    # Establecemos el nombre a traducir indicando el tipo de nombre que es
    $Type.InvokeMember("Set", "InvokeMethod", $Null, $Trans, `
                                                 ($NameType, "$Name")) |Out-Null
    # Obtenemos el nombre traducido indicando el tipo de nombre que queremos
    # obtener
    Return $type.InvokeMember("Get", "InvokeMethod", $Null, $Trans, $NewType)

<#
    .SYNOPSIS
        Esta función recibe un nombre NetBios y devuelve el correspondiente
nombre distinguido o viceversa.

    .DESCRIPTION
        Es un encapsulamiento del objeto NameTranslate. Permite obtener el
nombre distinguido de un objeto cuyo nombre NetBios sea el pasado como parámetro
NetBios o al reves, esto es, obtener el nombre NetBios del objeto cuyo nombre
distinguido sea el pasado como parámetro distinguishedName.

    .PARAMETER  NetBios
        Nombre NetBios del objeto de Active Directory del que se quiere obtener
su nombre distinguido. Si se omite, se realizará la traducción del nombre
distinguido al nombre NetBios. Este parámetro admite, además, el alias NB.

    .PARAMETER  distinguishedName
        Nombre distinguido del objeto de Active Directory del que se quiere
obtener su nombre NetBios. Si se omite, se realizará la traducción del NetBios
al nombre distinguido. Este parámetro admite, además, el alias DN.

    .PARAMETER  UserName
        Usuario con el que se producirá la validación al conectar al dominio;
en caso de no establecerse, la conexión se realizará validando con el usuario
que lanza la función. Se trata de o bien un nombre NetBios (DOMINIO\Usuario) o
del nombre sAMAccountName de un usuario, en cuyo caso la función asumirá que se
trata de un usuario del dominio al que pertenece el equipo desde el que se lanza
la función. Este parámetro admite, además, el alias U.

    .PARAMETER  Password
        Contraseña del usuario con el que se producirá la validación al conectar
al dominio; en caso de no pasarse el parámetro UserName, la conexión se
realizará validando con el usuario que lanza la función y si Password fuese
pasado se ignorará este parámetro. Este parámetro admite, además, el alias P.

    .EXAMPLE
        PS C:\> Get-NB2DN -NetBios "TIA\Bacterio"
La Función devolverá el nombre distinguido, por ejemplo
"cn=Bacterio,OU=I+D,DC=tia,DC=org"

    .EXAMPLE
        PS C:\> Get-NB2DN -distinguishedName "cn=Bacterio,OU=I+D,DC=tia,DC=org"
La función devolverá el nombre NetBios, por ejemplo "TIA\Bacterio"

    .INPUTS
        System.String,System.String

    .OUTPUTS
        System.String

    .NOTES
        Fernando Reyes López © 2012

    .LINK
        http://freyes.svetlian.com

    .LINK
        https://urpiano.wordpress.com

#>


}

Function Get-LDAPSearch([Alias("C")][String] $StartContainer,
                        [Alias("DC")][String]$DomainController,
                        [Alias("U")][String] $UserName,
                        [Alias("P")][String] $Password,
                        [Alias("F")][String] $Filter,
                        [Alias("S")]
                        [ValidateSet("Subtree","OneLevel")]
                        [String]             $SearchScope = "Subtree")
{
    # Revisamos si se ha recibido dominio, en caso negativo lo obtendremos
    If([System.String]::IsNullOrEmpty($StartContainer))
    {
        # Obtenemos el dominio gracias al método GetCurrentDomain del objeto
        # Domain
        $Dominio = `
           [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        # Montamos la ruta LDAP del dominio conectando al PDC Emulator
        $Ruta = "LDAP://$($Dominio.PdcRoleOwner)"
        $Ruta = "$Ruta/DC=$($Dominio.Name -replace "\.",",DC=")"
    }
    # Se ha recibido domino pero no servidor
    ElseIf([System.String]::IsNullOrEmpty($DomainController))
    {
        # Montamos la ruta LDAP que usa el catálogo global más cercano
        $Ruta = "LDAP://$StartContainer"
    }
    # Se ha recibido tanto nombre de dominio como servidor; montamos la ruta
    # LDAP con esos datos
    Else
    {
        $Ruta = "LDAP://$DomainController/$StartContainer"
    }
    # Debemos ver si se han recibido credenciales de usuario; en caso contrario
    # obtendremos el objeto DirectoryEntry sin pasar credenciales
    If([System.String]::IsNullOrEmpty($UserName) -eq $true)
    {
        $Dominio = New-Object System.DirectoryServices.DirectoryEntry $Ruta
    }
    # Obtenemos el objeto DirectoryEntry del dominio, pasando las credenciales
    # y estableciendo el sevidor LDAP en el que se consulta, ya que se han
    # pasado credenciales de usuario
    Else
    {
        $Dominio = New-Object System.DirectoryServices.DirectoryEntry `
                                 $Ruta,$UserName,$Password
    }
    # Creamos un objeto DirectorySearcher, contectado al dominio, con el que
    # realizaremos las búsquedas
    $Buscador = [System.DirectoryServices.DirectorySearcher]$Dominio

    # Establecemos el filtro en el objeto DirectorySearcher
    $Buscador.Filter = $Filter

    # Establecemos el ámbito de búsqueda
    $Buscador.SearchScope = $SearchScope

    # Establecemos paginación para que así no se limiten los resultados
    # obtenidos
    $Buscador.PageSize = 100

    # La función devuelve los objetos DirectoryEntry correspondientes a los
    # objetos devueltos por el objeto buscador
    $Buscador.FindAll() | ForEach{$_.GetDirectoryEntry()}

<#
    .SYNOPSIS
        Esta función ejecuta una consulta LDAP y devuelve los objetos
DirectoryEntry encontrados.

    .DESCRIPTION
        Esta función ejecuta una consulta LDAP y devuelve los objetos 
encontrados no como DirectorySearchResult si no como DirectoryEntry. Permite
especificar el contenedor en el que buscar, el controlador de dominio al que se
conectará, el usuario y la contraseña con la que se conectará y si se búscará
únicamente en el contenedor o la búsqueda será recursiva (todo el árbol de
contenedores cuya raíz sea la especificada como contenedor).

    .PARAMETER  Filter
        Esta es la consulta LDAP que se realizará. Sigue la nomenclatura de una
consulta LDAP:
(http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475(v=vs.85).aspx)
Este parámetro admite, además, el alias F.

    .PARAMETER  StartContainer
        Nombre distinguido del contenedor raíz de la búsqueda. Si se omite, la
función averiguará el nombre distinguido del dominio al que pertenece el equipo
desde el que se lanza la función y será éste el contenedor raíz de la búsqueda.
Este parámetro admite, además, el alias C.

    .PARAMETER  DomainController
        Nombre FQDN del controlador de dominio que se usará para conectar al
dominio. Si se omite y no es omitido el parámetro Domain, la ruta LDAP que se
monte para conectar al dominio no establecerá controlador al que conectar, lo
que hará que se conecte al catálogo global que el equipo considere el más
cercano; si también es omitido el parámetro Domain, el sevidor especificado será
el que ostente el rol de PDE Emulator en el dominio al que pertenezca el equipo
desde el que se lanza la función. Este parámetro admite, además, el alias DC.

    .PARAMETER  UserName
        Usuario con el que se producirá la validación al conectar al dominio;
en caso de no establecerse, la conexión se realizará validando con el usuario
que lanza la función. Este parámetro admite, además, el alias U.

    .PARAMETER  Password
        Contraseña del usuario con el que se producirá la validación al conectar
al dominio; en caso de no pasarse el parámetro UserName, la conexión se
realizará validando con el usuario que lanza la función y si Password fuese
pasado se ignorará este parámetro. Este parámetro admite, además, el alias P.

    .PARAMETER  SearchScope
        Este parámetro permite especificar el ámbito de búsqueda. Admite dos
valores:
    - SubTree  : la búsqueda se realiza en todo el árbol cuya raíz tiene como
                 nombre distinguido el especificado por el parámetro
                 StartContainer.
    - OneLevel : La búsqueda se realizará sólo en el contenedor cuyo nombre
                 distinguido es el especificado en el parámetro StartContainer.
     
    Si se omite este parámetro, el valor que se utilizará será SubTree. Este
parámetro admite, además, el alias S.

    .EXAMPLE
        PS C:\> Get-LDAPSearch -Filter "(objectClass=computer)"
La función devuelve los objetos DirectoryEntry correspondientes a las cuentas de
los equipos del dominio desde el que se lanza el script.

    .EXAMPLE
        PS C:\> $Filtro = "(&(objectClass=user)(objectCategory=person)"
        PS C:\> Get-LDAPSearch -F $Filtro
        
La función devuelve los objetos DirectoryEntry correspondientes a las cuentas de
los usuarios y contactos del dominio desde el que se lanza el script.

    .EXAMPLE
        PS C:\> $Raiz = ""OU=Agentes,DC=tia,DC=org"
        PS C:\> $DC = "bacterio-dc.tia.org"
        PS C:\> $Filtro = "(&(objectClass=user)(objectCategory=person)"
        PS C:\> Get-LDAPSearch -F $Filtro -C $Raiz -DomainController $DC
        
La función devuelve los objetos DirectoryEntry correspondientes a las cuentas de
los usuarios y contactos que estén en el subárbol cuya raíz es la OU Agentes del
dominio tia.org, conectando al controlador de dominio bacterio-dc.tia.org.

    .EXAMPLE
        PS C:\> $Raiz = ""OU=Agentes,DC=tia,DC=org"
        PS C:\> $DC = "bacterio-dc.tia.org"
        PS C:\> $Filtro = "(&(objectClass=user)(objectCategory=person)"
        PS C:\> Get-LDAPSearch -F $Filtro -C $Raiz -DomainController $DC -S "OneLevel"
        
La función devuelve los objetos DirectoryEntry correspondientes a las cuentas de
los usuarios y contactos que estén en la OU Agentes del dominio tia.org,
conectando al controlador de dominio bacterio-dc.tia.org.

    .EXAMPLE
        PS C:\> $Dominio = "DC=tia,DC=org"
        PS C:\> $DC = "bacterio-dc.tia.org"
        PS C:\> $Usuario = "bacterio"
        PS C:\> $Clave = "OfeliaFOCA"
        PS C:\> $Filtro = "(&(objectClass=user)(objectCategory=person)"
        PS C:\> Get-LDAPSearch -F $Filtro -C $Dominio -DC $DC -U $Usuario -P $Clave
        
La función devuelve los objetos DirectoryEntry correspondientes a las cuentas de
los usuarios y contactos del dominio tia.org conectando al controlador de
dominio bacterio-dc.tia.org usando para validarse la cuenta "bacterio" con la
contraseña "OfeliaFOCA".

    .INPUTS
        System.String,System.String,System.String,System.String,System.String

    .OUTPUTS
        System.DirectoryServices.DirectoryEntry[]

    .NOTES
        Fernando Reyes López © 2012

    .LINK
        http://freyes.svetlian.com

    .LINK
        https://urpiano.wordpress.com

#>

}

Function Get-UserMembership([parameter(ValueFromPipeLine=$true)]
                            [Alias("DE")]
                            [System.DirectoryServices.DirectoryEntry] $UserDE,
                            [Alias("U")][String]                      $UserName,
                            [Alias("P")][String]                      $Password)
{
    Begin
    {
        # Iniciamos el contador de usuarios
        $NumUsers = 0
    }
    Process
    {
        # Incrementamos el contador de usuarios
        $NumUsers++
        
        # Lo primero es preparar la llamada a la función que nos permite obtener
        # el objeto DirectoryEntry del contendor del usuario recibido. Crearemos
        # un Splat al que añadiremos todos los parámetros comunes de esa función
        # (Get-DirectoryEntryByPath) con esta, es decir, usuario y contraseña.
        # Creamos un hashtable vacío
        $Parametros = @{}
        
        # En este bucle agregaremos los elementos del hastable con los
        # parámetros recibidos por la función en la que estamos
        # (Get-UserMembership)
        ForEach($Clave In $PSBoundParameters.Keys)
        {
            # Agregamos el parámetro actual
            $Parametros.Add($Clave,$PSBoundParameters[$Clave])
        }
        # Quitamos el parámetro UserDE, que no es recibido por
        # Get-DirectoryEntryByPath
        $Parametros.Remove("UserDE") | Out-Null
        
        # Agregamos el parámetro con la ruta del contenedor donde está ubicado
        # el objeto DirectoryEntry recibido
        $Parametros.Add("Path",$UserDE.Parent)
        
        # Obtenemos el objeto DirectoryEntry del contenedor
        $OU = Get-DirectoryEntryByPath @Parametros
        
        # Obtenemos la propiedad operacional "canonicalName" del contenedor
        $OU.RefreshCache("canonicalName")
        
        # Obtenemos la propiedad operacional "tokenGropups" del usuario
        $UserDE.RefreshCache("tokenGroups")
        
        # Creamos un array vacío en el que iremos añadiendo los objetos
        # personalizados que contendrán la información del usuario y de uno de
        # los grupos a los que pertenece
        $UserList = @()
        
        # Iniciamos a falso el marcador de que el usuario es administrador del
        # dominio 
        $Administrador = $false
        
        # Recorremos ahora los elementos de la propiedad tokenGroups del
        # usuario. Esta propiedad es un Array de SIDs, un SID por cada grupo
        ForEach($Value In $UserDE.tokenGroups)
        {
            # Obtenemos el SID actual
            $SID = New-Object System.Security.Principal.SecurityIdentifier $Value, 0
            # Con el SID obtenemos el objeto DirectoryEntry, con o sin
            # validación, segun se haya recibido nombre de usuario o no del
            # grupo.
            # En primer lugar sin validación si no se recibió usuario
            If([String]::IsNullOrEmpty($UserName))
            {
                $Grupo = New-Object System.DirectoryServices.DirectoryEntry `
                                        "LDAP://<SID=$SID>"
            }
            # Ahora con validación ,pues sí se recibió usuario
            Else
            {
                $Grupo = New-Object System.DirectoryServices.DirectoryEntry `
                                        "LDAP://<SID=$SID>",$UserName,$Password
            }
            # Obtenemos la cadena SID del grupo
            $gSID = Get-StringSID -SID $Grupo.objectSID
            
            # Si la booleana que controla si el usuario es administrador es
            # false, comprobamos si el grupo actual es el de administradores del
            # dominio. Hacemos la comprobación para que no quede desmarcado el 
            # verdadero cuando se recorra otro grupo que no sea el de
            # administradores del dominio
            If($Administrador -eq $false)
            {
                # El SID del grupo administradores del dominio comienza por
                # S-1-5-21 y termina con 512; por tanto si se cumplen ambas
                # condiciones, el marcador quedará como verdadero
                $Administrador = ($gSID.SubString(0,8) -eq "S-1-5-21" -and `
                                 $gSID.SubString($gSID.Length - 3,3) -eq "512")
            }   
            # Obtenemos el nombre NetBios del grupo. Podríamos haber usado su
            # propiedad sAMAccountName, pero no nos habría dado el nombre
            # NetBios completo (dominio\nombre), sólo el nombre 
            $gNT = Get-NB2DN -distinguishedName "$($UserDE.distinguishedName)"
            
            # Creamos un objeto personalizado que contendrá la información del
            # usuario y del grupo
            $User = New-Object PsCustomObject
            
            # Acontinuación agregamos las propiedades al objeto. En primer lugar
            # las propiedades del usuario: Nombre, Clase, Nombre para mostrar,
            # sAMAccountName, nombre principal, nombre distinguido,SID en
            # formato de cadena (S-5-...), si es o no administrador del dominio,
            # nombre distinguido del contenedor, nombre canónico del contenedor,
            # cargo, departamento, Fecha de creación, fecha de expiración, y los
            # objetos DirectoryEntry completos del usuario y el grupo.
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name Name `
                       -Value "$($UserDE.Name)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name objectClass `
                       -Value "$($UserDE.objectClass -join ",")"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name DisplayName `
                       -Value "$($UserDE.DisplayName)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name sAMAccountName `
                       -Value "$($UserDE.sAMAccountName)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name UserPrincipalName `
                       -Value "$($UserDE.userPrincipalName)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name distinguishedName `
                       -Value "$($UserDE.distinguishedName)"
            Add-Member -InputObject $User -MemberType NoteProperty `
                       -Name SID -Value "$(Get-StringSID $UserDE.objectSID)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name Administrator `
                       -Value $Administrador
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name ParentDN `
                       -Value "$($OU.distinguishedName)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name ParentCN `
                       -Value "$($OU.canonicalName)"
            Add-Member -InputObject $User -MemberType NoteProperty `
                       -Name Title `
                       -Value "$($UserDE.Title)"
            Add-Member -InputObject $User -MemberType NoteProperty `
                       -Name Department `
                       -Value "$($UserDE.Department)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name whenCreated `
                       -Value "$($UserDE.whenCreated)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name AccountExpirationDate `
                       -Value "$($UserDE.AccountExpirationDate.ToString())"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name User `
                       -Value $UserDE
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name Parent `
                       -Value $OU
            # Agregamos el número de grupos de los que es miembro el usuario
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name GroupsCount `
                       -Value $UserDE.tokenGroups.Count
            # A continuación agregamos las propiedades que hacen referencia al
            # grupo; esto es su nombre Pre-Windows 2000, su nombre distinguido,
            # su SID y el objeto DirectoryEntry del grupo
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name GroupNT `
                       -Value "$gNT"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name GroupDN `
                       -Value "$($Grupo.distinguishedName)"
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name GroupSID `
                       -Value $gSID
            Add-Member -InputObject $User `
                       -MemberType NoteProperty `
                       -Name Group `
                       -Value $Grupo
            # Agregamos el objeto al array de objetos del usuario actual
            $UserList += $User
        }
        # Recorremos el array de objetos del usuario actual
        ForEach($User In $UserList)
        {
            # Ponemos a todos los objetos el valor del marcador de administrador
            # del dominio
            $User.Administrator = $Administrador
            # La función devuelve el objeto
            $User
        }
    }
    End
    {
        Write-Host "$NumUsers Usuarios Encontrados"
    }

<#

    .SYNOPSIS
        Devuelve información de usuario, así como sus membresías.

    .DESCRIPTION
        Esta función recibe un objeto DirectoryEntry de un usuario de dominio y
devuelve una colección de objetos personalizados en el que hay propiedades del
usuario, la del grupo del que es miembro (por membresía directa, heredada o ser
el grupo primario), indica si es administrador del dominio o no, número de
grupos de los que es miembro, el nombre cacnónico y distinguido del contenedor
en que está ubicado y los objetos DirectoryEntry correspondientes al usuario, el
contenedor en que está ubicado y el grupo. Es un objeto de devolución
excesivamente "desnormalizado" (empleando terminología de base de datos), pero
esto es así porque la intención es que sea muy cómodo a la hora de realizar
listados (nuevamente en terminología de base de datos, está más pensado para
Business Intelligence que para operacional); si se quiere hacer de manera más
"normalizada", es fácil modificar el script para que devuelva un único objeto
por usuario (ahora devuelve uno por cada membresía de cada usuario) en el que se
incluya una colección con los datos de los grupos a los que pertenece.

La función permite la recepción por canalización del objeto DirectoryEntry del
usuario, lo que hace que se pueda recibir los objetos procedentes de una
consulta y poder así realizar un listado (es interesante, por tanto el uso de mi
otra función Get-LDAPSearch que encapsula al objeto DirectorySearcher y no
devuelve objetos DirectorySearchResult, como hace DirectorySearcher, si no que
devuelve objetos DirectoryEntry.

La función admite establecer el usuario y la contraseña con la que se conectará
a Active Directory para obtener la información. 

    .PARAMETER  UserDE
        Objeto DirectoryEntry del usuario a procesar. Se puede recibir por
canalización. Admite el alias DE.

    .PARAMETER  UserName
        Usuario con el que se producirá la validación al conectar al dominio;
en caso de no establecerse, la conexión se realizará validando con el usuario
que lanza la función. Este parámetro admite, además, el alias U.

    .PARAMETER  Password
        Contraseña del usuario con el que se producirá la validación al conectar
al dominio; en caso de no pasarse el parámetro UserName, la conexión se
realizará validando con el usuario que lanza la función y si Password fuese
pasado se ignorará este parámetro. Este parámetro admite, además, el alias P.

    .EXAMPLE
        PS C:\> $DN = "CN=Pi, Filemon,OU=Agentes,DC=Tia,DC=org"
        PS C:\> $Filemon = New-Object System.DirectoryServices.DirectoryEntry $DN
        PS C:\> Get-UserMembership -UserDE $Filemon

Obtiene la información y membresías del usuario cuyo nombre distinguido ha sido
establecido en la variable $DN

    .EXAMPLE
        PS C:\> $DN = "CN=Pi, Filemon,OU=Agentes,DC=Tia,DC=org"
        PS C:\> $Bac = "AdminBacterio"
        PS C:\> $Pass = "OfeliaFOCA"
        PS C:\> $Filemon = New-Object System.DirectoryServices.DirectoryEntry $DN,$Bac,$Pass
        PS C:\> Get-UserMembership -DE $Filemon -U $Bac -P $Pass

Obtiene la información y membresías del usuario cuyo nombre distinguido ha sido
establecido en la variable $DN, validándose al dominio con el usuario
AdminBacterio y contraseña OfeliaFOCA

    .EXAMPLE
        PS C:\> $DN = "OU=Agentes,DC=Tia,DC=org"
        PS C:\> $Filtro = "(&(objectClass=user)"
        PS C:\> $Filtro = "$Filtro(objectCategory=person)"
        PS C:\> $Filtro = "$Filtro(!(objectClass=contact)))"
        PS C:\> Get-LDAPSearch -C $DN -F $Filtro -S OneLevel | `
        >>  Get-UserMembership -U $Bac -P $Pass

Obtiene la información y membresías de los usuarios ubacdos en el contenedor
cuyo nombre distinguido ha sido establecido en la variable $DN, validándose al
dominio con el usuario AdminBacterio y contraseña OfeliaFOCA

    .EXAMPLE
        PS C:\> $DN = "OU=Agentes,DC=Tia,DC=org"
        PS C:\> $Bac = "AdminBacterio"
        PS C:\> $Pass = "OfeliaFOCA"
        PS C:\> $Filtro = "(&(objectClass=user)"
        PS C:\> $Filtro = "$Filtro(objectCategory=person)"
        PS C:\> $Filtro = "$Filtro(!(objectClass=contact)))"
        PS C:\> Get-LDAPSearch -C $DN -F $Filtro -S Subtree -U $Bac -P $Pass | `
        >>  Get-UserMembership -U $Bac -P $Pass

Obtiene la información y membresías de los usuarios contenidos en el sub-árbol
cuyo contenedor raíz tiene como nombre distinguido el establecido en la variable
$DN, validándose al dominio con el usuario AdminBacterio y contraseña OfeliaFOCA

    .INPUTS
        System.DirectoryServices.DirectoryEntry,System.String,System.String

    .OUTPUTS
        System.Management.Automation.PSCustomObject

    .NOTES
        Fernando Reyes López © 2012

    .LINK
        http://freyes.svetlian.com

    .LINK
        https://urpiano.wordpress.com

#>

}

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

 
A %d blogueros les gusta esto: