Posts RSS Comments RSS 253 Posts and 411 Comments till now

Getting users group membership (tokengroups)

Around the same time as I wrote my Linked-Value Attribute script I also came up with this little gem. It also uses a constructed attribute provided with Windows 2003 called “tokengroups.” (Did I mention this includes recursive groups?)

Effectively it gets the attribute which returns an array of SIDs (in byte array form) for each group. I then use a function I posted about eariler called ConvertTo-Name to convert that BYTE array into a friendly group name we humans like.

Parameters:

  • – Account: Can be User samAccountName or DN of the user
  • – Verbose: Enables verbose output

More Info:
You may notice the GetInfoEx call I make on the user object. This is because tokengroups is not an actual attribute and does not get “populated” until you specifically request it. The GetInfoEx does exactly that.

Links:
MSDN: GetInfoEx
MSDN: tokenGroups

Get-TokenGroups.ps1

Param($Account,[switch]$Verbose)

if($verbose){$verbosepreference="Continue"}

Write-Host
Write-Verbose " – Account: $Account"
Write-Verbose " – Verbose: $Verbose"

function ConvertTo-Name($sid,[switch]$FromByte) {
    if($FromByte)
    {
        $ID = New-Object System.Security.Principal.SecurityIdentifier($sid,0)
    }
    else
    {
        $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
    }
    if($ID)
    {
        $User = $ID.Translate([System.Security.Principal.NTAccount])
        $User.Value
    }
}
function GetDNfromName{
    Param($name)
    $root = [ADSI]""
    $filter = "(sAMAccountName=$name)"
    $props = @("distinguishedName")
    $Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
    $Searcher.FindOne().properties.distinguishedname
}

if($Account -notmatch "CN=(.*),((OU|DC)=\w*)*")
{
    Write-Verbose " + Getting User DN for [$Account]"
    $Account = GetDNfromName $Account
    Write-Verbose "   – GetDNfromName returned [$Account]"
}

Write-Verbose " – Getting User Object"
$UserAccount = [ADSI]"LDAP://$Account"

Write-Verbose " – Calling GetInfoEx"
$UserAccount.GetInfoEx(@("tokengroups"),0)

Write-Verbose " – Getting tokengroups"
$groups = $UserAccount.Get("tokengroups")

Write-Verbose " + Processing Groups"
foreach($group in $groups)
{
    $GroupName = ConvertTo-Name $group -FromByte
    Write-Verbose "   – Found group [$GroupName]"
    $GroupName
}

Write-Host

Constructed Properties and LVR (Linked-Value Replication)

There was an interesting question that came up on the news group that discussed getting Active Directory replication metadata and while the question really wasn’t directly a Powershell question I found it intriguing. I knew I had the answer to the question back in my brain, but I couldn’t retrieve it.

Here is the Question:

“I’m working on a script where I need to compare the last time the member
attribute of a distribution group was modified (not the AD group object
itself) with the time stamp on a file (I’m exporting distribution group
memberships to a file, but only ones that have changed). The problem
I’m running into is that some Active Directory distribution groups
aren’t returning the member attribute when I look at the replication
meta data.

Any one have any thoughts on why some distribution groups return the
member attribute when I run GetReplicationMetadata and some don’t? This
returns all kinds of other attributes and their metadata, but naturally
not the one I’m interested in (it does some times, on some groups, but
not all). I could run my script by the actual AD object WhenChanged
attribute, but I’ll be processing a large number of lists and I want it
to run as fast as possible and since other attributes can change on a
group object, I don’t want to have to export a 8,000 member group if the
displayName changes, for example (I’m only interested in the member
attribute).
As always, any insight is appreciated.”

I thought about this for a bit and also consulted some AD friends and we determined the issue was LVR (Link Value Replication.) This was introduced in Windows 2003 (specifically 2003 Native Mode.)

Basically, LVR changed the unit of replication for some attributes to be the actual values. Prior to LVR if you changed group membership the entire attribute member would have to replicate. With LVR, just the link you added for user replicates.

As one can imagine this changed the Metadata and therefore GetReplicationMetadata() didnt get retrieve the data for you. Where does this leave us?

