cActiveDirectorySecurity.psm1

# Localized messages
data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData @'
        # Common Messages
        ADPSDriveError = An error occurred connecting to AD: PSDrive for Server '{0}'.
        ADDefaultPSDriveError = An error occurred connecting to AD: PSDrive for the default domain.
        IdentityNotFound = An error occurred locating an object with the identity specified ('{0}'). Identities must be passed in either distinguishedName or objectGUID format, or as a reference object.
        IdentityTypeInvalid = An error occurred validating the identity reference ('{0}'). Reference objects must include either a distinguishedName or objectGUID property.
        GetAclError = An error occurred running Get-Acl on object '{0}'.
        NetBIOSDomainError = An error occurred resolving NetBIOS Domain Name for the Domain.
        # Function Add-ADObjectAce
        IdentityReferenceInvalid = An error occurred translating the Identity Reference ('{0}') to a Sid value. Processing cannot continue.
        ObjectTypeNameNotFound = An error occurred locating the ObjectTypeName with the name specified ('{0}').
        InheritedObjectTypeNameNotFound = An error occurred locating the InheritedObjectTypeName with the name specified ('{0}').
        CreateNewAceError = An error occurred creating the new Ace on object '{0}'.
        AddAceError = An error occurred adding the new Ace to existing Acl on object '{0}'.
        SetAclError = An error occurred applying updated Acl on object '{0}'.
        # Function Remove-ADObjectAce
        AceNotFound = An error occurred locating an Ace that matched the specified filter on object '{0}'.
        MultipleAceValuesFound = An error occurred locating a single Ace that matched the specified filter on object '{0}'. Multiple Ace values were returned ('{1}').
        MatchingAceNotFound = An error occurred locating an Ace within the Acl which matched the specified filter on object '{0}'.
        RemoveAceError = An error occurred removing the Ace from existing Acl on object '{0}'.
        ClearAclError = An error occurred applying updated Acl on object '{0}'.
        # Function Get-ADObjectRightsGUID
        RootDSEError = An error occurred enumerating Root DSE for the Domain.
        # Function Resolve-ObjectSidToName
        GetADObjectSidError = An error occurred resolving the name for Sid value '{0}'.
        # Function Resolve-NameToObjectSid
        TranslateNameError = An error occurred translating the objectSid for identity reference '{0}'.
        GetADObjectNameError = An error occurred resolving the objectSid for identity reference '{0}'. An object with the sAMAccountName value '{1}' could not be found.
'@

}

<#
.Synopsis
    Gets the permissions from the specified Active Directory Object.
.DESCRIPTION
    Gets the permissions / access control list (ACL) from the specified Active Directory Object.
 
    The function can either write to the standard output stream, or copy the information to the clipboard in tab-delimited format that can be pasted directly into a Microsoft Excel for review.
.EXAMPLE
    Get-AdUser -Identity JBloggs | Get-ADObjectAcl
 
    Gets the permissions for the ADUser Object 'JBloggs' from the default Active Directory Domain.
.EXAMPLE
    Get-ADObjectAcl -Identity 29f0c9c7-aef4-4823-99a1-0f5f1df395d5
 
    Gets the permissions for the ADObject with GUID '29f0c9c7-aef4-4823-99a1-0f5f1df395d5' from the default Acive Directory Domain.
.EXAMPLE
    Get-ADObjectAcl -Identity "OU=Domain Controllers,DC=contoso,DC=com"
 
    Gets the permissions for the ADObject with distinguishedName 'OU=Domain Controllers,DC=contoso,DC=com' from the default Acive Directory Domain.
.EXAMPLE
    Get-ADUser -filter {surname -eq "Bloggs"} | Get-ADObjectAcl -Clip
 
    Gets the permissions for all the ADUser objects with the surname 'Bloggs' from the default Active Directory Domain and copies those permissions to the clipboard in tab delimited format which can be pasted directly into Microsoft Excel.
.EXAMPLE
   Get-AdUser -Identity JBloggs -Server dc1.contoso.com | Get-ADObjectAcl -Server dc1.contoso.com
 
   Gets the permissions for the ADUser Object 'JBloggs' from the server 'dc1.contoso.com'.
.EXAMPLE
    Get-ADOrganizationalUnit -filter {name -eq "Domain Controllers"} | Get-ADObjectAcl -IsInherited $false
 
    Gets all non-inherited permissions from the 'Domain Controllers' Organizational Unit fom the default Active Directory Domain.
.EXAMPLE
    Get-ADObjectAcl -Identity "OU=Domain Controllers,DC=contoso,DC=com" -IdentityReference "BUILTIN\Administrators"
 
    Gets all permissions from the 'Domain Controllers' Organizational Unit from the default Active Directory Domain which are granted to the identity reference 'BUILTIN\Administrators'.
.EXAMPLE
    Get-ADObjectAcl -Identity "OU=Domain Controllers,DC=contoso,DC=com" -ActiveDirectoryRights GenericAll
 
    Gets all permissions from the 'Domain Controllers' Organizational Unit from the default Active Directory Domain which are granted the Active Directory Right 'GenericAll'.
.EXAMPLE
    Get-ADObjectAcl -Identity "cn=Users,DC=contoso,DC=com" -ObjectTypeName user
 
    Gets all permissions from the 'Users' container from the default Active Directory Domain which are granted over objects of type 'user'.
.EXAMPLE
    Get-ADObjectAcl -Identity "cn=Users,DC=contoso,DC=com" -ObjectTypeName RAS-Information -InheritedObjectTypeName user
 
    Gets all permissions from the 'Users' container from the default Active Directory Domain which are granted over object type of 'RAS-Information' and inherited object of type of 'user'.
.INPUTS
   The identity parameter of the CmdLet accepts either a distinguishedName or ObjectGUID or AD Objects. AD Objects which are passed by reference must include either a distinguishedName or ObjectGUID property.
.OUTPUTS
   Outputs the Access Control List from the Active Directory Object or Objects.
#>

function Get-ADObjectAcl
{
    [CmdletBinding(SupportsShouldProcess = $false, PositionalBinding = $true)]
    [OutputType([Object])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
    Param(
        # The Identity of the Active Directory Object in either distinguishedName or GUID format or by reference.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 1)]
        [object]
        $Identity,

        # The target Active Directory Server / Domain Controller.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $Server,

        # Send the output to the Clipboard in tab-delimited format (can be pasted directly into Microsoft Excel for review).
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [Alias("Clip")]
        [switch]
        $SendToClipboard,

        # Filter the returned Access Control Entries based on IsInherited ($true / $false).
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [bool]
        $IsInherited,

        # Filter the returned Access Control Entries based on the IdentityReference of the ACE (DOMAIN\USERNAME).
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $IdentityReference,

        # Filter the returned Access Control Entries based on the IdentityReference Name of the ACE (USERNAME).
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $IdentityReferenceName,

        # Filter the returned Access Control Entries based on the IdentityReference Domain of the ACE (DOMAIN / BUILTIN / NT AUTHORITY).
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $IdentityReferenceDomain,

        # Filter the returned Access Control Entries based on the Active Directory Rights of the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [ValidateSet( "CreateChild", "DeleteChild", "ListChildren", "Self", "ReadProperty", "WriteProperty", "DeleteTree", "ListObject", "ExtendedRight", "Delete", "ReadControl", "GenericExecute", "GenericWrite", "GenericRead", "WriteDacl", "WriteOwner", "GenericAll", "Synchronize", "AccessSystemSecurity")]
        [string[]]
        $ActiveDirectoryRights,

        # Filter the returned Access Control Entries based on the Object Type Name of the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $ObjectTypeName,

        # Filter the returned Access Control Entries based on the Inherited Object Type Name of the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $InheritedObjectTypeName,

        # Filter the returned Access Control Entries based on the Inheritance Type of the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [ValidateSet( "All", "DeleteChild", "Children", "Descendents", "ReadProperty", "None", "SelfAndChildren")]
        [string[]]
        $InheritanceType,

        # Filter the returned Access Control Entries based on the Access Control Type of the ACE (Allow / Deny)
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [ValidateSet("Allow", "Deny")]
        [string]
        $AccessControlType,

        # Credential to use.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, ParameterSetName = "Credential")]
        [System.Management.Automation.PSCredential]
        $Credential
    )
    Begin
    {
        # no need to perform AD PSDrive setup if the command is being called by another function within the same module
        if ( ((Get-PSCallStack)[0].Location).Split(":")[0] -ne ((Get-PSCallStack)[1].Location).Split(":")[0])
        {
            Push-Location -StackName 'cActiveDirectorySecurity' # keep a record of the current location

            $PSDriveParams = @{"ErrorAction" = "Stop"}

            if ($PSBoundParameters.ContainsKey('Credential'))
            {
                $PSDriveParams.Add("Credential", $Credential)
            }
            if ($PSBoundParameters.ContainsKey('Server'))
            {
                $PSDriveParams.Add("Server", $Server)
            }
            else
            {
                $Server = (Get-AdDomain).NetBIOSName
            }

            if (($PSBoundParameters.ContainsKey('Server')) -or ($PSBoundParameters.ContainsKey('Credential')))
            {
                try
                {
                    # create a new PSDrive to the target AD Domain Controller / or using different credentials
                    $PSDriveName = $Server.Split(".")[0]
                    New-PSDrive -Name $PSDriveName -PSProvider ActiveDirectory -root "//RootDSE/" @PSDriveParams | Out-Null
                    Set-Location ($PSDriveName + ":") -ErrorAction Stop
                    Write-Verbose "Connecting to specified server / domain controller."
                    Write-Verbose -Message "Server: $Server"
                }
                catch
                {
                    Write-Warning -Message ($LocalizedData.ADPSDriveError -f $Server)
                    Pop-Location
                    throw $_
                }
            }
            else
            {
                # connect to the default Active Directory PSDrive
                try
                {
                    Set-Location -Path AD: -ErrorAction Stop
                    Write-Verbose "Connecting to default domain."
                }
                catch
                {
                    Write-Warning -Message ($LocalizedData.ADDefaultPSDriveError)
                    Pop-Location
                    throw $_
                }
            }
        }

        # retrieve hashtable of schema and access right GUIDs
        $schemaAndAccessRights = Get-ADObjectRightsGUID
        # variable used to store objectSID to account mappings
        $objectSIDs = @{}
        # variable used to store the resulting output
        $report = @()

        # make a record of the current location to private stack
        Push-Location -StackName 'Get-ADObjectAcl' -ErrorAction SilentlyContinue # keep a record of the current location
    }
    Process
    {
        $localLocationStack = (Get-Location -StackName 'Get-ADObjectAcl' -ErrorAction SilentlyContinue)
        if ((Get-Location).Path -ne $LocalLocationStack.Path)
        {
            Pop-Location -StackName 'Get-ADObjectAcl' -ErrorAction SilentlyContinue
        }

        # determine whether a string or reference object has been passed
        if ($Identity -is "String")
        {
            Write-Verbose " Resolving String Identity Reference $Identity."
            try
            {
                $Identity = Get-ADObject -Identity $Identity -ErrorAction Stop
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.IdentityNotFound -f $Identity)
                Write-Error $_
                Return $null
            }
        }
        else
        {
            if (($null -eq $Identity.DistinguishedName) -and ($null -eq $Identity.ObjectGUID))
            {
                Write-Warning -Message ($LocalizedData.IdentityTypeInvalid -f $Identity)
                Write-Error $_
                Return $null
            }
            Write-Verbose " Reference Identity $($Identity.ToString())."
        }

        # enumerate permissions on the Active Directory Object
        try
        {
            Write-Verbose " Extracting Access Control List."
            $Permissions = (Get-Acl -Path $Identity.DistinguishedName).Access
        }
        catch
        {
            Write-Warning -Message ($LocalizedData.GetAclError -f ($Identity.DistinguishedName))
            Write-Error $_
            Return $null
        }

        forEach ($Permission in $Permissions)
        {
            $IdentityReferenceLabel = $null
            # extract the ObjectType and InheritedObjectType friendly names from the schemaAndAccessRights hash table from their GUIDs.
            $PermissionObjectTypeName = $schemaAndAccessRights.Item($Permission.ObjectType)
            $PermissionInheritedObjectTypeName = $schemaAndAccessRights.Item($Permission.InheritedObjectType)
            if ($Permission.IdentityReference -is [System.Security.Principal.SecurityIdentifier])
            {
                # certain Well Known SIDs don't seem to be resolved either by Get-Acl or using Translate()? And neither do SIDs from trusting Domains under certain circumstances
                # therefore a helper function is used to translate names for these; but first check that this SID hasn't previously been resolved
                if ($objectSIDs.ContainsKey($Permission.IdentityReference.Value))
                {
                    $IdentityReferenceLabel = $objectSIDs.($Permission.IdentityReference.Value)
                }
                else
                {
                    try
                    {
                        $IdentityReferenceLabel = (Resolve-ObjectSidToName -Sid $Permission.IdentityReference)
                    }
                    catch
                    {
                        # ignore errors returned by the Resolve-ObjectSidToName function and continue
                    }

                    # add the resolved name to objectSIDs to prevent having to resolve the same SID again
                    $objectSIDs.Add($Permission.IdentityReference.Value, $IdentityReferenceLabel)
                }
            }
            else
            {
                $IdentityReferenceLabel = $Permission.IdentityReference
            }

            # filter the ACE items returned, based on the parameters which have been passed.
            if ($PSBoundParameters.ContainsKey('IsInherited'))
            {
                if ($Permission.IsInherited -ne $IsInherited)
                {
                    Continue
                }
            }
            if ($PSBoundParameters.ContainsKey('IdentityReference'))
            {
                if ($IdentityReferenceLabel -ne $IdentityReference)
                {
                    Continue
                }
            }
            if ($PSBoundParameters.ContainsKey('ActiveDirectoryRights'))
            {
                $PermissionActiveDirectoryRights = ($Permission.ActiveDirectoryRights -split ", ")
                if ($null -ne (Compare-Object -ReferenceObject $PermissionActiveDirectoryRights -DifferenceObject $ActiveDirectoryRights))
                {
                    Continue
                }
            }
            if ($PSBoundParameters.ContainsKey('ObjectTypeName'))
            {
                if ($ObjectTypeName -ne $PermissionObjectTypeName)
                {
                    Continue
                }
            }
            if ($PSBoundParameters.ContainsKey('InheritedObjectTypeName'))
            {
                if ($InheritedObjectTypeName -ne $PermissionInheritedObjectTypeName)
                {
                    Continue
                }
            }
            if ($PSBoundParameters.ContainsKey('InheritanceType'))
            {
                if ($InheritanceType -ne $Permission.InheritanceType)
                {
                    Continue
                }
            }
            if ($PSBoundParameters.ContainsKey('AccessControlType'))
            {
                if ($AccessControlType -ne $Permission.AccessControlType)
                {
                    Continue
                }
            }

            $reportItem = New-Object -TypeName "PSObject" -Property @{
                distinguishedName       = $Identity.DistinguishedName
                ActiveDirectoryRights   = ($Permission.ActiveDirectoryRights.ToString() -Split ", ")
                InheritanceType         = $Permission.InheritanceType
                ObjectType              = $Permission.ObjectType
                ObjectTypeName          = $PermissionObjectTypeName
                InheritedObjectType     = $Permission.InheritedObjectType
                InheritedObjectTypeName = $PermissionInheritedObjectTypeName
                ObjectFlags             = $Permission.ObjectFlags
                AccessControlType       = $Permission.AccessControlType
                IdentityReference       = $IdentityReferenceLabel
                IsInherited             = $Permission.IsInherited
                InheritanceFlags        = $Permission.InheritanceFlags
                PropagationFlags        = $Permission.PropagationFlags
            }
            $reportItem.PSObject.TypeNames.Insert(0, "cActiveDirectorySecurity.ACE")

            # apply additional filters for those properties associated with cActiveDirectorySecurity.ACE type
            if ($PSBoundParameters.ContainsKey('IdentityReferenceName'))
            {
                if ($reportItem.IdentityReferenceName -ne $IdentityReferenceName)
                {
                    Continue
                }
                Write-Verbose $IdentityReferenceName
            }
            if ($PSBoundParameters.ContainsKey('IdentityReferenceDomain'))
            {
                if ($reportItem.IdentityReferenceDomain -ne $IdentityReferenceDomain)
                {
                    Continue
                }
            }

            $report += $reportItem
        }
    }
    End
    {
        # no need to perform AD PSDrive clear down if the command is being called by another function within the same module
        if ( ((Get-PSCallStack)[0].Location).Split(":")[0] -ne ((Get-PSCallStack)[1].Location).Split(":")[0])
        {
            Pop-Location -StackName 'cActiveDirectorySecurity' -ErrorAction SilentlyContinue
            if ($null -ne $PSDriveName)
            {
                Remove-PSDrive -Name $PSDriveName -Force # remove AD PSDrive if one was created
            }
        }

        if ($SendToClipboard)
        {
            $props = @("DistinguishedName"
                "IdentityReference"
                "IdentityReferenceName"
                "IdentityReferenceDomain"
                @{n = "ActiveDirectoryRights"; e = {$_.ActiveDirectoryRights -Join ", "}}
                "ObjectType"
                "ObjectTypeName"
                "InheritedObjectType"
                "InheritedObjectTypeName"
                "AccessControlType"
                "PropagationFlags"
                "IsInherited"
                "InheritanceFlags"
                "ObjectFlags"
                "InheritanceType"
            )
            # send output to clipboard in tab delimited format (can be pasted directly into Microsoft Excel)
            if ($PSVersionTable.PSVersion.Major -lt 5)
            {
                $report | Select-Object $props | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Clip.exe
            }
            else
            {
                $report | Select-Object $props | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Set-Clipboard
            }
        }
        else
        {
            Return $report
        }
    }
}

