diff --git a/source/Public/Get-MFAStatus.ps1 b/source/Public/Get-MFAStatus.ps1 new file mode 100644 index 0000000..e026790 --- /dev/null +++ b/source/Public/Get-MFAStatus.ps1 @@ -0,0 +1,99 @@ +<# + .SYNOPSIS + Retrieves the MFA (Multi-Factor Authentication) status for Azure Active Directory users. + .DESCRIPTION + The Get-MFAStatus function connects to Microsoft Online Service and retrieves the MFA status for all Azure Active Directory users, excluding guest accounts. Optionally, you can specify a single user by their User Principal Name (UPN) to get their MFA status. + .PARAMETER UserId + The User Principal Name (UPN) of a specific user to retrieve MFA status for. If not provided, the function retrieves MFA status for all users. + .EXAMPLE + Get-MFAStatus + Retrieves the MFA status for all Azure Active Directory users. + .EXAMPLE + Get-MFAStatus -UserId "example@domain.com" + Retrieves the MFA status for the specified user with the UPN "example@domain.com". + .OUTPUTS + System.Object + Returns a sorted list of custom objects containing the following properties: + - UserPrincipalName + - DisplayName + - MFAState + - MFADefaultMethod + - MFAPhoneNumber + - PrimarySMTP + - Aliases + .NOTES + The function requires the MSOL module to be installed and connected to your tenant. + Ensure that you have the necessary permissions to read user and MFA status information. +#> +function Get-MFAStatus { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$UserId + ) + + begin { + # Connect to Microsoft Online service + } + + process { + if (Get-Module MSOnline){ + Connect-MsolService + Write-Host "Finding Azure Active Directory Accounts..." + # Get all users, excluding guests + $Users = if ($PSBoundParameters.ContainsKey('UserId')) { + Get-MsolUser -UserPrincipalName $UserId + } else { + Get-MsolUser -All | Where-Object { $_.UserType -ne "Guest" } + } + $Report = [System.Collections.Generic.List[Object]]::new() # Create output list + Write-Host "Processing" $Users.Count "accounts..." + ForEach ($User in $Users) { + $MFADefaultMethod = ($User.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq "True" }).MethodType + $MFAPhoneNumber = $User.StrongAuthenticationUserDetails.PhoneNumber + $PrimarySMTP = $User.ProxyAddresses | Where-Object { $_ -clike "SMTP*" } | ForEach-Object { $_ -replace "SMTP:", "" } + $Aliases = $User.ProxyAddresses | Where-Object { $_ -clike "smtp*" } | ForEach-Object { $_ -replace "smtp:", "" } + + If ($User.StrongAuthenticationRequirements) { + $MFAState = $User.StrongAuthenticationRequirements.State + } + Else { + $MFAState = 'Disabled' + } + + If ($MFADefaultMethod) { + Switch ($MFADefaultMethod) { + "OneWaySMS" { $MFADefaultMethod = "Text code authentication phone" } + "TwoWayVoiceMobile" { $MFADefaultMethod = "Call authentication phone" } + "TwoWayVoiceOffice" { $MFADefaultMethod = "Call office phone" } + "PhoneAppOTP" { $MFADefaultMethod = "Authenticator app or hardware token" } + "PhoneAppNotification" { $MFADefaultMethod = "Microsoft authenticator app" } + } + } + Else { + $MFADefaultMethod = "Not enabled" + } + + $ReportLine = [PSCustomObject] @{ + UserPrincipalName = $User.UserPrincipalName + DisplayName = $User.DisplayName + MFAState = $MFAState + MFADefaultMethod = $MFADefaultMethod + MFAPhoneNumber = $MFAPhoneNumber + PrimarySMTP = ($PrimarySMTP -join ',') + Aliases = ($Aliases -join ',') + } + + $Report.Add($ReportLine) + } + + Write-Host "Processing complete." + return $Report | Select-Object UserPrincipalName, DisplayName, MFAState, MFADefaultMethod, MFAPhoneNumber, PrimarySMTP, Aliases | Sort-Object UserPrincipalName + } + else { + Write-Host "You must first install MSOL using:`nInstall-Module MSOnline -Scope CurrentUser -Force" + } + } + +} \ No newline at end of file diff --git a/tests/Unit/Public/Get-MFAStatus.tests.ps1 b/tests/Unit/Public/Get-MFAStatus.tests.ps1 new file mode 100644 index 0000000..5998a20 --- /dev/null +++ b/tests/Unit/Public/Get-MFAStatus.tests.ps1 @@ -0,0 +1,71 @@ +BeforeAll { + $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' + + # If the module is not found, run the build task 'noop'. + if (-not (Get-Module -Name $script:moduleName -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # Re-import the module using force to get any code changes between runs. + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Module -Name $script:moduleName +} + +Describe Get-Something { + + Context 'Return values' { + BeforeEach { + $return = Get-Something -Data 'value' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + } + + Context 'Pipeline' { + It 'Accepts values from the pipeline by value' { + $return = 'value1', 'value2' | Get-Something + + $return[0] | Should -Be 'value1' + $return[1] | Should -Be 'value2' + } + + It 'Accepts value from the pipeline by property name' { + $return = 'value1', 'value2' | ForEach-Object { + [PSCustomObject]@{ + Data = $_ + OtherProperty = 'other' + } + } | Get-Something + + + $return[0] | Should -Be 'value1' + $return[1] | Should -Be 'value2' + } + } + + Context 'ShouldProcess' { + It 'Supports WhatIf' { + (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true + { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw + } + + + } +} +