There were also a few constructed attributes that were added with 2003. One of which is called “msds-replvaluemetadata.” This attribute provided the metadata for these links in wonderful XML format. You will find code using this XML below.

Some Useful Links regarding LVR and Constructed Attributes:

Below is the script that resulted from the investigation


 

Get-GroupMemberMetadata.ps1

Param($GroupName)

$GroupMembers = @()

$root = [ADSI]""
$filter = "(&(objectclass=group)(name=$GroupName))"
$props = @("msDS-ReplValueMetaData")
$Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
foreach($Group in $Searcher.findall())
{
    foreach($childMember in $Group.Properties."msds-replvaluemetadata")
    {
        $GroupMembers += [xml+site:msdn.microsoft.com”>XML]$ChildMember
    }
}

foreach($Member in $GroupMembers)
{
    $Member.ChildNodes
}

Setting the WriteProperty on Members (ManagedBy Check box)

This has come up three times in the last week which triggers my auto blog response 🙂

Below is a function called New-ADAce. This function creates an Access Control Entry that can be applied to an AD Object (in this case the member property of an AD object.)

Basically what the code below does is:

  • Gets the ID object of the Manager
  • Creates ACE that gives the Manager Write access to the member property
  • Gets the Object to be managed
  • Gets the existing ACL
  • Addes the ACE to the ACL
  • Sets the ManagedBy Property to the DN of the Manager
  • Commits the changes
Param(
   $myGuid = "bf9679c0-0de6-11d0-a285-00aa003049e2", #GUID for the Members property
   $DN = "cn=jloser,cn=users,dc=corp,dc=lab",
   $domain = $env:UserDomain,
   $manager = "jmanager",
   $MangedByDN = ""cn=jmanager,cn=users,dc=corp,dc=lab""
)

function New-ADACE {
   Param([System.Security.Principal.IdentityReference]$identity,
   [System.DirectoryServices.ActiveDirectoryRights]$adRights,
   [System.Security.AccessControl.AccessControlType]$type,
   $Guid)

   $help = @"
   $identity
      System.Security.Principal.IdentityReference
      http://msdn.microsoft.com/en-us/library/system.security.principal.ntaccount.aspx
   $adRights
      System.DirectoryServices.ActiveDirectoryRights
      http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights.aspx
   $type
      System.Security.AccessControl.AccessControlType
      http://msdn.microsoft.com/en-us/library/w4ds5h86(VS.80).aspx
   $Guid
      Object Type of the property
      The schema GUID of the object to which the access rule applies.
"
@
   $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$guid)
   $ACE
}

# Some example code on how to use the New-ADACE function

# Create ACE to add to object
$ID = New-Object System.Security.Principal.NTAccount($domain,$manager)
$newAce = New-ADACE $ID "WriteProperty" "Allow" $myGuid

# Get Object
$ADObject = [ADSI]"LDAP://$DN"

# Set Access Entry on Object
$ADObject.psbase.ObjectSecurity.SetAccessRule($newAce)

# Set the manageBy property
$ADObject.Put("managedBy",$MangedByDN)

# Commit changes to the backend
$ADObject.psbase.commitchanges()

Bulk Imports using CSV (cross post from TurboChargeAD.org)

As some of you may know I am doing some guest blogging over at TurboChargeAD.org and below is a link to the second such article.

Here is the Link: Bulking Importing User from CSV file using Quest cmdlets

Here is the code

#Import-ADUser.ps1
#requires -pssnapin Quest.ActiveRoles.ADManagement
Param($file,$OU,[switch]$whatif,[switch]$verbose)

if($verbose){$VerbosePreference = "Continue"}

if(!(test-path $file))
{
   throw " File NOT found!"
   return
}

if(!$ou)
{
    # Setting the OU to the Users container
    $ou = "CN=Users,{0}" -f ([ADSI]"").distinguishedName[0]
}

Write-Verbose " Using File: $file"
Write-Verbose " Using OU: $ou"

