Category Archives: Powershell

Finding the Source of Active Directory Account Lockouts with PowerShell

Finding the Source of Active Directory Account Lockouts with PowerShell

As organizations continue their journey towards Microsoft 365, Entra ID, and cloud-first identity management, traditional on-premises Active Directory environments are becoming less common. Many organizations today operate in hybrid environments where only a handful of services still rely on their local Active Directory.

Despite this shift, one classic problem hasn’t disappeared:

Users still get locked out.

And when they do, finding why and where the lockout originated can still be surprisingly frustrating.

The Challenge

Account lockouts are typically caused by:

  • Stale credentials stored in Windows Credential Manager
  • Scheduled tasks running with old passwords
  • Windows services using outdated service accounts
  • Mapped drives
  • Mobile devices
  • Legacy applications
  • Cached credentials on remote servers

Although Active Directory records lockout events, they’re stored in the Security log on the Domain Controller that processed the lockout. In environments with multiple Domain Controllers, tracking down the relevant event often means logging into several servers and searching manually.

That’s exactly the problem this PowerShell script aims to solve.

What the Script Does

Instead of checking each Domain Controller individually, the script:

  1. Prompts for a user’s sAMAccountName.
  2. Lets you define how far back in time to search (default is 24 hours).
  3. Automatically discovers every Domain Controller in the domain.
  4. Queries the Security log on each Domain Controller for Event ID 4740 (Account Lockout).
  5. Parses the event XML to extract the important fields.
  6. Displays all matching lockout events in a single consolidated table.
  7. Optionally exports the results to CSV for documentation or further investigation.

The result is a quick overview showing:

  • When the lockout happened
  • Which Domain Controller processed it
  • Which account was locked
  • The computer that triggered the lockout
  • The domain involved
  • Which security principal recorded the event

How It Works

The script leverages the Active Directory PowerShell module to discover the current domain and all available Domain Controllers.

For each Domain Controller, it remotely queries the Windows Security event log using:

Get-WinEvent

filtered for:

  • Security Log
  • Event ID 4740
  • Configurable time window

Rather than relying on localized event messages, the script converts each event to XML and extracts the structured EventData fields. This makes the script language-independent and significantly more reliable across different Windows installations.

Only events where the TargetUserName matches the supplied account are returned.

If a Domain Controller cannot be contacted or the Security log cannot be read, the script continues processing the remaining servers and reports the error instead of terminating.

Why This Approach?

Many existing lockout troubleshooting scripts rely on querying a single Domain Controller or using the lockoutstatus.exe utility.

While those tools certainly have their place, they often don’t immediately tell you which device actually caused the lockout.

By reading Event ID 4740 directly from every Domain Controller, this script provides the information administrators usually need first:

Which computer is repeatedly locking out my user?

In many cases, that single piece of information dramatically reduces troubleshooting time.

Example Output

The resulting output is intentionally simple:

TimeDomain ControllerLocked AccountCaller Computer
2026-07-10 08:14DC01jsmithLAPTOP-123
2026-07-10 08:14DC02jsmithLAPTOP-123

Optionally, the data can also be exported to CSV for reporting or attaching to support cases.

Final Thoughts

Even as more organizations embrace cloud identity, hybrid Active Directory environments will likely remain with us for years to come. And as long as on-premises Active Directory exists, account lockouts will continue to generate support tickets.

Having a simple script that automatically searches every Domain Controller and pinpoints the originating device can save valuable troubleshooting time and eliminate much of the guesswork.

Sometimes, the simplest tools end up being the ones you reach for the most.

#Need to run on a DC's, or a machine with PowerShell module ActiveDirectory installed.
#The concurrent user, need access to security logs on each domain conntroller from where the script is running.

Import-Module ActiveDirectory -ErrorAction Stop

$AccountName = Read-Host "Enter the AD account name / sAMAccountName"
$HoursBack = Read-Host "How many hours back would you like to search? Default is 24"

if ([string]::IsNullOrWhiteSpace($HoursBack)) {
    $HoursBack = 24
}

$StartTime = (Get-Date).AddHours(-[int]$HoursBack)

$Domain = Get-ADDomain
$DCs = Get-ADDomainController -Filter * | Sort-Object HostName

Write-Host ""
Write-Host "Domain: $($Domain.DNSRoot)"
Write-Host "Searching for lockout events for '$AccountName' since $StartTime"
Write-Host "Domain Controllers found: $($DCs.Count)"
Write-Host ""

$Results = foreach ($DC in $DCs) {
    Write-Host "Checking $($DC.HostName)..."

    try {
        $Events = Get-WinEvent -ComputerName $DC.HostName -FilterHashtable @{
            LogName   = 'Security'
            Id        = 4740
            StartTime = $StartTime
        } -ErrorAction Stop

        foreach ($Event in $Events) {
            $Xml = [xml]$Event.ToXml()

            $EventData = @{}
            foreach ($Data in $Xml.Event.EventData.Data) {
                $EventData[$Data.Name] = $Data.'#text'
            }

            if ($EventData['TargetUserName'] -ieq $AccountName) {
                [PSCustomObject]@{
                    TimeCreated        = $Event.TimeCreated
                    DomainController   = $DC.HostName
                    LockedOutAccount   = $EventData['TargetUserName']
                    CallerComputerName = $EventData['CallerComputerName']
                    TargetDomainName   = $EventData['TargetDomainName']
                    SubjectUserName    = $EventData['SubjectUserName']
                    EventId            = $Event.Id
                }
            }
        }
    }
    catch {
        if ($_.Exception.Message -notlike "*No events were found*") {
            [PSCustomObject]@{
                TimeCreated        = $null
                DomainController   = $DC.HostName
                LockedOutAccount   = $AccountName
                CallerComputerName = "Unable to read the Security log"
                TargetDomainName   = $null
                SubjectUserName    = $_.Exception.Message
                EventId            = $null
            }
        }
    }
}

Write-Host ""
Write-Host "Results:"
Write-Host ""

$Results |
    Sort-Object TimeCreated -Descending |
    Format-Table -AutoSize

$Export = Read-Host "Would you like to export the results to CSV? (Y/N)"

if ($Export -match '^[Yy]') {
    $Path = ".\AD-Lockout-$AccountName-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"

    $Results |
        Sort-Object TimeCreated -Descending |
        Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8

    Write-Host "Results exported to: $Path"
}

Add a User to All AD Groups in an OU Using PowerShell

Managing Active Directory permissions can sometimes involve giving a user access to a large number of security groups — for example during onboarding, when changing someone’s role, or when granting temporary elevated access.
Doing this manually through Active Directory Users & Computers is time-consuming and error-prone.

PowerShell provides a clean and efficient way to automate the process.

In this post, I’ll show a small and elegant PowerShell script that:

  • Retrieves all groups inside a specific OU
  • Adds a chosen user as a member of each group
  • Outputs both successes and errors, so you know exactly what happened
Import-Module ActiveDirectory

$OU = "OU=Grupper,OU=Company,DC=domain,DC=local"
$User = "debie2dal"

Get-ADGroup -SearchBase $OU -Filter * | ForEach-Object {
    try {
        Add-ADGroupMember -Identity $_ -Members $User -ErrorAction Stop
        Write-Host "OK: Added $User in $($_.Name)"
    }
    catch {
        Write-Host "Error in group $($_.Name): $($_.Exception.Message)"
    }
}

When This Script Is Useful

This simple script is extremely helpful in scenarios such as:

  • New employee onboarding
    Add a full set of role-based permissions in seconds.
  • Role changes
    Assign users to new group structures effortlessly.
  • Service accounts
    Grant access to multiple groups quickly and repeatably.
  • Environment cleanup
    Identify or replicate required group memberships.
  • Bulk operations
    Especially during system migrations or reorganisations.

Final Thoughts

PowerShell is a powerful tool for automating Active Directory tasks — and this small script can save a lot of time when managing group memberships across large environments. It reduces manual effort, eliminates repetitive clicking, and ensures consistent access assignments.

If you need help customizing this script for your organization or extending it with additional capabilities, feel free to reach out.

VMware and Powershell

Broadcom the owner and vendor of VMWare have release a new powershell module, called VCF.PowerCLI instead of the vmware PowerCLI.
If you computer is online, you can installed it with the powershell command Install-Module

Install-Module -Name VCF.PowerCLI

If yours computer/server is not online, you can download it from another computer, and installed it though the manuel way, please look here: https://developer.broadcom.com/powercli/installation-guide

When upgradering from Vmware PowerCLI – Throubleshooting
If you allready have Vmware PowerCLI installed on the computer, you may encounter the following error when trying to install VCF.PowerCLI

PackageManagement\Install-Package : The following commands are already available on this system

This happens because some of the commandlets which are part of the PowerCLI SDK have been moved to other modules. To resolve the conflict you need to add the AllowClobber parameter to Install-Module

Install-Module -Name VCF.PowerCLI -AllowClobber

AllowClobber overrides warning messages about installation conflicts about existing commands on a computer. Overwrites existing commands that have the same name as commands being installed by a module. AllowClobber and Force can be used together in an Install-Module command.

Problems in connection to a Webservice from Windows Server 2016 / 2019

Today, I have this issue, that i could not connect to a webservices, that are hosted by the Danish Tax Service, the URL where: https://emcstest.skat.dk
I got this warning:

Invoke-WebRequest : The request was aborted: Could not create SSL/TLS secure channel.

