User & Group Audit Script - powershell

I am looking for help writing a powershell script that will query Active Directory and output a CSV.
This script will list all groups and all users and signify with a character when a user belongs to that group.
The output will look like this: https://imgur.com/1MfFv7Q
I've tried using dsquery and various other powershell methods, but none seem to work.
I'm hoping someone here will have a different perspective on this and be able to help out.
Thank you!
Update 1:
As requested, here's my code that I was trying to work with previously.
#Get a list of the groups
$groups = Get-ADGroup -filter * -Properties Name | Select Name
#iterate through groups array and append each with a comma
$output = ForEach ($g in $groups){
$topgroups.Add($g)
$topgroups.Add(",")
}
#for each group, find out if the user is part of that group
$output = ForEach ($g in $groups) {
$results = Get-ADGroupMember -Identity $g.name -Recursive | Get-ADUser -Properties enabled, SamAccountName, givenname, surname,physicalDeliveryOfficeName
ForEach ($r in $results){
New-Object PSObject -Property #{
GroupName = $g.Name
Username = $r.name
DisplayName = $r.displayname
}
}
}
$output | Export-Csv -path c:\temp\output.csv -NoTypeInformation
Update 2:
Added FTP Upload and some more information. Thanks again TheMadTechnician!
My goal is to get this information from each of my clients, import this into SQL with SSIS with a timestamp, and then I can do can do comparison through sql reporting.
Here's my script where it is currently:
New-Item c:\temp\audit -type directory
$Domain = (gwmi WIN32_ComputerSystem).Domain
$filename = $Domain + "_ADExport.csv"
$fileoutput = "c:\temp\audit\" + $filename
Remove-Item $fileoutput
$GroupRef = #{}
Get-ADGroup -filter * | ForEach{$GroupRef.Add($_.DistinguishedName,$_.Name)}
$Users = Get-ADUser -Filter * -Prop MemberOf, passwordlastset, LastLogonDate
ForEach($User in $Users){
$LineItem = [PSCustomObject]#{'Enabled'=$User.Enabled;'First Name'=$User.givenname;'Last Name'=$User.surname;'Location'=$User.physicalDeliveryOfficeName;'Domain'=$Domain;'SAMAccountName'=$User.samaccountname;'LastLoggedOn'=$User.lastlogonDate;'PasswordLastSet'=$User.passwordlastset}
$GroupRef.Values | ForEach{Add-Member -InputObject $LineItem -NotePropertyName $_ -NotePropertyValue ""}
$User.MemberOf | ForEach{$LineItem.$($GroupRef["$_"]) = "X"}
[Array]$Results += $LineItem
}
$Results|export-csv $fileoutput -notype
#we specify the directory where all files that we want to upload
$Dir="C:/temp/audit/"
#ftp server
$ftp = "ftp://8.8.8.8/"
$user = "test"
$pass = "ThisIsARea11yL0NgPa33Word"
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
#list every file
foreach($item in (dir $Dir "*.csv")){
"Uploading $item..."
$uri = New-Object System.Uri($ftp+$item.Name)
$webclient.UploadFile($uri, $item.FullName)
}
Update 3:
Good afternoon:
I've run into an issue where I am trying to restrict which OU this searches through:
$GroupRef = #{}
$OUPATH = (Get-ADOrganizationalUnit -Filter 'Name -like "CLIENT_GROUPS"' | FT DistinguishedName -HideTableHeaders | Out-String).Trim()
Get-ADGroup -SearchBase "$OUPATH" -Filter * | ForEach{$GroupRef.Add($_.DistinguishedName,$_.Name)}
The error is:
Exception setting "": "Cannot process argument because the value of argument "name" is not valid. Change the value of
the "name" argument and run the operation again."
At C:\Users\f12admin\Desktop\test.ps1:23 char:42
+ $User.MemberOf | ForEach{$LineItem.$($GroupRef["$_"]) = "X"}
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting

All you need are Get-ADUser, Get-ADGroup, New-Object, Add-Member, and Export-CSV. I'd build a hashtable of groups linking their distinguishedname and their displayname. Then I'd get a list of all users, create a custom object for each user, loop through the list of groups and add a property to the custom object for each group. Then loop through the user's MemberOf property and set the associated property on the custom object to "X" for everything there. Collect all of the custom objects in an array, and export it to a csv.
This isn't tested, but here's the theory...
$GroupRef = #{}
Get-ADGroup -filter * | ForEach{$GroupRef.Add($_.DistinguishedName,$_.Name)}
$Users = Get-ADUser -Filter * -Prop MemberOf
ForEach($User in $Users){
$LineItem = [PSCustomObject]#{'DisplayName'=$User.DisplayName;'SAMAccountName'=$User.samaccountname}
$GroupRef.Values | ForEach{Add-Member -InputObject $LineItem -NotePropertyName $_ -NotePropertyValue ""}
$User.MemberOf | ForEach{$LineItem.$($GroupRef["$_"]) = "X"}
[Array]$Results += $LineItem
}
$Results|export-csv c:\temp\output.csv -notype

Related

Powershell variable issue Get-Content and Get-ADGroup

I'm trying to figure out the reason why I can run the script using variable $groups with Get-Content but it wont work if variable $groups goes with Get-ADGroup list I did below...
Block that works:
$groups = Get-Content C:\groups.csv
$results = #()
$file = "C:\Usuarios_Grupos_Darwin_AD.csv"
foreach($Group in $Groups) {
$results +=Get-ADGroupMember -Id $Group -Recursive | %{Get-ADUser -Identity $_.SamAccountName -Properties Enabled,Name} | Select #{Expression={$Group};Label=”Group Name”},SamAccountName,Name,Enabled
}
$results | export-csv -notypeinformation -Delimiter ";" -path $file
Block that's not working:
(only the first line has been changed)
$groups = Get-ADGroup -Filter {Name -like '*Darwin*'} -Properties * | select -property Name
$results = #()
$file = "C:\Usuarios_Grupos_Darwin_AD.csv"
foreach($Group in $Groups) {
$results +=Get-ADGroupMember -Id $Group -Recursive | %{Get-ADUser -Identity $_.SamAccountName -Properties Enabled,Name} | Select #{Expression={$Group};Label=”Group Name”},SamAccountName,Name,Enabled
}
$results | export-csv -notypeinformation -Delimiter ";" -path $file
Here is the error:
Get-ADGroupMember : Cannot bind parameter 'Identity'. Cannot create object of type "Microsoft.ActiveDirectory.Management.ADGroup". The adapter cannot set the value of property
"Name".
At line:11 char:34
+ $results +=Get-ADGroupMember -Id $Group -Recursive | %{Get-ADUser -Id ...
+ ~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ADGroupMember], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember
I'm trying to embed the output list all in one script without having to generate csv with another script.
Thanks in advance !!
A few notes about your code:
the -Filter parameter should be a string, not a scriptblock
using $results += is very costly because the entire array needs to be rebuilt in memory on each addition
Get-ADGroupMember can return also computer and (when not used with -Recursive) also group objects, not just users, so you cannot pipe directly to Get-ADUser
never use -Properties * if all you want is one single property
Try this:
# Get-ADGroup already returns objects with these properties:
# DistinguishedName, GroupCategory, GroupScope, Name, ObjectClass, ObjectGUID, SamAccountName, SID
$groups = Get-ADGroup -Filter "Name -like '*Darwin*'"
$file = "C:\Usuarios_Grupos_Darwin_AD.csv"
# let PowerShell collect the objects for you instead of using +=
$results = foreach($Group in $Groups) {
# Get-ADGroupMember can return objects of type users and computers (also groups when used without -Recursive)
# so filter the result to get only user objects
$Group | Get-ADGroupMember -Recursive | Where-Object { $_.objectClass -eq 'user' } | ForEach-Object {
$_ | Get-ADUser | Select #{Name = 'Group Name'; Expression={$Group.Name}}, SamAccountName, Name, Enabled
}
}
$results | Export-Csv -Path $file -NoTypeInformation -Delimiter ";"

Search for specific entry in Get-Aduser - directreports

I have a list of user samaccount names that I need query for the manager attribute. Then I need to query the results (DN) of the manger account for the accounts directs reports, there will be multiple on each account. Then I need to search for direct report that contains "(admin)" in it (in the DN). I need that full DN and none that dont match (there should only be one)
My input is currently a get-aduser for testing. Everything works up to the "where"
I think I'm very close I just cant seem to get the last step
Get-Aduser -filter {cn -like "ea8f"} -Properties directreports -server domain.com |
select-Object -ExpandProperty directreports | Where $_.directreports -Contains "(admin)"
Where-Object : Cannot validate argument on parameter 'Property'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:2 char:57
+ ... Object -ExpandProperty directreports | Where $_.directreports -Contai ...
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Where-Object], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.WhereObjectCommand
Ok, I have not tested this at the moment, but I think this may work:
# read the users SamAccountNames from the file and loop through
$result = Get-Content -Path 'THE LIST.txt' | ForEach-Object {
# get the Manager attribute
$managerDN = Get-ADUser -Identity $_ -Properties Manager | Select-Object -ExpandProperty Manager
if ($managerDN) {
# if set, use Get-ADUser on the manager DN and try and find the DirectReports that contain the string "(admin)"
Get-ADUser -Identity $managerDN -Properties DirectReports |
Select-Object -ExpandProperty DirectReports |
Where-Object { $_ -match '\(admin\)'} |
ForEach-Object {
# output this DN
$directReports
}
}
}
# output to console
$result
This is what ended up working for me 100%, thanks to everyone who contributed I used something from everyone.
$input = Get-Content -Path $path
$results = #()
#Process each account
ForEach ($user in $input)
{
$current = get-aduser -Filter {samaccountname -like $user} -server $server -Properties Directreports |
Select-Object -ExpandProperty DirectReports |
Where-Object { $_ -match '\(admin\)'}
$dr = New-Object -TypeName PSObject
$dr | Add-Member -MemberType NoteProperty -Name SAM -Value $user
$dr | Add-Member -MemberType NoteProperty -Name Password -Value "z,7'*FBU+"
$dr | Add-Member -MemberType NoteProperty -Name TargetOU -Value $current.Substring($current.IndexOf('OU='))
$dr
$results += $dr
}
$results

ADAccount inactive accounts piping through ADUser issue

I did some PowerShell script to find inactive users in AD that are 90 days old looping through all DCs to also get LastLogon attribute. I also need some extra attributes that only ADUser can bring out. I've got an error when running my script with the piping in the $users = Search-ADAccount line.
Import-Module ActiveDirectory
function Get-ADUsersLastLogon() {
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$OUs = #()
$OU += "ou=Users-A,ou=Users,ou=Items,dc=mydc,dc=com"
$OU += "ou=Users-B,ou=Users,ou=Items,dc=mydc,dc=com"
$time = 0
$exportFilePath = "c:\tmp\lastLogon-test $(get-date -f dd-MM-yyyy).csv"
$columns = "name;username;whencreated;whenchanged;DNname;datetime"
#InactiveTest
$InactiveFilter = #{
UsersOnly = $true
AccountInactive = $true
TimeSpan = New-Timespan -Days 90
}
#EndInactiveTest
Out-File -FilePath $exportFilePath -Force -InputObject $columns
foreach ($OU in $OUs) {
$users = Search-ADAccount #InactiveFilter |
Get-ADUser -Filter * -SearchBase $OUs -Property displayName, whenCreated, whenChanged
foreach ($user in $users) {
foreach($dc in $dcs) {
$hostname = $dc.HostName
$currentUser = Get-ADUser $user.SamAccountName |
Get-ADObject -Server $hostname -Properties lastLogon
if ($currentUser.LastLogon -gt $time) {
$time = $currentUser.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
$row = $user.displayName + ";" + $user.SamAccountName + ";" +
$user.whenCreated + ";" + $user.whenChanged + ";" +
$user.distinguishedName + ";" + $dt
Out-File -FilePath $exportFilePath -Append -NoClobber -InputObject $row
$time = 0
}
}
}
Get-ADUsersLastLogon
I think iterating through DC's and OU's and then collecting only the inactive users last logon dates could best be done using a Hashtable object as intermediate storage.
This helps avoiding duplicate entries and gives the opportunity to compare the LastLogonDate properties.
For the final output, it uses one single cmdlet called Export-Csv.
Below my (untested) code:
function Get-ADUsersLastLogon {
# get your ad domain
$DomainName = (Get-ADDomain).DNSRoot
# get all DC hostnames as string array
$DCs = Get-ADDomainController -Filter * -Server $DomainName | Select-Object -ExpandProperty Hostname
# create an array of OU distinghuished names used as SearchBase
$OUs = "OU=Users-A,OU=Users,OU=Items,DC=mydc,DC=com", "OU=Users-B,OU=Users,OU=Items,DC=mydc,DC=com"
$exportFilePath = "c:\tmp\lastLogon-test $(Get-Date -Format dd-MM-yyyy).csv"
$InactiveFilter = #{
UsersOnly = $true
AccountInactive = $true
TimeSpan = New-Timespan -Days 90
}
# use a lookup Hashtable to eliminate duplicates and collect only the latest logon dates
$lookup = #{}
# loop through the list of dc's
foreach ($dc in $DCs) {
# loop through the list of OU's
foreach ($ou in $OUs) {
$users = Search-ADAccount #InactiveFilter -SearchBase $ou -Server $dc
foreach($user in $users) {
# get the properties we want from the AD User.
# using the PowerShell property names, we get the dates already converted into DateTime objects.
$usr = Get-ADUser -Identity $user.DistinguishedName -Server $dc -Properties DisplayName, Created, Modified, LastLogonDate |
Select-Object #{Name = 'Name'; Expression = {$_.DisplayName}},
SamAccountName,
#{Name = 'WhenCreated'; Expression = {$_.Created}},
#{Name = 'WhenChanged'; Expression = {$_.Modified}},
#{Name = 'DistinguishedName'; Expression = {$_.DistinguishedName}},
#{Name = 'LastLogon'; Expression = {$_.LastLogonDate}}
if ($usr) {
if ($lookup.ContainsKey($($user.DistinguishedName))) {
# we have collected this user before
$lastLogon = $lookup[$($user.DistinguishedName)].LastLogon
if ($lastLogon) {
if (($usr.LastLogon) -and $lastLogon -lt $usr.LastLogon) {
# only store this new instance if the $user.LastLogon property is of a later date
$lookup[$($user.DistinguishedName)] = $usr
}
}
}
else {
# this is a new user, so add the object to the HashTable
$lookup[$($user.DistinguishedName)] = $usr
}
}
else {
# should never happen..
Write-Warning "User $($user.SamAccountName) not found."
}
}
}
}
# export the objects contained in the $lookup Hashtable as CSV
($output = foreach ($key in $lookup.Keys) {
$lookup.$key
}) | Export-Csv -Path $exportFilePath -NoTypeInformation -Delimiter ';' -Encoding UTF8 -Force
}
Hope that helps
#voilier Sorry, I don't understand how it works for you. Pasted your code and Get-ADUser cmdlet expects filter value. If you use get-help get-aduser -full you will see that searchbase parameter can only be used with Filter or LDAPFilter parameters. More than that neither of them accept pipeline input. Identity parameter accepts pipeline input by value only. so you need to use the distinguishedname property from Search-ADAccount #InactiveFilter for example distinguishedname and pass it to filter
$users = Search-ADAccount #InactiveFilter | %{Get-ADUser -filter {distinguishedname -eq $_.distinguishedname} -SearchBase $OU -Property displayName, whenCreated, whenChanged}
I replaced your $users=... part with the code above and now I see no errors and CSV file created successfully.
Replace your foreach $ou in $ous with this and check the csv file. it works on my computer
Foreach ($ou in $ous){
$users = (Search-ADAccount #InactiveFilter | %{Get-ADUser -filter {distinguishedname -eq $_.distinguishedname} -SearchBase $OU -Property displayName, whenCreated, whenChanged})
foreach ($user in $users) {
foreach($dc in $dcs) {
$hostname = $dc.Name
$last_logon_time=((Get-ADUser $user.SamAccountName | Get-ADObject -Server "$hostname" -Properties lastLogon) |?{$_.lastlogon -gt $time}) | select -ExpandProperty lastlogon
}
$dt = [DateTime]::FromFileTime("$last_logon_time")
$row = $user.displayName + ";" + $user.SamAccountName + ";" +
$user.whenCreated + ";" + $user.whenChanged + ";" +
$user.distinguishedName + ";" + $dt
Out-File -FilePath $exportFilePath -Append -NoClobber -InputObject $row
$last_logon_time = 0
}
}
I hope it helps you

Get-adgroupmember and Displaying nested group name and its members in and exported to CSV file

I found this script online. It was original designed to get all members of one security group and if there are nested group it will write to the host the nested group name and members in hierarchy form.
I tweaked it to import AD security groups from a CSV file and to export the results to CSV with table format. CSV files has two security group with both security groups has nested groups. Script will only list the users in the second security group and it doesn't list the nested security group.
CSV File format:
Groupname groupad name
test.testdl office\test.testdl test.testdl
test.testsg office\test.testsg test.testsg
Import-Module ActiveDirectory
$GroupList = #{}
$Table = #()
$Record = #{
"Name" = ""
"nested" = ""
"domain" = ""
"userName" =""
}
function Get-GroupHierarchy {
param()
$searchGroups = Import-Csv -Path C:\temp\ad1.csv
foreach ($item in $searchGroups) {
$groupMember = Get-ADGroupMember -Identity $item.Groupname |
Select-Object name, samaccountname, distinguishedName, objectClass
}
}
foreach ($member in $groupMember) {
$username = $member.samaccountname
$distinguishedName = $member.distinguishedName
$dc = [regex]::Match($distinguishedName,'DC=([^,|$]+)').Groups[1].Value
$domainuser = '{0}\{1}' -f $dc, $username
$Record."userName" = $member.samaccountname
$Record."Name" = $member.name
$Record."nested" = $member.objectclass
$Record."Domain" = $domainuser
$objRecord = New-Object PSObject -Property $Record
$Table += [array]$objrecord
if ($member.ObjectClass -eq "group") {
$GroupList.add($member.name, $member.name)
Get-GroupHierarchy $member.name
}
Get-GroupHierarchy
}
$Table | Export-Csv "C:\temp\SecurityGroups01.csv" -NoTypeInformation
Error message:
Get-ADGroupMember : Cannot validate argument on parameter 'Identity'. The
argument is null or empty. Provide an argument that is not null or empty, and
then try the command again.
At line:1 char:48
+ $groupMember = Get-ADGroupMember -Identity $item.name | Select-Object name, ...
+ ~~~~~~~~~~
I Know it has been ages since you asked this question. But i was working last week on something similar and obtained some results through some work. I saw this question here working on that piece of work and thought to share my work if it can help somebody.
$members = Get-ADGroupMember 'GroupName'
foreach ($member in $members){
if ($member.objectClass -eq 'Group')
{$NestGroupUsers = Get-ADGroupMember $member | select name, objectclass }
Else {
$hash = [pscustomobject]#{
'name' = $member.name
'objectclass' = $member.objectClass
}
$hash | Export-Csv C:\users.csv -Append -NoTypeInformation
}
}
$NestGroupUsers |Export-Csv C:\users.csv -Append -NoTypeInformation

Output department and direct reports

I am trying to create an "initial" text file that will hold a script run of all users + department + direct reports. My next step after making this file is to create another file the same way but compare it to the original to see if the department for the users ever changed. (not sure yet how to compare the department value just yet)
My current issue is that the department, even though the process is identical to another program I have made in the past, won't print it. Furthermore, when it prints my direct reports it prints only the first one with the whole extension of CN=..., OU=... etc.
I want it to print this way:
username | Department(extensionAttribute14) | Direct Reports (as a single string)
we38432 | IT-Security | cm03456: 04555a: ....etc
My original script used this code for department:
$deps = Get-Aduser -filter {name -like *} -Properties name, extensionAttribute14 | Select name, extensionAttribute14 | Export-CSV $listing -notypeinformation
and this worked. I tried the {name -like *} but that gave me errors in my current program. I know the Export-CSV makes it work but I can't use this format anymore.
for the direct reports my original was this:
foreach ($ID in $directReports){
if ($ID -ne $Null){
$directreports = get-aduser $ID
$directreports.name | Out-File $output -Append
}
This code printed line by line the direct reports but I want them all listed in the same excel cell when I send it there.
I have printed a listing of all the members in the past using ":" and it worked but it is not the case with the direct reports listing. I just get errors when I use this format from my other program:
foreach ($member in $empty.members){
$string = $member.substring(3,$member.indexof(",")-3)
$members = $members + ":" + $string
}
I hope someone can help me with my two issues.
Import-Module ActiveDirectory
$documentOld = "C:\Temp\Old_Supervisor_list_mo_yyyy.txt"
Clear-Content $documentOld
$Header = `
"User ID" <#+ "|" + `
"Department" + "|" + `
"Direct Reports"#>
$Header | Out-File $documentOld -Append
$Users = Get-AdUser -Filter * -Properties name, Enabled, Manager, extensionAttribute14 | Select Enabled, name, Manager, extensionAttribute14
foreach ($user in $Users){
if ($user.enabled –eq $true) {
$name = $user.name
$directReports = Get-ADUser -Identity $name -Properties directreports | Select -ExpandProperty directreports
$department = $user.extensionAttribute14
foreach ($ID in $directReports){
if ($ID -ne $Null){
$directreports = get-aduser $ID
# $string = $directreports + ":"
}#end if $ID
}#end foreach $ID
$listing = `
$name + "|" + $deparment + "|" + $directreports#$string
$listing | Out-File $documentOld -Append
}# end if
}# end foreach $user
Let see if we can make this a little easier and efficient.
Import-Module ActiveDirectory
$documentOld = "C:\Temp\Old_Supervisor_list_mo_yyyy.txt"
$Users = Get-AdUser -Filter * -Properties name,Enabled,Manager,extensionAttribute14 | Where-Object{$_.Enabled}
$Users | ForEach-Object{
$props = #{
Name = $_.Name
Department = $_.extensionAttribute14
DirectReports = ($_.Manager | Where-Object{$_} | ForEach-Object{Get-Aduser $_ | Select-object -ExpandProperty Name}) -join ":"
}
New-Object -TypeName psobject -Property $props
} | Select-Object Name,Department,DirectReports | Export-CSV -Delimiter "|" -NoTypeInformation -Path $documentOld
First we get all the users from your directory with Get-AdUser -Filter * taking all the properties outside the norm that we want. Since you just wanted accounts that are enabled we filter those out now with Where-Object{$_.Enabled}.
The fun part is creating the custom object array ( which is necessary for input for Export-CSV). Create a small hashtable called $props where we set the properties by their friendly names. The special one being DirectReports where we take all the users manager DN's ( Assuming they have one where is what Where-Object{$_} does by filtering out nulls/empty strings.) and use Get-Aduser to get there names. Since you could have more than one manager an array is most likely returned we use -join to ensure only a single string is given for the DirectReports property. That property collection is created for every user and it is then used to create a New-Object which is sent to the output stream.
The Select-Object that follows is just to ensure the order of columns in the CSV that is created. No need for making a CSV file with lots of Out-Files when Export-CSV and -Delimiter "|" will do the hard work for you.