Using PIM Groups and Azure Key Vault as a Secure, Just in Time, Password Management Solution

As an MSP, CSP, general IT Service Provider or even a regular IT department, we generate a huge number of login credentials for different systems to keep everything running. While it is best practice to maintain a single source of identity using LDAP integrations, ADFS and delegation, sometimes the systems we work with don’t support this. Things like Certificate Authority logins, root passwords to non-Windows machines, local admin passwords etc. all need to be stored securely and potentially made available to a large team when needed.

This problem is very common, with many third party solutions such as LastPass having decent offerings around credential access management. One relatively low cost tool we have at our disposal in the Microsoft Cloud is Azure Key Vault. Key Vault allows us to securely store a range of sensitive credentials like secrets/passwords, keys and certificates and allow the other technologies in Azure to help us with access management. It also allows for logging of activity, backup and versioning of credentials which goes a long way towards making the solution scalable and secure.

We can create a Key Vault in our Azure Subscription very easily and start using it to store credentials for anyone with access, this is fine but persistent access to this secure data probably isn’t required and shouldn’t be in place.

To provide access to the keys we want to publish, we create a security group in Azure which will be granted permissions in the access policy to just the Keys required. As we want to make access available as ‘just in-time’ ensure to tick the option for “Azure AD roles can be assigned to the group (Preview)”.

When our group is created we can enable it for privileged access from the group settings under “Activity”.

Once enabled, we add our users who require access as eligible to the group.

Now that our group is ready, the last step is to assign access to the group. From the Key Vault we created, create a new Access Control policy, granting appropriate access to the Key Vault for our group. For this group I am granting the “Reader” permissions to the vault but this can be tailored to the specific group needs.

If required, we can customize the settings of the eligible role to put in any approval or notification requirements.

Now that the role is assigned, we can test with our user account.

Requesting Access

When our user requires access, they can navigate to the Privileged Identity Management section of the Azure portal and under Privileged Access Groups can request the access to the group providing justification for logging purposes and subject to any approval / notification settings etc.

The request will process with any notifications or approval we have specified.

Once activated, the role will allow our user to access to Key Vault for the time specified in the request. When this time expires they will need to activate the role again.


Key Vault and PIM work together to form an extremely secure workflow for secret management. While we can’t get granular for different individual objects, it’s recommended that a tiered approach using multiple vaults is taken to ensure segregation of permissions. Key Vault is also extremely cheap to run and using this model, is highly scalable.

Some other ideas would be to integrate something like Logic Apps to remind the Key Vault owners to rotate any stale passwords and update them in the vault. For certain services this rotation can even be automated with Logic Apps and/or Azure automation.

Using Delegated Access Permissions in PowerShell to Manage all Microsoft 365 Services

I recently posted about how we can use Delegated Access Permissions via a partner relationship to connect to an Exchange Online organization through PowerShell. This is a fantastic piece of functionality for MSPs and CSPs to manage multiple tenancies securely without having managing a set of admin identities for all of their customers.

To expand on the previous post, I thought I would put together each of the PowerShell modules that support delegated admin permissions in one place and also highlight any that I feel are missing.

In this post I will go through the connection methods (where available) using DAP for each of the below modules:

  • ExchangeOnline
  • MSOnline
  • Azure AD
  • MicrosoftTeams
  • Skype for Business
  • SharePoint Online
  • Security & Compliance Center

Exchange Online Module (v2)

I’ve gone through this one recently in another post so full information is available there. In short, we cann connect to Exchange Online Powershell using the Exchange Online (v2) PowerShell Module by specifying the tenant domain in our connection command.

First, install the module as normal:

Install-Module ExchangeOnline

Once installed, restart PowerShell and connect using the customer tenancy domain:

Connect-ExchangeOnline -DelegatedOrganization <>

MS Online Module

The MS Online Module works a little differently in that we don’t connect directly to our customer tenancy, we specify the tenancy in our commands.

We install the module with:

Install-Module MSOnline

Then we connect to our own service as normal:


Once we are connected, we need to locate the Tenant ID of our target organization. If we don’t have it to hand we can find it using the tenant domain in the below command:

Get-MsolPartnerContract -DomainName <> | Select-Object TenantID

Once we have the TenantID output (which will be a GUID), we can run commands against the tenant as below, using the -TenantID flag:

Get-MsolUser -All -TenantId <TenantID>

Azure AD Module

To connect to Azure AD, we need the Tenant ID from above to use in our connection. We can install the AzureADPreview Module:

Install-Module AzureADPreview

We then connect using our Tenant ID with the below command:

Connect-AzureAD -TenantId <TenantID>

Microsoft Teams Module

