Adding Users through PowerShell

Now that we have our OU structure in place, we can start adding users.

Since the Users contain alot of information we’ll go ahead and use the whole csv file.

Let’s take a quick look at the first entry in the Csv file:


PS > (Import-Csv StarTrek.csv)[0]


Character  : Jean-Luc Picard
Position   : Commanding Officer
Rank       : Captain
Department : Main Bridge
Species    : Human
Starship   : USS Enterprise (NCC-1701-D)
Class      : Galaxy
Registry   : NCC-1701-D
Series     : Star Trek: The Next Generation
Location   : Alpha Quadrant

Let’s start adding the Users to the Domain. First off, we need to build up a connectionstring. In the script, I build it based on


PS > $Series = "Star Trek: The Next Generation"
PS > $Domain = "powershell.nu"
PS > $Connection = $Series.Insert(0,"LDAP://OU=Users,OU=") + ($Domain.Replace(".",",DC=")).Insert(0,",DC=")
PS > $Connection

LDAP://OU=Users,OU=Star Trek: The Next Generation,DC=powershell,DC=nu

Now for the Fun part. In the Csv file, some Characters don’t have any surNames and some have middle names, so this is something I have to consider in the script. Thanks to the Power of PowerShell it doesn’t have to get to complex.

Step one is Checking the characters name. I use the Split() method to determine if the user has a SurName or any Middle Names.


PS > $Character = "Jean-Luc Picard"
PS > $CharacterName = $Character.Split(" ")
PS > $CharacterName

Jean-Luc
Picard

In this example, Jean-Luc would be the givenName and Picard the surName. I control this in the script through a if else statement that checks if the $CharacterName contains 1 row or more.


if (($CharacterName.Count) -le 1) {

} else {

}

In the case of Jean-Luc Picard, the Name contains more than 1 row, so let’s move on to the else statement.

The else statement starts of by setting up giveName and SurName. the $giveName variable contain all names except the last one. So if we would use Charles Trip Tucker instead of Picard, the givenName would be “Charles Trip” and surName would be set to “Tucker”.


PS > $givenName = $CharacterName[0..($CharacterName.Count -2)]
PS > $sn = $CharacterName[($CharacterName.Count -1)]
PS > $givenName

Jean-Luc

PS > $sn

Picard

Now that we’ve sliced and diced the characters name, we do an additional check to see if the character name will fit the sAMAccountName structure decided in Part 1.0.0 (First 3 from giveName and first 3 from surName).


if ($givenName.Length -lt 3 -AND $sn.Length -lt 3) {

   $Tempcn = $givenName + $sn
   $AddNum = 3 - $Tempcn.Length

   # Build Last Part Of cn

   for ($i = 0; $i -lt ($AddNum -1); $i ++) { $AddTocn += "0" }
   $AddTocn += "1"

   [string]$sAMAccountName = ($Tempcn).ToLower() + $Addtocn
   $cn = $givenName

   # Set Checker to Null

   $AddTocn = $Null

} elseif ($givenName.Length -lt 3 -OR $sn.Length -lt 3) {

   $Tempcn = $givenName + $sn

   [string]$sAMAccountName = ($Tempcn.SubString(0,6)).ToLower()
   $cn = $givenName + " " + $sn

} else {

   [string]$sAMAccountName = ($givenName.SubString(0,3)).ToLower() + ($sn.SubString(0,3)).ToLower()
   $cn = $givenName + " " + $sn
}

The if elseif else statement goes through the characters name and checks if it falls within parameters. If it does not, a number will be added at the end of the Name to compensate. The elseif statement takes care of users where givenName or surName has less than 3 characters and compensates this.

Here’s how Jean-Luc Picard is set up now:


PS > $givenName

Jean-Luc

PS > $sn

Picard

PS > $sAMAccountName

jeapic

PS > $cn

Jean-Luc Picard

Now that we have the sAMAccountName in place we have to check if the sAMAccountName is taken by some other User. We can do this by creating a function that looks up the users sAMAccountName from the Get-AD.ps1 script.


function Check-sAMAccountName ([string]$Domain, [string]$User) {

   trap {  $Script:sAMAccountNameDoesntExist = $True ; continue }
   .Get-AD.ps1 -Domain $Domain -User $User -Filter sAMAccountName | Out-Null
}

