diff --git a/src/AADRecommendations.xml b/src/AADRecommendations.xml
index e510f4e..671a4f1 100644
--- a/src/AADRecommendations.xml
+++ b/src/AADRecommendations.xml
@@ -203,7 +203,7 @@
Access Surface Area
AR0006
Grants for high level permissions
- Application can request high level permission trough fishing. Such permissions include those allowing "ReadWrite" actions or "Mail" operations. Regular reviews should be in place and non-necessary grants should be removed
+ Applications can request high level permission trough consent fishing. Such permissions include those allowing "ReadWrite" actions or "Mail" operations. Regular reviews should be in place and non-necessary grants should be removed
Review applications having high permissions and remove unnecessary grants
@@ -286,7 +286,7 @@
Entitlement Management
AR0009
Assignment of Apps with "All users" group
- The "All users" group contains both Members and Guests. Resource owners might misunderstand this group to contain only members. As a result, special condsideration should be takeb when using this group for application assignment or grant access tor resource such as SharePoint Content or Azure resources
+ The "All users" group contains both Members and Guests. Resource owners might misunderstand this group to contain only members. As a result, special condsideration should be taken when using this group for application assignment or grant access tor resource such as SharePoint Content or Azure resources
Fix the entittlements by creating the right groups (e.g. "all members")
@@ -2186,7 +2186,6 @@
roleDefinitions.csv
- RoleAssignmentReport.csv
conditionalAccessPolicies.json
diff --git a/src/AzureADAssessment.psd1 b/src/AzureADAssessment.psd1
index 2f8a78d..792e598 100644
--- a/src/AzureADAssessment.psd1
+++ b/src/AzureADAssessment.psd1
@@ -56,7 +56,7 @@ RequiredModules = @(
)
# Assemblies that must be loaded prior to importing this module
-# RequiredAssemblies = @()
+RequiredAssemblies = @("System.IO.Compression.FileSystem.dll")
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
@@ -123,6 +123,7 @@ NestedModules = @(
'.\Import-AADAssessmentEvidence.ps1'
'.\Export-AADAssessmentReportData.ps1'
'.\analysis\AccessManagement\AuthenticationExperience\Test-AADAssessmentEmailOtp.ps1'
+ '.\Test-AADAssessmentPackage.ps1'
)
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
@@ -151,6 +152,7 @@ FunctionsToExport = @(
'Export-AADAssessmentRecommendations'
'Test-AADAssessmentEmailOtp'
'Export-AADAssessmentReportData'
+ 'Test-AADAssessmentPackage'
)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
diff --git a/src/Complete-AADAssessmentReports.ps1 b/src/Complete-AADAssessmentReports.ps1
index 67e760a..1f41eb1 100644
--- a/src/Complete-AADAssessmentReports.ps1
+++ b/src/Complete-AADAssessmentReports.ps1
@@ -66,16 +66,57 @@ function Complete-AADAssessmentReports {
## Expand Data Package
Write-Progress -Id 0 -Activity 'Microsoft Azure AD Assessment Complete Reports' -Status 'Expand Data' -PercentComplete 0
- Expand-Archive $Path -DestinationPath $OutputDirectoryData -Force -ErrorAction Stop
+ #Expand-Archive $Path -DestinationPath $OutputDirectoryData -Force -ErrorAction Stop
+ # Remove destination before extract
+ if (Test-Path -Path $OutputDirectoryData) {
+ Remove-Item $OutputDirectoryData -Recurse -Force
+ }
+ # Extract content
+ [System.IO.Compression.ZipFile]::ExtractToDirectory($Path,$OutputDirectoryData)
$AssessmentDetail = Get-Content $AssessmentDetailPath -Raw | ConvertFrom-Json
+ #Check for DataFiles
+ $OutputDirectoryAAD = Join-Path $OutputDirectoryData 'AAD-*' -Resolve -ErrorAction Stop
+ [array] $DataFiles = Get-Item -Path (Join-Path $OutputDirectoryAAD "*") -Include "*Data.xml"
+ $SkippedReportOutput = $DataFiles -and $DataFiles.Count -eq 9
+
+ ## Check the provided archive
+ $archiveState = Test-AADAssessmentPackage -Path $Path -SkippedReportOutput $SkippedReportOutput
+ if (!$archiveState) {
+ Write-Warning "The provided package is incomplete. Please review how data was collected and any related errors"
+ Write-Warning "If reporting has been skipped this command will generate the reports"
+ }
+
+ # Check assessment version
+ $moduleVersion = $MyInvocation.MyCommand.ScriptBlock.Module.Version
+ [System.Version]$packageVersion = $AssessmentDetail.AssessmentVersion
+ if ($packageVersion.Build -eq -1) {
+ Write-Warning "The package was not generate with a module installed from the PowerShell Gallery"
+ Write-Warning "Please install the module from the gallery to generate the package:"
+ Write-Warning "PS > Install-Module -Name AzureADAssessment"
+ }
+ elseif ($moduleVersion.Build -eq -1) {
+ Write-Warning "The Azure AD Assessment module was not installed from the PowerShell Gallery"
+ Write-Warning "Please install the module from the gallery to complete the assessment:"
+ Write-Warning "PS > Install-Module -Name AzureADAssessment"
+ }
+ elseif ($moduleVersion -ne $packageVersion) {
+ Write-Warning "The module version differs from the provided package and the Assessment module version used to run the complete command"
+ Write-Warning "Please use the same module version to generate the package and complete the assessment"
+ Write-Warning ""
+ Write-Warning "package version: $packageVersion"
+ Write-Warning "module version: $moduleVersion"
+ Write-Warning ""
+ Write-Warning "To install a specific version of the module:"
+ Write-Warning "PS > Remove-Module -Name AzureADAssessment"
+ Write-Warning "PS > Install-Module -Name AzureADAssessment -RequiredVersion $packageVersion"
+ Write-Warning "PS > Import-Module -Name AzureADAssessment -RequiredVersion $packageVersion"
+ }
## Load Data
Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Complete Reports - {0}' -f $AssessmentDetail.AssessmentTenantDomain) -Status 'Load Data' -PercentComplete 10
- $OutputDirectoryAAD = Join-Path $OutputDirectoryData 'AAD-*' -Resolve -ErrorAction Stop
## Generate Reports
- [array] $DataFiles = Get-Item -Path (Join-Path $OutputDirectoryAAD "*") -Include "*Data.xml"
- if ($DataFiles -and $DataFiles.Count -eq 9) {
+ if ($SkippedReportOutput) {
Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Complete Reports - {0}' -f $AssessmentDetail.AssessmentTenantDomain) -Status 'Output Report Data' -PercentComplete 20
Export-AADAssessmentReportData -SourceDirectory $OutputDirectoryAAD -OutputDirectory $OutputDirectoryAAD
diff --git a/src/Export-AADAssessmentReportData.ps1 b/src/Export-AADAssessmentReportData.ps1
index 487cf03..f192b4c 100644
--- a/src/Export-AADAssessmentReportData.ps1
+++ b/src/Export-AADAssessmentReportData.ps1
@@ -148,7 +148,7 @@ function Export-AADAssessmentReportData {
}
# generate the report
- Set-Content -Path (Join-Path $OutputDirectory "users.csv") -Value 'id,userPrincipalName,displayName,userType,accountEnabled,onPremisesSyncEnabled,onPremisesImmutableId,mail,otherMails,AADLicense,lastInteractiveSignInDateTime,lastNonInteractiveSignInDateTime,isMfaRegistered,isMfaCapable,methodsRegistered'
+ Set-Content -Path (Join-Path $OutputDirectory "users.csv") -Value 'id,userPrincipalName,displayName,userType,accountEnabled,onPremisesSyncEnabled,onPremisesImmutableId,mail,otherMails,AADLicense,lastInteractiveSignInDateTime,lastNonInteractiveSignInDateTime,isMfaRegistered,isMfaCapable,methodsRegistered,defaultMfaMethod'
Get-AADAssessUserReport -Offline -UserData $LookupCache.user -RegistrationDetailsData $LookupCache.userRegistrationDetails`
| Use-Progress -Activity 'Exporting UserReport' -Property id -PassThru -WriteSummary `
| Format-Csv `
diff --git a/src/Get-AADAssessUserReport.ps1 b/src/Get-AADAssessUserReport.ps1
index f46b195..643b2e3 100644
--- a/src/Get-AADAssessUserReport.ps1
+++ b/src/Get-AADAssessUserReport.ps1
@@ -57,10 +57,14 @@ function Get-AADAssessUserReport {
$isMfaCapable = $false
$isMfaRegistered = $false
$methodsRegistered = ""
+ $defaultMfaMethod = ""
if ($registerationDetails) {
$isMfaRegistered = $registerationDetails.isMfaRegistered
$isMfaCapable = $registerationDetails.isMfaCapable
$methodsRegistered = $registerationDetails.methodsRegistered -join ";"
+ if ($registerationDetails.defaultMfaMethod -ne "none") {
+ $defaultMfaMethod = $registerationDetails.defaultMfaMethod
+ }
} else {
Write-Warning "authentication method registration not found for $($InputObject.id)"
}
@@ -81,6 +85,7 @@ function Get-AADAssessUserReport {
"isMfaRegistered" = $isMfaRegistered
"isMfaCapable" = $isMfaCapable
"methodsRegistered" = $methodsRegistered
+ "defaultMfaMethod" = $defaultMfaMethod
}
}
}
diff --git a/src/Invoke-AADAssessmentDataCollection.ps1 b/src/Invoke-AADAssessmentDataCollection.ps1
index 7382e9d..33f46b9 100644
--- a/src/Invoke-AADAssessmentDataCollection.ps1
+++ b/src/Invoke-AADAssessmentDataCollection.ps1
@@ -64,13 +64,13 @@ function Invoke-AADAssessmentDataCollection {
#$OutputDirectory = Join-Path $OutputDirectory "AzureADAssessment"
$OutputDirectoryData = Join-Path $OutputDirectory "AzureADAssessmentData"
$AssessmentDetailPath = Join-Path $OutputDirectoryData "AzureADAssessment.json"
- $PackagePath = Join-Path $OutputDirectory "AzureADAssessmentData.zip"
+ $PackagePath = Join-Path $OutputDirectory "AzureADAssessmentData.aad"
### Organization Data - 0
Write-Progress -Id 0 -Activity 'Microsoft Azure AD Assessment Data Collection' -Status 'Organization Details' -PercentComplete 0
$OrganizationData = Get-MsGraphResults 'organization?$select=id,displayName,verifiedDomains,technicalNotificationMails' -ErrorAction Stop
$InitialTenantDomain = $OrganizationData.verifiedDomains | Where-Object isInitial -EQ $true | Select-Object -ExpandProperty name -First 1
- $PackagePath = $PackagePath.Replace("AzureADAssessmentData.zip", "AzureADAssessmentData-$InitialTenantDomain.zip")
+ $PackagePath = $PackagePath.Replace("AzureADAssessmentData.aad", "AzureADAssessmentData-$InitialTenantDomain.aad")
$OutputDirectoryAAD = Join-Path $OutputDirectoryData "AAD-$InitialTenantDomain"
Assert-DirectoryExists $OutputDirectoryAAD
@@ -228,9 +228,10 @@ function Invoke-AADAssessmentDataCollection {
$ReferencedIdCache.servicePrincipal.Clear()
### Administrative units data - 14
+ Set-Content -Path (Join-Path $OutputDirectoryAAD "administrativeUnits.csv") -Value 'id,displayName,visibility'
Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Administrative Units' -PercentComplete 65
Get-MsGraphResults 'directory/administrativeUnits' -Select 'id,displayName,visibility' `
- | Export-Csv (Join-Path $OutputDirectoryAAD "administrativeUnits.csv")
+ | Export-Csv (Join-Path $OutputDirectoryAAD "administrativeUnits.csv") -NoTypeInformation -Append
### Registration details data - 15
if ($licenseType -ne "Free") {
@@ -333,10 +334,16 @@ function Invoke-AADAssessmentDataCollection {
}
if (!$SkipPackaging) {
+ ### Remove pre existing package (zip) if it exists
+ if (Test-Path -Path $PackagePath) {
+ Remove-Item $PackagePath -Force
+ }
+
+
### Package Output
- Compress-Archive (Join-Path $OutputDirectoryData '\*') -DestinationPath $PackagePath -Force -ErrorAction Stop
+ #Compress-Archive (Join-Path $OutputDirectoryData '\*') -DestinationPath $PackagePath -Force -ErrorAction Stop
+ [System.IO.Compression.ZipFile]::CreateFromDirectory($OutputDirectoryData,$PackagePath)
- ### Clean-Up Data Files
Remove-Item $OutputDirectoryData -Recurse -Force
}
@@ -345,5 +352,23 @@ function Invoke-AADAssessmentDataCollection {
}
catch { if ($MyInvocation.CommandOrigin -eq 'Runspace') { Write-AppInsightsException $_.Exception }; throw }
- finally { Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $? }
+ finally {
+ # check generated package and issue warning
+ $issue = $false
+ if (!(Test-Path -PathType Leaf -Path $PackagePath) -and !$SkipPackaging) {
+ Write-Warning "The export package has not been generated"
+ $issue = $true
+ } elseif (!$SkipPackaging) {
+ if (!(Test-AADAssessmentPackage -Path $PackagePath -SkippedReportOutput $SkipReportOutput)) {
+ Write-Warning "The generated package is missing some data"
+ $issue = $true
+ }
+ }
+ if ($issue) {
+ Write-Warning "If you are working with microsoft or a provider on the assessment please warn them"
+ Write-Warning "Please check GitHub issues and fill a new one or reply on existing ones mentionning the errors seen"
+ Write-warning "https://github.com/AzureAD/AzureADAssessment/issues"
+ }
+ Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $?
+ }
}
diff --git a/src/New-AADAssessmentRecommendations.ps1 b/src/New-AADAssessmentRecommendations.ps1
index ea8688d..538627b 100644
--- a/src/New-AADAssessmentRecommendations.ps1
+++ b/src/New-AADAssessmentRecommendations.ps1
@@ -27,7 +27,7 @@ function New-AADAssessmentRecommendations {
[string] $InterviewSpreadsheetPath
)
- #Start-AppInsightsRequest $MyInvocation.MyCommand.Name
+ Start-AppInsightsRequest $MyInvocation.MyCommand.Name
## Expand extracted data
if (-not $SkipExpand) {
@@ -185,7 +185,7 @@ function New-AADAssessmentRecommendations {
Write-Error "No Tenant Data found"
}
- #Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $?
+ Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $?
}
function Set-TypeQnAResult($data, $recommendationDef, $recommendation){
diff --git a/src/Test-AADAssessmentPackage.ps1 b/src/Test-AADAssessmentPackage.ps1
new file mode 100644
index 0000000..da54258
--- /dev/null
+++ b/src/Test-AADAssessmentPackage.ps1
@@ -0,0 +1,97 @@
+<#
+.SYNOPSIS
+ Test that the provided Azure AD Assessment package has the necessary content
+.DESCRIPTION
+ Test that the provided Azure AD Assessment package has the necessary content
+.EXAMPLE
+ PS C:\>Test-AADAssessmentPackage 'C:\AzureADAssessmentData-contoso.aad'
+ Test that the package for contoso has the necesary content for the assessment.
+.INPUTS
+ System.String
+#>
+function Test-AADAssessmentPackage {
+ [CmdletBinding()]
+ param
+ (
+ # Path to the file where the exported events will be stored
+ [Parameter(Mandatory = $true)]
+ [string] $Path,
+ # Reports should have been generated
+ [Parameter(Mandatory = $false)]
+ [bool] $SkippedReportOutput
+ )
+
+ if (!(Test-Path -path $Path)) {
+ Write-Warning "Assessment package not found"
+ return $false
+ }
+
+ $fullPath = Convert-Path $Path
+
+ $requiredEntries = @(
+ "AAD-*/administrativeUnits.csv",
+ "AAD-*/AppCredentialsReport.csv",
+ "AAD-*/applications.json",
+ "AAD-*/appRoleAssignments.csv",
+ "AAD-*/conditionalAccessPolicies.json",
+ "AAD-*/ConsentGrantReport.csv",
+ "AAD-*/emailOTPMethodPolicy.json",
+ "AAD-*/groups.csv",
+ "AAD-*/namedLocations.json",
+ "AAD-*/NotificationsEmailsReport.csv",
+ "AAD-*/oauth2PermissionGrants.csv",
+ "AAD-*/organization.json",
+ "AAD-*/RoleAssignmentReport.csv",
+ "AAD-*/roleDefinitions.csv",
+ "AAD-*/servicePrincipals.csv",
+ "AAD-*/servicePrincipals.json",
+ "AAD-*/subscribedSkus.json",
+ "AAD-*/userRegistrationDetails.json",
+ "AAD-*/users.csv",
+ "AzureADAssessment.json"
+ )
+
+ if ($SkippedReportOutput) {
+ $requiredEntries = @(
+ "AAD-*/administrativeUnits.csv",
+ "AAD-*/applicationData.xml",
+ "AAD-*/appRoleAssignmentData.xml",
+ "AAD-*/conditionalAccessPolicies.json",
+ "AAD-*/directoryRoleData.xml"
+ "AAD-*/emailOTPMethodPolicy.json",
+ "AAD-*/groupData.xml",
+ "AAD-*/namedLocations.json",
+ "AAD-*/oauth2PermissionGrantData.xml",
+ "AAD-*/organization.json",
+ "AAD-*/roleAssignmentSchedulesData.xml",
+ "AAD-*/roleDefinitions.csv",
+ "AAD-*/roleEligibilitySchedulesData.xml",
+ "AAD-*/servicePrincipalData.xml",
+ "AAD-*/subscribedSkus.json",
+ "AAD-*/userData.xml",
+ "AAD-*/userRegistrationDetails.json",
+ "AzureADAssessment.json"
+ )
+ }
+
+ $entries = [IO.Compression.ZipFile]::OpenRead($fullPath).Entries
+
+ $effectiveEntries = $entries | Where-Object { $_.Length -gt 0}
+
+ $validPackage = $true
+ foreach($requiredEntry in $requiredEntries) {
+ $found = $false
+ foreach ($effectiveEntry in $effectiveEntries) {
+ if (($effectiveEntry.FullName -replace "\\","/") -like $requiredEntry) {
+ $found = $true
+ }
+ }
+ if (!$found) {
+ Write-Warning "Required entry '$requiredEntry' not found or empty"
+ $validPackage = $false
+ }
+ }
+
+ # retrun package vaility
+ return $validPackage
+}
diff --git a/src/internal/Expand-MsGraphRelationship.ps1 b/src/internal/Expand-MsGraphRelationship.ps1
index bf8cae2..c36360f 100644
--- a/src/internal/Expand-MsGraphRelationship.ps1
+++ b/src/internal/Expand-MsGraphRelationship.ps1
@@ -53,9 +53,11 @@ function Expand-MsGraphRelationship {
[array] $Results = $InputObjects[0..($BatchSize - 1)] | Get-MsGraphResults $uri -DisableUniqueIdDeduplication -GroupOutputByRequest
}
for ($i = 0; $i -lt $InputObjects.Count; $i++) {
- [array] $refValues = $Results[$i]
+ $refValues = @()
+ if ($i -lt $Results.Count) {
+ [array] $refValues = $Results[$i]
+ }
if ($References) { $refValues = $refValues | Expand-ODataId | Select-Object -Property "*" -ExcludeProperty '@odata.id' }
- if ($null -eq $refValues) { $refValues = @() }
$InputObjects[$i] | Add-Member -Name $PropertyName -MemberType NoteProperty -Value $refValues -PassThru -ErrorAction Ignore
}
$InputObjects.RemoveRange(0, $BatchSize)
@@ -72,9 +74,11 @@ function Expand-MsGraphRelationship {
[array] $Results = $InputObjects | Get-MsGraphResults $uri -DisableUniqueIdDeduplication -GroupOutputByRequest
}
for ($i = 0; $i -lt $InputObjects.Count; $i++) {
- [array] $refValues = $Results[$i]
+ $refValues = @()
+ if ($Results.Count -gt $i) {
+ [array] $refValues = $Results[$i]
+ }
if ($References) { $refValues = $refValues | Expand-ODataId | Select-Object -Property "*" -ExcludeProperty '@odata.id' }
- if ($null -eq $refValues) { $refValues = @() }
$InputObjects[$i] | Add-Member -Name $PropertyName -MemberType NoteProperty -Value $refValues -PassThru -ErrorAction Ignore
}
}