powershellcsvautomationget-aduser

Define AD Account Expiry Date Time and AD groups for Bulk AD Account Creation via CSV file


I have a script which automates bulk AD account creations.

The script works fine but when I try to define account expiry date and time for each user (-AccountExpirationDate) it fails.

The script is as follows:

# Store the data from NewUsersFinal.csv in the $ADUsers variable
$ADUsers = Import-Csv C:\temp\NewUsersSent.csv

# Define UPN
$UPN = "my.domain.net"

# Loop through each row containing user details in the CSV file
foreach ($User in $ADUsers) {

    #Read user data from each field in each row and assign the data to a variable as below
    $username = $User.username
    $password = $User.password
    $firstname = $User.firstname
    $lastname = $User.lastname
    $OU = $User.ou #This field refers to the OU the user account is to be created in
    $email = $User.email
    $telephone = $User.telephone
    $jobtitle = $User.jobtitle
    $accountexpires = $User.accountexpires

    # Check to see if the user already exists in AD
    if (Get-ADUser -F { SamAccountName -eq $username }) {
        
        # If user does exist, give a warning
        Write-Warning "A user account with username $username already exists in Active Directory."
    }
    else {

        # User does not exist then proceed to create the new user account
        # Account will be created in the OU provided by the $OU variable read from the CSV file
        New-ADUser `
            -SamAccountName $username `
            -UserPrincipalName "$username@$UPN" `
            -Name "$firstname $lastname" `
            -GivenName $firstname `
            -Surname $lastname `
            -Enabled $True `
            -DisplayName "$lastname, $firstname" `
            -Path $OU `
            -OfficePhone $telephone `
            -MobilePhone $telephone `
            -HomePhone $telephone `
            -EmailAddress $email `
            -Description $jobtitle `
            -AccountExpirationDate $accountexpires `
            -AccountPassword (ConvertTo-secureString $password -AsPlainText -Force) -ChangePasswordAtLogon $False -PasswordNeverExpires $True

        # If user is created, show message.
        Write-Host "The user account $username is created." -ForegroundColor Cyan
    }
}

The CSV file is below:

CSV File Format

When I run the code I get the following errors (the account don't actually get created):

New-ADUser : Cannot bind parameter 'AccountExpirationDate'. Cannot convert value ""11/17/2023 2:22:48 PM"" to type "System.DateTime". Error: "String was not recognized 
as a valid DateTime."
At C:\Users\Me\Desktop\BulkNewUsers.ps1:45 char:36
+             -AccountExpirationDate $accountexpires `
+                                    ~~~~~~~~~~~~~~~
   + CategoryInfo          : InvalidArgument: (:) [New-ADUser], ParameterBindingException
   + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.ActiveDirectory.Management.Commands.NewADUser

The user account TTesting1 is created.
New-ADUser : Cannot bind parameter 'AccountExpirationDate'. Cannot convert value "0" to type "System.DateTime". Error: "String was not recognized as a valid DateTime."
At C:\Users\Me\Desktop\BulkNewUsers.ps1:45 char:36
+             -AccountExpirationDate $accountexpires `
+                                    ~~~~~~~~~~~~~~~
   + CategoryInfo          : InvalidArgument: (:) [New-ADUser], ParameterBindingException
   + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.ActiveDirectory.Management.Commands.NewADUser

The user account TTesting2 is created.
Press Enter to exit: 

Seems like PS is not accepting my date time format.

I have tried to read up from the following sites:

https://ss64.com/ps/new-aduser.html

https://ss64.com/ps/syntax-dateformats.html

I believe I need to specify format pattern for PS to accept my date time in the CSV.

I have also read that stating "0" sets the account not to expire - could this be clarified?

I was also wondering if it is also possible to define AD groups to be added into the CSV to add new users too with using ; as delimiter to each AD group name? - I believe I may need to incorporate Get-ADPrincipalGroupMembership into the script.

UPDATE 1

Thanks Theo ran you script executed with no error BUT the account expiry was not set:

AD Account Expiry1

AD Account Expiry2

Is it because I created these account with previous failed attempts (do I need to wait for 24 hours before attempting to create the account again - cached detail) I did delete the account and forced replication in DCs.

UPDATE 2

Theo as requested.

I tried the first suggested code below:

Code Alteration 1

Got this error code:

PS C:\Windows\system32> C:\Users\Me\Desktop\BulkNewUsers.ps1
At C:\Users\Me\Desktop\BulkNewUsers.ps1:48 char:67
+     $accountexpires = (Get-Date) ; [datetime]::TryParse(11/17/2023 2:22:48 PM, [ ...
+                                                                   ~
Missing ')' in method call.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:48 char:68
+     $accountexpires = (Get-Date) ; [datetime]::TryParse(11/17/2023 2:22:48 PM, [ ...
+                                                                    ~~~~~~~
Unexpected token '2:22:48' in expression or statement.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:8 char:29
+ foreach ($User in $ADUsers) {
+                             ~
Missing closing '}' in statement block.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:48 char:100
+ ... $accountexpires) {
+                    ~
Unexpected token ')' in expression or statement.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:64 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

I tried the second suggested code below:

Code Alteration 2

Got this error code:

PS C:\Windows\system32> C:\Users\Me\Desktop\BulkNewUsers.ps1
At C:\Users\Me\Desktop\BulkNewUsers.ps1:48 char:72
+     $accountexpires = (Get-Date) ; [datetime]::TryParseExact(11/17/2023 2:22:48  ...
+                                                                        ~
Missing ')' in method call.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:48 char:73
+     $accountexpires = (Get-Date) ; [datetime]::TryParseExact(11/17/2023 2:22:48  ...
+                                                                         ~~~~~~~
Unexpected token '2:22:48' in expression or statement.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:8 char:29
+ foreach ($User in $ADUsers) {
+                             ~
Missing closing '}' in statement block.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:48 char:159
+ ... $accountexpires){
+                    ~
Unexpected token ')' in expression or statement.
At C:\Users\Me\Desktop\BulkNewUsers.ps1:64 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

Seems like the same error code?

Please see CSV in notepad format below:

FirstName,LastName,Username,Email,Password,Telephone,JobTitle,AccountExpires,OU
Test1,Test2,TTesting1,Testing1@email.co.uk,V6DyG3H7a,4.47723E+11,This is a Test 1,11/17/2023 2:22:48 PM,"CN=Users,DC=my,DC=domain,DC=net"
Test3,Test4,TTesting2,Testing2@email.co.uk,438MXn8D5,4.47723E+11,This is a Test 2,0,"CN=Users,DC=my,DC=domain,DC=net"

UPDATE 3

I did what you asked

$accountexpires = (Get-Date)  
[datetime]::TryParseExact('11/17/2023 2:22:48 PM', 'MM/dd/yyyy h:mm:ss tt', [cultureinfo]'en-US', 'None',[ref]$accountexpires) 

This returned "true" in PowerShell.

It weird you mentioned the non printing character as we had an issue with copying text from MS SharePoint to Notepad then to AD - which would contain special hidden characters, to overcome this (being lazy) I instead copied to from MS SharePoint to MS Word then into AD which fixed the special hidden characters (to view the special hidden characters we performed conversion on the text to "ASCII to Hex" then back to "HEX to ASCII" which would display the special hidden characters)

I did what you asked I made the changes in Notepad fields in between double quotes and re-typed the expiry dates and this fixed the issue! Expiry date displaying on newly created users.

Thanks!!!

Now I tried to add AD Groups and it semi failed (I guess to naming conventions of the AD groups which they contain "-" and "_" in there names)

Please see New CSV in notepad format below:

FirstName,LastName,Username,Email,Password,Telephone,JobTitle,AccountExpires,OU,ADGroups
"Test1","Test2","TTesting1","Testing1@email.co.uk","V6DyG3H7a","447723000000","This is a Test 1","11/17/2023 2:22:48 PM","CN=Users,DC=my,DC=domain,DC=net","ADGroupName1;AD_Group_Name_2;AD-Group-Name-3;"
"Test3","Test4","TTesting2","Testing2@email.co.uk","438MXn8D5","447723000001","This is a Test 2","0","CN=Users,DC=my,DC=domain,DC=net""ADGroupName1;AD_Group_Name_2;"

The code I used below:

# Store the data from NewUsersFinal.csv in the $ADUsers variable
$ADUsers = Import-Csv -Path 'C:\temp\NewUsersSent.csv'

# Define UPN
$UPN = "my.domain.net"

# Loop through each row containing user details in the CSV file
foreach ($User in $ADUsers) {
    # Read some user data from each field in each row and assign the data to a variable as below
    $username = $User.username

    # Check to see if the user already exists in AD
    if (Get-ADUser -Filter "SamAccountName -eq '$username'") {
        # If user does exist, give a warning
        Write-Warning "A user account with username $username already exists in Active Directory."
        continue  # skip this one and proceed with the next user
    }

    # User does not exist then proceed to create the new user account
    # Account will be created in the OU provided by the $OU variable read from the CSV file

    # create some convenience variables from the csv data as these are used more than once
    $firstname      = $User.firstname
    $lastname       = $User.lastname
    $telephone      = $User.telephone

    # create a splatting Hashtable for the New-ADUser cmdlet
    $userParams = @{
        SamAccountName        = $username
        UserPrincipalName     = "$username@$UPN"
        Name                  = "$firstname $lastname"
        GivenName             = $firstname
        Surname               = $lastname
        Enabled               = $true
        DisplayName           = "$lastname, $firstname"
        Path                  = $User.ou
        OfficePhone           = $telephone
        MobilePhone           = $telephone
        HomePhone             = $telephone
        EmailAddress          = $User.email
        Description           = $User.jobtitle
        AccountPassword       = (ConvertTo-secureString $User.password -AsPlainText -Force)
        ChangePasswordAtLogon = $false
        PasswordNeverExpires  = $true
    }

    # see if we can get a valid DateTime from the csv field 'accountexpires'
    $accountexpires = (Get-Date)
    if ([datetime]::TryParseExact($User.accountexpires, 'MM/dd/yyyy h:mm:ss tt', [cultureinfo]'en-US', 'None',[ref]$accountexpires)) {
        # this user does have an account expiration date, so add it to the Hash
        $userParams['AccountExpirationDate'] = $accountexpires
    }
    # create the user (PassThru makes the cmdlet return the user object just created)
    $newUser = New-ADUser @userParams -PassThru
    Write-Host "The user account $username is created." -ForegroundColor Cyan

    #####################################################################################
    # If you have a field in the CSV where groups are listed the new user should become
    # a member of (as you said, separated by ;), you can add the user to these groups here:
    
    $User.adgroups -split ';' | Where-Object { $_ -match '\S' } | ForEach-Object {
         Add-ADGroupMember -Identity $_ -Members $newUser
     }

}

There error messages I got from PS when I tried to run the code with AD groups:

PS C:\Windows\system32> C:\Users\Me\Desktop\BulkNewUsers.ps1
The user account TTesting1 is created.
New-ADUser : The object name has bad syntax
At C:\Users\Me\Desktop\BulkNewUsers.ps1:54 char:16
+     $newUser = New-ADUser @userParams -PassThru
+                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (CN=Test3 Test4,...AD_Group_Name_2;:String) [New-ADUser], ADException
    + FullyQualifiedErrorId : ActiveDirectoryServer:8335,Microsoft.ActiveDirectory.Management.Commands.NewADUser
 
The user account TTesting2 is created.

Weirdly the first account is created but the second is not.

Do I have also have to define Groups = $User.ADGroups in $userParams part of the code?

UPDATE 4

Theo I figured out the issue in UPDATE 3 is was a "," was not defined between double quotations of the OU field and the AD Group field for the 2nd user in the CSV file:

"CN=Users,DC=my,DC=domain,DC=net""ADGroupName1;AD_Group_Name_2;"

Changed to:

"CN=Users,DC=my,DC=domain,DC=net","ADGroupName1;AD_Group_Name_2;"

This resolved the issue!


Solution

  • The main problem here is that when a user should be created with an account that never expires, the AccountExpirationDate should not be set. Otherwise, a valid DateTime object should be used to set the expiration date and time. (if no time is specified, the time is assumed to 12:00:00 AM local time.)

    Your code uses the nasty backticks to separate the parameters for New-ADUser each on a new line, but a far better approach is to use Splatting on cmdlets that take a lot of parameters.
    That way, the code is easy to read/maintain, PLUS you can either add of omit certain parameters based on a condition like in this case the AccountExpirationDate.

    Please also read the commented code at the bottom to show how you can add the new user to groups too.

    # Store the data from NewUsersFinal.csv in the $ADUsers variable
    $ADUsers = Import-Csv -Path 'C:\temp\NewUsersSent.csv'
    
    # Define UPN
    $UPN = "my.domain.net"
    
    # Loop through each row containing user details in the CSV file
    foreach ($User in $ADUsers) {
        # Read some user data from each field in each row and assign the data to a variable as below
        $username = $User.username
    
        # Check to see if the user already exists in AD
        if (Get-ADUser -Filter "SamAccountName -eq '$username'") {
            # If user does exist, give a warning
            Write-Warning "A user account with username $username already exists in Active Directory."
            continue  # skip this one and proceed with the next user
        }
    
        # User does not exist then proceed to create the new user account
        # Account will be created in the OU provided by the $OU variable read from the CSV file
    
        # create some convenience variables from the csv data as these are used more than once
        $firstname      = $User.firstname
        $lastname       = $User.lastname
        $telephone      = $User.telephone
    
        # create a splatting Hashtable for the New-ADUser cmdlet
        $userParams = @{
            SamAccountName        = $username
            UserPrincipalName     = "$username@$UPN"
            Name                  = "$firstname $lastname"
            GivenName             = $firstname
            Surname               = $lastname
            Enabled               = $true
            DisplayName           = "$lastname, $firstname"
            Path                  = $User.ou
            OfficePhone           = $telephone
            MobilePhone           = $telephone
            HomePhone             = $telephone
            EmailAddress          = $User.email
            Description           = $User.jobtitle
            AccountPassword       = (ConvertTo-secureString $User.password -AsPlainText -Force)
            ChangePasswordAtLogon = $false
            PasswordNeverExpires  = $true
        }
    
        # see if we can get a valid DateTime from the csv field 'accountexpires'
        $accountexpires = (Get-Date)
        if ([datetime]::TryParse($User.accountexpires, [ref]$accountexpires)) {
            # this user does have an account expiration date, so add it to the Hash
            $userParams['AccountExpirationDate'] = $accountexpires
        }
        # create the user (PassThru makes the cmdlet return the user object just created)
        $newUser = New-ADUser @userParams -PassThru
        Write-Host "The user account $username is created." -ForegroundColor Cyan
    
        #####################################################################################
        # If you have a field in the CSV where groups are listed the new user should become
        # a member of (as you said, separated by ;), you can add the user to these groups here:
    
        # $User.groups -split ';' | Where-Object { $_ -match '\S' } | ForEach-Object {
        #     Add-ADGroupMember -Identity $_ -Members $newUser
        # }
    
    }
    

    Instead of [datetime]::TryParse() you can also use the [datetime]::TryParseExact() method, depending on how you formatted the dates in your csv file.

    If you do use this English 12-hour clock format, [datetime]::TryParseExact($User.accountexpires, 'MM/dd/yyyy h:mm:ss tt', [cultureinfo]'en-US', 'None',[ref]$accountexpires) works.

    You can test this in Powershell easily with

    $accountexpires = (Get-Date)  
    [datetime]::TryParseExact('11/17/2023 2:22:48 PM', 'MM/dd/yyyy h:mm:ss tt', [cultureinfo]'en-US', 'None',[ref]$accountexpires) 
    

    which should return True

    If this returns False, you either mismatched the date pattern, or (as in your example) the date string got screwed-up somehow by invisible control characters. In that case, manually retype the date and test again.