Powershell to CSV compare - powershell

Hello I'm trying to use some code I found on here to compare a CSV with Active Directory. I have a csv file with a list of users. I want to check this file and see if anything in the "Email" column from the spreadsheet matches an email address from AD. If it does, I want to list that email address and include the Canonical Name from AD so I can easily see what OU the user account is in.
This is what I'm working with:
$path = "C:\Scripts\Import.csv"
$outpath = "C:\Scripts\Export.csv"
$csv = Import-Csv $path
Import-Module ActiveDirectory
foreach ($line in $csv)
{
$User = Get-ADUser -LDAPFilter "(&(objectclass=user)(mail=$($line.Email)))" -Properties CanonicalName
If ($User -ne $Null) {"User does exist in OU" + $line.Email + $User.CanonicalName}
Else {"User not found in AD OU - " + $line.Email}
}
I've been able to modify this to suit my needs but I'm having some trouble piping the results out to a CSV file. Running the script as it's shown above outputs what I want to the screen but I'd like to have it in a CSV format. If I do something like:
$Results = foreach ($line in $csv)
and then use
$Results | export-CSV $outpath -NotypeInformation
I get the csv created but it just includes a the string value, header for Length and then a numeric value for each line. I can use Out-File to send the results to a txt file, which includes the same results that were displayed on the screen, but I'd really like this to be a csv, not txt file. I believe I need to reference the properties of the csv file and AD in order to build these into my export file but I'm having trouble doing that as I'm not sure how to build in the status of whether the user was found or not.
Any assistance would be appreciated.
UPDATE - Final code
This is the final code I went with. This compares the users in the csv with AD users in the parent and child domain. It uses the email address field to match the users. It grabs the Canonical Name so I can see the OU the user is in and if the user is not found, it reports that in the Canonical Name field.
$path = "$env:userprofile\Desktop\InFile.csv"
$outpath = "$env:userprofile\Desktop\OutFile.csv"
# Importing CSV file
Import-Module ActiveDirectory
$Users = Import-Csv $path |
ForEach-Object {
Write-Progress -Activity "Comparing Imported File to Active Directory" -Status "Processing $($_.Email)"
# Comparing CSV file to Domain.com users
If (
$Value1 = (Get-ADUser -LDAPFilter "(&(objectclass=user)(mail=$($_.Email)))" -server Domain.com -Properties CanonicalName).CanonicalName)
{$_ | Add-Member -MemberType NoteProperty -Name CanonicalName -Value $value1 -PassThru}
# Comparing CSV file to child.Domain.com users
ElseIF (
$Value2 = (Get-ADUser -LDAPFilter "(&(objectclass=user)(mail=$($_.Email)))" -server child.Domain.com-Properties CanonicalName).CanonicalName)
{$_ | Add-Member -MemberType NoteProperty -Name CanonicalName -Value $value2 -PassThru}
# Writing output for users not found in either domain
Else {$_ | Add-Member -MemberType NoteProperty -Name CanonicalName -Value "Email Address not found in Active Directory" -PassThru}
#Exporting to CSV file
New-Object -TypeName pscustomobject -Property #{
Email = $_.Email
CanonicalName = $_.CanonicalName
LastName = $_."Last Name"
FirstName = $_."First Name"
}
} | Select-Object LastName, Firstname, Email, CanonicalName | Sort-Object CanonicalName | Export-CSV $outpath -NoTypeInformation

I'm not sure what resulting CSV should look like, so this code just adds CannonicalName using Calculated Properties to Import.Csv and saves it as Export.Csv.
$path = "C:\Scripts\Import.csv"
$outpath = "C:\Scripts\Export.csv"
Import-Module ActiveDirectory
Import-Csv -Path $path |
Select-Object -Property *, #{
n = 'CanonicalName'
e = {(Get-ADUser -LDAPFilter "(&(objectclass=user)(mail=$($_.Email)))" -Properties CanonicalName).CanonicalName}
} | Export-Csv -Path $outpath -NoTypeInformation
Update
This version will create a new CSV file with 3 columns: UserExistInOu, Email and CanonicalName if any:
Import-Csv -Path $path | ForEach-Object {
$UserExistInOu = $false
if($CanonicalName = (Get-ADUser -LDAPFilter "(&(objectclass=user)(mail=$($_.Email)))" -Properties CanonicalName).CanonicalName)
{
$UserExistInOu = $true
}
New-Object -TypeName pscustomobject -Property #{
UserExistInOu = $UserExistInOu
Email = $_.Email
CanonicalName = $CanonicalName
}
} | Export-Csv -Path $outpath -NoTypeInformation

Related

Merging two CSV files with Powershell

