Powershell hashtable.containskey is always returning true - powershell

The following code is trying to do one thing. Its trying to find IDs that have been added to an Active Directory group since the last time the job was run.
It does this by reading the user IDs from an Active Directory group and compares them against the IDs it saved in a file the day before.
I first read the AD group into a hashtable ($ADUsersHashtable)
I then read the file into a similar hashtable ($YesterdaysADUsersFile)
Both hashtables use the UserID as the key.
I then check to see if each ID in $ADUsersHashtable is in $YesterdaysADUsersFile. An ID that is in $ADUsersHashtable but not in $YesterdaysADUsersFile is an ID that was added to AD since the last time this job ran.
The problem is, if $YesterdaysADUsersFile has more than one entry in it, the containskey method always returns true (see output below)
If I delete all but one entry in the file, the code works as expected.
If i have more than one entry in the file the code doesn't work as expected.
The following is the code that reads AD and the file into the hashtables and then compares the keys.
$scriptName = $MyInvocation.MyCommand.Name
$LogFile = "D:\Polarion\data\logs\User Access Dates\$scriptName.log"
$Today = Get-Date
$outDate = get-date -format "yyyy-MM-dd"
Add-content $LogFile "$Today Running $scriptName"
$MyServer = $env:computername
Add-content $LogFile "`t$Today Running on $MyServer"
#Will be populated with IDs from AD that i didn't find yesterday
$NewUsersFile = "D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt"
clear-content $NewUsersFile #I only want the new users from todays AD group.
$ADUsersHashtable = #{} #Contains IDs of the members in the AD Group.
Get-ADGroupMember -Identity PEALM_ALL_USERS -Recursive `
| Get-ADObject -Properties SamAccountName, mail `
| select SamAccountName, mail `
| foreach {$ADUsersHashtable.Add($_.SamAccountName, $_.mail)}
#$ADUsersHashtable
$YesterdaysADUsersFile = "D:\Polarion\data\logs\User Access Dates\UserIdFromPEALM_ALL_USERS.txt" #Contains the IDs that I knew about the last time this ran.
$YesterdaysADUsersHashTable = #{}
$YesterdaysADUsersHashTable = Get-Content($YesterdaysADUsersFile) |
foreach {$_.ToString().Replace(":", "=")} |
ConvertFrom-StringData
$YesterdaysADUsersHashTable
$NoNewUsersFound = $true
foreach ($UserIDFromAD in $ADUsersHashtable.keys){ #For each user ID in Todays AD group
if ($YesterdaysADUsersHashTable.containsKey($UserIDFromAD)){ #If the UserID is in Yesterdays list ignore it.
write-host YesterdaysADUsersHashTable contains key $UserIDFromAD
} else {
$NoNewUsersFound = $false
write-host YesterdaysADUsersHashTable Doesnt contains key $UserIDFromAD
write-host "`tadding $UserIDFromAD to the $NewUsersFile file."
Add-content $LogFile "`t$Today Adding $UserIDFromAD to $NewUsersFile file"
Add-Content $NewUsersFile "$UserIDFromAD : $outDate" #if its not in yesterdays list write it to the new users file.
}
}
if ($NoNewUsersFound){
Add-content $LogFile "`t$Today No new users IDs found in Active Directory."
}
#Clear-Content $YesterdaysADUsersFile #we want to overwrite the file, not append to it.
#$ADUsersHashtable.keys `
# | %{ Add-Content $YesterdaysADUsersFile "$_ : $($ADUsersHashtable.$_)" } #writes the content of the hashtable to a file.
The following is the output when the file (and hence $YesterdaysADUsersHashTable) has two entries in it.
The first four lines are the dump of $YesterdaysADUsersHashTable.
The next five lines are from the output from the write-host commands in the if-containskey block. They show that hashtable.containskey is returning true for every key in $ADUsersHashtable. But those keys are not in $YesterdaysADUsersHashTable, this is what i don't understand.
Name Value
---- -----
QZMRW2 xxxx.xxxx#xxxx.com
dzrbcn xxxx.xxxx#xxxx.com
YesterdaysADUsersHashTable contains key QZMRW2
YesterdaysADUsersHashTable contains key dzrbcn
YesterdaysADUsersHashTable contains key MZDP2G
YesterdaysADUsersHashTable contains key BZ5LBQ
YesterdaysADUsersHashTable contains key FZ080Y
$YesterdaysADUsersHashTable clearly doesn't contain "MZDP2G", "BZ5LBQ", or "FZ080Y"
And, if I remove everything from the file except one user ID, the code seems to work.
$YesterdaysADUsersHashTable now has only one entry "QZMRW2" and the code seems to work.
Name Value
---- -----
QZMRW2 xxxx.xxxx#xxxx.com
YesterdaysADUsersHashTable contains key QZMRW2
YesterdaysADUsersHashTable Doesnt contains key dzrbcn
adding dzrbcn to the D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt file.
YesterdaysADUsersHashTable Doesnt contains key MZDP2G
adding MZDP2G to the D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt file.
YesterdaysADUsersHashTable Doesnt contains key BZ5LBQ
adding BZ5LBQ to the D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt file.
YesterdaysADUsersHashTable Doesnt contains key FZ080Y
I am clearly not understanding somehting.
Any suggestions would be greatly appreciated.

Just because a key exists doesn't mean that the corresponding value is not $null.
See below for an example:
$a = #{ keyName = "Value" }
$b = #{ keyName = $null }
foreach($key in $a.Keys){
if($b.ContainsKey($key)){
$bValue = $b[$key] # or `$b.$key` if the key is a string
if($null -eq $bValue){
Write-Host "Key '$key' exists in '`$b', but its value is `$null :("
}
}
}
Without details about how you populate your hashtables it's hard to say why :)

I got the code to work by changing from hashtable.containskey to -contains. In the following code snippit, i commented our the line containing .containsKey($UserIDFromAD) and added the line containing $UserIDsFromFile.keys -contains $UserIDFromAD. The code now works as expected.
This has to be something to do with the difference between checking by reference and checkig by value. I know how this works in Java but not in Powershell.
#if ($UserIDsFromFile.containsKey($UserIDFromAD)){ #If the UserID is in Yesterdays list ignore it.
if ($UserIDsFromFile.keys -contains $UserIDFromAD){ #If the UserID is in Yesterdays list ignore it.

Related

How to add a column to an existing CSV row in PowerShell?

I'm trying to write a simple usage logger into my script that would store information about the time when user opened the script, finished using the script and the user name.
The first part of the logger where I gather the first two data works fine and adds two necessary columns with values to the CSV file. Yet when I run the second part of the logger it does not add a new column to my existing CSV file.
#Code I will add at the very beginning of my script
$FileNameDate = Get-Date -Format "MMM_yyyy"
$FilePath = "C:\Users\Username\Desktop\Script\Logs\${FileNameDate}_MonthlyLog.csv"
$TimeStamp = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
$UserName = [string]($env:UserName)
$LogArray = #()
$LogArrayDetails = #{
Username = $UserName
StartDate = $TimeStamp
}
$LogArray += New-Object PSObject -Property $LogArrayDetails | Export-Csv $FilePath -Notypeinformation -Append
#Code I will add at the very end of my script
$logArrayFinishDetails = #{FinishDate = $TimeStamp}
$LogCsv = Import-Csv $FilePath | Select Username, StartDate, #{$LogArrayFinishDetails} | Export-Csv $FilePath -NoTypeInformation -Append
CSV file should look like this when the script is closed:
Username StartDate FinishDate
anyplane 08/Apr/2018 23:47:55 08/Apr/2018 23:48:55
Yet it looks like this:
StartDate Username
08/Apr/2018 23:47:55 anyplane
The other weird thing is that it puts the StartDate first while I clearly stated in $LogArrayDetails that Username goes first.
Assuming that you only ever want to record the most recent run [see bottom if you want to record multiple runs] (PSv3+):
# Log start of execution.
[pscustomobject] #{ Username = $env:USERNAME; StartDate = $TimeStamp } |
Export-Csv -Notypeinformation $FilePath
# Perform script actions...
# Log end of execution.
(Import-Csv $FilePath) |
Select-Object *, #{ n='FinishDate'; e={ (Get-Date).toString("dd/MMM/yyyy HH:mm:ss") } } |
Export-Csv -Notypeinformation $FilePath
As noted in boxdog's helpful answer, using -Append with Export-Csv won't add additional columns.
However, since you're seemingly attempting to rewrite the entire file, there is no need to use
-Append at all.
So as to ensure that the old version of the file has been read in full before you attempt to replace it with Export-Csv, be sure to enclose your Import-Csv $FilePath call in (...), however.
This is not strictly necessary with a 1-line file such as in this case, but a good habit to form for such rewrites; do note that this approach is somewhat brittle in general, as something could go wrong while rewriting the file, resulting in potential data loss.
#{ n='FinishDate'; e={ (Get-Date).toString("dd/MMM/yyyy HH:mm:ss") } is an example of a calculated property/column that is appended to the preexisting columns (*)
The other weird thing is that it puts the StartDate first while I clearly stated in $LogArrayDetails that Username goes first.
You've used a hashtable (#{ ... }) to declare the columns for the output CSV, but the order in which a hashtable's entries are enumerated is not guaranteed.
In PSv3+, you can use an ordered hashtable instead ([ordered] #{ ... }) to achieve predictable enumeration, which you also get if you convert the hashtable to a custom object by casting to [pscustomobject], as shown above.
If you do want to append to the existing file, you can use the following, but note that:
this approach does not scale well, because the entire log file is read into memory every time (and converted to objects), though limiting the entries to a month's worth should be fine.
as stated, the approach is brittle, as things can go wrong while rewriting the file; consider simply writing 2 rows per execution instead, which allows you to append to the file line by line.
there's no concurrency management, so the assumption is that only ever one instance of the script is run at a time.
$FilePath = './t.csv'
$TimeStamp = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
$env:USERNAME = $env:USER
# Log start of execution. Note the empty 'FinishDate' property
# to ensure all rows ultimately have the same column structure.
[pscustomobject] #{ Username = $env:USERNAME; StartDate = $TimeStamp; FinishDate = '' } |
Export-Csv -Notypeinformation -Append $FilePath
# Perform script actions...
# Log end of execution:
# Read the entire existing file...
$logRows = Import-Csv $FilePath
# ... update the last row's .FinishDate property
$logRows[-1].FinishDate = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
# ... and rewrite the entire file, keeping only the last 30 entries
$logRows[-30..-1] | Export-Csv -Notypeinformation $FilePath
Because your CSV already has a structure (i.e. defined headers), PowerShell honours this when appending and doesn't add additional columns. It is (sort of) explained in this excerpt from the Export-Csv help:
When you submit multiple objects to Export-CSV, Export-CSV organizes
the file based on the properties of the first object that you submit.
If the remaining objects do not have one of the specified properties,
the property value of that object is null, as represented by two
consecutive commas. If the remaining objects have additional
properties, those property values are not included in the file.
You could include the FinishDate property in the original file (even though it would be empty), but the best option might be to export your output to a different CSV at the end, perhaps deleting the original after import then recreating it with the additional data. In fact, just removing the -Append will likely give the result you want.

String matching in PowerShell

I am new to scripting, and I would like to ask you help in the following:
This script should be scheduled task, which is working with Veritas NetBackup, and it creates a backup register in CSV format.
I am generating two source files (.csv comma delimited):
One file contains: JobID, FinishDate, Policy, etc...
The second file contains: JobID, TapeID
It is possible that in the second file there are multiple same JobIDs with different TapeID-s.
I would like to reach that, the script for each line in source file 1 should check all of the source file 2 and if there is a JobID match, if yes, it should have the following output:
JobID,FinishDate,Policy,etc...,TapeID,TapeID....
I have tried it with the following logic, but sometimes I have no TapeID, or I have two same TapeID-s:
Contents of sourcefile 1 is in $BackupStatus
Contents of sourcefile 2 is in $TapesUsed
$FinalReport =
foreach ($FinalPart1 in $BackupStatus) {
write-output $FinalPart1
$MediaID =
foreach ($line in $TapesUsed){
write-output $line.split(",")[1] | where-object{$line.split(",")[0] -like $FinalPart1.split(",")[0]}
}
write-output $MediaID
}
If the CSV files are not huge, it is easier to use Import-Csv instead of splitting the files by hand:
$BackupStatus = Import-Csv "Sourcefile1.csv"
$TapesUsed = Import-Csv "Sourcefile2.csv"
This will generate a list of objects for each file. You can then compare these lists quite easily:
Foreach ($Entry in $BackupStatus) {
$Match = $TapesUsed | Where {$_.JobID -eq $Entry.JobID}
if ($Match) {
$Output = New-Object -TypeName PSCustomObject -Property #{"JobID" = $Entry.JobID ; [...] ; "TapeID" = $Match.TapeID # replace [...] with the properties you want to use
Export-Csv -InputObject $Output -Path <OUTPUTFILE.CSV> -Append -NoTypeInformation }
}
This is a relatively verbose variant, but I prefer it like this.
I am checking for each entry in the first file whether there is a matching entry in the second. If there is one I combine the required fields from the entry of the first list with the ones from the entry in the second list into one object that I can then export very comfortably using Export-Csv.

Line break issue when configuring "send on behalf of"

I have a script to set send on behalf of permissions in Exchange Management Shell, but when you try and use it it fails because the output of the first part is too long and truncates over 2 lines.
First thing we do is build our array from lists of people and put them into some variables to pass:
function Add-Send ($mailbox, $target) {
#"Granting send on behalf for $mailbox to $target"
Set-Mailbox -Identity $mailbox -GrantSendOnBehalfTo #{ Add = $target }
}
We pass a long list as the $target and the maibox name is $mailbox and if we output the text we get:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson" }
All fine and good but if there are more than N characters in the output then we get a line break:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson", ...
..., "cath.cathdotir" }
When you run this script with the overlength output, then command fails as the output which should be passed to the CLI is passed over more than one line. PowerShell treats each line as a separate command, and they obviously fail with bad syntax.
Our string is output from an array that we build like this:
function Send-Array ($mailbox) {
$target = Get-Content ".\list\dpt1.txt"
$target += Get-Content ".\list\$mailbox.txt"
$target += Get-Content ".\list\dpt2.txt"
$target = $target | Select-Object -Unique
$separator = '", "'
$target= $target -replace '^|$','"' -join ','
Add-Send $mailbox $target
}
This gives us an array with strings that look like:
"alan.alanson", "bob.bobson", "steve.stevenson"
From here I am at a loss any ideas would be much appreciated.
The obvious solution would be to pass the names one at a time, but due to a gotcha with Exchange Server every time you set send on behalf of permissions with PowerShell it wipes the existing permissions, so you only end up with he last person granted permissions being able to send on behalf of.
See this link for help with your underlying issue.
Very basically, you will have to:
get the DistinguishedName of the user you need to add
store the current value of GrantSendOnBehalfTo in a variable
append the new user's distinguished name to the list
replace GrantSendOnBehalfTo with the new list
Afterwards you should not need to pass endless strings to the EMS (I hope so).

Layering multiple 'ForEach' statements to loop through a directory and perform actions on each file found

I've been working on this Powershell script for a good week now, and it almost works as expected.
Essentially, the script reaches into the specified directory which we have another script dropping .CSV files into, grabs the .CSV file(s) and pushes the information found into a Sharepoint list, well, that's the intention anyway. I've gotten the script to work perfectly if I manually specify the file, the issue I am having is actually getting all the .CSV files into a group, and then looping through each .CSV to pull the information out and push it into a Sharepoint list. Once done, it renames the file from .CSV to .ARCHIVED for another script to come in and re-locate after we're done with it.
I think I have, through selective (creative) troubleshooting, figured out what I am doing wrong, I just don't know how to proceed after identifying the issue.
I declare the string $Filecsv like so:
$Filecsv = get-childitem "Z:\" -recurse | where {$_.extension -eq ".csv"}
So, this reaches into my 'Z:\' directory, and pulls all the files with .CSV extension and combines them into a table...
ForEach ($items in $Filecsv) {
And this says for each item, perform logic...
foreach($row in $Filecsv)
The only problem is, when I call $Filecsv, it is returning the list of each .CSV file in the directory like such:
And as such, when I execute the bit of code that says 'put the information into my list', only the file name is added to my Sharepoint list....
Now, I can see what's going on here, it's pulling the 'Name' from the $Filecsv table, and pushing that up to Sharepoint, however, I am not sure how to re-construct my logic so that it operates as expected because as it exists now, it should (to me anyway) work as I think it does, but I am still new to Sharepoint and am certainly missing something here.
Below, is the full code, if it helps:
# Add SharePoint PowerShell Snapin which adds SharePoint specific cmdlets
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
#start the counter at 1 to track times script has looped
$iterations = 1
# set the location where the .CSV files will be pulled from and define the
# file extension we are concerned with
$filecsv = get-childitem "Z:\" -recurse | where {$_.extension -eq ".csv"}
# for each file found in the directory
ForEach ($items in $Filecsv) {
# check to see if files exist, if not exit cleanly
if ($Filecsv) {"File exists" + $Filecsv} else {exit}
# count the times we've looped through
"Iterations : $iterations"
# specify variables needed. The webURL should be the site URL, not including the list
# the listName should be the list name
$WebURL = "http://SHAREPOINTURL/"
$listName = "test"
# Get the SPWeb object and save it to a variable
$web = Get-SPWeb -identity $WebURL
# Get the SPList object to retrieve the list
$list = $web.Lists[$listName]
# START deletes all items. code shows the number of items in a list, then deletes all items
# If you don't want your script to delete items, then remove this
$site = new-object Microsoft.SharePoint.SPSite ( $WebURL )
$web = $site.OpenWeb()
"Web is : " + $web.Title
# Enter name of the List below instead of
$oList = $web.Lists["test"];
"List is :" + $oList.Title
"List Item Count: " + $oList.ItemCount
#delete existing contents and replace with new stuff
$collListItems = $oList.Items;
$count = $collListItems.Count - 1
for($intIndex = $count; $intIndex -gt -1; $intIndex--) {
"Deleting record: " + $intIndex
$collListItems.Delete($intIndex);
}
# END Deletes all items
# goes through the CSV file and performs action for each row
foreach($row in $Filecsv)
{
$newItem = $list.items.Add()
$item = $list.items.add()
# Check if cell value is not null in excel
if ($row."Name" -ne $null)
# Add item to sharepoint list. for this one, I had to use the internal column name.
#You don't always have to, but I had trouble with one SharePoint column, so I did
{$newItem["Name"] = $row."Name"}
else{$newItem["Name"] = $row."Not Provided"}
if ($row."Description" -ne $null)
{$newItem["Description"] = $row."Description"}
else{$newItem["Description"] = $row."No Description"}
if ($row."NetworkID" -ne $null)
{$newItem["Network ID"] = $row."NetworkID"}
else{$newItem["Network ID"] = $row."No NetworkID"}
if ($row."Nested" -ne $null)
{$newItem["Nested"] = $row."Nested"}
else{$newItem["Nested"] = $row."Not Nested"}
# Commit the update, then loop again until end of file
$newItem.Update()
}
# get the date and time from the system
$datetime = get-date -f MMddyy-hhmmtt
# rename the file
$NewName = $items.fullname -replace ".csv$","$datetime.csv.archived"
$Items.MoveTo($NewName)
# +1 the counter to count the number of files we've looped through
$iterations ++
}
a very cursory look would suggest that you need to use $items not $filecsv in your main loop.
essentially you are looping over the contents of the $filecsv collection, so you need to look at $items.
Your ForEach loops look redundant since they are both looping through a list of FileInfo objects. I think you want to find all the files, and for each file load it into memory and process it's contents. We'll go that route.
I have moved your SharePoint object creation out of the loop since I don't see any point to creating the object over and over for each file processed since it never references anything based on the file or it's contents. It simply makes the same object over, and over, and over.
# Add SharePoint PowerShell Snapin which adds SharePoint specific cmdlets
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
#start the counter at 1 to track times script has looped
$iterations = 1
# specify variables needed. The webURL should be the site URL, not including the list
# the listName should be the list name
#Setup SP object
$WebURL = "http://SHAREPOINTURL/"
$listName = "test"
# Get the SPWeb object and save it to a variable
$web = Get-SPWeb -identity $WebURL
# Get the SPList object to retrieve the list
$list = $web.Lists[$listName]
# START deletes all items. code shows the number of items in a list, then deletes all items
# If you don't want your script to delete items, then remove this
$site = new-object Microsoft.SharePoint.SPSite ( $WebURL )
$web = $site.OpenWeb()
"Web is : " + $web.Title
# Enter name of the List below instead of
$oList = $web.Lists["test"];
"List is : " + $oList.Title
"List Item Count: " + $oList.ItemCount
#delete existing contents and replace with new stuff
$collListItems = $oList.Items;
$count = $collListItems.Count - 1
for($intIndex = $count; $intIndex -gt -1; $intIndex--) {
"Deleting record: " + $intIndex
$collListItems.Delete($intIndex);
}
# END Deletes all items
Find all the CSV files, and start looping through the list of them. I removed the check to see if the file exists. You just pulled a directory listing to find these files, they really should exist.
# set the location where the .CSV files will be pulled from and define the
# file extension we are concerned with
$CSVList = get-childitem "Z:\" -recurse | where {$_.extension -eq ".csv"}
ForEach ($CSVFile in $CSVList) {
# count the times we've looped through
"Iterations : $iterations"
Now, this is different. It loads the CSV file, and processes each row in it as $row. I'm pretty sure this is what you intended to do from the start. I also changed it from If(Something -ne $null) to check for either null, or empty since either can actually exist and the later can cause you some issues. It's just a safer method in general.
foreach($row in (Import-CSV $CSVFile.FullName))
{
$newItem = $list.items.Add()
$item = $list.items.add()
# Check if cell value is not null in excel
if (![string]::IsNullOrEmpty($row."Name"))
# Add item to sharepoint list. for this one, I had to use the internal column name.
#You don't always have to, but I had trouble with one SharePoint column, so I did
{$newItem["Name"] = $row."Name"}
else{$newItem["Name"] = $row."Not Provided"}
if (![string]::IsNullOrEmpty($row."Description"))
{$newItem["Description"] = $row."Description"}
else{$newItem["Description"] = $row."No Description"}
if (![string]::IsNullOrEmpty($row."NetworkID"))
{$newItem["Network ID"] = $row."NetworkID"}
else{$newItem["Network ID"] = $row."No NetworkID"}
if (![string]::IsNullOrEmpty($row."Nested"))
{$newItem["Nested"] = $row."Nested"}
else{$newItem["Nested"] = $row."Not Nested"}
# Commit the update, then loop again until end of file
$newItem.Update()
}
I don't really understand why you are adding a new item twice, but if it works then more power to you. Then your bit to rename files when you're done with them (hey, this looks familiar):
# get the date and time from the system
$datetime = get-date -f MMddyy-hhmmtt
# rename the file
$NewName = $CSVFile.fullname -replace ".csv$","$datetime.csv.archived"
$CSVFile.MoveTo($NewName)
# +1 the counter to count the number of files we've looped through
$iterations ++
}
I did rename a few things to make them more indicative of what they represent ($Items to $CSVFile and what not). See if this works for you. If you have questions or concerns let me know.
Edit: Ok, to fix the loop trying to pull each item from the current folder we reference the FullName property of it. One line changed:
foreach($row in (Import-CSV $CSVFile.FullName))

PowerShell CSV comparison

I am very, very, very new to Powershell. I was wondering if any one an help me with the following script:
The idea is to have two excel spreadsheets.
1.csv
QCODE
PC1009
PC1009
PC1011
PC1012
2.csv
QCODE
PC1009
PC1009
PC1009
PC1012
I am trying to compare values between the two CSV documents. If the value in cell1 in 1.csv is equal to any cell in 2.csv the script must perform a certain action, once the action is finished it must loop over to cell2 in 1.csv and compare it again with all the values in 2.csv
This is about as far as I have managed yo get:
$CSV=Import-Csv C:\1.csv
$COMP=Import-Csv C:\2.csv
$count=0
$cnt=0
while($count -le $CSV.Count)
{
while($validator -eq $false)
{
if($CSV[$count].QCODE -eq $COMP[$cnt].QCODE)
{
Write-Host "Exiting"
$validator=$true
}
else{
$cnt++
}
}
$count++
}
It's a mess, I apologize. Any help would be greatly appreciated
Here is a solution for you. I have created two CSV files with matching headers. The column names are:
Prop1
Prop2
Prop3
Prop4
Prop5
When these lines are imported into PowerShell, it will automatically create a PSObject for each line. The property names on the PSObject will be the column headers. These two CSV files exist in the folder named c:\test.
NOTE: There is a single, mismatching value between the two files, in the dead middle. This will be our test.
The code looks like this. There are some in-line comments to help guide you. Basically, we're dynamically querying all of the property (column) names, getting the value of each one (the cell values), and comparing them. If they do not match, we throw a warning. Based on the single, mismatching "cell" in this example, the output I get is in a screenshot below. It seems to be working quite well in my testing.
NOTE: Even though it says that line #1 is mismatching, and you might think it's line #2, that's because arrays are zero-based. Therefore, in array terminology, #1 is actually #2, because it starts counting at zero.
# Import both CSV files
$Csv1 = Import-Csv -Path C:\test\csv1.csv;
$Csv2 = Import-Csv -Path C:\test\csv2.csv;
# For each line in CSV1 ...
foreach ($Line1 in $Csv1) {
$LineNumber = $Csv1.IndexOf($Line1);
# Get the same line from CSV2
$Line2 = $Csv2[$LineNumber];
# For each property (column) ...
foreach ($Property in (Get-Member -InputObject $Line1 -MemberType NoteProperty)) {
# Get the property's name
$PropertyName = $Property.Name;
# If the value of the property doesn't match each CSV file ..
if ($Line1.$PropertyName -ne $Line2.$PropertyName) {
# Warn the user
Write-Warning -Message ('Value of property {0} did not match for line # {1}' -f $PropertyName, $LineNumber);
# PERFORM SOME CUSTOM ACTION HERE
};
}
}