Powershell : Non-LDAPS Bindings Investigation


Looking for non-secure LDAP binding is nothing new, in fact Microsoft will discourage you from using un-secured bindings (and this does make sense) and every so often then will try and enforce secure bindings, only to revert that update and simply provide a monitoring or audit mode, the desire to switch to LDAP-S needs to be a corporate decision - this background and scripting will allow you to make calculated decisions as to what is not using secure bindings.

LDAP-S

Yes, this is not a particularly new topic for a LDAP request that has a Secure certificate binding attached to it, this is their known as LDAP-S (due to the certificate) and with this update, you get a port change from TCP:389 to TCP:636

Everybody should know about the existence of LDAP-S It’s just for many organizations. It’s convenient to ignore it until Microsoft tries to turn it off, Which has happened a couple of times and the immediate hot fix is to put a mode where you’re still allowed to use LDAP but in audit mode.

Global catalogue (GC)  port..

You also need to remember that domain controller is also have a global catalogue (GC) port and in this scenario, you also get an unsecured port and a secured port? - except this time it will be TCP:3268 (unsecured) and TCP:3269 (secured)

When would I use a global catalogue port?

The Global Catalog (GC) port is used for LDAP queries when you need to access forest-wide data, such as performing cross-domain searches, resolving Universal Group memberships, or supporting applications like Exchange. It improves query performance by providing a subset of essential attributes for all objects in the forest.

Service bindings with LDAP/GC

The Active Directory Domain Service (ADDS) is responsible for providing availability on both the LDAP and GC ports

ADDS and ADWS

Yes, you now have two services on domain controllers, all the LDAP/GC ports and availability all served by the ADDS service.

Whereas, on the other side of the equation, you have the ADWS (Active Directory Web Service)  this Provides programmatic access to AD via Web Services protocols (e.g., PowerShell, WS-Transfer)

What is ADWS is not available?

If Active Directory Web Services (ADWS) is unavailable, the following problems can occur:

  1. PowerShell AD cmdlets fail (e.g., Get-ADUserGet-ADGroup).
  2. Active Directory Administrative Center (ADAC) cannot connect.
  3. Remote AD management tools stop working.
  4. Modern applications relying on web protocols fail.
  5. Advanced Group Policy tasks, like modeling will be impacted.
Unsigned protocols what’s the security impact?

Using non-signed LDAP (unencrypted) poses significant security risks, including:

Data Exposure: Credentials and sensitive information, like user details, can be transmitted in plaintext and intercepted by attackers.
Man-in-the-Middle (MITM) Attacks: Without signing or encryption, attackers can modify or intercept LDAP traffic.
Authentication Spoofing: Unsigned LDAP allows attackers to impersonate users or services.

Many of these threats are currently “in the wild” and it does rely on malicious actors being able to talk to you on the domain controllers.

Note : Remember this can be directly or indirectly and by indirectly, I mean via a zero day or an unpatched server.

Looking at crtedentials with "simple bind"

If you are allowed to connect with a "simple bind" then the credentials will be send in "clear text" which is bad, as this means from the client of the domain controller if you have a packet capture like Wireshark installed then you can actually see the username and password of that user that did the did the simple bind.

This is a simple bind by using "ldp" the default LDAP connection tool in Windows, as you can see from below, so lets get Wireshark ready before we click OK and capture all the traffic on my client:


The moment "ldp" connects stop the traffic capture and then look for the protocol of LDAP and find the packet that specifies your username as below here you can see the user is wifi.surfer:

Wireshark Filter :  tcp.dstport == 389



Then if you double click that packet you can see the password being used with the user account right there is plain text for all to see with the password of "ReallyBadPassword1" as below:



How do I stop clear text password connections?

If you wish to stop this behaviour then you will need to on the Domain Controller security policy set the option of "Domain Controller : LDAP server signing requirements" to "Require Signing" as below:


WARNING : This will then prevent this clear-text password behaviour however remember this will have an impact on your older/legacy applications if you "just enable" this setting, you first need to complete a risk assessment on what the impact will be - that is before you cause an outage.


