PSAdsi.psm1


# ipmo .\RDS.DragonFly.psd1 -Force; rds LOGEXI@OPEN.ADDS
# https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx

function Get-AllADs {
    <#
        .SYNOPSIS
            Liste toutes le Foret accessible
        .DESCRIPTION
            les ForestName : Current + AllTrustRelationships
            Prefer to Use $global:ADs
        .EXAMPLE
            Get-AllADs
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>


    begin {
    }
    process {
        if (!$global:ADs){
            $global:ADs = @()
            try {
                $global:ADs += ([System.DirectoryServices.ActiveDirectory.Domain]::getCurrentDomain()).Forest.name
                $global:ADs += ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().GetAllTrustRelationships()).targetName
                $global:ADs += ([System.DirectoryServices.ActiveDirectory.Domain]::getCurrentDomain().GetAllTrustRelationships()).targetName
            } catch {
                Write-LogStep 'Chargement [Domain-Forest] ',$_ error
            }
            [array]::sort($global:ADs)
        }
    }
    end {
        return $global:ADs
    }
}
function Get-ADSIGroup {
    <#
        .SYNOPSIS
            liste tous les Groups qui correcponde a un pattern
        .DESCRIPTION
            recherche dans toute les AD
        .PARAMETER NtGroupName
            pattern de recherche
        .EXAMPLE
            Get-ADSIGroup 'MyDomain\Groupe *'
        .EXAMPLE
            'Groupe *@Domain.tld' | Get-ADSIGroup
        .EXAMPLE
            Get-ADSIGroup 'Groupe admin@*.tld'
        .EXAMPLE
            Get-ADSIGroup '*\*'
        .EXAMPLE
            'OU=compta,DC=open,DC=tld' | Get-ADSIGroup
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
            [alias("NtAccountName")]
                $NtGroupName
    )
    begin {
        $Searcher = New-Object DirectoryServices.DirectorySearcher
    }
    process {
        foreach ($Item in $NtGroupName) {
            if($item -like '*\*'){
                $domain,$SamAccountName = $item.split('\')
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "NtAccount > $Item"
            } elseif($item -like '*@*') {
                $SamAccountName,$domain = $item.split('@')
                $domain=$domain.Split('.')[0]
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "UPN > $Item"
            } elseif($item -match '((CN=[^,]+,)?(OU=[^,]+,)?DC=([^,]+),DC=(\w+))$') {
                $SamAccountName = '*'
                $SearchRoot = ($item -replace ('LDAP://'))
                Write-Verbose "DN > $($item -replace ('LDAP://'))"
            } else {
                $SamAccountName = $item
                $domain = '*'
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "FULL > $Item"
            }
            $Searcher.Filter = "(objectCategory=group)"
            if($SamAccountName) {
                $Searcher.Filter = "(&(objectCategory=group)(samAccountName=$SamAccountName))"
            }
            # (&(objectClass=group)(email=*))
            foreach($AD in $Searchroot){
                $Searcher.SearchRoot = "LDAP://$($AD -replace('^(.+)\.(.+)$','DC=$1,DC=$2'))"
                try {
                    $out = $Searcher.FindAll() | ?{$_} # | ForEach-Object {[adsi]$_.path}).Properties
                    if (!$out) { throw "Aucune Correspondance [$item] !" }
                    Write-LogStep "Collecte ADSI [$AD]",$samAccountName,"[$($out.count)] match" ok
                    $Out
                } catch {
                    Write-LogStep "Collecte ADSI [$SamAccountName@$AD]",$_ error
                }
            }
        }
    }
    end {}
}
function Get-ADSIUser {
    <#
        .SYNOPSIS
            liste tous les Users d'une par le pattern ntaccountName
        .DESCRIPTION
            Retoune tous les Objet ADSI Users d'un ensemble d'OU et qui
        .PARAMETER NtAccountName
            pattern de recherche
        .EXAMPLE
            Get-ADSIUser (whoami)
        .EXAMPLE
            Get-ADSIUser 'jDoe@domain.tld'
        .EXAMPLE
            '*\jDoe' | Get-ADSIUser
        .EXAMPLE
            'OU=paris,OU=User,DC=open,DC=tld' | Get-ADSIUser
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]$NtAccountName
    )
    begin {
        $Searcher = New-Object DirectoryServices.DirectorySearcher
    }
    process {
        foreach ($Item in $NtAccountName) {
            if($item -like '*\*'){
                $domain,$SamAccountName = $item.split('\')
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "NtAccount > $Item"
            } elseif($item -like '*@*') {
                $SamAccountName,$domain = $item.split('@')
                $domain=$domain.Split('.')[0]
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "UPN > $Item"
            } elseif($item -match '((CN=[^,]+,)?(OU=[^,]+,)?DC=([^,]+),DC=(\w+))$') {
                $SamAccountName = '*'
                $SearchRoot = ($item -replace ('LDAP://'))
                Write-Verbose "DN > $($item -replace ('LDAP://'))"
            } else {
                $SamAccountName = $item
                $domain = '*'
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "FULL > $Item"
            }
            $Searcher.Filter = "(objectCategory=user)"
            if($SamAccountName) {
                $Searcher.Filter = "(&(objectCategory=user)(samAccountName=$SamAccountName))"
            }
            # (&(objectClass=user)(email=*))
            foreach($AD in $Searchroot){
                $Searcher.SearchRoot = "LDAP://$($AD -replace('^(.+)\.(.+)$','DC=$1,DC=$2'))"
                try {
                    $out = $Searcher.FindAll() | ?{$_} # | ForEach-Object {[adsi]$_.path}).Properties
                    if (!$out) { throw "Aucune Correspondance [$item] !" }
                    Write-LogStep "Collecte ADSI [$AD]",$samAccountName ok
                    $Out
                } catch {
                    Write-LogStep "Collecte ADSI [$SamAccountName@$AD]",$_ error
                }
            }
        }
    }
    end {}
}
function Get-ADSIComputer {
    <#
        .SYNOPSIS
            liste tous les Ordineteur d'une OU
        .DESCRIPTION
            Retoune tous les Objet ADSI Ordinateur d'un ensemble d'OU et qui
        .PARAMETER Searchroot
            liste des DistingishedName a parcourir
        .PARAMETER ComputerName
            pattern de recherche
        .EXAMPLE
            Get-ADSIComputer 'open\PC01'
        .EXAMPLE
            'OU=portable,OU=ordi,DC=open,DC=tld' | Get-ADSIComputer
        .EXAMPLE
            '*\PC*' | Get-ADSIComputer
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]$ComputerName
    )
    begin {
        $Searcher = New-Object DirectoryServices.DirectorySearcher
    }
    process {
        foreach ($Item in $ComputerName) {
            if($item -like '*\*'){
                $domain,$Name = $item.split('\')
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "NtAccount > $Item"
            } elseif($item -like '*@*') {
                $Name,$domain = $item.split('@')
                $domain=$domain.Split('.')[0]
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "UPN > $Item"
            } elseif($item -match '((CN=[^,]+,)?(OU=[^,]+,)?DC=([^,]+),DC=(\w+))$') {
                $Name = '*'
                $SearchRoot = ($item -replace ('LDAP://'))
                Write-Verbose "DN > $($item -replace ('LDAP://'))"
            } else {
                $Name = $item
                $domain = '*'
                $Searchroot = (Get-AllADs) -like "$domain.*"
                Write-Verbose "FULL > $Item"
            }
            $Searcher.Filter = "(objectCategory=computer)"
            if($name) {
                $Searcher.Filter = "(&(objectCategory=computer)(name=$name))"
            }
            foreach($AD in $Searchroot){
                $Searcher.SearchRoot = "LDAP://$($AD -replace('^(.+)\.(.+)$','DC=$1,DC=$2'))"
                try {
                    $out = $Searcher.FindAll() | ?{$_} # | ForEach-Object {[adsi]$_.path}).Properties
                    if (!$out) { throw "Aucune Correspondance [$item] !" }
                    Write-LogStep "Collecte ADSI [$AD]",$name ok
                    $Out
                } catch {
                    Write-LogStep "Collecte ADSI [$name@$AD]",$_ error
                }
            }
        }
    }
    end {
    }
}
# function Get-ADSIPwdPolicy {
# <#
# .SYNOPSIS
# liste toutes les strategies de Pwd
# .DESCRIPTION
# Retoune tous les Objet ADSI Password Settings
# .PARAMETER Name
# pattern de recherche
# .EXAMPLE
# Get-ADSIPwdPolicy 'open\*'
# .EXAMPLE
# Get-ADSIPwdPolicy
# .NOTES
# Alban LOPEZ 2019
# alban.lopez@gmail.com
# #>
# [CmdletBinding()]
# param (
# [Parameter(ValueFromPipeline = $true)]$NtAccountName
# )
# begin {
# $Searcher = New-Object DirectoryServices.DirectorySearcher
# }
# process {
# foreach ($Item in $NtAccountName) {
# if($item -like '*\*'){
# $domain,$SamAccountName = $item.split('\')
# $Searchroot = (Get-AllADs) -like "$domain.*"
# } elseif($item -like '*@*') {
# $SamAccountName,$domain = $item.split('@')
# $domain=$domain.Split('.')[0]
# $Searchroot = (Get-AllADs) -like "$domain.*"
# } else {
# $SamAccountName = $item
# $domain = '*'
# $Searchroot = (Get-AllADs) -like "$domain.*"
# }
# $Searcher.Filter = "(Name=$SamAccountName)"
# foreach($AD in $Searchroot){
# $Searcher.SearchRoot = "LDAP://CN=Password Settings Container,CN=System,$($AD -replace('^(.+)\.(.+)$','DC=$1,DC=$2'))"
# try {
# $Searcher.FindAll() | ?{$_} # | ForEach-Object {[adsi]$_.path}).Properties
# Write-LogStep "Collecte ADSI [$AD]",$samAccountName ok
# } catch {
# Write-LogStep "Collecte ADSI [$SamAccountName@$AD]",$_ error
# }
# }
# }
# }
# end {}
# }
# function Get-ADSIUserPwdPolicy {
# <#
# .SYNOPSIS
# recherche la strategies de Pwd
# .DESCRIPTION
# Retoune user Password Settings
# .PARAMETER Name
# pattern de recherche
# .EXAMPLE
# Get-ADSIObject (whoami)
# .EXAMPLE
# Get-ADSIPwdPolicy
# .EXAMPLE
# '*\jDoe' | Get-ADSIObject
# .EXAMPLE
# 'OU=paris,OU=User,DC=open,DC=tld' | Get-ADSIObject
# .NOTES
# Alban LOPEZ 2019
# alban.lopez@gmail.com
# #>
# [CmdletBinding()]
# param (
# [Parameter(ValueFromPipeline = $true)]$NtAccountName
# )
# begin {
# $Searcher = New-Object DirectoryServices.DirectorySearcher
# }
# process {
# foreach ($Item in $NtAccountName) {
# $users = Get-ADSIUser $item
# foreach ($user in $users) {
# $AllMemberOf = @()
# $memberOf = ($user | Get-ADSIMemberOf -Recurse)
# $AllMemberOf += $memberOf.MemberOf.distinguishedName
# $AllMemberOf += $MemberOf.NestedMemberOf.distinguishedName
# # $AllMemberOf | Write-Object -backGroundColor Black -foreGroundColor Cyan
# Get-ADSIPwdPolicy "$(($user.properties.distinguishedname -split(',DC='))[-2])\*" | ?{
# # $_.properties.'msds-psoappliesto' | Write-Object -backGroundColor Black -foreGroundColor red
# $AllMemberOf -contains $_.properties.'msds-psoappliesto'
# }
# }
# }
# }
# end {}
# }
function Set-AdsiProperties {
    <#
        .SYNOPSIS
            Modifie un ensemble de proprietes sur chacun des objets
        .DESCRIPTION
            selement les propriete accessible en ecriture directe
        .PARAMETER AdsiItems
            Liste des compte AD
        .PARAMETER ExtraParameters
            Password
            Disable / Enable
            PasswordNeverExpire
            CantChangePassword
            TsAllow / AllowLogon / BalOnly
            AddMemberOf
            SetMemberOf


            mstsallowlogon
            givenname
            codepage
            objectcategory
            description
            usnchanged
            instancetype
            mail
            logoncount
            name
            badpasswordtime
            msds-supportedencryptiontypes
            badpwdcount
            lastlogontimestamp
            usncreated
            sn
            company
            userparameters
            objectguid
            memberof
            whencreated
            adspath
            useraccountcontrol
            cn
            countrycode
            primarygroupid
            dscorepropagationdata
            lastlogon
            msexchomaadminwirelessenable
            extensionattribute1
            [...]
            extensionattribute15
            othermailbox
            samaccountname
            lastlogoff
            displayname
            accountexpires
            userprincipalname
        .EXAMPLE
            Set-AdsiProperties -displayname 'Jhon Doe'
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>

    param (
        [Parameter(ValueFromPipeline = $true)] $AdsiItems = $null,
        [Parameter(ValueFromRemainingArguments=$true)]$ExtraParameters,
        [switch]$PassThru
    )
    begin {
        $Properties = @{}
        $UnnamedParams = @()
        $Name = $null
        $ExtraParameters | ForEach-Object -Process {
            if ($_ -match "^-") {
                if ($Name) {
                    $Properties.$Name = $true
                }
                $Name = $_ -replace "^-|:$"
                # Write-Verbose $name
            } else {
                if ($Name) {
                    $Properties.$Name += $_
                    $Name = $null
                } else {
                    $UnnamedParams += $_
                }
                # Write-Verbose $name
            }
            # Write-Verbose $_
        } -End {
            if ($Name) {
                $Properties.$Name = $true
            }
        }
        # $properties | Write-Object -back black -fore Yellow
    }
    process {
        foreach ($item in $AdsiItems) {
            # $whenchanged = $item.properties.whenchanged

            # $item | Write-Object -back black -fore DarkYellow
            if ($item -is [System.DirectoryServices.SearchResult]) {
                $item = [adsi]$item.path
            }
            # elseif ($item -is [string]) {
                # if ($item -match '^LDAP:\/\/CN=(.+),(OU=([\w\d\s_-]+))*,DC=([\w\d\s_-]+),DC=(com|adds)$') {
                # $item = [adsi]$item
                # } elseif ($item -match '^CN=(.+),(OU=([\w\d\s_-]+))*,DC=([\w\d\s_-]+),DC=(com|adds)$') {
                # $item = [adsi]"LDAP://$item"
                # } elseif ($item -match '^S-1-5-21-') {
                # $item = [adsi]"LDAP://<SID=$item>"
                # } else {
            if ($item -isnot [System.DirectoryServices.DirectoryEntry]) {
                Write-LogStep "Format de parametre","[ADSI] required !" error
                break;
            }
            Write-Verbose $item.path
            # $Item.Properties | ft * # %{$_.PropertyName;$_.Value}
            $Changed = @()
            foreach ($Property in $Properties.GetEnumerator()) {
                $item.GetInfo() | Out-Null # ceci reset la fille d'attente des autre .Put()
                try {
                    if ($Property.name -match '^Password$') {
                        # $pwdlastset = $item.properties.pwdlastset
                        $item.setpassword($Property.Value) | Out-Null
                    } else {
                        switch -regex ($Property.name) {
                            '^Disable$' {
                                if (!($item.Properties.useraccountcontrol.value -band 2) -eq $Property.Value) {
                                    # write-host $Item.Properties.useraccountcontrol.value,' > ' -NoNewline -fore Red
                                    $Item.Put('useraccountcontrol', $item.Properties.useraccountcontrol.value -bxor 2) | Out-Null
                                    # write-host ($Item.Properties.useraccountcontrol.value -bxor 2) -fore Red
                                    Write-Verbose "Disable = $($Property.Value)"
                                }
                            }
                            '^Enable$' {
                                if ([bool]($item.Properties.useraccountcontrol.value -band 2) -eq $Property.Value) {
                                    # write-host $Item.Properties.useraccountcontrol.value,' > ' -NoNewline -fore green
                                    $Item.Put('useraccountcontrol', $item.Properties.useraccountcontrol.value -bxor 2) | Out-Null
                                    # write-host ($Item.Properties.useraccountcontrol.value -bxor 2) -fore green
                                    Write-Verbose "Enable = $($Property.Value)"
                                }
                            }
                            '^PasswordNeverExpire$' {
                                if (!($item.Properties.useraccountcontrol.value -band 65536) -eq $Property.Value) {
                                    # write-host $Item.Properties.useraccountcontrol.value,' > ' -NoNewline -fore green
                                    $Item.Put('useraccountcontrol', $item.Properties.useraccountcontrol.value -bxor 65536) | Out-Null
                                    # write-host ($Item.Properties.useraccountcontrol.value -bxor 65536) -fore green
                                    Write-Verbose "Password Never Expire = $($Property.Value)"
                                }
                            }
                            '^CantChangePassword$' {
                                if (!($item.Properties.useraccountcontrol.value -band 64) -eq $Property.Value) {
                                    # write-host $Item.Properties.useraccountcontrol.value,' > ' -NoNewline -fore green
                                    $Item.Put('useraccountcontrol', $item.Properties.useraccountcontrol.value -bxor 64) | Out-Null
                                    # write-host ($Item.Properties.useraccountcontrol.value -bxor 64) -fore green
                                    Write-Verbose "Can't Change Password = $($Property.Value)"
                                }
                            }
                            '^(TsAllow|AllowLogon)?$' {
                                # write-host 'TsAllow' -fore red
                                $Item.InvokeSet('allowlogon', [int]$Property.Value)
                                Write-Verbose "Ts Session Allowed = $($Property.Value)"
                            }
                            '^BalOnly$' {
                                # write-host 'BalOnly' -fore red
                                $Item.InvokeSet('allowlogon',[int](!$Property.Value))
                                Write-Verbose "Bal Only = $($Property.Value)"
                            }
                            '^AddMemberOf$' {
                                $groups = $Property.Value | ?{$_} | Get-ADSIGroup | %{[adsi]$_.Path}
                                # $groups = Get-ADSIGroup -NtGroupName 'open\groupe sotrima *' | %{[adsi]$_.Path}
                                foreach ($group in $groups) {
                                    try {
                                        if ($group.properties.member -notcontains $item.distinguishedName){
                                            $group.add($item.path) # $group.members.add($item.path)
                                            Write-Verbose "Add MemberOf = $($group.Name)"
                                        } else {
                                            Write-Verbose "Already Exist = $($group.Name)"
                                        }
                                    } catch {
                                        # write-color '[',($error[0].Exception.innerException.gettype().BaseType),']' -ForeGroundColor Red,Gray,Red -ea 0
                                        Write-LogStep -prefix "L.$($_.InvocationInfo.ScriptLineNumber)" "",$_ error
                                    }
                                }
                            }
                            '^(Set)?MemberOf$' {
                                $groups = $Property.Value | ?{$_} | Get-ADSIGroup | %{[adsi]$_.Path}
                                ($item | Get-ADSIMemberOf).memberOf | ?{
                                    # Write-host "filter MemberOf = $($_.ADsPath)" -fore cyan
                                    $groups.ADsPath -notcontains $_.ADsPath
                                } | %{
                                    # Write-host "filtered MemberOf = $($_.Name)" -fore Red
                                    try {
                                        $_.Remove($item.Path)
                                        Write-Verbose "Remove MemberOf = $($_.Name)"
                                    } catch {
                                        # write-color '[',($error[0].Exception.innerException.gettype().BaseType),']' -ForeGroundColor Red,Gray,Red -ea 0
                                        Write-LogStep -prefix "L.$($_.InvocationInfo.ScriptLineNumber)" "",$_ error
                                    }
                                }
                                # $groups = Get-ADSIGroup -NtGroupName 'open\groupe sotrima *' | %{[adsi]$_.Path}
                                foreach ($group in $groups) {
                                    try {
                                        # $group.properties.member, $item.distinguishedName | Write-Object -backGroundColor Black -fore Red
                                        if ($group.properties.member -notcontains $item.distinguishedName){
                                            $group.add($item.path) # $group.members.add($item.path)
                                            Write-Verbose "Set MemberOf = $($group.Name)"
                                        } else {
                                            Write-Verbose "Already Exist = $($group.Name)"
                                        }
                                    } catch {
                                        # $_ | Write-Object -backGroundColor Black -fore Red
                                        # write-color '[',($error[0].Exception.innerException.gettype().BaseType),']' -ForeGroundColor Red,Gray,Red -ea 0
                                        Write-LogStep -prefix "L.$($_.InvocationInfo.ScriptLineNumber)" "",$_ error
                                    }
                                }
                            }
                            default {
                                $Item.Put($Property.name, @($Property.Value)) | Out-Null
                                Write-Verbose "$($Property.name) = $($Property.Value)"
                            }
                        }
                        $Item.CommitChanges()
                        $Item.SetInfo() | Out-Null
                    }
                    $Changed += $Property.name
                } catch {
                    Write-LogStep -prefix "L.$($_.InvocationInfo.ScriptLineNumber) %caller%" '',"[$($Property.name)]",$_ Error
                }
            }
            if ($PassThru -and $Changed) {
                $Item.refreshcache() | Out-Null
                # $item.GetInfo() | Out-Null # ceci reset la fille d'attente des autre .Put()
                $item
            }
        }
    }
    end {}
}
# -mstsallowlogon $true -givenname 'Jh0n' -codepage 1 -objectcategory 'CN=Group,CN=Schema,CN=Configuration,DC=open,DC=adds' -description 'kjg321' -usnchanged '102267828' -instancetype '5' -mail 'kjg3@f21.fr'
# -logoncount 3 -name 'J Doe' -badpasswordtime 131941170462578977 -badpwdcount '0' -lastlogontimestamp 132018623536360111 -usncreated 39795111 -sn 'JOE' -company 'kjg321'
# -userparameters 'kjg321' -objectguid 'kjg321' -memberof @('CN=Groupe SOTRIMA Global,OU=Sotrima,DC=open,DC=adds') -whencreated 'kjg321' -adspath 'kjg321' -useraccountcontrol 'kjg321' -cn 'kjg321' -countrycode 'kjg321' -primarygroupid 'kjg321' -dscorepropagationdata 'kjg321' -lastlogon 'kjg321' -msexchomaadminwirelessenable 'kjg321' -extensionattribute15 'kjg321' -othermailbox 'kjg321' -samaccountname 'kjg321' -lastlogoff 'kjg321' -displayname 'kjg321' -accountexpires 136080899111100000 -userprincipalname 'jhondhoe@open.adds'
Function Get-ADSIMemberOf{
    <#
        .SYNOPSIS
            Identifi les groupe dont est membre un Object
        .DESCRIPTION
            Membre direct et indirect (sans doublon)
        .PARAMETER ADSIObject
            Object a analyser
        .PARAMETER Recurse
            Mode recursif pour les groupe indirect
        .PARAMETER Groups
            Ne pas utiliser, reservé pour le mode recursif
        .EXAMPLE
            Get-ADSIMemberOf
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>


    Param(
        [Parameter(ValueFromPipeline=$true)]$ADSIObject,
        [switch]$Recurse,
        $Groups = $null
    )
    begin {
    }
    process {
        # Write-verbose $ADSIObject.Path
        if($null -eq $Groups){
            $Groups = [PSCustomObject]@{
                MemberOf      = @()
                NestedMemberOf = @()
            }
            Foreach($Memberof in $ADSIObject.properties.memberof){
                $Group = [adsi]"LDAP://$MemberOf"
                $Groups.Memberof += $group
                if($Group.properties.memberof -and $Recurse){
                    $Groups = $group | Get-ADSIMemberOf -Groups $Groups -Recurse
                }
            }
        } else {
            Foreach($Memberof in $ADSIObject.properties.memberof){
                $path = "LDAP://$MemberOf"
                if($Groups.MemberOf.path -contains $path){
                    # write-verbose "Deja [DirectMemberOf] $path"
                }elseif($Groups.NestedMemberOf.path -contains $path){
                    # write-verbose "Deja [NestedMemberOf] $path"
                } else {
                    # write-verbose "Nouveau [NestedMemberOf] $path"
                    $Group = [adsi]$path
                    $Groups.NestedMemberof += $group
                    $Groups = $group | Get-ADSIMemberOf -Groups $Groups -Recurse
                }
            }
        }
        # Write-Object $Groups.memberOf.Path -Depth 1 -backGroundColor Black -foreGroundColor darkGreen
        # Write-Object $Groups.NestedMemberOf.Path -Depth 1 -backGroundColor Black -foreGroundColor Green
        $Groups
    }
    end {
    }
}
Function Get-ADSIMembers{
    <#
        .SYNOPSIS
            Identifi les groupe dont est membre un Object
        .DESCRIPTION
            Membre direct et indirect (sans doublon)
        .PARAMETER ADSIObject
            Object a analyser
        .PARAMETER Recurse
            Mode recursif pour les groupe indirect
        .PARAMETER Items
            Ne pas utiliser, reservé pour le mode recursif
        .EXAMPLE
            Get-ADSIMembers
        .NOTES
            Alban LOPEZ 2019
            alban.lopez@gmail.com
        #>


    Param(
        [Parameter(ValueFromPipeline=$true)]$AdsiGroup,
        [switch]$Recurse,
        $Items = $null
    )
    begin {
        
    }
    process {
        # Write-Object $AdsiGroup.Path,$AdsiGroup.properties.member
        if($null -eq $Items){
            $Items = [PSCustomObject]@{
                Members       = @()
                Groups        = @()
                NestedMembers = @()
                NestedGroups  = @()
            }
            Foreach($Member in $AdsiGroup.properties.member){
                $item = [adsi]"LDAP://$Member"
                if ($item.properties.objectcategory -eq 'CN=Group,CN=Schema,CN=Configuration,DC=open,DC=adds' -and $Recurse) {
                    $Items.groups += $item
                } else {
                    $Items.Members += $item
                }
            }
            foreach ($Group in $Items.groups) {
                $Items = $Group | Get-ADSIMembers -Items $Items -Recurse # -Unique:$Unique
            }
        } else {
            Foreach($Member in $AdsiGroup.properties.member){
                $item = [adsi]"LDAP://$Member"
                if ($item.properties.objectcategory -eq 'CN=Group,CN=Schema,CN=Configuration,DC=open,DC=adds'){
                    # Write-Host ' -> ',$item.path -fore DarkYellow
                    if ($Items.NestedGroups.path -notcontains $item.path -and $Items.Groups.path -notcontains $item.path) {
                        $Items.NestedGroups += $item
                        $Items = $item | Get-ADSIMembers -Items $Items -Recurse # -Unique:$Unique
                    }
                } elseif($Items.NestedMembers.path -notcontains $item.path -and $Items.Members.path -notcontains $item.path) {
                    # Write-Host ' -> ',$item.path -fore Yellow
                    $Items.NestedMembers += $item
                } else {
                    # Write-Host ' -X ',$item.path -fore Red
                }
            }
        }
        $Items
    }
    end {
    }
}
# function Get-ADSIObject {
# <#
# .SYNOPSIS
# liste tous les Objects qui correcponde a un pattern
# .DESCRIPTION
# recherche dans toute les AD
# .PARAMETER NtGroupName
# pattern de recherche
# .EXAMPLE
# Get-ADSIGroup 'MyDomain\Groupe *'
# .EXAMPLE
# 'Groupe *@Domain.tld' | Get-ADSIGroup
# .EXAMPLE
# Get-ADSIGroup 'Groupe admin@*.tld'
# .EXAMPLE
# Get-ADSIGroup '*\*'
# .EXAMPLE
# 'OU=compta,DC=open,DC=tld' | Get-ADSIGroup
# .NOTES
# Alban LOPEZ 2019
# alban.lopez@gmail.com
# #>
# [CmdletBinding()]
# param (
# [Parameter(ValueFromPipeline = $true)]
# [alias("NtAccountName")]
# $Identity ='*',
# [validateset('user','group','computer','Container','Domain-Policy','Organizational-Unit',$null)][string]$type = $null
# )
# begin {
# $Searcher = New-Object DirectoryServices.DirectorySearcher
# switch ($type) {
# # 'group' { $filterProperty = 'samAccountName' }
# 'user' { $filterProperty = 'samAccountName' }
# # 'computer' { }
# Default { $filterProperty = 'name' }
# }
# }
# process {
#
# }
# end {}
# }
function Add-ADSIMemberOf {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)] $ADSIObject = $null,
        $Groups = $null
    )
    begin {
        $AdsiGroup = @()
        foreach ($group in $Groups) {
            if ($Group -is [System.DirectoryServices.DirectoryEntry]){
                $AdsiGroup += $Groups
            } elseif ($group -is [string]) {
                $AdsiGroup += $Groups | Get-ADSIGroup | %{[adsi]$_.Path}
            }
        }
    }
    process {
        if ($ADSIObject -is [string]) {
            $ADSIObject = $ADSIObject | Get-ADSIUser
        }
        foreach ($item in $ADSIObject) {
            if ($item -is [System.DirectoryServices.SearchResult]) {
                $item = [adsi]$item.Path
            }
            foreach ($Group in $AdsiGroup) {
                try {
                    $Group.add($item.Path)
                    Write-LogStep "To [$($Group.Name)]","[$($item.Name)] est maintenant dans ce groupe" ok
                } catch {
                    if($group.member -contains $item.distinguishedName){
                        Write-LogStep "To [$($Group.Name)]","[$($item.Name)] est deja membre !" Warn
                    } else {
                        Write-LogStep "To [$($Group.Name)]","[$($item.Name)] n'as pu etre Ajoute !" error
                    }
                    Write-Verbose $_
                    # $_ | Write-Object -back black -fore red
                    # write-color '[',($_.Exception.innerException.gettype().BaseType),']' -ForeGroundColor Red,Gray,Red -ea 0
                }
            }
        }
    }
    end {}
}
function Remove-ADSIMemberOf {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)] $ADSIObject = $null,
        $Groups = $null
    )
    begin {
        $AdsiGroup = @()
        foreach ($group in $Groups) {
            if ($Group -is [System.DirectoryServices.DirectoryEntry]){
                $AdsiGroup += $Groups
            } elseif ($group -is [string]) {
                $AdsiGroup += $Groups | Get-ADSIGroup | %{[adsi]$_.Path}
            }
        }
    }
    process {
        if ($ADSIObject -is [string]) {
            $ADSIObject = $ADSIObject | Get-ADSIUser
        }
        foreach ($item in $ADSIObject) {
            if ($item -is [System.DirectoryServices.SearchResult]) {
                $item = [adsi]$item.Path
            }
            foreach ($Group in $AdsiGroup) {
                try {
                    $Group.remove($item.Path)
                    Write-LogStep "From [$($Group.Name)]","[$($item.Name)] n'est plus dans ce groupe" ok
                } catch {
                    if($group.member -contains $item.distinguishedName){
                        Write-LogStep "To [$($Group.Name)]","[$($item.Name)] n'a pu etre retire !" Warn
                    } else {
                        Write-LogStep "From [$($Group.Name)]","[$($item.Name)] n'est pas dans ce groupe !" warn
                    }
                    Write-Verbose $_
                    # $_ | Write-Object -back black -fore red
                    # write-color '[',($_.Exception.innerException.gettype().BaseType),']' -ForeGroundColor Red,Gray,Red -ea 0
                }
            }
        }
    }
    end {}
}


if (Get-Module PsWrite) {
    # Export-ModuleMember -Function Convert-RdSession, Get-RdSession
    Write-LogStep 'Chargement du module ',$PSCommandPath ok
} else {
    function Script:Write-logstep {
        param ( [string[]]$messages, $mode, $MaxWidth, $EachLength, $prefixe, $logTrace )
        Write-Verbose "$($messages -join(',')) [$mode]"
    }
}
Get-AllADs