I used to be chatting with my pal Gladys Kravitz lately about some PowerShell scripting she was doing with Lively Listing and the DirectorySearcher object. For a variety of causes, the Lively Listing module from Distant Server Administration Instruments (RSAT) will not be an possibility. However that’s effective. Gladys is an expertise AD admin and PowerShell scripter. Nonetheless, she had a query concerning the output from her instructions, which led to some fascinating work that you just may discover useful. Regardless that I’m going to be discussing a particular object sort, there are a number of rules that you could apply to your PowerShell scripting.
ManageEngine ADManager Plus – Obtain Free Trial
Search Outcome Properties
Let’s start with some PowerShell code to discover a person account utilizing the DirectorySearcher object.
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.filter ="(&(objectcategory=particular person)(objectclass=person))"
$searcher.FindOne().Properties
The DirectorySearcher has strategies of FindOne() and FindAll(). The ensuing object has a Properties property.
You should utilize this in any variety of methods.
New-Object -typename PSObject -property $searcher.FindOne().Properties | Choose Title,Description,WhenChanged
Leaving the formatting of the values apart, discover the property names. Practical, however not professional-looking. That is all the time an possibility.
New-Object -typename PSObject -property $searcher.FindOne().Properties |
Choose-Object @{Title="Title";Expression = {$_.identify}},
@{Title="Description";Expression = {$_.Description}},
@{Title="WhenChanged";Expression = {$_.WhenChanged}}
Or you may outline a customized object with a kind identify and use a customized format file. However that’s lots of work and never versatile.
Right here’s one other method to creating an object however with correctly cased property names.
$r = $searcher.FindOne()
$r.Properties.GetEnumerator() |
The place-Object Description |
ForEach-Object -Start {
#initialize an empty hashtable
$h = @{}
} -Course of {
#convert to title case
$n = [cultureinfo]::CurrentCulture.TextInfo.ToTitleCase($_.identify.tolower())
if ($_.worth.depend -gt 1) {
#get the array of values
$v = $_.worth
}
else {
#get the one worth
$v = $_.worth[0]
}
#add the property identify and worth to the hashtable
$h.Add($n, $v)
} -Finish {
#create a customized object from the hashtable
[pscustomobject]$h
}
On this specific state of affairs, the item I’m formatting is a DirectoryServices.ResultCollection, which is, in essence, a hashtable, so it requires a distinct method. The GetEnumerator() methodology writes every aspect within the assortment as an object with Key and Worth properties. My instance is filtering keys for particular “properties.”
The filtered objects are then piped to ForEach-Object. Earlier than any objects are processed within the pipeline, I initialize an empty hashtable. Subsequent, for every filtered outcome, I convert the identify, which is an alias for ‘key,’ to title case. For this to work, I all the time make the string all decrease case earlier than invoking the strategy. This reformatted identify and the corresponding worth are added to the hashtable. After all the things has been processed, I flip the hashtable right into a customized object.
That is promising however must be extra versatile. The ForEach-Object construction is similar format as a complicated PowerShell perform that takes pipeline enter. In different phrases, I’ve a prototype.
Creating Configuration Knowledge
My concept is to take a DirectorySearcher outcome and create a customized object changing the property identify with a correctly formatted identify and the worth. However in trying on the output, I would like greater than title case. ‘Logoncount’ may look nicer formatted as ‘LogonCount.’ I favor SamAccountName. I need to exchange the incoming property identify with a correctly formatted identify. I’ll want an inventory of all doable property names formatted the best way I would like. As a result of I do know I’ll finally need to use this listing as a hashtable, I’ll create a configuration information file that I can use later with Import-PowerShellDataFile.
Right here’s the PowerShell script to generate totally different configuration information information relying on the item sort.
#requires -version 5.1
<# Create-ADPropertyFile.ps1
proof-of-concept code to create an Lively Listing property configuration information file
.Create-ADPropertyFile.ps1 -Filter "(&(objectclass=organizationalunit)(Title=Staff))" -FilePath .adou.psd1
.Create-ADPropertyFile.ps1 Group -FilePath .adgroup.psd1
#>
[cmdletbinding(SupportsShouldProcess, DefaultParameterSetName = "byType")]
Param(
[Parameter(Position = 0, ParameterSetName = "byType")]
[ValidateSet("User", "Group", "Computer", "OU")]
[string]$ObjectType = "Person",
[Parameter(HelpMessage = "Specify an LDAP search filter to a template object.", ParameterSetName = "byFilter")]
[ValidateNotNullOrEmpty()]
[string]$Filter,
[Parameter(Mandatory, HelpMessage = "Specify the filename and path to the psd1 file.")]
[ValidateScript({ Test-Path (Split-Path $_) })]
[string]$FilePath
)
if ($pscmdlet.ParameterSetName -eq "byType") {
Swap ($ObjectType) {
"Person" { $filter = "(&(objectcategory=particular person)(objectclass=person))" }
"Group" { $filter = "(Objectclass=group)" }
"Pc" { $filter = "(objectclass=laptop)" }
"OU" { $filter = "(objectclass=organizationalunit)" }
}
}
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.filter = $filter
#do not want values from the search
$searcher.PropertyNamesOnly = $true
$searcher.FindOne().Properties.GetEnumerator() | Type-Object -Property Key |
ForEach-Object -Start {
#initialize an inventory
$listing = [System.Collections.Generic.List[string]]::new()
$listing.add("@{")
} -Course of {
$worth = [cultureinfo]::CurrentCulture.TextInfo.ToTitleCase($($_.key.ToLower()))
#add every property to the listing
$Listing.Add("$($_.key) = '$worth'")
} -Finish {
#shut the psd1
$listing.Add("}")
}
#save the listing to a file
$listing | Out-File -FilePath $FilePath
#edit psd1 file along with your desired formatting
The default filters will discover the primary matching outcome. Nevertheless, the DirectorySearcher will solely return property names which have been outlined. You may need to create a template object with all of the properties outlined that you just intend to make use of after which construct the property file utilizing a customized LDAP filter to that object.
I’ll use the script to create a psd1 file for person objects.
c:scriptsCreate-ADPropertyFile.ps1 -ObjectType Person -FilePath c:scriptsaduser.psd1
I can edit the file and alter the property names.
@{
accountexpires="AccountExpires"
admincount="AdminCount"
adspath="AdsPath"
badpasswordtime="BadPasswordTime"
badpwdcount="BadPwdCount"
cn = 'CN'
codepage="CodePage"
countrycode="CountryCode"
division = "Division"
description = 'Description'
displayname = "DisplayName"
distinguishedname="DistinguishedName"
dscorepropagationdata="DscorePropagationData"
givenname = "GivenName"
instancetype="InstanceType"
iscriticalsystemobject="IsCriticalSystemObject"
lastlogoff="LastLogoff"
lastlogon = 'Lastlogon'
lastlogontimestamp = 'LastLogonTimestamp'
logoncount="LogonCount"
logonhours="LogonHours"
memberof="MemberOf"
identify="Title"
objectcategory = 'ObjectCategory'
objectclass="ObjectClass"
objectguid = 'ObjectGuid'
objectsid = 'ObjectSid'
primarygroupid = 'PrimaryGroupId'
pwdlastset="PwdlastSet"
samaccountname="SamAccountName"
samaccounttype="SamAccountType"
sn = "Surname"
title = "Title"
useraccountcontrol="UserAccountcontrol"
usnchanged = 'UsnChanged'
usncreated = 'UsnCreated'
whenchanged = 'WhenChanged'
whencreated = 'WhenCreated'
}
}
If you understand extra LDAP property names, you may manually add them. The final step is to make use of this file.
Optimize AD Search Outcome
We are likely to keep away from monolithic instructions in PowerShell. As an alternative, we need to leverage the pipeline. I’ve code utilizing the DirectorySearcher to seek out objects.
I need to take these outcomes and optimize the property names.
Perform Optimize-ADSearchResult {
[cmdletbinding()]
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
HelpMessage = "This should be the input from an ADSearcher FindOne() or FindAll() method."
)]
[System.DirectoryServices.SearchResult]$InputObject,
[Parameter(
Mandatory,
HelpMessage = "Enter the path to the psd1 file with your property names. See Create-ADPropertyFile."
)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path $_ })]
[ValidatePattern('.psd1$')]
[string]$ConfigurationData,
[Parameter(HelpMessage = "Specify a custom type name, like CorpADUser. You might add this if using a custom format file or type extensions.")]
[ValidateNotNullOrEmpty()]
[string]$TypeName
)
Start {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Beginning $($myinvocation.mycommand)"
#import the configuration information
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Importing configuration information from $(Convert-Path $ConfigurationData)"
$PropertyData = Import-PowerShellDataFile -Path $ConfigurationData
} #start
Course of {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing enter"
$InputObject.properties.GetEnumerator() |
ForEach-Object -Start {
$new = [ordered]@{ }
if ($TypeName) {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Utilizing typename $TypeName"
$new.Add("PSTypename", $TypeName)
}
} -Course of {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing property $($_.key)"
#Get formatted property identify from configuration information
if ($PropertyData.Accommodates($_.key)) {
$identify = $PropertyData["$($_.key)"]
}
else {
$identify = $_.key
}
if ($_.worth.depend -gt 1) {
$worth = $_.worth
}
else {
$worth = $_.worth[0]
}
$new.Add($identify, $worth)
} -Finish {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Creating output"
New-Object -TypeName psobject -Property $new
}
} #course of
Finish {
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"
} #finish
}
The perform seems to be at every search outcome and tries to match the property identify from the configuration information. If it finds a match, it will likely be used. In any other case, PowerShell will use the unique property identify.
I may have saved all the property identify hashtables within the perform, however that will have added complexity and pointless size. If I would like so as to add a property, I’ve to edit the perform. It’s significantly better to separate information from the code that implements it. My perform is an efficient instance of utilizing configuration information.
I can take the search outcomes, optimize them with this perform, after which choose what I would like.
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.filter = "(&(objectcategory=particular person)(objectclass=person)(division=gross sales))"
$searcher.FindAll() |
Optimize-ADSearchResult -ConfigurationData C:scriptsaduser.psd1 |
Choose-Object -property DistinguishedName,Givenname,Surname,Description,Title
Abstract
There’s a lot happening on this submit—many transferring elements and presumably some new instructions. I hope you’ll evaluation the code and no less than stroll by it in your head. Please be at liberty to depart feedback and questions.