.NET Assemblies In PowerShell – Part 3: Create Active Directory objects

In the first parts of the series “.NET Assemblies In PowerShell”, I wrote about managing existing Active Directory groups and user accounts without the PowerShell ActiveDirectory module.

This time, I will show you how you can create new Active Directory objects with PowerShell by using the namespace System.DirectoryServices.AccountManagement. This namespace is available on any Windows computer by default.

As a prerequisite I recommend to read the first two parts of this series:

Part 1: Manage Active Directory groups
Part 2: Manage Active Directory group members and user accounts 

If you want to skip this, that´s fine. Just know that the base of all this is the above mentioned namespace.

If you want to skip all the technical details and you are only here to grab some code, that´s fine too. In this case, you can just go to functions.

The name space

To add the namespace to your Powershell script, use the command:

Add-Type -AssemblyName System.DirectoryServices.AccountManagement

Create a new Active Directory user account

To create a new account in Active Directory, we have to do two things in advance: Define the domain and set the organizational unit in which we want the new account to be created in.

Preparations

If we want to use the domain that we are currently logged on to, we can get it by using the GetCurrentDomain method of the System.DirectoryServices.ActiveDirectory.Domain class.

#Get the current domain
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

This creates a new object of type Domain, from which we only need the name right now. Therefore, we´ll grab only the name property.

#store domain name
$domname = $dom.name

If you don´t want the domain name to be set dynamically, you can just define a fixed name in the format “contoso.com”

#Set fixed domain name 
$domname = "lab.net"

For the organizational unit we just need to provide its distinguished name as a string.

#Define the OU in which the user account will be created
$OU = "OU=Users,DC=lab,DC=net"

Next, we´re going to create a PrincipalContext object. This object sets the target location in Active Directory, where the new account will be inserted.

Create the object

The constructor for our purpose needs the parameters ContextType, Name and Container . The ContextType in this case is “Domain”. The parameter “Name” refers to the domain name and “Container” is the distinguished name of the organizational unit.

Since we already stored this info, we can use the domain name and organizational unit variables from before to create the object:

