Sending Email with Powershell using the Microsoft Graph API

Sending email from a server that isn’t one of your main email servers these days has become a royal pain in the ass. Even more so if you don’t want it stuck in your own SPAM filter. Doubly so, if you using Microsoft’s Server products.

Recently, I’ve found myself managing a large number of Microsoft Servers, while it’s not my dream job, it is pretty OK and I like the people I work with. One problem I’ve had is that a lot of remote monitoring software doesn’t really support the kinds of things that I need. For instance, if I want to create CSV file that contains all the local users on the computers and load it into a SQL database, so that I can document and take action on local accounts that should/should not be there, I’m basically shit out of luck. To make the matters worse, the networks that the servers reside on are completely disparate and disconnected. Email is perfect for handling the connectivity, and creating a bot to pull those emails and load the data from the CSV files into a SQL database is easy enough.

I have access to a Microsoft 365 setup, so I started looking into how I was going to accomplish sending email. Microsoft’s Server products don’t really have any support for TLS or encryption, or even authentication, IF they support sending email at all. So, using the built in options is basically worthless without setting up a mail server and managing it myself. That isn’t even an option. The task I want to accomplish is running a PowerShell script, so I figured that might prove to be a good route to investigate. There really aren’t any built in options, once again, that seem to work well with what I have been provided. HOWEVER, I did stumble upon a solution using the Microsoft Graph API.

The Solution

What I ended up doing was creating a new App Registration in Microsoft Entra (I REALLY hate that name by the way, and that fact that Azure Active Directory was rebranded) and granting the App Registration the ability to send emails using the Graph API. Then, I had to generate a certificate on the server, I ended up generating a self-signed certificate using the following command:

New-SelfSignedCertificate -DnsName servername.whatever.local `
  -CertStoreLocation 'Cert:\LocalMachine\My' `
  -FriendlyName 'Email_Certificate' -KeySpec Signature `
  -NotAfter (Get-Date).AddYears(1)

After generating the certificate, I uploaded that to certificate to Microsoft Entra. That should take care of all the authorization and authentication stuff, so then I had to install the Microsoft Graph PowerShell Module using:

Install-Module Microsoft.Graph -Scope AllUsers

Now, all I needed was the script. Here it is in all of it’s glory.

# Only import these two modules, otherwise, there's a chance the script won't
# run on older versions of PowerShell
Import-Module Microsoft.Graph.Authentiation
Import-Module Microsoft.Graph.Users.Actions

$ClientId = '' # Taken from the App Registration
$TenantId = ''
$CertThumbprint = '' # This is the thumbprint from the certificate that I
                     # generated earlier

# Build the message
$Message = @{
  Subject = '[Automated Email] Local User Report';
  ToRecipients = @(
    @{
      emailAddress = @{ address = 'me@grafton.me' };
    }
  );
  Body = @{
    contentType = "text";
    content = "Some text that you want to send goes here";
  };
  # This is optional, use if you want to include some attachments
  Attachments = @(
    @{
        "@odata.type" = "#microsoft.graph.fileAttachment";
        ContentType = "text/csv";
        Name = "local_user_audit.csv";
        ContentBytes = ([convert]::ToBase64String("Some content"));
    }
  )
}

# Connect and send the message
Connect-MgGraph -ClientId $ClientId -TenantId $TenantId `
  -CertificateThumbprint $CertThumbprint -NoWelcome
Send-MgUserMail -UserId server@grafton.me -Message $Message

Conclusion

This process is a bit of pain in the ass, but once you have it set up and working, it’s really not TOO bad. I’d rather see a better built in solution, preferably one that doesn’t require access to Microsoft 365, but when life gives you Microsoft products, I guess you try to make the best of it.