Posts RSS Comments RSS 130 Posts and 202 Comments till now

Archive for the 'Active Directory' Category

Get/Set-ADACL (ACL and SDDLs for Active Directory!)

A friend had a need to get/set Active Directory ACLs. So I wrote these.

They will use [System.DirectoryServices.ActiveDirectoryAccessRule] objects or SDDLs strings.

Note: I put the .NET classes and MS Spec for SDDLs at the bottom. Dont miss it!

Get-ADACL.ps1

# Get-ADACL.ps1
Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
function HelpMe{
    Write-Host
    Write-Host " Get-ADACL.ps1:" -fore Green
    Write-Host "   Gets ACL object or SDDL for AD Object"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -DNPath                : Parameter: DN of Object"
    Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
    Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
    Write-Host "   -Help                  : [SWITCH]:  Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
    Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
    Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
    Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
    Write-Host
}

if(!$DNPath -or $help){HelpMe;return}

Write-Host
if($verbose){$verbosepreference="continue"}

Write-Verbose " + Processing Object [$DNPath]"
$DE = [ADSI]"LDAP://$DNPath"

Write-Verbose "   - Getting ACL"
$acl = $DE.psbase.ObjectSecurity
if($SDDL)
{
    Write-Verbose "   - Returning SDDL"
    $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
}
else
{
    Write-Verbose "   - Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
    $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
}

Set-ADACL.ps1

# Set-ADACL.ps1
Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
function HelpMe{
    Write-Host
    Write-Host " Set-ADACL.ps1:" -fore Green
    Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -DNPath                : Parameter: DN of Object"
    Write-Host "   -ACL                   : Parameter: ACL Object"
    Write-Host "   -sddl                  : Parameter: SDDL String"
    Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
    Write-Host "   -Help                  : [SWITCH]:  Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
    Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
    Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
    Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
    Write-Host
}

if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}

Write-Host
if($verbose){$verbosepreference="continue"}
Write-Verbose " + Processing Object [$DNPath]"

$DE = [ADSI]"LDAP://$DNPath"
if($sddl)
{
    Write-Verbose "   - Setting ACL using SDDL [$sddl]"
    $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
}
else
{
    foreach($ace in $acl)
    {
        Write-Verbose "   - Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
        $DE.psbase.ObjectSecurity.SetAccessRule($ace)
    }
}
$DE.psbase.commitchanges()
Write-Host

More Info
I used the following .NET Classes
System.DirectoryServices.DirectoryEntry
http://msdn2.microsoft.com/en-us/library/system.directoryservices.directoryentry.aspx
System.DirectoryServices.ActiveDirectoryAccessRule
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectoryaccessrule.aspx
System.DirectoryServices.ActiveDirectorySecurity
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurity.aspx
System.Security.AccessControl.AccessControlSections
http://msdn2.microsoft.com/en-us/library/system.security.accesscontrol.accesscontrolsections(vs.80).aspx

SDDL Info
MS: http://msdn2.microsoft.com/en-us/library/aa379567.aspx

More userAccountControl Flag Fun (Convert-ToUACFlag.ps1)

A question on the NG made me think about this. While I personally prefer the decimal that comes from userAccountControl, others may prefer to actually see the FLAGS that are set.

Here is the script I came up with. It will output and array by default, but -toString will output a “,” delimited string.

It has a great -help function with -verbose output that explains each UAC Flag

Convert-ToUACFlag.ps1

