1. Save it as New-CADUser.psm1
  2. Import it using Import-Module .\\New-CADUser.psm1
  3. 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