Resetting Azure AD User Passwords with Microsoft Graph PowerShell

Not many things in IT easier than resetting a user’s password, right? Well, I found the Graph SDK PowerShell module’s Reset-MgUserAuthenticationMethodPassword to be pretty unintuitive. But as with many things, it’s not hard to use once you have an example. I couldn’t find one online, including the usual places (the “example” tab on the resetpassword api article & the cmdlet article), so here ya go:

Connect-MgGraph
Select-MgProfile -Name beta
$user = user1@example.com
$method = Get-MgUserAuthenticationPasswordMethod -UserId $user
  
Reset-MgUserAuthenticationMethodPassword -UserId $user -RequireChangeOnNextSignIn -AuthenticationMethodId $method.id -NewPassword "zQ7!Ra3MM6ha" 

But why use this cmdlet anyway?

Well, in addition to all the reasons tied to the deprecation of Azure AD Graph, and the potential demise of the msonline & azuread PowerShell modules, Microsoft’s Graph beta API currently stands on a short list of places to reset a password and have it also write back to the on-premises Active Directory.

Supported administrator operations

  • Any administrator self-service voluntary change password operation.
  • Any administrator self-service force change password operation, for example, password expiration.
  • Any administrator self-service password reset that originates from the password reset portal.
  • Any administrator-initiated end-user password reset from the Azure portal.
  • Any administrator-initiated end-user password reset from the Microsoft Graph API.

Using this API is especially handy if you have a multi-domain hybrid environment, where connecting to the requisite domain controllers is obnoxious, or in scenarios when you don’t have connectivity to the on-premises Active Directory in the first place.

What is an AuthenticationMethodId?

As you can see in the above code, we use Get-MgUserAuthenticationPasswordMethod to first learn the correct “AuthenticationMethodId”. These are Microsoft’s way to define the various types of authentication you can use with Azure AD, including FIDO2 keys, passwords, the Authenticator app, etc.

