NewADUser/New-ADUser.ps1

460 lines
16 KiB
PowerShell

<#
.SYNOPSIS
Creates or copies AD users with complete error handling
.DESCRIPTION
A script that can create a new user account from scratch, or copy an existing user for their security groups.
Has built-in error tolerant handling and recommendations, will continue where possible and report any issues.
Script will automatically add 'mailnickname' and 'proxyAddresses' to attribute editor.
.NOTES
Author : Kyle Pope + AI
Created : 13/04/25
Version : 1.0.7
Requires : PowerShell with Active Directory module
File Name : New-ADUser.ps1
#>
# Import Active Directory module
try {
Import-Module ActiveDirectory -ErrorAction Stop
}
catch {
Write-Host "Active Directory module not found. Please install RSAT tools." -ForegroundColor Red
exit
}
function Show-Menu {
param (
[string]$Title = 'AD User Management'
)
Clear-Host
Write-Host "================ $Title ================"
Write-Host ""
Write-Host "1: Create new user"
Write-Host "2: Copy existing user"
Write-Host ""
Write-Host "Q: Quit"
Write-Host ""
Write-Host "========================================"
}
function Get-DomainSelection {
$domains = (Get-ADForest).Domains
if ($domains.Count -gt 1) {
Write-Host "Multiple domains available:"
for ($i = 0; $i -lt $domains.Count; $i++) {
Write-Host "$($i+1): $($domains[$i])"
}
$selection = Read-Host "Select domain (1-$($domains.Count))"
while ($selection -notmatch "^\d+$" -or [int]$selection -lt 1 -or [int]$selection -gt $domains.Count) {
$selection = Read-Host "Invalid selection. Please enter a number between 1 and $($domains.Count)"
}
return $domains[[int]$selection-1]
}
else {
return $domains[0]
}
}
function Get-ValidPassword {
do {
$password = Read-Host "Enter password (min 8 chars, complex)" -AsSecureString
$passwordText = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
if ($passwordText.Length -lt 8) {
Write-Host "Password must be at least 8 characters" -ForegroundColor Red
$validPwd = $false
}
elseif (-not ($passwordText -match "[A-Z]") -or
-not ($passwordText -match "[a-z]") -or
-not ($passwordText -match "[0-9]") -or
-not ($passwordText -match "[^a-zA-Z0-9]")) {
Write-Host "Password must contain uppercase, lowercase, number and special character" -ForegroundColor Red
$validPwd = $false
}
else {
$validPwd = $true
}
if (-not $validPwd) {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
}
} while (-not $validPwd)
return $password
}
function Test-ADUserExists {
param (
[string]$Username,
[int]$RetryCount = 3,
[int]$DelaySeconds = 2
)
for ($i = 1; $i -le $RetryCount; $i++) {
try {
$user = Get-ADUser -Identity $Username -ErrorAction Stop
return $user
}
catch {
if ($i -lt $RetryCount) {
Write-Host "User not found yet, retrying in $DelaySeconds seconds... (Attempt $i of $RetryCount)" -ForegroundColor Yellow
Start-Sleep -Seconds $DelaySeconds
}
}
}
return $null
}
function Set-UserEmailAttributes {
param (
[string]$Username,
[string]$EmailAddress,
[string]$Domain
)
$errors = @()
# Set mailNickname if available
try {
Set-ADUser -Identity $Username -Replace @{mailNickname=$Username} -ErrorAction Stop
Write-Host "Successfully set mailNickname: $Username" -ForegroundColor Green
}
catch {
$errors += "mailNickname: $($_.Exception.Message)"
Write-Host "Warning: Could not set mailNickname - $($_.Exception.Message)" -ForegroundColor Yellow
}
# Set proxyAddresses (primary SMTP address)
try {
$proxyAddress = "SMTP:$EmailAddress"
Set-ADUser -Identity $Username -Add @{proxyAddresses=$proxyAddress} -ErrorAction Stop
Write-Host "Successfully set proxyAddresses: $proxyAddress" -ForegroundColor Green
}
catch {
$errors += "proxyAddresses: $($_.Exception.Message)"
Write-Host "Warning: Could not set proxyAddresses - $($_.Exception.Message)" -ForegroundColor Yellow
}
return $errors
}
function Show-FinalUserDetails {
param (
[string]$Username,
[array]$AttributeErrors
)
try {
$finalUser = Get-ADUser -Identity $Username -Properties * -ErrorAction SilentlyContinue
if ($finalUser) {
Write-Host "`nFinal User Details:" -ForegroundColor Cyan
$finalUser | Select-Object Name, SamAccountName, UserPrincipalName,
mailNickname, proxyAddresses, EmailAddress,
DistinguishedName, Enabled | Format-List
if ($AttributeErrors) {
Write-Host "`nAttribute Setting Errors:" -ForegroundColor Yellow
$AttributeErrors | ForEach-Object {
Write-Host " - $_" -ForegroundColor Yellow
}
}
}
else {
Write-Host "`nNote: Could not retrieve final user details (replication in progress)" -ForegroundColor Yellow
if ($AttributeErrors) {
Write-Host "`nAttribute Setting Errors:" -ForegroundColor Yellow
$AttributeErrors | ForEach-Object {
Write-Host " - $_" -ForegroundColor Yellow
}
}
}
}
catch {
Write-Host "`nNote: Could not retrieve complete user details - $($_.Exception.Message)" -ForegroundColor Yellow
if ($AttributeErrors) {
Write-Host "`nAttribute Setting Errors:" -ForegroundColor Yellow
$AttributeErrors | ForEach-Object {
Write-Host " - $_" -ForegroundColor Yellow
}
}
}
}
function CreateNewUser {
param (
[string]$Domain
)
# Collect user information
$firstName = Read-Host "Enter first name"
$lastName = Read-Host "Enter last name"
$username = Read-Host "Enter username (leave blank to auto-generate)"
if ([string]::IsNullOrWhiteSpace($username)) {
$username = ($firstName.Substring(0,1) + $lastName).ToLower()
Write-Host "Auto-generated username: $username" -ForegroundColor Yellow
}
$displayName = "$firstName $lastName"
$ou = Read-Host "Enter OU distinguished name (e.g., OU=Users,DC=domain,DC=com) or leave blank for default"
$password = Get-ValidPassword
$emailAddress = "$username@$Domain"
$attributeErrors = @()
try {
# Base parameters
$newUserParams = @{
GivenName = $firstName
Surname = $lastName
Name = $displayName
DisplayName = $displayName
SamAccountName = $username
UserPrincipalName = $emailAddress
AccountPassword = $password
Enabled = $true
ChangePasswordAtLogon = $true
EmailAddress = $emailAddress
ErrorAction = 'Stop'
}
# Set OU path
if (-not [string]::IsNullOrWhiteSpace($ou)) {
$newUserParams['Path'] = $ou
Write-Host "Creating user in specified OU: $ou" -ForegroundColor Cyan
}
else {
$defaultOU = "CN=Users," + (Get-ADDomain).DistinguishedName
$newUserParams['Path'] = $defaultOU
Write-Host "Using default Users container: $defaultOU" -ForegroundColor Cyan
}
# Create the user
Write-Host "Creating user with basic attributes..." -ForegroundColor Cyan
New-ADUser @newUserParams
# Verify creation with retry logic
$createdUser = Test-ADUserExists -Username $username
if (-not $createdUser) {
Write-Host "WARNING: Cannot verify user immediately (replication delay?)" -ForegroundColor Yellow
Write-Host "The user was created but may not be immediately visible." -ForegroundColor Yellow
Write-Host "Please check again in a few minutes." -ForegroundColor Yellow
}
else {
Write-Host "SUCCESS: User verified with DN: $($createdUser.DistinguishedName)" -ForegroundColor Green
}
# Set email attributes after creation
if ($username -and $emailAddress) {
Write-Host "`nSetting email attributes..." -ForegroundColor Cyan
$attributeErrors = Set-UserEmailAttributes -Username $username -EmailAddress $emailAddress -Domain $Domain
}
# Show final details with any errors
Show-FinalUserDetails -Username $username -AttributeErrors $attributeErrors
# Clean up password
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
# Group membership
$addGroups = Read-Host "`nAdd user to groups? (Y/N)"
if ($addGroups -eq 'Y' -or $addGroups -eq 'y') {
$groups = Read-Host "Enter group names (comma separated)"
foreach ($group in ($groups -split ',').Trim()) {
try {
Add-ADGroupMember -Identity $group -Members $username -ErrorAction Stop
Write-Host "Added to $group successfully" -ForegroundColor Green
}
catch {
Write-Host "Failed to add to $group : $_" -ForegroundColor Red
}
}
}
}
catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
if ($_.Exception.Message -like "*Access is denied*") {
Write-Host "`nTROUBLESHOOTING:" -ForegroundColor Yellow
Write-Host "1. Run as Administrator" -ForegroundColor Yellow
Write-Host "2. Verify permissions on target OU" -ForegroundColor Yellow
Write-Host "3. Try simpler OU path" -ForegroundColor Yellow
}
}
Write-Host "`nScript completed. Press Enter to continue..." -ForegroundColor Gray
$null = Read-Host
}
function CopyExistingUser {
param (
[string]$Domain
)
$sourceUsername = Read-Host "Enter username to copy"
try {
$sourceUser = Get-ADUser -Identity $sourceUsername -Properties * -ErrorAction Stop
Write-Host "`nCopying from: $($sourceUser.Name)" -ForegroundColor Cyan
Write-Host "Current location: $($sourceUser.DistinguishedName)" -ForegroundColor Cyan
# Get parent OU
$sourceOU = $sourceUser.DistinguishedName -replace '^CN=[^,]+,',''
$firstName = Read-Host "First name [$($sourceUser.GivenName)]"
if ([string]::IsNullOrWhiteSpace($firstName)) { $firstName = $sourceUser.GivenName }
$lastName = Read-Host "Last name [$($sourceUser.Surname)]"
if ([string]::IsNullOrWhiteSpace($lastName)) { $lastName = $sourceUser.Surname }
$username = Read-Host "New username (blank to auto-generate)"
if ([string]::IsNullOrWhiteSpace($username)) {
$username = ($firstName.Substring(0,1) + $lastName).ToLower()
Write-Host "Auto-generated username: $username" -ForegroundColor Yellow
}
$displayName = "$firstName $lastName"
$emailAddress = "$username@$Domain"
$attributeErrors = @()
# OU Selection
$ouChoice = Read-Host "Use source OU? [$sourceOU] (Y/N)"
if ($ouChoice -eq 'Y' -or $ouChoice -eq 'y') {
$ou = $sourceOU
}
else {
$ou = Read-Host "Enter target OU DN"
}
# Validate OU
try {
$null = Get-ADObject -Identity $ou -ErrorAction Stop
Write-Host "Validated target OU: $ou" -ForegroundColor Cyan
}
catch {
Write-Host "Invalid OU, using source OU instead" -ForegroundColor Yellow
$ou = $sourceOU
}
$password = Get-ValidPassword
try {
# Base parameters
$newUserParams = @{
GivenName = $firstName
Surname = $lastName
Name = $displayName
DisplayName = $displayName
SamAccountName = $username
UserPrincipalName = $emailAddress
AccountPassword = $password
Enabled = $true
ChangePasswordAtLogon = $true
Path = $ou
EmailAddress = $emailAddress
ErrorAction = 'Stop'
}
# Copy organizational attributes if they exist
$attributesToCopy = @('Company','Department','Title','Office',
'StreetAddress','City','State','PostalCode',
'Country','OfficePhone')
foreach ($attr in $attributesToCopy) {
if ($sourceUser.$attr) {
$newUserParams[$attr] = $sourceUser.$attr
}
}
Write-Host "Creating user with basic attributes..." -ForegroundColor Cyan
New-ADUser @newUserParams
# Verify creation with retry logic
$createdUser = Test-ADUserExists -Username $username
if (-not $createdUser) {
Write-Host "WARNING: Cannot verify user immediately (replication delay?)" -ForegroundColor Yellow
Write-Host "The user was created but may not be immediately visible." -ForegroundColor Yellow
Write-Host "Please check again in a few minutes." -ForegroundColor Yellow
}
else {
Write-Host "SUCCESS: User verified with DN: $($createdUser.DistinguishedName)" -ForegroundColor Green
}
# Set email attributes after creation
if ($username -and $emailAddress) {
Write-Host "`nSetting email attributes..." -ForegroundColor Cyan
$attributeErrors = Set-UserEmailAttributes -Username $username -EmailAddress $emailAddress -Domain $Domain
}
# Show final details with any errors
Show-FinalUserDetails -Username $username -AttributeErrors $attributeErrors
# Clean up password
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
# Copy groups
$copyGroups = Read-Host "`nCopy group memberships? (Y/N)"
if ($copyGroups -eq 'Y' -or $copyGroups -eq 'y') {
$groups = Get-ADPrincipalGroupMembership -Identity $sourceUsername |
Where-Object { $_.Name -ne "Domain Users" }
if ($groups) {
foreach ($group in $groups) {
try {
Add-ADGroupMember -Identity $group -Members $username -ErrorAction Stop
Write-Host "Added to $($group.Name)" -ForegroundColor Green
}
catch {
Write-Host "Failed to add to $($group.Name) : $_" -ForegroundColor Red
}
}
}
else {
Write-Host "No additional groups to copy" -ForegroundColor Yellow
}
}
}
catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
}
}
catch {
Write-Host "Error accessing source user: $_" -ForegroundColor Red
}
Write-Host "`nScript completed. Press Enter to continue..." -ForegroundColor Gray
$null = Read-Host
}
# Main execution
do {
Show-Menu
$selection = Read-Host "Please make a selection"
switch ($selection) {
'1' {
$domain = Get-DomainSelection
CreateNewUser -Domain $domain
}
'2' {
$domain = Get-DomainSelection
CopyExistingUser -Domain $domain
}
'Q' {
exit
}
default {
Write-Host "Invalid selection" -ForegroundColor Red
pause
}
}
} while ($selection -ne 'Q')