<#
.Synopsis
    Adds a new Access Control Entry to an Access Control List defined on an Active Directory Object.
.DESCRIPTION
    Adds a new Access Control Entry (ACE) to an Access Control List (ACL) defined on an Active Directory Object.
 
.EXAMPLE
    Add-ADObjectAce -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName Description -InheritedObjectTypeName User -InheritanceType Descendents -WhatIf
 
    Adds a new ACE to ACL on AD object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Description" and "InheritedObjectType" of Name "User" propagated to "Descendants".
 
    As the -WhatIf parameter is specified the format of the new ACE is displayed, without being applied.
.EXAMPLE
    Add-ADObjectAce -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName Description -InheritedObjectTypeName User -InheritanceType Descendents -Server dc1.contoso.com
 
    Adds a new ACE to ACL on AD object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Description" and "InheritedObjectType" of Name "User" propagated to "Descendants" targeting Domain Controller "dc1.contoso.com".
 
    User is prompted for confirmation.
.EXAMPLE
    Add-ADObjectAce -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName Description -InheritedObjectTypeName User -InheritanceType Descendents -Server dc1.contoso.com -Credential $Credential -Force
 
    Adds a new ACE to ACL on AD object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Description" and "InheritedObjectType" of Name "User" propagated to "Descendants" targeting Domain Controller "dc1.contoso.com" with the specified credentials.
 
    As the -Force parameter is specified, the user is not prompted for confirmation.
.EXAMPLE
    Get-ADUser -Filter {department -like "Marketing"} | Add-ADObjectAce -IdentityReference "CONTOSO\Marketing Support Team" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName "Private-Information" -InheritanceType All -WhatIf
 
    Retrieves all users with a department value of "Marketing" and adds a new ACE to ACL for the Identity Reference "CONTOSO\Marketing Support Team" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Private-Information" and "InheritanceType" of "All".
 
    As the -WhatIf parameter is specified the format of the new ACE is displayed, without being applied.
.INPUTS
   The identity parameter of the CmdLet accepts either a distinguishedName or ObjectGUID or AD Objects. AD Objects which are passed by reference must include either a distinguishedName or ObjectGUID property.
.OUTPUTS
   None unless -WhatIf parameter is used in which case a cActiveDirectorySecurity.ACE object is returned.
#>

function Add-ADObjectAce
{
    [CmdletBinding(SupportsShouldProcess = $true, PositionalBinding = $false, ConfirmImpact = 'High')]
    [OutputType([Object])]
    Param(
        # The Identity of the Active Directory Object in either distinguishedName or GUID format or by reference.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)]
        [object]$Identity,

        # The target Active Directory Server / Domain Controller.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]$Server,

        # The IdentityReference that will be defined on the ACE (DOMAIN\USERNAME).
        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [string]
        $IdentityReference,

        # The Active Directory Rights that will be defined on the ACE.
        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateSet( "CreateChild", "DeleteChild", "ListChildren", "Self", "ReadProperty", "WriteProperty", "DeleteTree", "ListObject", "ExtendedRight", "Delete", "ReadControl", "GenericExecute", "GenericWrite", "GenericRead", "WriteDacl", "WriteOwner", "GenericAll", "Synchronize", "AccessSystemSecurity")]
        [string[]]
        $ActiveDirectoryRights,

        # The Object Type Name that will be defined on the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $ObjectTypeName,

        # The Inherited Object Type Name that will be defined on the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $InheritedObjectTypeName,

        # The Access Control Type (Allow / Deny) that will be defined on the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [ValidateSet("Allow", "Deny")]
        [string]
        $AccessControlType = "Allow",

        # The Inheritance Type that will be defined on the ACE.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [ValidateSet("All", "Children", "Descendents", "None", "SelfAndChildren")]
        [string]
        $InheritanceType,

        # Credential to use.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [System.Management.Automation.PSCredential]
        $Credential,

        # Ignore any should process warnings and apply the new Ace.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $Force
    )
    Begin
    {
        Push-Location # keep a record of the current location

        $PSDriveParams = @{"ErrorAction" = "Stop"}

        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $PSDriveParams.Add("Credential", $Credential)
        }
        if ($PSBoundParameters.ContainsKey('Server'))
        {
            $PSDriveParams.Add("Server", $Server)
        }
        else
        {
            $Server = (Get-AdDomain).NetBIOSName
        }

        if (($PSBoundParameters.ContainsKey('Server')) -or ($PSBoundParameters.ContainsKey('Credential')))
        {
            try
            {
                # create a new PSDrive to the target AD Domain Controller / or using different credentials
                $PSDriveName = $Server.Split(".")[0]
                New-PSDrive -Name $PSDriveName -PSProvider ActiveDirectory -root "//RootDSE/" @PSDriveParams -WhatIf:$false | Out-Null
                Set-Location ($PSDriveName + ":") -ErrorAction Stop
                Write-Verbose "Connecting to specified server / domain controller."
                Write-Verbose -Message "Server: $Server"
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.ADPSDriveError -f $Server)
                Pop-Location
                throw $_
            }
        }
        else
        {
            # connect to the default Active Directory PSDrive
            try
            {
                Set-Location -Path AD: -ErrorAction Stop
                Write-Verbose "Connecting to default domain."
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.ADDefaultPSDriveError)
                Pop-Location
                throw $_
            }
        }
    }
    Process
    {
        # determine whether a string or reference object has been passed
        if ($Identity -is "String")
        {
            Write-Verbose " Resolving String Identity Reference $Identity."
            try
            {
                $Identity = Get-ADObject -Identity $Identity -ErrorAction Stop
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.IdentityNotFound -f $Identity)
                Write-Error $_
                Return $null
            }
        }
        else
        {
            if (($null -eq $Identity.DistinguishedName) -and ($null -eq $Identity.ObjectGUID))
            {
                Write-Warning -Message ($LocalizedData.IdentityTypeInvalid -f $Identity)
                Write-Error $_
                Return $null
            }
            Write-Verbose " Reference Identity $($Identity.ToString())."
        }

        # list of parameters that will passed to the constructor of new ace
        $NewAceParams = @()

        # convert the Identity Reference into a System.Security.Principal.SecurityIdentifier
        try
        {
            $sid = (Resolve-NameToObjectSid -IdentityReference $IdentityReference)
            $NewAceParams += $sid
        }
        catch
        {
            Write-Error $_
            Return $null
        }

        # the $sid of the $IdentityReference is mandatory in order to generate the new ACE
        if ($null -eq $sid)
        {
            Write-Warning -Message ($LocalizedData.IdentityReferenceInvalid -f $IdentityReference)
            Return $null
        }
        else
        {
            Write-Verbose " Identity Reference $($IdentityReference.distinguishedName) translated to Sid value ($Sid)."
        }

        # convert ActiveDirectoryRights into ActiveDirectoryRights Object for generating the Ace
        $ActiveDirectoryRightsObject = [System.DirectoryServices.ActiveDirectoryRights]$ActiveDirectoryRights
        $NewAceParams += $ActiveDirectoryRightsObject
        # convert the AccessControlType into AccessControlType for generating the Ace
        $AccessControlTypeObject = [System.Security.AccessControl.AccessControlType]$AccessControlType
        $NewAceParams += $AccessControlTypeObject

        if ($PSBoundParameters.ContainsKey('ObjectTypeName'))
        {
            Write-Verbose " ObjectTypeName $ObjectTypeName has been passed."
            try
            {
                # retrieve the object Type Guid
                $ObjectType = Get-ADObjectRightsGUID -Name $ObjectTypeName
            }
            catch
            {
                Write-Error $_
                Return $null
            }

            if ($null -eq $ObjectType)
            {
                Write-Warning -Message ($LocalizedData.ObjectTypeNameNotFound -f ($ObjectTypeName))
                Return $null
            }
            else
            {
                $ObjectTypeGUID = $ObjectType.Keys | Select-Object -First 1
                Write-Verbose " ObjectTypeName of $ObjectTypeName resolved to GUID $ObjectTypeGUID."
                $NewAceParams += $ObjectTypeGUID
            }
        }

        if ($PSBoundParameters.ContainsKey('InheritanceType'))
        {
            Write-Verbose " InheritanceType $InheritanceType has been passed."
            # convert the InheritanceType into ActiveDirectorySecurityInheritance Object for generating the Ace (if the parameter has been passed)
            $InheritanceTypeObject = [System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType
            $NewAceParams += $InheritanceTypeObject
        }

        if ($PSBoundParameters.ContainsKey('InheritedObjectTypeName'))
        {
            Write-Verbose " InheritedObjectTypeName $InheritedObjectTypeName has been passed."
            try
            {
                # retrieve the inherited object type Guid
                $InheritedObjectType = Get-ADObjectRightsGUID -Name $InheritedObjectTypeName
            }
            catch
            {
                Write-Error $_
                Return $null
            }

            if ($null -eq $InheritedObjectType)
            {
                Write-Warning -Message ($LocalizedData.InheritedObjectTypeNameNotFound -f ($InheritedObjectTypeName))
                Return $null
            }
            else
            {
                $InheritedObjectTypeGUID = $InheritedObjectType.Keys | Select-Object -First 1
                Write-Verbose " InheritedObjectTypeName of $InheritedObjectTypeName resolved to GUID $InheritedObjectTypeGUID."
                $NewAceParams += $InheritedObjectTypeGUID
            }
        }

        # create the new ACE
        try
        {
            Write-Verbose " Creating new ACE."
            $Ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($NewAceParams)
        }
        catch
        {
            Write-Warning -Message ($LocalizedData.CreateNewAceError -f $Identity.DistinguishedName)
            Write-Error $_
            Return $null
        }

        if ($Force -or $pscmdlet.ShouldProcess($Identity.DistinguishedName, "Add new Access Control Entry."))
        {
            # retrieve existing ACL permissions from the Active Directory Object
            Write-Verbose " Retrieving existing ACL."
            try
            {
                $Permissions = Get-Acl -Path $Identity.DistinguishedName
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.GetAclError -f ($Identity.DistinguishedName))
                Write-Error $_
                Return $null
            }

            # add the new Ace to the Acl
            try
            {
                Write-Verbose " Adding new ACE to existing ACL."
                $Permissions.AddAccessRule($Ace)
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.AddAceError -f ($Identity.DistinguishedName))
                Write-Error $_
                Return $null
            }

            # finally re-apply the updated Acl to the object
            try
            {
                Write-Verbose " Applying the updated ACL to the AD object."
                Set-Acl -AclObject $Permissions -Path $Identity -ErrorAction Stop
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.SetAclError -f ($Identity.DistinguishedName))
                Write-Error $_
                Return $null
            }
        }
        else
        {
            # otherwise simply output the generated $Ace for review
            $AceToOutput = New-Object -TypeName "PSObject" -Property @{
                distinguishedName       = $Identity.DistinguishedName
                ActiveDirectoryRights   = ($Ace.ActiveDirectoryRights.ToString() -Split ", ")
                InheritanceType         = $Ace.InheritanceType
                ObjectType              = $Ace.ObjectType
                ObjectTypeName          = (Get-ADObjectRightsGUID -GUID $Ace.ObjectType | Select-Object -ExpandProperty Values | Select-Object -First 1)
                InheritedObjectType     = $Ace.InheritedObjectType
                InheritedObjectTypeName = (Get-ADObjectRightsGUID -GUID $Ace.InheritedObjectType | Select-Object -ExpandProperty Values | Select-Object -First 1)
                ObjectFlags             = $Ace.ObjectFlags
                AccessControlType       = $Ace.AccessControlType
                IdentityReference       = $IdentityReference
                IsInherited             = $Ace.IsInherited
                InheritanceFlags        = $Ace.InheritanceFlags
                PropagationFlags        = $Ace.PropagationFlags
            }
            $AceToOutput.PSObject.TypeNames.Insert(0, "cActiveDirectorySecurity.ACE")
            Write-Output $AceToOutput
        }
    }
    End
    {
        Pop-Location # return to original location
        if ($null -ne $PSDriveName)
        {
            Remove-PSDrive -Name $PSDriveName -Force -WhatIf:$false # remove AD PSDrive if one was created
        }
    }
}

