Last Update: Sep 04, 2024 | Published: Apr 06, 2017
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"
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.
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.
Some information, like whether the account is enabled or not, is tucked away. This particular tidbit is the UserAccountControl value.
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]
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]
$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.
This is actually a datetime value, which we can convert like this:
$val = $al.ConvertLargeIntegerToInt64($al.pwdLastSet.value) $last = [datetime]::FromFileTime($val)
It does not take much more effort to figure out the age of the password.
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.
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]
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"})
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.
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
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.