Creating Active Directory User Accounts with ADSI and PowerShell

PowerShell Text Purple hero
I hope you have been enjoying our exploration of PowerShell Active Directory alternatives. Certainly, the Active Directory module from RSAT is the way to go but sometimes you may want a bit more control. You also may have a need to roll your own tools. Today, I want to give you some ideas on how to create user accounts using LDAP and ADSI. If you are just jumping in, I recommend you take a few minutes to get caught up with the previous articles:

 
 
 
 

 
 
The first thing you will need is an ADSI reference to the organizational unit or container.

[ADSI]$OU = "LDAP://OU=IT,OU=Departments,OU=Employees,DC=Globomantics,DC=Local"

I want to create a user account for a new hire, Ginger Snaps. I will do this with the Create() method on the OU object. This method needs an object type and a canonical name. Be sure to save the results to a variable because you will need it.

$new = $OU.Create("user","CN=Ginger Snaps")

Right now, this account only exists locally. The first property to set is the account name. This is a new object, which means we need to use the Put() method.

$new.put("samaccountname","gsnaps")

You can also use the InvokeSet() method.

$new.InvokeSet("samaccountname","gsnaps")

Even though I will be setting additional properties, I need to commit the account to Active Directory before going any further.

$new.setinfo()

Don’t worry. The account is disabled by default and does not have a password. So let’s set those things now.

$new.put("userAccountControl",544)

If you want to disable the account, do not set it to this or 546. You can define a new password and configure the account. The user will have to change the password at next login.

#set initial password
$new.setpassword("P@ssw0rd")
#force change at next logon
$new.Put("pwdLastSet",0)

All of these changes are with the local cached copy of the user account. While I am at it, I might as well set some additional user properties.

$new.put("UserPrincipalName","[email protected]")
$new.put("DisplayName","Ginger Snaps")
$new.Put("Department","IT")
$new.put("Title","PowerShell Specialist")
$new.put("GivenName","Ginger")
$new.put("sn","Snaps")
$new.put("company","Globomantics")
$new.put("description","PowerShell/DevOps Team")

The tricky part is figuring out the LDAP property name. Some of them are not obvious. One thing you can do is use the AttributeEditor on a user account in Active Directory Users and Computers. I started a disabled dummy account and filled out all of the fields. Next, I used the Attribute Editor tab.

User Attribute Editor (Image Credit: Jeff Hicks)
User Attribute Editor (Image Credit: Jeff Hicks)

If you do not see this, make sure you have checked the Advanced Features under the View menu.

Do not forget to commit the changes one more time.

$new.setinfo()

As soon as replication converges, I can see the new account.

Verify the new account (Image Credit: Jeff Hicks)
Verify the New Account (Image Credit: Jeff Hicks)

Deleting an account is even easier. Invoke the Delete method on the OU or container object.

$OU.Delete("user","CN=Ginger Snaps")

Be careful. The deletion is immediate and there is no -WhatIf support.
Of course, this would be much easier with some sort of tooling like a function. You could write a version of New-ADUser customized to your environment with different parameters for the different user properties. I wrote something a bit more flexible.

Function New-LDAPUser {
[cmdletbinding()]
Param(
[parameter(Position = 0, Mandatory)]
[ValidatePattern("^\w+\s\w+$")]
[string]$Name,
[string]$DefaultPassword = "P@ssw0rd",
[string]$OU = "OU=Employees,DC=Globomantics,DC=Local",
[hashtable]$Properties,
[switch]$Disable,
[switch]$Passthru
)
#try to get the OU
[ADSI]$Parent = "LDAP://$OU"
#verify the OU exists
if (-Not $parent.distinguishedname) {
    Write-Warning "Can't find  OU $OU"
    #bail out
    Return
}
#split name into two variables
$firstname,$lastname = $name.split()
#define samaccountname
$sam = "{0}{1}" -f $firstname[0],$lastname
Write-Verbose "Testing if $sam already exists in the domain"
#test if name already exists in the domain
[ADSI]$Test = "WinNT://$($env:userdomain)/$sam,user"
If ($test.ADSPath) {
    Write-Warning "A user with the account name of $sam already exists"
    #bail out
    Return
}
Write-Verbose "Creating new user $Name in $OU"
$new = $parent.Create("user","CN=$Name")
$new.put("samaccountname",$sam)
$new.setinfo()
Write-Verbose "Setting name properties"
$new.put("givenname",$firstname)
$new.put("sn",$lastname)
$new.put("userprincipalname","[email protected]")
$new.put("Displayname",$name)
if ($hash) {
    Write-Verbose "Setting additional properties"
    foreach ($key in $hash.keys) {
        Write-Verbose "...$key"
        #verify property is valid
        Try {
           $new.invokeGet($key)
           $new.put($key,$hash.item($key))
        }
        Catch {
            Write-Warning "$key is not a valid property name"
        }
     }
}
Write-Verbose "set initial password"
$new.setpassword("P@ssw0rd")
Write-Verbose "force change at next logon"
$new.Put("pwdLastSet",0)
if ($Disable) {
    Write-Verbose "Disabling the account"
    $new.put("userAccountControl",546)
}
else {
    $new.put("userAccountControl",544)
}
Write-Verbose "committing changes"
$new.setinfo()
if ($Passthru) {
    $new.refreshcache()
    $new
}
} #end function

This version does not include support for WhatIf but you could add it. The function needs the distinguishedname for the parent container and the user’s name. I am using simple names such as John Deere. The samAccountname is derived from the first initial of both the first and last name. I have a code that creates it from the username, which I have split.

#split name into two variables
$firstname,$lastname = $name.split()
#define samaccountname
$sam = "{0}{1}" -f $firstname[0],$lastname

Of course, you would need to define your own standard code. I have included some validation to ensure a valid OU without a naming conflict. This version simply reports any of these problems and bails out.
Otherwise, the function uses the steps I outlined above to create and define a user account.

Creating a new user (Image Credit: Jeff Hicks)
Creating a New User (Image Credit: Jeff Hicks)

Here is the account in ADUC:
The new account (Image Credit: Jeff Hicks)
The New Account (Image Credit: Jeff Hicks)

The other feature in this function is that it accepts a hashtable of LDAP property names and values.

$hash=@{
Company = "globomantics"
Department = "IT"
Title = "DevOps Engineer"
Description = "Project Phoenix"
PhysicalDeliveryOfficeName = "QK-456"
phone = "x8732"
}

The function processes this hashtable and sets the value for each entry, assuming it is valid. I use the InvokeGet() method, which will throw an error with an invalid property. I can create a richer user account.

New-LDAPUser -Name "Ken Dew" -OU "OU=IT,OU=Departments,OU=Employees,DC=Globomantics,DC=local" -Properties $hash -Passthru -Verbose

Creating a rich user object (Image Credit: Jeff Hicks)
Creating a Rich User Object (Image Credit: Jeff Hicks)


Understanding the ADSI basics is key. It is not that difficult to build custom AD tooling for your environment that does not rely on RSAT. Next time, we will turn our attention to managing groups and group membership with ADSI and PowerShell.