Active Directory PowerShell with ADSI

PowerShell Text Purple hero
One of the most popular targets for PowerShell management is Active Directory. While we may be moving to a post-AD world, it will be a while before you no longer need to deal with users, groups, and computers in Active Directory. Certainly, the easiest approach is to use the Active Directory module. You can get this by installing the latest version of Remote Server Administration Tools (RSAT). There may be situations, however, where you do not have access to those tools. There also may be times you want to build a customized toolset that doesn’t rely on the module. PowerShell is all about having options. I thought I would spend some time over the next several articles on how to manage Active Directory with alternate methods.
 

 
It is probably a safe bet that there is an entirely new generation of IT Pros who have never been exposed to VBScript and other “old school” techniques. Sometimes those old school ways still have value. For Active Directory, one such tool is the ADSI accelerator. ADSI used to be the only way to connect and manage directory services. You should consider it a legacy technique. ADSI communicates with domain controllers over TCP port 389. Keep in mind, this might not work with cloud-based servers.
It is very easy to make an ADSI connection to a domain. You won’t even need to specify a domain controller.

$domainname = "globomantics" #or use $env:userdomain
#connect to the $domain
[ADSI]$domain = "WinNT://$domainname"

One very important thing to keep in mind, the ADSI accelerator is not case-sensitive. The WinNT moniker is case-sensitive.
Case sensitive moniker (Image Credit: Jeff Hicks)
The object isn’t especially rich (once you use the correct moniker).
The Domain ADSI object (Image Credit: Jeff Hicks)
And using the properties isn’t exactly obvious.
Selecting domain properties (Image Credit: Jeff Hicks)
First, properties are returned as arrays. This requires some extra work to improve readability.

$domain | Select @{Name="Name";Expression={$_.name.value}},
@{Name="PwdHistory";Expression={$_.PasswordHistoryLength.value}},
@{Name="MinPasswordAge";Expression={$_.MinPasswordAge.value}},
@{Name="MaxPasswordAge";Expression={$_.MaxPasswordAge.value}}

Formatting properties (Image Credit: Jeff Hicks)
You also may need to do some research to understand values. For example, the password age properties are given in seconds. Therefore, this might help to make it more meaningful.

$domain | Select @{Name="Name";Expression={$_.name.value}},
@{Name="PwdHistory";Expression={$_.PasswordHistoryLength.value}},
@{Name="MinPasswordAge";Expression={New-Timespan -seconds $_.MinPasswordAge.value}},
@{Name="MaxPasswordAge";Expression={New-Timespan -seconds $_.MaxPasswordAge.value}}

Formatted timespans (Image Credit: Jeff Hicks)
We will be using these techniques repeatedly.

The last part of the domain object I want to touch on is children. While ADSI is somewhat hierarchical, the WinNT moniker doesn’t expose the Active Directory objects in organizational units. But you can still get access to the objects in the domain through the Children property.
Counting domain objects (Image Credit: Jeff Hicks)
This value includes all objects. Separating them out takes a little work because you need to use each child’s object schemaclassname property.

$domain.children | group {$_.schemaclassname}

Grouping domain objects (Image Credit: Jeff Hicks)
If you need to select a certain type, you can use a command like this:

$g = ($domain.children).Where({$_.schemaclassname -eq 'group'})

As before, you need to massage the objects a bit to produce reader-friendly output.

$g | Select @{Name="Name";Expression={$_.name.value}},
@{Name="Description";Expression={$_.Description.value}},
@{Name = "Type";Expression = {
  switch ($_.grouptype.value)  {
      "2" {"Global"}
      "4" {"DomainLocal"}
      "8" {"Universal"}
  }
}},
Path | Out-Gridview

And even though I’m showing the de-code for a universal group, using WinNT doesn’t always identify those groups. In my case, I have universal groups but ADSI reports them as global groups.
ADSI Groups (Image Credit: Jeff Hicks)
You can do something similar with user accounts:

$u = ($domain.children).Where({$_.schemaclassname -eq 'user'})
$u[0..49] | Select @{Name="Name";Expression={$_.name.value}},
@{Name="FullName";Expression={$_.fullname.value}},
@{Name="Description";Expression={$_.Description.value}}

User Accounts via WinNT (Image Credit: Jeff Hicks)
Again, everything exposed through WinNT is very simple. But it can be quick to access. You don’t need to know where the object lives in AD, just its name or ADSI path.
Selecting a single ADSI object (Image Credit: Jeff Hicks)

We are just scratching the surface here. There is so much more I want to get into. Next time we’ll dig a bit more into using ADSI and we will begin exploring LDAP.