How do I find a user by DisplayName in one CSV file given a DistinguishedName is another CSV file? - powershell

I have two CSV files with tab-separated values.
CSV1:
GivenName Surname DisplayName Manager
John Smith John Smith Oliver Twist
CSV2:
SAMAccountName mail DistinguishedName
John Smith johns#example.com CN=John Smith,CN=Users,DC=example,DC=com
Oliver Twist olivert#example.com CN=Oliver Twist,CN=Users,DC=example,DC=com
What I've wanted to do is to compare the CSV1 with CSV2 and match the manager name in Manager column in CSV1 file with CSV2 DistinguishedName column value (i.e. "CN=Oliver Twist") as in this example and export to a new CSV file like this:
SAMAccountName mail Manager
John Smith johns#example.com Oliver Twist
There might be hundreds of distinguished names in CSV2 file as mentioned and all I want is output from comparing and matching the DistinguishedName column value "CN=whatever name " with the Manager column in CSV1.
Here is the script I tried:
$csv1 = Import-Csv -Path 'C:temp\AD.csv'
$csv2 = Import-Csv -Path 'C:\temp\AD_ExportDN.csv'
$csv3 = foreach ($record in $csv2) {
$matchingRecord = $csv1 | Where-Object { $_.manager -eq $record.manager }
# if your goal is to ONLY export records with a matching email address,
# uncomment the if ($matchingRecord) condition
#if ($matchingRecord) {
$record | Select-Object *, #{Name = 'manager'; Expression = {$matchingRecord.DistinguishedName}}
#}
}
$csv3 | Export-Csv -NoTypeInformation -Force -Path 'C:\temp\merged_for_AD2.csv'

Your question inspired me to add an new feature to the Join-Object script/Join-Object Module I am maintaining (see also: In Powershell, what's the best way to join two tables into one?).
The feature is explained in issue: #29 key expressions.
In your case the required command will be something like:
$Csv1 |Join $Csv2 -On Manager -Equals { [RegEx]::Match($_.DistinguishedName, '(?<=CN=).*(?=,CN=Users)') }
Which returns:
GivenName : John
Surname : Smith
DisplayName : John Smith
Manager : Oliver Twist
SAMAccountName : Oliver Twist
mail : olivert#example.com
DistinguishedName : CN=Oliver Twist,CN=Users,DC=example,DC=com
For the specific regular expression '(?<=CN=).*(?=,CN=Users)':
.* refers to any sequence of characters that is
Preceded by CN= ((?<=CN=), regex Lookbehind)
and followed by CN=Users ((?=,CN=Users), regex lookahead)

Related

how can I correct my reconciliation of .csv files to remove dupes/nulls

I have been using code from this answer to check for additions/changes to class rosters from MS Teams:
$set = [System.Collections.Generic.HashSet[string]]::new(
[string[]] (Import-CSV -Path stundent.csv).UserPrincipalName,
[System.StringComparer]::InvariantCultureIgnoreCase
)
Import-Csv ad.csv | Where-Object { $set.Add($_.UserPrincipalName) } |
Export-Csv path\to\output.csv -NoTypeInformation
Ideally, I want to be able to check if there have been removals when compared to a new file, swap the import file positions, and check for additions. If my files look like Source1 and Source2 (below), the check for removals would return Export1, and the check for additions would return Export2.
Since there will be multiple instances of students across multiple classes, I want to include TeamDesc in the filter query to make sure only the specific instance of that student with that class is returned.
Source1.csv
TeamDesc
UserPrincipalName
Name
Team 1
student1#domain.com
john smith
Team 1
student2#domain.com
nancy drew
Team 2
student3#domain.com
harvey dent
Team 3
student1#domain.com
john smith
Source2.csv
TeamDesc
UserPrincipalName
Name
Team 1
student2#domain.com
nancy drew
Team 2
student3#domain.com
harvey dent
Team 2
student4#domain.com
tim tams
Team 3
student1#domain.com
john smith
Export1.csv
TeamDesc
UserPrincipalName
Name
Team 1
student1#domain.com
john smith
Export2.csv
TeamDesc
UserPrincipalName
Name
Team 2
student4#domain.com
tim tams
Try the following, which uses Compare-Object to compare the CSV files by two column values, simply by passing the property (column) names of interest to -Property; the resulting output is split into two collections based on which input side a differing property combination is unique to, using the intrinsic .Where() method:
$removed, $added = (
Compare-Object (Import-Csv Source1.csv) (Import-Csv Source2.csv) -PassThru `
-Property TeamDesc, UserPrincipalName
).Where({ $_.SideIndicator -eq '=>' }, 'Split')
$removed |
Select-Object -ExcludeProperty SideIndicator |
Export-Csv -NoTypeInformation Export1.csv
$added |
Select-Object -ExcludeProperty SideIndicator |
Export-Csv -NoTypeInformation Export2.csv
Assuming both Csvs are stored in memory, Source1.csv is $csv1 and Source2.csv is $csv2, you already have the logic for Export2.csv using the HashSet<T>:
$set = [System.Collections.Generic.HashSet[string]]::new(
[string[]] $csv1.UserPrincipalName,
[System.StringComparer]::InvariantCultureIgnoreCase
)
$csv2 | Where-Object { $set.Add($_.UserPrincipalName) }
Outputs:
TeamDesc UserPrincipalName Name
-------- ----------------- ----
Team 2 student4#domain.com tim tams
For the first requirement, Export1.csv, the reference object would be $csv2 and instead of a HashSet<T> you could use a hash table, Group-Object -AsHashTable makes it really easy in this case:
$map = $csv2 | Group-Object UserPrincipalName -AsHashTable -AsString
# if Csv2 has unique values for `UserPrincipalName`
$csv1 | Where-Object { $map[$_.UserPrincipalName].TeamDesc -ne $_.TeamDesc }
# if Csv2 has duplicated values for `UserPrincipalName`
$csv1 | Where-Object { $_.TeamDesc -notin $map[$_.UserPrincipalName].TeamDesc }
Outputs:
TeamDesc UserPrincipalName Name
-------- ----------------- ----
Team 1 student1#domain.com john smith
Using this Join-Object script/Join-Object Module (see also: How to compare two CSV files and output the rows that are just in either of the file but not in both and In Powershell, what's the best way to join two tables into one?):
Loading your sample data:
(In your case you probably want to use Import-Csv to import your data)
Install-Script -Name Read-HtmlTable
$Csv1 = Read-HtmlTable https://stackoverflow.com/q/74452725 -Table 0 # Import-Csv .\Source1.csv
$Csv2 = Read-HtmlTable https://stackoverflow.com/q/74452725 -Table 1 # Import-Csv .\Source2.csv
Install-Module -Name JoinModule
$Csv1 |OuterJoin $Csv2 -On TeamDesc, UserPrincipalName -Name Out,In
TeamDesc UserPrincipalName OutName InName
-------- ----------------- ------- ------
Team 1 student1#domain.com john smith
Team 2 student4#domain.com tim tams
You might use the (single) result file as is. If you really want to work with two different files, you might split the results as in the nice answer from mklement0.

Check if a value exists in one csv file and add static text to another csv field with Powershell

I need to check if the column value from the first csv file, exists in any of the three other csv files and return a value into a new csv file, in order of precedence.So if the username field from allStaff.csv exists in the list of usernames in the sessionVPNct.csv file, put the static text into the final csv file as 'VPN'. If it does not exist, check the next csv file: sessionCRXct.csv then put the static text 'CRX', if not check the last csv file: sessionTMSct.csv then put the static text: TM if not the put the static text 'none' into the final csv file.
I have four csv files as below:
1. allStaff.csv
2. VPN.csv
3. CRX.csv
4. TMS.csv
I have imported the csv files into variables as below:
$allUsers = Import-Csv -Path "C:\allStaff.csv"
$vpn = Import-Csv -Path "C:\VPN.csv" | Select-Object -ExpandProperty UserName
$crx = Import-Csv -Path "C:\CRX.csv" | Select-Object -ExpandProperty UserName
$tms = Import-Csv -Path "C:\TMS.csv" | Select-Object -ExpandProperty UserName
The $allUsers variable displays the following:
Firstname LastName Username Position Area
--------- -------- -------- -------- ----
Joe Bloggs jbloggs Gardener Maintenance
Jim Smith jsmith Accountant Finance
Bob Seger bseger HR Advisor Human Resources
Adam Boson aboson Customer Support IT
Adele bree abree Payroll Finance
The $vpn variable displays the following:
Username
--------
jbloggs
jsmith
The $crx variable displays the following:
Username
--------
jbloggs
jsmith
bseger
The $tms variable displays the following:
Username
--------
jbloggs
jsmith
bseger
aboson
Then I have the following line to start the result csv file
$result = $allUsers | Select-Object *,ConnectionMethod
Not quite sure how to do the final query, which I believe should be an if else loop to go through all rows in the $result variable, and check the other csv if the username field exists, then return the static text.
$result | Export-Csv -Path "C:\allStaffConnections.csv"
This is how I need the final allStaffConnections.csv file to be displayed.
Firstname LastName Username Position Area ConnectionMethod
--------- -------- -------- -------- ---- --------------
Joe Bloggs jbloggs Gardener Maintenance VPN
Jim Smith jsmith Accountant Finance VPN
Bob Seger bseger HR Advisor Human Resources CRX
Adam Boson aboson Customer Support IT TMS
Adele bree abree Payroll Finance none
Am I on the right track with the below code?
$allUsers = Import-Csv -Path "C:\allStaff.csv"
$vpn = Import-Csv -Path "C:\VPN.csv" | Select-Object -ExpandProperty UserName
$crx = Import-Csv -Path "C:\CRX.csv" | Select-Object -ExpandProperty UserName
$tms = Import-Csv -Path "C:\TMS.csv" | Select-Object -ExpandProperty UserName
$vpnText = 'VPN'
$crxText = 'CRX'
$txsText = 'TMS'
$noneText = 'none'
$allUsersExtended = $allUsers | Select-Object *,ConnectionMethod
$results = $allUsersExtended.ForEach(
{
if($vpn -Contains $PSItem.UserName) {
# add $vpnText to ConnectionMethod column for that row in the $result
$PSItem.ConnectionMethod = $vpnText
}elseif($crx -Contains $PSItem.UserName) {
# add $crxText to ConnectionMethod column for that row in the $result
$PSItem.ConnectionMethod = $crxText
}elseif($tms -Contains $PSItem.UserName) {
# add $txsText to ConnectionMethod column for that row in the $result
$PSItem.ConnectionMethod = $tmsText
}else {
# add $noneText to ConnectionMethod column for that row in the $result
$PSItem.ConnectionMethod = $noteText
}
})
$results | Export-Csv -Path "C:\allStaffConnections.csv" -NoTypeInformation
This gives me an empty allStaffConnections.csv file.
I have run the code line by line and can get as far as:
$allUsersExtended = $allUsers | Select-Object *,ConnectionMethod
Which gives me the extra column "ConnectionMethod", but after running the loop, it gives me an empty allStaffConnections.csv file.
here is one way to do the job. [grin] it presumes that you only want to 1st connection type found. if you want all of them [for instance, JBloggs has all 3 types listed], you will need to concatenate them.
what it does ...
fakes reading in the CSV files
when ready to use real data, comment out or remove the entire #region/#endregion section and use Get-Content.
iterates thru the main collection
uses a switch to test for membership in each connection type list
this breaks out of the switch when it finds a match since it presumes you only want the 1st match. if you want all of them, then you will need to accumulate them instead of breaking out of the switch block.
sets the $ConnectionType as appropriate
builds a PSCO with all the wanted props
this likely could be shortened by using Select-Object, a wildcard property selector, and a calculated property.
sends it out to the $Results collection
shows it on screen
saves it to a CSV file
the code ...
#region >>> fake reading in CSV files
# in real life, use Import-CSV
$AllUsers = #'
FirstName, LastName, UserName, Position, Area
Joe, Bloggs, jbloggs, Gardener, Maintenance
Jim, Smith, jsmith, Accountant, Finance
Bob, Seger, bseger, HR Advisor, Human Resources
Adam, Boson, aboson, Customer Support, IT
Adele, bree, abree, Payroll, Finance
'# | ConvertFrom-Csv
$Vpn = #'
UserName
jbloggs
jsmith
'# | ConvertFrom-Csv
$Crx = #'
UserName
jbloggs
jsmith
bseger
'# | ConvertFrom-Csv
$Tms = #'
UserName
jbloggs
jsmith
bseger
aboson
'# | ConvertFrom-Csv
#endregion >>> fake reading in CSV files
$Results = foreach ($AU_Item in $AllUsers)
{
# this presumes you want only the 1st connection type found
# if you want all of them, then you will need to concatenate them
switch ($AU_Item.UserName)
{
{$_ -in $Vpn.UserName}
{
$ConnectionType = 'VPN'
break
}
{$_ -in $Crx.UserName}
{
$ConnectionType = 'CRX'
break
}
{$_ -in $Tms.UserName}
{
$ConnectionType = 'TMS'
break
}
default
{
$ConnectionType = 'None'
}
}
[PSCustomObject]#{
FirstName = $AU_Item.FirstName
LastName = $AU_Item.LastName
UserName = $AU_Item.UserName
Position = $AU_Item.Position
Area = $AU_Item.Area
ConnectionTYpe = $ConnectionType
}
}
# on screen
$Results
# send to CSV
$Results |
Export-Csv -LiteralPath "$env:TEMP\brokencrow_-_UserConnectionType.csv" -NoTypeInformation
truncated on screen output ...
FirstName : Joe
LastName : Bloggs
UserName : jbloggs
Position : Gardener
Area : Maintenance
ConnectionTYpe : VPN
[*...snip...*]
FirstName : Adele
LastName : bree
UserName : abree
Position : Payroll
Area : Finance
ConnectionTYpe : None
the CSV file content from brokencrow_-_UserConnectionType.csv ...
"FirstName","LastName","UserName","Position","Area","ConnectionTYpe"
"Joe","Bloggs","jbloggs","Gardener","Maintenance","VPN"
"Jim","Smith","jsmith","Accountant","Finance","VPN"
"Bob","Seger","bseger","HR Advisor","Human Resources","CRX"
"Adam","Boson","aboson","Customer Support","IT","TMS"
"Adele","bree","abree","Payroll","Finance","None"

Get-ADUser account name whose email address matches any of the emails indicated in csv file

I have a comma-separated csv file as below (first row is the header):
category;email.1;email.2;email.3;email.4;email.5;email.6
category1;sample#gmail.com;;;;;
category2;;;sample2#gmail.com;;;
category3;;sample3#gmail.com;;sample44#hotmail.com;;sample55#gmail.com
and so on...
Now I import it:
$emails = Import-CSV -Encoding Default -Delimiter ";" "c:\temp\myFile.csv"
And finally, for each row in the csv I want to get the AD user account name whose email address matches any from email.1 to email.6
So I try this using a foreach loop but I have no idea what have to put within it to get what I want.
$emails | foreach {
$userAccountName = Get-ADUser -filter {something}
# do some stuff with $userAccountName
}
Note: I would like a generic solution taking into account that in future can be more than 6 emails by category. Also take into account that some emails can be empty for the category.
Once imported you can enumerate the headers matching a pattern
$emails = Import-CSV -Encoding Default -Delimiter ";" "c:\temp\myFile.csv"
$EmailHeaders = ($Emails[0].psobject.Properties|Where-Object Name -like 'email.*').Name
ATM this returns:
> $EmailHeaders
email.1
email.2
email.3
email.4
email.5
email.6
Nest foreachs's to iterate the emails per row which are populated.
Get-ADUser commented out for testing
foreach($Row in $Emails){
foreach($EmailHeader in $EmailHeaders){
if($Email=$Row.$EmailHeader){
[PSCustomObject]#{
Category = $Row.Category
Email_x = $EmailHeader
Email = $Email
SamAccountName= $Null #(Get-ADUser -Filter {EmailAddress -eq "$Email"} -Properties SamAccountName).SamAccountName
}
}
}
}
Category Email_x Email SamAccountName
-------- ------- ----- --------------
category1 email.1 sample#gmail.com
category2 email.3 sample2#gmail.com
category3 email.2 sample3#gmail.com
category3 email.4 sample44#hotmail.com
category3 email.6 sample55#gmail.com

Change email address in CSV

I am writing PowerShell code to export email addresses to a csv file and edit them. I've written the following:
# Script to get all DLs and email addresses in csv file
Get-ADGroup -Filter 'groupcategory -eq "distribution"' -Properties * |
select Name, mail |
Export-csv "C:\temp\Distribution-Group-Members.csv"
# Following will import the csv, make new column as proxy and save the file
# as update.csv
Import-Csv "C:\temp\Distribution-Group-Members.csv" |
Select-Object "Name", "mail", #{n = "proxy"; e = "mail"} |
Export-Csv "c:\temp\Distribution-Group-Members-Updated.csv" -NoTypeInfo
# Following script can import the csv and set proxy addresses from proxy
# column
Import-Csv "c:\temp\Distribution-Group-Members-Updated.csv" |
Foreach {
Get-ADGroup $_.Name | Set-ADGroup -Add #{
proxyaddresses = ($_.proxy -split ";")
}
}
Now, I would like to add 2 more features in the script:
update domain for existing mail column e.g. update mail address form test#abc.com to test#xyz.com
Add SMTP:test#xyz.com;smtp:test#abc.com" as Proxy mail address, so that xyz become primary mail address and abc as proxy domain
So assuming my DL name is "DL Test" email is "test#abc.com" => The script should update the email address of DL to "test#xyz.com" and add "smtp:test#abc.com" as proxy mail address
Can someone please advise, how can I achieve that?
The part where you have written:
select-object "Name", "mail", #{n = "proxy"; e = "mail"}|
The proxy part is called a calculated property. The first two with just names Name and Mail are copied directly from the input objects, but using the #{..} syntax, you can put code to calculate a new value instead.
So you can use this to achieve both your desired changes:
Import-Csv -Path 'C:\temp\Distribution-Group-Members.csv' |
Select-Object -Property Name,
#{Label='Mail'; Expression={$_.Mail -replace 'abc', 'xyz'}},
#{Label='Proxy'; Expression={"SMTP:$($_.Mail -replace 'abc', 'xyz');smtp:$($_.Mail)"}}|
Export-csv 'C:\temp\Distribution-Group-Members.csv' -NoTypeInformation

Compare 2 csv files and match based on 1 column then export new file that contains fields from both

I have 2 csv files. Each have different headers and different number of columns, and have different number of entries.
Here are some examples of the first couple lines
CSV 1
ID,Last_Name,First_Name,Middle_Name,Email_Addr,Title,Gender
###1,smith,bill,p,smith#soso.com,boss,m
###2,smith2,billy,p,smith2#soso.com,someguy,m
CSV 2
ID,Name Id,Last Name,First Name,Middle Name,Gender
###2,ID1010,smith2,billy,p,M
I am trying to import them and compare the ID column. When a match is found I am wanting a new csv file with All info from CSV 1 and the matched Name ID from csv 2.
New CSV Example:
ID,Last_Name,First_Name,Middle_Name,Email_Addr,Title,Gender,Name Id
###1,smith,bill,p,smith#soso.com,boss,m,
###2,smith2,billy,p,smith2#soso.com,someguy,m,ID1010
Ive been looking and came across this Stackoverflow from about a year ago that seemed to be on the right track but I cant seem to get code modified for my needs. Here is what I have tried.
$csv1 = Import-Csv -Path C:\STAFF\test1sky.csv
$csv2 = Import-Csv -Path C:\STAFF\test1power.csv
ForEach($Record in $csv2){
$MatchedValue = (Compare-Object $csv1 $Record -Property "ID" -IncludeEqual -ExcludeDifferent -PassThru).value
$Record = Add-Member -InputObject $Record -Type NoteProperty -Name "Name Id" -Value $MatchedValue
}
$csv2|Export-Csv 'C:\STAFF\combined.csv' -NoTypeInformation
I get the correct header in the new file but I never get the Name ID values to come though.
Any idea where I went wrong? I maybe on the wrong path completely and there be a easier way, but I need to be able to do this nightly without user interaction. Any help is appreciated!!
Let's try to simplify this. Add the 'Name ID' field to all records in CSV1. Then loop through it, and get the matches, and update the field. Something like:
$CSV1 = C:\Path\To\File1.csv
$CSV2 = C:\Path\To\File2.csv
$CSV1|ForEach{$_|Add-Member 'Name ID' $Null}
ForEach($Record in $CSV1){
$Record.'Name ID' = $CSV2|Where{$_.ID -eq $Record.ID}|Select -Expand 'Name ID'
}
$CSV1 = import-csv C:\Path\To\File1.csv
$CSV2 = import-csv C:\Path\To\File2.csv
#adds a row named "Name ID" to the PS Object( the CSV Import)
$CSV1|ForEach{$_|Add-Member 'Name ID' $Null}
ForEach($Record in $CSV1){
#gets the value from CSV1 for comparing to CSV2
$NameValue=Record."Last_Name"
#gets the Power Shell Object from the CSV2 Import that matches the Name ID from $csv1
$Nameobject= $CSV2|Where-object "Last Name" -contains $Namevalue
#Sets the Field "Name ID" in the PS Object $CSV1 Record to the Name ID from $csv2
$record."Name ID" = $Nameobject."Name ID"
}
You can easily grab addtional fields by adding other references to the CSV1 File by manipulating the CSV2 PS Object.
$record."Middle Name" = $nameobject."Middle_Name"
Since you have the entire object in the for loop form $csv2 you can call any of its fields or manipulate them by using variables and " |select -Property "Value" Like this
$objlength = $nameobject |select "First_Name"
$objlength.length
but i prefer to call it directly from the object as the output looks cleaner like this
$nameobject."First_Name".length
The operation you are looking for is called a relational join. Sometimes it's called an inner join, and sometimes just a join. My knowledge of join comes from SQL, not from Powershell.
Here's a description of "Join-Object". It seems to be what you are looking for.
http://blogs.msdn.com/b/powershell/archive/2012/07/13/join-object.aspx