PowerShell Active Directory

Active Directory administration with PowerShell.

Module Setup and Prerequisites

# Check if AD module is available
Get-Module -ListAvailable ActiveDirectory

# Import module (auto-imports on first cmdlet use in PS 3.0+)
Import-Module ActiveDirectory

# Install RSAT tools (Windows 10/11 workstation)
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0

# Install RSAT on Windows Server
Install-WindowsFeature -Name RSAT-AD-Tools -IncludeAllSubFeature

# Check AD connection
Get-ADDomain

# View domain info
Get-ADDomain | Select-Object DNSRoot, Forest, DomainMode, PDCEmulator

# View forest info
Get-ADForest | Select-Object Name, ForestMode, DomainNamingMaster, SchemaMaster

# Connect to specific DC
Get-ADUser -Identity AdminErosado -Server home-dc01.inside.domusdigitalis.dev

# Get current AD context
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

# Get all domain controllers
Get-ADDomainController -Filter * |
    Select-Object Name, IPv4Address, Site, OperatingSystem, IsGlobalCatalog

# Get closest domain controller
Get-ADDomainController -Discover -NextClosestSite

Important Paths:

  • Domain - DC=inside,DC=domusdigitalis,DC=dev

  • Users OU - OU=Users,DC=inside,DC=domusdigitalis,DC=dev

  • Computers OU - OU=Computers,DC=inside,DC=domusdigitalis,DC=dev

User Management

# Get single user (by SamAccountName)
Get-ADUser -Identity AdminErosado

# Get user with all properties
Get-ADUser -Identity AdminErosado -Properties *

# Get user with specific properties
Get-ADUser -Identity AdminErosado -Properties DisplayName, Mail, MemberOf, LastLogonDate, PasswordLastSet

# Find user by email
Get-ADUser -Filter { EmailAddress -eq "admin@domusdigitalis.dev" }

# Find user by display name (wildcards)
Get-ADUser -Filter { DisplayName -like "*Rosado*" }

# All enabled users
Get-ADUser -Filter { Enabled -eq $true } | Measure-Object

# All disabled users
Get-ADUser -Filter { Enabled -eq $false } | Select-Object SamAccountName, Name, DistinguishedName

# Users who haven't logged in for 90 days
$threshold = (Get-Date).AddDays(-90)
Get-ADUser -Filter { LastLogonDate -lt $threshold } -Properties LastLogonDate |
    Select-Object SamAccountName, LastLogonDate |
    Sort-Object LastLogonDate

# Users with expired passwords
Get-ADUser -Filter { PasswordExpired -eq $true } -Properties PasswordExpired, PasswordLastSet |
    Select-Object SamAccountName, PasswordLastSet

# Users created in last 7 days
$weekAgo = (Get-Date).AddDays(-7)
Get-ADUser -Filter { Created -gt $weekAgo } -Properties Created |
    Select-Object SamAccountName, Created |
    Sort-Object Created -Descending

# Export all users to CSV
Get-ADUser -Filter * -Properties DisplayName, Mail, Department, Title, Manager |
    Select-Object SamAccountName, DisplayName, Mail, Department, Title,
        @{N='Manager';E={(Get-ADUser $_.Manager -ErrorAction SilentlyContinue).Name}} |
    Export-Csv -Path C:\Reports\AllUsers.csv -NoTypeInformation

# Search in specific OU
Get-ADUser -Filter * -SearchBase "OU=IT,OU=Users,DC=inside,DC=domusdigitalis,DC=dev"

# Search with LDAP filter (more complex queries)
Get-ADUser -LDAPFilter "(&(objectCategory=person)(objectClass=user)(memberOf=CN=Domain Admins,CN=Users,DC=inside,DC=domusdigitalis,DC=dev))"

Create and Modify Users

# Create basic user
New-ADUser -Name "John Smith" -SamAccountName "jsmith" -UserPrincipalName "jsmith@inside.domusdigitalis.dev"

