O365 Powershell | Breaking up a long list into two sets of 100 - powershell

I am looking to create a rule in Office 365 applied to all of the members in our org.
I would like this rule to append a warning on all incoming email from outside the organization with the same Display Names as our users.
When I attempt to apply it to all of the users in our org I get an error stating that the rule is too long.
In order to solve that I pulled a group, but I am still about 1000 characters over the limit.
I would like to make two variables, that each hold one half of the list, created by this command:
(Get-DistibutionGroupMember -Identity email#contoso.com -ResultSize Unlimited).DisplayName
I have attempted to modify the ResultSize parameter, but what I would need is result 1-100 and then 100-200 from the same list.
Another caveat to this problem is that the list cannot be static. It is something that the script will have to update every time it is run.
There is a sub-string command that you can use on a particular username that I have utilized when I made something for AD, but I am not aware of any way to break up a list like this.
If anyone has any other ways to solve this issue I would be more than open to any suggestion.
Thanks for taking the time to read this!

There are many ways of doing it. I found it very readable.
My favorite one is this one:
$ObjectList = 1..1000
$Step = 100
$counter = [pscustomobject] #{ Value = 0 }
$ObjectListSplitted = $ObjectList | Group-Object -Property { math]::Floor($counter.Value++ / $step) }
Then if you want to show the third subset just use this format :
$ObjectListSplitted[3].Group
Have a look to this solution already explained.
As a note other languages are capable of slicing an array of object with a start, stop and a step, have a look here if you're curious.

Related

PowerShell script to apply next available number to a user

Total noob here and i have a dilema... I need to apply Microsoft Calling Plan numbers to users based on region. Now what i want to do is import a .csv file of all the users and have powershell run a command that looks up available calling plan numbers in that region and then assign one to a user. then onto the next user, then the next and so on using the next available number each time.
As i have said i am not great with opwershell and i have thrown this togeth.
$Users = Import-CSV c:\filelocation\users.csv
$loc= (Get-CsOnlineLisLocation -City <city>)
$usernumber = (Get-CsPhoneNumberAssignment -isocountrycode GB -LocationId $loc.LocationId -NumberType CallingPlan -CapabilitiesContain UserAssignment -PstnAssignmentStatus Unassigned)
Foreach($user in $users)
{
Set-CsPhoneNumberAssignment -Identity $_.UPN -PhoneNumber $usernumber -PhoneNumberType CallingPlan
}
I have recently been scolded for using back ticks so that is something i need to ammend here but what i want it to do is lookup unassigned calling plan numbers for the $usernumber parameter and apply it in the set-CsPhoneNumberAssignment.
I have no idea how i loop it to apply the first available number and then move onto the next..
please help.
This script has not yet been run but i dont think it will work.
For matched loops like this you can use regular for($index = 0; $index -le $users.Length; $index++) {} syntax.
Then pass the index to both $Users and $UserNumber lists: $Users[$index].
Check for appropriate sizes first if necessary (especially if there are more users than there are available plans) to avoid ArrayOutOfBoundsExceptions.

Is there any way to export the contents of my Teams Chats for one day, by chat and time?

Is there any way to get and list out all my conversations in teams chats for one day? When I'm logging time, I lose track of what I worked on when I'm helping others, and the find function just gives me 8 messages on a big screen, which means I have to scroll through dozens of screens to get the "oh, this is what we're doing". I'd rather have a list with the chat name, time, and what was said. Powershell, CLI, KQL, don't care how. Thanks.
John Doe Chat: Doe, John [10:35] what is this about
John Doe Chat: Me, Myself [10:36] trying to come up with an example
PeopleChatOne: Joe [10:37] what are we, chopped liver?
Okay, after digging for a bit... turns out the answer is, as #sayali-msft and #anothervictimofthemouse mentioned, is the Graph API - which has a module. Note that you DO have a certain amount of access to your own stuff without needing an admins help, but you will have to explicitly grant it to each "application" that will connect to it. And there's a good amount of playing you can do with the Graph Explorer.
The below code will grab all of your chats, then grab the most recent 200 messages, filtered for the past day, and then will return the pertinent info.
Still missing at this point:
time zones seem wonky, specifically people in other time zones.
piping to OGV gives you all lines, when I really only want the first.
Fortunately, format-table DOES limit to one line per message
stripping out HTML works but is not great.
members may not be working.
Install-Module Microsoft.Graph
Import-Module Microsoft.Graph.Teams
$RequiredScopes = #("Chat.ReadBasic", "Chat.ReadWrite")
Connect-MgGraph -Scopes $RequiredScopes
#at this point a browser window should appear - allow it.
#I had to find my user id in graph explorer UI by running GET V1.0 https://graph.microsoft.com/v1.0/me
#unsure how to get it otherwise - but you don't need it with get-mgchat
get-mgchat
#take one of those IDs
#let's look at this chat:
get-mgchat -ChatId 19:deadbeefb2d949d88d4455f5279e5d8b#thread.v2
get-mgchatmessage -ChatId 19:deadbeefb2d949d88d4455f5279e5d8b#thread.v2
#this nests and walks through properly, strips HTML, but lord this will be slow. And shows more than one line per message, even when I try now to.
#By default you can get all your chats by running get-mgchat. -all and -pagesize 50 is required for the module to paginate the request and get you everything. But in my case it grabbed all 2000 chats. The -first 5 is for testing.
$tzone = Get-TimeZone # conversion from GMT to local time. https://jdhitsolutions.com/blog/powershell/7962/convert-to-local-time-with-powershell/
$mychats = get-mgchat -all -PageSize 50 |select -first 5
$all_chat_info = #() #force-setting to an array
$all_chat_info = foreach ($chat in $mychats) {
$chatinfo = get-mgchat -ChatId $chat.id #get base details about the CHAT itself
#set some details about the chat itself for the later query
$chatname = $chat.Topic
$members = $chat.Members
$chattype = $chat.chattype
#now get every message from that chat since midnight yesterday. Note LastModifiedDateTime is GMT. The jdhit page says -($tzone...), but all I had to do was .tolocaltime() ... I think.
#the -top 200 -pagesize 50 is to get the most recent 200 messages, and again you have to paginate.
$recentchatmessages = get-mgchatmessage -ChatId $chat.id -top 200 -pagesize 50 |where {$_.LastModifiedDateTime.tolocaltime() -gt (get-date).date.AddDays(-1)} # all from after midnight yesterday |select -first 5
#and now use select expression to add the above fields and parse the below fields, stripping out HTML (but I can't seem to only get the first line in OGV)
$recentchatmessages | select #{Label='LastModified';Expression={($_.LastModifiedDateTime.tolocaltime())}}, #{Label='ChatName';Expression={($chatname)}}, #{Label='members';Expression={($members)}}, #{Label='ChatType';Expression={($chattype)}},
#{Label='From';Expression={($_.from.user.displayname)}}, #{Label='Body';Expression={ ($_.Body.content -split '\n')[0] -replace '<[^>]+>',''}}
##{Label='From';Expression={($_.from.user.displayname)}}, #{Label='Body';Expression={( ($_.Body.content -replace '<[^>]+>','').split([Environment]::NewLine)|select -first 1)}}
}
$all_chat_info|format-table
#and now close/disconnect
Disconnect-MgGraph
Several problems present themselves:
All and PageSize appear broken (update: use "-all -pagesize 50")
Expanding the fields. Probably trivial for others, but a pain for me.
currently comes back like
Microsoft.Graph.PowerShell.Models.MicrosoftGraphItemBody
Microsoft.Graph.PowerShell.Models.MicrosoftGraphChatMessageFromIdentitySet
Some references I found while getting it working.
https://practical365.com/connect-microsoft-graph-powershell-sdk/ - getting the scope set
graph explorer
https://www.powershellgallery.com/packages/Microsoft.Graph/1.9.2
https://github.com/microsoftgraph/msgraph-sdk-powershell - the github for the actual modules. the readme is gold specifically https://github.com/microsoftgraph/msgraph-sdk-powershell/blob/dev/samples/5-Teams.ps1

Copy users from a series of groups based on a filter to another series of groups based on a separate filter

Thanks for taking a minute to look at this.
Scenario: Copy users from a series of groups based on a filter to another series of groups based on a separate filter. Essentially a one for one copy of group memberships.
I created some code that grabs all the groups based on the filter and can get all of the users but am having a hard time translating that into the individual pieces. For each group I want just that groups members and then add them to a group with the same name but of a different type (one is OKTA_GROUP and the other is APP_GROUP).
Any help on this would be appreciated. Getting the groups and members works but putting those into variables and passing them into the PUT is not working.
Here is what I have so far.
function get-oktaInvokeGroupMembers () {
$groups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22APP_GROUP%22&q=test"
Write-Output $groups
foreach ($group in $groups) {
$members = Get-OktaGroupMember $group.id
Write-Output $members
}
$oktagroups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22OKTA_GROUP%22&q=Test"
Write-Output $oktagroups
foreach ($okta in $oktagroups) {
Invoke-Method PUT "/api/v1/groups/$okta.id/users/$members.id"
}
}
You have to get your APP_GROUP.
Then for each group, you loop to get the members with Get-OktaGroupMember. Each member has normally an id.
You need to retrieve the OKTA_GROUP which has the same name than the group you are currently processing to get its group id.
Then finally for each member, you put the member.
Something like that will be a good starting point.
function get-oktaInvokeGroupMembers ($groupName) {
$sourceGroups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22APP_GROUP%22&q=$groupName"
foreach ($sourceGroup in $sourceGroups) {
$sourceMembers = Get-OktaGroupMember $sourceGroup.id
$targetGroups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22OKTA_GROUP%22&q=$groupName"
foreach($targetGroup in $targetGroups) {
foreach($sourceMember in $sourceMembers) {
Invoke-Method PUT "/api/v1/groups/$($targetGroup.id)/users/$($sourceMember.id)"
}
}
}
}
I've defined a parameter $groupName.
I don't know your API but difficulty is that for a specific groupName, you may get zero, one or many groups as result.
So you need to handle all these cases. I've handled multiple results nesting foreach loop but in such case for one APP_GROUP, the script will set members on all OKTA_GROUP that have the same name than APP_GROUP. There may be zero, one or many OKTA_GROUP groups returned by the API.
Another point, you will probably need to handle errors when adding a member. Calling the API with PUT when the member is already in the OKTA_GROUP may return an error.
And last, the script will only append members to a group, but it will never "synchronize" both groups. Meaning deleting members isn't handled. If you need to synchronize members this is bit more complexe.

Powershell: Checking for duplicate email in AD

Background:
I'm trying to make a script that will see if a new users email ($email) is the same as one already existing (which would cause an error). I have a very remedial understanding of objects so this is what I have so far (yes it is ugly):
$email = "smithj#company.com"
$mailcheck = Get-ADUser -filter * -Properties * | ForEach-Object {$_.mail}
$mailcheck | ForEach-Object {if ($email -eq $_.mail){"$email = $($_.mail) - Matching email"}else{"$email = $($_.mail) - No duplicate email"}}
Problem 1:
The script doesn't match emails. When I have a matching email in AD it doesn't recognize it.
Problem 2: When executing just the 2nd line, indexing doesn't work properly. While it looks like a consecutive list of emails, if a user doesn't have an email at all (blank) really it could be something like this:
smithj#company.com
johnsonj#company.com
robertsr#company.com
doej#company.com
So $mailcheck[0] returns smithj#company.com while $mailcheck[1] returns blank despite the list actually looking like this:
smithj#company.com
johnsonj#company.com
robertsr#company.com
doej#company.com
Conclusion: I really just need problem 1 solved but problem 2 peaked my curiosity. Thanks.
The way you are doing it above is really inefficient. -Properties * will return every property on the user, some properties are expensive in terms of processing power to return. Only use the properties you need. The properties returned by default without specifying that parameters do not need to be specified with -Properties, only additional nondefault properties. -Filter * will also match on literally any value for any field, effectively returning every ADUser, further increasing the resources required for your script to execute as you will now have to process every user to find any accounts matching that email.
Now that that's out of the way, here is a more efficient method to implement what you're asking:
# Set the email address to search for
$emailAddress = 'box#domain.tld'
# Get all users where the email address matches what is set above
# Force it as an array so you can treat it like one even if only
# one or zero users are returned
$adUsers = #( Get-ADUser -Filter "EmailAddress -eq '${emailAddress}'" )
# Make sure no accounts were returned
# If there are, throw an error with the number of users and who they are
if( $adUsers ) {
throw "Found $($adUsers.Count) users matching EmailAddress ${emailAddress}: $($adUsers.SamAccountName -join ', ')"
}
By using the filter to only match the specific email address, Powershell does not need to collect every single AD user in the system, or iterate over all of them to find a specific email address. This will take a long time to check, especially in larger environments, whereas filtering the returned objects based on email address (or on any other property) results in a faster operation and less data to sift through.
You can then check whether $adUsers contains anything (an array count of anything but 0 evaluates to $True, you could also use if( $adUsers.Count -gt 0 ) as the condition), and if so, throw an error with more information as I do above.
Update for comment question:
To answer your other question in the comment, "I didn't know what object to compare $email to", EmailAddress and Mail both look to be valid properties, but I don't know the difference between them. In my environment, both Mail and EmailAddress are populated with my email address, but have always used EmailAddress and haven't run into issues using that. Maybe one is deprecated and the other is new or something, but I'm not really sure.
There is also yet another property called proxyAddresses as well, which preliminary research shows that both EmailAddress and Mail are related to it, but I don't know much about it. It's not populated on my ADUser objects, so I can't poke around with it.

Always getting 1500 member of distribution list using PowerShell

I would like to get all members (mail address) of a certain distribution list.
Currently I just recieve the first 1500 members. My Script looks like that:
$group = [ADSI]"LDAP://CN=distListOne,OU=Groups,DC=XYZ,DC=XYZ"
$group.member.count ##Always 1500
foreach($member in $group.member)
{
$filter = "LDAP://"+$member
$user = [ADSI]$filter
$user.properties.mail | out-file "C:\distrUser.txt" -append
}
I know that there are more than 1500 users in the distribution list. I need anyhow to extend the maximum recieved groupmembers.
You need to change your code to use a DirectorySearcher approach, and check out the PageSize property of the DirectorySearcher
Setting that value to something (instead of leaving it 0) will start paged searching to allow AD to return more than 1500 members. It is recommended to set the PageSize to a sensible value like 500 or 1000 - if you set it too high (higher than the system limit of 1500), it will be ignored and won't work!
See some other blog posts on how to tackle this problem:
Get more than 1500 members from an Active Directory group
List Members of Large Group
When retrieving a large attribute you need to ask for the values in it in batches. This is often called "ranged retrieval" in directory speak.
Nearly every well behaving MSFT LDAP API supports this, including ADSI...
http://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
This will work quite nicely, requires the active directory module
(Get-ADGroup $Group -Properties members).members