# Convert-ToUACFlag.ps1
Param([int]$uac,[switch]$ToString,[switch]$help,[switch]$verbose)
function HelpMe{
    Write-Host
    Write-Host " Convert-ToUACFlag.ps1:" -fore Green
    Write-Host "   Converts UAC from Decimal or Hex to User Account Control Flags (described verbose help)"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -UAC                   : Parameter User Account Control Value"
    Write-Host "   -toString              : [SWITCH]  Output to String instead of Array"
    Write-Host "   -Help                  : [SWITCH]  Displays This"
    Write-Host "   -Verbose               : [SWITCH]  Displays This and User Account Control Definitions"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Convert to Flag getting back array" -fore White
    Write-Host "     .\Convert-ToUACFlag.ps1 69649" -fore Yellow
    Write-Host "   Convert to Flag getting back string" -fore White
    Write-Host "     .\Convert-ToUACFlag.ps1 69649 -toString" -fore Yellow
    Write-Host
    if($verbose)
    {
        Write-Host " User Account Control Flags and Definition" -fore Green
        Write-Host "  + SCRIPT" -fore Yellow
        Write-Host "    - The logon script will be run."
        Write-Host
        Write-Host "  + ACCOUNTDISABLE" -fore Yellow
        Write-Host "    - The user account is disabled."
        Write-Host
        Write-Host "  + HOMEDIR_REQUIRED" -fore Yellow
        Write-Host "    - The home folder is required."
        Write-Host
        Write-Host "  + PASSWD_NOTREQD" -fore Yellow
        Write-Host "    - No password is required."
        Write-Host
        Write-Host "  + PASSWD_CANT_CHANGE" -fore Yellow
        Write-Host "    - The user cannot change the password."
        Write-Host "    - This is a permission on the user’s object."
        Write-Host
        Write-Host "  + ENCRYPTED_TEXT_PASSWORD_ALLOWED" -fore Yellow
        Write-Host "    - The user can send an encrypted password."
        Write-Host
        Write-Host "  + TEMP_DUPLICATE_ACCOUNT" -fore Yellow
        Write-Host "    - This is an account for users whose primary account is in another domain."
        Write-Host "    - This account provides user access to this domain,"
        Write-Host "      but not to any domain that trusts this domain."
        Write-Host "    - This is sometimes referred to as a local user account."
        Write-Host
        Write-Host "  + NORMAL_ACCOUNT" -fore Yellow
        Write-Host "    - This is a default account type that represents a typical user."
        Write-Host
        Write-Host "  + INTERDOMAIN_TRUST_ACCOUNT" -fore Yellow
        Write-Host "    - This is a permit to trust an account for a system domain that trusts other domains."
        Write-Host
        Write-Host "  + WORKSTATION_TRUST_ACCOUNT" -fore Yellow
        Write-Host "    - This is a computer account for a computer that is running"
        Write-Host "    - Microsoft Windows NT 4.0 and above and is a member of this domain."
        Write-Host
        Write-Host "  + SERVER_TRUST_ACCOUNT" -fore Yellow
        Write-Host "    - This is a computer account for a domain controller that is a member of this domain."
        Write-Host
        Write-Host "  + DONT_EXPIRE_PASSWD" -fore Yellow
        Write-Host "    - Represents the password, which should never expire on the account."
        Write-Host
        Write-Host "  + MNS_LOGON_ACCOUNT" -fore Yellow
        Write-Host "    - This is an MNS logon account."
        Write-Host
        Write-Host "  + SMARTCARD_REQUIRED" -fore Yellow
        Write-Host "    - When this flag is set, it forces the user to log on by using a smart card."
        Write-Host
        Write-Host "  + TRUSTED_FOR_DELEGATION" -fore Yellow
        Write-Host "    - When this flag is set, the service account (the user or computer account)"
        Write-Host "      under which a service runs is trusted for Kerberos delegation."
        Write-Host "    - Any such service can impersonate a client requesting the service."
        Write-Host "    - To enable a service for Kerberos delegation, you must set this flag on the"
        Write-Host "      userAccountControl property of the service account."
        Write-Host
        Write-Host "  + NOT_DELEGATED" -fore Yellow
        Write-Host "    - When this flag is set, the security context of the user is not delegated to"
        Write-Host "      a service even if the service account is set as trusted for Kerberos delegation."
        Write-Host
        Write-Host "  + USE_DES_KEY_ONLY" -fore Yellow
        Write-Host "    - (Windows 2000/Windows Server 2003) Restrict this principal to use only"
        Write-Host "      Data Encryption Standard (DES) encryption types for keys."
        Write-Host
        Write-Host "  + DONT_REQUIRE_PREAUTH" -fore Yellow
        Write-Host "    - (Windows 2000/Windows Server 2003) This account does not require"
        Write-Host "      Kerberos pre+authentication for logging on."
        Write-Host
        Write-Host "  + PASSWORD_EXPIRED" -fore Yellow
        Write-Host "    - (Windows 2000/Windows Server 2003) The user’s password has expired."
        Write-Host
        Write-Host "  + TRUSTED_TO_AUTH_FOR_DELEGATION" -fore Yellow
        Write-Host "    - (Windows 2000/Windows Server 2003) The account is enabled for delegation."
        Write-Host "    - This is a security-sensitive setting."
        Write-Host "    - Accounts with this option enabled should be tightly controlled."
        Write-Host "    - This setting allows a service that runs under the account to assume a client’s"
        Write-Host "      identity and authenticate as that user to other remote servers on the network."
    }
    Write-Host
}

