<# .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')