There is an upcoming audit of our institution and I just need to verify this script.
because our system admins are saying they are MFA enabled but when I run the script it displays that they are not. So upon further checking they might be enabled using StrongAuthenticationRequirements not through Strong Authentication methods as per the the script or code. Please clarify or enlighten me as I am not a pro when it comes to powershell.
Function Get-O365AdminMFAStatus{
$AdminData=#()
$objRole=#()
$Domain = $(get-addomain).dnsroot
$Log = "C:\temp\Audit\$Domain O365 Admin MFA Status $(get-date -f yyyy-MM-dd).csv"
try{
$Roles = Get-MsolRole | where {$_.name -LIKE "*Administrator*"}
$Roles = ($Roles).name
foreach ($Role in $Roles){
$Members = Get-MsolRoleMember -RoleObjectId (Get-MsolRole -RoleName $Role).ObjectId
foreach ($Member in $Members){
$MsUser = $Member | Get-MsolUser
if($MsUser.StrongAuthenticationMethods.Count -eq 0) {
$Enabled = "False"
write-host $Role - $Member.DisplayName "No MFA enabled" -foregroundcolor red
}
Else{
$Enabled = "True"
write-host $Role - $Member.DisplayName "MFA enabled" -foregroundcolor green
}
Try{
$Exist = [bool](Get-mailbox $MsUser.UserPrincipalName -erroraction SilentlyContinue)
if ($Exist){
$MBStats = Get-MailboxStatistics $MsUser.UserPrincipalName
$LastLogon = $MBstats.LastLogonTime
}
Else{
$LastLogon = "N/A"
}
}
Catch{
$LastLogon = "N/A"
}
$objRole = New-Object -TypeName PSObject
$objRole | Add-Member -MemberType NoteProperty -Name "Role Name" -Value $Role
$objRole | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $Member.DisplayName
$objRole | Add-Member -MemberType NoteProperty -Name "UPN" -Value $Member.UserPrincipalName
$objRole | Add-Member -MemberType NoteProperty -Name "Licensed" -Value $Member.IsLicensed
$objRole | Add-Member -MemberType NoteProperty -Name "Last Logon" -Value $LastLogon
$objRole | Add-Member -MemberType NoteProperty -Name "MFA Enabled?" -Value $Enabled
$AdminData += $objRole
}
}
$AdminData | Export-Csv -NoTypeInformation $Log
write-host ""
write-host "CSV Export Complete to $Log" -foregroundcolor yellow
}
Catch{
Write-host "There was an error: $($_.Exception.Message)"
}
}
Get-O365AdminMFAStatus
what does eq-0 mean ($MsUser.StrongAuthenticationMethods.Count -eq 0)
-eq is one of the Comparison Operators in PowerShell that stands for Equal. In this case it means:
If ($MsUser.StrongAuth...... is empty)
e.g. If there are no StrongAuthenticationMethods, then{ do something }
There are many Comparison Operators.
Take a look at [https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-5.1][1]
Related
I am doing an export of mailboxes from Office 365.
Some mailboxes the have the same primarysmtpaddress are active and also Inactive.
I enter code here have the following code:
$MBX = import-csv "c:\retention\LegalHold\test.csv"
$MBX | Add-Member -MemberType NoteProperty -Name Retention -Value NoPolicy
$MBX | Add-Member -MemberType NoteProperty -Name NoMailbox -Value $False
$MBX | Add-Member -MemberType NoteProperty -Name IsInactiveMailbox -Value $null
$MBX | Add-Member -MemberType NoteProperty -Name PrimarySmtpAddress -Value $null
$count=0
$fullcount = ($mbx | measure-object).count
foreach ($M in $MBX){
$policy = $null
$count++
$search = $m.custodian.tostring()
Write-progress -activity "Checking Mailbox $count out of $fullcount --- $search " -percentcomplete (($count / $fullcount)*100) -status "Processing"
Try{
$ErrorActionPreference = 'Stop'
$Policy = Invoke-Command -scriptblock {Get-EXOMailbox $m.custodian -IncludeInactiveMailbox -properties primarysmtpaddress,inplaceholds,isinactivemailbox,LitigationHoldEnabled,LitigationHoldDuration,Office}
#$Policy = Invoke-Command -scriptblock {Get-Mailbox $m.name -IncludeInactiveMailbox}
#$m.MBXFound = $True
$m.IsInactiveMailbox = $Policy.IsInactiveMailbox -join ','
$m.PrimarySmtpAddress = $Policy.PrimarySmtpAddress -join ','
}Catch{$m.NoMailbox = "$True"}
}
$MBX | export-csv C:\retention\legalhold\test_check.csv -NoTypeInformation
Instead of outputting to the CSV like this:
I'd like to have the mailboxes with an active and inactive mailbox on separate lines like this:
You would need to loop through the results of $Policy instead of just using -join.
$MBX = import-csv "c:\retention\LegalHold\test.csv"
$MBX | Add-Member -MemberType NoteProperty -Name Retention -Value NoPolicy
$MBX | Add-Member -MemberType NoteProperty -Name NoMailbox -Value $False
$MBX | Add-Member -MemberType NoteProperty -Name IsInactiveMailbox -Value $null
$MBX | Add-Member -MemberType NoteProperty -Name PrimarySmtpAddress -Value $null
$count=0
$fullcount = ($mbx | measure-object).count
$Output = foreach ($M in $MBX){
$policy = $null
$count++
$search = $m.custodian.tostring()
Write-progress -activity "Checking Mailbox $count out of $fullcount --- $search " -percentcomplete (($count / $fullcount)*100) -status "Processing"
Try{
$ErrorActionPreference = 'Stop'
$Policy = Invoke-Command -scriptblock {Get-EXOMailbox $m.custodian -IncludeInactiveMailbox -properties primarysmtpaddress,inplaceholds,isinactivemailbox,LitigationHoldEnabled,LitigationHoldDuration,Office}
#$Policy = Invoke-Command -scriptblock {Get-Mailbox $m.name -IncludeInactiveMailbox}
#$m.MBXFound = $True
$Policy | ForEach-Object {
$m.IsInactiveMailbox = $_.IsInactiveMailbox
$m.PrimarySmtpAddress = $_.PrimarySmtpAddress
$m
}
}Catch{$m.NoMailbox = "$True";$m}
}
$Output | export-csv C:\retention\legalhold\test_check.csv -NoTypeInformation
I'm working on a script that allows me to fetch all of our Teams groups by their IDs and list the Id, Name, Owner(s), Member(s) and Guest(s).
The code works to a certain point, I get all the needed information, but it seems to be limiting it to 2 owners, 4 members and no guests...
When I run the code with adding it to a PSObject and simply do a write-host all the data is there, but I can't append it correctly to a CSV.
Code below, its either a limitation of the PSObject or I am doing something wrong/ missing something (hoping on the 2nd part ;) )
try
{
$host.Runspace.ThreadOptions = "ReuseThread"
# Get the credentials
Connect-AzureAD
# Connect to Microsoft Teams
Connect-MicrosoftTeams
# Get all the teams from tenant
[array]$teamColl = $null
[array]$ownerColl = $null
[array]$memberColl = $null
[array]$guestColl = $null
$teamColl=Get-Team
$date = Get-Date -Format "yyyy-MM-dd"
$OutputFile01 = "C:\temp\GetTeamsOwnersAndMembers-$date.csv"
# Clean file
Remove-Item $OutputFile01 -ErrorAction SilentlyContinue
$objectCollection=#()
$ownerCount = 0
$memberCount = 0
$guestCount = 0
# Loop through the teams
foreach($team in $teamColl)
{
$object = New-Object PSObject
# Get the Teams basic information
$object | Add-Member -type NoteProperty -Name ID -Value $team.GroupId
$object | Add-Member -type NoteProperty -Name TeamsName -Value $team.DisplayName
#$object | Add-Member -type NoteProperty -Name Description -Value $team.Description
# Get the Teams owners
$ownerColl = Get-TeamUser -GroupId $team.GroupId -Role Owner
$memberColl = Get-TeamUser -GroupId $team.GroupId -Role Member
$guestColl = Get-TeamUser -GroupId $team.GroupId -Role Guest
#Write-Host "$ownerColl"
#Write-Host "$memberColl"
#Write-Host "$guestColl"
# Loop through the owners
foreach($owner in $ownerColl)
{
$ownerCount++
$object | Add-Member -type NoteProperty -Name Owner_$ownerCount -Value $owner.User
}
# Loop through the members
foreach($member in $memberColl)
{
$memberCount++
$object | Add-Member -type NoteProperty -Name Member_$memberCount -Value $member.User
}
# Loop through the guests
foreach($guest in $guestColl)
{
$guestCount++
$object | Add-Member -type NoteProperty -Name Guest_$guestCount -Value $guest.User
}
# Reset counters
$ownerCount = 0
$memberCount = 0
$guestCount = 0
$objectCollection += $object
}
$objectCollection | Export-Csv $OutputFile01 -NoTypeInformation
}
catch [System.Exception]
{
Write-Host -ForegroundColor Red $_.Exception.ToString()
}
finally
{
Write-Host "Done"
}
Was able to solve it, I needed to use the -join to add the additional users :)
Working code:
try
{
$host.Runspace.ThreadOptions = "ReuseThread"
# Get the credentials
Connect-AzureAD
# Connect to Microsoft Teams
Connect-MicrosoftTeams
# Get all the teams from tenant
[array]$teamColl = $null
[array]$ownerColl = $null
[array]$memberColl = $null
[array]$guestColl = $null
$teamColl=Get-Team
$date = Get-Date -Format "yyyy-MM-dd"
$OutputFile01 = "C:\temp\GetTeamsOwnersAndMembers-$date.csv"
# Clean file
Remove-Item $OutputFile01 -ErrorAction SilentlyContinue
$GroupsCSV=#()
Write-Host -ForegroundColor Green "Processing Groups"
# Loop through the teams
foreach($team in $teamColl)
{
$ownerCount = 0
$memberCount = 0
$guestCount = 0
Write-Host -ForegroundColor Yellow -NoNewline "."
$ownerColl = Get-TeamUser -GroupId $team.GroupId -Role Owner
$ownerCollection=#()
# Loop through the owners
foreach($owner in $ownerColl)
{
$ownerCount++
$ownerCollection += $owner.User
}
$memberColl = Get-TeamUser -GroupId $team.GroupId -Role Member
$memberCollection=#()
# Loop through the members
foreach($member in $memberColl)
{
$memberCount++
$memberCollection += $member.User
}
$guestColl = Get-TeamUser -GroupId $team.GroupId -Role Guest
$guestCollection=#()
# Loop through the guests
foreach($guest in $guestColl)
{
$guestCount++
$guestCollection += $guest.User
}
# Create CSV file line
$GroupsRow = [pscustomobject]#{
GroupId = $team.GroupId
Name = $team.DisplayName
OwnerCount = $ownerCount
MemberCount = $memberCount
GuestCount = $guestCount
Owners = $ownerCollection -join " | "
Members = $memberCollection -join " | "
Guests = $guestCollection -join " | "
}
# Add to export array
$GroupsCSV+=$GroupsRow
}
# Export to CSV
Write-Host -ForegroundColor Green "`nCreating and exporting CSV file"
$GroupsCSV | Export-Csv -NoTypeInformation -Path $OutputFile01
}
catch [System.Exception]
{
Write-Host -ForegroundColor Red $_.Exception.ToString()
}
finally
{
Write-Host "Done"
}
I'm having trouble running this script. I am supposed to call using Strong authentication requirements which means users that enabled MFA using the MFA portal will be displayed if I am not mistaken.
Connect-MsolService
$role = getMsolRole -rolename "Company Administrator"
$rm = get-MsolRoleMember -RoleObjectId $role.ObjectId
foreach ($c in $rm)
{
Get-MsolUser -UserPrincipalName $c.EmailAddress | Select DisplayName, UserPrincipalName, #{N="MFA Status"; E={ if($_.StrongAuthenticationRequirements.Count -ne 0) { $_.StrongAuthenticationRequirements.State.toString() } else 'Disabled' }}
Error is
At line:9 char:225
+ ... { $_.StrongAuthenticationRequirements.State.toString() } else 'Disabl ...
+ ~
Missing statement block after 'else' keyword.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingStatementBlockAfterElse
Edited:
If even you can clarify the difference between calling MFA using StrongAuthenticationMethods and using StrongAuthenticationRequirements that will be good. So I can reproduce this code.
Problem with our MFA audit code is that it displays that system administrators are having MFA disabled even though they are claiming they are already enabled.
This is the audit code which returns admins with wildcard administrator are disabled.
Function Get-O365AdminMFAStatus{
$AdminData=#()
$objRole=#()
$Domain = $(get-addomain).dnsroot
$Log = "C:\temp\Audit\$Domain O365 Admin MFA Status $(get-date -f yyyy-MM-dd).csv"
try{
$Roles = Get-MsolRole | where {$_.name -LIKE "*Administrator*"}
$Roles = ($Roles).name
foreach ($Role in $Roles){
$Members = Get-MsolRoleMember -RoleObjectId (Get-MsolRole -RoleName $Role).ObjectId
foreach ($Member in $Members){
$MsUser = $Member | Get-MsolUser
if($MsUser.StrongAuthenticationMethods.Count -eq 0) {
$Enabled = "False"
write-host $Role - $Member.DisplayName "No MFA enabled" -foregroundcolor red
}
Else{
$Enabled = "True"
write-host $Role - $Member.DisplayName "MFA enabled" -foregroundcolor green
}
Try{
$Exist = [bool](Get-mailbox $MsUser.UserPrincipalName -erroraction SilentlyContinue)
if ($Exist){
$MBStats = Get-MailboxStatistics $MsUser.UserPrincipalName
$LastLogon = $MBstats.LastLogonTime
}
Else{
$LastLogon = "N/A"
}
}
Catch{
$LastLogon = "N/A"
}
$objRole = New-Object -TypeName PSObject
$objRole | Add-Member -MemberType NoteProperty -Name "Role Name" -Value $Role
$objRole | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $Member.DisplayName
$objRole | Add-Member -MemberType NoteProperty -Name "UPN" -Value $Member.UserPrincipalName
$objRole | Add-Member -MemberType NoteProperty -Name "Licensed" -Value $Member.IsLicensed
$objRole | Add-Member -MemberType NoteProperty -Name "Last Logon" -Value $LastLogon
$objRole | Add-Member -MemberType NoteProperty -Name "MFA Enabled?" -Value $Enabled
$AdminData += $objRole
}
}
$AdminData | Export-Csv -NoTypeInformation $Log
write-host ""
write-host "CSV Export Complete to $Log" -foregroundcolor yellow
}
Catch{
Write-host "There was an error: $($_.Exception.Message)"
}
}
Get-O365AdminMFAStatus
anyways if you have clarifications with the question I will edit.
From the error you receive it's quite obvious, what you should correct:
Missing statement block after 'else' keyword
You're missing curly braces after else so it should be:
else {'Disabled'}
I checked your cmdlet against my accounts (I have 2FA enabled) and StrongAuthenticationRequirements is empty object for me (checked on many accounts - attempt to clarify below). I think that you should use StrongAuthenticationMethods property instead. It contains information about channels that were configured for 2FA.
Finally, your code would look like this:
foreach ($c in $rm) {
Get-MsolUser -UserPrincipalName $c.EmailAddress | Select DisplayName, UserPrincipalName,
#{N="MFA Status"; E={ if($_.StrongAuthenticationMethods.Count -ne 0) { "$($_.StrongAuthenticationMethods.Count) methods found" } else {'Disabled'} }}
}
But you might notice some entries erroring with such info:
Get-MsolUser : Cannot bind argument to parameter 'UserPrincipalName' because it is null.
To get rid of that, important thing is to filter out ServicePrincipal members from Get-MsolRoleMember (for example, I have RMS and PowerBI Information Service, you might have none or different ones):
foreach ($c in $rm | Where-Object {$_.rolemembertype -eq 'user'}) {
Get-MsolUser -UserPrincipalName $c.EmailAddress | Select DisplayName, UserPrincipalName,
#{N="MFA Status"; E={ if($_.StrongAuthenticationMethods.Count -ne 0) { "$($_.StrongAuthenticationMethods.Count) methods found" } else {'Disabled'} }}
}
Clarification about StrongAuthenticationMethods and StrongAuthenticationRequirements
From what I read here it seems like StrongAuthenticationRequirements applies for per-user MFA. If your tenant is using Conditional Access based MFA, that property might be empty (checked on my tenant). So I guess StrongAuthenticationMethods is more reliable.
NOTE: I also tested the part of long code you have posted and it works correctly for me. And you have a typo in getMsolRole - should be Get-MsolRole
The script below works out great for identifying licensing for each individual host across multiple vCenters. What I am trying to include is the tag for each host as well. When I run the command individually it works fine, however when I run it as part of the code it is not functioning correctly. I highlighted the section if anyone can please take a look thanks. The line of code with the issue is commented out within the script below.
I attempted pushing this into a variable outside and insideof the foreach loop but I am receiving either 0 output, or the same output across each object.
Below is the actual command I put inside the foreach loop which is not functional.
(Get-VMhost | where{$_.Category -like "*Host*"})
$sw = [Diagnostics.Stopwatch]::StartNew()
# Declare our list of vCenters
[array]$vclistall = "vcenter01"
# Ensure were not connected to any vcenters
if ($DefaultVIServer.Count -gt 0) {
Disconnect-VIServer * -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Force:$true > $null
}
[array]$report = $null
foreach ($ScriptVCInstance in $vclistall) {
$connection = Connect-VIServer $ScriptVCInstance -ErrorAction SilentlyContinue
if ($connection) {
Write-Host "Collecting License Assets on vCenter $($ScriptVCInstance)"
# Get the license manager assets
$LicenseManager = Get-view LicenseManager
$LicenseAssignmentManager = Get-View $LicenseManager.LicenseAssignmentManager
$licenses = $LicenseAssignmentManager.GetType().GetMethod("QueryAssignedLicenses").Invoke($LicenseAssignmentManager, #($null))
#Format the asset into an object
foreach ($license in $Licenses) {
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name "vCenter" -Value $($connection.name)
$object | Add-Member -MemberType NoteProperty -Name "Entity" -Value $($license.EntityDisplayName)
$object | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $($license.Properties | where{$_.Key -eq 'ProductName'} | select -ExpandProperty Value)
$object | Add-Member -MemberType NoteProperty -Name "Product Version" -Calue $($License.Properties | where{$_.Key -eq 'FileVersion'} | select -ExpandProperty Value)
$object | Add-Member -MemberType NoteProperty -Name "License" -Value $($license.AssignedLicense.LicenseKey)
$object | Add-Member -MemberType NoteProperty -Name "License Name" -Value $($license.AssignedLicense.Name)
$object | Add-Member -MemberType NoteProperty -Name "Cost Unit" -Value $($license.Properties | where{$_.Key -eq 'CostUnit'} | select -ExpandProperty Value)
$object | Add-Member -MemberType NoteProperty -Name "Used License" -Value $($license.Properties | where{$_.Key -eq 'EntityCost'} | select -ExpandProperty Value)
$object | Add-Member -MemberType NoteProperty -Name "Total Licenses" -Value $($license.AssignedLicense.Total)
# Issue--> $object | Add-Member -MemberType NoteProperty -Name "Tag" -Value $(Get-VMhost | where{$_.Category -like "*Host*"})
$report += $object
if ($DefaultVIServer.Count -gt 0) {
Disconnect-VIServer * -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Force:$true > $null
}
} #end foreach $license
} else { # Else for if $connection
Write-warning "Not connected to vCenter $($ScriptVCInstance)"
} # endif $connection
} # End foreach $ScriptVCInstance
# write-out as a CSV file
Write-host "Exporting CSV $($env:USERPROFILE)\Licensed-Assets.csv"
$report | Sort-object "vCenter","License","Entity" | Export-csv "$($env:USERPROFILE)\Licensed-Assets.csv" -NoTypeInformation -UseCulture
$sw.Stop()
$sw.Elapsed
I have problems to list lists permissions using CSOM/PowerShell.
Variables / filters
$spSiteUrl = "https://mytenant.sharepoint.com"
Getting credentials
if($cred -eq $null)
{
$cred = Get-Credential
}
Loading Assemblies
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime") | Out-Null
Connecting into SharePoint and showing site title
Write-Host "Connecting to SharePoint"
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($spSiteUrl)
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($cred.UserName, $cred.Password)
$web = $ctx.Web
$ctx.Load($web)
$ctx.ExecuteQuery()
Write-host "Site Name : $($web.Title)"
Function listing "useful" applications
function getApps($web)
{
$appsArray = #()
$apps = $web.Lists
$ctx.Load($apps)
$ctx.ExecuteQuery()
Write-Host "List of aplications : "
foreach($app in $apps){
if($app.Hidden -eq $false)
{
$item = New-Object PSObject
$item | Add-Member -MemberType NoteProperty -Name 'Col1' -Value $($app.Title)
$item | Add-Member -MemberType NoteProperty -Name 'Col2' -Value $($app.HasUniqueRoleAssignments)
$item | Add-Member -MemberType NoteProperty -Name 'Col3' -Value $($app.RoleAssignments)
$item | Add-Member -MemberType NoteProperty -Name 'Col4' -Value $($app.BrowserFileHandling)
$item | Add-Member -MemberType NoteProperty -Name 'Col5' -Value $($app.EffectiveBasePermissions)
$item | Add-Member -MemberType NoteProperty -Name 'Col6' -Value $($app.Fields)
$item | Add-Member -MemberType NoteProperty -Name 'Col7' -Value $($app.WorkflowAssociations)
$appsArray += $item
}
}
$appsArray | Format-Table
}
Calling the function
getApps($web)
My problem is that :
$app.HasUniqueRoleAssignments
$app.RoleAssignments
$app.BrowserFileHandling
$app.EffectiveBasePermissions
$app.Fields
$app.WorkflowAssociations
Return me errors
The collection has not been initialized. It has not been requested or
the request has not been executed. It may need to be explicitly
requested..
The exception
The collection has not been initialized. It has not been requested or
the request has not been executed. It may need to be explicitly
requested.
Typically means that the property you are trying to work with (e.g. HasUniqueRoleAssignments) was not retrieved from the server yet.
You probably need an additional ExecuteQuery to load each app
foreach($app in $apps){
if($app.Hidden -eq $false)
{
$ctx.Load($app)
$ctx.ExecuteQuery()
You will eventually notice that some properties can't be retrieved with regular csom api (such as HasUniqueRoleAssignments) and for those you can use Gary's powershell, giving you the possibility to do what you otherwise would use linq
foreach($app in $apps){
if($app.Hidden -eq $false)
{
$ctx.Load($app)
Load-CSOMProperties -object $app -propertyNames #("HasUniqueRoleAssignments")
$ctx.ExecuteQuery()
https://gist.github.com/glapointe/cc75574a1d4a225f401b#file-load-csomproperties-ps1
https://sharepoint.stackexchange.com/questions/126221/spo-retrieve-hasuniqueroleassignements-property-using-powershell