Introduction
In
Use PowerShell to Query and Display Data, I presented a .ps1 script that connects to a SQL Server table, fetches some data and displays it in a simple user interface based on Windows Forms.
In this article, I will present a script that can connect to and search any LDAP-enabled directory, like Microsoft Active Directory. The features of this application are:
- it uses the standard syntax to connect to, filter records on and search an LDAP-enabled directory
- it contains a ‘fuzzy search’ mechanism whereby a wildcard search is enabled not only on attribute values but also on attributes themselves. I.e., search attributes that are like ‘*phone*’, enabling us to look at all the phone-like attributes in a directory (‘telephoneNumber,’ ‘otherTelephone,’ ‘officeTelephone,’ etc.)
- it stops execution and presents a form containing information about the phone number and its user that match the search criteria.
- it uses a list of servers that you specify in a text file so that more than one server can be searched, if desired
- it populates a text file, "C:\output22.txt" with attribute values from the search; comment out this code to eliminate this functionality.
As noted in the earlier article noted above, PowerShell has been designed by Microsoft to become the primary command-line tool for network and server admins, but is vastly overpowered relative to that ecospace. It is a fully functional language in itself and integrates well with many other tools and languages, including especially the functions available in the .NET Framework that all .NET programmers use. In the example script here, we use LDAP query syntax and Windows Forms together under the aegis of PowerShell.
Background
The idea of this presentation is to demonstrate how PowerShell can be used to do an LDAP query and display some of the resulting data in a simple GUI. Again, this should suggest to readers what possibilities there are to be explored in using PowerShell for a large variety of purposes.
This solution gives an illustration of how to use .NET Framework and LDAP-related functions in the context of PowerShell, including the attachment of a Windows form from within Powershell and the creation of a ‘fuzzy search’ tool for both attributes and attribute values.
Using the Code
As one might do in C#, the code sets up references to the Windows Forms DLL and the DirectoryServices DLL. The references are instantiated and their methods invoked to query and display some data.
#access Windows Forms\Drawing DLLs
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices")
$formtype = ‘System.Windows.Forms’
$drawinfo = 'System.Drawing'
To run the code, you need to create a .txt file with your server names:
ABCDC01.companyname.com
ABCDC02.compnayname.com
(in the path "C:\server_names.txt")
The code can be downloaded to a directory and run using a command at the PowerShell prompt:
<&<br />
'YourAppName.ps1'>
. If you don't precede the file with the '&', PowerShell will not run the script.
You can also follow these steps:
- Open the command prompt, type "powershell.exe" and press enter, starting PowerShell
- use the ‘cd’ command to navigate to the directory where you have saved this .ps1 file
- at the prompt type ".\nameofapplication.ps1" and press enter
If you are using Windows 7, PowerShell v.2 is included and you can also open the
.ps1 file in the PowerShell ISE, the 'integrated scripting environment', a free development tool for PowerShell. Things to note: in PS, the '#' symbol is used for comments. A multiline comment would be started with '<#' and then closed with '#>.' The scopes for variables are Current, Local, Script, and Global, the details of which can be viewed by using the 'Help' button on the PS ISE toolbar.
## this prevents overlooking common script errors, analogous to 'Option Explicit' in VB
Set-StrictMode -Version Latest
#access Windows Forms\Drawing DLLs
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices")
$formtype = ‘System.Windows.Forms’
$drawinfo = 'System.Drawing'
#add a function to handle button click event
function button_click_function($y)
{
$form.Close()
}
# variables to track number of users found and number of DCs searched
$name_counter = 0
$dc_counter = 0
# list your domain controllers from a text file
$DCs = Get-Content C:\server_names.txt
# e.g., ABCDC01.jonescorp.com etc.
#use the syntax below to get ALL Domain Controllers rather than a fixed list of servers in a file
#[DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers
Write-Host ''
Write-Host ''
Write-Host 'COUNT = ',$name_counter
# Hold username for reference
$Username = $_
#if desired print out: 'username = ',$Username
#establish a filter, in this case, all users with a userprincipalname
$LdapFilter = "(&(objectCategory=user)(userprincipalname=*))"
# Loop through the DCs now
$DCs | ForEach-Object {
Write-Host 'DC-COUNTER',$dc_counter
Write-Host 'Server ', $_ #use "$_.Name" if going through all servers
Write-Host ''
Write-Host ''
$dc_counter +=1
write-host 'DCs = ', $DCs
# Hold onto the server name so it can be added to the output
$Server = $_ #use $_.Name if looping through all servers
# Create a search using the current server
$SearchRoot = [ADSI]"LDAP://$Server"
# Set up a search
$Searcher = New-Object DirectoryServices.DirectorySearcher($SearchRoot,
$LdapFilter)
#execute the search
$results = $Searcher.FindAll()
foreach ($result in $results)
{
#separate each user with some blank lines
Write-Host ''
Write-Host ''
Write-Host ''
Write-Host ''
' ' >> 'c:\output2.txt'
' ' >> 'c:\output2.txt'
$name_counter +=1
'USER NUMBER: ',$name_counter >> 'c:\output22.txt'
Write-Host 'USER NUMBER: ',$name_counter
foreach ($propertyKey in
$result.Properties.PropertyNames)
{
#single value attributes
if($result.Properties[$propertyKey].Count -eq 1)
{
$valueCollection = $result.Properties[$propertyKey]
#loop through values
foreach ($propertyValue in $valueCollection)
{
#write to host
Write-Host $propertyKey.ToUpper(), ' = ',$propertyValue.ToString()
#output to text file
$propertyKey.ToUpper() + ' = ' + $propertyValue >> 'c:\output22.txt'
if($propertyKey -eq 'displayname')
{
$current_displayname = $propertyValue
}
#application stops and displays a form when any of the '*phone*' attributes
#contains *50* as part of its value
if($propertyKey -like '*phone*' -and $propertyValue -like `
'*50*')
{
write-host 'value here is ',$propertyValue
$form = New-Object "$formtype.form"
$textbox1 = New-Object "$formtype.textbox"
$textbox1.Multiline = $true
$button = New-Object "$formtype.button"
$form.Controls.Add($button)
$point1 = New-Object "$drawinfo.point"
$point1.X = 170;
$point1.Y = 250;
$button.Location = $point1
$button.Text = "Continue"
$button.add_Click({button_click_function(0)})
$point2 = New-Object "$drawinfo.point"
$point2.X = 50
$point2.Y = 50
$textbox1.Location = $point2
$textbox1.Width = 400
$textbox1.Height = 200
$form.Controls.Add($textbox1)
$form.Width = 500
#$form.Height 300
$holdt1 = $current_displayname #item_result.Properties['displayName']
$holdt2 = 'Found a phone number with "50" in it: ', $propertyKey
$holdt3 = $propertyValue
$textbox1.Text = $holdt1 + " `r`n " + $holdt2 + " `r`n " + $holdt3
$form.Height = 500
$form.Showdialog()
Sleep(1)
}
}
}
#multivalue attributes
if($result.Properties[$propertyKey].Count -gt 1)
{
$valueCollection = $result.Properties[$propertyKey]
foreach ($propertyValue in $valueCollection)
{
$propertyKey + ' = ' + $propertyValue >> 'c:\output22.txt'
Write-Host $propertyKey.ToUpper(), ' = ',$propertyValue.ToString()
}
}
# break
}
# End of loop through DCs
#put some blank rows between users in the text file
' ' >> 'c:\output22.txt'
' ' >> 'c:\output22.txt'
}
# End of loop through users
}
I hope developers and server administrators who are interested in learning about Powershell can use this solution as a further introduction to the language.