{"id":3230,"date":"2024-08-31T17:10:22","date_gmt":"2024-08-31T16:10:22","guid":{"rendered":"https:\/\/mortenknudsen.net\/?p=3230"},"modified":"2024-09-18T10:36:46","modified_gmt":"2024-09-18T09:36:46","slug":"detect-impact-mfa-enforcement","status":"publish","type":"post","link":"https:\/\/mortenknudsen.net\/?p=3230","title":{"rendered":"Detect Impact MFA Enforcement"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">You may have noticed that Microsoft will enforce MFA requirement per <strong>October 15, 2024<\/strong> for Azure\/Entra\/Intune. If this is new to you &#8211; or you want to find out (potential) impacted accounts, keep reading \ud83d\ude42<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"355\" height=\"146\" src=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-5.png\" alt=\"\" class=\"wp-image-3257\" srcset=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-5.png 355w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-5-300x123.png 300w\" sizes=\"auto, (max-width: 355px) 100vw, 355px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Content<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"#background\" data-type=\"internal\" data-id=\"#background\">Background<\/a><\/li>\n\n\n\n<li><a href=\"#protect\" data-type=\"internal\" data-id=\"#protect\">Protect identities and secrets &#8211; Impact Oct 15, 2024<\/a><\/li>\n\n\n\n<li><a href=\"#impact\" data-type=\"internal\" data-id=\"#impact\">Impacted Accounts (official statement Microsoft)<\/a><\/li>\n\n\n\n<li><a href=\"#find\" data-type=\"internal\" data-id=\"#find\">Find potential accounts with issues (script)<\/a><\/li>\n\n\n\n<li><a href=\"#correlation\">Correlation of data (script)<\/a><\/li>\n\n\n\n<li><a href=\"#output\" data-type=\"internal\" data-id=\"#output\">Output (script)<\/a><\/li>\n\n\n\n<li><a href=\"#script\">Script<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"background\">Background<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">MFA should be something you are familiar with or have enforced in you enterprise, but unfortunately there are still many tenants or configurations, where security improvements are needed &#8211; maybe due to exceptions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In November 2023, Microsoft launched the&nbsp;<a href=\"https:\/\/www.microsoft.com\/en-us\/microsoft-cloud\/resources\/built-in-security\">Secure Future Initiative<\/a>&nbsp;(SFI) to prepare for the increasing scale of cyberattacks. SFI brings together every part of Microsoft to advance cybersecurity protection across the whole stack of existing and future products. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is important to understand that SFI is not just <strong>technology<\/strong> but also includes <strong>processes<\/strong> and <strong>people skills<\/strong>; ultimately <strong>establishing a<\/strong> <strong>security culture<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"870\" height=\"481\" src=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-3.png\" alt=\"\" class=\"wp-image-3235\" srcset=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-3.png 870w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-3-300x166.png 300w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-3-768x425.png 768w\" sizes=\"auto, (max-width: 870px) 100vw, 870px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As part of Microsoft&#8217;s Secure Future Initiative (SFI), a number of global security changes will happen, which will impact us all. Each of the initiatives are detailed <a href=\"https:\/\/www.microsoft.com\/en-us\/security\/blog\/2024\/05\/03\/security-above-all-else-expanding-microsofts-secure-future-initiative\/\" data-type=\"link\" data-id=\"https:\/\/www.microsoft.com\/en-us\/security\/blog\/2024\/05\/03\/security-above-all-else-expanding-microsofts-secure-future-initiative\/\">here<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"protect\">Identities and secrets &#8211; Impact Oct 15, 2024<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As detailed link, Microsoft will enforce MFA requirement for accounts on <strong>October 15, 2024<\/strong>, that makes <strong>interactive sign-ins<\/strong> to <strong>specific portals<\/strong>:<\/p>\n\n\n\n<figure class=\"wp-block-table has-small-font-size\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Application Name<\/strong><\/td><td><strong>App ID<\/strong><\/td><td><strong>Enforcement phase<\/strong><\/td><\/tr><tr><td><a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-portal\/\">Azure portal<\/a><\/td><td>c44b4083-3bb0-49c1-b47d-974e53cbdf3c<\/td><td><br>October 15, 2024<\/td><\/tr><tr><td><a href=\"https:\/\/aka.ms\/MSEntraPortal\">Microsoft Entra admin center<\/a><\/td><td>c44b4083-3bb0-49c1-b47d-974e53cbdf3c<\/td><td>October 15, 2024<\/td><\/tr><tr><td><a href=\"https:\/\/aka.ms\/IntunePortal\">Microsoft Intune admin center<\/a><\/td><td>c44b4083-3bb0-49c1-b47d-974e53cbdf3c<\/td><td>October 15, 2024<\/td><\/tr><tr><td><a href=\"https:\/\/learn.microsoft.com\/en-us\/cli\/azure\/\">Azure command-line interface\u202f(Azure CLI)<\/a><\/td><td>04b07795-8ddb-461a-bbee-02f9e1bf7b46<\/td><td>Early 2025<\/td><\/tr><tr><td><a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/azure\/\">Azure PowerShell<\/a><\/td><td>1950a258-227b-4e31-a9cf-717495945fc2<\/td><td>Early 2025<\/td><\/tr><tr><td><a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-portal\/mobile-app\/overview\">Azure mobile app<\/a>\u202f<\/td><td><br>0c1307d4-29d6-4389-a11c-5cbe7f65d7fa<\/td><td>Early 2025<\/td><\/tr><tr><td><a href=\"https:\/\/learn.microsoft.com\/en-us\/devops\/deliver\/what-is-infrastructure-as-code\">Infrastructure as Code\u202f(IaC) tools<\/a><\/td><td><br>Use Azure CLI or Azure PowerShell IDs<\/td><td>Early 2025<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"impacted\">Impacted Accounts (official statement Microsoft)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/authentication\/concept-mandatory-multifactor-authentication\" data-type=\"link\" data-id=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/authentication\/concept-mandatory-multifactor-authentication\">Statements from this article<\/a><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">All users who sign into the\u202f<a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/authentication\/concept-mandatory-multifactor-authentication#applications\">applications<\/a>&nbsp;listed previously to perform any Create, Read, Update, or Delete (CRUD) operation will require MFA when the enforcement begins. <\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Workload identities,\u202fsuch as managed identities and service principals,\u202faren&#8217;t impacted by MFA enforcement. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If user identities sign in as a service account to run automation (including scripts or other automated tasks), those user identities need to sign in with MFA once enforcement begins. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">User identities aren&#8217;t recommended for automation. You should migrate those user identities to\u202f<a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/workload-id\/workload-identities-overview\">workload identities<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Break glass or emergency access accounts are also required to sign in with MFA once enforcement begins. I recommend updating these accounts to use\u202f<a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/authentication\/how-to-enable-passkey-fido2\">passkey (FIDO2)<\/a>\u202for\u202fconfigure&nbsp;<a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/authentication\/how-to-certificate-based-authentication\">certificate-based authentication<\/a>&nbsp;for MFA.\u202fBoth methods satisfy the MFA requirement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"find\">Find potential accounts with issues (script)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In order for us to find (potential) <strong>impacted accounts<\/strong>, I categorize into 3 checks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Check 1: Accounts that <span style=\"text-decoration: underline;\">will<\/span> be impacted as sign-in activity is detected to portals <\/strong>\n<ul class=\"wp-block-list\">\n<li>Active Account<\/li>\n\n\n\n<li>Sign-ins during last 90 days in cloud<\/li>\n\n\n\n<li>Accounts signed in and used one of the MS Admin Portals during last 90 days<\/li>\n\n\n\n<li>MFA not registered<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Check 2: Accounts that <span style=\"text-decoration: underline;\">may<\/span> be impacted<\/strong> &#8211; missing MFA reg.\n<ul class=\"wp-block-list\">\n<li>Both Inactive or Active accounts. \n<ul class=\"wp-block-list\">\n<li>We include Disabled accounts, as some companies tend to disable some admin accounts e.g. consultants and then re-enable them again.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Sign-ins during last 90 days in cloud<\/li>\n\n\n\n<li>MFA not registered<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Check 3: Accounts that <span style=\"text-decoration: underline;\">may <\/span>be impacted<\/strong> &#8211; MFA not capable, MFA not registered\n<ul class=\"wp-block-list\">\n<li>Both Inactive or Active accounts.\n<ul class=\"wp-block-list\">\n<li>We include Disabled accounts, as some companies tend to disable some admin accounts e.g. consultants and then re-enable them again.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Sign-ins during last 90 days in cloud<\/li>\n\n\n\n<li>MFA not registered<\/li>\n\n\n\n<li><strong>MFA not capable<\/strong><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It is important to note, that we <span style=\"text-decoration: underline;\">cannot <\/span>conclude based solely on recent sign-in events against above admin portal (check 1), as some accounts are used rarely like service accounts with interactive logins and break-glass accounts. For example, if you haven&#8217;t logged in with your break-glass accounts during the last 90 days, you will face issues if they are only protected with userid\/password and have no MFA registrations. Therefore we also have check 2 and 3. Feel free to modify the checks for your requirement. Happy to learn how you do the filtering ?<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Entra ID Sign-in logs keeps 90 days of events. The provided script receives the data directly from Microsoft Graph and is therefore limited to last 90 days of sign-in events. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you send your Sign-in logs into Sentinel, you can also consider to adjust the script to enumerate a larger dataset by looking up the Azure LogAnalytics <strong>SigninLogs<\/strong> table. Query can be done using <strong>Invoke-AzOperationalInsightsQuery<\/strong><\/p>\n<\/blockquote>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"561\" src=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1-1024x561.png\" alt=\"\" class=\"wp-image-3233\" srcset=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1-1024x561.png 1024w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1-300x164.png 300w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1-768x421.png 768w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1.png 1452w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"correlation\">Correlation of data (script)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The script correlates information from <strong>Entra ID SignIn Log<\/strong>, <strong>Entra ID Authentication Methods<\/strong> and <strong>Entra ID User information<\/strong>. Script can easily be extended to include e.g. Active Directory sign-ins, tagging information, etc.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"411\" src=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-2-1024x411.png\" alt=\"\" class=\"wp-image-3234\" srcset=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-2-1024x411.png 1024w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-2-300x120.png 300w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-2-768x308.png 768w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-2.png 1263w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Currently, the following fields are available in the report (array):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Id\nGivenName\nSurName\nUserPrincipalName\nDisplayName\nAccountEnabled\nMail\nSignInsDetectedMSAdminPortals\nIsAdmin\nDefaultMfaMethod\nMethodsRegistered\nIsMfaCapable\nIsMfaRegistered\nIsPasswordlessCapable\nIsSsprCapable\nIsSsprEnabled\nIsSsprRegistered\nIsSystemPreferredAuthenticationMethodEnabled\nAuthMethodsLastUpdatedDateTime\nCloud_LastSignInDateTime\nCloud_LastNonInteractiveSignInDateTime\nCloud_PasswordPolicies\nActiveDirectoryDistinguishedName\nUserLicenseList<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"output\">Output (script)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Output of the script is both shown on screen &#8211; but also exported to 2 Excel files.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"113\" src=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1024x113.png\" alt=\"\" class=\"wp-image-3231\" srcset=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-1024x113.png 1024w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-300x33.png 300w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-768x85.png 768w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image.png 1463w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">File 1 (<a href=\"https:\/\/github.com\/KnudsenMorten\/DetectAccountsWithMFAEnforcementImpact\/raw\/main\/Identity_Overview.xlsx\">sample Identity_Overview.xlsx<\/a>) includes an <strong>overview <\/strong>and <strong>observations<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">File 2 (<a href=\"https:\/\/github.com\/KnudsenMorten\/DetectAccountsWithMFAEnforcementImpact\/raw\/main\/Identity_Overview_Events.xlsx\" data-type=\"link\" data-id=\"https:\/\/github.com\/KnudsenMorten\/DetectAccountsWithMFAEnforcementImpact\/raw\/main\/Identity_Overview_Events.xlsx\">sample Identity_Overview_Events.xlsx<\/a>) includes <strong>actual events<\/strong> which can be used to investigate further.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"329\" src=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-4-1024x329.png\" alt=\"\" class=\"wp-image-3236\" srcset=\"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-4-1024x329.png 1024w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-4-300x96.png 300w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-4-768x247.png 768w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-4-1536x494.png 1536w, https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/image-4-2048x658.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"script\">Script<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Script can be found <a href=\"https:\/\/github.com\/KnudsenMorten\/DetectAccountsWithMFAEnforcementImpact\/raw\/main\/IdentitySecurityOverview.ps1\" data-type=\"link\" data-id=\"https:\/\/github.com\/KnudsenMorten\/DetectAccountsWithMFAEnforcementImpact\/raw\/main\/IdentitySecurityOverview.ps1\">here on Github<\/a><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Bug Fix &#8211; &#8220;command not found&#8221;  Get-MgAuditLogSignIn<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you ran the script on a machine with only the beta version of Microsoft Graph installed, you would before Sept 18, 2024 get an error with &#8220;command not found&#8221; referencing to the command Get-MgAuditLogSignIn<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Script has now been updated to use the beta-version which will installed as part of the script.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">$Events = Get-MgBetaAuditLogSignIn -Filter &#8220;(AppId eq &#8216;$($AppId)&#8217;) and (createdDateTime ge $SearchDateFrom)&#8221; -All<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Variables<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">You can fine-tune the script scope and output by modifying the variables in the header.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    $LogDaysToSearch = 90\n    $File_Overview = \".\\Identity_Overview.xlsx\"\n    $File_Events   = \".\\Identity_Overview_Events.xlsx\"<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Complete script<\/h4>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>#Requires -Version 5.0\n&lt;#\n    .SYNOPSIS\n    \n    Detect Accounts Impacted By The MFA Enforcement October 15, 2025 and Early 2025\n\n    .MORE INFORMATION\n    https:&#47;&#47;learn.microsoft.com\/en-us\/entra\/identity\/authentication\/concept-mandatory-multifactor-authentication\n\n    .NOTES\n    VERSION: 2409\n\n    .COPYRIGHT\n    @mortenknudsendk on Twitter | mok@mortenknudsen.net\n    Blog: https:\/\/mortenknudsen.net\n    \n    .LICENSE\n    Licensed under the MIT license.\n\n    .WARRANTY\n    Use at your own risk, no warranty given!\n#>\n\n#####################################################################\n# Variables\n#####################################################################\n\n    $LogDaysToSearch = 90\n    $File_Overview = \".\\Identity_Overview.xlsx\"\n    $File_Events   = \".\\Identity_Overview_Events.xlsx\"\n\n    $AppInScope = @(\n                      &#91;PSCustomObject]@{\n                                          AppId = \"c44b4083-3bb0-49c1-b47d-974e53cbdf3c\"\n                                          AppName = \"Azure Portal\"\n                                          EnforceMent = \"Oct 15, 2024\"\n                                       }\n                      &#91;PSCustomObject]@{\n                                          AppId = \"04b07795-8ddb-461a-bbee-02f9e1bf7b46\"\n                                          AppName = \"Azure CLI\"\n                                          EnforceMent = \"Early 2025\"\n                                       }\n                      &#91;PSCustomObject]@{\n                                          AppId = \"1950a258-227b-4e31-a9cf-717495945fc2\"\n                                          AppName = \"Azure Powershell\"\n                                          EnforceMent = \"Early 2025\"\n                                       }\n                      &#91;PSCustomObject]@{\n                                          AppId = \"0c1307d4-29d6-4389-a11c-5cbe7f65d7fa\"\n                                          AppName = \"Azure Mobile App\"\n                                          EnforceMent = \"Early 2025\"\n                                       }\n                   )\n\n\n#####################################################################\n# Microsoft Graph\n#####################################################################\n\n    write-host \"\"\n    Write-host \"Step 1\/8: Checking Microsoft Powershell Modules\"\n\n    $InstalledModules = Get-InstalledModule\n    If (\"Microsoft.Graph.Beta\" -notin $InstalledModules.Name)\n        {\n            # Beta-module contains more info than normal module - why ? Good question, but that is a fact !\n            Install-module Microsoft.Graph.Beta\n        }\n    If (\"ImportExcel\" -notin $InstalledModules.Name)\n        {\n            # ImportExcel - Used for Excel file reporting\n            #   Github: https:\/\/github.com\/dfinke\/ImportExcel\n            #   Powershell Gallery: https:\/\/www.powershellgallery.com\/packages\/ImportExcel\n\n            Install-module ImportExcel\n        }\n\n#####################################################################\n# Microsoft Graph\n#####################################################################\n\n    write-host \"\"\n    Write-host \"Step 2\/8: Connectivity to Microsoft Graph\"\n\n    Connect-MgGraph -scopes Directory.Read.All, AuditLog.Read.All, UserAuthenticationMethod.Read.All\n\n\n#####################################################################\n# Step 3: Get SignIn Events from Entra ID SignInLog\n#####################################################################\n\n    write-host \"\"\n    Write-host \"Step 3\/8: Checking Sign-in logs for last $($LogDaysToSearch) day(s) - looking for interactive sign-ins\"\n\n    $SearchDateFrom = (Get-date) - (New-TimeSpan -Days $LogDaysToSearch)\n    $SearchDateFrom = Get-date $SearchDateFrom -format yyyy-MM-ddTHH:mm:ssZ\n\n    $LogEvents = &#91;System.Collections.ArrayList]@()\n    $TotalEvents = 0\n    ForEach ($Entry in $AppInScope)\n        {\n            $Events = @()\n            write-host \"\"\n            Write-host \"  Searching for events for app &#91; $($Entry.AppName) ] ... Please Wait !\"\n\n            $AppId = &#91;guid]$Entry.AppId\n            $Events = Get-MgBetaAuditLogSignIn -Filter \"(AppId eq '$($AppId)') and (createdDateTime ge $SearchDateFrom)\" -All\n\n            $TotalEvents = $TotalEvents + $Events.Count\n\n            write-host \"  Found $($Events.Count) event(s)\"\n            $Add = $LogEvents.add($Events)\n        }\n\n    If ($LogEvents)\n        {\n            write-host \"\"\n            write-host \"  Found $($TotalEvents) total event(s)\"\n\n            $LogEvents_Users_unique = $LogEvents.UserPrincipalName | Sort-Object -Unique\n\n            write-host \"\"\n            write-host \"  Found $($LogEvents_Users_unique.count) unique user(s) with sign-ins\"\n            write-host \"\"\n        }\n\n#########################################################################################################\n# Step 4: Get AuthenticationMethods from the users doing interactive sign-ins\n#########################################################################################################\n\n    write-host \"\"\n    write-host \"Step 4\/8: Getting Authentication Methods from Entra ID .... Please Wait !\"\n\n    $UsersAuthMethods = Get-MgBetaReportAuthenticationMethodUserRegistrationDetail -All\n\n    If ($UsersAuthMethods)\n        {\n            write-host \"\"\n            write-host \"  Found $($UsersAuthMethods.count) authentication methods\"\n            write-host \"\"\n            write-host \"  Converting UserAuthenticationMethod array to hash-table for faster searching as hash-tables per definition must be unique\"\n\n            $UsersAuthMethods_Hash = &#91;ordered]@{}\n            $UsersAuthMethods | ForEach-Object { $UsersAuthMethods_Hash.add($_.UserPrincipalName,$_)}\n        }\n\n#####################################################################\n# Step 5: Get user info\n#####################################################################\n\n    write-host \"\"\n    write-host \"Step 5\/8: Getting User info from Entra ID .... Please Wait !\"\n\n    $Users = Get-MgBetaUser -All -property AccountEnabled, id, givenname, surname, userprincipalname, AssignedLicenses, AssignedPlans, Authentication, Devices, CreatedDateTime, Department, Identities, InvitedBy, IsResourceAccount, JoinedTeams, JoinedGroups, LastPasswordChangeDateTime, LicenseDetails, Mail, Manager, MobilePhone, OfficeLocation, PasswordPolicies, ProxyAddresses, UsageLocation, OnPremisesDistinguishedName, OnPremisesExtensionAttributes, OnPremisesSyncEnabled, displayname, signinactivity `\n                            | select-object id, givenname, surname, userprincipalname, OnPremisesDistinguishedName, AccountEnabled, displayname, AssignedLicenses, AssignedPlans, Authentication, Devices, CreatedDateTime, Department, Identities, InvitedBy, IsResourceAccount, JoinedTeams, JoinedGroups, LastPasswordChangeDateTime, LicenseDetails, Mail, Manager, MobilePhone, OfficeLocation, PasswordPolicies, ProxyAddresses, UsageLocation, OnPremisesSyncEnabled, `\n                            @{name='LastSignInDateTime'; expression = {$_.signinactivity.lastsignindatetime}}, `\n                            @{name='LastNonInteractiveSignInDateTime'; expression = {$_.signinactivity.LastNonInteractiveSignInDateTime}}, `\n                            @{name='AuthPhoneMethods'; expression = {$_.authentication.PhoneMethods}}, `\n                            @{name='AuthMSAuthenticator'; expression = {$_.authentication.MicrosoftAuthenticatorMethods}}, `\n                            @{name='AuthPassword'; expression = {$_.authentication.PasswordMethods}}\n\n    If ($Users)\n        {\n            write-host \"\"\n            write-host \"  Found $($Users.count) users\"\n            write-host \"\"\n            write-host \"  Converting User array to hash-table for faster searching as hash-tables per definition must be unique\"\n\n            $Users_Hash = &#91;ordered]@{}\n            $Users | ForEach-Object { $Users_Hash.add($_.UserPrincipalName,$_)}\n        }\n    \n#####################################################################\n# Step 6: Correlate date - build array\n#####################################################################\n\n    Write-host \"\"\n    Write-host \"Step 6\/8: Correlating data-sources into user array for validation &amp; reporting purpose .... Please Wait !\"\n    Write-host \"\"\n\n    # Get license details from Microsoft - https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/users\/licensing-service-plan-reference\n    $LicenseTranslationTable = Invoke-WebRequest -Method Get -UseBasicParsing -Uri \"https:\/\/download.microsoft.com\/download\/e\/3\/e\/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0\/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv\" | ConvertFrom-Csv\n\n    $UserInfoArray = &#91;System.Collections.ArrayList]@()\n    $UsersTotal = $Users.count\n\n    $Users | ForEach-Object -Begin  {\n            $i = 0\n    } -Process {\n            \n            # Default values\n            $User = $_\n            $SignInsDetected = $false\n\n            write-host \"  Processing $($User.DisplayName)\"\n\n            #------------------------------------------------------------------------------------------------\n            # Sign-in Events\n               If ($User.UserPrincipalName -in $LogEvents_Users_unique)\n                    {\n                        $SignInsDetected = $true\n                    }\n\n            #------------------------------------------------------------------------------------------------\n            # Authentication Methods\n                \n                If ($UsersAuthMethods)\n                    {\n                        $AuthMethods = $UsersAuthMethods_Hash&#91;$user.UserPrincipalName]\n                    }\n\n            #------------------------------------------------------------------------------------------------\n            # Get Licenses\n\n                # Get user licenses\n                    $LicenseInfo = @()\n                    ForEach ($License in $User.AssignedLicenses)\n                        {\n                            $LicenseInfo += $LicenseTranslationTable | where { $_.Guid -eq $License.SkuID }\n                        }\n                    If ($LicenseInfo)\n                        {\n                            $UserLicenseInfo_List = (($LicenseInfo.\"???Product_Display_Name\" | Sort-Object -Unique) -join \",\")\n                        }\n\n                    $LicenseInfo = $LicenseInfo.String_ID | Sort-Object -Unique\n                    $UserAssignedPlans = $User.AssignedPlans\n\n            #------------------------------------------------------------------------------------------------\n            # Building array\n\n            $Object = &#91;PSCustomObject]@{\n                                            Id                                           = $User.Id\n                                            GivenName                                    = $User.GivenName\n                                            SurName                                      = $User.Surname\n                                            UserPrincipalName                            = $User.UserPrincipalName\n                                            DisplayName                                  = $User.DisplayName\n                                            AccountEnabled                               = $User.AccountEnabled\n                                            Mail                                         = $User.Mail\n                                            SignInsDetectedMSAdminPortals                = $SignInsDetected\n                                            IsAdmin                                      = $AuthMethods.IsAdmin\n                                            DefaultMfaMethod                             = $AuthMethods.DefaultMfaMethod\n                                            MethodsRegistered                            = $AuthMethods.MethodsRegistered -join ','\n                                            IsMfaCapable                                 = $AuthMethods.IsMfaCapable\n                                            IsMfaRegistered                              = $AuthMethods.IsMfaRegistered\n                                            IsPasswordlessCapable                        = $AuthMethods.IsPasswordlessCapable\n                                            IsSsprCapable                                = $AuthMethods.IsSsprCapable\n                                            IsSsprEnabled                                = $AuthMethods.IsSsprEnabled\n                                            IsSsprRegistered                             = $AuthMethods.IsSsprRegistered\n                                            IsSystemPreferredAuthenticationMethodEnabled = $AuthMethods.IsSystemPreferredAuthenticationMethodEnabled\n                                            AuthMethodsLastUpdatedDateTime               = $AuthMethods.LastUpdatedDateTime\n                                            Cloud_LastSignInDateTime                     = $User.LastSignInDateTime\n                                            Cloud_LastNonInteractiveSignInDateTime       = $User.LastNonInteractiveSignInDateTime\n                                            Cloud_PasswordPolicies                       = $User.PasswordPolicies\n                                            ActiveDirectoryDistinguishedName             = $User.OnPremisesDistinguishedName\n                                            UserLicenseList                              = $UserLicenseInfo_List\n                                        }\n            $Result = $UserInfoArray.add($object)\n\n            # Increment the $i counter variable which is used to create the progress bar.\n            $i = $i+1\n\n            # Determine the completion percentage\n            $Completed = ($i\/$UsersTotal) * 100\n            Write-Progress -Activity \"Correlating User Info\" -Status \"Progress:\" -PercentComplete $Completed\n            } -End {\n                \n                Write-Progress -Activity \"Correlating User Info\" -Status \"Ready\" -Completed\n            }\n\n\n#####################################################################\n# Step 7: Build conclusions\n#####################################################################\n\n    $DaysLastSignInCheck = (Get-date) - (New-TimeSpan -Days 90)\n\n    Write-host \"\"\n    Write-host \"Step 7\/8: Building conclusions .... Please Wait !\"\n\n    # Scoping\n        $UserInfoArray_Scoped = $UserInfoArray | Where-Object { $_.DisplayName -notlike \"On-Premises Directory Synchronization Service Account\" }\n\n    #--------------------------------------------------------------------------------------------------------\n    # IMPACT: Accounts that will have impact, when Microsoft enforce MFA Requirement (sign-ins was detected)\n        $Impact_MFA_Enforcement = $UserInfoArray_Scoped | Where-Object { ( (!($_.IsMfaRegistered)) -and ($_.SignInsDetectedMSAdminPortals) -and ($_.AccountEnabled) ) }\n\n        write-host \"\"\n        write-host \"Check 1: Active account, MFA missing, cloud sign-ins during last 90 days, Sign-in events against MS admin portals detected last $($LogDaysToSearch) day(s)\"\n\n        If ($Impact_MFA_Enforcement)\n            {\n                write-host \"\"\n                Write-host \"Users ($(($Impact_MFA_Enforcement | measure-object).count)) that will be impacted by MFA enforcement (sign-ins detected):\" -ForegroundColor Yellow\n\n                $Impact_MFA_Enforcement | Select-Object DisplayName,UserPrincipalName | Out-Default\n            }\n        Else\n            {\n                write-host \"No issues found !\" -ForegroundColor Green\n                write-host \"\"\n            }\n\n    #--------------------------------------------------------------------------------------------------------\n    # INVESTIGATION NEEDED: Accounts that are missing MFA registration - SignIn during Last 90 days - Account Active\n        $MissingMFA_ActiveAccount_RecentSignIns = $UserInfoArray_Scoped | Where-Object { ( (!($_.IsMFARegistered) -and (!($_.SignInsDetectedMSAdminPortals)) -and ($_.Cloud_LastSignInDateTime -gt $DaysLastSignInCheck)) ) }\n\n        write-host \"\"\n        write-host \"Check 2: Active account, MFA missing, cloud sign-ins during last 90 days, no sign-in events against MS admin portals\"\n\n        If ($MissingMFA_ActiveAccount_RecentSignIns)\n            {\n                write-host \"\"\n                Write-host \"Users ($(($MissingMFA_ActiveAccount_RecentSignIns | measure-object).count)) that MAY be impacted by MFA enforcement if account may do interactive login against MS admin Portals (no events detected):\" -ForegroundColor Yellow\n\n                $MissingMFA_ActiveAccount_RecentSignIns | Select-object DisplayName, UserPrincipalName | Out-Default\n            }\n        Else\n            {\n                write-host \"No issues found !\" -ForegroundColor Green\n                write-host \"\"\n            }\n\n    #--------------------------------------------------------------------------------------------------------\n    # INVESTIGATION NEEDED: Accounts that are missing MFA registration - SignIn during Last 90 days - Account Active - MFA not capable\n    # MFA NOT capable: Users registered and enabled for a strong authentication method in Microsoft Entra ID. Either a user or an admin may register an authentication method on behalf of a user. \n    # Authentication methods are enabled by authentication method policy or multifactor authentication service settings\n\n        $MissingMFA_ActiveAccount_RecentSignIns_MfaNotCapable = $UserInfoArray_Scoped | Where-Object { ( (!($_.IsMfaRegistered) -and (!($_.SignInsDetectedMSAdminPortals)) -and (!($_.IsMfaCapable)) -and ($_.Cloud_LastSignInDateTime -gt $DaysLastSignInCheck) ) ) }\n\n        write-host \"\"\n        write-host \"Check 3: Active account, MFA missing, MFA not capable, cloud sign-ins during last 90 days, no sign-in events against MS admin portals\"\n        \n        If ($MissingMFA_ActiveAccount_RecentSignIns_MfaNotCapable)\n            {\n                write-host \"\"\n                Write-host \"Users ($(($MissingMFA_ActiveAccount_RecentSignIns_MfaNotCapable | measure-object).count)) that MAY be impacted by MFA enforcement if account may do interactive login against MS admin Portals:\" -ForegroundColor Yellow\n\n                $MissingMFA_ActiveAccount_RecentSignIns_MfaNotCapable | Select-object DisplayName, UserPrincipalName | Out-Default\n            }\n        Else\n            {\n                write-host \"No issues found !\" -ForegroundColor Green\n                write-host \"\"\n            }\n\n\n#####################################################################\n# Step 8: Export to Excel\n#####################################################################\n\n    If (Test-Path $File_Overview)\n        {\n            Remove-Item $File_Overview -Force\n        }\n\n    Write-host \"\"\n    Write-host \"\"\n    Write-host \"Step 8\/8: Exporting Overview, Observations &amp; Events .... Please Wait !\"\n    write-host \"\"\n\n    #-----------------------------------\n    Write-host \"  Exporting Overview to file ($($File_Overview))\"\n\n    $Target = \"Users_Scoped\"\n    $UserInfoArray_Scoped | Export-Excel -Path $File_Overview -WorksheetName $Target -AutoFilter -AutoSize -BoldTopRow -tablename $Target -tablestyle medium9\n\n    $Target = \"Check1_Impact_MFA_Enforcement\"\n    $Impact_MFA_Enforcement | Export-Excel -Path $File_Overview -WorksheetName $Target -AutoFilter -AutoSize -BoldTopRow -tablename $Target -tablestyle medium9\n\n    $Target = \"Check2_Missing_MFA\"\n    $MissingMFA_ActiveAccount_RecentSignIns | Export-Excel -Path $File_Overview -WorksheetName $Target -AutoFilter -AutoSize -BoldTopRow -tablename $Target -tablestyle medium9\n\n    $Target = \"Check3_Missing_MFA_Not_Capable\"\n    $MissingMFA_ActiveAccount_RecentSignIns_MfaNotCapable | Export-Excel -Path $File_Overview -WorksheetName $Target -AutoFilter -AutoSize -BoldTopRow -tablename $Target -tablestyle medium9\n\n    #-----------------------------------\n\n    If (Test-Path $File_Events)\n        {\n            Remove-Item $File_Events -Force\n        }\n\n    write-host \"\"\n    Write-host \"  Exporting Sign-In events to file ($($File_Events))\"\n    write-host \"\"\n\n    $Target = \"Events\"\n    $LogEvents | Export-Excel -Path $File_Events -WorksheetName $Target -AutoFilter -AutoSize -BoldTopRow -tablename $Target -tablestyle medium9\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>You may have noticed that Microsoft will enforce MFA requirement per October 15, 2024 for Azure\/Entra\/Intune. If this is new &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"Detect Impact MFA Enforcement\" class=\"read-more button\" href=\"https:\/\/mortenknudsen.net\/?p=3230#more-3230\" aria-label=\"Read more about Detect Impact MFA Enforcement\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":3240,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"ngg_post_thumbnail":0,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[55,161,164,156,133,157,132],"tags":[18,168,172,171,167,166,123,170,17,169,173],"class_list":["post-3230","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-entra-id","category-identity-2","category-microsoft-graph","category-microsoft-security","category-powershell","category-security","tag-azure","tag-enforcement","tag-entraid","tag-impact","tag-mfa","tag-microsoft","tag-policy","tag-securefutureinitiative","tag-security","tag-sfi","tag-tenant","infinite-scroll-item","resize-featured-image"],"featured_image_src":"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/MFA_Enforcement.png","author_info":{"display_name":"Morten Knudsen","author_link":"https:\/\/mortenknudsen.net\/?author=1"},"jetpack_featured_media_url":"https:\/\/mortenknudsen.net\/wp-content\/uploads\/2024\/08\/MFA_Enforcement.png","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/posts\/3230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3230"}],"version-history":[{"count":32,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/posts\/3230\/revisions"}],"predecessor-version":[{"id":3365,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/posts\/3230\/revisions\/3365"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=\/wp\/v2\/media\/3240"}],"wp:attachment":[{"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3230"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mortenknudsen.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}