If the UserName already exists the script uses a While loop to determine how many sAMAccountNames are taken and when an empty sAMAccountName is found it will be used.


if ($sAMAccountNameDoesntExist -eq $True) {

} else {

   While ($Script:sAMAccountNameDoesntExist -eq $False) {

    $LastChar = $sAMAccountName.SubString($sAMAccountName.Length -1)

    if(1..9 -Contains $LastChar) {

       $sAMAccountName = ($sAMAccountName.TrimEnd([string]$LastChar)) + ([int]$LastChar + 1)
    } else {

       $sAMAccountName = $sAMAccountName + 1
    }

    Check-sAMAccountName -Domain $Domain -User $sAMAccountName
   }
}

If jeapic already exists the number 1 will be appended to his sAMAccountName, and if jeapic1 already exists 1+1 ( 2) would be appended instead. Note that doing this sets the sAMAccountName to 7 characters instead of 6. If you always want to use 6 chars you could substring() out characters 1 to 5 ( 0,4 ) and build up a sAMAccountName containing only 6 characters.

Now that we have a unique sAMAccountName, we want to check the Users distinguishedName. If the distinguishedName already exists then we will not Add the User, since he already exists. If not, we will go ahead and add our user. The distinguishedName check function is basically the same as the sAMAccountName checker, only differens is that it filters on distinguishedName instead.


function Check-distinguishedName ([string]$Domain, [string]$User) {
   trap {  $Script:distinguishedNameDoesntExist = $True ; continue }
   .Get-AD.ps1 -Domain $Domain -User $User -filter distinguishedName | Out-Null
}

The function takes distinguishedName as an argument, so we have to build up this string. The OU structure was set up in Part 1.1.1 so the User should be in the following OU: “OU=Users,OU=Star Trek: The Next Generation,DC=powershell,DC=nu”.


$distinguishedName = "CN=" + $cn + ",OU=Users,OU=" + $Series + ($Domain.Replace(".",",DC=")).Insert(0,",DC=")

If the disyinguishedName doesnt exist, we can go ahead and create the User. For safety reasons, we generate a secure password for each user that we create and store the passwords in a password file. We could obviously set all Passwords to “Password1″ but that wouldn’t be fun.

Here’s a simple password generator that I’ve created. Using the System.Random Class to generate random numbers.


function Generate-Password {

  $Random = New-Object System.Random

  # Two Upper Case Characters

  [string]$Password += [char]$Random.Next(49,57)
  [string]$Password += [char]$Random.Next(65,72)

  # Two LowerCase Characters

  [string]$Password += [char]$Random.Next(97,107)
  [string]$Password += [char]$Random.Next(109,122)

  # One Special Char

  [string]$Password += [char]$Random.Next(36,43)

  # Two UpperCase Characters

  [string]$Password += [char]$Random.Next(65,72)
  [string]$Password += [char]$Random.Next(80,91)

  # One LowerCase

  [string]$Password += [char]$Random.Next(97,107)

  $Password
  $Password = $Null
}

Why not make it a a shorter script and randomize [char]49 to [char]122? Well, if I randomize any character from [char]49 to [char]122 i might get an “O” and numeric “0″, or “l”,”I” or numeric “1″. Users might find it hard to differentiate an O from an 0 so I’ve excluded Characters that look alike in the function. Also, if I use a for statement and loop through [char]$Random.Next(49,122) eight times, it’s not certain that i get an acceptable Password, i might all uppercase characters and AD wouldn’t accept that.

Now that we have a Password generator we can create our User.


PS > $Description = $Position + " (" + $Species + ")"
PS > $Title = $Rank
PS > $physicalDeliveryOfficeName = $Starship
PS > $userPrincipalName = $sAMAccountName + "@" + $Domain
PS > $mail = ([string]$Character).Replace(" ",".") + "@" + $Domain

PS > [string]$Password = Generate-Password

PS > $OU = [adsi] $Connection
PS > $User = $OU.Create("user", "cn=$cn")
PS > $User.Put("sAMAccountName", $sAMAccountName)
PS > $User.Put("userPrincipalName", $userPrincipalName)
PS > $User.Put("DisplayName", $cn)
PS > $User.Put("givenName", $givenName)
PS > $User.Put("sn", $sn)
PS > $User.Put("Description", $Description)
PS > $User.Put("l", $Starship)
PS > $User.Put("streetAddress",$Location)
PS > $User.Put("physicalDeliveryOfficeName", $physicalDeliveryOfficeName)
PS > $User.Put("Title", $Title)
PS > $User.Put("Department", $Department)
PS > $User.Put("Company", $Starship)
PS > $User.Put("mail", $mail)
PS > $User.SetInfo()

