Dynamically populating user properties in Active Directory

Recently I was asked to help out with a small issue pertaining to rolling out MS Live Communications Server. Two SIP properties (msRTCSIP-LineServer & msRTCSIP-Line) on the user object in AD were set dependent on which office the user was located in, and their Direct Dial (DDI) number. These properties needed to be set on the users on 3 sites, and I was asked if PowerShell could assist.

Seeing as I don’t have the schema extensions in my test lab, we will step through a similar scenario, but we’ll update the users description property instead.

In this example we have two test users:
Test Users for setting description property

We’re interested in the two User properties: Office and Telephone number. These would make up part of the SIP information for LCS, but in our example we just want to add them to the description property for easier viewing in AD User’s and Computers.
User1 Property page

Presuming, that we want to update the description field against a large selection of users we will take advantage of how PowerShell handles CSV’s:
Accounts.csv


#
# NAME: Set-UsrDesc.ps1
#
# AUTHOR: Adam Bell, http://www.leadfollowmove.com
# RELEASE: Contractors Creed – All care taken, no responsibility admitted. Use at your own risk.
# VERSION:
# 1.0.1 25/09/2007 Adam Bell Initial.
# 1.0.2 26/09/2007 Adam Bell refined for live.
#
# COMMENT: Best viewed using Notepad2
#
# ———————————————————–
# Check command-line arguments
# ———————————————————–
if ($args.count -ne 1)
{
write-host “Usage:
write-host “CSV requires a header line.”
write-host ” UserName,OU is the only data needed. No DomainDN in the OU DN.”
write-host “”
exit
}
$InFile = $args[0]
If (-not(test-path $InFile))
{
write-host “Unable to locate” $InFile
exit
}
# ———————————————————–
# Globals
# ———————————————————–
# If there are any errors,we’ll handle them in code.

