Ldap.psm1
function Expand-Collection { # Simple helper function to expand a collection into a PowerShell array. # The advantage to this is that if it's a collection with a single element, # PowerShell will automatically parse that as a single entry. [CmdletBinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromRemainingArguments)] [ValidateNotNull()] [Object[]] $InputObject ) process { foreach ($i in $InputObject) { ForEach-Object -InputObject $i -Process { Write-Output $_ } } } } function Send-LdapRequest { [CmdletBinding()] [OutputType([System.DirectoryServices.Protocols.DirectoryResponse])] param( [Parameter(Mandatory)] [ValidateNotNull()] [System.DirectoryServices.Protocols.LdapConnection] $LdapConnection , [Parameter(Mandatory)] [ValidateNotNull()] [System.DirectoryServices.Protocols.DirectoryRequest] $Request , [Parameter()] [System.TimeSpan] $Timeout ) end { if ($Timeout) { $LdapConnection.SendRequest($Request, $Timeout) | Write-Output } else { $LdapConnection.SendRequest($Request) } } } function Get-LdapConnection { [CmdletBinding()] [OutputType([System.DirectoryServices.Protocols.LdapDirectoryIdentifier])] param( [Parameter(Mandatory)] [String] $Server, # LDAP port to use. Default is 389 for LDAP or 636 for LDAPS [Parameter()] [Int] $Port, # Do not use SSL [Parameter()] [Switch] $NoSsl, # Ignore certificate validation (use with self-signed certs) [Parameter()] [Switch] $IgnoreCertificate, # Version of the LDAP protocol to use [Parameter()] [Int] $ProtocolVersion, # Specify how the LDAP library follows referrals [Parameter()] [System.DirectoryServices.ReferralChasingOption] $ReferralChasing, [Parameter()] [PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.DirectoryServices.Protocols.AuthType] $AuthType ) process { $ldapIdentifier = New-Object -TypeName System.DirectoryServices.Protocols.LdapDirectoryIdentifier -ArgumentList $Server, $Port if ($Credential) { Write-Debug "[Get-LdapConnection] Creating authenticated LdapConnection for user $($Credential.UserName)" $ldap = New-Object -TypeName System.DirectoryServices.Protocols.LdapConnection -ArgumentList $ldapIdentifier, ($Credential.GetNetworkCredential()) if (-not $AuthType) { Write-Debug "[Get-LdapConnection] AuthType was not specified; defaulting to Basic" $AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic } } else { Write-Debug "[Get-LdapConnection] Creating anonymous LdapConnection" $ldap = New-Object -TypeName System.DirectoryServices.Protocols.LdapConnection -ArgumentList $ldapIdentifier if (-not $AuthType) { Write-Debug "[Get-LdapConnection] AuthType was not specified; defaulting to Anonymous" $AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous } } $ldap.AuthType = $AuthType if ($NoSsl) { Write-Debug "[Get-LdapConnection] NoSsl was sent; not setting SSL" } else { $ldap.SessionOptions.SecureSocketLayer = $true } if ($IgnoreCertificate) { $ldap.SessionOptions.VerifyServerCertificate = { $true } } if ($ProtocolVersion) { $ldap.SessionOptions.ProtocolVersion = $ProtocolVersion } if ($ReferralChasing) { $ldap.SessionOptions.ReferralChasing = $ReferralChasing } Write-Output $ldap } } function Get-LdapObject { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNull()] [System.DirectoryServices.Protocols.LdapConnection] $LdapConnection, [Parameter(ParameterSetName = 'DistinguishedName', Mandatory)] [String] $Identity, [Parameter(ParameterSetName = 'LdapFilter', Mandatory)] [Alias('Filter')] [String] $LdapFilter, [Parameter(ParameterSetName = 'LdapFilter', Mandatory)] [String] $SearchBase, [Parameter(ParameterSetName = 'LdapFilter')] [System.DirectoryServices.Protocols.SearchScope] $Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree, [Parameter()] [String[]] $Property, [Parameter()] [ValidateSet('String', 'ByteArray')] [String] $AttributeFormat = 'String', [Parameter()] [int] $PageSize, [Parameter()] [uint32] $TimeoutSeconds, # Do not attempt to clean up the LDAP output - provide the output as-is [Parameter()] [Switch] $Raw ) begin { if ($AttributeFormat -eq 'String') { $attrType = [string] } else { $attrType = [byte[]] } } process { $request = New-Object -TypeName System.DirectoryServices.Protocols.SearchRequest if ($PSCmdlet.ParameterSetName -eq 'DistinguishedName') { $request.DistinguishedName = $Identity } else { $request.Filter = $LdapFilter $request.DistinguishedName = $SearchBase } if (-not $Property -or $Property -contains '*') { Write-Debug "[Get-LdapObject] Returning all properties" } else { foreach ($p in $Property) { [void] $request.Attributes.Add($p) } } # Declare this outside of the below If block to support Strict mode $pageControl = $null if ($PageSize) { $pageControl = New-Object -TypeName System.DirectoryServices.Protocols.PageResultRequestControl -ArgumentList $PageSize [void] $request.Controls.Add($pageControl) } Write-Debug "[Get-LdapObject] Sending LDAP request" $splat = @{ 'LdapConnection' = $LdapConnection 'Request' = $request } if ($TimeoutSeconds) { $splat['Timeout'] = [System.TimeSpan]::FromSeconds($TimeoutSeconds) } # Again, we need to define the variable outside the scope of the do/until loop # or else Strict mode will yell at us $hasMore = $false do { # Stop after this run unless we explicitly say otherwise $hasMore = $false # Since we are sending a SearchRequest, we will get a SearchResponse back [System.DirectoryServices.Protocols.SearchResponse] $response = Send-LdapRequest @splat if (-not $response) { Write-Verbose "No response was returned from the LDAP server." } elseif ($response.ResultCode -ne 'Success') { Write-Warning "The LDAP server returned response code $($response.ResultCode) instead of Success" Write-Output $response } else { if ($Raw) { Write-Debug "[Get-LdapObject] -Raw was passed; outputting raw directory entries" Write-Output ($response.Entries) } else { # Convert results to a PSCustomObject. $response.Entries | ForEach-Object { $hash = [Ordered] @{ PSTypeName = 'LdapObject' DistinguishedName = $_.DistinguishedName } # Attributes are returned as an instance of the class # System.DirectoryServices.Protocols.SearchResultAttributeCollection, # which is a collection of DirctoryAttribute objects. # # DirectoryAttribute extends CollectionBase, which PowerShell can iterate # through using ForEach-Object, but PowerShell doesn't automatically # expand them the way it handles IEnumerables. # # The ForEach-Object here iterates through the values in the # DirectoryEntry and converts them to an IEnumerable instance, which is # much more PowerShell-friendly. foreach ($a in $_.Attributes.Keys | Sort-Object) { $hash[$a] = $_.Attributes[$a].GetValues($attrType) | ForEach-Object { $_ } } [PSCustomObject] $hash } | Write-Output } # If we're paging, see if we need to return another page if ($PageSize) { [System.DirectoryServices.Protocols.PageResultResponseControl] $pageResult = $response.Controls | Where-Object {$_ -is [System.DirectoryServices.Protocols.PageResultResponseControl]} | Select-Object -First 1 # There should only be one, but this is defensive programming if (-not $pageResult) { Write-Warning "No paging controls were returned from the LDAP server. Results may be incomplete." } elseif ($pageResult.Cookie.Length -eq 0) { # Length of 0 indicates that we've returned all results Write-Debug "[Get-LdapObject] Paging cookie with length of 0 returned; completed paging" } else { # Update the page control in the request with the new paging info in the response Write-Debug "[Get-LdapObject] More paging information was provided ($($pageResult.Cookie))" $pageControl.Cookie = $pageResult.Cookie $hasMore = $true } } } } while ($hasMore) } } function Remove-LdapConnection { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline = $true)] [System.DirectoryServices.Protocols.LdapConnection[]] $LdapConnection , [Parameter()] [Switch] $Force ) process { foreach ($l in $LdapConnection) { if ($l) { if (-not ($Force -or $PSCmdlet.ShouldProcess($l, "Close LDAP connection"))) { Write-Debug "[Remove-LdapConnection] WhatIf mode or user denied prompt; not closing connection [[ $l ]" } else { Write-Debug "[Remove-LdapConnection] Disposing LdapConnection [$l]" $l.Dispose() } } } } } Set-StrictMode -Version Latest |