Active Directory User Accounts with PowerShell, ADSI, and LDAP

PowerShell Text Purple hero
We have been exploring some alternatives to the Active Directory (AD) PowerShell module. Most of the time, this module should meet your needs. It is always good to have options so I have been demonstrating how to use the ADSI type accelerator with the LDAP moniker. As long as you know the distinguished name to an AD object, you can reference it in PowerShell. When we ended last time, we were beginning to look at AD user objects. Let’s pick up that idea with a single-user account.
 

 

[ADSI]$al = "LDAP://CN=Al Fredo,OU=Sales and Marketing,OU=Departments,OU=Employees,DC=globomantics,DC=local"

User LDAP Properties (Image Credit: Jeff Hicks)
User LDAP Properties (Image Credit: Jeff Hicks)

To display user properties, I have been using Select-Object to expand each property value. There is an alternative that takes a list of property names and then creates an ordered hashtable. This gets the value from the user object. The [ordered] directive keeps all of the properties in the same order. This is not required but I like the look.

$props = "Name","sAMAccountname","Displayname","Description","Title","UserPrincipalName","WhenCreated","WhenChanged"
$hash = [ordered]@{}
foreach ($item in $props) {
  $hash.add($item,$al.$item.Value)
}
[pscustomobject]$hash

Once the hashtable is complete, it can be treated as a PowerShell custom object.

Selected properties (Image Credit: Jeff Hicks)
Selected Properties (Image Credit: Jeff Hicks)

You could use this technique for most properties. Let’s say you want to change a property. In most cases, all you need to do is assign a new value to the object.

$al.description = "sales laptop user"

An alternative is to use the Put() method.

$al.put("description","sales laptop user")

You have modified the locally-cached instance of the user account. To commit the change, invoke the Setinfo() method.

$al.setInfo()

You will not see this method with Get-Member but it is there. You can refresh the local version of the object.

$al.refreshcache()

And once replication finishes, you can see the change in Active Directory Users and Computers.

Viewing the change (Image Credit: Jeff Hicks)
Viewing the Change (Image Credit: Jeff Hicks)

Some information, like whether the account is enabled or not, is tucked away. This particular tidbit is the UserAccountControl value.
User account control (Image Credit: Jeff Hicks)
User Account Control (Image Credit: Jeff Hicks)


In order to determine if the account is enabled, you need to perform a bitwise operation using a hex flag value.

New-Variable UF_ACCOUNTDISABLE 0x2 -option Constant
($al.userAccountControl.value -band $UF_ACCOUNTDISABLE) -as [boolean]

Bitwise operation (Image Credit: Jeff Hicks)
Bitwise Operation (Image Credit: Jeff Hicks)

This is to be expected. Let’s disable the account.

$al.userAccountControl.value = $al.userAccountControl.value -bor $UF_ACCOUNTDISABLE
$al.setInfo()
$al.RefreshCache()
($al.userAccountControl.value -band $UF_ACCOUNTDISABLE) -as [boolean]
Testing the new value (Image Credit: Jeff Hicks)
Testing the New Value (Image Credit: Jeff Hicks)
$al.userAccountControl.value = $al.userAccountControl.value -bxor $UF_ACCOUNTDISABLE
$al.setInfo()
$al.RefreshCache()

Another property that you might want to get is the password last set. This is stored as a COM object. This is also a large-integer value.

Converting large AD integer (Image Credit: Jeff Hicks)
Converting Large AD Integer (Image Credit: Jeff Hicks)

This is actually a datetime value, which we can convert like this:

$val = $al.ConvertLargeIntegerToInt64($al.pwdLastSet.value)
$last = [datetime]::FromFileTime($val)

Calculating Password Last Set Date (Image Credit: Jeff Hicks)
Calculating Password Last Set Date (Image Credit: Jeff Hicks)

It does not take much more effort to figure out the age of the password.
Calculating Password Age (Image Credit: Jeff Hicks)
Calculating Password Age (Image Credit: Jeff Hicks)

The last bit of the user account that needs coaxing is the account properties. An example of this is figuring out whether the user’s password ever expires.
Account Properties (Image Credit: Jeff Hicks)
Account Properties (Image Credit: Jeff Hicks)

