So if I create an array from CSV file
name,age,height,fruit
Jon,34,5,orange
Jane,23,4,apple
Dave,27,6,pear
I can read this in using
$list = Import-CSV .....
but now i have the question. "tell me about Jane"
So I could say
foreach ($val in $list)
{
if ($val.name = "jane" ) {$currentuser = $val}
}
write-host $currentuser.name
write-host $currentuser.age
write-host $currentuser.hight
write-host $currentuser.fruit
Is there a better way to do this rather than stepping through? In my actually case i have a list of staff from HR and a separate one from Active directory.
I want to step through the HR list to find the user in AD, set this user as a variable/object. and then update the user using information from HR.
The above method will work but seems very inefficient to be loping through two lists of several thousand users.
Given the array created from the CSV, I want a method that by inputting the string "jane" it will return jane's info to a object i can use.
If you have two lists, both with distinct keys by which the two can be correlated, the best way is to store one list in a lookup table (any type of dictionary will do, including a hashtable in PowerShell), and then loop sequentially through the other list:
$HRList = #'
Name,Position,Department
John,Manager,Sales
Bob,Worker,Production
Sally,Accountant,Finance
'# |ConvertFrom-Csv
$ADList = #'
Name,Username
Sally,sallysalt
Bob,bobburrows
John,johnjames
'# |ConvertFrom-Csv
# Convert the AD list to a hash table
$ADLookupTable = #{}
foreach($ADUser in $ADList)
{
$ADLookupTable[$ADUser.Name] = $ADUser
}
# Go through HR list
foreach($HRUser in $HRList)
{
# Now you can find the relevant object in the other list
$ADUser = $ADLookupTable[$HRUser.Name]
Set-ADUser -Identity $ADUser.Username -Title $ADUser.Position -Department $ADUser.Department
}
Related
I'm attempting to compare two arrays: one contains a list of usernames (dynamic, sometimes more usernames, sometimes less) and the other contains a list of file names (also dynamic). Every file name contains the username along with other text, e.g "Username report [date].xlsx". The goal is to match the elements between Array A and Array B.
Array A is just usernames.
Output of Array A, contained in $Username is just:
PersonA
PersonB
PersonC
etc...
Array B contains filepaths, but I can narrow it down to just filenames like so $ArrayB.Name (the full path would be $ArrayB.FullName). The naming format for Array B is "Username report [date].xlsx".
Output of Array B, contained within $LatestFiles.Name (for the file name) is:
PersonA Report 1-1-21.xlsx
PersonB Report 1-1-21.xlsx
PersonC Report 1-1-21.xlsx
After matching, the final piece would be if element in Array A matches element in Array B, attach ArrayB.FullName to the corresponding username + "#domain.com".
Unfortunately I can't even get the matching to work properly.
I've tried:
foreach ($elem in $UserName) { if ($LatestFiles.Name -contains $elem) { "there is a match" } }
and
foreach ($elem in $UserName) {
if($LatestFiles.Name -contains $elem) {
"There is a match"
} else {
"There is no match"
}
}
and a couple different variations, but I can't get them to output the matches. Any assistance is appreciated.
Short answer to why you can't get matches:
-Contains is meant for matching a value against a collection, not a String. You would be better off using -Like for your comparison operator. Or, at the very least, you may be trying to see if the name of the file, and not simply the part of the name that holds the user, is in the collection of user names.
I sounds like you are not simply comparing the arrays, but the more complicated matter of what you do with the two matching elements.
$LatestFiles |
ForEach-Object {
# first, let's take the formatted file name and make it more usable
$fileName = $_.BaseName -split ' ' # BaseName over Name so that it strips the extension
Write-Output #{
User = $fileName[0]
Date = $fileName[2]
File = $_
}
} -PipelineVariable FileData |
# next, only accept valid users
Where-Object User -In $UserName |
# note that we don't need the value from $UserName because we already have it in $FileData.User (what we matched)
ForEach-Object {
# finally do your thing with it.
$userWithDomain = "$($FileData.User)#domain.com"
Write-Output $userWithDomain # or whatever you want
}
I've taken an issue with a fairly old script we use at my office to assign eFax numbers. Its owner is of course long gone. I've fixed the basic use issues with the script, but I'm trying to streamline it. The current issue I want to address is how it checks a number isn't in use.
I've deciphered that the current build has the phone number ranges listed in a text file like "236,4000,4199" and then it goes through that range and checks each number against every user profile in Active Directory, so its certainly slow. So what I'm looking at is a way to have the array of phone numbers (about 4k total) pulled and entered into a spreadsheet and then I would just use a separate script to do that check against AD to remove the ones in use. That way it just needs to reference a single document instead of a few thousand numbers against a few thousand users.
I don't have anything yet on putting it into Excel as I haven't fully understood the array to verify the phone number and I don't know how I'd output each number into Excel. Here's the original code related to the array to read the numbers:
ipmo act*
$FaxNUmbersINuse = #()
$AvailableFaxNumbers = #()
$AllFaxNumbers = #()
# - WFM Fax Numbers
$FaxNumberRange = Get-Content "\\CEWP9023\Share\FaxNumberRange.txt"
# - Get all Fax numbers
Write-Host "Generating fax numbers..."
foreach ($FaxNumber in $FaxNumberRange) {
$inparray = $FaxNumber.Split(',')
$v1 = $inparray[0]
$v2 = $inparray[1]
$v3 = $inparray[2]
$AllFaxNumbers += $v2..$v3 | foreach {"512-"+$v1+"-"+$_}
}
$FaxNumbersList = $null
$FaxNumbersList = #{}
$TMwithFaxs = Get-ADUser -Filter 'Fax -like "512-*"' -SearchBase "OU=REGIONS,DC=wfm,DC=pvt" -Properties * | select DisplayName, Fax
foreach ($TM in $TMwithFaxs) {
$FaxNumbersList.Add($TM.DisplayName, $TM.Fax)
}
foreach ($AllFaxNumber in $AllFaxNumbers) {
if ($FaxNumbersList.ContainsValue($AllFaxNumber)) {
$FaxNUmbersINuse += $FaxNumbersList
If you are making a new CSV file (and not modifying an existing one), consider Export-CSV.
I'm looking at is a way to have the array of phone numbers (about 4k total) pulled and entered into a spreadsheet
You can export the generated range or the Active Directory range using these lines. I've put them into the script below, and annotated it to try and explain how the numbers are generated.
$AllFaxNumbers | Out-File "C:\temp\AllFaxNumber.txt"
$TMwithFaxs | Export-Csv "C:\temp\ADFaxNumbers.csv" -NoType
The reason these are two different methods is because of the types of objects.
$AllFaxNumbers is an array. It's a simple list of fax numbers and has a single property - Length - that tells you how many fax numbers are in it.
TMwithFaxs is a bit more complex; it's a Microsoft.ActiveDirectory.Management.ADUser object*, with only the Displayname and Fax pulled out and all of the other AD information discarded.
Most likely this won't resolve everything for you - it's best to break your problem down into pieces and ask about a specific problem when you post a question.
ipmo act*
$FaxNUmbersINuse = #()
$AvailableFaxNumbers = #()
$AllFaxNumbers = #()
# - WFM Fax Numbers
$FaxNumberRange = Get-Content "\\CEWP9023\Share\FaxNumberRange.txt"
# - Get all Fax numbers
Write-Host "Generating fax numbers..."
#using 236,4000,4199 as an example
foreach ($FaxNumber in $FaxNumberRange) {
$inparray = $FaxNumber.Split(',') # split 236,4000,4199 into an array
$v1 = $inparray[0] # 236 is the first element
$v2 = $inparray[1] # 4000 is the second element
$v3 = $inparray[2] # 4199 is the third element
# generate the numbers "512-236-4000" to "512-236-4199"
# i.e. "512-236-4000", "512-236-4001", "512-236-4002" etc
$AllFaxNumbers += $v2..$v3 | foreach {"512-"+$v1+"-"+$_}
}
$AllFaxNumbers | Out-File "C:\temp\AllFaxNumber.txt"
$FaxNumbersList = $null
$FaxNumbersList = #{}
$TMwithFaxs = Get-ADUser -Filter 'Fax -like "512-*"' -SearchBase "OU=REGIONS,DC=wfm,DC=pvt" -Properties * | select DisplayName, Fax
$TMwithFaxs | Export-Csv "C:\temp\ADFaxNumbers.csv" -NoType
foreach ($TM in $TMwithFaxs) {
$FaxNumbersList.Add($TM.DisplayName, $TM.Fax)
}
foreach ($AllFaxNumber in $AllFaxNumbers) {
if ($FaxNumbersList.ContainsValue($AllFaxNumber)) {
$FaxNUmbersINuse += $FaxNumbersList
* Because you've selected a subset of all available properties, it may just be a generic PSObject - i don't have AD locally to test.
If I understand the question correctly, you have a text file from which you can generate a list of all possible fax numbers.
Next, you want to compare that to the numbers found in AD and using that, you want to create a new file containing all numbers that are available.
This approach is maybe a little different in that it creates a single CSV file for import in Excel with information about both the used and the available numbers.
The resulting CSV file will be like:
"UserName","FaxNumber","Available"
"Billy Joel","512-326-4000","No"
"","512-326-4001","Yes"
"Alice Cooper","512-326-4002","No"
The code for this:
Import-Module ActiveDirectory
$path = '\\CEWP9023\Share'
$adSearchBase = 'OU=REGIONS,DC=wfm,DC=pvt'
##########################################################
# Step 1: read the number ranges from the text file
##########################################################
# - WFM Fax Numbers. Format: AreaCode, RangeStart, RangeEnd
$faxNumberRange = Get-Content (Join-Path -Path $path -ChildPath 'FaxNumberRange.txt')
# - Get all Fax possible numbers
Write-Host "Generating all possible fax numbers..."
# use a HashSet object for faster lookup
$generatedNumbers = New-Object 'System.Collections.Generic.HashSet[String]'
foreach ($item in $faxNumberRange) {
$areaCode, [int]$rangeStart, [int]$rangeEnd = $item -split ','
# add the numbers unformatted for easier comparison later
$rangeStart..$rangeEnd | ForEach-Object {
[void]$generatedNumbers.Add(("512{0}{1}" -f $areaCode, $_ ))
}
}
##########################################################
# Step 2: find AD users with faxnumber
##########################################################
# create a list containing PSCustomObjects storing user DisplayName
# and the faxnumber as it is entered in AD
Write-Host "Retrieving all users with fax numbers..."
# create a Hasttable for the info gathered from AD
$userNumbers = #{}
Get-ADUser -Filter 'Fax -like "512-*"' -SearchBase $adSearchBase -Properties DisplayName, Fax |
ForEach-Object {
$fax = $_.Fax -replace '\D', '' # remove all non-numeric characters
if ($userNumbers.ContainsKey($fax)) {
# Hmmmm. This number is already in the list. Possibly two or more users are found with the same number
# Add this Nth user with a pipe symbol to the UserName property.
$userNumbers.$fax.UserName += (' | {0}' -f $_.DisplayName)
}
else {
$faxInfo = [PSCustomObject]#{
UserName = $_.DisplayName
FaxNumber = $_.Fax
Available = 'No'
}
# use the unformatted faxnumber as Key
$userNumbers.$fax = $faxInfo
}
}
##########################################################
# Step 3: create a final list containing all numbers
##########################################################
# Test each generated number against the (unformatted) AD faxnumbers
# and add them in a new All-Numbers list
Write-Host "Generating complete list of fax numbers and users..."
# use an arraylist object for better performance
$allNumbers = New-Object 'System.Collections.ArrayList'
# loop through all generated numbers
foreach ($fax in $generatedNumbers) {
if ($userNumbers.ContainsKey($fax)) {
# this is a number in use so add the info from AD
[void]$allNumbers.Add($userNumbers.$fax)
}
else {
# create a new entry for this unused number
$faxInfo = [PSCustomObject]#{
UserName = '' # no user DisplayName also means: number is available
FaxNumber = '{0}-{1}-{2}' -f $fax.Substring(0,3), $fax.Substring(3,3), $fax.Substring(6)
Available = 'Yes'
}
[void]$allNumbers.Add($faxInfo)
}
}
# you can free some memory here
$generatedNumbers.Clear()
$userNumbers.Clear()
# output the completed list as CSV file
$allNumbers.ToArray() | Export-Csv -Path (Join-Path -Path $path -ChildPath 'AllFaxNumbers.csv') -NoTypeInformation
I have a CSV file (Comma Separated Values)
The file looks like this:
20171108,120909470,SO1244,12,101
20171109,122715740,AG415757,11,101
I need to obscure the data in (for example) columns 3 and, without affecting any of the other entries in the file.
I want to do this using a hashing algorithm like SHA1 or MD5, so that the same strings will resove to the same hash values anywhere they are encountered.
I need to send data to a third party, and certain columns contain sensitive information (e.g. customer names). I need the file to be complete, and where a string is replaced, I need it to be done in the same way every time it is encountered (so that any mapping or grouping remains). It does not need military encryption, just to be difficult to reverse. As I need to to this intermittently, a scripted solution would be ideal.
What is the easiest way to achieve this using a command line tool or script?
By preference, I would like a PowerShell script, since that does not require any additional software to achieve...
This question seems like a duplicate of I need to hash (obfuscate) a column of data in a CSV file. Script preferred but the proposed solution didn't resolve my problem and throws the following error
You cannot call a method on a null-valued expression.
At C:\Users\mey\Hashr.ps1:4 char:5
+ $_.column3 = $_.column3.gethashcode()
The script is the following
(Import-Csv .\results.csv -delimiter ',' ) | ForEach-Object{
$_.column3 = $_.column3.gethashcode()
$_
} | Export-Csv .\myobfuscated.csv -NoTypeInformation -delimiter ','
Update:
Here's the program i am running and that has been proposed by #BaconBits:
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[String[]]
$String,
[Parameter(Position = 1)]
[ValidateSet('SHA1', 'MD5', 'SHA256', 'SHA384', 'SHA512')]
[String]
$HashName = 'SHA256'
)
process {
$StringBuilder = [System.Text.StringBuilder]::new(128)
[System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | ForEach-Object {
[Void]$StringBuilder.Append($_.ToString("x2"))
}
$StringBuilder.ToString()
}
}
$csv = Import-Csv .\results.csv -delimiter ','
foreach ($line in $csv) {
$line.column1 = Get-StringHash $line.column1
}
$csv | Export-Csv .\myobfuscated.csv -NoTypeInformation -delimiter ','
The csv file i am importing is an output from another java program i made and it creates no header, it just fill the csv file with values
I am getting this error
Get-StringHash : Cannot bind argument to parameter 'String' because it is null.
Based on the doc, you're not going to want to use GetHashCode() this way:
A hash code is intended for efficient insertion and lookup in
collections that are based on a hash table. A hash code is not a
permanent value. For this reason:
Do not serialize hash code values or store them in databases.
Do not use the hash code as the key to retrieve an object from a keyed collection.
Do not send hash codes across application domains or processes. In some cases, hash codes may be computed on a per-process or
per-application domain basis.
Do not use the hash code instead of a value returned by a cryptographic hashing function if you need a cryptographically strong
hash. For cryptographic hashes, use a class derived from the
System.Security.Cryptography.HashAlgorithm or
System.Security.Cryptography.KeyedHashAlgorithm class.
Do not test for equality of hash codes to determine whether two objects are equal. (Unequal objects can have identical hash codes.) To
test for equality, call the ReferenceEquals or Equals method.
Bullet point 4 is the main problem. There's no guarantee that the hashing isn't reversible. The hashing function used is an implementation detail, not a secure cryptographic function like SHA.
I'd use a function like this one:
function Get-StringHash {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[String[]]
$String,
[Parameter(Position = 1)]
[ValidateSet('SHA1', 'MD5', 'SHA256', 'SHA384', 'SHA512')]
[String]
$HashName = 'SHA256'
)
process {
$StringBuilder = [System.Text.StringBuilder]::new(128)
[System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | ForEach-Object {
[Void]$StringBuilder.Append($_.ToString("x2"))
}
$StringBuilder.ToString()
}
}
$csv = Import-Csv .\results.csv -delimiter ',' -Header column1,column2,column3,column4,column5
foreach ($line in $csv) {
$line.column3 = Get-StringHash $line.column3
}
$csv | Export-Csv .\myobfuscated.csv -NoTypeInformation -delimiter ','
I believe I based that function off of this one, but it's been awhile since I've written it.
Edit by LotPings to show results of hash
"column1","column2","column3","column4","column5"
"20171108","120909470","0cdd3c3acdb7cfa107286565c044c5a0f1e58268f6f10e7e3415ff84942e577d","12","101 "
"20171109","122715740","0a7fb9f6bb7a180f2fd9429b0fbd1e7b0a83597b6a64aa6a123cef3e84700fe3","11","101"
Bacon Bits appears to have the correct methodology minus one part. The ForEach loop in your original example does not modify the original variable. Also, it appears the column you want to modify is not 'Column3', but 'Column #2' as the headers begin at zero. I'll repeat the function provided in Bacon Bits's suggestion.
function Get-StringHash {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[String[]]
$String,
[Parameter(Position = 1)]
[ValidateSet('SHA1', 'MD5', 'SHA256', 'SHA384', 'SHA512')]
[String]
$HashName = 'SHA256'
)
process {
$StringBuilder = [System.Text.StringBuilder]::new(128)
[System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | ForEach-Object {
[Void]$StringBuilder.Append($_.ToString("x2"))
}
$StringBuilder.ToString()
}
}
I would suggest for the substitution:
$csv = Import-Csv .\results.csv | Select-Object *,#{n='Column #2';e={Get-StringHash $_.'Column #2'}} -ExcludeProperty 'Column #2'
$CSV | Export-Csv .\myobfuscated.csv -NoTypeInformation
This will put the 'Column #2' last in the CSV. You can simply list them explicitly if you need it to appear in the same order, e.g.:
Select-Object 'Column #0','Column #1',#{n='Column #2';e={Get-StringHash $_.'Column #2'}},'Column #3'
I wrote script to create N number of items to custom SharePoint list in PowerShell. I made it work with fixed values, but now I would like to create items with random values.
I created internal variables that are array of values and I would like out of those to be set in list columns.
I made it work for some simple columns like date, single and multi line of text, etc. But I can't seem to make it work for lookup and people picker values. Bellow is example of script I made.
For this example, I don't get error in PowerShell I just get incorrect result in columns, example is I get username in people picker column that is not in value userName array. I get ID;#User8 (for example) that is not in array.
If you have any suggestion what I should change or add to get only values from array?
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
$webUrl = "site url"
$listName = "list name"
$numberItemsToCreate = N
# userName is array of values of usernames that would be set to people picker column
$userName = ID;#user1, ID;#User2, ID;#User3, ID;#User6, ID;#User10
# projectName is array of values of projects that are lookup column from another list
$projectName = 1;#Project1, 2;#Project2, 3;#Project3, 4;#Project4
#
$web = Get-SPWeb $webUrl
$list = $web.Lists[$listName]
for($i=1; $i -le $numberItemsToCreate; $i++)
{
$newItem = $list.AddItem()
$newItem["Title"] = "Title"
$newItem["Project"] = (Get-Random $projectName)
$newItem["DueDate"] = "2017-12-12 00:00:00"
$newItem["Assigned_x0020_To"] = (Get-Random $userName)
$newItem["EstimatedFinishDate"] = "2017-12-12 00:00:00"
#$newItem["Status"] = "ToDo"
$newItem["URLs"] = "www.google.com"
$newItem.Update()
}
$web.Dispose()
This "$userName = ID;#user1, ID;#User2, ID;#User3, ID;#User6, ID;#User10" is not a PowerShell array or hashtable
Using an array:
$userName = #("#user1", "#User2", "#User3", "#User6", "#User10")
$newItem["Assigned_x0020_To"] = Get-Random $userName
Using a hashtable:
$projectName = #{1="#Project1"; 2="#Project2"; 3="#Project3"; 4="#Project4"}
$newItem["Project"] = $ProjectName.(Get-Random #($ProjectName.Keys))
Or just:
$newItem["Project"] = Get-Random #($ProjectName.Values)
I have two arrays imported from csv files, which from here on i will refer to as the master array and update array.
The master array has three extra columns on the front, and three extra columnns on the back.
Each day i get a new update array that i need to do two things with.
A) Remove any rows on the master that do not appear in the update
B) Add any rows that appear in the update but not the master to the master
I am still fairly new to powershell, and scripting in general(mostly self taught) and can't figure out how to approach this. I know there's a compare-object command, so I can get a list of which rows match pretty easily, but I'm not sure how to combine them the way I want.
Edit:
The master array entries have this information:
ef: true
ea: true
rem: true
accountname: example1
Enabled: True
PasswordLastSet: 01/08/2002 13:14:19
whencreated: 01/08/2002 13:14:19
Description:
Owner Email: johnsmith#email.com
givenname: John
sn: Smith
manager: John Doe
Level2: Person 1
Level3: Person 2
Level4: Person 3
While the updates only have:
accountname: example1
Enabled: True
PasswordLastSet: 01/08/2002 13:14:19
whencreated: 01/08/2002 13:14:19
Description:
Owner Email: johnsmith#email.com
givenname: John
sn: Smith
manager: John Doe
Assuming the accountname column can be used as the unique key that ties the two arrays together, you could use something like the below script. It creates a third array and then overwrites the master array csv once completed.
$arrmaster = import-csv c:\temp\arrmaster.csv
$arrupdate = import-csv c:\temp\arrupdate.csv
$arrworking = #()
foreach ($rowupdate in $arrupdate){
$rowmaster = #($arrmaster | where {$_.accountname -eq $rowupdate.accountname})
if ($rowmaster.Count -lt 1){
Write-Debug "Could not find record for $($row.accountname)"
}
if ($rowmaster.Count -gt 1){
Write-Debug "Found duplicate records for $($row.accountname)"
}
if ($rowmaster.Count -eq 1){
$rowworking = "" | select ef,ea,rem,accountname,Enabled,PasswordLastSet,whencreated,Description,"Owner Email",givenname,sn,manager,Level2,Level3,Level4
$rowworking.ef = $rowmaster.ef
$rowworking.ea = $rowmaster.ea
$rowworking.rem = $rowmaster.rem
$rowworking.accountname = $rowupdate.accountname
$rowworking.Enabled = $rowupdate.Enabled
$rowworking.PasswordLastSet = $rowupdate.PasswordLastSet
$rowworking.whencreated = $rowupdate.whencreated
$rowworking.Description = $rowupdate.Description
$rowworking."Owner Email" = $rowupdate."Owner Email"
$rowworking.givenname = $rowupdate.givenname
$rowworking.sn = $rowupdate.sn
$rowworking.manager = $rowupdate.manager
$rowworking.Level2 = $rowmaster.Level2
$rowworking.Level3 = $rowmaster.Level3
$rowworking.Level4 = $rowmaster.Level4
$arrworking += $rowworking
}
}
$arrworking | Export-Csv -Force -NoTypeInformation c:\temp\arrmaster.csv
Not tested, but I think this should work:
$MasterFile = 'c:\somedir\master.csv'
$UpdateFile = 'c:\somedir\update.csv'
$master= #{}
$update = #{}
import-csv $MasterFile |
ForEach-Object { $master[$_.accountname] = $_ }
import-csv $update |
ForEach-Object { $update[$_.accountname] = $_ }
#Get Master entries contained in Update
[array]$NewArray = $master.keys |
Where-Object { $update.keys -contains $_ } |
ForEach-Object { $master[$_] }
#Get Updates not in Master
$NewArray += $update.keys |
Where-Object { $master.keys -notcontains $_ } |
ForEach-Object { $update[$_] }
$NewArray | Export-Csv 'c:\somedir\new_master.csv' -NoTypeInformation
That starts by loading each of your arrays into a hash table, indexed by the accountname. Then the keys are used to extract the master entries that have an accountname that appears in the update keys and load that into a new array. Then the process is reversed and the update keys compared to the master keys, and any entries that do not have a matching key in the master are added to the array. Then the array is exported to csv.
The CSV export will create it's header row from the first entry, and add the necessary commas for any objects in the array afterward that are missing properties. You don't have to worry about adding the missing properties to the update entries as long as they're added after the master entries.
Ok, again based off the assumption that AccountName is a unique identifier that both lists would have in common you can run this:
$Master = Import-CSV Master.csv
$Update = Import-CSV Update.csv
$T2Keys = $Master|gm|?{$_.MemberType -match "Property"}|Select -ExpandProperty Name
$T1Keys = $Update|gm|?{$_.MemberType -match "Property"}|Select -ExpandProperty Name
$KeysToAdd = $T2Keys|?{$T1Keys -notcontains $_}
$NewMaster = #()
$NewMaster += $Update | ?{!($Master.accountname -contains $_.accountname)}
$KeysToAdd|%{$NewMaster|Add-Member $_ ""}
$NewMaster += $Master | ?{$Update.accountname -contains $_.accountname}
$Newmaster| Select ef,ea,rem,accountname,enabled,passwordlastset,whencreated,description,'owner email',givenname,sn,manager,level2,level3,level4|Export-CSV NewMaster.csv -notype
Ok, that will import a CSV for the master list and the updates list. If you already have those as objects then skip the import-csv lines. Then it gets all properties from both, and figures out which ones to add to the updates (the 6 that the master has that the updates doesn't). It then creates an empty array and adds all records from the Updates list to it that aren't in the master list. Then it adds the missing fields, and adds all the records from the master list that are in the updates list. Then it exports it to a CSV. So it does what you asked:
Gets all records from the master list that are in both lists.
Adds
records from the update list that are missing from the master list.
Edit: The reason I had asked if you had searched is that 95% of my answer there was almost copied and pasted from this question that I answered just under a month ago. But hey, it's all good, not that hard for me to copy and paste to get you an answer, and I kinda knew what I was looking for anyway. I don't know that the other question's title would have been indicative that it had what you needed.