PS > $User.PsBase.Invoke("SetPassword", $Password)
PS > $User.PsBase.InvokeSet("AccountDisabled", $false)
PS > $User.SetInfo()

Now all i have to do is Collect the Generated Password in a file.


PS > $FileName = "PasswordList " + (get-date -uformat "%Y-%m-%d") + ".txt"
PS > "$sAMAccountName,$cn,$Password" | Add-Content $FileName

Running the script in our test environment would look something like this:

add-stuser01

If we repeat the script, the checker function fould tell us that the Users already exist:

add-stuser02

Now for a peek in Active-Directory snap-in dsa.msc

add-stuser03

And Finally, let’s look closer on Captain Jean-Luc Picard and see if the Values specified in the script are set.

add-stuser04

And here’s an example on the Autogenerated Password List.

Click Here to Download the Complete Script.

The Get-AD.ps1 script is also required.

Click here to download the Csv File

Rating 3.00 out of 5
[?]

Adding Ou Structure using Powershell

Starting of, we need to set up a couple of OrganizationalUnits in our test environment. Following the structure set up in Part 1.1.0:

Organizational Unit Structure

  • ou = Series
  • l = Location
  • Description = Starship
  • Child OU: Computers
  • Child OU: Groups
  • Child OU: Users

The first step in scripting up a OU structure from based on the StarTrek Csv file is to collect the information through PowerShell. Since the Csv file contains 68 rows of information and 10 different columns, we want to retrieve only information that we need to create a Csv Structure. The columns of interest are: Series, Starship and Location. Using Import-Csv in combination With Select-Object gets all entries matching this.


PS > Import-Csv StarTrek.csv | Select-Object Series, Starship, Location


Series                         Starship                    Location
------                         --------                    --------
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant

Now we,ve managed to retrieve the specified columns and rows. Next we need to narrrow the list down to only unique entries. We can achieve this through the -Unique SwitchParameter.


PS > Import-Csv .StarTrek.csv | Select-Object Series, Starship, Location -Unique


Series                         Starship                    Location
------                         --------                    --------
Star Trek: The Next Generation USS Enterprise (NCC-1701-D) Alpha Quadrant
Star Trek: Deep Space Nine     Deep Space Nine             Alpha Quadrant
Star Trek: Voyager             USS Voyager (NCC-74656)     Delta Quadrant
Star Trek: Enterprise          Enterprise (NX-01)          Alpha Quadrant

Now that we’ve narrowed down our list, we can start creating the OU structure.

The OU:s Name should be “Series”, so the structure that we’re looking for is:

  • Star Trek: The Next Generation
  • Star Trek: Deep Space Nine
  • Star Trek: Voyager
  • Star Trek: Enterprise

But how do we know that these OU:s dont already exist in our Environment ? We have to check this in some way so we need a checker that makes sure that the OU doesn’t exist. This is an excellent oppurtunity to use the Get-AD.ps1 script that I wrote. The checker is rather simple, it consists of a function that sets a variable to $True if the OU does not exist.


function Check-distinguishedName ([string]$Domain, [string]$OU) {

	trap {  $Script:distinguishedNameDoesntExist = $True ; continue }
	.\Get-AD.ps1 -Domain $Domain -OU $OU -Filter distinguishedName | Out-Null
}

The Variable $Script:distinguishedNameDoesntExist is set to $True if we test the function on a non existing OU. Here’s an example on how it works:


PS > Check-distinguishedName -Domain powershell.nu -OU "OU=Domain Controllers,DC=powershell,DC=nu"
PS > $distinguishedNameDoesntExist

Since Domain Controllers Exist, the variable $distinguishedNameDoesntExist is not set to anything. Running the same function on a OU that does not exist:


PS > Check-distinguishedName -Domain powershell.nu -OU "OU=Non Existing OU,DC=powershell,DC=nu"
PS > $distinguishedNameDoesntExist

True


PS > $distinguishedNameDoesntExist = $Null