if(!$uac -or $help){HelpMe;Return}
$flags = @()
switch ($uac)
{
    {($uac -bor 0×0002) -eq $uac}    {$flags += "ACCOUNTDISABLE"}
    {($uac -bor 0×0008) -eq $uac}    {$flags += "HOMEDIR_REQUIRED"}
    {($uac -bor 0×0010) -eq $uac}    {$flags += "LOCKOUT"}
    {($uac -bor 0×0020) -eq $uac}    {$flags += "PASSWD_NOTREQD"}
    {($uac -bor 0×0040) -eq $uac}    {$flags += "PASSWD_CANT_CHANGE"}
    {($uac -bor 0×0080) -eq $uac}    {$flags += "ENCRYPTED_TEXT_PWD_ALLOWED"}
    {($uac -bor 0×0100) -eq $uac}    {$flags += "TEMP_DUPLICATE_ACCOUNT"}
    {($uac -bor 0×0200) -eq $uac}    {$flags += "NORMAL_ACCOUNT"}
    {($uac -bor 0×0800) -eq $uac}    {$flags += "INTERDOMAIN_TRUST_ACCOUNT"}
    {($uac -bor 0×1000) -eq $uac}    {$flags += "WORKSTATION_TRUST_ACCOUNT"}
    {($uac -bor 0×2000) -eq $uac}    {$flags += "SERVER_TRUST_ACCOUNT"}
    {($uac -bor 0×10000) -eq $uac}   {$flags += "DONT_EXPIRE_PASSWORD"}
    {($uac -bor 0×20000) -eq $uac}   {$flags += "MNS_LOGON_ACCOUNT"}
    {($uac -bor 0×40000) -eq $uac}   {$flags += "SMARTCARD_REQUIRED"}
    {($uac -bor 0×80000) -eq $uac}   {$flags += "TRUSTED_FOR_DELEGATION"}
    {($uac -bor 0×100000) -eq $uac}  {$flags += "NOT_DELEGATED"}
    {($uac -bor 0×200000) -eq $uac}  {$flags += "USE_DES_KEY_ONLY"}
    {($uac -bor 0×400000) -eq $uac}  {$flags += "DONT_REQ_PREAUTH"}
    {($uac -bor 0×800000) -eq $uac}  {$flags += "PASSWORD_EXPIRED"}
    {($uac -bor 0×1000000) -eq $uac} {$flags += "TRUSTED_TO_AUTH_FOR_DELEGATION"}
}
if($toString){$flags | %{if($mystring){$mystring += ",$_"}else{$mystring = $_}};$mystring}else{$flags}

Oisin the “obsessive programmer” sent me this as another option

