{"id":730,"date":"2022-08-04T12:33:37","date_gmt":"2022-08-04T19:33:37","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/powershell-community\/?p=730"},"modified":"2022-09-01T00:51:22","modified_gmt":"2022-09-01T07:51:22","slug":"password-expiry-notification-using-teams-and-graph-api","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell-community\/password-expiry-notification-using-teams-and-graph-api\/","title":{"rendered":"Password Expiry Notification Using Teams and Graph API"},"content":{"rendered":"<p><strong>Q<\/strong>: How do I send a password expiration notification to a user using Teams chat?<\/p>\n<p><strong>A<\/strong>: Not only can you send the password notification, but you can use PowerShell with the Teams Graph API to send any message to a Teams user.<\/p>\n<p>But first, let&#8217;s talk about Graph API, so we are all on the same page.<\/p>\n<h2>What is the Graph API?<\/h2>\n<p>Microsoft had a different endpoint for each cloud service. This makes it hard for the admin as it needs knowledge of each endpoint API URI and manages the authentication and authorization separately. So Microsoft came up with the Graph API as a one-stop shop to manage all the cloud services using a single endpoint, authentication, and a scoped authorization.<\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/graph\/overview\">Microsoft Graph<\/a> is the gateway to read data from a wide range of Microsoft services, including Azure Active Directory, Teams, OneDrive\u2026etc. You can get the data using a single module and a single interface.<\/p>\n<p>Microsoft Graph API supports modern authentication protocols such as access token, certificate, and browser authentication.<\/p>\n<blockquote>\n<p>You can read more about the Graph API available endpoint from the <a href=\"https:\/\/docs.microsoft.com\/graph\/api\/overview\">Microsoft Graph REST API Endpoint v1.0 Reference<\/a>.<\/p>\n<\/blockquote>\n<h2>Downloading Graph API PowerShell Module<\/h2>\n<p>You can download Microsoft Graph PowerShell Module by running the following command.<\/p>\n<pre><code class=\"language-powershell\">Install-Module -Name Microsoft.Graph<\/code><\/pre>\n<p>Microsoft PowerShell Graph Module SDK is cross-platform and supports Windows, macOS, and Linux.<\/p>\n<h2>Connecting To Graph API Using PowerShell<\/h2>\n<p>Unlike other modules such as the AzureAD, ExchangeOnline, etc. where the admin needs only to connect with the right credentials and have full access, the graph has a different approach.<\/p>\n<p>When connecting to the Graph API, you need to specify the scope of permissions or, let&#8217;s say, declare the required permissions that are used during the script execution. The script fails if it tries to perform an action that was not in the scope.<\/p>\n<p>For example, if the script needs to read all user data in the azure directory, it&#8217;s not enough just to connect to read all the data, even if the user credentials are for the global admin for the tenant. Instead, you must declare and specify that you will connect and use the <code>User.Read.All<\/code> permission.<\/p>\n<p>To connect to Graph API with the required scope, use the following:<\/p>\n<pre><code class=\"language-powershell-console\">PS&gt; $Scope=@('User.Read.All','User.ReadWrite.All')\nPS&gt; Connect-MgGraph -Scopes $Scope\n\nWelcome To Microsoft Graph!<\/code><\/pre>\n<p>To check which identity is used during the connecting with the Graph API along with the used scope, use the <code>Get-MgContext<\/code> cmdlet.<\/p>\n<pre><code class=\"language-powershell-console\">PS&gt; Get-MgContext\n\nClientId              : 2ee82eec-204b-204b-204b-e55eec26bf5a\nTenantId              : 14d82eec-4d5a-4d5a-4d5a-26bf5ae55eec\nCertificateThumbprint : \nScopes                : {User.Read.All, User.ReadWrite.All\u2026}\nAuthType              : Delegated\nAuthProviderType      : InteractiveAuthenticationProvider\nCertificateName       : \nAccount               : farismalaeb@contoso.com\nAppName               : Microsoft Graph PowerShell\nContextScope          : CurrentUser\nCertificate           : \nPSHostVersion         : 2022.6.3\nClientTimeout         : 00:05:00<\/code><\/pre>\n<p>To add additional permission to the scope, rerun <code>Connect-MgGraph<\/code>, setting the new scope with the <strong>Scope<\/strong> parameter and connect again. There is no need to specify the same scope already provided.<\/p>\n<p>So let&#8217;s assume that an admin connected to the Graph API using the following scope:<\/p>\n<pre><code class=\"language-powershell\">$Scope=@('User.Read.All')\nConnect-MgGraph -Scopes $Scope<\/code><\/pre>\n<p>Later on, the admin wants to add the <code>User.ReadWrite.All<\/code> permission, all the admin needs to do is run the <code>Connect-MgGraph<\/code> and set the new scope<\/p>\n<pre><code class=\"language-powershell\">$Scope=@('User.ReadWrite.All')\nConnect-MgGraph -Scopes $Scope<\/code><\/pre>\n<p>The new scope permission adds to the current one.<\/p>\n<p>A good starting point in finding out the required permission to execute a certain cmdlet is <a href=\"https:\/\/docs.microsoft.com\/powershell\/microsoftgraph\/find-mg-graph-command\">Find-MgGraphcommand<\/a> and <a href=\"https:\/\/developer.microsoft.com\/graph\/graph-explorer\">Graph Explorer<\/a>.<\/p>\n<h2>Password Expiry Notification Using Teams and Graph API<\/h2>\n<h3>Getting Password Expiration information<\/h3>\n<p>To make this tutorial more fun, let&#8217;s make a scenario. Consider a company named Contoso.com. Contoso.com have an on-premise AD syncing with AAD. The Password Expiration policy is set to 3 months.<\/p>\n<p>The administrator is looking for a way to send the users a notification through Microsoft Teams chat one week before the password expires, so, how to start?<\/p>\n<p>To get the password expiration for users, use the following code. This code reads the <strong>Name<\/strong>, <strong>EmailAddress<\/strong>, <strong>UserPrincipalName<\/strong> and <a href=\"https:\/\/docs.microsoft.com\/openspecs\/windows_protocols\/ms-adts\/f9e9b7e2-c7ac-4db6-ba38-71d9696981e9\"><strong>msDS-UserPasswordExpiryTimeComputed<\/strong><\/a>. The <strong>msDS-UserPasswordExpiryTimeComputed<\/strong> property notes when the user&#8217;s password expires, check it below.<\/p>\n<pre><code class=\"language-powershell\">$DaysToSendWarning = (Get-Date).AddDays(7).ToLongDateString()\n\n$QueryParameters = @{\n    Filter     = {\n        Enabled -eq $true -and\n        PasswordNeverExpires -eq $false -and\n        PasswordLastSet -gt 0\n    }\n    Properties = @(\n        'Name'\n        'EmailAddress'\n        'msDS-UserPasswordExpiryTimeComputed'\n        'UserPrincipalName'\n    )\n}\n\n$SelectionProperties = @(\n    \"Name\"\n    \"UserPrincipalName\"\n    \"EmailAddress\"\n    @{\n        Name = 'PasswordExpiry'\n        Expression = {\n            [datetime]::FromFileTime($_.\"msDS-UserPasswordExpiryTimeComputed\").ToLongDateString()\n        }\n    }\n)\n\n$Users = Get-ADUser @QueryParameters | Select-Object -Property $SelectionProperties<\/code><\/pre>\n<p>To see the result, call the <code>$Users<\/code> variable.<\/p>\n<pre><code class=\"language-powershell-console\">Name              UserPrincipalName          EmailAddress            PasswordExpiry\n----              -----------------          ------------           --------------\ntest               test@contoso.com          test1@contoso.com      Monday, August 15, 2022\nUser1              User1@contoso.com         User1@contoso.com      Tuesday, October 18, 2022\nUser2              User2@contoso.com         User2@contoso.com      Sunday, August 7, 2022\n.\n.\n.\nOutput trimmed<\/code><\/pre>\n<p>Later on a <code>ForEach-Object<\/code> loop goes through the users, and if any user <strong>msDS-UserPasswordExpiryTimeComputed<\/strong> matches the date in the <code>$DaysToSendWarning<\/code>, then the script sends them the chat message. More to come in the full script.<\/p>\n<h2>Using Microsoft Graph to Create a Teams Chat Session<\/h2>\n<p><a href=\"https:\/\/docs.microsoft.com\/graph\/\">Microsoft Graph API documentation<\/a> is the best start with anything related to Graph API.<\/p>\n<p>We need to connect to the Graph API and use the following permission in the scope.<\/p>\n<pre><code class=\"language-powershell\">$Scope=@('Chat.Create','Chat.ReadWrite','User.Read','User.Read.All') \nConnect-MgGraph -Scopes $Scope<\/code><\/pre>\n<p>First, start a chat session. The chat session contains a list of all the parties involved in the chat session. Also, it will provide a unique ID representing the communication between all the parties involved in the chat.<\/p>\n<pre><code class=\"language-powershell-console\">$NewChatIDParam = @{\n    ChatType = \"oneOnOne\"\n    Members  = @(\n        @{\n            \"@odata.type\" = \"#microsoft.graph.aadUserConversationMember\"\n            Roles = @(\n                \"owner\"\n            )\n            \"User@odata.bind\" = \"https:\/\/graph.microsoft.com\/v1.0\/users('*XXXXXXXXXX*')\"\n        }\n        @{\n            \"@odata.type\" = \"#microsoft.graph.aadUserConversationMember\"\n            Roles = @(\n                \"owner\"\n            )\n            \"User@odata.bind\" = \"https:\/\/graph.microsoft.com\/v1.0\/users('*XXXXXXXXXX*')\"\n        }\n    )\n}\n\n$ChatSessionID = New-MgChat -BodyParameter $NewChatIDParam\n\nPS&gt; $ChatSessionID.id\n19:b980153c-9129-9129-9129-fb57a348d4d3_eafa5e65-9129-4e69-4e69-19f3fe6@unq.gbl.spaces\n<\/code><\/pre>\n<p>Replace each <code>*XXXXXXXXXX*<\/code> with a user ID who is participating in the chat. It doesn&#8217;t matter if the sender or the recipient is first. it&#8217;s a two-way communication bridge. But that the caller user id must be one of the members specified in the request body.<\/p>\n<p>You can get the user id by running <code>(Get-MgUser -userID user1@contoso.com).id<\/code><\/p>\n<p>Read more about the parameters in the chat session from the <a href=\"https:\/\/docs.microsoft.com\/graph\/api\/chat-post?view=graph-rest-beta&amp;tabs=http\">Create chat<\/a>.<\/p>\n<p>Executing the example above returns a long ID. The chat session ID must be used between these parties specified in the chat body.<\/p>\n<p>Running the example above again and again returns the same chat session id if the chat session already exists. So no need to go through all the chat sessions to seek a certain chat conversation id.<\/p>\n<h2>Using Microsoft Graph to Send a Teams Chat Message<\/h2>\n<p>As for now, we have the chat session id. We can send a message with a line of code.<\/p>\n<pre><code class=\"language-powershell\">New-MgChatMessage -ChatId $ChatSessionID.id -Body  @{Content ='&lt;strong&gt;Hello, I am PowerShell&lt;\/strong&gt;';ContentType='html'}<\/code><\/pre>\n<p>Looking to know more about <code>New-MgChatMessage<\/code> parameters, take a look at <a href=\"https:\/\/docs.microsoft.com\/graph\/api\/chatmessage-post?view=graph-rest-beta&amp;tabs=http\">Send chatMessage in channel or a chat<\/a> and also <a href=\"https:\/\/www.powershellcenter.com\/2022\/07\/15\/new-mgchat\/\">Send HTML Teams Message Using PowerShell Graph<\/a>.<\/p>\n<h2>Full Script to send notification<\/h2>\n<p>So now we know the basics. Let&#8217;s build it all together.<\/p>\n<p>There is no need to modify anything except the <strong>$DaysToSendWarning<\/strong> variable. Set it to the number of days you want. Everything else should be fine with no issues.<\/p>\n<p>You might need to consent and accept the new permission after connecting using the <code>Connect-MgGraph<\/code>.<\/p>\n<pre><code class=\"language-powershell\">Import-Module ActiveDirectory\nImport-Module Microsoft.Graph.Teams\n\n$Scope = @(\n    'Chat.Create'\n    'Chat.ReadWrite'\n    'User.Read'\n    'User.Read.All'\n)\nConnect-MgGraph -Scopes $Scope\n\n$DaysToSendWarning = 7\n\n#Find accounts that are enabled and have expiring passwords\n$QueryParameters = @{\n    Filter     = {\n        Enabled -eq $true -and\n        PasswordNeverExpires -eq $false -and\n        PasswordLastSet -gt 0\n    }\n    Properties = @(\n        'Name'\n        'EmailAddress'\n        'msDS-UserPasswordExpiryTimeComputed'\n        'UserPrincipalName'\n    )\n\n}\n\n$SelectionProperties = @(\n    \"Name\"\n    \"UserPrincipalName\"\n    \"EmailAddress\"\n    @{\n        Name = 'PasswordExpiry'\n        Expression = {\n            [datetime]::FromFileTime($_.\"msDS-UserPasswordExpiryTimeComputed\").ToLongDateString()\n        }\n    }\n)\n\n$Users = Get-ADUser @QueryParameters | Select-Object -Property $SelectionProperties\n\nforeach ($User in $Users) {\n    $RecpID = Get-MgUser -UserId $User.UserPrincipalName -ErrorAction Stop\n    if ($User.PasswordExpiry -eq $DaysToSendWarning) {\n        $NewChatIDParam = @{\n            ChatType = \"oneOnOne\"\n            Members = @(\n                @{\n                    \"@odata.type\" = \"#microsoft.graph.aadUserConversationMember\"\n                    Roles = @(\n                        \"owner\"\n                    )\n                    \"User@odata.bind\" = \"https:\/\/graph.microsoft.com\/v1.0\/users('\"+(get-mguser -userid (Get-MgContext).account).id +\"')\"\n                }\n                @{\n                    \"@odata.type\" = \"#microsoft.graph.aadUserConversationMember\"\n                    Roles = @(\n                        \"owner\"\n                    )\n                    \"User@odata.bind\" = \"https:\/\/graph.microsoft.com\/v1.0\/users('\"+$RecpID.id +\"')\"\n                }\n            )\n        }\n\n        $ChatSessionID = New-MgChat -BodyParameter $NewChatIDParam\n\n        Write-Host \"Sending Message to $($RecpID.Mail)\" -ForegroundColor Green\n\n        try {\n            #### Sending The Message\n            $Body = @{\n                ContentType = 'html'\n                Content = @\"\n                Hello $($RecpID.DisplayName)&lt;br&gt;\n                Your password will expire in $($DaysToSendWarning), Please follow &lt;Strong&gt;&lt;a href='www.office.com'&gt;the instruction here to update it&lt;\/a&gt; &lt;\/Strong&gt; &lt;BR&gt;\n                Thanks for your attention\n\"@\n              }\n\n        New-MgChatMessage -ChatId $ChatSessionID.ID -Body $Body -Importance Urgent\n        } catch{\n            Write-Host $_.Exception.Message\n        }\n    }\n}<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>This post shows how to send a basic HTML message, but there is still a lot. Take a look at the Graph API documentation to know how to receive, read and have a wider control of not only Teams but also other Microsoft cloud services.<\/p>\n<p>It&#8217;s ok to feel a bit lost about all these hashtables, arrays, and new things and the structure. No need to memorize it. just open the Graph API documentation, guiding you straight to the point.<\/p>\n<p>Lets me know if you try it; how did it go \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post&#8217;s intent is to show how to use Graph API through PowerShell to send a Teams message.<\/p>\n","protected":false},"author":53227,"featured_media":77,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[13],"tags":[75,3,76],"class_list":["post-730","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-graphapi","tag-powershell","tag-teams"],"acf":[],"blog_post_summary":"<p>This post&#8217;s intent is to show how to use Graph API through PowerShell to send a Teams message.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/730","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/users\/53227"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/comments?post=730"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/730\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media\/77"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media?parent=730"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/categories?post=730"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/tags?post=730"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}