foreach($user in (Import-Csv $file))
{
    $props = @{}
    # Getting a list property names.
    $propNames = $user | Get-Member -MemberType properties | %{$_.name}
    # Foreach of the property names add an entry in the hash table with
    # the key being the property name and value being the value from the object
    foreach($prop in $propNames)
    {
        # This removes quotes if they exist
    $value = $user.$prop -replace "’|`"",""
        $props += @{$prop=$value}
    }
    # Create User using the displayname as the CN
    $MyUser = new-qaduser -name $user.displayName `
                          -ObjectAttributes $props `
                          -parent $OU `
                          -whatif:$whatif `
                          -verbose:$verbose
    $MyUser
}

userAccountControl and “User cannot change password”

Someone asked me a question about setting the “User cannot change password” check box in ADUC. They were creating the user account and setting PASSWD_CANT_CHANGE along with other settings (see my post about HERE about userAccountControl and values) and they couldn’t figure out why the check box wasn’t being applied.

I thought about this, and my first impression was they had the wrong bit value. So, I posted the “correct” one. They came back and said “That didn’t work.”

Hmmm, that’s curious. I turned to Dean and asked him if I was missing something obvious (I had a nagging feeling I had been here before) and he informed me that it has to be set with an “Extended Right” (control access right) via an ACE.

DOH! Now the whole scenario I had THAT feeling about came back to me. I recall having this discussion with someone and providing them a Script that would set the ACE. I searched for the script and I couldn’t find it. This happens to me a lot so I decided a while ago… when I run across this again, BLOG IT!

Technical Info:

In the past, permissions on the ‘userAccountControl’ attribute could be edited oftentimes making the effective password policy moot, i.e. you could end up with accounts that don’t comply with the domain’s password policy.

In Windows 2000, you can’t easily prevent this except by using third party front-end/provisioning tools to manage user objects. In Windows 2003 and later, you can use three newly-added extended rights (Control Access Rights) to prevent these bits from being edited even when the caller has permission to do so. The three (new) ‘Extended Rights’ are –

• Update password not required bit [controls ‘password not required’ and maps to ACE in footnote below]
• Enable per user reversible encryption [controls whether password is stored reversibly encrypted or not]
• Unexpire password [controls ‘password never expires’]

Each of these extended rights MUST be configured on the domain head and scoped as “This Object only” with ALLOW or DENY for the security principals you designate. By default, ‘Authenticated Users’ is granted an ALLOW ACE for each of the three extended rights. This doesn’t mean any old authenticated user can alter the password related bits in the ‘userAccountControl’ attribute; they still require the permission to modify ‘userAccountControl’.

USAGE SCENARIO – create a single group representing ALL three extended rights (or perhaps ONE group for EACH extended right). Then ACL the group(s) accordingly on the domain head with a DENY ACE. Finally, place the account-administrator users and groups that have management permissions to user objects (i.e. they have write permissions to the ‘userAccountControl’ property) in the group(s) you just created thereby preventing those account-administrators from altering the password related bits on the ‘userAccountControl’ attribute resulting in an enforced password policy.

IMPORTANT NOTE [Observed Behavior] – When viewing or changing a user’s ability to change their own password (User Cannot Change Password) through the GUI, it no longer appears to touch ‘userAccountControl’s bit 0x40 (64) — rather, it simply grant’s the ‘SELF’ security principal ‘ALLOW’ or ‘DENY’ to ‘Change Password’ — this can be easily verified by viewing the DACL.

Links:
Modifying User Cannot Change Password (LDAP Provider)

So… Here it is (Set-UserCannotChangePassword.ps1)
Parameters
User: The sAMAccountName of the User
CheckBox: If passed it checks the box
Default: Remove Check box.

Param($User = $(throw ‘$User is Required’,[switch]$CheckBox)
Write-Host

$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"","(&(objectcategory=User)(sAMAccountName=$user))")
$MyUser = $Searcher.FindOne().GetDirectoryEntry()

if(!$?){" !! Failed to Get User !!";Return}

