
Creating a new Active Directory user is not hard.
It is not hard enough to feel important, but it has enough small steps to become irritating. Type the name. Pick the OU. Copy the department. Copy the title. Maybe copy groups. Set a temporary password. Force the user to change it on first login. Then stare at ADUC like it personally stole your lunch.
So I wrote a PowerShell script to make the process cleaner.
The idea is simple: create a new AD user by copying selected settings from an existing user.
Not everything should be copied. Some things must stay unique, like the username, UPN, password, SID, GUID, and email address. But things like department, title, office, manager, and group membership can usually follow a similar user.
The Username Convention
Our username format is simple:
first initial + full last name
So:
Teo Espero = tespero
John Smith-Jones = jsmithjones
The script asks for the first name and last name, then generates the username automatically.
function New-GeneratedUsername {
param (
[string]$FirstName,
[string]$LastName
)
$FirstInitial = $FirstName.Trim().Substring(0, 1).ToLower()
$CleanLastName = $LastName.Trim().ToLower()
$CleanLastName = $CleanLastName -replace "\s+", ""
$CleanLastName = $CleanLastName -replace "'", ""
$CleanLastName = $CleanLastName -replace "\.", ""
$CleanLastName = $CleanLastName -replace "-", ""
return "$FirstInitial$CleanLastName"
}
This avoids a lot of small mistakes. It also keeps the username format consistent.
And yes, it still lets me override the generated username, because real life always finds a way to ruin clean naming conventions.
Checking the User Being Copied
Before copying anything, the script checks the existing user.
It exits cleanly if the copy-from user:
does not exist
is disabled
has never logged in
has not logged in within the inactive threshold
That matters because copying a bad or stale account is a great way to automate garbage. And while automation is good, automated garbage is still garbage. It is just faster garbage.
try {
$TemplateUser = Get-ADUser -Identity $CopyFrom -Properties `
Department,
Title,
Company,
Office,
OfficePhone,
Manager,
Description,
Enabled,
LastLogonDate `
-ErrorAction Stop
}
catch {
Exit-Gracefully -Message "The copy-from user '$CopyFrom' was not found in Active Directory."
}
if ($TemplateUser.Enabled -ne $true) {
Exit-Gracefully -Message "The copy-from user '$CopyFrom' is disabled."
}
if (-not $TemplateUser.LastLogonDate) {
Exit-Gracefully -Message "The copy-from user '$CopyFrom' appears inactive or unused."
}
$InactiveCutoffDate = (Get-Date).AddDays(-$InactiveDays)
if ($TemplateUser.LastLogonDate -lt $InactiveCutoffDate) {
Exit-Gracefully -Message "The copy-from user '$CopyFrom' appears inactive."
}
The default inactivity threshold is 90 days, but I can adjust it when running the script.
.\New-CopiedADUser.ps1 -InactiveDays 180
Checking for Existing or Similar Usernames
The script also checks if the new username already exists.
That part is obvious.
The more useful part is checking for similar usernames, UPNs, names, and display names. This helps catch cases where someone already exists under a slightly different username.
$ExistingUser = Get-ADUser -LDAPFilter "(sAMAccountName=$EscapedNewUser)"
if ($ExistingUser) {
throw "User '$NewUser' already exists in Active Directory."
}
$SimilarUsers = Get-ADUser -LDAPFilter "(|(sAMAccountName=*$EscapedNewUser*)(userPrincipalName=*$EscapedNewUser*)(name=*$EscapedNewUser*)(displayName=*$EscapedNewUser*))" `
-Properties DisplayName, UserPrincipalName |
Select-Object Name, SamAccountName, UserPrincipalName, DisplayName
If similar users are found, the script shows them and asks before continuing.
Because nothing says “fun afternoon” like accidentally creating a duplicate account and then explaining it later.
Setting a Temporary Password
The script prompts for a temporary password using Read-Host -AsSecureString.
The password does not show on screen and does not get written to the log.
$TemporaryPassword = Read-Host "Enter temporary password for $NewUser" -AsSecureString
Then the user is required to change it at first login.
ChangePasswordAtLogon = $true
That part is important. Temporary passwords should be temporary. Shocking concept, apparently.
Creating the User
The script creates the new account in the same OU as the user being copied.
$OU = ($TemplateUser.DistinguishedName -split ",", 2)[1]
Then it creates the user.
$NewUserParams = @{
Name = $DisplayName
GivenName = $FirstName
Surname = $LastName
DisplayName = $DisplayName
SamAccountName = $NewUser
UserPrincipalName = $UPN
Path = $OU
AccountPassword = $TemporaryPassword
Enabled = $true
ChangePasswordAtLogon = $true
ErrorAction = "Stop"
}
New-ADUser @NewUserParams
This handles the basic account creation without having to manually click through Active Directory Users and Computers.
Copying Selected AD Fields
The script does not blindly clone the user.
It only copies selected profile fields that make sense.
$SetUserParams = @{
Identity = $NewUser
ErrorAction = "Stop"
}
if ($TemplateUser.Department) { $SetUserParams.Department = $TemplateUser.Department }
if ($TemplateUser.Title) { $SetUserParams.Title = $TemplateUser.Title }
if ($TemplateUser.Company) { $SetUserParams.Company = $TemplateUser.Company }
if ($TemplateUser.Office) { $SetUserParams.Office = $TemplateUser.Office }
if ($TemplateUser.OfficePhone) { $SetUserParams.OfficePhone = $TemplateUser.OfficePhone }
if ($TemplateUser.Manager) { $SetUserParams.Manager = $TemplateUser.Manager }
if ($TemplateUser.Description) { $SetUserParams.Description = $TemplateUser.Description }
Set-ADUser @SetUserParams
This keeps the useful stuff and avoids copying things that should never be duplicated.
Optional Group Copy
The script can also copy group membership from the template user.
This is optional because there are times when copying groups is helpful, and there are times when it is a shortcut to giving someone access they should not have.
So the script asks first.
$Groups = Get-ADPrincipalGroupMembership -Identity $CopyFrom |
Where-Object { $_.Name -ne "Domain Users" }
foreach ($Group in $Groups) {
Add-ADGroupMember -Identity $Group.DistinguishedName -Members $NewUser
}
It skips Domain Users because that is the default primary group.
Logging Errors
The script also creates a log file for each run.
$LogFolder = Join-Path -Path $ScriptRoot -ChildPath "Logs"
if (-not (Test-Path $LogFolder)) {
New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null
}
$LogFile = Join-Path -Path $LogFolder -ChildPath ("New-CopiedADUser_{0}.log" -f (Get-Date -Format "yyyyMMdd_HHmmss"))
When something fails, the script logs useful troubleshooting details.
function Write-ErrorLog {
param (
[System.Management.Automation.ErrorRecord]$ErrorRecord
)
Write-Log -Level "ERROR" -Message "Error Message: $($ErrorRecord.Exception.Message)"
Write-Log -Level "ERROR" -Message "Error Type: $($ErrorRecord.Exception.GetType().FullName)"
Write-Log -Level "ERROR" -Message "Script Line: $($ErrorRecord.InvocationInfo.ScriptLineNumber)"
Write-Log -Level "ERROR" -Message "Command: $($ErrorRecord.InvocationInfo.Line)"
Write-Log -Level "ERROR" -Message "Stack Trace: $($ErrorRecord.ScriptStackTrace)"
}
The password is not logged. Because logging passwords is how you turn a helpful script into an incident report.
Running the Script
The easiest way is interactive:
.\New-CopiedADUser.ps1
Or with parameters:
.\New-CopiedADUser.ps1 -FirstName Maria -LastName Garcia -CopyFrom jsmith -CopyGroups
Or with a different inactivity threshold:
.\New-CopiedADUser.ps1 -FirstName Maria -LastName Garcia -CopyFrom jsmith -InactiveDays 180
Final Thought
This is not some massive automation platform. It is just a practical script that removes repetitive AD account creation steps and adds a few guardrails.
It checks the account being copied. It checks for existing and similar usernames. It creates the new user in the right OU. It copies selected fields. It optionally copies groups. It forces a password change. It logs errors.
Basically, it does the boring part without turning off the part of your brain that still needs to ask, “Should this person actually have that access?”
Which is the whole point.
Automation should make the work cleaner.
Not dumber.