<#
.Synopsis
    Removes an Access Control Entry from an Access Control List defined on an Active Directory Object.
.DESCRIPTION
    Removes an Access Control Entry (ACE) from an Access Control List (ACL) defined an Active Directory Object.
 
.EXAMPLE
    Remove-ADObjectAce -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName Description -InheritedObjectTypeName User -InheritanceType Descendents -WhatIf
 
    Removes the ACE from the ACL on AD object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Description" and "InheritedObjectType" of Name "User" propagated to "Descendants".
 
    As the -WhatIf parameter is specified the details of the existing ACE are displayed, without being removed.
.EXAMPLE
    Remove-ADObjectAce -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName Description -InheritedObjectTypeName User -InheritanceType Descendents -Server dc1.contoso.com
 
    Remove the ACE from the ACL on AD object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Description" and "InheritedObjectType" of Name "User" propagated to "Descendants" targeting Domain Controller "dc1.contoso.com".
 
    User is prompted for confirmation.
.EXAMPLE
    Remove-ADObjectAce -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" -ActiveDirectoryRights ReadProperty,WriteProperty -ObjectTypeName Description -InheritedObjectTypeName User -InheritanceType Descendents -Server dc1.contoso.com -Credential $Credential -Force
 
    Removes the ACE from the ACL on AD object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management" with Active Directory Rights "ReadProperty", "WriteProperty" for the Object Type with Name "Description" and "InheritedObjectType" of Name "User" propagated to "Descendants" targeting Domain Controller "dc1.contoso.com" with the specified credentials.
 
    As the -Force parameter is specified, the user is not prompted for confirmation.
.EXAMPLE
    Get-ADObjectAcl -Identity "OU=Users,OU=GB,DC=contoso,DC=com" -IdentityReference "CONTOSO\GB User Management" | Remove-ADObjectAce -Force
 
    Removes all matching ACEs from ACL on object "OU=Users,OU=GB,DC=contoso,DC=com" for the Identity Reference "CONTOSO\GB User Management".
 
    As the -Force parameter is specified, the user is not prompted for confirmation.
.INPUTS
   The identity parameter of the CmdLet accepts either a distinguishedName or ObjectGUID or AD Objects. AD Objects which are passed by reference must include either a distinguishedName or ObjectGUID property.
.OUTPUTS
   None unless -WhatIf parameter is used in which case a cActiveDirectorySecurity.ACE object is returned.
#>

