Manage-ADShadowGroup.psm1

#
# Created by: lucas.cueff[at]lucas-cueff.com
#
# Released on: 02/2017
#
#'(c) 2017 lucas-cueff.com - Distributed under Artistic Licence 2.0 (https://opensource.org/licenses/artistic-license-2.0).'
#
<#
    .SYNOPSIS
    Help managing Active Directory ShadowGroup (create, add member, remove...)
 
    .DESCRIPTION
    Manage-ADShadowGroup.psm1 module help admins to manage AD Shadow Group object in an Active Directory environment.
    Require a 2016 Active Directory with "Privileged Access Management Feature" to be used.
    Require RSAT if used on non Domain Controller environment.
 
    .NOTE
    https://support.microsoft.com/en-us/help/3155495/you-can-t-use-the-active-directory-shadow-principal-groups-feature-for-groups-that-are-always-filtered-out-in-windows
    https://msdn.microsoft.com/en-us/library/mt220488.aspx
    https://msdn.microsoft.com/en-us/library/mt220162.aspx
     
    .EXAMPLE
    C:\PS> import-module Manage-ADShadowGroup.psm1
#>


# begin functions
Function Get-ADShadowGroup { 
<#
    .SYNOPSIS
    Get properties of an AD ShadowGroup, including members
 
    .DESCRIPTION
    Get properties of an AD ShadowGroup, including members
 
    .PARAMETER GroupNameValue
    Mandatory parameter
    -GroupNameValue string
    Provide Shadow Group name to search in configuration partition of directory (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
     
    .NOTE
    TTL value could not be shown if set due to Microsoft limitation (not managed at this time, even if the query is made directly in LDAP)
 
    .OUTPUTS
    TypeName: Microsoft.ActiveDirectory.Management.ADObject
     
    CanonicalName : admin.ad/Configuration/Services/Shadow Principal Configuration/PROD-Domain Admins
    CN : PROD-Domain Admins
    Created : 08/03/2017 16:00:41
    createTimeStamp : 08/03/2017 16:00:41
    Deleted :
    Description :
    DisplayName :
    DistinguishedName : CN=PROD-Domain Admins,CN=Shadow Principal
                                      Configuration,CN=Services,CN=Configuration,DC=admin,DC=ad
    dSCorePropagationData : {01/01/1601 01:00:00}
    instanceType : 4
    isDeleted :
    LastKnownParent :
    member : {CN=Temp Prod Admins Shadow,OU=Admins,DC=admin,DC=ad, CN=Temp Prod
                                      Admins,OU=Admins,DC=admin,DC=ad, CN=Prod Admins,OU=Admins,DC=admin,DC=ad}
    Modified : 08/03/2017 19:12:47
    modifyTimeStamp : 08/03/2017 19:12:47
    msDS-ShadowPrincipalSid : S-1-1-11-1111111111-1111111111-111111111-111
    Name : PROD-Domain Admins
    nTSecurityDescriptor : System.DirectoryServices.ActiveDirectorySecurity
    ObjectCategory : CN=ms-DS-Shadow-Principal,CN=Schema,CN=Configuration,DC=admin,DC=ad
    ObjectClass : msDS-ShadowPrincipal
    ObjectGUID : 37e6ba38-1787-41ef-937f-02a7c0be6fc3
    ProtectedFromAccidentalDeletion : False
    sDRightsEffective : 11
    uSNChanged : 16646
    uSNCreated : 16442
    whenChanged : 08/03/2017 19:12:47
    whenCreated : 08/03/2017 16:00:41
 
    .EXAMPLE
    C:\PS> Get-ADShadowGroup -GroupNameValue "Shadow-Domain Admins"
#>
    
    [CmdletBinding()] 
    Param( 
          [parameter(Mandatory=$True)] 
          [String]$GroupNameValue
          )           
    try {
        import-module ActiveDirectory
    } catch {
        Write-Host "Not able to load active directory module - KO"  -foregroundcolor "Red"
        Write-Host "Please check RSAT is installed if you are running the script on A PC"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    $CurrentConfigurationPartDN = ([ADSI]"LDAP://RootDSE").configurationNamingContext
    $ShadowGroupPath = "cn=Shadow Principal Configuration,cn=Services,$($CurrentConfigurationPartDN)"
    $CheckObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties *
    If (-not $CheckObject) {
        Write-Host "ShadowPrincipal group $($GroupNameValue) not found ! - KO"  -foregroundcolor "Red"
        return
    }
    $CheckObject
}

Function Add-ADShadowGroup {
    <#
    .SYNOPSIS
    Create a new AD ShadowGroup
 
    .DESCRIPTION
    Create a new AD ShadowGroup in the default container (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
 
    .PARAMETER GroupNameValue
    Mandatory parameter
    -GroupNameValue string
    Provide Shadow Group name to be created in configuration partition of directory (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
     
    .PARAMETER GroupSIDValue
    Mandatory parameter
    -GroupSIDValue string
    Provide SID of another AD group or user to shadow
 
    .OUTPUTS
    TypeName: Microsoft.ActiveDirectory.Management.ADObject
     
    CanonicalName : admin.ad/Configuration/Services/Shadow Principal Configuration/PROD-Domain Admins
    CN : PROD-Domain Admins
    Created : 08/03/2017 16:00:41
    createTimeStamp : 08/03/2017 16:00:41
    Deleted :
    Description :
    DisplayName :
    DistinguishedName : CN=PROD-Domain Admins,CN=Shadow Principal
                                      Configuration,CN=Services,CN=Configuration,DC=admin,DC=ad
    dSCorePropagationData : {01/01/1601 01:00:00}
    instanceType : 4
    isDeleted :
    LastKnownParent :
    member : {CN=Temp Prod Admins Shadow,OU=Admins,DC=admin,DC=ad, CN=Temp Prod
                                      Admins,OU=Admins,DC=admin,DC=ad, CN=Prod Admins,OU=Admins,DC=admin,DC=ad}
    Modified : 08/03/2017 19:12:47
    modifyTimeStamp : 08/03/2017 19:12:47
    msDS-ShadowPrincipalSid : S-1-1-11-1111111111-1111111111-111111111-111
    Name : PROD-Domain Admins
    nTSecurityDescriptor : System.DirectoryServices.ActiveDirectorySecurity
    ObjectCategory : CN=ms-DS-Shadow-Principal,CN=Schema,CN=Configuration,DC=admin,DC=ad
    ObjectClass : msDS-ShadowPrincipal
    ObjectGUID : 37e6ba38-1787-41ef-937f-02a7c0be6fc3
    ProtectedFromAccidentalDeletion : False
    sDRightsEffective : 11
    uSNChanged : 16646
    uSNCreated : 16442
    whenChanged : 08/03/2017 19:12:47
    whenCreated : 08/03/2017 16:00:41
 
    .EXAMPLE
    C:\PS> Add-ADShadowGroup -GroupNameValue "Shadow-Domain Admins" -GroupSIDValue "S-1-1-11-1111111111-1111111111-111111111-111"
    #>
    
    [CmdletBinding()]
    Param(
      [Parameter(Mandatory=$False,Position=1)]
        [string]$GroupNameValue,
          [Parameter(Mandatory=$False,Position=2)]
        [string]$GroupSIDValue
    )
    try {
        import-module ActiveDirectory
    } catch {
        Write-Host "Not able to load active directory module - KO"  -foregroundcolor "Red"
        Write-Host "Please check RSAT is installed if you are running the script on A PC"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    $CurrentConfigurationPartDN = ([ADSI]"LDAP://RootDSE").configurationNamingContext
    $ShadowGroupPath = "cn=Shadow Principal Configuration,cn=Services,$($CurrentConfigurationPartDN)"
    $CheckObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties DistinguishedName
    If ($CheckObject) {
        Write-Host "ShadowPrincipal group $($GroupNameValue) already exists ! - KO"  -foregroundcolor "Red"
        return
    }
    If ($GroupSIDValue -match 'S-[0-9]-[0-9]-[0-9]{2}-[0-9]{10}-[0-9]{10}-[0-9]{9}-[0-9]{2,}') {
        try {
            New-ADObject -Type "msDS-ShadowPrincipal" -Name "$($GroupNameValue)" -Path "$($ShadowGroupPath)" -OtherAttributes @{'msDS-ShadowPrincipalSid'= "$($GroupSIDValue)"}
        } catch {
            Write-Host "Not able to create new ShadowPrincipal group $($GroupNameValue) for SID $($GroupSIDValue) - KO"  -foregroundcolor "Red"
            write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
            write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
            return 
        }
    } Else {
        Write-Host "provided SID not in the right format - KO"  -foregroundcolor "Red"
    }
    $CheckObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties *
    $CheckObject
}

function Remove-ADShadowGroup {
    <#
    .SYNOPSIS
    Remove an existing AD ShadowGroup
 
    .DESCRIPTION
    Remove a existing AD ShadowGroup hosted in the default container (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
 
    .PARAMETER GroupNameValue
    Mandatory parameter
    -GroupNameValue string
    Provide Shadow Group name to be removed in configuration partition of directory (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
     
    .OUTPUTS
    Console write-line output
    "ShadowPrincipal group XXXX removed correctly ! - OK"
 
    .EXAMPLE
    C:\PS> Remove-ADShadowGroup -GroupNameValue "Shadow-Domain Admins"
    #>

    [CmdletBinding()]
    Param(
      [Parameter(Mandatory=$False,Position=1)]
        [string]$ShadowGroupToremove
    )
    try {
        import-module ActiveDirectory
    } catch {
        Write-Host "Not able to load active directory module - KO"  -foregroundcolor "Red"
        Write-Host "Please check RSAT is installed if you are running the script on A PC"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    $CurrentConfigurationPartDN = ([ADSI]"LDAP://RootDSE").configurationNamingContext
    $ShadowGroupPath = "cn=Shadow Principal Configuration,cn=Services,$($CurrentConfigurationPartDN)"

    try {
        $ShadowGroups = Get-ADObject -Filter * -SearchBase "$($ShadowGroupPath)" -properties CN
    } catch {
        Write-Host "Not able to find shadow group in default container of configuration, please investigate - KO"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return
    }

    try {
        $GroupToRemove = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($ShadowGroupToremove.trim())'" -SearchBase "$($ShadowGroupPath)" -properties DistinguishedName
    } catch {
        Write-Host "Not able to locate ShadowPrincipal group $($ShadowGroupToremove) - KO"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return
    }

    try {
        Remove-ADObject $GroupToRemove.distinguishedname -Confirm:$false
    } catch {
        Write-Host "Not able to remove ShadowPrincipal group $($ShadowGroupToremove) - KO"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    
    $CheckObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($ShadowGroupToremove)'" -SearchBase "$($ShadowGroupPath)" -properties *
    If (-not $CheckObject) {
        Write-Host "ShadowPrincipal group $($ShadowGroupToremove) removed correctly ! - OK"  -foregroundcolor "green"
        return
    }
}

function Add-ADShadowGroupMember {
    <#
    .SYNOPSIS
    Add a new member in an existing AD ShadowGroup
 
    .DESCRIPTION
    Add a new member in an existing AD ShadowGroup. The member could be a user or a group but must be located in the same AD hosting the Shadow Group.
 
    .PARAMETER GroupNameValue
    Mandatory parameter
    -GroupNameValue string
    Provide Shadow Group name to be used in configuration partition of directory (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
     
    .PARAMETER MemberNameValue
    Mandatory parameter
    -MemberNameValue string
    Provide the name of user or group to be added in the "member" attribute of the AD Shadow Group (object to be searched only in the current AD)
     
    .PARAMETER MemberNameValue
    Mandatory parameter
    -TypeValue string (user or group)
    Provide the type of member to add (user or group) (object to be searched only in the current AD)
     
    .PARAMETER TTLValue
    Optional parameter
    -TTLValue string (time in second)
    Provide the TTL membership for the entry to be added (user or group). At the end of the TTL, the entry is removed automatically from the member attribute of the object.
 
    .OUTPUTS
    TypeName: Microsoft.ActiveDirectory.Management.ADObject
     
    CanonicalName : admin.ad/Configuration/Services/Shadow Principal Configuration/PROD-Domain Admins
    CN : PROD-Domain Admins
    Created : 08/03/2017 16:00:41
    createTimeStamp : 08/03/2017 16:00:41
    Deleted :
    Description :
    DisplayName :
    DistinguishedName : CN=PROD-Domain Admins,CN=Shadow Principal
                                      Configuration,CN=Services,CN=Configuration,DC=admin,DC=ad
    dSCorePropagationData : {01/01/1601 01:00:00}
    instanceType : 4
    isDeleted :
    LastKnownParent :
    member : {CN=Temp Prod Admins Shadow,OU=Admins,DC=admin,DC=ad, CN=Temp Prod
                                      Admins,OU=Admins,DC=admin,DC=ad, CN=Prod Admins,OU=Admins,DC=admin,DC=ad}
    Modified : 08/03/2017 19:12:47
    modifyTimeStamp : 08/03/2017 19:12:47
    msDS-ShadowPrincipalSid : S-1-1-11-1111111111-1111111111-111111111-111
    Name : PROD-Domain Admins
    nTSecurityDescriptor : System.DirectoryServices.ActiveDirectorySecurity
    ObjectCategory : CN=ms-DS-Shadow-Principal,CN=Schema,CN=Configuration,DC=admin,DC=ad
    ObjectClass : msDS-ShadowPrincipal
    ObjectGUID : 37e6ba38-1787-41ef-937f-02a7c0be6fc3
    ProtectedFromAccidentalDeletion : False
    sDRightsEffective : 11
    uSNChanged : 16646
    uSNCreated : 16442
    whenChanged : 08/03/2017 19:12:47
    whenCreated : 08/03/2017 16:00:41
 
    .EXAMPLE
    C:\PS> Add-ADShadowGroupMember -GroupNameValue "Shadow-Domain Admins" -MemberNameValue "Domain Admins" -MemberNameValue group
     
    .EXAMPLE
    C:\PS> Add-ADShadowGroupMember -GroupNameValue "Shadow-Domain Admins" -MemberNameValue "Super-Admin" -MemberNameValue user
     
    .EXAMPLE
    C:\PS> Add-ADShadowGroupMember -GroupNameValue "Shadow-Domain Admins" -MemberNameValue "Temp-Super-Admin" -MemberNameValue user -TTLValue "3600"
    #>

    Param(
      [Parameter(Mandatory=$true,Position=1)]
        [string]$GroupNameValue,
      [Parameter(Mandatory=$true,Position=2)]
        [string]$MemberNameValue,
     [parameter(Mandatory=$true,Position=3)]
     [ValidateSet("user", "group")]
        [String]$TypeValue,
     [Parameter(Mandatory=$False,Position=4)]
        [string]$TTLValue
    )
    try {
        import-module ActiveDirectory
    } catch {
        Write-Host "Not able to load active directory module - KO"  -foregroundcolor "Red"
        Write-Host "Please check RSAT is installed if you are running the script on A PC"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    $CurrentConfigurationPartDN = ([ADSI]"LDAP://RootDSE").configurationNamingContext
    $ADPArtDN = ([ADSI]"LDAP://RootDSE").defaultNamingContext
    $ShadowGroupPath = "cn=Shadow Principal Configuration,cn=Services,$($CurrentConfigurationPartDN)"
    $CheckShadowObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties member
    $CheckShadowObjectDN = $CheckShadowObject.DistinguishedName | select-object
    If (-not $CheckShadowObject) {
        Write-Host "ShadowPrincipal group $($GroupNameValue) not found ! - KO"  -foregroundcolor "Red"
        return
    }
    try {
        $checkUserObject = get-adobject -Filter "ObjectClass -eq '$($TypeValue)' -and cn -eq '$($MemberNameValue)'" -SearchBase "$($ADPArtDN)" -SearchScope Subtree -properties *
        $checkUserObjectDN = $checkUserObject.DistinguishedName | select-object
    } catch {
        Write-Host "Not able to find $($MemberNameValue) in current AD - KO"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    
    If ($TTLValue) {
        try {
            Set-ADObject -Identity "$($CheckShadowObjectDN)" -Add @{'member'="<TTL=$($TTLValue),$($checkUserObjectDN)>"}
        } catch {
            Write-Host "Not able to add $($MemberNameValue) to $($GroupNameValue) with TTL $($TTLValue) - KO"  -foregroundcolor "Red"
            write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
            write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
            return 
        }
    } Else {
        try {
            Set-ADObject -Identity "$($CheckShadowObjectDN)" -Add @{'member'="$($checkUserObjectDN)"}
        } catch {
            Write-Host "Not able to add $($MemberNameValue) to $($GroupNameValue) - KO"  -foregroundcolor "Red"
            write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
            write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
            return 
        }
    }

    $CheckObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties *
    $CheckObject
}

function Remove-ADShadowGroupMember {
        <#
    .SYNOPSIS
    Remove an existing member in an existing AD ShadowGroup
 
    .DESCRIPTION
    Remove an existing member in an existing AD ShadowGroup.
 
    .PARAMETER GroupNameValue
    Mandatory parameter
    -GroupNameValue string
    Provide Shadow Group name to be used in configuration partition of directory (cn=Shadow Principal Configuration,cn=Services...) (object to be searched only in the current AD)
     
    .PARAMETER MemberNameValue
    Mandatory parameter
    -MemberNameValue string
    Provide the name of user or group to be removed in the "member" attribute of the AD Shadow Group (object to be searched only in the current AD)
     
    .PARAMETER MemberNameValue
    Mandatory parameter
    -TypeValue string (user or group)
    Provide the type of member to remove (user or group) (object to be searched only in the current AD)
     
    .OUTPUTS
    TypeName: Microsoft.ActiveDirectory.Management.ADObject
     
    CanonicalName : admin.ad/Configuration/Services/Shadow Principal Configuration/PROD-Domain Admins
    CN : PROD-Domain Admins
    Created : 08/03/2017 16:00:41
    createTimeStamp : 08/03/2017 16:00:41
    Deleted :
    Description :
    DisplayName :
    DistinguishedName : CN=PROD-Domain Admins,CN=Shadow Principal
                                      Configuration,CN=Services,CN=Configuration,DC=admin,DC=ad
    dSCorePropagationData : {01/01/1601 01:00:00}
    instanceType : 4
    isDeleted :
    LastKnownParent :
    member : {CN=Temp Prod Admins Shadow,OU=Admins,DC=admin,DC=ad, CN=Temp Prod
                                      Admins,OU=Admins,DC=admin,DC=ad, CN=Prod Admins,OU=Admins,DC=admin,DC=ad}
    Modified : 08/03/2017 19:12:47
    modifyTimeStamp : 08/03/2017 19:12:47
    msDS-ShadowPrincipalSid : S-1-1-11-1111111111-1111111111-111111111-111
    Name : PROD-Domain Admins
    nTSecurityDescriptor : System.DirectoryServices.ActiveDirectorySecurity
    ObjectCategory : CN=ms-DS-Shadow-Principal,CN=Schema,CN=Configuration,DC=admin,DC=ad
    ObjectClass : msDS-ShadowPrincipal
    ObjectGUID : 37e6ba38-1787-41ef-937f-02a7c0be6fc3
    ProtectedFromAccidentalDeletion : False
    sDRightsEffective : 11
    uSNChanged : 16646
    uSNCreated : 16442
    whenChanged : 08/03/2017 19:12:47
    whenCreated : 08/03/2017 16:00:41
 
    .EXAMPLE
    C:\PS> Remove-ADShadowGroupMember -GroupNameValue "Shadow-Domain Admins" -MemberNameValue "Domain Admins" -MemberNameValue group
     
    .EXAMPLE
    C:\PS> Remove-ADShadowGroupMember -GroupNameValue "Shadow-Domain Admins" -MemberNameValue "Super-Admin" -MemberNameValue user
    #>

    Param(
      [Parameter(Mandatory=$true,Position=1)]
        [string]$GroupNameValue,
      [Parameter(Mandatory=$true,Position=2)]
        [string]$MemberNameValue,
     [parameter(Mandatory=$true,Position=3)]
     [ValidateSet("user", "group")]
        [String]$TypeValue
    )
    try {
        import-module ActiveDirectory
    } catch {
        Write-Host "Not able to load active directory module - KO"  -foregroundcolor "Red"
        Write-Host "Please check RSAT is installed if you are running the script on A PC"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    $CurrentConfigurationPartDN = ([ADSI]"LDAP://RootDSE").configurationNamingContext
    $ADPArtDN = ([ADSI]"LDAP://RootDSE").defaultNamingContext
    $ShadowGroupPath = "cn=Shadow Principal Configuration,cn=Services,$($CurrentConfigurationPartDN)"
    $CheckShadowObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties member
    $CheckShadowObjectDN = $CheckShadowObject.DistinguishedName | select-object
    If (-not $CheckShadowObject) {
        Write-Host "ShadowPrincipal group $($GroupNameValue) not found ! - KO"  -foregroundcolor "Red"
        return
    }
    try {
        $checkUserObject = get-adobject -Filter "ObjectClass -eq '$($TypeValue)' -and cn -eq '$($MemberNameValue)'" -SearchBase "$($ADPArtDN)" -SearchScope Subtree -properties *
        $checkUserObjectDN = $checkUserObject.DistinguishedName | select-object
    } catch {
        Write-Host "Not able to find $($MemberNameValue) in current AD - KO"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }
    
    try {
        Set-ADObject -Identity "$($CheckShadowObjectDN)" -Remove @{'member'="$($checkUserObjectDN)"}
    } catch {
        Write-Host "Not able to remove $($MemberNameValue) from $($GroupNameValue) - KO"  -foregroundcolor "Red"
        write-host "Error Type: $($_.Exception.GetType().FullName)" -ForegroundColor "Yellow"
        write-host "Error Message: $($_.Exception.Message)" -ForegroundColor "Yellow"
        return 
    }

    $CheckObject = get-adobject -Filter "ObjectClass -eq 'msDS-ShadowPrincipal' -and Name -eq '$($GroupNameValue)'" -SearchBase "$($ShadowGroupPath)" -properties *
    $CheckObject
}

Export-ModuleMember -Function Add-ADShadowGroupMember, Remove-ADShadowGroup, Add-ADShadowGroup, Get-ADShadowGroup, Remove-ADShadowGroupMember