Tag Archives: Active Directory Friday

Active Directory Friday: Find groups with no members

Occasionally groups may become obsolete or are never populated with members. It can be interesting to find out how many groups are in your organization that have no members, as action can be taken on it based on the output.

Overview of articles in this series
Active Directory Friday: Find groups with no members
Active Directory Friday: Principal group membership
Active Directory Friday: User account group membership

Because of the nature of how group membership is defined this article will be the first in a series of three. In this article I will show how group membership can be determined using an LDAP queries. The next article in this series will go into principal group membership and its implications and the final article will go into constructed attributes and how to work with constructed attributes, specifically the memberof attribute.

In this article I will give a a number of examples that can be used to determine which groups are empty. Using Get-ADGroup the following command can be executed to retrieve memberless groups:

Get-ADGroup -LDAPFilter '(!(member=*))'

Get-ADGroupNotMemberAnything

Alternatively the DirectoryServices.DirectorySearcher object can be used to achieve a similar result:

(New-Object DirectoryServices.DirectorySearcher -Property @{
 Filter = '(&(objectClass=group)(!(member=*)))'
 PageSize = 100
}).FindAll()

The [adsisearcher] type accelerator is another interesting alternative for this purpose, here is an example:

([adsisearcher]'(&(objectClass=group)(!(member=*)))').FindAll()

The problem with the above examples however, is that some groups will show up as being empty, for example the Domain Users group. Next week I will go into Principal group membership, what this is and how to query for this and by doing so generate more accurate results in regards to group membership.

For more information about the topics discussed in this article, please have a look at the following resources:

Active Directory Friday: Find groups with no members
Get-ADGroup
JaapBrasser.com – Active Directory Friday
Free ebook – Active Directory Friday All Articles
DirectoryServices.DirectorySearcher

Active Directory Friday: Use MSAD_ReplNeighbor WMI class to replicate Active Directory

This article will provide details of initiating replication of using WMI in PowerShell. There are a lot of possible methods of starting replication such as using the ‘Active Directory Sites and Services’ console, using the Active Directory PowerShell module and the Sync-ADObject cmdlet.

We will use the MSAD_ReplNeighbor WMI Class for this purpose and specifically the SyncNamingContext method to initiate replication. This class is available in the root\MicrosoftActiveDirectory path on your domain controllers. The commands in this article should be executed on a domain controller. I have made the choice the use the Get-WmiObject in this article as the SyncNamingContext method is not available when using the Get-CimInstance cmdlet, aside from that method all commands can be executed using the Get-CimInstance

In the first code example I will access the MSAD_ReplNeighbor class and retrieve all information. In this article I will use a technique called splatting to input the parameters into the cmdlet:

1
2
3
4
5
$WmiSplat = @{
    Class = 'MSAD_ReplNeighbor'
    Namespace = 'root\MicrosoftActiveDirectory'
}
Get-WmiObject @WmiSplat | Get-Member

The output from this command will display all the available properties and methods, including the SyncNamingContext method that we will use in this article. To view the available partitions for replication, we can use the Select-Object cmdlet to only select the Unique NamingContextDN properties of the returned objects:

1
2
3
4
5
$WmiSplat = @{
    Class = 'MSAD_ReplNeighbor'
    Namespace = 'root\MicrosoftActiveDirectory'
}
Get-WmiObject @WmiSplat | Select-Object -Property NamingContextDN -Unique

Replicate-UniqueNamingDN

Based on this output we determine that the ‘DC=jaapbrasser,DC=com’ partition is the partition we are interested in. In the following code we will filter out only that partion and display a number of selected properties:

1
2
3
4
5
6
$WmiSplat = @{
    Class = 'MSAD_ReplNeighbor'
    Namespace = 'root\MicrosoftActiveDirectory'
    Filter = "NamingContextDN = 'DC=jaapbrasser,DC=com'"
}
Get-WmiObject @WmiSplat | Select-Object -Property SourceDsaCN,SourceDsaSite,TimeOfLastSyncSuccess,Replicaflags

Replicate-ViewReplication

The first thing that you’ll notice is that the two properties Replicaflags and TimeOfLastSyncSuccess are hard to read, in the following two examples two different approaches are shown to properly convert the TimeOfLastSyncSuccess property to a more human readable output:

