Skip to content

Commit

Permalink
Update 2.1.1
Browse files Browse the repository at this point in the history
- Accepted pull reuqest from @angrybender updating the date format in Get-UALGraph for improved readability and consistency.
- Corrected a typo in the $filePath variable when using the -Download flag in Get-MessageIDs.
- Implemented suggestions from @Calvindd2f to add additional parameters for connection scripts. Users can now connect using an access token.
- Reworked the $areYouConnected functionality for the UAL scripts.
- Introduced the -All parameter to Get-ADAuditLogsGraph. By default, filtering with the UserIds field retrieves only actions directly performed by the specified user. With the new -All flag, the command now includes all related events involving the user, such as events where an MFA device was added for them.
- Fixed an issue where the merge output would throw "out of memory" errors. Now, while merging the output files, each file is written directly to the merged output file instead of reading everything into memory first and then saving it.

As suggested by @evild3ad:
- Updated the import command: Import-Module .\Microsoft-Extractor-Suite.psm1 -ArgumentList $true to suppress the logo output, optimizing it for automation scenarios.
- Replaced remaining Write-Host commands in Get-Rules.ps1 with the custom Write-LogFile function for consistent logging.
- Fixed an issue in Get-MailboxRules where using the -UserIDs flag with no rules found would incorrectly display the total inbox rules.
- Added support for the -UserIds flag to Risky Users and Detections.
- Added support for the -UserIds flag to the Get-MFA functionality.
  • Loading branch information
JoeyInvictus committed Oct 29, 2024
1 parent 32ca6a2 commit 42bf551
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 127 deletions.
2 changes: 1 addition & 1 deletion Microsoft-Extractor-Suite.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Author = 'Joey Rentenaar & Korstiaan Stam'
CompanyName = 'Invictus-IR'

# Version number of this module.
ModuleVersion = '2.1.0'
ModuleVersion = '2.1.1'

# ID used to uniquely identify this module
GUID = '4376306b-0078-4b4d-b565-e22804e3be01'
Expand Down
41 changes: 30 additions & 11 deletions Microsoft-Extractor-Suite.psm1
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
param (
[switch]$NoWelcome = $false
)

# Set supported TLS methods
[Net.ServicePointManager]::SecurityProtocol = "Tls12, Tls13"

$manifest = Import-PowerShellDataFile "$PSScriptRoot\Microsoft-Extractor-Suite.psd1"
$version = $manifest.ModuleVersion
$host.ui.RawUI.WindowTitle = "Microsoft-Extractor-Suite $version"

$logo=@"
if (-not $NoWelcome) {
$logo=@"
+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+
|M|i|c|r|o|s|o|f|t| |E|x|t|r|a|c|t|o|r| |S|u|i|t|e|
+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+
Copyright 2024 Invictus Incident Response
Created by Joey Rentenaar & Korstiaan Stam
"@

Write-Host $logo -ForegroundColor Yellow
Write-Host $logo -ForegroundColor Yellow
}