For Microsoft Teams we use the Tenant ID again. Install with:

Install-Module MicrosoftTeams

And then we connect with the Tenant ID as below:

Connect-MicrosoftTeams -TenantId <TenantID>

Skype for Business Module

The Skype for Business Module is interesting in that a lot of organizations have moved off Skype to use Microsoft Teams. The Skype module is still required to manage certain aspects of Teams though. The connection to the module is equally as strange. Once we have connected to Teams as above, we then new to create out connection to Skype using the below commands to create the session and then import it:

$session = New-CsOnlineSession
Import-PSSession $session

This will connect our existing Teams session to the Skype for Business module!

SharePoint Online Module

Unfortunately the SharePoint Online Module does not support DAP at the moment. I will update this post when/if it becomes available.

Security & Compliance Center Module

The Security and Compliance Center Module is installed as part of the Exchange Online (v2) module and allows connection to services such as DLP and Information Protection.

To connect to the Security & Compliance Center we can install the Exchange Online (v2) module as above and use the -DelegatedOrganization flag to specify our customer domain:

Connect-IPPSSession -DelegatedOrganization <CustomerDomain>

And that’s it, that’s pretty much all the modules I use on a daily basis, I will update this post as/when more updates or modules are available.

Azure AD Group Role Assignment – Exchange Online Access Denied

Azure AD Group Role Assignment is a great new preview feature that provides a lot of flexibility and governance to assigning admin roles in Azure AD / Office 365. When combined with Privileged Identity Managements new Privileged Access Groups (Preview) feature, we can begin to set up a really slick permission eligibility structure that is both easy to manage and easy to administer.

I recently came across an issue when testing this set up. I had requested and activated my Global Admin role via a dedicated global admin role group and got into the Admin Portal without issue. I then opened the Exchange Online Admin Portal and was greeted with this message:

Very strange considering I was now logged in as a Global Admin and could see pretty much everything in the Admin Portal. I tried the usual signing out and clearing cookies etc. but couldn’t get it to work.

Finally I did some digging into the documentation and sure enough found my problem within the known issues section of the ‘Assign Roles to Groups‘ section.

“Use the new Exchange Admin Center for role assignments via group membership. The old Exchange Admin Center doesn’t support this feature yet. Exchange PowerShell cmdlets will work as expected.”

From the documentation I could see that the regular Exchange Admin Center doesn’t support Group Based role assignments yet. Ok, no problem, except the link in the Admin Portal takes me directly to the old Admin Center, with no option for the new until you are logged in.

To work around this we can just go directly to and gain access with our group based role assignment.

Luckily Microsoft documentation is miles ahead of where it used to be and this was quickly found and resolved, even if I felt a bit stupid for not realizing this limitation in the first place. Can’t really complain considering it’s a preview feature currently.

There are a few other known issues to be aware of with Group Based role assignments but for the most part I have found the perform very well and are a welcome addition:

Group Based Role Assignments Known Issues

  • The Enable staged rollout for managed user sign-in feature doesn’t support assignment via group.
  • Azure AD P2 licensed customers only: Don’t assign a group as Active to a role through both Azure AD and Privileged Identity Management (PIM). Specifically, don’t assign a role to a role-assignable group when it’s being created and assign a role to the group using PIM later. This will lead to issues where users can’t see their active role assignments in the PIM as well as the inability to remove that PIM assignment. Eligible assignments are not affected in this scenario. If you do attempt to make this assignment, you might see unexpected behavior such as:
    • End time for the role assignment might display incorrectly.
    • In the PIM portal, My Roles can show only one role assignment regardless of how many methods by which the assignment is granted (through one or more groups and directly).
  • Azure AD P2 licensed customers only Even after deleting the group, it is still shown an eligible member of the role in PIM UI. Functionally there’s no problem; it’s just a cache issue in the Azure portal.
  • Use the new Exchange Admin Center for role assignments via group membership. The old Exchange Admin Center doesn’t support this feature yet. Exchange PowerShell cmdlets will work as expected.
  • Azure Information Protection Portal (the classic portal) doesn’t recognize role membership via group yet. You can migrate to the unified sensitivity labeling platform and then use the Office 365 Security & Compliance center to use group assignments to manage roles.

Send Azure AD Guest User Invitations via Graph API

The built in controls in Azure AD for Guest User invitations are great for most cases. You can lock down guest invitations to specific users or groups and even specific recipient domains. When we look at more highly secure tenancies however, we often see requirements for approval flows or custom workflows to be associated with Guest User invitations.

