- Save it as
New-CADUser.psm1
- Import it using
Import-Module .\\New-CADUser.psm1
- Use it with:
New-CADUser -FirstName "John" -LastName "Doe" -SourceUser "existinguser"
<#
.SYNOPSIS
Creates a new Active Directory user based on a source user template.
.DESCRIPTION
The New-CADUser function creates a new Active Directory user by copying attributes from an existing source user.
It supports Just-In-Time (JIT) privilege elevation, logging, and optional group memberships.
The function also generates a secure password for the new user and can copy it to the clipboard.
.PARAMETER FirstName
The first name of the new user. This parameter is mandatory.
.PARAMETER LastName
The last name of the new user. This parameter is mandatory.
.PARAMETER SourceUser
The SamAccountName of the source user to copy attributes from. This parameter is mandatory.
.PARAMETER Groups
An array of additional groups to add the new user to. This parameter is optional.
.PARAMETER PasswordLength
The length of the generated password. Must be between 16 and 32 characters. Default is 16. This parameter is optional.
.PARAMETER LogPath
The path to the directory where log files will be stored. Default is "$env:USERPROFILE\\Documents\\AD_User_Creation_Logs". This parameter is optional.
.PARAMETER DisableClipboard
Switch to disable copying the generated password to the clipboard. This parameter is optional.
.PARAMETER JitDuration
The duration in minutes for which JIT privileges are granted. Must be between 1 and 60 minutes. Default is 5. This parameter is optional.
.EXAMPLE
New-CADUser -FirstName "John" -LastName "Doe" -SourceUser "templateUser"
Creates a new AD user "John Doe" based on the attributes of "templateUser".
.EXAMPLE
New-CADUser -FirstName "Jane" -LastName "Smith" -SourceUser "templateUser" -Groups "HR", "Finance" -PasswordLength 20
Creates a new AD user "Jane Smith" based on the attributes of "templateUser" and adds the user to the "HR" and "Finance" groups with a 20-character password.
.NOTES
Requires PowerShell 5.1 or later.
Requires the ActiveDirectory and Microsoft.PowerShell.Security modules.
Requires administrative privileges to run.
#>
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Security
function New-CADUser {
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[a-zA-Z\\-]+$')]
[string]$FirstName,
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[a-zA-Z\\-]+$')]
[string]$LastName,
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[string]$SourceUser,
[Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string[]]$Groups,
[Parameter(Mandatory = $false)]
[ValidateRange(16, 32)]
[int]$PasswordLength = 16,
[Parameter(Mandatory = $false)]
[ValidateScript({ Test-Path -Path $_ -IsValid })]
[string]$LogPath = "$env:USERPROFILE\\Documents\\AD_User_Creation_Logs",
[Parameter(Mandatory = $false)]
[switch]$DisableClipboard,
[Parameter(Mandatory = $false)]
[ValidateRange(1, 60)]
[int]$JitDuration = 5
)
begin {
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
if (-not (Test-Path -Path $LogPath)) {
New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
}
$LogFile = Join-Path -Path $LogPath -ChildPath "AD_User_Creation_$(Get-Date -Format 'yyyyMMdd').log"
$script:CreatedUsers = @{}
$script:FailedUsers = @{}
$script:ElevatedPrivs = $false
function Write-AuditLog {
param([string]$Message, [string]$Level = 'Information')
$LogEntry = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] [$env:USERNAME] $Message"
Add-Content -Path $LogFile -Value $LogEntry
switch ($Level) {
'Error' { Write-Error $Message }
'Warning' { Write-Warning $Message }
default { Write-Verbose $Message }
}
}
function Request-ElevatedPrivileges {
try {
$adminGroup = [ADSI]"WinNT://./Administrators,group"
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$adminGroup.Add("WinNT://$currentUser")
$script:ElevatedPrivs = $true
$Timer = New-Object System.Timers.Timer
$Timer.Interval = $JitDuration * 60 * 1000
$Timer.AutoReset = $false
$Timer.Enabled = $true
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
$adminGroup = [ADSI]"WinNT://./Administrators,group"
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$adminGroup.Remove("WinNT://$currentUser")
Write-AuditLog "JIT privileges removed for $currentUser"
} | Out-Null
Write-AuditLog "JIT privileges granted for $JitDuration minutes"
return $true
}
catch {
Write-AuditLog "Failed to elevate privileges: $_" -Level Error
return $false
}
}
function Remove-FailedUser {
param([string]$Username)
try {
if (Get-ADUser -Identity $Username -ErrorAction SilentlyContinue) {
Remove-ADUser -Identity $Username -Confirm:$false
Write-AuditLog "Cleaned up failed user creation: $Username"
}
$script:FailedUsers.Remove($Username)
}
catch {
Write-AuditLog "Failed to cleanup user $Username`: $_" -Level Error
}
}
if (-not (Request-ElevatedPrivileges)) {
throw "Failed to obtain required privileges"
}
}
process {
try {
$FirstName = $FirstName.Trim()
$LastName = $LastName.Trim()
$SourceUser = $SourceUser.Trim()
$Username = $null
$SourceUserInfo = Get-ADUser -Identity $SourceUser -Properties Title, Department, EmailAddress,
DistinguishedName, AccountExpirationDate, Manager, Company -ErrorAction Stop
$Password = New-SecurePassword -Length $PasswordLength
$SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
$OUPath = ($SourceUserInfo.DistinguishedName -split ',', 2)[1]
$Username = ($FirstName[0] + $LastName).ToLower()
if (Get-ADUser -Filter "SamAccountName -eq '$Username'" -ErrorAction SilentlyContinue) {
$Increment = 1
while (Get-ADUser -Filter "SamAccountName -eq '$Username$Increment'" -ErrorAction SilentlyContinue) {
$Increment++
}
$Username = "$Username$Increment"
}
$EmailDomain = ($SourceUserInfo.EmailAddress -split '@')[1]
$Email = "$Username@$EmailDomain"
$UserParams = @{
Name = "$FirstName $LastName"
SamAccountName = $Username
UserPrincipalName = $Email
GivenName = $FirstName
Surname = $LastName
Title = $SourceUserInfo.Title
Department = $SourceUserInfo.Department
EmailAddress = $Email
Path = $OUPath
AccountPassword = $SecurePassword
Enabled = $true
ChangePasswordAtLogon = $true
PasswordNeverExpires = $false
CannotChangePassword = $false
Manager = $SourceUserInfo.Manager
Company = $SourceUserInfo.Company
AccountExpirationDate = $SourceUserInfo.AccountExpirationDate
}
if ($PSCmdlet.ShouldProcess($Username, "Create new AD user")) {
try {
$NewUser = New-ADUser @UserParams -PassThru
$script:CreatedUsers[$Username] = $true
Write-Host "Created user account: $Username" -ForegroundColor Green
$SourceGroups = Get-ADPrincipalGroupMembership -Identity $SourceUser |
Where-Object { $_.Name -ne 'Domain Users' }
foreach ($Group in $SourceGroups) {
Add-ADPrincipalGroupMembership -Identity $Username -MemberOf $Group.Name
}
if ($Groups) {
foreach ($Group in $Groups) {
if (Get-ADGroup -Identity $Group) {
Add-ADPrincipalGroupMembership -Identity $Username -MemberOf $Group
}
}
}
Set-ADUser -Identity $Username -Replace @{
SmartcardLogonRequired = $false
PasswordNotRequired = $false
TrustedForDelegation = $false
AccountNotDelegated = $true
}
if (-not $DisableClipboard) {
$Password | Set-Clipboard
}
return [PSCustomObject]@{
Username = $Username
Password = $Password
Email = $Email
FullName = "$FirstName $LastName"
Created = (Get-Date).ToString()
Path = $OUPath
}
}
catch {
$script:FailedUsers[$Username] = $true
throw
}
}
}
catch {
Write-AuditLog "Failed to create user account for $Username`: $_" -Level Error
throw
}
}
end {
foreach ($FailedUser in $script:FailedUsers.Keys) {
Remove-FailedUser -Username $FailedUser
}
foreach ($Username in $script:CreatedUsers.Keys) {
$User = Get-ADUser -Identity $Username -Properties PasswordLastSet,
PasswordNeverExpires, PasswordNotRequired, Enabled
$SecurityIssues = @()
if (-not $User.PasswordLastSet) { $SecurityIssues += "Password not set" }
if ($User.PasswordNeverExpires) { $SecurityIssues += "Password never expires" }
if ($User.PasswordNotRequired) { $SecurityIssues += "Password not required" }
if (-not $User.Enabled) { $SecurityIssues += "Account disabled" }
if ($SecurityIssues) {
Write-AuditLog "Security issues found for $Username`: $($SecurityIssues -join ', ')" -Level Warning
}
}
Write-AuditLog "Operation completed"
}
}
function New-SecurePassword {
param (
[ValidateRange(16, 32)]
[int]$Length = 16
)
$RNG = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
try {
$CharSets = @{
UpperCase = [char[]]('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
LowerCase = [char[]]('abcdefghijklmnopqrstuvwxyz')
Numbers = [char[]]('0123456789')
Specials = [char[]]('!@#$%^&*()-_=+[]{}|;:,.<>?')
}
$Password = @(
Get-SecureRandomElement -CharArray $CharSets.UpperCase -RNG $RNG
Get-SecureRandomElement -CharArray $CharSets.LowerCase -RNG $RNG
Get-SecureRandomElement -CharArray $CharSets.Numbers -RNG $RNG
Get-SecureRandomElement -CharArray $CharSets.Specials -RNG $RNG
)
$AllChars = $CharSets.Values | ForEach-Object { $_ }
while ($Password.Length -lt $Length) {
$Password += Get-SecureRandomElement -CharArray $AllChars -RNG $RNG
}
# Fisher-Yates shuffle
$MaxIndex = $Password.Length - 1
for ($i = $MaxIndex; $i -gt 0; $i--) {
$RandomIndex = Get-SecureRandomRange -Max ($i + 1) -RNG $RNG
$Temp = $Password[$i]
$Password[$i] = $Password[$RandomIndex]
$Password[$RandomIndex] = $Temp
}
return -join $Password
}
finally {
if ($RNG) { $RNG.Dispose() }
}
}
function Get-SecureRandomElement {
param (
[char[]]$CharArray,
[System.Security.Cryptography.RNGCryptoServiceProvider]$RNG
)
return $CharArray[(Get-SecureRandomRange -Max $CharArray.Length -RNG $RNG)]
}
function Get-SecureRandomRange {
param (
[int]$Max,
[System.Security.Cryptography.RNGCryptoServiceProvider]$RNG
)
$Bytes = New-Object byte[](4)
do {
$RNG.GetBytes($Bytes)
$Value = [BitConverter]::ToInt32($Bytes, 0)
}
while ($Value -lt 0 -or $Value -ge ([int]::MaxValue - ([int]::MaxValue % $Max)))
return $Value % $Max
}
Export-ModuleMember -Function New-CADUser