if($CheckBox)
{
    Write-Host " – Checking Box for User [$($MyUser.distinguishedName)]"
    $self = [System.Security.Principal.SecurityIdentifier]‘S-1-5-10’
    $ExtendedRight = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight
    $deny = [System.Security.AccessControl.AccessControlType]::Deny
    $selfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule($self,$ExtendedRight,$deny,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
    $MyUser.psbase.get_ObjectSecurity().AddAccessRule($selfDeny)
    $MyUser.psbase.CommitChanges()
}
else
{
    Write-Host " – Removing Check Box for User [$($MyUser.distinguishedName)]"
    $ACL = $MyUser.psbase.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.NTAccount])
    $ACEs = $ACL | ?{($_.ObjectType -eq ‘ab721a53-1e2f-11d0-9819-00aa0040529b’) -and ($_.AccessControlType -eq ‘Deny’)}
    foreach($ACE in $ACEs){if($ACE){[void]$MyUser.psbase.get_ObjectSecurity().RemoveAccessRule($ACE)}}
    $MyUser.psbase.CommitChanges()
}

Write-Host

Phase 3 of S.DS.P “CSV Output” (as much as 21 sec faster!)

In the next phase I wanted to actually get some data back and display it. Because I just wanted to test performance I kept the test as close as possible. ADFind has the built in ability to output a csv file so I mimicked his output. I also added two properties to return name and sAMAccountName. For those interested, my next step will be to output objects instead of text (really the whole point.)

My expectation for this test was that ADFind would pull ahead due to the processing of each record, but I was pleasantly surprised when the script was actually faster in all environments except 700k Environment. I will let joe explain why his is slower.

Testing
– I ran each 10x in a Row (as joe Suggested) in its own environement
– ADFind I tested using CMD.exe and used ptime.exe for time measurements.
– Get-DSPObject I used Measure-Command and outputed only TotalSeconds

UPDATED!: I had several people suggest that I should have a consolidated view of the results. So here it is. I took the Average from each test and put into and Excel spreadsheet and then plotted on a chart.

Here is the screenshot (lower is better)
DSP vs ADFind Chart

400k objects in a Prod Quality VM running Win2008 RTM 64bit (local)
Average: ADFind = 90.45
Average: DSP = 68.79
Winner: DSP 21.66 secs faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 70.455s
Execution time: 69.551s
Execution time: 68.466s
Execution time: 68.085s
Execution time: 68.611s
Execution time: 68.179s
Execution time: 68.837s
Execution time: 68.670s
Execution time: 69.105s
Execution time: 67.994s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 100.538 s
Execution time: 109.511 s
Execution time: 92.499 s
Execution time: 96.063 s
Execution time: 91.601 s
Execution time: 80.693 s
Execution time: 81.551 s
Execution time: 81.044 s
Execution time: 90.945 s
Execution time: 80.143 s

400k objects in a Prod Quality VM running Win2008 RTM 64bit (remote)
Average: ADFind = 77.18
Average: DSP = 64.23
Winner: DSP 13 secs faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 67.065 s
Execution time: 63.871 s
Execution time: 63.330 s
Execution time: 63.027 s
Execution time: 62.630 s
Execution time: 64.692 s
Execution time: 64.451 s
Execution time: 64.450 s
Execution time: 64.594 s
Execution time: 64.219 s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 77.280 s
Execution time: 77.616 s
Execution time: 77.304 s
Execution time: 76.899 s
Execution time: 77.426 s
Execution time: 76.773 s
Execution time: 76.699 s
Execution time: 77.445 s
Execution time: 77.463 s
Execution time: 76.912 s

700k objects on a Physical machine Win2k3 x86
Average: ADFind = 101.383
Average: DSP = 109.600
Winner: ADFind 8.21 secs faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 111.088s
Execution time: 109.962s
Execution time: 110.112s
Execution time: 110.832s
Execution time: 110.167s
Execution time: 109.853s
Execution time: 109.757s
Execution time: 110.387s
Execution time: 109.070s
Execution time: 109.065s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 101.571 s
Execution time: 101.313 s
Execution time: 101.386 s
Execution time: 101.593 s
Execution time: 101.539 s
Execution time: 100.917 s
Execution time: 101.020 s
Execution time: 101.286 s
Execution time: 101.544 s
Execution time: 101.667 s

