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