This time the variable $distinguishedNameDoesntExist was set to True, which means that the OU does not exist and it’s available for creation. I also set the Variable to $Null so that i can reuse the function.

Moving on. If the $distinguishedNameDoesntExist equals $True we can start building up the OU Structure. In the script, there’s a paramter called -Domain which takes the domain name as an argument. In my examples I’m going to use the powershell.nu domain. Setting this as an argument is similar to creating a Variable holding the domain name:


PS > $Domain = "powershell.nu
PS > $Domain

powershell.nu

With this information we can create a Connection string that we can use when connecting to Active-Directory. We can make use of the methods() withing System.String to alter the string as we want it. I’m using Replace() and Insert() to get the result I want:


PS > $Connection = ($Domain.Replace(".",",DC=")).Insert(0,"LDAP://DC=")

PS > $AD = [adsi] $Connection
PS > $AD


distinguishedName
-----------------
{DC=powershell,DC=nu}

Now that we’ve set up a connection we can start creating an OU. It’s pretty straight forward, nothing fancy here:


PS > $OU = $AD.Create("OrganizationalUnit", "ou=$Series")
PS > $OU.SetInfo()

PS > $OU.put("l", $Location)
PS > $OU.put("Description", $Starship)
PS > $OU.setinfo()

When creating Child OU:s, we need to alter the Connection string so that we connect to the OU that we’ve just created, here’s an example on doing that:


$NewConnection = "LDAP://OU=" + $Series + ($Domain.Replace(".",",DC=")).Insert(0,",DC=")
$NewOU = [adsi]$NewConnection

Now we can start creating Child OU:s that are structured after our purpose:


$Users = $NewOU.Create("OrganizationalUnit", "ou=Users")
$Users.SetInfo()

$Users.put("l", $Location)
$Users.put("Description", $Starship)
$Users.setinfo()

This describes the steps that I’ve set up in the Script. Running the script in the test environment would look like this:

add-stou01

If i repeat the script, the Check function finds that the top OU:s already exists and the following is returned to the host:

add-stou02

Finally, taking a peek in dsa.msc.

add-stou03

Click Here to Download the Complete Script.

The Get-AD.ps1 script is also required.

Click here to download the Csv File

Rating 4.50 out of 5
[?]

Scripting up an Active-Directory Test Environment through PowerShell

The first step in building up a Test Environment is analyzing the data that we have to work with. In this case, the Star Trek Reference Csv file.

Since the Csv file doesn’t say sAMAccountName or organizationalUnit I’ve to set up a routine for handling this. Based on the information, I’ve set up the following rules:

User Information

  • Common Name = Character Name
  • sAMAccountName = First 3 Characters from givenName and surName
  • userPrincipalName = sAMAccountName and Domain
  • DisplayName = Character Name
  • givenName = First part of Character Name
  • surName = Last part of Character Name
  • Description = Postition + Species
  • l = Starship
  • streetAddress = Location
  • physicalDeliveryOfficeName = Starship
  • Title = Rank
  • Department = Department
  • Company = Starship
  • mail = CharacterName and Domain

Computer Information

  • Common Name = Starship
  • sAMAccountName = Registry
  • Location = Location
  • Description = Starship

Group Information

  • Common Name = Position
  • Description = Position
  • sAMAccountName = Position

Group MemberShip

  • Members = Based on Users Position

Organizational Unit Structure

  • ou = Series
  • l = Location
  • Description = Starship
  • Child OU: Computers
  • Child OU: Groups
  • Child OU: Users

Users HomeFolder

  • HomeFolder = Based on Users sAMAccountName and a defined Path

Most of these steps may seem trivial and easy to script up based on a Csv file, but there are numerous steps to consider, for example: some Characters, such as T’pol and Phlox don’t have any surnames (at least not any surnames recorded in the Vulcan Database) so these names must be handled in the script. Another step to consider are sAMAccountNames conatining a invalid set of characters and so on. I’m going to go through each step of the scripts and explain in detail how to avoid the exceptions that we might encounter in each new post regarding the migration.

The Scripts will also use the Get-AD.ps1 script when chekcing if objects exist in the Domain.

Next step is Part 1.1: Adding Ou Structure using Powershell

The Get-AD.ps1 script.

Here’s a link to the Csv File refered to in this post.

Rating 4.00 out of 5
[?]