300k Objects on Physical Win2k3 x86 (Remote)
Average: ADFind = 49.90
Average: DSP = 44.44
Winner: DSP 5 secs Faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 44.782 s
Execution time: 44.316 s
Execution time: 44.473 s
Execution time: 44.322 s
Execution time: 44.380 s
Execution time: 44.440 s
Execution time: 44.394 s
Execution time: 44.544 s
Execution time: 44.428 s
Execution time: 44.247 s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 51.983 s
Execution time: 51.937 s
Execution time: 51.810 s
Execution time: 49.142 s
Execution time: 48.913 s
Execution time: 49.073 s
Execution time: 48.765 s
Execution time: 49.125 s
Execution time: 49.110 s
Execution time: 49.105 s

200k Objects on my Server at Home (Win2k8 x64)
Average: ADFind = 35.50
Average: DSP = 31.74
Winner: DSP 4 secs Faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 31.887 s
Execution time: 31.888 s
Execution time: 31.044 s
Execution time: 30.746 s
Execution time: 31.549 s
Execution time: 31.601 s
Execution time: 31.168 s
Execution time: 31.528 s
Execution time: 31.481 s
Execution time: 31.269 s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 37.305 s
Execution time: 34.815 s
Execution time: 34.275 s
Execution time: 34.193 s
Execution time: 39.116 s
Execution time: 34.634 s
Execution time: 39.265 s
Execution time: 33.686 s
Execution time: 33.793 s
Execution time: 33.917 s

Here is the script I used for the DSP Tests

function Get-DSPObject {
    Param(
            $filter = "(objectclass=user)",
            $base = ([ADSI]"").distinguishedName,
            $Server,
            [int]$pageSize = 1000,
            [string[]]$props = @("1.1"),
            [switch]$noHeader,
            [switch]$csv,
            [switch]$count
        )
       
    [VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
       
    [int]$pageCount = 0
    [int]$objcount = 0
   
    if(!$server){$server = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=","" }
   
    $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
    $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
   
    $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$subtree,$props)  
    $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
    $searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
    $searchRequest.Controls.add($pagedRequest) | out-null
    $searchRequest.Controls.Add($searchOptions) | out-null
   
    # Output Prep
    if($props -notcontains "1.1")
    {
        $MyProps = @()
        foreach($prop in $props){$MyProps += $prop.ToLower()}
        if($csv)
        {
            if(!$noHeader)
            {
                $header = "distinguishedName"
                foreach($prop in $props){$header += ",$prop"}
                $header
            }
        }
        else
        {
            $MyUserObj = New-Object System.Object
            $MyUserObj | Add-Member -name distinguishedName -MemberType "NoteProperty" -value $null
            foreach($prop in $props){$MyUserObj | Add-Member -name $prop -MemberType "NoteProperty" -value $null}
        }
    }
   
    # Process Pages
    while ($True)
    {
        # Increment the pageCount by 1
        $pageCount++
   
        # Cast the directory response into a SearchResponse object
        $searchResponse = $connection.SendRequest($searchRequest)
   
        # Display the retrieved page number and the number of directory entries in the retrieved page
        # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
        $objcount += ($searchResponse.entries).count
       
        # Display the entries within this page
        if(!$count)
        {
            if($props -notcontains "1.1")
            {
                foreach($entry in $searchResponse.Entries)
                {
                    if($csv)
                    {
                        $results = "`"{0}`"" -f $entry.distinguishedName
                        foreach($prop in $MyProps)
                        {
                            $results += ",`"{0}`"" -f ($entry.Attributes[$prop][0])
                        }
                        $results
                    }
                    else
                    {
                        $MyUserObj.distinguishedName = $entry.distinguishedName
                        foreach($prop in $MyProps)
                        {
                            $MyUserObj."$prop" = $null
                            $MyUserObj."$prop" = $entry.Attributes[$prop][0]
                        }
                        $MyUserObj
                    }
                }
            }
            else{$searchResponse.Entries | select distinguishedName}
        }
       
        # if this is true, there are no more pages to request
        if ($searchResponse.Controls[0].Cookie.Length -eq 0){if($count){$objcount};break}
   
        # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
        # page of data in the send request and cast the directory control into a PageResultResponseControl object
        $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
    }
}

foreach($i in (1..10))
{
    $time = Measure-Command { Get-DSPObject -prop "name","sAMAccountName" -csv }
    "Execution time: {0:n3} s" -f $time.TotalSeconds
}

Build Lab w/ Quest AD CMDLets