# Create user with full details
$securePassword = ConvertTo-SecureString "TempP@ssw0rd!" -AsPlainText -Force
New-ADUser -Name "John Smith" `
    -GivenName "John" `
    -Surname "Smith" `
    -SamAccountName "jsmith" `
    -UserPrincipalName "jsmith@inside.domusdigitalis.dev" `
    -EmailAddress "jsmith@domusdigitalis.dev" `
    -Path "OU=IT,OU=Users,DC=inside,DC=domusdigitalis,DC=dev" `
    -AccountPassword $securePassword `
    -ChangePasswordAtLogon $true `
    -Enabled $true `
    -Department "IT" `
    -Title "Systems Engineer" `
    -Office "HQ" `
    -Description "IT Staff"

# Create user from hash table (cleaner for automation)
$userParams = @{
    Name              = "Jane Doe"
    SamAccountName    = "jdoe"
    UserPrincipalName = "jdoe@inside.domusdigitalis.dev"
    GivenName         = "Jane"
    Surname           = "Doe"
    EmailAddress      = "jdoe@domusdigitalis.dev"
    Path              = "OU=Users,DC=inside,DC=domusdigitalis,DC=dev"
    AccountPassword   = $securePassword
    Enabled           = $true
}
New-ADUser @userParams

# Bulk create users from CSV
# CSV format: FirstName,LastName,Username,Department,Title
Import-Csv C:\NewUsers.csv | ForEach-Object {
    $name = "$($_.FirstName) $($_.LastName)"
    $sam = $_.Username
    $upn = "$sam@inside.domusdigitalis.dev"

    New-ADUser -Name $name -SamAccountName $sam -UserPrincipalName $upn `
        -GivenName $_.FirstName -Surname $_.LastName `
        -Department $_.Department -Title $_.Title `
        -Path "OU=Users,DC=inside,DC=domusdigitalis,DC=dev" `
        -AccountPassword $securePassword `
        -ChangePasswordAtLogon $true `
        -Enabled $true

    Write-Host "Created: $sam"
}

# Modify user attributes
Set-ADUser -Identity jsmith -Title "Senior Systems Engineer" -Department "Infrastructure"

# Modify multiple attributes
Set-ADUser -Identity jsmith -Replace @{
    title       = "Senior Systems Engineer"
    department  = "Infrastructure"
    telephoneNumber = "+1-555-1234"
    description = "Infrastructure Team Lead"
}

# Add attribute value
Set-ADUser -Identity jsmith -Add @{ proxyAddresses = "smtp:john@company.com" }

# Remove attribute value
Set-ADUser -Identity jsmith -Remove @{ proxyAddresses = "smtp:old@company.com" }

# Clear attribute
Set-ADUser -Identity jsmith -Clear description

# Enable/disable user
Disable-ADAccount -Identity jsmith
Enable-ADAccount -Identity jsmith

# Unlock account
Unlock-ADAccount -Identity jsmith

# Reset password
$newPassword = ConvertTo-SecureString "NewP@ssw0rd!" -AsPlainText -Force
Set-ADAccountPassword -Identity jsmith -NewPassword $newPassword -Reset

# Force password change at next logon
Set-ADUser -Identity jsmith -ChangePasswordAtLogon $true

# Move user to different OU
Move-ADObject -Identity "CN=John Smith,OU=IT,OU=Users,DC=inside,DC=domusdigitalis,DC=dev" `
    -TargetPath "OU=Managers,OU=Users,DC=inside,DC=domusdigitalis,DC=dev"

# Rename user
Rename-ADObject -Identity "CN=John Smith,OU=Users,DC=inside,DC=domusdigitalis,DC=dev" -NewName "John A. Smith"

# Delete user
Remove-ADUser -Identity jsmith -Confirm:$false

Group Management

# Get group info
Get-ADGroup -Identity "Domain Admins"

# Get group with all properties
Get-ADGroup -Identity "Domain Admins" -Properties *

# Get group members
Get-ADGroupMember -Identity "Domain Admins"

# Get group members recursively (nested groups)
Get-ADGroupMember -Identity "Domain Admins" -Recursive |
    Select-Object Name, SamAccountName, objectClass

# Get all groups a user belongs to
Get-ADUser -Identity AdminErosado -Properties MemberOf |
    Select-Object -ExpandProperty MemberOf |
    ForEach-Object { Get-ADGroup $_ } |
    Select-Object Name, GroupCategory, GroupScope

# More efficient way to get user's groups
(Get-ADUser -Identity AdminErosado -Properties MemberOf).MemberOf |
    Get-ADGroup |
    Select-Object Name, GroupCategory, GroupScope |
    Sort-Object Name

# Get nested group memberships (recursive)
Get-ADPrincipalGroupMembership -Identity AdminErosado |
    Select-Object Name, GroupCategory, GroupScope

# All security groups
Get-ADGroup -Filter { GroupCategory -eq 'Security' }

# All distribution groups
Get-ADGroup -Filter { GroupCategory -eq 'Distribution' }

# Find groups by name
Get-ADGroup -Filter { Name -like "*Admin*" }

# Find empty groups
Get-ADGroup -Filter * | Where-Object {
    (Get-ADGroupMember -Identity $_ -ErrorAction SilentlyContinue | Measure-Object).Count -eq 0
} | Select-Object Name, DistinguishedName

# Create security group
New-ADGroup -Name "IT-Admins" `
    -SamAccountName "IT-Admins" `
    -GroupCategory Security `
    -GroupScope Global `
    -Path "OU=Groups,DC=inside,DC=domusdigitalis,DC=dev" `
    -Description "IT Department Administrators"

# Create distribution group
New-ADGroup -Name "IT-Staff-DL" `
    -SamAccountName "IT-Staff-DL" `
    -GroupCategory Distribution `
    -GroupScope Universal `
    -Path "OU=Groups,DC=inside,DC=domusdigitalis,DC=dev" `
    -Description "IT Staff Distribution List"

# Add members to group
Add-ADGroupMember -Identity "IT-Admins" -Members AdminErosado, jsmith

# Add multiple members from array
$members = @("user1", "user2", "user3")
Add-ADGroupMember -Identity "IT-Admins" -Members $members

# Remove member from group
Remove-ADGroupMember -Identity "IT-Admins" -Members jsmith -Confirm:$false

# Replace all group members
Set-ADGroup -Identity "IT-Admins" -Clear member
Add-ADGroupMember -Identity "IT-Admins" -Members AdminErosado, newuser1

# Copy group membership from one user to another
Get-ADUser -Identity AdminErosado -Properties MemberOf |
    Select-Object -ExpandProperty MemberOf |
    ForEach-Object {
        Add-ADGroupMember -Identity $_ -Members newuser -ErrorAction SilentlyContinue
    }

# Compare group memberships between users
$user1Groups = (Get-ADUser -Identity user1 -Properties MemberOf).MemberOf | Get-ADGroup | Select-Object -ExpandProperty Name
$user2Groups = (Get-ADUser -Identity user2 -Properties MemberOf).MemberOf | Get-ADGroup | Select-Object -ExpandProperty Name

Compare-Object -ReferenceObject $user1Groups -DifferenceObject $user2Groups -IncludeEqual

