M365Buddy
PowerShell · Microsoft Graph · Exchange Online

Email PowerShell Reports Using Microsoft Graph

Replace SMTP delivery with Microsoft Graph when the report must be emailed but an SMTP relay is not available.

First-time user? Start here. Do not edit your existing Site Inventory script yet. First, create a small separate PowerShell file that sends a test CSV using Microsoft Graph. Once that works, you can decide whether to add the same email section to your report script. This is safer and makes troubleshooting much easier.

What you will do

Use a separate PowerShell script to send an existing CSV file as an email attachment through Microsoft Graph. This lets you test email delivery without changing your report-generation script.

Download the PowerShell script

When should you use Microsoft Graph instead of SMTP?

Use Microsoft Graph when a PowerShell report must be emailed with a CSV attachment and your organization does not provide an approved SMTP relay. This article covers email delivery only; the report-generation script remains separate.

Beginner path: test email delivery in a separate script first

Think of your existing report script as two jobs: Job 1 creates the CSV; Job 2 emails that CSV. Microsoft Graph changes Job 2 only. To avoid breaking Job 1, test Job 2 in a new file first.

  1. Run your existing report and note the exact full path of the CSV it creates.
  2. Download Test-GraphCsvEmail-Fixed.ps1.
  3. Update the configuration values near the top of the downloaded script.
  4. Run the email script and confirm the email and attachment arrive before editing your main report script.
Important: The sample requires an Entra app configured for certificate authentication and Microsoft Graph Mail.Send Application permission with admin consent. The sender mailbox must be allowed by your organization’s Exchange app-access restrictions. This standalone sample stores the PFX password in a plaintext configuration variable to match the tested setup. Keep the file private and never commit it to a public repository. For production automation, prefer an approved secret store or a secure password prompt.

What replaces the SMTP section?

Do not put an Exchange Online hostname into $SmtpServer. Microsoft Graph uses an HTTPS API, not SMTP. In the Site Inventory script, find the email section after the CSV export and storage summary—usually headed # EMAIL or # SEND EMAIL. Replace only the SMTP delivery logic there with a Graph implementation. Do not change site collection discovery, report calculations, or the approved CSV columns.

Keep these parts unchanged: site collection discovery, report building, Export-Csv, storage calculations and output path. The change is only the email-delivery portion.

What do I put where?

Old SMTP settingGraph-based replacement
$SmtpServerRemove it. Graph sends through Exchange Online; you do not provide an SMTP server.
$FromUse the approved mailbox/user that is permitted to send the report.
$ToKeep the recipient addresses here or in the Graph request body.
SMTP authenticationReplace with the approved Entra ID app/certificate authentication described in the Entra setup article.

Exact Microsoft Graph permission required

For the unattended certificate/app-only pattern described here, the Microsoft Graph permission required by the POST /users/{sender}/sendMail endpoint is:

SettingValue
APIMicrosoft Graph
PermissionMail.Send
Permission typeApplication for unattended app-only/certificate authentication
Admin consentRequired for Application Mail.Send
EndpointPOST https://graph.microsoft.com/v1.0/users/{sender-UPN-or-id}/sendMail
Important scope note: Application Mail.Send can allow sending as users across the organization unless mailbox access is restricted. Ask your Exchange administrator to apply an approved mailbox-scoping control, such as Exchange Online Application RBAC, so the app can send only from the dedicated reporting mailbox. Grant no Mail.Read or Mail.ReadWrite permission unless another documented feature actually needs it.

If you use a signed-in-user/delegated flow instead of certificate app-only authentication, the permission type and sign-in requirements differ. Do not mix delegated and application permissions; this article's unattended example assumes Application Mail.Send.

The app also needs an authentication method supported by your implementation, such as a certificate registered on the Entra app. The certificate authenticates the app; it does not replace the Graph API permission. The mailbox in the /users/{sender}/sendMail URL must exist in Exchange Online and be permitted by the app's effective access configuration.

Related setup: Register an Entra ID App for PnP PowerShell Certificate Authentication. Follow the certificate steps, but add the Graph Mail.Send Application permission specifically for this mail-sending scenario.

Download and use the PowerShell email script

This standalone script sends an existing CSV file; it does not create the SharePoint report. Download it, open it in Notepad or VS Code, and replace the example values. The configuration excerpt below shows which settings you need to update.

Use the downloadable script as the source of truth. The full script includes configuration validation, certificate checks, CSV size checks, Graph connection, attachment creation and error handling. To keep this article easy to follow, the complete code is provided as a separate .ps1 download.

Configuration section in the script

Open the downloaded file and update the example values near the top. These settings tell the script which Entra app to use, which mailbox should send the email, who should receive it, and where the CSV is saved.

$TenantId              = 'YOUR-TENANT-ID'
$ClientId              = 'YOUR-APP-CLIENT-ID'
$PfxPath               = 'C:\Path\To\YourCertificate.pfx'
$SenderMailbox         = 'reports@contoso.com'
$Recipient             = 'you@contoso.com'
$CsvPath               = 'C:\Reports\SharePointSiteInventory.csv'
$EmailSubject          = 'SharePoint Site Inventory Report'
$EmailBody             = 'Please find the SharePoint CSV report attached.'

Replace every example value with your real environment details. Set $PfxPath to the full path of your PFX file. The script uses the $PfxPasswordPlainText value in its configuration section; it does not prompt for the password. Keep this file private because the password is stored as plaintext. For production use, replace this with your organization's approved secret-storage method. You do not need to know the thumbprint—the script loads the certificate directly from the PFX. The matching public certificate must already be uploaded under your Entra app's Certificates section.

Download Test-GraphCsvEmail-Fixed.ps1

View the script source on GitHub

CSV attachment limit: This script is intended for CSV files up to 3 MB. Larger reports need a Microsoft Graph upload-session implementation.

Part B — only after the test works. Open your existing Site Inventory .ps1 file in VS Code or Notepad. Find Send-MailMessage (or the existing email-send section). Keep the report generation and Export-Csv lines. Replace only the old email-send block with the working Graph send section, and update $CsvPath to the variable/path your report already uses. Do not paste the whole test script over your existing report script.

If you are not comfortable merging the tested email block, keep two scripts: one creates the CSV, and a second sends it. That is a valid first version, although you will need to run or schedule them in the correct order.

Save the script and run it normally

For your first test, run Test-GraphCsvEmail-Fixed.ps1 after the report CSV already exists. Once email delivery is confirmed, you can run or schedule the report-generation script and email script in the correct order:

# Open PowerShell in the folder where you saved the email script
.\Test-GraphCsvEmail-Fixed.ps1
Elevation: Administrator rights are not inherently required to run the script or call Microsoft Graph. Elevate only when a specific setup task requires it. If execution policy is controlled by Group Policy or Intune, do not bypass it.

Error: The script is not digitally signed

You may see an error similar to this when running the downloaded file:

File C:\Users\yourname\Downloads\Test-GraphCsvEmail-Fixed.ps1 cannot be loaded.
The file is not digitally signed. You cannot run this script on the current system.

This is a PowerShell execution-policy issue. It does not necessarily mean the script itself is broken. Follow the steps below in the same PowerShell session you use to run the script.

Step 1 — Check the current policy

Get-ExecutionPolicy -List

Check MachinePolicy, UserPolicy, Process, CurrentUser and LocalMachine. If MachinePolicy or UserPolicy is set, your organization may be enforcing the policy through Group Policy. Do not try to override an organization-managed policy; contact your IT/security administrator.

Step 2 — If the file was downloaded, unblock that file

If your organization allows unsigned local scripts and the effective policy is RemoteSigned, Windows may still mark a downloaded file as coming from the internet. Unblock only the script you have reviewed and trust:

Unblock-File -LiteralPath "$env:USERPROFILE\Downloads\Test-GraphCsvEmail-Fixed.ps1"

Then try running it again from its folder:

Set-Location "$env:USERPROFILE\Downloads"
.\Test-GraphCsvEmail-Fixed.ps1