Earlier I wrote a post about a script that I used to build my AD Lab Build Lab (v1 w/out Quest Tools) and I mentioned I
would post a Quest version. I had some time run it (took about 6hrs.) So without further ado:

Whats it do Again?
# Creates A TestOU OU
# Creates A TestComputers OU
# Creates A TestUsers OU
# Creates A TestGroups OU
# Creates 10K OU’s Under TestOU
## Each of the 10k OUs will have 4 Child OUs
### Each OU should have 5 users Accounts and 5 Machines Accounts
# Create 500 Group Policies.
# Link 100 policies on the 10k Base OUs
# Create 2000 Users in the TestUser OU
# Create 2000 Computers in the TestComputer OU
# Create 2K Groups

Note: Added Write-Progress for OU/User Creation

# Adding Required Snapins
Add-PSSnapin SDMSoftware.PowerShell.GPMC -ea 0
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea 0

$DomainDN = (([ADSI]"").distinguishedName[0])
$DomainDNS = (([ADSI]"").distinguishedName[0]) -replace "DC=","" -replace ",","."
$users = @()

# A TestOU OU
$BaseOU = New-QADObject -Type OrganizationalUnit -ParentContainer $DomainDN  -Name TestOU

# A TestComputers OU
$TestComps = New-QADObject -Type OrganizationalUnit -ParentContainer $DomainDN -Name TestComputers

# A TestUsers OU
$TestUsers = New-QADObject -Type OrganizationalUnit -ParentContainer $DomainDN -Name TestUsers

# A TestGroups OU
$TestGrps = New-QADObject -Type OrganizationalUnit -ParentContainer $DomainDN -Name TestGroups

# 10K OUs Under TestOU
foreach($i in 1..10000)
{
    $lvl1Child = New-QADObject -Type OrganizationalUnit -ParentContainer $BaseOU.dn -Name "LvL1ChildOU$i"
    Write-Progress "Creating OUs LvL1ChildOU$i" -status "Updating" -perc ($i/10000*100)
    ## Each of the 10k OUs will have 4 Child OUs
    foreach($x in 1..4)
    {
        $lvl2Child = New-QADObject -Type OrganizationalUnit -ParentContainer $lvl1Child.dn -Name "LvL2Child${i}${x}"
        Write-Progress "Creating Child OUs LvL2Child${i}${x}" -status "Updating" -perc ($x/4*100) -id 1  
        foreach($y in 1..5)
        {
            ## Each OU should have 5 users Accounts and 5 Machines Accounts
            Write-Progress "Creating Child Users/Computers" -status "Updating" -perc ($y/5*100) -id 2
            New-QADUser -ParentContainer $lvl2Child.dn -Name "usr${i}${x}${y}" -SamAccountName "usr${i}${x}${y}" -UserPrincipalName "usr${i}${x}${y}@$DomainDNS" -UserPass "!P@ssw0rd22!" | Out-Null
            New-QADObject -ParentContainer $lvl2Child.dn -name "srv${i}${x}${y}" -objectAttributes @{"sAMAccountName"="srv${i}${x}${y}`$"} -type "Computer" | out-Null
        }
    }
}

# Create 500 Group Policies.
1..500 | %{New-SDMgpo "TestGPO$_"}

# Link 100 policies on the 10k Base OUs
1..100 | %{Add-SDMgpLink -name "TestGPO$_" -scope "OU=LvL1ChildOU$i,$($BaseOU.DN)"}

# Create 2000 Users in the TestUser OU
1..2000 | %{New-QADUser -ParentContainer $TestUsers.dn -Name "Testusr$_" -SamAccountName "Testusr$_" -UserPrincipalName "Testusr$($_)@$DomainDNS" -UserPass "!P@ssw0rd22!"}

# Create 2000 Computers in the TestComputer OU
1..2000 | %{New-QADObject -ParentContainer $TestComps.dn -name "TestComp$($_)" -objectAttributes @{"sAMAccountName"="TestComp$($_)`$"}}

# Create 2K Groups
1..2000 | %{New-QADGroup -ParentContainer $TestGrps.dn -name "TestGrp$_" -sAMAccountName "TestGrp$_"}

Import-ADUser: All I can say is WoW! Posh 55 / Vbscript 210

