Welcome to MSDN Blogs Sign in | Join | Help

In this post we will see the Powershell way of redirecting Users and Computers containers (i.e. Powershell equivalent of tools: redirusr.exe and redircmp.exe).

By now you might know that you can use Get-ADDomain cmdlet for viewing the well-known containers of a domain, For example:

PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Get-ADDomain | select *Container


ComputersContainer                 : CN=Computers,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
DeletedObjectsContainer            : CN=Deleted Objects,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
DomainControllersContainer         : OU=Domain Controllers,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
ForeignSecurityPrincipalsContainer : CN=ForeignSecurityPrincipals,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
LostAndFoundContainer              : CN=LostAndFound,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
QuotasContainer                    : CN=NTDS Quotas,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
SystemsContainer                   : CN=System,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
UsersContainer                     : OU=SwamTempOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com

However, Get-ADDomain cmdlet does not show you all the well-known containers. For example some lesser known containers such as: Program Data Container, Managed Service Account Container (which is technically an Other-Well-Known container) etc. are missing. Also, one cannot change/redirect a well-known container to some other OU using Set-ADDomain cmdlets.

These limitations can be easily overcome using Get-ADObject and Set-ADObject cmdlets. This blog discusses how to do these tasks using ADObject cmdlets and also provides easy-to-use functions for them.

Fetching well-known containers is really simple. All you have to do is read the wellKnownObjects and otherWellKnownObjects properties of the domain’s default naming context.

PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Get-ADObject (Get-ADRootDSE).DefaultNamingContext -Properties otherWellKnownObjects, wellKnownObjects | fl OtherWellKnownObjects, wellKnownObjects


OtherWellKnownObjects : {B:32:1EB93889E40C45DF9F0C64D23BBB6237:OU=TestMSAOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com}
wellKnownObjects      : {B:32:A9D1CA15768811D1ADED00C04FD8D5CD:OU=SwamTempOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com, B:32:6227F0AF1FC2410D8E3BB10615BB5B0F:CN=NTDS Quotas,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com, B:32:F4BE92A4C777485E878E9421D53087DB:CN=Microsoft,CN=Program Data,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com, B:32:09460C08AE1E4A4EA0F64AEE7DAA1E5A:CN=Program Data, DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com...}

These properties contain a list of well-known object containers by GUID and distinguished name. Of course you must know the GUID of the container that you are looking for, to find its value.

Also, changing/redirecting a well-known container can be done using Set-ADObject cmdlet by removing the old value from wellKnownObjects/otherWellKnownObjects attribute on the default naming context (i.e. DC=domainname,DC=com) and adding a new value. For example in order to redirect the Users container, one would run the following command:

PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Set-ADObject (Get-ADRootDSE).DefaultNamingContext -Remove @{wellKnownObjects = "B:32:A9D1CA15768811D1ADED00C04FD8D5CD:CN=Users,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com" } -Add @{wellKnownObjects = "B:32:A9D1CA15768811D1ADED00C04FD8D5CD:OU=SwamTempOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com" } -server (Get-ADDomain).PDCEmulator

NOTE: The operation must be performed on the Primary domain controller (PDC).

Though it is feasible to use Get-ADObject and Set-ADObject cmdlets to read and manipulate well known containers, it is cumbersome. I have written few functions that would Get and Set the values of these containers.

In order to express the container name in a user-friendly way (rather than a cryptic GUID) I have created a new Enum called: WellKnownGuid. (I used a modified version of Add-Enum script described here in order to create the Enum)

There are two functions Get-XADWellKnownContainer and Set-XADWellKnownContainer that would get and set the value of a well-known container identified by its WellKnownGuid.

Example:

PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Get-XADWellKnownContainer UsersContainer
OU=SwamTempOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com


PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Get-XADWellKnownContainer ComputersContainer
CN=Computers,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com

For getting the values of all the Well-known containers of a domain type this:

PS C:\Users\Administrator.DSWAMIPAT-W7-V1> [Enum]::GetNames([WellKnownGuid]) | %{ $_.PadRight(30) + " : "+(Get-XADWellKnownContainer $_)}
UsersContainer                 : OU=SwamTempOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
ComputersContainer             : CN=Computers,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
SystemsContainer               : CN=System,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
DCContainer                    : OU=Domain Controllers,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
InfrastructureContainer        : CN=Infrastructure,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
DeletedObjectsContainer        : CN=Deleted Objects,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
LostAndFoundContainer          : CN=LostAndFound,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
ForeignSecurityPrincipalContainer : CN=ForeignSecurityPrincipals,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
ProgramDataContainer           : CN=Program Data,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
MicrosoftProgramDataContainer  : CN=Microsoft,CN=Program Data,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
NtdsQuotasContainer            : CN=NTDS Quotas,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
ManagedServiceAccountContainer : OU=LeakTestOU,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com

For changing/redirecting a well-known container simply pass the container name and new DN to Set-XADWellKnownContainer function.

Example:

PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Set-XADWellKnownContainer  UsersContainer CN=Users,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com"


PS C:\Users\Administrator.DSWAMIPAT-W7-V1> Get-XADWellKnownContainer  UsersContainer
CN=Users,DC=dswamipat-w7-vm1,DC=nttest,DC=microsoft,DC=com

The script that contains these functions can be found attached to this blog.

For more information on well-known containers, read this: Binding to Well-Known Objects using WKGUID.

Cheers,

Swami

Metadata associated with Active Directory Replication is exposed in AD via many constructed attributes. Some of these metadata attributes come in pairs of binary blob & xml representation of the metadata element.

Constructed Replication XML Metadata Attribute

Available on Objects

Corresponding Binary Attribute

msDS-ReplQueueStatistics

Root DSE

-

msDS-ReplAllInboundNeighbors

Root DSE

-

msDS-ReplAllOutboundNeighbors

Root DSE

-

msDS-NCReplCursors

NC Head

replUpToDateVector

msDS-NCReplInboundNeighbors

NC Head

repsFrom

msDS-NCReplOutboundNeighbors

NC Head

repsTo

msDS-ReplAttributeMetaData

All Objects

replPropertyMetaData

msDS-ReplValueMetaData

All Objects

-

For the Inspecting Deleted Objects before Restore blog post script, I had parsed the msDS-ReplAttributeMetaData attribute of the deleted objects to determine the actual deleted time of the objects. I had found the parsing so simple to do using ADPowerShell, that I decided to write about it in this separate post.

This post is focused on the msDS-ReplAttributeMetaData attribute … but the technique shown here can be used to parse all of the above listed XML format attributes …

Let’s start by a simple Get-ADObject call and requesting the msDS-ReplAttributeMetaData attribute.

Get-ADObject "DC=DUGILL,DC=DEMO" -Properties "msDS-ReplAttributeMetaData"

Simple Get-ADObject with msDS-ReplAttributeMetaData

Let’s store the xml string value in a local variable so that we can play with it without cluttering the commandline. Now, the attribute name has a hyphen (‘-’) in it so we will need to help the powershell parser and put quotes around the attribute name.

$xmlAttribute = (Get-ADObject "DC=DUGILL,DC=DEMO" -Properties "msDS-ReplAttributeMetaData")."msDS-ReplAttributeMetaData"

Above we see that the xml text from the attribute is not a valid xml string, as it does not have a root tag. So let’s add a root tag to it.

