From c781036bd1020607424d2ddf2b14991a7e78b52c Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 14:25:34 -0500 Subject: [PATCH 01/19] Add Test-HawkInvestigationParameters function --- .../Test-HawkInvestigationParameter.ps1 | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 diff --git a/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 b/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 new file mode 100644 index 0000000..8c3e673 --- /dev/null +++ b/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 @@ -0,0 +1,143 @@ +# Internal validation function +Function Test-HawkInvestigationParameter { + <# + .SYNOPSIS + Validates investigation parameters to ensure they meet expected requirements. + + .DESCRIPTION + This function performs internal validation of parameters used for Hawk investigations, such as StartDate, EndDate, DaysToLookBack, and FilePath. + It checks for missing or invalid values and enforces rules around date ranges and file paths, ensuring investigations are configured correctly + before proceeding. + + .PARAMETER StartDate + Specifies the start date of the investigation period. This date must be provided in a valid DateTime format and cannot be more recent than EndDate. + When used in conjunction with EndDate, the date range must not exceed 365 days. + + .PARAMETER EndDate + Specifies the end date of the investigation period. This date must be provided in a valid DateTime format and cannot be in the future. + If StartDate is provided, EndDate must also be specified. + + .PARAMETER DaysToLookBack + Specifies the number of days to look back from the current date to gather log data. The value must be an integer between 1 and 365. + This parameter is typically used as an alternative to specifying a StartDate and EndDate. + + .PARAMETER FilePath + Specifies the directory path where investigation output files will be saved. This path must be valid and accessible. + The parameter is required in non-interactive mode to ensure logs are written to a specified location. + + .PARAMETER NonInteractive + A switch parameter that indicates the function is running in non-interactive mode. In this mode, required parameters must be provided upfront, + as user prompts are disabled. This is typically used in automated scripts or CI/CD pipelines. + + .OUTPUTS + Returns a custom PowerShell object with two properties: + - IsValid: A boolean value indicating whether the parameters passed validation. + - ErrorMessages: An array of error messages explaining why validation failed (if applicable). + + .NOTES + - The function converts StartDate and EndDate to UTC to ensure consistent date comparisons. + - If a date range exceeds 365 days or if EndDate is in the future, the function returns a validation error. + - DaysToLookBack must be between 1 and 365 to comply with log retention policies in Microsoft 365. + + .EXAMPLE + Test-HawkInvestigationParameter -StartDate "2024-01-01" -EndDate "2024-03-31" -FilePath "C:\Logs" -NonInteractive + + This example validates the parameters for a non-interactive Hawk investigation. The function checks that the FilePath is valid, + the date range is within limits, and that both StartDate and EndDate are provided. + + .EXAMPLE + Test-HawkInvestigationParameter -DaysToLookBack 90 -FilePath "C:\Logs" + + This example validates an investigation configured to look back 90 days from the current date. The function ensures that DaysToLookBack + is within the allowable range and that the FilePath is valid. + + .EXAMPLE + $validationResult = Test-HawkInvestigationParameter -StartDate "2024-01-01" -EndDate "2024-02-15" -DaysToLookBack 45 + if (-not $validationResult.IsValid) { + $validationResult.ErrorMessages | ForEach-Object { Write-Host $_ -ForegroundColor Red } + } + + This example stores the validation result in a variable and outputs any error messages if the parameters failed validation. + + .LINK + https://cloudforensicator.com/ + + .LINK + https://docs.microsoft.com/en-us/powershell/scripting/ + + #> + [CmdletBinding()] + param ( + [DateTime]$StartDate, + [DateTime]$EndDate, + [int]$DaysToLookBack, + [string]$FilePath, + [switch]$NonInteractive + ) + + # Store validation results + $isValid = $true + $errorMessages = @() + + # If in non-interactive mode, validate required parameters + if ($NonInteractive) { + # Validate FilePath + if ([string]::IsNullOrEmpty($FilePath)) { + $isValid = $false + $errorMessages += "FilePath parameter is required in non-interactive mode" + } + elseif (-not (Test-Path -Path $FilePath -IsValid)) { + $isValid = $false + $errorMessages += "Invalid file path provided: $FilePath" + } + + # Validate date parameters + if (-not ($StartDate -or $DaysToLookBack)) { + $isValid = $false + $errorMessages += "Either StartDate or DaysToLookBack must be specified in non-interactive mode" + } + + if ($StartDate -and -not $EndDate) { + $isValid = $false + $errorMessages += "EndDate must be specified when using StartDate in non-interactive mode" + } + } + + # Validate DaysToLookBack regardless of mode + if ($DaysToLookBack) { + if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { + $isValid = $false + $errorMessages += "DaysToLookBack must be between 1 and 365" + } + } + + # Validate date range if both dates provided + if ($StartDate -and $EndDate) { + # Convert to UTC for consistent comparison + $utcStartDate = $StartDate.ToUniversalTime() + $utcEndDate = $EndDate.ToUniversalTime() + $currentDate = (Get-Date).ToUniversalTime() + + if ($utcStartDate -gt $utcEndDate) { + $isValid = $false + $errorMessages += "StartDate must be before EndDate" + } + + if ($utcEndDate -gt $currentDate) { + $isValid = $false + $errorMessages += "EndDate cannot be in the future" + } + + $daysDifference = ($utcEndDate - $utcStartDate).Days + if ($daysDifference -gt 365) { + $isValid = $false + $errorMessages += "Date range cannot exceed 365 days" + } + } + + # Return validation results + [PSCustomObject]@{ + IsValid = $isValid + ErrorMessages = $errorMessages + } +} From ee4bfe11b0c00d32f2fdd974483cbae934844b91 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 14:41:21 -0500 Subject: [PATCH 02/19] Update Start-HawkTenatnInvestigation to allow for command line parameter passing. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 297 +++++++++++------- .../Test-HawkInvestigationParameter.ps1 | 83 +++-- 2 files changed, 222 insertions(+), 158 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index 487bda2..0828d6e 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -1,125 +1,194 @@ Function Start-HawkTenantInvestigation { - <# -.SYNOPSIS - Gathers common data about a tenant. -.DESCRIPTION - Runs all Hawk Basic tenant related cmdlets and gathers data about the tenant's configuration, - security settings, and audit logs. This comprehensive investigation helps identify potential - security issues and configuration changes. - -.PARAMETER Confirm - Prompts for confirmation before running operations that could modify system state. - -.PARAMETER WhatIf - Shows what would happen if the command runs. The command is not run. - -.EXAMPLE - PS C:\> Start-HawkTenantInvestigation - Runs a complete tenant investigation, gathering all available data. - -.EXAMPLE - PS C:\> Start-HawkTenantInvestigation -WhatIf - Shows what data gathering operations would be performed without executing them. - -.EXAMPLE - PS C:\> Start-HawkTenantInvestigation -Confirm - Prompts for confirmation before running each data gathering operation. - -.OUTPUTS - Various CSV and files containing investigation results. - See help from individual cmdlets for specific output details. - All outputs are placed in the $Hawk.FilePath directory. -#> + <# + .SYNOPSIS + Validates parameters for Hawk investigation commands in both interactive and non-interactive modes. + + .DESCRIPTION + The Test-HawkInvestigationParameters function performs comprehensive validation of parameters used in Hawk's investigation commands. + It ensures that all required parameters are present and valid when running in non-interactive mode, while also validating date ranges + and other constraints that apply in both modes. + + The function validates: + - File path existence and validity + - Presence of required date parameters in non-interactive mode + - Date range constraints (max 365 days, start before end) + - DaysToLookBack value constraints (1-365 days) + - Future date restrictions + + When validation fails, the function returns detailed error messages explaining which validations failed and why. + These messages can be used to provide clear guidance to users about how to correct their parameter usage. + + .PARAMETER StartDate + The beginning date for the investigation period. Must be provided with EndDate in non-interactive mode. + Cannot be later than EndDate or result in a date range exceeding 365 days. + + .PARAMETER EndDate + The ending date for the investigation period. Must be provided with StartDate in non-interactive mode. + Cannot be in the future or result in a date range exceeding 365 days. + + .PARAMETER DaysToLookBack + Alternative to StartDate/EndDate. Specifies the number of days to look back from the current date. + Must be between 1 and 365. Cannot be used together with StartDate/EndDate parameters. + + .PARAMETER FilePath + The file system path where investigation results will be stored. + Must be a valid file system path. Required in non-interactive mode. + + .PARAMETER NonInteractive + Switch that indicates whether Hawk is running in non-interactive mode. + When true, enforces stricter parameter validation requirements. + + .OUTPUTS + PSCustomObject with two properties: + - IsValid (bool): Indicates whether all validations passed + - ErrorMessages (string[]): Array of error messages when validation fails + + .EXAMPLE + $validation = Test-HawkInvestigationParameters -StartDate "2024-01-01" -EndDate "2024-01-31" -FilePath "C:\Investigation" -NonInteractive + + Validates parameters for investigating January 2024 in non-interactive mode. + + .EXAMPLE + $validation = Test-HawkInvestigationParameters -DaysToLookBack 30 -FilePath "C:\Investigation" -NonInteractive + + Validates parameters for a 30-day lookback investigation in non-interactive mode. + + .NOTES + This is an internal function used by Start-HawkTenantInvestigation and Start-HawkUserInvestigation. + It is not intended to be called directly by users of the Hawk module. + + All datetime operations use UTC internally for consistency. + #> [CmdletBinding(SupportsShouldProcess)] - param() + param ( + [DateTime]$StartDate, + [DateTime]$EndDate, + [ValidateRange(1, 365)] + [int]$DaysToLookBack, + [string]$FilePath, + [switch]$SkipUpdate, + [switch]$NonInteractive + ) - # Check if Hawk object exists and is fully initialized - if (Test-HawkGlobalObject) { - Initialize-HawkGlobalObject + + begin { + # Validate parameters if in non-interactive mode + if ($NonInteractive) { + $validation = Test-HawkInvestigationParameters -StartDate $StartDate -EndDate $EndDate ` + -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive + + if (-not $validation.IsValid) { + foreach ($error in $validation.ErrorMessages) { + Stop-PSFFunction -Message $error -EnableException $true + } + } + } + + try { + # Initialize with provided parameters + Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack ` + -FilePath $FilePath -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + } + catch { + Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true + } } + + process { - Out-LogFile "Starting Tenant Sweep" -action - Send-AIEvent -Event "CmdRun" - - # Wrap operations in ShouldProcess checks - if ($PSCmdlet.ShouldProcess("Tenant Configuration", "Get configuration data")) { - Out-LogFile "Running Get-HawkTenantConfiguration" -action - Get-HawkTenantConfiguration - } - - if ($PSCmdlet.ShouldProcess("EDiscovery Configuration", "Get eDiscovery configuration")) { - Out-LogFile "Running Get-HawkTenantEDiscoveryConfiguration" -action - Get-HawkTenantEDiscoveryConfiguration - } - - if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Creation Audit Log", "Search Admin Inbox Rule Creation")) { - Out-LogFile "Running Get-HawkTenantAdminInboxRuleCreation" -action - Get-HawkTenantAdminInboxRuleCreation - } - - if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Modification Audit Log", "Search Admin Inbox Rule Modification")) { - Out-LogFile "Running Get-HawkTenantInboxRuleModification" -action - Get-HawkTenantAdminInboxRuleModification - } + if (Test-PSFFunctionInterrupt) { return } - if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Removal Audit Log", "Search Admin Inbox Rule Removal")) { - Out-LogFile "Running Get-HawkTenantAdminInboxRuleRemoval" -action - Get-HawkTenantAdminInboxRuleRemoval - } - - if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Permission Change Audit Log", "Search Admin Inbox Permission Changes")) { - Out-LogFile "Running Get-HawkTenantAdminMailboxPermissionChange" -action - Get-HawkTenantAdminMailboxPermissionChange - } + # Check if Hawk object exists and is fully initialized + if (Test-HawkGlobalObject) { + Initialize-HawkGlobalObject + } - if ($PSCmdlet.ShouldProcess("Admin Email Forwarding Change Change Audit Log", "Search Admin Email Forwarding Changes")) { - Out-LogFile "Running Get-HawkTenantAdminEmailForwardingChange" -action - Get-HawkTenantAdminEmailForwardingChange - } - - - if ($PSCmdlet.ShouldProcess("EDiscovery Logs", "Get eDiscovery logs")) { - Out-LogFile "Running Get-HawkTenantEDiscoveryLog" -action - Get-HawkTenantEDiscoveryLog - } - - if ($PSCmdlet.ShouldProcess("Domain Activity", "Get domain activity")) { - Out-LogFile "Running Get-HawkTenantDomainActivity" -action - Get-HawkTenantDomainActivity - } - - if ($PSCmdlet.ShouldProcess("RBAC Changes", "Get RBAC changes")) { - Out-LogFile "Running Get-HawkTenantRBACChange" -action - Get-HawkTenantRBACChange - } - - if ($PSCmdlet.ShouldProcess("Azure App Audit Log", "Get app audit logs")) { - Out-LogFile "Running Get-HawkTenantAzureAppAuditLog" -action - Get-HawkTenantAzureAppAuditLog - } - - if ($PSCmdlet.ShouldProcess("Exchange Admins", "Get Exchange admin list")) { - Out-LogFile "Running Get-HawkTenantEXOAdmin" -action - Get-HawkTenantEXOAdmin - } - - if ($PSCmdlet.ShouldProcess("Consent Grants", "Get consent grants")) { - Out-LogFile "Running Get-HawkTenantConsentGrant" -action - Get-HawkTenantConsentGrant - } - - if ($PSCmdlet.ShouldProcess("Entra ID Admins", "Get Entra ID admin list")) { - Out-LogFile "Running Get-HawkTenantEntraIDAdmin" -action - Get-HawkTenantEntraIDAdmin - } + Out-LogFile "Starting Tenant Sweep" -action + Send-AIEvent -Event "CmdRun" + + # Wrap operations in ShouldProcess checks + if ($PSCmdlet.ShouldProcess("Tenant Configuration", "Get configuration data")) { + Out-LogFile "Running Get-HawkTenantConfiguration" -action + Get-HawkTenantConfiguration + } + + if ($PSCmdlet.ShouldProcess("EDiscovery Configuration", "Get eDiscovery configuration")) { + Out-LogFile "Running Get-HawkTenantEDiscoveryConfiguration" -action + Get-HawkTenantEDiscoveryConfiguration + } + + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Creation Audit Log", "Search Admin Inbox Rule Creation")) { + Out-LogFile "Running Get-HawkTenantAdminInboxRuleCreation" -action + Get-HawkTenantAdminInboxRuleCreation + } + + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Modification Audit Log", "Search Admin Inbox Rule Modification")) { + Out-LogFile "Running Get-HawkTenantInboxRuleModification" -action + Get-HawkTenantAdminInboxRuleModification + } + + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Removal Audit Log", "Search Admin Inbox Rule Removal")) { + Out-LogFile "Running Get-HawkTenantAdminInboxRuleRemoval" -action + Get-HawkTenantAdminInboxRuleRemoval + } + + if ($PSCmdlet.ShouldProcess("Admin Inbox Rule Permission Change Audit Log", "Search Admin Inbox Permission Changes")) { + Out-LogFile "Running Get-HawkTenantAdminMailboxPermissionChange" -action + Get-HawkTenantAdminMailboxPermissionChange + } + + if ($PSCmdlet.ShouldProcess("Admin Email Forwarding Change Change Audit Log", "Search Admin Email Forwarding Changes")) { + Out-LogFile "Running Get-HawkTenantAdminEmailForwardingChange" -action + Get-HawkTenantAdminEmailForwardingChange + } + + + if ($PSCmdlet.ShouldProcess("EDiscovery Logs", "Get eDiscovery logs")) { + Out-LogFile "Running Get-HawkTenantEDiscoveryLog" -action + Get-HawkTenantEDiscoveryLog + } + + if ($PSCmdlet.ShouldProcess("Domain Activity", "Get domain activity")) { + Out-LogFile "Running Get-HawkTenantDomainActivity" -action + Get-HawkTenantDomainActivity + } + + if ($PSCmdlet.ShouldProcess("RBAC Changes", "Get RBAC changes")) { + Out-LogFile "Running Get-HawkTenantRBACChange" -action + Get-HawkTenantRBACChange + } + + if ($PSCmdlet.ShouldProcess("Azure App Audit Log", "Get app audit logs")) { + Out-LogFile "Running Get-HawkTenantAzureAppAuditLog" -action + Get-HawkTenantAzureAppAuditLog + } + + if ($PSCmdlet.ShouldProcess("Exchange Admins", "Get Exchange admin list")) { + Out-LogFile "Running Get-HawkTenantEXOAdmin" -action + Get-HawkTenantEXOAdmin + } + + if ($PSCmdlet.ShouldProcess("Consent Grants", "Get consent grants")) { + Out-LogFile "Running Get-HawkTenantConsentGrant" -action + Get-HawkTenantConsentGrant + } + + if ($PSCmdlet.ShouldProcess("Entra ID Admins", "Get Entra ID admin list")) { + Out-LogFile "Running Get-HawkTenantEntraIDAdmin" -action + Get-HawkTenantEntraIDAdmin + } + + if ($PSCmdlet.ShouldProcess("App and SPN Credentials", "Get credential details")) { + Out-LogFile "Running Get-HawkTenantAppAndSPNCredentialDetail" -action + Get-HawkTenantAppAndSPNCredentialDetail + } + + if ($PSCmdlet.ShouldProcess("Entra ID Users", "Get Entra ID user list")) { + Out-LogFile "Running Get-HawkTenantEntraIDUser" -action + Get-HawkTenantEntraIDUser + } - if ($PSCmdlet.ShouldProcess("App and SPN Credentials", "Get credential details")) { - Out-LogFile "Running Get-HawkTenantAppAndSPNCredentialDetail" -action - Get-HawkTenantAppAndSPNCredentialDetail } - if ($PSCmdlet.ShouldProcess("Entra ID Users", "Get Entra ID user list")) { - Out-LogFile "Running Get-HawkTenantEntraIDUser" -action - Get-HawkTenantEntraIDUser - } + } \ No newline at end of file diff --git a/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 b/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 index 8c3e673..7bfb0a1 100644 --- a/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 +++ b/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 @@ -2,70 +2,65 @@ Function Test-HawkInvestigationParameter { <# .SYNOPSIS - Validates investigation parameters to ensure they meet expected requirements. + Validates parameters for Hawk investigation commands in both interactive and non-interactive modes. .DESCRIPTION - This function performs internal validation of parameters used for Hawk investigations, such as StartDate, EndDate, DaysToLookBack, and FilePath. - It checks for missing or invalid values and enforces rules around date ranges and file paths, ensuring investigations are configured correctly - before proceeding. + The Test-HawkInvestigationParameters function performs comprehensive validation of parameters used in Hawk's investigation commands. + It ensures that all required parameters are present and valid when running in non-interactive mode, while also validating date ranges + and other constraints that apply in both modes. + + The function validates: + - File path existence and validity + - Presence of required date parameters in non-interactive mode + - Date range constraints (max 365 days, start before end) + - DaysToLookBack value constraints (1-365 days) + - Future date restrictions + + When validation fails, the function returns detailed error messages explaining which validations failed and why. + These messages can be used to provide clear guidance to users about how to correct their parameter usage. .PARAMETER StartDate - Specifies the start date of the investigation period. This date must be provided in a valid DateTime format and cannot be more recent than EndDate. - When used in conjunction with EndDate, the date range must not exceed 365 days. + The beginning date for the investigation period. Must be provided with EndDate in non-interactive mode. + Cannot be later than EndDate or result in a date range exceeding 365 days. .PARAMETER EndDate - Specifies the end date of the investigation period. This date must be provided in a valid DateTime format and cannot be in the future. - If StartDate is provided, EndDate must also be specified. + The ending date for the investigation period. Must be provided with StartDate in non-interactive mode. + Cannot be in the future or result in a date range exceeding 365 days. .PARAMETER DaysToLookBack - Specifies the number of days to look back from the current date to gather log data. The value must be an integer between 1 and 365. - This parameter is typically used as an alternative to specifying a StartDate and EndDate. + Alternative to StartDate/EndDate. Specifies the number of days to look back from the current date. + Must be between 1 and 365. Cannot be used together with StartDate/EndDate parameters. .PARAMETER FilePath - Specifies the directory path where investigation output files will be saved. This path must be valid and accessible. - The parameter is required in non-interactive mode to ensure logs are written to a specified location. + The file system path where investigation results will be stored. + Must be a valid file system path. Required in non-interactive mode. .PARAMETER NonInteractive - A switch parameter that indicates the function is running in non-interactive mode. In this mode, required parameters must be provided upfront, - as user prompts are disabled. This is typically used in automated scripts or CI/CD pipelines. + Switch that indicates whether Hawk is running in non-interactive mode. + When true, enforces stricter parameter validation requirements. .OUTPUTS - Returns a custom PowerShell object with two properties: - - IsValid: A boolean value indicating whether the parameters passed validation. - - ErrorMessages: An array of error messages explaining why validation failed (if applicable). - - .NOTES - - The function converts StartDate and EndDate to UTC to ensure consistent date comparisons. - - If a date range exceeds 365 days or if EndDate is in the future, the function returns a validation error. - - DaysToLookBack must be between 1 and 365 to comply with log retention policies in Microsoft 365. - - .EXAMPLE - Test-HawkInvestigationParameter -StartDate "2024-01-01" -EndDate "2024-03-31" -FilePath "C:\Logs" -NonInteractive - - This example validates the parameters for a non-interactive Hawk investigation. The function checks that the FilePath is valid, - the date range is within limits, and that both StartDate and EndDate are provided. + PSCustomObject with two properties: + - IsValid (bool): Indicates whether all validations passed + - ErrorMessages (string[]): Array of error messages when validation fails .EXAMPLE - Test-HawkInvestigationParameter -DaysToLookBack 90 -FilePath "C:\Logs" - - This example validates an investigation configured to look back 90 days from the current date. The function ensures that DaysToLookBack - is within the allowable range and that the FilePath is valid. + $validation = Test-HawkInvestigationParameters -StartDate "2024-01-01" -EndDate "2024-01-31" -FilePath "C:\Investigation" -NonInteractive + + Validates parameters for investigating January 2024 in non-interactive mode. .EXAMPLE - $validationResult = Test-HawkInvestigationParameter -StartDate "2024-01-01" -EndDate "2024-02-15" -DaysToLookBack 45 - if (-not $validationResult.IsValid) { - $validationResult.ErrorMessages | ForEach-Object { Write-Host $_ -ForegroundColor Red } - } - - This example stores the validation result in a variable and outputs any error messages if the parameters failed validation. - - .LINK - https://cloudforensicator.com/ - - .LINK - https://docs.microsoft.com/en-us/powershell/scripting/ + $validation = Test-HawkInvestigationParameters -DaysToLookBack 30 -FilePath "C:\Investigation" -NonInteractive + + Validates parameters for a 30-day lookback investigation in non-interactive mode. + .NOTES + This is an internal function used by Start-HawkTenantInvestigation and Start-HawkUserInvestigation. + It is not intended to be called directly by users of the Hawk module. + + All datetime operations use UTC internally for consistency. #> + [CmdletBinding()] param ( [DateTime]$StartDate, From 4e46962e9fd68e16d7db400603f8ffc5211537cf Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 14:44:32 -0500 Subject: [PATCH 03/19] Update Start-HawkTenatnInvestigation to allow for command line parameter passing. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 103 ++++++++++++------ 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index 0828d6e..6d66738 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -1,30 +1,37 @@ Function Start-HawkTenantInvestigation { <# .SYNOPSIS - Validates parameters for Hawk investigation commands in both interactive and non-interactive modes. + Performs a comprehensive tenant-wide investigation using Hawk's automated data collection capabilities. .DESCRIPTION - The Test-HawkInvestigationParameters function performs comprehensive validation of parameters used in Hawk's investigation commands. - It ensures that all required parameters are present and valid when running in non-interactive mode, while also validating date ranges - and other constraints that apply in both modes. - - The function validates: - - File path existence and validity - - Presence of required date parameters in non-interactive mode - - Date range constraints (max 365 days, start before end) - - DaysToLookBack value constraints (1-365 days) - - Future date restrictions - - When validation fails, the function returns detailed error messages explaining which validations failed and why. - These messages can be used to provide clear guidance to users about how to correct their parameter usage. + Start-HawkTenantInvestigation automates the collection and analysis of Microsoft 365 tenant-wide security data. + It gathers information about tenant configuration, security settings, administrative changes, and potential security + issues across the environment. + + The command can run in either interactive mode (default) or non-interactive mode. In interactive mode, it prompts + for necessary information such as date ranges and output location. In non-interactive mode, these must be provided + as parameters. + + Data collected includes: + - Tenant configuration settings + - eDiscovery configuration and logs + - Administrative changes and permissions + - Domain activities + - Application consents and credentials + - Exchange Online administrative activities + + All collected data is stored in a structured format for analysis, with suspicious findings highlighted + for investigation. .PARAMETER StartDate - The beginning date for the investigation period. Must be provided with EndDate in non-interactive mode. - Cannot be later than EndDate or result in a date range exceeding 365 days. + The beginning date for the investigation period. When specified, must be used with EndDate. + Cannot be later than EndDate and the date range cannot exceed 365 days. + Format: MM/DD/YYYY .PARAMETER EndDate - The ending date for the investigation period. Must be provided with StartDate in non-interactive mode. - Cannot be in the future or result in a date range exceeding 365 days. + The ending date for the investigation period. When specified, must be used with StartDate. + Cannot be in the future and the date range cannot exceed 365 days. + Format: MM/DD/YYYY .PARAMETER DaysToLookBack Alternative to StartDate/EndDate. Specifies the number of days to look back from the current date. @@ -32,32 +39,62 @@ .PARAMETER FilePath The file system path where investigation results will be stored. - Must be a valid file system path. Required in non-interactive mode. + Required in non-interactive mode. Must be a valid file system path. + + .PARAMETER SkipUpdate + Switch to bypass the automatic check for Hawk module updates. + Useful in automated scenarios or air-gapped environments. .PARAMETER NonInteractive - Switch that indicates whether Hawk is running in non-interactive mode. - When true, enforces stricter parameter validation requirements. + Switch to run the command in non-interactive mode. Requires all necessary parameters + to be provided via command line rather than through interactive prompts. + + .PARAMETER Confirm + Prompts you for confirmation before executing each investigation step. + By default, confirmation prompts appear for operations that could collect sensitive data. + + .PARAMETER WhatIf + Shows what would happen if the command runs. The command is not executed. + Use this parameter to understand which investigation steps would be performed without actually collecting data. .OUTPUTS - PSCustomObject with two properties: - - IsValid (bool): Indicates whether all validations passed - - ErrorMessages (string[]): Array of error messages when validation fails + Creates multiple CSV and JSON files containing investigation results. + All outputs are placed in the specified FilePath directory. + See individual cmdlet help for specific output details. + + .EXAMPLE + Start-HawkTenantInvestigation + + Runs a tenant investigation in interactive mode, prompting for date range and output location. + + .EXAMPLE + Start-HawkTenantInvestigation -DaysToLookBack 30 -FilePath "C:\Investigation" -NonInteractive + + Performs a tenant investigation looking back 30 days from today, saving results to C:\Investigation. + Runs without any interactive prompts. .EXAMPLE - $validation = Test-HawkInvestigationParameters -StartDate "2024-01-01" -EndDate "2024-01-31" -FilePath "C:\Investigation" -NonInteractive - - Validates parameters for investigating January 2024 in non-interactive mode. + Start-HawkTenantInvestigation -StartDate "01/01/2024" -EndDate "01/31/2024" -FilePath "C:\Investigation" -NonInteractive -SkipUpdate + + Investigates tenant activity for January 2024, saving results to C:\Investigation. + Skips the update check and runs without prompts. .EXAMPLE - $validation = Test-HawkInvestigationParameters -DaysToLookBack 30 -FilePath "C:\Investigation" -NonInteractive - - Validates parameters for a 30-day lookback investigation in non-interactive mode. + Start-HawkTenantInvestigation -WhatIf + + Shows what investigation steps would be performed without actually executing them. + Useful for understanding the investigation process or validating parameters. .NOTES - This is an internal function used by Start-HawkTenantInvestigation and Start-HawkUserInvestigation. - It is not intended to be called directly by users of the Hawk module. - + Requires appropriate Microsoft 365 administrative permissions. All datetime operations use UTC internally for consistency. + Large date ranges may result in longer processing times. + + .LINK + https://cloudforensicator.com + + .LINK + https://github.com/T0pCyber/hawk #> [CmdletBinding(SupportsShouldProcess)] param ( From 529636dc9282b026620d426e9f5ef1abc0e6514b Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 15:28:06 -0500 Subject: [PATCH 04/19] Add initial ability to pass command line parameters to stat-hawkTenantinvestigation --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 2 +- .../functions/Initialize-HawkGlobalObject.ps1 | 500 ++++++++++-------- 2 files changed, 272 insertions(+), 230 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index 6d66738..c88e3c0 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -111,7 +111,7 @@ begin { # Validate parameters if in non-interactive mode if ($NonInteractive) { - $validation = Test-HawkInvestigationParameters -StartDate $StartDate -EndDate $EndDate ` + $validation = Test-HawkInvestigationParameter -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive if (-not $validation.IsValid) { diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index c473ddd..d0ef065 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -39,11 +39,13 @@ [CmdletBinding()] param ( - [switch]$Force, - [switch]$SkipUpdate, [DateTime]$StartDate, [DateTime]$EndDate, - [string]$FilePath + [int]$DaysToLookBack, + [string]$FilePath, + [switch]$SkipUpdate, + [switch]$NonInteractive, + [switch]$Force ) @@ -132,7 +134,8 @@ Function Set-LoggingPath { [CmdletBinding(SupportsShouldProcess)] - param ([string]$Path) + param ( + [string]$Path) # Get the current timestamp in the format yyyy-MM-dd HH:mm:ssZ $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss'Z'") @@ -190,268 +193,307 @@ ### Main ### $InformationPreference = "Continue" - if (($null -eq (Get-Variable -Name Hawk -ErrorAction SilentlyContinue)) -or ($Force -eq $true) -or ($null -eq $Hawk)) { + Write-HawkBanner + + if (-not $NonInteractive) { + + $OutputPath = Set-LoggingPath($FilePath) - Write-HawkBanner - # Create the global $Hawk variable immediately with minimal properties $Global:Hawk = [PSCustomObject]@{ - FilePath = $null # Will be set shortly - DaysToLookBack = $null - StartDate = $null - EndDate = $null + FilePath = $OutputPath + DaysToLookBack = $DaysToLookBack + StartDate = $StartDate + EndDate = $EndDate WhenCreated = $null } - # Set up the file path first, before any other operations - if ([string]::IsNullOrEmpty($FilePath)) { - # Suppress Graph connection output during initial path setup - $Hawk.FilePath = Set-LoggingPath -ErrorAction Stop - } - else { - $Hawk.FilePath = Set-LoggingPath -path $FilePath -ErrorAction Stop 2>$null - } - # Now that FilePath is set, we can use Out-LogFile - Out-LogFile "Hawk output directory created at: $($Hawk.FilePath)" -Information - - # Setup Application insights - Out-LogFile "Setting up Application Insights" -Action - New-ApplicationInsight - ### Checking for Updates ### - # If we are skipping the update log it - if ($SkipUpdate) { - Out-LogFile -string "Skipping Update Check" -Information - } - # Check to see if there is an Update for Hawk - else { - Update-HawkModule - } - - # Test Graph connection - Out-LogFile "Testing Graph Connection" -Action - - Test-GraphConnection - - try { - $LicenseInfo = Test-LicenseType - $MaxDaysToGoBack = $LicenseInfo.RetentionPeriod - $LicenseType = $LicenseInfo.LicenseType - - Out-LogFile -string "Detecting M365 license type to determine maximum log retention period" -action - Out-LogFile -string "M365 License type detected: $LicenseType" -Information - Out-LogFile -string "Max log retention: $MaxDaysToGoBack days" -action -NoNewLine - - } catch { - Out-LogFile -string "Failed to detect license type. Max days of log retention is unknown." -Information - $MaxDaysToGoBack = 90 - $LicenseType = "Unknown" + # Configuration Example, currently not used + #TODO: Implement Configuration system across entire project + # Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig + if ($OutputPath) { + Set-PSFConfig -Module 'Hawk' -Name 'FilePath' -Value $OutputPath -PassThru | Register-PSFConfig } - # Ensure MaxDaysToGoBack does not exceed 365 days - if ($MaxDaysToGoBack -gt 365) { $MaxDaysToGoBack = 365 } - - # Start date validation: Add check for negative numbers - while ($null -eq $StartDate) { - Write-Output "`n" - Out-LogFile "Please specify the first day of the search window:" -isPrompt - Out-LogFile " Enter a number of days to go back (1-$MaxDaysToGoBack)" -isPrompt - Out-LogFile " OR enter a date in MM/DD/YYYY format" -isPrompt - Out-LogFile " Default is 90 days back: " -isPrompt -NoNewLine - $StartRead = (Read-Host).Trim() - - # Determine if input is a valid date - if ($null -eq ($StartRead -as [DateTime])) { - #### Not a DateTime #### - if ([string]::IsNullOrEmpty($StartRead)) { - $StartRead = 90 - } - - # Validate input is a positive number - if ($StartRead -match '^\-') { - Out-LogFile -string "Please enter a positive number of days." -isError - continue - } - - # Validate numeric value - if ($StartRead -notmatch '^\d+$') { - Out-LogFile -string "Please enter a valid number of days." -isError - continue - } - - # Validate the entered days back - if ($StartRead -gt $MaxDaysToGoBack) { - Out-LogFile -string "You have entered a time frame greater than your license allows ($MaxDaysToGoBack days)." -isWarning - Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the value: " -isPrompt -NoNewLine - $Proceed = (Read-Host).Trim() - if ($Proceed -eq 'R') { continue } - } - - if ($StartRead -gt 365) { - Out-LogFile -string "Log retention cannot exceed 365 days. Setting retention to 365 days." -isWarning - $StartRead = 365 - } + # Continue populating the Hawk object with other properties + # $Hawk.DaysToLookBack = $Days + $Hawk.StartDate = $StartDate + $Hawk.EndDate = $EndDate + $Hawk.WhenCreated = (Get-Date).ToUniversalTime().ToString("g") - # Calculate start date - [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date - Write-Output "" - Out-LogFile -string "Start date set to: $StartDate [UTC]" -Information + Write-HawkConfigurationComplete -Hawk $Hawk + + return + + } else { + if (($null -eq (Get-Variable -Name Hawk -ErrorAction SilentlyContinue)) -or ($Force -eq $true) -or ($null -eq $Hawk)) { + Write-HawkBanner + + # Create the global $Hawk variable immediately with minimal properties + $Global:Hawk = [PSCustomObject]@{ + FilePath = $null # Will be set shortly + DaysToLookBack = $null + StartDate = $null + EndDate = $null + WhenCreated = $null } - # Handle DateTime input - elseif (!($null -eq ($StartRead -as [DateTime]))) { - [DateTime]$StartDate = (Get-Date $StartRead).ToUniversalTime().Date - - # Validate the date - if ($StartDate -gt (Get-Date).ToUniversalTime()) { - Out-LogFile -string "Start date cannot be in the future." -isError - $StartDate = $null - continue - } - - if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-$MaxDaysToGoBack))) { - Out-LogFile -string "The date entered exceeds your license retention period of $MaxDaysToGoBack days." -isWarning - Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the date:" -isPrompt -NoNewLine - $Proceed = (Read-Host).Trim() - if ($Proceed -eq 'R') { $StartDate = $null; continue } - } - - if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-365))) { - Out-LogFile -string "The date cannot exceed 365 days. Setting to the maximum limit of 365 days." -isWarning - [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-365)).Date - - } - - Out-LogFile -string "Start Date (UTC): $StartDate" -Information + + # Set up the file path first, before any other operations + if ([string]::IsNullOrEmpty($FilePath)) { + # Suppress Graph connection output during initial path setup + $Hawk.FilePath = Set-LoggingPath -ErrorAction Stop } else { - Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError - $StartDate = $null - continue + $Hawk.FilePath = Set-LoggingPath -path $FilePath -ErrorAction Stop 2>$null } - } - - # End date logic with enhanced validation - while ($null -eq $EndDate) { - Write-Output "`n" - Out-LogFile "Please specify the last day of the search window:" -isPrompt - Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt - Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt - Out-LogFile " Default is today's date:" -isPrompt -NoNewLine - $EndRead = (Read-Host).Trim() - - # End date validation - if ($null -eq ($EndRead -as [DateTime])) { - if ([string]::IsNullOrEmpty($EndRead)) { - [DateTime]$tempEndDate = (Get-Date).ToUniversalTime().Date - } - else { + + # Now that FilePath is set, we can use Out-LogFile + Out-LogFile "Hawk output directory created at: $($Hawk.FilePath)" -Information + + # Setup Application insights + Out-LogFile "Setting up Application Insights" -Action + New-ApplicationInsight + + ### Checking for Updates ### + # If we are skipping the update log it + if ($SkipUpdate) { + Out-LogFile -string "Skipping Update Check" -Information + } + # Check to see if there is an Update for Hawk + else { + Update-HawkModule + } + + # Test Graph connection + Out-LogFile "Testing Graph Connection" -Action + + Test-GraphConnection + + try { + $LicenseInfo = Test-LicenseType + $MaxDaysToGoBack = $LicenseInfo.RetentionPeriod + $LicenseType = $LicenseInfo.LicenseType + + Out-LogFile -string "Detecting M365 license type to determine maximum log retention period" -action + Out-LogFile -string "M365 License type detected: $LicenseType" -Information + Out-LogFile -string "Max log retention: $MaxDaysToGoBack days" -action -NoNewLine + + } catch { + Out-LogFile -string "Failed to detect license type. Max days of log retention is unknown." -Information + $MaxDaysToGoBack = 90 + $LicenseType = "Unknown" + } + + # Ensure MaxDaysToGoBack does not exceed 365 days + if ($MaxDaysToGoBack -gt 365) { $MaxDaysToGoBack = 365 } + + # Start date validation: Add check for negative numbers + while ($null -eq $StartDate) { + Write-Output "`n" + Out-LogFile "Please specify the first day of the search window:" -isPrompt + Out-LogFile " Enter a number of days to go back (1-$MaxDaysToGoBack)" -isPrompt + Out-LogFile " OR enter a date in MM/DD/YYYY format" -isPrompt + Out-LogFile " Default is 90 days back: " -isPrompt -NoNewLine + $StartRead = (Read-Host).Trim() + + # Determine if input is a valid date + if ($null -eq ($StartRead -as [DateTime])) { + #### Not a DateTime #### + if ([string]::IsNullOrEmpty($StartRead)) { + $StartRead = 90 + } + # Validate input is a positive number - if ($EndRead -match '^\-') { + if ($StartRead -match '^\-') { Out-LogFile -string "Please enter a positive number of days." -isError continue } - + # Validate numeric value - if ($EndRead -notmatch '^\d+$') { + if ($StartRead -notmatch '^\d+$') { Out-LogFile -string "Please enter a valid number of days." -isError continue } - - Out-LogFile -string "End Date (UTC): $EndRead days." -Information - [DateTime]$tempEndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date + + # Validate the entered days back + if ($StartRead -gt $MaxDaysToGoBack) { + Out-LogFile -string "You have entered a time frame greater than your license allows ($MaxDaysToGoBack days)." -isWarning + Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the value: " -isPrompt -NoNewLine + $Proceed = (Read-Host).Trim() + if ($Proceed -eq 'R') { continue } + } + + if ($StartRead -gt 365) { + Out-LogFile -string "Log retention cannot exceed 365 days. Setting retention to 365 days." -isWarning + $StartRead = 365 + } + + # Calculate start date + [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date + Write-Output "" + Out-LogFile -string "Start date set to: $StartDate [UTC]" -Information + } - - if ($StartDate -gt $tempEndDate) { - Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError - continue + # Handle DateTime input + elseif (!($null -eq ($StartRead -as [DateTime]))) { + [DateTime]$StartDate = (Get-Date $StartRead).ToUniversalTime().Date + + # Validate the date + if ($StartDate -gt (Get-Date).ToUniversalTime()) { + Out-LogFile -string "Start date cannot be in the future." -isError + $StartDate = $null + continue + } + + if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-$MaxDaysToGoBack))) { + Out-LogFile -string "The date entered exceeds your license retention period of $MaxDaysToGoBack days." -isWarning + Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the date:" -isPrompt -NoNewLine + $Proceed = (Read-Host).Trim() + if ($Proceed -eq 'R') { $StartDate = $null; continue } + } + + if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-365))) { + Out-LogFile -string "The date cannot exceed 365 days. Setting to the maximum limit of 365 days." -isWarning + [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-365)).Date + + } + + Out-LogFile -string "Start Date (UTC): $StartDate" -Information } - - $EndDate = $tempEndDate - Write-Output "" - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information - } - elseif (!($null -eq ($EndRead -as [DateTime]))) { - - [DateTime]$tempEndDate = (Get-Date $EndRead).ToUniversalTime().Date - - if ($StartDate -gt $tempEndDate) { - Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError + else { + Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError + $StartDate = $null continue } - elseif ($tempEndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { - Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning - $tempEndDate = (Get-Date).ToUniversalTime().Date - } - - $EndDate = $tempEndDate - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information } - else { - Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError - continue - } - } - # End date logic remains unchanged - if ($null -eq $EndDate) { - Write-Output "`n" - Out-LogFile "Please specify the last day of the search window:" -isPrompt - Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt - Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt - Out-LogFile " Default is today's date:" -isPrompt -NoNewLine - $EndRead = (Read-Host).Trim() - - # End date validation - if ($null -eq ($EndRead -as [DateTime])) { - if ([string]::IsNullOrEmpty($EndRead)) { - [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date - } else { - Out-LogFile -string "End Date (UTC): $EndRead days." -Information - [DateTime]$EndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date - } - - if ($StartDate -gt $EndDate) { - Out-LogFile -string "StartDate cannot be more recent than EndDate" -isError - } else { + + # End date logic with enhanced validation + while ($null -eq $EndDate) { + Write-Output "`n" + Out-LogFile "Please specify the last day of the search window:" -isPrompt + Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt + Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt + Out-LogFile " Default is today's date:" -isPrompt -NoNewLine + $EndRead = (Read-Host).Trim() + + # End date validation + if ($null -eq ($EndRead -as [DateTime])) { + if ([string]::IsNullOrEmpty($EndRead)) { + [DateTime]$tempEndDate = (Get-Date).ToUniversalTime().Date + } + else { + # Validate input is a positive number + if ($EndRead -match '^\-') { + Out-LogFile -string "Please enter a positive number of days." -isError + continue + } + + # Validate numeric value + if ($EndRead -notmatch '^\d+$') { + Out-LogFile -string "Please enter a valid number of days." -isError + continue + } + + Out-LogFile -string "End Date (UTC): $EndRead days." -Information + [DateTime]$tempEndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date + } + + if ($StartDate -gt $tempEndDate) { + Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError + continue + } + + $EndDate = $tempEndDate Write-Output "" Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information } - } elseif (!($null -eq ($EndRead -as [DateTime]))) { - [DateTime]$EndDate = (Get-Date $EndRead).ToUniversalTime().Date - - if ($StartDate -gt $EndDate) { - Out-LogFile -string "EndDate is earlier than StartDate. Setting EndDate to today." -isWarning - [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date - } elseif ($EndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { - Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning - [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date + elseif (!($null -eq ($EndRead -as [DateTime]))) { + + [DateTime]$tempEndDate = (Get-Date $EndRead).ToUniversalTime().Date + + if ($StartDate -gt $tempEndDate) { + Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError + continue + } + elseif ($tempEndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { + Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning + $tempEndDate = (Get-Date).ToUniversalTime().Date + } + + $EndDate = $tempEndDate + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + } + else { + Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError + continue + } + } + # End date logic remains unchanged + if ($null -eq $EndDate) { + Write-Output "`n" + Out-LogFile "Please specify the last day of the search window:" -isPrompt + Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt + Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt + Out-LogFile " Default is today's date:" -isPrompt -NoNewLine + $EndRead = (Read-Host).Trim() + + # End date validation + if ($null -eq ($EndRead -as [DateTime])) { + if ([string]::IsNullOrEmpty($EndRead)) { + [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date + } else { + Out-LogFile -string "End Date (UTC): $EndRead days." -Information + [DateTime]$EndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date + } + + if ($StartDate -gt $EndDate) { + Out-LogFile -string "StartDate cannot be more recent than EndDate" -isError + } else { + Write-Output "" + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + } + } elseif (!($null -eq ($EndRead -as [DateTime]))) { + [DateTime]$EndDate = (Get-Date $EndRead).ToUniversalTime().Date + + if ($StartDate -gt $EndDate) { + Out-LogFile -string "EndDate is earlier than StartDate. Setting EndDate to today." -isWarning + [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date + } elseif ($EndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { + Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning + [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date + } + + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + } else { + Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError } - - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information - } else { - Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError } + + # Configuration Example, currently not used + #TODO: Implement Configuration system across entire project + Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig + if ($OutputPath) { + Set-PSFConfig -Module 'Hawk' -Name 'FilePath' -Value $OutputPath -PassThru | Register-PSFConfig + } + + # Continue populating the Hawk object with other properties + $Hawk.DaysToLookBack = $Days + $Hawk.StartDate = $StartDate + $Hawk.EndDate = $EndDate + $Hawk.WhenCreated = (Get-Date).ToUniversalTime().ToString("g") + + Write-HawkConfigurationComplete -Hawk $Hawk + + } - - # Configuration Example, currently not used - #TODO: Implement Configuration system across entire project - Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig - if ($OutputPath) { - Set-PSFConfig -Module 'Hawk' -Name 'FilePath' -Value $OutputPath -PassThru | Register-PSFConfig + else { + Out-LogFile -string "Valid Hawk Object already exists no actions will be taken." -Information } - # Continue populating the Hawk object with other properties - $Hawk.DaysToLookBack = $Days - $Hawk.StartDate = $StartDate - $Hawk.EndDate = $EndDate - $Hawk.WhenCreated = (Get-Date).ToUniversalTime().ToString("g") - - Write-HawkConfigurationComplete -Hawk $Hawk + } - } - else { - Out-LogFile -string "Valid Hawk Object already exists no actions will be taken." -Information - } } From 6eee8734c6cc6052caf4cd812d67df6caf60d286 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 15:29:31 -0500 Subject: [PATCH 05/19] Add initial ability to pass command line parameters to stat-hawkTenantinvestigation --- Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index d0ef065..8c01566 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -21,6 +21,9 @@ Last day that data will be retrieved (in UTC) .PARAMETER FilePath Provide an output file path. + .PARAMETER NonInteractive + Switch to run the command in non-interactive mode. Requires all necessary parameters + to be provided via command line rather than through interactive prompts. .OUTPUTS Creates the $Hawk global variable and populates it with a custom PS object with the following properties From 0c41e1a652bf916e5e1d68dc9b108c1d4730f6c1 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 15:42:20 -0500 Subject: [PATCH 06/19] Create convert-hawkdaystodates function. --- .../functions/Convert-HawkDaysToDate.ps1 | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Hawk/internal/functions/Convert-HawkDaysToDate.ps1 diff --git a/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 b/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 new file mode 100644 index 0000000..f4bd57c --- /dev/null +++ b/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 @@ -0,0 +1,44 @@ +Function Convert-HawkDaysToDate { + <# + .SYNOPSIS + Converts the DaysToLookBack parameter into a StartDate and EndDate for use in Hawk investigations. + + .DESCRIPTION + This function takes the number of days to look back from the current date and calculates the corresponding + StartDate and EndDate in UTC format. The StartDate is calculated by subtracting the specified number of days + from the current date, and the EndDate is set to one day in the future (to include the entire current day). + + .PARAMETER DaysToLookBack + The number of days to look back from the current date. Must be between 1 and 365. + + .OUTPUTS + A PSCustomObject with two properties: + - StartDate: The calculated start date in UTC format. + - EndDate: The calculated end date in UTC format (one day in the future). + + .EXAMPLE + Convert-HawkDaysToDates -DaysToLookBack 30 + Returns a StartDate of 30 days ago and an EndDate of tomorrow in UTC format. + + .NOTES + This function ensures that the date range does not exceed 365 days and that the dates are properly formatted + for use with Hawk investigation functions. + #> + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateRange(1, 365)] + [int]$DaysToLookBack + ) + + # Calculate the dates + $startDate = (Get-Date).ToUniversalTime().AddDays(-$DaysToLookBack).Date + $endDate = (Get-Date).ToUniversalTime().AddDays(1).Date + + # Return the dates as a PSCustomObject + [PSCustomObject]@{ + StartDate = $startDate + EndDate = $endDate + } +} From 49dab29357737e9e985f7eeef4d8bd0e99651321 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 20:28:47 -0500 Subject: [PATCH 07/19] Modify functions to pass all test cmd line argument test cases except test cases 8 - 10 --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 88 +++- .../functions/Convert-HawkDaysToDate.ps1 | 2 +- .../functions/Initialize-HawkGlobalObject.ps1 | 474 ++++++++---------- .../Test-HawkInvestigationParameter.ps1 | 1 + 4 files changed, 298 insertions(+), 267 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index c88e3c0..d1552c4 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -100,7 +100,6 @@ param ( [DateTime]$StartDate, [DateTime]$EndDate, - [ValidateRange(1, 365)] [int]$DaysToLookBack, [string]$FilePath, [switch]$SkipUpdate, @@ -108,29 +107,92 @@ ) + # begin { + # # Validate parameters if in non-interactive mode + # if ($NonInteractive) { + + + # # if ($NonInteractive) { + + # # Write-Output "ENTERING COMMAND LINE INTERFACE NON INTERACTIVE MODE" + + # # # Check if DaysToLookBack is valid before calling the conversion function + # # if ($DaysToLookBack -ne $null -and $DaysToLookBack -ge 1 -and $DaysToLookBack -le 365) { + # # $ConvertedDates = Convert-HawkDaysToDates -DaysToLookBack $DaysToLookBack + # # $StartDate = $ConvertedDates.StartDate + # # $EndDate = $ConvertedDates.EndDate + # # } + + # # } + # # if ($NonInteractive) { + + # # Write-Output "ENTERING COMMAND LINE INTERFACE NON INTERACTIVE MODE" + + # # # Check if DaysToLookBack is valid before calling the conversion function + # # if ($DaysToLookBack -ne $null -and $DaysToLookBack -ge 1 -and $DaysToLookBack -le 365) { + # # $ConvertedDates = Convert-HawkDaysToDates -DaysToLookBack $DaysToLookBack + # # $StartDate = $ConvertedDates.StartDate + # # $EndDate = $ConvertedDates.EndDate + # # } + + # # } + + + # $validation = Test-HawkInvestigationParameter -StartDate $StartDate -EndDate $EndDate ` + # -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive + + # if (-not $validation.IsValid) { + # foreach ($error in $validation.ErrorMessages) { + # Stop-PSFFunction -Message $error -EnableException $true + # } + # } + + + # try { + # # Initialize with provided parameters + # Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack ` + # -FilePath $FilePath -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + # } + # catch { + # Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true + # } + # } + + # } + + begin { - # Validate parameters if in non-interactive mode if ($NonInteractive) { - $validation = Test-HawkInvestigationParameter -StartDate $StartDate -EndDate $EndDate ` + # If DaysToLookBack is specified, convert it first + if ($PSBoundParameters.ContainsKey('DaysToLookBack') -and $DaysToLookBack -ge 1 -and $DaysToLookBack -le 365) { + $convertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack + $StartDate = $convertedDates.StartDate + $EndDate = $convertedDates.EndDate + } + + # Now call validation with updated StartDate/EndDate + $validation = Test-HawkInvestigationParameter ` + -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive - + if (-not $validation.IsValid) { foreach ($error in $validation.ErrorMessages) { Stop-PSFFunction -Message $error -EnableException $true } } - } - - try { - # Initialize with provided parameters - Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack ` - -FilePath $FilePath -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive - } - catch { - Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true + + try { + Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` + -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` + -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + } + catch { + Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true + } } } + process { if (Test-PSFFunctionInterrupt) { return } diff --git a/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 b/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 index f4bd57c..74b2594 100644 --- a/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 +++ b/Hawk/internal/functions/Convert-HawkDaysToDate.ps1 @@ -34,7 +34,7 @@ Function Convert-HawkDaysToDate { # Calculate the dates $startDate = (Get-Date).ToUniversalTime().AddDays(-$DaysToLookBack).Date - $endDate = (Get-Date).ToUniversalTime().AddDays(1).Date + $endDate = (Get-Date).ToUniversalTime().Date # Return the dates as a PSCustomObject [PSCustomObject]@{ diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index 8c01566..c253c15 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -196,85 +196,53 @@ ### Main ### $InformationPreference = "Continue" - Write-HawkBanner - - if (-not $NonInteractive) { - - $OutputPath = Set-LoggingPath($FilePath) + + if (($null -eq (Get-Variable -Name Hawk -ErrorAction SilentlyContinue)) -or ($Force -eq $true) -or ($null -eq $Hawk)) { + Write-HawkBanner + # Create the global $Hawk variable immediately with minimal properties $Global:Hawk = [PSCustomObject]@{ - FilePath = $OutputPath - DaysToLookBack = $DaysToLookBack - StartDate = $StartDate - EndDate = $EndDate + FilePath = $null # Will be set shortly + DaysToLookBack = $null + StartDate = $null + EndDate = $null WhenCreated = $null } + # Set up the file path first, before any other operations + if ([string]::IsNullOrEmpty($FilePath)) { + # Suppress Graph connection output during initial path setup + $Hawk.FilePath = Set-LoggingPath -ErrorAction Stop + } + else { + $Hawk.FilePath = Set-LoggingPath -path $FilePath -ErrorAction Stop 2>$null + } + # Now that FilePath is set, we can use Out-LogFile + Out-LogFile "Hawk output directory created at: $($Hawk.FilePath)" -Information + + # Setup Application insights + Out-LogFile "Setting up Application Insights" -Action + New-ApplicationInsight - # Configuration Example, currently not used - #TODO: Implement Configuration system across entire project - # Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig - if ($OutputPath) { - Set-PSFConfig -Module 'Hawk' -Name 'FilePath' -Value $OutputPath -PassThru | Register-PSFConfig + ### Checking for Updates ### + # If we are skipping the update log it + if ($SkipUpdate) { + Out-LogFile -string "Skipping Update Check" -Information + } + # Check to see if there is an Update for Hawk + else { + Update-HawkModule } - # Continue populating the Hawk object with other properties - # $Hawk.DaysToLookBack = $Days - $Hawk.StartDate = $StartDate - $Hawk.EndDate = $EndDate - $Hawk.WhenCreated = (Get-Date).ToUniversalTime().ToString("g") + # Test Graph connection + Out-LogFile "Testing Graph Connection" -Action - Write-HawkConfigurationComplete -Hawk $Hawk - - return - - } else { - if (($null -eq (Get-Variable -Name Hawk -ErrorAction SilentlyContinue)) -or ($Force -eq $true) -or ($null -eq $Hawk)) { + Test-GraphConnection - Write-HawkBanner - - # Create the global $Hawk variable immediately with minimal properties - $Global:Hawk = [PSCustomObject]@{ - FilePath = $null # Will be set shortly - DaysToLookBack = $null - StartDate = $null - EndDate = $null - WhenCreated = $null - } - - # Set up the file path first, before any other operations - if ([string]::IsNullOrEmpty($FilePath)) { - # Suppress Graph connection output during initial path setup - $Hawk.FilePath = Set-LoggingPath -ErrorAction Stop - } - else { - $Hawk.FilePath = Set-LoggingPath -path $FilePath -ErrorAction Stop 2>$null - } - - # Now that FilePath is set, we can use Out-LogFile - Out-LogFile "Hawk output directory created at: $($Hawk.FilePath)" -Information - - # Setup Application insights - Out-LogFile "Setting up Application Insights" -Action - New-ApplicationInsight - - ### Checking for Updates ### - # If we are skipping the update log it - if ($SkipUpdate) { - Out-LogFile -string "Skipping Update Check" -Information - } - # Check to see if there is an Update for Hawk - else { - Update-HawkModule - } - - # Test Graph connection - Out-LogFile "Testing Graph Connection" -Action - - Test-GraphConnection - + + if (-not $NonInteractive) { try { $LicenseInfo = Test-LicenseType $MaxDaysToGoBack = $LicenseInfo.RetentionPeriod @@ -290,213 +258,213 @@ $LicenseType = "Unknown" } - # Ensure MaxDaysToGoBack does not exceed 365 days - if ($MaxDaysToGoBack -gt 365) { $MaxDaysToGoBack = 365 } - - # Start date validation: Add check for negative numbers - while ($null -eq $StartDate) { - Write-Output "`n" - Out-LogFile "Please specify the first day of the search window:" -isPrompt - Out-LogFile " Enter a number of days to go back (1-$MaxDaysToGoBack)" -isPrompt - Out-LogFile " OR enter a date in MM/DD/YYYY format" -isPrompt - Out-LogFile " Default is 90 days back: " -isPrompt -NoNewLine - $StartRead = (Read-Host).Trim() - - # Determine if input is a valid date - if ($null -eq ($StartRead -as [DateTime])) { - #### Not a DateTime #### - if ([string]::IsNullOrEmpty($StartRead)) { - $StartRead = 90 - } - + } + + + # Ensure MaxDaysToGoBack does not exceed 365 days + if ($MaxDaysToGoBack -gt 365) { $MaxDaysToGoBack = 365 } + + # Start date validation: Add check for negative numbers + while ($null -eq $StartDate) { + Write-Output "`n" + Out-LogFile "Please specify the first day of the search window:" -isPrompt + Out-LogFile " Enter a number of days to go back (1-$MaxDaysToGoBack)" -isPrompt + Out-LogFile " OR enter a date in MM/DD/YYYY format" -isPrompt + Out-LogFile " Default is 90 days back: " -isPrompt -NoNewLine + $StartRead = (Read-Host).Trim() + + # Determine if input is a valid date + if ($null -eq ($StartRead -as [DateTime])) { + #### Not a DateTime #### + if ([string]::IsNullOrEmpty($StartRead)) { + $StartRead = 90 + } + + # Validate input is a positive number + if ($StartRead -match '^\-') { + Out-LogFile -string "Please enter a positive number of days." -isError + continue + } + + # Validate numeric value + if ($StartRead -notmatch '^\d+$') { + Out-LogFile -string "Please enter a valid number of days." -isError + continue + } + + # Validate the entered days back + if ($StartRead -gt $MaxDaysToGoBack) { + Out-LogFile -string "You have entered a time frame greater than your license allows ($MaxDaysToGoBack days)." -isWarning + Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the value: " -isPrompt -NoNewLine + $Proceed = (Read-Host).Trim() + if ($Proceed -eq 'R') { continue } + } + + if ($StartRead -gt 365) { + Out-LogFile -string "Log retention cannot exceed 365 days. Setting retention to 365 days." -isWarning + $StartRead = 365 + } + + # Calculate start date + [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date + Write-Output "" + Out-LogFile -string "Start date set to: $StartDate [UTC]" -Information + + } + # Handle DateTime input + elseif (!($null -eq ($StartRead -as [DateTime]))) { + [DateTime]$StartDate = (Get-Date $StartRead).ToUniversalTime().Date + + # Validate the date + if ($StartDate -gt (Get-Date).ToUniversalTime()) { + Out-LogFile -string "Start date cannot be in the future." -isError + $StartDate = $null + continue + } + + if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-$MaxDaysToGoBack))) { + Out-LogFile -string "The date entered exceeds your license retention period of $MaxDaysToGoBack days." -isWarning + Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the date:" -isPrompt -NoNewLine + $Proceed = (Read-Host).Trim() + if ($Proceed -eq 'R') { $StartDate = $null; continue } + } + + if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-365))) { + Out-LogFile -string "The date cannot exceed 365 days. Setting to the maximum limit of 365 days." -isWarning + [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-365)).Date + + } + + Out-LogFile -string "Start Date (UTC): $StartDate" -Information + } + else { + Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError + $StartDate = $null + continue + } + } + + # End date logic with enhanced validation + while ($null -eq $EndDate) { + Write-Output "`n" + Out-LogFile "Please specify the last day of the search window:" -isPrompt + Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt + Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt + Out-LogFile " Default is today's date:" -isPrompt -NoNewLine + $EndRead = (Read-Host).Trim() + + # End date validation + if ($null -eq ($EndRead -as [DateTime])) { + if ([string]::IsNullOrEmpty($EndRead)) { + [DateTime]$tempEndDate = (Get-Date).ToUniversalTime().Date + } + else { # Validate input is a positive number - if ($StartRead -match '^\-') { + if ($EndRead -match '^\-') { Out-LogFile -string "Please enter a positive number of days." -isError continue } - + # Validate numeric value - if ($StartRead -notmatch '^\d+$') { + if ($EndRead -notmatch '^\d+$') { Out-LogFile -string "Please enter a valid number of days." -isError continue } - - # Validate the entered days back - if ($StartRead -gt $MaxDaysToGoBack) { - Out-LogFile -string "You have entered a time frame greater than your license allows ($MaxDaysToGoBack days)." -isWarning - Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the value: " -isPrompt -NoNewLine - $Proceed = (Read-Host).Trim() - if ($Proceed -eq 'R') { continue } - } - - if ($StartRead -gt 365) { - Out-LogFile -string "Log retention cannot exceed 365 days. Setting retention to 365 days." -isWarning - $StartRead = 365 - } - - # Calculate start date - [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date - Write-Output "" - Out-LogFile -string "Start date set to: $StartDate [UTC]" -Information - - } - # Handle DateTime input - elseif (!($null -eq ($StartRead -as [DateTime]))) { - [DateTime]$StartDate = (Get-Date $StartRead).ToUniversalTime().Date - - # Validate the date - if ($StartDate -gt (Get-Date).ToUniversalTime()) { - Out-LogFile -string "Start date cannot be in the future." -isError - $StartDate = $null - continue - } - - if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-$MaxDaysToGoBack))) { - Out-LogFile -string "The date entered exceeds your license retention period of $MaxDaysToGoBack days." -isWarning - Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the date:" -isPrompt -NoNewLine - $Proceed = (Read-Host).Trim() - if ($Proceed -eq 'R') { $StartDate = $null; continue } - } - - if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-365))) { - Out-LogFile -string "The date cannot exceed 365 days. Setting to the maximum limit of 365 days." -isWarning - [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-365)).Date - - } - - Out-LogFile -string "Start Date (UTC): $StartDate" -Information + + Out-LogFile -string "End Date (UTC): $EndRead days." -Information + [DateTime]$tempEndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date } - else { - Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError - $StartDate = $null + + if ($StartDate -gt $tempEndDate) { + Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError continue } + + $EndDate = $tempEndDate + Write-Output "" + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information } - - # End date logic with enhanced validation - while ($null -eq $EndDate) { - Write-Output "`n" - Out-LogFile "Please specify the last day of the search window:" -isPrompt - Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt - Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt - Out-LogFile " Default is today's date:" -isPrompt -NoNewLine - $EndRead = (Read-Host).Trim() - - # End date validation - if ($null -eq ($EndRead -as [DateTime])) { - if ([string]::IsNullOrEmpty($EndRead)) { - [DateTime]$tempEndDate = (Get-Date).ToUniversalTime().Date - } - else { - # Validate input is a positive number - if ($EndRead -match '^\-') { - Out-LogFile -string "Please enter a positive number of days." -isError - continue - } - - # Validate numeric value - if ($EndRead -notmatch '^\d+$') { - Out-LogFile -string "Please enter a valid number of days." -isError - continue - } - - Out-LogFile -string "End Date (UTC): $EndRead days." -Information - [DateTime]$tempEndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date - } - - if ($StartDate -gt $tempEndDate) { - Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError - continue - } - - $EndDate = $tempEndDate - Write-Output "" - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information - } - elseif (!($null -eq ($EndRead -as [DateTime]))) { - - [DateTime]$tempEndDate = (Get-Date $EndRead).ToUniversalTime().Date - - if ($StartDate -gt $tempEndDate) { - Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError - continue - } - elseif ($tempEndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { - Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning - $tempEndDate = (Get-Date).ToUniversalTime().Date - } - - $EndDate = $tempEndDate - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information - } - else { - Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError + elseif (!($null -eq ($EndRead -as [DateTime]))) { + + [DateTime]$tempEndDate = (Get-Date $EndRead).ToUniversalTime().Date + + if ($StartDate -gt $tempEndDate) { + Out-LogFile -string "End date must be more recent than start date ($StartDate)." -isError continue } + elseif ($tempEndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { + Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning + $tempEndDate = (Get-Date).ToUniversalTime().Date + } + + $EndDate = $tempEndDate + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information } - # End date logic remains unchanged - if ($null -eq $EndDate) { - Write-Output "`n" - Out-LogFile "Please specify the last day of the search window:" -isPrompt - Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt - Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt - Out-LogFile " Default is today's date:" -isPrompt -NoNewLine - $EndRead = (Read-Host).Trim() - - # End date validation - if ($null -eq ($EndRead -as [DateTime])) { - if ([string]::IsNullOrEmpty($EndRead)) { - [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date - } else { - Out-LogFile -string "End Date (UTC): $EndRead days." -Information - [DateTime]$EndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date - } - - if ($StartDate -gt $EndDate) { - Out-LogFile -string "StartDate cannot be more recent than EndDate" -isError - } else { - Write-Output "" - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information - } - } elseif (!($null -eq ($EndRead -as [DateTime]))) { - [DateTime]$EndDate = (Get-Date $EndRead).ToUniversalTime().Date - - if ($StartDate -gt $EndDate) { - Out-LogFile -string "EndDate is earlier than StartDate. Setting EndDate to today." -isWarning - [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date - } elseif ($EndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { - Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning - [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date - } - - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + else { + Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError + continue + } + } + # End date logic remains unchanged + if ($null -eq $EndDate) { + Write-Output "`n" + Out-LogFile "Please specify the last day of the search window:" -isPrompt + Out-LogFile " Enter a number of days to go back from today (1-365)" -isPrompt + Out-LogFile " OR enter a specific date in MM/DD/YYYY format" -isPrompt + Out-LogFile " Default is today's date:" -isPrompt -NoNewLine + $EndRead = (Read-Host).Trim() + + # End date validation + if ($null -eq ($EndRead -as [DateTime])) { + if ([string]::IsNullOrEmpty($EndRead)) { + [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date } else { - Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError + Out-LogFile -string "End Date (UTC): $EndRead days." -Information + [DateTime]$EndDate = ((Get-Date).ToUniversalTime().AddDays(-($EndRead - 1))).Date } + + if ($StartDate -gt $EndDate) { + Out-LogFile -string "StartDate cannot be more recent than EndDate" -isError + } else { + Write-Output "" + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + } + } elseif (!($null -eq ($EndRead -as [DateTime]))) { + [DateTime]$EndDate = (Get-Date $EndRead).ToUniversalTime().Date + + if ($StartDate -gt $EndDate) { + Out-LogFile -string "EndDate is earlier than StartDate. Setting EndDate to today." -isWarning + [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date + } elseif ($EndDate -gt ((Get-Date).ToUniversalTime().AddDays(1))) { + Out-LogFile -string "EndDate too far in the future. Setting EndDate to today." -isWarning + [DateTime]$EndDate = (Get-Date).ToUniversalTime().Date + } + + Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + } else { + Out-LogFile -string "Invalid date information provided. Could not determine if this was a date or an integer." -isError } - - # Configuration Example, currently not used - #TODO: Implement Configuration system across entire project - Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig - if ($OutputPath) { - Set-PSFConfig -Module 'Hawk' -Name 'FilePath' -Value $OutputPath -PassThru | Register-PSFConfig - } - - # Continue populating the Hawk object with other properties - $Hawk.DaysToLookBack = $Days - $Hawk.StartDate = $StartDate - $Hawk.EndDate = $EndDate - $Hawk.WhenCreated = (Get-Date).ToUniversalTime().ToString("g") - - Write-HawkConfigurationComplete -Hawk $Hawk - - } - else { - Out-LogFile -string "Valid Hawk Object already exists no actions will be taken." -Information + + # Configuration Example, currently not used + #TODO: Implement Configuration system across entire project + Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig + if ($OutputPath) { + Set-PSFConfig -Module 'Hawk' -Name 'FilePath' -Value $OutputPath -PassThru | Register-PSFConfig } - } + # Continue populating the Hawk object with other properties + $Hawk.DaysToLookBack = $DaysToLookBack + $Hawk.StartDate = $StartDate + $Hawk.EndDate = $EndDate + $Hawk.WhenCreated = (Get-Date).ToUniversalTime().ToString("g") + + Write-HawkConfigurationComplete -Hawk $Hawk + + } + else { + Out-LogFile -string "Valid Hawk Object already exists no actions will be taken." -Information + } } diff --git a/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 b/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 index 7bfb0a1..3bf9a4f 100644 --- a/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 +++ b/Hawk/internal/functions/Test-HawkInvestigationParameter.ps1 @@ -99,6 +99,7 @@ Function Test-HawkInvestigationParameter { } # Validate DaysToLookBack regardless of mode + if ($DaysToLookBack) { if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { $isValid = $false From 8da02fe56ab6f4d17b0b76b93b2157bc46c9c1ef Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 13 Jan 2025 20:56:38 -0500 Subject: [PATCH 08/19] Modify functions to pass all test cmd line argument test cases except test case 10 --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index d1552c4..f71fc82 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -163,11 +163,26 @@ begin { if ($NonInteractive) { - # If DaysToLookBack is specified, convert it first - if ($PSBoundParameters.ContainsKey('DaysToLookBack') -and $DaysToLookBack -ge 1 -and $DaysToLookBack -le 365) { - $convertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack - $StartDate = $convertedDates.StartDate - $EndDate = $convertedDates.EndDate + ############################################################################## + # 1. SHORT-CIRCUIT CHECK: Must specify either StartDate or DaysToLookBack + ############################################################################## + if (-not $PSBoundParameters.ContainsKey('DaysToLookBack') -and -not $PSBoundParameters.ContainsKey('StartDate')) { + Stop-PSFFunction -Message "Either StartDate or DaysToLookBack must be specified in non-interactive mode" -EnableException $true + } + + ############################################################################## + # 2. If -DaysToLookBack was explicitly passed, validate it up front + ############################################################################## + if ($PSBoundParameters.ContainsKey('DaysToLookBack')) { + if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { + Stop-PSFFunction -Message "DaysToLookBack must be between 1 and 365" -EnableException $true + } + else { + # Convert DaysToLookBack to StartDate/EndDate so they're never null + $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack + $StartDate = $ConvertedDates.StartDate + $EndDate = $ConvertedDates.EndDate + } } # Now call validation with updated StartDate/EndDate From 6d55b711f7d0d059e92946ec39e7232388dcd982 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 13:24:36 -0500 Subject: [PATCH 09/19] Implement changes to Start-HawkTenantInvestigation, passing all test cases. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 95 +++++++++++-------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index f71fc82..3c27ee8 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -107,62 +107,64 @@ ) - # begin { - # # Validate parameters if in non-interactive mode - # if ($NonInteractive) { - # # if ($NonInteractive) { + # begin { + # if ($NonInteractive) { + # ############################################################################## + # # 1. SHORT-CIRCUIT CHECK: Must specify either StartDate or DaysToLookBack + # ############################################################################## + # if (-not $PSBoundParameters.ContainsKey('DaysToLookBack') -and -not $PSBoundParameters.ContainsKey('StartDate')) { + # Stop-PSFFunction -Message "Either StartDate or DaysToLookBack must be specified in non-interactive mode" -EnableException $true + # } - # # Write-Output "ENTERING COMMAND LINE INTERFACE NON INTERACTIVE MODE" - - # # # Check if DaysToLookBack is valid before calling the conversion function - # # if ($DaysToLookBack -ne $null -and $DaysToLookBack -ge 1 -and $DaysToLookBack -le 365) { - # # $ConvertedDates = Convert-HawkDaysToDates -DaysToLookBack $DaysToLookBack - # # $StartDate = $ConvertedDates.StartDate - # # $EndDate = $ConvertedDates.EndDate - # # } - - # # } - # # if ($NonInteractive) { - - # # Write-Output "ENTERING COMMAND LINE INTERFACE NON INTERACTIVE MODE" - - # # # Check if DaysToLookBack is valid before calling the conversion function - # # if ($DaysToLookBack -ne $null -and $DaysToLookBack -ge 1 -and $DaysToLookBack -le 365) { - # # $ConvertedDates = Convert-HawkDaysToDates -DaysToLookBack $DaysToLookBack - # # $StartDate = $ConvertedDates.StartDate - # # $EndDate = $ConvertedDates.EndDate - # # } + # ############################################################################## + # # 2. If -DaysToLookBack was explicitly passed, validate it up front + # ############################################################################## + # if ($PSBoundParameters.ContainsKey('DaysToLookBack')) { + # if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { + # Stop-PSFFunction -Message "DaysToLookBack must be between 1 and 365" -EnableException $true + # } + # else { + # # Convert DaysToLookBack to StartDate/EndDate so they're never null + # $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack + # $StartDate = $ConvertedDates.StartDate + # $EndDate = $ConvertedDates.EndDate + # } + # } - # # } - - - # $validation = Test-HawkInvestigationParameter -StartDate $StartDate -EndDate $EndDate ` + # # Now call validation with updated StartDate/EndDate + # $validation = Test-HawkInvestigationParameter ` + # -StartDate $StartDate -EndDate $EndDate ` # -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive - + # if (-not $validation.IsValid) { # foreach ($error in $validation.ErrorMessages) { # Stop-PSFFunction -Message $error -EnableException $true # } # } - - + # try { - # # Initialize with provided parameters - # Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack ` - # -FilePath $FilePath -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + # Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` + # -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` + # -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive # } # catch { # Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true # } # } - # } - begin { if ($NonInteractive) { + ############################################################################## + # 0. Check if the user provided both StartDate AND DaysToLookBack + ############################################################################## + if ($PSBoundParameters.ContainsKey('DaysToLookBack') -and $PSBoundParameters.ContainsKey('StartDate')) { + # This is the new disallowed scenario + Stop-PSFFunction -Message "DaysToLookBack cannot be used together with StartDate in non-interactive mode." -EnableException $true + } + ############################################################################## # 1. SHORT-CIRCUIT CHECK: Must specify either StartDate or DaysToLookBack ############################################################################## @@ -178,10 +180,22 @@ Stop-PSFFunction -Message "DaysToLookBack must be between 1 and 365" -EnableException $true } else { - # Convert DaysToLookBack to StartDate/EndDate so they're never null - $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack - $StartDate = $ConvertedDates.StartDate - $EndDate = $ConvertedDates.EndDate + # Check if user also provided EndDate (but no StartDate) + if ($PSBoundParameters.ContainsKey('EndDate') -and -not $PSBoundParameters.ContainsKey('StartDate')) { + # EndDate - DaysToLookBack = StartDate + # For example, if EndDate=3/1/2024 and DaysToLookBack=30 => StartDate=1/31/2024 + $EndDateUTC = $EndDate.ToUniversalTime() + $StartDateUTC = $EndDateUTC.AddDays(-$DaysToLookBack) + + $StartDate = $StartDateUTC + $EndDate = $EndDateUTC + } + else { + # Original: Convert DaysToLookBack to StartDate/EndDate from "today" + $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack + $StartDate = $ConvertedDates.StartDate + $EndDate = $ConvertedDates.EndDate + } } } @@ -206,7 +220,6 @@ } } } - process { From 76db2723a5e5891ba75e11d859145170423b5c3b Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 14:36:32 -0500 Subject: [PATCH 10/19] Fix bug in initalize-hawkglobalobject where entering a day less than the max license value, states that the daystogoback exceeds the license value and miscaluclates teh dates. --- .../functions/Initialize-HawkGlobalObject.ps1 | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index c253c15..a3005ac 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -275,11 +275,16 @@ # Determine if input is a valid date if ($null -eq ($StartRead -as [DateTime])) { + #### Not a DateTime #### + # First convert StartRead to integer for comparison if ([string]::IsNullOrEmpty($StartRead)) { - $StartRead = 90 + [int]$StartRead = 90 } - + else { + [int]$StartRead = $StartRead + } + # Validate input is a positive number if ($StartRead -match '^\-') { Out-LogFile -string "Please enter a positive number of days." -isError @@ -293,18 +298,19 @@ } # Validate the entered days back - if ($StartRead -gt $MaxDaysToGoBack) { + if ([int]$StartRead -gt [int]$MaxDaysToGoBack) { Out-LogFile -string "You have entered a time frame greater than your license allows ($MaxDaysToGoBack days)." -isWarning Out-LogFile "Press ENTER to proceed or type 'R' to re-enter the value: " -isPrompt -NoNewLine $Proceed = (Read-Host).Trim() if ($Proceed -eq 'R') { continue } } - if ($StartRead -gt 365) { + if ([int]$StartRead -gt 365) { Out-LogFile -string "Log retention cannot exceed 365 days. Setting retention to 365 days." -isWarning - $StartRead = 365 + [int]$StartRead = 365 } + # Calculate start date [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date Write-Output "" From 2b4daafcce73a1a7c78702d0d56aeab51cbe86a4 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 17:26:40 -0500 Subject: [PATCH 11/19] Update Write-HawkBanner to include switch to display welcome message or not. Update Initalize-HawkGlobalObject to only show welcome message during interactive mode.. --- .../functions/Initialize-HawkGlobalObject.ps1 | 73 ++++++++++++------- Hawk/internal/functions/Write-HawkBanner.ps1 | 45 +++++++++--- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index a3005ac..6437914 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -199,7 +199,13 @@ if (($null -eq (Get-Variable -Name Hawk -ErrorAction SilentlyContinue)) -or ($Force -eq $true) -or ($null -eq $Hawk)) { - Write-HawkBanner + if ($NonInteractive) { + Write-HawkBanner + } else { + Write-HawkBanner -DisplayWelcomeMessage + } + + # Create the global $Hawk variable immediately with minimal properties $Global:Hawk = [PSCustomObject]@{ @@ -272,31 +278,34 @@ Out-LogFile " OR enter a date in MM/DD/YYYY format" -isPrompt Out-LogFile " Default is 90 days back: " -isPrompt -NoNewLine $StartRead = (Read-Host).Trim() - + # Determine if input is a valid date if ($null -eq ($StartRead -as [DateTime])) { - #### Not a DateTime #### - # First convert StartRead to integer for comparison + #### Not a DateTime => interpret as # of days #### if ([string]::IsNullOrEmpty($StartRead)) { [int]$StartRead = 90 } - else { - [int]$StartRead = $StartRead + # Validates the input is an integer + elseif ($StartRead -match '^\d+$') { + # Only convert to int if it is a valid positive number + [int]$StartRead = [int]$StartRead } - - # Validate input is a positive number - if ($StartRead -match '^\-') { - Out-LogFile -string "Please enter a positive number of days." -isError + else { + Out-LogFile -string "Invalid input. Please enter a number between 1 and 365, or a date in MM/DD/YYYY format." -isError continue } - - # Validate numeric value - if ($StartRead -notmatch '^\d+$') { - Out-LogFile -string "Please enter a valid number of days." -isError + + # We store this integer in $StartDays so we can potentially re-anchor from EndDate later + $StartDays = $StartRead + + # Validate the input is within range + if (($StartRead -gt 365) -or ($StartRead -lt 1)) { + Out-LogFile -string "Days to go back must be between 1 and 365." -isError continue } + # Validate the entered days back if ([int]$StartRead -gt [int]$MaxDaysToGoBack) { Out-LogFile -string "You have entered a time frame greater than your license allows ($MaxDaysToGoBack days)." -isWarning @@ -305,22 +314,16 @@ if ($Proceed -eq 'R') { continue } } - if ([int]$StartRead -gt 365) { - Out-LogFile -string "Log retention cannot exceed 365 days. Setting retention to 365 days." -isWarning - [int]$StartRead = 365 - } - - - # Calculate start date + + # At this point, we do not yet have EndDate set. So temporarily anchor from "today": [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date + Write-Output "" - Out-LogFile -string "Start date set to: $StartDate [UTC]" -Information - + Out-LogFile -string "Start date set to: $StartDate" -Information + } - # Handle DateTime input elseif (!($null -eq ($StartRead -as [DateTime]))) { - [DateTime]$StartDate = (Get-Date $StartRead).ToUniversalTime().Date - + # ========== The user entered a DateTime, so $StartDays stays 0 ========== # Validate the date if ($StartDate -gt (Get-Date).ToUniversalTime()) { Out-LogFile -string "Start date cannot be in the future." -isError @@ -452,6 +455,24 @@ } } + # --- AFTER the EndDate block, do a final check to "re-anchor" StartDate if it was given in days --- + if ($StartDays -gt 0) { + # Recalculate StartDate anchored to the final EndDate + Out-LogFile -string "Recalculating StartDate based on EndDate = $EndDate and StartDays = $StartDays" -Information + + $StartDate = $EndDate.ToUniversalTime().AddDays(-$StartDays).Date + + # (Optional) Additional validations again if necessary: + if ($StartDate -gt (Get-Date).ToUniversalTime()) { + Out-LogFile -string "Start date is in the future. Resetting to today's date." -isWarning + $StartDate = (Get-Date).ToUniversalTime().Date + } + + + Out-LogFile -string "Final StartDate (UTC) after re-anchoring: $StartDate" -Information + } + + # Configuration Example, currently not used #TODO: Implement Configuration system across entire project Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig diff --git a/Hawk/internal/functions/Write-HawkBanner.ps1 b/Hawk/internal/functions/Write-HawkBanner.ps1 index a7745e6..89f118e 100644 --- a/Hawk/internal/functions/Write-HawkBanner.ps1 +++ b/Hawk/internal/functions/Write-HawkBanner.ps1 @@ -1,16 +1,38 @@ Function Write-HawkBanner { <# .SYNOPSIS - Displays the Hawk welcome banner. + Displays the Hawk welcome banner in the terminal. + .DESCRIPTION - Displays an ASCII art banner when starting Hawk operations. - The banner is sized to fit most terminal windows. + The `Write-HawkBanner` function displays a visually appealing ASCII art banner + when starting Hawk operations. The banner includes the Hawk logo and additional + information about the tool. Optionally, the function can display a welcome + message to guide users through the initial setup process. + + .PARAMETER DisplayWelcomeMessage + This optional switch parameter displays a series of informational messages + to help the user configure their investigation environment. + + .INPUTS + None. The function does not take pipeline input. + + .OUTPUTS + [String] + The function outputs the Hawk banner as a string to the terminal. + .EXAMPLE Write-HawkBanner - Displays the Hawk welcome banner + Displays the Hawk welcome banner without the welcome message. + + .EXAMPLE + Write-HawkBanner -DisplayWelcomeMessage + Displays the Hawk welcome banner followed by a welcome message that guides + the user through configuring the investigation environment. #> [CmdletBinding()] - param() + param( + [Switch]$DisplayWelcomeMessage + ) $banner = @' ======================================== @@ -31,11 +53,12 @@ https://cloudforensicator.com Write-Output $banner - Write-Information "Welcome to Hawk! Let's get your investigation environment set up." - Write-Information "We'll guide you through configuring the output file path and investigation date range." - Write-Information "You'll need to specify where logs should be saved and the time window for data retrieval." - Write-Information "If you're unsure, don't worry! Default options will be provided to help you out." - Write-Information "`nLet's get started!`n" - + if ($DisplayWelcomeMessage) { + Write-Information "Welcome to Hawk! Let's get your investigation environment set up." + Write-Information "We'll guide you through configuring the output file path and investigation date range." + Write-Information "You'll need to specify where logs should be saved and the time window for data retrieval." + Write-Information "If you're unsure, don't worry! Default options will be provided to help you out." + Write-Information "`nLet's get started!`n" + } } \ No newline at end of file From 0dc8523c340036c76e0ce5ca95b1d3e96d33f869 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 20:08:49 -0500 Subject: [PATCH 12/19] Add test-exoconneciton to get-hawktenantmailitemsaccessed. --- Hawk/functions/Tenant/Get-HawkTenantMailItemsAccessed.ps1 | 3 +++ Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Hawk/functions/Tenant/Get-HawkTenantMailItemsAccessed.ps1 b/Hawk/functions/Tenant/Get-HawkTenantMailItemsAccessed.ps1 index bf42c3e..b2c57d8 100644 --- a/Hawk/functions/Tenant/Get-HawkTenantMailItemsAccessed.ps1 +++ b/Hawk/functions/Tenant/Get-HawkTenantMailItemsAccessed.ps1 @@ -37,6 +37,9 @@ BEGIN { if (Test-HawkGlobalObject) { Initialize-HawkGlobalObject } + + Test-EXOConnection + Out-LogFile "Starting Unified Audit Log (UAL) search for 'MailItemsAccessed'" -Action diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index 6437914..cfdb9fb 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -376,7 +376,7 @@ # Validate numeric value if ($EndRead -notmatch '^\d+$') { - Out-LogFile -string "Please enter a valid number of days." -isError + Out-LogFile -string "Invalid input. Please enter a number between 1 and 365, or a date in MM/DD/YYYY format." -isError continue } From 990d2cafed3b6bc6b2621c0536272b8bfc0ecd22 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 21:46:04 -0500 Subject: [PATCH 13/19] All interactive tests pass except test 6. --- Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index cfdb9fb..e0da699 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -323,6 +323,8 @@ } elseif (!($null -eq ($StartRead -as [DateTime]))) { + [DateTime]$StartDate = $StartRead -as [DateTime] # <--- Add this line + # ========== The user entered a DateTime, so $StartDays stays 0 ========== # Validate the date if ($StartDate -gt (Get-Date).ToUniversalTime()) { @@ -337,6 +339,7 @@ $Proceed = (Read-Host).Trim() if ($Proceed -eq 'R') { $StartDate = $null; continue } } + if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-365))) { Out-LogFile -string "The date cannot exceed 365 days. Setting to the maximum limit of 365 days." -isWarning @@ -391,7 +394,7 @@ $EndDate = $tempEndDate Write-Output "" - Out-LogFile -string "End date set to: $EndDate [UTC]`n" -Information + Out-LogFile -string "End date set to: $EndDate (UTC)`n" -Information } elseif (!($null -eq ($EndRead -as [DateTime]))) { From 679299b955566e1bdce281acc969002dbf5cd52e Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 22:01:21 -0500 Subject: [PATCH 14/19] Update initialize-hawkglobalobject to allow for all interactive tests to pass. --- Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index e0da699..5b9fe4a 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -329,7 +329,7 @@ # Validate the date if ($StartDate -gt (Get-Date).ToUniversalTime()) { Out-LogFile -string "Start date cannot be in the future." -isError - $StartDate = $null + Remove-Variable -Name StartDate -ErrorAction SilentlyContinue continue } From 4fa8bab4d620f9ad5ebe10e01eac44186f924b7b Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Tue, 14 Jan 2025 22:10:03 -0500 Subject: [PATCH 15/19] Remove uncessary comments from Start-HawkTenatnInvestigation. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index 3c27ee8..2d9f759 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -107,54 +107,6 @@ ) - - - # begin { - # if ($NonInteractive) { - # ############################################################################## - # # 1. SHORT-CIRCUIT CHECK: Must specify either StartDate or DaysToLookBack - # ############################################################################## - # if (-not $PSBoundParameters.ContainsKey('DaysToLookBack') -and -not $PSBoundParameters.ContainsKey('StartDate')) { - # Stop-PSFFunction -Message "Either StartDate or DaysToLookBack must be specified in non-interactive mode" -EnableException $true - # } - - # ############################################################################## - # # 2. If -DaysToLookBack was explicitly passed, validate it up front - # ############################################################################## - # if ($PSBoundParameters.ContainsKey('DaysToLookBack')) { - # if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { - # Stop-PSFFunction -Message "DaysToLookBack must be between 1 and 365" -EnableException $true - # } - # else { - # # Convert DaysToLookBack to StartDate/EndDate so they're never null - # $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack - # $StartDate = $ConvertedDates.StartDate - # $EndDate = $ConvertedDates.EndDate - # } - # } - - # # Now call validation with updated StartDate/EndDate - # $validation = Test-HawkInvestigationParameter ` - # -StartDate $StartDate -EndDate $EndDate ` - # -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive - - # if (-not $validation.IsValid) { - # foreach ($error in $validation.ErrorMessages) { - # Stop-PSFFunction -Message $error -EnableException $true - # } - # } - - # try { - # Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` - # -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` - # -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive - # } - # catch { - # Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true - # } - # } - # } - begin { if ($NonInteractive) { ############################################################################## From d0a3802047d9301da794de643e34b0e5ac11567b Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Wed, 15 Jan 2025 14:39:07 -0500 Subject: [PATCH 16/19] Add Test-HawkDateParamter fuction. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 48 +------ .../functions/Test-HawkDateParameter.ps1 | 133 ++++++++++++++++++ 2 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 Hawk/internal/functions/Test-HawkDateParameter.ps1 diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index 2d9f759..ab2b349 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -109,50 +109,12 @@ begin { if ($NonInteractive) { - ############################################################################## - # 0. Check if the user provided both StartDate AND DaysToLookBack - ############################################################################## - if ($PSBoundParameters.ContainsKey('DaysToLookBack') -and $PSBoundParameters.ContainsKey('StartDate')) { - # This is the new disallowed scenario - Stop-PSFFunction -Message "DaysToLookBack cannot be used together with StartDate in non-interactive mode." -EnableException $true - } - - ############################################################################## - # 1. SHORT-CIRCUIT CHECK: Must specify either StartDate or DaysToLookBack - ############################################################################## - if (-not $PSBoundParameters.ContainsKey('DaysToLookBack') -and -not $PSBoundParameters.ContainsKey('StartDate')) { - Stop-PSFFunction -Message "Either StartDate or DaysToLookBack must be specified in non-interactive mode" -EnableException $true - } - - ############################################################################## - # 2. If -DaysToLookBack was explicitly passed, validate it up front - ############################################################################## - if ($PSBoundParameters.ContainsKey('DaysToLookBack')) { - if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { - Stop-PSFFunction -Message "DaysToLookBack must be between 1 and 365" -EnableException $true - } - else { - # Check if user also provided EndDate (but no StartDate) - if ($PSBoundParameters.ContainsKey('EndDate') -and -not $PSBoundParameters.ContainsKey('StartDate')) { - # EndDate - DaysToLookBack = StartDate - # For example, if EndDate=3/1/2024 and DaysToLookBack=30 => StartDate=1/31/2024 - $EndDateUTC = $EndDate.ToUniversalTime() - $StartDateUTC = $EndDateUTC.AddDays(-$DaysToLookBack) - - $StartDate = $StartDateUTC - $EndDate = $EndDateUTC - } - else { - # Original: Convert DaysToLookBack to StartDate/EndDate from "today" - $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack - $StartDate = $ConvertedDates.StartDate - $EndDate = $ConvertedDates.EndDate - } - } - } + $processedDates = Process-HawkDateParameter -PSBoundParameters $PSBoundParameters -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack + $StartDate = $processedDates.StartDate + $EndDate = $processedDates.EndDate # Now call validation with updated StartDate/EndDate - $validation = Test-HawkInvestigationParameter ` + $validation = Test-HawkDateParameter ` -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive @@ -161,7 +123,7 @@ Stop-PSFFunction -Message $error -EnableException $true } } - + try { Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` diff --git a/Hawk/internal/functions/Test-HawkDateParameter.ps1 b/Hawk/internal/functions/Test-HawkDateParameter.ps1 new file mode 100644 index 0000000..fe101ec --- /dev/null +++ b/Hawk/internal/functions/Test-HawkDateParameter.ps1 @@ -0,0 +1,133 @@ +Function Test-HawkDateParameter { + <# + .SYNOPSIS + Internal helper function that processes and validates date parameters for Hawk investigations. + + .DESCRIPTION + The Test-HawkDateParmeter function is an internal helper used by Start-HawkTenantInvestigation + and Start-HawkUserInvestigation to process date-related parameters. It handles both direct date + specifications and the DaysToLookBack parameter, performing initial validation and date conversions. + + The function: + - Validates the combination of provided date parameters + - Processes DaysToLookBack into concrete start/end dates + - Converts dates to UTC format + - Performs bounds checking on date ranges + - Handles both absolute dates and relative date calculations + + This is designed as an internal function and should not be called directly by end users. + + .PARAMETER PSBoundParameters + The PSBoundParameters hashtable from the calling function. Used to check which parameters + were explicitly passed to the parent function. Must contain information about whether + StartDate, EndDate, and/or DaysToLookBack were provided. + + .PARAMETER StartDate + The starting date for the investigation period, if specified directly. + Can be null if using DaysToLookBack instead. + When provided with EndDate, defines an explicit date range for the investigation. + + .PARAMETER EndDate + The ending date for the investigation period, if specified directly. + Can be null if using DaysToLookBack instead. + When provided with StartDate, defines an explicit date range for the investigation. + + .PARAMETER DaysToLookBack + The number of days to look back from either the current date or a specified EndDate. + Must be between 1 and 365. + Cannot be used together with StartDate. + + .OUTPUTS + PSCustomObject containing: + - StartDate [DateTime]: The calculated or provided start date in UTC + - EndDate [DateTime]: The calculated or provided end date in UTC + + .EXAMPLE + $dates = Test-HawkDateParmeter -PSBoundParameters $PSBoundParameters -DaysToLookBack 30 + + Processes a request to look back 30 days from the current date, returning appropriate + start and end dates in UTC format. + + .EXAMPLE + $dates = Test-HawkDateParmeter ` + -PSBoundParameters $PSBoundParameters ` + -StartDate "2024-01-01" ` + -EndDate "2024-01-31" + + Processes explicit start and end dates, validating them and converting to UTC format. + + .EXAMPLE + $dates = Test-HawkDateParmeter ` + -PSBoundParameters $PSBoundParameters ` + -DaysToLookBack 30 ` + -EndDate "2024-01-31" + + Processes a request to look back 30 days from a specific end date. + + .NOTES + Author: Jonathan Butler + Internal Function: This function is not meant to be called directly by users + Dependencies: Requires PSFramework module for error handling + Validation: Initial parameter validation only; complete validation is done by Test-HawkInvestigationParameter + + .LINK + Test-HawkInvestigationParameter + + .LINK + Start-HawkTenantInvestigation + + .LINK + Start-HawkUserInvestigation + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [hashtable]$PSBoundParameters, + + [AllowNull()] + [Nullable[DateTime]]$StartDate, + + [AllowNull()] + [Nullable[DateTime]]$EndDate, + + [int]$DaysToLookBack + ) + + # Check if user provided both StartDate AND DaysToLookBack + if ($PSBoundParameters.ContainsKey('DaysToLookBack') -and $PSBoundParameters.ContainsKey('StartDate')) { + Stop-PSFFunction -Message "DaysToLookBack cannot be used together with StartDate in non-interactive mode." -EnableException $true + } + + # Must specify either StartDate or DaysToLookBack + if (-not $PSBoundParameters.ContainsKey('DaysToLookBack') -and -not $PSBoundParameters.ContainsKey('StartDate')) { + Stop-PSFFunction -Message "Either StartDate or DaysToLookBack must be specified in non-interactive mode" -EnableException $true + } + + # Process DaysToLookBack if provided + if ($PSBoundParameters.ContainsKey('DaysToLookBack')) { + if ($DaysToLookBack -lt 1 -or $DaysToLookBack -gt 365) { + Stop-PSFFunction -Message "DaysToLookBack must be between 1 and 365" -EnableException $true + } + else { + # Handle EndDate with DaysToLookBack but no StartDate + if ($PSBoundParameters.ContainsKey('EndDate') -and -not $PSBoundParameters.ContainsKey('StartDate')) { + $EndDateUTC = $EndDate.ToUniversalTime() + $StartDateUTC = $EndDateUTC.AddDays(-$DaysToLookBack) + + $StartDate = $StartDateUTC + $EndDate = $EndDateUTC + } + else { + # Convert DaysToLookBack to StartDate/EndDate from "today" + $ConvertedDates = Convert-HawkDaysToDate -DaysToLookBack $DaysToLookBack + $StartDate = $ConvertedDates.StartDate + $EndDate = $ConvertedDates.EndDate + } + } + } + + [PSCustomObject]@{ + StartDate = $StartDate + EndDate = $EndDate + } +} \ No newline at end of file From 84e13316eba2a66504dbf38c7587a96a508a9af1 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Wed, 15 Jan 2025 15:23:02 -0500 Subject: [PATCH 17/19] Add Test-HawnNonInterActiveMode and update start-HawkUserInvestigation to allow for command line parametsers --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 10 ++-- .../User/Start-HawkUserInvestigation.ps1 | 59 +++++++++++++++---- .../functions/Test-HawkNonInteractiveMode.ps1 | 42 +++++++++++++ 3 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index ab2b349..056fc6d 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -102,19 +102,21 @@ [DateTime]$EndDate, [int]$DaysToLookBack, [string]$FilePath, - [switch]$SkipUpdate, - [switch]$NonInteractive + [switch]$SkipUpdate ) + begin { + $NonInteractive = Test-HawkNonInteractiveMode -PSBoundParameters $PSBoundParameters + if ($NonInteractive) { - $processedDates = Process-HawkDateParameter -PSBoundParameters $PSBoundParameters -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack + $processedDates = Test-HawkDateParameter -PSBoundParameters $PSBoundParameters -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack $StartDate = $processedDates.StartDate $EndDate = $processedDates.EndDate # Now call validation with updated StartDate/EndDate - $validation = Test-HawkDateParameter ` + $validation = Test-HawkInvestigationParameter ` -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index d295c90..7b0db7c 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -44,22 +44,56 @@ .NOTES Ensure the Hawk global object is initialized with a valid logging file path before running this function. #> - [CmdletBinding(SupportsShouldProcess = $true)] - param ( - [Parameter(Mandatory = $true)] - [array]$UserPrincipalName - ) + [CmdletBinding(SupportsShouldProcess = $true)] + param ( + [Parameter(Mandatory = $true)] + [array]$UserPrincipalName, + + [DateTime]$StartDate, + [DateTime]$EndDate, + [int]$DaysToLookBack, + [string]$FilePath, + [switch]$SkipUpdate + ) + + begin { + $NonInteractive = Test-HawkNonInteractiveMode -PSBoundParameters $PSBoundParameters + + if ($NonInteractive) { + $processedDates = Test-HawkDateParameter -PSBoundParameters $PSBoundParameters -StartDate $StartDate -EndDate $EndDate -DaysToLookBack $DaysToLookBack + $StartDate = $processedDates.StartDate + $EndDate = $processedDates.EndDate + + # Now call validation with updated StartDate/EndDate + $validation = Test-HawkInvestigationParameter ` + -StartDate $StartDate -EndDate $EndDate ` + -DaysToLookBack $DaysToLookBack -FilePath $FilePath -NonInteractive + + if (-not $validation.IsValid) { + foreach ($error in $validation.ErrorMessages) { + Stop-PSFFunction -Message $error -EnableException $true + } + } + + try { + Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` + -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` + -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + } + catch { + Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true + } + } + } + + process { + if (Test-PSFFunctionInterrupt) { return } + # Check if Hawk object exists and is fully initialized if (Test-HawkGlobalObject) { Initialize-HawkGlobalObject } - - # Check if the logging filepath is set - if ([string]::IsNullOrEmpty($Hawk.FilePath)) { - Initialize-HawkGlobalObject - } - if ($PSCmdlet.ShouldProcess("Investigating Users")) { Out-LogFile "Investigating Users" -Action Send-AIEvent -Event "CmdRun" @@ -119,5 +153,8 @@ } } } + } + +} \ No newline at end of file diff --git a/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 b/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 new file mode 100644 index 0000000..c3141cd --- /dev/null +++ b/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 @@ -0,0 +1,42 @@ +Function Test-HawkNonInteractiveMode { + <# + .SYNOPSIS + Internal function to detect if Hawk should run in non-interactive mode. + + .DESCRIPTION + Tests whether Hawk should operate in non-interactive mode by checking if any initialization + parameters (StartDate, EndDate, DaysToLookBack, FilePath, SkipUpdate) were provided at + the command line. + + Non-interactive mode is automatically enabled if any of these parameters are present, + removing the need for users to explicitly specify -NonInteractive. + + .PARAMETER PSBoundParameters + The PSBoundParameters hashtable from the calling function. Used to check which parameters + were explicitly passed to the parent function. + + .OUTPUTS + [bool] True if any initialization parameters were provided, indicating non-interactive mode. + False if no initialization parameters were provided, indicating interactive mode. + + .EXAMPLE + $NonInteractive = Test-HawkNonInteractiveMode -PSBoundParameters $PSBoundParameters + + Checks the bound parameters to determine if non-interactive mode should be enabled. + + .NOTES + Internal function used by Start-HawkTenantInvestigation and Start-HawkUserInvestigation. + #> + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter(Mandatory = $true)] + [hashtable]$PSBoundParameters + ) + + return $PSBoundParameters.ContainsKey('StartDate') -or + $PSBoundParameters.ContainsKey('EndDate') -or + $PSBoundParameters.ContainsKey('DaysToLookBack') -or + $PSBoundParameters.ContainsKey('FilePath') -or + $PSBoundParameters.ContainsKey('SkipUpdate') +} \ No newline at end of file From fda48035579a6aedf05d4341700d5aed603aacb6 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Wed, 15 Jan 2025 15:26:33 -0500 Subject: [PATCH 18/19] Update comment based help. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 30 ++-- .../User/Start-HawkUserInvestigation.ps1 | 139 ++++++++++++------ 2 files changed, 107 insertions(+), 62 deletions(-) diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index 056fc6d..d104c98 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -8,9 +8,9 @@ It gathers information about tenant configuration, security settings, administrative changes, and potential security issues across the environment. - The command can run in either interactive mode (default) or non-interactive mode. In interactive mode, it prompts - for necessary information such as date ranges and output location. In non-interactive mode, these must be provided - as parameters. + The command can run in either interactive mode (default) or non-interactive mode. Interactive mode is used + when no parameters are provided, while non-interactive mode is automatically enabled when any parameter is + specified. In interactive mode, it prompts for necessary information such as date ranges and output location. Data collected includes: - Tenant configuration settings @@ -26,28 +26,29 @@ .PARAMETER StartDate The beginning date for the investigation period. When specified, must be used with EndDate. Cannot be later than EndDate and the date range cannot exceed 365 days. + Providing this parameter automatically enables non-interactive mode. Format: MM/DD/YYYY .PARAMETER EndDate The ending date for the investigation period. When specified, must be used with StartDate. Cannot be in the future and the date range cannot exceed 365 days. + Providing this parameter automatically enables non-interactive mode. Format: MM/DD/YYYY .PARAMETER DaysToLookBack Alternative to StartDate/EndDate. Specifies the number of days to look back from the current date. - Must be between 1 and 365. Cannot be used together with StartDate/EndDate parameters. + Must be between 1 and 365. Cannot be used together with StartDate. + Providing this parameter automatically enables non-interactive mode. .PARAMETER FilePath The file system path where investigation results will be stored. Required in non-interactive mode. Must be a valid file system path. + Providing this parameter automatically enables non-interactive mode. .PARAMETER SkipUpdate Switch to bypass the automatic check for Hawk module updates. Useful in automated scenarios or air-gapped environments. - - .PARAMETER NonInteractive - Switch to run the command in non-interactive mode. Requires all necessary parameters - to be provided via command line rather than through interactive prompts. + Providing this parameter automatically enables non-interactive mode. .PARAMETER Confirm Prompts you for confirmation before executing each investigation step. @@ -68,16 +69,16 @@ Runs a tenant investigation in interactive mode, prompting for date range and output location. .EXAMPLE - Start-HawkTenantInvestigation -DaysToLookBack 30 -FilePath "C:\Investigation" -NonInteractive + Start-HawkTenantInvestigation -DaysToLookBack 30 -FilePath "C:\Investigation" Performs a tenant investigation looking back 30 days from today, saving results to C:\Investigation. - Runs without any interactive prompts. + Runs in non-interactive mode because parameters were specified. .EXAMPLE - Start-HawkTenantInvestigation -StartDate "01/01/2024" -EndDate "01/31/2024" -FilePath "C:\Investigation" -NonInteractive -SkipUpdate + Start-HawkTenantInvestigation -StartDate "01/01/2024" -EndDate "01/31/2024" -FilePath "C:\Investigation" -SkipUpdate Investigates tenant activity for January 2024, saving results to C:\Investigation. - Skips the update check and runs without prompts. + Skips the update check. Runs in non-interactive mode because parameters were specified. .EXAMPLE Start-HawkTenantInvestigation -WhatIf @@ -85,11 +86,6 @@ Shows what investigation steps would be performed without actually executing them. Useful for understanding the investigation process or validating parameters. - .NOTES - Requires appropriate Microsoft 365 administrative permissions. - All datetime operations use UTC internally for consistency. - Large date ranges may result in longer processing times. - .LINK https://cloudforensicator.com diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index 7b0db7c..f587f4d 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -1,49 +1,98 @@ Function Start-HawkUserInvestigation { - <# - .SYNOPSIS - Gathers common data about a provided user. - - .DESCRIPTION - Runs all Hawk user-related cmdlets against the specified user and gathers the data. - - Cmdlet Information Gathered - ------------------------- ------------------------- - Get-HawkTenantConfiguration Basic Tenant information - Get-HawkUserConfiguration Basic User information - Get-HawkUserInboxRule Searches the user for Inbox Rules - Get-HawkUserEmailForwarding Looks for email forwarding configured on the user - Get-HawkUserAutoReply Looks for enabled AutoReplyConfiguration - Get-HawkUserAuthHistory Searches the unified audit log for user logons - Get-HawkUserMailboxAuditing Searches the unified audit log for mailbox auditing information - Get-HawkUserAdminAudit Searches the EXO Audit logs for commands run against the provided user - Get-HawkUserMessageTrace Pulls emails sent by the user in the last 7 days - - .PARAMETER UserPrincipalName - Single UPN of a user, comma-separated list of UPNs, or an array of objects that contain UPNs. - - .PARAMETER Confirm - Prompts for confirmation before running operations that could modify system state. - - .PARAMETER WhatIf - Shows what would happen if the command runs. The command is not actually run. - - .OUTPUTS - See help from individual cmdlets for output list. - All outputs are placed in the $Hawk.FilePath directory. - - .EXAMPLE - Start-HawkUserInvestigation -UserPrincipalName bsmith@contoso.com - - Runs all Get-HawkUser* cmdlets against the user with UPN bsmith@contoso.com. - - .EXAMPLE - Start-HawkUserInvestigation -UserPrincipalName (Get-Mailbox -Filter {CustomAttribute1 -eq "C-level"}) - - Runs all Get-HawkUser* cmdlets against all users who have "C-Level" set in CustomAttribute1. - - .NOTES - Ensure the Hawk global object is initialized with a valid logging file path before running this function. - #> + <# + .SYNOPSIS + Performs a comprehensive user-specific investigation using Hawk's automated data collection capabilities. + + .DESCRIPTION + Start-HawkUserInvestigation automates the collection and analysis of Microsoft 365 security data + for specific users. It runs multiple specialized cmdlets to gather detailed information about user + configuration, activities, and potential security concerns. + + The command can run in either interactive mode (default) or non-interactive mode. Interactive mode is used + when only UserPrincipalName is provided, while non-interactive mode is automatically enabled when any + additional parameter is specified. + + Data collected includes: + - User mailbox configuration and statistics + - Inbox rules and email forwarding settings + - Authentication history and mailbox audit logs + - Administrative changes affecting the user + - Message trace data and mobile device access + - AutoReply configuration + + All collected data is stored in a structured format for analysis, with suspicious findings + highlighted for investigation. + + .PARAMETER UserPrincipalName + Single UPN of a user, comma-separated list of UPNs, or an array of objects that contain UPNs. + This is the only required parameter and specifies which users to investigate. + + .PARAMETER StartDate + The beginning date for the investigation period. When specified, must be used with EndDate. + Cannot be later than EndDate and the date range cannot exceed 365 days. + Providing this parameter automatically enables non-interactive mode. + Format: MM/DD/YYYY + + .PARAMETER EndDate + The ending date for the investigation period. When specified, must be used with StartDate. + Cannot be in the future and the date range cannot exceed 365 days. + Providing this parameter automatically enables non-interactive mode. + Format: MM/DD/YYYY + + .PARAMETER DaysToLookBack + Alternative to StartDate/EndDate. Specifies the number of days to look back from the current date. + Must be between 1 and 365. Cannot be used together with StartDate. + Providing this parameter automatically enables non-interactive mode. + + .PARAMETER FilePath + The file system path where investigation results will be stored. + Required in non-interactive mode. Must be a valid file system path. + Providing this parameter automatically enables non-interactive mode. + + .PARAMETER SkipUpdate + Switch to bypass the automatic check for Hawk module updates. + Useful in automated scenarios or air-gapped environments. + Providing this parameter automatically enables non-interactive mode. + + .PARAMETER Confirm + Prompts you for confirmation before executing each investigation step. + By default, confirmation prompts appear for operations that could collect sensitive data. + + .PARAMETER WhatIf + Shows what would happen if the command runs. The command is not executed. + Use this parameter to understand which investigation steps would be performed without actually collecting data. + + .OUTPUTS + Creates multiple CSV and JSON files containing investigation results. + All outputs are organized in user-specific folders under the specified FilePath directory. + See individual cmdlet help for specific output details. + + .EXAMPLE + Start-HawkUserInvestigation -UserPrincipalName user@contoso.com + + Investigates a single user in interactive mode, prompting for date range and output location. + + .EXAMPLE + Start-HawkUserInvestigation -UserPrincipalName user@contoso.com -DaysToLookBack 30 -FilePath "C:\Investigation" + + Investigates a single user looking back 30 days, saving results to C:\Investigation. + Runs in non-interactive mode because parameters beyond UserPrincipalName were specified. + + .EXAMPLE + Start-HawkUserInvestigation ` + -UserPrincipalName (Get-Mailbox -Filter {CustomAttribute1 -eq "C-level"}) ` + -StartDate "01/01/2024" ` + -EndDate "01/31/2024" ` + -FilePath "C:\Investigation" + + Investigates all users with CustomAttribute1="C-level" for January 2024. + Runs in non-interactive mode because multiple parameters were specified. + .LINK + https://cloudforensicator.com + + .LINK + https://github.com/T0pCyber/hawk + #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] From c527b5235f35b50402299231550ace36174c078a Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Wed, 15 Jan 2025 17:05:21 -0500 Subject: [PATCH 19/19] Update change log --- Hawk/changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Hawk/changelog.md b/Hawk/changelog.md index 2f2aee0..41c8d6b 100644 --- a/Hawk/changelog.md +++ b/Hawk/changelog.md @@ -95,4 +95,5 @@ - Implemented PROMPT tag to display to screen when prompting user - Added functionality to expand detect M365 license type to determine max log retention time - Added ability to expand search up to 365 days - +- Added search of mail items accessed to the User Investigation (Get-HawkUserMailItemsAccessed) +- Add ability to pass command line arguments to Start-HawkUserInvestigation and Start-HawkTenantInvestigation