public/Invoke-AzureAdDeployer.ps1
function Invoke-AzureAdDeployer { [CmdletBinding()] Param( [switch]$UseExistingExoSession, [switch]$KeepExoSessionAlive, [switch]$UseExistingGraphSession, [switch]$KeepGraphSessionAlive, [switch]$UseExistingSpoSession, [switch]$KeepSpoSessionAlive, [switch]$AddExchangeOnlineReport, [switch]$AddSharePointOnlineReport, [switch]$CreateBreakGlassAccount, [switch]$EnableSecurityDefaults, [switch]$DisableSecurityDefaults, [switch]$DisableEnterpiseApplicationUserConsent, [switch]$DisableUsersToCreateAppRegistrations, [switch]$DisableUsersToReadOtherUsers, [switch]$DisableUsersToCreateSecurityGroups, [switch]$DisableUsersToCreateUnifiedGroups, [switch]$CreateUnifiedGroupCreationAllowedGroup, [switch]$EnableBlockMsolPowerShell, [switch]$SetMailboxLanguage, [switch]$DisableSharedMailboxLogin, [switch]$EnableSharedMailboxCopyToSent, [switch]$HideUnifiedMailboxFromOutlookClient, [switch]$DisableAddToOneDrive ) $ReportTitle = "Microsoft 365 Security Report" $Version = "2.15.4" $script:VersionMessage = "AzureAdDeployer version: $($Version)" $ReportImageUrl = "https://cdn-icons-png.flaticon.com/512/3540/3540926.png" $script:InteractiveMode = $false $script:MailboxLanguageCode = "de-CH" $script:MailboxTimeZone = "W. Europe Standard Time" $script:UnifiedGroupCreationAllowedGroupName = "M365_GROUP_CREATORS" $script:CustomerName = "" $script:CreateBreakGlassAccount = $CreateBreakGlassAccount $script:EnableSecurityDefaults = $EnableSecurityDefaults $script:DisableSecurityDefaults = $DisableSecurityDefaults $script:DisableEnterpiseApplicationUserConsent = $DisableEnterpiseApplicationUserConsent $script:DisableUsersToCreateAppRegistrations = $DisableUsersToCreateAppRegistrations $script:DisableUsersToReadOtherUsers = $DisableUsersToReadOtherUsers $script:DisableUsersToCreateSecurityGroups = $DisableUsersToCreateSecurityGroups $script:DisableUsersToCreateUnifiedGroups = $DisableUsersToCreateUnifiedGroups $script:CreateUnifiedGroupCreationAllowedGroup = $CreateUnifiedGroupCreationAllowedGroup $script:EnableBlockMsolPowerShell = $EnableBlockMsolPowerShell $script:SetMailboxLanguage = $SetMailboxLanguage $script:DisableSharedMailboxLogin = $DisableSharedMailboxLogin $script:EnableSharedMailboxCopyToSent = $EnableSharedMailboxCopyToSent $script:HideUnifiedMailboxFromOutlookClient = $HideUnifiedMailboxFromOutlookClient $script:DisableAddToOneDrive = $DisableAddToOneDrive $script:AddExchangeOnlineReport = $AddExchangeOnlineReport $script:AddSharePointOnlineReport = $AddSharePointOnlineReport <# Interactive inputs section #> function CheckInteractiveMode { Param( $Parameters ) if ($Parameters.Count) { Write-Host $script:VersionMessage return } $script:InteractiveMode = $true } function InteractiveMenu { $script:AddExchangeOnlineReport = $true $script:AddSharePointOnlineReport = $true mainMenu } function mainMenu { $StartOptionValue = 0 while (($result -ne $StartOptionValue) -or ($result -ne 1)) { Clear-Host $Status = @" $($script:VersionMessage) Main menu: S: Start C: Configure options 1: Add SharePoint Online report: $($script:AddSharePointOnlineReport) 2: Add Exchange Online report: $($script:AddExchangeOnlineReport) "@ $StartOption = New-Object System.Management.Automation.Host.ChoiceDescription "&START", "Start" $ConfigureOption = New-Object System.Management.Automation.Host.ChoiceDescription "&CONFIGURE", "Add SharePoint Online report" $AddSharePointOnlineReportOption = New-Object System.Management.Automation.Host.ChoiceDescription "&1 SPO", "Add SharePoint Online report" $AddExchangeOnlineReportOption = New-Object System.Management.Automation.Host.ChoiceDescription "&2 EXO", "Add Exchange Online report" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($StartOption, $ConfigureOption, $AddSharePointOnlineReportOption, $AddExchangeOnlineReportOption ) Write-Host $Status $result = $host.ui.PromptForChoice("", "", $Options, $StartOptionValue) switch ($result) { 0 { return } 1 { configMenu } 2 { $script:AddSharePointOnlineReport = ! $script:AddSharePointOnlineReport } 3 { $script:AddExchangeOnlineReport = ! $script:AddExchangeOnlineReport } } } } function configMenu { $StartOptionValue = 0 Clear-Host $Status = @" $($script:VersionMessage) Configure menu: 1: Azure Active Directory 2: SharePoint Online 3: Exchange Online B: Back to main menu "@ $BackOption = New-Object System.Management.Automation.Host.ChoiceDescription "&BACK", "Back to main menu" $AADOption = New-Object System.Management.Automation.Host.ChoiceDescription "&1 AAD", "Azure Active Directory options" $SPOOption = New-Object System.Management.Automation.Host.ChoiceDescription "&2 SPO", "SharePoint Online options" $EXOOption = New-Object System.Management.Automation.Host.ChoiceDescription "&3 EXO", "Exchange Online options" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($BackOption, $AADOption, $SPOOption, $EXOOption) Write-Host $Status $result = $host.ui.PromptForChoice("", "", $Options, $StartOptionValue) switch ($result) { 0 { return } 1 { AADMenu } 2 { SPOMenu } 3 { EXOMenu } } } function AADMenu { $StartOptionValue = 0 while ($result -ne $StartOptionValue) { Clear-Host $Status = @" $($script:VersionMessage) Azure Active Directory options: 1: Create BreakGlass account: $($script:CreateBreakGlassAccount) 2: Enable security defaults: $($script:EnableSecurityDefaults) 3: Disable security defaults: $($script:DisableSecurityDefaults) 4: Disable enterprise application user consent: $($script:DisableEnterpiseApplicationUserConsent) 5: Disable user to create app registrations: $($script:DisableUsersToCreateAppRegistrations) 6: Disable user to read other users: $($script:DisableUsersToReadOtherUsers) 7: Disable users to create security groups: $($script:DisableUsersToCreateSecurityGroups) 8: Disable users to create unified groups: $($script:DisableUsersToCreateUnifiedGroups) 9: Create UnifiedGroupCreationAllowed group: $($script:CreateUnifiedGroupCreationAllowedGroup) 0: Disable legacy MsolPowerShell access: $($script:EnableBlockMsolPowerShell) B: Back to main menu "@ $BackOption = New-Object System.Management.Automation.Host.ChoiceDescription "&BACK", "Back to main menu" $CreateBreakGlassAccountOption = New-Object System.Management.Automation.Host.ChoiceDescription "&1", "Create BreakGlass account" $EnableSecurityDefaultsOption = New-Object System.Management.Automation.Host.ChoiceDescription "&2", "Enable security defaults" $DisableSecurityDefaultsOption = New-Object System.Management.Automation.Host.ChoiceDescription "&3", "Disable security defaults" $DisableEnterpiseApplicationUserConsentOption = New-Object System.Management.Automation.Host.ChoiceDescription "&4", "Disable enterprise application user consent" $DisableUsersToCreateAppRegistrationsOption = New-Object System.Management.Automation.Host.ChoiceDescription "&5", "Disable user to create app registrations" $DisableUsersToReadOtherUsersOption = New-Object System.Management.Automation.Host.ChoiceDescription "&6", "Disable user to read other users" $DisableUsersToCreateSecurityGroupsOption = New-Object System.Management.Automation.Host.ChoiceDescription "&7", "Disable users to create security groups" $DisableUsersToCreateUnifiedGroupsOption = New-Object System.Management.Automation.Host.ChoiceDescription "&8", "Disable users to create unified groups" $CreateUnifiedGroupCreationAllowedGroupOption = New-Object System.Management.Automation.Host.ChoiceDescription "&9", "Create UnifiedGroupCreationAllowed group" $EnableBlockMsolPowerShellOption = New-Object System.Management.Automation.Host.ChoiceDescription "&0", "Disable legacy MsolPowerShell access" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($BackOption, $CreateBreakGlassAccountOption, $EnableSecurityDefaultsOption, $DisableSecurityDefaultsOption, $DisableEnterpiseApplicationUserConsentOption, $DisableUsersToCreateAppRegistrationsOption, $DisableUsersToReadOtherUsersOption, $DisableUsersToCreateSecurityGroupsOption, $DisableUsersToCreateUnifiedGroupsOption, $CreateUnifiedGroupCreationAllowedGroupOption, $EnableBlockMsolPowerShellOption) Write-Host $Status $result = $host.ui.PromptForChoice("", "", $Options, $StartOptionValue) switch ($result) { 0 { return } 1 { $script:CreateBreakGlassAccount = ! $script:CreateBreakGlassAccount } 2 { $script:EnableSecurityDefaults = ! $script:EnableSecurityDefaults } 3 { $script:DisableSecurityDefaults = ! $script:DisableSecurityDefaults } 4 { $script:DisableEnterpiseApplicationUserConsent = ! $script:DisableEnterpiseApplicationUserConsent } 5 { $script:DisableUsersToCreateAppRegistrations = ! $script:DisableUsersToCreateAppRegistrations } 6 { $script:DisableUsersToReadOtherUsers = ! $script:DisableUsersToReadOtherUsers } 7 { $script:DisableUsersToCreateSecurityGroups = ! $script:DisableUsersToCreateSecurityGroups } 8 { $script:DisableUsersToCreateUnifiedGroups = ! $script:DisableUsersToCreateUnifiedGroups } 9 { $script:CreateUnifiedGroupCreationAllowedGroup = ! $script:CreateUnifiedGroupCreationAllowedGroup } 10 { $script:EnableBlockMsolPowerShell = ! $script:EnableBlockMsolPowerShell } } } } function SPOMenu { $StartOptionValue = 0 while ($result -ne $StartOptionValue) { Clear-Host $Status = @" $($script:VersionMessage) SharePoint Online options: 1: Disable add to OneDrive button: $($script:DisableAddToOneDrive) B: Back to main menu "@ $BackOption = New-Object System.Management.Automation.Host.ChoiceDescription "&BACK", "Back to main menu" $DisableAddToOneDriveOption = New-Object System.Management.Automation.Host.ChoiceDescription "&1}", "Disable add to OneDrive button" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($BackOption, $DisableAddToOneDriveOption) Write-Host $Status $result = $host.ui.PromptForChoice("", "", $Options, $StartOptionValue) switch ($result) { 0 { return } 1 { $script:DisableAddToOneDrive = ! $script:DisableAddToOneDrive } } } } function EXOMenu { $StartOptionValue = 0 while ($result -ne $StartOptionValue) { Clear-Host $Status = @" $($script:VersionMessage) Exchange Online options: 1: Set mailbox language: $($script:SetMailboxLanguage) 2: Disable shared mailbox login: $($script:DisableSharedMailboxLogin) 3: Enable shared mailbox copy to sent: $($script:EnableSharedMailboxCopyToSent) 4: Hide unified mailbox from outlook client: $($script:HideUnifiedMailboxFromOutlookClient) B: Back to main menu "@ $BackOption = New-Object System.Management.Automation.Host.ChoiceDescription "&BACK", "Back to main menu" $SetMailboxLanguageOption = New-Object System.Management.Automation.Host.ChoiceDescription "&1", "Set mailbox language" $DisableSharedMailboxLoginOption = New-Object System.Management.Automation.Host.ChoiceDescription "&2", "Disable shared mailbox login" $EnableSharedMailboxCopyToSentOption = New-Object System.Management.Automation.Host.ChoiceDescription "&3", "Enable shared mailbox copy to sent" $HideUnifiedMailboxFromOutlookClientOption = New-Object System.Management.Automation.Host.ChoiceDescription "&4", "Hide unified mailbox from outlook client" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($BackOption, $SetMailboxLanguageOption, $DisableSharedMailboxLoginOption, $EnableSharedMailboxCopyToSentOption, $HideUnifiedMailboxFromOutlookClientOption) Write-Host $Status $result = $host.ui.PromptForChoice("", "", $Options, $StartOptionValue) switch ($result) { 0 { return } 1 { $script:SetMailboxLanguage = ! $script:SetMailboxLanguage } 2 { $script:DisableSharedMailboxLogin = ! $script:DisableSharedMailboxLogin } 3 { $script:EnableSharedMailboxCopyToSent = ! $script:EnableSharedMailboxCopyToSent } 4 { $script:HideUnifiedMailboxFromOutlookClient = ! $script:HideUnifiedMailboxFromOutlookClient } } } } <# Connect sessions section #> function connectGraph { disconnectGraph Write-Host "Connecting Graph API PowerShell" Connect-MgGraph -Scopes "Policy.Read.All, Policy.ReadWrite.ConditionalAccess, Application.Read.All, User.Read.All, User.ReadWrite.All, Domain.Read.All, Directory.Read.All, Directory.ReadWrite.All, RoleManagement.ReadWrite.Directory, DeviceManagementApps.Read.All, DeviceManagementApps.ReadWrite.All, Policy.ReadWrite.Authorization, Sites.Read.All, AuditLog.Read.All, UserAuthenticationMethod.Read.All, Organization.Read.All" | Out-Null if ( -not (checkGraphSession)) { exit } } function connectSpo { disconnectSpo Write-Host "Connecting SharePoint Online PowerShell" if ($PSVersionTable.PSEdition -eq "Core") { Connect-PnPOnline -Url (getSpoAdminUrl) -Interactive -LaunchBrowser } if ($PSVersionTable.PSEdition -eq "Desktop") { Connect-PnPOnline -Url (getSpoAdminUrl) -Interactive } if ( -not (checkSpoSession)) { exit } } function getSpoAdminUrl { return ((Invoke-MgGraphRequest -Method GET -Uri https://graph.microsoft.com/v1.0/sites/root).siteCollection.hostname) -replace ".sharepoint.com", "-admin.sharepoint.com" } function connectExo { disconnectExo Write-Host "Connecting Exchange Online PowerShell" Connect-ExchangeOnline -ShowBanner:$false if ( -not (checkExoSession)) { exit } } <# Check session section#> function checkGraphSession { if (Get-MgContext) { Write-Host "Connected to Graph API PowerShell using $((Get-MgContext).Account) account" return $true } Write-Host "Not connected to Graph API PowerShell" return $false } function checkSpoSession { if (Get-PnPConnection) { Write-Host "Connected to SharePoint Online PowerShell tenant $((Get-PnPConnection).Url)" return $true } Write-Host "Not connected to SharePoint Online PowerShell" return $false } function checkExoSession { if ((Get-ConnectionInformation).State -eq "Connected") { Write-Host "Connected to Exchange Online PowerShell using $((Get-ConnectionInformation).UserPrincipalName) account" return $true } Write-Host "Not connected to Exchange Online PowerShell" return $false } <# Disconect session section #> function disconnectGraph { Write-Host "Disconnecting existing Graph API PowerShell" Disconnect-Graph | Out-Null } function disconnectSpo { Write-Host "Disconnecting existing SharePoint Online PowerShell" Disconnect-PnPOnline } function disconnectExo { Write-Host "Disconnecting existing Exchange Online PowerShell" Disconnect-ExchangeOnline -Confirm:$false } <# Customer infos#> function organizationReport { $Organization = Get-MgOrganization -Property DisplayName, Id $script:CustomerName = $Organization.DisplayName return "<h2>$($Organization.DisplayName) ($($Organization.Id))</h2>" } <# User settings policy section #> function checkTenanUserSettingsReport { param( [System.Boolean]$DisableUserConsent, [System.Boolean]$DisableUsersToCreateAppRegistrations, [System.Boolean]$DisableUsersToReadOtherUsers, [System.Boolean]$DisableUsersToCreateSecurityGroups, [System.Boolean]$DisableUsersToCreateUnifiedGroups, [System.Boolean]$CreateUnifiedGroupCreationAllowedGroup, [System.Boolean]$EnableBlockMsolPowerShell ) if ($DisableUserConsent) { disableApplicationUserConsent } if ($DisableUsersToCreateAppRegistrations) { disableUsersToCreateAppRegistrations } if ($DisableUsersToReadOtherUsers) { disableUsersToReadOtherUsers } if ($DisableUsersToCreateSecurityGroups) { disableUsersToCreateSecurityGroups } if ($DisableUsersToCreateUnifiedGroups) { disableUsersToCreateUnifiedGroups } if ($CreateUnifiedGroupCreationAllowedGroup) { createUnifiedGroupCreationAllowedGroup } if ($EnableBlockMsolPowerShell) { enableBlockMsolPowerShell } Write-Host "Checking user settings" $Policy = Get-MgPolicyAuthorizationPolicy -Property BlockMsolPowerShell, DefaultUserRolePermissions $Report = $Policy | Select-Object -Property @{Name = "PermissionGrantPoliciesAssigned"; Expression = { [string]$_.DefaultUserRolePermissions.PermissionGrantPoliciesAssigned } }, @{Name = "AllowedToCreateApps"; Expression = { [string]$_.DefaultUserRolePermissions.AllowedToCreateApps } }, @{Name = "AllowedToCreateSecurityGroups"; Expression = { [string]$_.DefaultUserRolePermissions.AllowedToCreateSecurityGroups } }, @{Name = "AllowedToCreateUnifiedGroups"; Expression = { checkAllowedToCreateUnifiedGroups } }, @{Name = "AllowedToCreateUnifiedGroupsGroupName"; Expression = { checkUnifiedGroupCreationAllowedGroup } }, @{Name = "AllowedToReadOtherUsers"; Expression = { [string]$_.DefaultUserRolePermissions.AllowedToReadOtherUsers } }, @{Name = "AllowedToCreateTenants"; Expression = { checkAllowedToCreateTenants } }, BlockMsolPowerShell | ConvertTo-Html -As List -Fragment -PreContent "<h3 id='AAD_USER_SETTINGS'>User settings</h3>" -PostContent "<p>PermissionGrantPoliciesAssigned: empty (user consent not allowed), microsoft-user-default-legacy (user consent allowed for all apps), microsoft-user-default-low (user consent allowed for low permission apps)</p><p>Unified groups = Microsoft 365 groups</p>" $Report = $Report -Replace "<td>PermissionGrantPoliciesAssigned:</td><td>ManagePermissionGrantsForSelf.microsoft-user-default-legacy</td>", "<td>PermissionGrantPoliciesAssigned:</td><td class='red'>microsoft-user-default-legacy</td>" $Report = $Report -Replace "<td>PermissionGrantPoliciesAssigned:</td><td>ManagePermissionGrantsForSelf.microsoft-user-default-low</td>", "<td>PermissionGrantPoliciesAssigned:</td><td class='orange'>microsoft-user-default-low</td>" $Report = $Report -Replace "<td>AllowedToCreateApps:</td><td>True</td>", "<td>AllowedToCreateApps:</td><td class='red'>True</td>" $Report = $Report -Replace "<td>AllowedToCreateSecurityGroups:</td><td>True</td>", "<td>AllowedToCreateSecurityGroups:</td><td class='red'>True</td>" $Report = $Report -Replace "<td>AllowedToCreateUnifiedGroups:</td><td>True</td>", "<td>AllowedToCreateUnifiedGroups:</td><td class='red'>True</td>" $Report = $Report -Replace "<td>AllowedToCreateUnifiedGroups:</td><td>false</td>", "<td>AllowedToCreateUnifiedGroups:</td><td>False</td>" $Report = $Report -Replace "<td>AllowedToReadOtherUsers:</td><td>True</td>", "<td>AllowedToReadOtherUsers:</td><td class='red'>True</td>" $Report = $Report -Replace "<td>AllowedToCreateTenants:</td><td>True</td>", "<td>AllowedToCreateTenants:</td><td class='red'>True</td>" $Report = $Report -Replace "<td>BlockMsolPowerShell:</td><td>False</td>", "<td>BlockMsolPowerShell:</td><td class='red'>False</td>" return $Report } function disableApplicationUserConsent { Write-Host "Disable enterprise application user consent" Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{ "PermissionGrantPoliciesAssigned" = @() } } function disableUsersToCreateAppRegistrations { Write-Host "Disable users to create app registrations" Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{ "AllowedToCreateApps" = $false } } function disableUsersToReadOtherUsers { Write-Host "Disable users to read other users" Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{ "AllowedToReadOtherUsers" = $false } } function disableUsersToCreateSecurityGroups { Write-Host "Disable users to create security groups" Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{ "AllowedToCreateSecurityGroups" = $false } } function enableBlockMsolPowerShell { Write-Host "Disable legacy MsolPowerShell access" Update-MgPolicyAuthorizationPolicy -BlockMsolPowerShell } function checkAllowedToCreateUnifiedGroups { if ($GroupSettingsUnified = getGroupSettingsUnified) { return ($GroupSettingsUnified.values | Where-Object name -eq "EnableGroupCreation").value } return $true } function checkUnifiedGroupCreationAllowedGroup { $GroupSettingsUnified = getGroupSettingsUnified if ($GroupId = ($GroupSettingsUnified.values | Where-Object name -eq "GroupCreationAllowedGroupId").value) { return (Get-MgGroup -GroupId $GroupId -Property DisplayName).DisplayName } } function createUnifiedGroupCreationAllowedGroup { Write-Host "Creating UnifiedGroupCreationAllowed group:" $script:UnifiedGroupCreationAllowedGroupName if (checkUnifiedGroupCreationAllowedGroup) { Write-Host "UnifiedGroupCreationAllowed group already assigned" return } if ($GroupId = (Get-MgGroup -Property Id, DisplayName -Filter "DisplayName eq '$($script:UnifiedGroupCreationAllowedGroupName)'").Id) { Write-Host "UnifiedGroupCreationAllowed group already exists" } else { $GroupId = (New-MgGroup -DisplayName $script:UnifiedGroupCreationAllowedGroupName -MailEnabled:$False -MailNickname $script:UnifiedGroupCreationAllowedGroupName -SecurityEnabled).Id } $Body = @{ templateId = (getGroupSettingsTemplateUnified).id values = @( @{ Name = "GroupCreationAllowedGroupId" ; Value = $GroupId } ) } if ($GroupSettingsUnified = getGroupSettingsUnified) { Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/groupSettings/$($GroupSettingsUnified.id)" -Body $Body } else { Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/groupSettings" -Body $Body } } function getGroupSettingsTemplateUnified { $GroupSettingTemplates = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/groupSettingTemplates?$select=value" return $GroupSettingTemplates.value | Where-Object { $_.displayName -eq "Group.Unified" } | Select-Object -Property id, DisplayName } function getGroupSettingsUnified { $GroupSettings = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/groupSettings?$select=value" return $GroupSettings.value | Where-Object { $_.templateId -eq (getGroupSettingsTemplateUnified).id } | Select-Object -Property id, templateId, values } function disableUsersToCreateUnifiedGroups { $Body = @{ templateId = (getGroupSettingsTemplateUnified).id values = @( @{ Name = "EnableGroupCreation" ; Value = "false" } ) } if ($GroupSettingsUnified = getGroupSettingsUnified) { Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/groupSettings/$($GroupSettingsUnified.id)" -Body $Body } else { Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/groupSettings" -Body $Body } } function checkAllowedToCreateTenants { $DefaultUserRolePermissions = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy").defaultUserRolePermissions return $DefaultUserRolePermissions["allowedToCreateTenants"] } <# Device join settings#> function checkDeviceJoinSettingsReport { Write-Host "Checking device join settings" $DeviceSettings = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy?$select=azureAdJoin, userDeviceQuota, multiFactorAuthConfiguration" $Report = $DeviceSettings | Select-Object -Property @{Name = "Require MFA"; Expression = { if ($_.multiFactorAuthConfiguration -eq 1) { return $true } return $false } }, @{Name = "Allowed to join"; Expression = { if ($_.azureAdJoin.appliesTo -eq 1) { return "All users" } if ($_.azureAdJoin.appliesTo -eq 2) { return "Selected users" } return "No users" } }, @{Name = "Users"; Expression = { $Users = @() ; foreach ($UserId in $_.azureAdJoin.allowedUsers) { $Users += (Get-MgUser -UserId $UserId -Property UserPrincipalName).UserPrincipalName } return $Users -join "; " } }, @{Name = "Groups"; Expression = { $Groups = @() ; foreach ($GroupId in $_.azureAdJoin.allowedGroups) { $Groups += (Get-MgGroup -GroupId $GroupId -Property DisplayName).DisplayName } return $Groups -join "; " } }, userDeviceQuota | ConvertTo-Html -As List -Fragment -PreContent "<br><h3 id='AAD_DEVICE_JOIN_SETTINGS'>Device join settings</h3>" $Report = $Report -Replace "<td>Require MFA:</td><td>False</td>", "<td>Require MFA:</td><td class='red'>False</td>" $Report = $Report -Replace "<td>Allowed to join:</td><td>All users</td>", "<td>Allowed to join:</td><td class='red'>All users</td>" return $Report } <# License SKU section#> function checkUsedSKUReport { Write-Host "Checking licenses" $SKU = Get-MgSubscribedSku -Property SkuPartNumber, ConsumedUnits, PrepaidUnits, AppliesTo return $SKU | Select-Object -Property @{Name = "Name"; Expression = { if ($_.SkuPartNumber -eq "EXCHANGESTANDARD") { return "Exchange Online (Plan 1)" } if ($_.SkuPartNumber -eq "EXCHANGEENTERPRISE") { return "Exchange Online (PLAN 2)" } if ($_.SkuPartNumber -eq "EXCHANGEARCHIVE_ADDON") { return "Exchange Online Archiving for Exchange Online" } if ($_.SkuPartNumber -eq "EXCHANGE_S_ESSENTIALS") { return "Exchange Online Essentials" } if ($_.SkuPartNumber -eq "SHAREPOINTSTANDARD") { return "SharePoint Online (Plan 1)" } if ($_.SkuPartNumber -eq "SHAREPOINTENTERPRISE") { return "SharePoint Online (Plan 2)" } if ($_.SkuPartNumber -eq "AAD_BASIC") { return "Azure Active Directory Basic" } if ($_.SkuPartNumber -eq "AAD_PREMIUM") { return "Azure Active Directory Premium P1" } if ($_.SkuPartNumber -eq "AAD_PREMIUM_P2") { return "Azure Active Directory Premium P2" } if ($_.SkuPartNumber -eq "EMS") { return "Enterprise Mobility + Security E3" } if ($_.SkuPartNumber -eq "EMSPREMIUM") { return "Enterprise Mobility + Security E5" } if ($_.SkuPartNumber -eq "INTUNE_A") { return "Intune" } if ($_.SkuPartNumber -eq "INTUNE_A_D") { return "Microsoft Intune Device" } if ($_.SkuPartNumber -eq "INTUNE_SMB") { return "Microsoft Intune SMB" } if ($_.SkuPartNumber -eq "WINDOWS_STORE") { return "Windows Store for Business" } if ($_.SkuPartNumber -eq "RMSBASIC") { return "Rights Management Service Basic Content Protection" } if ($_.SkuPartNumber -eq "RIGHTSMANAGEMENT_ADHOC") { return "Rights Management Adhoc" } if ($_.SkuPartNumber -eq "VISIO_PLAN1_DEPT") { return "Visio Plan 1" } if ($_.SkuPartNumber -eq "VISIO_PLAN2_DEPT ") { return "Visio Plan 2" } if ($_.SkuPartNumber -eq "VISIOONLINE_PLAN1") { return "Visio Online Plan 1" } if ($_.SkuPartNumber -eq "VISIOCLIENT") { return "Visio Online Plan 2" } if ($_.SkuPartNumber -eq "PROJECTESSENTIALS") { return "Project Online Essentials" } if ($_.SkuPartNumber -eq "PROJECTPREMIUM") { return "Project Online Premium" } if ($_.SkuPartNumber -eq "PROJECT_P1") { return "Project Plan 1" } if ($_.SkuPartNumber -eq "PROJECTPROFESSIONAL") { return "Project Plan 3" } if ($_.SkuPartNumber -eq "MS_TEAMS_IW") { return "Microsoft Teams Trial" } if ($_.SkuPartNumber -eq "MCOCAP") { return "Microsoft Teams Shared Devices" } if ($_.SkuPartNumber -eq "MCOEV") { return "Microsoft Teams Phone Standard" } if ($_.SkuPartNumber -eq "MCOEV_DOD") { return "Microsoft Teams Phone Standard for DOD" } if ($_.SkuPartNumber -eq "MCOTEAMS_ESSENTIALS") { return "Teams Phone with Calling Plan" } if ($_.SkuPartNumber -eq "TEAMS_FREE") { return "Microsoft Teams (Free)" } if ($_.SkuPartNumber -eq "Teams_Ess") { return "Microsoft Teams Essentials" } if ($_.SkuPartNumber -eq "Microsoft_Teams_Premium") { return "Microsoft Teams Premium" } if ($_.SkuPartNumber -eq "TEAMS_EXPLORATORY") { return "Microsoft Teams Exploratory" } if ($_.SkuPartNumber -eq "BUSINESS_VOICE_DIRECTROUTING") { return "Microsoft 365 Business Voice (without calling plan)" } if ($_.SkuPartNumber -eq "PHONESYSTEM_VIRTUALUSER") { return "Microsoft Teams Phone Resoure Account" } if ($_.SkuPartNumber -eq "Microsoft_Teams_Rooms_Basic_without_Audio_Conferencing") { return "Microsoft Teams Rooms Basic without Audio Conferencing" } if ($_.SkuPartNumber -eq "Microsoft_Teams_Rooms_Pro") { return "Microsoft Teams Rooms Pro" } if ($_.SkuPartNumber -eq "BUSINESS_VOICE_MED2") { return "Microsoft 365 Business Voice" } if ($_.SkuPartNumber -eq "MCOPSTN_5") { return "Microsoft 365 Domestic Calling Plan (120 Minutes)" } if ($_.SkuPartNumber -eq "POWER_BI_PRO") { return "Power BI Pro" } if ($_.SkuPartNumber -eq "POWERAPPS_VIRAL") { return "Microsoft Power Apps Plan 2 Trial" } if ($_.SkuPartNumber -eq "SPZA_IW") { return "App Connect IW" } if ($_.SkuPartNumber -eq "FLOW_FREE") { return "Microsoft Flow Free" } if ($_.SkuPartNumber -eq "CCIBOTS_PRIVPREV_VIRAL") { return "Power Virtual Agents Viral Trial" } if ($_.SkuPartNumber -eq "VIRTUAL_AGENT_BASE") { return "Power Virtual Agent" } if ($_.SkuPartNumber -eq "WIN_DEF_ATP") { return "Microsoft Defender for Endpoint" } if ($_.SkuPartNumber -eq "ADALLOM_STANDALONE") { return "Microsoft Cloud App Security" } if ($_.SkuPartNumber -eq "DEFENDER_ENDPOINT_P1") { return "Microsoft Defender for Endpoint P1" } if ($_.SkuPartNumber -eq "MDATP_Server") { return "Microsoft Defender for Endpoint Server" } if ($_.SkuPartNumber -eq "ATP_ENTERPRISE_FACULTY") { return "Microsoft Defender for Office 365 (Plan 1) Faculty" } if ($_.SkuPartNumber -eq "ATA") { return "Microsoft Defender for Identity" } if ($_.SkuPartNumber -eq "ATP_ENTERPRISE") { return "Microsoft Defender for Office 365 (Plan 1)" } if ($_.SkuPartNumber -eq "M365_F1") { return "Microsoft 365 F1" } if ($_.SkuPartNumber -eq "SPE_F1") { return "Microsoft 365 F3" } if ($_.SkuPartNumber -eq "DESKLESSPACK") { return "Office 365 F3" } if ($_.SkuPartNumber -eq "SPE_E3") { return "Microsoft 365 E3" } if ($_.SkuPartNumber -eq "SPE_E5") { return "Microsoft 365 E5" } if ($_.SkuPartNumber -eq "SPE_E5_CALLINGMINUTES") { return "Microsoft 365 E5 with Calling Minutes" } if ($_.SkuPartNumber -eq "INFORMATION_PROTECTION_COMPLIANCE") { return "Microsoft 365 E5 Compliance" } if ($_.SkuPartNumber -eq "IDENTITY_THREAT_PROTECTION") { return "Microsoft 365 E5 Security" } if ($_.SkuPartNumber -eq "SPE_E5_NOPSTNCONF") { return "Microsoft 365 E5 without Audio Conferencing" } if ($_.SkuPartNumber -eq "STANDARDPACK") { return "Office 365 E1" } if ($_.SkuPartNumber -eq "STANDARDWOFFPACK") { return "Office 365 E2" } if ($_.SkuPartNumber -eq "ENTERPRISEPACK") { return "Office 365 E3" } if ($_.SkuPartNumber -eq "ENTERPRISEWITHSCAL") { return "Office 365 E4" } if ($_.SkuPartNumber -eq "ENTERPRISEPREMIUM") { return "Office 365 E5" } if ($_.SkuPartNumber -eq "ENTERPRISEPREMIUM_NOPSTNCONF") { return "Office 365 E5 without Audio Conferencing" } if ($_.SkuPartNumber -eq "SPB") { return "Microsoft 365 Business Premium" } if ($_.SkuPartNumber -eq "O365_BUSINESS_PREMIUM") { return "Microsoft 365 Business Standard" } if (($_.SkuPartNumber -eq "O365_BUSINESS") -or ($_.SkuPartNumber -eq "SMB_BUSINESS")) { return "Microsoft 365 Apps for Business" } if ($_.SkuPartNumber -eq "OFFICESUBSCRIPTION") { return "Microsoft 365 Apps for enterprise" } if (($_.SkuPartNumber -eq "O365_BUSINESS_ESSENTIALS") -or ($_.SkuPartNumber -eq "SMB_BUSINESS_ESSENTIALS")) { return "Microsoft 365 Business Basic" } else { return $_.SkuPartNumber } } }, @{Name = "Total"; Expression = { $_.PrepaidUnits.Enabled } }, @{Name = "Assigned"; Expression = { $_.ConsumedUnits } } , @{Name = "Available"; Expression = { ($_.PrepaidUnits.Enabled) - ($_.ConsumedUnits) } } , AppliesTo | ConvertTo-Html -As Table -Fragment -PreContent "<br><h3 id='AAD_SKU'>Licenses</h3>" } <# User Account section #> function disableUserAccount { param ( $Users ) $params = @{ AccountEnabled = "false" } Write-Host "Disable user accounts" foreach ($User in $Users) { Update-MgUser -UserId $User.UserPrincipalName -BodyParameter $params } } function checkUserAccountStatus { param( $UserId ) return (Get-MgUser -UserId $UserId -Property AccountEnabled).AccountEnabled } <# Admin role section #> function checkAdminRoleReport { Write-Host "Checking admin role assignments" $Assignments = Get-MgRoleManagementDirectoryRoleAssignment -Property PrincipalId, RoleDefinitionId foreach ($Assignment in $Assignments) { $ProcessedCount++ Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Assignment.PrincipalId)" if ($User = Get-MgUser -UserId $Assignment.PrincipalId -Property DisplayName, UserPrincipalName -ErrorAction SilentlyContinue) { $Assignment | Add-Member -NotePropertyName "DisplayName" -NotePropertyValue $User.DisplayName $Assignment | Add-Member -NotePropertyName "UserPrincipalName" -NotePropertyValue $User.UserPrincipalName $Assignment | Add-Member -NotePropertyName "RoleName" -NotePropertyValue (Get-MgRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $Assignment.RoleDefinitionId -Property DisplayName).DisplayName } } Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Assignment.PrincipalId)" -Status "Ready" -Completed return $Assignments | Where-Object { -not ($null -eq $_.DisplayName) } | Sort-Object -Property UserPrincipalName | ConvertTo-HTML -Property DisplayName, UserPrincipalName, RoleName -As Table -Fragment -PreContent "<br><h3 id='AAD_ADMINS'>Admin role assignments</h3>" } <# BreakGlass account Section #> function checkBreakGlassAccountReport { param ( $Create ) if ($BgAccount = getBreakGlassAccount) { $Report = $BgAccount | ConvertTo-HTML -Property DisplayName, UserPrincipalName, AccountEnabled, GlobalAdmin, LastSignIn -As Table -Fragment -PreContent "<br><h3 id='AAD_BG'>BreakGlass account</h3>" $Report = $Report -Replace "<td>False</td>", "<td class='red'>False</td>" return $Report } if ($create) { createBreakGlassAccount $Report = getBreakGlassAccount | ConvertTo-HTML -Property DisplayName, UserPrincipalName, AccountEnabled, GlobalAdmin, LastSignIn -As Table -Fragment -PreContent "<br><h3 id='AAD_BG'>BreakGlass account</h3>" -PostContent "<p>Check console log for credentials</p>" $Report = $Report -Replace "<td>False</td>", "<td class='red'>False</td>" return $Report } return "<br><h3 id='AAD_BG'>BreakGlass account</h3><p>Not found</p>" } function getBreakGlassAccount { Write-Host "Checking BreakGlass account" Select-MgProfile -Name "beta" $BgAccounts = Get-MgUser -Filter "startswith(displayName, 'BreakGlass ')" -Property Id, DisplayName, UserPrincipalName, AccountEnabled, SignInActivity Select-MgProfile -Name "v1.0" if (-not $bgAccounts) { $BgAccounts = Get-MgUser -Filter "startswith(displayName, 'BreakGlass ')" -Property Id, DisplayName, UserPrincipalName, AccountEnabled } if (-not $bgAccounts) { return } foreach ($BgAccount in $BgAccounts) { Add-Member -InputObject $BgAccount -NotePropertyName "GlobalAdmin" -NotePropertyValue (checkGlobalAdminRole $BgAccount.Id) Add-Member -InputObject $BgAccount -NotePropertyName "LastSignIn" -NotePropertyValue $BgAccount.SignInActivity.LastSignInDateTime } return $BgAccounts } function getGlobalAdminRoleId { return (Get-MgDirectoryRole -Filter "DisplayName eq 'Global Administrator'" -Property Id).Id } function checkGlobalAdminRole { param ( $AccountId ) if (Get-MgDirectoryRoleMember -DirectoryRoleId (getGlobalAdminRoleId) -Filter "id eq '$($AccountId)'") { return $true } } function createBreakGlassAccount { Write-Host "Creating BreakGlass account:" $Name = -join ((97..122) | Get-Random -Count 64 | ForEach-Object { [char]$_ }) $DisplayName = "BreakGlass $Name" $Domain = (Get-MgDomain -Property id, IsInitial | Where-Object { $_.IsInitial -eq $true }).Id $UPN = "$Name@$Domain" $PasswordProfile = @{ ForceChangePasswordNextSignIn = $false ForceChangePasswordNextSignInWithMfa = $false Password = generatePassword } $BgAccount = New-MgUser -DisplayName $DisplayName -UserPrincipalName $UPN -MailNickName $Name -PasswordProfile $PasswordProfile -PreferredLanguage "en-US" -AccountEnabled $DirObject = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$($BgAccount.id)" } New-MgDirectoryRoleMemberByRef -DirectoryRoleId (getGlobalAdminRoleId) -BodyParameter $DirObject Add-Member -InputObject $BgAccount -NotePropertyName "Password" -NotePropertyValue $PasswordProfile.Password Write-Host ($BgAccount | Select-Object -Property Id, DisplayName, UserPrincipalName, Password | Format-List | Out-String) } function generatePassword { param ( [ValidateRange(4, [int]::MaxValue)] [int] $length = 64, [int] $upper = 4, [int] $lower = 4, [int] $numeric = 4, [int] $special = 4 ) if ($upper + $lower + $numeric + $special -gt $length) { throw "number of upper/lower/numeric/special char must be lower or equal to length" } $uCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" $lCharSet = "abcdefghijklmnopqrstuvwxyz" $nCharSet = "0123456789" $sCharSet = "/*-+, !?=()@; :._" $charSet = "" if ($upper -gt 0) { $charSet += $uCharSet } if ($lower -gt 0) { $charSet += $lCharSet } if ($numeric -gt 0) { $charSet += $nCharSet } if ($special -gt 0) { $charSet += $sCharSet } $charSet = $charSet.ToCharArray() $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider $bytes = New-Object byte[]($length) $rng.GetBytes($bytes) $result = New-Object char[]($length) for ($i = 0 ; $i -lt $length ; $i++) { $result[$i] = $charSet[$bytes[$i] % $charSet.Length] } $password = (-join $result) $valid = $true if ($upper -gt ($password.ToCharArray() | Where-Object { $_ -cin $uCharSet.ToCharArray() }).Count) { $valid = $false } if ($lower -gt ($password.ToCharArray() | Where-Object { $_ -cin $lCharSet.ToCharArray() }).Count) { $valid = $false } if ($numeric -gt ($password.ToCharArray() | Where-Object { $_ -cin $nCharSet.ToCharArray() }).Count) { $valid = $false } if ($special -gt ($password.ToCharArray() | Where-Object { $_ -cin $sCharSet.ToCharArray() }).Count) { $valid = $false } if (!$valid) { $password = Get-RandomPassword $length $upper $lower $numeric $special } return $password } <# User MFA section#> function checkUserMfaStatusReport { Write-Host "Checking user MFA status" $Users = Get-MgUser -All -Filter "UserType eq 'Member'" -Property Id, DisplayName, UserPrincipalName, AssignedLicenses, AccountEnabled $Users | ForEach-Object { $ProcessedCount++ if (($_.AssignedLicenses).Count -ne 0) { $LicenseStatus = "Licensed" } else { $LicenseStatus = "Unlicensed" } Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($_.DisplayName)" [array]$MFAData = Get-MgUserAuthenticationMethod -UserId $_.Id $AuthenticationMethod = @() $AdditionalDetails = @() foreach ($MFA in $MFAData) { Switch ($MFA.AdditionalProperties["@odata.type"]) { "#microsoft.graph.passwordAuthenticationMethod" { $AuthMethod = 'PasswordAuthentication' $AuthMethodDetails = $MFA.AdditionalProperties["displayName"] } "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" { $AuthMethod = 'AuthenticatorApp' $AuthMethodDetails = $MFA.AdditionalProperties["displayName"] } "#microsoft.graph.phoneAuthenticationMethod" { $AuthMethod = 'PhoneAuthentication' $AuthMethodDetails = $MFA.AdditionalProperties["phoneType", "phoneNumber"] -join ' ' } "#microsoft.graph.fido2AuthenticationMethod" { $AuthMethod = 'Fido2' $AuthMethodDetails = $MFA.AdditionalProperties["model"] } "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" { $AuthMethod = 'WindowsHelloForBusiness' $AuthMethodDetails = $MFA.AdditionalProperties["displayName"] } "#microsoft.graph.emailAuthenticationMethod" { $AuthMethod = 'EmailAuthentication' $AuthMethodDetails = $MFA.AdditionalProperties["emailAddress"] } "microsoft.graph.temporaryAccessPassAuthenticationMethod" { $AuthMethod = 'TemporaryAccessPass' $AuthMethodDetails = 'Access pass lifetime (minutes): ' + $MFA.AdditionalProperties["lifetimeInMinutes"] } "#microsoft.graph.passwordlessMicrosoftAuthenticatorAuthenticationMethod" { $AuthMethod = 'PasswordlessMSAuthenticator' $AuthMethodDetails = $MFA.AdditionalProperties["displayName"] } "#microsoft.graph.softwareOathAuthenticationMethod" { $AuthMethod = 'SoftwareOath' } } $AuthenticationMethod += $AuthMethod if ($null -ne $AuthMethodDetails) { $AdditionalDetails += "$AuthMethod : $AuthMethodDetails" } } $AuthenticationMethod = $AuthenticationMethod | Sort-Object | Get-Unique $AdditionalDetail = $AdditionalDetails -join ', ' [array]$StrongMFAMethods = ("Fido2", "PasswordlessMSAuthenticator", "AuthenticatorApp", "WindowsHelloForBusiness", "SoftwareOath") $MFAStatus = "Disabled" foreach ($StrongMFAMethod in $StrongMFAMethods) { if ($AuthenticationMethod -contains $StrongMFAMethod) { $MFAStatus = "Strong" break } } if ( ($AuthenticationMethod -contains "PhoneAuthentication") -or ($AuthenticationMethod -contains "EmailAuthentication")) { $MFAStatus = "Weak" } Add-Member -InputObject $_ -NotePropertyName "LicenseStatus" -NotePropertyValue $LicenseStatus Add-Member -InputObject $_ -NotePropertyName "MFAStatus" -NotePropertyValue $MFAStatus Add-Member -InputObject $_ -NotePropertyName "AdditionalDetail" -NotePropertyValue $AdditionalDetail } Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($_.DisplayName)" -Status "Ready" -Completed $Report = $Users | Sort-Object -Property UserPrincipalName | ConvertTo-HTML -Property DisplayName, UserPrincipalName, LicenseStatus, AccountEnabled, MFAStatus, AdditionalDetail -As Table -Fragment -PreContent "<br><h3 id='AAD_MFA'>User MFA status</h3>" -PostContent "<p>Weak: PhoneAuthentication, EmailAuthentication</p><p>Strong: Fido2, PasswordlessMSAuthenticator, AuthenticatorApp, WindowsHelloForBusiness, SoftwareOath</p>" $Report = $Report -Replace "<td>True</td><td>Disabled</td>", "<td>True</td><td class='red'>Disabled</td>" $Report = $Report -Replace "<td>True</td><td>Weak</td>", "<td>True</td><td class='orange'>Weak</td>" return $Report } <# Guest user section#> function checkGuestUserReport { Write-Host "Checking guest accounts" Select-MgProfile -Name "beta" $Users = Get-MgUser -All -Filter "UserType eq 'Guest'" -Property Id, DisplayName, UserPrincipalName, AccountEnabled, SignInActivity Select-MgProfile -Name "v1.0" if (-not $Users) { return "<br><h3 id='AAD_GUEST'>Guest accounts</h3><p>Not found</p>" } return $Users | Select-Object -Property DisplayName, UserPrincipalName, AccountEnabled, @{Name = "LastSignIn"; Expression = { $_.SignInActivity.LastSignInDateTime } } | Sort-Object -Property LastSignIn | ConvertTo-HTML -As Table -Fragment -PreContent "<br><h3 id='AAD_GUEST'>Guest accounts</h3>" } <# Security defaults section #> function checkSecurityDefaultsReport { param ( [System.Boolean]$EnableSecurityDefaults, [System.Boolean]$DisableSecurityDefaults ) if ($EnableSecurityDefaults -and (-not $DisableSecurityDefaults)) { updateSecurityDefaults -Enable $true } if ($DisableSecurityDefaults -and (-not $EnableSecurityDefaults)) { updateSecurityDefaults -Enable $false } if (checkSecurityDefaults) { return "<br><h3 id='AAD_SEC_DEFAULTS'>Security defaults</h3><p>Enabled</p>" } return "<br><h3 id='AAD_SEC_DEFAULTS'>Security defaults</h3><p>Disabled</p>" } function checkSecurityDefaults { Write-Host "Checking security defaults" return (Get-MgPolicyIdentitySecurityDefaultEnforcementPolicy -Property "isEnabled").IsEnabled } function updateSecurityDefaults { param ([System.Boolean]$Enable) $params = @{ IsEnabled = $Enable } Write-Host "Updating security defaults enable:" $Enable Update-MgPolicyIdentitySecurityDefaultEnforcementPolicy -BodyParameter $params } <# Conditional access section #> function checkConditionalAccessPolicyReport { Write-Host "Checking conditional access policies" if ($Policy = Get-MgIdentityConditionalAccessPolicy -Property Id, DisplayName, State) { return $Policy | ConvertTo-HTML -Property DisplayName, Id, State -As Table -Fragment -PreContent "<br><h3 id='AAD_CA'>Conditional access policies</h3>" } return "<br><h3 id='AAD_CA'>Conditional access policies</h3><p>Not found</p>" } function checkNamedLocationReport { Write-Host "Checking named locations" if ($Locations = Get-MgIdentityConditionalAccessNamedLocation) { return $Locations | Select-Object -Property DisplayName, @{Name = "Trusted"; Expression = { $_.additionalProperties["isTrusted"] } }, @{Name = "IPRange"; Expression = { $IpRangesReport = @() foreach ($IpRange in ($_.additionalProperties["ipRanges"])) { $IpRangesReport += $IpRange["cidrAddress"] } return $IpRangesReport } }, @{Name = "Countries"; Expression = { $_.additionalProperties["countriesAndRegions"] } } | ConvertTo-HTML -As Table -Fragment -PreContent "<br><h3 id='AAD_CA_LOCATIONS'>Named locations</h3>" } return "<br><h3 id='AAD_CA_LOCATIONS'>Named locations</h3><p>Not found</p>" } <# Application protection polices section#> function checkAppProtectionPolicesReport { Write-Host "Checking app protection policies" if ($Polices = getAppProtectionPolices) { return $Polices | ConvertTo-HTML -As Table -Property DisplayName, IsAssigned -Fragment -PreContent "<br><h3 id='AAD_APP_POLICY'>App protection policies</h3>" } return "<br><h3 id='AAD_APP_POLICY'>App protection policies</h3><p>Not found</p>" } function getAppProtectionPolices { $IOSPolicies = Get-MgDeviceAppManagementiOSManagedAppProtection -Property DisplayName, IsAssigned $AndroidPolicies = Get-MgDeviceAppManagementAndroidManagedAppProtection -Property DisplayName, IsAssigned $Policies = @() $Policies += $IOSPolicies $Policies += $AndroidPolicies return $Policies } <# SharePoint Tenant section #> function checkSpoTenantReport { param( [System.Boolean]$DisableAddToOneDrive ) Write-Host "Checking tenant settings" if ($DisableAddToOneDrive) { Write-Host "Disable add to OneDrive button" Set-PnPTenant -DisableAddToOneDrive $True } $Report = Get-PnPTenant | ConvertTo-HTML -As List -Property LegacyAuthProtocolsEnabled, DisableAddToOneDrive, ConditionalAccessPolicy, SharingCapability, ODBMembersCanShare, PreventExternalUsersFromResharing, DefaultSharingLinkType, DefaultLinkPermission, FolderAnonymousLinkType, FileAnonymousLinkType, RequireAnonymousLinksExpireInDays -Fragment -PreContent "<h3 id='SPO_SETTINGS'>Tenant settings</h3>" -PostContent "<p>ConditionalAccessPolicy: AllowFullAccess, AllowLimitedAccess, BlockAccess</p> <p>SharingCapability: Disabled, ExternalUserSharingOnly, ExternalUserAndGuestSharing, ExistingExternalUserSharingOnly</p> <p>DefaultSharingLinkType: None, Direct, Internal, AnonymousAccess</p>" $Report = $Report -Replace "<td>LegacyAuthProtocolsEnabled:</td><td>True</td>", "<td>LegacyAuthProtocolsEnabled:</td><td class='red'>True</td>" $Report = $Report -Replace "<td>DisableAddToOneDrive:</td><td>False</td>", "<td>DisableAddToOneDrive:</td><td class='red'>False</td>" $Report = $Report -Replace "<td>ConditionalAccessPolicy:</td><td>AllowFullAccess</td>", "<td>ConditionalAccessPolicy:</td><td class='red'>AllowFullAccess</td>" $Report = $Report -Replace "<td>SharingCapability:</td><td>ExternalUserAndGuestSharing</td>", "<td>SharingCapability:</td><td class='red'>ExternalUserAndGuestSharing</td>" $Report = $Report -Replace "<td>PreventExternalUsersFromResharing:</td><td>False</td>", "<td>PreventExternalUsersFromResharing:</td><td class='red'>False</td>" $Report = $Report -Replace "<td>DefaultSharingLinkType:</td><td>AnonymousAccess</td>", "<td>DefaultSharingLinkType:</td><td class='red'>AnonymousAccess</td>" return $Report } <# Mail Domain section #> function checkMailDomainReport { Write-Host "Checking domains" $Domains = Get-DkimSigningConfig | Select-Object -Property Id, @{Name = "Default"; Expression = { $_.IsDefault } }, @{Name = "DKIM"; Expression = { $_.Enabled } } if (-not ($Domains)) { $Domains = Get-AcceptedDomain | Select-Object -Property Id, "Default", @{Name = "DKIM"; Expression = { $false } } } $DomainsReport = @() foreach ($Domain in $Domains) { $ProcessedCount++ Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Domain.Id)" $Domain = checkDMARC -Domain $Domain $Domain = checkSPF -Domain $Domain $DomainsReport += $Domain } Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Domain.Id)" -Status "Ready" -Completed $Report = $DomainsReport | ConvertTo-Html -As Table -Property Id, DKIM, DMARC, SPF, "DMARC record", "SPF record", "DMARC hint", "SPF hint", "Default" -Fragment -PreContent "<h3 id='EXO_DOMAIN'>Domains</h3>" $Report = $Report -Replace "<td>False</td><td>False</td><td>False</td>", "<td class='red'>False</td><td class='red'>False</td><td class='red'>False</td>" $Report = $Report -Replace "<td>False</td><td>False</td><td>True</td>", "<td class='red'>False</td><td class='red'>False</td><td>True</td>" $Report = $Report -Replace "<td>True</td><td>False</td><td>False</td>", "<td>True</td><td class='red'>False</td><td class='red'>False</td>" $Report = $Report -Replace "<td>True</td><td>False</td><td>True</td>", "<td>True</td><td class='red'>False</td><td>True</td>" $Report = $Report -Replace "<td>False</td><td>True</td><td>False</td>", "<td class='red'>False</td><td>True</td><td class='red'>False</td>" $Report = $Report -Replace "<td>False</td><td>True</td><td>True</td>", "<td class='red'>False</td><td>True</td><td>True</td>" $Report = $Report -Replace "<td>Should be p=reject</td>", "<td class='orange'>Should be p=reject</td>" $Report = $Report -Replace "<td>Not sufficiently stricth</td>", "<td class='orange'>Not sufficiently strict</td>" $Report = $Report -Replace "<td>Not effective enough</td>", "<td class='red'>Not effective enough</td>" $Report = $Report -Replace "<td>Does not protect</td>", "<td class='red'>Does not protect</td>" $Report = $Report -Replace "<td>No qualifier found</td>", "<td class='red'>No qualifier found</td>" return $Report } function checkDMARC { param($Domain) if ($PSVersionTable.Platform -eq "Unix") { $DMARCRecord = (Resolve-Dns -Query "_dmarc.$($Domain.Id)" -QueryType TXT | Select-Object -Expand Answers).Text } else { $DMARCRecord = Resolve-DnsName -Name "_dmarc.$($Domain.Id)" -Type TXT -ErrorAction SilentlyContinue | Select-Object -ExpandProperty strings } if ($null -eq $DMARCRecord ) { $DMARC = $false } else { switch -Regex ($DMARCRecord ) { ('p=none') { $DmarcHint = "Does not protect" $DMARC = $true } ('p=quarantine') { $DmarcHint = "Should be p=reject" $DMARC = $true } ('p=reject') { $DmarcHint = "Will protect" $DMARC = $true } ('sp=none') { $DmarcHint += "Does not protect" $DMARC = $true } ('sp=quarantine') { $DmarcHint += "Should be p=reject" $DMARC = $true } ('sp=reject') { $DmarcHint += "Will protect" $DMARC = $true } } } $Domain | Add-Member NoteProperty "DMARC" $DMARC $Domain | Add-Member NoteProperty "DMARC record" "$($DMARCRecord )" $Domain | Add-Member NoteProperty "DMARC hint" $DmarcHint return $Domain } function checkSPF { param($Domain) if ($PSVersionTable.Platform -eq "Unix") { $SPFRecord = (Resolve-Dns -Query $Domain.Id -QueryType TXT | Select-Object -Expand Answers).Text | where-object { $_ -match "v=spf1" } } else { $SPFRecord = Resolve-DnsName -Name $Domain.Id -Type TXT -ErrorAction SilentlyContinue | where-object { $_.strings -match "v=spf1" } | Select-Object -ExpandProperty strings } if ($SPFRecord -match "redirect") { $redirect = $SPFRecord.Split(" ") $RedirectName = $redirect -match "redirect" -replace "redirect=" if ($PSVersionTable.Platform -eq "Unix") { $SPFRecord = (Resolve-Dns -Query $RedirectName -QueryType TXT | Select-Object -Expand Answers).Text | where-object { $_ -match "v=spf1" } } else { $SPFRecord = Resolve-DnsName -Name $RedirectName -Type TXT -ErrorAction SilentlyContinue | where-object { $_.strings -match "v=spf1" } | Select-Object -ExpandProperty strings } } if ($null -eq $SPFRecord) { $SPF = $false } if ($SPFRecord -is [array]) { $SPFHint = "More than one SPF-record" $SPF = $true } Else { switch -Regex ($SPFRecord) { '~all' { $SPFHint = "Not sufficiently strict" $SPF = $true } '-all' { $SPFHint = "Sufficiently strict" $SPF = $true } "\?all" { $SPFHint = "Not effective enough" $SPF = $true } '\+all' { $SPFHint = "Not effective enough" $SPF = $true } Default { $SPFHint = "No qualifier found" $SPF = $true } } } $Domain | Add-Member NoteProperty "SPF" "$($SPF)" $Domain | Add-Member NoteProperty "SPF record" "$($SPFRecord)" $Domain | Add-Member NoteProperty "SPF hint" $SPFHint return $Domain } <# Mail connector section#> function checkMailConnectorReport { Write-Host "Checking mail connectors" if (-not ($Inbound = Get-InboundConnector)) { $InboundReport = "<br><h3 id='EXO_CONNECTOR_IN'>Inbound mail connector</h3><p>Not found</p>" } else { $InboundReport = $Inbound | ConvertTo-Html -As Table -Property Name, SenderDomains, SenderIPAddresses, Enabled -Fragment -PreContent "<br><h3 id='EXO_CONNECTOR_IN'>Inbound mail connector</h3>" } if (-not ($Outbound = Get-OutboundConnector -IncludeTestModeConnectors:$true)) { $OutboundReport = "<br><h3 id='EXO_CONNECTOR_OUT'>Outbound mail connector</h3><p>Not found</p>" } else { $OutboundReport = $Outbound | ConvertTo-Html -As Table -Property Name, RecipientDomains, SmartHosts, Enabled -Fragment -PreContent "<br><h3 id='EXO_CONNECTOR_OUT'>Outbound mail connector</h3>" } $Report = @() $Report += $InboundReport $Report += $OutboundReport return $Report } <# User mailbox section #> function checkMailboxReport { param( [System.Boolean]$Language ) Write-Host "Checking user mailboxes" if ( -not ($Mailboxes = Get-EXOMailbox -RecipientTypeDetails UserMailbox -ResultSize:Unlimited -Properties DisplayName, UserPrincipalName)) { return "<br><h3 id='EXO_USER'>User mailbox</h3><p>Not found</p>" } if ($Language) { setMailboxLang -Mailbox $Mailboxes } $MailboxReport = @() foreach ($Mailbox in $Mailboxes) { $ProcessedCount++ Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Mailbox.DisplayName)" $MailboxReport += checkMailboxLoginAndLocation $Mailbox } Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Mailbox.DisplayName)" -Status "Ready" -Completed return $MailboxReport | ConvertTo-HTML -As Table -Property UserPrincipalName, DisplayName, Language, TimeZone, LoginAllowed ` -Fragment -PreContent "<br><h3 id='EXO_USER'>User mailbox</h3>" } function setMailboxLang { param( $Mailbox ) Write-Host "Setting mailboxes language:" $script:MailboxLanguageCode "timezone:" $script:MailboxTimeZone $Mailbox | Set-MailboxRegionalConfiguration -LocalizeDefaultFolderName:$true -Language $script:MailboxLanguageCode -TimeZone $script:MailboxTimeZone } <# Shared mailbox section #> function checkSharedMailboxReport { param( [System.Boolean]$Language, [System.Boolean]$DisableLogin, [System.Boolean]$EnableCopy ) Write-Host "Checking shared mailboxes" if ( -not ($Mailboxes = Get-EXOMailbox -RecipientTypeDetails SharedMailbox -ResultSize:Unlimited -Properties DisplayName, UserPrincipalName, MessageCopyForSentAsEnabled, MessageCopyForSendOnBehalfEnabled)) { return "<br><h3 id='EXO_SHARED'>Shared mailbox</h3><p>Not found</p>" } if ($Language) { setMailboxLang -Mailbox $Mailboxes } if ($DisableLogin) { disableUserAccount $Mailboxes } if ($EnableCopy) { setSharedMailboxEnableCopyToSent $Mailboxes $Mailboxes = Get-EXOMailbox -RecipientTypeDetails SharedMailbox -ResultSize:Unlimited -Properties DisplayName, UserPrincipalName, MessageCopyForSentAsEnabled, MessageCopyForSendOnBehalfEnabled } $MailboxReport = @() foreach ($Mailbox in $Mailboxes) { $ProcessedCount++ Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Mailbox.DisplayName)" $MailboxReport += checkMailboxLoginAndLocation $Mailbox } Write-Progress -Activity "Processed count: $ProcessedCount; Currently processing: $($Mailbox.DisplayName)" -Status "Ready" -Completed $Report = $MailboxReport | ConvertTo-HTML -As Table -Property UserPrincipalName, DisplayName, Language, TimeZone, MessageCopyForSentAsEnabled, MessageCopyForSendOnBehalfEnabled, LoginAllowed -Fragment -PreContent "<br><h3 id='EXO_SHARED'>Shared mailbox</h3>" $Report = $Report -Replace "<td>True</td><td>True</td><td>True</td>", "<td>True</td><td>True</td><td class='red'>True</td>" $Report = $Report -Replace "<td>False</td><td>False</td><td>True</td>", "<td>False</td><td>False</td><td class='red'>True</td>" $Report = $Report -Replace "<td>True</td><td>False</td><td>True</td>", "<td>True</td><td>False</td><td class='red'>True</td>" $Report = $Report -Replace "<td>False</td><td>True</td><td>True</td>", "<td>False</td><td>True</td><td class='red'>True</td>" return $Report } function checkMailboxLoginAndLocation { param ( $Mailbox ) $ReginalConfig = $Mailbox | Get-MailboxRegionalConfiguration Add-Member -InputObject $Mailbox -NotePropertyName "Language" -NotePropertyValue $ReginalConfig.Language Add-Member -InputObject $Mailbox -NotePropertyName "TimeZone" -NotePropertyValue $ReginalConfig.TimeZone Add-Member -InputObject $Mailbox -NotePropertyName "LoginAllowed" -NotePropertyValue (checkUserAccountStatus $Mailbox.UserPrincipalName) return $Mailbox } function setSharedMailboxEnableCopyToSent { param( $Mailbox ) Write-Host "Enable shared mailbox copy to sent" $Mailbox | Set-Mailbox -MessageCopyForSentAsEnabled $True -MessageCopyForSendOnBehalfEnabled $True } <# Unified mailbox section #> function checkUnifiedMailboxReport { param( [System.Boolean]$HideFromClient ) Write-Host "Checking unified mailboxes" if ( -not ($Mailboxes = Get-UnifiedGroup -ResultSize Unlimited)) { return "<br><h3 id='EXO_UNIFIED'>Unified mailbox</h3><p>Not found</p>" } if ($HideFromClient) { Write-Host "Hiding unified mailboxes from outlook client" $Mailboxes | Set-UnifiedGroup -HiddenFromExchangeClientsEnabled:$true -HiddenFromAddressListsEnabled:$false $Mailboxes = Get-UnifiedGroup -ResultSize Unlimited } return $Mailboxes | Sort-Object -Property PrimarySmtpAddress | ConvertTo-HTML -As Table -Property DisplayName, PrimarySmtpAddress, HiddenFromAddressListsEnabled, HiddenFromExchangeClientsEnabled -Fragment -PreContent "<br><h3 id='EXO_UNIFIED'>Unified mailbox</h3>" -PostContent "<p>Unified groups = Microsoft 365 groups</p>" } <# HTML table of content section #> $TableOfContents = @() $TableOfContents += "<br><hr><h2>Contents</h2>" $AADTableOfContents = @" <h3 class='TOC'><a href="#AAD">Azure Active Directory</a></h3> <ul> <li><a href="#AAD_USER_SETTINGS">User settings</a></li> <li><a href="#AAD_DEVICE_JOIN_SETTINGS">Device join settings</a></li> <li><a href="#AAD_SKU">Licenses</a></li> <li><a href="#AAD_ADMINS">Admin role assignments</a></li> <li><a href="#AAD_BG">BreakGlass account</a></li> <li><a href="#AAD_MFA">User MFA status</a></li> <li><a href="#AAD_GUEST">Guest accounts</a></li> <li><a href="#AAD_SEC_DEFAULTS">Security defaults</a></li> <li><a href="#AAD_CA">Conditional access policies</a></li> <li><a href="#AAD_CA_LOCATIONS">Named locations</a></li> <li><a href="#AAD_APP_POLICY">App protection policies</a></li> </ul> "@ $SPOTableOfContents = @" <h3 class='TOC'><a href="#SPO">SharePoint Online</a></h3> <ul> <li><a href="#SPO_SETTINGS">Tenant settings</a></li> </ul> "@ $EXOTabelOfContents = @" <h3 class='TOC'><a href="#EXO">Exchange Online</a></h3> <ul> <li><a href="#EXO_DOMAIN">Domains</a></li> <li><a href="#EXO_CONNECTOR_IN">Inbound mail connector</a></li> <li><a href="#EXO_CONNECTOR_OUT">Outbound mail connector</a></li> <li><a href="#EXO_USER">User mailbox</a></li> <li><a href="#EXO_SHARED">Shared mailbox</a></li> <li><a href="#EXO_UNIFIED">Unified mailbox</a></li> </ul> "@ <# Script logic start section #> CheckInteractiveMode -Parameters $PSBoundParameters if ($script:InteractiveMode) { InteractiveMenu } if (-not $UseExistingGraphSession) { connectGraph } else { if ( -not (checkGraphSession)) { exit } } $TableOfContents += $AADTableOfContents if ($script:AddSharePointOnlineReport -or $script:DisableAddToOneDrive) { if (-not $UseExistingSpoSession) { connectSpo } else { if ( -not (checkSpoSession)) { exit } } $TableOfContents += $SPOTableOfContents } if ($script:AddExchangeOnlineReport -or $script:SetMailboxLanguage -or $script:DisableSharedMailboxLogin -or $script:EnableSharedMailboxCopyToSent -or $script:HideUnifiedMailboxFromOutlookClient) { if (-not $UseExistingExoSession) { connectExo } else { if ( -not (checkExoSession)) { exit } } $TableOfContents += $EXOTabelOfContents } $Report = @() $Report += organizationReport $Report += $TableOfContents $Report += "<br><hr><h2 id='AAD'>Azure Active Directory</h2>" Write-Host "Azure Active Directory" $Report += checkTenanUserSettingsReport -DisableUserConsent $script:DisableEnterpiseApplicationUserConsent -DisableUsersToCreateAppRegistrations $script:DisableUsersToCreateAppRegistrations -DisableUsersToReadOtherUsers $script:DisableUsersToReadOtherUsers -DisableUsersToCreateSecurityGroups $script:DisableUsersToCreateSecurityGroups -DisableUsersToCreateUnifiedGroups $script:DisableUsersToCreateUnifiedGroups -CreateUnifiedGroupCreationAllowedGroup $script:CreateUnifiedGroupCreationAllowedGroup -EnableBlockMsolPowerShell $script:EnableBlockMsolPowerShell $Report += checkDeviceJoinSettingsReport $Report += checkUsedSKUReport $Report += checkAdminRoleReport $Report += checkBreakGlassAccountReport -Create $script:CreateBreakGlassAccount $Report += checkUserMfaStatusReport $Report += checkGuestUserReport $Report += checkSecurityDefaultsReport -Enable $script:EnableSecurityDefaults -Disable $script:DisableSecurityDefaults $Report += checkConditionalAccessPolicyReport $Report += checkNamedLocationReport $Report += checkAppProtectionPolicesReport if ($script:AddSharePointOnlineReport -or $script:DisableAddToOneDrive) { $Report += "<br><hr><h2 id='SPO'>SharePoint Online</h2>" Write-Host "SharePoint Online" $Report += checkSpoTenantReport -DisableAddToOneDrive $script:DisableAddToOneDrive } if ($script:AddExchangeOnlineReport -or $script:SetMailboxLanguage -or $script:DisableSharedMailboxLogin -or $script:EnableSharedMailboxCopyToSent -or $script:HideUnifiedMailboxFromOutlookClient) { $Report += "<br><hr><h2 id='EXO'>Exchange Online</h2>" Write-Host "Exchange Online" $Report += checkMailDomainReport $Report += checkMailConnectorReport $Report += checkMailboxReport -Language $script:SetMailboxLanguage $Report += checkSharedMailboxReport -Language $script:SetMailboxLanguage -DisableLogin $script:DisableSharedMailboxLogin -EnableCopy $script:EnableSharedMailboxCopyToSent $Report += checkUnifiedMailboxReport -HideFromClient $script:HideUnifiedMailboxFromOutlookClient } if (-not $KeepGraphSessionAlive) { disconnectGraph } if (-not $KeepSpoSessionAlive) { if ($script:AddSharePointOnlineReport -or $script:DisableAddToOneDrive) { disconnectSpo } } if (-not $KeepExoSessionAlive) { if ($script:AddExchangeOnlineReport -or $script:SetMailboxLanguage -or $script:DisableSharedMailboxLogin -or $script:EnableSharedMailboxCopyToSent -or $script:HideUnifiedMailboxFromOutlookClient) { disconnectExo } } <# CSS styles section #> $Header = @" <title>$($ReportTitle)</title> <link rel="icon" type="image/png" href="$($ReportImageUrl)"> <style> html { display: table; margin: auto; } body { display: table-cell; vertical-align: middle; padding-right: 200px; padding-left: 200px; } h1 { font-family: Arial, Helvetica, sans-serif; color: #666666; font-size: 32px; } h2 { font-family: Arial, Helvetica, sans-serif; color: #666666; font-size: 24px; } h3 { font-family: Arial, Helvetica, sans-serif; color: #666666; font-size: 16px; } p { font-family: Arial, Helvetica, sans-serif; font-size: 14px; } a { font-family: Arial, Helvetica, sans-serif; font-size: 16px; text-decoration: none; color: #666666; } ul { list-style-type: none; margin-top: 5px; } li { padding: 5px; } table { font-size: 14px; border: 0px; font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; margin: 25px 0; min-width: 400px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); } th, td { padding: 4px; margin: 0px; border: 0; padding: 12px 15px; } th { background: #666666; color: #fff; font-size: 11px; padding: 10px 15px; vertical-align: middle; } tbody tr:nth-child(even) { background: #f0f0f2; } thead tr { color: #ffffff; text-align: left; } tbody tr { border-bottom: 1px solid #dddddd; } tbody tr:nth-of-type(even) { background-color: #f3f3f3; } .red { color: red; } .orange { color: orange; } .TOC { margin: 5px; } #FootNote { font-family: Arial, Helvetica, sans-serif; color: #666666; font-size: 12px; } </style> "@ <# HTML report section #> $Desktop = [Environment]::GetFolderPath("Desktop") $ReportTitleHtml = "<h1>" + $ReportTitle + "</h1>" $ReportName = ("Microsoft365-Report-$($script:CustomerName).html").Replace(" ", "") $PostContentHtml = @" <p id='FootNote'>$($script:VersionMessage)</p> <p id='FootNote'>Creation date: $(Get-Date -Format "dd.MM.yyyy HH:mm")</p> "@ Write-Host "Generating HTML report:" $ReportName $Report = ConvertTo-HTML -Body "$ReportTitleHtml $Report" -Title $ReportTitle -Head $Header -PostContent $PostContentHtml $Report | Out-File $Desktop\$ReportName Invoke-Item $Desktop\$ReportName if ($script:InteractiveMode) { Read-Host "Click [ENTER] key to exit AzureAdDeployer" } } Set-Alias aaddepl -Value Invoke-AzureAdDeployer |