When running these commands

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$WebResponse = Invoke-WebRequest "https://emcstest.skat.dk"

No matter what I did. But final I tested the webpage against SSLLabs
https://www.ssllabs.com/ssltest/analyze.html?d=emcstest.skat.dk
Found these ciphers

I then run these commands:

Enable-TlsCipherSuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Enable-TlsCipherSuite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
Enable-TlsCipherSuite TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
Enable-TlsCipherSuite TLS_AES_256_GCM_SHA384
Enable-TlsCipherSuite TLS_AES_128_GCM_SHA256
Enable-TlsCipherSuite TLS_CHACHA20_POLY1305_SHA256
Enable-TlsCipherSuite TLS_AES_256_GCM_SHA384

And finally I where able to access the webpage though powershell.

Get Certificate from remote computers

If you need to get all certificates, from a list of remote computers, you can use this script:

$Servers = "kennethdalbjerg-dc01",
           "kennethdalbjerg-dc02",
           "kennethdalbjerg-exch01",
           "kennethdalbjerg-fs01"
 
  $Results = @()
  $Results = Invoke-Command -cn $Servers {
          $Certs = @{} | Select Certificate,Expired
          $Cert = Get-ChildItem Cert:\LocalMachine\My
          If($Cert){
              $Certs.Certificate = $Cert.subject
              $Certs.Expired = $Cert.NotAfter
          }
          Else{
              $Certs.Certificate = " - "
              $Certs.Expired = " - "
          }
          $Certs
  } | Select-Object @{n='ServerName';e={$_.pscomputername}},Certificate,Expired
 
  #Display results in console
  $Results | Sort-Object Expired -Descending
 
  #Save results to CSV file
  $Results | Sort-Object Expired -Descending | Export-Csv -Path C:\users\$env:username\desktop\cert_results.csv -NoTypeInformation -Force
 
  #Open results in new window
  $Results | Sort-Object Expired -Descending | Out-GridView -Title "Final results"

Found groups that a 365 contacts is member of

If you need to found out whitch groups (mailling groups) a contact is member of in Microsoft 365, then you case use these powershell commands:

$getsessions = Get-PSSession | Select-Object -Property State, Name
$isconnected = (@($getsessions) -like '@{State=Opened; Name=ExchangeOnlineInternalSession*').Count -gt 0
If ($isconnected -ne "True") {
Connect-ExchangeOnline
}

$dn = (Get-MailContact "[email protected]").DistinguishedName
Get-Recipient -Filter "Members -eq '$dn'"

Replace [email protected] with yours mail address.

Passler PRTG Windows update Probe says missing updates, but none in windows update

You can run this commands to see what updates, that PRTG windows update probes is warning about.

$searcher = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher();$searcher.Search("Type='Software'").Updates | Sort-Object -Property Dates -Descending | ft -autosize IsInstalled, MsrcSeverity,title

Installation of New Exchange server

If you ever have installed a new Exchange server in an Active Directory orgainisation that allready have an Exchange server (That’s not so often anymore), you may have discovered that users get Certificate warning in Outlook, unto you have changed the URL’s to match the certificates.

But I found this script on Github: https://gist.github.com/kevinblumenfeld/31b03bf439b1f94f460eb754ef74120e

It copy the settings of all IIS url’s to the new Exchange server, when it found it in Active Directory.
So run this script before the start of the installation, and you will not receive the certificate warnings after the installation.

Compare two folders with Powershell

This script checks first that folders exist, then that filename are the same, and then that file Hash is the same

#$folder1 = "C:\Temp\test"
#$folder2 = "C:\Temp\test2"


if ((Test-Path -Path $folder1) -and (Test-Path -Path $folder2)) {
    echo "The folders exist"
    echo "Folder1: $folder1"
    echo "Folder2: $folder2"
    $sourceFiles = Get-ChildItem $folder1 -Recurse
    $destFiles = Get-ChildItem $folder2 -Recurse
    if (Compare-Object $sourceFiles.Name $destFiles.Name) {
        echo "The folders is not the same"
    } else {
        echo "Check of the folders show us that there have the same content - OK"
        $SourceDocs = Get-ChildItem –Path $folder1 -Recurse | foreach  {Get-FileHash –Path $_.FullName}
        $DestDocs = Get-ChildItem –Path $folder2 -Recurse | foreach  {Get-FileHash –Path $_.FullName}
        if ($SourceDocs.Hash -ne $destDocs.Hash) {
            echo "There are difference in the the files"
            echo "folder1 Hash: $SourceDocs.hash"
            echo "folder2 Hash: $DestDocs.hash"
        } else  {
            echo "The folders are the same!"
        }
    }
} else {
    Echo "One of the folders or both, dosn't exist"
}