$xmlAttribute = “<root>” + $xmlAttribute + “</root>”

Now that we’ve made our msDS-ReplAttributeMetaData attribute value a valid xml string, comes the turn of some PowerShell magic. PowerShell makes it really simple to convert XML string to an XML Document Object (a form which can be worked on programmatically in a simpler way …)

SO, let’s go ahead and cast the string into an XML Document object.

$xmlAttributeDocObject = [xml] $xmlAttribute # wow … it’s that simple

Null Charecter Error

Ok not that simple … we hit a pretty nasty looking parsing error. The error is because the XML string contains some ASCII null characters (a bug in AD). So how do we get rid of these null charecters?

The hint is in the error which says the char has a hex value of 0x00 which is basically decimal 0. Can I use String.Replace function to get rid of these characters? As it turns out – I can …

String.Replace takes two arguments: first, the char or string to replace and second, the char or string to replace with. Note I’m using PowerShell casting again, to convert the hex value 0x00 to a char value. Replacing all occurrences of ascii nulls in the string with a space char (“ ”) …

$xmlAttribute = $xmlAttribute.Replace([char]0,” ”)

Now let’s try converting it into the XML Doc Object again … this time it works!!

$xmlAttributeDocObject = [XML]$xmlAttribute

Let’s inspect the xml doc object and admire the wonderful XML experience provided by PowerShell :)

Inspecting XML Doc Object

And finally $xmlAttributeDocObject.root.DS_REPL_ATTR_META_DATA | ft #… viola!

BTW, this output had got me wondering if I had just typed repadmin /showmeta …

Inspecting XML Doc Object - Repadmin Like Output

Focusing on selected interesting XML elements …

$xmlAttributeDocObject.root.DS_REPL_ATTR_META_DATA | ft pszAttributeName,ftimeLastOriginatingChange,pszLastOriginatingDsaDN

Inspecting XML Doc Object - FT Selected Attributes

We see that the originating change time is in the Zulu time format and the originating dc’ NTDS Settings object DN is what is available … with some format transforms we can get to a better looking output

$ObjectMetadataTableFormatter = @()

$ObjectMetadataTableFormatter += @{Label="Attribute LDAP Name"; Expression={$_.pszAttributeName}}

$ObjectMetadataTableFormatter += @{Label="Originating DC"; Expression={(Get-ADObject $_.pszLastOriginatingDsaDN.Substring(17,$_.pszLastOriginatingDsaDN.length-17) -properties dnshostname).DNSHostName}}

$ObjectMetadataTableFormatter += @{Label="Originating Change Time"; Expression={[datetime]::parse($_.ftimeLastOriginatingChange)}}

$xmlAttributeDocObject.root.DS_REPL_ATTR_META_DATA | ft $ObjectMetadataTableFormatter

Inspecting XML Doc Object - Using Table Formatter

You may also use the attached script function (or add it into your function profile script). The function allows pipelining objects from other Get-AD cmdlets to fetch the object replication metadata …

Using the function

enjoy!

Dushyant Gill

Program Manager - Microsoft

ACL inheritance is one of key concept in Active Directory delegation of control. It allows ACEs set on a parent container gets inherited by its child objects. It simplifies access management significantly as it allows the management to be done on the container level rather than on individual leaf objects. However, sometimes we may want to block such inheritance on some special objects. With a protected ACL set on the object, any inheritable ACEs set on its parent will not be able to propagate down to the object and as a result only the ACEs explicitly set on it take effect. The following examples show how to find out a protected ACL and set/unset such an ACL:

## Get the object’s DACL

PS AD:\> $acl = get-acl "OU=myou,DC=fabrikam,DC=com”

## Finding out if an ACL is protected is very simple.

PS AD:\> $acl.AreAccessRulesProtected

## When setting ACL to be protected with SetAccessRuleProtection with the first parameter set to $true, we also 

## have two options for the ACEs already inherited, we may choose either to remove them or convert them to 

## explicit ACEs. In the example below, we choose to convert the inherited ACEs by setting the second parameter 

## to $true.  

PS AD:\> $acl.SetAccessRuleProtection($true, $true);

## Make sure set the ACL on the object after the change has been made to the ACL.

PS AD:\> set-acl -aclobject $acl "OU=myou,DC=fabrikam,DC=com”

## Once the ACL is protected the inheritance chain is broken

## To unprotect the ACL (i.e. to fix the broken inheritance), set the first parameter to $false. The second parameter is ignored in the case.

PS AD:\> $acl.SetAccessRuleProtection($false, $false);

## Always make sure set the ACL on the object after any change has been made to the ACL

PS AD:\> set-acl -aclobject $acl "OU=myou,DC=fabrikam,DC=com”

Thanks,

David

Active Directory Powershell implements two Powershell Provider cmdlets specifically for access control management in Active Directory: Get-ACL and Set-ACL. This blog series is to give a few examples on how to use them. Note that it is not intended for a detailed explanation of access control and delegation in Active Directory and with an assumption that the readers have had basic knowledge. Additional information on Active Directory access control and delegation can be found in the Best Practices for Delegating ActiveDirectory Administration and related topics in MSDN.

One of the unique aspects in access control management in Active Directory is the introduction of object specific ACEs which allow an administrator to delegate Active Directory specific rights (i.e. extended rights) or read/write access to a property set (i.e. a named collection of attributes) by setting ObjectType field in an object specific ACE to the rightsGuid of the extended right or property set. The delegation can also be created to target child objects of a specific class by setting the InheritedObjectType field to the schemaIDGuid of the class. For more information on how to retrieve the rightsGuid or schemaIDGuid using Active Directory Powershell, please refer to Swami’s blog How to find extended rights that apply to a schema class. In the example below, we are going to create two object specific ACEs with one granting the group “myGroup” the extended right “Reset Password” for all users and the other giving it permission to delete computer objects, all under the organizationUnit “myOU”.

## Load Active Directory Powershell Module

PS C:\> cd ad:

PS AD:\> 

## Create myOU

PS AD:\> $ou = new-adorganizationalunit -name myOU -passthru

## Create myGroup and obtain its SID 

PS AD:\> $group = new-adgroup myGroup -groupscope global -passthru

PS AD:\> $sid = new-object System.Security.Principal.SecurityIdentifier $group.SID

## Get the DACL of myOU

PS AD:\> $acl = get-acl $ou

## The following object specific ACE is to grant myGroup permission to create computer objects under myOU.

## Note that bf967a86-0de6-11d0-a285-00aa003049e2 is the schemaIDGuid for the computer class.

PS AD:\> $objectguid = new-object Guid bf967a86-0de6-11d0-a285-00aa003049e2                          

PS AD:\> $ace1 = new-object System.DirectoryServices.ActiveDirectoryAccessRule $sid,"CreateChild","Allow",$objectguid

## The following object specific ACE is to grant myGroup permission to change user password on all user objects 

## under myOU. 00299570-246d-11d0-a768-00aa006e0529 is the rightsGuid for the extended right 

## User-Force-Change-Password (“Reset Password”). bf967aba-0de6-11d0-a285-00aa003049e2 is the schemaIDGuid 

## for the user class. 

PS AD:\> $objectguid = new-object Guid  00299570-246d-11d0-a768-00aa006e0529

PS AD:\> $inheritedobjectguid = new-object Guid  bf967aba-0de6-11d0-a285-00aa003049e2

