Initialize-PimMailSender
Set up the shared mailbox PIM Manager sends mail from
Purpose
Set up the shared mailbox PIM Manager sends its notification mail from: create the mailbox, give each sending identity a send right scoped to that one mailbox (Exchange RBAC for applications), remove any tenant-wide Microsoft Graph Mail.Send from those identities, and store the sender in the PIM store.
When to run it
Once per environment, when PIM Manager > Get Started > Mail sender (shared mailbox) or the end-of-install check says the mail sender is not set up -- typically because the install ran as a person without an Exchange role. Run it again at any time: every step reads first and an environment that is already set up is left unchanged. Needs an Exchange Online plan in the tenant (EXCHANGE_S_FOUNDATION alone is not enough).
Who may run it
| Kind | Role | Scope | Why |
|---|---|---|---|
| entraRole | Exchange Administrator (or Global Administrator) | the tenant (an ACTIVE assignment -- activate it first if it is eligible) | Browser sign-in (the default): the person's own Exchange role creates the mailbox, the Exchange registrations, the management scope and the scoped Application Mail.Send assignments. No app is granted any Exchange right in this mode. |
| other | Delegated Microsoft Graph consent: Organization.Read.All, Application.Read.All, User.Read.All, AppRoleAssignment.ReadWrite.All (public client Microsoft Graph Command Line Tools) | the signed-in person, this tenant | Reads the Exchange plan (subscribedSkus), the initial domain (organization), the sending identities' service principals and the mailbox's directory user; AppRoleAssignment.ReadWrite.All is used only to remove a tenant-wide Mail.Send. The consent prompt appears at the first sign-in when the tenant has not consented yet. |
| entraRole | Privileged Role Administrator, Cloud Application Administrator or Global Administrator | the tenant | Only when a sending identity or the engine identity still holds a tenant-wide Microsoft Graph Mail.Send: removing an application permission needs one of these roles besides the delegated AppRoleAssignment.ReadWrite.All. An Exchange Administrator alone can do every other step. |
| azureRbac | Reader | the Container Apps job and/or Manager container app named by -TickJobName / -ManagerAppName (or their resource group) | Only with -SubscriptionId -ResourceGroup -TickJobName / -ManagerAppName: reads the app's managed identity so its object id does not have to be typed. |
| other | Member of the environment's SQL admin group (grp-pim-sql-admins) | the environment's SQL database (pim.Settings) | Only with -SqlServerFqdn: stores MailSender and MailMode. From a computer that cannot reach the store this step is reported as 'Saved: no ... Finish in PIM Manager' (Get Started > Mail sender > Check and send a test mail) and is not a failure in the browser sign-in -- or, with -AllowThisIpTemporarily, a temporary firewall rule is added (see below). |
| azureRbac | SQL Server Contributor (or Contributor) | the environment's SQL server | Only with -AllowThisIpTemporarily: adds a temporary firewall rule for the refused IP and removes it again in the same run. |
| other | Invardia Support app (-AdminAppId + -AdminSecret) holding Microsoft Graph application permissions AppRoleAssignment.ReadWrite.All and RoleManagement.ReadWrite.Directory | the tenant | Only for the unattended support / deploy path instead of a person: the app gives ITSELF Exchange.ManageAsApp and activates a time-bound Exchange Administrator through PIM (see changes). When the app cannot grant itself, a Global Administrator signs in in the browser for those two grants only. |
What it changes
| Where | Action | What | Permission / role | Scope | Why | Undo |
|---|---|---|---|---|---|---|
| Exchange Online | create | shared mailbox <MailboxName>@<initial domain> (New-Mailbox -Shared; default PIM-Engine@<tenant>.onmicrosoft.com, display name 'PIM Manager') | the tenant | The address PIM Manager sends its notifications from. A shared mailbox needs no licence and no password; nobody signs in to it. | Remove the mailbox (Exchange admin center > Mailboxes, or Remove-Mailbox) -- PIM Manager then stops sending mail. | |
| Exchange Online | create | Exchange registration of each sending identity (New-ServicePrincipal with the identity's app id and object id): the engine job's and the Manager's managed identity, or the engine application when no managed identity sends | the tenant's Exchange organization | Exchange RBAC for applications can only assign a role to a service principal Exchange knows. | Remove-ServicePrincipal -Identity <app id> (after removing its role assignment). | |
| Exchange Online | create | management scope -ScopeName (default <MailboxName>-SendScope; a scope that already restricts to this mailbox -- e.g. PIM4EntraPS-Sender from an earlier release -- is kept under its own name, never duplicated) with RecipientRestrictionFilter PrimarySmtpAddress -eq '<sender>' (New-ManagementScope) | exactly one recipient: the sender mailbox | Limits the send right below to this one mailbox. A scope that already exists for ANOTHER mailbox is never reused or changed (the run stops instead). | Remove-ManagementScope -Identity <scope name> (after removing the assignments that use it). | |
| Exchange Online | grant | each sending identity (New-ManagementRoleAssignment, name <AssignmentNamePrefix>-<short id>-MailSend or <AssignmentNamePrefix>-App-MailSend; an assignment the identity already holds -- e.g. PIM4EntraPS-MI-<id>-MailSend -- is kept, never duplicated) | Exchange role 'Application Mail.Send' (Exchange RBAC for applications) | the management scope above = the sender mailbox only | Lets PIM Manager's engine and Manager send as that one mailbox and no other. This is the ONLY send right the script grants -- there is no tenant-wide Mail.Send. | Remove-ManagementRoleAssignment -Identity <assignment name>. |
| Exchange Online | remove | a sending identity's Application Mail.Send assignment on ANOTHER sender scope of PIM Manager (<mailbox>-SendScope or PIM4EntraPS-Sender*; Remove-ManagementRoleAssignment) -- and, only with -RemoveEngineSpnAssignment, the engine application's older assignment on this scope | the one assignment named | An identity scoped to another environment's mailbox would send as the wrong mailbox (assignment names are tenant-unique, so the right one cannot be created beside it). The engine application's own older assignment is left in place unless explicitly asked. | Re-create it with New-ManagementRoleAssignment (same name, role, app and scope). | |
| Exchange Online | update | the organization: Enable-OrganizationCustomization -- only when the organization is still 'dehydrated' | the tenant's Exchange organization | A new Exchange organization refuses every custom RBAC object until customization is enabled. | None -- Microsoft offers no way back, and it changes nothing visible. | |
| Microsoft Graph | remove | application permission (app role assignment) Microsoft Graph Mail.Send held TENANT-WIDE by a sending identity or the engine identity | the tenant (the assignment being removed was tenant-wide) | A tenant-wide Mail.Send lets the identity send as ANY mailbox and silently defeats the per-mailbox scope above (measured: with it, an out-of-scope send is accepted). Removing it makes the scope govern. | Not recommended. Re-grant Mail.Send (application) to that identity in Entra admin center > Enterprise applications > the identity > Permissions. | |
| PIM store (Azure SQL) | update | pim.Settings rows MailSender (the mailbox's directory UPN -- the key Microsoft Graph sends as) and MailMode = sharedMailbox, MailSenderAddress (the mailbox's primary SMTP address -- the address you chose -- that PIM Manager shows, with the UPN as detail), plus MailSenderSetupCheck (the read-back that confirmed the scoped Mail.Send for every sending identity in one scope, so PIM Manager shows the engine job's send right as granted once its own test mail works) | this environment | Only with -SqlServerFqdn. The engine and the Manager read the sender from here; without it the mail is rendered and never sent. | PIM Manager > Settings > Mail & alerting: choose another sender or mode. | |
| Azure SQL server | create | a TEMPORARY firewall rule PimSetupTemp-<ip>-<utc time> for exactly the refused IP -- only with -AllowThisIpTemporarily and only when the store refused this computer | the environment's SQL server, one IP | Lets this computer store the sender when the SQL firewall refuses it. Removed again in the same run (finally), whether the write worked or not; a rule that could not be removed is reported with its name. | It is removed by the run. If the run reports it could not: SQL server > Networking > remove the PimSetupTemp-* rule. | |
| Microsoft Graph (Invardia Support app sign-in only) | grant | the setup app itself (-AdminAppId) | Exchange.ManageAsApp (application permission of Office 365 Exchange Online) | the tenant | App-only Exchange administration needs it. Not used and not granted in the browser sign-in. It has no expiry but is inert without the directory role below. | Entra admin center > Enterprise applications > the setup app > Permissions: remove Exchange.ManageAsApp. |
| Microsoft Entra ID (Invardia Support app sign-in only) | grant | the setup app itself (-AdminAppId), through PIM (roleAssignmentScheduleRequests, time-bound) | Exchange Administrator directory role (active, -ExchangeAdminDuration, default PT4H) | the tenant | Exchange accepts the app's admin calls only while it holds an Exchange role. Granted through PIM so it EXPIRES by itself; an assignment that is already active is reused, and one that lasts longer than asked is reported as standing privilege. | It expires by itself. To end it sooner: Entra admin center > Roles > Exchange Administrator > Active assignments > remove (a different administrator: an identity cannot remove its own role). |
| Microsoft Graph (Invardia Support app sign-in only) | grant | the setup app itself (-AdminAppId) -- self-heal, only when it does not hold it yet | RoleManagement.ReadWrite.Directory (application permission of Microsoft Graph) | the tenant | Needed to read and request the time-bound Exchange Administrator role above. A setup identity created before 2026-09-26 lacks it; it holds AppRoleAssignment.ReadWrite.All, which is exactly the right to add it. | Entra admin center > Enterprise applications > the setup app > Permissions: remove RoleManagement.ReadWrite.Directory. |
| This computer | create | the result file named by -OutFile (JSON: ok, sender, steps, privileged grants); a transcript in <temp>\pim-manager-logs | this computer | -OutFile: how a deploy step reads the outcome. The transcript is the local record of what the run did (no secret values). | Delete the files. |
What it only reads
- Microsoft Graph: subscribedSkus (is there an Exchange Online plan beyond EXCHANGE_S_FOUNDATION), organization (the initial domain), the sending identities' and the engine's service principals and their app role assignments, the Microsoft Graph and Office 365 Exchange Online service principals (app role ids), users (the mailbox's UPN)
- Microsoft Graph (Support app sign-in only): the setup app's app role assignments and its active directory roles (roleAssignmentScheduleInstances, roleDefinitions)
- Exchange Online: Get-OrganizationConfig, Get-Mailbox, Get-ServicePrincipal, Get-ManagementScope, Get-ManagementRoleAssignment
- Azure Resource Manager (only with -TickJobName / -ManagerAppName): the Container Apps job / container app (its managed identity)
- PIM store (only with -SqlServerFqdn): pim.Settings, to read back what was stored
Parameters
| Parameter | Required | Default | What it does |
|---|---|---|---|
| -TenantId | yes | The tenant id. A sign-in that lands in another tenant is refused. | |
| -ManagedIdentityObjectId | no | Object ids of the managed identities that send (the engine job's and the Manager's), comma-separated. PIM Manager's Get Started fills them in. A service principal that is not a managed identity is refused. | |
| -SubscriptionId | no | With -ResourceGroup and -TickJobName / -ManagerAppName: read those apps' managed identities from Azure instead of typing their ids (added to -ManagedIdentityObjectId). | |
| -ResourceGroup | no | Resource group of the engine job / Manager app (see -SubscriptionId). | |
| -TickJobName | no | The engine's Container Apps job (e.g. ca-pim-tick); its managed identity is resolved by the same rule the engine uses. | |
| -ManagerAppName | no | The Manager's container app (e.g. ca-pim-manager); it sends the alert notices. | |
| -SqlServerFqdn | no | The environment's SQL server (<server>.database.windows.net). With it the sender is stored in pim.Settings; without it the script tells you to finish in PIM Manager. | |
| -SqlDatabase | no | PimPlatform | The store database. |
| -MailboxName | no | PIM-Engine | Your organisation's name for the sender mailbox (the part before the @). PIM Manager > Get Started > Mail sender step 1 fills it in. A mailbox that already exists at that address is used as it is. |
| -MailDomain | no | the tenant's initial (onmicrosoft.com) domain | Domain of the sender mailbox. Never guessed: a domain that does not receive mail gives a mailbox nobody can reply to. |
| -DisplayName | no | PIM Manager | Display name of the mailbox (what recipients see as the sender). |
| -ScopeName | no | <MailboxName>-SendScope | Name of the Exchange management scope. A scope that already restricts to this mailbox is kept under its own name. |
| -AssignmentNamePrefix | no | the mailbox name | Prefix of the Exchange role-assignment names (<prefix>-<short id>-MailSend). Assignments the sending identities already hold are kept under their own names. |
| -AllowThisIpTemporarily | no | false | Opt-in: when the PIM database refuses this computer (Azure SQL firewall), add a temporary firewall rule for exactly the refused IP, store the sender, and always remove the rule again. Needs the right to change the SQL server's firewall. Honours -WhatIf / -Confirm. Without it no firewall rule is changed. |
| -EngineAppId | no | The engine application (client) id: receives the scoped send right only when no managed identity sends (a non-hosted engine); always checked for a tenant-wide Mail.Send. | |
| -RemoveEngineSpnAssignment | no | false | Also remove the engine application's older scoped assignment (off by default: it is scoped to the same mailbox and widens nothing). |
| -AdminAppId | no | The Invardia Support app's client id, with -AdminSecret (unattended support / deploy). Leave it out to sign in in the browser as a person. | |
| -AdminSecret | no | The Invardia Support app's secret (the one allowed secret, framework 4.1a). Never printed. Never what a page or a document prints. | |
| -AdminCertThumbprint | no | Older callers only: a certificate of -AdminAppId instead of the secret. Not used by anything PIM Manager prints. | |
| -UseSignedInAccount | no | false | Use the signed-in session of this window: the Invardia Support app session opened by Invardia's connect script, otherwise your own browser sign-in. |
| -KeyVaultName | no | Older callers only: read the engine application id (secret Modern-AppId) from this vault when neither -EngineAppId nor a managed identity is given; needs -BootstrapAppId and -BootstrapThumbprint. | |
| -BootstrapAppId | no | Older callers only (see -KeyVaultName): the application that reads that vault. | |
| -BootstrapThumbprint | no | Older callers only (see -KeyVaultName): its certificate in the local machine store. | |
| -OutFile | no | Write the result as JSON here (for a deploy step). Under -WhatIf it is written only to a path in the temp folder. | |
| -TimeoutSeconds | no | 600 | How long to wait for Exchange administration to become usable. |
| -ExchangeAdminDuration | no | PT4H | Support app sign-in only: how long the time-bound Exchange Administrator role stays active (ISO 8601, PT15M..PT24H). |
| -ExchangeReadyMinutes | no | 75 | How long to wait for a just-created Exchange organization that still refuses role assignments (1-240). |
| -ExchangeReadyIntervalSeconds | no | 150 | How often to try again while waiting (10-900). |
| -OnProgress | no | For a calling deploy: a script block that receives one plain progress line per wait. |
Preview with -WhatIf
Signs in and reads everything above, then prints every change it would make: the mailbox to create, each Exchange registration, the scope and each scoped Application Mail.Send assignment, each tenant-wide Mail.Send it would remove, organization customization when needed, and the MailSender / MailMode it would store -- and makes none of them (no Exchange create or remove, no Graph write, no SQL connection). With the Support app sign-in, the app's own grants (Exchange.ManageAsApp, the time-bound Exchange Administrator) are previewed too; Exchange may then not be usable for the preview yet, which the run says ('steps [1]-[3] cannot be previewed; run without -WhatIf to apply').
Undo
In Exchange Online: Remove-ManagementRoleAssignment for each *-MailSend assignment the run reported, Remove-ManagementScope <the scope it reported> (default <MailboxName>-SendScope; PIM4EntraPS-Sender on an install from before 2.4.538), Remove-ServicePrincipal for each registered identity, then remove the shared mailbox. In PIM Manager: Settings > Mail & alerting (another sender or mode). With the Support app sign-in also remove Exchange.ManageAsApp and RoleManagement.ReadWrite.Directory from the setup app if no longer wanted (the Exchange Administrator role expires by itself). A removed tenant-wide Mail.Send is not restored on purpose.
Network
It connects to: login.microsoftonline.com (sign-in; the browser answers on a localhost port), graph.microsoft.com, outlook.office365.com (Exchange Online admin API, /adminapi/beta/<tenant>/InvokeCommand), management.azure.com (only with -TickJobName / -ManagerAppName, and with -AllowThisIpTemporarily), <server>.database.windows.net, TCP 1433 (only with -SqlServerFqdn), <vault>.vault.azure.net (older callers only, -KeyVaultName)
Download and run
Run these lines in PowerShell: the first downloads the script into the current folder, the second runs it.
Invoke-WebRequest https://invardia.com/support/pim/Initialize-PimMailSender.ps1 -OutFile Initialize-PimMailSender.ps1
.\Initialize-PimMailSender.ps1 -TenantId <TenantId>To check the download first, compare its SHA-256 with the published one:
SHA-256 7e8d17a5b1f0737584ef7c149664c8ced97ec5c9da7ed4d9beeeca94ee7bdef1
Checksums: SHA256SUMS.txt · Version 3.0.70
Before you run it, compare the file's SHA-256 with its line in SHA256SUMS.txt.
Every parameter: Get-Help .\Initialize-PimMailSender.ps1 -Full · Source: Initialize-PimMailSender.ps1
Every script, identity and permission for your security review: PIM Manager