Report : Unsecured LDAP requests

Let’s get a report that will analyze all the unsecured LDAP bindings, this will be accomplished by setting this "16 LDAP Interface Events" to the debug value to 0x2.

I have created a script that has a menu system, that will enable you to:
  1. Enable debugging mode in the registry
  2. Disable debugging mode in the Registry
  3. Check the debugging level of the flag
  4. Optional : Restart ADDS on all domain controllers
Restarting ADDS

This feature is only really required when you’ve had to change the debugging level using some of the other commands in the menu from earlier.

Remember, when you restart ADDS you cannot perform this action on all domain controllers at the same time, when you do restart the service, it will temporarily disrupt the authentication of any applications or services that directly point at that domain controller

When you choose the restart option, the first thing the automated script does is check that the domain controller is listening on all relevant ports and it can successfully connect, these will be the following:
  1. LDAP
  2. LDAP-S
  3. GC
  4. GC-S
If this condition is true, it will restart ADDS on the first to make controller and once restarted, it will do the same check again to make sure that the domain controller is fully back online before moving onto the next domain controllers.

The automated script will not move on until The checks for the correct port pass with zero exceptions, There are many reasons for this extra checking, more on that later.

Restarting incorrectly : USN Journal Interlock

If you restart all your domain controllers with the script that simply issues the command:

Restart-Service -Name "NTDS" -Force

When is command is issued, ADDS will restart and one of the checks it does is to confirm everything is valid with another domain controller by confirming information such as the database and zone file (DNS) - if all your domain controllers are off-line, you enter what is known as USN Journal Lock.

USN Journal Lock?

"USN Journal Interlock" or "Interlock Condition." It occurs when multiple domain controllers in an Active Directory environment are restarted simultaneously and each waits for the others to verify database consistency before proceeding with startup.

Why did USN Journal Lock occur?

Remember, this situation has risen because best practice have not been followed for restarting services on Domain Controller's.

This situation specifically arises because each domain controller is designed to:

  1. Check its replication partners for more up-to-date information before becoming fully operational
  2. Verify the consistency of its database with other domain controllers

When all domain controllers are restarting at the same time, they enter a deadlock situation where each is waiting for the others to become available for verification, but none can complete their startup process because they're all in this waiting state.

Its recommended to restart domain controllers sequentially rather than simultaneously, allowing each one to fully complete its startup process before moving to the next, which is exactly what the script of would have done if you used it!

Recovering from USN Journal lock?

You need to set a key called "DontWaitForNetlogonReady" in the registry to force the domain controller to start Active Directory without waiting for other DCs - this only needs to completed on the first domain controller you wish to bring online.

Path: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters 

Value
Name: DontWaitForNetlogonReady
Type: REG_DWORD
Value: 1

I would recommend starting with the domain controller holding the PDC emulator role, then you need to follow these steps:

  1. Confirm the Registry key has been created accurately
  2. Reboot the domain controller
  3. Upon boot up, the Active Directory services will start and become operational, they will not enter the interlock condition
  4. All other main controllers will then detect This valid start up and will come online once checks have been completed
  5. Back online ? Confirm you have deleted this registry key, it is no longer required

Script : LDAPDebugCheck.ps1

This script will enable to you check the status of the debugging flag and then give you the option to enable/disable this flag as outlined earlier.

# Requires -RunAsAdministrator
$VerbosePreference = "Continue"
Import-Module ActiveDirectory

$logPath = "LDAP_Binds.log"
$eventLogName = "Directory Service"
$daysToSearch = 7

if (!(Test-Path (Split-Path $logPath -Parent))) {
    New-Item -ItemType Directory -Path (Split-Path $logPath -Parent)
}

function Write-ToLog {
    param($Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "$timestamp - $Message"
    Write-Verbose $logMessage
    $logMessage | Add-Content -Path $logPath
}

function Show-Menu {
    Clear-Host
    Write-Host "================ LDAP Configuration and Monitoring ================"
    Write-Host "1: Enable LDAP Interface Events Logging on all DCs"
    Write-Host "2: Disable LDAP Interface Events Logging on all DCs"
    Write-Host "3: Check Current Diagnostic Status on all DCs"
    Write-Host "4: Restart NTDS Service on all DCs (with safety checks)"
    Write-Host "Q: Quit"
    Write-Host "=============================================================="
}

function Test-LDAPPorts {
    param (
        [string]$DomainController,
        [switch]$Verbose
    )
    $ports = @{
        389  = "LDAP"
        636  = "LDAPS"
        3268 = "Global Catalog"
        3269 = "Global Catalog SSL"
    }
    
    $allPorts = $true
    foreach ($port in $ports.Keys) {
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        try {
            $tcpClient.Connect($DomainController, $port)
            if ($Verbose) {
                Write-Host "$($ports[$port]) (Port $port): Connected" -ForegroundColor Green
            }
        }
        catch {
            $allPorts = $false
            if ($Verbose) {
                Write-Host "$($ports[$port]) (Port $port): Failed - $_" -ForegroundColor Red
            }
        }
        finally {
            if ($tcpClient) { $tcpClient.Close() }
        }
    }
    return $allPorts
}

function Enable-LDAPDebug {
    param ($DomainController)
    Write-Host "`nEnabling LDAP Debug on: $DomainController" -ForegroundColor Cyan
    try {
        reg.exe add "\\$DomainController\HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics" /v "16 LDAP Interface Events" /t REG_DWORD /d 2 /f
        Write-Host "Success: LDAP Interface Events logging enabled on $DomainController" -ForegroundColor Green
        return $true
    }
    catch {
        Write-Host "Failed on $DomainController : $_" -ForegroundColor Red
        return $false
    }
}

function Disable-LDAPDebug {
    param ($DomainController)
    Write-Host "`nDisabling LDAP Debug on: $DomainController" -ForegroundColor Cyan
    try {
        reg.exe add "\\$DomainController\HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics" /v "16 LDAP Interface Events" /t REG_DWORD /d 0 /f
        Write-Host "Success: LDAP Interface Events logging disabled on $DomainController" -ForegroundColor Green
        return $true
    }
    catch {
        Write-Host "Failed on $DomainController : $_" -ForegroundColor Red
        return $false
    }
}

function Get-DebugStatus {
    param ($DomainController)
    Write-Host "`nChecking status on: $DomainController" -ForegroundColor Cyan
    try {
        Write-Host "Testing LDAP ports:" -ForegroundColor Yellow
        Test-LDAPPorts -DomainController $DomainController -Verbose

        $regValue = reg.exe query "\\$DomainController\HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics" /v "16 LDAP Interface Events"
        Write-Host "`nLDAP Interface Events Status:" -ForegroundColor Yellow
        Write-Host $regValue
        return $true
    }
    catch {
        Write-Host "Failed to query status on $DomainController : $_" -ForegroundColor Red
        return $false
    }
}

function Restart-NTDSService {
    param ($DomainController)
    Write-Host "`nProcessing NTDS restart for: $DomainController" -ForegroundColor Cyan
    
    # Check if other DCs are available first
    $otherDCs = Get-ADDomainController -Filter "Name -ne '$DomainController'" | Select-Object -ExpandProperty HostName
    $healthyDCs = $otherDCs | Where-Object { Test-LDAPPorts -DomainController $_ }
    
    if ($healthyDCs.Count -eq 0) {
        Write-Host "No other healthy DCs available - skipping restart of $DomainController" -ForegroundColor Red
        return $false
    }

    Write-Host "Testing initial LDAP connectivity on $DomainController" -ForegroundColor Yellow
    $preTestResult = Test-LDAPPorts -DomainController $DomainController -Verbose
    if (!$preTestResult) {
        Write-Host "Pre-restart port test failed on $DomainController - skipping restart" -ForegroundColor Red
        return $false
    }

    try {
        Write-Host "Stopping NTDS service..." -ForegroundColor Yellow
        $stopResult = sc.exe \\$DomainController stop ntds
        Start-Sleep -Seconds 10

        Write-Host "Starting NTDS service..." -ForegroundColor Yellow
        $startResult = sc.exe \\$DomainController start ntds
        Start-Sleep -Seconds 30
        
        Write-Host "Testing post-restart LDAP connectivity" -ForegroundColor Yellow
        $postTestResult = Test-LDAPPorts -DomainController $DomainController -Verbose
        
        if ($postTestResult) {
            Write-Host "NTDS restart completed successfully on $DomainController" -ForegroundColor Green
            return $true
        } else {
            Write-Host "Post-restart port test failed on $DomainController" -ForegroundColor Red
            return $false
        }
    }
    catch {
        Write-Host "Failed to restart NTDS on $DomainController : $_" -ForegroundColor Red
        return $false
    }
}

# Main script
try {
    Write-ToLog "Script started"
    $dcs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
    
    do {
        Show-Menu
        $choice = Read-Host "`nEnter choice"
        
        switch ($choice) {
            '1' { 
                foreach ($dc in $dcs) {
                    if (Enable-LDAPDebug -DomainController $dc) {
                        Write-ToLog "Enabled LDAP debug on $dc"
                    }
                }
                Read-Host "`nPress Enter to continue" 
            }
            '2' { 
                foreach ($dc in $dcs) {
                    if (Disable-LDAPDebug -DomainController $dc) {
                        Write-ToLog "Disabled LDAP debug on $dc"
                    }
                }
                Read-Host "`nPress Enter to continue" 
            }
            '3' { 
                foreach ($dc in $dcs) {
                    if (Get-DebugStatus -DomainController $dc) {
                        Write-ToLog "Checked debug status on $dc"
                    }
                }
                Read-Host "`nPress Enter to continue" 
            }
            '4' { 
                Write-Host "`nStarting NTDS service restart sequence..."
                foreach ($dc in $dcs) {
                    if (Restart-NTDSService -DomainController $dc) {
                        Write-ToLog "Successfully restarted NTDS on $dc"
                        Write-Host "Waiting 60 seconds before next DC..." -ForegroundColor Yellow
                        Start-Sleep -Seconds 60
                    } else {
                        Write-ToLog "Failed to restart NTDS on $dc"
                    }
                }
                Read-Host "`nPress Enter to continue" 
            }
            'Q' { Write-ToLog "Script ended"; return }
            Default { Write-Host "Invalid choice" -ForegroundColor Red }
        }
    } until ($choice -eq 'Q')
}
catch {
    Write-Warning "Critical Error: $_"
    Write-ToLog "Critical Error: $_"
    throw $_
}

When the logging flag has been enabled you will need to give that a moment to collect events then we need a script to extract those events from all the Domain Controllers, this will then in the example save those CSV's when valid data is found to a sub-folder called LDAP_Audit.

Script : InsecureLDAPBindings.ps1

# Requires -RunAsAdministrator
<#
.SYNOPSIS
    Queries all domain controllers for insecure LDAP binds and exports results to CSV files.
.DESCRIPTION
    Exports CSVs containing all Unsigned and Clear-text LDAP binds made to each domain controller
    by extracting Event 2889 from the "Directory Services" event log.
.PARAMETER Hours
    Number of hours to look back for events. Default is 24.
.PARAMETER OutputPath
    Path where the CSV files will be stored. Defaults to ".\LDAP_Audit"
#>

[CmdletBinding()]
param (
    [Parameter(Mandatory=$false)]
    [Int]$Hours = 24,
    
    [Parameter(Mandatory=$false)]
    [String]$OutputPath = ".\LDAP_Audit"
)

# Import required module
Import-Module ActiveDirectory

# Create output directory if it doesn't exist
if (!(Test-Path $OutputPath)) {
    New-Item -ItemType Directory -Path $OutputPath | Out-Null
    Write-Verbose "Created output directory: $OutputPath"
}

function Get-InsecureLDAPBinds {
    param (
        [Parameter(Mandatory=$true)]
        [String]$ComputerName
    )
    
    try {
        Write-Verbose "Testing connection to $ComputerName..."
        if (!(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)) {
            throw "Unable to connect to $ComputerName"
        }
        
        Write-Verbose "Querying events from $ComputerName..."
        $Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable @{
            Logname='Directory Service'
            Id=2889
            StartTime=(Get-Date).AddHours("-$Hours")
        } -ErrorAction Stop
        
        # Create an Array to hold our returned values
        $InsecureLDAPBinds = @()
        
        # Process each event
        foreach ($Event in $Events) {
            $eventXML = [xml]$Event.ToXml()
            
            # Extract client information
            $Client = ($eventXML.event.EventData.Data[0])
            $IPAddress = $Client.SubString(0,$Client.LastIndexOf(":")) # Accommodates IPV6 Addresses
            $Port = $Client.SubString($Client.LastIndexOf(":")+1)
            $User = $eventXML.event.EventData.Data[1]
            
            # Determine bind type
            Switch ($eventXML.event.EventData.Data[2]) {
                0 {$BindType = "Unsigned"}
                1 {$BindType = "Simple"}
            }
            
            # Create and populate row object
            $Row = [PSCustomObject]@{
                TimeCreated = $Event.TimeCreated
                DomainController = $ComputerName
                IPAddress = $IPAddress
                Port = $Port
                User = $User
                BindType = $BindType
            }
            
            # Add the row to our array
            $InsecureLDAPBinds += $Row
        }
        
        return $InsecureLDAPBinds
    }
    catch {
        Write-Warning "Error processing $ComputerName : $_"
        return $null
    }
}

# Main script execution
try {
    Write-Host "Starting insecure LDAP bind analysis..." -ForegroundColor Cyan
    
    # Get all domain controllers
    $DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
    Write-Host "Found $($DCs.Count) domain controllers" -ForegroundColor Cyan
    
    # Process each DC
    foreach ($DC in $DCs) {
        Write-Host "`nProcessing DC: $DC" -ForegroundColor Green
        
        $DCName = $DC.Split('.')[0] # Extract DC name without domain
        $OutputFile = Join-Path $OutputPath "$DCName`_InsecureLDAPBinds.csv"
        
        $Results = Get-InsecureLDAPBinds -ComputerName $DC
        
        if ($Results) {
            $Results | Export-Csv -Path $OutputFile -NoTypeInformation
            Write-Host "Exported $($Results.Count) records to $OutputFile" -ForegroundColor Yellow
        }
        else {
            Write-Host "No insecure LDAP binds found for $DC" -ForegroundColor Yellow
        }
    }
    
    Write-Host "`nAnalysis complete. Results can be found in: $OutputPath" -ForegroundColor Cyan
}
catch {
    Write-Error "Critical Error: $_"
    throw $_
}

This will then export log files that contain all the Domain Controllers with all the "simple binds" over LDAP (not secured) which are detected in the event log as you can see below:



This will give us a rather unhelpful list that only includes the IP and the username of the bind activity:

"TimeCreated","DomainController","IPAddress","Port","User","BindType"
"15/01/2025 08:05:39","beardc1.bear.local","10.11.55.299","63352","bear\simple.bind","Simple"
"15/01/2025 08:05:39","beardc1.bear.local","10.11.55.299","63351","bear\simple.bind","Simple"
"15/01/2025 08:05:39","beardc1.bear.local","10.11.55.299","63350","bear\simple.bind","Simple"
"15/01/2025 08:05:39","beardc1.bear.local","10.11.55.299","63349","bear\simple.bind","Simple"

Reporting on the data (Devices)

You now need the  "IPAddress" resolved to a hostname which can be done with DNS if your reverse zones are setup and working as they should be, if that the case we can use:

nslookup -a <ipaddress>

This should then return the hostname, if this returns the IP address then you do not have a valid reverse DNS zone to resolve that name, so that is this example cannot be used.

We need the hostname to get more information like, Computer Description and the OU from Active Directory so the plan here is all the servers use RDP, so we will connect to that service and extract the name of the server from the certificate this works like this:
  1. Checks if port 3389 (RDP) is open with a 1-second timeout
  2. If open, initiates an SSL connection to get the certificate
  3. Extracts the hostname from the certificate's Common Name (CN) field
  4. Properly disposes of all network resources after use
This should then give a more complete list where RDP is enabled and accessible from the server being used for the scripting.

This will then output a CSV file with the following fields as below:

"IP address","Hostname","Organizational unit","Computer description"

Script : OutlineDevices.ps1

# Error handling preference
$ErrorActionPreference = "Continue"

# Function to normalize IP address
function Normalize-IPAddress {
    param([string]$IPAddress)
    try {
        # Remove any 'vip-' prefix and replace dashes with dots
        $cleanIP = $IPAddress -replace 'vip-', '' -replace '-', '.'
        
        # Validate IP format
        $ipObj = [System.Net.IPAddress]::Parse($cleanIP)
        return $ipObj.ToString()
    }
    catch {
        Write-Warning "Invalid IP address format: $IPAddress"
        return $null
    }
}

# Function to check RDP and get hostname from certificate
function Get-RDPHostname {
    param([string]$IPAddress)
    
    try {
        # First check if port 3389 is open
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        $asyncResult = $tcpClient.BeginConnect($IPAddress, 3389, $null, $null)
        $wait = $asyncResult.AsyncWaitHandle.WaitOne(1000) # 1 second timeout
        
        if ($wait) {
            try {
                $tcpClient.EndConnect($asyncResult)
                # Port is open, now get the certificate
                $ssl = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, {$true})
                $ssl.AuthenticateAsClient($IPAddress)
                
                $cert = $ssl.RemoteCertificate
                if ($cert) {
                    # Extract hostname from certificate subject
                    $subject = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($cert).Subject
                    if ($subject -match "CN=([^,]+)") {
                        return $matches[1]
                    }
                }
            }
            finally {
                if ($ssl) { $ssl.Dispose() }
                $tcpClient.Close()
            }
        }
        else {
            Write-Verbose "RDP port not accessible on $IPAddress"
            return $null
        }
    }
    catch {
        Write-Warning "Could not get certificate from $IPAddress : $_"
        return $null
    }
    finally {
        if ($tcpClient) { $tcpClient.Dispose() }
    }
    
    return $null
}