# Delete group
Remove-ADGroup -Identity "OldGroup" -Confirm:$false

# Rename group
Rename-ADObject -Identity "CN=IT-Admins,OU=Groups,DC=inside,DC=domusdigitalis,DC=dev" -NewName "Infrastructure-Admins"

Group Scopes:

  • Domain Local - Can contain members from any domain, used for resource permissions

  • Global - Members from same domain only, used for organizing users

  • Universal - Members from any domain in forest, replicated to all GCs

Computer Management

# Get computer object
Get-ADComputer -Identity "workstation1"

# Get computer with properties
Get-ADComputer -Identity "home-dc01" -Properties * |
    Select-Object Name, OperatingSystem, OperatingSystemVersion, IPv4Address, LastLogonDate

# All computers
Get-ADComputer -Filter * | Measure-Object

# All Windows Server computers
Get-ADComputer -Filter { OperatingSystem -like "*Server*" } |
    Select-Object Name, OperatingSystem

# Computers that haven't logged in for 90 days
$threshold = (Get-Date).AddDays(-90)
Get-ADComputer -Filter { LastLogonDate -lt $threshold } -Properties LastLogonDate |
    Select-Object Name, LastLogonDate |
    Sort-Object LastLogonDate

# Computers with specific OS
Get-ADComputer -Filter { OperatingSystem -eq "Windows Server 2025 Datacenter" }

# Computers in specific OU
Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=inside,DC=domusdigitalis,DC=dev"

# Export computer inventory
Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion, LastLogonDate, IPv4Address |
    Select-Object Name, OperatingSystem, OperatingSystemVersion, LastLogonDate, IPv4Address |
    Export-Csv -Path C:\Reports\ComputerInventory.csv -NoTypeInformation

# Create computer account (pre-staging)
New-ADComputer -Name "newserver01" `
    -SamAccountName "newserver01" `
    -Path "OU=Servers,DC=inside,DC=domusdigitalis,DC=dev" `
    -Enabled $true `
    -Description "New application server"

# Allow specific group to join computer to domain
$computer = Get-ADComputer -Identity "newserver01"
$acl = Get-Acl "AD:$($computer.DistinguishedName)"
$group = Get-ADGroup "Server-Admins"
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $group.SID,
    "GenericAll",
    "Allow"
)
$acl.AddAccessRule($ace)
Set-Acl "AD:$($computer.DistinguishedName)" $acl

# Move computer to different OU
Move-ADObject -Identity "CN=newserver01,OU=Staging,DC=inside,DC=domusdigitalis,DC=dev" `
    -TargetPath "OU=Production,DC=inside,DC=domusdigitalis,DC=dev"

# Disable computer account
Disable-ADAccount -Identity "CN=oldserver,OU=Servers,DC=inside,DC=domusdigitalis,DC=dev"

# Reset computer account (rejoin required)
Reset-ADComputerMachineAccountPassword -Identity "workstation1"

# Delete computer account
Remove-ADComputer -Identity "oldserver" -Confirm:$false

# Find computers by name pattern
Get-ADComputer -Filter { Name -like "srv-*" }

# Get local admin group members (requires remoting)
Invoke-Command -ComputerName home-dc01 -ScriptBlock {
    Get-LocalGroupMember -Group "Administrators"
}

Organizational Units

# Get all OUs
Get-ADOrganizationalUnit -Filter * | Select-Object Name, DistinguishedName

# Get OUs in tree structure
Get-ADOrganizationalUnit -Filter * |
    Select-Object Name, DistinguishedName |
    Sort-Object DistinguishedName

# Get OU by name
Get-ADOrganizationalUnit -Filter { Name -eq "IT" }

# Get OU with properties
Get-ADOrganizationalUnit -Identity "OU=IT,DC=inside,DC=domusdigitalis,DC=dev" -Properties *

# Create OU
New-ADOrganizationalUnit -Name "Infrastructure" `
    -Path "OU=IT,DC=inside,DC=domusdigitalis,DC=dev" `
    -Description "Infrastructure Team Resources" `
    -ProtectedFromAccidentalDeletion $true

# Create nested OU structure
$basePath = "DC=inside,DC=domusdigitalis,DC=dev"
$structure = @(
    "OU=Company",
    "OU=IT,OU=Company",
    "OU=Users,OU=IT,OU=Company",
    "OU=Groups,OU=IT,OU=Company",
    "OU=Computers,OU=IT,OU=Company"
)

foreach ($ou in $structure) {
    $name = ($ou -split ",")[0] -replace "OU=", ""
    $parentPath = ($ou -split ",", 2)[1]
    if ($parentPath) {
        $fullParentPath = "$parentPath,$basePath"
    } else {
        $fullParentPath = $basePath
    }

    if (-not (Get-ADOrganizationalUnit -Filter { DistinguishedName -eq "$ou,$basePath" } -ErrorAction SilentlyContinue)) {
        New-ADOrganizationalUnit -Name $name -Path $fullParentPath
        Write-Host "Created: $ou"
    }
}

# Get objects in OU
Get-ADUser -Filter * -SearchBase "OU=IT,DC=inside,DC=domusdigitalis,DC=dev"
Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=inside,DC=domusdigitalis,DC=dev"
Get-ADGroup -Filter * -SearchBase "OU=Groups,DC=inside,DC=domusdigitalis,DC=dev"