$ace2 = new-object System.DirectoryServices.ActiveDirectoryAccessRule $sid,"ExtendedRight",$objectGuid,"Descendents",$inheritedobjectguid

## Add the ACE in the ACL and set the ACL on the object 

PS AD:\> $acl.AddAccessRule($ace1)

PS AD:\> $acl.AddAccessRule($ace2)

PS AD:\> set-acl -aclobject $acl $ou

Thanks,

David

I am sure many of us are curious to see the XML messages communicated between the AD Powershell webservices client and a Windows server hosting AD Webservices whenever a powershell cmdlet gets executed. In this blog, I am providing information to view those messages by enabling Windows Communication Foundation (WCF) logging through ADWS configuration. For more in general on WCF configuring message logging, please see: http://msdn.microsoft.com/en-us/library/ms730064.aspx

1) Stop Active Directory Web services by typing “net stop adws” on a command prompt.

2) Save a copy of the original %systemroot%\windows\adws\Microsoft.ActiveDirectory.WebServices.exe.config file.

3) Now open the file %systemroot%\windows\adws\Microsoft.ActiveDirectory.WebServices.exe.config for editing.

4) Copy and paste the following lines after </appSettings> tag in the file above:

 

   <system.diagnostics>
      <sources>
        <source name="System.ServiceModel.MessageLogging">
          <listeners>
                   <add name="messages"
                   type="System.Diagnostics.XmlWriterTraceListener"
                   initializeData="c:\messages.svclog" />   <!-- Provide your desired path for log file here -->   
          </listeners>
       </source>
     </sources>
  </system.diagnostics> 

And the following lines just under <system.serviceModel> tag:

 

   <diagnostics>
     <messageLogging 
          logEntireMessage="true" 
          logMalformedMessages="true"
          logMessagesAtServiceLevel="true" 
          logMessagesAtTransportLevel="true"/>
    </diagnostics>

5)  Save and close the config file.

6)  Start the service using “net start adws” and make sure configuration file got loaded successfully by checking the ADWS event log ID 1100  “The configuration file for Active Directory Web Services has been loaded without errors.”

7) Open Active Directory Module for Windows Powershell and run any AD cmdlet.

8) Note that a file called c:\message.svclog got created which contains all the XML messages!

Message.svclog contains a sequence of XML fragments and is not a valid XML file. It is recommended that you use the Service Trace Viewer Tool (SvcTraceViewer.exe) to view the message log files in a more formatted manner. For more information on how to use this tool, see Using Service Trace Viewer for Viewing Correlated Traces and Troubleshooting.

For e.g. below is a Get-Transfer root dse request from AD powershell to ADWS:

clip_image002

And the Get-transfer response to the above request from ADWS:

clip_image002[4]

This mechanism of logging messages comes very handy while debugging errors and exceptions. It is also useful to see the exact message sent to/from the ADWS in raw form, for ex: by looking at the request XML sent by Get-ADObject one can find out how Advanced Filters are converted to ldap filters. By setting appropriate message logging options it is possible to view malformed messages and faults. One can also view XML messages communication between Active Directory Administrative Center and ADWS through such logging. 

Enjoy!

Swasti Mishra

Developer – Active Directory Web Services team

Recently, I came across this question (how to find extended rights that apply to a schema class) in our internal mailing lists. Extended rights are special permissions that denote a special task or function. These rights apply to one or more object classes and can be found stamped in the security descriptor of an object.

There are KB articles that describe how to find extended rights that apply to a given objectClass using VBScript or C++.

http://support.microsoft.com/kb/302514

I was curious to find out how easy or difficult it is to do this in AD Powershell. It turned out to be pretty straightforward, just a few lines of code. Compare this with the ~81 lines of VB Script code in the above KB article.

Here is the script in AD Powershell:

$inputObjectClass = "group"

$rootDSE = Get-ADRootDSE
$configNCDN = $rootDSE.ConfigurationNamingContext
$schemaNCDN = $rootDSE.SchemaNamingContext
$extendedRightsDN = "CN=Extended-Rights," + $configNCDN
$classObject = get-adobject -SearchBase $schemaNCDN -Filter { name -eq $inputObjectClass -and objectClass -eq "classSchema"} -Properties SchemaIDGUID
if ($classObject -ne $null) {

    $schemaIDGuid = [System.Guid] $classObject.SchemaIDGUID
    Get-ADObject -SearchBase $extendedRightsDN -Filter { appliesTo -eq $schemaIDGuid  } -Properties RightsGuid,cn,displayname | Select RightsGuid,cn,displayname

} else {

    Write-Error ("Specified class object not found! : " + $inputObjectClass)

}

Although SchemaIDGuid is stored as a Octet String (i.e. Byte[]), look how easy it is to convert that into a System.Guid object. Just a simple type-cast in Powershell does that.

    $schemaIDGuid = [System.Guid] $classObject.SchemaIDGUID

Cheers,

Swami

RTW version of Active Directory Management Gateway Service (ADMGS), an Active Directory Web Services (ADWS overview here) out of band release for down level servers is now available to download from Microsoft Download Center Page. ADMGS is a down level release of in-box version of Windows Server 2008 R2  ADWS and provides the same functionality. One can download and install ADMGS on servers and domain controllers running the following operating systems:

  • Windows Server® 2003 R2 with Service Pack 2 (SP2)
  • Windows Server 2003 SP2
  • Windows Server 2008
  • Windows Server 2008 SP2

ADMGS enables administrators to use Windows Server 2008 R2 or Windows 7 client applications such as the Active Directory module for Windows PowerShell and the Active Directory Administrative Center (ADAC) to access or manage their existing Active Directory Domain Services (ADDS) and Active Directory Lightweight Directory Services (AD LDS) instances that are running on Windows Server 2008-based (both writable and read-only) or Windows Server 2003-based servers.

The only difference between the functionality that ADWS provides for domain controllers that are running Windows Server 2008 R2 operating system and the functionality that ADMGS provides to domain controllers that are running Windows Server 2008 and Windows Server 2008 SP2 operating systems is that ADMGS does not support instances of the Active Directory Database Mounting Tool running on Windows Server 2008-based servers.

Downloadable install guide at the same location provides information on ADMGS installation, uninstallation and upgrade scenarios.

Enjoy!

Swasti Mishra

Developer -  Active Directory Web Services team

This tool started when I was finding ways to analyze the complexity of group memberships in AD. Other than the usual average/median/min/max of number of members, number of memberships etc, I was also interested in finding out the maximum nesting levels of groups and the recursive group membership count. For e.g. in the diagram below, the maximum nesting level of ‘group a’ is 3 and its recursive group membership count is 6.

Image1

Analyzing the recursive group membership of a group is helpful in troubleshooting many scenarios, for e.g. Token Bloat troubleshooting/monitoring, misdirected distribution group memberships etc…

The attached script (Get-ADGroupNesting) retrieves an AD group with 2 additional properties:

1. NestedGroupMembershipCount

2. MaxNestingLevel

Image2

When used with the –ShowTree parameter, the script displays the recursive group membership tree along with emitting the ADGroup object.

Image3

The script can be used with Get-ADPrincipalGroupMembership cmdlet to analyze the recursive group membership of a user/computer/group.

Image4

The above usage can be filtered to analyze the recursive group membership of security groups only, by adding a Where clause … This usage can be utilized in troubleshooting token bloat issues.

Image5

Finally, a sample of usage with Get-ADUser cmdlet:

Image6

Usage:

Step 1 (Important): Map a new AD PowerShell Provider drive to the Global Catalog. And CD to it.

PS C:\> New-PSDrive -PSProvider ActiveDirectory -Server <dc/domain name> -Root "" –GlobalCatalog –Name GC

PS C:\> cd GC:

PS GC:\>

Step2: Use the script Get-ADGroupNesting.ps1 in the below ways.

Here’ the commands in the above screenshots:

1. PS GC:\> Get-ADGroupNesting.ps1 CarAnnounce

2. PS GC:\> Get-ADGroupNesting.ps1 CarAnnounce –ShowTree

3. PS GC:\> Get-ADPrincipalGroupMembership DonFu | % {Get-ADGroupNesting $_} | FT Name,GroupCategory,NestedGroupMembershipCount,MaxNestingLevel –A

4. PS GC:\> Get-ADPrincipalGroupMembership DonFu | Where {$_.GroupCategory -eq "Security"} | % {Get-ADGroupNesting $_ -ShowTree | FT Name,GroupCategory,NestedGroupMembershipCount,MaxNestingLevel -A}

5. PS GC:\> (Get-ADUser DonFu -Properties MemberOf).MemberOf | % {Get-ADGroupNesting.ps1 $_ -ShowTree} | FL DistinguishedName,NestedGroupMembershipCount,MaxNestingLevel

 

cheers,

Dushyant Gill

Program Manager - Microsoft Corporation

 

##########Copy the below script into a new file called Get-ADGroupNesting.ps1

Param (
    [Parameter(Mandatory=$true,
        Position=0,
        ValueFromPipeline=$true,
        HelpMessage="DN or ObjectGUID of the AD Group."
    )]
    [string]$groupIdentity,
    [switch]$showTree
    )

$global:numberOfRecursiveGroupMemberships = 0
$lastGroupAtALevelFlags = @()

function Get-GroupNesting ([string] $identity, [int] $level, [hashtable] $groupsVisitedBeforeThisOne, [bool] $lastGroupOfTheLevel)
{
    $group = $null
    $group = Get-ADGroup -Identity $identity -Properties "memberOf"   
    if($lastGroupAtALevelFlags.Count -le $level)
    {
        $lastGroupAtALevelFlags = $lastGroupAtALevelFlags + 0
    }
    if($group -ne $null)
    {
        if($showTree)
        {
            for($i = 0; $i -lt $level - 1; $i++)
            {
                if($lastGroupAtALevelFlags[$i] -ne 0)
                {
                    Write-Host -ForegroundColor Yellow -NoNewline "  "
                }
                else
                {
                    Write-Host -ForegroundColor Yellow -NoNewline "│ "
                }
            }
            if($level -ne 0)
            {
                if($lastGroupOfTheLevel)
                {
                    Write-Host -ForegroundColor Yellow -NoNewline "└─"
                }
                else
                {
                    Write-Host -ForegroundColor Yellow -NoNewline "├─"
                }
            }
            Write-Host -ForegroundColor Yellow $group.Name
        }
        $groupsVisitedBeforeThisOne.Add($group.distinguishedName,$null)
        $global:numberOfRecursiveGroupMemberships ++
        $groupMemberShipCount = $group.memberOf.Count
        if ($groupMemberShipCount -gt 0)
        {
            $maxMemberGroupLevel = 0
            $count = 0
            foreach($groupDN in $group.memberOf)
            {
                $count++
                $lastGroupOfThisLevel = $false
                if($count -eq $groupMemberShipCount){$lastGroupOfThisLevel = $true; $lastGroupAtALevelFlags[$level] = 1}
                if(-not $groupsVisitedBeforeThisOne.Contains($groupDN)) #prevent cyclic dependancies
                {
                    $memberGroupLevel = Get-GroupNesting -Identity $groupDN -Level $($level+1) -GroupsVisitedBeforeThisOne $groupsVisitedBeforeThisOne -lastGroupOfTheLevel $lastGroupOfThisLevel
                    if ($memberGroupLevel -gt $maxMemberGroupLevel){$maxMemberGroupLevel = $memberGroupLevel}
                }
            }
            $level = $maxMemberGroupLevel
        }
        else #we've reached the top level group, return it's height
        {
            return $level
        }
        return $level
    }
}
$global:numberOfRecursiveGroupMemberships = 0
$groupObj = $null
$groupObj = Get-ADGroup -Identity $groupIdentity
if($groupObj)
{
    [int]$maxNestingLevel = Get-GroupNesting -Identity $groupIdentity -Level 0 -GroupsVisitedBeforeThisOne @{} -lastGroupOfTheLevel $false
    Add-Member -InputObject $groupObj -MemberType NoteProperty  -Name MaxNestingLevel -Value $maxNestingLevel -Force
    Add-Member -InputObject $groupObj -MemberType NoteProperty  -Name NestedGroupMembershipCount -Value $($global:numberOfRecursiveGroupMemberships - 1) -Force
    $groupObj
}

Hello folks! Here are few Active Directory Powershell script snippets that you will find useful while writing scripts. They deal with fetching sites, subnets and servers. Most of the snippets are simple and self-explanatory and can be simply copy-pasted in your existing script.

## Get a specified Active Directory Site.    
$siteName =  "Default-First-Site-Name"
$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
$siteDN = "CN=" + $siteName + "," + $siteContainerDN
Get-ADObject -Identity $siteDN -properties *


##  Get all Active Directory Sites (and fetch relevant properties)

$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
Get-ADObject -SearchBase $siteContainerDN -filter { objectClass -eq "site" } -properties "siteObjectBL", "location", "description" | select Name, Location, Description


##  Get all Servers in a specified Active Directory site.

$siteName =   "Default-First-Site-Name"
$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
$serverContainerDN = "CN=Servers,CN=" + $siteName + "," + $siteContainerDN
Get-ADObject -SearchBase $serverContainerDN -SearchScope OneLevel -filter { objectClass -eq "Server" } -Properties "DNSHostName", "Description" | Select Name, DNSHostName, Description


##  Get all Subnets in a specified Active Directory site.

$siteName =  "Default-First-Site-Name"
$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
$siteDN = "CN=" + $siteName + "," + $siteContainerDN
$siteObj = Get-ADObject -Identity $siteDN -properties "siteObjectBL", "description", "location" 
foreach ($subnetDN in $siteObj.siteObjectBL) {
    Get-ADObject -Identity $subnetDN -properties "siteObject", "description", "location" 
}


##  Print a list of site and their subnets

$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
$siteObjs = Get-ADObject -SearchBase $siteContainerDN -filter { objectClass -eq "site" } -properties "siteObjectBL", name
foreach ($siteObj in $siteObjs) {
    $subnetArray = New-Object -Type string[] -ArgumentList $siteObj.siteObjectBL.Count
    $i = 0
    foreach ($subnetDN in $siteObj.siteObjectBL) {
        $subnetName = $subnetDN.SubString(3, $subnetDN.IndexOf(",CN=Subnets,CN=Sites,") - 3)
        $subnetArray[$i] = $subnetName
        $i++
    }
    $siteSubnetObj = New-Object PSCustomObject | Select SiteName, Subnets
    $siteSubnetObj.SiteName = $siteObj.Name
    $siteSubnetObj.Subnets = $subnetArray
    $siteSubnetObj
}


## Print the site name of a Domain Controller

$dcName = (Get-ADRootDSE).DNSHostName   ## Gets the name of DC to which this cmdlet is connected
(Get-ADDomainController $dcName).Site

Hope you find them useful.

Cheers,

Swami

--

Swaminathan Pattabiraman

Developer – Active Directory Powershell Team

In an earlier post “Active Directory Powershell to manage sites – Part 1 (New-XADSite)” Jairo explained in detail about how to create a Site in Active Directory using AD Powershell. In today’s post I am going to discuss about how to create Subnets using AD Powershell.

Before going into details of creating a subnet object, first let us understand what is a Site and Subnet.

The following definition is from: http://technet.microsoft.com/en-us/library/cc782048(WS.10).aspx

In Active Directory, a site is a set of computers well-connected by a high-speed network, such as a local area network (LAN). All computers within the same site typically reside in the same building, or on the same campus network. A single site consists of one or more Internet Protocol (IP) subnets. Subnets are subdivisions of an IP network, with each subnet possessing its own unique network address. A subnet object in AD DS groups neighboring computers in much the same way that postal codes group neighboring postal addresses.

Sites and subnets are represented in Active Directory by site and subnet objects. Each site object is associated with one or more subnet objects. By associating a site with one or more subnets, you assign a set of IP addresses to the site.

NOTE: The term "subnet" in AD DS does not have the strict networking definition of the set of all addresses behind a single router. The only requirement for an AD DS subnet is that the address prefix conforms to the IP version 4 (IPv4) or IP version 6 (IPv6) format.

A subnet object (objectClass=subnet) in AD DS stores the prefix information in its name and the site information in attribute siteObject. It should be created under “CN=Subnets,CN=Sites,<Configuration-NC>”. For documentation on “how to determine the prefix of a subnet” bing for the phrase “Entering Address Prefixes”.

I have written a function below called New-XADSubnet that creates a new subnet. It accepts the subnet-prefix, site name, description and location of the subnet as input. The function also does some validation to provide a reliable experience, for example: it validates whether the specified subnet prefix length is correct or not. If a prefix length is not specified then this function will auto-generate the prefix length based on the number of trailing zero bits found in the prefix-ip-address.

NOTE: The script below uses Test-XADObject function published in a previous post.

   1:  #
   2:  # Advanced Function to create a new subnet.
   3:  #
   4:  function New-XADSubnet() {
   5:     [CmdletBinding(ConfirmImpact="Low")]
   6:     Param (
   7:        [Parameter(Mandatory=$true,
   8:                   Position=0,
   9:                   ValueFromPipeline=$true,
  10:                   HelpMessage="Prefix of the subnet to be created. This should be a combination of IP Address followed by / and Prefix length. IPv4 example: 157.54.208.0/20  IPv6 example: 3FFE:FFFF:0:C000::/64 . NOTE: If the prefix length is not specified then this cmdlet will auto-generate the prefix length based on the number of trailing zero bits. For example: If supplied Prefix is 157.54.208.0 then the subnet created is 157.54.208.0/20. Another example: If supplied Prefix is 3FFE:FFFF:0:C000:: then the subnet created is 3FFE:FFFF:0:C000::/34"
  11:                  )]
  12:        [String] $Prefix,
  13:        [Parameter(Mandatory=$false,
  14:                   Position=1,
  15:                   ValueFromPipeline=$false,
  16:                   HelpMessage="Site to which the subnet will be applied. Accepts Site name, Guid, DN or ADObject representing the site"
  17:                  )]
  18:        [Object] $Site,
  19:        [Parameter(Mandatory=$false,
  20:                   ValueFromPipeline=$false,
  21:                   HelpMessage="Description"
  22:                  )]
  23:        [String] $Description,
  24:        [Parameter(Mandatory=$false,
  25:                   ValueFromPipeline=$false,
  26:                   HelpMessage="Location"
  27:                  )]
  28:        [String] $Location
  29:     )
  30:     PROCESS {
  31:   
  32:        if ([String]::IsNullOrEmpty($Prefix)) {
  33:           throw New-Object System.Management.Automation.PSArgumentException("Prefix name cannot be an empty string, please try again.")
  34:        }
  35:        
  36:        $newSubnetName = $Prefix
  37:        
  38:        if ($Prefix.Contains("/")) {
  39:            $subnetIPAddressStr,$prefixLengthStr = $newSubnetName.Split("/")
  40:            $subnetIPAddress = [System.Net.IPAddress]::Parse($subnetIPAddressStr)
  41:            $specifiedPrefixLength = [int]::Parse($prefixLengthStr)
  42:            
  43:            $ipAddressPrefixLength = GetIPAddressPrefixLength $subnetIPAddress
  44:            if ($ipAddressPrefixLength -gt $specifiedPrefixLength) {
  45:                throw New-Object System.Management.Automation.PSArgumentException("The subnet prefix length you specified is incorrect. Please check the prefix and try again.")
  46:            }
  47:            
  48:        } else {
  49:            $subnetIPAddress = [System.Net.IPAddress]::Parse($newSubnetName)
  50:            $prefixLength = GetIPAddressPrefixLength $subnetIPAddress
  51:            $newSubnetName = $newSubnetName + "/" + $prefixLength
  52:        }
  53:   
  54:        # Get the configuration partition DN, the sites container and build the new site DN
  55:        $configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
  56:        $subnetContainerDN = ("CN=Subnets,CN=Sites," + $configNCDN)
  57:        $newSubnetDN = ("CN=" + $newSubnetName +"," + $subnetContainerDN)
  58:        $siteDN = $null
  59:        if ($Site -ne $null) {
  60:            $siteDN = (Get-XADSite $Site).DistinguishedName
  61:        }
  62:   
  63:        # Verify if the subnet already exists
  64:        $subnetExists = Test-XADObject -Identity $newSubnetDN
  65:        if ($subnetExists) {
  66:           throw New-Object System.Management.Automation.PSArgumentException("Subnet already exists. Please check the name and try again.")
  67:        }
  68:        
  69:        [Hashtable] $ht = new-object -type hashtable
  70:        if ($siteDN -ne $null) {
  71:            $ht.Add("siteObject", $siteDN)
  72:        }
  73:        if (-not [String]::IsNullOrEmpty($Description)) {
  74:            $ht.Add("description", $Description)
  75:        }
  76:        if (-not [String]::IsNullOrEmpty($Location)) {
  77:            $ht.Add("location", $Location)
  78:        }
  79:   
  80:   
  81:        # Create subnet object 
  82:        if ($ht.Count -eq 0) {
  83:            New-ADObject -Name $newSubnetName -Path $subnetContainerDN -Type subnet 
  84:        } else {
  85:            New-ADObject -Name $newSubnetName -Path $subnetContainerDN -Type subnet -OtherAttributes $ht
  86:        }
  87:   
  88:        # Fetch the subnet object
  89:        Get-ADObject $newSubnetDN -properties "siteObject", "description", "location"
  90:   
  91:      }
  92:  }
  93:   
  94:   
  95:   
  96:  #
  97:  # Internal utility function
  98:  # This function returns the number of trailing zeroes in the input byte
  99:  #
 100:  function GetNumberOfTrailingZeroes {
 101:  Param ([byte] $x)
 102:  $numOfTrailingZeroes = 0;
 103:  if ( $x -eq 0) {
 104:     return 8
 105:  }
 106:  if ( $x % 2 -eq 0) {
 107:     $numOfTrailingZeroes ++;
 108:     $numOfTrailingZeroes +=  GetNumberOfTrailingZeroes($x/2);
 109:  }
 110:  return $numOfTrailingZeroes
 111:  }
 112:   
 113:   
 114:   
 115:   
 116:  #
 117:  # Internal utility function
 118:  # This function returns the number of non-zero bits in an ip-address
 119:  #
 120:  function GetIPAddressPrefixLength {
 121:  Param ([System.Net.IPAddress] $ipAddress)
 122:  $byteArray = $ipAddress.GetAddressBytes()
 123:  $numOfTrailingZeroes = 0;
 124:  for ($i = $byteArray.Length - 1; $i -ge 0; $i--) {
 125:      $numOfZeroesInByte = GetNumberOfTrailingZeroes($byteArray[$i]);
 126:      if ($numOfZeroesInByte -eq 0) {
 127:         break;
 128:      }
 129:      $numOfTrailingZeroes += $numOfZeroesInByte;
 130:  }
 131:  (($byteArray.Length * 8) - $numOfTrailingZeroes)
 132:  }

