Give a user Full Access or Send As on a shared mailbox, or take it away

Build a workflow action that gives a user Full Access, Send As or both on a shared mailbox, or removes them, so MSPs and IT teams can handle shared mailbox requests by asking SuperIT.

Runs in Cloud sandbox (PowerShell)
Uses Microsoft Graph credential with an Exchange Online scope
Suggested approval Require approval from superit:admins for every run
Category Email

Test it with SuperIT before you rely on it

This is an example, not a ready-made action, and its script hasn't been tested end to end in a SuperIT tenant. The Exchange Online credential it depends on hasn't been tested with SuperIT either, so it may not work as written. Before you use it, work through it with SuperIT: ask SuperIT to review the script against your environment and requirements, adjust it together, then test it on a test account or device. Test in the action editor runs the script for real.

Why use an action instead of asking SuperIT

SuperIT reads Microsoft 365 through your Microsoft 365 connection, but that connection is read-only, and mailbox changes like this one need Exchange Online, which SuperIT can't call in a chat. Granting or removing shared mailbox permissions needs an action with an Exchange Online credential. Shared mailboxes often hold sensitive mail, so an admin approves every run, ideally after checking with the mailbox's business owner.

To check the user's account, licences or groups, just ask SuperIT in chat. Mailbox permissions aren't readable through the Microsoft 365 connection.

When to use it

  • "Can Priya get access to the accounts@ mailbox?"
  • "Give Tom Send As on [email protected]."
  • "Remove Jane's access to the HR shared mailbox."

What the action does

  1. Checks the inputs, and that the mailbox is a shared mailbox and the user exists.
  2. Connects to Exchange Online with the credential.
  3. Reads the user's current permissions, then grants or removes Full Access (with automatic mapping, so the mailbox appears in the user's Outlook) and Send As, as requested. It skips any permission that's already as requested, so removing access the user doesn't have reports no change instead of failing.
  4. Prints the user's permissions on the mailbox after the change.

What it doesn't do: it doesn't create shared mailboxes, grant Send on Behalf, or change folder-level permissions. It refuses user mailboxes, so it can't be used to open a colleague's personal mailbox.

Before you begin

  • A Microsoft Graph credential for Exchange Online actions, named M365 Exchange, with the scope https://outlook.office365.com/Exchange.Manage, signed in with an account that holds Exchange Administrator. Link it only to Exchange Online actions. This credential setup hasn't been tested with SuperIT; if the credential won't register or the script can't connect, see Exchange Online tasks.
  • An authorization policy with one rule: Specific groups superit:engineers, superit:admins → Require approval from superit:admins. Don't add an Anyone rule: the script doesn't receive the requester's identity, so an end user could give themselves access to any shared mailbox.
  • The Engineer or Admin role to build the action in Workflows → Actions. Only admins can create actions in chat.

Build it with SuperIT

Create a workflow action called "Grant shared mailbox access".
Runtime: PowerShell in the cloud sandbox. Credential: M365 Exchange
(Exchange Online, connect with Connect-ExchangeOnline -AccessToken).
Inputs:
- shared_mailbox (email, required)
- user_upn (email, required)
- access (select, required, choices: full_access, send_as, both)
- operation (select, required, choices: grant, remove)
It should refuse anything that isn't a shared mailbox, only change permissions that
differ from what was asked, use Add/Remove-MailboxPermission and
Add/Remove-RecipientPermission, and print the user's permissions afterwards.
Authorization policy: engineers and admins, every run approved by superit:admins.

SuperIT repeats the brief back, drafts the script, checks it, and creates the action as a draft. Review the script in Workflows → Actions, test it, then publish it.

Or build it yourself

Name Type Required Example
shared_mailbox email Yes [email protected]
user_upn email Yes [email protected]
access select (choices full_access, send_as, both) Yes both
operation select (choices grant, remove) Yes grant
# Goal: grant or remove a user's Full Access and/or Send As on a shared mailbox in Exchange Online,
# then print the user's permissions on it.
$token = $env:CREDENTIAL_M365_EXCHANGE_TOKEN

