Powershell

PowerShell – Getting M365 Tenant ID From Domain List

It’s been a while since I’ve broken out PowerShell to solve a problem, but a scenario came up where I thought I could automate something I needed to do – look up a bunch of Microsoft 365 Tenant IDs based on domain names. Here’s how I tackled it:

First, I actually had a list of email addresses and just wanted the domain of each one. The list was in Excel so that’s easy enough – using the Text to Columns feature I selected the data, used the ‘Delimited’ option under Original data type then pressed Next:

Then on the next step, changed the Delimiters from the default ‘Tab’ to ‘Other’ and put the @ symbol on, and as you can see in the Data preview it takes the alias off the email address for the first column, and leaves the domain in the second:

Clicking ‘Finish’ gave me a column full of domains. From this, I created a header for each row (alias and domain):

And then in Excel went to File > Save As > and called the file ‘addresses’ while picking CSV from the dropdown:

Easy enough. From here, I knew I’d need to feed this data into PowerShell using the Import-CSV command, but first I wanted to work out what the one liner command was to get a M365 Tenant ID…. except I couldn’t find one. All the examples were how to find your own M365 Tenant ID after authenticating. I knew it was public and easily accessible since sites like https://whatismytenantid.com/ work great but only accept one domain at a time.

I ended up finding a Function written by Daniel Bradley which was fairly simple and using an API, with the core of it being this one line:

Invoke-RestMethod -UseBasicParsing -Uri "https://odc.officeapps.live.com/odc/v2.1/federationprovider?domain=$domain"

Swapping the $domain variable with an actual domain and piping to just selecting tenantid

Invoke-RestMethod -UseBasicParsing -Uri "https://odc.officeapps.live.com/odc/v2.1/federationprovider?domain=microsoft.com" | select tenantid

tenantId
--------
72f988bf-86f1-41af-91ab-2d7cd011db47

Alright, we should be able to put this all together. Set the $file variable as the imported CSV file, then for each domain record run the Invoke-RestMethod command using the current $record.

Except that didn’t work because I forgot the $record is the entire object and not just the domain membertype. To specify that, we just use $record.domain so the pure domain is used.

Except that didn’t work either and I don’t know why. Instead, I just made a new variable from the $record.domain and called that $newdomain, then referenced THAT in the Invoke-Restmethod line.

That did work, so I could then echo out the results of both the current $newdomain variable, and the newly looked up $result and again specifying the membertype of tenantid (as a bunch of other info gets looked up with that command).

I also then wanted to export this data back out to a new CSV, in this case one called ‘myfile.csv’. Again, I have to work around membertypes so just make a new variable containing the single tenantid line, and use the >> operator to create/append to a file:

$file = import-csv c:\temp\addresses.csv

foreach ($record in $file){
        $newdomain = $record.domain
    $result = Invoke-RestMethod -UseBasicParsing -Uri "https://odc.officeapps.live.com/odc/v2.1/federationprovider?domain=$newdomain"
$newtenantid= $result.tenantid
echo $newdomain $result.tenantid
"$newdomain,$newtenantid" >> c:\temp\myfile.csv
}

Works perfectly and I end up with a CSV that has a column of domains, and a column of Tenant IDs. If a domain had no Tenant ID then that value will be blank.

I’m sure this could be written better, but for quick occasional tasks for yourself, you just need something that works.

5 Things To Check In Your Microsoft 365 Tenant

I’ve been diving into the Center for Internet Security’s (CIS) benchmarks lately – which are a set of benchmarks to use against different technologies (including Microsoft 365 and freely available for non-commercial use). They are a good set of checks to go through in a tenant to review configuration with a security focus; including how to remediate.

There is of course a lot more to it than reading a document and configuring items the way it says to; you need to understand what you’re changing, and what impact that may have to the business and it’s end users. For example; blocking the ability to share anonymous links from SharePoint/OneDrive is generally ‘a good idea’ security wise, but if your users are actually doing that you probably don’t want to just shut that off. You need to assess what’s being used and how, and have a strategy to get to a more secure point.

Anyway, I’ve picked my favourite 5 settings from their comprehensive list that I feel people could miss; I may have missed these myself when I used to be a Microsoft 365 administrator.

For PowerShell commands, if you’re not sure how to get to the right module (e.g. Exchange Online from my first example) then check out msshells.net which will show you how to install and connect.

The headings are quoted from CIS, but the rest of the material is my own:

1 – 3.1.1 Ensure Microsoft 365 audit log search is Enabled
If you’re a Microsoft 365 focused admin, Azure and log search may not be a front of mind for you unless you go looking to solve a problem that arises.

This should be enabled in new tenants, but older ones may not have it. First check it’s status with the PowerShell command in Exchange Online:

Get-AdminAuditLogConfig | Select UnifiedAuditLogIngestionEnabled

This will show a True or Fasle value – if it’s True, it’s on. If it’s off/false, enable it with this command:

Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true

This should have no user impact and just enables the ingestion of the Audit Logs.

To view these logs you can use PowerShell commands, but this is generally one I’d rather use a GUI for – go to the Microsoft Purview portal and the Audit section, and trigger a search. Without getting into too much detail, there’s two tiers of Audit – Standard and Premium. Read further information here.

2. Ensure modern authentication for SharePoint applications is required

This is another that many old tenants may have disabled. SharePoint has ‘legacy authentication’ similar to other services that are planning or already deprecated legacy auth – Exchange Online being the common one most people know about.

To check if you have this disabled, connect via PowerShell to SharePoint Online and run the command:

Get-SPOTenant | ft LegacyAuthProtocolsEnabled

True means it’s enabled, False means disabled – and we want it to be False. The command to enable it is:

Set-SPOTenant -LegacyAuthProtocolsEnabled $false

Entra ID’s Conditional Access should be configured to block all Legacy Auth requests also, but this is an extra layer to make sure SharePoint won’t work that way anyway (plus holes are poked through Conditional Access all the time!). There seems to be very little official public documentation about this option from Microsoft – I could find this example where they show how to set it to $true to avoid some login issues which is a bit concerning.

This is one that may have some user impact or application impact if systems are connecting to SharePoint Online in legacy ways. Users should be used to modern auth and match their experiences in other Microsoft 365 services – applications however would need to be redesigned or updated to accommodate this. You can search the Entra ID authentication logs for any attempts to connect to SharePoint Online using legacy authentication over as long as possible before changing this setting.

3. Ensure sign-in to shared mailboxes is blocked (Automated)

Shared Mailboxes are both a delight for having a central area for emails to go to, and multiple staff having access to them, but also a dismay in user expectations of being able to send as the account, and potentially log in as it. A reception desk or similar may have multiple people jumping in and out of the location, but they want to access the same contents without the time-taking task of logging in and out of the computer each time. Regardless – security wise each user should have a unique login, and all their actions performed under that login.

Although it can be a fight and go way beyond a technical issue – shared mailboxes should be disabled from logging in. A disabled from login shared mailbox can still send and receive emails; you’re only disabling the ability to log in using that account itself, and the mailbox can still be accessed as a delegate.

As this is a per account setting, you’ll need to check all shared mailboxes. As per Microsoft Learn, you can block a single account from the Microsoft 365 Admin Center and go to Users > Active Users, select a Shared Mailbox, and click the ‘Block Sign-In’ option:

… but this doesn’t really scale to check all Shared Mailboxes and change the setting. Instead, as per Microsoft Learn, we have to use Exchange Online PowerShell to find the shared mailboxes, then we can use Microsoft Graph PowerShell SDK to disable them. After connecting to Exchange Online and Microsoft Graph with the below scope:

Connect-Graph -Scopes User.ReadWrite.All

You can then run the one command to use Exchange Online to find all Shared mailboxes and then use Microsoft Graph to set the account to disabled:

Get-EXOMailbox -RecipientTypeDetails "SharedMailbox" | ForEach {Update-MgUser -UserId $_.ExternalDirectoryObjectId -AccountEnabled:$false}

You could also do the same for Room and Equipment mailboxes if they don’t need to sign in:

Get-EXOMailbox -RecipientTypeDetails "RoomMailbox","EquipmentMailbox" | ForEach {Update-MgUser -UserId $_.ExternalDirectoryObjectId -AccountEnabled:$false}

Also note that Exchange Online has it’s own ‘AccountDisabled’ variable which you could set to true, but this blocks sign in to the mailbox, and not the entire account to any M365 service/Entra ID authentication.

User impact on this needs to be assessed by again checking the Entra ID logs against each Shared Mailbox, and working out how to set systems up to avoid the shared account login. There may be some user resistance to this, but one argument could be ‘what if someone sent a nasty email to your boss under the account and they thought it was you – you couldn’t prove it wasn’t easily if others are also using that same account’.

4. Ensure ‘Per-user MFA’ is disabled

This is the ‘old’ MFA before Conditional Access was around. This only suported the MFA methods of ‘Call to phone, Text message to phone, Notification through mobile app, and Verification code deom mobile app or hardware token’.

You can check if any users have this enabled by going to https://account.activedirectory.windowsazure.com/usermanagement/multifactorverification.aspx and ensuring all users show as ‘disabled’ for the ‘MULTI-FACTOR AUTHENTICATION STATUS’ column.

If you see any users enabled, then you should ensure Conditional Access is set up and ready to go, then change the users to disabled. You also shouldn’t be using this function at all when Conditional Access is enabled. A few warnings from Microsoft:

More details here: https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-userstates

5. Ensure a dynamic group for guest users is created

I quite like this one. Yes, you can reasonably easily determine if an account is a guest account or not, but having an automated group means it’s easy to point other Conditional Access policies, or other monitoring, on what these accounts are doing in your tenant. It’s very little effort to create, doesn’t need maintenance, and can help in other scenarios when you want to review what guest accounts are around.

How to do this is well documented by Microsoft but you do need an Entra ID Plan 1 or Plan 2 license to create dynamic groups.

Simply create a New Group in the Entra Admin Center https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AddGroupBlade

and after selecting a Group Name and choosing Memebership type: Dynamics user, click ‘add dynamic query’:

Use the filters Property = userType, Operator = Equals, Value = Guest, and click ‘Save’, then ‘Create’.

Note that it can take a minute for the group to initially populate. You can now use this group to block from certain things as an extra layer of protection against accidental permissions, or have extra Conditional Access policies that always require certain MFA methods.

If you don’t have Entra ID Plan 2 to be able to have guest reviews, you could use the membership of this group as a simple way to review the guest accounts in your tenant.


That’s my top 5 picks – check out the CIS Benchmark for Microsoft 365 yourself along with their other benchmarks as there’s a lot to learn and check through. It’s also not a one-time thing, settings change, the benchmark itself grows (currently at v3.1 at the time of writing), plus there’s more security to check beyond this!

Microsoft Teams PowerShell Phone Number Assigning Cmdlet Change

Microsoft has sent out an announcement on PowerShell changes for setting and removing phone numbers in Microsoft Teams:

Changes coming to phone number assignment using Teams PowerShell Module cmdlets
MC316139 ยท Published 19 Jan 2022

In summary, these commands are being deprecated “The retirement is planned to begin in early April and be complete by mid-April.” :

Set-CsOnlineVoiceUser
Set-CsOnlineApplicationInstance
Set-CsOnlineVoiceApplicationInstance

and Set-CSUser can’t be used to allocate phone numbers either. I’d been allocating numbers with the Set-CsOnlineVoiceUser command. The replacement for this is:

Set-CsPhoneNumberAssignment and Remove-CsPhoneNumberAssignment

They run under the MicrosoftTeams module for PowerShell, but you also need to make sure you have the latest version. If you don’t have a version that supports this new command, you’ll get the error:

Set-CsPhoneNumberAssignment : The term 'Set-CsPhoneNumberAssignment' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.
At line:1 char:1

To update, run the command:

Update-module MicrosoftTeams 

Then try the above cmdlet again. If you’re feeling really brave, you can update all your modules with:

Update-module *

Disconnect or restart PowerShell or you’ll get problems running the new cmdlet if you had it connected while updating.

The new cmdlet Set-CsPhoneNumberAssignment doesn’t work exactly the same way as the old cmdlets. Read the documentation for more details

Set-CsPhoneNumberAssignment -Identity [email protected] -PhoneNumber +61987654321 -PhoneNumberType CallingPlan

The options for -PhoneNumberType (required) are DirectRouting, CallingPlan and OperatorConnect.

I’d suggest testing and migrating soon, before you miss the April deadline of the command being dropped.

PowerShell Slow to Load and AutoFill

I had this problem on a server for a while – when first launching PowerShell, it would take ~20 seconds or so to accept input. Also, when pressing tab to auto-complete a command, it would again take ~20 seconds to start, like it was freezing. These were one time problems when launching PowerShell, after that it would work fine until a new session was launched.

A lot of searching didn’t help me work it out, so I logged a Microsoft case. After a few task manager executable dumps, they worked out the delay was on a path I had in an environment variable. Somehow in my account’s user variable, I had a github desktop path that was mapping to a network share, using a PC name that was decommissioned (e.g. ;\\pcname\c$\Users\AdamFowler\AppData\Local\GitHubDesktop\bin.

I expect that this name was timing out, and PowerShell was waiting a while before giving up. In case you have the same symptoms as me, check the environment variables – user variables paths if it’s only your account affected, or the system variables if it’s all users. Click on the path value, then click edit, and remove anything that shoudn’t be there (take a backup of the text if you aren’t sure, it’s easy to put back in if you keep a copy).

To get to Environment Variables, depending on the OS version, get to System Properties, the Advanced tab, and then the Environment Variables button:

Hope that helps someone else with the same problem!

Microsoft Lists Date/Time Settings Incorrect

Microsoft Lists is available for a lot of people already, and should be globally available by the end of October 2020. Users can start using it as soon as it’s released for your tenant, which is great; but you might get caught out by the same date and time regional problem I did.

Creating a List is easy (right now I have the option available in Teams, but the app in Office 365 hasn’t turned up yet), and there’s many use cases for wanting a date or time field.

However, the suggestion on what day it is was wrong:

Today is actually Wednesday, October 14th 2020. It’s being caused because the timezone is wrong for the list. How do we fix that?

A Microsoft List can be created two ways – in the single user context, or in a Microsoft 365 Group context. If you’re doing in in Teams then the later only applies. Individually, it’s saved in the same area as your OneDrive for Business (which is backeneded by SharePoint), but for a Group it’s saved straight into the Site for the Group.

Lists in OneDrive for Business

For the individual point of view, there’s already a Microsoft Answer on how to fix this – change your Time Zone and Region Locale. The link for this is indivualised for your tenant and account, but you can access it by:

  • Browse to office.com and sign in
  • Click the OneDrive app from the left hand menu
  • Click the cog in the top right corner and choose ‘OneDrive Settings’
  • Click ‘More Settings’ in the left hand list
  • Under ‘Region and Language’ choose ‘Regional Settings’
  • Choose the correct Time Zone and Locale for your account

Changing this for all users is a bit more of a problem. There’s a PowerShell script here to update all existing ones, and new users there appears to be no way to do it based on this outstanding UserVoice – if you find anything different, please share and I’ll update this post.

Lists in SharePoint Online

A Microsoft List tied to a Microsoft 365 Group will read the Time Zone and Region settings from the Group’s site, which is accessed a bit differently:

  1. Browse to office.com and sign in
  2. If you have the Lists app in the left hand menu, choose that and skip to step 5
  3. If there is no Lists app, click the SharePoint app from the left hand menu
  4. Choose the Microsoft 365 Group that contains the Microsoft List (if you’re unsure, you can try finding the List in Teams, clicking the elipsis and choosing ‘Open in SharePoint’.
  5. Click the cog in the top right corner and choose ‘Site Contents’ then choose ‘Site Settings’
  6. Click ‘Regional Settings’ under ‘Site Administration’
  7. Choose the correct Time Zone and Locale for your Group and press ‘OK’ in the bottom right corner.

This works for a single site, but what about a company wide default?

In the SharePoint admin center, under Settings then Site creation, you can set the default time zone for new sites. This won’t help any existing Microsoft 365 Group already created (as a site is created at the time the group gets created), but will help with future sites.

If you want to update existing sites in PowerShell, you’ll need to start with this command:

Set-SPoSitesRegionalSettings -Url site.url.goes.here -TimeZoneID 19

That will change just the specified site.

The list of TimeZoneIDs is available from Microsoft here and there’s also a Gallery Script called Update the time zones in all sites in SharePoint Online which you could use to update all sites if you can’t work out how to do it.

A lot of details there just to change the date detection in Lists, but hopefully this gives you enough information to understand the scenarios and how to resolve them.