#Create the PrincipalContext object
$pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::` 
new([System.DirectoryServices.AccountManagement.ContextType]::Domain,$domname,$OU)

Because ContextType is just an enumerator with the values “Machine”, “Domain” and “ApplicationDirectory”, we can also just use the string “Domain” as the ContextType to simplify the lines:

#Create the PrincipalContext object
$pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new("Domain",$domname,$OU)

Looks a little tidier, right?

Now, we are all set to create new Active Directory user accounts using our PrincipalContext object.

First, we initialize an object of type UserPrincipal, while passing the PrincipalContext object as a parameter to the constructor.

#Initialize a new UserPrincipal object with the PrincipalContext from before
$user = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($pc)

We now basically have an empty user account object, besides domain and organizational attributes.

#Check the new user object
PS C:\windows\system32> $user

GivenName : 
MiddleName : 
Surname : 
EmailAddress : 
VoiceTelephoneNumber : 
EmployeeId : 
AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters
Enabled : 
AccountLockoutTime : 
LastLogon : 
PermittedWorkstations : 
PermittedLogonTimes : 
AccountExpirationDate : 
SmartcardLogonRequired : False
DelegationPermitted : False
BadLogonCount : 0
HomeDirectory : 
HomeDrive : 
ScriptPath : 
LastPasswordSet : 
LastBadPasswordAttempt : 
PasswordNotRequired : False
PasswordNeverExpires : False
UserCannotChangePassword : False
AllowReversiblePasswordEncryption : False
Certificates : {}
Context : System.DirectoryServices.AccountManagement.PrincipalContext
ContextType : Domain
Description : 
DisplayName : 
SamAccountName : 
UserPrincipalName : 
Sid : 
Guid : 
DistinguishedName : 
StructuralObjectClass : 
Name :

We can see that everything is empty, except of the domain name and the container in the context property:

PS C:\windows\system32> $user.Context

ContextType : Domain
Name : lab.net
Container : OU=USERS,DC=lab,DC=net
UserName : 
Options : Negotiate, Signing, Sealing
ConnectedServer :

 

Now, let´s give this user some attributes.

Fill the attributes

The attributes are accessible as properties of the UserPrincipal object through dot notation.

#Set some attributes for the account
$user.Surname = "User123"
$user.GivenName = "Test"
$user.DisplayName = "Test User123"
$user.Name = "Test User123"
$user.Description = "Test account MSB365"
$user.UserPrincipalName = "test.user123@lab.net"
$user.SamAccountName = "test.user123"
$user.SetPassword("P@ssW0rd1234")

By default the user is disabled. So, don´t forget to set the “enabled” attribute to true:

#Enable the user
$user.Enabled = $true

The option “User must change password at next logon” will automatically be set.

Finalize

If we are sure, that the user account is set with everything it needs, we can save it. This will actually create it in our Active Directory.

#Save the user to AD
$user.Save()

If we want to verify the result, we can just search the account in Active Directory:

# Find the user in AD by SAMAccountName
# using the PrincipalContext object from before and the SAMAccountname of the new account
PS C:\windows\system32> [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($pc,"test.user123")
GivenName : Test
MiddleName : 
Surname : User123
EmailAddress : 
VoiceTelephoneNumber : 
EmployeeId : 
AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters
Enabled : False
AccountLockoutTime : 
LastLogon : 
PermittedWorkstations : {}
PermittedLogonTimes : 
AccountExpirationDate : 
SmartcardLogonRequired : False
DelegationPermitted : True
BadLogonCount : 0
HomeDirectory : 
HomeDrive : 
ScriptPath : 
LastPasswordSet : 
LastBadPasswordAttempt : 
PasswordNotRequired : True
PasswordNeverExpires : False
UserCannotChangePassword : False
AllowReversiblePasswordEncryption : False
Certificates : {}
Context : System.DirectoryServices.AccountManagement.PrincipalContext
ContextType : Domain
Description : Test account MSB365
DisplayName : Test User123
SamAccountName : test.user123
UserPrincipalName : Test.User123@lab.net
Sid : S-1-5-21-4025995969-482537669-3923321509-xxxx
Guid : 79556f9a-9af5-4fff-8cee-062731a91b03
DistinguishedName : CN=Test User123,CN=Users,DC=lab,DC=net
StructuralObjectClass : user
Name : Test User123

There you go. We created a new Active Directory user account. Now, let´s create an Active Directory group.

Create a new Active Directory group

Preparations

The preparation here is the same as for creating Active Directory accounts. We need to create the PrincipalContext object with our domain name and organizational unit in which the group should be created.

#Get the current domain 
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

#store the domain name
$domname = $dom.name

Set the path of the organizational unit :

#Define the OU in which the group will be created
$OU = "OU=Groups,DC=lab,DC=net"
Create the object

Here, instead of initializing an UserPrincipal object, we create a GroupPrincipal object:

#Initialize a new GroupPrincipal object with the PrincipalContext from before
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::new($pc)

And again, we have a nearly empty object, which is waiting to be filled with attributes. Let´s not let it wait any longer…

Fill the attributes
#Since I´ll use the group name for multiple attributes I just store it to a variable 
$groupname = "test-group-MSB365_001"

#Set the groups attributes
$group.Description = "Test group MSB365"
$group.SamAccountName = $groupname
$group.DisplayName = $groupname
$group.GroupScope = "Global" #Valid values are: "Global", "Local", "Universal"
$group.IsSecurityGroup = $true
Finalize

And with all necessary attributes set, we can save the group to Active Directory:

#Save the group to AD
$group.Save()

Let´s check the new group and its attributes:

PS C:\windows\system32> [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc, $groupname)

IsSecurityGroup : True
GroupScope : Global
Members : {}
Context : System.DirectoryServices.AccountManagement.PrincipalContext
ContextType : Domain
Description : Test group MSB365
DisplayName : test-group-MSB365_001
SamAccountName : test-group-MSB365_001
UserPrincipalName : 
Sid : S-1-5-21-4025995969-482537669-3923321509-xxxxx
Guid : 267c6d7e-5981-4169-8c65-10e8cc3cc45a
DistinguishedName : CN=test-group-MSB365_001,OU=GROUPS,DC=lab,DC=net
StructuralObjectClass : group
Name : test-group-MSB365_001

And there´s our group.

If you want to know how to add members to our new group, you can read the how-to in part 1 of this series.

 

Extended properties

The UserPrincipal and the GroupPrincipal classes might not provide all attributes which we want to set on a new Active Directory object. For instance the “mail” attribute is not directly accessible. To set values for those attributes, we can access the underlying Active Directory object, after we have saved the PrincipalObject.

To do so, we use the GetUnderlyingObject method of the PrincipalObject. Here´s an example how to add an e-mail address to the “mail” attribute of our new group.

#Get underlying AD object
$adobject = $group.GetUnderlyingObject()

This will get us to the DirectoryEntry object through which we can access the Active Directory object more directly. To get a list of all its properties and their values, we can access its property Properties. Properties will give us back a PropertyCollection object.

#Get current properties
$adobject.Properties
#Get only the property names
PS C:\Windows\system32>$adobject.Properties.PropertyNames
objectClass
cn
distinguishedName
instanceType
whenCreated
whenChanged
displayName
uSNCreated
uSNChanged
nTSecurityDescriptor
name
objectGUID
objectSid
sAMAccountName
sAMAccountType
groupType
objectCategory
dSCorePropagationData

We can see, that the “mail” attribute is not in the list. To add it, we use the Add method, while indexing the property by name:

PS C:\Windows\system32> $adobject.Properties["mail"].Add("test-group-MSB365_001@lab.net")
0

The return code “0” tells us that everything went fine and the attribute could be set.
As last step, we have to save the object by calling the method CommitChanges:

PS C:\Windows\system32> $adobject.CommitChanges()

When we check in Active Directory now, we can see the group exists and that it has an e-mail address:

This works the same way for user accounts, too. The attribute just has to be present in the Active Directory schema.

As functions

Create an Active Directory user account
Function cCreate-ADUser {
    Param(
        [Parameter(Mandatory=$true,
        ValueFromPipeline=$true, HelpMessage="Enter surname for the new user")]
        [String]
        $surname,

        [Parameter(Mandatory=$true,
        ValueFromPipeline=$true, HelpMessage="Enter givenname for the new user")]
        [String]
        $givenname,

        [Parameter(Mandatory=$false,
        ValueFromPipeline=$true, HelpMessage="Enter a description for the new user")]
        [String]
        $description,

        [Parameter(Mandatory=$false,
        ValueFromPipeline=$true, HelpMessage="Enter a initial password for the new user. Default password will be used if left blank.")]
        [String]
        $InitialPW
   )
    
    #use default password as initial password or use provided
    if ($InitialPW){
        $password = $InitialPW
    }Else{
        $password = "P@ssW0rd1234"
    }

    #Add the assembly
    Add-Type -AssemblyName System.DirectoryServices.AccountManagement

    #Get the current domain
    $dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

    #Set domain name
    $domname = $dom.Name

    #Define the container in which the user account will be created
    $OU = "OU=Users," + $dom.GetDirectoryEntry().distinguishedname

    #Create the Principalcontext object
    $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new( `
    [System.DirectoryServices.AccountManagement.ContextType]::Domain,$domname,$OU)

    #Initialize the new UserPrincipal object with the Principalcontext from before
    $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($pc)

    #Set the attributes of the user
    $user.Surname = $surname
    $user.GivenName = $givenname
    $user.DisplayName = $givenname + " " + $surname
    $user.Name = $givenname + " " + $surname
    $user.Description = $description
    $user.UserPrincipalName = $givenname + "." + $surname + "@" + $domname
    $user.SamAccountName = $givenname + "." + $surname
    $user.SetPassword($password)
    #Enable the user
    $user.Enabled = $true
    #Save the user to AD
    $user.Save()
    
    #clean up
	$user.Dispose()
	$pc.Dispose()
    $dom.Dispose()
	Return "Done"
}

The function will create a new Active Directory user with surname, given name and description provided in the parameters. As SAMaccountName and UserPrincipalName it will use a combination of surname and givenname.

You can set your own initial password through the parameter “InitialPW” as plain text string. If this parameter is left empty, the function will set a predefined password.

Here´s an example how to create an account with the function, using a specific initial password:

PS C:\Windows\system32> cCreate-ADUser -surname TestMSB365 -givenname User01 -description "test user MSB365" -InitialPW "W€lc0me2021!"
Create an Active Directory group
Function cCreate-ADGroup {
    Param(
        [Parameter(Mandatory=$true,
        ValueFromPipeline=$true, HelpMessage="Enter a name for the new group")]
        [String]
        $groupname,

        [Parameter(Mandatory=$true,
        ValueFromPipeline=$true, HelpMessage="Enter a description for the new group")]
        [String]
        $description,

        [Parameter(Mandatory=$false,
        ValueFromPipeline=$true, HelpMessage="Enter a group scope for the new group")]
        [ValidateSet("Global", "Local", "Universal")]
        [String]
        $Scope = "Global",

        [Parameter(Mandatory=$false,
        ValueFromPipeline=$true, HelpMessage="Choose if group is security group. If not set a distribution group will be created")]
        [Switch]
        $IsSecurityGroup,

        [Parameter(Mandatory=$false,
        ValueFromPipeline=$true, HelpMessage="Only for distribution groups: Enter an e-mail address for the new group. If not specified groupname@domainname will be used.")]
        [String]
        $mail
   )
    
    
    #Add the assembly
    Add-Type -AssemblyName System.DirectoryServices.AccountManagement

    #Get the current domain
    $dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

    #Set domain name
    $domname = $dom.Name

    
    #Define the container in which the group will be created
    $OU = "OU=Groups," + $dom.GetDirectoryEntry().distinguishedname
    
    #Create the Principalcontext object
    $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new( `
    [System.DirectoryServices.AccountManagement.ContextType]::Domain,$domname,$OU)

    #Initialize the new GroupPrincipal object with the PrincipalContext from before
    $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::new($pc)

    #Set the attributes of the group
    
    $group.DisplayName = $groupname
    $group.Description = $description
    $group.SamAccountName = $groupname
    $group.IsSecurityGroup = $IsSecurityGroup.IsPresent
    $group.GroupScope = $Scope
    
    
    #Save the group to AD
    $group.Save()

    #Set e-mail address if group type is distribution group
    if (!$IsSecurityGroup){
        #Generate e-mail address if none provided
        If (!$mail){
            $mail = $groupname + "@" + $domname
        }
        #get underlying AD object
        $adobject = $group.GetUnderlyingObject()
        #Add mail property and save
        $result = $adobject.Properties["mail"].Add($mail)
        $adobject.CommitChanges()
    }
    
    #clean up
	$group.Dispose()
	$pc.Dispose()
    $dom.Dispose()
	Return "Done"
}

This function creates an Active Directory group, while using the value in the parameter “Groupname” as SAMaccountName and DisplayName. If the switch parameter “IsSecurityGroup” is not set a distribution group will be created. Then, you can also provide an e-mail address. If none is provided, the function will build one using the group name and the domain name of the current domain.

Here´s an example how to create a distribution group with a specified mail address:

cCreate-ADGroup -groupname testgroup-msb365_002 -description "test distgrp MSB365" -Scope Universal -mail "testgroup.msb365@lab.net"

And here´s how to create a simple global security group:

PS C:\Windows\system32> cCreate-ADGroup -groupname testgroup-msb365_003 -description "test sec group global MSB365" -Scope Global -IsSecurityGroup

Further info

The “System.DirectoryServices.AccountManagement” namespace

The PrincipalContext class

The Add-Type cmdlet

How to work with .Net classes in PowerShell


Leave a Reply

Your email address will not be published. Required fields are marked *