1
2
3
4
5
6
7
8
9
10
$WmiSplat = @{
    Class = 'MSAD_ReplNeighbor'
    Namespace = 'root\MicrosoftActiveDirectory'
    Filter = "NamingContextDN = 'DC=jaapbrasser,DC=com'"
}
$DateTimeOfLastSyncSuccess = @{
    Name = 'DateTimeOfLastSyncSuccess'
    Expression = {[Management.ManagementDateTimeConverter]::ToDateTime($_.TimeOfLastSyncSuccess)}
}
Get-WmiObject @WmiSplat | Select-Object -Property SourceDsaCN,SourceDsaSite,$DateTimeOfLastSyncSuccess,Replicaflags

Alternatively we can use the Get-CimInstance cmdlet which already provides the converted object:

1
2
3
4
5
6
$CimSplat = @{
    Class = 'MSAD_ReplNeighbor'
    Namespace = 'root\MicrosoftActiveDirectory'
    Filter = "NamingContextDN = 'DC=jaapbrasser,DC=com'"
}
Get-CimInstance @CimSplat | Select-Object -Property SourceDsaCN,SourceDsaSite,TimeOfLastSyncSuccess,Replicaflags

Replicate-CimInstance

In order to be able to convert the Replicaflags attribute the value in the property should be converted using the table found in MSAD_ReplNeighbor class article on MSDN. Here is a portion of the the table available on MSDN:

Value

DS_REPL_NBR_WRITEABLE
16 (0x10)

DS_REPL_NBR_SYNC_ON_STARTUP
32 (0x20)

DS_REPL_NBR_DO_SCHEDULED_SYNCS
64 (0x40)

DS_REPL_NBR_USE_ASYNC_INTERSITE_TRANSPORT
128 (0x80)

DS_REPL_NBR_TWO_WAY_SYNC
512 (0x200)

DS_REPL_NBR_RETURN_OBJECT_PARENTS
2048 (0x800)

DS_REPL_NBR_FULL_SYNC_IN_PROGRESS
65536 (0x10000)

DS_REPL_NBR_FULL_SYNC_NEXT_PACKET
131072 (0x20000)

DS_REPL_NBR_NEVER_SYNCED
2097152 (0x200000)

DS_REPL_NBR_PREEMPTED
16777216 (0x1000000)

DS_REPL_NBR_IGNORE_CHANGE_NOTIFICATIONS
67108864 (0x4000000)

DS_REPL_NBR_DISABLE_SCHEDULED_SYNC
134217728 (0x8000000)

DS_REPL_NBR_COMPRESS_CHANGES
268435456 (0x10000000)

DS_REPL_NBR_NO_CHANGE_NOTIFICATIONS
536870912 (0x20000000)

DS_REPL_NBR_PARTIAL_ATTRIBUTE_SET
1073741824 (0x40000000)

Using the information in this table in combination with the Switch statement the output can be converted in a more human readable format, listing all the flags in text instead of a single integer. In the next example I have also updated the Filter to automatically fill in the current domain name by using the [adsi] type accelerator to select the distinguishedname of the default naming context:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$WmiSplat = @{
    Class = 'MSAD_ReplNeighbor'
    Namespace = 'root\MicrosoftActiveDirectory'
    Filter = "NamingContextDN = '$(-join ([adsi]'').distinguishedname)'"
}
$DateTimeOfLastSyncSuccess = @{
    Name = 'DateTimeOfLastSyncSuccess'
    Expression = {[Management.ManagementDateTimeConverter]::ToDateTime($_.TimeOfLastSyncSuccess)}
}
$ReadableReplicaFlags = @{
    Name = 'ReadableReplicaFlags'
    Expression = {
        Switch ($_.ReplicaFlags) {
            {$_ -bor 0x10} {'DS_REPL_NBR_WRITEABLE'}
            {$_ -bor 0x20} {'DS_REPL_NBR_SYNC_ON_STARTUP'}
            {$_ -bor 0x40} {'DS_REPL_NBR_DO_SCHEDULED_SYNCS'}
            {$_ -bor 0x80} {'DS_REPL_NBR_USE_ASYNC_INTERSITE_TRANSPORT'}
            {$_ -bor 0x200} {'DS_REPL_NBR_TWO_WAY_SYNC'}
            {$_ -bor 0x800} {'DS_REPL_NBR_RETURN_OBJECT_PARENTS'}
            {$_ -bor 0x10000} {'DS_REPL_NBR_FULL_SYNC_IN_PROGRESS'}
            {$_ -bor 0x20000} {'DS_REPL_NBR_FULL_SYNC_NEXT_PACKET'}
            {$_ -bor 0x200000} {'DS_REPL_NBR_NEVER_SYNCED'}
            {$_ -bor 0x1000000} {'DS_REPL_NBR_PREEMPTED'}
            {$_ -bor 0x4000000} {'DS_REPL_NBR_IGNORE_CHANGE_NOTIFICATIONS'}
            {$_ -bor 0x8000000} {'DS_REPL_NBR_DISABLE_SCHEDULED_SYNC'}
            {$_ -bor 0x10000000} {'DS_REPL_NBR_COMPRESS_CHANGES'}
            {$_ -bor 0x20000000} {'DS_REPL_NBR_NO_CHANGE_NOTIFICATIONS'}
            {$_ -bor 0x40000000} {'DS_REPL_NBR_PARTIAL_ATTRIBUTE_SET'}
        }
    }
}
Get-WmiObject @WmiSplat | Select-Object -Property SourceDsaCN,SourceDsaSite,$DateTimeOfLastSyncSuccess,$ReadableReplicaFlags

