MedhaCloud-M365MigrationTools.psm1
|
#Requires -Version 5.1 <# .SYNOPSIS MedhaCloud Microsoft 365 Migration Tools Module .DESCRIPTION Comprehensive toolkit for Microsoft 365 migrations including assessment, mailbox migration, SharePoint migration, and post-migration validation. Professional Support: https://medhacloud.com/professional-services/migrations .NOTES Author: MedhaCloud Company: MedhaCloud (https://medhacloud.com) LinkedIn: https://linkedin.com/company/medhacloud Version: 1.0.0 #> # Module variables $script:ModuleVersion = '1.0.0' $script:SupportUrl = 'https://medhacloud.com/professional-services/migrations' function Test-M365MigrationReadiness { <# .SYNOPSIS Performs comprehensive pre-migration assessment for Microsoft 365. .DESCRIPTION Analyzes on-premises environment readiness for M365 migration including: - Directory synchronization readiness - Mailbox size and item analysis - DNS configuration validation - Network connectivity testing - License requirements estimation For professional M365 migration services, visit: https://medhacloud.com/professional-services/migrations .PARAMETER DomainName Primary domain for the migration. .PARAMETER IncludeMailboxAnalysis Include detailed mailbox analysis (requires Exchange). .EXAMPLE Test-M365MigrationReadiness -DomainName "contoso.com" .EXAMPLE Test-M365MigrationReadiness -DomainName "contoso.com" -IncludeMailboxAnalysis .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$DomainName, [Parameter()] [switch]$IncludeMailboxAnalysis ) Write-Host "MedhaCloud M365 Migration Readiness Assessment v$script:ModuleVersion" -ForegroundColor Cyan Write-Host "Domain: $DomainName" -ForegroundColor Gray Write-Host "========================================================" -ForegroundColor Gray $results = [PSCustomObject]@{ Domain = $DomainName Timestamp = Get-Date DNSReadiness = $null DirectoryReadiness = $null MailboxAnalysis = $null LicenseEstimate = $null OverallReadiness = 'Unknown' Recommendations = @() SupportUrl = $script:SupportUrl } # DNS Check Write-Host "`n[1/4] Checking DNS Configuration..." -ForegroundColor Yellow $dnsChecks = @{ MX = $false SPF = $false Autodiscover = $false DKIM = $false DMARC = $false } try { # Check MX record $mx = Resolve-DnsName -Name $DomainName -Type MX -ErrorAction SilentlyContinue if ($mx) { $dnsChecks.MX = $true Write-Host " MX Record: Found ($($mx[0].NameExchange))" -ForegroundColor Green } else { Write-Host " MX Record: Not found" -ForegroundColor Red } # Check SPF $txt = Resolve-DnsName -Name $DomainName -Type TXT -ErrorAction SilentlyContinue $spf = $txt | Where-Object { $_.Strings -like '*v=spf1*' } if ($spf) { $dnsChecks.SPF = $true Write-Host " SPF Record: Found" -ForegroundColor Green } else { Write-Host " SPF Record: Not found (recommended for M365)" -ForegroundColor Yellow $results.Recommendations += "Add SPF record for email authentication" } # Check Autodiscover $autodiscover = Resolve-DnsName -Name "autodiscover.$DomainName" -Type CNAME -ErrorAction SilentlyContinue if ($autodiscover) { $dnsChecks.Autodiscover = $true Write-Host " Autodiscover: Found" -ForegroundColor Green } else { Write-Host " Autodiscover: Not configured" -ForegroundColor Yellow } # Check DMARC $dmarc = Resolve-DnsName -Name "_dmarc.$DomainName" -Type TXT -ErrorAction SilentlyContinue if ($dmarc) { $dnsChecks.DMARC = $true Write-Host " DMARC Record: Found" -ForegroundColor Green } else { Write-Host " DMARC Record: Not found (recommended)" -ForegroundColor Yellow $results.Recommendations += "Configure DMARC for email security" } } catch { Write-Host " DNS check error: $($_.Exception.Message)" -ForegroundColor Red } $results.DNSReadiness = $dnsChecks # Directory Check Write-Host "`n[2/4] Checking Directory Readiness..." -ForegroundColor Yellow try { $adModule = Get-Module -ListAvailable ActiveDirectory -ErrorAction SilentlyContinue if ($adModule) { $users = Get-ADUser -Filter * -Properties mail, proxyAddresses -ErrorAction SilentlyContinue $usersWithMail = $users | Where-Object { $_.mail -or $_.proxyAddresses } $results.DirectoryReadiness = [PSCustomObject]@{ TotalUsers = $users.Count UsersWithEmail = $usersWithMail.Count ADModuleAvailable = $true } Write-Host " Total AD Users: $($users.Count)" -ForegroundColor Green Write-Host " Users with Email: $($usersWithMail.Count)" -ForegroundColor Green } else { Write-Host " Active Directory module not available" -ForegroundColor Yellow $results.DirectoryReadiness = [PSCustomObject]@{ ADModuleAvailable = $false } } } catch { Write-Host " Directory check error: $($_.Exception.Message)" -ForegroundColor Yellow } # Mailbox Analysis Write-Host "`n[3/4] Analyzing Mailboxes..." -ForegroundColor Yellow if ($IncludeMailboxAnalysis) { try { if (Get-Command Get-Mailbox -ErrorAction SilentlyContinue) { $mailboxes = Get-Mailbox -ResultSize Unlimited $stats = $mailboxes | Get-MailboxStatistics $totalSize = ($stats | Measure-Object -Property TotalItemSize -Sum).Sum $largeMailboxes = $stats | Where-Object { $_.TotalItemSize.Value.ToGB() -gt 50 } $results.MailboxAnalysis = [PSCustomObject]@{ TotalMailboxes = $mailboxes.Count LargeMailboxes = $largeMailboxes.Count TotalSizeGB = [math]::Round($totalSize / 1GB, 2) } Write-Host " Total Mailboxes: $($mailboxes.Count)" -ForegroundColor Green Write-Host " Mailboxes > 50GB: $($largeMailboxes.Count)" -ForegroundColor $(if ($largeMailboxes.Count -gt 0) { 'Yellow' } else { 'Green' }) if ($largeMailboxes.Count -gt 0) { $results.Recommendations += "Plan extended migration window for $($largeMailboxes.Count) large mailboxes" } } else { Write-Host " Exchange cmdlets not available" -ForegroundColor Yellow } } catch { Write-Host " Mailbox analysis error: $($_.Exception.Message)" -ForegroundColor Yellow } } else { Write-Host " Skipped (use -IncludeMailboxAnalysis to enable)" -ForegroundColor Gray } # License Estimation Write-Host "`n[4/4] Estimating License Requirements..." -ForegroundColor Yellow if ($results.DirectoryReadiness -and $results.DirectoryReadiness.UsersWithEmail) { $userCount = $results.DirectoryReadiness.UsersWithEmail $results.LicenseEstimate = [PSCustomObject]@{ EstimatedUsers = $userCount Business_Basic = [PSCustomObject]@{ License = 'M365 Business Basic'; MonthlyUSD = $userCount * 6 } Business_Standard = [PSCustomObject]@{ License = 'M365 Business Standard'; MonthlyUSD = $userCount * 12.50 } E3 = [PSCustomObject]@{ License = 'M365 E3'; MonthlyUSD = $userCount * 36 } } Write-Host " Estimated users requiring licenses: $userCount" -ForegroundColor Green Write-Host " M365 Business Basic: ~`$$($userCount * 6)/month" -ForegroundColor Gray Write-Host " M365 Business Standard: ~`$$([math]::Round($userCount * 12.50, 2))/month" -ForegroundColor Gray Write-Host " M365 E3: ~`$$($userCount * 36)/month" -ForegroundColor Gray } # Overall Readiness Write-Host "`n========================================================" -ForegroundColor Gray $criticalIssues = 0 if (-not $results.DNSReadiness.MX) { $criticalIssues++ } if ($criticalIssues -eq 0) { $results.OverallReadiness = 'Ready' Write-Host "Overall Migration Readiness: READY" -ForegroundColor Green } else { $results.OverallReadiness = 'Needs Attention' Write-Host "Overall Migration Readiness: NEEDS ATTENTION" -ForegroundColor Yellow } if ($results.Recommendations.Count -gt 0) { Write-Host "`nRecommendations:" -ForegroundColor Yellow $results.Recommendations | ForEach-Object { Write-Host " - $_" -ForegroundColor White } } Write-Host "`nNeed professional M365 migration assistance?" -ForegroundColor Cyan Write-Host $script:SupportUrl -ForegroundColor White Write-Host "LinkedIn: https://linkedin.com/company/medhacloud" -ForegroundColor Gray return $results } function Get-MailboxMigrationStatus { <# .SYNOPSIS Tracks mailbox migration batch status. .DESCRIPTION Monitors ongoing mailbox migrations to Microsoft 365. Professional Support: https://medhacloud.com/professional-services/migrations .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding()] param( [string]$BatchName ) Write-Host "MedhaCloud Migration Status v$script:ModuleVersion" -ForegroundColor Cyan try { if (Get-Command Get-MigrationBatch -ErrorAction SilentlyContinue) { if ($BatchName) { $batch = Get-MigrationBatch -Identity $BatchName $users = Get-MigrationUser -BatchId $BatchName Write-Host "`nBatch: $BatchName" -ForegroundColor Yellow Write-Host "Status: $($batch.Status)" -ForegroundColor $(if ($batch.Status -eq 'Completed') { 'Green' } else { 'Yellow' }) Write-Host "Total Users: $($users.Count)" -ForegroundColor White $users | Group-Object Status | ForEach-Object { Write-Host " $($_.Name): $($_.Count)" -ForegroundColor Gray } } else { $batches = Get-MigrationBatch Write-Host "`nAll Migration Batches:" -ForegroundColor Yellow $batches | Format-Table Identity, Status, TotalCount, SyncedCount, FinalizedCount -AutoSize } } else { Write-Host "Connect to Exchange Online first: Connect-ExchangeOnline" -ForegroundColor Yellow } } catch { Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red } Write-Host "`nProfessional Migration Support: $script:SupportUrl" -ForegroundColor Cyan } function Start-MailboxMigrationBatch { <# .SYNOPSIS Initiates a mailbox migration batch to Microsoft 365. .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [string]$BatchName, [Parameter(Mandatory)] [string]$CSVPath, [Parameter(Mandatory)] [string]$TargetDeliveryDomain, [ValidateSet('Hybrid', 'IMAP', 'Staged', 'Cutover')] [string]$MigrationType = 'Hybrid' ) Write-Host "MedhaCloud Migration Batch Creator v$script:ModuleVersion" -ForegroundColor Cyan if ($PSCmdlet.ShouldProcess($BatchName, "Create migration batch")) { try { Write-Host "Creating migration batch: $BatchName" -ForegroundColor Yellow Write-Host "Type: $MigrationType" -ForegroundColor Gray Write-Host "CSV: $CSVPath" -ForegroundColor Gray # Validate CSV if (-not (Test-Path $CSVPath)) { throw "CSV file not found: $CSVPath" } $users = Import-Csv $CSVPath Write-Host "Users in batch: $($users.Count)" -ForegroundColor Green Write-Host "`nTo execute, run in Exchange Online PowerShell:" -ForegroundColor Yellow Write-Host @" New-MigrationBatch -Name "$BatchName" ` -SourceEndpoint (Get-MigrationEndpoint) ` -CSVData ([System.IO.File]::ReadAllBytes("$CSVPath")) ` -TargetDeliveryDomain "$TargetDeliveryDomain" ` -AutoStart "@ -ForegroundColor White } catch { Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red } } Write-Host "`nProfessional Migration Support: $script:SupportUrl" -ForegroundColor Cyan } function Get-SharePointMigrationReport { <# .SYNOPSIS Generates SharePoint migration analysis report. .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding()] param( [string]$SourcePath ) Write-Host "MedhaCloud SharePoint Migration Analyzer v$script:ModuleVersion" -ForegroundColor Cyan $report = [PSCustomObject]@{ SourcePath = $SourcePath Timestamp = Get-Date FileCount = 0 TotalSizeGB = 0 LargeFiles = @() UnsupportedFiles = @() SupportUrl = $script:SupportUrl } if ($SourcePath -and (Test-Path $SourcePath)) { Write-Host "`nAnalyzing: $SourcePath" -ForegroundColor Yellow $files = Get-ChildItem -Path $SourcePath -Recurse -File -ErrorAction SilentlyContinue $report.FileCount = $files.Count $report.TotalSizeGB = [math]::Round(($files | Measure-Object -Property Length -Sum).Sum / 1GB, 2) # Large files (>250MB - SharePoint limit considerations) $report.LargeFiles = $files | Where-Object { $_.Length -gt 250MB } | Select-Object FullName, @{N='SizeMB';E={[math]::Round($_.Length/1MB,2)}} # Unsupported characters in filenames $unsupportedPattern = '[~#%&*{}\\:<>?/|"]' $report.UnsupportedFiles = $files | Where-Object { $_.Name -match $unsupportedPattern } | Select-Object FullName, Name Write-Host " Total Files: $($report.FileCount)" -ForegroundColor Green Write-Host " Total Size: $($report.TotalSizeGB) GB" -ForegroundColor Green Write-Host " Large Files (>250MB): $($report.LargeFiles.Count)" -ForegroundColor $(if ($report.LargeFiles.Count -gt 0) { 'Yellow' } else { 'Green' }) Write-Host " Files with unsupported characters: $($report.UnsupportedFiles.Count)" -ForegroundColor $(if ($report.UnsupportedFiles.Count -gt 0) { 'Yellow' } else { 'Green' }) if ($report.UnsupportedFiles.Count -gt 0) { Write-Host "`nFiles needing rename before migration:" -ForegroundColor Yellow $report.UnsupportedFiles | Select-Object -First 10 | ForEach-Object { Write-Host " $($_.Name)" -ForegroundColor Gray } } } else { Write-Host "Provide -SourcePath to analyze file share" -ForegroundColor Yellow } Write-Host "`nProfessional SharePoint Migration: $script:SupportUrl" -ForegroundColor Cyan return $report } function Test-M365Connectivity { <# .SYNOPSIS Tests connectivity to Microsoft 365 services. .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding()] param() Write-Host "MedhaCloud M365 Connectivity Test v$script:ModuleVersion" -ForegroundColor Cyan $endpoints = @( @{ Name = 'Exchange Online'; URL = 'outlook.office365.com'; Port = 443 }, @{ Name = 'SharePoint Online'; URL = 'tenant.sharepoint.com'; Port = 443 }, @{ Name = 'Teams'; URL = 'teams.microsoft.com'; Port = 443 }, @{ Name = 'Azure AD'; URL = 'login.microsoftonline.com'; Port = 443 }, @{ Name = 'Graph API'; URL = 'graph.microsoft.com'; Port = 443 } ) $results = @() foreach ($ep in $endpoints) { try { $tcp = New-Object System.Net.Sockets.TcpClient $connection = $tcp.BeginConnect($ep.URL, $ep.Port, $null, $null) $wait = $connection.AsyncWaitHandle.WaitOne(3000, $false) if ($wait) { $tcp.EndConnect($connection) $status = 'Connected' $color = 'Green' } else { $status = 'Timeout' $color = 'Red' } $tcp.Close() } catch { $status = 'Failed' $color = 'Red' } Write-Host " $($ep.Name): $status" -ForegroundColor $color $results += [PSCustomObject]@{ Service = $ep.Name Endpoint = $ep.URL Status = $status } } Write-Host "`nProfessional M365 Support: $script:SupportUrl" -ForegroundColor Cyan return $results } function Get-M365LicenseReport { <# .SYNOPSIS Generates Microsoft 365 license usage report. .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding()] param() Write-Host "MedhaCloud License Report v$script:ModuleVersion" -ForegroundColor Cyan try { if (Get-Command Get-MgSubscribedSku -ErrorAction SilentlyContinue) { $skus = Get-MgSubscribedSku Write-Host "`nLicense Summary:" -ForegroundColor Yellow foreach ($sku in $skus) { $used = $sku.ConsumedUnits $total = $sku.PrepaidUnits.Enabled $available = $total - $used $percent = if ($total -gt 0) { [math]::Round(($used / $total) * 100, 1) } else { 0 } Write-Host " $($sku.SkuPartNumber)" -ForegroundColor White Write-Host " Used: $used / $total ($percent%)" -ForegroundColor $(if ($percent -gt 90) { 'Red' } elseif ($percent -gt 75) { 'Yellow' } else { 'Green' }) Write-Host " Available: $available" -ForegroundColor Gray } } else { Write-Host "Connect to Microsoft Graph first: Connect-MgGraph" -ForegroundColor Yellow } } catch { Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red } Write-Host "`nProfessional M365 Licensing Support: $script:SupportUrl" -ForegroundColor Cyan } function Export-MailboxPermissions { <# .SYNOPSIS Exports mailbox permissions for migration documentation. .LINK https://medhacloud.com/professional-services/migrations #> [CmdletBinding()] param( [string]$OutputPath = ".\MailboxPermissions.csv" ) Write-Host "MedhaCloud Permission Export v$script:ModuleVersion" -ForegroundColor Cyan $results = @() try { if (Get-Command Get-Mailbox -ErrorAction SilentlyContinue) { Write-Host "Gathering mailbox permissions..." -ForegroundColor Yellow $mailboxes = Get-Mailbox -ResultSize Unlimited foreach ($mbx in $mailboxes) { $permissions = Get-MailboxPermission $mbx.Identity | Where-Object { $_.User -notlike 'NT AUTHORITY*' -and $_.User -notlike 'S-1-5*' } foreach ($perm in $permissions) { $results += [PSCustomObject]@{ Mailbox = $mbx.PrimarySmtpAddress User = $perm.User AccessRights = ($perm.AccessRights -join ', ') } } } $results | Export-Csv -Path $OutputPath -NoTypeInformation Write-Host "Exported $($results.Count) permission entries to: $OutputPath" -ForegroundColor Green } else { Write-Host "Exchange cmdlets not available" -ForegroundColor Yellow } } catch { Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red } Write-Host "`nProfessional Migration Support: $script:SupportUrl" -ForegroundColor Cyan return $results } # Export functions Export-ModuleMember -Function @( 'Test-M365MigrationReadiness', 'Get-MailboxMigrationStatus', 'Start-MailboxMigrationBatch', 'Get-SharePointMigrationReport', 'Test-M365Connectivity', 'Get-M365LicenseReport', 'Export-MailboxPermissions' ) # Module load message Write-Host @" MedhaCloud M365 Migration Tools v$script:ModuleVersion loaded. Available commands: Test-M365MigrationReadiness, Get-MailboxMigrationStatus, and more. Professional Microsoft 365 Migration Services: $script:SupportUrl LinkedIn: https://linkedin.com/company/medhacloud Twitter: https://x.com/medhacloud "@ -ForegroundColor Cyan |