Appearance
This guide applies to imper.ai for Hiring, where interviews are scheduled in Microsoft 365 or Outlook.
To secure an interview, imper.ai needs three Microsoft 365 permissions:
Calendars.ReadWrite- to see the scheduled interview, and to attach the candidate's verification link to itOnlineMeetings.Read.All- to read the Teams meeting the interview is held inOnlineMeetingArtifact.Read.All- to read that meeting's attendance report, so imper.ai can confirm the candidate actually attended
Calendars.ReadWrite, granted through the standard consent flow, applies to every mailbox in your tenant, and Microsoft offers no way to narrow it at consent time.
The two Teams permissions work differently. Consenting to them grants imper.ai access to no meeting at all: an application access policy has to name the application before it can read anything, and that policy is what decides whose meetings. They are never tenant-wide in effect, however they were consented.
This guide shows how to grant them to a specific set of users instead, using a single security group that you control. Once configured, the group's membership is the access list: adding an interviewer grants access, removing them revokes it, and no further changes to the configuration are needed.
What You Will Need
- The imper.ai application already installed in your tenant, with
OnlineMeetings.Read.AllandOnlineMeetingArtifact.Read.Allconsented to - An account with the Exchange Administrator role, or membership in the Organization Management role group
- An account with the Teams Administrator role - the same account, if it holds both roles
- PowerShell 7 or later, on Windows, macOS, or Linux
- The imper.ai Application ID, supplied by your imper.ai contact
Everything else this guide needs is looked up from your own tenant.
The application has to exist in your directory before anything below can be granted to it, and the two Teams permissions have to be held by the application itself - the policy in Step 9 grants them for your group's members and no one else, so consenting to them is a prerequisite here rather than something this guide replaces. On their own they open nothing. Calendars.ReadWrite is the opposite: leave it unconsented, because Step 8 grants it through Exchange instead.
Confirm both are in place under Microsoft Entra ID > Enterprise applications > imper.ai > Permissions. If they are missing, or the application is not listed at all, complete the standard consent flow for those two permissions first - then return here.
IMPORTANT
If Calendars.ReadWrite has already been consented to tenant-wide, revoke it on that same page before you begin. Microsoft treats the two grants as additive, so a tenant-wide grant left in place keeps access to every mailbox and the group restriction has no effect.
Step 1 - Fill in your values
Every command in this guide reads from the variables below. Fill this block in once, paste it into your PowerShell session, and then run the remaining steps unchanged - there is nothing else to substitute by hand.
powershell
# ─── Your tenant ────────────────────────────────────────────────────────────
$organization_domain = "yourdomain.com"
# ─── The users imper.ai should be able to see ───────────────────────────────
# One line per interviewer or recruiter. You can change this list later
# without repeating any other step - see "Changing Who Is Covered".
$users_list = @(
"recruiter@$organization_domain"
"interviewer@$organization_domain"
)
# ─── The value supplied by your imper.ai contact ────────────────────────────
$app_id = "<imper.ai Application ID>"
# ─── Names for the objects this guide creates - the defaults are fine ───────
$group_name = "imper-microsoft-365-access"
$group_email = "$group_name@$organization_domain"
$scope_name = "imper calendar scope"
$assignment_name = "imper calendar access"
$policy_name = "imper-online-meetings"What this does: Defines the values used by every later command. Nothing is created or changed yet.
Where the user list comes from: if you already know the addresses of the recruiters and interviewers to cover, type them in. Otherwise export them from the system that schedules your interviews - in Workday, a report of the workers who act as interviewers, exported to CSV - and read the addresses straight from the file instead of pasting them in one by one:
powershell
$users_list = (Import-Csv .\interviewers.csv).EmailReplace Email with whichever column of the export holds the work email address.
Expected result: No output. Confirm the values were accepted:
powershell
$organization_domain, $group_email, $app_id
$users_listIMPORTANT
These variables live only in the current PowerShell session. If you close the window, or the session times out, paste this block again before continuing - otherwise later commands will run with empty values and create objects with the wrong names.
Step 2 - Connect to Exchange Online, Microsoft Graph and Teams
powershell
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Install-Module Microsoft.Graph -Scope CurrentUser
Install-Module MicrosoftTeams -Force -Scope CurrentUser
Connect-ExchangeOnline
Connect-MgGraph -Scopes "Application.Read.All"
Connect-MicrosoftTeamsNOTE
The Graph connection is read-only - Application.Read.All allows looking up an application, not modifying one. If your organization requires admin consent for the Graph PowerShell module, an administrator grants it once at first sign-in.
Step 3 - Look up imper.ai's identity
powershell
$sp = Get-MgServicePrincipal -Filter "appId eq '$app_id'"
$sp.Id, $sp.DisplayNameExpected result: Two lines - a GUID, then imper.ai. Confirm the display name matches before continuing.
Step 4 - Create the security group
powershell
New-DistributionGroup -Name $group_name `
-Alias $group_name `
-Type Security `
-PrimarySmtpAddress $group_emailExpected result: The group is created and appears in the Exchange admin center under Recipients > Groups.
Step 5 - Add the users to be covered
powershell
$users_list | ForEach-Object { Add-DistributionGroupMember -Identity $group_name -Member $_ }Expected result: No output. Confirm the membership:
powershell
Get-DistributionGroupMember -Identity $group_name -ResultSize Unlimited |
Select-Object DisplayName, PrimarySmtpAddress, RecipientTypeEvery address from $users_list should be listed, with its PrimarySmtpAddress shown.
IMPORTANT
Only direct members of the group are covered. If you add another group as a member, that nested group's users are not included, and no warning is shown. Add users individually, or ask your imper.ai contact about scoping to an administrative unit instead.
Step 6 - Retrieve the group's identifiers
powershell
$dn = (Get-Group -Identity $group_name).DistinguishedName
$group_id = (Get-DistributionGroup -Identity $group_name).ExternalDirectoryObjectId
$dn, $group_idExpected result: Two lines - a long string beginning with CN=imper-microsoft-365-access,OU=..., then a GUID. Storing them in variables avoids the transcription errors that copying such values by hand tends to produce.
Step 7 - Create the calendar access rule
powershell
New-ManagementScope -Name $scope_name `
-RecipientRestrictionFilter "MemberOfGroup -eq '$dn'"Expected result: The rule is created and its name is echoed back.
IMPORTANT
Confirm the rule resolves to real people. A rule that matches nobody is created successfully and reports no error - it surfaces later only as access being denied for everyone. Check it now:
powershell
Get-Recipient -RecipientPreviewFilter (Get-ManagementScope $scope_name).RecipientFilter |
Select-Object DisplayName, PrimarySmtpAddressEvery group member should be listed. An empty result means $dn was not set correctly in Step 6, or the group is not security-enabled.
Step 8 - Grant imper.ai calendar access, limited to the group
Register imper.ai in Exchange and grant the scoped role:
powershell
New-ServicePrincipal -AppId $app_id `
-ObjectId $sp.Id `
-DisplayName "imper.ai"
New-ManagementRoleAssignment -Name $assignment_name `
-App $app_id `
-Role "Application Calendars.ReadWrite" `
-CustomResourceScope $scope_nameStep 9 - Grant imper.ai Teams meeting and attendance access, limited to the group
powershell
New-CsApplicationAccessPolicy -Identity $policy_name `
-AppIds $app_id `
-Description "imper.ai online meeting and attendance access"
Grant-CsApplicationAccessPolicy -Group $group_id -PolicyName $policy_name -Rank 1What this does: Creates a policy naming imper.ai's application, then assigns it to the group. imper.ai can read a Teams meeting, and its attendance report, only when the meeting's organizer is a member of that group.
-Rank 1 sets this assignment's priority against any other group assignment of the same policy type. With a single assignment the value makes no difference; it is worth setting explicitly so a second group assignment added later has a defined order to slot into.
IMPORTANT
Propagation takes up to 30 minutes, and longer for a group assignment than a direct one, because membership has to be expanded first. Microsoft documents 30 minutes for application access policy changes to reach the Graph API.
Verify the Result
Calendar access
powershell
# Every user who should be covered
$users_list | ForEach-Object {
Test-ServicePrincipalAuthorization -Identity $app_id -Resource $_ |
Select-Object @{ n = 'Resource'; e = { $_ } }, RoleName, AllowedResourceScope, InScope
}
# And one user who should NOT be
Test-ServicePrincipalAuthorization -Identity $app_id `
-Resource "someone.else@$organization_domain" | Format-TableWhat this does: Reports whether imper.ai is permitted to access the calendar of each mailbox named.
Expected result:
InScope: Truefor every member of the groupInScope: Falsefor the non-member
Both outcomes are needed to confirm the configuration - the second is what proves the restriction is actually holding.
NOTE
This command reports your configuration immediately. The live permission change takes longer to apply - see Timing below.
Teams meeting and attendance access
Confirm the assignment, and where a given user's policy comes from:
powershell
Get-CsGroupPolicyAssignment -PolicyType ApplicationAccessPolicy
Get-CsUserPolicyAssignment -Identity "recruiter@$organization_domain" `
-PolicyType ApplicationAccessPolicy |
Select-Object -ExpandProperty PolicySourceThe first lists the group assignment with its rank. The second reports AssignmentType as Group, alongside the group's object ID, for a covered user.
NOTE
Do not verify this with Get-CsOnlineUser -Property ApplicationAccessPolicy. That property reports only a direct per-user assignment, so it reads as blank for a user covered through the group - which looks identical to having no access at all.
Timing
Microsoft caches application permissions, so any change in this guide takes up to 2 hours to take effect. It is often quicker - Microsoft's cache is refreshed after 30 minutes for an application that has been idle, and held for up to 2 hours for one that is actively in use - but plan for the full 2 hours.
Changing Who Is Covered
Add or remove group members. Nothing else needs to change - the rule from Step 7 re-reads the membership on every access attempt, and the Teams assignment from Step 9 is re-evaluated from the group, so no command from either needs to be repeated.
powershell
Add-DistributionGroupMember -Identity $group_name -Member "newuser@$organization_domain"
Remove-DistributionGroupMember -Identity $group_name -Member "olduser@$organization_domain"To replace the whole list at once, update $users_list and re-run Step 5. Allow for the caching window above before any change takes effect.
Removing the Configuration
To return to the standard tenant-wide arrangement, remove the objects in the order below, then grant Calendars.ReadWrite to imper.ai through the usual consent flow in the Azure portal. Paste the Step 1 variable block first if you are in a new session.
Undo the Teams grant, which has to go before the group it is assigned to:
powershell
$group_id = (Get-DistributionGroup -Identity $group_name).ExternalDirectoryObjectId
Remove-CsGroupPolicyAssignment -GroupId $group_id -PolicyType ApplicationAccessPolicy
Remove-CsApplicationAccessPolicy -Identity $policy_nameThen the calendar grant, the rule, the group and imper.ai's Exchange registration:
powershell
Remove-ManagementRoleAssignment -Identity $assignment_name -Confirm:$false
Remove-ManagementScope -Identity $scope_name -Confirm:$false
Remove-DistributionGroup -Identity $group_name -Confirm:$false
Remove-ServicePrincipal -Identity $app_id -Confirm:$falseThe order matters - a rule cannot be removed while a grant still refers to it, so the grant comes first.
Confirm nothing is left behind:
powershell
Get-ManagementRoleAssignment -Identity $assignment_name -ErrorAction SilentlyContinue
Get-ManagementScope -Identity $scope_name -ErrorAction SilentlyContinue
Get-DistributionGroup -Identity $group_name -ErrorAction SilentlyContinue
Get-ServicePrincipal -Identity $app_id -ErrorAction SilentlyContinue
Get-CsApplicationAccessPolicy -Identity $policy_name -ErrorAction SilentlyContinueEach should return nothing.
Troubleshooting
"Could not load file or assembly ... manifest definition does not match the assembly reference"
A Connect- command fails with something like:
OperationStopped: Could not load file or assembly
'~/.local/share/powershell/Modules/ExchangeOnlineManagement/3.10.1/netCore/Microsoft.Identity.Client.dll'.
The located assembly's manifest definition does not match the assembly reference. (0x80131040)Exit PowerShell, start a new pwsh session, and run Step 2 again - skipping the Install-Module commands, which already completed. The modules installed in Step 2 share Microsoft's authentication library, and installing them into a session that has already loaded an earlier copy of it leaves the loaded version and the one on disk mismatched. Nothing is wrong with the installation; only a fresh session loads the new files.
For the same reason, prefer running all of Step 2's installs first, then restarting PowerShell once before the Connect- commands, rather than interleaving installs and connections.
"The term 'New-DistributionGroup' is not recognized"
The Exchange commands are added to your session by the sign-in in Step 2. Run Connect-ExchangeOnline first.
"The term 'Get-MgServicePrincipal' is not recognized"
The Graph commands come from a separate module. Run Import-Module Microsoft.Graph.Applications and Connect-MgGraph -Scopes "Application.Read.All" from Step 2.
Get-MgServicePrincipal returns nothing
The Application ID does not match any application in your tenant. Two common causes:
- The value in
$app_idhas a typo, or is the imper.ai Object ID rather than the Application ID. Confirm with your imper.ai contact. - imper.ai has not yet been consented to in your tenant. The application must exist under Enterprise applications before it can be granted a scoped role. Complete the standard consent flow first, then return to this guide.
Confirm what the filter matches:
powershell
Get-MgServicePrincipal -Filter "appId eq '$app_id'" | Select-Object Id, AppId, DisplayNameA command creates an object with a blank or wrong name
The variables from Step 1 are not set in the current session - most often because the window was closed or the session timed out. Paste the Step 1 block again, verify with $group_name, $scope_name, $policy_name, $app_id, then remove any misnamed object and repeat the step.
InScope: False for a user who is in the group
Work through these in order:
powershell
# 1. What does the rule actually say?
Get-ManagementScope -Identity $scope_name | Format-List Name, RecipientFilter, RecipientRoot
# 2. Does it resolve to anyone?
Get-Recipient -RecipientPreviewFilter (Get-ManagementScope $scope_name).RecipientFilter |
Select-Object DisplayName, PrimarySmtpAddress
# 3. Is the group security-enabled, and is the user a direct member?
Get-DistributionGroup -Identity $group_name | Format-List Name, GroupType, RecipientTypeDetails
Get-DistributionGroupMember -Identity $group_name | Select-Object DisplayName, PrimarySmtpAddress
# 4. What is the grant bound to?
Get-ManagementRoleAssignment -Identity $assignment_name |
Format-List Name, Role, App, CustomResourceScope, EnabledIf the identifier stored in the rule does not match the group, correct it:
powershell
$dn = (Get-Group -Identity $group_name).DistinguishedName
Set-ManagementScope -Identity $scope_name -RecipientRestrictionFilter "MemberOfGroup -eq '$dn'"imper.ai reports "No application access policy found for this app"
The user imper.ai tried to act as is not covered by the Teams policy from Step 9. Either they are not a member of the group, or the group assignment has not propagated yet. Confirm membership with Get-DistributionGroupMember, then check the assignment:
powershell
Get-CsUserPolicyAssignment -Identity "user@$organization_domain" `
-PolicyType ApplicationAccessPolicy |
Select-Object -ExpandProperty PolicySourceNo output means the user is covered by neither a direct nor a group assignment.
imper.ai reports "3003: User does not have access to lookup meeting"
This is the opposite situation, and it is not a configuration fault. The user is covered by the policy, but the meeting being looked up is not theirs - a Teams meeting can only be read through its organizer. imper.ai reads each interview as the organizer who scheduled it, so this appears only when the wrong user is asked.
Both messages arrive as HTTP 403. The text is what distinguishes "outside the policy" from "inside the policy, wrong meeting", so include it when reporting a problem to your imper.ai contact.
imper.ai still reaches users outside the group
For calendars, either the caching window has not elapsed, or a tenant-wide Calendars.ReadWrite grant is still in place from an earlier consent flow. Check the application's Permissions page under Microsoft Entra ID > Enterprise applications > imper.ai and revoke it - Microsoft treats the two grants as additive.
For Teams meetings, the consent to OnlineMeetings.Read.All and OnlineMeetingArtifact.Read.All is expected and should stay - it grants nothing on its own, and the policy from Step 9 is what makes those permissions usable, for your group's members only. Because no policy means no access, a meeting readable outside the group means a second application access policy is granting it. List every group assignment with Get-CsGroupPolicyAssignment -PolicyType ApplicationAccessPolicy, check for a direct per-user assignment with Get-CsUserPolicyAssignment, and allow up to 30 minutes for any change to propagate.
A user in the group cannot be reached
Confirm they are a direct member with Get-DistributionGroupMember. Users belonging to a nested group are not covered.
"Insufficient permissions" when running a command
The Exchange and group steps require the Exchange Administrator role or membership in the Organization Management role group. Step 9 requires the Teams Administrator role.
Related
- Integrating Workday with imper.ai for Hiring - the calendar and meeting permissions in the context of the full Hiring setup
- Microsoft Entra Permissions - the full list of permissions imper.ai requests and why
- Integrations Overview