Step 3 — If your effective policy is restricted, and you are allowed to change it

For a personally managed computer or a user account where your organization permits this setting, you can set RemoteSigned for your user account rather than changing the policy for every user on the computer:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

Read the confirmation prompt and proceed only if you are authorized to make this change. Then unblock the reviewed downloaded file as shown above and run it again. You generally do not need to run PowerShell as Administrator for a CurrentUser setting.

If the policy is AllSigned or managed by your organization: do not lower or bypass the policy. Use a script signed by your organization's trusted code-signing certificate, or ask your administrator for the approved method. Avoid setting Unrestricted or using a broad execution-policy bypass as a routine fix.

ParserError: Missing closing '}' near try {

If PowerShell reports a parser error such as Missing closing '}' in statement block or type definition at the line containing try {, the cause may be earlier in the file. Check the configuration section first—especially $PfxPasswordPlainText if you added the password directly to the script.

Apostrophes inside a single-quoted PowerShell string must be doubled. For example, this is invalid:

$PfxPasswordPlainText = 'abc'def123'

Use two single quotes to represent one apostrophe inside the value:

$PfxPasswordPlainText = 'abc''def123'

Do not share your actual PFX password in screenshots or support requests. If the password does not contain an apostrophe, inspect earlier lines for an unmatched quote, parenthesis, bracket, or brace. Make sure the complete script was copied and saved, then try running it again in a new PowerShell session.

Other common errors and fixes

CSV file not found

Run the report-generation script first, then verify that $CsvPath at the top of the email script matches the full path and file name of the CSV that was created.

Test-Path -LiteralPath 'C:\Reports\SharePointSiteInventory.csv'

PFX file not found or password/certificate error

Check that $PfxPath points to the actual PFX file, that the password is correct, and that the PFX contains the private key. The matching public certificate must be uploaded to the Entra app registration. Do not share the PFX or password in logs or screenshots.

Microsoft.Graph.Authentication module missing

If permitted by your organization's PowerShell Gallery policy, install the required module for your user and retry:

Install-Module Microsoft.Graph.Authentication -Scope CurrentUser

Graph returns 401 or 403 / Access denied

Check the tenant ID and client ID, certificate match, Microsoft Graph Mail.Send Application permission, admin consent, and whether the application is allowed to send from the configured sender mailbox. Ask the Entra/Exchange administrator to verify the app's effective mailbox scope.

Graph request succeeds but no email arrives

A 202 Accepted response means Graph accepted the send request; it does not guarantee final delivery. Check the recipient address, junk/quarantine, Exchange message trace, and any mail-flow restrictions.

When asking for help: Share the exact error text and the output of Get-ExecutionPolicy -List, but remove tenant identifiers, email addresses, tokens, certificate details and passwords first.
Attachment limit: This sample is for CSV files up to 3 MB. Larger reports need a Microsoft Graph upload-session implementation.

Test before scheduling

  1. Run the script manually in the same PowerShell version and identity that will be used for scheduled runs.
  2. Confirm the CSV is created at the expected path and contains the expected columns.
  3. Confirm Graph returns a successful response (typically 202 Accepted for sendMail).
  4. Confirm delivery in the recipient mailbox; a successful API response means the request was accepted, not that final delivery is guaranteed.
  5. Only then move the script into Task Scheduler, Azure Automation or another approved unattended process.

Security

  • Never commit a PFX password, private key, client secret or access token to a public repository. This sample stores the PFX password in plaintext locally; protect the file and use an approved secret store for production automation.
  • Use Application Mail.Send only for app-only mail sending; admin consent is required.
  • Restrict the app to the required reporting mailbox using your organization's supported Exchange application-access controls.
  • Use a dedicated reporting mailbox where appropriate.
  • Do not expose access tokens, email content or recipient details in transcript files or logs.

Related Article

Return to the SharePoint Site Inventory article to configure, run and troubleshoot the report.

M365Buddy · Microsoft 365 · PowerShell · Microsoft Graph · Exchange Online