Skip to content

Instantly share code, notes, and snippets.

@XPlantefeve
Last active January 24, 2023 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save XPlantefeve/5b18c2c53d0c2a96cc53b351557b90b7 to your computer and use it in GitHub Desktop.
Save XPlantefeve/5b18c2c53d0c2a96cc53b351557b90b7 to your computer and use it in GitHub Desktop.
getting Active Directory info through ADSI

Mocking Active Directory module cmdlets with ADSI, a learning project

This is a re-implementation of certain ActiveDirectory module cmdlets using only the ADSI provider. It's not meant to be a replacement, nor is it even planned to be finished: it's just a learning tool: it gives me a clearer idea of what's under AD or .Net's hoods.

I'm trying to follow some Powershell good practices, but I sometimes get caried away. Besides, I know about the "don't use aliases in a script" rule, but I allow myself to use the most obvious ones: ?,%,select,sort...

General information

[adsi] is a type shortcut to [System.DirectoryServices.DirectoryEntry]

Hence [adsi]'LDAP://\<DistinguishedName\>' is equal to the following: New-Object -New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList 'LDAP://\<DistinguishedName\>'

See Get-AdsiDirectoryEntry for info on how to specify credential & target server

Properties

Get-AD* cmdlets objects have three types of properties:

  • Default properties
  • Extended properties
  • ldap attributes properties

Default & extended properties are specific to AD cmdlets and are wrappers for ldap attributes and flags. Default properties are always displayed, extended and ldap attributes properties need to be specifically asked for through the -properties parameter. Ldap attributes properties can never be $null (if the attributes does not exist or is empty, the property is not added to the object).

For info about default and extended properties, see https://social.technet.microsoft.com/wiki/contents/articles/12031.active-directory-powershell-ad-module-properties.aspx

RootDSE

The RootDSE contains information about the AD/LDAP server.

For more information about the Active Directory RootDSE, see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse

Limits

Active Directory cmdlets are weird animals whose behaviours cannot be completely reproduced in a script form:

  • Properties are really methods under the hood
  • Properties pretend to exist, but are really only created when accessed

Corollary of the last one: properties CAN NOT be tested for existence: they always exist, and are populated, or not, when accessed:

$Thing = Get-ADSomething $Thing.ObviouslyNonExistentProperty won't raise an error.

