From 731ec82dfa1e242bff7dad28ad0a7500ecc0a17d Mon Sep 17 00:00:00 2001 From: JoeyInvictus <129975292+JoeyInvictus@users.noreply.github.com> Date: Fri, 24 May 2024 16:39:19 +0200 Subject: [PATCH] update 1.3.5 Get-AzureADLogs and Get-AzureADGraphLogs: - Changed the output directory names for the Audit and Sign-in logs to make it clearer which folders contain what logs. - Accepted pull request by angry-bender, which added the split by time feature to Get-AzureADAuditLogs with a 12-hour interval (larger dataset than SignInLogs). - Both Graph and AD collections for the audit logs and sign-in logs now support date and time instead of only date. - Added error handling to the Graph and AD functionalities to retry if they fail, ensuring all data is collected. Get-Email - The functionality Get-Email now supports an input text file containing multiple message IDs, and the functionality will download all messages. --- Microsoft-Extractor-Suite.psd1 | 2 +- Scripts/Get-AzureADGraphLogs.ps1 | 192 +++-- Scripts/Get-AzureADLogs.ps1 | 667 +++++++++--------- Scripts/Get-Emails.ps1 | 177 +++-- Scripts/Get-MailItemsAccessed.ps1 | 8 +- docs/source/conf.py | 4 +- .../AzureActiveDirectoryAuditLog.rst | 13 +- .../AzureActiveDirectorysign-inlogs.rst | 16 +- .../functionality/AzureAuditLogsGraph.rst | 9 +- .../functionality/AzureSignInLogsGraph.rst | 10 +- docs/source/functionality/GetEmails.rst | 13 +- .../functionality/MailItemsAccessed.rst | 2 +- 12 files changed, 646 insertions(+), 467 deletions(-) diff --git a/Microsoft-Extractor-Suite.psd1 b/Microsoft-Extractor-Suite.psd1 index 85b2751..192a5d8 100644 --- a/Microsoft-Extractor-Suite.psd1 +++ b/Microsoft-Extractor-Suite.psd1 @@ -8,7 +8,7 @@ Author = 'Joey Rentenaar & Korstiaan Stam' CompanyName = 'Invictus-IR' # Version number of this module. -ModuleVersion = '1.3.4' +ModuleVersion = '1.3.5' # ID used to uniquely identify this module GUID = '4376306b-0078-4b4d-b565-e22804e3be01' diff --git a/Scripts/Get-AzureADGraphLogs.ps1 b/Scripts/Get-AzureADGraphLogs.ps1 index 3ce1714..ab524b4 100644 --- a/Scripts/Get-AzureADGraphLogs.ps1 +++ b/Scripts/Get-AzureADGraphLogs.ps1 @@ -5,7 +5,6 @@ function Get-ADSignInLogsGraph { .DESCRIPTION The Get-ADSignInLogsGraph GraphAPI cmdlet collects the contents of the Azure Active Directory sign-in logs. - The output will be written to: Output\AzureAD\SignInLogsGraph.json .PARAMETER startDate The startDate parameter specifies the date from which all logs need to be collected. @@ -15,7 +14,7 @@ function Get-ADSignInLogsGraph { .PARAMETER OutputDir outputDir is the parameter specifying the output directory. - Default: Output\AzureAD + Default: The output will be written to: Output\AzureAD\{date_SignInLogs}\SignInLogs.json .PARAMETER Encoding Encoding is the parameter specifying the encoding of the JSON output file. @@ -32,6 +31,10 @@ function Get-ADSignInLogsGraph { .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. + .PARAMETER Interval + Interval is the parameter specifying the interval in which the logs are being gathered. + Default: 1440 minutes + .EXAMPLE Get-ADSignInLogsGraph Get all audit logs of sign-ins. @@ -47,7 +50,7 @@ function Get-ADSignInLogsGraph { .EXAMPLE Get-ADSignInLogsGraph -startDate 2023-04-12 Get audit logs after 2023-04-12. - #> +#> [CmdletBinding()] param( [string]$startDate, @@ -85,7 +88,7 @@ function Get-ADSignInLogsGraph { $date = [datetime]::Now.ToString('yyyyMMddHHmmss') if ($OutputDir -eq "" ){ - $OutputDir = "Output\AzureAD\$date" + $OutputDir = "Output\AzureAD\$($date)_SignInLogs" if (!(test-path $OutputDir)) { write-logFile -Message "[INFO] Creating the following directory: $OutputDir" New-Item -ItemType Directory -Force -Name $OutputDir | Out-Null @@ -104,66 +107,71 @@ function Get-ADSignInLogsGraph { } - if ($UserIds){ + if ($UserIds){ Write-LogFile -Message "[INFO] UserID's eq $($UserIds)" } StartDateAz EndDate + if ($Interval -eq "") { + $Interval = 720 + Write-LogFile -Message "[INFO] Setting the Interval to the default value of 720 (Larger values may result in out of memory errors)" + } + $date = Get-Date -Format 'yyyyMMddHHmmss' $filePath = Join-Path -Path $outputDir -ChildPath "$($date)-SignInLogsGraph.json" [DateTime]$currentStart = $script:StartDate [DateTime]$currentEnd = $script:EndDate - [DateTime]$lastLog = $script:EndDate $currentDay = 0 - Write-LogFile -Message "[INFO] Extracting all available Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-dd")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-dd"))" -Color "Green" + Write-LogFile -Message "[INFO] Extracting all available Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" if($currentStart -gt $script:EndDate){ - Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-dd")) is greather than $($script:EndDate.ToString("yyyy-MM-dd")) - are you sure you put in the correct year? Exiting!" -Color "Red" + Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ")) is greather than $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) - are you sure you put in the correct year? Exiting!" -Color "Red" return } while ($currentStart -lt $script:EndDate) { - $currentEnd = $currentStart.AddMinutes($Interval) - if ($UserIds){ - Write-LogFile -Message "[INFO] Collecting Directory Sign-in logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-dd")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-dd"))." - try{ - [Array]$results = Get-MgBetaAuditLogSignIn -ExpandProperty * -All -Filter "UserPrincipalName eq '$($Userids)' and createdDateTime lt $($currentEnd.ToString("yyyy-MM-dd")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-dd"))" | Select-Object AppDisplayName,AppId,AppTokenProtectionStatus,AppliedConditionalAccessPolicies,ConditionsNotSatisfied,ConditionsSatisfied,AppliedConditionalAccessPoliciesDisplayName,EnforcedGrantControls,EnforcedSessionControls,AppliedConditionalAccessPoliciesId,AppliedConditionalAccessPoliciesResult,AppliedConditionalAccessPolicies2,AppliedEventListeners,AuthenticationAppDeviceDetails,AppVersion,ClientApp,DeviceId,OperatingSystem,AuthenticationAppPolicyEvaluationDetails,AdminConfiguration,AuthenticationEvaluation,AuthenticationAppPolicyEvaluationDetailsPolicyName,AuthenticationAppPolicyEvaluationDetailsStatus,AuthenticationContextClassReferences,@{N='AuthDetailsAuthenticationMethod';E={$_.AuthenticationDetails.AuthenticationMethod.ToString()}},@{N='AuthDetailsAuthenticationMethodDetail';E={$_.AuthenticationDetails.AuthenticationMethodDetail.ToString()}},@{N='AuthDetailsAuthenticationStepDateTime';E={$_.AuthenticationDetails.AuthenticationStepDateTime.ToString()}},@{N='AuthDetailsAuthenticationStepRequirement';E={$_.AuthenticationDetails.AuthenticationStepRequirement.ToString()}},@{N='AuthDetailsAuthenticationStepResultDetail';E={$_.AuthenticationDetails.AuthenticationStepResultDetail.ToString()}},@{N='AuthDetailsSucceeded';E={$_.AuthenticationDetails.Succeeded.ToString()}},AuthenticationMethodsUsed,AuthenticationProcessingDetails,AuthenticationProtocol,AuthenticationRequirement,AuthenticationRequirementPolicies,Detail,RequirementProvider,AutonomousSystemNumber,AzureResourceId,ClientAppUsed,ClientCredentialType,ConditionalAccessStatus,CorrelationId,@{N='CreatedDateTime';E={$_.CreatedDateTime.ToString()}},CrossTenantAccessType,DeviceDetail,Browser,DeviceDetailDeviceId,DisplayName,IsCompliant,IsManaged,DeviceDetailOperatingSystem,TrustType,FederatedCredentialId,FlaggedForReview,HomeTenantId,HomeTenantName,IPAddress,IPAddressFromResourceProvider,Id,IncomingTokenType,IsInteractive,IsTenantRestricted,Location,City,CountryOrRegion,State,ManagedServiceIdentity,AssociatedResourceId,FederatedTokenId,FederatedTokenIssuer,MsiType,MfaDetail,AuthDetail,AuthMethod,NetworkLocationDetails,OriginalRequestId,OriginalTransferMethod,PrivateLinkDetails,PolicyId,PolicyName,PolicyTenantId,PrivateLinkDetailsResourceId,ProcessingTimeInMilliseconds,ResourceDisplayName,ResourceId,ResourceServicePrincipalId,ResourceTenantId,RiskDetail,RiskEventTypesV2,RiskLevelAggregated,RiskLevelDuringSignIn,RiskState,ServicePrincipalCredentialKeyId,ServicePrincipalCredentialThumbprint,ServicePrincipalId,ServicePrincipalName,SessionLifetimePolicies,SignInEventTypes,SignInIdentifier,SignInIdentifierType,SignInTokenProtectionStatus,Status,StatusAdditionalDetails,TokenIssuerName,TokenIssuerType,UniqueTokenIdentifier,UserAgent,UserDisplayName,UserId,UserPrincipalName,UserType,AdditionalProperties - } - catch{ - Start-Sleep -Seconds 20 - [Array]$results = Get-MgBetaAuditLogSignIn -ExpandProperty * -All -Filter "UserPrincipalName eq '$($Userids)' and createdDateTime lt $($currentEnd.ToString("yyyy-MM-dd")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-dd"))" | Select-Object AppDisplayName,AppId,AppTokenProtectionStatus,AppliedConditionalAccessPolicies,ConditionsNotSatisfied,ConditionsSatisfied,AppliedConditionalAccessPoliciesDisplayName,EnforcedGrantControls,EnforcedSessionControls,AppliedConditionalAccessPoliciesId,AppliedConditionalAccessPoliciesResult,AppliedConditionalAccessPolicies2,AppliedEventListeners,AuthenticationAppDeviceDetails,AppVersion,ClientApp,DeviceId,OperatingSystem,AuthenticationAppPolicyEvaluationDetails,AdminConfiguration,AuthenticationEvaluation,AuthenticationAppPolicyEvaluationDetailsPolicyName,AuthenticationAppPolicyEvaluationDetailsStatus,AuthenticationContextClassReferences,@{N='AuthDetailsAuthenticationMethod';E={$_.AuthenticationDetails.AuthenticationMethod.ToString()}},@{N='AuthDetailsAuthenticationMethodDetail';E={$_.AuthenticationDetails.AuthenticationMethodDetail.ToString()}},@{N='AuthDetailsAuthenticationStepDateTime';E={$_.AuthenticationDetails.AuthenticationStepDateTime.ToString()}},@{N='AuthDetailsAuthenticationStepRequirement';E={$_.AuthenticationDetails.AuthenticationStepRequirement.ToString()}},@{N='AuthDetailsAuthenticationStepResultDetail';E={$_.AuthenticationDetails.AuthenticationStepResultDetail.ToString()}},@{N='AuthDetailsSucceeded';E={$_.AuthenticationDetails.Succeeded.ToString()}},AuthenticationMethodsUsed,AuthenticationProcessingDetails,AuthenticationProtocol,AuthenticationRequirement,AuthenticationRequirementPolicies,Detail,RequirementProvider,AutonomousSystemNumber,AzureResourceId,ClientAppUsed,ClientCredentialType,ConditionalAccessStatus,CorrelationId,@{N='CreatedDateTime';E={$_.CreatedDateTime.ToString()}},CrossTenantAccessType,DeviceDetail,Browser,DeviceDetailDeviceId,DisplayName,IsCompliant,IsManaged,DeviceDetailOperatingSystem,TrustType,FederatedCredentialId,FlaggedForReview,HomeTenantId,HomeTenantName,IPAddress,IPAddressFromResourceProvider,Id,IncomingTokenType,IsInteractive,IsTenantRestricted,Location,City,CountryOrRegion,State,ManagedServiceIdentity,AssociatedResourceId,FederatedTokenId,FederatedTokenIssuer,MsiType,MfaDetail,AuthDetail,AuthMethod,NetworkLocationDetails,OriginalRequestId,OriginalTransferMethod,PrivateLinkDetails,PolicyId,PolicyName,PolicyTenantId,PrivateLinkDetailsResourceId,ProcessingTimeInMilliseconds,ResourceDisplayName,ResourceId,ResourceServicePrincipalId,ResourceTenantId,RiskDetail,RiskEventTypesV2,RiskLevelAggregated,RiskLevelDuringSignIn,RiskState,ServicePrincipalCredentialKeyId,ServicePrincipalCredentialThumbprint,ServicePrincipalId,ServicePrincipalName,SessionLifetimePolicies,SignInEventTypes,SignInIdentifier,SignInIdentifierType,SignInTokenProtectionStatus,Status,StatusAdditionalDetails,TokenIssuerName,TokenIssuerType,UniqueTokenIdentifier,UserAgent,UserDisplayName,UserId,UserPrincipalName,UserType,AdditionalProperties - } - } - else { - try{ - [Array]$results = Get-MgBetaAuditLogSignIn -ExpandProperty * -All -Filter "createdDateTime lt $($currentEnd.ToString("yyyy-MM-dd")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-dd"))" | Select-Object AppDisplayName,AppId,AppTokenProtectionStatus,AppliedConditionalAccessPolicies,ConditionsNotSatisfied,ConditionsSatisfied,AppliedConditionalAccessPoliciesDisplayName,EnforcedGrantControls,EnforcedSessionControls,AppliedConditionalAccessPoliciesId,AppliedConditionalAccessPoliciesResult,AppliedConditionalAccessPolicies2,AppliedEventListeners,AuthenticationAppDeviceDetails,AppVersion,ClientApp,DeviceId,OperatingSystem,AuthenticationAppPolicyEvaluationDetails,AdminConfiguration,AuthenticationEvaluation,AuthenticationAppPolicyEvaluationDetailsPolicyName,AuthenticationAppPolicyEvaluationDetailsStatus,AuthenticationContextClassReferences,@{N='AuthDetailsAuthenticationMethod';E={$_.AuthenticationDetails.AuthenticationMethod.ToString()}},@{N='AuthDetailsAuthenticationMethodDetail';E={$_.AuthenticationDetails.AuthenticationMethodDetail.ToString()}},@{N='AuthDetailsAuthenticationStepDateTime';E={$_.AuthenticationDetails.AuthenticationStepDateTime.ToString()}},@{N='AuthDetailsAuthenticationStepRequirement';E={$_.AuthenticationDetails.AuthenticationStepRequirement.ToString()}},@{N='AuthDetailsAuthenticationStepResultDetail';E={$_.AuthenticationDetails.AuthenticationStepResultDetail.ToString()}},@{N='AuthDetailsSucceeded';E={$_.AuthenticationDetails.Succeeded.ToString()}},AuthenticationMethodsUsed,AuthenticationProcessingDetails,AuthenticationProtocol,AuthenticationRequirement,AuthenticationRequirementPolicies,Detail,RequirementProvider,AutonomousSystemNumber,AzureResourceId,ClientAppUsed,ClientCredentialType,ConditionalAccessStatus,CorrelationId,@{N='CreatedDateTime';E={$_.CreatedDateTime.ToString()}},CrossTenantAccessType,DeviceDetail,Browser,DeviceDetailDeviceId,DisplayName,IsCompliant,IsManaged,DeviceDetailOperatingSystem,TrustType,FederatedCredentialId,FlaggedForReview,HomeTenantId,HomeTenantName,IPAddress,IPAddressFromResourceProvider,Id,IncomingTokenType,IsInteractive,IsTenantRestricted,Location,City,CountryOrRegion,State,ManagedServiceIdentity,AssociatedResourceId,FederatedTokenId,FederatedTokenIssuer,MsiType,MfaDetail,AuthDetail,AuthMethod,NetworkLocationDetails,OriginalRequestId,OriginalTransferMethod,PrivateLinkDetails,PolicyId,PolicyName,PolicyTenantId,PrivateLinkDetailsResourceId,ProcessingTimeInMilliseconds,ResourceDisplayName,ResourceId,ResourceServicePrincipalId,ResourceTenantId,RiskDetail,RiskEventTypesV2,RiskLevelAggregated,RiskLevelDuringSignIn,RiskState,ServicePrincipalCredentialKeyId,ServicePrincipalCredentialThumbprint,ServicePrincipalId,ServicePrincipalName,SessionLifetimePolicies,SignInEventTypes,SignInIdentifier,SignInIdentifierType,SignInTokenProtectionStatus,Status,StatusAdditionalDetails,TokenIssuerName,TokenIssuerType,UniqueTokenIdentifier,UserAgent,UserDisplayName,UserId,UserPrincipalName,UserType,AdditionalProperties + $currentEnd = $currentStart.AddMinutes($Interval) + $retryCount = 0 + $maxRetries = 3 + $success = $false + + while (-not $success -and $retryCount -lt $maxRetries) { + try { + if ($UserIds) { + Write-LogFile -Message "[INFO] Collecting Directory Sign-in logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." + [Array]$results = Get-MgBetaAuditLogSignIn -ExpandProperty * -All -Filter "UserPrincipalName eq '$($Userids)' and createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object AppDisplayName,AppId,AppTokenProtectionStatus,AppliedConditionalAccessPolicies,ConditionsNotSatisfied,ConditionsSatisfied,AppliedConditionalAccessPoliciesDisplayName,EnforcedGrantControls,EnforcedSessionControls,AppliedConditionalAccessPoliciesId,AppliedConditionalAccessPoliciesResult,AppliedConditionalAccessPolicies2,AppliedEventListeners,AuthenticationAppDeviceDetails,AppVersion,ClientApp,DeviceId,OperatingSystem,AuthenticationAppPolicyEvaluationDetails,AdminConfiguration,AuthenticationEvaluation,AuthenticationAppPolicyEvaluationDetailsPolicyName,AuthenticationAppPolicyEvaluationDetailsStatus,AuthenticationContextClassReferences,@{N='AuthDetailsAuthenticationMethod';E={$_.AuthenticationDetails.AuthenticationMethod.ToString()}},@{N='AuthDetailsAuthenticationMethodDetail';E={$_.AuthenticationDetails.AuthenticationMethodDetail.ToString()}},@{N='AuthDetailsAuthenticationStepDateTime';E={$_.AuthenticationDetails.AuthenticationStepDateTime.ToString()}},@{N='AuthDetailsAuthenticationStepRequirement';E={$_.AuthenticationDetails.AuthenticationStepRequirement.ToString()}},@{N='AuthDetailsAuthenticationStepResultDetail';E={$_.AuthenticationDetails.AuthenticationStepResultDetail.ToString()}},@{N='AuthDetailsSucceeded';E={$_.AuthenticationDetails.Succeeded.ToString()}},AuthenticationMethodsUsed,AuthenticationProcessingDetails,AuthenticationProtocol,AuthenticationRequirement,AuthenticationRequirementPolicies,Detail,RequirementProvider,AutonomousSystemNumber,AzureResourceId,ClientAppUsed,ClientCredentialType,ConditionalAccessStatus,CorrelationId,@{N='CreatedDateTime';E={$_.CreatedDateTime.ToString()}},CrossTenantAccessType,DeviceDetail,Browser,DeviceDetailDeviceId,DisplayName,IsCompliant,IsManaged,DeviceDetailOperatingSystem,TrustType,FederatedCredentialId,FlaggedForReview,HomeTenantId,HomeTenantName,IPAddress,IPAddressFromResourceProvider,Id,IncomingTokenType,IsInteractive,IsTenantRestricted,Location,City,CountryOrRegion,State,ManagedServiceIdentity,AssociatedResourceId,FederatedTokenId,FederatedTokenIssuer,MsiType,MfaDetail,AuthDetail,AuthMethod,NetworkLocationDetails,OriginalRequestId,OriginalTransferMethod,PrivateLinkDetails,PolicyId,PolicyName,PolicyTenantId,PrivateLinkDetailsResourceId,ProcessingTimeInMilliseconds,ResourceDisplayName,ResourceId,ResourceServicePrincipalId,ResourceTenantId,RiskDetail,RiskEventTypesV2,RiskLevelAggregated,RiskLevelDuringSignIn,RiskState,ServicePrincipalCredentialKeyId,ServicePrincipalCredentialThumbprint,ServicePrincipalId,ServicePrincipalName,SessionLifetimePolicies,SignInEventTypes,SignInIdentifier,SignInIdentifierType,SignInTokenProtectionStatus,Status,StatusAdditionalDetails,TokenIssuerName,TokenIssuerType,UniqueTokenIdentifier,UserAgent,UserDisplayName,UserId,UserPrincipalName,UserType,AdditionalProperties + } else { + Write-LogFile -Message "[INFO] Collecting Directory Sign-in logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." + [Array]$results = Get-MgBetaAuditLogSignIn -ExpandProperty * -All -Filter "createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object AppDisplayName,AppId,AppTokenProtectionStatus,AppliedConditionalAccessPolicies,ConditionsNotSatisfied,ConditionsSatisfied,AppliedConditionalAccessPoliciesDisplayName,EnforcedGrantControls,EnforcedSessionControls,AppliedConditionalAccessPoliciesId,AppliedConditionalAccessPoliciesResult,AppliedConditionalAccessPolicies2,AppliedEventListeners,AuthenticationAppDeviceDetails,AppVersion,ClientApp,DeviceId,OperatingSystem,AuthenticationAppPolicyEvaluationDetails,AdminConfiguration,AuthenticationEvaluation,AuthenticationAppPolicyEvaluationDetailsPolicyName,AuthenticationAppPolicyEvaluationDetailsStatus,AuthenticationContextClassReferences,@{N='AuthDetailsAuthenticationMethod';E={$_.AuthenticationDetails.AuthenticationMethod.ToString()}},@{N='AuthDetailsAuthenticationMethodDetail';E={$_.AuthenticationDetails.AuthenticationMethodDetail.ToString()}},@{N='AuthDetailsAuthenticationStepDateTime';E={$_.AuthenticationDetails.AuthenticationStepDateTime.ToString()}},@{N='AuthDetailsAuthenticationStepRequirement';E={$_.AuthenticationDetails.AuthenticationStepRequirement.ToString()}},@{N='AuthDetailsAuthenticationStepResultDetail';E={$_.AuthenticationDetails.AuthenticationStepResultDetail.ToString()}},@{N='AuthDetailsSucceeded';E={$_.AuthenticationDetails.Succeeded.ToString()}},AuthenticationMethodsUsed,AuthenticationProcessingDetails,AuthenticationProtocol,AuthenticationRequirement,AuthenticationRequirementPolicies,Detail,RequirementProvider,AutonomousSystemNumber,AzureResourceId,ClientAppUsed,ClientCredentialType,ConditionalAccessStatus,CorrelationId,@{N='CreatedDateTime';E={$_.CreatedDateTime.ToString()}},CrossTenantAccessType,DeviceDetail,Browser,DeviceDetailDeviceId,DisplayName,IsCompliant,IsManaged,DeviceDetailOperatingSystem,TrustType,FederatedCredentialId,FlaggedForReview,HomeTenantId,HomeTenantName,IPAddress,IPAddressFromResourceProvider,Id,IncomingTokenType,IsInteractive,IsTenantRestricted,Location,City,CountryOrRegion,State,ManagedServiceIdentity,AssociatedResourceId,FederatedTokenId,FederatedTokenIssuer,MsiType,MfaDetail,AuthDetail,AuthMethod,NetworkLocationDetails,OriginalRequestId,OriginalTransferMethod,PrivateLinkDetails,PolicyId,PolicyName,PolicyTenantId,PrivateLinkDetailsResourceId,ProcessingTimeInMilliseconds,ResourceDisplayName,ResourceId,ResourceServicePrincipalId,ResourceTenantId,RiskDetail,RiskEventTypesV2,RiskLevelAggregated,RiskLevelDuringSignIn,RiskState,ServicePrincipalCredentialKeyId,ServicePrincipalCredentialThumbprint,ServicePrincipalId,ServicePrincipalName,SessionLifetimePolicies,SignInEventTypes,SignInIdentifier,SignInIdentifierType,SignInTokenProtectionStatus,Status,StatusAdditionalDetails,TokenIssuerName,TokenIssuerType,UniqueTokenIdentifier,UserAgent,UserDisplayName,UserId,UserPrincipalName,UserType,AdditionalProperties + } + $success = $true } - catch{ - Start-Sleep -Seconds 20 - [Array]$results = Get-MgBetaAuditLogSignIn -ExpandProperty * -All -Filter "createdDateTime lt $($currentEnd.ToString("yyyy-MM-dd")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-dd"))" | Select-Object AppDisplayName,AppId,AppTokenProtectionStatus,AppliedConditionalAccessPolicies,ConditionsNotSatisfied,ConditionsSatisfied,AppliedConditionalAccessPoliciesDisplayName,EnforcedGrantControls,EnforcedSessionControls,AppliedConditionalAccessPoliciesId,AppliedConditionalAccessPoliciesResult,AppliedConditionalAccessPolicies2,AppliedEventListeners,AuthenticationAppDeviceDetails,AppVersion,ClientApp,DeviceId,OperatingSystem,AuthenticationAppPolicyEvaluationDetails,AdminConfiguration,AuthenticationEvaluation,AuthenticationAppPolicyEvaluationDetailsPolicyName,AuthenticationAppPolicyEvaluationDetailsStatus,AuthenticationContextClassReferences,@{N='AuthDetailsAuthenticationMethod';E={$_.AuthenticationDetails.AuthenticationMethod.ToString()}},@{N='AuthDetailsAuthenticationMethodDetail';E={$_.AuthenticationDetails.AuthenticationMethodDetail.ToString()}},@{N='AuthDetailsAuthenticationStepDateTime';E={$_.AuthenticationDetails.AuthenticationStepDateTime.ToString()}},@{N='AuthDetailsAuthenticationStepRequirement';E={$_.AuthenticationDetails.AuthenticationStepRequirement.ToString()}},@{N='AuthDetailsAuthenticationStepResultDetail';E={$_.AuthenticationDetails.AuthenticationStepResultDetail.ToString()}},@{N='AuthDetailsSucceeded';E={$_.AuthenticationDetails.Succeeded.ToString()}},AuthenticationMethodsUsed,AuthenticationProcessingDetails,AuthenticationProtocol,AuthenticationRequirement,AuthenticationRequirementPolicies,Detail,RequirementProvider,AutonomousSystemNumber,AzureResourceId,ClientAppUsed,ClientCredentialType,ConditionalAccessStatus,CorrelationId,@{N='CreatedDateTime';E={$_.CreatedDateTime.ToString()}},CrossTenantAccessType,DeviceDetail,Browser,DeviceDetailDeviceId,DisplayName,IsCompliant,IsManaged,DeviceDetailOperatingSystem,TrustType,FederatedCredentialId,FlaggedForReview,HomeTenantId,HomeTenantName,IPAddress,IPAddressFromResourceProvider,Id,IncomingTokenType,IsInteractive,IsTenantRestricted,Location,City,CountryOrRegion,State,ManagedServiceIdentity,AssociatedResourceId,FederatedTokenId,FederatedTokenIssuer,MsiType,MfaDetail,AuthDetail,AuthMethod,NetworkLocationDetails,OriginalRequestId,OriginalTransferMethod,PrivateLinkDetails,PolicyId,PolicyName,PolicyTenantId,PrivateLinkDetailsResourceId,ProcessingTimeInMilliseconds,ResourceDisplayName,ResourceId,ResourceServicePrincipalId,ResourceTenantId,RiskDetail,RiskEventTypesV2,RiskLevelAggregated,RiskLevelDuringSignIn,RiskState,ServicePrincipalCredentialKeyId,ServicePrincipalCredentialThumbprint,ServicePrincipalId,ServicePrincipalName,SessionLifetimePolicies,SignInEventTypes,SignInIdentifier,SignInIdentifierType,SignInTokenProtectionStatus,Status,StatusAdditionalDetails,TokenIssuerName,TokenIssuerType,UniqueTokenIdentifier,UserAgent,UserDisplayName,UserId,UserPrincipalName,UserType,AdditionalProperties + + catch { + $retryCount++ + if ($retryCount -lt $maxRetries) { + Start-Sleep -Seconds 10 + Write-LogFile -Message "[WARNING] Failed to acquire logs. Retrying... Attempt $retryCount of $maxRetries" -Color "Yellow" + } else { + Write-LogFile -Message "[ERROR] Failed to acquire logs after $maxRetries attempts. Moving on." -Color "Red" + } } } + if ($null -eq $results -or $results.Count -eq 0) { - Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-dd")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-dd")). Moving On!" + Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Moving On!" -Color "Yellow" } else { $currentCount = $results.Count - if ($currentDay -ne 0){ - $currentTotal = $currentCount + $results.Count - } - else { - $currentTotal = $currentCount - } - - Write-LogFile -Message "[INFO] Found $currentCount Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-dd")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-dd"))" -Color "Green" + Write-LogFile -Message "[INFO] Found $currentCount Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" $filePath = "$OutputDir\SignInLogsGraph-$($CurrentStart.ToString("yyyyMMdd"))-$($CurrentEnd.ToString("yyyyMMdd")).json" $results | ConvertTo-Json -Depth 100 | Out-File -Append $filePath -Encoding $Encoding - Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range." + Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records for the current time range." } [Array]$results = @() $CurrentStart = $CurrentEnd @@ -200,16 +208,16 @@ function Get-ADAuditLogsGraph { .DESCRIPTION The Get-ADAuditLogsGraph GraphAPI cmdlet to collect the contents of the Azure Active Directory Audit logs. - The output will be written to: "Output\AzureAD\AuditlogsGraph.json .PARAMETER startDate The startDate parameter specifies the date from which all logs need to be collected. + .PARAMETER endDate The Before parameter specifies the date endDate which all logs need to be collected. .PARAMETER OutputDir outputDir is the parameter specifying the output directory. - Default: Output\AzureAD + Default: The output will be written to: "Output\AzureAD\{date_AuditLogs}\Auditlogs.json .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. @@ -218,9 +226,17 @@ function Get-ADAuditLogsGraph { Encoding is the parameter specifying the encoding of the JSON output file. Default: UTF8 + .PARAMETER MergeOutput + MergeOutput is the parameter specifying if you wish to merge outputs to a single file + Default: No + .PARAMETER Application Application is the parameter specifying App-only access (access without a user) for authentication and authorization. Default: Delegated access (access on behalf a user) + + .PARAMETER Interval + Interval is the parameter specifying the interval in which the logs are being gathered. + Default: 720 minutes .EXAMPLE Get-ADAuditLogsGraph @@ -243,9 +259,11 @@ function Get-ADAuditLogsGraph { [string]$startDate, [string]$endDate, [string]$OutputDir, + [switch]$MergeOutput, [string]$Encoding, [string]$UserIds, - [switch]$Application + [switch]$Application, + [string]$Interval ) try { @@ -263,11 +281,19 @@ function Get-ADAuditLogsGraph { if (!($Application.IsPresent)) { Connect-MgGraph -Scopes AuditLog.Read.All, Directory.Read.All -NoWelcome } + + if ($Interval -eq "") { + $Interval = 720 + Write-LogFile -Message "[INFO] Setting the Interval to the default value of 720 (Larger values may result in out of memory errors)" + } + + StartDateAz + EndDate Write-logFile -Message "[INFO] Running Get-ADAuditLogsGraph" -Color "Green" if ($OutputDir -eq "" ){ - $OutputDir = "Output\AzureAD" + $OutputDir = "Output\AzureAD\$($date)_Auditlogs" if (!(test-path $OutputDir)) { New-Item -ItemType Directory -Force -Name $OutputDir | Out-Null write-logFile -Message "[INFO] Creating the following directory: $OutputDir" @@ -286,31 +312,81 @@ function Get-ADAuditLogsGraph { } $date = [datetime]::Now.ToString('yyyyMMddHHmmss') - $filePath = "$OutputDir\$($date)-AuditlogsGraph.json" + [DateTime]$currentStart = $script:StartDate + [DateTime]$currentEnd = $script:EndDate + $currentDay = 0 - if ($Before -and $After) { - write-logFile -Message "[WARNING] Please provide only one of either a start date or end date" -Color "Red" + Write-LogFile -Message "[INFO] Extracting all available Directory Audit Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" + if($currentStart -gt $script:EndDate){ + Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ")) is greather than $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) - are you sure you put in the correct year? Exiting!" -Color "Red" return } - $filter = "" - if ($endDate) { - $filter = "activityDateTime lt $endDate" - } - if ($startDate) { - $filter = "activityDateTime gt $startDate" + while ($currentStart -lt $script:EndDate) { + $currentEnd = $currentStart.AddMinutes($Interval) + $retryCount = 0 + $maxRetries = 3 + $success = $false + + while (-not $success -and $retryCount -lt $maxRetries) { + try { + if ($UserIds) { + Write-LogFile -Message "[INFO] Collecting Directory Audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." + [Array]$results = Get-MgAuditLogDirectoryAudit -ExpandProperty * -All -Filter "initiatedBy/user/userPrincipalName eq '$UserIds' and activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" + } else { + Write-LogFile -Message "[INFO] Collecting Directory Audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." + [Array]$results = Get-MgAuditLogDirectoryAudit -ExpandProperty * -All -Filter "activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" + } + $success = $true + } + catch { + $retryCount++ + if ($retryCount -lt $maxRetries) { + Start-Sleep -Seconds 10 + Write-LogFile -Message "[WARNING] Failed to acquire logs. Retrying... Attempt $retryCount of $maxRetries" -Color "Yellow" + } else { + Write-LogFile -Message "[ERROR] Failed to acquire logs after $maxRetries attempts. Moving on." -Color "Red" + } + } + } + + if ($null -eq $results -or $results.Count -eq 0) { + Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Moving On!" -Color "Yellow" + } + else { + $currentCount = $results.Count + Write-LogFile -Message "[INFO] Found $currentCount Directory Audit Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" + + $filePath = "$OutputDir\AuditLogs-$($CurrentStart.ToString("yyyyMMddHHmmss"))-$($CurrentEnd.ToString("yyyyMMddHHmmss")).json" + $results | ConvertTo-Json -Depth 100 | Out-File -Append $filePath -Encoding $Encoding + + Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records for the current time range." + } + [Array]$results = @() + $CurrentStart = $CurrentEnd + $currentDay++ } - - if ($UserIds) { - if ($filter) { - $filter = " and $filter" + + if ($MergeOutput.IsPresent) + { + Write-LogFile -Message "[INFO] Merging output files into one file" + $outputDirMerged = "$OutputDir\Merged\" + If (!(test-path $outputDirMerged)) { + Write-LogFile -Message "[INFO] Creating the following directory: $outputDirMerged" + New-Item -ItemType Directory -Force -Path $outputDirMerged | Out-Null } - Get-MgAuditLogDirectoryAudit -ExpandProperty * -All -Filter $filter | ConvertTo-Json -Depth 100 | out-File -FilePath $filePath -Encoding $Encoding - } - else { - Get-MgAuditLogDirectoryAudit -ExpandProperty * -All -Filter $filter | ConvertTo-Json -Depth 100 | out-File -FilePath $filePath -Encoding $Encoding - } + + $allJsonObjects = @() + + Get-ChildItem $OutputDir -Filter *.json | ForEach-Object { + $content = Get-Content -Path $_.FullName -Raw + $jsonObjects = $content | ConvertFrom-Json + $allJsonObjects += $jsonObjects + } + + $allJsonObjects | ConvertTo-Json -Depth 100 | Set-Content "$outputDirMerged\AuditLogs-Combined.json" + } - write-logFile -Message "[INFO] Audit logs written to $filePath" -Color "Green" + Write-LogFile -Message "[INFO] Acquisition complete, check the $($OutputDir) directory for your files.." -Color "Green" } diff --git a/Scripts/Get-AzureADLogs.ps1 b/Scripts/Get-AzureADLogs.ps1 index 06841ba..d0ade66 100644 --- a/Scripts/Get-AzureADLogs.ps1 +++ b/Scripts/Get-AzureADLogs.ps1 @@ -1,368 +1,369 @@ # This contains functions for getting Azure AD logging function Get-ADSignInLogs { - <# - .SYNOPSIS - Get sign-in logs. - - .DESCRIPTION - The Get-ADSignInLogs cmdlet collects the contents of the Azure Active Directory sign-in logs. - The output will be written to: Output\AzureAD\SignInLogs.json - - .PARAMETER startDate - The startDate parameter specifies the date from which all logs need to be collected. - - .PARAMETER endDate - The Before parameter specifies the date endDate which all logs need to be collected. - - .PARAMETER OutputDir - OutputDir is the parameter specifying the output directory. - Default: Output\AzureAD - - .PARAMETER Encoding - Encoding is the parameter specifying the encoding of the JSON output file. - Default: UTF8 - - .PARAMETER MergeOutput - MergeOutput is the parameter specifying if you wish to merge outputs to a single file - Default: No - - .PARAMETER UserIds - UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. - - .EXAMPLE - Get-ADSignInLogs - Get all sign-in logs. - - .EXAMPLE - Get-ADAuditLogs -UserIds Test@invictus-ir.com - Get sign-in logs for the user Test@invictus-ir.com. - - .EXAMPLE - Get-ADSignInLogs -endDate 2023-04-12 - Get sign-in logs before 2023-04-12. - - .EXAMPLE - Get-ADSignInLogs -startDate 2023-04-12 - Get sign-in logs after 2023-04-12. - #> - [CmdletBinding()] - param( - [string]$startDate, - [string]$endDate, - [string]$outputDir, - [string]$UserIds, - [switch]$MergeOutput, - [string]$Encoding, - [string]$Interval - ) - - try { - import-module AzureADPreview -force -ErrorAction stop - $areYouConnected = Get-AzureADAuditSignInLogs -ErrorAction stop - } - catch { - Write-logFile -Message "[WARNING] You must call Connect-Azure or install AzureADPreview before running this script" -Color "Red" - break - } - - Write-logFile -Message "[INFO] Running Get-AADSignInLogs" -Color "Green" - - StartDateAz - EndDate - - if ($Interval -eq "") { - $Interval = 1440 - Write-LogFile -Message "[INFO] Setting the Interval to the default value of 1440" - } - - if ($Encoding -eq "" ){ - $Encoding = "UTF8" - } - - $date = [datetime]::Now.ToString('yyyyMMddHHmmss') - if ($OutputDir -eq "" ){ - $OutputDir = "Output\AzureAD\$date" - if (!(test-path $OutputDir)) { - write-logFile -Message "[INFO] Creating the following directory: $OutputDir" - New-Item -ItemType Directory -Force -Name $OutputDir | Out-Null - } - } - - if ($UserIds){ - Write-LogFile -Message "[INFO] UserID's eq $($UserIds)" - } - - - $filePath = "$OutputDir\SignInLogs.json" - - [DateTime]$currentStart = $script:StartDate - [DateTime]$currentEnd = $script:EndDate - [DateTime]$lastLog = $script:EndDate - $currentDay = 0 +<# + .SYNOPSIS + Get sign-in logs. + + .DESCRIPTION + The Get-ADSignInLogs cmdlet collects the contents of the Azure Active Directory sign-in logs. + + .PARAMETER startDate + The startDate parameter specifies the date from which all logs need to be collected. + + .PARAMETER endDate + The Before parameter specifies the date endDate which all logs need to be collected. + + .PARAMETER OutputDir + OutputDir is the parameter specifying the output directory. + Default: The output will be written to: Output\AzureAD\{date_SignInLogs}\SignInLogs.json + + .PARAMETER Encoding + Encoding is the parameter specifying the encoding of the JSON output file. + Default: UTF8 + + .PARAMETER MergeOutput + MergeOutput is the parameter specifying if you wish to merge outputs to a single file + Default: No + + .PARAMETER UserIds + UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. + + .PARAMETER Interval + Interval is the parameter specifying the interval in which the logs are being gathered. + Default: 720 minutes - Write-LogFile -Message "[INFO] Extracting all available Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" - if($currentStart -gt $script:EndDate){ - Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ")) is greather than $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) - are you sure you put in the correct year? Exiting!" -Color "Red" - return + .EXAMPLE + Get-ADSignInLogs + Get all sign-in logs. + + .EXAMPLE + Get-ADAuditLogs -UserIds Test@invictus-ir.com + Get sign-in logs for the user Test@invictus-ir.com. + + .EXAMPLE + Get-ADSignInLogs -endDate 2023-04-12 + Get sign-in logs before 2023-04-12. + + .EXAMPLE + Get-ADSignInLogs -startDate 2023-04-12 + Get sign-in logs after 2023-04-12. +#> + [CmdletBinding()] + param( + [string]$startDate, + [string]$endDate, + [string]$outputDir, + [string]$UserIds, + [switch]$MergeOutput, + [string]$Encoding, + [string]$Interval + ) + + try { + import-module AzureADPreview -force -ErrorAction stop + $areYouConnected = Get-AzureADAuditSignInLogs -ErrorAction stop + } + catch { + Write-logFile -Message "[WARNING] You must call Connect-Azure or install AzureADPreview before running this script" -Color "Red" + break + } + + Write-logFile -Message "[INFO] Running Get-AADSignInLogs" -Color "Green" + + StartDateAz + EndDate + + if ($Interval -eq "") { + $Interval = 1440 + Write-LogFile -Message "[INFO] Setting the Interval to the default value of 1440" + } + + if ($Encoding -eq "" ){ + $Encoding = "UTF8" + } + + $date = [datetime]::Now.ToString('yyyyMMddHHmmss') + if ($OutputDir -eq "" ){ + $OutputDir = "Output\AzureAD\$($date)_SignInLogs" + if (!(test-path $OutputDir)) { + write-logFile -Message "[INFO] Creating the following directory: $OutputDir" + New-Item -ItemType Directory -Force -Name $OutputDir | Out-Null } - - while ($currentStart -lt $script:EndDate) { - $currentEnd = $currentStart.AddMinutes($Interval) - if ($UserIds){ - Write-LogFile -Message "[INFO] Collecting Directory Sign-in logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." - try{ - [Array]$results = Get-AzureADAuditSignInLogs -All $true -Filter "UserPrincipalName eq '$($Userids)' and createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" - } - catch{ - Start-Sleep -Seconds 20 - [Array]$results = Get-AzureADAuditSignInLogs -All $true -Filter "UserPrincipalName eq '$($Userids)' and createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" + } + + if ($UserIds){ + Write-LogFile -Message "[INFO] UserID's eq $($UserIds)" + } + + $filePath = "$OutputDir\SignInLogs.json" + + [DateTime]$currentStart = $script:StartDate + [DateTime]$currentEnd = $script:EndDate + [DateTime]$lastLog = $script:EndDate + $currentDay = 0 + + Write-LogFile -Message "[INFO] Extracting all available Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" + if($currentStart -gt $script:EndDate){ + Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ")) is greather than $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) - are you sure you put in the correct year? Exiting!" -Color "Red" + return + } + + while ($currentStart -lt $script:EndDate) { + $currentEnd = $currentStart.AddMinutes($Interval) + $retryCount = 0 + $maxRetries = 3 + $success = $false + + while (-not $success -and $retryCount -lt $maxRetries) { + try { + if ($UserIds) { + Write-LogFile -Message "[INFO] Collecting Directory Sign-in logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." + [Array]$results = Get-AzureADAuditSignInLogs -All $true -Filter "UserPrincipalName eq '$($UserIds)' and createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" + } else { + Write-LogFile -Message "[INFO] Collecting Directory Sign-in logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." + [Array]$results = Get-AzureADAuditSignInLogs -All $true -Filter "createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" } + $success = $true } - else { - try{ - [Array]$results = Get-AzureADAuditSignInLogs -All $true -Filter "createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" - } - catch{ - Start-Sleep -Seconds 20 - [Array]$results = Get-AzureADAuditSignInLogs -All $true -Filter "createdDateTime lt $($currentEnd.ToString("yyyy-MM-ddTHH:mm:ssZ")) and createdDateTime gt $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ"))" + + catch { + $retryCount++ + if ($retryCount -lt $maxRetries) { + Start-Sleep -Seconds 10 + Write-LogFile -Message "[WARNING] Failed to acquire logs. Retrying... Attempt $retryCount of $maxRetries" -Color "Yellow" + } else { + Write-LogFile -Message "[ERROR] Failed to acquire logs after $maxRetries attempts. Moving on." -Color "Red" } } - if ($null -eq $results -or $results.Count -eq 0) { - Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Moving On!" - } - else { - $currentCount = $results.Count - if ($currentDay -ne 0){ - $currentTotal = $currentCount + $results.Count - } - else { - $currentTotal = $currentCount - } + } - Write-LogFile -Message "[INFO] Found $currentCount Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" - - $filePath = "$OutputDir\SignInLogs-$($CurrentStart.ToString("yyyyMMdd"))-$($CurrentEnd.ToString("yyyyMMdd")).json" - $results | ConvertTo-Json -Depth 100 | Out-File -Append $filePath -Encoding $Encoding - - Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range." - } - [Array]$results = @() - $CurrentStart = $CurrentEnd - $currentDay++ + if ($null -eq $results -or $results.Count -eq 0) { + Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Moving On!" -Color "Yellow" } - - if ($MergeOutput.IsPresent) - { - Write-LogFile -Message "[INFO] Merging output files into one file" - $outputDirMerged = "$OutputDir\Merged\" - If (!(test-path $outputDirMerged)) { - Write-LogFile -Message "[INFO] Creating the following directory: $outputDirMerged" - New-Item -ItemType Directory -Force -Path $outputDirMerged | Out-Null - } - - $allJsonObjects = @() - - Get-ChildItem $OutputDir -Filter *.json | ForEach-Object { - $content = Get-Content -Path $_.FullName -Raw - $jsonObjects = $content | ConvertFrom-Json - $allJsonObjects += $jsonObjects - } - - $allJsonObjects | ConvertTo-Json -Depth 100 | Set-Content "$outputDirMerged\SignInLogs-Combined.json" + else { + $currentCount = $results.Count + Write-LogFile -Message "[INFO] Found $currentCount Directory Sign-in Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" + + $filePath = "$OutputDir\SignInLogs-$($CurrentStart.ToString("yyyyMMdd"))-$($CurrentEnd.ToString("yyyyMMdd")).json" + $results | ConvertTo-Json -Depth 100 | Out-File -Append $filePath -Encoding $Encoding + + Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records for the current time range." } - - Write-LogFile -Message "[INFO] Acquisition complete, check the $($OutputDir) directory for your files.." -Color "Green" + [Array]$results = @() + $CurrentStart = $CurrentEnd + $currentDay++ } - function Get-ADAuditLogs { - <# - .SYNOPSIS - Get directory audit logs. - - .DESCRIPTION - The Get-ADAuditLogs cmdlet collects the contents of the Azure Active Directory Audit logs. - The output will be written to: "Output\AzureAD\Auditlogs.json - - .PARAMETER startDate - The startDate parameter specifies the date from which all logs need to be collected. - - .PARAMETER endDate - The endDate parameter specifies the date before which all logs need to be collected. - - .PARAMETER OutputDir - outputDir is the parameter specifying the output directory. - Default: Output\AzureAD - - .PARAMETER Encoding - Encoding is the parameter specifying the encoding of the JSON output file. - Default: UTF8 - - .PARAMETER UserIds - UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. - - .EXAMPLE - Get-ADAuditLogs - Get directory audit logs. - - .EXAMPLE - Get-ADAuditLogs -UserIds Test@invictus-ir.com - Get directory audit logs for the user Test@invictus-ir.com. - - .EXAMPLE - Get-ADAuditLogs -endDate 2024-04-12T01:00:00Z - Get directory audit logs before 2023-04-12 at 01:00. - - .EXAMPLE - Get-ADAuditLogs -startDate 2024-04-12T01:00:00Z - Get directory audit logs after 2023-04-12 at 01:00. - - .EXAMPLE - Get-ADAuditLogs -startDate 2024-04-12T01:00:00Z -endDate 2024-04-12T02:00:00Z - Get directory audit logs after 2023-04-12 between 01:00 and 02:00 - #> - [CmdletBinding()] - param( - [string]$startDate, - [string]$endDate, - [string]$outputDir, - [string]$UserIds, - [switch]$MergeOutput, - [string]$Encoding, - [string]$Interval - ) - - try { - $areYouConnected = Get-AzureADAuditDirectoryLogs -ErrorAction stop + if ($MergeOutput.IsPresent) + { + Write-LogFile -Message "[INFO] Merging output files into one file" + $outputDirMerged = "$OutputDir\Merged\" + If (!(test-path $outputDirMerged)) { + Write-LogFile -Message "[INFO] Creating the following directory: $outputDirMerged" + New-Item -ItemType Directory -Force -Path $outputDirMerged | Out-Null } - catch { - Write-logFile -Message "[WARNING] You must call Connect-Azure or install AzureADPreview before running this script" -Color "Red" - break + + $allJsonObjects = @() + + Get-ChildItem $OutputDir -Filter *.json | ForEach-Object { + $content = Get-Content -Path $_.FullName -Raw + $jsonObjects = $content | ConvertFrom-Json + $allJsonObjects += $jsonObjects } - if ($Encoding -eq "" ){ - $Encoding = "UTF8" - } + $allJsonObjects | ConvertTo-Json -Depth 100 | Set-Content "$outputDirMerged\SignInLogs-Combined.json" + } - Write-logFile -Message "[INFO] Running Get-ADAuditLogs" -Color "Green" - - StartDateAz - EndDate + Write-LogFile -Message "[INFO] Acquisition complete, check the $($OutputDir) directory for your files.." -Color "Green" +} - if ($Interval -eq "") { - $Interval = 720 - Write-LogFile -Message "[INFO] Setting the Interval to the default value of 720 (Larger values may result in out of memory errors)" - } +function Get-ADAuditLogs { +<# + .SYNOPSIS + Get directory audit logs. + + .DESCRIPTION + The Get-ADAuditLogs cmdlet collects the contents of the Azure Active Directory Audit logs. + + .PARAMETER startDate + The startDate parameter specifies the date from which all logs need to be collected. + + .PARAMETER endDate + The endDate parameter specifies the date before which all logs need to be collected. + + .PARAMETER OutputDir + outputDir is the parameter specifying the output directory. + Default: The output will be written to: "Output\AzureAD\{date_AuditLogs}\Auditlogs.json + + .PARAMETER Encoding + Encoding is the parameter specifying the encoding of the JSON output file. + Default: UTF8 + + .PARAMETER MergeOutput + MergeOutput is the parameter specifying if you wish to merge outputs to a single file + Default: No + + .PARAMETER UserIds + UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. + + .PARAMETER Interval + Interval is the parameter specifying the interval in which the logs are being gathered. + Default: 720 minutes + .EXAMPLE + Get-ADAuditLogs + Get directory audit logs. + + .EXAMPLE + Get-ADAuditLogs -UserIds Test@invictus-ir.com + Get directory audit logs for the user Test@invictus-ir.com. + + .EXAMPLE + Get-ADAuditLogs -endDate 2024-04-12T01:00:00Z + Get directory audit logs before 2023-04-12 at 01:00. + + .EXAMPLE + Get-ADAuditLogs -startDate 2024-04-12T01:00:00Z + Get directory audit logs after 2023-04-12 at 01:00. + + .EXAMPLE + Get-ADAuditLogs -startDate 2024-04-12T01:00:00Z -endDate 2024-04-12T02:00:00Z + Get directory audit logs after 2023-04-12 between 01:00 and 02:00 +#> + [CmdletBinding()] + param( + [string]$startDate, + [string]$endDate, + [string]$outputDir, + [string]$UserIds, + [switch]$MergeOutput, + [string]$Encoding, + [string]$Interval + ) + + try { + $areYouConnected = Get-AzureADAuditDirectoryLogs -ErrorAction stop + } + catch { + Write-logFile -Message "[WARNING] You must call Connect-Azure or install AzureADPreview before running this script" -Color "Red" + break + } + + if ($Encoding -eq "" ){ + $Encoding = "UTF8" + } + + Write-logFile -Message "[INFO] Running Get-ADAuditLogs" -Color "Green" - $date = [datetime]::Now.ToString('yyyyMMddHHmmss') - if ($OutputDir -eq "" ){ - $OutputDir = "Output\AzureAD\$date" - if (!(test-path $OutputDir)) { - write-logFile -Message "[INFO] Creating the following directory: $OutputDir" - New-Item -ItemType Directory -Force -Name $OutputDir | Out-Null - } - } - else { - if (Test-Path -Path $OutputDir) { - write-LogFile -Message "[INFO] Custom directory set to: $OutputDir" - } - - else { - write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop - write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" - } + StartDateAz + EndDate + + if ($Interval -eq "") { + $Interval = 720 + Write-LogFile -Message "[INFO] Setting the Interval to the default value of 720 (Larger values may result in out of memory errors)" + } + + + $date = [datetime]::Now.ToString('yyyyMMddHHmmss') + if ($OutputDir -eq "" ){ + $OutputDir = "Output\AzureAD\$($date)_AuditLogs" + if (!(test-path $OutputDir)) { + write-logFile -Message "[INFO] Creating the following directory: $OutputDir" + New-Item -ItemType Directory -Force -Name $OutputDir | Out-Null } - - - if ($UserIds){ - Write-LogFile -Message "[INFO] UserID's eq $($UserIds)" + } + else { + if (Test-Path -Path $OutputDir) { + write-LogFile -Message "[INFO] Custom directory set to: $OutputDir" } - - $filePath = "$OutputDir\$($date)-Auditlogs.json" - - [DateTime]$currentStart = $script:StartDate - [DateTime]$currentEnd = $script:EndDate - [DateTime]$lastLog = $script:EndDate - $currentDay = 0 - - Write-LogFile -Message "[INFO] Extracting all available Directory Audit Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" - if($currentStart -gt $script:EndDate){ - Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ")) is greather than $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) - are you sure you put in the correct year? Exiting!" -Color "Red" - return + else { + write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop + write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" } - - while ($currentStart -lt $script:EndDate) { - $currentEnd = $currentStart.AddMinutes($Interval) - Start-Sleep -Seconds 5 - if ($UserIds){ - Write-LogFile -Message "[INFO] Collecting Directory Audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." - try{ - [Array]$results = Get-AzureADAuditDirectoryLogs -All $true -Filter "initiatedBy/user/userPrincipalName eq '$Userids' and activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object Id,Category,CorrelationId,Result,ResultReason,ActivityDisplayName,@{N='ActivityDateTime';E={$_.ActivityDateTime.ToString()}},LoggedByService,OperationType,InitiatedBy,TargetResources,AdditionalDetails - } - catch{ - Write-LogFile -Message "[WARNING] Failed to acquire logs $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Retrying after sleep " -Color "Yellow" - Start-Sleep -Seconds 30 + } + + if ($UserIds){ + Write-LogFile -Message "[INFO] UserID's eq $($UserIds)" + } + + $filePath = "$OutputDir\$($date)-Auditlogs.json" + + [DateTime]$currentStart = $script:StartDate + [DateTime]$currentEnd = $script:EndDate + $currentDay = 0 + + Write-LogFile -Message "[INFO] Extracting all available Directory Audit Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" + if($currentStart -gt $script:EndDate){ + Write-LogFile -Message "[ERROR] $($currentStart.ToString("yyyy-MM-ddTHH:mm:ssZ")) is greather than $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) - are you sure you put in the correct year? Exiting!" -Color "Red" + return + } + + while ($currentStart -lt $script:EndDate) { + $currentEnd = $currentStart.AddMinutes($Interval) + $retryCount = 0 + $maxRetries = 3 + $success = $false + + while (-not $success -and $retryCount -lt $maxRetries) { + try { + if ($UserIds) { Write-LogFile -Message "[INFO] Collecting Directory Audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." - [Array]$results = Get-AzureADAuditDirectoryLogs -All $true -Filter "initiatedBy/user/userPrincipalName eq '$Userids' and activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object Id,Category,CorrelationId,Result,ResultReason,ActivityDisplayName,@{N='ActivityDateTime';E={$_.ActivityDateTime.ToString()}},LoggedByService,OperationType,InitiatedBy,TargetResources,AdditionalDetails - } - } - else { - Write-LogFile -Message "[INFO] Collecting Directory Audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." - try{ - [Array]$results = Get-AzureADAuditDirectoryLogs -All $true -Filter "activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object Id,Category,CorrelationId,Result,ResultReason,ActivityDisplayName,@{N='ActivityDateTime';E={$_.ActivityDateTime.ToString()}},LoggedByService,OperationType,InitiatedBy,TargetResources,AdditionalDetails - } - catch{ - Write-LogFile -Message "[WARNING] Failed to acquire logs $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Retrying after sleep " -Color "Yellow" - Start-Sleep -Seconds 30 + [Array]$results = Get-AzureADAuditDirectoryLogs -All $true -Filter "initiatedBy/user/userPrincipalName eq '$UserIds' and activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object Id,Category,CorrelationId,Result,ResultReason,ActivityDisplayName,@{N='ActivityDateTime';E={$_.ActivityDateTime.ToString()}},LoggedByService,OperationType,InitiatedBy,TargetResources,AdditionalDetails + } else { Write-LogFile -Message "[INFO] Collecting Directory Audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))." - [Array]$results = Get-AzureADAuditDirectoryLogs -All $true -Filter "activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object Id,Category,CorrelationId,Result,ResultReason,ActivityDisplayName,@{N='ActivityDateTime';E={$_.ActivityDateTime.ToString()}},LoggedByService,OperationType,InitiatedBy,TargetResources,AdditionalDetails + [Array]$results = Get-AzureADAuditDirectoryLogs -All $true -Filter "activityDateTime gt $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and activityDateTime lt $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" | Select-Object Id,Category,CorrelationId,Result,ResultReason,ActivityDisplayName,@{N='ActivityDateTime';E={$_.ActivityDateTime.ToString()}},LoggedByService,OperationType,InitiatedBy,TargetResources,AdditionalDetails } + $success = $true } - if ($null -eq $results -or $results.Count -eq 0) { - Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Moving On!" -Color "Yellow" - } - else { - $currentCount = $results.Count - if ($currentDay -ne 0){ - $currentTotal = $currentCount + $results.Count - } - else { - $currentTotal = $currentCount + catch { + $retryCount++ + if ($retryCount -lt $maxRetries) { + Start-Sleep -Seconds 10 + Write-LogFile -Message "[WARNING] Failed to acquire logs. Retrying... Attempt $retryCount of $maxRetries" -Color "Yellow" + } else { + Write-LogFile -Message "[ERROR] Failed to acquire logs after $maxRetries attempts. Moving on." -Color "Red" } - - Write-LogFile -Message "[INFO] Found $currentCount Directory Audit Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" - - $filePath = "$OutputDir\AuditLogs-$($CurrentStart.ToString("yyyyMMddHHmmss"))-$($CurrentEnd.ToString("yyyyMMddHHmmss")).json" - $results | ConvertTo-Json -Depth 100 | Out-File -Append $filePath -Encoding $Encoding - - Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range." } - [Array]$results = @() - $CurrentStart = $CurrentEnd - $currentDay++ } - - if ($MergeOutput.IsPresent) - { - Write-LogFile -Message "[INFO] Merging output files into one file" - $outputDirMerged = "$OutputDir\Merged\" - If (!(test-path $outputDirMerged)) { - Write-LogFile -Message "[INFO] Creating the following directory: $outputDirMerged" - New-Item -ItemType Directory -Force -Path $outputDirMerged | Out-Null - } - - $allJsonObjects = @() + + if ($null -eq $results -or $results.Count -eq 0) { + Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")). Moving On!" -Color "Yellow" + } + else { + $currentCount = $results.Count + Write-LogFile -Message "[INFO] Found $currentCount Directory Audit Logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))" -Color "Green" + + $filePath = "$OutputDir\AuditLogs-$($CurrentStart.ToString("yyyyMMddHHmmss"))-$($CurrentEnd.ToString("yyyyMMddHHmmss")).json" + $results | ConvertTo-Json -Depth 100 | Out-File -Append $filePath -Encoding $Encoding + + Write-LogFile -Message "[INFO] Successfully retrieved $($currentCount) records for the current time range." + } + [Array]$results = @() + $CurrentStart = $CurrentEnd + $currentDay++ + } - Get-ChildItem $OutputDir -Filter *.json | ForEach-Object { - $content = Get-Content -Path $_.FullName -Raw - $jsonObjects = $content | ConvertFrom-Json - $allJsonObjects += $jsonObjects - } - - $allJsonObjects | ConvertTo-Json -Depth 100 | Set-Content "$outputDirMerged\AuditLogs-Combined.json" + if ($MergeOutput.IsPresent) + { + Write-LogFile -Message "[INFO] Merging output files into one file" + $outputDirMerged = "$OutputDir\Merged\" + If (!(test-path $outputDirMerged)) { + Write-LogFile -Message "[INFO] Creating the following directory: $outputDirMerged" + New-Item -ItemType Directory -Force -Path $outputDirMerged | Out-Null } - - Write-LogFile -Message "[INFO] Acquisition complete, check the $($OutputDir) directory for your files.." -Color "Green" + + $allJsonObjects = @() + + Get-ChildItem $OutputDir -Filter *.json | ForEach-Object { + $content = Get-Content -Path $_.FullName -Raw + $jsonObjects = $content | ConvertFrom-Json + $allJsonObjects += $jsonObjects + } + + $allJsonObjects | ConvertTo-Json -Depth 100 | Set-Content "$outputDirMerged\AuditLogs-Combined.json" } + + Write-LogFile -Message "[INFO] Acquisition complete, check the $($OutputDir) directory for your files.." -Color "Green" +} diff --git a/Scripts/Get-Emails.ps1 b/Scripts/Get-Emails.ps1 index 58133b3..3fbcb13 100644 --- a/Scripts/Get-Emails.ps1 +++ b/Scripts/Get-Emails.ps1 @@ -4,7 +4,7 @@ Function Get-Email { Get a specific email. .DESCRIPTION - Get a specific email based on userId and Internet Message Id and saves the output to a msg or txt file. + Get a specific email based on userId and Internet Message Id and saves the output to a eml or txt file. .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. @@ -17,8 +17,11 @@ Function Get-Email { The InternetMessageId parameter represents the Internet message identifier of an item. .PARAMETER output - Output is the parameter specifying the msg or txt output type. - Default: msg + Output is the parameter specifying the eml or txt output type. + Default: eml + + .PARAMETER inputFile + The inputFile parameter specifies the .txt file containing multiple Internet Message Identifiers. You can include multiple Internet Message Identifiers in the file. Ensure each ID is placed on a new line. .PARAMETER attachment The attachment parameter specifies whether the attachment should be saved or not. @@ -26,11 +29,11 @@ Function Get-Email { .EXAMPLE Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "" - Retrieves an email from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a msg file. + Retrieves an email from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a eml file. .EXAMPLE Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "" -attachment True - Retrieves an email and the attachment from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a msg file. + Retrieves an email and the attachment from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a eml file. .EXAMPLE Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "" -OutputDir C:\Windows\Temp @@ -39,23 +42,15 @@ Function Get-Email { [CmdletBinding()] param( [Parameter(Mandatory=$true)]$userIds, - [Parameter(Mandatory=$true)]$internetMessageId, + [string]$internetMessageId, [string]$output, [string]$outputDir, - [string]$attachment - ) + [string]$attachment, + [string]$inputFile + ) Write-logFile -Message "[INFO] Running Get-Email" -Color "Green" - try { - $areYouConnected = Get-MgUserMessage -UserId $userIds -Filter "internetMessageId eq '$internetMessageId'" - } - catch { - Write-logFile -Message "[WARNING] You must call Connect-MgGraph -Scopes Mail.Read, Mail.ReadBasic, Mail.ReadBasic.All before running this script" -Color "Red" - Write-logFile -Message "[WARNING] The 'Mail.ReadBasic.All' is an application-level permission, requiring an application-based connection through the 'Connect-MgGraph' command for its use." -Color "Red" - break - } - if ($outputDir -eq "" ){ $outputDir = "Output\EmailExport" if (!(test-path $outputDir)) { @@ -75,28 +70,98 @@ Function Get-Email { } } - $getMessage = Get-MgUserMessage -UserId $userIds -Filter "internetMessageId eq '$internetMessageId'" - $messageId = $getMessage.Id - - $subject = $getMessage.Subject - $subject = $subject -replace '[\\/:*?"<>|]', '_' - - $ReceivedDateTime = $getMessage.ReceivedDateTime.ToString("yyyyMMdd_HHmmss") - if ($output -eq "txt") { - $filePath = "$outputDir\$ReceivedDateTime-$subject.txt" - } + if ($inputFile) { + try { + $internetMessageIds = Get-Content $inputFile + } + catch { + Write-Error "[ERROR] Failed to read the input file. Ensure it is a text file with the message IDs on new lines: $_" + return + } - else { - $filePath = "$outputDir\$ReceivedDateTime-$subject.msg" + # Loop through each internetMessageId in the inputFile + $notCollected = @() + foreach ($id in $internetMessageIds) { + $id = $id.Trim() + write-host "[INFO] Identified: $id" + try { + $getMessage = Get-MgUserMessage -UserId $userIds -Filter "internetMessageId eq '$id'" + $messageId = $getMessage.Id + + $subject = $getMessage.Subject + $subject = $subject -replace '[\\/:*?"<>|]', '_' + + $ReceivedDateTime = $getMessage.ReceivedDateTime.ToString("yyyyMMdd_HHmmss") + + if ($output -eq "txt") { + $filePath = "$outputDir\$ReceivedDateTime-$subject.txt" + } + + else { + $filePath = "$outputDir\$ReceivedDateTime-$subject.eml" + } + + Get-MgUserMessageContent -MessageId $messageId -UserId $userIds -OutFile $filePath + Write-logFile -Message "[INFO] Output written to $filePath" -Color "Green" + + if ($attachment -eq "True"){ + Get-Attachment -Userid $Userids -internetMessageId $id + } + } + catch { + Write-Warning "[WARNING] Failed to collect message with ID '$id': $_" + $notCollected += $id # Add the message ID to the list of not collected IDs + } + } + # Check if there are any message IDs that were not collected and write them to the log file + if ($notCollected.Count -gt 0) { + Write-logFile -Message "[INFO] The following messages have not been collected:" -Color "Yellow" + foreach ($notCollectedID in $notCollected) { + Write-logFile -Message " $notCollectedID" -Color "Yellow" + } + } } - Get-MgUserMessageContent -MessageId $messageId -UserId $userIds -OutFile $filePath - Write-logFile -Message "[INFO] Output written to $filePath" -Color "Green" - - if ($attachment -eq "True"){ - Get-Attachment -Userid $Userids -internetMessageId $internetMessageId - } + else { + # Check if internetMessageId is provided + if (-not $internetMessageId) { + Write-Error "[ERROR] Either internetMessageId or inputFile must be provided." + return + } + + try { + $areYouConnected = Get-MgUserMessage -UserId $userIds -Filter "internetMessageId eq '$internetMessageId'" + } + catch { + Write-logFile -Message "[WARNING] You must call Connect-MgGraph -Scopes Mail.ReadBasic.All before running this script" -Color "Red" + Write-logFile -Message "[WARNING] The 'Mail.ReadBasic.All' is an application-level permission, requiring an application-based connection through the 'Connect-MgGraph' command for its use." -Color "Red" + return + } + + $getMessage = Get-MgUserMessage -UserId $userIds -Filter "internetMessageId eq '$internetMessageId'" + $messageId = $getMessage.Id + + $subject = $getMessage.Subject + $subject = $subject -replace '[\\/:*?"<>|]', '_' + + $ReceivedDateTime = $getMessage.ReceivedDateTime.ToString("yyyyMMdd_HHmmss") + + if ($output -eq "txt") { + $filePath = "$outputDir\$ReceivedDateTime-$subject.txt" + } + + else { + $filePath = "$outputDir\$ReceivedDateTime-$subject.eml" + } + + Get-MgUserMessageContent -MessageId $messageId -UserId $userIds -OutFile $filePath + Write-logFile -Message "[INFO] Output written to $filePath" -Color "Green" + + if ($attachment -eq "True"){ + Get-Attachment -Userid $Userids -internetMessageId $internetMessageId + } + } } @@ -133,6 +198,9 @@ Function Get-Attachment { [string]$outputDir ) + write-host $internetMessageId + write-host $userIds + Write-logFile -Message "[INFO] Running Get-Attachment" -Color "Green" try { @@ -163,28 +231,35 @@ Function Get-Attachment { } } - $getMessage = Get-MgUserMessage -Filter "internetMessageId eq '$internetMessageId'" -UserId $userIds + #$getMessage = Get-MgUserMessage -Filter "internetMessageId eq '$internetMessageId'" -UserId $userIds + $getMessage = Get-MgUserMessage -UserId $userIds -Filter "internetMessageId eq '$internetMessageId'" $messageId = $getMessage.Id + $messageId = $messageId.Trim() $hasAttachment = $getMessage.HasAttachments $ReceivedDateTime = $getMessage.ReceivedDateTime.ToString("yyyyMMdd_HHmmss") $subject = $getMessage.Subject + $subject = $subject -replace '[\\/:*?"<>|]', '_' if ($hasAttachment -eq "True"){ - $attachment = Get-MgUserMessageAttachment -UserId $userIds -MessageId $messageId - $filename = $attachment.Name - - Write-logFile -Message "[INFO] Downloading attachment" - Write-host "[INFO] Name: $filename" - write-host "[INFO] Size: $($attachment.Size)" - - $base64B = ($attachment).AdditionalProperties.contentBytes - $decoded = [System.Convert]::FromBase64String($base64B) - - $filename = $filename -replace '[\\/:*?"<>|]', '_' - $filePath = Join-Path -Path $outputDir -ChildPath "$ReceivedDateTime-$filename" - Set-Content -Path $filePath -Value $decoded -Encoding Byte - - Write-logFile -Message "[INFO] Output written to '$subject-$filename'" -Color "Green" + $attachments = Get-MgUserMessageAttachment -UserId $userIds -MessageId $messageId + + foreach ($attachment in $attachments){ + $filename = $attachment.Name + + Write-logFile -Message "[INFO] Found attachment named $filename" + Write-logFile -Message "[INFO] Downloading attachment" + Write-host "[INFO] Name: $filename" + write-host "[INFO] Size: $($attachment.Size)" + + $base64B = ($attachment).AdditionalProperties.contentBytes + $decoded = [System.Convert]::FromBase64String($base64B) + + $filename = $filename -replace '[\\/:*?"<>|]', '_' + $filePath = Join-Path -Path $outputDir -ChildPath "$ReceivedDateTime-$subject-$filename" + Set-Content -Path $filePath -Value $decoded -Encoding Byte + + Write-logFile -Message "[INFO] Output written to '$subject-$filename'" -Color "Green" + } } else { diff --git a/Scripts/Get-MailItemsAccessed.ps1 b/Scripts/Get-MailItemsAccessed.ps1 index 2a3264b..fe6f7ef 100644 --- a/Scripts/Get-MailItemsAccessed.ps1 +++ b/Scripts/Get-MailItemsAccessed.ps1 @@ -36,7 +36,7 @@ Function Get-Sessions { Collects all sessions for all users between 1/4/2023 and 5/4/2023. .EXAMPLE - Get-Sessions Get-Sessions -StartDate 1/4/2023 -EndDate 5/4/2023 -UserIds HR@invictus-ir.com + Get-Sessions -StartDate 1/4/2023 -EndDate 5/4/2023 -UserIds HR@invictus-ir.com Collects all sessions for the user HR@invictus-ir.com. #> [CmdletBinding()] @@ -271,11 +271,11 @@ function Get-MessageIDs { Collects all sessions for all users between 1/4/2023 and 5/4/2023. .EXAMPLE - Get-MessageIDs Get-Sessions -StartDate 1/4/2023 -EndDate 5/4/2023 -IP 1.1.1.1 + Get-MessageIDs -StartDate 1/4/2023 -EndDate 5/4/2023 -IP 1.1.1.1 Collects all sessions for the IP address 1.1.1.1. .EXAMPLE - Get-MessageIDs Get-Sessions -StartDate 1/4/2023 -EndDate 5/4/2023 -IP 1.1.1.1 -Download Yes + Get-MessageIDs -StartDate 1/4/2023 -EndDate 5/4/2023 -IP 1.1.1.1 -Download Yes Collects all sessions for the IP address 1.1.1.1 and downloads the e-mails and attachments. #> [CmdletBinding()] @@ -646,7 +646,7 @@ function DownloadMails($iMessageID,$UserIds){ $subject = $getMessage.Subject $subject = $subject -replace '[\\/:*?"<>|]', '_' - $filePath = "$outputDir\$ReceivedDateTime-$subject.msg" + $filePath = "$outputDir\$ReceivedDateTime-$subject.elm" Get-MgUserMessageContent -MessageId $messageId -UserId $userId -OutFile $filePath Write-logFile -Message "[INFO] Output written to $filePath" -Color "Green" diff --git a/docs/source/conf.py b/docs/source/conf.py index 1ba5390..620f38f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,8 +6,8 @@ copyright = 'Copyright (c) 2024 Invictus Incident Response' author = 'Joey Rentenaar & Korstiaan Stam' -release = '1.3.4' -version = '1.3.4' +release = '1.3.5' +version = '1.3.5' # -- General configuration diff --git a/docs/source/functionality/AzureActiveDirectoryAuditLog.rst b/docs/source/functionality/AzureActiveDirectoryAuditLog.rst index 4f9656e..6e801cf 100644 --- a/docs/source/functionality/AzureActiveDirectoryAuditLog.rst +++ b/docs/source/functionality/AzureActiveDirectoryAuditLog.rst @@ -26,19 +26,26 @@ Get the Azure Active Directory Audit Log after 2023-04-12: Parameters """""""""""""""""""""""""" -startDate (optional) - - startDate is the parameter specifying the start date of the date range. The time format supported is limited to yyyy-mm-dd only. + - startDate is the parameter specifying the start date of the date range. -endDate (optional) - - endDate is the parameter specifying the end date of the date range. The time format supported is limited to yyyy-mm-dd only. + - endDate is the parameter specifying the end date of the date range. -OutputDir (optional) - OutputDir is the parameter specifying the output directory. - - Default: Output\AzureAD + - Default: The output will be written to: "Output\AzureAD\{date_AuditLogs}\Auditlogs.json -Encoding (optional) - Encoding is the parameter specifying the encoding of the JSON output file. - Default: UTF8 +-MergeOutput (optional) + - MergeOutput is the parameter specifying if you wish to merge CSV outputs to a single file. + +-Interval (optional) + - Interval is the parameter specifying the interval in which the logs are being gathered. + - Default: 720 minutes + Output """""""""""""""""""""""""" The output will be saved to the 'AzureAD' directory within the 'Output' directory, with the file name 'Auditlogs.json'. Each time an acquisition is performed, the output JSON file will be overwritten. Therefore, if you perform multiple acquisitions, the JSON file will only contain the results from the latest acquisition. \ No newline at end of file diff --git a/docs/source/functionality/AzureActiveDirectorysign-inlogs.rst b/docs/source/functionality/AzureActiveDirectorysign-inlogs.rst index 2be6689..9478c39 100644 --- a/docs/source/functionality/AzureActiveDirectorysign-inlogs.rst +++ b/docs/source/functionality/AzureActiveDirectorysign-inlogs.rst @@ -27,14 +27,14 @@ Get the Azure Active Directory Audit Log after 2023-04-12: Parameters """""""""""""""""""""""""" -startDate (optional) - - startDate is the parameter specifying the start date of the date range. The time format supported is limited to yyyy-mm-dd only. + - startDate is the parameter specifying the start date of the date range. -endDate (optional) - - endDate is the parameter specifying the end date of the date range. The time format supported is limited to yyyy-mm-dd only. + - endDate is the parameter specifying the end date of the date range. -OutputDir (optional) - OutputDir is the parameter specifying the output directory. - - Default: Output\AzureAD + - Default: The output will be written to: Output\AzureAD\{date_SignInLogs}\SignInLogs.json -Encoding (optional) - Encoding is the parameter specifying the encoding of the JSON output file. @@ -43,6 +43,16 @@ Parameters -UserIds (optional) - UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. +-MergeOutput (optional) + - MergeOutput is the parameter specifying if you wish to merge CSV outputs to a single file. + +-UserIds (optional) + - UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. + +-Interval (optional) + - Interval is the parameter specifying the interval in which the logs are being gathered. + - Default: 1440 minutes + Output """""""""""""""""""""""""" The output will be saved to the 'AzureAD' directory within the 'Output' directory, with the file name 'SignInLogs.json'. Each time an acquisition is performed, the output JSON file will be overwritten. Therefore, if you perform multiple acquisitions, the JSON file will only contain the results from the latest acquisition. diff --git a/docs/source/functionality/AzureAuditLogsGraph.rst b/docs/source/functionality/AzureAuditLogsGraph.rst index f4ad3da..d9d7f87 100644 --- a/docs/source/functionality/AzureAuditLogsGraph.rst +++ b/docs/source/functionality/AzureAuditLogsGraph.rst @@ -26,19 +26,22 @@ Get the Azure Active Directory Audit Log after 2023-04-12: Parameters """""""""""""""""""""""""" -startDate (optional) - - startDate is the parameter specifying the start date of the date range. The time format supported is limited to yyyy-mm-dd only. + - startDate is the parameter specifying the start date of the date range. -endDate (optional) - - endDate is the parameter specifying the end date of the date range. The time format supported is limited to yyyy-mm-dd only. + - endDate is the parameter specifying the end date of the date range. -OutputDir (optional) - OutputDir is the parameter specifying the output directory. - - Default: Output\AzureAD + - Default: The output will be written to: "Output\AzureAD\{date_AuditLogs}\Auditlogs.json -Application (optional) - Application is the parameter specifying App-only access (access without a user) for authentication and authorization. - Default: Delegated access (access on behalf a user) +-MergeOutput (optional) + - MergeOutput is the parameter specifying if you wish to merge CSV outputs to a single file. + -Encoding (optional) - Encoding is the parameter specifying the encoding of the JSON output file. - Default: UTF8 diff --git a/docs/source/functionality/AzureSignInLogsGraph.rst b/docs/source/functionality/AzureSignInLogsGraph.rst index 449d27a..1007604 100644 --- a/docs/source/functionality/AzureSignInLogsGraph.rst +++ b/docs/source/functionality/AzureSignInLogsGraph.rst @@ -26,14 +26,14 @@ Get the Azure Active Directory Audit Log after 2023-04-12: Parameters """""""""""""""""""""""""" -startDate (optional) - - startDate is the parameter specifying the start date of the date range. The time format supported is limited to yyyy-mm-dd only. + - startDate is the parameter specifying the start date of the date range. -endDate (optional) - - endDate is the parameter specifying the end date of the date range. The time format supported is limited to yyyy-mm-dd only. + - endDate is the parameter specifying the end date of the date range. -OutputDir (optional) - OutputDir is the parameter specifying the output directory. - - Default: AzureAD + - Default: The output will be written to: Output\AzureAD\{date_SignInLogs}\SignInLogs.json -Encoding (optional) - Encoding is the parameter specifying the encoding of the JSON output file. @@ -47,6 +47,10 @@ Parameters - MergeOutput is the parameter specifying if you wish to merge CSV outputs to a single file. - Default: No +-Interval (optional) + - Interval is the parameter specifying the interval in which the logs are being gathered. + - Default: 1440 minutes + -UserIds (optional) - UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. diff --git a/docs/source/functionality/GetEmails.rst b/docs/source/functionality/GetEmails.rst index 1a9d625..5137a07 100644 --- a/docs/source/functionality/GetEmails.rst +++ b/docs/source/functionality/GetEmails.rst @@ -8,16 +8,16 @@ This section comprises a variety of functions designed to gather e-mails and the Get a specific email. ^^^^^^^^^^^ -Get a specific email based on userId and Internet Message Id and saves the output to a msg or txt file. +Get a specific email based on userId and Internet Message Id and saves the output to a eml or txt file. Usage """""""""""""""""""""""""" -Retrieves an email from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a msg file. +Retrieves an email from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a eml file. :: Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "" -Retrieves an email and the attachment from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a msg file. +Retrieves an email and the attachment from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier to a eml file. :: Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "" -attachment True @@ -36,13 +36,16 @@ Parameters - The InternetMessageId parameter represents the Internet message identifier of an item. -Output (optional) - - Output is the parameter specifying the msg or txt output type. - - Default: msg + - Output is the parameter specifying the eml or txt output type. + - Default: eml -OutputDir (optional) - OutputDir is the parameter specifying the output directory. - Default: EmailExport +-inputFile (optional) + - The inputFile parameter specifies the .txt file containing multiple Internet Message Identifiers. You can include multiple Internet Message Identifiers in the file. Ensure each ID is placed on a new line. + -Attachment (optional) - The attachment parameter specifies whether the attachment should be saved or not. - Default: False diff --git a/docs/source/functionality/MailItemsAccessed.rst b/docs/source/functionality/MailItemsAccessed.rst index 42100a8..e46140d 100644 --- a/docs/source/functionality/MailItemsAccessed.rst +++ b/docs/source/functionality/MailItemsAccessed.rst @@ -62,7 +62,7 @@ Collects all sessions for all users between 1/4/2023 and 5/4/2023. Collects all sessions for the IP address 1.1.1.1. :: - Get-MessageIDs Get-Sessions -StartDate 1/4/2023 -EndDate 5/4/2023 -IP 1.1.1.1 + Get-MessageIDs -StartDate 1/4/2023 -EndDate 5/4/2023 -IP 1.1.1.1 Parameters """"""""""""""""""""""""""