Public/Invoke-M365QuickAssess.ps1
|
################################################################################################### # Function: Invoke-M365QuickAssess # Module: M365-QuickAssess # Author: Ryan Holderread - Rackspace Technology # Repository: https://github.com/HennepinCrawler/M365-QuickAssess # Version: 2.0 # Last Updated: 2026-03-25 # # Description: # Main entry point for the M365 Quick Assessment module. # Orchestrates workload collection, authentication, and JSON output. # Run via Launch-M365Assessment.ps1 for automatic dependency handling. ################################################################################################### # Ensure only loading modules for M365-QuickAssess if it has already been installed $env:PSModulePath = ( $env:PSModulePath -split ";" | Where-Object { $_ -notmatch "M365-QuickAssess" } ) -join ";" function Invoke-M365QuickAssess { [CmdletBinding()] param ( [ValidateSet("All","Entra","Exchange","Device","SharePoint","OneDrive","Teams","Purview","Azure","Copilot","PowerPlatform")] [string[]]$Workloads = @("All"), [string]$OutputPath = "C:\ProgramData\Rackspace-Technology" ) # ------------------------------------------------------------------- # Version Check # ------------------------------------------------------------------- try { $installedVersion = Get-InstalledModule -Name M365-QuickAssess -ErrorAction Stop $galleryVersion = Find-Module -Name M365-QuickAssess -Repository PSGallery -ErrorAction Stop if ( [version]$galleryVersion.Version -gt [version]$installedVersion.Version ) { Write-Host "" Write-Host " A new version of M365-QuickAssess is available!" -ForegroundColor Yellow Write-Host " Installed : $( $installedVersion.Version )" -ForegroundColor Yellow Write-Host " Available : $( $galleryVersion.Version )" -ForegroundColor Yellow Write-Host " Run: Install-Module M365-QuickAssess -Force -Scope CurrentUser" -ForegroundColor Yellow Write-Host "" } } catch { Write-Log "Version check failed: $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # Initialize Context # ------------------------------------------------------------------- $script:Context = @{ TenantId = $null TenantPrefix = $null GraphAccount = $null RunStamp = ( Get-Date ).ToString("yyyyMMdd-HHmmss-fff") OutputPath = $OutputPath WorkloadLabel = $null } # ------------------------------------------------------------------- # Workload Flags # ------------------------------------------------------------------- $RunAll = $Workloads -contains "All" $RunEntra = $RunAll -or ( $Workloads -contains "Entra" ) $RunDevice = $RunAll -or ( $Workloads -contains "Device" ) $RunExchange = $RunAll -or ( $Workloads -contains "Exchange" ) $RunSharePoint = $RunAll -or ( $Workloads -contains "SharePoint" ) $RunOneDrive = $RunAll -or ( $Workloads -contains "OneDrive" ) $RunTeams = $RunAll -or ( $Workloads -contains "Teams" ) $RunPurview = $RunAll -or ( $Workloads -contains "Purview" ) $RunAzure = $RunAll -or ( $Workloads -contains "Azure" ) $RunCopilot = $RunAll -or ( $Workloads -contains "Copilot" ) $script:Context.WorkloadLabel = if ( $RunAll ) { "Full" } else { ( $Workloads | Where-Object { $_ } | ForEach-Object { $_.ToLower() } | Sort-Object ) -join "+" } # ------------------------------------------------------------------- # Disclaimer / Intro # ------------------------------------------------------------------- Write-AssessmentBanner # ------------------------------------------------------------------- # Output Directory # ------------------------------------------------------------------- if ( -not ( Test-Path $OutputPath ) ) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } # ------------------------------------------------------------------- # Assessment Schema # ------------------------------------------------------------------- $Assessment = New-AssessmentSchema # ------------------------------------------------------------------- # Connect to Microsoft Graph (required for all workloads) # ------------------------------------------------------------------- $graphConnected = $false try { Connect-GraphService $graphConnected = $true } catch { Write-Log "Fatal: Graph connection failed. Cannot continue." "ERROR" } if ( -not $graphConnected ) { Write-Host "" Write-Host " Assessment could not start - Graph connection failed." -ForegroundColor Red Write-Host " Check the log file for details: $( $script:Context.OutputPath )" -ForegroundColor Yellow Write-Host "" return } # Log workloads after connection so tenant prefix is available Write-Log "Workloads: $( $Workloads -join ', ' )" # ------------------------------------------------------------------- # Metadata (always runs - fatal if it fails, nothing else will work) # ------------------------------------------------------------------- $metadataOk = $false try { Get-AssessmentMetadata -Assessment $Assessment $metadataOk = $true } catch { Write-Log "Fatal: Metadata collection failed. Cannot continue." "ERROR" } if ( -not $metadataOk ) { Write-Host "" Write-Host " Assessment could not start - metadata collection failed." -ForegroundColor Red Write-Host " Check the log file for details: $( $script:Context.OutputPath )" -ForegroundColor Yellow Write-Host "" return } # ------------------------------------------------------------------- # Power Platform License Check (always runs - gates PP collection) # ------------------------------------------------------------------- Test-PowerPlatformLicense -Assessment $Assessment # ------------------------------------------------------------------- # Entra # ------------------------------------------------------------------- if ( $RunEntra ) { Write-Log "--- Starting Entra workload ---" Invoke-WithErrorHandling "LicenseData" { Get-LicenseData -Assessment $Assessment } Invoke-WithErrorHandling "UserData" { Get-UserData -Assessment $Assessment } Invoke-WithErrorHandling "ConditionalAccess" { Get-ConditionalAccessData -Assessment $Assessment } Invoke-WithErrorHandling "UserUsage" { Get-UserUsage -Assessment $Assessment } } # ------------------------------------------------------------------- # Device Management # ------------------------------------------------------------------- if ( $RunDevice ) { Write-Log "--- Starting Device Management workload ---" Invoke-WithErrorHandling "DeviceManagement" { Get-DeviceManagementData -Assessment $Assessment } } # ------------------------------------------------------------------- # Exchange # ------------------------------------------------------------------- if ( $RunExchange ) { Write-Log "--- Starting Exchange workload ---" $exchangeConnected = $false try { Connect-ExchangeService $exchangeConnected = $true } catch { Write-Log "Exchange connection failed - skipping Exchange workload" "WARN" } if ( $exchangeConnected ) { Invoke-WithErrorHandling "ExchangeData" { Get-ExchangeData -Assessment $Assessment } Invoke-WithErrorHandling "ExchangeUsage" { Get-ExchangeUsage -Assessment $Assessment } Invoke-WithErrorHandling "DNSData" { Get-DNSData -Assessment $Assessment } } } # ------------------------------------------------------------------- # SharePoint # ------------------------------------------------------------------- if ( $RunSharePoint ) { Write-Log "--- Starting SharePoint workload ---" Invoke-WithErrorHandling "SharePointData" { Get-SharePointData -Assessment $Assessment } Invoke-WithErrorHandling "SharePointUsage" { Get-SharePointUsage -Assessment $Assessment } } # ------------------------------------------------------------------- # OneDrive # ------------------------------------------------------------------- if ( $RunOneDrive ) { Write-Log "--- Starting OneDrive workload ---" Invoke-WithErrorHandling "OneDriveData" { Get-OneDriveData -Assessment $Assessment } Invoke-WithErrorHandling "OneDriveUsage" { Get-OneDriveUsage -Assessment $Assessment } } # ------------------------------------------------------------------- # Teams # ------------------------------------------------------------------- if ( $RunTeams ) { Write-Log "--- Starting Teams workload ---" Invoke-WithErrorHandling "TeamsData" { Get-TeamsData -Assessment $Assessment } Invoke-WithErrorHandling "TeamsUsage" { Get-TeamsUsage -Assessment $Assessment } } # ------------------------------------------------------------------- # Azure # ------------------------------------------------------------------- if ( $RunAzure ) { Write-Log "--- Starting Azure workload ---" Invoke-WithErrorHandling "AzureData" { Get-AzureData -Assessment $Assessment } } # ------------------------------------------------------------------- # Copilot / AI # ------------------------------------------------------------------- if ( $RunCopilot ) { Write-Log "--- Starting Copilot / AI workload ---" Invoke-WithErrorHandling "CopilotData" { Get-CopilotData -Assessment $Assessment } } # ------------------------------------------------------------------- # Purview # ------------------------------------------------------------------- if ( $RunPurview ) { Write-Log "--- Starting Purview workload ---" Invoke-WithErrorHandling "PurviewData" { Get-PurviewData -Assessment $Assessment } } # ------------------------------------------------------------------- # Power Platform (stubbed - coming in a future release) # ------------------------------------------------------------------- if ( $Workloads -contains "PowerPlatform" ) { Write-Log "Power Platform collection is not yet implemented in this version." "WARN" } # ------------------------------------------------------------------- # Write Output # ------------------------------------------------------------------- $outputFile = Write-AssessmentOutput -Assessment $Assessment # ------------------------------------------------------------------- # Done # ------------------------------------------------------------------- Write-Host "" Write-Host "====================================================================" -ForegroundColor Cyan Write-Host " Assessment Complete" -ForegroundColor Green Write-Host "====================================================================" -ForegroundColor Cyan Write-Host "" Write-Host " Output saved to:" -ForegroundColor White Write-Host " $outputFile" -ForegroundColor Yellow Write-Host "" Write-Host " Please send the JSON and log file to your Rackspace Solutions Architect." -ForegroundColor White Write-Host "" } ################################################################################################### # Helper: Invoke-WithErrorHandling # Wraps a workload scriptblock so a single failure does not abort the entire run. ################################################################################################### function Invoke-WithErrorHandling { param ( [string]$Name, [scriptblock]$ScriptBlock ) try { Write-Host $ScriptBlock Write-Log "--- Starting Device Management workload ---" & $ScriptBlock } catch { Write-Log "$Name failed unexpectedly: $( $_.Exception.Message )" "ERROR" } } ################################################################################################### # Helper: Write-AssessmentBanner # Displays the intro disclaimer and countdown. ################################################################################################### function Write-AssessmentBanner { Write-Host "" Write-Host "=====================================================================" -ForegroundColor Cyan Write-Host " Microsoft 365 Quick Assessment (Read-Only) - Rackspace Technology" -ForegroundColor Cyan Write-Host "=====================================================================" -ForegroundColor Cyan Write-Host "" Write-Host " This script performs READ-ONLY operations against your Microsoft 365 tenant." Write-Host " No changes will be made to your environment." Write-Host "" Write-Host " Workloads collected:" Write-Host " Entra ID, Exchange Online, SharePoint, OneDrive" Write-Host " Microsoft Teams, Device Management, Azure, Copilot, Purview" Write-Host "" Write-Host " Execution time will vary depending on tenant size:" Write-Host " ~1,000 users ~20 minutes" Write-Host " Larger tenants may take significantly longer" Write-Host "" Write-Host " Requirements:" -ForegroundColor DarkYellow Write-Host " PowerShell 7+" Write-Host " Microsoft Graph PowerShell SDK" Write-Host " Exchange Online Management Module" Write-Host " SharePoint Online Management Shell" Write-Host " Az.Accounts + Az.Resources" Write-Host "" Write-Host " Permissions Required (Read-Only):" -ForegroundColor DarkYellow Write-Host " Directory.Read.All Policy.Read.All" Write-Host " User.Read.All RoleManagement.Read.Directory" Write-Host " Reports.Read.All Group.Read.All" Write-Host " Team.ReadBasic.All Channel.ReadBasic.All" Write-Host " AuditLog.Read.All Files.Read.All" Write-Host " Sites.Read.All SecurityEvents.Read.All" Write-Host " DeviceManagementManagedDevices.Read.All" Write-Host " DeviceManagementConfiguration.Read.All" Write-Host " DeviceManagementServiceConfig.Read.All" Write-Host " DeviceManagementApps.Read.All" Write-Host "" Write-Host " Please ensure you authenticate with the correct tenant." -ForegroundColor DarkYellow Write-Host "" Write-Host "=====================================================================" -ForegroundColor Cyan Write-Host " What happens with your results?" -ForegroundColor Yellow Write-Host " Rackspace Technology specializes in Tenant to Tenant migrations and" Write-Host " Managed Services for your Microsoft 365 Eco system. Whether that is" Write-Host " on-prem to Microsoft 365 migrations, tenant-to-tenant migrations," Write-Host " Copilot readiness, Entra ID, Exchange Online, Teams, and more." Write-Host " Send us your JSON report and someone will reach out to walk you" Write-Host " through the findings and discuss how we can help." Write-Host "" Write-Host "---------------------------------------------------------------------" -ForegroundColor DarkGray Write-Host " Legal Disclaimer:" -ForegroundColor Yellow Write-Host " This script is provided as-is without warranty of any kind." Write-Host " The author assumes no liability for any damages resulting from its use." Write-Host " All findings should be reviewed and validated prior to taking action." Write-Host "---------------------------------------------------------------------" -ForegroundColor DarkGray Write-Host "" Start-AssessmentCountdown -Seconds 10 -Message "Assessment will begin in" } ################################################################################################### # Helper: New-AssessmentSchema # Returns the empty ordered hashtable that all workloads populate. ################################################################################################### function New-AssessmentSchema { return [ordered]@{ Metadata = [ordered]@{ TenantName = $null TenantId = $null AssessmentDate = $null AssessmentVersion = "2.0" SecureScore = $null SecureScoreMax = $null SecureScorePercent = $null } Summary = [ordered]@{ UserCount = $null GuestUserCount = $null GlobalAdminCount = $null MFAEnabledPercent = $null LegacyAuthEnabled = $null IsHybridIdentity = $null HasDirectorySync = $null CustomDomainCount = $null Mailboxes = $null MailboxCount = $null TotalMailboxSizeGB = $null SharePointSiteCount = $null SharePointTotalStorageGB = $null OneDriveCount = $null OneDriveTotalStorageGB = $null TeamCount = $null } Licensing = [ordered]@{ SkuSummary = @() TotalLicensedUsers = $null UnlicensedUsers = $null } Users = [ordered]@{ ActiveUserCount = $null InactiveUserCount = $null } Groups = [ordered]@{ TotalGroups = $null M365Groups = $null SecurityGroups = $null MailEnabledSecurityGroups = $null DistributionLists = $null DynamicGroupsTotal = $null DynamicM365Groups = $null DynamicSecurityGroups = $null } ConditionalAccess = [ordered]@{ HasConditionalAccessPolicies = $null ConditionalAccessPolicyCount = $null HasMFAEnforcement = $null HasTrustedLocations = $null Policies = @() } DeviceManagement = [ordered]@{ IntuneConfigured = $null MDMConfigured = $null HasAutopilot = $null AutopilotDeviceCount = $null ManagedDeviceCount = $null HasManagedDevices = $null WindowsDeviceCount = $null iOSDeviceCount = $null AndroidDeviceCount = $null macOSDeviceCount = $null CompliantDeviceCount = $null HasCompliantDevices = $null HasHybridJoinedDevices = $null HybridJoinedDeviceCount = $null HasEntraJoinedDevices = $null EntraJoinedDeviceCount = $null ConfigurationPolicyCount = $null CompliancePolicyCount = $null DeviceProfileCount = $null ManagedAppCount = $null AppProtectionPolicyCount = $null HasDefenderOnboardedDevices = $null DefenderOnboardedDeviceCount = $null } Exchange = [ordered]@{ MailboxCount = $null UserMailboxCount = $null SharedMailboxCount = $null ResourceMailboxCount = $null LargeMailboxCount = $null ArchiveMailboxCount = $null ActiveMailboxCount = $null TotalMailboxSizeGB = $null HasConnectors = $null ConnectorCount = $null HasTransportRules = $null TransportRuleCount = $null AcceptedDomainCount = $null MailboxWithDelegatesCount = $null HasExchangeRetentionPolicies = $null IsExchangeHybrid = $null HasRemoteMailboxes = $null RemoteMailboxCount = $null HasOnPremMailboxes = $null MailContactCount = $null MailUserCount = $null HasContacts = $null DistributionGroupCount = $null MailEnabledSecurityGroupCount = $null DynamicDistributionGroupCount = $null HasDistributionLists = $null } SharePoint = [ordered]@{ SharePointSiteCount = $null SharePointTotalStorageGB = $null LargeSiteCount = $null ActiveSiteCount = $null HasExternalSharing = $null HasUniquePermissions = $null HasSharePointRetentionPolicies = $null HasAppCatalog = $null HasSharePointCustomApps = $null SharePointCustomAppCount = $null } OneDrive = [ordered]@{ OneDriveCount = $null OneDriveTotalStorageGB = $null LargeOneDriveCount = $null ActiveOneDriveCount = $null HasUnprovisionedOneDrives = $null } Teams = [ordered]@{ TeamCount = $null ActiveUserCount = $null HasPrivateChannels = $null PrivateChannelCount = $null HasSharedChannels = $null SharedChannelCount = $null HasTeamsApps = $null HasTeamsGuestAccess = $null HasTeamsExternalAccess = $null } Applications = [ordered]@{ HasEnterpriseApplications = $null CustomerOwnedApps = $null ManagedIdentities = $null HasAppRegistrations = $null AppRegistrations = $null } PowerPlatform = [ordered]@{ HasPowerPlatform = $null Licenses = $null PowerPlatformEnvironmentCount = $null HasDataverse = $null PowerAppCount = $null PowerAutomateFlowCount = $null HasCustomConnectors = $null HasDLPPolicies = $null } Azure = [ordered]@{ HasAzureSubscriptions = $null AzureSubscriptionCount = $null HasSubscriptionAccess = $null } Purview = [ordered]@{ RetentionPolicies = $null RetentionLabels = $null DlpPolicies = $null SensitivityLabels = $null EdiscoveryCases = $null } AI = [ordered]@{ HasAILicenses = $null AILicenseCount = $null HasCopilot = $null HasAIAgents = $null AIAgentCount = $null IsAIDataReady = $null HasAIGovernance = $null } Findings = @() } } ################################################################################################### # Helper: Write-AssessmentOutput # Cleans nulls and writes the final JSON file. Returns the output file path. ################################################################################################### function Write-AssessmentOutput { param ( $Assessment ) $file = Join-Path $script:Context.OutputPath ( "$( $script:Context.TenantPrefix )-$( $script:Context.WorkloadLabel )-$( $script:Context.RunStamp ).json" ) $logFile = Join-Path $script:Context.OutputPath ( "$( $script:Context.TenantPrefix )-$( $script:Context.WorkloadLabel )-$( $script:Context.RunStamp ).log" ) Write-Log "Writing output to $file" $clean = Remove-NullProperties -Object $Assessment $clean | ConvertTo-Json -Depth 10 | Out-File $file -Encoding utf8 Write-Log "Assessment output written successfully" return $file } |