Functions/New-DryADUser.ps1

Using NameSpace System.Management.Automation.Runspaces
Using NameSpace System.IO
Using NameSpace System.Security
# DryActiveDirectory is an AD config module for use with DryDeploy, or by itself.
#
# Copyright (C) 2021 Bjørn Henrik Formo (bjornhenrikformo@gmail.com)
# LICENSE: https://raw.githubusercontent.com/bjoernf73/DryActiveDirectory/main/LICENSE
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Function New-DryADUser {
    [CmdletBinding(DefaultParameterSetName='Local')] 
    Param (
        [Parameter(Mandatory,HelpMessage="User Object Specification")]
        [ValidateNotNullOrEmpty()]
        [PSObject]
        $User,

        [Parameter(Mandatory,HelpMessage="NetBIOS domain name")]
        [String] 
        $DomainNB,

        [Parameter(Mandatory,ParameterSetName='Remote',
        HelpMessage="PSSession to run the script blocks in")]
        [PSSession] 
        $PSSession,

        [Parameter(Mandatory,ParameterSetName='Local',
        HelpMessage="For 'Local' sessions, specify the Domain Controller to use")]
        [String] 
        $DomainController,

        [Parameter(Mandatory,ParameterSetName='Remote',
        HelpMessage="Path to the Domain Controller's Web Certificate ('Server
        Authentication') Public Certificate file.
        Used in Remote execution to encrypt users's password"
)]
        [FileInfo]
        $DCPublicCertificateFilePath,

        [Parameter(HelpMessage="Should only be `$True when called from DryDeploy. Will allow autogenerated
        passwords for users that are created to be stored in DryDeploy's Credentials store. If you're running
        standalone (not as part of DryDeploy, hence `$DryDeploy = `$False), then passwords are autogenerated,
        but lost. You will have to reset the passwords of the created users to access those accounts"
)]
        [Switch]$DryDeploy
    )

    # Test if object exists. Currently does not test properties, only if it exists or not
    Try {
        If ($PSCmdlet.ParameterSetName -eq 'Remote') {
            $Server = 'localhost'
            $ExecutionType = 'Remote'
            ol v @('Session Type','Remote')
            ol v @('Remoting to Domain Controller',$PSSession.ComputerName)
        }
        Else {
            $Server = $DomainController
            $ExecutionType = 'Local'
            ol v @('Session Type','Local')
            ol v @('Using Domain Controller',$Server)
        }

        $GetArgumentList = @(
            $User.Name,
            $Server
        )
        $InvokeGetParams = @{
            ScriptBlock  = $DryAD_SB_User_Get
            ArgumentList = $GetArgumentList
        }
        If ($PSCmdlet.ParameterSetName -eq 'Remote') {
            $InvokeGetParams += @{
                Session = $PSSession
            }
        }
        $GetResult = Invoke-Command @InvokeGetParams
        
        Switch ($GetResult) {
            $True {
                ol s 'User exists already'
                ol v @('The User exists already',"$($User.Name)")
            }
            $False {
                ol v @('The user does not exist',"$($User.Name)")
            }
            Default {
                ol e @('Error trying to get user',"$($User.Name)")
                Throw $GetResult
            }
        }
        
        If ($GetResult -eq $False) {
            
            # If $User.Enabled not specified, enable the account
            If ($Null -eq $User.Enabled) {
                $User | Add-Member -MemberType 'NoteProperty' -Name 'Enabled' -Value $True
            }
            
            # Create a hash to splat to new-aduser
            $UserSpec = @{}
            $ExcludeProperties = @(
                'tag',
                'scope',
                'memberof',
                'password'
            )
            $User.PSObject.Properties | Foreach-Object {
                If ($_.Name -notin $ExcludeProperties) {
                    $UserSpec+=@{$_.Name=$_.Value}
                }
            }

            $GetDryADCredentialParams = @{
                UserName = "$DomainNB\$($User.Name)"
            }
            If ($User.password.get_or_generate -eq 'generate') {
                $GetDryADCredentialParams += @{
                    Random = $True
                }
                If ($User.password.length) {
                    $GetDryADCredentialParams += @{
                        Length = [Int]$User.password.length
                    }
                }
                If ($User.password.nonalphabetics) {
                    $GetDryADCredentialParams += @{
                        NonAlphabetics = [Int]$User.password.nonalphabetics
                    }
                }
                
                [PSCredential]$UserCredential = Get-DryADCredential @GetDryADCredentialParams

                If ($DryDeploy) {
                    # This sessions is running from DryDeploy - so add to DryDeploy Credential Store
                    Add-DryCredential -Name $User.Name -Credential $UserCredential
                }
            }
            Else {
                If ($DryDeploy) {
                    # Using function from DryDeploy to get Credential from it's Credential's Store
                    [PSCredential]$UserCredential = Get-DryCredential -Name $User.Name
                }
                Else {
                    # Invoking an interactive prompt
                    [PSCredential]$UserCredential = Get-DryADCredential @GetDryADCredentialParams
                }
            }
 
            Switch ($ExecutionType) {
                'Remote' {
                    # If remote execution, we must send the password over the network.
                    # If so, make sure the password is encrypted with the target Domain
                    # Controller's Server Certificate
                    $ConvertPwdParams = @{
                        ClearText       = $UserCredential.GetNetworkCredential().Password
                        CertificateFile = $DCPublicCertificateFilePath
                    }
                    $Base64EncodedPass = Convert-DryADClearTextToEncryptedString @ConvertPwdParams
                    ol i @("Encrypted password for $($User.Name)","$Base64EncodedPass")
                }
                'Local' {
                    # If local execution, we must send the password to Invoke-Command.
                    # It is good practise to use SecureString, which ensures that only
                    # the local system may decrypt it, so if it is accidentally sent
                    # over a PSSession to a remote system, it is unusable
                    [SecureString]$SecureString = $UserCredential.Password
                }
                Default {
                    Throw "Unknown ExecutionType: $ExecutionType"
                }
            }

            $SetArgumentList = @(
                $UserSpec,
                $ExecutionType,
                $Server
            )
            Switch ($ExecutionType) {
                'Remote' {
                    $SetArgumentList += $Base64EncodedPass
                }
                'Local' {
                    $SetArgumentList += $SecureString
                }
            }

            $InvokeSetParams = @{
                ScriptBlock  = $DryAD_SB_User_Set
                ArgumentList = $SetArgumentList
            }
            Switch ($ExecutionType) {
                'Remote' {
                    $InvokeSetParams += @{
                        Session = $PSSession
                    }
                }
                Default {
                }
            }
            $SetResult = Invoke-Command @InvokeSetParams
            
            If ($SetResult[0] -eq $True) {
                ol s 'User created'
                ol i @('Successfully created user',"$($User.Name)")
            }
            Else {
                ol f 'User not created'
                ol e "Error creating user $($User.Name): $($SetResult[1])"
                Throw "$($SetResult[1])"
            }
        }
    }
    Catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }
}