function Remove-ADObjectAce
{
    [CmdletBinding(SupportsShouldProcess = $true, PositionalBinding = $false, ConfirmImpact = 'High')]
    [OutputType([Object])]
    Param(
        # The Identity of the Active Directory Object in either distinguishedName or GUID format or by reference.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)]
        [object]
        $Identity,

        # The target Active Directory Server / Domain Controller.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [string]
        $Server,

        # The IdentityReference defined on the ACE (DOMAIN\USERNAME) to be removed.
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $IdentityReference,

        # The Active Directory Rights defined on the ACE to be removed.
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet( "CreateChild", "DeleteChild", "ListChildren", "Self", "ReadProperty", "WriteProperty", "DeleteTree", "ListObject", "ExtendedRight", "Delete", "ReadControl", "GenericExecute", "GenericWrite", "GenericRead", "WriteDacl", "WriteOwner", "GenericAll", "Synchronize", "AccessSystemSecurity")]
        [string[]]
        $ActiveDirectoryRights,

        # The Object Type Name defined on the ACE to be removed.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]
        $ObjectTypeName,

        # The Inherited Object Type Name defined on the ACE to be removed.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]
        $InheritedObjectTypeName,

        # The Access Control Type (Allow / Deny) defined on the ACE to be removed.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet("Allow", "Deny")]
        [string]
        $AccessControlType = "Allow",

        # The Inheritance Type defined on the ACE to be removed.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet("All", "Children", "Descendents", "None", "SelfAndChildren")]
        [string]
        $InheritanceType,

        # Credential to use.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [System.Management.Automation.PSCredential]
        $Credential,

        # Ignore any should process warnings and remove the matching Ace.
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $Force
    )
    Begin
    {
        # make a record of the initial location stack
        $locationStack = (Get-Location -StackName "cActiveDirectorySecurity" -ErrorAction SilentlyContinue)
        if ($locationStack.count -eq 0)
        {
            Push-Location -StackName 'CActiveDirectorySecurity' -ErrorAction SilentlyContinue # keep a record of the current location
        }
        else
        {
            Set-Location -Path ($locationStack.Path[0]) -ErrorAction SilentlyContinue
            Push-Location -StackName 'CActiveDirectorySecurity' -Path ($locationStack.Path[0]) -ErrorAction SilentlyContinue
        }

        $PSDriveParams = @{"ErrorAction" = "Stop"}

        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $PSDriveParams.Add("Credential", $Credential)
        }
        if ($PSBoundParameters.ContainsKey('Server'))
        {
            $PSDriveParams.Add("Server", $Server)
        }
        else
        {
            $Server = (Get-AdDomain).NetBIOSName
        }

        if (($PSBoundParameters.ContainsKey('Server')) -or ($PSBoundParameters.ContainsKey('Credential')))
        {
            try
            {
                # create a new PSDrive to the target AD Domain Controller / or using different credentials
                $PSDriveName = $Server.Split(".")[0]
                New-PSDrive -Name $PSDriveName -PSProvider ActiveDirectory -root "//RootDSE/" @PSDriveParams -WhatIf:$false | Out-Null
                Set-Location ($PSDriveName + ":") -ErrorAction Stop
                Write-Verbose "Connecting to specified server / domain controller."
                Write-Verbose -Message "Server: $Server"
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.ADPSDriveError -f $Server)
                Pop-Location
                throw $_
            }
        }
        else
        {
            # connect to the default Active Directory PSDrive
            try
            {
                Set-Location -Path AD: -ErrorAction Stop
                Write-Verbose "Connecting to default domain."
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.ADDefaultPSDriveError)
                Pop-Location
                throw $_
            }
        }

        # make a record of the current location to private stack
        Push-Location -StackName 'Remote-ADObjectAce' -ErrorAction SilentlyContinue # keep a record of the current location
    }
    Process
    {
        $LocalLocationStack = (Get-Location -StackName 'Remote-ADObjectAce' -ErrorAction SilentlyContinue)
        if ((Get-Location).Path -ne ($LocalLocationStack.Path))
        {
            Pop-Location -StackName 'Remote-ADObjectAce' -ErrorAction SilentlyContinue
        }

        # where the Get-ADObjectAcl function is used to feed this function the reference object passed will be of type cActiveDirectorySecurity.ACE
        # therefore ensure it gets converted to an appropriate AD object
        if ("Get-ADObjectAcl" -eq (Get-PSCallStack)[1].Command)
        {
            $Identity = $Identity.DistinguishedName.ToString()
        }

        # determine whether a string or reference object has been passed
        if ($Identity -is "String")
        {
            Write-Verbose " Resolving String Identity Reference $Identity."
            try
            {
                $Identity = Get-ADObject -Identity $Identity -ErrorAction Stop
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.IdentityNotFound -f $Identity)
                Write-Error $_
                Return $null
            }
        }
        else
        {
            if (($null -eq $Identity.DistinguishedName) -and ($null -eq $Identity.ObjectGUID))
            {
                Write-Warning -Message ($LocalizedData.IdentityTypeInvalid -f $Identity)
                Write-Error $_
                Return $null
            }
            Write-Verbose " Reference Identity $($Identity.ToString())."
        }

        # build param list to pass to Get-ADObjectAcl
        $GetADObjectAclParams = $PSBoundParameters
        if ($GetADObjectAclParams.ContainsKey('WhatIf')) { $GetADObjectAclParams.Remove("WhatIf") | Out-Null }
        if ($GetADObjectAclParams.ContainsKey('Identity')) { $GetADObjectAclParams.Remove("Identity") | Out-Null }
        if ($GetADObjectAclParams.ContainsKey('Force')) { $GetADObjectAclParams.Remove("Force") | Out-Null }
        if (-Not ($GetADObjectAclParams.ContainsKey('Identity'))) { $GetADObjectAclParams.Add("Identity", $Identity) | Out-Null }
        if (-Not ($GetADObjectAclParams.ContainsKey('IsInherited'))) { $GetADObjectAclParams.Add("IsInherited", $False) | Out-Null }

        # confirm that the ACE to remove is actually present - using Get-ADObjectAcl
        try
        {
            $Ace = Get-ADObjectAcl @GetADObjectAclParams
        }
        catch
        {
            Write-Error $_
            Return $null
        }

        # no ACE found matching the specified filter
        if ($null -eq $Ace)
        {
            Write-Warning -Message ($LocalizedData.AceNotFound -F $Identity)
            Return $null
        }

        # count the number of ACE objects returned (should only be one present that matches the filter parameters passed!)
        if ($null -ne $Ace.Count -and $Ace.Count -gt 1)
        {
            Write-Warning -Message ($LocalizedData.MultipleAceValuesFound -F $Identity, ($Ace.Count))
            Return $null
        }

        # retrieve existing ACL permissions from the Active Directory Object
        Write-Verbose " Retrieving existing ACL."
        try
        {
            $Permissions = Get-Acl -Path $Identity -ErrorAction Stop
        }
        catch
        {
            Write-Warning -Message ($LocalizedData.GetAclError -f ($Identity))
            Write-Error $_
            Return $null
        }

        # find a matching ACE within the ACL
        $MatchedAce = $null
        forEach ($Permission in $Permissions.Access)
        {
            if ($Permission.ActiveDirectoryRights -ne $Ace.ActiveDirectoryRights)
            {
                Continue
            }
            If ($Permission.ObjectType -ne $Ace.ObjectType)
            {
                Continue
            }
            If ($Permission.InheritedObjectType -ne $Ace.InheritedObjectType)
            {
                Continue
            }
            If ($Permission.AccessControlType -ne $Ace.AccessControlType)
            {
                Continue
            }
            if ($Permission.PropagationFlags -ne $Ace.PropagationFlags)
            {
                Continue
            }
            if ($Permission.IsInherited -ne $Ace.IsInherited)
            {
                Continue
            }
            If ($Permission.ObjectFlags -ne $Ace.ObjectFlags)
            {
                Continue
            }
            If ($Permission.InheritanceFlags -ne $Ace.InheritanceFlags)
            {
                Continue
            }
            if ($Permission.IdentityReference -is [System.Security.Principal.SecurityIdentifier])
            {
                $IdentityReferenceLabel = (Resolve-ObjectSidToName -Sid $Permission.IdentityReference)

                if ($IdentityReferenceLabel -ne $Ace.IdentityReference)
                {
                    Continue
                }
            }
            elseIf ($Permission.IdentityReference -ne $Ace.IdentityReference)
            {
                Continue
            }

            # if we reach this point we have a match
            $MatchedAce = $Permission
            Break
        }

        if ($null -eq $MatchedAce)
        {
            Write-Warning -Message ($LocalizedData.MatchingAceNotFound -f ($Identity.DistinguishedName))
            Return $null
        }

        if ($Force -or $pscmdlet.ShouldProcess($Identity.DistinguishedName, "Removing matching Access Control Entry from the ACL."))
        {
            # remove the new Ace from the Acl
            try
            {
                Write-Verbose " Removing matching ACE from the existing ACL."
                $Permissions.RemoveAccessRuleSpecific($MatchedAce)
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.RemoveAceError -f ($Identity))
                Write-Error $_
                Return $null
            }

            # finally re-apply the updated Acl to the object
            try
            {
                Write-Verbose " Applying the updated ACL to the AD object."
                Set-Acl -AclObject $Permissions -Path $Identity -ErrorAction Stop
            }
            catch
            {
                Write-Warning -Message ($LocalizedData.ClearAclError -f $Identity)
                Write-Error $_
                Return $null
            }
        }
        else
        {
            # otherwise simply output the generated $Ace for review
            $AceToOutput = New-Object -TypeName "PSObject" -Property @{
                DistinguishedName       = $Identity.DistinguishedName
                ActiveDirectoryRights   = ($MatchedAce.ActiveDirectoryRights.ToString() -Split ", ")
                InheritanceType         = $MatchedAce.InheritanceType
                ObjectType              = $MatchedAce.ObjectType
                ObjectTypeName          = (Get-ADObjectRightsGUID -GUID $MatchedAce.ObjectType | Select-Object -ExpandProperty Values | Select-Object -First 1)
                InheritedObjectType     = $MatchedAce.InheritedObjectType
                InheritedObjectTypeName = (Get-ADObjectRightsGUID -GUID $MatchedAce.InheritedObjectType | Select-Object -ExpandProperty Values | Select-Object -First 1)
                ObjectFlags             = $MatchedAce.ObjectFlags
                AccessControlType       = $MatchedAce.AccessControlType
                IdentityReference       = $IdentityReference
                IsInherited             = $MatchedAce.IsInherited
                InheritanceFlags        = $MatchedAce.InheritanceFlags
                PropagationFlags        = $MatchedAce.PropagationFlags
            }
            $AceToOutput.PSObject.TypeNames.Insert(0, "cActiveDirectorySecurity.ACE")
            Write-Output $AceToOutput
        }
    }
    End
    {
        Pop-Location -StackName 'cActiveDirectorySecurity' # return to original location
        if ($null -ne $PSDriveName)
        {
            Remove-PSDrive -Name $PSDriveName -Force -WhatIf:$false # remove AD PSDrive if one was created
        }
    }
}