Replicate-Formatted

By creating both the $DateTimeOfLastSyncSuccess and $ReadableReplicaFlags hashtables, the content can now be displayed in a human readable format, which should make it easier to determine how replication is progressing or where it would be interesting to attempt to initiate replication.

The following example will start replication of the domain partition with all site links of the current server. The ClassName and Filter parameter have been replaced by the Query parameter to illustrate a different approach of querying for this information.

1
2
3
4
5
6
7
8
$WmiSplat = @{
    Namespace = 'root\MicrosoftActiveDirectory'
    Query = "Select * From MSAD_ReplNeighbor Where NamingContextDN = '$(-join ([adsi]'').distinguishedname)'"
}
Get-WmiObject @WmiSplat | ForEach-Object {
    Write-Output "Replicate domain partition: $($_.Domain) from DC: $($_.SourceDsaCN) in the $($_.SourceDsaSite) site"
    $_.SyncNamingContext()
}

Replicate-Replication

This will replicate the domain partition with the defined site links. The Write-Output cmdlet will show which DC and which site replication has been started with. The SyncNamingContext method does accept a single argument as well, an integer that contains the parameters for this method.

For a complete overview of the available parameters please refer to this table:

Parameter Value
DS_REPSYNC_ASYNCHRONOUS_OPERATION 0x1
DS_REPSYNC_WRITEABLE 0x2
DS_REPSYNC_PERIODIC 0x4
DS_REPSYNC_INTERSITE_MESSAGING 0x8
DS_REPSYNC_ALL_SOURCES 0x10
DS_REPSYNC_FULL 0x20
DS_REPSYNC_URGENT 0x40
DS_REPSYNC_NO_DISCARD 0x80
DS_REPSYNC_FORCE 0x100
DS_REPSYNC_ADD_REFERENCE 0x200

Using the examples in this article you can now verify when replication last occurred and replicate a Active Directory partition between two or multiple sites. If you have any questions or feedback on this article feel free to use the comments section and for more information on this MSAD_ReplNeighbor WMI class have a look at the following resources:

MSAD_ReplNeighbor WMI class
MSAD_ReplNeighbor class
SyncNamingContext method of the MSAD_ReplNeighbor class

Free ebook – Active Directory Friday All Articles

ADF-AllArticles

The Active Directory Friday articles have proven to be quite popular among my readers and as a thank you to all my readers I decided to publish the series as an Ebook. The reason for publishing this series as an ebook is to make the content more easily accessible. The ebook is available in PDF, EPUB and MOBI formats to allow for complete portability and free choice for any device to read these articles upon. I have placed this ebook in the Books section of my blog and the download links are available below.

PDF_download Download PDF EPub_logo Download EPUB mobi Download MOBI

The ebook covers the following topics:

  • Creating Active Directory groups using PowerShell
  • Determine the forest functional level
  • Find empty Organizational Unit
  • Use the ANR filter for LDAP Queries
  • Find users with password never expires
  • Change a user’s password
  • Create new OU
  • Determine tombstone lifetime
  • Search for computers accounts
  • List password information for Domain Administrators
  • Get DistinguishedName of current domain
  • Query Group Policy Objects in Active Directory
  • Find user accounts that have not changed password in 90 days

This resource will be updated on a regular basis as new articles are published, to keep the content up-to-date with the latest articles. If you have any requests or feedback for topics to be included in this ebook or the Active Directory Friday series, please leave a comment below.

Active Directory Friday All Articles
Books
Active Directory Friday
PDF_download Download PDF
EPub_logo Download EPUB
mobi Download MOBI

Active Directory Friday: Distribution group membership for AD User

To get a list of distribution groups an Active Directory user account is a member of of we can query Active Directory. For example by combining the Get-ADUser and Get-ADGroup cmdlets. To generate this list the following code can be used:

1
2
3
Get-ADUser -Identity JaapBrasser -property memberof |
Select-Object -ExpandProperty memberof | Get-ADGroup |
Where-Object {$_.groupcategory -eq 'Distribution'}

The Get-ADUser cmdlet gets all the groups Jaap Brasser is a member of, the Select-Object cmdlet expands the MemberOf attribute which is then piped into the Get-ADGroup cmdlet. The last step is using the Where-Object cmdlet to filter out only the Distribution groups to get the desired results.

Alternatively the DirectoryServices DirectorySearcher object can be used. This object does not require the Active Directory module to be installed and can run on any version of PowerShell. The following code can be used:

1
2
3
4
5
6
7
8
9
10
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property @{
    Filter = "(samaccountname=JaapBrasser)"
} | ForEach-Object {
    $_.FindOne().Properties.memberof | ForEach-Object {
        $CurrentGroup = [adsi]"LDAP://$_"
        if (-not ([int](-join $CurrentGroup.Properties.grouptype) -band 0x80000000)) {
            $CurrentGroup.Properties.name
        }
    }
}

This sample works by querying Active Directory for the samaccountname JaapBrasser. Of this user account the distinguishedname of each group object is retrieved. The group type is explained in last weeks post as well, in which I explained about the hex codes which defines whether a group is a Security Group or a Distribution group. The article is available here: Creating Active Directory groups using PowerShell

For more information on this subject please refer to the following links:

Distribution group membership
Get-ADGroup
Get-ADUser
Understanding Groups
2.2.12 Group Type Flags
Creating Active Directory groups using PowerShell

Active Directory Friday: Creating Active Directory groups using PowerShell

Creating a group in Active Directory using PowerShell is relatively simple when using the Active Directory module. To create a Global Distribution Group the following code can be executed:

1
New-ADGroup -Name NewGlobalDG_1 -GroupScope Global -GroupCategory Distribution

When creating a Domain Local Security Group the GroupScope can be changed to DomainLocal and GroupCategory can be omitted since the default is a Security Group:

1
New-ADGroup -Name NewDLSG_1 -GroupScope DomainLocal

Creating groups using the [adsi] provider is a three step process. First we bind to the OU in which the group should be created. Secondly we enter the name and the properties of the group that should be created. And by finally calling the SetInfo() method to create the group. The following code will create a group:

1
2
3
4
5
$TargetOU = [adsi]'LDAP://OU=Groups,DC=jaapbrasser,DC=com'
$Group = $TargetOU.Create('group','cn=System_Operators')
$Group.put('grouptype',0x80000004)
$Group.put('samaccountname','System_Operators')
$Group.SetInfo()

To specify the Group Type a hexadecimal value is required as specified in the following MSDN article: 2.2.12 Group Type Flags. The following table lists all the possible values:

Symbolic name Value
GROUP_TYPE_BUILTIN_LOCAL_GROUP 0x00000001
GROUP_TYPE_ACCOUNT_GROUP 0x00000002
GROUP_TYPE_RESOURCE_GROUP 0x00000004
GROUP_TYPE_UNIVERSAL_GROUP 0x00000008
GROUP_TYPE_APP_BASIC_GROUP 0x00000010
GROUP_TYPE_APP_QUERY_GROUP 0x00000020
GROUP_TYPE_SECURITY_ENABLED 0x80000000

It is important to note that only four values are relevant to us when creating Active Directory accounts:

  • GROUP_TYPE_ACCOUNT_GROUP – 0x00000002
  • GROUP_TYPE_RESOURCE_GROUP – 0x00000004
  • GROUP_TYPE_UNIVERSAL_GROUP – 0x00000008
  • GROUP_TYPE_SECURITY_ENABLED – 0x80000000

To simplify the creation of groups these values can be place in a hashtable:

1
2
3
4
5
6
$GroupType = @{
    Global      = 0x00000002
    DomainLocal = 0x00000004
    Universal   = 0x00000008
    Security    = 0x80000000
}

Using the values stored in the hash table it is now possible to create any of the three group scopes as either a distribution group or security group. The following example uses the -bor operator to combine the values to create a Universal Security Group:

1
2
3
4
5
$TargetOU = [adsi]'LDAP://OU=Groups,DC=jaapbrasser,DC=com'
$Group = $TargetOU.Create('group','cn=Universal_Operators')
$Group.put('grouptype',($GroupType.Universal -bor $GroupType.Security))
$Group.put('samaccountname','Universal_Operators')
$Group.SetInfo()

That is all there is to it, using this methodology it is possible to create any type of Active Directory group using either the Active Directory module or the [adsi] type accelerator. Below I have included some links in regards to this topic.

Creating Active Directory Groups
New-ADGroup
Understanding Groups
2.2.12 Group Type Flags

Active Directory Friday: Determine the forest functional level

Knowing the Forest Functional Level can be important when implementing new products or when considering to upgrade your functional level. This information can be view in the ‘Active Directory Domains and Trusts’ console, but for the purpose of this article we will take a look how this information can be retrieved programmatically, or to be more specific: How to retrieve this using PowerShell.

In the following example we use the Get-ADForest cmdlet to Retrieve information about the current forest. In particular the property we are interested in is the ForestMode property:

1
Get-ADForest | Select-Object ForestMode

Alternatively the [adsi] type accelerator can be used, this has the advantage that it works on any computer that has PowerShell installed as it does not rely on having the Active Directory module installed, the following code will retrieve the Forest Functional level:

1
([adsi]"LDAP://CN=Partitions,$(([adsi](“LDAP://RootDSE”)).configurationNamingContext)").'msDS-Behavior-Version'

The problem with this is that the value of the Forest Functional Level is stored as an integer. Luckily for us this integer can be found on MSDN. So by combining the previous command with a switch statement we can get the expected output:

1
2
3
4
5
6
7
8
9
switch (([adsi]"LDAP://CN=Partitions,$(([adsi](“LDAP://RootDSE”)).configurationNamingContext)").'msDS-Behavior-Version') {
    0 {'DS_BEHAVIOR_WIN2000'}
    1 {'DS_BEHAVIOR_WIN2003_WITH_MIXED_DOMAINS'}
    2 {'DS_BEHAVIOR_WIN2003'}
    3 {'DS_BEHAVIOR_WIN2008'}
    4 {'DS_BEHAVIOR_WIN2008R2'}
    5 {'DS_BEHAVIOR_WIN2012'}
    6 {'DS_BEHAVIOR_WIN2012R2'}
}

For more information about the Forest Functional Level I have included a TechNet article that goes in depth about the implications of the various forest and domain functional levels. For more information about the msDS-Behavior-Version attribute I have included the link to the MSDN entry.

Forest Functional Level
Understanding Active Directory Domain Services (AD DS) Functional Levels
msDS-Behavior-Version: Forest Functional Level

Active Directory Friday: Find empty Organizational Unit

As an Active Directory Administrator there are some moments, few and far in between where you might have a moment to yourself. In this article I will give you a short line of code so you can use this moment to find out if you have any empty Organizational Units in your domain. The definition of empty is an OU that does not contain any child objects. By this definition an OU containing another OU would not be considered empty. Because there is no LDAP filter for this we will take a look at how to do this using the Cmdlets and the [adsisearcher] type accelerator.

In the following example I will use Get-ADOrganizationalUnit in combination with an if-statement and Get-ADObject to gather empty OUs:

1
2
3
4
5
Get-ADOrganizationalUnit -Filter * | ForEach-Object {
	   if (-not (Get-ADObject -SearchBase $_ -SearchScope OneLevel -Filter * )) {
      		$_
   	}
}

So lets have a look at what this code does, the first portion is straight forward, gather all OUs using the Get-ADOrganizationalUnit cmdlet and pipe it into the ForEach-Object cmdlet. The if-statement is the interesting part here, I am using the Get-ADObject cmdlet to establish if this OU contains any child object, by setting the SearchBase to that OU and setting the SearchScope to OneLevel. Setting the SearchScope to OneLevel will only return direct child objects of the parent, the OU, without returning the OU itself. Because of this Get-ADObject will not return any objects if the OU is empty.

For more information about the SearchScope parameter and the possible arguments have a look at the following link: Specifying the Search Scope

Because you might not have the ActiveDirectory module loaded in your current PowerShell session it can be useful to know the [adsisearcher] alternative:

1
2
([adsisearcher]'(objectcategory=organizationalunit)').FindAll() | Where-Object {
   -not (-join $_.GetDirectoryEntry().psbase.children) }

This is a slightly different approach to illustrate a different method of gathering empty OUs, here we check the Children property part of the base object that is retrieved. The -join operator is used to ensure the -not does not evaluate the empty System.DirectoryServices.DirectoryEntries object as true.

Using the logic in this post it is also possible to filter for other specific objects contained in the OUs. For example display OUs that only have user objects, display OUs with both user and computer objects and so on.

For more information on this subject please refer to the following links:

Additional resources
Specifying the Search Scope
Get-ADObject
Get-ADOrganizationalUnit