(Image source: https://docs.microsoft.com/en-us/azure/active-directory/authentication/concept-authentication-methods)

As of this writing, there are 9 methods to choose from. When resetting a password, you want passwordAuthenticationMethod, which returns a well-known GUID of 28c10230-6103-485e-b985-444c60001490. In fact, this means you may not need to use Get-MgUserAuthenticationPasswordMethod, making this approach faster:

Reset-MgUserAuthenticationMethodPassword -UserId $user -RequireChangeOnNextSignIn -AuthenticationMethodId "28c10230-6103-485e-b985-444c60001490" -NewPassword "zQ7!Ra3MM6ha"

Arguably, a cmdlet whose sole purpose is to reset passwords should have this baked in already, but I suppose the current implementation allows for future scalability.

Azure AD Sign-In Activity Report (Via Get-MgUser)

Another post on the Azure SDK cmdlet module, this time regarding the new SignInActivity attribute.

I am regularly being asked by customers to identify inactive Office 365 accounts. I’m well past the: “lol, you’re saying you don’t know who works here?” part of this problem and just follow up with a discussion on the criteria for “inactive”. The most in-depth analysis include looking at Exchange, Teams, and SharePoint usage, but I’m finding this new beta API attribute to be real handy.

Question-1: What is SignInActivity?

Answer-1: It’s an attribute exposed through the the Microsoft Graph Beta API (not Get-Msoluser or Get-AzureADUser). The How to detect inactive user accounts topic states it as such:

You detect inactive accounts by evaluating the lastSignInDateTime property exposed by the signInActivity resource type of the Microsoft Graph API. The lastSignInDateTime property shows the last time a user made a successful interactive sign-in to Azure AD.

Question-2: Ok, super, how do I use it with PowerShell?

Answer-2a: Graph of course! Here is a script that I wrote that worked for me:

https://github.com/Mike-Crowley/Public-Scripts/blob/main/Graph_SignInActivity_Report.ps1

Answer-2b: If you aren’t comfortable working with Microsoft Graph directly, or if you just don’t want the hassle, you can also use the Get-MgUser cmdlet from the aforementioned SDK module.

In either case, you’ll need to decide if you’re going to run this against the built-in app: “Microsoft Graph PowerShell” (simpler), or if you want to run it as an custom app registration (less simple, but better for automation). Once you’ve made that decision, assign the permissions.

If you’re using a custom app, use the Azure portal and search the Microsoft Graph API permissions list for:

  • AuditLog.Read.All
  • Organization.Read.All

If you’re using the Get-MgUser cmdlet, you can add these ‘scopes’ with -Scopes parameter of Connect-MgGraph.

Connect-MgGraph -Scopes AuditLog.Read.All, Organization.Read.All

In both of theses cases (app & delegated), you should be prompted once, and depending on how your tenant is configured, your administrator (you?) will need to give consent to the permissions.

NOTE: You don’t need to use the -scopes parameter each time you connect. This parameter is to prompt the permission consent page, which, once approved, is then recorded with the app.

After you’ve connected to Graph, using Connect-MgGraph, you’ll need to switch to the ‘beta’ API. The SignInActivity attribute isn’t exposed to the v1.0 API yet.

Select-MgProfile beta

From here, you can start using PowerShell like you’re used to. One thing to note when pulling the SignInActivity attribute is that you must use the user’s Azure AD GUID for the -UserId parameter, even if normally UPN would have sufficed. For example:

Get-MgUser -UserId 3a927ed7-d581-40ff-96a0-f5185014f0bf -Property SignInActivity | Select-Object -ExpandProperty SignInActivity | Format-List *


LastSignInDateTime   : 10/28/2021 12:10:41 AM
LastSignInRequestId  : c344b3c5-02fc-4fb4-b46f-7fe1624f5e91
AdditionalProperties : {[lastNonInteractiveSignInDateTime, 2021-10-28T02:02:41Z], [lastNonInteractiveSignInRequestId, 5ffc8a91-85b5-421c-8201-75e9fa40797d]}

Here is a more involved example, that demonstrates SignInActivity put together with some Exchange Online attributes (as I said, I am trying to find inactive users, and these additional fields can be helpful). It will create an Excel (.xlsx) file with the following column headers:

UserPrincipalName, CreatedDateTime, DisplayName, Mail, OnPremisesImmutableId, OnPremisesDistinguishedName, OnPremisesLastSyncDateTime, SignInSessionsValidFromDateTime, RefreshTokensValidFromDateTime, Id, PrimarySmtpAddress, ExternalEmailAddress, LastSignInDateTime, lastNonInteractiveSignInDateTime

Before you run the script, you’ll need to install the following modules:

Install-Module ExchangeOnlineManagement
Install-Module ImportExcel
Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Users

Finally, the script itself:

Connect-ExchangeOnline -UserPrincipalName <user>
Connect-MgGraph -TenantId <tenant>
Select-MgProfile beta

$MailUsers = Get-User -filter {recipienttypedetails -eq 'MailUser'} -ResultSize unlimited
$ReportUsers = $MailUsers | ForEach-Object {
    $MailUser = $_   
    
    $Counter ++
    $percentComplete = (($Counter / $MailUsers.count) * 100)
    Write-Progress -Activity "Getting MG Objects" -PercentComplete $percentComplete -Status "$percentComplete% Complete:"    
       
       #to do - organize properties better
    Get-MgUser -UserId $MailUser.ExternalDirectoryObjectId -ConsistencyLevel eventual -Property @(
        'UserPrincipalName'
        'SignInActivity'
        'CreatedDateTime'        
        'DisplayName'
        'Mail'
        'OnPremisesImmutableId'
        'OnPremisesDistinguishedName'
        'OnPremisesLastSyncDateTime'
        'SignInSessionsValidFromDateTime'
        'RefreshTokensValidFromDateTime'
        'id'
    ) | Select-Object @(
        'UserPrincipalName'        
        'CreatedDateTime'        
        'DisplayName'
        'Mail'
        'OnPremisesImmutableId'
        'OnPremisesDistinguishedName'
        'OnPremisesLastSyncDateTime'
        'SignInSessionsValidFromDateTime'
        'RefreshTokensValidFromDateTime'
        'id'        
        @{n='PrimarySmtpAddress'; e={$MailUser.PrimarySmtpAddress}}
        @{n='ExternalEmailAddress'; e={$MailUser.ExternalEmailAddress}}
        @{n='LastSignInDateTime'; e={[datetime]$_.SignInActivity.LastSignInDateTime}}
        @{n='lastNonInteractiveSignInDateTime'; e={[datetime]$_.SignInActivity.AdditionalProperties.lastNonInteractiveSignInDateTime}}           
    )
}

$Common_ExportExcelParams = @{
    # PassThru     = $true
    BoldTopRow   = $true
    AutoSize     = $true
    AutoFilter   = $true
    FreezeTopRow = $true
}

$FileDate = Get-Date -Format yyyyMMddTHHmmss

$ReportUsers | Sort-Object UserPrincipalName | Export-Excel @Common_ExportExcelParams -Path ("c:\tmp\" + $filedate + "_report.xlsx") -WorksheetName report

NOTE: Future updates to this script will be on GitHub:

https://github.com/Mike-Crowley/Public-Scripts/blob/main/MailUser-MgUser-Activity-Report.ps1

Sending Email with Send-MgUserMail (Microsoft Graph PowerShell)

As a follow-on post to the Send-MgUserMessage article, I’d like to discuss Send-MgUserMail.

  • What is this cmdlet?
  • How is Send-MgUserMail different from Send-MgUserMessage?
  • Did I waste a whole bunch of time with the other cmdlet and article only to find a better approach?

To answer the last question first: “The Time You Enjoy Wasting Is Not Wasted Time” 😊

At first glance, it’s silly that there are two brand new cmdlets that basically do the same thing. Though when you look at the underlying Graph namespaces they interact with, you’ll realize this is Graph’s fault for publishing two namespaces! The SDK cmdlet module is just trying to make Graph easier, so we can’t really blame PowerShell.

Send-MgUserMailSend-MgUserMessage
• Uses the Graph sendmail namespace

• Can send an email in a single step

• Requires Mail.Send permission
• Uses the Graph send namespace

• Sends a previously created message

• Could send emails created by an outside process or user

• Can reply and forward messages in a mailbox

• Requires Mail.Send permission, though you’ll possibly be creating the message itself with New-MgUserMessage, which requires Mail.ReadWrite (arguably too much power in most cases)

As we begin to understand by the above table, Send-MgUserMail requires fewer permissions and requires fewer moving parts*, so I expect to be using that instead of Send-MgUserMessage in the future.

*You’re about to see, the “moving parts” may not actually be fewer, since its more complex to use Send-MgUserMail, but I’ve made some functions below that you are welcome to include in your projects to simplify some of the more complex data structures.

To get started, be sure you have the correct modules on your computer and that the app you are using has the correct permissions. In my case, I am using application-based authentication as I mentioned in the previous post. You could also use delegated permissions for this task.

Install the modules:

Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Users.Actions

Next, assign your custom application (or the built-in “Microsoft Graph PowerShell” application) the sendmail permission. You can do this with the -Scopes parameter of Connect-MgGraph, or on the app directly, in the Azure AD Portal. I realize that this is a loaded statement, but Graph API permissions are a topic that is beyond the scope (heh) of this article.

If you need help, you can start with this article for guidance:

Determine required permission scopes

Once permissions are set, you can connect to Graph and start sending email. Unfortunately, Send-MgUserMail‘s main parameter -BodyParameter takes the following object type:

IPathsFh5OjtUsersUserIdMicrosoftGraphSendmailPostRequestbodyContentApplicationJsonSchema

No, I’m not joking. That’s what the documentation says! Needless to say, figuring out what this meant took some tinkering. In the end, it seems that these Mg* cmdlets love nested hash tables. I wrote a couple of functions to simplify the creation of two of them. the rest aren’t functions, but you can follow along to see the PowerShell interpretation of their structure.

This one takes a list ([array]) of email addresses and converts it into the IMicrosoftGraphRecipient object type:

Function ConvertTo-IMicrosoftGraphRecipient {
    [cmdletbinding()]
    Param(
        [array]$smtpAddresses        
    )
    foreach ($address in $smtpAddresses) {
        @{
            emailAddress = @{address = $address}
        }    
    }    
}

Note: Graph’s toRecipients property requires an array of hash tables, even if you have only one recipient. I’ve addressed this in the script with [array]$toRecipients, because I had trouble making the function output an array in all cases.

This next function uploads attachments, first by converting to Base64, and then nesting them in the IMicrosoftGraphAttachment format.

Function ConvertTo-IMicrosoftGraphAttachment {
    [cmdletbinding()]
    Param(
        [string]$UploadDirectory        
    )
    $DirectoryContents = Get-ChildItem $UploadDirectory -Attributes !Directory -Recurse
    foreach ($File in $DirectoryContents) {
        $EncodedAttachment = [convert]::ToBase64String((Get-Content $File.FullName -Encoding byte))
        @{
            "@odata.type"= "#microsoft.graph.fileAttachment"
            name = ($File.FullName -split '\\')[-1]
            contentBytes = $EncodedAttachment
        }   
    }    
}

As I mentioned on the last post, if your attachment size is above 3MB, you should check out this function written by Glen Scales:

Sending a Message with a Large attachment using the Microsoft Graph and Powershell

Finally, here is the script itself. Hopefully, if you’ve followed along thus far, the rest doesn’t require much interpretation.

<#
    Script for sending email with send-mgusermail
    
    Ref:

    https://mikecrowley.us/2021/10/27/sending-email-with-send-mgusermail-microsoft-graph-powershell
    https://docs.microsoft.com/en-us/graph/api/user-sendmail
    https://docs.microsoft.com/en-us/powershell/module/microsoft.graph.users.actions/send-mgusermail

#>

#region 1: Setup

    $emailRecipients   = @(
        'user1@domain.com'
        'user2@domain.biz'
    )
    $emailSender  = 'me@domain.info'

    $emailSubject = "Sample Email | " + (Get-Date -UFormat %e%b%Y)

    $MgConnectParams = @{
        ClientId              = '<your app>'
        TenantId              = '<your tenant id>'
        CertificateThumbprint = '<your thumbprint>'
    }

    Function ConvertTo-IMicrosoftGraphRecipient {
        [cmdletbinding()]
        Param(
            [array]$SmtpAddresses        
        )
        foreach ($address in $SmtpAddresses) {
            @{
                emailAddress = @{address = $address}
            }    
        }    
    }

    Function ConvertTo-IMicrosoftGraphAttachment {
        [cmdletbinding()]
        Param(
            [string]$UploadDirectory        
        )
        $directoryContents = Get-ChildItem $UploadDirectory -Attributes !Directory -Recurse
        foreach ($file in $directoryContents) {
            $encodedAttachment = [convert]::ToBase64String((Get-Content $file.FullName -Encoding byte))
            @{
                "@odata.type"= "#microsoft.graph.fileAttachment"
                name = ($File.FullName -split '\\')[-1]
                contentBytes = $encodedAttachment
            }   
        }    
    }

#endregion 1


#region 2: Run

    [array]$toRecipients = ConvertTo-IMicrosoftGraphRecipient -SmtpAddresses $emailRecipients 

    $attachments = ConvertTo-IMicrosoftGraphAttachment -UploadDirectory C:\tmp

    $emailBody  = @{
        ContentType = 'html'
        Content = Get-Content 'C:\tmp\HelloWorld.htm'    
    }

    Connect-Graph @MgConnectParams
    Select-MgProfile v1.0
    
    $body += @{subject      = $emailSubject}
    $body += @{toRecipients = $toRecipients}    
    $body += @{attachments  = $attachments}
    $body += @{body         = $emailBody}

    $bodyParameter += @{'message'         = $body}
    $bodyParameter += @{'saveToSentItems' = $false}

    Send-MgUserMail -UserId $emailSender -BodyParameter $bodyParameter

#endregion 2

Note: Future updates to this script will be on GitHub:

https://github.com/Mike-Crowley/Public-Scripts/blob/main/MgUserMail.ps1

Using Send-MgUserMessage to send Email (with Attachments)

EDIT 25Oct2021:

This article discusses Send-MgUserMessage, though I’ve since realized Send-MgUserMail might be a more efficient option in many cases.

EDIT 27Oct2021:

I have written a follow on-article for Send-MgUserMail. Check it out when you’re done with this one.

Original Post:

I’ve been incorporating the new “SDK” cmdlets into my work lately, and though I’m not entirely convinced using them is any easier than just working with Graph directly, I wanted to update an old script that used Send-Mailmessage, and managing tokens and HTTP headers felt like overkill. Speaking of Send-MailMessage, you may have noticed this harshly worded message on its help page:

Warning

The Send-MailMessage cmdlet is obsolete. This cmdlet does not guarantee secure connections to SMTP servers. While there is no immediate replacement available in PowerShell, we recommend you do not use Send-MailMessage. For more information, see Platform Compatibility note DE0005.

Send-MgUserMessage is arguably the most direct replacement (see 25Oct2021 edit), but it is more difficult to use, due to the fact we need to build several custom objects, whereas with Send-MailMessage, the parameters did the work for us.

In my scenario, I also wanted this task to run as an application, defined in Azure Active Directory, using application permissions – not delegated through my personal account. Additionally, I wanted to use a certificate to authenticate to Azure instead of managing a “client secret”. I’m choosing not to walk through those prerequisites here, because they are already well documented:

  1. Use app-only authentication with the Microsoft Graph PowerShell SDK
  2. Create a self-signed public certificate to authenticate your application

To get started, install the following PowerShell Modules. I should also point out that if you already have these installed, be sure to upgrade to the latest version (1.7.0 at time of writing), since there have been significant changes in how this cmdlet works.

Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Mail
Install-Module Microsoft.Graph.Users.Actions

Once you’ve got those, you can start sending email. Here is a “simple” example that uses a HTML body, as well as uploads a small attachment (attachments above 3MB are more complicated). In the below example, I’m using a sample book1.xlsx file.

#requires -modules Microsoft.Graph.Authentication,Microsoft.Graph.Mail,Microsoft.Graph.Users

#connect with CBA
# https://docs.microsoft.com/en-us/graph/powershell/app-only?tabs=azure-portal
# https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-self-signed-certificate

$ConnectParams = @{
    ClientId = '84af9676-<not real>4194bb6d9e3'
    TenantId = 'ef508849-9d0<not real>5d800549'
    CertificateThumbprint = '96546bf89<not real>67332c703e15123b07'
}
Connect-Graph @ConnectParams

#recipients
$EmailAddress  = @{address = 'user1@recipientDomain.com'} # https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
$Recipient = @{EmailAddress = $EmailAddress}  # https://docs.microsoft.com/en-us/graph/api/resources/emailaddress?view=graph-rest-1.0

#Body
$body  = @{
    content = '<html>hello <b>world</b></html>'
    ContentType = 'html'
}

#Attachments
# If over ~3MB: https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=http
$AttachmentPath = 'C:\tmp\Book1.xlsx'
$EncodedAttachment = [convert]::ToBase64String((Get-Content $AttachmentPath -Encoding byte)) 
$Attachment = @{
    "@odata.type"= "#microsoft.graph.fileAttachment"
    name = ($AttachmentPath -split '\\')[-1]
    contentBytes = $EncodedAttachment
}

#Create Message (goes to drafts)
$Message = New-MgUserMessage -UserId me@mydomain.com -Body $body -ToRecipients $Recipient -Subject Subject1 -Attachments $Attachment

#Send Message
Send-MgUserMessage -UserId me@mydomain.com -MessageId $Message.Id
 

#end

Once you’re done, the message should look like this in the Sent Items folder of whoever you used to do the sending: