I'm writing a powershell script that searches for users inside an Active Directory OU and allows me to reset passwords by choosing matches from a list. I found a Tutorial that uses the System.DirectoryServices.DirectoryEntry and System.DirectoryServices.DirectorySearcher, and modified it like so:
$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP:\\[REDACTED]")
##ReadSTDIN
$strSearch = Read-Host -Prompt "Search"
$strCat = "(&(objectCategory=User)(Name=*" + $strSearch + "*))"
## Search Object
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strCat
$objSearcher.SearchScope = "Subtree"
#Load Required Properties into the dynObjLink
$objSearcher.PropertiesToLoad.Add("name")
$objSearcher.PropertiesToLoad.Add("userPrincipalName")
$objSearcher.PropertiesToLoad.Add("SamAccountName")
##Magical Search Function
$colResults = $objSearcher.FindAll()
$colResults.PropertiesLoaded
#for every returned userID add them to a table
ForEach ($objResult in $colResults)
{$a++
$objResult.count
$objItem = $objResult.Properties
$objItem.name
$objItem.userPrincipalName
$results.Add($a, $objItem.name + $objItem.userPrincipalName + $objItem.SamAccountName)
}
#Print Table
$results | Format-Table -AutoSize
This works well enough, but when it prints data I can only get the "first name" value of anything that comes back. Everything else becomes NULL and I can't figure out why.
Name Value
---- -----
3 {James3 [REDACTED], $null, $null}
2 {James2 [REDACTED], $null, $null}
1 {James1 [REDACTED], $null, $null}
I've tried different kinds of authentication and manipulating values, but the DirectorySearcher object only seems to collect the "name" value of any record it returns, no matter what I load into it. Help?
Here's a bit shorter (and PowerShell v2-compatible) way of doing this:
#requires -version 2
param(
[Parameter(Mandatory=$true)]
[String] $SearchPattern
)
$searcher = [ADSISearcher] "(&(objectClass=user)(name=$SearchPattern))"
$searcher.PageSize = 1000
$searcher.PropertiesToLoad.AddRange(#("name","samAccountName","userPrincipalName"))
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$properties = $searchResult.Properties
$searchResult | Select-Object `
#{Name = "name"; Expression = {$properties["name"][0]}},
#{Name = "sAMAccountName"; Expression = {$properties["samaccountname"][0]}},
#{Name = "userPrincipalName"; Expression = {$properties["userprincipalname"][0]}}
}
}
$searchResults.Dispose()
Note that there's no need to build a list and output afterwards. Just output each search result. Put this code in a script file and call it:
PS C:\Scripts> .\Searcher.ps1 "*dyer*"
If you omit the parameter, PowerShell will prompt you for it (because the parameter is marked as mandatory).
try using Properties matching to the PropertiesToLoad
$entry = new-object -typename system.directoryservices.directoryentry -ArgumentList $LDAPServer, "ldap", "esildap"
$entry.Path="LDAP://OU=childOU,OU=parentOU,DC=dc1,DC=dc2"
$searcher = new-object -typename system.directoryservices.directorysearcher -ArgumentList $entry
$searcher.PropertiesToLoad.Add('samaccountname')
$searcher.PropertiesToLoad.Add('mail')
$searcher.PropertiesToLoad.Add('displayname')
$objs = $searcher.findall()
foreach($data in $objs)
{
$samaccountname = $data.properties['samaccountname'][0] + ''
$mail = $data.properties['mail'][0] + ''
$displayname = $data.properties['displayname'][0] + ''
}
when accessing the properties of the resultset you get a System.DirectoryServices.ResultPropertyValueCollection type for each property
to get a string value for passing to a database the property value access the zero index of the object
Related
I am trying to populate a list with the servers in my domain, and i have partial success. There are 5 items in my list, which is as many servers as i have.
Unfortunately they are all just called [Collection]
Form is generated with Sapien Powershell Studio
$strCategory = "computer"
$strOperatingSystem = "Windows*Server*"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = ("OperatingSystem=$strOperatingSystem")
$colProplist = "name"
foreach ($i in $colPropList) { $objSearcher.PropertiesToLoad.Add($i) }
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{
$objComputer = $objResult.Properties;
$objComputer.name
$checkedlistbox1.Items.add($objComputer.name)
}
What can I do to have the proper name show up in the checkedlist.
Thanks for any assistance :)
The result object from DirectorySearcher.FindAll() method contains a special property named Properties that returns a typed collection containing the values of properties of the object found in the AD.
This means that you can simply do
. . .
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults) {
$checkedlistbox1.Items.add($objResult.Properties['name'][0])
}
I suggest you use Get-ADComputer instead to get the list of your servers.
The you just loop thrue the list and add the servername to your checkedlist
$Servers= Get-ADComputer -Filter {OperatingSystem -Like 'Windows *Server*'} #-Property * #the property flag is not needed if you just want the Name (see comment from Theo)
foreach ($srv in $Servers) {
#Unmark to debug
#$srv.Name
#$srv.OperatingSystem
$checkedlistbox1.Items.add($srv.Name)
}
I have a powershell script that measures download time on some pages, however I get the error above, I am unsure what I am doing wrong
error is
Cannot bind argument to parameter 'InputObject' because it is null.
function ResponseTime($CommonName,$URL, $environment)
{
$Times = 5
$i = 0
$TotalResponseTime = 0
Write-HOst $URL
While ($i -lt $Times) {
$Request = New-Object System.Net.WebClient
$Request.UseDefaultCredentials = $true
$Start = Get-Date
Write-HOst $URL
$PageRequest = $Request.DownloadString($URL)
$TimeTaken = ((Get-Date) - $Start).TotalMilliseconds
$Request.Dispose()
$i ++
$TotalResponseTime += $TimeTaken
}
$AverageResponseTime = $TotalResponseTime / $i
Write-Host Request to $CommonName took $AverageResponseTime ms in average -ForegroundColor Green
$details = #{
Date = get-date
AverageResponseTime = $AverageResponseTime
ResponseTime = $Destination
Environment = $environment
}
$results += New-Object PSObject -Property $details
$random = Get-Random -minimum 1 -maximum 30
Start-Sleep -s $random
}
#PRODUCTION
ResponseTime -commonname 'app homepage' -URL 'https://url1' -environment 'PRODUCTION'
ResponseTime -commonname 'department homepage' -URL 'https://url2' -environment 'PRODUCTION'
$results | export-csv -Path c:\so.csv -NoTypeInformation
Reviewing your last edit, it seems that $results simply returns $null (As your error says)
The only line setting $results is $results += New-Object PSObject -Property $details
It is not in the scope of your Export-CSV call and - even if it would, $results could be empty, if this line is not called.
You should IMHO set it to e.g. an ArrayList like follows:
$results = New-Object -TypeName System.Collections.ArrayList
And add items to it via
$times = ResponseTime -commonname '' #etc
$results.Add($times) | Out-Null
This gives you an ArrayList - even if there are no items in it - which can easily be transformed to CSV and other formats.
#Clijsters has given the correct answer; i.e. the issue being the scope of your $results variable.
This answer just provides a bit of a code review to help you with other bits going forwards...
function Get-ResponseTime {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$CommonName
,
[Parameter(Mandatory = $true)]
[string]$URL
,
[Parameter(Mandatory = $true)]
[string]$Environment
,
[Parameter(Mandatory = $false)]
[int]$Times = 5
)
[System.Int64]$TotalResponseTime = 0
[System.Diagnostics.Stopwatch]$stopwatch = New-Object 'System.Diagnostics.Stopwatch'
Write-Verbose "Processing URL: $URL"
1..$times | foreach-object {
[System.Net.WebClient]$Request = New-Object 'System.Net.WebClient'
$Request.UseDefaultCredentials = $true
Write-Verboset "Call $_ to URL: $URL"
$stopwatch.Restart()
$PageRequest = $Request.DownloadString($URL)
$stopwatch.Stop()
$TimeTaken = $stopwatch.Elapsed.TotalMilliseconds
$Request.Dispose()
$TotalResponseTime += $TimeTaken
}
$AverageResponseTime = $TotalResponseTime / $Times
Write-Verbose "Request to $CommonName took $AverageResponseTime ms on average"
$details = #{
Date = get-date
AverageResponseTime = $AverageResponseTime
#ResponseTime = $Destination #this is not declared anywhere / don't know what this field's for
Environment = $environment
}
Write-Output (New-Object 'PSObject' -Property $details)
#do you really want a delay here? Doesn't make much sense... may make sense to include a delay in the above loop; i.e. to stagger your tests?
#$random = Get-Random -minimum 1 -maximum 30
#Start-Sleep -s $random
}
#PRODUCTION
[PSObject[]]$results = #(
(Get-ResponseTime -commonname 'app homepage' -URL 'https://url1' -environment 'PRODUCTION' -Verbose)
,(Get-ResponseTime -commonname 'department homepage' -URL 'https://url2' -environment 'PRODUCTION' -Verbose)
)
$results | Export-Csv -LiteralPath 'c:\so.csv' -NoTypeInformation
Use verb-noun function names (e.g. Get-Item). What is the naming convention for Powershell functions with regard to upper/lower case usage?
Use "Cmdlets" (Advanced Functions) instead of (Basic) Functions; they're basically the same thing, only tagged with [Cmdletbinding()]. The reason for this you get support for functionality such as verbose output. http://www.lazywinadmin.com/2015/03/standard-and-advanced-powershell.html
Use a stopwatch to time processes (you could also use measure-command; but any output would be suppressed / consumed by the measure-command function). Timing a command's execution in PowerShell
Have your cmdlet output its values to the pipeline via Write-Output (or you can leave off the function name; any output caused by placing a variable with nothing to process it will be fed to the pipeline; i.e. write-object $a is the same as a line solely consisting of $a).
Capture the output into your $results variable outside of the function, and handle the results there.
I am running into an issue where about 10% of computers on my network are throwing a very strange errors when processing. The error I get is "Where-Object : A Parameter cannot be found that matches paramter name 'Property'" the code I'm using is as follows.
#Create ADSI Search object to query Active Directory for usernames
#Start-Transcript -Path "$env:userprofile\Desktop\log.txt"
$strFilter = "objectCategory=user"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=SD25;DC=DC;DC=DC")
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 100000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
#Populate ADSI with the extra fields of samaccountname which is the username, and memberof which gives you roughly which groups they are a memberof
$colProplist = "samaccountname", "memberof"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
#Run the Search
$colResults = $objSearcher.FindAll()
#$colResults
$resultsarray = #()
#The way ADSI returns results, it populates all an array of every username listed within the scope, I then use this foreach recursive loop to find the name I need
foreach ($objResult in $colResults)
{
#Here I am taking each of the users, and finding the one which has the samaccountname of the user that is currently logged in
$objItem = $objResult.Properties | Where-Object -Property memberof -like ALL
#$groups = $objItem.memberof
#This is for diagnostics, if you output a logfile it will tell you the name and groups it is a member of
$objitem
}
#This is the beginnings of searching for a computer container in active directory.
$compFilter = "objectCategory=computer"
$compDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=OU;DC=DC;DC=DC")
$compSearcher = New-Object System.DirectoryServices.DirectorySearcher
$compSearcher.SearchRoot = $objDomain
$compSearcher.PageSize = 100000
$compSearcher.Filter = $strFilter
$compSearcher.SearchScope = "Subtree"
$compProplist = "name"
foreach ($i in $compPropList){$compSearcher.PropertiesToLoad.Add($i)}
$compResults = $compSearcher.FindAll()
foreach ($compR in $compResults)
{
}
#Stop-Transcript
IIRC -Property was introduced to Where-Object with PowerShell 3.0. Could you script be running on PowerShell 2.0?
Responding to comment
You need to create a filter script in the form of a scriptblock (i.e. PowerShell code in a set of braces) instead of using the comparison operator parameters they added for 3.0.
Try using
Where-Object { $_.memberof -like "ALL" }
or something like that. $_ refers to the current object in the pipeline. I couldn't find the docs for version 2.0 but I found Using the Where-Object Cmdlet for version 1.0 which was relevant for 2.0 AFAIK and should help you.
So at the moment I have some code which can lock down to a specific OU via ADSI in Powershell, loop through, and store them into an Array. In turn I loop through this and run a Test-Connection. I have my reasons...
Anyway, is it possible (using only inbuilt cmdlets, i.e. no Quest stuff) to recurse through the whole of AD and add all Computer Objects to the array?
$myArrayOfComputers = #()
$orgUnit = [ADSI]"LDAP://OU=foo,DC=foo,dc=co,dc=uk"
ForEach($child in $orgUnit.psbase.Children) {
if ($child.ObjectCategory -like '*computer*') { $myArrayOfComputers += $child.Name }
}
ForEach($i in $myArrayOfComputers) {
Test-Connection $i
}
In PowerShell V2.0 you can try :
Import-module ActiveDirectory
$computers = Get-ADComputer *
In PowerShell V1.0 You can try :
# dom.fr is the DNS root name of the domain
$dn = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://dom.fr:389/dc=dom,dc=fr","administrator#dom.fr","admin")
# Look for computers
$Rech = new-object System.DirectoryServices.DirectorySearcher($dn)
$Rech.filter = "((objectClass=computer))"
$Rech.SearchScope = "subtree"
$Rech.PropertiesToLoad.Add("sAMAccountName");
$Rech.PropertiesToLoad.Add("lastLogon");
$Rech.PropertiesToLoad.Add("distinguishedname");
$computers = $Rech.findall()
On V2 using .net:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement | out-null
$ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$pc = new-object 'System.DirectoryServices.AccountManagement.PrincipalContext'($ct, "foo.co.uk", "OU=foo,DC=foo,dc=co,dc=uk");
$cpp = New-Object 'System.DirectoryServices.AccountManagement.Computerprincipal'($pc)
$ps = new-object 'System.DirectoryServices.AccountManagement.PrincipalSearcher'
$ps.QueryFilter = $cpp
$MyListArray = $ps.FindAll() | select -expa name
Pretty straight forward question: I'm not sure why the "physicalDeliveryOfficeName" property is not showing up in my output. I've read that it is a non-standard property, but I have not been able to find a way to add it. Everything works perfectly except for the missing "physicalDeliveryOfficeName." Thanks for the help!
$Dom = 'LDAP://OU=XX;DC=XX;DC=local'
$Root = New-Object DirectoryServices.DirectoryEntry $Dom
$selector = New-Object DirectoryServices.DirectorySearcher
$selector.SearchRoot = $root
$selector.pagesize = 1000
$adobj= $selector.findall() | where {$_.properties.objectcategory -match "CN=Person"}
(Get-Content c:\FILENAME.txt) | Foreach-Object `
{ `
foreach ($person in $adobj){
$prop=$person.properties
if ($prop.cn -like "*" + $_.substring(1, 3) + "*")
{
$s1 = $_ -replace $_.substring(0, 4), $prop.cn
$s2 = $s1 -replace "AD_DEPT", $prop.department
$s3 = $s2 -replace "AD_BRANCH", $prop.physicalDeliveryOfficeName
add-content C:\FILENAME2.txt $s3
}
}
}
The AD_DEPT and AD_BRANCH are just placeholders in my original file.
EDIT
I read through JPBlanc's answer and did some more research and ended up with this working example. The key seems to be in specifying the properties to load. Thanks!
$strFilter = "(&(objectClass=Person)(department=*))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objOU = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=XX;DC=XX;DC=local")
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objOU
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "OneLevel"
$colProplist = "cn","department","physicaldeliveryofficename"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
$colResults = $objSearcher.FindAll()
remove-item \\SERVER\FTPROOT\FOLDER\FILENAME.MODIFIED
(Get-Content \\SERVER\FTPROOT\FOLDER\FILENAME) | Foreach-Object `
{ `
foreach ($person in $colResults){
$prop = $person.properties
if ($prop.cn -like "*" + $_.substring(1, 3) + "*")
{
$s1 = $_ -replace $_.substring(0, 4), $prop.cn
$s2 = $s1 -replace "AD_DEPT", $prop.department
$s3 = $s2 -replace "AD_BRANCH", $prop.physicaldeliveryofficename
add-content \\SERVER\FTPROOT\FOLDER\FILENAME.MODIFIED $s3
break
}
}
}
Much things have to be said there.
1. The presence of the attribute
For an attribute to be queried, it first must be present in the SCHEMA of you directory. SCHEMA defines types and attributes that directory entries can contain. In the schema this attribute has to be defined as "MAY be" or "MUST be " present in a type. For example objectClass attribute MUST be present in all types.
If I have a look in the schema of my Windows 2K8 R2, I can see your attribute :
Now if I use Apache Directory Studio I can see that physicalDeliveryOfficeName is present 12 types (11 on a normal server forget SlxAuteur)
Conclusion of this first part : You probably (if you have enough rights) set this attribute on a user or an inetOrgPerson.
2. The way you search an attribute
You'll find here under a sample of usage of a directory searcher. I add the code to modify physicalDeliveryOfficeName attribute on a specified user.
$dn = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://192.168.183.138:389/dc=societe,dc=fr","administrateur#societe.fr","blabla")
# Look for users
$Rech = new-object System.DirectoryServices.DirectorySearcher($dn)
$rc = $Rech.filter = "((objectCategory=person))"
$rc = $Rech.SearchScope = "subtree"
$rc = $Rech.PropertiesToLoad.Add("distinguishedName");
$rc = $Rech.PropertiesToLoad.Add("sAMAccountName");
$rc = $Rech.PropertiesToLoad.Add("ipphone");
$rc = $Rech.PropertiesToLoad.Add("telephoneNumber");
$rc = $Rech.PropertiesToLoad.Add("memberOf");
$rc = $Rech.PropertiesToLoad.Add("distinguishedname");
$rc = $Rech.PropertiesToLoad.Add("physicalDeliveryOfficeName"); # Your attribute
$liste = $Rech.findall()
foreach ($usr in $liste)
{
# Write-Host $usr.Properties["samaccountname"]
if ($usr.Properties["samaccountname"] -eq "massin")
{
Write-Host $usr.Properties["distinguishedname"]
$dnUser = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://192.168.183.138:389/$($usr.Properties["distinguishedname"])","administrateur#societe.fr","blabla")
$dnUser.put("physicalDeliveryOfficeName", "1 rue de la source")
$res = $dnUser.setinfo()
$res
}
}
Here is the result :
Remarks : a Directory search is
The node where begin the search
the attributes you want (it's not mandatory, but it's a best practice) if you no give them you CAN'T be sure that they are retrieved.
The depth (base, onelevel, subtree)
The filter
If an attribute is not queried or is empty, it will not be present in the result