# Count objects in each OU
Get-ADOrganizationalUnit -Filter * | ForEach-Object {
    $userCount = (Get-ADUser -Filter * -SearchBase $_.DistinguishedName -SearchScope OneLevel -ErrorAction SilentlyContinue | Measure-Object).Count
    $computerCount = (Get-ADComputer -Filter * -SearchBase $_.DistinguishedName -SearchScope OneLevel -ErrorAction SilentlyContinue | Measure-Object).Count

    [PSCustomObject]@{
        OU        = $_.Name
        Path      = $_.DistinguishedName
        Users     = $userCount
        Computers = $computerCount
    }
} | Format-Table

# Rename OU
Rename-ADObject -Identity "OU=OldName,DC=inside,DC=domusdigitalis,DC=dev" -NewName "NewName"

# Move OU
Move-ADObject -Identity "OU=Staging,DC=inside,DC=domusdigitalis,DC=dev" `
    -TargetPath "OU=Archive,DC=inside,DC=domusdigitalis,DC=dev"

# Delete OU (must remove protection first)
Set-ADOrganizationalUnit -Identity "OU=Empty,DC=inside,DC=domusdigitalis,DC=dev" `
    -ProtectedFromAccidentalDeletion $false
Remove-ADOrganizationalUnit -Identity "OU=Empty,DC=inside,DC=domusdigitalis,DC=dev" -Confirm:$false

# Recursive delete (all objects inside)
Get-ADOrganizationalUnit -Identity "OU=Deprecated,DC=inside,DC=domusdigitalis,DC=dev" |
    Set-ADOrganizationalUnit -ProtectedFromAccidentalDeletion $false -PassThru |
    Remove-ADOrganizationalUnit -Recursive -Confirm:$false

Group Policy Objects

# Import Group Policy module
Import-Module GroupPolicy

# List all GPOs
Get-GPO -All | Select-Object DisplayName, GpoStatus, CreationTime, ModificationTime

# Get specific GPO
Get-GPO -Name "Default Domain Policy"

# Get GPO with details
Get-GPO -Name "Default Domain Policy" | Format-List *

# GPOs linked to specific OU
Get-ADOrganizationalUnit -Identity "OU=Workstations,DC=inside,DC=domusdigitalis,DC=dev" |
    Get-GPInheritance |
    Select-Object -ExpandProperty GpoLinks

# Get GPO links for entire domain
Get-ADOrganizationalUnit -Filter * |
    ForEach-Object {
        $inheritance = Get-GPInheritance -Target $_.DistinguishedName
        foreach ($link in $inheritance.GpoLinks) {
            [PSCustomObject]@{
                OU        = $_.Name
                GPO       = $link.DisplayName
                Enabled   = $link.Enabled
                Enforced  = $link.Enforced
                Order     = $link.Order
            }
        }
    } | Format-Table

# Create new GPO
New-GPO -Name "Workstation-Security-Baseline" -Comment "Security settings for workstations"

# Link GPO to OU
New-GPLink -Name "Workstation-Security-Baseline" `
    -Target "OU=Workstations,DC=inside,DC=domusdigitalis,DC=dev"

# Link GPO with enforcement
New-GPLink -Name "Critical-Security-Settings" `
    -Target "OU=Workstations,DC=inside,DC=domusdigitalis,DC=dev" `
    -Enforced Yes

# Disable GPO link
Set-GPLink -Name "Workstation-Security-Baseline" `
    -Target "OU=Workstations,DC=inside,DC=domusdigitalis,DC=dev" `
    -LinkEnabled No

# Remove GPO link
Remove-GPLink -Name "Old-Policy" `
    -Target "OU=Workstations,DC=inside,DC=domusdigitalis,DC=dev"

# Backup GPO
Backup-GPO -Name "Workstation-Security-Baseline" -Path C:\GPOBackups

# Backup all GPOs
Backup-GPO -All -Path C:\GPOBackups

# Restore GPO
Restore-GPO -Name "Workstation-Security-Baseline" -Path C:\GPOBackups

# Import GPO settings
Import-GPO -BackupGpoName "Source-Policy" -TargetName "New-Policy" -Path C:\GPOBackups

# Copy GPO
Copy-GPO -SourceName "Template-Policy" -TargetName "Department-Policy"

# Get GPO report (HTML)
Get-GPOReport -Name "Default Domain Policy" -ReportType HTML -Path C:\Reports\DDPolicy.html

# Get GPO report (XML for parsing)
[xml]$gpoXml = Get-GPOReport -Name "Default Domain Policy" -ReportType Xml
$gpoXml.GPO.Computer.ExtensionData

# Set GPO registry value
Set-GPRegistryValue -Name "Workstation-Security-Baseline" `
    -Key "HKLM\Software\Policies\Microsoft\Windows\System" `
    -ValueName "EnableSmartScreen" `
    -Type DWord `
    -Value 1

# Get GPO registry values
Get-GPRegistryValue -Name "Workstation-Security-Baseline" `
    -Key "HKLM\Software\Policies\Microsoft\Windows\System"

# Set permissions on GPO
Set-GPPermission -Name "Workstation-Security-Baseline" `
    -TargetName "IT-Admins" `
    -TargetType Group `
    -PermissionLevel GpoEditDeleteModifySecurity

# Get GPO permissions
Get-GPPermission -Name "Workstation-Security-Baseline" -All

# Force Group Policy update on remote computer
Invoke-GPUpdate -Computer "workstation1" -Force

# Get GPO results for computer
Get-GPResultantSetOfPolicy -Computer workstation1 -ReportType HTML -Path C:\Reports\RSOP.html

Password Operations

# Set user password
$newPassword = ConvertTo-SecureString "NewP@ssw0rd!" -AsPlainText -Force
Set-ADAccountPassword -Identity jsmith -NewPassword $newPassword -Reset

# Force password change at next logon
Set-ADUser -Identity jsmith -ChangePasswordAtLogon $true

# Set password to never expire (service accounts)
Set-ADUser -Identity svc_backup -PasswordNeverExpires $true

# Check password last set date
Get-ADUser -Identity jsmith -Properties PasswordLastSet | Select-Object SamAccountName, PasswordLastSet

# Find users with passwords about to expire
$maxAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$warningDays = 14
$threshold = (Get-Date).AddDays($maxAge - $warningDays)

Get-ADUser -Filter { Enabled -eq $true -and PasswordNeverExpires -eq $false } -Properties PasswordLastSet |
    Where-Object { $_.PasswordLastSet -lt $threshold } |
    Select-Object SamAccountName, PasswordLastSet,
        @{N='ExpiresIn';E={$maxAge - ((Get-Date) - $_.PasswordLastSet).Days}}

# Find users with expired passwords
Get-ADUser -Filter { PasswordExpired -eq $true } |
    Select-Object SamAccountName, Name

# Find users with "password never expires" set
Get-ADUser -Filter { PasswordNeverExpires -eq $true } |
    Select-Object SamAccountName, Name

# Get domain password policy
Get-ADDefaultDomainPasswordPolicy

# Get fine-grained password policies
Get-ADFineGrainedPasswordPolicy -Filter *

# Create fine-grained password policy
New-ADFineGrainedPasswordPolicy -Name "ServiceAccountPolicy" `
    -Precedence 50 `
    -ComplexityEnabled $true `
    -MinPasswordLength 20 `
    -MinPasswordAge (New-TimeSpan -Days 1) `
    -MaxPasswordAge (New-TimeSpan -Days 365) `
    -PasswordHistoryCount 24 `
    -LockoutThreshold 0 `
    -ReversibleEncryptionEnabled $false

# Apply fine-grained policy to group
Add-ADFineGrainedPasswordPolicySubject -Identity "ServiceAccountPolicy" -Subjects "SVC-Accounts"

# Check which password policy applies to user
Get-ADUserResultantPasswordPolicy -Identity svc_backup

# Unlock account
Unlock-ADAccount -Identity jsmith

# Check account lockout
Get-ADUser -Identity jsmith -Properties LockedOut, LockoutTime, BadLogonCount |
    Select-Object SamAccountName, LockedOut, LockoutTime, BadLogonCount

# Find locked out accounts
Search-ADAccount -LockedOut | Select-Object SamAccountName, LockedOut

# Unlock all locked accounts
Search-ADAccount -LockedOut | Unlock-ADAccount

# Find source of account lockouts (requires Security log access on DCs)
$lockedUser = "jsmith"
Get-ADDomainController -Filter * | ForEach-Object {
    $dc = $_.HostName
    Get-WinEvent -ComputerName $dc -FilterHashtable @{
        LogName = 'Security'
        Id = 4740
    } -MaxEvents 10 -ErrorAction SilentlyContinue |
    Where-Object { $_.Properties[0].Value -eq $lockedUser } |
    Select-Object TimeCreated, @{N='User';E={$_.Properties[0].Value}},
        @{N='SourceComputer';E={$_.Properties[1].Value}},
        @{N='DC';E={$dc}}
}

Service Accounts and gMSA

# Create traditional service account
New-ADUser -Name "svc_backup" `
    -SamAccountName "svc_backup" `
    -UserPrincipalName "svc_backup@inside.domusdigitalis.dev" `
    -Path "OU=Service Accounts,DC=inside,DC=domusdigitalis,DC=dev" `
    -AccountPassword (ConvertTo-SecureString "ComplexP@ss123!" -AsPlainText -Force) `
    -PasswordNeverExpires $true `
    -CannotChangePassword $true `
    -Enabled $true `
    -Description "Backup service account"

# Create KDS root key (required for gMSA, run once per domain)
# For production (10-hour wait for replication)
Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

# For testing (immediate, NOT for production)
# Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

# Create group for gMSA usage
New-ADGroup -Name "gMSA-BackupService-Hosts" `
    -GroupScope Global `
    -GroupCategory Security `
    -Path "OU=Groups,DC=inside,DC=domusdigitalis,DC=dev"

# Add computers that can use the gMSA
Add-ADGroupMember -Identity "gMSA-BackupService-Hosts" -Members "server1$", "server2$"

# Create gMSA (Group Managed Service Account)
New-ADServiceAccount -Name "gMSA_Backup" `
    -DNSHostName "gMSA_Backup.inside.domusdigitalis.dev" `
    -PrincipalsAllowedToRetrieveManagedPassword "gMSA-BackupService-Hosts" `
    -Path "CN=Managed Service Accounts,DC=inside,DC=domusdigitalis,DC=dev"

# Install gMSA on target server (run on each server that needs it)
Install-ADServiceAccount -Identity "gMSA_Backup"

# Test gMSA installation
Test-ADServiceAccount -Identity "gMSA_Backup"

# List all service accounts
Get-ADServiceAccount -Filter * |
    Select-Object Name, DistinguishedName, Enabled

# Get gMSA details
Get-ADServiceAccount -Identity "gMSA_Backup" -Properties *

# Check which computers can use gMSA
Get-ADServiceAccount -Identity "gMSA_Backup" -Properties PrincipalsAllowedToRetrieveManagedPassword |
    Select-Object -ExpandProperty PrincipalsAllowedToRetrieveManagedPassword

# Delete gMSA
Remove-ADServiceAccount -Identity "gMSA_Backup" -Confirm:$false

# Use gMSA in service configuration (the trailing $ is required)
# sc.exe config "MyService" obj= "INSIDE\gMSA_Backup$" type= own
# Or in PowerShell:
# Set-Service -Name "MyService" -ServiceCredential (Get-Credential "INSIDE\gMSA_Backup$")

# Service account audit - find SPNs
Get-ADUser -Filter * -Properties ServicePrincipalName |
    Where-Object { $_.ServicePrincipalName } |
    Select-Object SamAccountName, @{N='SPNs';E={$_.ServicePrincipalName -join "; "}}

# Set SPN
Set-ADUser -Identity svc_sql -ServicePrincipalName @{Add="MSSQLSvc/sqlserver.inside.domusdigitalis.dev:1433"}

Security and Auditing

# Find privileged users (Domain Admins, Enterprise Admins, Schema Admins)
$privilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators")
foreach ($group in $privilegedGroups) {
    Write-Host "=== $group ===" -ForegroundColor Yellow
    Get-ADGroupMember -Identity $group -Recursive |
        Where-Object { $_.objectClass -eq 'user' } |
        Get-ADUser -Properties LastLogonDate, PasswordLastSet |
        Select-Object SamAccountName, LastLogonDate, PasswordLastSet |
        Format-Table
}

# Find users with AdminCount=1 (formerly privileged, possibly still has elevated perms)
Get-ADUser -Filter { AdminCount -eq 1 } -Properties AdminCount |
    Select-Object SamAccountName, DistinguishedName

# Find users who can perform DCSync (dangerous)
$domainDN = (Get-ADDomain).DistinguishedName
$aclEntries = (Get-Acl "AD:$domainDN").Access |
    Where-Object {
        $_.ActiveDirectoryRights -match "ExtendedRight" -and
        ($_.ObjectType -eq "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2" -or  # DS-Replication-Get-Changes
         $_.ObjectType -eq "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2")    # DS-Replication-Get-Changes-All
    }

$aclEntries | Select-Object IdentityReference, ActiveDirectoryRights, ObjectType

# Find stale computer accounts
$threshold = (Get-Date).AddDays(-90)
Get-ADComputer -Filter { LastLogonDate -lt $threshold } -Properties LastLogonDate |
    Select-Object Name, LastLogonDate |
    Sort-Object LastLogonDate

# Find accounts with Kerberos pre-authentication disabled (AS-REP roastable)
Get-ADUser -Filter { DoesNotRequirePreAuth -eq $true } |
    Select-Object SamAccountName, DistinguishedName

# Find accounts with constrained delegation
Get-ADUser -Filter { TrustedForDelegation -eq $true } |
    Select-Object SamAccountName, DistinguishedName

Get-ADUser -Filter { msDS-AllowedToDelegateTo -ne "$null" } -Properties msDS-AllowedToDelegateTo |
    Select-Object SamAccountName, msDS-AllowedToDelegateTo

# Find computers with unconstrained delegation (risky)
Get-ADComputer -Filter { TrustedForDelegation -eq $true } |
    Select-Object Name, DistinguishedName

# Get recent password changes
Get-ADUser -Filter * -Properties PasswordLastSet |
    Where-Object { $_.PasswordLastSet -gt (Get-Date).AddDays(-7) } |
    Select-Object SamAccountName, PasswordLastSet |
    Sort-Object PasswordLastSet -Descending

# Get recent account creations
Get-ADUser -Filter * -Properties Created |
    Where-Object { $_.Created -gt (Get-Date).AddDays(-7) } |
    Select-Object SamAccountName, Created |
    Sort-Object Created -Descending

# Get users with reversible encryption enabled (bad)
Get-ADUser -Filter { AllowReversiblePasswordEncryption -eq $true } |
    Select-Object SamAccountName

# Check for LAPS deployment
Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwd, ms-Mcs-AdmPwdExpirationTime |
    Select-Object Name,
        @{N='LAPSEnabled';E={[bool]$_.'ms-Mcs-AdmPwd'}},
        @{N='PasswordExpiry';E={
            if ($_.'ms-Mcs-AdmPwdExpirationTime') {
                [datetime]::FromFileTime($_.'ms-Mcs-AdmPwdExpirationTime')
            }
        }}

# Get LAPS password (requires appropriate permissions)
Get-ADComputer -Identity workstation1 -Properties ms-Mcs-AdmPwd |
    Select-Object Name, ms-Mcs-AdmPwd

Replication and DC Health

# Get all domain controllers
Get-ADDomainController -Filter * |
    Select-Object Name, IPv4Address, Site, IsGlobalCatalog, OperatingSystem

# Check replication status
Get-ADReplicationPartnerMetadata -Target home-dc01 |
    Select-Object Partner, LastReplicationAttempt, LastReplicationResult, LastReplicationSuccess

# Check replication for all DCs
Get-ADDomainController -Filter * | ForEach-Object {
    Get-ADReplicationPartnerMetadata -Target $_.HostName -ErrorAction SilentlyContinue |
        Select-Object Server, Partner, LastReplicationAttempt, LastReplicationResult
} | Format-Table

# Check replication failures
Get-ADReplicationFailure -Target home-dc01

# Get replication failures for all DCs
Get-ADDomainController -Filter * | ForEach-Object {
    Get-ADReplicationFailure -Target $_.HostName -ErrorAction SilentlyContinue
}

# Force replication
Sync-ADObject -Object "CN=AdminErosado,OU=Users,DC=inside,DC=domusdigitalis,DC=dev" -Source home-dc01 -Destination home-dc02

# Replicate all from source DC
repadmin /syncall home-dc01 /Ade

# Check replication queue
repadmin /queue home-dc01

# Show replication summary
repadmin /replsum