$erroractionpreference = “SilentlyContinue”
$root = [adsi]“”
# ———————————————————–
function set-Desc
# ———————————————————–
{
# Inputs: 1) The CN UserName of the Account object
# 2) The OU DN without the Domain DN portion included.
# Objective: 1) Process each user, and update the description property based on Office and DDI info.
# Returns: 1) Nothing. Updates the users props.
Param (
$UserName,
$OUname
)
$error.clear()
$user = [adsi](“LDAP://cn=”+$UserName `
+”,”+$OUname+”,”+$root.distinguishedName)
if (!$?)
{
write-warning “Unable to bind to $UserName”
write-host “”
continue
}
$office = $user.get(“physicalDeliveryOfficeName”)
if (!$?)
{
write-warning “Unable to retrieve Office Location for $UserName”
write-host “”
continue
}
$ddi = $user.get(“telephoneNumber”)
if (!$?)
{
write-warning “Unable to retrieve DDI number for $UserName”
write-host “”
continue
}
$user.put(“description”, “($office) $ddi”)
$user.setinfo()
If (!$error[0])
{
write-host “$username updated.” -foregroundcolor “green”
}
else
{
write-warning “Updating $UserName failed!”
}
write-host “”
}
# ———————————————————–
function get-userlist
# ———————————————————–
{
# Inputs: 1) Path to CSV file containing Users to process. Needs a header line: UserName, OU
# Objective: 1) Get the data from the CSV file and pass it to Set-Desc
# Returns: 1) Nothing.
# Depends: 1) Set-Desc
Param (
$PathToCSV
)
$accounts = Import-Csv $PathToCSV
foreach ($account in $accounts)
{
$UserName = $account.UserCN
If ($UserName.StartsWith(“#”) -eq $true)
{
}
Else
{
“Processing … “+$UserName
set-Desc $UserName $account.OU
}
}
}
# ———————————————————–
# Main
# ———————————————————–
get-userlist $InFile
# ———————————————————–

A couple of points of interest, from the script above:
The Username is the CN (usually the displayName property), and is added with the ouName provided in the CSV, and the distinguishedName of the domain to build the ADSI binding to the user object.

The get(),put() and setinfo() methods are pretty self explanatory, especially if you’ve done any ADSI scripting before.

What’s nice about PowerShell is that you can user variables “inline”. It doesn’t work when constructing the ADSI binding, but it does when constructing strings (in this case for out put() method).

The (!$?) if block is a nice error checking step that check’s if the the last command wasn’t successful.

The !error[0] if block tests if we have had any errors in our process, and provides feedback to the console accordingly.

Running the script gives us the following output:
Set-UsrDesc.ps1 script console output

And if we check the user account, we can see that the description field has been updated for easy viewing , if we had a lot of users.
User1 account - updated by the set-usrdesc.ps1 script

Control Access Rights in Active Directory

Recently we looked at Extended-Rights in Active directory, and today we’ll complete our series of posts on permissions in AD with a post on Control Access Rights, also known as Specific Rights.

Control Access Rights are similar to Standard Rights in the permissions applied, however it is applied to a specific object, or property set. This property is also represented by a system.guid like we have previously seen with Extended Rights.

In this case however the GUID is the schemaIDGUID attribute on the object located in the Schema partition. Provided the Common Name (cn) is known of the object needed the function below will retrieve the GUID in question:

$dse = [adsi](“LDAP://RootDSE”)

#———————————————————————————————————-
function get-SchemaGUID
#———————————————————————————————————-
{
Param (
$DSobject
)
$guid = $null
$obj = [adsi](“LDAP://cn=”+$DSobject+”,”+$dse.schemaNamingContext)
[system.guid]$guid = $obj.schemaIDGUID[0]
return $guid.ToString()
}
#———————————————————————————————————-
get-schemaguid “computer”

Once we have the GUID for the object or Property Set, we can then start thinking about our ACE. For Control Access Rights there are two types of scenarios, and hence two constructors available. We would use the following constructor when delegating the ability to create and delete computer objects in the computers container, for example.

Void .ctor(
Void .ctor(
System.Security.Principal.IdentityReference,
System.DirectoryServices.ActiveDirectoryRights,
System.Security.AccessControl.AccessControlType,
System.Guid,
System.DirectoryServices.ActiveDirectorySecurityInheritance)

The ACE can be further controlled by specificing the type of object that may inherit the ACE. In that scenario you would use the following constructor:

Void .ctor(
System.Security.Principal.IdentityReference,
System.DirectoryServices.ActiveDirectoryRights,
System.Security.AccessControl.AccessControlType,
System.Guid,
System.DirectoryServices.ActiveDirectorySecurityInheritance,
System.Guid)

As you can see with two GUID’s in the syntax it becomes much easier to get things in the wrong order and in turn get an unexpected result.

MSDN has a clearer description of the constructors, and you can see that the GUID’s are for different objects.

So, armed with the GUID of the object, and the rest of the of our information we can go about setting the Control Access Right.

$root = [adsi]“”
$test = [adsi](“LDAP://ou=Test OU,”+$root.distinguishedName)

$account = New-Object System.Security.Principal.NTAccount(“bella”)
$inherit = [System.DirectoryServices.ActiveDirectorySecurityInheritance]“All”

$rights = “CreateChild, DeleteChild”
$gplink = “f30e3bbe-9ff0-11d1-b603-0000f80367c1″
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($acc, $rights, “Allow”, $gplink, $Inherit)

$test.psbase.get_objectSecurity().AddAccessRule($ace)
$test.psbase.CommitChanges()

The above example gives the Bella account the rights to link Group Policy objects in the Test OU, and any sub OU’s (due to the inheritances flag).

Now we’ve covered the three types of permissions within the Active Directory, you should be able to apply any combination of permissions you need to organise the delegation model within your environment.

It makes me smile when I think back to the old NT4 days! ;)

[Updated:] The paragraph describing InheritedObjectTypes was rewritten to try and clarify the difference between the constructors.

Extended Rights in Active Directory

Extended Rights are one of the mechanisms behind Active Directory permissions that allow for such granular control over the delegation of tasks in your environment. There’s a Technet article that explains delegation and touches on Extended Rights (near the bottom).

Extended Rights exists in AD as objects stored within the Extended-Rights container, which is located in the Configuration partition.

If you’re not sure about the different partitions within AD there is an excellent primer on TechNet about AD Architecture.

So how do we set an Extended Right permission? It’s actually very similiar to applying a Standard Rights ACE. The difference comes in the parameters we pass, and hence the constructor we use in the ActiveDirectoryAccessRule method.


Void .ctor(
System.Security.Principal.IdentityReference,
System.DirectoryServices.ActiveDirectoryRights,
System.Security.AccessControl.AccessControlType,
System.Guid,
System.DirectoryServices.ActiveDirectorySecurityInheritance)

In this case the ActiveDirectoryRights value is the keyword “ExtendedRight“, and the system.Guid refers to an attribute of the Extended Rights object called the rightsGUID.

I’ve got a function that takes the CN of the right as input and returns the GUID.

Lookup Extended-Right GUID:

$dse = [adsi](“LDAP://Rootdse”)

#———————————————————————————————————-
function Get-RightsGUID
{
Param (
$ExtendedRight
)
$ER = [adsi](“LDAP://cn=Extended-Rights,”+$dse.configurationNamingContext)
$DSobject = [adsi](“LDAP://cn=”+$ExtendedRight+”,”+$ER.distinguishedName)
$ERguid = $DSobject.rightsGUID
return $ERguid
}
#———————————————————————————————————-
Get-RightsGUID “Change-PDC”

If you don’t know the right you’re looking for you have a couple of options. TechNet have a reference that lists them, but we can also look them up.

$dse = [adsi](“LDAP://Rootdse”)

# Build a Hashtable to translate the displayName (used by DSACL) to the objects CN.
$ERtbl = @{}
$ext = [adsi](“LDAP://cn=Extended-rights,”+$dse.configurationNamingContext)
$ext.psbase.children |% { $ERtbl.Add($_.displayName.toString(), $_.cn.toString()) }

The function above enumerates through the Extended-Rights container and loads the values for the displayName and cn into an associative array (hash table). Now we can look them up by either typing

$ERtbl

Which will list our complete hash table, or we can lookup specific translations

$ERtbl["Change PDC"]

So far so good. So putting it all together then. A code block to add the Change PDC right on the domain:

$root = [adsi]“”

$account = New-Object System.Security.Principal.NTAccount(“bella”)
$inherit = [System.DirectoryServices.ActiveDirectorySecurityInheritance]“All”
$rights = “ExtendedRight”

$changepdc = “bae50096-4752-11d1-9052-00c04fc2d4cf”
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($account, $rights, “Allow”, $changpdc, $Inherit)

$root.psbase.get_objectSecurity().AddAccessRule($ace)
$root.psbase.CommitChanges()

Next time we’ll look at our last post in on the topic of Active Directory permissions when we look at Control Access Rights.