# Quick and very dirty PS filter to LDAP filters translation
function Format-LdapEscaped ($string,[switch]$wildcard) {
Switch -Regex ($string.Trim()) {
'^"(?<str>.*)"$' { $string = $Matches.str -replace '`"','"' }
"^'(?<str>.*)'$" { $string = $Matches.str -replace "''","'" }
}
$string = $string -replace '\\5c','\'
$string = $string -replace '\\00','NUL'
$string = $string -replace '\(','\28'
$string = $string -replace '\)','\29'
$string = $string -replace '/','\2f'
if ( -not ($PSBoundParameters.ContainsKey('wildcard') -and $wildcard) ) { $string = $string -replace '\*','\2a' }
$string
}
$operators = @{
eq = '({0}={1})',$false
like = '({0}={1})',$true
ne = '(!({0}={1}))',$false
notlike = '(!({0}={1}))',$true
le = '({0}<={1})',$false
lt = '(!({0}>={1}))',$false
ge = '({0}>={1})',$false
gt = '(!({0}<={1}))',$false
approx = '({0}~={1})',$true
}
function ConvertTo-LdapTest ($filter) {
Write-Verbose -Message ('Processing {0}' -f $filter)
if ( $filter -match ' -or ' ) {
$res = $filter -split '\s+-or\s+' | % { ConvertTo-LdapTest -filter $_ }
'(|{0})' -f ($res -join '')
} elseif ( $filter -match ' -and ' ) {
$res = $filter -split '\s+-and\s+' | % { ConvertTo-LdapTest -filter $_ }
'(&{0})' -f ($res -join '')
} elseif ( $filter -match '\s*-not\s+(?<yop>.*)' ) {
$res = ConvertTo-LdapTest -filter $Matches.yop
'(!{0})' -f $res
} else {
switch -Regex ($filter) {
('(?<attr>.*)\s+-(?<op>{0})\s+(?<value>.*)' -f ($operators.Keys -join '|')) {
$operators[$Matches.op][0] -f $Matches.attr,(Format-LdapEscaped -string $Matches.value -wildcard:$operators[$Matches.op][1])
}
default { '({0})' -f $filter }
}
}
}
function ConvertTo-LdapFilter($filter,$tokens) {
if (-not $PSBoundParameters.ContainsKey('tokens')) {
$ast = [System.Management.Automation.Language.Parser]::ParseInput($filter,[ref]$null,[ref]$null)
$tokens = $ast.FindAll({$args[0] -is [System.Management.Automation.Language.ParenExpressionAst]},$true )
}
$before = $filter
$i = 0
$sons = $tokens | ? { $_.Parent.ToString() -eq $filter.ToString().Trim() }
foreach ($son in $sons) {
Write-Verbose -Message ('son: {0}' -f $son.Pipeline.ToString())
$replacement = ConvertTo-LdapFilter -filter $son.Pipeline.ToString() -tokens $tokens
$filter = $filter -replace [regex]::Escape($son.Extent.ToString()),$replacement
}
$filter = ConvertTo-LdapTest -filter $filter
Write-Verbose -Message ('filter: {0} => {1}' -f $before,$filter)
$filter
}
function Get-AdsiDirectoryEntry {
<#
.SYNOPSIS
Gets a directory entry from a LDAP server
.DESCRIPTION
The Get-AdsiDirectoryEntry cmdlet leverages the DirectoryServices .Net
namespace to give an alternative to the [ADSI] constructor, allowing
automatic handling of the server name and the option to use a different
set of credentials.
.PARAMETER adsObject
Initializes an instance that binds to the specified native Active
Directory Domain Services object.
.PARAMETER ADsPath
The LDAP path of the entry to open, in the form 'LDAP://<DistinguishedName>'.
Any server information will be discarded if the -server parameter is used.
.PARAMETER Credential
The credential of the user that will authenticate to the server.
.PARAMETER AuthenticationType
The type of authentication used to connect to the LDAP server.
.PARAMETER Server
The server to connect to.
.EXAMPLE
Get-AdsiDirectoryEntry -ADsPath 'LDAP://CN=Jeff Smith,CN=users,DC=fabrikam,DC=com'
Retrives LDAP information about the user Jeff Smith. Functionally equivalent to
[ADSI]'LDAP://CN=Jeff Smith,CN=users,DC=fabrikam,DC=com'
.EXAMPLE
Get-AdsiDirectoryEntry -ADsPath 'LDAP://RootDSE' -Credential $Credential -Server DC01
Retrieves the RootDSE information of the DC01 server, using specific credentials.
.NOTES
[adsi] is a type accelerator for [System.DirectoryServices.DirectoryEntry]
.LINK
https://gist.github.com/XPlantefeve/5b18c2c53d0c2a96cc53b351557b90b7
https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.directoryentry
https://docs.microsoft.com/en-us/windows/win32/adsi/ldap-adspath
https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.authenticationtypes
.INPUTS
Object.
An ADS object that implements IADs.
.OUTPUTS
System.DirectoryServices.DirectoryEntry
#>
[CmdletBinding(DefaultParameterSetName='ADsPath')]
param(
[Parameter(ParameterSetName='adsObject',Mandatory,Position=0,ValueFromPipeline)]
[Object]$adsObject,
[Parameter(ParameterSetName='ADsPath',Mandatory,Position=0)]
[string]$ADsPath,
[Parameter(ParameterSetName='ADsPath',Position=1)]
[pscredential]$Credential,
[Parameter(ParameterSetName='ADsPath',Position=2)]
[DirectoryServices.AuthenticationTypes]$AuthenticationType,
[Parameter(ParameterSetName='ADsPath')]
[Alias('LdapPath')]
[string]$Server
)
if ($PSBoundParameters.ContainsKey('adsObject')) {
# This is the first of the DirectoryEntry constructors listed at
# https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.directoryentry.-ctor
# full disclosure: I don't know where to get those objects.
$directoryEntry = New-Object -TypeName adsi -ArgumentList $adsObject
} else {
if ($PSBoundParameters.ContainsKey('Server')) {
# An ADsPath can contain information about the server to connect to.
# We add the information to the given path (overwriting any server
# info that might have been there).
$ADsPath = $ADsPath -replace 'LDAP://(.*/)?',('LDAP://{0}/' -f $Server)
}
$directoryEntryArgs = @($ADsPath)
if ($PSBoundParameters.ContainsKey('Credential')) {
# if we're given a credential, we add the info to the constructor parameters.
$directoryEntryArgs += @(
$Credential.UserName
$Credential.GetNetworkCredential().Password
)
}
if ($PSBoundParameters.ContainsKey('AuthenticationType')) {
# if we're given an authentication type, we add the info to the constructor parameters.
$directoryEntryArgs += $AuthenticationType
}
Write-Verbose -Message ( '[Adsi]Get-DirectoryEntry: {0}' -f $ADsPath )
$directoryEntry = New-Object -TypeName adsi -ArgumentList $directoryEntryArgs
}
$directoryEntry.psobject.TypeNames.Insert(0,'Xpla.Adsi.DirectoryEntry')
$directoryEntry
}
function Get-AdsiObject {
<#
.SYNOPSIS
Gets one or more Active Directory objects.
.DESCRIPTION
The Get-AdsiObject cmdlet gets an Active Directory object or performs a
search to get multiple objects.
The Identity parameter specifies the Active Directory object to get. You
can identify the object to get by its distinguished name or GUID.
To search for and get more than one object, use the Filter or LDAPFilter
parameters. The Filter parameter uses the PowerShell Expression Language
to write query strings for Active Directory. PowerShell Expression Language
syntax provides rich type conversion support for value types received by
the Filter parameter. For more information about the Filter parameter syntax,
type Get-Help about_ActiveDirectory_Filter. If you have existing Lightweight
Directory Access Protocol (LDAP) query strings, you can use the LDAPFilter
parameter.
This cmdlet gets a default set of Active Directory object properties. To
get additional properties use the Properties parameter. For more information
about the how to determine the properties for computer objects, see the
Properties parameter description.
.PARAMETER Filter
Specifies a query string that retrieves Active Directory objects. This string
uses the PowerShell Expression Language syntax. The PowerShell Expression
Language syntax provides rich type-conversion support for value types received
by the Filter parameter. The syntax uses an in-order representation, which
means that the operator is placed between the operand and the value. For
more information about the Filter parameter, type Get-Help
about_ActiveDirectory_Filter
Important note: this implementation of the -Filter parameter is considered
experimental.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory
objects. You can use this parameter to run your existing LDAP queries.
.PARAMETER ResultPageSize
Specifies the number of objects to include in one page for an AD DS query.
The default is 256 objects per page.
.PARAMETER ResultSetSize
Specifies the maximum number of objects to return for an AD DS query. If you
want to receive all of the objects, set this parameter to $Null (null value).
You can use Ctrl+C to stop the query and return of objects.
The default is $Null.
.PARAMETER SearchBase
Specifies an Active Directory path to search.
When you run a cmdlet against an AD DS target, the default value of this
parameter is the default naming context of the target domain.
When you run a cmdlet against an AD LDS target, the default value is the
default naming context of the target AD LDS instance if one has been
specified by setting the msDS-defaultNamingContext property of the Active
Directory directory service agent object (nTDSDSA) for the AD LDS instance.
If no default naming context has been specified for the target AD LDS instance,
then this parameter has no default value.
.PARAMETER SearchScope
Specifies the scope of an Active Directory search. The acceptable values for
this parameter are:
* Base or 0
* OneLevel or 1
* Subtree or 2
A Base query searches only the current path or object. A OneLevel query
searches the immediate children of that path or object. A Subtree query searches
the current path or object and all children of that path or object.
.PARAMETER Identity
Specifies an Active Directory object by providing one of the following property
values. The identifier in parentheses is the LDAP display name for the attribute.
The acceptable values for this parameter are:
* A distinguished name
* A GUID (objectGUID)
The cmdlet searches the default naming context or partition to find the object.
If two or more objects are found, the cmdlet returns a non-terminating error.
.PARAMETER Partition
Specifies the distinguished name of an Active Directory partition. The
distinguished name must be one of the naming contexts on the current directory
server. The cmdlet searches this partition to find the object defined by the
Identity parameter.
In many cases, a default value is used for the Partition parameter if no value
is specified. The rules for determining the default value are given below. Note
that rules listed first are evaluated first and once a default value can be
determined, no further rules are evaluated.
In Active Directory Domain Services (AD DS) environments, a default value for
Partition is set in the following cases:
* If the Identity parameter is set to a distinguished name, the default value
of Partition is automatically generated from this distinguished name.
* If the previous cases does not apply, the default value of Partition is set
to the default partition or naming context of the target domain.
In Active Directory Lightweight Directory Services (AD LDS) environments, a
default value for Partition is set in the following cases:
* If the Identity parameter is set to a distinguished name, the default value
of Partition is automatically generated from this distinguished name.
* If the target AD LDS instance has a default naming context, the default value
of Partition is set to the default naming context. To specify a default naming
context for an AD LDS environment, set the msDS-defaultNamingContext property
of the Active Directory directory service agent object (nTDSDSA) for the AD
LDS instance.
* If none of the previous cases apply, the Partition parameter does not take a
default value.
.PARAMETER AuthType
Specifies the authentication method to use. The acceptable values for this
parameter are:
* Negotiate or 0
* Basic or 1
The default authentication method is Negotiate.
A Secure Sockets Layer (SSL) connection is required for the Basic authentication
method.
.PARAMETER Credential
Specifies the user account credentials to use to perform this task. The default
credentials are the credentials of the currently logged on user.
You can also create a PSCredential object by using a script or by using the
Get-Credential cmdlet. You can then set the Credential parameter to the PSCredential
object.
If the acting credentials do not have directory-level permission to perform the
task, ADSI module for Windows PowerShell returns a terminating error.
.PARAMETER IncludeDeletedObjects
Indicates that this cmdlet retrieves deleted objects and the deactivated forward
and backward links. When this parameter is specified, the cmdlet uses the following
LDAP controls:
Note: If this parameter is not specified, the cmdlet does not return or operate on
deleted objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server. Use this
parameter to retrieve properties that are not included in the default set.
Specify properties for this parameter as a comma-separated list of names. To display
all of the attributes that are set on the object, specify * (asterisk).
To specify an individual extended property, use the name of the property. For
properties that are not default or extended properties, you must specify the
LDAP display name of the attribute.
To retrieve properties and display them for an object, you can use the Get-*
cmdlet associated with the object and pass the output to the Get-Member cmdlet.
.PARAMETER Server
Specifies the AD DS instance to connect to, by providing one of the following
values for a corresponding domain name or directory server. The service may be
an AD LDS or an AD DS instance.
Specify the AD DS instance in one of the following ways:
Domain name values:
* Fully qualified domain name
* NetBIOS name
Directory server values:
* Fully qualified directory server name
* NetBIOS name
* Fully qualified directory server name and port
The default value for this parameter is determined by one of the following
methods in the order that they are listed:
* By using the Server value from objects passed through the pipeline
* By using the domain of the computer running Windows PowerShell
.EXAMPLE
Get-AdsiObject -LDAPFilter "(objectClass=site)" -SearchBase 'CN=Configuration,DC=Fabrikam,DC=Com' -Properties CanonicalName | FT Name,CanonicalName -A
Name CanonicalName
---- -------------
HQ FABRIKAM.COM/Configuration/Sites/HQ
BO1 FABRIKAM.COM/Configuration/Sites/BO1
BO2 FABRIKAM.COM/Configuration/Sites/BO2
BO3 FABRIKAM.COM/Configuration/Sites/BO3
Get the sites for a domain using LDAP filter syntax
.EXAMPLE
Get-ADObject -Filter 'ObjectClass -eq "site"' -SearchBase 'CN=Configuration,DC=Fabrikam,DC=Com' -Properties siteObjectBL | foreach {$_.siteObjectBL}
CN=192.167.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.166.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.168.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.165.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.164.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.163.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.162.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.161.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.160.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.159.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.158.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
CN=192.157.1.0/26,CN=Subnets,CN=Sites,CN=Configuration,DC=FABRIKAM,DC=COM
This command gets the Site objects from the configuration naming context and displays a list of siteObjectBL properties.
.EXAMPLE
PS C:\> $ChangeDate = New-Object DateTime(2008, 11, 18, 1, 40, 02)
PS C:\> Get-ADObject -Filter 'whenChanged -gt $ChangeDate' -IncludeDeletedObjects
This command gets all the objects, including the deleted ones, whose whenChanged attribute
is greater than the specified date. Note that both deleted and non-deleted (and
non-recycled) objects matching the filter are returned.
.EXAMPLE
PS C:\> $ChangeDate = New-Object DateTime(2008, 11, 18, 1, 40, 02)
PS C:\> Get-ADObject -Filter 'whenChanged -gt $ChangeDate -and isDeleted -eq $True -and -not (isRecycled -eq $True) -and name -ne "Deleted Objects"' -IncludeDeletedObjects
ObjectGUID : 98118958-91c7-437d-8ada-ba0b66db823b
Deleted : True
DistinguishedName : CN=Andrew Ma\0ADEL:98118958-91c7-437d-8ada-ba0b66db823b,CN=Deleted Objects,DC=FABRIKAM,DC=COM
Name : Andrew Ma
DEL:98118958-91c7-437d-8ada-ba0b66db823b
ObjectClass : user
This example gets all the deleted objects, whose whenChanged attribute is greater than the specified date. The clause name -ne "Deleted Objects" ensures that the Deleted Objects Container is not returned. This example only returns objects that can be restored.
.EXAMPLE
PS C:\> $ChangeDate = New-Object DateTime(2008, 11, 18, 1, 40, 02)
PS C:\> Get-ADObject -Filter 'whenChanged -gt $ChangeDate -and isDeleted -eq $True -and -not (isRecycled -eq $True) -and lastKnownParent -eq "OU=Accounting,DC=Fabrikam,DC=com"' -IncludeDeletedObjects
ObjectGUID : 12d53e7f-aaf7-4790-b41a-da19044504db
Deleted : True
DistinguishedName : CN=Craig Dewar\0ADEL:12d53e7f-aaf7-4790-b41a-da19044504db,CN=Deleted Objects,DC=Fabrikam,DC=com
Name : Craig Dewar
DEL:12d53e7f-aaf7-4790-b41a-da19044504db
ObjectClass : user
This example gets all the deleted objects whose whenChanged attribute is greater than the specified date and at the time of deletion were the children of the specified organizational unit.
.EXAMPLE
PS C:\> Get-ADObject -Identity "DC=AppNC" -Server "FABRIKAM-SRV1:60000"
ObjectGUID DistinguishedName Name ObjectClass
---------- ----------------- ---- -----------
62b2e185-9322-4980-9c93-cf... DC=AppNC AppNC domainDNS
This command gets the information of the domainDNS object of an LDS instance.
.NOTES
Place additional notes here.
.LINK
URLs to related sites
The first link is opened by Get-Help -Online Get-AdsiObject
.INPUTS
None
.OUTPUTS
[Object]
#>
[cmdletbinding(DefaultParameterSetName='identity')]
param(
[parameter(ParameterSetName='filter',position=0)]
[string]$Filter,
[parameter(ParameterSetName='ldapfilter',position=0)]
[string]$LDAPFilter,
[parameter(ParameterSetName='filter')]
[parameter(ParameterSetName='ldapfilter')]
[int]$ResultPageSize,
[parameter(ParameterSetName='filter')]
[parameter(ParameterSetName='ldapfilter')]
[int]$ResultSetSize,
[parameter(ParameterSetName='filter')]
[parameter(ParameterSetName='ldapfilter')]
[string]$SearchBase,
[parameter(ParameterSetName='filter')]
[parameter(ParameterSetName='ldapfilter')]
[System.DirectoryServices.SearchScope]$SearchScope,
[parameter(ParameterSetName='identity',position=0)]
$Identity, # [ADObject]
[parameter(ParameterSetName='identity')]
[String]$Partition,
[ValidateSet('Basic','Negociate')]
[string]$AuthType,
[PSCredential]$Credential,
[switch]$IncludeDeletedObjects,
[String[]]$Properties,
[String]$Server
)
function _CanonicalFromDN([string]$dn) {
$cninfo = $dn -split ','
$root = ($cninfo | ? { $_ -match '^DC=' }) -replace '^DC='
$path = ($cninfo | ? { $_ -notmatch '^DC=' }) -replace '^\w\w='
[array]::Reverse($path)
'{0}/{1}' -f ($root -join '.'),($path -join '/')
}
if ($PSBoundParameters.ContainsKey('Identity')) {
# Identity is either a GUID or a DistinguishedName. We try to parse
# a GUID and if it fails, we consider it's a DN and we populate
# the LdapFilter accordingly.
$guid = [guid]::Empty
if ([guid]::TryParse($Identity,[ref]$guid)) {
$charArray = foreach ($byte in $guid.ToByteArray()) { $byte.ToString('x2') }
$LDAPFilter = 'objectguid=\{0}' -f ( $charArray -join '\' )
} else {
$LDAPFilter = 'distinguishedname={0}' -f $Identity
}
} elseif ($PSBoundParameters.ContainsKey('Filter')) {
# If We receive a filter, we TRY to parse it.
Write-Warning -Message 'The ''-filter'' parameter is experimental.'
$LDAPFilter = ConvertTo-LdapFilter -filter $Filter
}
$getRootDseParameters = @{}
# Server and Credential parameters are forwarded to Get-AdsiRootDSE
#
# The first thing we're going to do is to get the Root DSE. We do it
# for two reasons:
# 1) We need some of the info it provides (naming contexts).
# 2) We're going to use an adsisearcher object (adsisearcher is a
# type accelerator for System.DirectoryServices.DirectorySearcher).
# The way to select a specific set of credentials to the Directory
# Searcher is to enter it in the RootDSE directory, then to
# feed that directory to the Searcher.
if ($PSBoundParameters.ContainsKey('Server')) { $getRootDseParameters += @{Server = $Server} }
if ($PSBoundParameters.ContainsKey('Credential')) { $getRootDseParameters += @{Credential = $Credential} }
$RootDSE = Get-AdsiRootDSE @getRootDseParameters
if (-not $PSBoundParameters.ContainsKey('Partition')) {
$Partition = $RootDSE.defaultNamingContext
} else {
# If a partition has been specified, we check that it's one
# of the NamingContexts values.
#
# Active Directory is splitted in several physical (IE. different files) partitions.
# At the very list, there's one that contains the configuration, one that contains
# the schema, and at least one for the primary domain. Those partitions each have
# their own distinguished name, and those partition addressing DNs are the
# available naming contexts. The list can be found in the namingContexts attribute
# of the RootDSE.
if ($Partition -notin $RootDSE.namingContexts) {
Throw ('The supplied partition distinguishedName should have one of the following value(s): ''{0}''.' -f ($RootDSE.namingContexts -join ' , '))
}
}
if ($PSBoundParameters.ContainsKey('server')) {
$Server = '{0}/' -f $Server
}
$aDSISearcher = New-Object -TypeName adsisearcher -ArgumentList $RootDSE
$aDSISearcher.SearchRoot.Path = 'LDAP://{0}{1}' -f $Server,$Partition
$aDSISearcher.Filter = $LDAPFilter
if ($PSBoundParameters.ContainsKey('ResultPageSize')) {
$aDSISearcher.PageSize = $ResultPageSize
}
if ($PSBoundParameters.ContainsKey('ResultSetSize')) {
$aDSISearcher.SizeLimit = $ResultSetSize
}
if ($PSBoundParameters.ContainsKey('SearchBase')) {
$aDSISearcher.SearchRoot = 'LDAP://{0}' -f $SearchBase
}
if ($PSBoundParameters.ContainsKey('SearchScope')) {
$aDSISearcher.SearchScope = $SearchScope
}
if ($PSBoundParameters.ContainsKey('AuthType')) {
# The AdsiSearcher object AuthenticationType takes a AuthenticationTypes
# (https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.authenticationtypes),
# a flag with 11 defferent values, whereas Get-AdObject takes a ADAuthType
# (https://docs.microsoft.com/en-us/dotnet/api/microsoft.activedirectory.management.adauthtype),
# which only has two values. the nice thing is that the two possible adauthtype
# values are the first flag in authenticationtypes, IE. 0 or 1, which
# is a boolean casted as an integer, just like this:
$aDSISearcher.AuthenticationType = [int]($AuthType -eq 'Negociate')
}
if ($PSBoundParameters.ContainsKey('IncludeDeletedObjects') -and $IncludeDeletedObjects) {
$aDSISearcher.Tombstone = $true
}
# # Removed the following lines to use the pipeline.
# $results = $aDSISearcher.FindAll()
# if ( $PSCmdlet.ParameterSetName -eq 'Identity' -and $results.Count -gt 1) {
# Write-Error -Message 'More than one object have been found'
# }
foreach($rawObject in ($aDSISearcher.FindAll())) {
$baseObject = $rawObject.GetDirectoryEntry()
$defaultProperties = 'DistinguishedName','Name','ObjectClass','ObjectGUID'
$extendedProperties = 'CanonicalName','CN','Created','Deleted','Description','DisplayName','LastKnownParent','Modified','ObjectCategory','ProtectedFromAccidentalDeletion'
if ($Properties -eq '*') {
$Properties = $rawObject.Properties.keys + ($Properties | ? { $_ -ne '*' })
$defaultProperties += $extendedProperties
}
$Properties = $defaultProperties + ( $Properties | ? {$_ -notin $defaultProperties} ) | sort
$aDSIObject = New-Object -TypeName psobject
$PSDefaultParameterValues = @{'Add-Member:MemberType' = 'NoteProperty'}
switch ($Properties) {
'CanonicalName' { $aDSIObject | Add-Member -Name 'CanonicalName' -Value (_CanonicalFromDN -dn $rawObject.Properties.distinguishedname[0]) }
'CN' { $aDSIObject | Add-Member -Name 'CN' -Value $rawObject.Properties.cn[0] }
'Created' { $aDSIObject | Add-Member -Name 'Created' -Value $rawObject.Properties.whencreated[0] }
'Deleted' { $aDSIObject | Add-Member -Name 'Deleted' -Value $( if ($rawObject.Properties.contains('isDeleted')) {$rawObject.Properties.isDeleted[0]} ) }
'Description' { $aDSIObject | Add-Member -Name 'Description' -Value $rawObject.Properties.description[0] }
'DisplayName' { $aDSIObject | Add-Member -Name 'DisplayName' -Value $rawObject.Properties.displayname[0] }
'DistinguishedName' { $aDSIObject | Add-Member -Name 'DistinguishedName' -Value $rawObject.Properties.distinguishedname[0] }
'LastKnownParent' { $aDSIObject | Add-Member -Name 'LastKnownParent' -Value $( if ($rawObject.Properties.contains('LastKnownParent')) {$rawObject.Properties.lastKnownParent[0]} ) }
'Modified' { $aDSIObject | Add-Member -Name 'Modified' -Value 'TODO' }
'Name' { $aDSIObject | Add-Member -Name 'Name' -Value $rawObject.Properties.cn[0] }
'ObjectCategory' { $aDSIObject | Add-Member -Name 'ObjectCategory' -Value $rawObject.Properties.objectcategory[0] }
'ObjectClass' { $aDSIObject | Add-Member -Name 'ObjectClass' -Value ( $rawObject.Properties.objectclass | select -Last 1 ) }
'ObjectGuid' { $aDSIObject | Add-Member -Name 'ObjectGuid' -Value ([guid]$rawObject.Properties.objectguid[0]) }
'ProtectedFromAccidentalDeletion' { $aDSIObject | Add-Member -Name 'ProtectedFromAccidentalDeletion' -Value ( $null -ne ( $baseObject.ObjectSecurity.Access | ? { $_.ActiveDirectoryRights -eq 'DeleteTree,Delete' -and $_.AccessControlType -eq 'Deny' -and $_.IsInherited -eq $false -and $_.IdentityReference -eq 'EveryOne' } ) ) }
default {
$propName = ($rawObject.Properties.Keys -eq $_)[0]
if ( $rawObject.Properties.Contains($_) ) {
$propValue = $rawObject.Properties.$propName[0]
$aDSIObject | Add-Member -Name $propName -Value $propValue
}
}
}
$aDSIObject
}
}
<#
The comment based help for Get-AdsiObject has been pasted from the help for
Get-ADObject. The sentences below have been removed because they do not
apply to our current implementation.
.Description
You can
also set the parameter to an Active Directory object variable, such as
$<localADObject> or pass an object through the pipeline to the Identity
parameter.
.PARAMETER SearchBase
When the value of the SearchBase parameter is set to an empty string and you
are connected to a global catalog (GC) port, all partitions are searched. If
the value of the SearchBase parameter is set to an empty string and you are
not connected to a GC port, an error is thrown.
.PARAMETER Identity
This parameter can also get this object through the pipeline or you can set
this parameter to an object instance.
Derived types, such as the following, are also accepted:
* Microsoft.ActiveDirectory.Management.ADGroup
* Microsoft.ActiveDirectory.Management.ADUser
* Microsoft.ActiveDirectory.Management.ADComputer
* Microsoft.ActiveDirectory.Management.ADServiceAccount
* Microsoft.ActiveDirectory.Management.ADFineGrainedPasswordPolicy
* Microsoft.ActiveDirectory.Management.ADDomain
.PARAMETER Partition
* If running cmdlets from an Active Directory provider drive, the default value of Partition is automatically generated from the current path in the drive.
* If running cmdlets from an Active Directory provider drive, the default value of Partition is automatically generated from the current path in the drive.
.PARAMETER Credential
unless the cmdlet is run from an Active
Directory module for Windows PowerShell provider drive. If the cmdlet is run from such a
provider drive, the account associated with the drive is the default.
To specify this parameter, you can type a user name, such as User1 or Domain01\User01 or
you can specify a PSCredential object. If you specify a user name for this parameter, the
cmdlet prompts for a password.
.PARAMETER IncludeDeletedObjects
When this parameter is specified, the cmdlet uses the following LDAP controls:
Show Deleted Objects (1.2.840.113556.1.4.417)
Show Deactivated Links (1.2.840.113556.1.4.2065)
.PARAMETER Server
Specifies the AD DS instance to connect to, by providing one of the following
values for a corresponding domain name or directory server. The service may be
any of the following: AD LDS, AD DS, or Active Directory snapshot instance.
* By using the server information associated with the AD DS Windows PowerShell provider drive, when the cmdlet runs in that drive
#>
function Get-AdsiRootDSE {
<#
.SYNOPSIS
Gets the server/domain Root DSE
.DESCRIPTION
the Get-AdsiRootDSE cmdlet reads the RootDSE entry of the domain
or server. The RootDSE gives the main configuration information
for the domain.
.PARAMETER Credential
The credential of the user that will authenticate to the server.
.PARAMETER AuthenticationType
The type of authentication used to connect to the LDAP server.
.PARAMETER Server
The server to connect to.
.EXAMPLE
Get-AdsiRootDSE -Server DC01
Gets the RootDSE for DC01 and the domain it's a domain controller for.
.NOTES
Terminology:
- The DSA (Directory System Agent) is the LDAP server.
- A DSE (DSA-Specific Entry) contains info about the DSA.
- The Root DSE contains the main information about the server.
.LINK
https://ldapwiki.com/wiki/DSA
https://ldapwiki.com/wiki/DSA-Specific%20Entry
https://ldapwiki.com/wiki/RootDSE
https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse
.INPUTS
None
.OUTPUTS
System.DirectoryServices.DirectoryEntry
#>
[CmdletBinding()]
param(
[Parameter(Position=0)]
[pscredential]$Credential,
[Parameter(Position=1)]
[DirectoryServices.AuthenticationTypes]$AuthenticationType,
[string]$Server
)
# But for the ADsPath parameter, we just
# forward everything to Get-AdsiDirectoryEntry
$Parameters = @{ ADsPath = 'LDAP://RootDSE' }
if ($PSBoundParameters.ContainsKey('Server')) {
$Parameters += @{Server = $Server}
}
if ($PSBoundParameters.ContainsKey('Credential')) {
$Parameters += @{Credential = $Credential}
}
if ($PSBoundParameters.ContainsKey('AuthenticationType')) {
$Parameters += $AuthenticationType
}
Write-Verbose -Message '[Adsi]Get-RootDSE'
$rootDSE = Get-AdsiDirectoryEntry @Parameters
$rootDSE.psobject.TypeNames.Insert(0,'Xpla.Adsi.RootDSE')
$rootDSE
}
$test = ' samAccountName -like "Andy*" '
$test.Trim() -replace '^(.*)\s+-like\s+"(.*)"','($1=$2)'
$test = " samAccountName -like 'Andy*' "
$test = $test -replace "'",'"'
$test.Trim() -replace '^(.*)\s+-like\s+"(.*)"','($1=$2)'
$test = 'CN -like "andy*" -or (CN -eq "steve" -and (displayname -eq "test" -or displayname -eq "houla" )) -or ( CN -eq "margaret" -and displayname -eq "toto") '
$ast = [System.Management.Automation.Language.Parser]::ParseInput($test,[ref]$null,[ref]$null)
$ast.FindAll({$args[0] -is [System.Management.Automation.Language.ParenExpressionAst]},$true ) # | select *
'(&({0}))' -f (('CN -eq "margaret" -and displayname -eq "toto" ' -split '\s+-and\s+' | % {$_.Trim() -replace '^(.*)\s+-eq\s+"(.*)"','($1=$2)'})-join ')(')
$root = 'CN -like "andy*" -or (CN -eq "steve" -and (displayname -eq "test" -or displayname -eq "houla" )) -or ( CN -eq "margaret" -and displayname -eq "toto") '
Pipeline StaticType Extent Parent
-------- ---------- ------ ------
CN -eq "steve" -and (displayname -eq "test" -or displayname -eq "houla" ) System.Object (CN -eq "steve" -and (displayname -eq "test" -or displayname -eq "houla" )) CN -like "andy*" -or (CN -eq "steve" ...
displayname -eq "test" -or displayname -eq "houla" System.Object (displayname -eq "test" -or displayname -eq "houla" ) CN -eq "steve" -and (displayname -eq ...
CN -eq "margaret" -and displayname -eq "toto" System.Object ( CN -eq "margaret" -and displayname -eq "toto") CN -like "andy*" -or (CN -eq "steve" ...
1: non, car pipeline in $all.parent
2: oui => TOKEN1 = (|(displayname=test)(displayname=houla))
=> 1.pipeline => CN -eq "steve" -and @@TOKEN1@@ => TOKEN2 = (&(CN=steve)@@TOKEN1@@) ## => TOKEN2 = (&(CN=steve)(|(displayname=test)(displayname=houla)))
=> 1.Parent = root => root -replace 2.Extent @@TOKEN2@@
3: oui => TOKEN3 = (&(CN=margaret)(displayname=toto))
=> 3.Parent = root => root -replace 3.Extent @@TOKEN3@@
$root = 'CN -like "andy*" -or @@TOKEN2@@ -or @@TOKEN3@@ '
=> (|(CN=andy*)@@TOKEN2@@@@TOKEN3@@)
=> (|(CN=andy*)(&(CN=steve)@@TOKEN1@@)(&(CN=margaret)(displayname=toto)))
=> (|(CN=andy*)(&(CN=steve)(|(displayname=test)(displayname=houla)))(&(CN=margaret)(displayname=toto)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment