## Azure Cloud Shell is now integrated with the Office 365 Admin Center

Over an year ago, at last Ignite conference, Microsoft previewed an early version of Azure Cloud Shell integrated modules that will make it easier for Office 365 admins to connect to and manage their environments. Now, an year later, this is ready for prime time and it is now actually integrated as part of the Office 365/Microsoft 365 Admin Center:

Now, in my case I’ve already set up Azure Cloud Shell and even played with the installed modules a bit. If you haven’t done so already, you will have to go over the provisioning process, including creating an Azure subscription, in order to use the feature. Once you go over all that, you will have access to the Azure, AzureAD and Exchange Online cmdlets straight from your Office 365 Admin Center. And yes, it’s only available from the Office 365 Admin Center for now, and of course via the Azure portal as well.

I’ve covered this topic extensively in a review of PowerShell Web Access methods over at Practical 365 and more recently when the EXOPSSessionConnector module become available in Azure Cloud Shell back in May 2019, so I will not go into any additional details here. Check the above articles if you are interested.

## Office 365 admins need no help (pane)

Over the past few months, Microsoft has been rolling out some updates on the help pane across different Office 365 workloads. The pane now gives you some contextual recommendations and suggestions around the top issues you might encounter, as well as search functionality powered by the results from support.office.com. Or at least that’s the theory.

As the first and most obvious example, you can try navigating to any page inside the Office 365 Admin Center, then hit the help button there. Instead of actually seeing the help pane, you will be presented with the “vastly improved” service requests pane. Makes sense I suppose, as an admin user you shouldn’t really need help, and when you do need it, you obviously must contact the support agents. And since being able to access the support requests pane is tied in to you having the corresponding roles assigned, guess what happens when you open the portal as a user with some limited role, say a Security Reader, and hit that button. Why nothing of course, you can press and press it how many times you like, and it will not result in anything.

Actually, the above is not entirely true. If you open the admin center via the https://admin.microsoft.com link and stay on the landing page, hitting the help button will actually bring the help pane, along with the Custom help desk info. If you however navigate to any other page within the admin center, including returning to the landing page, hitting the button will result in displaying the service requests pane. Always nice to have uniform experience! Luckily, the Help pane will stay open while you navigate between pages, unless you decide to close it. Which in turn you should do by pressing the “x” button on top, as pressing the “?” (Help) button will toggle the service requests pane once you navigate away from the landing page.

Within other administrative endpoints, such as the EAC or the Teams Admin Center, the help button works as expected, but the actual “recommendations” presented there include things like “Install Office”, “Where’s my app” or “Give Office 365 a try”. That last one is a hoot! Overall, the Office 365 Admin Center is the only one that gets any in-context content displayed on the Help pane, if you can even see the Help pane that is. On the positive side, at least they didn’t remove the “Show cmdlet logging” option from within the EAC, which is still accessible by pressing the Help button on top.

As a side effect to all this, Office 365 Global admins will have to deal with slight inconveniences when working with the Custom help desk functionality, as they simply cannot preview the changes they make from within the Office 365 Admin Center. Navigating to any other admin portal or web app should expose the changes though, although I would really recommend that you do so in a private session as there seems to be a lot of caching being done and no matter how many times I try to refresh OWA, my GA account never sees the custom help desk info exposed within the Help pane therein.

While browsing the Graph API documentation earlier today, I spotted a new addition to the \beta APIs – endpoints to manage Azure AD administrative units. For those of you that aren’t aware of AUs, here’s the one-minute version: AUs are “containers” for user and group objects, which you can then use in order to delegate someone control over said objects. For example, you can delegate permissions for Admin1 to manage all objects inside AU1.

Simple. And very limited. And overlooked for years. AUs first appeared in 2015, and I covered them in this article. Nothing changed for few years, until 2018 when Microsoft finally added support for AUs for the Office 365 Admin Center, as detailed in this article. Now, we get to manage those via the Graph API as well, which hopefully means AUs will finally get some love and be made more useful, or maybe even better – we might get full-fledged RBAC controls for Azure AD.

Anyway, to the subject at hand. You can use the /beta/administrativeUnits endpoint to list all AUs created in the tenant. To get the properties of a specific AU, use the /beta/administrativeUnits/{id} one, as shown below:

Looking at the output, we can spot a new property listed, namely visibility. This property is not available via the MSOnline AU cmdlets, nor the Azure AD ones, and serves to limit visibility of the AU members. Here’s the description straight from the documentation:

Controls whether the adminstrative unit and its members are hidden or public. Can be set to HiddenMembership or Public. If not set, default behavior is Public. When set to HiddenMembership, only members of the administrative unit can list other members of the adminstrative unit.

To test this new property, we will create a new AU, using the Graph API explorer. In order to do so however, we first need to make sure permission requirements are met, meaning add the Directory.AccessAsUser.All scope (alternatively you can use the AdministrativeUnit.ReadWrite.All scope, but that’s not available for selection in the Graph explorer, so you will have to add it by other methods). Once we have sufficient permissions, we can execute the request:

After running the request, I noticed I made a small typo in the description field, which we can easily remedy with a PATCH operation. Next, we want to add some members to the AU, which unfortunately can be done one object at a time only. This is done by calling the corresponding /beta/administrativeUnits/18560689-d221-4f88-a713-17e973cf6498/members/$ref endpoint. Once we have added a member to this AU, we can explore how the visibility setting is affecting it. If we try to list the members of this AU with a Global admin account, we can still see all its members, which I’d wager is the expected behavior. If we use a scoped-role user, such as the ones you’d assign to manage other AUs, the membership will return empty. This is illustrated on the screenshot below, where the first user cannot see members of the AU, while my GA account can see members of the same AU just fine: Other than that, managing AUs remains pretty much the same. We still can only assign either the User account administrator or the Helpdesk administrator roles, so not much flexibility there. Still no way to dynamically add members, and as mentioned above you can only add them one at a time. Posted in Azure AD, Graph API | Leave a comment ## Managing folder-level permissions for Office 365 Groups In a recent article, we discussed some recent improvements for managing Office 365 Group mailboxes via PowerShell. No, we will focus specifically on the management of folder-level permissions, but first, some history. Back when Office 365 Group first launched, the corresponding group mailbox was exposed to most of the mailbox-related PowerShell cmdlets and we were able to perform actions such as delegating permissions, changing attributes or even use the Search-Mailbox cmdlet. Later on, Microsoft removed this functionality and introduced the *-UnifiedGroup cmdlets, which covered only a limited subset of the available operations. Some cmdlets were then updated to support a –GroupMailbox switch, making them usable against Groups, most notably the Get-MailboxFolderPermission cmdlet. It took Microsoft several more years before actions other than viewing folder-level permissions become supported for Groups, and now we finally have them working, as detailed in the above article. So how do we go about managing folder-level permissions for Groups then? As already mentioned, we have been able to report on the permissions for a while now, thanks to the Get-MailboxFolderPermission cmdlet. To represent the permissions given to owners and members respectively, two new principals were introduced, owner@local and member@local respectively: Get-MailboxFolderPermission GroupName -GroupMailbox FolderName User AccessRights SharingPermissionFlags ---------- ---- ------------ ---------------------- Top of Informatio... Default {None} Top of Informatio... Anonymous {None} Top of Informatio... Member@local {Author} Top of Informatio... Owner@local {ReadItems, CreateItems, EditOwnedItems, DeleteOwnedItems, DeleteAllItems, … Well, technically those are not actual security principals but are being recognized as the External type:  (Get-MailboxFolderPermission default -GroupMailbox -User "member@local").User UserType ADRecipient DisplayName -------- ----------- ----------- External Member@local Regardless of the type, the corresponding entries represent the list of owners or members of the Office 365 Group respectively, and are in fact utilized to grant them permissions. In the example above, you can see the default set of permissions for every folder in the Group mailbox sans the Calendar one. Namely, all members get Author permissions, meaning they can create new items, but can only edit and delete items they’ve created. Owners get a broader set of permissions, which includes: ReadItems, CreateItems, EditOwnedItems, DeleteOwnedItems, DeleteAllItems, FolderVisible. Interestingly enough, they don’t have the EditAllItems permission, and also lack the CreateSubfolders one. This makes sense, as clients do not expose folders of the Group mailbox other than the Inbox, and also never show any subfolders. And it also explains why they aren’t given Owner permissions. The Calendar folder gets its own set of permissions, governed by the value of the CalendarMemberReadOnly parameter. In the default configuration, CalendarMemberReadOnly is set to$false, even though the cmdlet output doesn’t show the value. This in turn means that Members get the exact same access to the Calendar folder as Owners, in other words everyone has Editor permissions. Toggling the value of CalendarMemberReadOnly to $true reconfigures the permissions so that Members now only get Reviewer access, in other words they can no longer create or edit meetings. This is all illustrated on the screenshot below: Another way to change the permissions is now supported, via the corresponding *-MailboxFolderPermission PowerShell cmdlets. For example, if you want to grant permissions to specific folder, you can use: Add-MailboxFolderPermission default -User vasil -AccessRights Owner -GroupMailbox FolderName User AccessRights ---------- ---- ------------ Top of Informatio... Vasil Michev {Owner} To remove any permission entry, use Remove-MailboxFolderPermission cmdlet. Interestingly, the Set-MailboxFolderPermission does not support the –GroupMailbox switch, meaning we cannot directly edit permissions. We can however remove them, then add the new entry. This includes the member/owner permissions as well, as illustrated in the next example: An unfortunate side effect of this limitation of the Set-MailboxFolderPermission cmdlet is the fact that we cannot change the “Default” or “Anonymous” permission levels, as those cannot be removed via the Remove-MailboxFolderPermission cmdlet. It is important to understand that not all clients might work with custom permissions, as they simply might lack the necessary code to handle them. In my test, OWA correctly recognized the fact that a member of the group was now given Editor permissions to the Inbox folder, and allowed them to remove any item found there. The same behavior was observed in the Outlook app on Android. However, there were some reports over at the MTC that the iOS Outlook app didn’t respect the permissions, which I wasn’t able to confirm as an Android user. Another important remark is that even though you can set permissions on any folder or subfolder, clients will still not show them unless you use some workaround akin to the “open Group as shared mailbox in OWA” one. So in most situations the information presented above is of purely academic value 🙂 Posted in Exchange Online, Office 365, PowerShell | Leave a comment ## Converting item and folder IDs via the Graph API In the Exchange world, items (and folders) can be represented by several different identifiers, depending on the interface used. As detailed in the documentation, those unique-valued identifiers include: • EwsId – the identifier returned in operations performed via the EWS API. For example, when you do a search for messages or enumerate folders in a mailbox, the object returned will return this value in the Id (UniqueId) property. Here’s how a sample value looks like: AAMkADQxMTViNmEzLWViNWYtNGYxNy1iNmQ2LTZmNDVhN2Q5ZDI0NQAuAAAAAABCOIqaOs9mS5duvcGwkHhzAQAg/oCKFU2uQqU6r5NcDVwAAACcapkwAAA= Another place where you will find the EwsId is as part of the URL when opening messages in OWA, for example: https://outlook.office.com/owa/sharednew@michev.info/?viewmodel=ReadMessageItem&ItemID=AAMkADQxMTViNmEzLWViNWYtNGYxNy1iNmQ2LTZmNDVhN2Q5ZDI0NQBGAAAAAABCOIqaOs9mS5duvcGwkHhzBwAg%2foCKFU2uQqU6r5NcDVwAAAAAAAEMAAAg%2foCKFU2uQqU6r5NcDVwAAAKtgxPpAAA%3d • EntryId – the MAPI identifier for the item/folder, corresponding to the binary PR_ENTRYID property. The value is Base64-encoded and looks something like this: AAAAAEI4ipo6z2ZLl269wbCQeHMBACD+gIoVTa5CpTqvk1wNXAAAAAAAAQwAAA== • HexEntryId – the hex-encoded representation of the EntryID. Here’s a sample value: 0000000042388A9A3ACF664B976EBDC1B0907873010020FE808A154DAE42A53AAF935C0D5C0000000000010C0000 • StoreId – the Exchange store identifier, which you can find for example in the output of Get-MailboxFolderStatistics cmdlet. Among other things, this value is used for generating folder IDs for use with the targeted collection Content search functionality. Here’s a sample value: LgAAAABCOIqaOs9mS5duvcGwkHhzAQAg/oCKFU2uQqU6r5NcDVwAAAAAAAEMAAAB • OwaId – as the name suggests, used by the web client. Basically an URL-safe representation of the StoreId. A sample value looks like this: LgAAAABCOIqaOs9mS5duvcGwkHhzAQAg%2FoCKFU2uQqU6r5NcDVwAAAAAAAEMAAAB • RestId – the default format used by the Graph API. Basically an URL-safe representation of the EntryId. Here’s an example: AAMkADQxMTViNmEzLWViNWYtNGYxNy1iNmQ2LTZmNDVhN2Q5ZDI0NQBGAAAAAABCOIqaOs9mS5duvcGwkHhzBwAg-oCKFU2uQqU6r5NcDVwAAAAAAAEMAAAg-oCKFU2uQqU6r5NcDVwAAAKtgxPpAAA= • RestImmutableEntryId – the ImmutableID format used by the Graph API. Sample string below: AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0AIP6AihVNrkKlOq_TXA1cAAACrYM5GAAA • EwsLegacyId – used by the initial EWS version and only added for completeness. Whenever you need to convert between those values, the ConvertId method comes to help. For example, the below EWS code will convert the EwsId of an item returned via the search operation ($fiItems[0].Items[0].Id.UniqueId) to all the other Id types:

$aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId$aiItem.Mailbox = "sharednew@michev.info"
$aiItem.UniqueId =$fiItems[0].Items[0].Id.UniqueId
$aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId$aiItem.UniqueId
$exchangeService.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EntryId)
$exchangeService.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId)
$exchangeService.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::StoreId)
$exchangeService.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::OwaId)

UniqueId                                                                                                                                     Mailbox               IsArchive     Format
--------                                                                                                                                     -------               ---------     ------
AAAAAEI4ipo6z2ZLl269wbCQeHMHACD+gIoVTa5CpTqvk1wNXAAAAAAAAQwAACD+gIoVTa5CpTqvk1wNXAAAAq2DE+kAAA==                                             sharednew@michev.info     False    EntryId
RgAAAABCOIqaOs9mS5duvcGwkHhzBwAg/oCKFU2uQqU6r5NcDVwAAAAAAAEMAAAg/oCKFU2uQqU6r5NcDVwAAAKtgxPpAAAA                                             sharednew@michev.info     False    StoreId
RgAAAABCOIqaOs9mS5duvcGwkHhzBwAg%2FoCKFU2uQqU6r5NcDVwAAAAAAAEMAAAg%2FoCKFU2uQqU6r5NcDVwAAAKtgxPpAAAA                                         sharednew@michev.info     False      OwaId

Now, this is all very well known. The reason for writing an article on this is the recently introduced Graph API endpoint that allows you to perform the same operation via the Graph, and in addition allows the conversion to RestId. That, and the fact that it actually helps me remember stuff, once I put it in a blog post 🙂

As with any other Graph operations, you need to run the request against the corresponding endpoint, namely /translateExchangeIds. You will need to provide a collection of identifiers to convert, and the types between the conversion should happen. Even if you have a single identifier to convert, you must provide a collection, otherwise you will get a Bad request error, so remember that. One other important thing to remember is that the Graph uses URL-safe encoding, so if you are providing an EwsId, EntryId or StoreId, you need to prepare the strings correspondingly. Finally you put it all together in as a JSON-formatted string in a POST request.

The below example takes the EntryId of the same item we used for the EWS ConvertId operation, and asks the Graph to return the EwsId. Comparing the two examples shows the exact same value, so the operation was a success. We then ask to return the ImmutableEntryId:

#Graph uses URL-safe encoding, work around it
$sourceID = ("AAAAAEI4ipo6z2ZLl269wbCQeHMHACD+gIoVTa5CpTqvk1wNXAAAAAAAAQwAACD+gIoVTa5CpTqvk1wNXAAAAq2DE+kAAA==" -replace "\+","-") -replace "/","_"$countToReplace = ($sourceID.ToCharArray() | ? {$_ -eq "="}).Count
$sourceID =$sourceID.TrimEnd("=") + $countToReplace$body = @{
"inputIds"= @($sourceID) "sourceIdType"= "entryId" "targetIdType"= "ewsId" } | ConvertTo-Json$res = Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/users/sharednew@Michev.info/translateExchangeIds" -Headers $authHeader -Verbose -ContentType "application/json" -Body$body
$res.value.targetId$body = @{
"inputIds"= @($sourceID) "sourceIdType"= "entryId" "targetIdType"= "immutableEntryId" } | ConvertTo-Json$res = Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/users/sharednew@Michev.info/translateExchangeIds" -Headers $authHeader -Verbose -ContentType "application/json" -Body$body
\$res.value.targetId

AAAAAB2EAxGqZhHNm8gAqgAvxFoNACD-gIoVTa5CpTqvk1wNXAAAAq2DORgAAA2