Wow.. Just another example of Powershell Proving it is the BIG DOG in the Admin and scripting World!
55 lines vs 200+ line VBScript and the output is PRETTY! 🙂

CSV file should look like this although only Fullname/sAMAccountName/Mail are required
FullName,sAMAccountName,Mail,Title,Description,Department,manager,Groups
John Smith,jsmith,jsmith@lab.com,BossMan,IdaMaster,IT,me,”Tgroup1,Tgroup2,Tgroup3″

### Import-ADUsersFromCSV

Params($ImportFile,$Password = "P@ssW0rd!",$domain,$OU)
function Get-UserDN{
    Param($usr,$dom)
    $root = [ADSI]"LDAP://$dom"
    $filter = "(&(objectcategory=user)(sAMAccountName=$usr))"
    $searcher = New-Object System.DirectoryServices.DirectorySearcher($root,$filter)
    $searcher.findone() | %{$_.properties.distinguishedname}
}
function Add-UsertoGroup{
    param($group,$UserDN,$dom)
    $root = [ADSI]"LDAP://$dom"
    $filter = "(&(objectcategory=group)(Name=$Group))"
    $searcher = New-Object System.DirectoryServices.DirectorySearcher($root,$filter)
    $grp = ($searcher.findone()).GetDirectoryEntry()
    $grp.add("LDAP://$dom/$UserDN")
    $grp.SetInfo()
}
$Users = Import-Csv $ImportFile
foreach($user in $users)
{
    Write-Host "+ Creating User <$($User.FullName)>"
    # Checking for sAMAccountName/Mail/FullName
    if(!$user.sAMAccountName){Write-Host "  – User $($User.FullName) has no sAMAccountName";continue}
    if(!$user.mail){Write-Host "  – User $($User.FullName) has no mail";continue}
    if(!$user.fullname){Write-Host "  – User $($User.sAMAccountName) has no FullName";continue}
    if($user.ManagerName)
    {
        $manager = Get-UserDN $user.ManagerName -dom $domain
        Write-Host "  – Manager DN $Manager"
    }
    # Creating Account in OU
    $UserOU = [ADSI]"LDAP://$domain/$OU"
    $userObj = $UserOU.Create("User","CN=$($User.FullName)")
    $userObj.put("givenName",($user.FullName).Split()[0])
    $userObj.put("sn",($user.FullName).Split()[1])
    Write-Host "  – Setting User NTLogin $($user.sAMAccountName)";$userObj.put("samAccountName",$user.sAMAccountName)
    Write-Host "  – Setting User Email $($user.mail)";$userObj.put("mail",$user.mail)
    if($user.Designation)
    {Write-Host "  – Setting User Designation $($user.Title)";$userObj.put("Title",$user.Title)}
    if($user.Description)
    {Write-Host "  – Setting User Description $($user.Description)";$userObj.put("Description",$user.Description)}
    if($user.department)
    {Write-Host "  – Setting User Department $($user.department)";$userObj.put("department",$user.department)}
    $userObj.Setinfo()
    $userObj.psbase.invokeset(‘accountdisabled’, $false)
    $userObj.Setinfo()
    $userObj.psbase.invoke("setpassword",$password)
    $userObj.Setinfo()
    foreach($g in (($user.Groups).Split(",")))
    {
        Write-Host "  – Adding User to $g"
        Add-UsertoGroup -group $g -UserDN $userObj.distinguishedname -dom $domain
    }
    write-Host
}

###################################
OUTPUT
###################################

+ Creating User
– Manager DN CN=me,OU=MyUsers,DC=lab,DC=com
– Setting User NTLogin jsmith
– Setting User Email jsmith@lab.com
– Setting User Designation Loser
– Setting User Description Pretty Cool Guy
– Setting User Department IT
– Adding User to tgroup1
– Adding User to tgroup2

+ Creating User
– Manager DN CN=me,OU=MyUsers,DC=lab,DC=com
– Setting User NTLogin gsmith
– Setting User Email gsmith@lab.com
– Setting User Designation Loser
– Setting User Description Pretty Cool Guy
– Setting User Department Sales
– Adding User to tgroup1
– Adding User to tgroup2