function Invoke-SharedMailboxAccessChange {
    param([string]$SharedMailbox, [string]$UserUpn, [string]$Access, [string]$Operation)

    $SharedMailbox = $SharedMailbox.Trim()
    $UserUpn = $UserUpn.Trim()
    $Access = $Access.Trim().ToLower()
    $Operation = $Operation.Trim().ToLower()
    Assert-Inputs -SharedMailbox $SharedMailbox -UserUpn $UserUpn -Access $Access -Operation $Operation

    Connect-ExchangeWithToken
    try {
        Assert-SharedMailbox -SharedMailbox $SharedMailbox -UserUpn $UserUpn
        $before = Get-UserMailboxPermissions -SharedMailbox $SharedMailbox -UserUpn $UserUpn
        $want = $Operation -eq 'grant'
        # Only change what differs, so removing access the user doesn't have reports "no change" instead of failing.
        if (($Access -in 'full_access', 'both') -and $before.full_access -ne $want) {
            Set-FullAccess -SharedMailbox $SharedMailbox -UserUpn $UserUpn -Operation $Operation
        }
        if (($Access -in 'send_as', 'both') -and $before.send_as -ne $want) {
            Set-SendAs -SharedMailbox $SharedMailbox -UserUpn $UserUpn -Operation $Operation
        }
        $permissions = Get-UserMailboxPermissions -SharedMailbox $SharedMailbox -UserUpn $UserUpn
        Write-AccessSummary -SharedMailbox $SharedMailbox -UserUpn $UserUpn -Before $before -Permissions $permissions
    } finally {
        Disconnect-ExchangeOnline -Confirm:$false
    }
}

function Assert-Inputs {
    param([string]$SharedMailbox, [string]$UserUpn, [string]$Access, [string]$Operation)

    if (-not $token) { throw "Link the M365 Exchange credential to this action." }
    foreach ($a in $SharedMailbox, $UserUpn) { if ($a -notmatch '^[^@\s]+@[^@\s]+$') { throw "'$a' is not an email address." } }
    if ($Access -notin 'full_access', 'send_as', 'both') { throw "access must be full_access, send_as or both." }
    if ($Operation -notin 'grant', 'remove') { throw "operation must be grant or remove." }
}

function Connect-ExchangeWithToken {
    $upn = Get-TokenUpn
    Import-Module ExchangeOnlineManagement
    try {
        # A delegated token must be paired with the signed-in admin's UPN, read from the token itself.
        Connect-ExchangeOnline -AccessToken $token -UserPrincipalName $upn -ShowBanner:$false
    } catch {
        Write-Error "Failed to connect to Exchange Online as '$upn': $_"
        exit 1
    }
}

function Get-TokenUpn {
    $part = $token.Split('.')[1].Replace('-', '+').Replace('_', '/')
    $part += '=' * ((4 - $part.Length % 4) % 4)
    ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($part)) | ConvertFrom-Json).upn
}

function Assert-SharedMailbox {
    param([string]$SharedMailbox, [string]$UserUpn)

    $mbx = Get-Mailbox -Identity $SharedMailbox
    # Refusing user mailboxes stops this action from opening a colleague's personal mail.
    if ($mbx.RecipientTypeDetails -ne 'SharedMailbox') { throw "$SharedMailbox is a $($mbx.RecipientTypeDetails), not a shared mailbox." }
    $null = Get-User -Identity $UserUpn
}

function Set-FullAccess {
    param([string]$SharedMailbox, [string]$UserUpn, [string]$Operation)

    try {
        # -ErrorAction Stop: Exchange cmdlets run in their own module and ignore the script's $ErrorActionPreference.
        if ($Operation -eq 'grant') {
            Add-MailboxPermission -Identity $SharedMailbox -User $UserUpn -AccessRights FullAccess -InheritanceType All -AutoMapping $true -ErrorAction Stop | Out-Null
        } else {
            Remove-MailboxPermission -Identity $SharedMailbox -User $UserUpn -AccessRights FullAccess -InheritanceType All -Confirm:$false -ErrorAction Stop
        }
    } catch {
        Write-Error "Failed to $Operation Full Access on '$SharedMailbox' for '$UserUpn': $_"
        exit 1
    }
}