$outputDir = "Output"
if (!(test-path $outputDir)) {
Expand Down Expand Up @@ -186,20 +192,33 @@ function Merge-OutputFiles {
Write-LogFile -Message "[INFO] CSV files merged into $mergedPath"
}
'JSON' {
$allJsonObjects = @()
"[" | Set-Content $mergedPath -Encoding UTF8

Get-ChildItem $OutputDir -Filter *.json | ForEach-Object {
$jsonContent = Get-Content -Path $_.FullName -Raw | ConvertFrom-Json
if ($jsonContent -is [System.Collections.ArrayList] -or $jsonContent -is [System.Collections.Generic.List[object]]) {
$allJsonObjects += $jsonContent
$firstFile = $true
Get-ChildItem $OutputDir -Filter *.json | ForEach-Object {
$content = Get-Content -Path $_.FullName -Raw

$content = $content.Trim()
if ($content.StartsWith('[')) {
$content = $content.Substring(1)
}
if ($content.EndsWith(']')) {
$content = $content.Substring(0, $content.Length - 1)
}
else {
$allJsonObjects += @($jsonContent)
$content = $content.Trim()

if (-not $firstFile -and $content) {
Add-Content -Path $mergedPath -Value "," -Encoding UTF8
}

if ($content) {
Add-Content -Path $mergedPath -Value $content -Encoding UTF8
$firstFile = $false
}
}

$allJsonObjects | ConvertTo-Json -Depth 100 | Set-Content $mergedPath
Write-Host "[INFO] JSON files merged into $mergedPath"
"]" | Add-Content $mergedPath -Encoding UTF8
Write-LogFile -Message "[INFO] JSON files merged into $mergedPath"
}
default {
Write-LogFile -Message "[ERROR] Unsupported file type specified: $OutputType" -Color Red
Expand Down
175 changes: 167 additions & 8 deletions Scripts/Connect.ps1
Original file line number Diff line number Diff line change
@@ -1,18 +1,177 @@
Function Connect-M365
{
versionCheck
Connect-ExchangeOnline > $null
PARAM(
[string]
$ConnectionUri,
[string]
$AzureADAuthorizationEndpointUri,
[ValidateSet('O365China', 'O365Default', 'O365GermanyCloud', 'O365USGovDoD', 'O365USGovGCCHigh')]
[string]
$ExchangeEnvironmentName,
[string[]]
$PSSessionOptions,
[string]
$DelegatedOrganization,
[string]
$Prefix,
[string[]]
$CommandName,
[string[]]
$FormatTypeName,
[string]
$AccessToken,
[string]
$AppId,
[switch]
$BypassMailboxAnchoring,
[X509Certificate]
$Certificate,
[string]
$CertificateFilePath,
[SecureString]
$CertificatePassword,
[string]
$CertificateThumbprint,
[PSCredential]
$Credential,
[switch]
$Device,
[switch]
$EnableErrorReporting,
[switch]
$InlineCredential,
[string]
$LogDirectoryPath,
[string]
$LogLevel,
[switch]
$ManagedIdentity,
[string]
$ManagedIdentityAccountId,
[string]
$Organization,
[int]
$PageSize,
[switch]
$ShowBanner,
[X509Certificate]
$SigningCertificate,
[switch]
$SkipLoadingCmdletHelp,
[switch]
$SkipLoadingFormatData,
[Boolean]
$TrackPerformance,
[Boolean]
$UseMultithreading,
[string]
$UserPrincipalName,
[Switch]
$UseRPSSession
)
versionCheck
Connect-ExchangeOnline @PSBoundParameters > $null;
}

Function Connect-Azure
{
versionCheck
Connect-AzureAD > $null
PARAM(
[ValidateSet('AzureChinaCloud', 'AzureCloud', 'AzureGermanyCloud', 'AzurePPE', 'AzureUSGovernment', 'AzureUSGovernment2', 'AzureUSGovernment3')]
[string]
$AzureEnvironmentName,
[string]
$TenantId,
[pscredential]
$Credential,
[string]
$CertificateThumbprint,
[string]
$ApplicationId,
[string]
$AadAccessToken,
[string]
$MsAccessToken,
[string]
$AccountId,
[ValidateSet('Error', 'Info', 'None', 'Warning')]
[string]
$LogLevel,
[string]
$LogFilePath,
[switch]
$WhatIf,
[switch]
$Confirm,
[Switch]
$Verbose,
[switch]
$Debug
)
versionCheck
Connect-AzureAD @PSBoundParameters > $null;
}

Function Connect-AzureAZ
{
versionCheck
Connect-AzAccount > $null
}

PARAM(
[String]
$AccessToken ,
[String]
$AccountId ,
[String]
$ApplicationId ,
[String]
$AuthScope ,
[SecureString]
$CertificatePassword,
[String]
$CertificatePath ,
[String]
$CertificateThumbprint ,
[String]
$ContextName ,
[PSCredential]
$Credential,
[string]
$DefaultProfile ,
[String]
$Environment ,
[String]
$FederatedToken ,
[switch]
$Force ,
[String]
$GraphAccessToken ,
[switch]
$Identity,
[String]
$KeyVaultAccessToken ,
[int]
$MaxContextPopulation,
[String]
$MicrosoftGraphAccessToken ,
[ValidateSet('CurrentUser', 'Process')]
[string]
$Scope,
[switch]
$SendCertificateChain,
[switch]
$ServicePrincipal,
[switch]
$SkipContextPopulation ,
[switch]
$SkipValidation ,
[String]
$Subscription ,
[String]
$Tenant ,
[switch]
$UseDeviceAuthentication,
[switch]
$Confirm,
[switch]
$WhatIf
)
versionCheck
Connect-AzAccount @PSBoundParameters > $null;
}
52 changes: 36 additions & 16 deletions Scripts/Get-AzureADGraphLogs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ function Get-ADSignInLogsGraph {

$StartDate = $script:StartDate.ToString('yyyy-MM-ddTHH:mm:ssZ')
$EndDate = $script:EndDate.ToString('yyyy-MM-ddTHH:mm:ssZ')
$TotalTicks = ($script:EndDate-$script:StartDate).Ticks

$filterQuery = "createdDateTime ge $StartDate and createdDateTime le $EndDate"
if ($UserIds) {
Expand All @@ -100,16 +99,18 @@ function Get-ADSignInLogsGraph {
$date = [datetime]::Now.ToString('yyyyMMddHHmmss')
$filePath = Join-Path -Path $OutputDir -ChildPath "$($date)-SignInLogsGraph.json"

$responseJson.value | ConvertTo-Json -Depth 100 | Out-File -FilePath $filePath -Append -Encoding $Encoding
$dates = $responseJson.value | ForEach-Object { [DateTime]::Parse($_.CreatedDateTime) } | Sort-Object
$responseJson.value | ConvertTo-Json -Depth 100 | Out-File -FilePath $filePath -Append -Encoding $Encoding
#$dates = $responseJson.value | ForEach-Object { [DateTime]::Parse($_.CreatedDateTime) } | Sort-Object

$dates = $responseJson.value | ForEach-Object {
[DateTime]::Parse($_.CreatedDateTime, [System.Globalization.CultureInfo]::InvariantCulture)
} | Sort-Object

$from = $dates | Select-Object -First 1
$fromstr = $from.ToString('yyyy-MM-ddTHH:mmZ')
$to = ($dates | Select-Object -Last 1).ToString('yyyy-MM-ddTHH:mmZ')
# $fromstr = $from.ToString('yyyy-MM-ddTHH:mmZ')
$to = ($dates | Select-Object -Last 1) #.ToString('yyyy-MM-ddTHH:mmZ')
$count = ($responseJson.value | measure).Count
Write-Host "[INFO] Sign-in logs written to $filePath ($count records between $fromStr and $to)" -ForegroundColor Green

$progress = [Math]::Round(($script:EndDate-$from).Ticks / $TotalTicks * 100, 2)
Write-Progress -Activity "Collecting Sign-in logs" -Status "$progress% Complete" -PercentComplete $progress
Write-LogFile -Message "[INFO] Sign-in logs written to $filePath ($count records between $from and $to)" -Color Green
}
$apiUrl = $responseJson.'@odata.nextLink'
} While ($apiUrl)
Expand Down Expand Up @@ -147,6 +148,9 @@ function Get-ADAuditLogsGraph {
.PARAMETER UserIds
UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions.
.PARAMETER All
When specified along with UserIds, this parameter filters the results to include events where the provided UserIds match any user principal name found in either the userPrincipalNames or targetResources fields.
.PARAMETER Encoding
Encoding is the parameter specifying the encoding of the JSON output file.
Default: UTF8
Expand All @@ -163,6 +167,10 @@ function Get-ADAuditLogsGraph {
Get-ADAuditLogsGraph -Application
Get directory audit logs via application authentication.
.EXAMPLE
Get-ADAuditLogsGraph -UserIds 'user@example.com' -All
Get sign-in logs for 'user@example.com', including both userPrincipalName and targetResources in the filter.
.EXAMPLE
Get-ADAuditLogsGraph -Before 2023-04-12
Get directory audit logs before 2023-04-12.
Expand All @@ -178,7 +186,8 @@ function Get-ADAuditLogsGraph {
[string]$OutputDir,
[string]$Encoding = "UTF8",
[switch]$MergeOutput,
[string]$UserIds
[string]$UserIds,
[switch]$All
)

$requiredScopes = @("AuditLog.Read.All", "Directory.Read.All")
Expand Down Expand Up @@ -209,12 +218,20 @@ function Get-ADAuditLogsGraph {

$StartDate = $script:StartDate.ToString('yyyy-MM-ddTHH:mm:ssZ')
$EndDate = $script:EndDate.ToString('yyyy-MM-ddTHH:mm:ssZ')
$TotalTicks = ($script:EndDate-$script:StartDate).Ticks

$filterQuery = "activityDateTime ge $StartDate and activityDateTime le $EndDate"
if ($UserIds) {
$filterQuery += " and startsWith(initiatedBy/user/userPrincipalName, '$UserIds')"

if ($All.IsPresent) {
$filterQuery = "($filterQuery) or (targetResources/any(tr: tr/userPrincipalName eq '$UserIds'))"
}
}
else {
if ($All.IsPresent) {
Write-LogFile -Message "[WARNING] '-All' switch has no effect without specifying UserIds"
}
}

$encodedFilterQuery = [System.Web.HttpUtility]::UrlEncode($filterQuery)
$apiUrl = "https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?`$filter=$encodedFilterQuery"
Expand All @@ -227,15 +244,18 @@ function Get-ADAuditLogsGraph {
$date = [datetime]::Now.ToString('yyyyMMddHHmmss')
$filePath = Join-Path -Path $OutputDir -ChildPath "$($date)-AuditLogs.json"
$responseJson.value | ConvertTo-Json -Depth 100 | Out-File -FilePath $filePath -Append -Encoding $Encoding
$dates = $responseJson.value | ForEach-Object { [DateTime]::Parse($_.activityDateTime) } | Sort-Object

#$dates = $responseJson.value | ForEach-Object { [DateTime]::Parse($_.activityDateTime) } | Sort-Object

$dates = $responseJson.value | ForEach-Object {
[DateTime]::Parse($_.activityDateTime, [System.Globalization.CultureInfo]::InvariantCulture)
} | Sort-Object

$from = $dates | Select-Object -First 1
$fromstr = $from.ToString('yyyy-MM-ddTHH:mmZ')
$to = ($dates | Select-Object -Last 1).ToString('yyyy-MM-ddTHH:mmZ')
$count = ($responseJson.value | measure).Count
Write-Host "[INFO] Audit logs written to $filePath ($count records between $fromstr and $to)" -ForegroundColor Green

$progress = [Math]::Round(($script:EndDate-$from).Ticks / $TotalTicks * 100, 2)
Write-Progress -Activity "Collecting Audit logs" -Status "$progress% Complete" -PercentComplete $progress
Write-LogFile -Message "[INFO] Audit logs written to $filePath ($count records between $fromstr and $to)" -Color Green
}
$apiUrl = $responseJson.'@odata.nextLink'
} While ($apiUrl)
Expand Down
Loading

0 comments on commit 42bf551

Please sign in to comment.