I'm having an issue trying to find the index number of an item in an array that contains Active directory users.
I create the array as follows:
$outarray = #()
$outarray = get-aduser -Filter * -Properties LastLogon | select "Name","SAMAccountName","LastLogon" | sort samaccountname
Now i have the users in an array, and i can prove it using standard variable queries
$outarray[0]
$outarray[1]
Returns exactly what i expect.
BUT
I completely fail to search for the index of a name or SAMAccountName in the array, as they are properties of the array.
$index = [array]::IndexOf($outarray.samaccountname, "testuser")
returns -1 (not found) or 0 only if testuser is the FIRST user in the array.
I cannot find any other user index in the array.
My goal after getting the index is to use it to update the property for lastlogon. This works if i do it manually
e.g.
$outarray[123].lastlogon = 12345678
The only way i can make this work is to manually build the array initially, one entry at a time instead of filling directly
foreach ($user in $outArray)
{
$myobj = #()
$myobj = "" | Select "Name","SAMAccountName","LastLogon"
#fill the object
$myobj.Name = $user.name
$myobj.SAMAccountName = $user.samaccountname
$myobj.LastLogon = $user.LastLogon
#Add the object to the array
$userarray += $myobj
}
$userarray[[array]::IndexOf($userarray.samaccountname, "testuser")].LastLogon = 12345678
Then the search works. I assume this has to do with property types, but im completely out my depth by this stage.
Thanks in advance for any help, I'm no expert on powershell arrays, they confuse me! :)
I think you're looking at this the wrong way. Instead of finding the index of a specific item and then access that item by its index you could do it the PoSh way by filtering the array for the item you want to update, like this:
$userarray | ? {
$_.SamAccountName -eq 'testuser'
} | % {
$_.LastLogon = 12345678
}
or like this:
$acct = $userarray | ? { $_.SamAccountName -eq 'testuser' } | select -First 1
$acct.LastLogon = 12345678
Related
I have one array of hashtables like the one below:
$hashtable1 = #{}
$hashtable1.name = "aaa"
$hashtable1.surname =#()
$hashtable1.surname += "bbb"
$hashtable2 = #{}
$hashtable2.name = "aaa"
$hashtable2.surname =#()
$hashtable2.surname += "ccc"
$hashtable3 = #{}
$hashtable3.name = "bbb"
$hashtable3.surname = #()
$hashtable3.surname += "xxx"
$A = #($hashtable1; $hashtable2; $hashtable3)
I need to iterate though the array and I need to find out duplicates based on hashtable[].name
Then I need to group those hashtable.surname to hashtable[].surname so that the result will be an array of hashtables that will group all for name all the surnames:
$hashtable1.name = "aaa"
$hashtable1.surname = ("bbb","ccc")
$hashtable3.name = "bbb"
$hashtable3.surname = ("xxx")
I was looking into iterating to empty array
+
I have found this link:
powershell compare 2 arrays output if match
but I am not sure on how to reach into the elements of the hashtable.
My options:
I was wondering if -contain can do it.
I have read about compare-object but I am not sure it can be done like that.
(It looks a bit scary in the moment)
I am on PS5.
Thanks for your help,
Aster
You can group your array items by the names using a scriptblock like so.
Once grouped, you can easily build your output to do what you seek.
#In PS 7.0+ you can use Name directly but earlier version requires the use of the scriptblock when dealing with arrays of hashtables.
$Output = $A | Group-Object -Property {$_.Name} | % {
[PSCustomObject]#{
Name = $_.Name
Surname = $_.Group.Surname | Sort-Object -Unique
}
}
Here is the output variable content.
Name Surname
---- -------
aaa {bbb, ccc}
bbb xxx
Note
Improvements have been made in PS 7.0 that allows you to use simply the property name (eg: Name) in Group-Object for arrays of hashtables, just like you would do for any other arrays type. For earlier version though, these particular arrays must be accessed by passing the property in a scriptblock, like so: {$_.Name}
References
MSDN - Group_Object
SS64 - Group Object
Dr Scripto - Use a Script block to create custom groupings in PowerShell
Can't get my head around how to check if an object member exists in array by property.
I have the following object:
PS> $siteUser
Id Title LoginName Email
-- ----- --------- -----
1305 cinuwyl#banit.club i:0#.f|membership|urn%3aspo%3aguest#cinuwyl#banit.club cinuwyl#banit.club
I would like to check if the string membership from the property LoginName exists within the array:
federateddirectoryclaimprovider
tenant
membership
I've gotten only as far as getting a match by specifying the array index for membership:
$siteUsers.LoginName | Where-Object {$_ -match $inclusionObjects[2]}
However, this requires that I know the array index for the matching string in advance.
Another thing I've tried but that yields no results is:
$siteUsers | Where-Object {$inclusionObjects | ForEach-Object {$_ -match $_.LoginName}}
Is there a way to kind of go through each item in the array?
To check if ANY word in list matches the $siteUsers.LoginName, you can use following:
$siteUsers = [pscustomobject]#{
Id=1305;
Title='cinuwyl#banit.club';
LoginName='i:0#.f|membership|urn%3aspo%3aguest#cinuwyl#banit.club';
Email='cinuwyl#banit.club'
}
$inclusionObjects = [string[]]'federateddirectoryclaimprovider','tenant','membership'
$predicate = [Func[string,bool]]{$siteUsers.LoginName.Contains($args[0])}
[System.Linq.Enumerable]::Any($inclusionObjects, $predicate)
I've been struggling with this for a couple of days, and I'm not sure how to conquer it. I need to do the following:
Import a csv of users with the following values:
ID, Name, Region
Create an array based on the Region values that I can then use to populate with ID's and Names with that region, ie.
Array_SEA
AA_SCOM, Adam Andrews, SEA
Array_OAK
BB_SCOM, Bob Barker, OAK
Here's the code I've got right now:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#()
foreach ($vitem in $list2)
{
$arraylist += New-Object PsObject -Property #{'Array' = "Array_" + $vitem.bu}
}
foreach ($varray in $arraylist)
{
$arr = new-variable -Name $varray
$arr.value += $varray.array
$arr
}
This produces the following error for records with a duplicate regions:
New-Variable: A variable with name '#{Array=Array_SCA}' already exists.
I'm also getting the following when it tries to add values:
Property 'value' cannot be found on this object; make sure it exists and is settable.
I get that I'm not actually creating arrays in the second section, but I'm not sure how to pass the output of the variable to an array name without turning the variable declaration into the array name, if that makes sense.
I've tried the following with hash tables, and it gets closer:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#{}
foreach ($vitem in $list2){$arraylist[$vitem.bu] = #()}
foreach ($record in $list2)
{
$arraylist[$vitem.bu] += ($record.SCOMID,$record.Name,$record.BU)
Write-host "Array: "
$arraylist[$vitem.bu]
write-host ""
}
The output on this shows no errors, but it just keeps showing the added fields for all of the records for each iteration of the list, so I don't think that it's actually assigning each unique BU to the array name.
I like the hashtable-approach, but I would finetune it a little. Try:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist = #{}
foreach ($vitem in $list2){
if($arraylist.ContainsKey($vitem.BU)) {
#Array exists, add item
$arraylist[($vitem.BU)] += $vitem
} else {
#Array not found, creating it
$arraylist[($vitem.BU)] = #($vitem)
}
}
#TEST: List arrays and number of entries
$arraylist.GetEnumerator() | % {
"Array '$($_.Key)' has $($_.Value.Count) items"
}
You could also use Group-Object like:
$list2 = ipcsv .\TSE_Contact_List.csv | Group-Object BU
#TEST: List groups(regions) and number of entries
$list2 | % {
"Region '$($_.Name)' has $(#($_.Group).Count) items"
}
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.
I am new to powershell and am writing my first somewhat complicated script. I would like to import a .csv file and create multiple text arrays with it. I think that I have found a way that will work but it will be time consuming to generate all of the lines that I need. I assume I can do it more simply using foreach-object but I can't seem to get the syntax right.
See my current code...
$vmimport = Import-Csv "gss_prod.csv"
$gssall = $vmimport | ForEach-Object {$_.vmName}`
$gssweb = $vmimport | Where-Object {$_.tier -eq web} | ForEach-Object {$_.vmName}
$gssapp = $vmimport | Where-Object {$_.tier -eq app} | ForEach-Object {$_.vmName}
$gsssql = $vmimport | Where-Object {$_.tier -eq sql} | ForEach-Object {$_.vmName}
The goal is to make 1 group with all entries containing only the vmName value, and then 3 separate groups containing only the vmName value but using the tier value to sort them.
Can anyone help me with an easier way to do this?
Thanks!
For the last three you can group the object by the Tier property and have the result as a hasthable. Then you can reference the Tier name to get its VMs.
#group objects by tier
$gs = $vmimport | Group-Object tier -AsHashTable
# get web VMs
$gs['web']
# get sql VMs
$gs['app']
You may want to use a dictionary for storing the data:
$vmimport = Import-Csv "gss_prod.csv"
$gssall = $vmimport | % { $_.vmName }
$categories = "web", "app", "sql", ...
$gss = #{}
foreach ($cat in $categories) {
$gss[$cat] = $vmimport | ? { $_.tier -eq $cat } | % { $_.vmName }
}
I like the Shay Levy way, but the values of hash tables remain hash tables. Here is an other more efficient approach where values are jagged arrays, and categories are made automatically (contrary to Ansgar Wiechers solution):
# define hashtable
$gs = #{};
# fill it
$vmimport | foreach {$gs[$_.tier]+=, $_.vmName};
# get web VMs
$gs['web'] # the result is an array of 'web' vmNames.