This isn’t something available in the native GUIs but, as usual, that doesn’t mean it’s not possible! This entire process can be automated and built into a simple Graph API call. We can then expand out what we build into a Logic App/Power Automate flow. Using the different Microsoft technologies, the entire process can be customized to an endless degree. Even non-Microsoft technologies such as Service Management tools can kick off the process and integrate nicely into Azure Automation etc.

To help people (including myself on several projects) to build out their custom processes around guest user invitations, I’ve built the below PowerShell function to provision a guest user invitation via Graph API

In this post, we’ll walk through setting up the environment for the function and how to run it.

Setting up an app registration

The first thing we need for our Graph call is an application registration. This can be set up in Azure AD and assigned the permissions as below:

Configure the new app registration with a name. A redirect URL of http://localhost can be set as we don’t need redirect.

Under API Permissions, add and grant User.Invite.All, User.ReadWrite.All and Directory.ReadWrite.All permissions. Depending on how you are connecting this can be delegated or application permissions but as I need to automate the calls, I’ve selected application permissions. Ensure to grant consent on the permissions after adding them

Finally, create a Client Secret and take note of it for use in the Graph Connection

Parameters Required

Before we can run the function, we need to collect some information. We need to gather the below information about our tenant:

  • The Client ID of the application registration we created above
  • The Tenant ID (Directory ID) from Azure AD
  • The Client Secret we noted when we created the application registration
  • A URL to redirect users to after completion, this isn’t mandatory and the default is

Once we have all this information, we can import the function by running:

import-module <Filepath to PS1 file>\graph-Send-GuestInvitation.ps1

Once imported, we can run our function with the below command:

SendGuestInvitation -UserEmail <Guest user email> -ClientSecret <Your Client Secret> -ClientID <Your Client ID> -TenantID <Your Tenant ID>

When the function is run, it will return an invitation object. This object contains the details of the invitation as well as the “inviteRedeemURL” which can be sent to the guest to redeem their invitation in a more custom, branded email from an offical corporate email address.

This function can be found on GitHub here

Note, this code is for illustration purposes and not fit for running in production. Please ensure you understand any code you are running in a production environment.

