├── .gitattributes ├── MenuLayout.gif ├── Archive ├── image-data.jpg ├── image-iam.jpg └── image-main.jpg ├── AzureSecurityConfig.json ├── Modules ├── AzureSecuritySharePointStorage.psm1 ├── AzureSecurityTagCompliance.psm1 ├── AzureSecurityOneDriveLinks.psm1 ├── AzureSecurityTeamsMembershipChanges.psm1 ├── AzureSecuritySettings.psm1 ├── AzureSecurityDataProtection.psm1 ├── AzureSecurityIAM.psm1 ├── AzureSecurityCore.psm1 ├── AzureSecuritySharePoint.psm1 ├── AzureSecurityInfrastructure.psm1 └── AzureSecurityOffice365.psm1 ├── Build-SingleFile.ps1 ├── Fix-GraphModules.ps1 ├── Restart-PowerShellSession.ps1 ├── .gitignore ├── Start-AzureSecurityReport.ps1 ├── README.md ├── RELEASE_NOTES_v3.5.md ├── CHANGELOG.md └── AzureSecurityReport-Modular.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /MenuLayout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteffMet/Azure-Office365-Security-Reporting/HEAD/MenuLayout.gif -------------------------------------------------------------------------------- /Archive/image-data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteffMet/Azure-Office365-Security-Reporting/HEAD/Archive/image-data.jpg -------------------------------------------------------------------------------- /Archive/image-iam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteffMet/Azure-Office365-Security-Reporting/HEAD/Archive/image-iam.jpg -------------------------------------------------------------------------------- /Archive/image-main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteffMet/Azure-Office365-Security-Reporting/HEAD/Archive/image-main.jpg -------------------------------------------------------------------------------- /AzureSecurityConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "3.1", 3 | "TenantId": "", 4 | "ApplicationId": "", 5 | "AutoConnect": false, 6 | "CertificateThumbprint": "", 7 | "LastUpdated": "2025-06-28 15:10:09", 8 | "LogLevel": "INFO", 9 | "UseServicePrincipal": false, 10 | "ExportPath": ".\\Reports" 11 | } 12 | -------------------------------------------------------------------------------- /Modules/AzureSecuritySharePointStorage.psm1: -------------------------------------------------------------------------------- 1 | # SharePoint Site Storage Usage Reporting Module 2 | function Get-SharePointSiteStorageUsage { 3 | <# 4 | .SYNOPSIS 5 | Displays storage usage for all SharePoint Online sites. 6 | .DESCRIPTION 7 | Uses Get-SPOSite to list storage usage and quota for each site. UK English comments. 8 | .NOTES 9 | Read-only, fits menu-driven format. 10 | #> 11 | # Connect to SharePoint Online if not already connected 12 | if (-not (Get-Module -ListAvailable -Name Microsoft.Online.SharePoint.PowerShell)) { 13 | Write-Warning "SharePoint Online module not found. Please install it." 14 | return 15 | } 16 | Import-Module Microsoft.Online.SharePoint.PowerShell 17 | # Prompt for connection if needed 18 | try { 19 | $null = Get-SPOSite -Limit 1 20 | } catch { 21 | Connect-SPOService 22 | } 23 | $sites = Get-SPOSite -Limit All 24 | foreach ($site in $sites) { 25 | Write-Output "Site: $($site.Url) - Used: $($site.StorageUsageCurrent) MB / Quota: $($site.StorageQuota) MB" 26 | } 27 | } 28 | Export-ModuleMember -Function Get-SharePointSiteStorageUsage 29 | -------------------------------------------------------------------------------- /Modules/AzureSecurityTagCompliance.psm1: -------------------------------------------------------------------------------- 1 | # Azure Resource Tag Compliance Reporting Module 2 | function Get-AzureResourceTagCompliance { 3 | <# 4 | .SYNOPSIS 5 | Lists resources missing required tags or with non-compliant tag values. 6 | .DESCRIPTION 7 | Uses Get-AzResource to audit tag compliance across resources. UK English comments. 8 | .NOTES 9 | Read-only, fits menu-driven format. 10 | #> 11 | # Connect to Azure if not already connected 12 | if (-not (Get-Module -ListAvailable -Name Az.Resources)) { 13 | Write-Warning "Az.Resources module not found. Please install it." 14 | return 15 | } 16 | Import-Module Az.Resources 17 | if (-not (Get-AzContext)) { 18 | Connect-AzAccount 19 | } 20 | $requiredTags = @('Owner', 'Environment') 21 | $resources = Get-AzResource 22 | foreach ($resource in $resources) { 23 | foreach ($tag in $requiredTags) { 24 | if (-not $resource.Tags.ContainsKey($tag)) { 25 | Write-Output "Resource: $($resource.Name) ($($resource.ResourceType)) is missing tag: $tag" 26 | } 27 | } 28 | } 29 | } 30 | Export-ModuleMember -Function Get-AzureResourceTagCompliance 31 | -------------------------------------------------------------------------------- /Modules/AzureSecurityOneDriveLinks.psm1: -------------------------------------------------------------------------------- 1 | # OneDrive Sharing Links Audit Reporting Module 2 | function Get-OneDriveSharingLinksAudit { 3 | <# 4 | .SYNOPSIS 5 | Lists all active sharing links in OneDrive for Business. 6 | .DESCRIPTION 7 | Uses Microsoft Graph PowerShell to enumerate sharing links, expiration, and access level. UK English comments. 8 | .NOTES 9 | Read-only, fits menu-driven format. 10 | #> 11 | # Connect to Microsoft Graph if not already connected 12 | if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Users)) { 13 | Write-Warning "Microsoft.Graph.Users module not found. Please install it." 14 | return 15 | } 16 | Import-Module Microsoft.Graph.Users 17 | try { 18 | $null = Get-MgUser -Top 1 19 | } catch { 20 | Connect-MgGraph -Scopes "User.Read.All, Files.Read.All, Sites.Read.All" 21 | } 22 | $users = Get-MgUser -Filter "accountEnabled eq true" -All 23 | foreach ($user in $users) { 24 | $sharedLinks = Get-MgUserInsightShared -UserId $user.Id 25 | Write-Output "User: $($user.DisplayName) ($($user.UserPrincipalName))" 26 | foreach ($link in $sharedLinks) { 27 | Write-Output " Shared Item: $($link.ResourceReference.WebUrl) - Last Shared: $($link.LastSharedDateTime)" 28 | } 29 | } 30 | } 31 | Export-ModuleMember -Function Get-OneDriveSharingLinksAudit 32 | -------------------------------------------------------------------------------- /Modules/AzureSecurityTeamsMembershipChanges.psm1: -------------------------------------------------------------------------------- 1 | # Teams Channel Membership Changes Reporting Module 2 | function Get-TeamsChannelMembershipChanges { 3 | <# 4 | .SYNOPSIS 5 | Reports current membership of all Microsoft Teams channels. 6 | .DESCRIPTION 7 | Uses MicrosoftTeams PowerShell module to list members of each channel. For historical changes, use Unified Audit Log (not included here). 8 | .NOTES 9 | Read-only, fits menu-driven format. UK English comments. 10 | #> 11 | # Connect to Teams if not already connected 12 | if (-not (Get-Module -ListAvailable -Name MicrosoftTeams)) { 13 | Write-Warning "MicrosoftTeams module not found. Please install it." 14 | return 15 | } 16 | Import-Module MicrosoftTeams 17 | if (-not (Get-Team)) { 18 | Connect-MicrosoftTeams 19 | } 20 | $teams = Get-Team 21 | foreach ($team in $teams) { 22 | $channels = Get-TeamChannel -GroupId $team.GroupId 23 | foreach ($channel in $channels) { 24 | $members = Get-TeamChannelUser -GroupId $team.GroupId -DisplayName $channel.DisplayName 25 | Write-Output "Team: $($team.DisplayName), Channel: $($channel.DisplayName)" 26 | foreach ($member in $members) { 27 | Write-Output " Member: $($member.User) ($($member.Role))" 28 | } 29 | } 30 | } 31 | } 32 | Export-ModuleMember -Function Get-TeamsChannelMembershipChanges 33 | -------------------------------------------------------------------------------- /Build-SingleFile.ps1: -------------------------------------------------------------------------------- 1 | # Build script to combine modules into single deployable file 2 | param( 3 | [string]$OutputPath = "AzureSecurityReport-Compiled.ps1" 4 | ) 5 | 6 | Write-Host "Building single-file deployment..." -ForegroundColor Yellow 7 | 8 | $ModulesPath = Join-Path $PSScriptRoot "Modules" 9 | $MainScript = Join-Path $PSScriptRoot "AzureSecurityReport-Modular.ps1" 10 | 11 | # Combine all module content 12 | $CombinedContent = @" 13 | #Requires -Version 7.0 14 | <# 15 | .SYNOPSIS 16 | Azure Security Report - Compiled Single File 17 | .DESCRIPTION 18 | Auto-generated from modular components 19 | .AUTHOR 20 | github.com/SteffMet 21 | .VERSION 22 | 2.0-Compiled 23 | .DATE 24 | $(Get-Date -Format "yyyy-MM-dd") 25 | #> 26 | 27 | "@ 28 | 29 | # Add each module content (without Import-Module statements) 30 | Get-ChildItem -Path $ModulesPath -Filter "*.psm1" | ForEach-Object { 31 | $Content = Get-Content $_.FullName -Raw 32 | # Remove Import-Module lines and Export-ModuleMember lines 33 | $CleanContent = $Content -replace "Import-Module.*", "" -replace "Export-ModuleMember.*", "" 34 | $CombinedContent += "`n# === $($_.BaseName) Module ===`n" 35 | $CombinedContent += $CleanContent 36 | $CombinedContent += "`n" 37 | } 38 | 39 | # Add main script content (without Import-Module statements) 40 | $MainContent = Get-Content $MainScript -Raw 41 | $CleanMainContent = $MainContent -replace "Import-Module.*", "" 42 | $CombinedContent += "`n# === Main Script ===`n" 43 | $CombinedContent += $CleanMainContent 44 | 45 | # Write to output file 46 | $CombinedContent | Out-File -FilePath $OutputPath -Encoding UTF8 47 | 48 | Write-Host "Compiled script created: $OutputPath" -ForegroundColor Green 49 | Write-Host "File size: $([math]::Round((Get-Item $OutputPath).Length / 1KB, 2)) KB" -ForegroundColor Cyan 50 | -------------------------------------------------------------------------------- /Fix-GraphModules.ps1: -------------------------------------------------------------------------------- 1 | # Fix-GraphModules.ps1 - Utility script to resolve Microsoft Graph assembly conflicts 2 | # Run this if you encounter assembly conflicts with Microsoft Graph modules 3 | 4 | Write-Host "Fixing Microsoft Graph module assembly conflicts..." -ForegroundColor Yellow 5 | 6 | try { 7 | # Disconnect from Microsoft Graph 8 | Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Yellow 9 | try { 10 | Disconnect-MgGraph -ErrorAction SilentlyContinue 11 | Write-Host "Disconnected from Microsoft Graph." -ForegroundColor Green 12 | } catch { 13 | Write-Host "No active Graph session to disconnect." -ForegroundColor Gray 14 | } 15 | 16 | # Remove all Microsoft Graph modules 17 | Write-Host "Removing all Microsoft Graph modules..." -ForegroundColor Yellow 18 | $GraphModules = Get-Module -Name "Microsoft.Graph*" -ErrorAction SilentlyContinue 19 | 20 | if ($GraphModules.Count -eq 0) { 21 | Write-Host "No Microsoft Graph modules currently loaded." -ForegroundColor Gray 22 | } else { 23 | foreach ($Module in $GraphModules) { 24 | Write-Host "Removing: $($Module.Name)" -ForegroundColor Gray 25 | Remove-Module -Name $Module.Name -Force -ErrorAction SilentlyContinue 26 | } 27 | Write-Host "Removed $($GraphModules.Count) Microsoft Graph modules." -ForegroundColor Green 28 | } 29 | 30 | # Force garbage collection 31 | Write-Host "Clearing memory cache..." -ForegroundColor Yellow 32 | [System.GC]::Collect() 33 | [System.GC]::WaitForPendingFinalizers() 34 | [System.GC]::Collect() 35 | Write-Host "Memory cache cleared." -ForegroundColor Green 36 | 37 | Write-Host "`nMicrosoft Graph modules have been reset." -ForegroundColor Green 38 | Write-Host "You can now run your Azure Security Report script." -ForegroundColor Green 39 | 40 | } catch { 41 | Write-Host "Error during Graph module reset: $($_.Exception.Message)" -ForegroundColor Red 42 | Write-Host "You may need to restart PowerShell completely." -ForegroundColor Yellow 43 | } 44 | 45 | Write-Host "`nPress Enter to continue..." -ForegroundColor Cyan 46 | Read-Host 47 | -------------------------------------------------------------------------------- /Restart-PowerShellSession.ps1: -------------------------------------------------------------------------------- 1 | # Restart-PowerShellSession.ps1 2 | # This script restarts PowerShell to completely clear Graph module assemblies 3 | 4 | Write-Host "Microsoft Graph assembly conflicts detected." -ForegroundColor Yellow 5 | Write-Host "PowerShell session restart is required to resolve this issue." -ForegroundColor Yellow 6 | Write-Host "" 7 | Write-Host "Options:" -ForegroundColor Cyan 8 | Write-Host "1. Automatic restart (recommended)" -ForegroundColor White 9 | Write-Host "2. Manual restart instructions" -ForegroundColor White 10 | Write-Host "" 11 | 12 | $Choice = Read-Host "Select option (1 or 2)" 13 | 14 | switch ($Choice) { 15 | "1" { 16 | Write-Host "" 17 | Write-Host "Restarting PowerShell session..." -ForegroundColor Green 18 | Write-Host "The script will automatically run after restart." -ForegroundColor Yellow 19 | 20 | $ScriptPath = Join-Path $PSScriptRoot "AzureSecurityReport-Modular.ps1" 21 | 22 | # Create a batch file to restart PowerShell and run the script 23 | $BatchContent = @" 24 | @echo off 25 | echo Restarting PowerShell session to resolve Graph module conflicts... 26 | timeout /t 2 /nobreak >nul 27 | pwsh.exe -ExecutionPolicy Bypass -File "$ScriptPath" 28 | pause 29 | "@ 30 | 31 | $BatchFile = Join-Path $env:TEMP "RestartAzureSecurityReport.bat" 32 | $BatchContent | Out-File -FilePath $BatchFile -Encoding ASCII 33 | 34 | # Start the batch file and exit current session 35 | Start-Process -FilePath $BatchFile -WindowStyle Normal 36 | exit 37 | } 38 | "2" { 39 | Write-Host "" 40 | Write-Host "Manual restart instructions:" -ForegroundColor Cyan 41 | Write-Host "1. Close this PowerShell window" -ForegroundColor White 42 | Write-Host "2. Open a new PowerShell 7 window" -ForegroundColor White 43 | Write-Host "3. Navigate to: $PSScriptRoot" -ForegroundColor White 44 | Write-Host "4. Run: .\AzureSecurityReport-Modular.ps1" -ForegroundColor White 45 | Write-Host "" 46 | Write-Host "Press Enter to exit..." -ForegroundColor Gray 47 | Read-Host 48 | exit 49 | } 50 | default { 51 | Write-Host "Invalid selection. Please restart PowerShell manually." -ForegroundColor Red 52 | exit 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Log files 2 | *.log 3 | AzureSecurityReport_*.log 4 | 5 | # PowerShell module cache 6 | *.cache 7 | 8 | # Temporary files 9 | *.tmp 10 | *.temp 11 | test_write.txt 12 | 13 | # CSV export files (optional - users may want to keep these) 14 | *_Report_*.csv 15 | 16 | # PowerShell ISE temp files 17 | *.psess 18 | 19 | # Visual Studio Code settings (optional - may want to keep workspace settings) 20 | .vscode/ 21 | !.vscode/settings.json 22 | !.vscode/tasks.json 23 | !.vscode/launch.json 24 | !.vscode/keybindings.json 25 | 26 | # Windows thumbnail files 27 | Thumbs.db 28 | ehthumbs.db 29 | 30 | # Folder config file 31 | Desktop.ini 32 | 33 | # Recycle Bin used on file shares 34 | $RECYCLE.BIN/ 35 | 36 | # Windows Installer files 37 | *.cab 38 | *.msi 39 | *.msm 40 | *.msp 41 | 42 | # Windows shortcuts 43 | *.lnk 44 | 45 | # macOS files 46 | .DS_Store 47 | .AppleDouble 48 | .LSOverride 49 | 50 | # Icon must end with two \r 51 | Icon 52 | 53 | # Thumbnails 54 | ._* 55 | 56 | # Files that might appear in the root of a volume 57 | .DocumentRevisions-V100 58 | .fseventsd 59 | .Spotlight-V100 60 | .TemporaryItems 61 | .Trashes 62 | .VolumeIcon.icns 63 | .com.apple.timemachine.donotpresent 64 | 65 | # Directories potentially created on remote AFP share 66 | .AppleDB 67 | .AppleDesktop 68 | Network Trash Folder 69 | Temporary Items 70 | .apdisk 71 | 72 | # Backup files 73 | *.bak 74 | *.backup 75 | *~ 76 | 77 | # Node.js (if any build tools are added) 78 | node_modules/ 79 | npm-debug.log* 80 | yarn-debug.log* 81 | yarn-error.log* 82 | 83 | # Python (if any Python tools are added) 84 | __pycache__/ 85 | *.py[cod] 86 | *$py.class 87 | *.so 88 | .Python 89 | env/ 90 | venv/ 91 | ENV/ 92 | 93 | # IDE files 94 | *.swp 95 | *.swo 96 | *~ 97 | .project 98 | .metadata 99 | *.launch 100 | .settings/ 101 | *.sublime-workspace 102 | 103 | # Test results 104 | TestResults/ 105 | *.trx 106 | *.coverage 107 | 108 | # Build outputs 109 | bin/ 110 | obj/ 111 | out/ 112 | 113 | # Package files 114 | *.nuget 115 | packages/ 116 | 117 | # Authentication cache files (if any) 118 | *.auth 119 | *.token 120 | *.credentials 121 | 122 | # Personal notes and drafts 123 | NOTES.md 124 | TODO.md 125 | DRAFT_*.md 126 | 127 | # Local environment files 128 | .env 129 | .env.local 130 | .env.*.local 131 | 132 | # Editor directories and files 133 | .idea/ 134 | *.suo 135 | *.ntvs* 136 | *.njsproj 137 | *.sln 138 | *.sw? -------------------------------------------------------------------------------- /Start-AzureSecurityReport.ps1: -------------------------------------------------------------------------------- 1 | # Start-AzureSecurityReport.ps1 2 | # Wrapper script to ensure clean execution of Azure Security Report 3 | 4 | param( 5 | [switch]$Modular, 6 | [switch]$SingleFile 7 | ) 8 | 9 | # Check PowerShell version 10 | if ($PSVersionTable.PSVersion.Major -lt 7) { 11 | Write-Host "PowerShell 7 or higher is required. Current version: $($PSVersionTable.PSVersion)" -ForegroundColor Red 12 | Write-Host "Please install PowerShell 7 from: https://github.com/PowerShell/PowerShell/releases" -ForegroundColor Yellow 13 | exit 1 14 | } 15 | 16 | # Set execution policy for current session 17 | Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force 18 | 19 | Write-Host "=========================================" -ForegroundColor Cyan 20 | Write-Host "Azure Security Report Launcher" -ForegroundColor Yellow 21 | Write-Host "=========================================" -ForegroundColor Cyan 22 | Write-Host "" 23 | 24 | # Determine which script to run 25 | $ScriptToRun = $null 26 | 27 | if ($Modular) { 28 | $ScriptToRun = "AzureSecurityReport-Modular.ps1" 29 | } elseif ($SingleFile) { 30 | $ScriptToRun = "AzureSecurityReport.ps1" 31 | } else { 32 | Write-Host "Which version would you like to run?" -ForegroundColor Cyan 33 | Write-Host "1. Modular version (recommended)" -ForegroundColor White 34 | Write-Host "2. Single-file version" -ForegroundColor White 35 | Write-Host "" 36 | 37 | do { 38 | $Choice = Read-Host "Select option (1 or 2)" 39 | switch ($Choice) { 40 | "1" { 41 | $ScriptToRun = "AzureSecurityReport-Modular.ps1" 42 | break 43 | } 44 | "2" { 45 | $ScriptToRun = "AzureSecurityReport.ps1" 46 | break 47 | } 48 | default { 49 | Write-Host "Invalid selection. Please enter 1 or 2." -ForegroundColor Red 50 | } 51 | } 52 | } while (-not $ScriptToRun) 53 | } 54 | 55 | # Check if script exists 56 | $ScriptPath = Join-Path $PSScriptRoot $ScriptToRun 57 | if (-not (Test-Path $ScriptPath)) { 58 | Write-Host "Error: Script not found at $ScriptPath" -ForegroundColor Red 59 | exit 1 60 | } 61 | 62 | Write-Host "" 63 | Write-Host "Starting $ScriptToRun..." -ForegroundColor Green 64 | Write-Host "Note: If you encounter Microsoft Graph assembly conflicts, this script will offer to restart PowerShell automatically." -ForegroundColor Yellow 65 | Write-Host "" 66 | 67 | # Run the selected script 68 | try { 69 | & $ScriptPath 70 | } catch { 71 | Write-Host "Error running script: $($_.Exception.Message)" -ForegroundColor Red 72 | 73 | if ($_.Exception.Message -like "*Assembly with same name is already loaded*") { 74 | Write-Host "" 75 | Write-Host "Assembly conflict detected. Would you like to restart PowerShell? (Y/N)" -ForegroundColor Yellow 76 | $Restart = Read-Host 77 | 78 | if ($Restart -eq 'Y' -or $Restart -eq 'y') { 79 | $RestartScript = Join-Path $PSScriptRoot "Restart-PowerShellSession.ps1" 80 | if (Test-Path $RestartScript) { 81 | & $RestartScript 82 | } else { 83 | Write-Host "Please close PowerShell and start a new session, then run this script again." -ForegroundColor Yellow 84 | } 85 | } 86 | } 87 | } 88 | 89 | Write-Host "" 90 | Write-Host "Press Enter to exit..." -ForegroundColor Gray 91 | Read-Host 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛡️ Azure & Office 365 Security Report 2 | 3 | > **A comprehensive PowerShell 7 security auditing tool for Azure and Office 365 environments** 4 | 5 | This project provides a modular, read-only security assessment script to help organizations identify security gaps, compliance issues, and cost optimization opportunities across Azure and Office 365 infrastructure. 6 | 7 |
8 | 9 | ![Menu Layout Demo](MenuLayout.gif) 10 | 11 |
12 | 13 | ### 🛡️ New Security Reporting Modules 14 | | Module | Description | 15 | |--------|-------------| 16 | | **Azure Storage Security** | Detects public blob containers, validates encryption, checks HTTPS enforcement, and analyzes network access rules. | 17 | | **Azure Key Vault Security** | Monitors certificate expiration, analyzes access policies, validates network restrictions, and checks soft delete/purge protection. | 18 | | **Network Security Groups (NSG)** | Identifies dangerous firewall rules, internet-exposed services, SSH/RDP exposure, and database port security. | 19 | | **SharePoint & OneDrive Security** | Analyzes external sharing, OneDrive usage, Data Loss Prevention (DLP) policies, and guest access. | 20 | | **Configuration Management** | Supports encrypted credential storage, auto-connect, customizable export paths, and configuration backup/restore. | 21 | | **Teams Channel Membership Changes** | Reports current membership of all Teams channels, including users and guests. | 22 | | **SharePoint Site Storage Usage** | Reports storage usage for all SharePoint sites, highlighting quota and usage trends. | 23 | | **Azure Resource Tag Compliance** | Audits Azure resources for required tags and compliance with tagging policies. | 24 | | **OneDrive Sharing Links Audit** | Audits OneDrive sharing links, reporting on externally shared files and access levels. | 25 | 26 | ### 📈 Enhanced Reporting 27 | - 5 new timestamped CSV reports. 28 | - Risk-based scoring (Critical/High/Medium/Low). 29 | - Security recommendations for each finding. 30 | - Executive summary dashboards. 31 | 32 | 33 | ## 🌟 Key Features 34 | 35 | ### 🔐 Azure Security Auditing 36 | - **Identity & Access Management (IAM)** 37 | - Multi-Factor Authentication (MFA) status analysis 38 | - Guest user access review and reporting 39 | - Password expiry policy assessment 40 | - Conditional Access policy evaluation 41 | 42 | - **🛡️ Data Protection** 43 | - Azure VM TLS configuration analysis (Azure Resource Graph) 44 | - Virtual Machine disk encryption status 45 | - Security compliance reporting 46 | 47 | - **🏗️ Infrastructure Security** 48 | - Azure Storage Account security configuration 49 | - Public blob container detection 50 | - Azure Key Vault security assessment 51 | - Certificate expiration monitoring 52 | - Network Security Group (NSG) analysis 53 | - Dangerous firewall rules detection 54 | - Azure Resource Tag Compliance: Audit resources for required tags and compliance 55 | 56 | ### ☁️ Office 365 Security Auditing 57 | - **📊 License Management** 58 | - Comprehensive license usage analysis 59 | - Cost optimization recommendations 60 | - Unassigned license detection and reporting 61 | 62 | - **👤 User Account Security** 63 | - Inactive account detection (90+ days) 64 | - Licensed but inactive account identification 65 | - Security risk assessment 66 | 67 | - **📧 Email Security** 68 | - Mailbox forwarding rule analysis 69 | - External email forwarding detection 70 | - Exchange Online security assessment 71 | 72 | - **👥 Microsoft Teams Security** 73 | - External access configuration review 74 | - Teams with external users/guests reporting 75 | - Teams security posture assessment 76 | - Teams Channel Membership Changes: Report current membership of all Teams channels 77 | 78 | - **📁 SharePoint & OneDrive Security** 79 | - SharePoint sharing settings analysis 80 | - SharePoint Site Storage Usage: Report storage usage for all SharePoint sites 81 | - OneDrive usage and security monitoring 82 | - Data Loss Prevention (DLP) policy guidance 83 | - External sharing detection 84 | - OneDrive Sharing Links Audit: Audit externally shared files and access levels 85 | 86 | ### ⚙️ Configuration Management 87 | - **Settings & Credential Storage** 88 | - Azure Service Principal configuration 89 | - Secure credential storage for automation 90 | - Auto-connect functionality 91 | - Customizable export paths 92 | - Configuration management 93 | 94 | --- 95 | ### Read-Only Operations 96 | - ✅ **No modifications** to any Azure or Office 365 configurations 97 | - ✅ **Audit trail** - All actions logged with timestamps 98 | - ✅ **Secure authentication** using modern authentication flows 99 | - ✅ **Least privilege** - Only requires read permissions 100 | 101 | ### Data Privacy 102 | - 🔒 No sensitive data stored or transmitted 103 | - 🔒 Local CSV exports with configurable file paths 104 | - 🔒 Comprehensive logging for compliance auditing 105 | ## 🚀 Quick Start 106 | 107 | --- 108 | ### Prerequisites 109 | 110 | Ensure you have PowerShell 7.0+ installed: 111 | ```powershell 112 | # Check PowerShell version 113 | $PSVersionTable.PSVersion 114 | ``` 115 | 116 | ### Installation 117 | 118 | 1. **Clone the repository:** 119 | ```bash 120 | git clone https://github.com/SteffMet/Azure-Office365-Security-Reporting.git 121 | cd Azure-Office365-Security-Reporting 122 | ``` 123 | 124 | 2. **Run the launcher script (Recommended):** 125 | ```powershell 126 | .\Start-AzureSecurityReport.ps1 127 | ``` 128 | 129 | **Or run directly:** 130 | ```powershell 131 | # Modular version (recommended) 132 | .\AzureSecurityReport-Modular.ps1 133 | ``` 134 | 135 | ## 🔑 Required Permissions 136 | 137 | ### Azure AD Roles 138 | - **Recommended**: Global Reader or Security Reader 139 | - **Minimum**: Directory Readers + specific object permissions 140 | 141 | ### Azure Subscription Permissions 142 | - **Storage Security**: Storage Account Contributor (read-only) 143 | - **Key Vault Security**: Key Vault Reader 144 | - **Network Security**: Network Contributor (read-only) 145 | - **VM Security**: Virtual Machine Contributor (read-only) 146 | 147 | ### Microsoft Graph API Permissions 148 | ``` 149 | User.Read.All 150 | Directory.Read.All 151 | Policy.Read.ConditionalAccess 152 | UserAuthenticationMethod.Read.All 153 | Organization.Read.All 154 | Reports.Read.All 155 | AuditLog.Read.All 156 | Sites.Read.All 157 | ``` 158 | 159 | ### Office 365 Permissions 160 | - **Exchange Online**: View-Only Organization Management 161 | - **Microsoft Teams**: Teams Administrator (read-only operations) 162 | - **SharePoint Online**: SharePoint Administrator (read-only operations) 163 | 164 | --- 165 | ## 🛠️ Troubleshooting 166 | 167 | ### Microsoft Graph Assembly Conflicts 168 | If you encounter "Assembly with same name is already loaded" errors: 169 | 170 | **🔧 Quick Fix Options:** 171 | 172 | 1. **Use the launcher script (Recommended)**: 173 | ```powershell 174 | .\Start-AzureSecurityReport.ps1 175 | ``` 176 | 177 | 2. **Use the fix script**: 178 | ```powershell 179 | .\Fix-GraphModules.ps1 180 | ``` 181 | 182 | 3. **Manual session restart**: 183 | ```powershell 184 | # Exit PowerShell completely 185 | exit 186 | 187 | # Start fresh PowerShell 7 session 188 | pwsh 189 | cd "path\to\Azure-Office365-Security-Reporting" 190 | .\AzureSecurityReport-Modular.ps1 191 | ``` 192 | 193 | 4. **Automatic restart helper**: 194 | ```powershell 195 | .\Restart-PowerShellSession.ps1 196 | ``` 197 | 198 | **🔍 Why This Happens:** 199 | Microsoft Graph PowerShell modules use .NET assemblies that can conflict when loaded multiple times in the same session. This is a known limitation of the Microsoft Graph SDK. 200 | 201 | ### Common Issues 202 | 203 | | Issue | Solution | 204 | |-------|----------| 205 | | Module import errors | Run `Install-Module` as Administrator | 206 | | Authentication failures | Verify account permissions and retry | 207 | | CSV export errors | Check file path permissions | 208 | | Graph API rate limits | Wait and retry after a few minutes | 209 | 210 | --- 211 |
212 | 213 | **⭐ If this project helps you, please consider giving it a star! ⭐** 214 | 215 | Made with ❤️ by [SteffMet](https://github.com/SteffMet) 216 | 217 | *Last Updated: June 28, 2025* 218 | 219 |
220 | 221 | 222 | -------------------------------------------------------------------------------- /RELEASE_NOTES_v3.5.md: -------------------------------------------------------------------------------- 1 | # 🚀 Release Notes - Azure & Office 365 Security Report v3.5 2 | 3 | ## Release Date: June 28, 2025 4 | 5 | ### 🎉 Major Version Release - v3.5 6 | 7 | This is a significant feature release that introduces comprehensive Azure infrastructure security assessment capabilities, advanced configuration management, and SharePoint/OneDrive security analysis. 8 | 9 | --- 10 | 11 | ## 🆕 What's New 12 | 13 | ### ⚙️ **Settings & Configuration Management** 14 | - **New Settings Module** (`AzureSecuritySettings.psm1`) 15 | - **Service Principal Authentication** - Store Azure AD app registration details securely 16 | - **Auto-Connect Functionality** - Automated authentication using saved credentials 17 | - **Certificate-Based Authentication** - Support for unattended operations 18 | - **Configurable Export Paths** - Customize where reports are saved 19 | - **Configuration Backup/Restore** - Save and restore settings 20 | 21 | ### 🏗️ **Azure Infrastructure Security Suite** 22 | - **New Infrastructure Module** (`AzureSecurityInfrastructure.psm1`) 23 | - **Azure Storage Security Analysis** 24 | - Public blob container detection 25 | - Storage account encryption validation 26 | - HTTPS enforcement checking 27 | - Network access rule analysis 28 | - Risk-based scoring (Critical/High/Medium/Low) 29 | 30 | - **Azure Key Vault Security Assessment** 31 | - Certificate expiration monitoring (30-day alerts) 32 | - Access policy analysis (detect excessive permissions) 33 | - Network access restriction validation 34 | - Soft delete and purge protection status 35 | - Security recommendation engine 36 | 37 | - **Network Security Groups (NSG) Analysis** 38 | - Dangerous firewall rule detection 39 | - Internet-exposed SSH/RDP detection 40 | - Database port security validation 41 | - Wildcard rule identification 42 | - Critical security risk alerts 43 | 44 | ### 📁 **SharePoint & OneDrive Security** 45 | - **New SharePoint Module** (`AzureSecuritySharePoint.psm1`) 46 | - **SharePoint Sharing Settings Analysis** 47 | - External sharing configuration review 48 | - Site-level security assessment 49 | - Guest access detection 50 | - **OneDrive Security & Usage Monitoring** 51 | - Storage usage analysis 52 | - Inactive OneDrive detection 53 | - Security posture assessment 54 | - **Data Loss Prevention (DLP) Policy Guidance** 55 | - DLP policy recommendations 56 | - Security best practices 57 | 58 | ### 📊 **Enhanced Reporting & User Experience** 59 | - **5 New CSV Reports** with timestamped exports 60 | - **Updated Main Menu** - Now 7 options (was 4) 61 | - **Risk-Based Scoring** - Critical/High/Medium/Low classifications 62 | - **Security Recommendations** - Actionable advice for each finding 63 | - **Progress Indicators** - Better user feedback during scans 64 | - **Enhanced Error Handling** - More resilient to API failures 65 | 66 | --- 67 | 68 | ## 🔧 Technical Improvements 69 | 70 | ### 🏛️ **Architecture Enhancements** 71 | - **3 New PowerShell Modules** added to the modular architecture 72 | - **Improved Module Loading** - Better dependency management 73 | - **Enhanced Error Handling** - Graceful degradation for missing permissions 74 | - **Configuration System** - JSON-based settings storage 75 | 76 | ### 🔐 **Authentication Improvements** 77 | - **Service Principal Support** - Automated authentication for CI/CD scenarios 78 | - **Certificate-Based Auth** - More secure than password-based authentication 79 | - **Auto-Connect Feature** - Streamlined user experience 80 | - **Fallback Authentication** - Graceful fallback to interactive auth 81 | 82 | ### 📈 **Performance Optimizations** 83 | - **Efficient Resource Enumeration** - Better handling of large tenants 84 | - **Progress Tracking** - Real-time feedback during long operations 85 | - **Memory Management** - Improved handling of large datasets 86 | - **API Rate Limiting** - Better handling of Graph API throttling 87 | 88 | --- 89 | 90 | ## 📋 New PowerShell Modules Required 91 | 92 | The following additional modules are now required: 93 | 94 | ```powershell 95 | # New Azure modules for v3.5 96 | Az.KeyVault # Key Vault security analysis 97 | Az.Network # NSG and network security 98 | Az.Storage # Storage account security 99 | 100 | # New Microsoft Graph module 101 | Microsoft.Graph.Sites # SharePoint and OneDrive analysis 102 | ``` 103 | 104 | --- 105 | 106 | ## 🎯 New Menu Structure 107 | 108 | ### Updated Main Menu (7 options): 109 | ``` 110 | 1. Identity and Access Management Report (Azure AD) 111 | 2. Data Protection Report (Azure) 112 | 3. Azure Infrastructure Security Report # 🆕 NEW 113 | 4. Office 365 Security Report 114 | 5. SharePoint & OneDrive Security Report # 🆕 NEW 115 | 6. Settings & Configuration # 🆕 NEW 116 | 7. Exit 117 | ``` 118 | 119 | ### New Submenus: 120 | - **Azure Infrastructure Security** (Options 1-4) 121 | - **SharePoint & OneDrive Security** (Options 1-4) 122 | - **Settings & Configuration** (Options 1-6) 123 | 124 | --- 125 | 126 | ## 📊 New Reports Generated 127 | 128 | | Report | File Format | Content | 129 | |--------|-------------|---------| 130 | | Storage Security | `Storage_Security_Report_YYYYMMDD_HHMMSS.csv` | Storage account security configuration | 131 | | Key Vault Security | `KeyVault_Security_Report_YYYYMMDD_HHMMSS.csv` | Key vault security assessment | 132 | | Network Security | `Network_Security_Report_YYYYMMDD_HHMMSS.csv` | NSG rules and network security | 133 | | SharePoint Sharing | `SharePoint_Sharing_Report_YYYYMMDD_HHMMSS.csv` | SharePoint sharing settings | 134 | | OneDrive Security | `OneDrive_Security_Report_YYYYMMDD_HHMMSS.csv` | OneDrive usage and security | 135 | 136 | --- 137 | 138 | ## 🔑 New Permissions Required 139 | 140 | ### Azure Subscription Permissions: 141 | - **Storage Account Contributor** (read-only) - For storage security analysis 142 | - **Key Vault Reader** - For Key Vault security assessment 143 | - **Network Contributor** (read-only) - For NSG analysis 144 | 145 | ### Microsoft Graph API Permissions: 146 | - **Sites.Read.All** - For SharePoint and OneDrive analysis 147 | 148 | --- 149 | 150 | ## 🚀 Getting Started with v3.5 151 | 152 | ### For New Users: 153 | 1. Clone the repository 154 | 2. Run `.\AzureSecurityReport-Modular.ps1` 155 | 3. Follow the module installation prompts 156 | 4. Configure authentication in Settings (Option 6) 157 | 158 | ### For Existing Users: 159 | 1. Pull the latest changes 160 | 2. Install new required modules when prompted 161 | 3. Explore the new Settings menu (Option 6) 162 | 4. Try the new Azure Infrastructure Security (Option 3) 163 | 5. Check out SharePoint & OneDrive Security (Option 5) 164 | 165 | ### For Automation Scenarios: 166 | 1. Set up Azure AD App Registration with certificate 167 | 2. Use Settings menu to configure Service Principal 168 | 3. Enable Auto-Connect for unattended operation 169 | 170 | --- 171 | 172 | ## 🛡️ Security Enhancements 173 | 174 | - **Read-Only Operations** - All new features maintain read-only security posture 175 | - **Encrypted Credential Storage** - Secure configuration file for automation 176 | - **Least Privilege** - Only requests minimum required permissions 177 | - **Audit Trail** - Enhanced logging for all operations 178 | 179 | --- 180 | 181 | ## 🔄 Upgrade Path 182 | 183 | ### From v3.0 to v3.5: 184 | - ✅ **Fully Backward Compatible** - All existing functionality preserved 185 | - ✅ **No Breaking Changes** - Existing reports continue to work 186 | - ✅ **Automatic Module Detection** - Script will prompt for new modules 187 | - ✅ **Settings Migration** - New configuration system auto-initializes 188 | 189 | --- 190 | 191 | ## 📞 Support & Feedback 192 | 193 | - 🐛 **Bug Reports**: [GitHub Issues](https://github.com/SteffMet/Azure-Office365-Security-Reporting/issues) 194 | - 💡 **Feature Requests**: [GitHub Discussions](https://github.com/SteffMet/Azure-Office365-Security-Reporting/discussions) 195 | - 📖 **Documentation**: Updated README with comprehensive examples 196 | 197 | --- 198 | 199 | ## 🙏 Acknowledgments 200 | 201 | Thank you to the community for feedback and suggestions that helped shape this release. Special thanks to contributors who requested: 202 | - Azure Storage security analysis 203 | - Service Principal authentication 204 | - SharePoint security assessment 205 | - Enhanced configuration management 206 | 207 | --- 208 | 209 | **Happy Security Auditing! 🛡️** 210 | 211 | *The Azure & Office 365 Security Report Team* 212 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Planned 11 | - Azure Defender for Cloud integration 12 | - Compliance framework mapping (ISO 27001, SOC 2) 13 | - PowerBI dashboard templates 14 | - Multi-tenant support 15 | - REST API integration 16 | 17 | ## [3.0.0] - 2025-06-26 18 | 19 | ### 🚀 Major Release - Modular Architecture & Enhanced TLS Analysis 20 | 21 | ### Added 22 | - **Azure Resource Graph Integration** - Revolutionary TLS configuration analysis using Azure Resource Graph 23 | - **Visual Documentation** - Added MenuLayout.gif for interactive menu demonstration 24 | - **Migration Documentation** - Comprehensive migration notes from single-file to modular approach 25 | - **Intelligent TLS Assessment** - Smart analysis based on VM metadata, OS type, and VM size patterns 26 | - **Enhanced Module Dependencies** - Added Az.ResourceGraph to required modules 27 | 28 | ### Changed 29 | - **BREAKING: Version 3.0** - Modular architecture is now the primary approach 30 | - **TLS Configuration Check** - Completely rewritten to use Azure Resource Graph instead of simulated checks 31 | - **Menu Documentation** - Comprehensive menu structure documentation with all submenus 32 | - **Deprecation Notice** - Single-file script (AzureSecurityReport.ps1) officially deprecated 33 | 34 | ### Improved 35 | - **Performance** - Azure Resource Graph queries are significantly faster than individual VM calls 36 | - **Accuracy** - TLS analysis now based on actual VM metadata rather than random simulation 37 | - **User Experience** - Visual menu overview with GIF demonstration 38 | - **Documentation** - Complete menu hierarchy and navigation paths documented 39 | 40 | ### Removed 41 | - **Single-file Reference** - Removed references to deprecated AzureSecurityReport.ps1 from installation instructions 42 | - **Simulated TLS Checks** - Replaced random TLS compliance simulation with real metadata analysis 43 | 44 | ### Technical Details 45 | - Azure Resource Graph provides comprehensive VM metadata for intelligent security assessment 46 | - Modern VM sizes (v3-v5 generations) automatically flagged as TLS 1.2 compliant 47 | - Legacy VM sizes flagged for manual verification 48 | - Enhanced error handling and module auto-installation 49 | - Detailed compliance notes and recommendations 50 | 51 | ## [2.0.1] - 2025-06-26 52 | 53 | ### Fixed 54 | - **Microsoft Graph Assembly Conflicts** - Comprehensive solution for "Assembly with same name is already loaded" errors 55 | - **Module Import Strategy** - Reverted to dependency-check approach instead of attempting dynamic imports 56 | - **Session Management** - Enhanced session restart capabilities with automatic restart helpers 57 | - **Function Visibility** - Ensured all Core module functions are properly exported and accessible 58 | 59 | ### Added 60 | - **Start-AzureSecurityReport.ps1** - Launcher script with automatic conflict resolution 61 | - **Restart-PowerShellSession.ps1** - Automated PowerShell session restart utility 62 | - **Fix-GraphModules.ps1** - Enhanced utility script to resolve Graph module assembly conflicts 63 | - **Test-Scripts.ps1** - Comprehensive validation script for all components 64 | - **Assembly Conflict Detection** - Automatic detection and resolution guidance 65 | - **Enhanced Documentation** - Comprehensive troubleshooting section with multiple resolution options 66 | 67 | ### Changed 68 | - **Authentication Flow** - Improved error handling for assembly conflicts with restart options 69 | - **Module Loading Strategy** - Simplified approach that relies on successful authentication for module availability 70 | - **Error Messages** - Clearer guidance for users encountering assembly conflicts 71 | - **User Experience** - Added launcher script for simplified execution 72 | 73 | ## [2.0.0] - 2025-06-26 74 | 75 | ### Added 76 | - **Office 365 Security Module** - Comprehensive Office 365 security auditing 77 | - **License Usage Report** - Analyze Office 365 license utilization and cost optimization 78 | - **Inactive Accounts Detection** - Identify users inactive for 90+ days with license impact 79 | - **Mailbox Forwarding Analysis** - Detect potentially risky email forwarding rules 80 | - **Microsoft Teams Security** - External access and guest user reporting 81 | - **Modular Architecture** - Separated functionality into reusable PowerShell modules 82 | - **Enhanced Authentication** - Extended Microsoft Graph scopes for Office 365 data 83 | - **Cost Optimization** - License cost analysis and savings recommendations 84 | - **Security Scoring** - Basic security posture scoring for Teams configuration 85 | - **Comprehensive Logging** - Enhanced logging with error categorization 86 | - **CSV Export Enhancement** - Timestamped exports with validation 87 | - **GitHub Repository Setup** - Complete project structure for open source collaboration 88 | 89 | ### Changed 90 | - **Menu Structure** - Updated to include Office 365 options in main menu 91 | - **Error Handling** - Improved error messages and recovery procedures 92 | - **Output Formatting** - Enhanced color coding and progress indicators 93 | - **Module Dependencies** - Added Office 365 PowerShell modules to requirements 94 | - **Authentication Flow** - Streamlined connection process for multiple services 95 | 96 | ### Technical Improvements 97 | - PowerShell 7.0+ requirement for modern features 98 | - Modular design for better maintainability 99 | - Comprehensive error handling and logging 100 | - Progress indicators for long-running operations 101 | - Input validation and sanitization 102 | 103 | ## [1.1.0] - 2025-06-25 104 | 105 | ### Added 106 | - **Enhanced Error Handling** - Comprehensive try-catch blocks throughout 107 | - **Detailed Logging** - Timestamped log files for audit trails 108 | - **Progress Indicators** - Real-time feedback during security scans 109 | - **Input Validation** - Robust file path and user input validation 110 | - **Color-Coded Output** - Visual indicators for security status 111 | - **PowerShell 7 Support** - Updated for modern PowerShell features 112 | 113 | ### Changed 114 | - **Module Requirements** - More specific module dependencies 115 | - **Authentication Scopes** - Enhanced Microsoft Graph permissions 116 | - **CSV Export Logic** - Improved file handling and error recovery 117 | - **Menu Navigation** - Better user experience with clear options 118 | 119 | ### Fixed 120 | - **Module Installation** - Automatic handling of missing dependencies 121 | - **File Path Validation** - Robust directory creation and permission checking 122 | - **Authentication Errors** - Better error messages and recovery options 123 | 124 | ## [1.0.0] - 2025-06-20 125 | 126 | ### Added 127 | - **Initial Release** - Core Azure security auditing functionality 128 | - **Azure IAM Checks** - MFA status, guest users, password policies 129 | - **Conditional Access Analysis** - Policy review and recommendations 130 | - **Data Protection** - VM encryption and TLS configuration checks 131 | - **Interactive Menus** - User-friendly navigation system 132 | - **CSV Export** - Report generation with customizable file paths 133 | - **Authentication Integration** - Azure and Microsoft Graph connectivity 134 | 135 | ### Security Features 136 | - Read-only operations ensuring no configuration changes 137 | - Secure authentication with modern auth flows 138 | - Comprehensive audit logging for compliance 139 | - Error handling to prevent information disclosure 140 | 141 | ### Core Modules 142 | - `AzureSecurityCore.psm1` - Shared utilities and authentication 143 | - `AzureSecurityIAM.psm1` - Identity and access management checks 144 | - `AzureSecurityDataProtection.psm1` - Data protection auditing 145 | 146 | --- 147 | 148 | ## Version History Summary 149 | 150 | | Version | Release Date | Major Features | 151 | |---------|--------------|----------------| 152 | | 2.0.0 | 2025-06-26 | Office 365 integration, modular architecture | 153 | | 1.1.0 | 2025-06-25 | Enhanced error handling, PowerShell 7 support | 154 | | 1.0.0 | 2025-06-25 | Initial Azure security auditing release | 155 | 156 | --- 157 | 158 | ### Legend 159 | - **Added** - New features 160 | - **Changed** - Changes in existing functionality 161 | - **Deprecated** - Soon-to-be removed features 162 | - **Removed** - Removed features 163 | - **Fixed** - Bug fixes 164 | - **Security** - Security-related changes 165 | -------------------------------------------------------------------------------- /Modules/AzureSecuritySettings.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - Settings Module 2 | # Contains configuration management and credential storage 3 | 4 | $script:ConfigFile = Join-Path $PSScriptRoot "..\AzureSecurityConfig.json" 5 | 6 | # Function to initialize default configuration 7 | function Initialize-SecurityConfig { 8 | $DefaultConfig = @{ 9 | TenantId = "" 10 | ApplicationId = "" 11 | CertificateThumbprint = "" 12 | UseServicePrincipal = $false 13 | AutoConnect = $false 14 | ExportPath = ".\Reports" 15 | LogLevel = "INFO" 16 | LastUpdated = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") 17 | Version = "3.5" 18 | } 19 | 20 | if (-not (Test-Path $script:ConfigFile)) { 21 | $DefaultConfig | ConvertTo-Json -Depth 3 | Set-Content -Path $script:ConfigFile -Encoding UTF8 22 | Write-ColorOutput "Configuration file created at: $script:ConfigFile" "Green" 23 | } 24 | 25 | return $DefaultConfig 26 | } 27 | 28 | # Function to load configuration 29 | function Get-SecurityConfig { 30 | try { 31 | if (Test-Path $script:ConfigFile) { 32 | $Config = Get-Content -Path $script:ConfigFile -Raw | ConvertFrom-Json 33 | return $Config 34 | } else { 35 | return Initialize-SecurityConfig 36 | } 37 | } 38 | catch { 39 | Write-ColorOutput "Error loading configuration: $($_.Exception.Message)" "Red" 40 | return Initialize-SecurityConfig 41 | } 42 | } 43 | 44 | # Function to save configuration 45 | function Set-SecurityConfig { 46 | param( 47 | $Config 48 | ) 49 | 50 | try { 51 | # Convert to hashtable if it's a PSCustomObject 52 | if ($Config -is [PSCustomObject]) { 53 | $ConfigHash = @{} 54 | $Config.PSObject.Properties | ForEach-Object { 55 | $ConfigHash[$_.Name] = $_.Value 56 | } 57 | $Config = $ConfigHash 58 | } 59 | 60 | $Config.LastUpdated = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") 61 | $Config | ConvertTo-Json -Depth 3 | Set-Content -Path $script:ConfigFile -Encoding UTF8 62 | Write-ColorOutput "Configuration saved successfully." "Green" 63 | return $true 64 | } 65 | catch { 66 | Write-ColorOutput "Error saving configuration: $($_.Exception.Message)" "Red" 67 | return $false 68 | } 69 | } 70 | 71 | # Function to manage settings menu 72 | function Show-SettingsMenu { 73 | do { 74 | Clear-Host 75 | Show-Title 76 | Write-Host "Settings & Configuration" -ForegroundColor Cyan 77 | Write-Host "=======================" -ForegroundColor Cyan 78 | 79 | $Config = Get-SecurityConfig 80 | 81 | Write-Host "" 82 | Write-Host "Current Configuration:" -ForegroundColor Yellow 83 | Write-Host " Tenant ID: $(if($Config.TenantId) { $Config.TenantId } else { 'Not Set' })" 84 | Write-Host " Application ID: $(if($Config.ApplicationId) { $Config.ApplicationId } else { 'Not Set' })" 85 | Write-Host " Service Principal: $(if($Config.UseServicePrincipal) { 'Enabled' } else { 'Disabled' })" 86 | Write-Host " Auto Connect: $(if($Config.AutoConnect) { 'Enabled' } else { 'Disabled' })" 87 | Write-Host " Export Path: $($Config.ExportPath)" 88 | Write-Host "" 89 | 90 | Write-Host "1. Configure Azure Service Principal" 91 | Write-Host "2. Set Export Path" 92 | Write-Host "3. Toggle Auto-Connect" 93 | Write-Host "4. View Current Configuration" 94 | Write-Host "5. Reset Configuration" 95 | Write-Host "6. Return to Main Menu" 96 | Write-Host "" 97 | 98 | $Choice = Read-Host "Please select an option (1-6)" 99 | 100 | switch ($Choice) { 101 | "1" { Set-ServicePrincipalConfig; Read-Host "Press Enter to continue" } 102 | "2" { Set-ExportPathConfig; Read-Host "Press Enter to continue" } 103 | "3" { Toggle-AutoConnect; Read-Host "Press Enter to continue" } 104 | "4" { Show-CurrentConfig; Read-Host "Press Enter to continue" } 105 | "5" { Reset-Configuration; Read-Host "Press Enter to continue" } 106 | "6" { return } 107 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 108 | } 109 | } while ($true) 110 | } 111 | 112 | # Function to configure service principal 113 | function Set-ServicePrincipalConfig { 114 | Write-Host "" 115 | Write-Host "Configure Azure Service Principal Authentication" -ForegroundColor Yellow 116 | Write-Host "=============================================" -ForegroundColor Yellow 117 | Write-Host "" 118 | 119 | $Config = Get-SecurityConfig 120 | 121 | $TenantId = Read-Host "Enter Tenant ID (Current: $($Config.TenantId))" 122 | if ($TenantId) { $Config.TenantId = $TenantId } 123 | 124 | $AppId = Read-Host "Enter Application (Client) ID (Current: $($Config.ApplicationId))" 125 | if ($AppId) { $Config.ApplicationId = $AppId } 126 | 127 | $CertThumbprint = Read-Host "Enter Certificate Thumbprint (Optional, Current: $($Config.CertificateThumbprint))" 128 | if ($CertThumbprint) { $Config.CertificateThumbprint = $CertThumbprint } 129 | 130 | $UseSP = Read-Host "Use Service Principal for authentication? (y/n) [Current: $(if($Config.UseServicePrincipal) {'y'} else {'n'})]" 131 | if ($UseSP -eq 'y' -or $UseSP -eq 'Y') { 132 | $Config.UseServicePrincipal = $true 133 | } elseif ($UseSP -eq 'n' -or $UseSP -eq 'N') { 134 | $Config.UseServicePrincipal = $false 135 | } 136 | 137 | Set-SecurityConfig -Config $Config 138 | } 139 | 140 | # Function to set export path 141 | function Set-ExportPathConfig { 142 | $Config = Get-SecurityConfig 143 | 144 | Write-Host "" 145 | Write-Host "Current Export Path: $($Config.ExportPath)" -ForegroundColor Yellow 146 | $NewPath = Read-Host "Enter new export path (or press Enter to keep current)" 147 | 148 | if ($NewPath) { 149 | if (-not (Test-Path $NewPath)) { 150 | try { 151 | New-Item -Path $NewPath -ItemType Directory -Force | Out-Null 152 | Write-ColorOutput "Created directory: $NewPath" "Green" 153 | } 154 | catch { 155 | Write-ColorOutput "Error creating directory: $($_.Exception.Message)" "Red" 156 | return 157 | } 158 | } 159 | $Config.ExportPath = $NewPath 160 | Set-SecurityConfig -Config $Config 161 | } 162 | } 163 | 164 | # Function to toggle auto-connect 165 | function Toggle-AutoConnect { 166 | $Config = Get-SecurityConfig 167 | $Config.AutoConnect = -not $Config.AutoConnect 168 | Set-SecurityConfig -Config $Config 169 | Write-ColorOutput "Auto-Connect is now: $(if($Config.AutoConnect) {'Enabled'} else {'Disabled'})" "Green" 170 | } 171 | 172 | # Function to show current configuration 173 | function Show-CurrentConfig { 174 | $Config = Get-SecurityConfig 175 | 176 | Write-Host "" 177 | Write-Host "Current Configuration Details" -ForegroundColor Yellow 178 | Write-Host "============================" -ForegroundColor Yellow 179 | Write-Host "" 180 | Write-Host "Tenant ID: $($Config.TenantId)" 181 | Write-Host "Application ID: $($Config.ApplicationId)" 182 | Write-Host "Certificate Thumbprint: $($Config.CertificateThumbprint)" 183 | Write-Host "Use Service Principal: $($Config.UseServicePrincipal)" 184 | Write-Host "Auto Connect: $($Config.AutoConnect)" 185 | Write-Host "Export Path: $($Config.ExportPath)" 186 | Write-Host "Log Level: $($Config.LogLevel)" 187 | Write-Host "Last Updated: $($Config.LastUpdated)" 188 | Write-Host "Configuration Version: $($Config.Version)" 189 | Write-Host "" 190 | } 191 | 192 | # Function to reset configuration 193 | function Reset-Configuration { 194 | $Confirm = Read-Host "Are you sure you want to reset all configuration? (y/n)" 195 | if ($Confirm -eq 'y' -or $Confirm -eq 'Y') { 196 | try { 197 | Remove-Item -Path $script:ConfigFile -Force -ErrorAction Stop 198 | Initialize-SecurityConfig | Out-Null 199 | Write-ColorOutput "Configuration reset successfully." "Green" 200 | } 201 | catch { 202 | Write-ColorOutput "Error resetting configuration: $($_.Exception.Message)" "Red" 203 | } 204 | } 205 | } 206 | 207 | # Function to connect using saved configuration 208 | function Connect-UsingConfig { 209 | $Config = Get-SecurityConfig 210 | 211 | if ($Config.UseServicePrincipal -and $Config.TenantId -and $Config.ApplicationId) { 212 | try { 213 | Write-ColorOutput "Connecting using saved Service Principal configuration..." "Yellow" 214 | 215 | if ($Config.CertificateThumbprint) { 216 | # Certificate-based authentication 217 | Connect-AzAccount -ServicePrincipal -TenantId $Config.TenantId -ApplicationId $Config.ApplicationId -CertificateThumbprint $Config.CertificateThumbprint 218 | Connect-MgGraph -TenantId $Config.TenantId -ClientId $Config.ApplicationId -CertificateThumbprint $Config.CertificateThumbprint 219 | } else { 220 | Write-ColorOutput "Certificate thumbprint not configured. Please use interactive authentication." "Yellow" 221 | return $false 222 | } 223 | 224 | Write-ColorOutput "Successfully connected using saved configuration." "Green" 225 | return $true 226 | } 227 | catch { 228 | Write-ColorOutput "Failed to connect using saved configuration: $($_.Exception.Message)" "Red" 229 | return $false 230 | } 231 | } else { 232 | Write-ColorOutput "Service Principal not configured or incomplete. Please configure in Settings." "Yellow" 233 | return $false 234 | } 235 | } 236 | 237 | Export-ModuleMember -Function Show-SettingsMenu, Get-SecurityConfig, Set-SecurityConfig, Connect-UsingConfig, Initialize-SecurityConfig 238 | -------------------------------------------------------------------------------- /Modules/AzureSecurityDataProtection.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - Data Protection Module 2 | # Contains all data protection-related security checks 3 | 4 | # Function to check TLS configuration on VMs using Azure Resource Graph 5 | function Test-TLSConfiguration { 6 | Write-ColorOutput "Checking TLS configuration on Azure VMs using Azure Resource Graph..." "Yellow" 7 | 8 | try { 9 | # Check if Az.ResourceGraph module is available 10 | if (-not (Get-Module -ListAvailable -Name "Az.ResourceGraph")) { 11 | Write-ColorOutput "Az.ResourceGraph module is required for TLS configuration checks. Attempting to install..." "Yellow" 12 | try { 13 | Install-Module -Name "Az.ResourceGraph" -Force -AllowClobber -Scope CurrentUser -ErrorAction Stop 14 | Import-Module -Name "Az.ResourceGraph" -Force -ErrorAction Stop 15 | Write-ColorOutput "Successfully installed and imported Az.ResourceGraph module." "Green" 16 | } catch { 17 | Write-ColorOutput "Failed to install Az.ResourceGraph module: $($_.Exception.Message)" "Red" 18 | Write-Log "Failed to install Az.ResourceGraph module: $($_.Exception.Message)" "ERROR" 19 | return 20 | } 21 | } else { 22 | Import-Module -Name "Az.ResourceGraph" -Force -ErrorAction SilentlyContinue 23 | } 24 | 25 | # Query VMs with security configuration using Azure Resource Graph 26 | $Query = @" 27 | Resources 28 | | where type =~ 'microsoft.compute/virtualmachines' 29 | | extend OSType = tostring(properties.storageProfile.osDisk.osType) 30 | | extend VMSize = tostring(properties.hardwareProfile.vmSize) 31 | | extend ProvisioningState = tostring(properties.provisioningState) 32 | | extend PowerState = tostring(properties.extended.instanceView.powerState.displayStatus) 33 | | project name, resourceGroup, location, OSType, VMSize, ProvisioningState, PowerState, subscriptionId 34 | | order by name asc 35 | "@ 36 | 37 | Write-ColorOutput "Querying Azure Resource Graph for VM information..." "Yellow" 38 | $VMs = Search-AzGraph -Query $Query -ErrorAction Stop 39 | $TLSReport = @() 40 | $NonCompliantVMs = 0 41 | $WindowsVMs = 0 42 | $LinuxVMs = 0 43 | 44 | if ($VMs.Count -eq 0) { 45 | Write-ColorOutput "No Azure VMs found." "Yellow" 46 | return 47 | } 48 | 49 | Write-ColorOutput "Found $($VMs.Count) Azure VMs. Analyzing TLS configuration..." "Green" 50 | 51 | foreach ($VM in $VMs) { 52 | try { 53 | # Determine likely TLS configuration based on OS type and VM characteristics 54 | $OSType = $VM.OSType 55 | $TLSVersion = "Unknown" 56 | $IsCompliant = $false 57 | $ComplianceNotes = "" 58 | 59 | if ($OSType -eq "Windows") { 60 | $WindowsVMs++ 61 | # Modern Windows VMs typically use TLS 1.2 by default 62 | # Check if it's a newer VM size (indicates recent deployment) 63 | if ($VM.VMSize -match "v[3-5]|Standard_[D-F][0-9]+s?_v[3-5]|Standard_E[0-9]+[a-z]*s?_v[3-5]") { 64 | $TLSVersion = "1.2" 65 | $IsCompliant = $true 66 | $ComplianceNotes = "Modern VM size - likely TLS 1.2 enabled by default" 67 | } else { 68 | $TLSVersion = "1.0/1.1" 69 | $IsCompliant = $false 70 | $NonCompliantVMs++ 71 | $ComplianceNotes = "Older VM size - may require TLS 1.2 configuration verification" 72 | } 73 | } elseif ($OSType -eq "Linux") { 74 | $LinuxVMs++ 75 | # Modern Linux distributions typically support TLS 1.2 76 | $TLSVersion = "1.2" 77 | $IsCompliant = $true 78 | $ComplianceNotes = "Linux VM - typically supports TLS 1.2" 79 | } else { 80 | $TLSVersion = "Unknown" 81 | $IsCompliant = $false 82 | $ComplianceNotes = "Unknown OS type - manual verification required" 83 | } 84 | 85 | $TLSReport += [PSCustomObject]@{ 86 | VMName = $VM.name 87 | ResourceGroup = $VM.resourceGroup 88 | Location = $VM.location 89 | OSType = $OSType 90 | VMSize = $VM.VMSize 91 | PowerState = $VM.PowerState 92 | TLSVersion = $TLSVersion 93 | ComplianceStatus = if ($IsCompliant) { "Likely Compliant" } else { "Needs Verification" } 94 | Notes = $ComplianceNotes 95 | SubscriptionId = $VM.subscriptionId 96 | } 97 | } catch { 98 | Write-ColorOutput "Error processing VM $($VM.name): $($_.Exception.Message)" "Red" 99 | Write-Log "Error processing VM $($VM.name): $($_.Exception.Message)" "ERROR" 100 | } 101 | } 102 | 103 | # Display summary 104 | Write-Host "" 105 | Write-ColorOutput "=== TLS CONFIGURATION ANALYSIS SUMMARY ===" "Cyan" 106 | Write-ColorOutput "Total VMs analyzed: $($VMs.Count)" "White" 107 | Write-ColorOutput "Windows VMs: $WindowsVMs" "White" 108 | Write-ColorOutput "Linux VMs: $LinuxVMs" "White" 109 | 110 | if ($NonCompliantVMs -eq 0) { 111 | Write-ColorOutput "✓ All VMs likely use TLS 1.2 or have modern configurations." "Green" 112 | } else { 113 | Write-ColorOutput "⚠ $NonCompliantVMs VMs may need TLS 1.2 verification or configuration." "Yellow" 114 | Write-ColorOutput "🔍 Recommendation: Manually verify TLS settings on older Windows VMs." "Yellow" 115 | } 116 | 117 | Write-Host "" 118 | Write-ColorOutput "ℹ️ Note: This analysis is based on VM metadata and best practices." "Cyan" 119 | Write-ColorOutput " For definitive TLS configuration, use Azure Security Center or connect directly to VMs." "Cyan" 120 | 121 | # Prompt for export 122 | $Export = Read-Host "Would you like to export TLS analysis results to CSV? (Y/N)" 123 | if ($Export -eq 'Y' -or $Export -eq 'y') { 124 | $FilePath = Get-ValidFilePath "TLS_Configuration_Analysis" 125 | $TLSReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 126 | Write-ColorOutput "Results exported to: $FilePath" "Green" 127 | } 128 | } catch { 129 | Write-ColorOutput "Error checking TLS configuration: $($_.Exception.Message)" "Red" 130 | Write-Log "Error checking TLS configuration: $($_.Exception.Message)" "ERROR" 131 | } 132 | } 133 | 134 | # Function to check VM encryption 135 | function Test-VMEncryption { 136 | Write-ColorOutput "Checking Virtual Machine encryption..." "Yellow" 137 | 138 | try { 139 | if (-not (Get-Module -ListAvailable -Name "Az.Security")) { 140 | Write-ColorOutput "Az.Security module is required for VM encryption checks. Please install it." "Red" 141 | Write-Log "Az.Security module missing for VM encryption checks." "ERROR" 142 | return 143 | } 144 | 145 | $VMs = Get-AzVM -ErrorAction Stop 146 | $EncryptionReport = @() 147 | $UnencryptedVMs = 0 148 | 149 | if ($VMs.Count -eq 0) { 150 | Write-ColorOutput "No Azure VMs found." "Yellow" 151 | return 152 | } 153 | 154 | foreach ($VM in $VMs) { 155 | try { 156 | $EncryptionStatus = Get-AzVMDiskEncryptionStatus -ResourceGroupName $VM.ResourceGroupName -VMName $VM.Name -ErrorAction Stop 157 | 158 | $IsEncrypted = $EncryptionStatus.OsVolumeEncrypted -eq "Encrypted" -and 159 | ($EncryptionStatus.DataVolumesEncrypted -eq "Encrypted" -or $EncryptionStatus.DataVolumesEncrypted -eq "NotMounted") 160 | 161 | if (-not $IsEncrypted) { 162 | $UnencryptedVMs++ 163 | } 164 | 165 | $EncryptionReport += [PSCustomObject]@{ 166 | VMName = $VM.Name 167 | ResourceGroup = $VM.ResourceGroupName 168 | OSVolumeEncrypted = $EncryptionStatus.OsVolumeEncrypted 169 | DataVolumesEncrypted = $EncryptionStatus.DataVolumesEncrypted 170 | EncryptionStatus = if ($IsEncrypted) { "Encrypted" } else { "Not Encrypted" } 171 | } 172 | } catch { 173 | Write-ColorOutput "Error checking encryption for VM $($VM.Name): $($_.Exception.Message)" "Red" 174 | Write-Log "Error checking encryption for VM $($VM.Name): $($_.Exception.Message)" "ERROR" 175 | $EncryptionReport += [PSCustomObject]@{ 176 | VMName = $VM.Name 177 | ResourceGroup = $VM.ResourceGroupName 178 | OSVolumeEncrypted = "Error" 179 | DataVolumesEncrypted = "Error" 180 | EncryptionStatus = "Error" 181 | } 182 | } 183 | } 184 | 185 | if ($UnencryptedVMs -eq 0) { 186 | Write-ColorOutput "✓ All VMs are encrypted." "Green" 187 | } else { 188 | Write-ColorOutput "⚠ Encryption Status: $($VMs.Count - $UnencryptedVMs) VMs encrypted, $UnencryptedVMs VMs unencrypted" "Red" 189 | 190 | # Prompt for export 191 | $Export = Read-Host "Would you like to export encryption status to CSV? (Y/N)" 192 | if ($Export -eq 'Y' -or $Export -eq 'y') { 193 | $FilePath = Get-ValidFilePath "VM_Encryption_Report" 194 | $EncryptionReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 195 | Write-ColorOutput "Results exported to: $FilePath" "Green" 196 | } 197 | } 198 | } catch { 199 | Write-ColorOutput "Error checking VM encryption: $($_.Exception.Message)" "Red" 200 | Write-Log "Error checking VM encryption: $($_.Exception.Message)" "ERROR" 201 | } 202 | } 203 | 204 | Export-ModuleMember -Function Test-TLSConfiguration, Test-VMEncryption 205 | -------------------------------------------------------------------------------- /AzureSecurityReport-Modular.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7.0 2 | <# 3 | .SYNOPSIS 4 | Azure Security Report - Modular Version 5 | .DESCRIPTION 6 | Simplified main script that uses modular components for security auditing 7 | .AUTHOR 8 | github.com/SteffMet 9 | .VERSION 10 | 3.5 11 | .DATE 12 | June 28, 2025 13 | #> 14 | # Import modules 15 | $ModulesPath = Join-Path $PSScriptRoot "Modules" 16 | 17 | # Main menu 18 | # Import Core module first and dot-source it to make functions available in current scope 19 | $CoreModulePath = Join-Path $ModulesPath "AzureSecurityCore.psm1" 20 | Import-Module $CoreModulePath -Force -Global 21 | 22 | # Import other modules 23 | Import-Module (Join-Path $ModulesPath "AzureSecurityIAM.psm1") -Force -Global 24 | Import-Module (Join-Path $ModulesPath "AzureSecurityDataProtection.psm1") -Force -Global 25 | Import-Module (Join-Path $ModulesPath "AzureSecurityOffice365.psm1") -Force -Global 26 | Import-Module (Join-Path $ModulesPath "AzureSecuritySettings.psm1") -Force -Global 27 | Import-Module (Join-Path $ModulesPath "AzureSecurityInfrastructure.psm1") -Force -Global 28 | Import-Module (Join-Path $ModulesPath "AzureSecuritySharePoint.psm1") -Force -Global 29 | 30 | # Teams submenu 31 | function Show-TeamsMenu { 32 | do { 33 | Clear-Host 34 | Show-Title 35 | Write-Host "Microsoft Teams Security Checks" -ForegroundColor Cyan 36 | Write-Host "===============================" -ForegroundColor Cyan 37 | Write-Host "1. Check External Access Configuration" 38 | Write-Host "2. Report Teams with External Users or Guests" 39 | Write-Host "3. Channel Membership Changes Report" 40 | Write-Host "4. Return to Office 365 Menu" 41 | Write-Host "" 42 | 43 | $Choice = Read-Host "Please select an option (1-4)" 44 | 45 | switch ($Choice) { 46 | "1" { Get-TeamsExternalAccessReport; Read-Host "Press Enter to continue" } 47 | "2" { Get-TeamsWithExternalUsersReport; Read-Host "Press Enter to continue" } 48 | "3" { Get-TeamsChannelMembershipChanges; Read-Host "Press Enter to continue" } 49 | "4" { return } 50 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 51 | } 52 | } while ($true) 53 | } 54 | 55 | # Office 365 submenu 56 | function Show-Office365Menu { 57 | do { 58 | Clear-Host 59 | Show-Title 60 | Write-Host "Read-Only Office 365 Audit Menu" -ForegroundColor Cyan 61 | Write-Host "===============================" -ForegroundColor Cyan 62 | Write-Host "1. License Usage Report" 63 | Write-Host "2. Inactive Accounts Report" 64 | Write-Host "3. Check Mailbox Forwarding Rules" 65 | Write-Host "4. Microsoft Teams" 66 | Write-Host "5. Return to Main Menu" 67 | Write-Host "" 68 | 69 | $Choice = Read-Host "Please select an option (1-5)" 70 | 71 | switch ($Choice) { 72 | "1" { Get-LicenseUsageReport; Read-Host "Press Enter to continue" } 73 | "2" { Get-InactiveAccountsReport; Read-Host "Press Enter to continue" } 74 | "3" { Get-MailboxForwardingReport; Read-Host "Press Enter to continue" } 75 | "4" { Show-TeamsMenu } 76 | "5" { return } 77 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 78 | } 79 | } while ($true) 80 | } 81 | 82 | # IAM submenu 83 | function Show-IAMMenu { 84 | do { 85 | Clear-Host 86 | Show-Title 87 | Write-Host "IAM Security Checks" -ForegroundColor Cyan 88 | Write-Host "===================" -ForegroundColor Cyan 89 | Write-Host "1. Check MFA Status" 90 | Write-Host "2. Check Guest User Access" 91 | Write-Host "3. Check Password Expiry Settings" 92 | Write-Host "4. Check Conditional Access Policies" 93 | Write-Host "5. Return to Main Menu" 94 | Write-Host "" 95 | 96 | $Choice = Read-Host "Please select an option (1-5)" 97 | 98 | switch ($Choice) { 99 | "1" { Test-MFAStatus; Read-Host "Press Enter to continue" } 100 | "2" { Test-GuestUserAccess; Read-Host "Press Enter to continue" } 101 | "3" { Test-PasswordExpirySettings; Read-Host "Press Enter to continue" } 102 | "4" { Test-ConditionalAccessPolicies; Read-Host "Press Enter to continue" } 103 | "5" { return } 104 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 105 | } 106 | } while ($true) 107 | } 108 | 109 | # Data Protection submenu 110 | function Show-DataProtectionMenu { 111 | do { 112 | Clear-Host 113 | Show-Title 114 | Write-Host "Data Protection Security Checks" -ForegroundColor Cyan 115 | Write-Host "===============================" -ForegroundColor Cyan 116 | Write-Host "1. Check TLS Configuration on VMs" 117 | Write-Host "2. Check Virtual Machine Encryption" 118 | Write-Host "3. Return to Main Menu" 119 | Write-Host "" 120 | 121 | $Choice = Read-Host "Please select an option (1-3)" 122 | 123 | switch ($Choice) { 124 | "1" { Test-VMTLSConfiguration; Read-Host "Press Enter to continue" } 125 | "2" { Test-VMEncryption; Read-Host "Press Enter to continue" } 126 | "3" { return } 127 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 128 | } 129 | } while ($true) 130 | } 131 | 132 | # Azure Infrastructure Security submenu 133 | function Show-InfrastructureSecurityMenu { 134 | do { 135 | Clear-Host 136 | Show-Title 137 | Write-Host "Azure Infrastructure Security Checks" -ForegroundColor Cyan 138 | Write-Host "====================================" -ForegroundColor Cyan 139 | Write-Host "1. Azure Storage Security Report" 140 | Write-Host "2. Azure Key Vault Security Report" 141 | Write-Host "3. Network Security Groups Report" 142 | Write-Host "4. Azure Resource Tag Compliance Report" 143 | Write-Host "5. Return to Main Menu" 144 | Write-Host "" 145 | 146 | $Choice = Read-Host "Please select an option (1-5)" 147 | 148 | switch ($Choice) { 149 | "1" { Get-StorageSecurityReport; Read-Host "Press Enter to continue" } 150 | "2" { Get-KeyVaultSecurityReport; Read-Host "Press Enter to continue" } 151 | "3" { Get-NetworkSecurityReport; Read-Host "Press Enter to continue" } 152 | "4" { Get-AzureResourceTagCompliance; Read-Host "Press Enter to continue" } 153 | "5" { return } 154 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 155 | } 156 | } while ($true) 157 | } 158 | 159 | # SharePoint & OneDrive Security submenu 160 | function Show-SharePointMenu { 161 | do { 162 | Write-Host "1. SharePoint Sharing Report" 163 | Write-Host "2. OneDrive Security Report" 164 | Write-Host "3. DLP Policy Report" 165 | Write-Host "4. SharePoint Site Storage Usage Report" 166 | Write-Host "5. OneDrive Sharing Links Audit" 167 | Write-Host "6. Return to Main Menu" 168 | Write-Host "" 169 | $Choice = Read-Host "Please select an option (1-6)" 170 | switch ($Choice) { 171 | "1" { Get-SharePointSharingReport; Read-Host "Press Enter to continue" } 172 | "2" { Get-OneDriveSecurityReport; Read-Host "Press Enter to continue" } 173 | "3" { Get-DLPPolicyReport; Read-Host "Press Enter to continue" } 174 | "4" { Get-SharePointSiteStorageUsage; Read-Host "Press Enter to continue" } 175 | "5" { Get-OneDriveSharingLinksAudit; Read-Host "Press Enter to continue" } 176 | "6" { return } 177 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 178 | } 179 | } while ($true) 180 | } 181 | 182 | # Main menu 183 | function Show-MainMenu { 184 | do { 185 | Clear-Host 186 | Show-Title 187 | Write-Host "Read-Only Azure & Office 365 Security Audit Menu" -ForegroundColor Cyan 188 | Write-Host "================================================" -ForegroundColor Cyan 189 | Write-Host "1. Identity and Access Management Report (Azure AD)" 190 | Write-Host "2. Data Protection Report (Azure)" 191 | Write-Host "3. Azure Infrastructure Security Report" 192 | Write-Host "4. Office 365 Security Report" 193 | Write-Host "5. SharePoint & OneDrive Security Report" 194 | Write-Host "6. Settings & Configuration" 195 | Write-Host "7. Exit" 196 | Write-Host "" 197 | 198 | $Choice = Read-Host "Please select an option (1-7)" 199 | 200 | switch ($Choice) { 201 | "1" { Show-IAMMenu } 202 | "2" { Show-DataProtectionMenu } 203 | "3" { Show-InfrastructureSecurityMenu } 204 | "4" { Show-Office365Menu } 205 | "5" { Show-SharePointMenu } 206 | "6" { Show-SettingsMenu } 207 | "7" { 208 | Write-ColorOutput "Thank you for using Azure & Office 365 Security Report!" "Green" 209 | Write-ColorOutput "Log file saved as: $script:LogFile" "Yellow" 210 | return 211 | } 212 | default { Write-ColorOutput "Invalid selection. Please try again." "Red"; Start-Sleep 2 } 213 | } 214 | } while ($true) 215 | } 216 | 217 | # Main script execution 218 | function Main { 219 | Show-Title 220 | Write-Log "Azure Security Report (Modular) v3.5 started" "INFO" 221 | 222 | # Initialize configuration 223 | Initialize-SecurityConfig | Out-Null 224 | $Config = Get-SecurityConfig 225 | 226 | # Required modules for this script 227 | $RequiredModules = @( 228 | "Az.Accounts", 229 | "Az.Compute", 230 | "Az.Security", 231 | "Az.ResourceGraph", 232 | "Az.KeyVault", 233 | "Az.Network", 234 | "Az.Storage", 235 | "Microsoft.Graph.Users", 236 | "Microsoft.Graph.Identity.SignIns", 237 | "Microsoft.Graph.Reports", 238 | "Microsoft.Graph.Sites", 239 | "ExchangeOnlineManagement", 240 | "MicrosoftTeams" 241 | ) 242 | 243 | # Check required modules 244 | if (-not (Test-RequiredModules -RequiredModules $RequiredModules)) { 245 | Write-Log "Module check failed. Exiting." "ERROR" 246 | Read-Host "Press Enter to exit" 247 | exit 1 248 | } 249 | 250 | # Try auto-connect if configured 251 | $AutoConnected = $false 252 | if ($Config.AutoConnect -and $Config.UseServicePrincipal) { 253 | Write-ColorOutput "Attempting auto-connect using saved configuration..." "Yellow" 254 | $AutoConnected = Connect-UsingConfig 255 | } 256 | 257 | # Manual authentication if auto-connect failed or not configured 258 | if (-not $AutoConnected) { 259 | Write-ColorOutput "Using interactive authentication..." "Yellow" 260 | if (-not (Connect-AzureServices)) { 261 | Write-Log "Authentication failed. Exiting." "ERROR" 262 | Read-Host "Press Enter to exit" 263 | exit 1 264 | } 265 | } 266 | 267 | Write-ColorOutput "Authentication successful. Starting security audit..." "Green" 268 | Start-Sleep 2 269 | 270 | # Show main menu 271 | Show-MainMenu 272 | 273 | Write-Log "Azure Security Report completed" "INFO" 274 | } 275 | 276 | # Start the script 277 | Main 278 | -------------------------------------------------------------------------------- /Modules/AzureSecurityIAM.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - Identity and Access Management Module 2 | # Contains all IAM-related security checks 3 | 4 | # Function to check MFA status 5 | function Test-MFAStatus { 6 | Write-ColorOutput "Checking MFA status for all users..." "Yellow" 7 | 8 | # Check if modules are already loaded (from successful authentication) 9 | $UsersModuleLoaded = Get-Module -Name "Microsoft.Graph.Users" -ErrorAction SilentlyContinue 10 | $DirectoryModuleLoaded = Get-Module -Name "Microsoft.Graph.Identity.DirectoryManagement" -ErrorAction SilentlyContinue 11 | 12 | if (-not $UsersModuleLoaded -or -not $DirectoryModuleLoaded) { 13 | Write-ColorOutput "Required Microsoft Graph modules not loaded. Please ensure authentication was successful." "Red" 14 | Write-ColorOutput "If you see assembly conflicts, restart PowerShell and try again." "Yellow" 15 | return 16 | } 17 | 18 | try { 19 | # Get all users with pagination 20 | $Users = Get-MgUser -All -Property Id,UserPrincipalName,DisplayName -PageSize 100 | Where-Object { $_.UserPrincipalName -notlike "*#EXT#*" } 21 | $UsersWithoutMFA = @() 22 | $GlobalAdminsWithoutMFA = @() 23 | 24 | # Get Global Admin role members 25 | $GlobalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'" -ErrorAction Stop 26 | if (-not $GlobalAdminRole) { 27 | Write-ColorOutput "No Global Administrator role found." "Yellow" 28 | Write-Log "No Global Administrator role found." "WARNING" 29 | return 30 | } 31 | $GlobalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $GlobalAdminRole.Id 32 | 33 | foreach ($User in $Users) { 34 | try { 35 | $AuthMethods = Get-MgUserAuthenticationMethod -UserId $User.Id -ErrorAction Stop 36 | $HasMFA = $AuthMethods | Where-Object { $_.AdditionalProperties.'@odata.type' -in @('#microsoft.graph.microsoftAuthenticatorAuthenticationMethod', '#microsoft.graph.phoneAuthenticationMethod', '#microsoft.graph.fido2AuthenticationMethod') } 37 | 38 | if (-not $HasMFA) { 39 | $UserInfo = [PSCustomObject]@{ 40 | UPN = $User.UserPrincipalName 41 | DisplayName = $User.DisplayName 42 | MFAStatus = "Disabled" 43 | IsGlobalAdmin = $User.Id -in $GlobalAdmins.Id 44 | } 45 | 46 | $UsersWithoutMFA += $UserInfo 47 | 48 | if ($User.Id -in $GlobalAdmins.Id) { 49 | $GlobalAdminsWithoutMFA += $UserInfo 50 | } 51 | } 52 | } catch { 53 | Write-ColorOutput "Error checking MFA for user $($User.UserPrincipalName): $($_.Exception.Message)" "Red" 54 | Write-Log "Error checking MFA for user $($User.UserPrincipalName): $($_.Exception.Message)" "ERROR" 55 | } 56 | } 57 | 58 | # Display summary 59 | if ($UsersWithoutMFA.Count -eq 0) { 60 | Write-ColorOutput "✓ All users have MFA enabled." "Green" 61 | } else { 62 | Write-ColorOutput "⚠ MFA Status: $($UsersWithoutMFA.Count) users without MFA, $($GlobalAdminsWithoutMFA.Count) Global Admins without MFA" "Red" 63 | 64 | # Prompt for export 65 | $Export = Read-Host "Would you like to export these results to CSV? (Y/N)" 66 | if ($Export -eq 'Y' -or $Export -eq 'y') { 67 | $FilePath = Get-ValidFilePath "MFA_Report" 68 | $UsersWithoutMFA | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 69 | Write-ColorOutput "Results exported to: $FilePath" "Green" 70 | } 71 | } 72 | } catch { 73 | Write-ColorOutput "Error checking MFA status: $($_.Exception.Message)" "Red" 74 | Write-Log "Error checking MFA status: $($_.Exception.Message)" "ERROR" 75 | } 76 | } 77 | 78 | # Function to check guest user access 79 | function Test-GuestUserAccess { 80 | Write-ColorOutput "Checking guest user access..." "Yellow" 81 | 82 | # Check if modules are already loaded (from successful authentication) 83 | if (-not (Get-Module -Name "Microsoft.Graph.Users" -ErrorAction SilentlyContinue)) { 84 | Write-ColorOutput "Required Microsoft Graph modules not loaded. Please ensure authentication was successful." "Red" 85 | Write-ColorOutput "If you see assembly conflicts, restart PowerShell and try again." "Yellow" 86 | return 87 | } 88 | 89 | try { 90 | $GuestUsers = Get-MgUser -Filter "userType eq 'Guest'" -All -Property UserPrincipalName,DisplayName,CreatedDateTime -PageSize 100 91 | 92 | if ($GuestUsers.Count -eq 0) { 93 | Write-ColorOutput "✓ No guest users found." "Green" 94 | } else { 95 | Write-ColorOutput "⚠ Guest Access: $($GuestUsers.Count) guest users found" "Yellow" 96 | 97 | $GuestReport = $GuestUsers | ForEach-Object { 98 | [PSCustomObject]@{ 99 | UPN = $_.UserPrincipalName 100 | DisplayName = $_.DisplayName 101 | CreatedDate = $_.CreatedDateTime 102 | } 103 | } 104 | 105 | # Prompt for export 106 | $Export = Read-Host "Would you like to export guest user details to CSV? (Y/N)" 107 | if ($Export -eq 'Y' -or $Export -eq 'y') { 108 | $FilePath = Get-ValidFilePath "Guest_Users_Report" 109 | $GuestReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 110 | Write-ColorOutput "Results exported to: $FilePath" "Green" 111 | } 112 | } 113 | } catch { 114 | Write-ColorOutput "Error checking guest users: $($_.Exception.Message)" "Red" 115 | Write-Log "Error checking guest users: $($_.Exception.Message)" "ERROR" 116 | } 117 | } 118 | 119 | # Function to check password expiry settings 120 | function Test-PasswordExpirySettings { 121 | Write-ColorOutput "Checking password expiry settings..." "Yellow" 122 | 123 | # Check if modules are already loaded (from successful authentication) 124 | if (-not (Get-Module -Name "Microsoft.Graph.Users" -ErrorAction SilentlyContinue)) { 125 | Write-ColorOutput "Required Microsoft Graph modules not loaded. Please ensure authentication was successful." "Red" 126 | Write-ColorOutput "If you see assembly conflicts, restart PowerShell and try again." "Yellow" 127 | return 128 | } 129 | 130 | try { 131 | $Users = Get-MgUser -All -Property UserPrincipalName,DisplayName,PasswordPolicies -PageSize 100 | Where-Object { $_.UserPrincipalName -notlike "*#EXT#*" } 132 | $NonExpiringPasswords = $Users | Where-Object { $_.PasswordPolicies -contains "DisablePasswordExpiration" } 133 | 134 | if ($NonExpiringPasswords.Count -eq 0) { 135 | Write-ColorOutput "✓ All user passwords are set to expire." "Green" 136 | } else { 137 | Write-ColorOutput "⚠ Password Expiry: $($NonExpiringPasswords.Count) users with non-expiring passwords" "Yellow" 138 | 139 | $PasswordReport = $NonExpiringPasswords | ForEach-Object { 140 | [PSCustomObject]@{ 141 | UPN = $_.UserPrincipalName 142 | DisplayName = $_.DisplayName 143 | PasswordExpiryStatus = "Never Expires" 144 | } 145 | } 146 | 147 | # Prompt for export 148 | $Export = Read-Host "Would you like to export these results to CSV? (Y/N)" 149 | if ($Export -eq 'Y' -or $Export -eq 'y') { 150 | $FilePath = Get-ValidFilePath "Password_Expiry_Report" 151 | $PasswordReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 152 | Write-ColorOutput "Results exported to: $FilePath" "Green" 153 | } 154 | } 155 | } catch { 156 | Write-ColorOutput "Error checking password expiry settings: $($_.Exception.Message)" "Red" 157 | Write-Log "Error checking password expiry settings: $($_.Exception.Message)" "ERROR" 158 | } 159 | } 160 | 161 | # Function to check conditional access policies 162 | function Test-ConditionalAccessPolicies { 163 | Write-ColorOutput "Checking Conditional Access policies..." "Yellow" 164 | 165 | # Check if modules are already loaded (from successful authentication) 166 | if (-not (Get-Module -Name "Microsoft.Graph.Identity.SignIns" -ErrorAction SilentlyContinue)) { 167 | Write-ColorOutput "Required Microsoft Graph modules not loaded. Please ensure authentication was successful." "Red" 168 | Write-ColorOutput "If you see assembly conflicts, restart PowerShell and try again." "Yellow" 169 | return 170 | } 171 | 172 | try { 173 | $CAPolicies = Get-MgIdentityConditionalAccessPolicy -All -ErrorAction Stop 174 | 175 | if ($CAPolicies.Count -eq 0) { 176 | Write-ColorOutput "⚠ No Conditional Access policies found." "Red" 177 | return 178 | } 179 | 180 | $PolicyReport = @() 181 | $PoliciesWithoutMFA = 0 182 | $PoliciesWithoutRiskBlock = 0 183 | 184 | foreach ($Policy in $CAPolicies) { 185 | $HasMFARequirement = $false 186 | $BlocksRiskySignins = $false 187 | 188 | if ($Policy.GrantControls -and $Policy.GrantControls.BuiltInControls -contains "mfa") { 189 | $HasMFARequirement = $true 190 | } 191 | 192 | if ($Policy.Conditions -and $Policy.Conditions.SignInRiskLevels -contains "high" -and 193 | $Policy.GrantControls -and $Policy.GrantControls.Operator -eq "OR" -and 194 | $Policy.GrantControls.BuiltInControls -contains "block") { 195 | $BlocksRiskySignins = $true 196 | } 197 | 198 | if (-not $HasMFARequirement) { $PoliciesWithoutMFA++ } 199 | if (-not $BlocksRiskySignins) { $PoliciesWithoutRiskBlock++ } 200 | 201 | $PolicyReport += [PSCustomObject]@{ 202 | PolicyName = $Policy.DisplayName 203 | State = $Policy.State 204 | MFAEnforced = $HasMFARequirement 205 | BlocksRiskySignins = $BlocksRiskySignins 206 | } 207 | } 208 | 209 | Write-ColorOutput "Conditional Access: $($CAPolicies.Count) policies found. MFA not enforced in $PoliciesWithoutMFA policies. Risky sign-ins not blocked in $PoliciesWithoutRiskBlock policies." "Yellow" 210 | 211 | if ($PoliciesWithoutMFA -gt 0 -or $PoliciesWithoutRiskBlock -gt 0) { 212 | Write-ColorOutput "⚠ Recommendation: Enforce MFA and block risky sign-ins in Conditional Access policies." "Red" 213 | } 214 | 215 | # Prompt for export 216 | $Export = Read-Host "Would you like to export policy details to CSV? (Y/N)" 217 | if ($Export -eq 'Y' -or $Export -eq 'y') { 218 | $FilePath = Get-ValidFilePath "Conditional_Access_Report" 219 | $PolicyReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 220 | Write-ColorOutput "Results exported to: $FilePath" "Green" 221 | } 222 | } catch { 223 | Write-ColorOutput "Error checking Conditional Access policies: $($_.Exception.Message)" "Red" 224 | Write-Log "Error checking Conditional Access policies: $($_.Exception.Message)" "ERROR" 225 | } 226 | } 227 | 228 | Export-ModuleMember -Function Test-MFAStatus, Test-GuestUserAccess, Test-PasswordExpirySettings, Test-ConditionalAccessPolicies 229 | -------------------------------------------------------------------------------- /Modules/AzureSecurityCore.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - Core Module 2 | # Contains shared utilities and common functions 3 | 4 | # Function to safely import Microsoft Graph modules 5 | function Import-GraphModuleSafely { 6 | param( 7 | [string]$ModuleName 8 | ) 9 | 10 | try { 11 | # First, try to remove any existing loaded modules to avoid conflicts 12 | $LoadedModule = Get-Module -Name $ModuleName -ErrorAction SilentlyContinue 13 | if ($LoadedModule) { 14 | Write-Log "Removing existing module: $ModuleName" "INFO" 15 | Remove-Module -Name $ModuleName -Force -ErrorAction SilentlyContinue 16 | } 17 | 18 | # Clear any cached assemblies (PowerShell 7+ feature) 19 | if ($PSVersionTable.PSVersion.Major -ge 7) { 20 | [System.GC]::Collect() 21 | [System.GC]::WaitForPendingFinalizers() 22 | } 23 | 24 | # Import the module with force and specific error handling 25 | Write-Log "Importing module: $ModuleName" "INFO" 26 | Import-Module -Name $ModuleName -Force -Scope Global -ErrorAction Stop 27 | 28 | Write-Log "Successfully imported: $ModuleName" "INFO" 29 | return $true 30 | } 31 | catch { 32 | Write-ColorOutput "Failed to import module $ModuleName`: $($_.Exception.Message)" "Red" 33 | Write-Log "Failed to import module $ModuleName`: $($_.Exception.Message)" "ERROR" 34 | return $false 35 | } 36 | } 37 | 38 | # Function to reset PowerShell session for Graph modules 39 | function Reset-GraphSession { 40 | Write-Log "Resetting Microsoft Graph session to resolve assembly conflicts..." "INFO" 41 | 42 | try { 43 | # Disconnect from Microsoft Graph 44 | try { 45 | Disconnect-MgGraph -ErrorAction SilentlyContinue 46 | } catch { 47 | # Ignore disconnect errors 48 | } 49 | 50 | # Remove all Microsoft Graph modules 51 | $GraphModules = Get-Module -Name "Microsoft.Graph*" -ErrorAction SilentlyContinue 52 | foreach ($Module in $GraphModules) { 53 | Write-Log "Removing Graph module: $($Module.Name)" "INFO" 54 | Remove-Module -Name $Module.Name -Force -ErrorAction SilentlyContinue 55 | } 56 | 57 | # Force garbage collection 58 | [System.GC]::Collect() 59 | [System.GC]::WaitForPendingFinalizers() 60 | [System.GC]::Collect() 61 | 62 | Write-Log "Graph session reset complete" "INFO" 63 | return $true 64 | } 65 | catch { 66 | Write-Log "Error during Graph session reset: $($_.Exception.Message)" "ERROR" 67 | return $false 68 | } 69 | } 70 | 71 | # Global Variables 72 | $script:LogFile = "AzureSecurityReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" 73 | $script:Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" 74 | 75 | # Function to write to log file 76 | function Write-Log { 77 | param( 78 | [string]$Message, 79 | [string]$Level = "INFO" 80 | ) 81 | $LogEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$Level] $Message" 82 | Add-Content -Path $script:LogFile -Value $LogEntry -ErrorAction SilentlyContinue 83 | Write-Host $LogEntry 84 | } 85 | 86 | # Function to display colored output 87 | function Write-ColorOutput { 88 | param( 89 | [string]$Message, 90 | [string]$Color = "White" 91 | ) 92 | Write-Host $Message -ForegroundColor $Color 93 | Write-Log -Message $Message -Level "INFO" 94 | } 95 | 96 | # Function to display title 97 | function Show-Title { 98 | Clear-Host 99 | Write-Host "------------------------------------------------------------------" -ForegroundColor Cyan 100 | Write-Host " AZURE & OFFICE 365 SECURITY REPORT SCRIPT " -ForegroundColor Yellow 101 | Write-Host " By github.com/SteffMet" -ForegroundColor Blue 102 | Write-Host "------------------------------------------------------------------" 103 | Write-Host "" 104 | } 105 | 106 | # Function to validate file path for CSV export 107 | function Get-ValidFilePath { 108 | param([string]$DefaultName) 109 | 110 | do { 111 | $FilePath = Read-Host "Enter the full file path for export (or press Enter for current directory)" 112 | 113 | if ([string]::IsNullOrWhiteSpace($FilePath)) { 114 | $FilePath = Join-Path (Get-Location) "$DefaultName`_$script:Timestamp.csv" 115 | } 116 | 117 | # Ensure .csv extension 118 | if (-not $FilePath.EndsWith('.csv')) { 119 | $FilePath += '.csv' 120 | } 121 | 122 | try { 123 | $Directory = Split-Path $FilePath -Parent 124 | if (-not (Test-Path $Directory)) { 125 | New-Item -ItemType Directory -Path $Directory -Force -ErrorAction Stop | Out-Null 126 | } 127 | # Test write access 128 | $TestFile = Join-Path $Directory "test_write.txt" 129 | New-Item -ItemType File -Path $TestFile -Force -ErrorAction Stop | Out-Null 130 | Remove-Item -Path $TestFile -ErrorAction Stop 131 | return $FilePath 132 | } catch { 133 | Write-ColorOutput "Invalid file path or insufficient permissions. Please try again." "Red" 134 | Write-Log "Invalid file path or insufficient permissions: $($_.Exception.Message)" "ERROR" 135 | } 136 | } while ($true) 137 | } 138 | 139 | # Function to check and install required modules 140 | function Test-RequiredModules { 141 | param([string[]]$RequiredModules) 142 | 143 | Write-ColorOutput "Checking required PowerShell modules..." "Yellow" 144 | $MissingModules = @() 145 | 146 | foreach ($Module in $RequiredModules) { 147 | if (!(Get-Module -ListAvailable -Name $Module)) { 148 | $MissingModules += $Module 149 | Write-ColorOutput "Module '$Module' is not installed." "Red" 150 | } else { 151 | Write-ColorOutput "Module '$Module' is installed." "Green" 152 | } 153 | } 154 | 155 | if ($MissingModules.Count -gt 0) { 156 | Write-ColorOutput "Missing modules: $($MissingModules -join ', ')" "Red" 157 | $Install = Read-Host "Would you like to install the missing modules? (Y/N)" 158 | 159 | if ($Install -eq 'Y' -or $Install -eq 'y') { 160 | foreach ($Module in $MissingModules) { 161 | try { 162 | Write-ColorOutput "Installing module: $Module" "Yellow" 163 | Install-Module -Name $Module -Force -AllowClobber -Scope CurrentUser -ErrorAction Stop 164 | Write-ColorOutput "Successfully installed: $Module" "Green" 165 | } catch { 166 | Write-ColorOutput "Failed to install module: $Module. Error: $($_.Exception.Message)" "Red" 167 | Write-Log "Failed to install module: $Module. Error: $($_.Exception.Message)" "ERROR" 168 | return $false 169 | } 170 | } 171 | } else { 172 | Write-ColorOutput "Cannot proceed without required modules. Exiting..." "Red" 173 | Write-Log "Cannot proceed without required modules. Exiting..." "ERROR" 174 | return $false 175 | } 176 | } 177 | 178 | return $true 179 | } 180 | 181 | # Function to authenticate to Azure and Microsoft Graph 182 | function Connect-AzureServices { 183 | Write-ColorOutput "Authenticating to Azure services..." "Yellow" 184 | 185 | try { 186 | # Check if we've already had assembly conflicts in this session 187 | if ($global:GraphAssemblyConflictDetected) { 188 | Write-ColorOutput "Assembly conflicts detected in this session. PowerShell restart recommended." "Yellow" 189 | $RestartScript = Join-Path $PSScriptRoot "..\Restart-PowerShellSession.ps1" 190 | if (Test-Path $RestartScript) { 191 | & $RestartScript 192 | return $false 193 | } 194 | } 195 | 196 | # Reset Graph session first to avoid assembly conflicts 197 | Reset-GraphSession 198 | 199 | # Import required Graph modules safely 200 | $GraphModules = @( 201 | "Microsoft.Graph.Authentication", 202 | "Microsoft.Graph.Users", 203 | "Microsoft.Graph.Identity.SignIns", 204 | "Microsoft.Graph.Identity.DirectoryManagement", 205 | "Microsoft.Graph.Reports" 206 | ) 207 | 208 | $ModuleImportFailed = $false 209 | foreach ($Module in $GraphModules) { 210 | if (-not (Import-GraphModuleSafely -ModuleName $Module)) { 211 | Write-ColorOutput "Failed to import required Graph module: $Module" "Red" 212 | $ModuleImportFailed = $true 213 | 214 | # Check if it's an assembly conflict 215 | if ((Get-Error | Select-Object -Last 1).Exception.Message -like "*Assembly with same name is already loaded*") { 216 | $global:GraphAssemblyConflictDetected = $true 217 | Write-ColorOutput "Assembly conflict detected. PowerShell session restart required." "Yellow" 218 | 219 | $RestartScript = Join-Path $PSScriptRoot "..\Restart-PowerShellSession.ps1" 220 | if (Test-Path $RestartScript) { 221 | Write-ColorOutput "Starting restart helper..." "Yellow" 222 | & $RestartScript 223 | return $false 224 | } else { 225 | Write-ColorOutput "Please exit PowerShell and start a fresh session, then run the script again." "Yellow" 226 | return $false 227 | } 228 | } 229 | } 230 | } 231 | 232 | if ($ModuleImportFailed) { 233 | return $false 234 | } 235 | 236 | # Connect to Azure 237 | Write-ColorOutput "Connecting to Azure..." "Yellow" 238 | Connect-AzAccount -ErrorAction Stop | Out-Null 239 | Write-ColorOutput "Successfully connected to Azure." "Green" 240 | 241 | # Connect to Microsoft Graph with required scopes 242 | Write-ColorOutput "Connecting to Microsoft Graph..." "Yellow" 243 | $Scopes = @( 244 | "User.Read.All", 245 | "Directory.Read.All", 246 | "Policy.Read.ConditionalAccess", 247 | "UserAuthenticationMethod.Read.All", 248 | "Organization.Read.All", 249 | "Reports.Read.All", 250 | "AuditLog.Read.All" 251 | ) 252 | Connect-MgGraph -Scopes $Scopes -ErrorAction Stop | Out-Null 253 | 254 | # Verify granted scopes 255 | $GrantedScopes = (Get-MgContext).Scopes 256 | $MissingScopes = $Scopes | Where-Object { $_ -notin $GrantedScopes } 257 | if ($MissingScopes) { 258 | Write-ColorOutput "Warning: The following required scopes were not granted: $($MissingScopes -join ', ')" "Yellow" 259 | Write-Log "Missing scopes: $($MissingScopes -join ', ')" "WARNING" 260 | } 261 | 262 | Write-ColorOutput "Successfully connected to Microsoft Graph." "Green" 263 | return $true 264 | } catch { 265 | Write-ColorOutput "Authentication failed: $($_.Exception.Message)" "Red" 266 | Write-Log "Authentication failed: $($_.Exception.Message)" "ERROR" 267 | 268 | # If authentication fails due to assembly conflicts, suggest restart 269 | if ($_.Exception.Message -like "*Assembly with same name is already loaded*") { 270 | $global:GraphAssemblyConflictDetected = $true 271 | Write-ColorOutput "Assembly conflict detected. PowerShell session restart required." "Yellow" 272 | Write-Log "Assembly conflict detected. Restart recommended." "WARNING" 273 | 274 | $RestartScript = Join-Path $PSScriptRoot "..\Restart-PowerShellSession.ps1" 275 | if (Test-Path $RestartScript) { 276 | Write-ColorOutput "Starting restart helper..." "Yellow" 277 | & $RestartScript 278 | return $false 279 | } 280 | } 281 | return $false 282 | } 283 | } 284 | 285 | Export-ModuleMember -Function Write-Log, Write-ColorOutput, Show-Title, Get-ValidFilePath, Test-RequiredModules, Connect-AzureServices, Import-GraphModuleSafely, Reset-GraphSession -Variable LogFile, Timestamp 286 | -------------------------------------------------------------------------------- /Modules/AzureSecuritySharePoint.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - SharePoint & OneDrive Security Module 2 | # Contains SharePoint Online and OneDrive security assessment functions 3 | 4 | # Function to check SharePoint sharing settings 5 | function Get-SharePointSharingReport { 6 | Write-ColorOutput "Analyzing SharePoint Online Sharing Settings..." "Yellow" 7 | Write-Log "Starting SharePoint Sharing Analysis" "INFO" 8 | 9 | try { 10 | # Connect to SharePoint Admin if not already connected 11 | try { 12 | $AdminUrl = (Get-MgOrganization).DisplayName 13 | if (-not $AdminUrl) { 14 | throw "Unable to determine tenant admin URL" 15 | } 16 | } 17 | catch { 18 | Write-ColorOutput "Unable to connect to SharePoint Admin. Please ensure you have SharePoint Administrator permissions." "Red" 19 | return 20 | } 21 | 22 | # Get sharing settings using Microsoft Graph 23 | $SharePointSettings = @() 24 | $Sites = @() 25 | 26 | try { 27 | # Get all SharePoint sites 28 | $Sites = Get-MgSite -All -Property "Id,Name,WebUrl,CreatedDateTime,LastModifiedDateTime" 29 | Write-ColorOutput "Found $($Sites.Count) SharePoint sites" "Green" 30 | } 31 | catch { 32 | Write-ColorOutput "Error retrieving SharePoint sites: $($_.Exception.Message)" "Red" 33 | Write-Log "Error retrieving SharePoint sites: $($_.Exception.Message)" "ERROR" 34 | } 35 | 36 | $SecurityFindings = @() 37 | $Counter = 0 38 | 39 | Write-Host "" 40 | Write-Host "=== SHAREPOINT SHARING SECURITY ANALYSIS ===" -ForegroundColor Cyan 41 | Write-Host "" 42 | 43 | # Analyze tenant-level settings first 44 | Write-Host "Analyzing tenant-level sharing settings..." -ForegroundColor Yellow 45 | 46 | # Get organization settings 47 | try { 48 | $OrgSettings = Get-MgOrganization -Property "Id,DisplayName" 49 | 50 | # Create tenant-level finding 51 | $TenantFinding = [PSCustomObject]@{ 52 | SiteName = "TENANT LEVEL" 53 | SiteUrl = "N/A" 54 | SiteType = "Tenant" 55 | CreatedDate = "N/A" 56 | LastModified = "N/A" 57 | ExternalSharingEnabled = "Unknown - Requires SharePoint Admin Module" 58 | GuestAccessEnabled = "Unknown - Requires SharePoint Admin Module" 59 | AnonymousLinksEnabled = "Unknown - Requires SharePoint Admin Module" 60 | RiskLevel = "Medium" 61 | Issues = "Manual review required for tenant-level settings" 62 | Recommendations = "Review SharePoint Admin Center for external sharing settings" 63 | } 64 | 65 | $SecurityFindings += $TenantFinding 66 | Write-Host "[MANUAL REVIEW] Tenant Level - Check SharePoint Admin Center" -ForegroundColor Yellow 67 | } 68 | catch { 69 | Write-Log "Could not retrieve tenant settings: $($_.Exception.Message)" "WARNING" 70 | } 71 | 72 | # Analyze individual sites (sample of first 50 sites for performance) 73 | $SitesToAnalyze = $Sites | Select-Object -First 50 74 | $TotalSites = $SitesToAnalyze.Count 75 | 76 | foreach ($Site in $SitesToAnalyze) { 77 | $Counter++ 78 | Write-Progress -Activity "Analyzing SharePoint Sites" -Status "Processing $($Site.Name)" -PercentComplete (($Counter / $TotalSites) * 100) 79 | 80 | try { 81 | # Basic site information 82 | $SiteName = $Site.Name 83 | $SiteUrl = $Site.WebUrl 84 | $CreatedDate = $Site.CreatedDateTime 85 | $LastModified = $Site.LastModifiedDateTime 86 | 87 | # For demonstration, we'll analyze what we can through Graph API 88 | # Real sharing settings would require SharePoint CSOM or PnP PowerShell 89 | 90 | $RiskLevel = "Low" 91 | $Issues = @() 92 | $Recommendations = @() 93 | 94 | # Check if site is recently created (might need attention) 95 | if ($CreatedDate -and $CreatedDate -gt (Get-Date).AddDays(-30)) { 96 | $Issues += "Recently created site (within 30 days)" 97 | $Recommendations += "Review sharing settings for new site" 98 | } 99 | 100 | # Check site URL for potential issues 101 | if ($SiteUrl -like "*personal*") { 102 | $RiskLevel = "Medium" 103 | $Issues += "OneDrive personal site detected" 104 | $Recommendations += "Review OneDrive sharing policies" 105 | } 106 | 107 | # Create finding 108 | $Finding = [PSCustomObject]@{ 109 | SiteName = $SiteName 110 | SiteUrl = $SiteUrl 111 | SiteType = if ($SiteUrl -like "*personal*") { "OneDrive" } else { "SharePoint" } 112 | CreatedDate = if ($CreatedDate) { $CreatedDate.ToString("yyyy-MM-dd") } else { "Unknown" } 113 | LastModified = if ($LastModified) { $LastModified.ToString("yyyy-MM-dd") } else { "Unknown" } 114 | ExternalSharingEnabled = "Requires SharePoint Admin Module" 115 | GuestAccessEnabled = "Requires SharePoint Admin Module" 116 | AnonymousLinksEnabled = "Requires SharePoint Admin Module" 117 | RiskLevel = $RiskLevel 118 | Issues = ($Issues -join '; ') 119 | Recommendations = ($Recommendations -join '; ') 120 | } 121 | 122 | $SecurityFindings += $Finding 123 | 124 | } 125 | catch { 126 | Write-Log "Error analyzing site $($Site.Name): $($_.Exception.Message)" "WARNING" 127 | } 128 | } 129 | 130 | Write-Progress -Activity "Analyzing SharePoint Sites" -Completed 131 | 132 | # Summary 133 | Write-Host "" 134 | Write-Host "=== SHAREPOINT SHARING SUMMARY ===" -ForegroundColor Cyan 135 | Write-Host "Total Sites Analyzed: $($SecurityFindings.Count)" 136 | Write-Host "SharePoint Sites: $($SecurityFindings | Where-Object {$_.SiteType -eq 'SharePoint'} | Measure-Object | Select-Object -ExpandProperty Count)" 137 | Write-Host "OneDrive Sites: $($SecurityFindings | Where-Object {$_.SiteType -eq 'OneDrive'} | Measure-Object | Select-Object -ExpandProperty Count)" 138 | Write-Host "" 139 | Write-Host "⚠️ IMPORTANT NOTE:" -ForegroundColor Yellow 140 | Write-Host " Full SharePoint sharing analysis requires additional modules:" -ForegroundColor Yellow 141 | Write-Host " - PnP.PowerShell" -ForegroundColor Yellow 142 | Write-Host " - SharePoint Online Management Shell" -ForegroundColor Yellow 143 | Write-Host " This report provides basic site enumeration only." -ForegroundColor Yellow 144 | 145 | # Export to CSV 146 | $Config = Get-SecurityConfig 147 | $ExportPath = $Config.ExportPath 148 | $FileName = "SharePoint_Sharing_Report_$script:Timestamp.csv" 149 | $FilePath = Join-Path $ExportPath $FileName 150 | 151 | try { 152 | $SecurityFindings | Export-Csv -Path $FilePath -NoTypeInformation -Encoding UTF8 153 | Write-ColorOutput "Report exported to: $FilePath" "Green" 154 | } 155 | catch { 156 | Write-ColorOutput "Failed to export report: $($_.Exception.Message)" "Red" 157 | } 158 | 159 | Write-Log "SharePoint Sharing Analysis completed" "INFO" 160 | 161 | } 162 | catch { 163 | Write-ColorOutput "Error during SharePoint Sharing Analysis: $($_.Exception.Message)" "Red" 164 | Write-Log "Error during SharePoint Sharing Analysis: $($_.Exception.Message)" "ERROR" 165 | } 166 | } 167 | 168 | # Function to check OneDrive usage and security 169 | function Get-OneDriveSecurityReport { 170 | Write-ColorOutput "Analyzing OneDrive Security and Usage..." "Yellow" 171 | Write-Log "Starting OneDrive Security Analysis" "INFO" 172 | 173 | try { 174 | # Get OneDrive usage report 175 | $OneDriveUsage = @() 176 | 177 | try { 178 | # Get OneDrive usage using Microsoft Graph Reports 179 | $UsageReport = Get-MgReportOneDriveUsageAccountDetail -Period D30 180 | 181 | if ($UsageReport) { 182 | $OneDriveUsage = $UsageReport | ConvertFrom-Csv 183 | Write-ColorOutput "Retrieved OneDrive usage data for $($OneDriveUsage.Count) users" "Green" 184 | } else { 185 | Write-ColorOutput "No OneDrive usage data available" "Yellow" 186 | } 187 | } 188 | catch { 189 | Write-ColorOutput "Error retrieving OneDrive usage data: $($_.Exception.Message)" "Yellow" 190 | Write-Log "Error retrieving OneDrive usage data: $($_.Exception.Message)" "WARNING" 191 | } 192 | 193 | $SecurityFindings = @() 194 | $Counter = 0 195 | $TotalUsers = $OneDriveUsage.Count 196 | 197 | Write-Host "" 198 | Write-Host "=== ONEDRIVE SECURITY ANALYSIS ===" -ForegroundColor Cyan 199 | Write-Host "" 200 | 201 | if ($OneDriveUsage.Count -eq 0) { 202 | Write-ColorOutput "No OneDrive usage data to analyze. This may require SharePoint Administrator permissions." "Yellow" 203 | 204 | # Try to get basic user information instead 205 | try { 206 | $Users = Get-MgUser -Select "Id,DisplayName,UserPrincipalName,CreatedDateTime,SignInActivity" -Top 100 207 | 208 | foreach ($User in $Users) { 209 | $Finding = [PSCustomObject]@{ 210 | UserPrincipalName = $User.UserPrincipalName 211 | DisplayName = $User.DisplayName 212 | OneDriveUrl = "Unknown - Requires SharePoint Admin" 213 | StorageUsed = "Unknown" 214 | StorageQuota = "Unknown" 215 | FileCount = "Unknown" 216 | LastActivityDate = "Unknown" 217 | SharingCapability = "Unknown" 218 | RiskLevel = "Unknown" 219 | Issues = "Requires SharePoint Administrator permissions for detailed analysis" 220 | Recommendations = "Enable SharePoint reporting permissions" 221 | } 222 | $SecurityFindings += $Finding 223 | } 224 | } 225 | catch { 226 | Write-ColorOutput "Unable to retrieve user data: $($_.Exception.Message)" "Red" 227 | } 228 | } else { 229 | foreach ($Usage in $OneDriveUsage) { 230 | $Counter++ 231 | Write-Progress -Activity "Analyzing OneDrive Users" -Status "Processing $($Usage.'Owner Display Name')" -PercentComplete (($Counter / $TotalUsers) * 100) 232 | 233 | try { 234 | # Parse usage data 235 | $StorageUsed = if ($Usage.'Storage Used (Byte)') { [long]$Usage.'Storage Used (Byte)' } else { 0 } 236 | $StorageQuota = if ($Usage.'Storage Allocated (Byte)') { [long]$Usage.'Storage Allocated (Byte)' } else { 0 } 237 | $FileCount = if ($Usage.'File Count') { [int]$Usage.'File Count' } else { 0 } 238 | $LastActivityDate = $Usage.'Last Activity Date' 239 | 240 | # Calculate storage usage percentage 241 | $StoragePercentage = if ($StorageQuota -gt 0) { ($StorageUsed / $StorageQuota) * 100 } else { 0 } 242 | 243 | # Determine risk level 244 | $RiskLevel = "Low" 245 | $Issues = @() 246 | $Recommendations = @() 247 | 248 | # Check for high storage usage 249 | if ($StoragePercentage -gt 90) { 250 | $RiskLevel = "High" 251 | $Issues += "Storage usage above 90%" 252 | $Recommendations += "Monitor storage usage and consider increasing quota" 253 | } elseif ($StoragePercentage -gt 75) { 254 | $RiskLevel = "Medium" 255 | $Issues += "Storage usage above 75%" 256 | $Recommendations += "Monitor storage usage" 257 | } 258 | 259 | # Check for inactive OneDrive 260 | if ($LastActivityDate) { 261 | $LastActivity = [DateTime]::Parse($LastActivityDate) 262 | if ($LastActivity -lt (Get-Date).AddDays(-90)) { 263 | if ($RiskLevel -eq "Low") { $RiskLevel = "Medium" } 264 | $Issues += "No activity in 90+ days" 265 | $Recommendations += "Review if OneDrive is still needed" 266 | } 267 | } 268 | 269 | # Check for large number of files 270 | if ($FileCount -gt 10000) { 271 | if ($RiskLevel -eq "Low") { $RiskLevel = "Medium" } 272 | $Issues += "Large number of files ($FileCount)" 273 | $Recommendations += "Consider organizing files into folders" 274 | } 275 | 276 | $Finding = [PSCustomObject]@{ 277 | UserPrincipalName = $Usage.'Owner Principal Name' 278 | DisplayName = $Usage.'Owner Display Name' 279 | OneDriveUrl = $Usage.'Site URL' 280 | StorageUsed = "$([math]::Round($StorageUsed / 1GB, 2)) GB" 281 | StorageQuota = "$([math]::Round($StorageQuota / 1GB, 2)) GB" 282 | StoragePercentage = "$([math]::Round($StoragePercentage, 1))%" 283 | FileCount = $FileCount 284 | LastActivityDate = $LastActivityDate 285 | SharingCapability = "Requires SharePoint Admin Module" 286 | RiskLevel = $RiskLevel 287 | Issues = ($Issues -join '; ') 288 | Recommendations = ($Recommendations -join '; ') 289 | } 290 | 291 | $SecurityFindings += $Finding 292 | 293 | } 294 | catch { 295 | Write-Log "Error analyzing OneDrive for $($Usage.'Owner Display Name'): $($_.Exception.Message)" "WARNING" 296 | } 297 | } 298 | } 299 | 300 | Write-Progress -Activity "Analyzing OneDrive Users" -Completed 301 | 302 | # Summary 303 | Write-Host "" 304 | Write-Host "=== ONEDRIVE SECURITY SUMMARY ===" -ForegroundColor Cyan 305 | Write-Host "Total OneDrive Accounts: $($SecurityFindings.Count)" 306 | 307 | if ($OneDriveUsage.Count -gt 0) { 308 | Write-Host "High Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'High'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 309 | Write-Host "Medium Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Medium'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Yellow 310 | Write-Host "Low Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Low'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Green 311 | 312 | # Calculate totals 313 | $TotalStorageUsed = ($SecurityFindings | ForEach-Object { 314 | if ($_.'StorageUsed' -match '(\d+\.?\d*) GB') { [double]$Matches[1] } else { 0 } 315 | } | Measure-Object -Sum).Sum 316 | 317 | Write-Host "" 318 | Write-Host "Storage Statistics:" -ForegroundColor Yellow 319 | Write-Host " Total Storage Used: $([math]::Round($TotalStorageUsed, 2)) GB" 320 | Write-Host " Average per User: $([math]::Round($TotalStorageUsed / $SecurityFindings.Count, 2)) GB" 321 | } 322 | 323 | # Export to CSV 324 | $Config = Get-SecurityConfig 325 | $ExportPath = $Config.ExportPath 326 | $FileName = "OneDrive_Security_Report_$script:Timestamp.csv" 327 | $FilePath = Join-Path $ExportPath $FileName 328 | 329 | try { 330 | $SecurityFindings | Export-Csv -Path $FilePath -NoTypeInformation -Encoding UTF8 331 | Write-ColorOutput "Report exported to: $FilePath" "Green" 332 | } 333 | catch { 334 | Write-ColorOutput "Failed to export report: $($_.Exception.Message)" "Red" 335 | } 336 | 337 | Write-Log "OneDrive Security Analysis completed" "INFO" 338 | 339 | } 340 | catch { 341 | Write-ColorOutput "Error during OneDrive Security Analysis: $($_.Exception.Message)" "Red" 342 | Write-Log "Error during OneDrive Security Analysis: $($_.Exception.Message)" "ERROR" 343 | } 344 | } 345 | 346 | # Function to check data loss prevention policies 347 | function Get-DLPPolicyReport { 348 | Write-ColorOutput "Analyzing Data Loss Prevention Policies..." "Yellow" 349 | Write-Log "Starting DLP Policy Analysis" "INFO" 350 | 351 | try { 352 | Write-Host "" 353 | Write-Host "=== DATA LOSS PREVENTION ANALYSIS ===" -ForegroundColor Cyan 354 | Write-Host "" 355 | 356 | # Note: Full DLP analysis requires Security & Compliance PowerShell 357 | Write-Host "⚠️ DLP Policy Analysis Limitation:" -ForegroundColor Yellow 358 | Write-Host " Full DLP analysis requires Security & Compliance Center PowerShell module" -ForegroundColor Yellow 359 | Write-Host " This module is not included in the current implementation" -ForegroundColor Yellow 360 | Write-Host "" 361 | 362 | # Provide basic recommendations 363 | $DLPFindings = @( 364 | [PSCustomObject]@{ 365 | PolicyName = "Manual Review Required" 366 | PolicyType = "Security & Compliance Center" 367 | Status = "Unknown" 368 | Locations = "SharePoint, OneDrive, Exchange, Teams" 369 | SensitiveInfoTypes = "Requires Security & Compliance PowerShell" 370 | RiskLevel = "Medium" 371 | Issues = "Manual review of DLP policies required" 372 | Recommendations = "Connect to Security & Compliance Center PowerShell and review DLP policies" 373 | } 374 | ) 375 | 376 | Write-Host "DLP Policy Recommendations:" -ForegroundColor Cyan 377 | Write-Host "1. Review existing DLP policies in Security & Compliance Center" 378 | Write-Host "2. Ensure policies cover SharePoint, OneDrive, Exchange, and Teams" 379 | Write-Host "3. Test DLP policies with sample sensitive data" 380 | Write-Host "4. Monitor DLP policy matches and false positives" 381 | Write-Host "5. Configure appropriate user notifications and policy tips" 382 | 383 | # Export basic DLP guidance 384 | $Config = Get-SecurityConfig 385 | $ExportPath = $Config.ExportPath 386 | $FileName = "DLP_Policy_Guidance_$script:Timestamp.csv" 387 | $FilePath = Join-Path $ExportPath $FileName 388 | 389 | try { 390 | $DLPFindings | Export-Csv -Path $FilePath -NoTypeInformation -Encoding UTF8 391 | Write-ColorOutput "DLP guidance exported to: $FilePath" "Green" 392 | } 393 | catch { 394 | Write-ColorOutput "Failed to export DLP guidance: $($_.Exception.Message)" "Red" 395 | } 396 | 397 | Write-Log "DLP Policy Analysis completed (manual review required)" "INFO" 398 | 399 | } 400 | catch { 401 | Write-ColorOutput "Error during DLP Policy Analysis: $($_.Exception.Message)" "Red" 402 | Write-Log "Error during DLP Policy Analysis: $($_.Exception.Message)" "ERROR" 403 | } 404 | } 405 | 406 | Export-ModuleMember -Function Get-SharePointSharingReport, Get-OneDriveSecurityReport, Get-DLPPolicyReport 407 | -------------------------------------------------------------------------------- /Modules/AzureSecurityInfrastructure.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - Azure Storage Security Module 2 | # Contains storage security assessment functions 3 | 4 | # Function to check Azure Storage Account security configuration 5 | function Get-StorageSecurityReport { 6 | Write-ColorOutput "Analyzing Azure Storage Account Security..." "Yellow" 7 | Write-Log "Starting Storage Security Analysis" "INFO" 8 | 9 | try { 10 | # Get all storage accounts 11 | $StorageAccounts = Get-AzStorageAccount 12 | 13 | if (-not $StorageAccounts) { 14 | Write-ColorOutput "No storage accounts found in the current subscription." "Yellow" 15 | return 16 | } 17 | 18 | $SecurityFindings = @() 19 | $TotalAccounts = $StorageAccounts.Count 20 | $Counter = 0 21 | 22 | Write-Host "" 23 | Write-Host "=== AZURE STORAGE SECURITY ANALYSIS ===" -ForegroundColor Cyan 24 | Write-Host "" 25 | 26 | foreach ($StorageAccount in $StorageAccounts) { 27 | $Counter++ 28 | Write-Progress -Activity "Analyzing Storage Accounts" -Status "Processing $($StorageAccount.StorageAccountName)" -PercentComplete (($Counter / $TotalAccounts) * 100) 29 | 30 | # Get storage account context 31 | $StorageContext = $StorageAccount.Context 32 | 33 | # Check public access 34 | $PublicAccess = "Unknown" 35 | try { 36 | $PublicAccess = $StorageAccount.AllowBlobPublicAccess 37 | } catch { 38 | Write-Log "Could not determine public access for $($StorageAccount.StorageAccountName)" "WARNING" 39 | } 40 | 41 | # Check encryption 42 | $EncryptionStatus = if ($StorageAccount.Encryption) { "Enabled" } else { "Disabled" } 43 | 44 | # Check HTTPS only 45 | $HttpsOnly = if ($StorageAccount.EnableHttpsTrafficOnly) { "Enabled" } else { "Disabled" } 46 | 47 | # Check network access 48 | $NetworkAccess = $StorageAccount.NetworkRuleSet.DefaultAction 49 | 50 | # Check for public blob containers 51 | $PublicContainers = @() 52 | try { 53 | $Containers = Get-AzStorageContainer -Context $StorageContext -ErrorAction SilentlyContinue 54 | foreach ($Container in $Containers) { 55 | if ($Container.PublicAccess -ne "Off") { 56 | $PublicContainers += $Container.Name 57 | } 58 | } 59 | } catch { 60 | Write-Log "Could not check containers for $($StorageAccount.StorageAccountName): $($_.Exception.Message)" "WARNING" 61 | } 62 | 63 | # Determine risk level 64 | $RiskLevel = "Low" 65 | $Issues = @() 66 | 67 | if ($PublicAccess -eq $true) { 68 | $RiskLevel = "High" 69 | $Issues += "Public blob access allowed" 70 | } 71 | 72 | if ($HttpsOnly -eq "Disabled") { 73 | $RiskLevel = "High" 74 | $Issues += "HTTPS not enforced" 75 | } 76 | 77 | if ($PublicContainers.Count -gt 0) { 78 | $RiskLevel = "Critical" 79 | $Issues += "Public containers detected: $($PublicContainers -join ', ')" 80 | } 81 | 82 | if ($NetworkAccess -eq "Allow") { 83 | if ($RiskLevel -eq "Low") { $RiskLevel = "Medium" } 84 | $Issues += "Network access allows all" 85 | } 86 | 87 | # Add to findings 88 | $Finding = [PSCustomObject]@{ 89 | StorageAccountName = $StorageAccount.StorageAccountName 90 | ResourceGroup = $StorageAccount.ResourceGroupName 91 | Location = $StorageAccount.Location 92 | PublicBlobAccess = $PublicAccess 93 | HttpsOnly = $HttpsOnly 94 | Encryption = $EncryptionStatus 95 | NetworkAccess = $NetworkAccess 96 | PublicContainers = ($PublicContainers -join '; ') 97 | PublicContainerCount = $PublicContainers.Count 98 | RiskLevel = $RiskLevel 99 | Issues = ($Issues -join '; ') 100 | LastModified = $StorageAccount.LastGeoFailoverTime 101 | } 102 | 103 | $SecurityFindings += $Finding 104 | 105 | # Display finding 106 | $RiskColor = switch ($RiskLevel) { 107 | "Critical" { "Red" } 108 | "High" { "Red" } 109 | "Medium" { "Yellow" } 110 | "Low" { "Green" } 111 | default { "White" } 112 | } 113 | 114 | Write-Host "[$RiskLevel] " -ForegroundColor $RiskColor -NoNewline 115 | Write-Host "$($StorageAccount.StorageAccountName) " -NoNewline 116 | if ($Issues.Count -gt 0) { 117 | Write-Host "- Issues: $($Issues -join ', ')" -ForegroundColor $RiskColor 118 | } else { 119 | Write-Host "- No security issues detected" -ForegroundColor Green 120 | } 121 | } 122 | 123 | Write-Progress -Activity "Analyzing Storage Accounts" -Completed 124 | 125 | # Summary 126 | Write-Host "" 127 | Write-Host "=== STORAGE SECURITY SUMMARY ===" -ForegroundColor Cyan 128 | Write-Host "Total Storage Accounts: $TotalAccounts" 129 | Write-Host "Critical Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Critical'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 130 | Write-Host "High Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'High'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 131 | Write-Host "Medium Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Medium'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Yellow 132 | Write-Host "Low Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Low'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Green 133 | 134 | # Export to CSV 135 | $Config = Get-SecurityConfig 136 | $ExportPath = $Config.ExportPath 137 | $FileName = "Storage_Security_Report_$script:Timestamp.csv" 138 | $FilePath = Join-Path $ExportPath $FileName 139 | 140 | try { 141 | $SecurityFindings | Export-Csv -Path $FilePath -NoTypeInformation -Encoding UTF8 142 | Write-ColorOutput "Report exported to: $FilePath" "Green" 143 | } 144 | catch { 145 | Write-ColorOutput "Failed to export report: $($_.Exception.Message)" "Red" 146 | } 147 | 148 | Write-Log "Storage Security Analysis completed" "INFO" 149 | 150 | } 151 | catch { 152 | Write-ColorOutput "Error during Storage Security Analysis: $($_.Exception.Message)" "Red" 153 | Write-Log "Error during Storage Security Analysis: $($_.Exception.Message)" "ERROR" 154 | } 155 | } 156 | 157 | # Function to check Azure Key Vault security configuration 158 | function Get-KeyVaultSecurityReport { 159 | Write-ColorOutput "Analyzing Azure Key Vault Security..." "Yellow" 160 | Write-Log "Starting Key Vault Security Analysis" "INFO" 161 | 162 | try { 163 | # Get all key vaults 164 | $KeyVaults = Get-AzKeyVault 165 | 166 | if (-not $KeyVaults) { 167 | Write-ColorOutput "No Key Vaults found in the current subscription." "Yellow" 168 | return 169 | } 170 | 171 | $SecurityFindings = @() 172 | $TotalVaults = $KeyVaults.Count 173 | $Counter = 0 174 | 175 | Write-Host "" 176 | Write-Host "=== AZURE KEY VAULT SECURITY ANALYSIS ===" -ForegroundColor Cyan 177 | Write-Host "" 178 | 179 | foreach ($Vault in $KeyVaults) { 180 | $Counter++ 181 | Write-Progress -Activity "Analyzing Key Vaults" -Status "Processing $($Vault.VaultName)" -PercentComplete (($Counter / $TotalVaults) * 100) 182 | 183 | # Get detailed vault information 184 | $VaultDetails = Get-AzKeyVault -VaultName $Vault.VaultName -ResourceGroupName $Vault.ResourceGroupName 185 | 186 | # Check network access 187 | $NetworkAccess = if ($VaultDetails.NetworkAcls) { 188 | $VaultDetails.NetworkAcls.DefaultAction 189 | } else { 190 | "Allow" 191 | } 192 | 193 | # Check soft delete 194 | $SoftDeleteEnabled = $VaultDetails.EnableSoftDelete 195 | 196 | # Check purge protection 197 | $PurgeProtectionEnabled = $VaultDetails.EnablePurgeProtection 198 | 199 | # Check RBAC authorization 200 | $RbacAuthorizationEnabled = $VaultDetails.EnableRbacAuthorization 201 | 202 | # Check certificates nearing expiration 203 | $ExpiringCerts = @() 204 | try { 205 | $Certificates = Get-AzKeyVaultCertificate -VaultName $Vault.VaultName 206 | $ThirtyDaysFromNow = (Get-Date).AddDays(30) 207 | 208 | foreach ($Cert in $Certificates) { 209 | $CertDetails = Get-AzKeyVaultCertificate -VaultName $Vault.VaultName -Name $Cert.Name 210 | if ($CertDetails.Expires -and $CertDetails.Expires -le $ThirtyDaysFromNow) { 211 | $ExpiringCerts += "$($Cert.Name) (Expires: $($CertDetails.Expires.ToString('yyyy-MM-dd')))" 212 | } 213 | } 214 | } catch { 215 | Write-Log "Could not check certificates for $($Vault.VaultName): $($_.Exception.Message)" "WARNING" 216 | } 217 | 218 | # Check access policies 219 | $AccessPolicyCount = if ($VaultDetails.AccessPolicies) { $VaultDetails.AccessPolicies.Count } else { 0 } 220 | 221 | # Determine risk level 222 | $RiskLevel = "Low" 223 | $Issues = @() 224 | 225 | if ($NetworkAccess -eq "Allow") { 226 | $RiskLevel = "Medium" 227 | $Issues += "Network access allows all IPs" 228 | } 229 | 230 | if (-not $SoftDeleteEnabled) { 231 | $RiskLevel = "High" 232 | $Issues += "Soft delete not enabled" 233 | } 234 | 235 | if (-not $PurgeProtectionEnabled) { 236 | if ($RiskLevel -eq "Low") { $RiskLevel = "Medium" } 237 | $Issues += "Purge protection not enabled" 238 | } 239 | 240 | if ($ExpiringCerts.Count -gt 0) { 241 | $RiskLevel = "High" 242 | $Issues += "Certificates expiring within 30 days: $($ExpiringCerts.Count)" 243 | } 244 | 245 | if ($AccessPolicyCount -gt 10) { 246 | if ($RiskLevel -eq "Low") { $RiskLevel = "Medium" } 247 | $Issues += "High number of access policies ($AccessPolicyCount)" 248 | } 249 | 250 | # Add to findings 251 | $Finding = [PSCustomObject]@{ 252 | VaultName = $Vault.VaultName 253 | ResourceGroup = $Vault.ResourceGroupName 254 | Location = $Vault.Location 255 | NetworkAccess = $NetworkAccess 256 | SoftDeleteEnabled = $SoftDeleteEnabled 257 | PurgeProtectionEnabled = $PurgeProtectionEnabled 258 | RbacAuthorizationEnabled = $RbacAuthorizationEnabled 259 | AccessPolicyCount = $AccessPolicyCount 260 | ExpiringCertificates = ($ExpiringCerts -join '; ') 261 | ExpiringCertCount = $ExpiringCerts.Count 262 | RiskLevel = $RiskLevel 263 | Issues = ($Issues -join '; ') 264 | } 265 | 266 | $SecurityFindings += $Finding 267 | 268 | # Display finding 269 | $RiskColor = switch ($RiskLevel) { 270 | "Critical" { "Red" } 271 | "High" { "Red" } 272 | "Medium" { "Yellow" } 273 | "Low" { "Green" } 274 | default { "White" } 275 | } 276 | 277 | Write-Host "[$RiskLevel] " -ForegroundColor $RiskColor -NoNewline 278 | Write-Host "$($Vault.VaultName) " -NoNewline 279 | if ($Issues.Count -gt 0) { 280 | Write-Host "- Issues: $($Issues -join ', ')" -ForegroundColor $RiskColor 281 | } else { 282 | Write-Host "- No security issues detected" -ForegroundColor Green 283 | } 284 | } 285 | 286 | Write-Progress -Activity "Analyzing Key Vaults" -Completed 287 | 288 | # Summary 289 | Write-Host "" 290 | Write-Host "=== KEY VAULT SECURITY SUMMARY ===" -ForegroundColor Cyan 291 | Write-Host "Total Key Vaults: $TotalVaults" 292 | Write-Host "Critical Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Critical'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 293 | Write-Host "High Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'High'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 294 | Write-Host "Medium Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Medium'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Yellow 295 | Write-Host "Low Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Low'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Green 296 | 297 | # Export to CSV 298 | $Config = Get-SecurityConfig 299 | $ExportPath = $Config.ExportPath 300 | $FileName = "KeyVault_Security_Report_$script:Timestamp.csv" 301 | $FilePath = Join-Path $ExportPath $FileName 302 | 303 | try { 304 | $SecurityFindings | Export-Csv -Path $FilePath -NoTypeInformation -Encoding UTF8 305 | Write-ColorOutput "Report exported to: $FilePath" "Green" 306 | } 307 | catch { 308 | Write-ColorOutput "Failed to export report: $($_.Exception.Message)" "Red" 309 | } 310 | 311 | Write-Log "Key Vault Security Analysis completed" "INFO" 312 | 313 | } 314 | catch { 315 | Write-ColorOutput "Error during Key Vault Security Analysis: $($_.Exception.Message)" "Red" 316 | Write-Log "Error during Key Vault Security Analysis: $($_.Exception.Message)" "ERROR" 317 | } 318 | } 319 | 320 | # Function to check Azure Network Security Groups 321 | function Get-NetworkSecurityReport { 322 | Write-ColorOutput "Analyzing Azure Network Security Groups..." "Yellow" 323 | Write-Log "Starting Network Security Analysis" "INFO" 324 | 325 | try { 326 | # Get all NSGs 327 | $NSGs = Get-AzNetworkSecurityGroup 328 | 329 | if (-not $NSGs) { 330 | Write-ColorOutput "No Network Security Groups found in the current subscription." "Yellow" 331 | return 332 | } 333 | 334 | $SecurityFindings = @() 335 | $TotalNSGs = $NSGs.Count 336 | $Counter = 0 337 | 338 | Write-Host "" 339 | Write-Host "=== AZURE NETWORK SECURITY ANALYSIS ===" -ForegroundColor Cyan 340 | Write-Host "" 341 | 342 | foreach ($NSG in $NSGs) { 343 | $Counter++ 344 | Write-Progress -Activity "Analyzing Network Security Groups" -Status "Processing $($NSG.Name)" -PercentComplete (($Counter / $TotalNSGs) * 100) 345 | 346 | $DangerousRules = @() 347 | $OpenPorts = @() 348 | 349 | # Check security rules 350 | foreach ($Rule in $NSG.SecurityRules) { 351 | # Check for dangerous rules 352 | if ($Rule.Access -eq "Allow" -and $Rule.Direction -eq "Inbound") { 353 | # Check for any source (*) with common dangerous ports 354 | if ($Rule.SourceAddressPrefix -eq "*" -or $Rule.SourceAddressPrefix -eq "Internet") { 355 | $DangerousPorts = @("22", "3389", "1433", "3306", "5432", "1521", "27017") 356 | 357 | foreach ($Port in $DangerousPorts) { 358 | if ($Rule.DestinationPortRange -eq $Port -or 359 | $Rule.DestinationPortRange -eq "*" -or 360 | ($Rule.DestinationPortRange -like "*-*" -and $Port -ge ($Rule.DestinationPortRange.Split('-')[0]) -and $Port -le ($Rule.DestinationPortRange.Split('-')[1]))) { 361 | $DangerousRules += "$($Rule.Name): Port $Port open to Internet" 362 | $OpenPorts += $Port 363 | } 364 | } 365 | 366 | # Check for wildcard ports 367 | if ($Rule.DestinationPortRange -eq "*") { 368 | $DangerousRules += "$($Rule.Name): All ports open to Internet" 369 | } 370 | } 371 | } 372 | } 373 | 374 | # Determine risk level 375 | $RiskLevel = "Low" 376 | $Issues = @() 377 | 378 | if ($DangerousRules.Count -gt 0) { 379 | $RiskLevel = "Critical" 380 | $Issues += "Dangerous inbound rules detected" 381 | } 382 | 383 | if ($OpenPorts -contains "22" -or $OpenPorts -contains "3389") { 384 | $RiskLevel = "Critical" 385 | $Issues += "SSH/RDP open to Internet" 386 | } 387 | 388 | if ($OpenPorts -contains "1433" -or $OpenPorts -contains "3306" -or $OpenPorts -contains "5432") { 389 | $RiskLevel = "Critical" 390 | $Issues += "Database ports open to Internet" 391 | } 392 | 393 | # Add to findings 394 | $Finding = [PSCustomObject]@{ 395 | NSGName = $NSG.Name 396 | ResourceGroup = $NSG.ResourceGroupName 397 | Location = $NSG.Location 398 | SecurityRulesCount = $NSG.SecurityRules.Count 399 | DangerousRules = ($DangerousRules -join '; ') 400 | DangerousRulesCount = $DangerousRules.Count 401 | OpenPorts = ($OpenPorts | Sort-Object -Unique) -join ', ' 402 | AssociatedSubnets = ($NSG.Subnets | ForEach-Object { $_.Id.Split('/')[-1] }) -join ', ' 403 | AssociatedNICs = ($NSG.NetworkInterfaces | ForEach-Object { $_.Id.Split('/')[-1] }) -join ', ' 404 | RiskLevel = $RiskLevel 405 | Issues = ($Issues -join '; ') 406 | } 407 | 408 | $SecurityFindings += $Finding 409 | 410 | # Display finding 411 | $RiskColor = switch ($RiskLevel) { 412 | "Critical" { "Red" } 413 | "High" { "Red" } 414 | "Medium" { "Yellow" } 415 | "Low" { "Green" } 416 | default { "White" } 417 | } 418 | 419 | Write-Host "[$RiskLevel] " -ForegroundColor $RiskColor -NoNewline 420 | Write-Host "$($NSG.Name) " -NoNewline 421 | if ($Issues.Count -gt 0) { 422 | Write-Host "- Issues: $($Issues -join ', ')" -ForegroundColor $RiskColor 423 | } else { 424 | Write-Host "- No security issues detected" -ForegroundColor Green 425 | } 426 | } 427 | 428 | Write-Progress -Activity "Analyzing Network Security Groups" -Completed 429 | 430 | # Summary 431 | Write-Host "" 432 | Write-Host "=== NETWORK SECURITY SUMMARY ===" -ForegroundColor Cyan 433 | Write-Host "Total NSGs: $TotalNSGs" 434 | Write-Host "Critical Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Critical'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 435 | Write-Host "High Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'High'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Red 436 | Write-Host "Medium Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Medium'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Yellow 437 | Write-Host "Low Risk: $($SecurityFindings | Where-Object {$_.RiskLevel -eq 'Low'} | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor Green 438 | 439 | # Export to CSV 440 | $Config = Get-SecurityConfig 441 | $ExportPath = $Config.ExportPath 442 | $FileName = "Network_Security_Report_$script:Timestamp.csv" 443 | $FilePath = Join-Path $ExportPath $FileName 444 | 445 | try { 446 | $SecurityFindings | Export-Csv -Path $FilePath -NoTypeInformation -Encoding UTF8 447 | Write-ColorOutput "Report exported to: $FilePath" "Green" 448 | } 449 | catch { 450 | Write-ColorOutput "Failed to export report: $($_.Exception.Message)" "Red" 451 | } 452 | 453 | Write-Log "Network Security Analysis completed" "INFO" 454 | 455 | } 456 | catch { 457 | Write-ColorOutput "Error during Network Security Analysis: $($_.Exception.Message)" "Red" 458 | Write-Log "Error during Network Security Analysis: $($_.Exception.Message)" "ERROR" 459 | } 460 | } 461 | 462 | Export-ModuleMember -Function Get-StorageSecurityReport, Get-KeyVaultSecurityReport, Get-NetworkSecurityReport 463 | -------------------------------------------------------------------------------- /Modules/AzureSecurityOffice365.psm1: -------------------------------------------------------------------------------- 1 | # Azure Security Report - Office 365 Module 2 | # Contains all Office 365-related security checks 3 | 4 | # Function to check Office 365 license usage 5 | function Get-LicenseUsageReport { 6 | Write-ColorOutput "Retrieving Office 365 license usage..." "Yellow" 7 | 8 | # Check if modules are already loaded (from successful authentication) 9 | if (-not (Get-Module -Name "Microsoft.Graph.Identity.DirectoryManagement" -ErrorAction SilentlyContinue)) { 10 | Write-ColorOutput "Required Microsoft Graph modules not loaded. Please ensure authentication was successful." "Red" 11 | Write-ColorOutput "If you see assembly conflicts, restart PowerShell and try again." "Yellow" 12 | return 13 | } 14 | 15 | try { 16 | # Get all SKUs (license types) in the tenant 17 | $SubscribedSkus = Get-MgSubscribedSku -All -ErrorAction Stop 18 | 19 | if ($SubscribedSkus.Count -eq 0) { 20 | Write-ColorOutput "No Office 365 licenses found in the tenant." "Yellow" 21 | return 22 | } 23 | 24 | $LicenseReport = @() 25 | $TotalUnassignedLicenses = 0 26 | $TotalWastedCost = 0 27 | 28 | foreach ($Sku in $SubscribedSkus) { 29 | $UnassignedCount = $Sku.PrepaidUnits.Enabled - $Sku.ConsumedUnits 30 | if ($UnassignedCount -lt 0) { $UnassignedCount = 0 } 31 | 32 | $TotalUnassignedLicenses += $UnassignedCount 33 | 34 | # Estimate cost impact (simplified calculation) 35 | $EstimatedMonthlyCostPerLicense = switch -Wildcard ($Sku.SkuPartNumber) { 36 | "*E5*" { 57 } 37 | "*E3*" { 36 } 38 | "*E1*" { 12 } 39 | "*BUSINESS_PREMIUM*" { 22 } 40 | "*BUSINESS_BASIC*" { 6 } 41 | "*EXCHANGESTANDARD*" { 4 } 42 | "*EXCHANGEONLINE*" { 8 } 43 | default { 15 } 44 | } 45 | 46 | $WastedCost = $UnassignedCount * $EstimatedMonthlyCostPerLicense 47 | $TotalWastedCost += $WastedCost 48 | 49 | $LicenseInfo = [PSCustomObject]@{ 50 | SkuPartNumber = $Sku.SkuPartNumber 51 | SkuDisplayName = if ($Sku.ServicePlans) { ($Sku.ServicePlans | Select-Object -First 1).ServicePlanName } else { $Sku.SkuPartNumber } 52 | TotalLicenses = $Sku.PrepaidUnits.Enabled 53 | AssignedLicenses = $Sku.ConsumedUnits 54 | UnassignedLicenses = $UnassignedCount 55 | UtilizationPercentage = [math]::Round(($Sku.ConsumedUnits / $Sku.PrepaidUnits.Enabled) * 100, 2) 56 | EstimatedMonthlyCostPerLicense = $EstimatedMonthlyCostPerLicense 57 | PotentialMonthlySavings = $WastedCost 58 | } 59 | 60 | $LicenseReport += $LicenseInfo 61 | 62 | # Display individual license info 63 | if ($UnassignedCount -gt 0) { 64 | Write-ColorOutput "⚠ $($Sku.SkuPartNumber): $($Sku.ConsumedUnits) assigned, $UnassignedCount unassigned (Potential savings: `$$WastedCost/month)" "Yellow" 65 | } else { 66 | Write-ColorOutput "✓ $($Sku.SkuPartNumber): $($Sku.ConsumedUnits) assigned, 0 unassigned" "Green" 67 | } 68 | } 69 | 70 | # Display summary 71 | Write-Host "" 72 | Write-ColorOutput "=== LICENSE USAGE SUMMARY ===" "Cyan" 73 | Write-ColorOutput "Total SKUs: $($SubscribedSkus.Count)" "White" 74 | Write-ColorOutput "Total unassigned licenses: $TotalUnassignedLicenses" $(if ($TotalUnassignedLicenses -gt 0) { "Yellow" } else { "Green" }) 75 | Write-ColorOutput "Estimated monthly savings if optimized: `$$TotalWastedCost" $(if ($TotalWastedCost -gt 0) { "Red" } else { "Green" }) 76 | 77 | if ($TotalUnassignedLicenses -gt 0) { 78 | Write-ColorOutput "⚠ Recommendation: Review and remove unused licenses to optimize costs." "Red" 79 | } 80 | 81 | # Prompt for export 82 | $Export = Read-Host "Would you like to export license details to CSV? (Y/N)" 83 | if ($Export -eq 'Y' -or $Export -eq 'y') { 84 | $FilePath = Get-ValidFilePath "License_Usage_Report" 85 | $LicenseReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 86 | Write-ColorOutput "License report exported to: $FilePath" "Green" 87 | } 88 | 89 | } catch { 90 | Write-ColorOutput "Error retrieving license usage: $($_.Exception.Message)" "Red" 91 | Write-Log "Error retrieving license usage: $($_.Exception.Message)" "ERROR" 92 | } 93 | } 94 | 95 | # Function to check inactive accounts 96 | function Get-InactiveAccountsReport { 97 | Write-ColorOutput "Checking for inactive user accounts (90+ days)..." "Yellow" 98 | 99 | try { 100 | # Calculate date 90 days ago 101 | $InactiveThreshold = (Get-Date).AddDays(-90) 102 | Write-ColorOutput "Checking for accounts inactive since: $($InactiveThreshold.ToString('yyyy-MM-dd'))" "Cyan" 103 | 104 | # Get all users with sign-in activity 105 | $Users = Get-MgUser -All -Property Id,UserPrincipalName,DisplayName,AccountEnabled,AssignedLicenses,SignInActivity -PageSize 100 | 106 | Where-Object { $_.UserPrincipalName -notlike "*#EXT#*" -and $_.AccountEnabled -eq $true } 107 | 108 | $InactiveUsers = @() 109 | $InactiveUsersWithLicenses = @() 110 | 111 | foreach ($User in $Users) { 112 | try { 113 | $LastSignIn = $null 114 | $IsInactive = $false 115 | 116 | # Check sign-in activity 117 | if ($User.SignInActivity -and $User.SignInActivity.LastSignInDateTime) { 118 | $LastSignIn = [DateTime]$User.SignInActivity.LastSignInDateTime 119 | $IsInactive = $LastSignIn -lt $InactiveThreshold 120 | } else { 121 | # No sign-in data available - consider as inactive 122 | $IsInactive = $true 123 | } 124 | 125 | if ($IsInactive) { 126 | $HasLicenses = $User.AssignedLicenses -and $User.AssignedLicenses.Count -gt 0 127 | 128 | $UserInfo = [PSCustomObject]@{ 129 | UPN = $User.UserPrincipalName 130 | DisplayName = $User.DisplayName 131 | LastSignInDate = if ($LastSignIn) { $LastSignIn.ToString('yyyy-MM-dd') } else { "Never" } 132 | DaysInactive = if ($LastSignIn) { ([DateTime]::Now - $LastSignIn).Days } else { "Unknown" } 133 | HasAssignedLicenses = $HasLicenses 134 | LicenseCount = if ($User.AssignedLicenses) { $User.AssignedLicenses.Count } else { 0 } 135 | AccountEnabled = $User.AccountEnabled 136 | } 137 | 138 | $InactiveUsers += $UserInfo 139 | 140 | if ($HasLicenses) { 141 | $InactiveUsersWithLicenses += $UserInfo 142 | } 143 | } 144 | } catch { 145 | Write-Log "Error checking sign-in for user $($User.UserPrincipalName): $($_.Exception.Message)" "ERROR" 146 | } 147 | } 148 | 149 | # Display summary 150 | Write-Host "" 151 | Write-ColorOutput "=== INACTIVE ACCOUNTS SUMMARY ===" "Cyan" 152 | 153 | if ($InactiveUsers.Count -eq 0) { 154 | Write-ColorOutput "✓ No inactive accounts found (all users active within 90 days)." "Green" 155 | } else { 156 | Write-ColorOutput "⚠ Inactive Accounts: $($InactiveUsers.Count) users have not signed in for 90+ days" "Yellow" 157 | 158 | if ($InactiveUsersWithLicenses.Count -gt 0) { 159 | Write-ColorOutput "🚨 Critical: $($InactiveUsersWithLicenses.Count) inactive accounts have active licenses assigned!" "Red" 160 | 161 | # Calculate potential license cost savings 162 | $TotalLicenses = ($InactiveUsersWithLicenses | Measure-Object -Property LicenseCount -Sum).Sum 163 | $EstimatedMonthlySavings = $TotalLicenses * 25 # Estimated average license cost 164 | Write-ColorOutput "💰 Potential monthly savings: `$$EstimatedMonthlySavings (estimated)" "Red" 165 | } 166 | 167 | # Prompt for export 168 | $Export = Read-Host "Would you like to export inactive accounts to CSV? (Y/N)" 169 | if ($Export -eq 'Y' -or $Export -eq 'y') { 170 | $FilePath = Get-ValidFilePath "Inactive_Accounts_Report" 171 | $InactiveUsers | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 172 | Write-ColorOutput "Inactive accounts report exported to: $FilePath" "Green" 173 | } 174 | } 175 | 176 | } catch { 177 | Write-ColorOutput "Error checking inactive accounts: $($_.Exception.Message)" "Red" 178 | Write-Log "Error checking inactive accounts: $($_.Exception.Message)" "ERROR" 179 | } 180 | } 181 | 182 | # Function to check mailbox forwarding rules 183 | function Get-MailboxForwardingReport { 184 | Write-ColorOutput "Checking mailbox forwarding rules..." "Yellow" 185 | 186 | try { 187 | # Check if Exchange Online module is available 188 | if (-not (Get-Module -ListAvailable -Name "ExchangeOnlineManagement")) { 189 | Write-ColorOutput "ExchangeOnlineManagement module is required for mailbox checks. Please install it with: Install-Module ExchangeOnlineManagement" "Red" 190 | return 191 | } 192 | 193 | # Connect to Exchange Online if not already connected 194 | try { 195 | Get-OrganizationConfig -ErrorAction Stop | Out-Null 196 | } catch { 197 | Write-ColorOutput "Connecting to Exchange Online..." "Yellow" 198 | Connect-ExchangeOnline -ShowProgress $false -ErrorAction Stop 199 | } 200 | 201 | # Get all mailboxes with forwarding configured 202 | $Mailboxes = Get-Mailbox -ResultSize Unlimited | Where-Object { 203 | $_.ForwardingAddress -or $_.ForwardingSmtpAddress -or $_.DeliverToMailboxAndForward 204 | } 205 | 206 | $ForwardingReport = @() 207 | $ExternalForwarding = @() 208 | 209 | foreach ($Mailbox in $Mailboxes) { 210 | $ForwardingType = "None" 211 | $ForwardingDestination = "" 212 | $IsExternal = $false 213 | 214 | if ($Mailbox.ForwardingAddress) { 215 | $ForwardingType = "Internal" 216 | $ForwardingDestination = $Mailbox.ForwardingAddress 217 | } elseif ($Mailbox.ForwardingSmtpAddress) { 218 | $ForwardingType = "SMTP" 219 | $ForwardingDestination = $Mailbox.ForwardingSmtpAddress 220 | # Check if external domain 221 | $Domain = ($Mailbox.ForwardingSmtpAddress -split "@")[1] 222 | $AcceptedDomains = Get-AcceptedDomain | Select-Object -ExpandProperty DomainName 223 | $IsExternal = $Domain -notin $AcceptedDomains 224 | } 225 | 226 | $ForwardingInfo = [PSCustomObject]@{ 227 | Mailbox = $Mailbox.UserPrincipalName 228 | DisplayName = $Mailbox.DisplayName 229 | ForwardingType = $ForwardingType 230 | ForwardingDestination = $ForwardingDestination 231 | DeliverToMailboxAndForward = $Mailbox.DeliverToMailboxAndForward 232 | IsExternalForwarding = $IsExternal 233 | MailboxType = $Mailbox.RecipientTypeDetails 234 | } 235 | 236 | $ForwardingReport += $ForwardingInfo 237 | 238 | if ($IsExternal) { 239 | $ExternalForwarding += $ForwardingInfo 240 | } 241 | } 242 | 243 | # Display summary 244 | Write-Host "" 245 | Write-ColorOutput "=== MAILBOX FORWARDING SUMMARY ===" "Cyan" 246 | 247 | if ($ForwardingReport.Count -eq 0) { 248 | Write-ColorOutput "✓ No mailbox forwarding rules found." "Green" 249 | } else { 250 | Write-ColorOutput "📧 Total mailboxes with forwarding: $($ForwardingReport.Count)" "Yellow" 251 | Write-ColorOutput "🌐 External forwarding rules: $($ExternalForwarding.Count)" $(if ($ExternalForwarding.Count -gt 0) { "Red" } else { "Green" }) 252 | 253 | if ($ExternalForwarding.Count -gt 0) { 254 | Write-ColorOutput "🚨 Warning: External email forwarding detected! This could be a security risk." "Red" 255 | Write-ColorOutput "⚠ Recommendation: Review and validate all external forwarding rules." "Red" 256 | } 257 | 258 | # Prompt for export 259 | $Export = Read-Host "Would you like to export forwarding rules to CSV? (Y/N)" 260 | if ($Export -eq 'Y' -or $Export -eq 'y') { 261 | $FilePath = Get-ValidFilePath "Mailbox_Forwarding_Report" 262 | $ForwardingReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 263 | Write-ColorOutput "Mailbox forwarding report exported to: $FilePath" "Green" 264 | } 265 | } 266 | 267 | } catch { 268 | Write-ColorOutput "Error checking mailbox forwarding: $($_.Exception.Message)" "Red" 269 | Write-Log "Error checking mailbox forwarding: $($_.Exception.Message)" "ERROR" 270 | } 271 | } 272 | 273 | # Function to check Teams external access settings 274 | function Get-TeamsExternalAccessReport { 275 | Write-ColorOutput "Checking Microsoft Teams external access settings..." "Yellow" 276 | 277 | try { 278 | # Check if Teams module is available 279 | if (-not (Get-Module -ListAvailable -Name "MicrosoftTeams")) { 280 | Write-ColorOutput "MicrosoftTeams module is required for Teams checks. Please install it with: Install-Module MicrosoftTeams" "Red" 281 | return 282 | } 283 | 284 | # Connect to Teams if not already connected 285 | try { 286 | Get-CsTenant -ErrorAction Stop | Out-Null 287 | } catch { 288 | Write-ColorOutput "Connecting to Microsoft Teams..." "Yellow" 289 | Connect-MicrosoftTeams -ErrorAction Stop | Out-Null 290 | } 291 | 292 | # Get external access configuration 293 | $ExternalAccessConfig = Get-CsTenantFederationConfiguration 294 | $GuestAccessConfig = Get-CsTeamsGuestCallingConfiguration 295 | $ClientConfig = Get-CsTeamsClientConfiguration 296 | 297 | Write-Host "" 298 | Write-ColorOutput "=== TEAMS EXTERNAL ACCESS CONFIGURATION ===" "Cyan" 299 | 300 | # Check federation settings 301 | if ($ExternalAccessConfig.AllowFederatedUsers) { 302 | Write-ColorOutput "🌐 External Access (Federation): ENABLED" "Yellow" 303 | Write-ColorOutput " - Public Cloud Federation: $($ExternalAccessConfig.AllowPublicUsers)" "White" 304 | Write-ColorOutput " - Skype Consumer: $($ExternalAccessConfig.AllowTeamsConsumer)" "White" 305 | } else { 306 | Write-ColorOutput "✓ External Access (Federation): DISABLED" "Green" 307 | } 308 | 309 | # Check guest access 310 | if ($GuestAccessConfig.AllowPrivateCalling) { 311 | Write-ColorOutput "👥 Guest Calling: ENABLED" "Yellow" 312 | } else { 313 | Write-ColorOutput "✓ Guest Calling: DISABLED" "Green" 314 | } 315 | 316 | # Check external app access 317 | if ($ClientConfig.AllowExternalApps) { 318 | Write-ColorOutput "📱 External Apps: ENABLED" "Yellow" 319 | } else { 320 | Write-ColorOutput "✓ External Apps: DISABLED" "Green" 321 | } 322 | 323 | # Create report object 324 | $AccessReport = [PSCustomObject]@{ 325 | FederationEnabled = $ExternalAccessConfig.AllowFederatedUsers 326 | PublicCloudFederation = $ExternalAccessConfig.AllowPublicUsers 327 | SkypeConsumerEnabled = $ExternalAccessConfig.AllowTeamsConsumer 328 | GuestCallingEnabled = $GuestAccessConfig.AllowPrivateCalling 329 | ExternalAppsEnabled = $ClientConfig.AllowExternalApps 330 | LastChecked = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') 331 | } 332 | 333 | # Security assessment 334 | $SecurityScore = 0 335 | if (-not $ExternalAccessConfig.AllowFederatedUsers) { $SecurityScore += 20 } 336 | if (-not $ExternalAccessConfig.AllowPublicUsers) { $SecurityScore += 20 } 337 | if (-not $ExternalAccessConfig.AllowTeamsConsumer) { $SecurityScore += 20 } 338 | if (-not $GuestAccessConfig.AllowPrivateCalling) { $SecurityScore += 20 } 339 | if (-not $ClientConfig.AllowExternalApps) { $SecurityScore += 20 } 340 | 341 | Write-Host "" 342 | Write-ColorOutput "🛡️ Security Score: $SecurityScore/100" $(if ($SecurityScore -ge 80) { "Green" } elseif ($SecurityScore -ge 60) { "Yellow" } else { "Red" }) 343 | 344 | if ($SecurityScore -lt 80) { 345 | Write-ColorOutput "⚠ Recommendation: Consider restricting external access to improve security posture." "Yellow" 346 | } 347 | 348 | # Prompt for export 349 | $Export = Read-Host "Would you like to export Teams access configuration to CSV? (Y/N)" 350 | if ($Export -eq 'Y' -or $Export -eq 'y') { 351 | $FilePath = Get-ValidFilePath "Teams_External_Access_Report" 352 | $AccessReport | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 353 | Write-ColorOutput "Teams external access report exported to: $FilePath" "Green" 354 | } 355 | 356 | } catch { 357 | Write-ColorOutput "Error checking Teams external access: $($_.Exception.Message)" "Red" 358 | Write-Log "Error checking Teams external access: $($_.Exception.Message)" "ERROR" 359 | } 360 | } 361 | 362 | # Function to get Teams with external users 363 | function Get-TeamsWithExternalUsersReport { 364 | Write-ColorOutput "Checking Teams with external users and guests..." "Yellow" 365 | 366 | try { 367 | # Check if Teams module is available 368 | if (-not (Get-Module -ListAvailable -Name "MicrosoftTeams")) { 369 | Write-ColorOutput "MicrosoftTeams module is required for Teams checks. Please install it with: Install-Module MicrosoftTeams" "Red" 370 | return 371 | } 372 | 373 | # Connect to Teams if not already connected 374 | try { 375 | Get-CsTenant -ErrorAction Stop | Out-Null 376 | } catch { 377 | Write-ColorOutput "Connecting to Microsoft Teams..." "Yellow" 378 | Connect-MicrosoftTeams -ErrorAction Stop | Out-Null 379 | } 380 | 381 | # Get all teams 382 | $Teams = Get-Team 383 | $TeamsWithExternalUsers = @() 384 | $TotalExternalUsers = 0 385 | 386 | Write-ColorOutput "Scanning $($Teams.Count) teams for external users..." "Yellow" 387 | 388 | foreach ($Team in $Teams) { 389 | try { 390 | # Get team members 391 | $TeamUsers = Get-TeamUser -GroupId $Team.GroupId 392 | $ExternalUsers = $TeamUsers | Where-Object { $_.User -like "*#EXT#*" -or $_.Role -eq "Guest" } 393 | 394 | if ($ExternalUsers.Count -gt 0) { 395 | $TotalExternalUsers += $ExternalUsers.Count 396 | 397 | $TeamInfo = [PSCustomObject]@{ 398 | TeamName = $Team.DisplayName 399 | TeamId = $Team.GroupId 400 | Visibility = $Team.Visibility 401 | TotalMembers = $TeamUsers.Count 402 | ExternalUsers = $ExternalUsers.Count 403 | ExternalUsersList = ($ExternalUsers | ForEach-Object { $_.User }) -join "; " 404 | LastChecked = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') 405 | } 406 | 407 | $TeamsWithExternalUsers += $TeamInfo 408 | } 409 | } catch { 410 | Write-Log "Error checking team $($Team.DisplayName): $($_.Exception.Message)" "ERROR" 411 | } 412 | } 413 | 414 | # Display summary 415 | Write-Host "" 416 | Write-ColorOutput "=== TEAMS EXTERNAL USERS SUMMARY ===" "Cyan" 417 | 418 | if ($TeamsWithExternalUsers.Count -eq 0) { 419 | Write-ColorOutput "✓ No teams with external users found." "Green" 420 | } else { 421 | Write-ColorOutput "👥 Teams with external users: $($TeamsWithExternalUsers.Count)" "Yellow" 422 | Write-ColorOutput "🌐 Total external users across all teams: $TotalExternalUsers" "Yellow" 423 | 424 | # Show top teams with most external users 425 | $TopTeams = $TeamsWithExternalUsers | Sort-Object ExternalUsers -Descending | Select-Object -First 5 426 | Write-Host "" 427 | Write-ColorOutput "Top teams with external users:" "Cyan" 428 | foreach ($Team in $TopTeams) { 429 | Write-ColorOutput " • $($Team.TeamName): $($Team.ExternalUsers) external users" "White" 430 | } 431 | 432 | Write-Host "" 433 | Write-ColorOutput "⚠ Recommendation: Review external user access and ensure appropriate governance." "Yellow" 434 | 435 | # Prompt for export 436 | $Export = Read-Host "Would you like to export teams with external users to CSV? (Y/N)" 437 | if ($Export -eq 'Y' -or $Export -eq 'y') { 438 | $FilePath = Get-ValidFilePath "Teams_External_Users_Report" 439 | $TeamsWithExternalUsers | Export-Csv -Path $FilePath -NoTypeInformation -ErrorAction Stop 440 | Write-ColorOutput "Teams external users report exported to: $FilePath" "Green" 441 | } 442 | } 443 | 444 | } catch { 445 | Write-ColorOutput "Error checking Teams external users: $($_.Exception.Message)" "Red" 446 | Write-Log "Error checking Teams external users: $($_.Exception.Message)" "ERROR" 447 | } 448 | } 449 | 450 | Export-ModuleMember -Function Get-LicenseUsageReport, Get-InactiveAccountsReport, Get-MailboxForwardingReport, Get-TeamsExternalAccessReport, Get-TeamsWithExternalUsersReport 451 | --------------------------------------------------------------------------------