# Check DC health
dcdiag /s:home-dc01

# Quick health check via PowerShell
Get-ADDomainController -Filter * | ForEach-Object {
    $dc = $_.HostName
    [PSCustomObject]@{
        DC         = $dc
        Site       = $_.Site
        Reachable  = Test-Connection -ComputerName $dc -Count 1 -Quiet
        DNSWorking = [bool](Resolve-DnsName -Name $dc -ErrorAction SilentlyContinue)
        LDAP       = (Test-NetConnection -ComputerName $dc -Port 389).TcpTestSucceeded
        Kerberos   = (Test-NetConnection -ComputerName $dc -Port 88).TcpTestSucceeded
    }
}

# Get FSMO role holders
Get-ADForest | Select-Object DomainNamingMaster, SchemaMaster
Get-ADDomain | Select-Object InfrastructureMaster, PDCEmulator, RIDMaster

# Transfer FSMO role
Move-ADDirectoryServerOperationMasterRole -Identity home-dc02 -OperationMasterRole PDCEmulator

# Get AD sites
Get-ADReplicationSite -Filter *

# Get site links
Get-ADReplicationSiteLink -Filter * |
    Select-Object Name, Cost, ReplicationFrequencyInMinutes, @{N='Sites';E={$_.SitesIncluded -join ", "}}

# Get subnets
Get-ADReplicationSubnet -Filter * |
    Select-Object Name, Site

DNS Integration

# Import DNS module
Import-Module DnsServer

# Get DNS zones on DC
Get-DnsServerZone -ComputerName home-dc01 |
    Select-Object ZoneName, ZoneType, DynamicUpdate

# Get DNS records
Get-DnsServerResourceRecord -ZoneName "inside.domusdigitalis.dev" -ComputerName home-dc01 |
    Select-Object HostName, RecordType, @{N='Data';E={$_.RecordData}}

# Get A records
Get-DnsServerResourceRecord -ZoneName "inside.domusdigitalis.dev" -RRType A -ComputerName home-dc01

# Get specific record
Get-DnsServerResourceRecord -ZoneName "inside.domusdigitalis.dev" -Name "home-dc01" -ComputerName home-dc01

# Add A record
Add-DnsServerResourceRecordA -ZoneName "inside.domusdigitalis.dev" `
    -Name "newserver" `
    -IPv4Address "10.50.1.100" `
    -ComputerName home-dc01

# Add PTR record
Add-DnsServerResourceRecordPtr -ZoneName "1.50.10.in-addr.arpa" `
    -Name "100" `
    -PtrDomainName "newserver.inside.domusdigitalis.dev" `
    -ComputerName home-dc01

# Add CNAME record
Add-DnsServerResourceRecordCName -ZoneName "inside.domusdigitalis.dev" `
    -Name "www" `
    -HostNameAlias "webserver.inside.domusdigitalis.dev" `
    -ComputerName home-dc01

# Remove DNS record
Remove-DnsServerResourceRecord -ZoneName "inside.domusdigitalis.dev" `
    -Name "oldserver" `
    -RRType A `
    -ComputerName home-dc01 `
    -Force

# Find stale DNS records (matching stale AD computers)
$staleComputers = Get-ADComputer -Filter { LastLogonDate -lt $threshold } |
    Select-Object -ExpandProperty Name

Get-DnsServerResourceRecord -ZoneName "inside.domusdigitalis.dev" -RRType A -ComputerName home-dc01 |
    Where-Object { $_.HostName -in $staleComputers } |
    Select-Object HostName, @{N='IP';E={$_.RecordData.IPv4Address}}

# Scavenge stale DNS records
Set-DnsServerScavenging -ComputerName home-dc01 -ScavengingState $true -ScavengingInterval 7.00:00:00

# Clear DNS cache
Clear-DnsServerCache -ComputerName home-dc01 -Force

# Test DNS resolution
Resolve-DnsName -Name "home-dc01.inside.domusdigitalis.dev" -Server home-dc01

# Get SRV records (for DC discovery)
Resolve-DnsName -Name "_ldap._tcp.dc._msdcs.inside.domusdigitalis.dev" -Type SRV

Bulk Operations

# Bulk modify users in OU
Get-ADUser -Filter * -SearchBase "OU=IT,DC=inside,DC=domusdigitalis,DC=dev" |
    Set-ADUser -Department "Information Technology"

# Bulk disable inactive accounts
$threshold = (Get-Date).AddDays(-90)
Get-ADUser -Filter { LastLogonDate -lt $threshold -and Enabled -eq $true } |
    ForEach-Object {
        Disable-ADAccount -Identity $_
        Move-ADObject -Identity $_.DistinguishedName -TargetPath "OU=Disabled,DC=inside,DC=domusdigitalis,DC=dev"
        Write-Host "Disabled and moved: $($_.SamAccountName)"
    }

# Bulk add to group from CSV
Import-Csv C:\UsersToAdd.csv | ForEach-Object {
    Add-ADGroupMember -Identity $_.GroupName -Members $_.Username
    Write-Host "Added $($_.Username) to $($_.GroupName)"
}

# Bulk create users with error handling
$users = Import-Csv C:\NewUsers.csv
$password = ConvertTo-SecureString "TempP@ss123!" -AsPlainText -Force

foreach ($user in $users) {
    try {
        New-ADUser -Name "$($user.FirstName) $($user.LastName)" `
            -SamAccountName $user.Username `
            -UserPrincipalName "$($user.Username)@inside.domusdigitalis.dev" `
            -GivenName $user.FirstName `
            -Surname $user.LastName `
            -Department $user.Department `
            -Path "OU=Users,DC=inside,DC=domusdigitalis,DC=dev" `
            -AccountPassword $password `
            -Enabled $true `
            -ErrorAction Stop

        Write-Host "Created: $($user.Username)" -ForegroundColor Green
    }
    catch {
        Write-Host "Failed: $($user.Username) - $($_.Exception.Message)" -ForegroundColor Red
    }
}