param
([int]$value)
$flags = @("","ACCOUNTDISABLE","", "HOMEDIR_REQUIRED",
"LOCKOUT", "PASSWD_NOTREQD","PASSWD_CANT_CHANGE", "ENCRYPTED_TEXT_PWD_ALLOWED",
"TEMP_DUPLICATE_ACCOUNT", "NORMAL_ACCOUNT", "","INTERDOMAIN_TRUST_ACCOUNT", "WORKSTATION_TRUST_ACCOUNT",
"SERVER_TRUST_ACCOUNT", "", "", "DONT_EXPIRE_PASSWORD", "MNS_LOGON_ACCOUNT", "SMARTCARD_REQUIRED",
"TRUSTED_FOR_DELEGATION", "NOT_DELEGATED","USE_DES_KEY_ONLY", "DONT_REQ_PREAUTH",
"PASSWORD_EXPIRED", "TRUSTED_TO_AUTH_FOR_DELEGATION")
1..($flags.length) | ? {$value -band [math]::Pow(2,$_)} | % { $flags[$_] }

A collection of LDAP Filter Info

I often find myself googling for LDAP filter info. This time I decided to post the resulting set of websites I hit for this info.

NOTE: MS release the Specs for Active Directory’s LDAP Compliance here. GREAT DOC!
http://download.microsoft.com/download/d/c/8/dc83e0b8-fc2c-4af4-bd27-45b5963ad98d/AD%20LDAP%20Compliance.doc

Blog Entry on LDAP Filters
————————-
http://bsonposh.com/modules/wordpress/?p=78

LDAP Filter Articles
——————-
query Active Directory by using a bitwise filter
http://support.microsoft.com/kb/269181

Search Filter Syntax
http://msdn2.microsoft.com/en-us/library/aa746475.aspx

Mastering the LDAP search filter
http://searchwinit.techtarget.com/tip/0,289483,sid1_gci1191071,00.html

userAccountControl
——————-
UserAccountControl flags
http://support.microsoft.com/kb/305144

User-Account-Control Attribute (Windows)
http://msdn2.microsoft.com/en-us/library/ms680832.aspx

Check Active Directory Latency

The other day I had a need to see how long it took for an object to be replicated to all the Domain Controllers in my Environment.

Here is the script I came up with. It does the following:
- Finds all Domain Controllers in the Domain (using .NET)
- Creates a contact object in a specified OU (Default is users container for the Domain)
- Gets the start Time
- Loops and Checks each DC for the object.
- Once all DCs have the object it gets End Time
- Outputs the results

Some extra features
- re-writes screen using $host.UI.RawUI.CursorPosition. No scrolling text :) - Outputs a custom object with Name and Time to Replicate (-table)

Parameters/Switches
-target: DC to create object on. (Default: it will find one for you)
-fqdn: Used to Find Domain Controllers (Default: Use current Domain)
-ou: DN of the path to create contact objects (Default Users Container)
-remove: If $true it removes the temp object (default is $true)
-table: switch that will return a table with the DC and Time it took to replicate (as output)

Here is the code:

Param($target = (([ADSI]"LDAP://rootDSE").dnshostname),
      $fqdn = (([ADSI]"").distinguishedname -replace "DC=","" -replace ",","."),
      $ou = ("cn=users," + ([ADSI]"").distinguishedname),
      $remove = $true,
      [switch]$table
      )

$context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$fqdn)
$dclist = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($context)
if($table)
{
    $DCTable = @()
    $myobj = "" | select Name,Time
    $myobj.Name = ("$target [SOURCE]").ToUpper()
    $myobj.Time = 0.00
    $DCTable += $myobj
}

$name = "rTest" + (Get-Date -f MMddyyHHmmss)
Write-Host "`n  Creating Temp Contact Object [$name] on [$target]"
$contact = ([ADSI]"LDAP://$target/$ou").Create("contact","cn=$Name")
$contact.SetInfo()
$dn = $contact.distinguishedname
Write-Host "  Temp Contact Object [$dn] Created! `n"

$start = Get-Date

$i = 0

Write-Host "  Found [$($dclist.count)] Domain Controllers"
$cont = $true