function GetGraphToken {
    # Azure AD OAuth Application Token for Graph API
    # Get OAuth token for a AAD Application (returned as $token)
    This function gets and returns a Graph Token using the provided details

    .PARAMETER clientSecret
    -is the app registration client secret

    .PARAMETER clientID
    -is the app clientID

    .PARAMETER tenantID
    -is the directory ID of the tenancy
        [parameter(Mandatory = $true)]
        [parameter(Mandatory = $true)]
        [parameter(Mandatory = $true)]


    # Construct URI
    $uri = "$tenantId/oauth2/v2.0/token"
    # Construct Body
    $body = @{
        client_id     = $clientId
        scope         = ""
        client_secret = $clientSecret
        grant_type    = "client_credentials"
    # Get OAuth 2.0 Token
    $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
    # Access Token
    $token = ($tokenRequest.Content | ConvertFrom-Json).access_token
    return $token

Function SendGuestInvitation {
    This function gets Graph Token from the GetGraphToken Function and uses it to request a new guest user
    .PARAMETER UserEmail
    -UserEmail is the email address of the requested user
    .PARAMETER clientSecret
    -is the app registration client secret

    .PARAMETER clientID
    -is the app clientID

    .PARAMETER tenantID
    -is the directory ID of the tenancy
    .PARAMETER tenantID
    -A URL to redrect to after the invitation is redeemed
        [parameter(Mandatory = $true)]
        [parameter(Mandatory = $true)]
        [parameter(Mandatory = $true)]
        [parameter(Mandatory = $true)]
        [parameter(Mandatory = $false)]
        $RedirectURL = ""


    $token = GetGraphToken -ClientSecret $ClientSecret -ClientID $ClientID -TenantID $TenantID

    $apiUri = ''
    $body = "{'invitedUserEmailAddress': '$UserEmail','inviteRedirectUrl': '$RedirectURL'}"
    $invitation = (Invoke-RestMethod -Headers @{Authorization = "Bearer $($token)" } -Uri $apiUri -Method Post -ContentType 'application/json' -Body $body)

    Return $invitation

Conditional Access for Office 365 Apps Goes GA

Conditional Access is one of the first steps any organization should take when protecting user identities in Azure AD. The flexibility available through Conditional Access policies is fantastic for meeting sign-in requirements and depending on licensing, can even do some proactive mitigation of breaches using risk and sign-in policies.

Office 365 relies heavily on Azure AD to service authentication for users. Conditional Access is often a minimum requirement to allow users to securely access Office 365 services by enforcing protection on the sign-in activity. Previously, it has been hard to manage Conditional Access policies that only target Office 365 apps as the platform constantly expands and when new apps are published, they are not automatically included in our Conditional Access Policies.

Last year, Microsoft made this much easier by including the ‘Office 365’ app in Conditional Access as a preview feature. This meant that the different components of Office 365 no longer had to be included separately.

Previously Applying Blanket Protection to Office 365 Apps Was Cumbersome
With the Office 365 App Available, This is Much Easier

This week marks the official General Availability of the Office 365 app in Conditional Access. There should now be no excuse not to be using this app to provide holistic protection to Office 365 users.

For more information on what the Office 365 app in Conditional Access applies to, see the Official Microsoft Documentation.

Protecting Office 365 Groups and Microsoft Teams with Sensitivity Labels (Preview)

I often end up in one of two conversations around Microsoft Teams governance with customers, the “Users can manage them themselves so we don’t need to worry” group, and the “Nobody gets a Team unless we follow this 20 step approval process and our service desk needs to set them up and lock them down” group.

Both options have their merits, but also their pitfalls. If we let everyone create teams we end up with sprawl and have no idea where our data is stored (why are there six “HR Teams”, and which one contains the right data?). On the other hand, if we don’t let our users use Teams without jumping through hoops while saying the alphabet backwards, in Latin, then we are preventing people from using some of the most powerful collaboration features available to them.

We usually end up finding a good middle ground in these discussions that leverages automation and some of the cool Information Protection features of Microsoft 365. My opinion on Teams provisioning process has been the same as it was for SharePoint sites, “I don’t care if we have ten thousand Teams, as long as they are named and protected correctly, the number doesn’t matter”.

This opinion was idealistic in the early days of Microsoft Teams as the governance features just weren’t where I wanted them to be. In the past year, Microsoft have taken strides in the features available and I’m pretty happy (albeit still a few features I’d like) with what’s available. I might even do a follow up post where I can rant about my Teams security and governance opinions down the line.

One feature that has made my life much easier since it was made available (in Preview) is the ability of Sensitivity Labels to be applied to Office 365 Groups / Teams / Sites . This feature allows us to define Sensitivity Labels which, when applied to a Group, can control the privacy level of the Group, the level of functionality available to unmanaged devices, and even the external access configuration.

During some early Teams projects I had automated scripts which changed these group settings in Azure AD, SharePoint sharing settings on the site level at the time of provisioning. That was a nightmare as it had to be maintained and knowledge transferred to the incumbent IT Teams.

When this feature is enabled, we just need to specify the settings in our sensitivity label and it’s all taken care of for us.

The site and group settings tab

To enable this feature for your tenant now, connect to the Azure AD Preview PowerShell Module and run the below to update the Directory Setting and enable MIP Labelling in Office 365 Groups.

##Copy Current Settings to $Settings Variable
$Setting = Get-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id

##Change EnableMIPLables setting
$Setting["EnableMIPLabels"] = "True"

##Write back changes to directory
Set-AzureADDirectorySetting -Id $Setting.Id -DirectorySetting $Setting

Next, connect to the Security & Compliance PowerShell Module and run the below to start the synchronization process between MIP and AAD.


After a little time to replicate, you will be able to see the above page when configuring a new sensitivity label and then apply them to Teams/Groups/Sites and once the labels are deployed (usually 24 hours after creation) you’ll be able to apply them at provisioning time!

Send Azure AD Audit and Sign-In Logs to Azure Log analytics

Microsoft have recently announced the availability of Azure Log analytics for Azure AD sign-in and audit logging. This is a really cool feature, especially for large organizations where there will be a lot of traffic to audit. In this post I will go through the basic setup.


  • Azure AD Global Admin
  • Azure Subscription

Log onto the Azure portal page and select Azure Active Directory > Diagnostic settings -> Add diagnostic setting

Select the option to “Turn on diagnostics” if it is not already turned on and configure Azure AD Diagnostics. Select the option to “Send to Log analytics” and click “Configure” to create a new OMS workspace. Choose to send either Audit logs, Sign-In Logs or both to Log analytics.




Click ‘Save’ to finalize the setup.

Wait for 15 minutes or so for the data to begin sending to Log Analytics. (Time to grab some tea)

When you’ve finished your tea, select Azure Active Directory  > Monitoring > Logs to view your new Log analytics workspace.

We can see in the default query that the configuration itself is showing up in the logs:



Now lets generate some sign in traffic by logging in as a user. After logging in, wait for 5 or so minutes for the log to show up.

After a couple of minutes, we can see sign in logs in our default query.


Using Log analytics, we can configure custom queries and set alerts and actions on pretty much anything that happens in Azure AD. This is an extremely powerful tool for organizations to leverage to enhance security, governance and automation across the platform.