We use both Azure MFA Server to secure our on-site resources, and Office365 MFA for our clients. To make sure we don’t have aggressors changing the MFA settings, or simply administrators forgetting to set-up MFA for clients we make sure that we alert on both.
The issue with monitoring the MFA server is that its a product Microsoft bought later on its in life. As such does not have a PowerShell module included, and it does not conform to the current Common Engineering Criteria.
To solve this, we add a DLL that exposes the functionality that we need, when they get a list of all users(starting with A, until Z).
Add-Type -Path "C:\Program Files\Multi-Factor Authentication Server\pfsvcclientclr.DLL" $problem = [PfSvcClientClr.ConstructResult]::miscError; $main = [PfSvcClientClr.PfSvcClient]::construct([PfSvcClientClr.ConstructTarget]::local, [ref] $problem); $DLLModule = $main.GetType().GetMethod("getInterface").MakeGenericMethod([PfSvcClientClr.IPfMasterComposite]).Invoke($main, $null); $users = $DLLModule.find_users_startsWith("a") foreach($username in $users){ if($DLLModule.get_user_disabledBehavior("$username") -eq "succeed") { $UserOutput += "$($username) is set to succeed authentication without MFA `n" } } if(!$UserOutput) { $UserOutput = "Healthy" }
Next we will monitor the multi-factor authentication on the Office365 side. For this we will use the MSOL module and your partner credentials you’ve generated using this blog. The script can be used both to get information for all clients, or a single client. To demonstrate I’ve added both
All Clients script:
$ApplicationId = "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx" $ApplicationSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx=" $RefreshToken = "VeryLongRefreshToken" $Tenantname = "YourTenant.onmicrosoft.com" $PasswordToSecureString = $ApplicationSecret | ConvertTo-SecureString -asPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($ApplicationId,$PasswordToSecureString) $aadGraphToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://graph.windows.net -Credential $credential -TenantId $Tenantname $graphToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://graph.microsoft.com -Credential $credential -TenantId $Tenantname Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken $AllUSers = Get-MsolPartnerContract | Get-MsolUser -all | select DisplayName,UserPrincipalName,@{N="MFA Status"; E={ if( $_.StrongAuthenticationRequirements.State -ne $null) {$_.StrongAuthenticationRequirements.State} else { "Disabled"}}} foreach($User in $allusers | Where-Object{ $_.'MFA Status' -eq "Disabled"}){ $DisabledUsers += "$($User.UserPrincipalName) Has MFA disabled `n" } if(!$DisabledUsers){ $DisabledUsers = "Healthy"}
Single client script
$ApplicationId = "xxxx-xxxxx-xxxxx-xxxxx-xxxxxx" $ApplicationSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx=" $RefreshToken = "VeryLongStringHere" $Tenantname = "YourTenant.onmicrosoft.com" $ClientTenantName = "TheClientsTenant.onmicrosoft.com" $PasswordToSecureString = $ApplicationSecret | ConvertTo-SecureString -asPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($ApplicationId,$PasswordToSecureString) $aadGraphToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://graph.windows.net -Credential $credential -TenantId $Tenantname $graphToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://graph.microsoft.com -Credential $credential -TenantId $Tenantname Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken $AllUSers = Get-MsolPartnerContract -DomainName $ClientTenantName | Get-MsolUser -all | select DisplayName,UserPrincipalName,@{N="MFA Status"; E={ if( $_.StrongAuthenticationRequirements.State -ne $null) {$_.StrongAuthenticationRequirements.State} else { "Disabled"}}} foreach($User in $allusers | Where-Object{ $_.'MFA Status' -eq "Disabled"}){ $DisabledUsers += "$($User.UserPrincipalName) Has MFA disabled" } if(!$DisabledUsers){ $DisabledUsers = "Healthy"}
What is inside the dll? Where does it go?
I ran this in powershell on my pc and it failed with errors:
Add-Type -Path “C:\Program Files\Multi-Factor Authentication Server\pfsvcclientclr.DLL”
$problem = [PfSvcClientClr.ConstructResult]::miscError;
$main = [PfSvcClientClr.PfSvcClient]::construct([PfSvcClientClr.ConstructTarget]::local, [ref] $problem);
$DLLModule = $main.GetType().GetMethod(“getInterface”).MakeGenericMethod([PfSvcClientClr.IPfMasterComposite]).Invoke($main, $null);
$users = $DLLModule.find_users_startsWith(“a”)
foreach($username in $users){
if($DLLModule.get_user_disabledBehavior(“$username”) -eq “succeed”) { $UserOutput += “$($username) is set to succeed authentication without MFA `n” }
}
if(!$UserOutput) { $UserOutput = “Healthy” }
Thank you
That script is only if you use Azure Multi Factor Authentication Server. If you do have that installed and want to check the status of it, run the script and then check the contents of the $problem variable. It should help you check where the issue comes from.
Hi Kelvin,
Thanks for this script!
I am getting an error with e the new-partneraccesstoken cmdlet, sying that resource is not a valid parameter. I am in PS ISE and do not see the Resource parameter.
Maybe something has changed in that cmdlet since you wrote this?
Regards,
Wes
This version is a bit older yes. The correct lines are now:
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes ‘https://graph.windows.net/.default’ -ServicePrincipal
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes ‘https://graph.microsoft.com/.default’ -ServicePrincipal
Thanks for the quick reply. I did find that it changed yesterday but couldn’t figure out what I needed to change. it was the -ServicePrincipal that I was missing.
I ran the code and get a permission denied for one of our tenants to run get-msoluser. We have around 100 tenants.
Any idea how I can determine which tenant is causing the error without going through each one?