I've tried this a few ways and have come very, very close but no cigar.
What I need help with is creating a report that shows the number of users in each OU. I know there are several ways to do this, but each method I've tried has given me varying degrees of usability. I would really appreciate some feedback on what I can do to improve my queries.
My first attempt was creating an object table, but unfortunately my knowledge of object tables isn't that great. The output was one large table of all the child OU's and their counts, but since a lot of our child OU's have the same name it wasn't any good. It may be possible to add another member on for the parent OU, but I'm not sure how to do that.
With this same attempt I also tried Out-Host and while this did break up the mass table into individual tables I was still stuck with the child OU name issue.
$OUName_ou = Get-ADOrganizationalUnit -filter * -searchbase ‘OU=Users,DC=business,DC=com’ -SearchScope 1
$OUName = #()
foreach ($ou in $OUName_ou) {
$Query = New-Object PSObject
$Query | Add-Member -membertype NoteProperty -Name OU_Name -Value “”
$Query | Add-Member -membertype NoteProperty -Name User_Count -Value “”
$Query.OU_Name = $ou.name
$users = get-aduser -Filter * -SearchBase $ou
$numb = $users | measure
$Query.User_Count = $numb.count
$OUName += $Query
}
$OUName
My next attempt was to try just for a list view, but the output is very muddled. It generates a list that shows the drill down for each OU, but since there are other items there I receive a long list with a lot of 0 counts. Because of the list view, and the number of items, the report is difficult to get relevant data from. If I could find a way to clean up the output it would work fine for my needs. Ideally there would be a way to modify this to exclude empty OU's and specific OU's (like test groups for example) then it would work well enough. I'm positive there is an if command I could use to do it, but so far I haven't found the right syntax.
$ous = Get-ADOrganizationalUnit -Filter * -SearchBase "ou=Users,ou=CMSG,dc=contoso,dc=com" | Select-Object -ExpandProperty DistinguishedName
$ous | ForEach-Object{
[psobject][ordered]#{
OU = $_
Count = (Get-ADUser -Filter * -SearchBase "$_").count
}
}
You could simply get the count value before outputting the PsCustomObject and only output that if the count is greater than 0
Something like:
$ous = Get-ADOrganizationalUnit -Filter * -SearchBase "ou=Users,ou=CMSG,dc=contoso,dc=com"
$result = $ous | ForEach-Object {
$count = (Get-ADUser -Filter * -SearchBase $_.DistinguishedName).Count
if ($count) {
[PsCustomObject]#{
OU = $_.DistinguishedName
Users = $count
}
}
}
# output as table on screen
$result | Format-Table -AutoSize
# output to CSV you can open in Excel
$result | Export-Csv -Path 'D:\Test\UserCount.csv' -UseCulture -NoTypeInformation
Use [PsCustomObject] here, because it is ordered by default
Related
I have this portion of code here that has worked in the past in multiple AD environments, however after testing within a new AD environment, I am getting no output to CSV or any errors being thrown. The size of the CSV file is always zero.
if (![string]::IsNullOrEmpty($searchbase))
{
$ADComputers = get-adcomputer -searchBase $searchbase -filter * -properties * -ResultPageSize $resultpagesize
}
else
{
$ADComputers=Get-ADComputer -Filter * -Properties * -ResultPageSize $resultpagesize
}
$data = #()
foreach ($computer in $ADComputers) {
$computer.member| foreach-object {$members += $_}
$computer.memberof | foreach-object {$memberof += $_}
$memstr = ($members -join ";")
$memstr2 = ($memberof -join ";")
$ADcomp = Get-ADComputer $computer -properties logonCount, ManagedBy | select-object logonCount, ManagedBy
$row = New-Object -TypeName psobject -Property #{
PrincipalID = $script:ctr;
logonCount=$ADcomp.logonCount;
ManagedBy=$ADcomp.ManagedBy;
}
$data += $row
$script:ctr++
}
$data | Export-Csv "ADComputers.csv" -NoTypeInformation
I'm not sure exactly where to go from here because I have tested multiple different options, so any help would be greatly appreciated!
The only reason you get no output is that $ADComputers has no elements. This may be related to a value in the variable $searchbase that does not exist or simply has no computer accounts in it.
But here are some general recommendations:
you do:
if (![string]::IsNullOrEmpty($searchbase))
you could also do:
If ($searchbase)
In principle if you have different scenarios to cover and so the parameter may change take a look at splatting.
Then you query all computers with all available properties but later in the loop you query the specific computer again which is not necessary. Also you should avoid adding elements to an array by using +=, this causes to rebuild the array each time which is slow.
Furthermore $computer.memberof is already an array holding the information but you pipe it to foreach and build a new array holding the identical information only to join it later to a string.
If this is not part of function I don't know why you raise the scope of the variable $ctr from local to script think this is not necessary.
Putting this all together you could do:
#Define HashTable for splatting
$parametersHt = #{
filer='*'
properties='*'
resultsetpagesize=$resultpagesize
}
#If searchbase is specified add parameter to splatting HashTable
If ($searchbase){
$parametersHt.add('Searchbase',$searchbase)
}
#Get computers
$ADComputers = get-adcomputer #parametersHt
#Set Counter
$ctr = 0
$data = #(
foreach ($computer in $ADComputers){
$ctr++
[PSCustomObject]#{
PrincipalId = $ctr #Really the counter here - not $computer.samaccountname?
logonCount=$computer.logonCount
manageddBy=$computer.ManagedBy
memberof=($computer.memberof -join ";") #added this, but in your code sample you don't return this value, if not needed remove. btw. a computer can be memberof a group but it can't have members
}
}
)
$data | Export-Csv -path ".\ADComputers.csv" -NoTypeInformation
I have a fairly simple script that needs to check around 20,000 AD Groups for their membership count. That all works fine, I can take the list of groups run it through the script and for the most entries it works fine. However I was getting some errors that I couldn't figure out and hopefully someone here can point me in the right direction.
I am using the DN of the object to query AD and for around 10% it fails, but when I copy the DN from the file, paste it into a command window and run the command manually it works fine. Some more checking and it seems that when I read an offending line into my variable there is a line break in the middle for some reason.
When looking at the value of the variable I get the following:
Working Example - "CN=ABC, OU=Location, OU=Distribution Lists, DC=Domain, DC=COM"
Error Example - "CN=ABC, OU=Location, OU=Distribution
Lists, DC=Domain, DC=COM"
It seems to insert a return in-between Distribution and Lists on certain entries in the file. I have tried deleting the character in-between and replacing it with a space but I get the same result.
Could it be the length? I am still looking for a common factor but any suggestions would be great.
Thanks
Updated with requested content.
$Groups = Import-Csv C:\Temp\DLName.csv
write-host ($Groups).Count
$i=1
foreach ($Group in $Groups)
{
$GroupInfo = Get-ADGroupMembersRecursive -Groups $Group.Name
$MembersCount = ($GroupInfo | Measure-Object).Count
$MembersList = $GroupInfo | Select Name -ExcludeProperty Name
$FriendlyName = Get-ADGroup -Identity $Group.Name
$Export = $FriendlyName.Name + ", " + $MembersCount
$Export | Out-File C:\Temp\DLMembers.csv -Append
Write-host $FriendlyName "," $MembersCount
$i
$i++
}
Entry 1 and 3 work 2 doesn't, but the formatting here seems to have wrapped the entries.
Name
"CN=Company - DL Name1,OU=Country1 Distribution Lists,OU=Europe,OU=Acc,DC=Domain,DC=Domain,DC=com"
"CN=Company - DL Name2,OU=Country2 Distribution Lists,OU=Europe,OU=Acc,DC=Domain,DC=Domain,DC=com"
"CN=Company - DL Name3,OU=Country3 Distribution Lists,OU=America,OU=Acc,DC=Domain,DC=Domain,DC=com"
Top pic is the failure second pic works.
List Creation:
$SearchScope = "OU=OUName,DC=Domain,DC=Domain,DC=com"
$SearchFilter = {GroupCategory -eq 'Distribution'}
$Groups = Get-ADGroup -SearchBase $SearchScope -Filter
$SearchFilter | Sort-Object Name
foreach ($Group in $Groups)
{
$Group.DistinguishedName | Select Name -ExpandProperty Name
$Group.DistinguishedName | Out-File C:\Temp\DLName.csv -Append
}
Do not use a self-combined comma separated string and Out-File to create CSV files, because that will get you into trouble when fields happen to contain the delimiter character like in this case the comma (which will lead to mis-aligned data).
Your List Creation code should be like this:
$SearchBase = "OU=OUName,DC=Domain,DC=Domain,DC=com"
$SearchFilter = "GroupCategory -eq 'Distribution'"
Get-ADGroup -SearchBase $SearchBase -Filter $SearchFilter |
Sort-Object Name | Select-Object Name, DistinguishedName |
Export-Csv -Path 'C:\Temp\DLName.csv' -NoTypeInformation
Then you can use that csv later to do:
$Groups = Import-Csv -Path 'C:\Temp\DLName.csv'
Write-Host $Groups.Count
$result = foreach ($Group in $Groups) {
$GroupInfo = Get-ADGroupMember -Identity $Group.DistinguishedName -Recursive
# unnecessary.. $MembersCount = ($GroupInfo | Measure-Object).Count
# unused.. $MembersList = $GroupInfo.Name
# unnecessary.. $FriendlyName = Get-ADGroup -Identity $Group.Name
# output an object with the wanted properties
[PsCustomObject]#{
GroupName = $Group.Name
MemberCount = #($GroupInfo).Count # #() in case there is only one member in the group
}
}
# show on screen
$result | Format-Table -AutoSize
# output to CSV file
$result | Export-Csv -Path 'C:\Temp\DLMembers.csv' -NoTypeInformation
As you can see, I'm not using your custom function Get-ADGroupMembersRecursive because I have no idea what that outputs.. Also, there is no need for that because you can use the Get-ADGroupMember cmdlet with the -Recursive switch added
We have an Active Directory with 5 millions of users. We're getting the error "Get-ADUser : This operation returned because the timeout period expired" when trying to extract users using powershell script.
Already tried searching the web for an optimized script. Below is what we have. This works fine for ~500k users.
Import-Module ActiveDirectory
$Users = Get-ADUser -SearchBase "CN=Users,DC=*****,DC=*****,DC=*****" -Server "*****" -ResultPageSize 1 -LDAPFilter "(&(objectCategory=User)(whenCreated>=20190101000000.0Z)(whenCreated<=20190131235959.0Z))" -Properties WhenCreated | Select-Object Name, WhenCreated
$Users | Export-Csv C:\Temp\January2019.csv -NoTypeInformation
Get-ADUser and all the other cmdlets that PowerShell makes available to you are convenient, but horrible when it comes to performance.
You're better off using .NET's DirectorySearcher, which PowerShell has a short-hand for: [ADSISearcher]. It's more code, yes, but it's much faster. Here's an example that should do what you want (make sure to change the first two lines for your OU and server):
$server = "****"
$ou = "CN=Users,DC=*****,DC=*****,DC=*****"
$searcher = [ADSISearcher]"(&(objectCategory=User)(whenCreated>=20190101000000.0Z)(whenCreated<=20190131235959.0Z))"
$searcher.PropertiesToLoad.Add("whenCreated") #We only want the whenCreated attribute
$searhcer.PageSize = 200 #Get the users in pages of 200
$searcher.SearchRoot = [ADSI]"LDAP://$server/$ou"
$ADObjects = #()
foreach($result in $searcher.FindAll()) {
#The SearchResultCollection doesn't output in PowerShell very well, so here we create
#a PSObject for each results with the properties that we can export later
[Array]$propertiesList = $result.Properties.PropertyNames
$obj = New-Object PSObject
foreach($property in $propertiesList) {
$obj | add-member -membertype noteproperty -name $property -value ([string]$result.Properties.Item($property))
}
$ADObjects += $obj
}
$ADObjects | Export-Csv C:\Temp\January2019.csv -NoTypeInformation
Thanks for all your help. Instead of using Get-ADUser, I was able to extract users using CSVDE (an LDIFDE variant that allows exporting data to CSV files) without any problem.
CSVDE -f D:\Temp\ADUseru.csv -d "CN=Users,DC=*****,DC=*****,DC=*****" -r "(&(objectClass=user)(objectCategory=person)(whenCreated>=20120101000000.0Z)(whenCreated<=20121231235959.0Z))" -l "name, whenCreated, memberOf, sAMAccountName"
I am trying to create a script that will check a list of user names and show the user full name and some attribute settings from AD. Basically I have been sent a list of usernames which are just numbers and management want to know the users full name for each username. they also want to know want division they work for.
Below is the script I have created which doesn't work.
$csv = Import-Csv "C:\temp\users.csv"
foreach ($user in $csv) {
$name = $user.myid
Get-ADUser -Filter {EmployeeID -eq $name} -Properties * |
Get-ADUser -Division $user.Programme
} | Export-Csv "C:\Temp\Results.csv"
So I'm working under the assumption that there is a column named myid in your csv file that contains the id you need to be looking up. Assuming that is the case you'll need to make a few changes here. You'll need to remove the second get-aduser as it is not really doing anything for you, and there is no -division switch available to the get-aduser cmdlet, if you need to restrict your results to just a few settings you can do that using the -properties switch and piping to select as shown below. Keep in mind that none of this will matter if the users do not have the "employeeid" and "division" properties set on their AD accounts, which is fairly rare in my experience but if your company does as a matter of policy when creating accounts should be fine. If you replace the get-aduser line in your script with this it should get the account of any user with an EmployeeID property that matches the one in your spreadsheet and then output that person's full name, division, and employeeid to your CSV file.
Get-ADUser -Filter {EmployeeID -eq $name} -Properties "displayname","division","employeeid" | Select-Object "employeeid","displayname","division"
When in doubt, read the documentation. Get-ADUser doesn't have a parameter -Division. You need to select the properties you want in the output file. Also, foreach loops don't pass output into the pipeline. You need a ForEach-Object loop if you want to pass the output directly into Export-Csv:
Import-Csv 'C:\temp\users.csv' |
ForEach-Object {
$name = $_.myid
Get-ADUser -Filter "EmployeeID -eq $name" -Properties *
} |
Select-Object SamAccountName, DisplayName, Division |
Export-Csv 'C:\Temp\Results.csv' -NoType
Otherwise you need to collect the output in a variable:
$users = foreach ($user in $csv) {
$name = $user.myid
Get-ADUser -Filter "EmployeeID -eq $name" -Properties *
}
$users | Export-Csv 'C:\Temp\Results.csv' -NoType
or run the loop in a subexpression:
$(foreach ($user in $csv) {
$name = $user.myid
Get-ADUser -Filter "EmployeeID -eq $name" -Properties *
}) | Export-Csv 'C:\Temp\Results.csv' -NoType
This is a generic code structure that can be adapted for data collection / enumeration and production of CSV files, tailored to your scenario. We use similar at my workplace. It contains some error handling - the last thing you'd want is inaccurate results in your CSV file.
# Create an array from a data source:
$dataArray = import-csv "C:\temp\users.csv"
# Create an array to store results of foreach loop:
$arrayOfHashtables = #()
# Loop the data array, doing additional work to create our custom data for the CSV file:
foreach($item in $dataArray)
{
try
{
$ADObject = Get-ADUser -Filter { EmployeeID -eq $item.MyID } -Properties DisplayName,Division -ErrorAction Stop
}
catch
{
Write-Output "$($item.MyID): Error looking up this ID. Error was $($Error[0].Exception.Message)"
}
if($ADObject)
{
# Create a hashtable to store information about a single item:
$hashTable = [ordered]#{
EmployeeID=$item.myID
DisplayName=$ADObject.DisplayName
}
# Add the hashtable into the results array:
$arrayOfHashtables += (New-Object -TypeName PSObject -Property $hashTable)
}
else
{
Write-Output "$($item.MyID): No result found for this ID."
}
}
# If the results array was populated, export it:
if($arrayOfHashtables.Count -gt 0)
{
$arrayOfHashtables | Export-CSV -Path "C:\Temp\Results.csv" -Confirm:$false -NoTypeInformation
}
As mentioned elsewhere, division isn't a property on an AD object so you might need to lookup this data elsewhere. If you can do that with another line of PowerShell inside your foreach loop, you could add this to your hashtable object like so:
$hashTable = [ordered]#{
EmployeeID=$item.myID
DisplayName=$ADObject.DisplayName
Division=$DivisionFromOtherSource
}
I am running a simple script to list all of my domain users, the last time they logged on, whether or not their accounts are enabled, and what groups they are a member of.
I have two problems. One, the lastLogon value is not a date/time format. Two, more importantly, the MemberOf column lists everything about the group object, when all I really want is the CN value. Moreover, for users in several groups, some of the information is truncated.
How do I fix this?
This is the script I am running:
Get-AdUser -Filter * -Properties Name,Description,Enabled,lastLogon,MemberOf | FT Name,Description,Enabled,lastLogon,MemberOf -autosize | format-list | out-string -width 4096 | Out-File C:\test.txt
So, first I would suggest outputting the info as a CSV rather than try to get it out into a text file. Trying to get all the accounts on one line is very impractical. For instance my user object is in 133 different groups in my environment.
Second, I would give up on making this a one liner and use an actual script to build custom objects that you can the export to CSV. Here is some top of the head code to get you going in the right direction. I'll make it actual functional tested code later if I have a chance.
$results = #()
$users = Get-ADUser -Filter * -Properties Name,Description,Enabled,lastLogon,MemberOf
foreach($user in $users){
$lastlogon = [datetime]::FromFileTime($user.lastlogon)
foreach($group in $user.MemberOf){
$temp = New-Object PSCustomObject -Property #{'Name' = $user.Name;
'Description' = $user.Description;
'Enabled' = $user.Enabled;
'LastLogon' = $lastlogon;
'Group' = '';
}
$temp.Group = $group.Split(',')[0]
$results += $temp
}
}
$results | Export-CSV C:\Temp\SomeFile.csv -NoTypeInformation
Tested it and that code worked for me.