While($cont)
{
    $i++
    $oldpos = $host.UI.RawUI.CursorPosition
    Write-Host "  =========== Check $i ===========" -fore white
    start-Sleep 1
    $replicated = $true
    foreach($dc in $dclist)
    {
        if($target -match $dc.Name){continue}
        $object = [ADSI]"LDAP://$($dc.Name)/$dn"
        if($object.name)
        {
            Write-Host "  - $($dc.Name.ToUpper()) Has Object [$dn]" (" "*5) -fore Green
            if($table -and !($dctable | ?{$_.Name -match $dc.Name}))
            {
                $myobj = "" | Select-Object Name,Time
                $myobj.Name = ($dc.Name).ToUpper()
                $myobj.Time = ("{0:n2}" -f ((Get-Date)-$start).TotalSeconds)
                $dctable += $myobj
            }
        }
        else{Write-Host "  ! $($dc.Name.ToUpper()) Missing Object [$dn]" -fore Red;$replicated  = $false}
    }
    if($replicated){$cont = $false}else{$host.UI.RawUI.CursorPosition = $oldpos}
}

$end = Get-Date
$duration = "{0:n2}" -f ($end.Subtract($start).TotalSeconds)
Write-Host "`n    Took $duration Seconds `n" -fore Yellow

if($remove)
{
    Write-Host "  Removing Test Object `n"
    ([ADSI]"LDAP://$target/$ou").Delete("contact","cn=$Name")
}
if($table){$dctable | Sort-Object Time | Format-Table -auto}

Active Directory Permission Inheritance (The Glories of Consistency!)

Someone asked a question (on experts-exchange) about how to Enable Permission Inheritance on an Active Directory Object.

Here is what I came up with.

# Enable AD Permission Inheritance on an Object
Param($DN)
$user = [ADSI]"LDAP://$dn"
$user.psbase.ObjectSecurity.SetAccessRuleProtection($false,$true)
$user.psbase.CommitChanges()

During the same thread someone also asked how to do it in the File System.

Check it out… It is very similar.

#  Enable File Permission Inheritance on an Object
Param($path)
$acl = Get-Acl $path
$acl.SetAccessRuleProtection($false,$true)
set-Acl -aclObject $acl -path $path

This is just another case where relying on .NET framework provides power and consistency.

Lab Build w/ Quest tools (coming soon!)

A user (valdezdj) pointed out that my build script would be much shorter if I had used the Quest tools.

I agree and have always planned on releasing a Quest version. I just haven’t had time :(
I love the Quest tools, but there are tons of people at there that don’t have the ability to use them. This is why usually only post “Powershell Only” examples. In this case I planned on posting both to show the power of the Quest CMDLets.

To Download Quest AD CMDLets: http://www.quest.com/powershell/

p.s. This is challenge… you guys and post code to :)

Build Lab (v1 w/out Quest Tools)

This script worked for me… just took a few days :)
To Recap. This does the following. In my final revision I am removing the last two steps… it TAKES FOREVER!!! and its not the useful.

# A TestOU OU
# A TestComputers OU
# A TestUsers OU
# A TestGroups OU
# 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
# Find all the Users
# Create 2K Groups
## Add Even Numbered Users to Even Groups
## Add Odd Numbered Users to Odd Groups