# Parallel bulk operations (PowerShell 7+)
$users = Get-ADUser -Filter { Department -eq "IT" }
$users | ForEach-Object -Parallel {
    Set-ADUser -Identity $_.SamAccountName -Replace @{ extensionAttribute1 = "Processed" }
} -ThrottleLimit 10

# Export users for review before changes
Get-ADUser -Filter { LastLogonDate -lt $threshold } -Properties LastLogonDate, Manager |
    Select-Object SamAccountName, Name, LastLogonDate,
        @{N='Manager';E={(Get-ADUser $_.Manager -ErrorAction SilentlyContinue).Name}},
        @{N='Action';E={'Disable'}} |
    Export-Csv C:\UsersToDisable.csv -NoTypeInformation

# Review, edit CSV, then process
Import-Csv C:\UsersToDisable.csv |
    Where-Object { $_.Action -eq 'Disable' } |
    ForEach-Object {
        Disable-ADAccount -Identity $_.SamAccountName
    }

# Cleanup old computers with logging
$logFile = "C:\Logs\ComputerCleanup-$(Get-Date -Format 'yyyyMMdd').log"
$staleComputers = Get-ADComputer -Filter { LastLogonDate -lt $threshold } -Properties LastLogonDate

foreach ($computer in $staleComputers) {
    $entry = "$(Get-Date) - Removing: $($computer.Name) - LastLogon: $($computer.LastLogonDate)"
    Add-Content -Path $logFile -Value $entry
    Remove-ADComputer -Identity $computer -Confirm:$false
}

Common Gotchas

# WRONG: Forgetting -Properties for extended attributes
$user = Get-ADUser -Identity jsmith
$user.EmailAddress  # Empty! Properties not retrieved

# CORRECT: Specify needed properties
$user = Get-ADUser -Identity jsmith -Properties EmailAddress, Department
$user.EmailAddress  # Has value

# CORRECT: Get all properties (slower)
$user = Get-ADUser -Identity jsmith -Properties *

# WRONG: Using -Filter with variables incorrectly
$name = "John"
Get-ADUser -Filter { Name -eq $name }  # May fail

# CORRECT: Use script block properly or -LDAPFilter
Get-ADUser -Filter "Name -eq '$name'"
# Or
Get-ADUser -LDAPFilter "(name=$name)"

# WRONG: Passing array to Add-ADGroupMember
$users = "user1,user2,user3"
Add-ADGroupMember -Identity "Group" -Members $users  # Fails

# CORRECT: Proper array
$users = @("user1", "user2", "user3")
Add-ADGroupMember -Identity "Group" -Members $users

# WRONG: Expecting MemberOf to return group names
$groups = (Get-ADUser -Identity jsmith -Properties MemberOf).MemberOf
$groups  # Returns Distinguished Names, not friendly names!

# CORRECT: Convert to group objects
(Get-ADUser -Identity jsmith -Properties MemberOf).MemberOf |
    Get-ADGroup |
    Select-Object Name

# WRONG: Moving object with wrong identity
Move-ADObject -Identity "jsmith" -TargetPath "OU=New,DC=inside,DC=domusdigitalis,DC=dev"  # Fails

# CORRECT: Use DistinguishedName
Move-ADObject -Identity "CN=John Smith,OU=Old,DC=inside,DC=domusdigitalis,DC=dev" -TargetPath "OU=New,DC=inside,DC=domusdigitalis,DC=dev"
# Or get it dynamically
$user = Get-ADUser -Identity jsmith
Move-ADObject -Identity $user.DistinguishedName -TargetPath "OU=New,DC=inside,DC=domusdigitalis,DC=dev"

# WRONG: Setting password directly
Set-ADUser -Identity jsmith -Password "NewPassword"  # No -Password parameter!

# CORRECT: Use Set-ADAccountPassword
Set-ADAccountPassword -Identity jsmith -NewPassword (ConvertTo-SecureString "NewP@ss!" -AsPlainText -Force) -Reset

# WRONG: Using computer name without $ for computer accounts
Add-ADGroupMember -Identity "Computers-Group" -Members "SERVER01"  # Looks for user

# CORRECT: Computer accounts have $ suffix
Add-ADGroupMember -Identity "Computers-Group" -Members "SERVER01$"

# WRONG: Case sensitivity in LDAP filters
Get-ADUser -LDAPFilter "(samaccountname=JSMITH)"  # Works, LDAP is case-insensitive
Get-ADUser -Filter { SamAccountName -eq "JSMITH" }  # PowerShell filter - also works

# GOTCHA: LastLogonDate vs lastLogon
# lastLogon - Not replicated, exists on each DC
# lastLogonTimestamp - Replicated, may be up to 14 days old
# LastLogonDate - PowerShell converts lastLogonTimestamp

# To get true last logon, query all DCs:
$user = "jsmith"
Get-ADDomainController -Filter * | ForEach-Object {
    Get-ADUser -Identity $user -Server $_.HostName -Properties lastLogon |
        Select-Object @{N='DC';E={$_.Server}},
            @{N='LastLogon';E={[datetime]::FromFileTime($_.lastLogon)}}
} | Sort-Object LastLogon -Descending | Select-Object -First 1