Sample usage in my test environment:

PS AD:\>
PS AD:\> New-XADSubnet "10.10.0.0/16"  -Location "Redmond,WA" -Description "Redmond Subnet"


Description       : Redmond Subnet
DistinguishedName : CN=10.10.0.0/16,CN=Subnets,CN=Sites,CN=Configuration,DC=dsw
                    amipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
location          : Redmond,WA
Name              : 10.10.0.0/16
ObjectClass       : subnet
ObjectGUID        : e4c924ff-ee39-47e3-8b92-71a8600188af

Cheers,

Swami

--

Swaminathan Pattabiraman

Developer – Active Directory Powershell Team

This is a short blog post to highlight a few module extensions that we have been demoing at conferences this year. They show some of the new features in our PowerShell module as well as a few examples extending them. In this update, there are three modules:

  1. DemoScript – This is module wrapping Jeffrey Snover’s start-demo script. It has several updates from various sources inside and outside of Microsoft. This would make a pretty good start to a set of cmdlets to create and run demo scripts, test scripts, etc. Included in this, is a file called DemoScripts.txt, which contains some general and some interesting uses of the cmdlets found in our module.
  2. DCHealth – This module has cmdlets to test the health of Domain Controllers. It is built in an incremental fashion, showing how to create a module piece by piece. Start with Send-Ping.
  3. TrustManagement – There are only two cmdlets here, but they make managing trusts a much more pleasant experience.

James McColl
Program manager – Directory Services

Many of the readers have expressed interest in having the ability to manage their down level (running Win 2003/2008) DCs using ADPowershell. The only missing piece was the availability of the Active Directory Web Service (ADWS overview here). Well, ADWS beta for down level DCs is here. Using this beta download, you can manage your existing Active Directory forests using ADPowershell. Below are the steps to participate in this beta program:

  1. Visit http://connect.microsoft.com and enter the invitation ID ADWS-FDBT-CVJK on the home page.
  2. Sign in using your live/hotmail ID
  3. Active Directory Management Gateway Service download details and instructions will be available to you on MS Connect site - http://connect.microsoft.com/ADWS/
Note that there is a feedback section on that page that you are encouraged to use :) Please install the beta and provide your feedback.

Thanks,

- Ali

---

Mudassir Ali
Software Development Engineer in Test - Active Directory Powershell Team

 

Accidental deletions can happen in Active Directory in many ways. An administrator can end up deleting a single user object unintentionally or fat finger an entire tree of OUs. A rogue script could end up deleting multiple objects at multiple locations in the AD hierarchy. In Active Directory, when deleted objects are moved to the deleted objects container, they lose their hierarchy (because the DN is mangled to ensure uniqueness).
Once accidental deletions happen it's important to analyze the impact carefully, before devising a restore strategy. Things which are required to analyze the accidental deletion are

  1. What got deleted?
  2. When did the deletion happen?
  3. Where did the deletion happen? (Which Domain Controller)
Once this analysis phase is done, and the objects to restore are identified … you may choose your favorite restore method.
The traditional recovery consist of hoping that you have a recent system state backup and performing an auth restore of the deleted objects … If you're on Windows Server 2008 R2 forest functional level … the Recycle Bin feature of Windows Server 2008 R2 AD has made restoring a single object very simple. Once the new feature is turned on, all attributes are now preserved on an object, when it's deleted - and once the object is restored using the Restore-ADObject cmdlet - the object is restored in its entirety. So, if a User is accidently deleted and restored using Restore-ADObject - all forward links and backlinks to that user object are restored too - Group Memberships, Manager, ManagedObjects, DirectReports etc. along with all the non-linked attributes.

This blog post focuses on the analysis phase: Identifying what to restore.
The need is to find ways in which you can analyze the accidental deletion disaster. I will take the following three categorization approaches:
  1. If an accidental deletion deletes multiple objects, usually, all of those objects are deleted within a short span of time. It may be an amateur consultant ending up deleting ad objects while installing his application, or a rogue script running and deleting objects here and there, or an admin fat fingering a tree of OUs or even a malicious action - most disastrous deletions happen in a span of seconds/minutes.
  2. If an accidental deletion deletes multiple objects, usually, all of those objects are deleted on the same domain controller.
  3. AD data is hierarchical, GUI is hierarchical - non-single object accidental deletions also usually delete a hierarchy.

In the absence of a good GUI for inspecting the recycle bin contents, here's a script that can inspect the contents of your deleted objects container, and give you info on the 3 categorizations above.
The script reads the deleted objects container, to construct a hierarchy of all deleted containers (organizational units and containers). Further it reads the replication metadata of these containers to ascertain when and where the object was deleted.
See in the figure below. 5 trees of OUs have been deleted. 4 of those trees were under the domain NC and one was under the users container. For each deleted OU, the script shows accurate when deleted (not the value of whenChanged attribute) and where the deletion happened.

Normal

I can now Pipeline the output of this command to Where-Object and filter out the view per criteria of my choosing - per where deleted and/or when deleted

Where Filter 

 I can also sort the output of the command on WhenDeleted - it does mess up the tree view though …

Sort WhenDeleted 

Tree Restoration
Restoring deleted trees isn't as easy as single object restores. When objects are deleted they are moved to a flat hierarchy in the Deleted Objects Container. The RDN of the object is mangled with its GUID and so a deleted tree loses its hierarchy. The Restore-ADTree script makes it easier to restore an entire tree. You can pass to the script the DN or the GUID of the root of the tree and it would restore all objects under that root recursively. You can use Restore-ADTree on deleted OUs if *all* the deleted children of that OU (presently residing in the deleted objects container) should be restored.

When used with the -LeafObjectInfo parameter, this script (Get-ADDeletedContainers) also shows info about the subordinates of each container. The 'subordinates' field shows the number of deleted objects which are immediate subordinates of the deleted OU, and the maximum gap of time between the when deleted time of the subordinates. So, if an fresh OU had 5 user objects, one was deleted 3 days back (a valid deletion) and today the OU and 4 of its children got deleted accidently - the subordinates field of the OU would show 5 (3.00:00:00).