function Set-SendAs {
    param([string]$SharedMailbox, [string]$UserUpn, [string]$Operation)

    try {
        if ($Operation -eq 'grant') {
            Add-RecipientPermission -Identity $SharedMailbox -Trustee $UserUpn -AccessRights SendAs -Confirm:$false -ErrorAction Stop | Out-Null
        } else {
            Remove-RecipientPermission -Identity $SharedMailbox -Trustee $UserUpn -AccessRights SendAs -Confirm:$false -ErrorAction Stop
        }
    } catch {
        Write-Error "Failed to $Operation Send As on '$SharedMailbox' for '$UserUpn': $_"
        exit 1
    }
}

function Get-UserMailboxPermissions {
    param([string]$SharedMailbox, [string]$UserUpn)

    @{
        full_access = [bool](Get-MailboxPermission -Identity $SharedMailbox -User $UserUpn -ErrorAction SilentlyContinue |
            Where-Object { $_.AccessRights -contains 'FullAccess' })
        send_as     = [bool](Get-RecipientPermission -Identity $SharedMailbox -Trustee $UserUpn -ErrorAction SilentlyContinue |
            Where-Object { $_.AccessRights -contains 'SendAs' })
    }
}

function Write-AccessSummary {
    param([string]$SharedMailbox, [string]$UserUpn, [hashtable]$Before, [hashtable]$Permissions)

    $changed = $Before.full_access -ne $Permissions.full_access -or $Before.send_as -ne $Permissions.send_as
    Write-Output "=== SHARED MAILBOX ACCESS ==="
    Write-Output "Mailbox     : $SharedMailbox"
    Write-Output "User        : $UserUpn"
    Write-Output "Full Access : $($Permissions.full_access)"
    Write-Output "Send As     : $($Permissions.send_as)"
    if (-not $changed) { Write-Output "Change      : none, the user already had the access you asked for" }
    # $script: so the runner's wrapper sees it; a plain assignment stays local to this function.
    $script:result = @{ mailbox = $SharedMailbox; user = $UserUpn; full_access = $Permissions.full_access; send_as = $Permissions.send_as }
}

Invoke-SharedMailboxAccessChange -SharedMailbox $ctx.inputs.shared_mailbox -UserUpn $ctx.inputs.user_upn `
    -Access $ctx.inputs.access -Operation $ctx.inputs.operation

Full Access with automatic mapping can take up to an hour to appear in the user's Outlook.

Test it

Test runs for real

Test skips authorization and approval, uses the real M365 Exchange credential, and really changes the mailbox's permissions. Use a test shared mailbox and a test user.

The script is untested, so treat your first test runs as debugging, and don't publish the action until a test run does exactly what you expect.

  1. On the action, select Test. Enter a test shared mailbox, a test user, both and grant.
  2. Read the This is not a dry run. warning, tick "I understand this is a real execution that may change real systems." and select Start test run.
  3. Check Stdout for Full Access = True, Send As = True.
  4. Test again with remove, then run remove a second time and check it reports no change.

Use it

Publish the action. Engineers ask SuperIT, for example "Give Priya full access and send as on [email protected]", and the run waits for an admin to approve it.

For MSPs, publish to managed teams from the Publish dialog; an engineer or admin in each team authorizes its own M365 Exchange credential.

Variations

  • Turn off automatic mapping (-AutoMapping $false) for users who prefer to add the mailbox themselves.
  • Add a send_on_behalf option with Set-Mailbox -GrantSendOnBehalfTo.
  • Report everyone who has access to a shared mailbox, for regular access reviews.