I have two CSV files.Columns names are like below example
CSV1
SAMAccountName mail
JohnS johns#example.com
CSV2
GivenName Surname DisplayName Department Title Mail MobilePhone Manager
What I've wanted to do is to compare the CSV1 with CSV2 and match the mail column in CSV2 file with SAMAccountName Column in CSV1 and export a new CSV which gives the below output
CSV3(merged.csv) as in script
GivenName Surname DisplayName Department Title Mail MobilePhone Manager SAMAccountName
I tried the below powershell script which i found in stack overflow site posted sometime back and i never got the values from the SAMAccountName column of CSV1.All other column names are coming up without any issue but SAMAccountName column is blank.I'm not a powershell script expert need some help here.Below is the example script i used.
$csv1 = Import-Csv -Path C:\TEMP\CSV1.csv
$csv2 = Import-Csv -Path C:\temp\CSV2.csv
ForEach($Record in $csv2){
$MatchedValue = (Compare-Object $csv1 $Record -Property "SAMAccountName" -IncludeEqual -ExcludeDifferent -PassThru).value
$Record = Add-Member -InputObject $Record -Type NoteProperty -Name "SAMAccountName" -Value $MatchedValue
}
$csv2|Export-Csv 'C:\temp\merged.csv' -NoTypeInformation
This is very common, You can do this quite easily using:
$csv1 = Import-Csv -Path 'C:\temp\CSV1.csv'
$csv2 = Import-Csv -Path 'C:\temp\CSV2.csv'
$csv3 = foreach ($record in $csv2) {
$matchingRecord = $csv1 | Where-Object { $_.mail -eq $record.mail }
# if your goal is to ONLY export records with a matching email address,
# uncomment the if ($matchingRecord) condition
#if ($matchingRecord) {
$record | Select-Object *, #{Name = 'SamAccountName'; Expression = {$matchingRecord.SamAccountName}}
#}
}
$csv3 | Export-Csv -Path 'C:\temp\merged.csv' -NoTypeInformation

Powershell to get active user OU, username, and group memberships

I am trying to get a powershell script to export all users in an OU and sub OUs which I can do fine, but when I try to get the user's OU, I get nothing for the OU. I have looked all over online and found a few scripts that pull just the user's OU, but they are a little slow and I can't seem to get them to pull groups or is for pulling from one group instead of listing all users and their groups.
I am trying to export this list and sort by OU so that I can ensure each student is in the proper groups. We have had a few students that were in extra groups and I want a quick and easy look to find those students.
#Student
$Report = #()
#Collect all users
$Users = Get-ADUser -Filter * -SearchBase 'OU=Student,DC=domain,DC=com' -Properties distinguishedname, Name, GivenName, SurName, SamAccountName, UserPrincipalName, MemberOf, Enabled -ResultSetSize $Null
# Use ForEach loop, as we need group membership for every account that is collected.
# MemberOf property of User object has the list of groups and is available in DN format.
Foreach($User in $users){
$UserGroupCollection = $User.MemberOf
#This Array will hold Group Names to which the user belongs.
$UserGroupMembership = #()
#To get the Group Names from DN format we will again use Foreach loop to query every DN and retrieve the Name property of Group.
Foreach($UserGroup in $UserGroupCollection){
$GroupDetails = Get-ADGroup -Identity $UserGroup
#Here we will add each group Name to UserGroupMembership array
$UserGroupMembership += $GroupDetails.Name
}
#As the UserGroupMembership is array we need to join element with ',' as the seperator
$Groups = $UserGroupMembership -join ','
#Creating custom objects
$Out = New-Object PSObject
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value #{Name="DistinguishedName";Expression={$_.distinguishedname | ForEach-Object {$_ -replace '^.+?(?<!\\),',''}}}
$Out | Add-Member -MemberType noteproperty -Name Name -Value $User.Name
$Out | Add-Member -MemberType noteproperty -Name UserName -Value $User.SamAccountName
$Out | Add-Member -MemberType noteproperty -Name Status -Value $User.Enabled
$Out | Add-Member -MemberType noteproperty -Name Groups -Value $Groups
$Report += $Out
}
#Output to screen as well as csv file.
$Report | Sort-Object DistinguishedName | FT -AutoSize
$Report | Sort-Object DistinguishedName | Export-Csv -Path $env:temp\students.csv -NoTypeInformation
There you go, I added some comments to help you understand the thought process.
This should be a lot faster than what you were doing.
The problem while adding your OUs was here:
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value #{Name="DistinguishedName";Expression={$_.distinguishedname | ForEach-Object {$_ -replace '^.+?(?<!\\),',''}}}
Which should've been:
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value ($user.distinguishedname -replace '^.+?(?<!\\),','')
#Student
$Report = [system.collections.generic.list[pscustomobject]]::new()
# Using Collection.Generic.List instead of System.Array for efficiency
#Collect all users
$Users = Get-ADUser -Filter * -SearchBase 'OU=Student,DC=domain,DC=com' -Properties MemberOf
# -> Use -SearchScope Subtree if you want to go all the way down in OU recursion starting from the 'OU=Student'
# -> distinguishedname, Name, GivenName, SurName, SamAccountName, UserPrincipalName and Enabled are Default Properties
# of Get-ADUser, no need to call them.
# -> -ResultSetSize $Null is default for Get-ADUSer, no need to call it
# Use ForEach loop, as we need group membership for every account that is collected.
# MemberOf property of User object has the list of groups and is available in DN format.
Foreach($User in $users)
{
#This Array will hold Group Names to which the user belongs.
$UserGroupMembership = [system.collections.generic.list[string]]::new()
#To get the Group Names from DN format we will again use Foreach loop to query every DN and retrieve the Name property of Group.
Foreach($UserGroup in $User.MemberOf)
{
# $GroupDetails = Get-ADGroup -Identity $UserGroup
# -> Instead of this, we can do some string manipulation
# which will be a lot faster and give you the same results.
$UserGroupMembership.Add($UserGroup.Split(',OU=')[0].replace('CN=',''))
}
#As the UserGroupMembership is array we need to join element with ',' as the seperator
$Groups = $UserGroupMembership -join ','
#Creating custom objects
<#
$Out = New-Object PSObject
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value #{Name="DistinguishedName";Expression={$_.distinguishedname | ForEach-Object {$_ -replace '^.+?(?<!\\),',''}}}
$Out | Add-Member -MemberType noteproperty -Name Name -Value $User.Name
$Out | Add-Member -MemberType noteproperty -Name UserName -Value $User.SamAccountName
$Out | Add-Member -MemberType noteproperty -Name Status -Value $User.Enabled
$Out | Add-Member -MemberType noteproperty -Name Groups -Value $Groups
$Report += $Out
-> Again, Add-Member is highly inefficient compared to casting PSCustomObject
-> += is evil ( •̀ᴗ•́ )و ̑̑
#>
$Report.Add(
[pscustomobject]#{
OrganizationalUnit = ($user.DistinguishedName -replace '^.+?(?<!\\),','')
Name = $user.Name
UserName = $user.sAMAccountName
Status = $user.Enabled
Membership = $Groups
})
}
#Output to screen as well as csv file.
$Report | Sort-Object OrganizationalUnit | FT -AutoSize
$Report | Sort-Object OrganizationalUnit | Export-Csv -Path $env:temp\students.csv -NoTypeInformation
I don't know how many users you have but every time you += on an array the entire array plus the new element is copied to a completely new array. This is a bad practice and gets exponentially worse with every item added the array. You can avoid this by building the arrays as a loop result or by using dotnet list object with an efficient add() method.
You also look up the same group names repeatedly. I don't know the numbers but it's probably a lot better to put all your groups in a hashtable once and then look them up.
Your question is unclear, but if you want a list of users and their groups, you are going the long way around. You mention the ou but AFAICS there is no org unit used in the code. Do you want the AD ou property or a part of the DN? You don't seem to be using either.
Note that the DN is a string and sorting by DN will just give an alpha string sort which is not helpful. Are your students in separate org units under OU=students ? This is not clear. If so, use the AD canonicalName to sort the list.
No need to include default properties in -property. Splatting is nice.
You should improve your question by indicating what your AD structure looks like and what you think your output should look like.
Also, format your code for readability.
You want something along these lines:
# group hashtable, for efficient name lookup
$groupName = #{}
$ignoredGroups = #( 'AllStudents','AllUsers', 'etc' ) # don't clutter list with these groups
Get-AdGroup -filter '*' | # any restrictions? searchbase, etc
ForEach-Object {
if ( $ignoredGroups -notcontains $_.Name ) {
$groupName[ $_.distinguishedName ] = $_.Name
}
}
# ADsplat, for readability
$AD_Splat = #{
Filter = '*'
SearchBase = 'OU=Student,DC=domain,DC=com'
Properties = 'MemberOf,CanonicalName,sn,givenName'.split(',') # split to array
ResultSetSize = $Null # !? also, there are system limits to size
}
$results = Get-ADUser #ad_splat |
ForEach-Object {
$DN = $_.distinguishedName # do you need this at all?
$CName = $_.canonicalName # for sorting by AD org unit
$XName = $_.sn + ', ' + $_.givenName
if ( $_.Enabled ) { $Enabled = 'Y'} else { $Enabled = '.' }
$groups = (
$_.memberOf |
ForEach-Object { $GroupName[ $_ ] } | # lookup name
where-Object { $_ } | # ignore nulls (when group not in hashtable)
sort-object # consistent ordering between users
) -join ';' # don't use comma, csv conflict
# leave custom object in pipe! This builds the array efficiently.
New-Object PSObject -Property #{
DistinguishedName = $dn
Name = $_.name
XName = $XName
Login = $_.SamAccountName
CName = $CName
Groups = $Groups
}
} | Sort-Object CName # sort the objects by canonical name
$results | format-table
$results | Export-Csv -Path 'c:\temp\usersgroups.csv' -NoTypeInformation

Filter Object Data before Export-CSV

I'm working on a powershell script that verifies a CSV with our AD, finds a list of accounts that are currently disabled, and then exports them to another CSV. I am currently able to read the CSV, compare, and export a list of all accounts with true/false flags regarding their status. I can also filter that output CSV using another script. IDEALLY, I would like to merge these into one script with only one output CSV. I'm just not sure how to do it.
I've tried moving the "where..." portion of script 2 into script 1 with no success.
Script 1:
Import-Csv C:\ServerScripts\Compare\students.csv | ForEach-Object {
New-Object -TypeName PSCustomObject -Property #{
samaccountname = $_.samaccountname
firstname = $_.firstname
lastname = $_.lastname
name = $_.name
password = $_.password
email = $_.email
description = $_.description
CAMPUS_ID = $_.CAMPUS_ID
exist = [bool]($account=([adsisearcher]"(samaccountname=$($_.samaccountname))").findone())
disabled = [bool]($account.properties.useraccountcontrol[0] -band 2)
}
} | Export-Csv C:\ServerScripts\Compare\output.csv -NoTypeInformation
Script 2:
Import-Csv C:\ServerScripts\Compare\output.csv | where {$_.disabled -ne "FALSE"} | Export-Csv C:\ServerScripts\Compare\disabled.csv -notypeinfo
Both scripts work independently, I just need a way to bring them together.
Add your check in the first pipeline just before calling Export-Csv:
Import-Csv C:\ServerScripts\Compare\students.csv | ForEach-Object {
New-Object -TypeName PSCustomObject -Property #{
samaccountname = $_.samaccountname
firstname = $_.firstname
lastname = $_.lastname
name = $_.name
password = $_.password
email = $_.email
description = $_.description
CAMPUS_ID = $_.CAMPUS_ID
exist = [bool]($account=([adsisearcher]"(samaccountname=$($_.samaccountname))").findone())
disabled = [bool]($account.properties.useraccountcontrol[0] -band 2)
}
} |Where-Object {$_.disabled -eq $true} | Export-Csv C:\ServerScripts\Compare\output.csv -NoTypeInformation

Create dynamic members of PSObject

This is what I got:
Import-Module ActiveDirectory
$objectCollection = #()
$groups = (Get-ADGroup -Filter *)
foreach ($group in $groups) {
$groupName = ($group.SamAccountName)
$object = (New-Object –Type PSObject)
Add-Member -InputObject $object -MemberType NoteProperty –Name ($groupName) –Value ""
$groupMembers = (Get-ADGroupMember -Identity "$groupName" -Recursive |
Select-Object -ExpandProperty Name)
$object.$groupName = $groupMembers
$objectCollection += $object
}
$objectCollection | Export-Csv -Path C:\Users\administrator\Desktop\test.csv `
-Encoding UTF8 -Delimiter ";" -NoTypeInformation
The goal with this script is to create a CSV file where the AD group name is the header in one column an then all the members of the group listed below on separate lines. Next group is in a new column with the AD-group name as header and so on...
Normally I create as many Members as I need but this time I want it to be dynamic to how many groups there is and the script above only displays the first group in the $groups array.
Any help is appreciated, thanks in advance!
Having each group on a separate column in a CSV file seems illogical to me ; one on each row would IMHO be better (and easier too).
Here is how I would do it:
Import-Module ActiveDirectory
Get-ADGroup -Filter * | ForEach-Object {
[PSCustomObject]#{
Name = $_.samAccountName
Members = (Get-ADGroupMember $_ -Recursive | Select-Object -ExpandProperty Name) -join ","
}
} | Export-Csv -Path "C:\Users\administrator\Desktop\test.csv" -Encoding UTF8 -Delimiter ";" -NoTypeInformation
Output:
"Name";"Members"
"Administrators";"Administrator,..."
"Domain Users";"Administrator,TestUser,..."

User & Group Audit Script

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