LeafObjectsInfo 

LeafObjectsInfo option will take longer to execute. This option will help you analyze whether or not to use the Restore-ADTree script on deleted OUs. If the subordinate time of an OU is more than a few minutes - you should analyze if all the deleted subordinates are candidates for restore or not.

So, before using Restore-ADObject or Restore-ADTree or even if you are planning an auth restore (if recycle bin feature isn't available) - you can use this script to better view and analyze the contents of your deleted objects container better.

Usage: Map an AD PowerShell drive to your AD or cd into the default AD: drive and use the script per the following samples. RecycleBin feature needn't be turned on for this script to work

  1. PS CONTOSO:\> Get-ADDeletedContainers.ps1 | ft DisplayName,WhenDeleted,WhereDeleted
  2. PS CONTOSO:\> Get-ADDeletedContainers.ps1 | ft DisplayName,ObjectClass,ObjectGUID,WhenDeleted,WhereDeleted
  3. PS CONTOSO:\> Get-ADDeletedContainers.ps1 -LeafObjectsInfo | ft DisplayName,Subordinates
  4. PS CONTOSO:\> Get-ADDeletedContainers.ps1 | where {$_.WhenDeleted -gt [DateTime]::Parse("5/25/2009 9:40:00 AM")}|ft DisplayName,WhenDeleted,WhereDeleted
  5. PS CONTOSO:\> Get-ADDeletedContainers.ps1 | where {$_.WhereDeleted -like "CONTOSO-DC2*"}|ft DisplayName,WhenDeleted,WhereDeleted
  6. PS CONTOSO:\> Get-ADDeletedContainers.ps1 | where {$_.WhenDeleted -gt [DateTime]::Parse("5/25/2009") -and $_.WhereDeleted -like "CONTOSO-DC2*"}|ft DisplayName,WhenDeleted,WhereDeleted

ps: Protect your OUs against accidental deletion to avoid future accidental deletions. To learn how - search for the phrase "Protect an Organizational Unit from Accidental Deletion"

enjoy!
Dushyant Gill
Program Manager, Directory Services

In my previous post about Advanced filters I showed how to use Powershell variables to represent values inside filters. Example:

PS D:\> $JohnSmith = Get-ADUser JohnSmith

PS D:\> Get-ADUser -Filter { manager -eq $JohnSmith.DistinguishedName }

## Gets all the user accounts whose manager is JohnSmith

What I didn’t tell you was Powershell variables could also be used to represent property names. Example:

PS D:\> $SamAccountNameProperty = "SamAccountName"

PS D:\> Get-ADUser -Filter { $SamAccountNameProperty -eq "JohnSmith" }
## Gets a user whose samAccountName is JohnSmith

You may ask: what is so interesting about this feature?

Now imagine if you had a Powershell variable that has all the LDAP attribute names and AD Powershell object property names exposed as .NET properties, then you could use such a variable inside this filter. When you are interactively typing a filter string in a Powershell console, you would get tab completion support for these properties. What this means is you do not have to memorize the spelling of your favorite LDAP attributes anymore!

Say, for example, if you wrote a simple .NET class like the following:

public class ADObjectProp

{

 public string ObjectGuid { get { return "objectGuid"; } }

 public string Name { get { return "name"; } }

 public string DistinguishedName { get { return "DistinguishedName"; } }

 public string ObjectClass { get { return "ObjectClass"; } }

}

Then you would be able to use a variable of type ADObjectProp inside the advanced filter. I call such variables: “query variables”.

PS D:\> $queryADObject = new-object –type ADObjectProp

PS D:\> Get-ADObject -Filter { $queryADObject.Name -eq "JohnSmith" }

In the above example, if you hit <TAB> after “$queryAODbject.” then Powershell would cycle through all the properties. You can also use partial property name and names with wild card and Powershell would cycle through the best matches.

This is a great feature, especially for people who find it difficult to memorize all the LDAP attribute names and/or learn new property names exposed in AD Powershell object model. Now, the question is how to best create such .NET classes that would contain all the LDAP attributes defined in your schema? For this, we can leverage the Get-PossibleLdapAttributes function available in Active Directory Extensions and the Add-Type cmdlet available in core Powershell.

In Powershell you can dynamically create your own .NET class using the Add-Type cmdlet. Here is the Powershell command to create the ADObjectProp class (shown above):

PS D:\> Add-Type -Name ADObjectProp -NameSpace "Microsoft.ActiveDirectoryExtensions" -MemberDefinition ' public string ObjectGuid { get { return "objectGuid"; } } public string Name { get { return "name"; } } public string DistinguishedName { get { return "DistinguishedName"; } } public string ObjectClass { get { return "ObjectClass"; } }' -PassThru

And you can initialize an object of the above class by typing:

PS D:\> $xADObject = new-object –type Microsoft.ActiveDirectoryExtensions .ADObjectProp

I have modified the ActiveDirectoryExtensions script to create a bunch of query variables. The script adds all the extended properties for the given object type and then gets a list of possible LDAP attributes for the corresponding object class and populates the query variables.

To get a list of query variables created type:

PS C:\> import-module ActiveDirectory

PS C:\> C:\ActiveDirectoryExtension.ps1
PS D:\> dir variable:xad*

Name Value

---- -----

xADObject Microsoft.ActiveDirectoryExtensions.ADObjectProp

xADPrincipal Microsoft.ActiveDirectoryExtensions.ADPrincipalProp

..

Here is a table that lists all the query variables created by the ActiveDirectoryExtensions script and a short description for each one of them:

$xADObject

Contains all extended properties of ADObject and LDAP attributes for top

$xADPrincipal

Contains all extended properties of ADPrincipal and LDAP attributes for top

$xADAccount

Contains all extended properties of ADAccount and LDAP attributes for top

$xADGroup

Contains all extended properties of ADGroup and LDAP attributes for group

$xADUser

Contains all extended properties of ADUser and LDAP attributes for user

$xADComputer

Contains all extended properties of ADComputer and LDAP attributes for computer

$xADOrganizationalUnit

Contains all extended properties of ADOrganizationalUnit and LDAP attributes for organizationalUnit

$xADFineGrainedPasswordPolicy

Contains all extended properties of ADFineGrainedPasswordPolicy and LDAP attributes for msDS-PasswordSettings

$xADServiceAccount

Contains all extended properties of ADServiceAccount and LDAP attributes for msDS-ManagedServiceAccount

$xADDomainController

Contains all extended properties of ADDomainController

Since the script now reads the schema when you load it, it will be a little slower.

Here are examples of how to use the query variables in your Powershell console:

Get-ADObject -Filter { $xADUser.SamAccountName -eq "Administrator" }

## You can hit $xADUser.Sam<TAB> to tab-complete into $xADUser.SamAccountName

## Or $xADUser.*Account*<TAB> and cycle through the values to find SamAccountName

Get-ADObject -Filter { $xADUser.displayName -like "*Policy*" }

Get-ADObject -Filter { $xADObject.Name -eq "Administrator" }

There is one small caveat when using query variables to provide tab completion:

- Many LDAP attribute names defined in AD schema have a hyphen ‘-’ in them. Since .NET property names cannot have a hyphen, I have modified the script to convert hypens ‘-’into underscores ‘_’. Thus, for writing a filter using attribute “msDS-LastKnownRDN”, you would have to use $xADUser.msDS_LastKnownRDN.

Get-ADObject -filter { $xADObject.msDS_LastKnownRDN -like "*" } -IncludeDeletedObjects

Enjoy!

Swami

--

Swaminathan Pattabiraman

Developer – Active Directory Powershell Team

In this opportunity I am going to provide a solution to manage sites in your Active Directory (AD) forest by extending Active Directory PowerShell by implementing functions that allow creation, retrieval, update (moving to a site link, renaming) and deletion of sites.

First of all we need to understand what an AD site is made of. We can say that an AD site is represented by three directory objects and a reference in an attribute in a different object (i.e. the Site Link object). In detail an AD site is a compound of:

1. The site object (objectClass=site) which is an object under the sites object in the configuration naming context.

2. The NTDS site settings object (objectClass=nTDSSiteSettings) a child object under the site object

3. The servers containers object (objectClass=serversContainer) a child object under the site object

4. A value in the multi-valued attribute siteList in the site link object.

In the first place we need to know the sites container distinguished name (DN). This object has a relative distinguished name (RDN) with value Sites RDN under the configuration naming context. The following shows how to build the DN string for the sites container:

      $configNCDN = (Get-ADRootDSE).ConfigurationNamingContext

      $sitesContainerDN = ("CN=Sites," + $configNCDN)

The new site object will be an object under the sites container object:

      $newSiteDN = ("CN=" + $newSiteName +"," + $sitesContainerDN)

We can use the New-ADObject cmdlet to create the Site, NTDS Site Settings and Servers container objects under the Sites container object:

      New-ADObject -Name $newSiteName -Path $sitesContainerDN -Type site

      New-ADObject -Name "NTDS Site Settings" -Path $newSiteDN -Type nTDSSiteSettings

      New-ADObject -Name "Servers" -Path $newSiteDN -Type serversContainer

Now to add the site to the site link we will need a reference to the site link object. The site link objects are located under the given inter-site transport object (IP or SMTP). For example for DEFAULTIPSITELINK the site link container location will be:

      $siteLinkContainer = ("CN=DEFAULTIPSITELINK,CN=IP,CN=Inter-Site Transports,CN=Sites," + $configNCDN)

The last step is to add the site to the site link by including the site name in the siteList attribute in the respective site link object. To modify the siteList attribute in the site link object we will need to get the site link object first with the siteList attribute using using the Get-ADObject cmdlet to create an instance and then modify it using the Set-ADObject cmdlet:

      $siteLink = Get-ADObject $siteLinkContainer -Properties siteList

      $siteLink.siteList.Add($newSiteDN)

      Set-ADObject -Instance $siteLink

And that will be it.

I have created the function below called New-XADSite which receives the site name and the site link name (IP based, you can do a small modification to add SMTP based site links). Also it contains special cases validations which provide you a more reliable experience if you want to use the function in your AD forests. In future posts I will show you how to extend AD PowerShell to include functions to get the site names of the forest, moving a site to a different site link, renaming a site and removing a site in the forest. Notice that the script uses the function Test-XADObject which is implemented in a previous posting.

   1:  function New-XADSite() {
   2:     [CmdletBinding(ConfirmImpact="Low")]
   3:     Param (
   4:        [Parameter(Mandatory=$true,
   5:                   Position=0,
   6:                   ValueFromPipeline=$true,
   7:                   HelpMessage="Identity of the site to be created. Name is the only supported identity for this version of the script."
   8:                  )]
   9:        [Object] $Identity,
  10:        [Parameter(Mandatory=$false,
  11:                   Position=1,
  12:                   ValueFromPipeline=$false,
  13:                   HelpMessage="Site link name to which the site will belong."
  14:                  )]
  15:        [Object] $SiteLinkName
  16:     )
  17:   
  18:     BEGIN {
  19:   
  20:     }
  21:   
  22:     PROCESS {
  23:   
  24:        # In this version of the script the only identity supported is the site name
  25:        # Replace the line below to support other identities
  26:        $newSiteName = $Identity
  27:   
  28:        if ([String]::IsNullOrEmpty($newSiteName)) {
  29:           throw New-Object System.Management.Automation.PSArgumentException("Site name cannot be an empty string, please try again.")
  30:        }
  31:   
  32:        if ($SiteLinkName -eq $null) {
  33:           $SiteLinkName = "DEFAULTIPSITELINK"
  34:        }
  35:   
  36:        # Get the configuration partition DN, the sites container and build the new site DN
  37:        $configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
  38:        $sitesContainerDN = ("CN=Sites," + $configNCDN)
  39:        $newSiteDN = ("CN=" + $newSiteName +"," + $sitesContainerDN)
  40:   
  41:        $siteLinkContainerDN = ("CN=" + $SiteLinkName + ",CN=IP,CN=Inter-Site Transports,CN=Sites," + $configNCDN)
  42:        # Verify if the site link exists
  43:        $siteLinkExists = Test-XADObject -Identity $siteLinkContainerDN
  44:        if ($siteLinkExists -eq $false) {
  45:           throw New-Object System.Management.Automation.RuntimeException("The site link you provided does not exist. Please check the name and try again.")
  46:        }
  47:   
  48:        # Verify if the site already exists
  49:        $siteExists = Test-XADObject -Identity $newSiteDN
  50:        if ($siteExists) {
  51:           throw New-Object System.Management.Automation.RuntimeException("Site already exists. Please check the name and try again.")
  52:        }
  53:        else {
  54:           $siteObjectCreated = $false
  55:   
  56:           trap [Exception] {
  57:              # Output the error to the pipeline
  58:              Write-Host $_
  59:   
  60:              # Remove any objects created for consistency
  61:              if ($siteObjectCreated -eq $true) {
  62:                 Remove-ADObject -Identity $newSiteDN -Recursive -Confirm:$false
  63:              }
  64:   
  65:              # Throw a new exception with the trapped exception as inner exception
  66:              throw New-Object System.Management.Automation.RuntimeException("An exception occurred when creating the site. See inner exceptions for more details.", $_.Exception)
  67:           }
  68:   
  69:           # Create Site, NTDS Site Settings and Server objects
  70:           New-ADObject -Name $newSiteName -Path $sitesContainerDN -Type site
  71:           $siteObjectCreated = $true
  72:           New-ADObject -Name "NTDS Site Settings" -Path $newSiteDN -Type nTDSSiteSettings
  73:           New-ADObject -Name "Servers" -Path $newSiteDN -Type serversContainer
  74:   
  75:           $siteLink = Get-ADObject $siteLinkContainerDN -Properties siteList
  76:           $numberOfSitesInSiteLink = $siteLink.siteList.Add($newSiteDN)
  77:           Set-ADObject -Instance $siteLink
  78:   
  79:           # Return the directory objects that make the AD site
  80:           Get-ADObject -Identity $newSiteDN
  81:           Get-ADObject -Identity ("CN=NTDS Site Settings," + $newSiteDN)
  82:           Get-ADObject -Identity ("CN=Servers," + $newSiteDN)
  83:        }
  84:   
  85:      }
  86:   
  87:      END {
  88:   
  89:      }
  90:  }

Enjoy,

Jairo Cadena

Active Directory

More Posts Next page »
 
Page view tracker