PowerShell hash table key issues - powershell

Maybe I am doing it wrong, but when I try and reference a hash table member using the key, I get no results, however when I filter a .GetEnumerator() output with the same key, I get a result.
This doesn't work:
$year = "2015"
$msol_year_members_table = #{}
foreach ($member in $(Get-MsolGroupMember -GroupObjectId $(Get-MsolGroup | ?{ $_.DisplayName -eq $("Class of " + $year) }).ObjectId)) {
$msol_year_members_table[$member.ObjectId] = $member
}
foreach ($mb in $(Get-Mailbox -ResultSize Unlimited)) {
if ($msol_year_members_table.ContainsKey($($mb.ExternalDirectoryObjectId))) {
$msol_year_members_table[$($mb.ExternalDirectoryObjectId)]
}
}
Doing this works though:
foreach ($mb in $(Get-Mailbox -ResultSize Unlimited)) {
if ($result = $msol_year_members_table.GetEnumerator() | ?{ $_.Name -eq $($mb.ExternalDirectoryObjectId) }) {
$result
}
}
Any pointers would be appreciated - assuming it is some stupid mistake.

Are you sure you don't have a type mismatch between the keys and the test values?
When you use the .containskey() method, the argument value must be the same type as the key, but when you use the .getenumerator() method, the -eq test is going to try to coerce the .Name value and the test value to the same type for the operation:
$ht = #{
1 = 'one'
2 = 'two'
3 = 'three'
}
$ht['1']
$ht.GetEnumerator() |? { $_.name -eq '1'}
Name Value
---- -----
1 one

Here is my working code - just in case it helps anyone, I used it to assign address book policies for dir sync'd groups to Office 365 mailboxes:
# Years to be processed
$years = #("2015","2016","2017","2018","2019","2020","2021","2022","2023","2024","2025","2026","2027")
# Loop through each year and retrieve the members for the groups
$msol_year_members_table = #{}
foreach ($year in $years) {
foreach ($member in $(Get-MsolGroupMember -GroupObjectId $(Get-MsolGroup | ?{ $_.DisplayName -eq $("Class of " + $year) }).ObjectId)) {
$key = $member.ObjectId.ToString()
$msol_year_members_table[$key] = $member
}
}
# Loop through the mailboxes and set the mailbox poilicies for matching members
foreach ($mb in $(Get-Mailbox -ResultSize Unlimited)) {
$key = $mb.ExternalDirectoryObjectId.ToString()
if ($msol_year_members_table.ContainsKey($key)) {
$alias = $msol_year_members_table[$key].Alias; $alias_split = $alias.Split(".")
$year = $alias_split[$alias_split.Length-1)]
Set-Mailbox -Identity $alias -AddressBookPolicy $("Class of " + $year + " ABP")
}
}

Related

Comparing all rows of a table Powershell

I have a DataTable ($dt = New-Object system.Data.datatable) which contains entries as below:
My objective is :
Find servers which same names ,ie , from ServerID column trim the part after underscore (_) (which I achieved via Split()) and then compare with rest of the rows.
If the Server Name is same, check the value of all respective "Status" column
If none of the columns have "IN PROCESS" in them for the respective server, then print the ServerID.
This is what I came up with but got stuck since values are not returned correctly:
foreach($backupid in ($dt.'ServerID' | %{foreach ($y in $_){$y.Split('_')[0]}} | sort -Unique)){
foreach ($row in $dt){
if ($row.'ServerID ' -match "^$backupid" -and $row.Status -ne "IN PROCESS" ){
$row.'ServerID '
}
}
}
Just use a hash table to check whether a server id is (not) IN PROCESS, like:
$dt = ConvertFrom-Csv #'
Server,Status
abc_123,"IN PROCESS"
abc_345,"INACTIVE"
abc_546,"INACTIVE"
xyz_123,"INACTIVE"
xyz_457,"INACTIVE"
xyz_230,"INACTIVE"
'#
$InProcess = #{}
$dt | Foreach-Object {
$Id = $_.Server.Split('_')[0]
if (!$InProcess.Contains($Id)) { $InProcess[$Id] = $False }
if ($_.Status -eq 'IN PROCESS') { $InProcess[$Id] = $True }
}
$dt | Foreach-Object {
$Id = $_.Server.Split('_')[0]
if ($InProcess[$Id] -eq $False) { $_ }
}
Server Status
------ ------
xyz_123 INACTIVE
xyz_457 INACTIVE
xyz_230 INACTIVE
Instead of nested loops, try Group-Object!
# Group objects by the first part of the Server ID
$dt |Group { $_.ServerID.Split('_')[0] } |Where-Object {
# Then find only the groups with no objects where Status is IN_PROGRESS
$_.Group.Status -notcontains 'IN_PROGRESS'
} |ForEach-Oject -MemberName ServerID # Output just the Server value

How to use 2 conditions in one foreach loop

I have a script snippet. This gives me an array with 2 propertys: Account and AccessRights. Now I want to build a foreach loop, but I also need to store the second value in a variable for further use.
So if I do:
foreach ($id in $ACLFile.Account) {
# do stuff
}
I only have the Account property saved in $id. But how can I also get its AccessRights value?
$ACLFile = GetNTFSAccess | select Account, AccessRights
$ACLGroup = $ACLFile | Group-Object Account
$Singles = $ACLGroup.Where({$_.Count -eq 1}).Group
$Duplicates = $ACLGroup.Where({$_.Count -gt 1})
$ItemizedDuplicates = $Duplicates | foreach {
[PSCustomObject][ordered]#{
"Account"=$_.Group.Account[0];
"AccessRights" = $_.Group.AccessRights -join ", "
}
}
#($ItemizedDuplicates, $Singles)
Iterate over the objects instead of just one property.
foreach ($acl in $ACLFile) {
$id = $acl.Account
$access = $acl.AccessRights
# ...
}

Is there a wild card that can be used for any column?

If I already have a variable $test with a set of users.
Each user entry has 10 columns that represent email addresses.
How to return only values with a specific entries from the $test variable.
Example of a user entry:
Alias : User01
EmailAddresses_1 : X500:/o=bla bla bla b
EmailAddresses_2 : x500:/o=bla1 bla1 bla1 bla1
EmailAddresses_3 : smtp:USR1#testdomain1.com
EmailAddresses_4 : smtp:user01#testdomain1.com
EmailAddresses_5 : smtp:user1#testdomain2.com
EmailAddresses_6 : SMTP:user001#testdomain1.com
EmailAddresses_7 : SIP:usr01#testdomain1.com
EmailAddresses_8 : smtp:u1#testdomain2.com
EmailAddresses_9 :
EmailAddresses_10 :
So as you can see, some columns are populated with different values and other are empty.
How can I return only the columns with a specific value assuming I only have the variable to work with.
For example all the user entries with only the values that start with "SIP:*"
A little guiding light is appreciated.
If you are looking for the Alias of all the users that have a property name that starts with EmailAddresses and contains a specific value, this might help you out:
$Test = [PSCustomObject]#{
Alias = 'User01'
EmailAddresses_1 = 'X500:/o=bla bla bla b'
EmailAddresses_2 = 'x500:/o=bla1 bla1 bla1 bla1'
EmailAddresses_3 = 'smtp:USR1#testdomain1.com'
EmailAddresses_4 = 'smtp:user01#testdomain1.com'
EmailAddresses_5 = 'smtp:user1#testdomain2.com'
EmailAddresses_6 = 'SMTP:user001#testdomain1.com'
EmailAddresses_7 = 'SIP:usr01#testdomain1.com'
EmailAddresses_8 = 'smtp:u1#testdomain2.com'
EmailAddresses_9 = $null
EmailAddresses_10 = $null
}
$SearchString = 'SIP:'
$Found = Foreach ($T in $Test) {
$Properties = $Test | Get-Member | Where {($_.MemberType -EQ 'NoteProperty') -and ($_.Name -like 'EmailAddresses*')}
Foreach ($P in $Properties) {
if ($T.($P.Name) -like "$SearchString*") {
$T.Alias
}
}
}
$Found | Select -Unique
After clarification in the comments, this might be more what you're looking for:
$SearchString = 'SIP:'
$Test | Select Alias,
#{Name='EmailAddres1';Expression={if ($_.EmailAddresses_1 -like "$SearchString*"){$_.EmailAddresses_1}}},
#{Name='EmailAddres2';Expression={if ($_.EmailAddresses_2 -like "$SearchString*"){$_.EmailAddresses_2}}},
#{Name='EmailAddres3';Expression={if ($_.EmailAddresses_3 -like "$SearchString*"){$_.EmailAddresses_3}}},
#{Name='EmailAddres4';Expression={if ($_.EmailAddresses_4 -like "$SearchString*"){$_.EmailAddresses_4}}},
#{Name='EmailAddres5';Expression={if ($_.EmailAddresses_5 -like "$SearchString*"){$_.EmailAddresses_5}}},
#{Name='EmailAddres6';Expression={if ($_.EmailAddresses_6 -like "$SearchString*"){$_.EmailAddresses_6}}},
#{Name='EmailAddres7';Expression={if ($_.EmailAddresses_7 -like "$SearchString*"){$_.EmailAddresses_7}}},
#{Name='EmailAddres8';Expression={if ($_.EmailAddresses_8 -like "$SearchString*"){$_.EmailAddresses_8}}},
#{Name='EmailAddres9';Expression={if ($_.EmailAddresses_9 -like "$SearchString*"){$_.EmailAddresses_9}}},
#{Name='EmailAddres10';Expression={if ($_.EmailAddresses_10 -like "$SearchString*"){$_.EmailAddresses_10}}}

Powershell : merge two CSV files with partially duplicate lines

I have scraped two files from a website in order to list the companies in my city.
The first lists : name, city, phone number, email
The second lists : name, city, phone number
And I will have duplicate lines if I merge them, as an example, i will have the following :
> "Firm1";"Los Angeles";"000000";"info#firm1.lol"
> "Firm1";"Los Angeles";"000000";""
> "Firm2";"Los Angeles";"111111";""
> "Firm3";"Los Angeles";"000000";"contact#firm3.lol"
> "Firm3";"Los Angeles";"000000";""
> ...
Is there a way to merge the two files and keep the max info like this :
> "Firm1";"Los Angeles";"000000";"info#firm1.lol"
> "Firm2";"Los Angeles";"111111";""
> "Firm3";"Los Angeles";"000000";"contact#firm3.lol"
> ...
According to the fact you've got a file like this called 'firm.csv'
"Firm1";"Los Angeles";"000000";"info#firm1.lol"
"Firm1";"Los Angeles";"000000";""
"Firm2";"Los Angeles";"111111";""
"Firm3";"Los Angeles";"000000";"contact#firm3.lol"
"Firm3";"Los Angeles";"000000";""
You can load it using :
$firms = import-csv C:\temp\firm.csv -Header 'Firm','Town','Tel','Mail' -Delimiter ';'
Then
$firms | Sort-Object -Unique -Property 'Firm'
According to Joey's comment I improved the solution :
$firms | Group-Object -Property 'firm' | % {$_.group | Sort-Object -Property mail -Descending | Select-Object -first 1}
EDIT: just realized the two files don't contain the same headers. Here is an update.
$main = Import-Csv firm1.csv -Header 'Firm','Town','Tel','Mail' -Delimiter ";"
$alt = Import-Csv firm2.csv -Header 'Firm','Town','Tel' -Delimiter ";"
foreach ($f in $alt)
{
$found = $false
foreach($g in $main)
{
if ($g.Firm -eq $f.Firm -and $g.city -eq $f.city)
{
$found = $true
if ($g.Tel -eq "")
{
$g.Tel = $f.Tel
}
}
}
if ($found -eq $false)
{
$main += $f
}
}
# Everything is merged into the $main array
$main
There must be better approach but this is one costy way to do this.
$firms = import-csv C:\firm.csv -Header 'Firm','Town','Tel','Mail' -Delimiter ';'
$Result = #()
ForEach($i in $firms){
$found = 0;
ForEach($m in $Result){
if($m.Firm -eq $i.Firm){
$found = 1
if( $i.Mail.length -ne 0 )
{
$m.Mail = $i.Mail
}
break;
}
}
if($found -eq 0){
$Result += [pscustomobject] #{Firm=$i.Firm; Town=$i.Town; Tel=$i.Tel; Mail=$i.Mail}
}
}
$Result | export-csv C:\out.csv

compare two csv using powershell and return matching and non-matching values

I have two csv files, i want to check the users in username.csv matches with userdata.csv copy
to output.csv. If it does not match return the name alone in the output.csv
For Ex: User Data contains 3 columns
UserName,column1,column2
Hari,abc,123
Raj,bca,789
Max,ghi,123
Arul,987,thr
Prasad,bxa,324
username.csv contains usernames
Hari
Rajesh
Output.csv should contain
Hari,abc,123
Rajesh,NA,NA
How to achieve this. Thanks
Sorry for that.
$Path = "C:\PowerShell"
$UserList = Import-Csv -Path "$($path)\UserName.csv"
$UserData = Import-Csv -Path "$($path)\UserData.csv"
foreach ($User in $UserList)
{
ForEach ($Data in $UserData)
{
If($User.Username -eq $Data.UserName)
{
# Process the data
$Data
}
}
}
This returns only matching values. I also need to add the non-matching values in output
file. Thanks.
something like this will work:
$Path = "C:\PowerShell"
$UserList = Import-Csv -Path "$($path)\UserName.csv"
$UserData = Import-Csv -Path "$($path)\UserData.csv"
$UserOutput = #()
ForEach ($name in $UserList)
{
$userMatch = $UserData | where {$_.UserName -eq $name.usernames}
If($userMatch)
{
# Process the data
$UserOutput += New-Object PsObject -Property #{UserName =$name.usernames;column1 =$userMatch.column1;column2 =$userMatch.column2}
}
else
{
$UserOutput += New-Object PsObject -Property #{UserName =$name.usernames;column1 ="NA";column2 ="NA"}
}
}
$UserOutput | ft
It loops through each name in the user list. Line 9 does a search of the userdata CSV for a matching user name if it finds it it adds the user data for that user to the output if no match is found it adds the user name to the output with NA in both columns.
had to change your userList csv:
usernames
Hari
Rajesh
expected output:
UserName column1 column2
-------- ------- -------
Hari abc 123
Rajesh NA NA
I had a similar situation, where I needed a "changed record collection" holding the entire record when the current record was either new or had any changes when compared to the previous record. This was my code:
# get current and previous CSV
$current = Import-Csv -Path $current_file
$previous = Import-Csv -Path $previous_file
# collection with new or changed records
$deltaCollection = New-Object Collections.Generic.List[System.Object]
:forEachCurrent foreach ($row in $current) {
$previousRecord = $previous.Where( { $_.Id -eq $row.Id } )
$hasPreviousRecord = ($null -ne $previousRecord -and $previousRecord.Count -eq 1)
if ($hasPreviousRecord -eq $false) {
$deltaCollection.Add($current)
continue forEachCurrent
}
# check if value of any property is changed when compared to the previous
:forEachCurrentProperty foreach ($property in $current.PSObject.Properties) {
$columnName = $property.Name
$currentValue = if ($null -eq $property.Value) { "" } else { $property.Value }
$previousValue = if ($hasPreviousRecord) { $previousRecord[0]."$columnName" } else { "" }
if ($currentValue -ne $previousValue -or $hasPreviousRecord -eq $false) {
$deltaCollection.Add($currentCenter)
continue forEachCurrentProperty
}
}
}