function New-ADOU{
    Param($Name,$OU,$DC)
    # Get Root Path for OU
    if($dc -and $ou){$root = "LDAP://$dc/$ou"}
    if($dc -and !$ou){$root = "LDAP://{0}" -f $dc,(([ADSI]"LDAP://$dc/rootDSE").DefaultNamingContext)}
    if(!$dc -and $ou){$root = "LDAP://$OU"}
    if(!$dc -and !$ou){$root = "LDAP://{0}" -f (([ADSI]"LDAP://rootDSE").DefaultNamingContext)}

    #Write-Host ("Creating OU [{0}] Using Path [{1}]" -f $Name,$Root)

    # Creating Account in OU
    $BaseOU = [ADSI]"$root"
    $NewOU = $BaseOU.Create("organizationalUnit","OU=$Name")
    $NewOU.Setinfo()
    $NewOU.distinguishedName
}
function New-ADUSer{
    Param($user,$password="P@ssw0rd",$dc,$ou)

    # Get Root Path for OU
    if($dc -and $ou){$root = "LDAP://$dc/$ou"}
    if($dc -and !$ou)
    {$root = "LDAP://{0}/CN=Users,{1}" -f $dc,(([ADSI]"LDAP://$dc/rootDSE").DefaultNamingContext)}
    if(!$dc -and $ou)
    {$root = "LDAP://$OU"}
    if(!$dc -and !$ou)
    {$root = "LDAP://CN=Users,{0}" -f (([ADSI]"LDAP://rootDSE").DefaultNamingContext)}

    #Write-Host ("Creating user [{0}] Using Path [{1}]" -f $user,$Root)

    # Creating Account in OU
    $UserOU = [ADSI]"$root"
    $userObj = $UserOU.Create("User","CN=$user")

    # Set samAccountName
    $userObj.put("samAccountName","$user")
    $userObj.Setinfo()

    # Set Password
    $userObj.psbase.invoke("setpassword",$password)
    $userObj.Setinfo()

    # Enable Account
    $userObj.psbase.invokeset(‘accountdisabled’, $false)
    $userObj.Setinfo()
    $userObj.distinguishedName
}
function New-ADComputer{
    Param($Name,$OU,$DC)
    # Get Root Path for OU
    if($dc -and $ou){$root = "LDAP://$dc/$ou"}
    if($dc -and !$ou)
    {$root = "LDAP://{0}/CN=Users,{1}" -f $dc,(([ADSI]"LDAP://$dc/rootDSE").DefaultNamingContext)}
    if(!$dc -and $ou)
    {$root = "LDAP://$OU"}
    if(!$dc -and !$ou)
    {$root = "LDAP://CN=Computers,{0}" -f (([ADSI]"LDAP://rootDSE").DefaultNamingContext)}

    #Write-Host ("Creating user [{0}] Using Path [{1}]" -f $user,$Root)

    # Creating Account in OU
    $CompOU = [ADSI]"$root"
    $CompObj = $CompOU.Create("Computer","CN=$Name")

    # Set samAccountName
    $CompObj.put("samAccountName","$Name`$")
    $CompObj.Setinfo()

    # Enable Account
    $CompObj.psbase.invokeset(‘accountdisabled’, $false)
    $CompObj.Setinfo()
    $CompObj.distinguishedName
}
function New-ADGroup{
    Param($OU,$Grp,$dc)
    Write-Host " + Creating Group [$Grp] in OU [$OU]"

    # Get Root Path of OU
    if($dc){$GroupOU  = [ADSI]"LDAP://$dc/$ou"}
    else{$GroupOU  = [ADSI]"LDAP://$ou"}

    # Create Group
    $GroupObj = $GroupOU.Create("Group","CN=$Grp")
    $Groupobj.SetInfo()
    $Groupobj.distinguishedName
}
function Add-UsertoGroup{
    Param($User,$Grp,$DC)
    if($DC){$myGroup = [ADSI]"LDAP://$DC/$Grp"}
    else{$myGroup = [ADSI]"LDAP://$Grp"}
    #Write-Host "     - Processing User [$User] in Group [$Grp]"
    $myGroup.Add("LDAP://$user")
    $myGroup.SetInfo()
}

#A TestOU OU
Write-Host " + Creating TestOU"
$TestOU = New-ADOU -name TestOU

#A TestComputers OU
Write-Host " + Creating TestComputers OU"
$TestComp = New-ADOU -name TestComputers

#A TestUsers OU
Write-Host " + Creating TestUsers OU"
$TestUsers = New-ADOU -name TestUsers

#A TestGroups OU
Write-Host " + Creating TestGroups OU"
$TestGroups = New-ADOU -name TestGroups

#10K OU’s Under TestOU
foreach($n in 1..10000)
{
    Write-Host " + Creating Level1 OU [Level1OU$N]"
    $Level1 = New-ADOU -name "Level1OU$N" -ou $TestOU
    # Each of the 10k OUs will have 4 Child OUs
    foreach($i in 1..4)
    {
        Write-Host "   + Creating Level2 OU [Level2OU$i]"
        $Level2 = New-ADOU -name "Level2OU$i" -ou $Level1
        #Each OU should have 5 users Accounts and 5 Machines Accounts
        foreach($x in 1..5)
        {
            Write-Host "     - Creating User [Lvl2User$n$i$x] in [$Level2]"
            New-ADUSer -user "Lvl2User$n$i$x" -OU $Level2 | out-Null
            Write-Host "     - Creating Computer [Lvl2Comp$n$i$x] in [$Level2]"
            New-ADComputer -name "Lvl2Comp$n$i$x" -OU $Level2 | 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=Level1OU$_,$TestOU" -Location -1}

#Create 2000 Users in the TestUser OU
1..2000 | %{New-ADUSer -user "TestUser$_" -OU $TestUsers}

#Create 2000 Computers in the TestComputer OU
1..2000 | %{New-ADComputer -user "TestComputer$_" -OU $TestComputers}

# Find all the Users
$props = @("sAMAccountName","distinguishedName")
$ds = new-Object System.DirectoryServices.DirectorySearcher([ADSI]"","(objectcategory=user)",$props)
$ds.pagesize = 100
$users = $ds.Findall()
$eUsers = $users | ?{ $_.properties.item("sAMAccountName") -match ‘(2|4|6|8|0)$’ } | `
              select-Object @{n="Name";e={$_.properties.item("distinguishedName")}}
$oUsers = $users | ?{ $_.properties.item("sAMAccountName") -match ‘(1|3|5|7|9)$’ } | `
              Select-Object @{n="Name";e={$_.properties.item("distinguishedName")}}

#Create 2K Groups
foreach($i in 1..2000)
{
    $NewGrp = New-ADGroup -Grp "TestGrp$i" -OU $TestGroups
    if($i%2 -eq 0)
    {
        Write-Host "   + Adding Even Users to Group [$NewGrp]"
        $eUsers | Select-Object | %{ Add-UsertoGroup -user $_.name -Grp $NewGrp }
    }
    else
    {
        Write-Host "   + Adding Odd Users to Group [$NewGrp]"
        $oUsers | Select-Object |%{ Add-UsertoGroup -user $_.name -Grp $NewGrp }
    }
}

My Own Scripting Games!

I haven’t had time to participate in the 2008 and I have kinda missed out. But alas… a need has come through for me.

This is what I am looking for


I need to create a Test AD environment. I don’t need to mimic a specific production AD, but more like all of them :) I want an AD environment that has plenty of user/computer accounts and plenty of Groups. I also want a large number of OUs for testing Group Policy Scripts.
These need to be realistic numbers so here is the challenge I came up with (note, Quest tools are allowed!)


# A TestOU OU
# A TestComputers OU
# A TestUsers OU
# A TestGroups OU
# 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
# Find all the Users
# Create 2K Groups
## Add Even Numbered Users to Even Groups
## Add Odd Numbered Users to Odd Groups


I will post my solution as soon as I am done, but I would like other people’s thoughts so POST away!

The Power of LDAP Filters

A common problem when dealing with Active Directory is the end user trying to parse the results themselves.

Let take this example

$selector = New-Object DirectoryServices.DirectorySearcher
$selector.SearchRoot = [ADSI]""
$selector.pagesize = 1000
$adobj= $selector.findall() | where {$_.properties.objectcategory -match "CN=Person"}
foreach ($person in $adobj) {
   $date120DaysAgo = [DateTime]::Now.AddDays(-120).ToFileTime()
   $LL1 = $person.properties.lastlogontimestamp
   if(($LL1 -le $date120DaysAgo) -and ($person.GetDirectoryEntry().psbase.invokeget(‘AccountDisabled’))){$person}
}

Instead of doing the parsing on results side… we should let the server do the work. How do we do that?

With LDAP filters. Here is an example.

$date = (Get-Date).AddDays(-120).ToFileTime()
$filter = "(&(objectcategory=user)(userAccountControl:1.2.840.113556.1.4.803:=2)(lastlogontimestamp<=$date))"
$ds = New-Object DirectoryServices.DirectorySearcher([ADSI]"",$filter)
$ds.PageSize = 1000
$users = $ds.FindAll()
$users

Or with Quest tools… even easier!

PS: $date = (Get-Date).AddDays(-120).ToFileTime()
PS: $filter = "(&(objectcategory=user)(userAccountControl:1.2.840.113556.1.4.803:=2)(lastlogontimestamp<=$date))"
PS: Get-QADUser -LdapFilter $filter

I think you will find with an LDAP filter you can save a TON of time.

Here is the output of measure-command for the two examples above (this was a very small sample.)

Without Filter
————–
Days : 0
Hours : 0
Minutes : 0
Seconds : 3
Milliseconds : 477
Ticks : 34776670

With Filter
———–
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 34
Ticks : 340740

If you were to do this on a large AD the difference in time would be HUGE! Here is an example with 600K users…

With Filter
————
Days : 0
Hours : 0
Minutes : 0
Seconds : 17
Milliseconds : 353
Ticks : 173535605

I can’t post one with out filter… because it has been hours and it is still not done :)

AD Replication Metadata (when did that change?)

There was a discussion on the NG about determining when a user was disabled. The initial request was to determine this based on whenChanged, but I thought that could be invalid as you can easily change an account after it was disabled. I can not think of a way to be sure, but the best way I can think of is to use the replication metadata on the attribute userAccountControl (the second bit is what determines if its disabled or not.) While it is possible to change the useraccountcontrol after a user is disabled it is unlikely.

More info for UserAccountControl bits
http://support.microsoft.com/kb/305144

Of course the next question was how do you check the Replication Metadata for an attribute on and AD object?

Enter Get-ADObjectREplicationMetadata.ps1

This uses

System.DirectoryServices.ActiveDirectory.DirectoryContext
- http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectory.directorycontext.aspx
System.DirectoryServices.ActiveDirectory.DomainController
- http://msdn2.microsoft.com/en-gb/library/system.directoryservices.activedirectory.domaincontroller.aspx

# Get-ADObjectREplicationMetadata.ps1
# Brandon Shell (www.bsonposh.com)
# Purpose: Get attribute(s) Replication Metadata from a Domain controller.
Param($Domain,$objectDN,$property)
# Sets Context to Domain for System.DirectoryServices.ActiveDirectory.DomainController
$context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$domain)
# .NET Class that returns a Domain Controller for Specified Context
$dc = [System.DirectoryServices.ActiveDirectory.DomainController]::findOne($context)
# GetReplicationMetadata returns metadate from the DC for the DN specified.
$meta = $dc.GetReplicationMetadata($objectDN)
if($property){$meta | %{$_.$Property}}else{$meta}

This will return either all the metadata or just the metadata for a specific attribute. I should note that if you do not specify an attribute it returns all of them. You should expect to parse these as each attribute has a child object with the data in it.

All Attributes. The value can be found by .PropertyName

PS# .\Get-ADObjectMetaData.ps1 ‘my.lab.domain’ ‘CN=TestUser,DC=my,dc=lab,dc=domain’

Name                           Value
—-                           —–
countrycode                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
cn                             System.DirectoryServices.ActiveDirectory.AttributeMetadata
mail                           System.DirectoryServices.ActiveDirectory.AttributeMetadata
scriptpath                     System.DirectoryServices.ActiveDirectory.AttributeMetadata
ntsecuritydescriptor           System.DirectoryServices.ActiveDirectory.AttributeMetadata
accountexpires                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
displayname                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
profilepath                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
primarygroupid                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
unicodepwd                     System.DirectoryServices.ActiveDirectory.AttributeMetadata
objectclass                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
objectcategory                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
instancetype                   System.DirectoryServices.ActiveDirectory.AttributeMetadata
homedrive                      System.DirectoryServices.ActiveDirectory.AttributeMetadata
samaccounttype                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
homedirectory                  System.DirectoryServices.ActiveDirectory.AttributeMetadata
whencreated                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
useraccountcontrol             System.DirectoryServices.ActiveDirectory.AttributeMetadata
msmqsigncertificates           System.DirectoryServices.ActiveDirectory.AttributeMetadata
dbcspwd