This is also stored in the UserAccountControl property. We will perform a bitwise operation to determine if the password can expire.

New-Variable ADS_UF_DONT_EXPIRE_PASSWD 0x10000 -Option Constant
($al.userAccountControl.value -band $ADS_UF_DONT_EXPIRE_PASSWD) -as [boolean]

Testing User Account Flag (Image Credit: Jeff Hicks)
Testing User Account Flag (Image Credit: Jeff Hicks)

The “user cannot change password” is actually an access control rule that uses an extended right.

$guid="ab721a53-1e2f-11d0-9819-00aa0040529b"
$al.ObjectSecurity.GetAccessRules($true,$true,[security.principal.NTAccount]).where({$_.objecttype -eq $guid -AND $_.IdentityReference -match "SELF"})

Change Password Rule (Image Credit: Jeff Hicks)
Change Password Rule (Image Credit: Jeff Hicks)

If the AccessControlType is set to Deny, then the user cannot change their password. This is the case with Al Fredo.
Let’s put all of this together into a single function. This will create a custom object with a user’s LDAP properties.

Function Get-LDAPUser {
[cmdletbinding()]
Param(
[Parameter(Position = 0, Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string]$ADSPath
)
Begin {
  #define some variables
  New-Variable UF_ACCOUNTDISABLE 0x2 -option Constant
  New-Variable ADS_UF_DONT_EXPIRE_PASSWD 0x10000 -Option Constant
  $guid="ab721a53-1e2f-11d0-9819-00aa0040529b"
  #define a set of properties to retrieve
  $props = "DistinguishedName","Name","sAMAccountname","Displayname","UserPrincipalName",
  "Parent","Description","Title","Department","WhenCreated","WhenChanged"
}
Process {
  Write-Verbose "Getting user properties for $ADSPath"
  [ADSI]$user = $ADSPath
  if ($user.ADSPath) {
    $hash = [ordered]@{}
    foreach ($item in $props) {
      if ($user.$item -is [System.Collections.CollectionBase]) {
        $hash.add($item,$user.$item.Value)
      }
      else {
        $hash.add($item,$user.$item)
      }
    }
   $hash.Add("IsDisabled",($user.userAccountControl.value -band $UF_ACCOUNTDISABLE) -as [boolean])
   $pwdLast = [datetime]::FromFileTime($user.ConvertLargeIntegerToInt64($user.pwdlastset.value))
   $pwdAge = (Get-Date) - $pwdLast
   $hash.Add("PasswordLastSet",$pwdLast)
   $hash.Add("PassswordAge",$pwdAge)
   $hash.Add("PasswordNeverExpires",($user.userAccountControl.value -band $ADS_UF_DONT_EXPIRE_PASSWD) -as [boolean])
   $rule = $user.ObjectSecurity.GetAccessRules($true,$true,[security.principal.NTAccount]).where({$_.objecttype -eq $guid -AND $_.IdentityReference -match "SELF"})
   if ($rule.accesscontroltype -eq 'Deny') {
     $CannotChange = $True
   }
   else {
     $CannotChange = $False
   }
     $hash.Add("UserCannotChangePwd",$CannotChange)
   #write result to the pipeline
   [pscustomobject]$hash
  }
  else {
     Write-Warning "Failed to find $distinguishedname"
  }
}
End {
 #not used
}
}

I wrote the function so that you can pipe a user object to it.

Getting LDAP user properties (Image Credit: Jeff Hicks)
Getting LDAP User Properties (Image Credit: Jeff Hicks)

Or you can get all users from a particular OU.

[ADSI]$OU = "LDAP://OU=IT,OU=Departments,OU=Employees,DC=Globomantics,DC=Local"
$ou.children.where({$_.schemaclassname -match 'user'}) | Get-LDAPUser | Out-GridView -Title IT

User Report (Image Credit: Jeff Hicks)
User Report (Image Credit: Jeff Hicks)


I hope you will give some of this a try in a test environment. Next time, we will look at creating user accounts with ADSI.