<#
.Synopsis
   Get list of AD Object GUIDs and Access Right GUIDs from Active Directory.
.DESCRIPTION
   Get a complete list of AD Object GUIDs and Access Rights GUIDs from either the default or specified Active Directory Forest / Domain.
.EXAMPLE
   Get-ADObjectRightsGUID
 
   Gets a complete list of AD Object GUIDs and Access Rights GUIDs from the default Active Directory Forest / Domain.
.EXAMPLE
   Get-ADObjectRightsGUID -Name 'Computer'
 
   Gets the Schema Object with the Name 'Computer'
.EXAMPLE
   Get-ADObjectRightsGUID -Name 'Personal-Information'
 
   Gets the Extended Right / controlAccessRight with the Name 'Personal-Information'
.INPUTS
   The (optional) hostname of a target Server / Domain Controller.
.OUTPUTS
   Outputs a hashtable of AD Object GUIDs and their associated names.
 
#>

function Get-ADObjectRightsGUID
{
    [CmdletBinding(DefaultParameterSetName = 'None',
        SupportsShouldProcess = $false,
        PositionalBinding = $false)]
    [OutputType([System.Collections.Hashtable])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
    Param
    (
        # the Name of the schema object or control access right to retrieve
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, ParameterSetName = 'Name')]
        [string]$Name,

        # the GUID of the schema object or control access right to retrieve
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, ParameterSetName = 'GUID')]
        [object]$GUID
    )
    Begin
    {
    }
    Process
    {
        # thanks to https://blogs.technet.microsoft.com/ashleymcglone/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download/ for much of this
        # retrieve details of schema GUIDs and Access Rights from the schema and the configuration naming contexts
        # note that these queries will be targeted to the correct domain as we have already switched to the correct AD PSdrive
        try
        {
            $rootDSE = Get-ADRootDSE
            $schemaNamingContext = ($rootDSE.schemaNamingContext)
            Write-Verbose -Message "schemaNamingContext: $schemaNamingContext"
            $configurationNamingContext = ($rootDSE.configurationNamingContext)
            Write-Verbose -Message "configurationNamingContext: $configurationNamingContext"
        }
        catch
        {
            Write-Warning -Message ($LocalizedData.RootDSEError -f $DomainName)
            Pop-Location
            throw $_
        }

        $schemaAndAccessRights = @{}

        switch ($PSCmdlet.ParameterSetName)
        {
            'Name'
            {
                if ("All" -eq $Name)
                {
                    $schemaAndAccessRights.Add([System.GUID]'00000000-0000-0000-0000-000000000000', 'All')
                }
                else
                {
                    try
                    {
                        $schemaObjects = Get-ADObject -SearchBase $schemaNamingContext -LDAPFilter "(&(schemaIDGUID=*)(Name=$Name))" -Properties name, schemaIDGUID
                    }
                    catch
                    {
                        # ignore errors
                    }

                    try
                    {
                        $accessRights = Get-ADObject -SearchBase "CN=Extended-Rights,$configurationNamingContext" -LDAPFilter "(&(objectClass=controlAccessRight)(Name=$Name))" -Properties name, rightsGUID
                    }
                    catch
                    {
                        # ignore errors
                    }

                    if (($null -eq $schemaObjects) -and ($null -eq $accessRights))
                    {
                        Return $null
                    }
                }
            }
            'GUID'
            {
                # determine whether a string or GUID value has been passed
                if (($GUID -is [GUID]) -or ($GUID -is [String]))
                {
                    if ($GUID -is [String])
                    {
                        $GUID = [GUID]$GUID
                    }
                    if ([GUID]"00000000-0000-0000-0000-000000000000" -eq $GUID)
                    {
                        $schemaAndAccessRights.Add([System.GUID]'00000000-0000-0000-0000-000000000000', 'All')
                    }
                    else
                    {
                        try
                        {
                            $schemaObjects = Get-ADObject -SearchBase $schemaNamingContext -Filter {schemaIDGUID -eq $GUID} -Properties name, schemaIDGUID
                        }
                        catch
                        {
                            # ignore errors
                        }

                        try
                        {
                            $accessRights = Get-ADObject -SearchBase "CN=Extended-Rights,$configurationNamingContext" -Filter {rightsGUID -eq $GUID} -Properties name, rightsGUID
                        }
                        catch
                        {
                            # ignore errors
                        }

                        if (($null -eq $schemaObjects) -and ($null -eq $accessRights))
                        {
                            Return $null
                        }
                    }
                }
                else
                {
                    Return $null
                }
            }
            Default
            {
                $schemaAndAccessRights.Add([System.GUID]'00000000-0000-0000-0000-000000000000', 'All')
                $schemaObjects = Get-ADObject -SearchBase $schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID
                $accessRights = Get-ADObject -SearchBase "CN=Extended-Rights,$configurationNamingContext" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID
            }
        }

        ForEach ($schemaObject in $schemaObjects)
        {
            try
            {
                $schemaAndAccessRights.Add([System.GUID]$schemaObject.schemaIDGUID, $schemaObject.name)
            }
            catch
            {
                # ignore errors
            }
        }
        ForEach ($accessRight in $accessRights)
        {
            try
            {
                $schemaAndAccessRights.Add([System.GUID]$accessRight.rightsGUID, $accessRight.name)
            }
            catch
            {
                # ignore errors
            }
        }

        Return $schemaAndAccessRights
    }
    End
    {
    }
}


<#
.Synopsis
   Resolves an object SID to an associated Windows Accounts or Security Principal.
.DESCRIPTION
   Resolves an object SID to an associated Windows Account or Security Principal using an Active Directory Web Services query. As the function was created as a utility function, it is assumged that
   (when resolving SIDs from the non-default domain) that an AD PSDrive has already been established with the external Domain and have changed directory to that PSDrive.
.EXAMPLE
   $stringsid= "S-1-5-32-554"
   $sid = new-object security.principal.securityidentifier($stringsid)
 
   Resolve-ObjectSidToName -Sid $sid
 
   Returns the name 'BUILTIN\Pre-Windows 2000 Compatible Access' for the SID with string value 'S-1-5-32-554'.
.EXAMPLE
   $stringsid= "S-1-5-21-2295585024-2604479722-1786026388-512"
   $sid = new-object security.principal.securityidentifier($stringsid)
 
   Resolve-ObjectSidToName -Sid $sid
 
   Returns the name 'CONTOSO\Domain Admins' for the SID with string value 'S-1-5-21-2295585024-2604479722-1786026388-512'.
.INPUTS
   A [System.Security.Principal.SecurityIdentifier] object.
.OUTPUTS
   Output the name associated Windows Accounts or Security Principal, otherwise returns the original (unresolved) Sid.
#>

function Resolve-ObjectSidToName
{
    [CmdletBinding(SupportsShouldProcess = $false,
        PositionalBinding = $true)]
    [OutputType([System.Security.Principal.IdentityReference])]
    Param
    (
        # The security identifier to translate
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 0)]
        [System.Security.Principal.IdentityReference]
        $Sid
    )
    Begin
    {
    }
    Process
    {
        Write-Verbose "Resolving sid $Sid..."

        # names are resolved using Get-AdObject. Ideally would prefer to use something like Translate([System.Security.Principal.NTAccount]) but doesn't work very well
        # with anything but the current domain or with BUILTIN security principals and don't really want to maintain tables of well-known-sids

        try
        {
            # build param list to splat to Get-ADObject
            $GetADObjectParams = @{"filter" = ("objectSid -eq " + '"' + $Sid + '"' + ""); "properties" = "sAMAccountName"; "ErrorAction" = "Stop"}
            $ADObject = Get-AdObject @GetADObjectParams
        }
        catch
        {
            Write-Warning -Message ($LocalizedData.GetADObjectSidError -f ($sid.Value))
            throw $_
        }

        if ($null -ne $ADObject)
        {
            # if object is located in the Builtin container then return BUILTIN\sAMAccountName
            if ($ADObject.DistinguishedName -match ",(?<Builtin>CN=Builtin,DC=.*$)")
            {
                Write-Verbose ("BUILTIN\" + $ADObject.sAMAccountName)
                Return ("BUILTIN\" + $ADObject.sAMAccountName)
            }
            # otherwise return NetBIOSName\sAMAccountName
            else
            {
                try
                {
                    $DomainNetBIOSName = (Get-ADDomain).NetBIOSName
                }
                catch
                {
                    Write-Warning -Message ($LocalizedData.NetBIOSDomainError)
                }

                if ($null -ne $DomainNetBIOSName)
                {
                    Write-Verbose ("$DomainNetBIOSName\" + $ADObject.sAMAccountName)
                    Return ("$DomainNetBIOSName\" + $ADObject.sAMAccountName)
                }
                else
                {
                    Write-Verbose ($ADObject.sAMAccountName)
                    Return ($ADObject.sAMAccountName)
                }
            }
        }
        else
        {
            # if object cannot be found then return the original Sid value
            Return $sid
        }
    }
    End
    {
    }
}

<#
.Synopsis
   Resolves an AD Account Name in the form DOMAIN\ACCOUNT to an object SID.
.DESCRIPTION
   A utility function to resolve an AD Account Name in the form DOMAIN\ACCOUNT to an object SID.
.EXAMPLE
   Resolve-NameToObjectSid -IdentityReference 'BUILTIN\Pre-Windows 2000 Compatible Access'
 
   Returns the SID for the 'BUILTIN\Pre-Windows 2000 Compatible Access' group as a security identifier ("S-1-5-32-554" in String format).
.INPUTS
   An IdentityReference in the form DOMAIN\ACOUNT format.
.OUTPUTS
   Outputs the object SID associated with the account name.
#>

function Resolve-NameToObjectSid
{
    [CmdletBinding(SupportsShouldProcess = $false,
        PositionalBinding = $true)]
    [OutputType([System.Security.Principal.IdentityReference])]
    Param
    (
        # The IdentityReference to Translate
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 0)]
        [String]
        $IdentityReference
    )
    Begin
    {
    }
    Process
    {
        Write-Verbose "Resolving name $IdentityReference..."

        # split the name into Domain and Account Name components
        if ($IdentityReference -match "(?<domain>.*)\x5c(?<account>.*)")
        {
            [string]$DomainName = $Matches.domain
            [string]$AccountName = $Matches.account
        }

        switch ($DomainName)
        {
            { (($null -eq $_) -or ("NT AUTHORITY" -eq $_ )) }
            {
                Write-Verbose " Translating name to Sid using Translate()."
                try
                {
                    $ntAccount = new-object System.Security.Principal.NTAccount($IdentityReference)
                    $objectSid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
                }
                catch
                {
                    Write-Warning -Message ($LocalizedData.TranslateNameError -f $IdentityReference)
                    throw $_
                }

                Return $objectSid
            }
            Default
            {
                # most sids are resolved using Get-AdObject. Ideally would prefer to use Translate([System.Security.Principal.SecurityIdentifier]) but doesn't work very well
                # with anything but the current domain or with certain BUILTIN security principals and don't really want to maintain tables of well-known-sids
                Write-Verbose " Resolving name to objectSid using Get-ADObject."

                try
                {
                    $DomainNetBIOSName = (Get-ADDomain).NetBIOSName
                }
                catch
                {
                    Write-Warning -Message ($LocalizedData.NetBIOSDomainError)
                    throw $_
                }

                # build param list to splat to Get-ADObject
                $GetADObjectParams = @{"filter" = ("sAMAccountName -eq " + '"' + $AccountName + '"' + ""); "properties" = "objectSid"; "ErrorAction" = "Stop"}
                if (($DomainName -ne $DomainNetBIOSName) -and ($DomainName -ne "BUILTIN"))
                {
                    $GetADObjectParams.Add("Server", $DomainName)
                }

                try
                {
                    $ADObject = Get-AdObject @GetADObjectParams
                }
                catch
                {
                    Write-Warning -Message ($LocalizedData.GetADObjectNameError -f $IdentityReference, $AccountName)
                    throw $_
                }

                if ($null -ne $ADObject)
                {
                    # return the objectSID
                    Return ($ADObject.objectSID)
                }
                else
                {
                    # else return $null
                    Return $null
                }
            }
        }
    }
    End
    {
    }
}