# Function to get AD computer information
function Get-ADComputerInfo {
    param([string]$ComputerName)
    try {
        $computer = Get-ADComputer -Identity $ComputerName -Properties Description, DistinguishedName
        return @{
            OU = ($computer.DistinguishedName -split ',', 2)[1]
            Description = if ($computer.Description) { $computer.Description } else { "No Description" }
        }
    }
    catch {
        Write-Warning "Could not retrieve AD information for computer: $ComputerName"
        return @{
            OU = "Not Found"
            Description = "Not Found"
        }
    }
}

# Initialize collections
$uniqueIPs = @{}
$results = @()

# First pass: Collect and normalize all IPs
Write-Host "Phase 1: Collecting and normalizing IP addresses..."
try {
    Write-Host "Searching for CSV files in LDAP_Audit folder and all subfolders..."
    $csvFiles = Get-ChildItem -Path ".\LDAP_Audit" -Filter "*.csv" -Recurse
    
    Write-Host "Found $($csvFiles.Count) CSV files:"
    $csvFiles | ForEach-Object {
        Write-Host "  - $($_.FullName)"
    }

    if ($csvFiles.Count -eq 0) {
        Write-Warning "No CSV files found in the LDAP_Audit directory"
        exit
    }

    foreach ($file in $csvFiles) {
        Write-Host "Reading file: $($file.FullName)"
        
        try {
            $csvData = Import-Csv -Path $file.FullName
            
            if ($null -eq $csvData[0].IPAddress) {
                Write-Warning "File $($file.Name) does not contain IPAddress column. Skipping..."
                continue
            }

            $csvData | ForEach-Object {
                $ip = $_.IPAddress
                if (![string]::IsNullOrWhiteSpace($ip)) {
                    $normalizedIP = Normalize-IPAddress -IPAddress $ip
                    if ($normalizedIP) {
                        $uniqueIPs[$normalizedIP] = $true
                    }
                }
            }
        }
        catch {
            Write-Warning "Error processing file $($file.Name): $_"
            continue
        }
    }
}
catch {
    Write-Error "Critical error occurred during IP collection: $_"
    exit
}

# Second pass: Process unique IPs
Write-Host "`nPhase 2: Processing $($uniqueIPs.Count) unique IP addresses..."
foreach ($ip in $uniqueIPs.Keys) {
    Write-Host "Processing IP: $ip"
    $hostname = Get-RDPHostname -IPAddress $ip
    
    if ($hostname) {
        $computerName = $hostname.Split('.')[0]
        $adInfo = Get-ADComputerInfo -ComputerName $computerName

        $results += [PSCustomObject]@{
            'IP address' = $ip
            'Hostname' = $hostname
            'Organizational unit' = $adInfo.OU
            'Computer description' = $adInfo.Description
        }
    }
    else {
        $results += [PSCustomObject]@{
            'IP address' = $ip
            'Hostname' = "Not Found"
            'Organizational unit' = "Not Found"
            'Computer description' = "Not Found"
        }
    }
}

# Output results
if ($results.Count -gt 0) {
    Write-Host "`nProcessing complete. Found $($results.Count) unique IP addresses."
    $results | Format-Table 'IP address', 'Hostname', 'Organizational unit', 'Computer description' -AutoSize
    
    # Export to CSV
    $outputPath = ".\LDAP_Audit_Results_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
    $results | Export-Csv -Path $outputPath -NoTypeInformation
    Write-Host "Results exported to: $outputPath"
}
else {
    Write-Warning "No valid results found to display"
}

Reporting on data (Users)

If you wish to report on the users then the CSV files will have lots of users over and over again, so we need to extract the "User" field recursively from all those log files and present that in a text file that contains a list of unique usernames that are affected by these "simple bind" operations, here you can see we have 981 unique users.


This information can also give you a clue as to what that service is as well from depending on the data in that report.

Script : OutlineUser.ps1

# Set the path to your LDAP_Audit folder
$auditPath = ".\LDAP_Audit"

# Create an empty array to store unique users
$uniqueUsers = @()

# Get all CSV files recursively
$csvFiles = Get-ChildItem -Path $auditPath -Filter "*.csv" -Recurse

foreach ($file in $csvFiles) {
    Write-Host "Processing file: $($file.FullName)"
    
    try {
        # Import CSV content
        $csvContent = Import-Csv -Path $file.FullName
        
        # Process each row in the CSV
        foreach ($row in $csvContent) {
            # Assuming there's a user-related column - adjust property name as needed
            # Common names might be 'User', 'Username', 'SamAccountName', etc.
            $userProperty = $row.PSObject.Properties | Where-Object { 
                $_.Name -match 'user|username|samaccountname|cn|displayname' 
            } | Select-Object -First 1

            if ($userProperty) {
                $username = $userProperty.Value
                
                # Skip empty usernames and add unique ones to the array
                if (![string]::IsNullOrWhiteSpace($username) -and $uniqueUsers -notcontains $username) {
                    $uniqueUsers += $username
                }
            }
        }
    }
    catch {
        Write-Warning "Error processing file $($file.Name): $_"
    }
}

# Sort the users alphabetically
$uniqueUsers = $uniqueUsers | Sort-Object

# Export just the usernames to a file
$exportPath = ".\LDAP_Audit_Users_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
$uniqueUsers | Out-File -FilePath $exportPath

Write-Host "`nTotal unique users found: $($uniqueUsers.Count)"
Write-Host "User list has been exported to: $exportPath"
Previous Post Next Post

نموذج الاتصال