PowerShell method Contains not working as expected - powershell

I am very new to PowerShell and I need to prepare some PowerShell commands to a assign users some roles in Exchange Online. I first need to check if the user is assigned the role already and if not, I'll assign the role. But I just can't figure out how to check if one email address is part of list of email addresses.
I found many discussions on -contains or .Contains method not working but their solutions never worked for me. My code is below.
$L1Group = Get-MsolGroup | Where-Object { $_.DisplayName -eq "TEST_GROUP"}
$L1Members = Get-MsolGroupMember -GroupObjectId $L1Group.ObjectId
$CurrentMembers = Get-RoleGroupMember "Recipient Management" |
Select -Property WindowsLiveID
foreach ($user in $L1Members) {
if (!$CurrentMembers.Contains($user.EmailAddress)) {
Add-RoleGroupMember "Recipient Management" -Member $user.EmailAddress
}
}
It works fine without the if statement and it assigns roles as expected, but I need to skip users who are already members of the role group. I just cant figure out how to make the if statement work. If I use echo $CurrentMembers it returns list of 4 email addresses, just as expected and even if I try
$CurrentMembers -contains 'MyEmail#MyDomain.com'
it always returns False but the address is in the list.

Your code doesn't work the way you expect because this statement
$CurrentMembers = Get-RoleGroupMember "Recipient Management" |
Select -Property WindowsLiveID
produces a list of custom objects with a single property WindowsLiveID, but your check expects a list of strings. Expand the property to get that list of strings:
$CurrentMembers = Get-RoleGroupMember "Recipient Management" |
Select -Expand WindowsLiveID

This is a common question. .contains and -contains are different. $CurrentMembers may contain sub-properties.
PS C:\Users\admin\foo> 'hi there'.contains('hi')
True
PS C:\Users\admin\foo> 'hi','there' -contains 'hi'
True
Does this work? Another common question is how to get just the value from the property.
if (! ($CurrentMembers.windowsliveid -Contains $user.EmailAddress) )
Hmm, interesting how .contains() works with arrays. It's more like -contains. More confusion!
PS C:\Users\js\foo> $a = 'one','two','three'
PS C:\Users\js\foo> $a.contains('one')
True
PS C:\Users\js\foo> $a.contains('o')
False
PS C:\Users\js\foo> $a.contains
OverloadDefinitions
-------------------
bool IList.Contains(System.Object value)

Solution is in comments - "Select -Property WindowsLiveID -> Select -Expand WindowsLiveID"
– Ansgar Wiechers 2 hours ago
Hopefully Angsar will post an answer so he can get credit...
Here's a link to the M$oft doc for the Select-Object cmdlet:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-object?view=powershell-6

Related

Powershell Azure See Cannot See what groups a contact is in

I am trying to see the groups a contact is apart of. To provide some background, the contact is hidden in the directory of the tenet I am looking in because it is the alias of a sister tenet that the user is actually apart of. I am using PS and the cmd runs but displays no results. Can someone tell me where I may be going wrong? The users email address is stored in variable string called contact
Code:
Get-MsolGroup -All | Where-Object {$_.Members -contains $contact}
When I enter this command in PowerShell I do not get any results displayed
Untested but, I believe this should work. It should return the ObjectId and DisplayName of the Groups where $contact is a member of.
$groups = Get-MsolGroup -All
$contact = 'someUser#someCompany.com'
foreach($group in $groups)
{
$members = Get-MsolGroupMember -GroupObjectId $group.ObjectId
if($contact -in $members.EmailAddress)
{
$group | Select-Object ObjectId, DisplayName
}
}

Foreach in foreach (nested)

I'm trying to loop all disabled users through an array of groups to check if the users have membership in any of the listed groups. My thought is that for every user in the list loop them through and check if they are present in one of the listed groups. That would require nesting foreach loops, right? The output I get is like this:
...
user1
user2
user3
is not a member of group1
Here is the source code:
$dUsers = Get-ADUser -Filter {enabled -eq $false} |
FT samAccountName |
Out-String
$groups = 'Group1', 'Group2'
foreach ($dUser in $dUsers) {
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive |
Select -ExpandProperty SamAccountName
if ($members -contains $dUsers) {
Write-Host "[+] $dUser is a member of $group"
} else {
Write-Host "[-] $dUser is not a member of $group"
}
}
}
I'm pulling my hair because I feel like there is a simple solution, but I'm lost.
Update:
I wanted to put all disabled users in variable $dUsers.
It actually works if I manually put users in the variable like this:
$dUsers = 'user1','user2','user3'
Which gives me the following output:
user1 is not a member of group1
user1 is not a member of group2
user2 is not a member of group1
user2 is not a member of group2
...
This makes me question how it gets "foreached" when the variable is:
$dUsers = Get-ADUser -Filter {enabled -eq $false} |
FT samAccountName |
Out-String
Anyone got a clarification on that?
Update:
This is the final code. It takes a long time to run, even with only two groups.
$dUsers = Get-ADUser -Filter {enabled -eq $false} | Select-Object -Expand SamAccountName
$groups = 'Group1', 'Group2'
Write-host '[+] Checking if any disabled user is member of any SSL groups'
Write-host '[+] This might take a while. Get a coffee!'
write-host '[+] Running...'`n
foreach ($dUser in $dUsers) {
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive | Select -ExpandProperty SamAccountName
if($members -contains $dUser) {
Write-Host "$dUser is a member of $group"
} Else {
# Remove or comment out the line below to get a clutterfree list.
# Write-Host "$dUser is not a member of $group"
}
}
}
You have two issues in your code:
You're creating a single string from the Get-ADUser output. Piping the output of that cmdlet through Format-Table (alias ft) and then Out-String creates one string with a tabular display of all matching account names including the table header.
If you output $dUsers in a way that makes beginning and end of a string visible you'd see something like this (the leading and trailing == marking the beginning and end):
PS> $dUsers | ForEach-Object { "==$_==" }
==samAccountName
--------------
user1
user2
user3==
Since there is no account with a username matching this string no match can be found in any group and you're getting the output you observed.
This misuse of Format-* cmdlets is a common beginner's mistake. People get a nicely formatted string output and then try to work with that. ONLY use Format-* cmdlets when you're presenting data directly to a user, NEVER when further processing of the data is required or intended.
What you actually want is not a string with a tabular display of usernames, but an array of username strings. You get that by expanding the SamAccountName property of the user objects you get from Get-ADUser.
$dUsers = Get-ADUser ... | Select-Object -Expand SamAccountName
The second issue is probably just a typo. Your condition $members -contains $dUsers won't work, since both $members and $dUsers are arrays (after fixing the first issue, that is). The -contains operator expects an array as the first operand and a single value as the second operand.
Change
$members -contains $dUsers
to
$members -contains $dUser
Depending on what PowerShell version you are on, there is a cmdlet for this use case and others.
As for
I'm Trying to loop all disabled users
Just do...
Search-ADAccount -AccountDisabled |
Select-Object -Property Name, Enabled,
#{Name = 'GroupName';Expression = {$_.DistinguishedName.Split(',')[1] -replace 'CN='}}
# Results
Name Enabled GroupName
---- ------- ---------
...
testuser2 NewTest False Users
Guest False Users
Or different cmdlet…
# Get disabled users and their group membership, display user and group name
ForEach ($TargetUser in (Get-ADUser -Filter {Enabled -eq $false}))
{
"`n" + "-"*12 + " Showing group membership for " + $TargetUser.SamAccountName
Get-ADPrincipalGroupMembership -Identity $TargetUser.SamAccountName | Select Name
}
# Results
...
------------ Showing group membership for testuser1
Domain Users
Users
------------ Showing group membership for testuser2
Domain Users
As for ...
an array of Groups
Just select or filter the DN for the group name you want using the normal comparison operators.
As for...
Unfortunately I'm not well versed in powershell.
… be sure to spend the necessary time to get ramped up on it, to limit the amount of misconceptions, confusions, errors, etc. that you are going to encounter. There are plenty of no cost / free video and text-based training / presentations all over the web.
Example:
Videos
Use tools that will write the code for you that you can later tweak as needed.
Step-By-Step: Utilizing PowerShell History Viewer in Windows Server 2012 R2
Learning PowerShell with Active Directory Administrative Center (PowerShell History Viewer)
As well as plenty of sample scripts and modules via the MS PowerShell Script / Module Gallery.
There are two commands for the AD Groups.
First I see that you want the membership of the disabled users that is easy.
#Get the dissabled users from your AD with all their attributes (properties and select)
$dUsers = Get-ADUser -Filter {Enabled -eq $false} -Properties * | Select *
#Run a loop for each user to get the group membership
Foreach ($User in $dUsers) {
$User = $User.SamAccountName
Get-ADUser $User -Properties * | Select Name, SamAccountName, MemberOf | Format-Table -Wrap # > "D:\test\$user.txt" -HideTableHeaders
}
This one can work but I don't like the output that we get.
I prefer to run the groupmembership command and check the users.
$GroupMembers = Get-ADGroupMember "groupname"| Select Name, SamAccountName
ForEach ($User in $GroupMembers)
{
$UserProperties = Get-ADUser $User.SamAccountName -Properties * | select *
If ($UserProperties.Enabled -eq $False) {
Write-Host $UserProperties.SamAccountName
}
}
Edit:
Let me know if those fits you.
Kind regards.
The first thing you should try to check is whenever you are only interested in direct memberships or indirect ones as well. Depending on the answer the options you got availabel change a bit. You probably will encounter Distinguished Names while working on this so check out what they are if you don't know (mostly a path for an object).
If it's only direct memberships using memberOf with Get-ADUser should be sufficient. The memberOf attribute contains every direct group membership of the user with the full Distinguished Name of the group.
Get-ADUser test -Properties MemberOf | Select-Object -ExpandProperty memberOf
You can match the groups you're looking for in various ways. You could get the whole Distinguished Name of those groups or you could do a partial match. It's up to you to decide how to proceed.
If you need the indirect memberships as well you might want to split up your code to make it easier for yourself. For instance you could first find the users and save them. Afterwards find all group members of those groups (You already got that with Get-ADGroupMember) and finally compare the two.
Currently for every user you build the whole list of group members again. This approach would save a few resources as you wouldn't be doing the same queries over and over again.
Finally you could also use the MemberOf approach but get the list of every direct and indirect membership of a user using an LDAP query.
$dn = (Get-ADUser example).DistinguishedName
$userGroups = Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn)
This approach uses a LDAP search query. It can be quite complex, you could also only check for one one of the groups by modifying it a bit.
In the end even your current approach should work. The problem is that you're comparing the AD object against the list of SAM Accountnames. You would need to check for the SAM Accountnames as well.
if($members -contains $dUsers.SamAccountName)
if($members -contains $dUsers | Select-Object -ExpandProperty SamAccountName)
One of these should work if you change your $dUsers as well. As it currently is you end up with a giant string. You probably can check that by checking $dUsers.length. Just drop the Format-Table and Out-String.

PowerShell foreach loop with embedded if statement yields "cannot accept 'True' as positional parameter"

I'm trying to write a simple PS line to take the exported .csv of AD groups in particular "regions" we have set up, then take the GroupScope (universal vs. global), and depending on the scope of the group, write the "department" attribute as either "Universal" or "Global." The reason for doing so is to help identify between the 2 scopes within SharePoint.
$uni="Universal"
Import-csv \\usershare\user\me\output\groups.csv | foreach {Get-ADGroup -Identity $_.Name -Properties * | Set-ADGroup if($_.GroupScope -eq $uni){-replace #{department=$uni}}}
This is returning the following error message though:
"A positional parameter cannot be found that accepts the argument 'True.'"
I'm probably missing something simple here but I just started out and I'm self-teaching by trial and error mostly. Thanks for any help you can provide!
You're use of the if block is not valid for what you are trying to accomplish, it will only return True or False and Set-ADGroup will not know what to do with that hence the error.
Try this:
$uni="Universal"
$csv = Import-Csv -Path '\\usershare\user\me\output\groups.csv'
foreach($i in $csv)
{
if($i.GroupScope -eq $uni)
{
Get-ADGroup -Identity $i.Name -Properties 'department' |
Set-ADGroup -Replace #{department=$uni}
}
}
I've set properties to only pull department as a smaller scope will speed up the query.

Remove #{ColumnName from Powershell results

I'm relatively new to PowerShell scripting and have mainly been cobbling together different scripts and cmdlets from Googling what I'm trying to do. One problem that I'm unable to Google, or search for on StackExchange, because of the special characters is having all my results come out as #{ColumnName=ColumnData}.
Here's an example script I found somewhere for pulling all the members of an AD group.
$Groups = Get-ADGroup -Filter {Name -like "!GroupName"}
$path = $groups
$myCol = #()
ForEach ($Group in $Groups)
{
$Members = #(Get-ADGroupMember "$Group")
ForEach ($Member in $Members){
try{
$user = get-aduser -identity $member -properties displayname
$MyObject = New-Object PSObject -Property #{
Displayname = $user.Displayname
}
$mycol += $MyObject}
catch {}}
}
Write-Host $MyCol | FL
I'm pretty sure there are better ways to get the members of an AD group but that's not the issue at the moment. The problem is all the data comes out like #{Displayname=Lawrence, Kimberly} and this happens with many of the scripts I've thrown together.
Any ideas on how to write scripts properly so I can just get DisplayName = Lawrence, Kimberly?
I agree your code is complex and it does not need to be. A couple of things that are important to mention is why your output looks the way it does.
You are creating an array of PSCustomObjects which function similar to hash-tables. In the end of your processing you have an array of objects with the property DisplayName. Since you are using Write-Host to display the the contents of $myCol it needs to cast it a string array in order to display it. You would see similar output if you just typed [string[]]$myCol. I should hope that you are doing something else in the processing as like the other answers show you have a very complicated way of getting what you are looking for.
Without beating the horse about those other solutions I will suggest some minor changes to yours as it stands. Your title and last sentence contradict what you are looking for since you want to remove the ColumnName in the title and the last sentence you are looking for output like DisplayName = Name.
Changing the last line will address both of these. If you just want the displaynames on there own
$myCols | Select-Object -ExpandProperty DisplayName
Lawrence, Kimberly
and if you actually want the other format you could do this
$myCols | ForEach-Object{
"DisplayName = $($_.DisplayName)"
}
Again, like the other answers, I stress that your code could use simple overhaul. If you needed to understand why it was working the way it way I hope I helped a little.
This actually has nothing to do with Active Directory; You are creating custom objects, then outputting them directly, and by default that is how PowerShell will format the output if you use Write-Host (which is intended to customize output). If you would like to be more specific about what your output should look like, we can help, but here is an example that outputs the results as just a list of strings, just using Write-Output:
$myCol = #()
$MyObject = New-Object PSObject -Property #{ DisplayName = "Lawrence, Kimberly" }
$myCol += $MyObject
$MyObject = New-Object PSObject -Property #{ DisplayName = "Hello, World" }
$myCol += $MyObject
# The default will show at-symbols, etc: Write-Host $MyCol | FL
Write-Output $myCol
The output will be:
DisplayName
-----------
Lawrence, Kimberly
Hello, World
Try this:
Get-ADGroup -Filter "Name -like '!GroupName'" |
Get-ADGroupMember |
Get-ADUser -Properties DisplayName |
Select-Object DisplayName
When I ran your code, I also got the hashes, even without special characters. Your code is way too unnecessarily complex.

powershell match multiple variables

So I read stackoverflow a lot and it always helps me with my understanding, at the moment, I'm a bit stumped on the matching any of multiple variables and as such, thought I'd ask the exports.
A bit of background on my task.
I am a systems admin for an AD forest with multiple child domains (+4), we are going through a process to consolidate these domains into one - however, unfortunately, as each was managed independently we have comes across the age old 'duplicate SAM' issue (and will for sure come across dupe UPNs too once we start digging).
As such - I am trying to build a script to find these duplicates so I can address them with the teams who support the users in these domains.
I've got a fair way through this, I've exported a list of the SAMs for one migrating domain, and I can parse this into PS and have it look up each SAM against all of my domains and either give me a variable with the username OR $null.
So, I now just want to understand what the 'IF' command is for matching more than one variable and showing which ones match.
So, for example, from these variables...
$one="MSmith01"
$oneNT="DOMAIN1\MSmith01"
$two="MSmith01"
$twoNT="DOMAIN2\MSmith01"
$three=$null
$threeNT=$null
$four=$null
$fourNT=$null
$five="MSmith01"
$fiveNT="DOMAIN5\MSmith01"
$six=$null
$sixNT=$null
How do I write the IF command to show me if more than one variable matches??
If I write this:
if ($one -match $two -match $three -match $four -match $five -match $six) {write-host "Duplicate Found - $oneNT, $twoNT, $threeNT, $fourNT, $fiveNT, $sixNT." -foregroundcolor "yellow"}
It will show me :
Duplicate Found - DOMAIN1\MSmith01, DOMAIN2\MSmith01, , , DOMAIN5\MSmith01,.
But, if the variables are:
$one=$null
$oneNT=$null
$two="MSmith01"
$twoNT="DOMAIN2\MSmith01"
$three="MSmith01"
$threeNT="DOMAIN3\MSmith01"
$four=$null
$fourNT=$null
$five=$null
$fiveNT=$null
$six=$null
$sixNT=$null
Then the result seems to ignore the fact that DOMAIN 2 and DOMAIN3 have matching SAMs and it appears that "x -match y -match z" is really actually only comparing x and y, so I get nothing.
Which is obviously incorrect.
Can anyone explain how I compare and look for ANY duplicate amongst several variables?
Thanks
To further elaborate:
To get my variables I'm doing this:
$SAMAccountList=gc "C:\My Documents\Domain2SAMList.txt"
$SamAccountList | % {
$one=Get-QADUser -SamAccountName $_ -IncludedProperties SamAccountName -Server dc.domain1.local | foreach { $_.SAMAccountName }
$oneNT=Get-QADUser -SamAccountName $_ -IncludedProperties NTAccountName -Service dc.domain1.local | foreach { $_.NTAccountName }
$twoSAM=Get-QADUser -SamAccountName $_ -IncludedProperties SamAccountName -Service dc.domain2.local | foreach { $_.SAMAccountName }
$twoNT=Get-QADUser -SamAccountName $_ -IncludedProperties NTAccountName -Service dc.domain2.local | foreach { $_.NTAccountName }
#...
#...
#...etc for each domain
}
This then gives me a set of variables as I mentioned above
I would put them in a customobject and search with the unique names.
$NAMES = #()
$NAMES += [pscustomobject] #{name=$one;nameNT=$oneNT}
$NAMES += [pscustomobject] #{name=$two;nameNT=$twoNT}
$NAMES += [pscustomobject] #{name=$three;nameNT=$threeNT}
$NAMES += [pscustomobject] #{name=$four;nameNT=$fourNT}
$NAMES += [pscustomobject] #{name=$five;nameNT=$fiveNT}
$NAMES += [pscustomobject] #{name=$six;nameNT=$sixNT}
$UniqueNames=($NAMES | Sort-Object -Property name -Unique).Name
ForEach($Uniquename in $UniqueNames){
if($Uniquename -ne $NULL){
$SEARCH_RESULT=$NAMES|?{$_.Name -eq $Uniquename}
if ( $SEARCH_RESULT.Count -gt 1)
{
$SEARCH_RESULT
}
}
}
I get the following results
First data set
name nameNT
---- ------
MSmith01 DOMAIN1\MSmith01
MSmith01 DOMAIN2\MSmith01
MSmith01 DOMAIN5\MSmith01
Second data set
name nameNT
---- ------
MSmith01 DOMAIN2\MSmith01
MSmith01 DOMAIN3\MSmith01