├── .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 | 
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 |
--------------------------------------------------------------------------------