Public/Helpers/Invoke-StealthOperation.ps1

function Invoke-StealthOperation {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [object]$InputObject,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Random", "Progressive", "BusinessHours", "Exponential")]
        [string]$DelayType = "Random",

        [Parameter(Mandatory = $false)]
        [ValidateRange(0, 600)]
        [int]$MinDelay = 1,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 3600)]
        [int]$MaxDelay = 5,

        [Parameter(Mandatory = $false)]
        [switch]$Silent,

        [Parameter(Mandatory = $false)]
        [ValidateSet("US", "UK", "EU", "JP", "AU", "ES", "IT", "FR", "MX", "CN", "BR", "IN", "KR")]
        [string]$Country = "EU",

        [Parameter(Mandatory = $false)]
        [string]$TimeZone = $null,

        [Parameter(Mandatory = $false)]
        [ValidateRange(0.0, 1.0)]
        [double]$Jitter = 0.2
    )

    begin {
        Write-Verbose " Starting stealth pipeline with $DelayType timing"
        $itemCount = 0
        
        # Business hours mapping for different countries with cultural patterns
        $businessHours = @{
            "US" = @{ Start = 9; End = 17; TimeZone = "Eastern Standard Time"; LunchBreak = $false }
            "UK" = @{ Start = 9; End = 17; TimeZone = "GMT Standard Time"; LunchBreak = $false }
            "EU" = @{ Start = 9; End = 17; TimeZone = "W. Europe Standard Time"; LunchBreak = $false }
            "JP" = @{ Start = 9; End = 18; TimeZone = "Tokyo Standard Time"; LunchBreak = $false }
            "AU" = @{ Start = 9; End = 17; TimeZone = "AUS Eastern Standard Time"; LunchBreak = $false }
            "ES" = @{ Start = 9; End = 14; End2 = 17; End2Close = 20; TimeZone = "Romance Standard Time"; LunchBreak = $true; LunchStart = 14; LunchEnd = 17; SiestaPattern = $true }
            "IT" = @{ Start = 9; End = 13; End2 = 14; End2Close = 18; TimeZone = "W. Europe Standard Time"; LunchBreak = $true; LunchStart = 13; LunchEnd = 14; SiestaPattern = $true }
            "FR" = @{ Start = 9; End = 12; End2 = 14; End2Close = 17; TimeZone = "Romance Standard Time"; LunchBreak = $true; LunchStart = 12; LunchEnd = 14; WorkWeekHours = 35 }
            "MX" = @{ Start = 9; End = 14; End2 = 16; End2Close = 19; TimeZone = "Central Standard Time (Mexico)"; LunchBreak = $true; LunchStart = 14; LunchEnd = 16; SiestaPattern = $true }
            "CN" = @{ Start = 9; End = 12; End2 = 14; End2Close = 18; TimeZone = "China Standard Time"; LunchBreak = $true; LunchStart = 12; LunchEnd = 14; NoonNap = $true }
            "BR" = @{ Start = 8; End = 12; End2 = 13; End2Close = 17; TimeZone = "E. South America Standard Time"; LunchBreak = $true; LunchStart = 12; LunchEnd = 13 }
            "IN" = @{ Start = 9; End = 18; TimeZone = "India Standard Time"; LunchBreak = $false; ExtendedHours = $true }
            "KR" = @{ Start = 9; End = 18; TimeZone = "Korea Standard Time"; LunchBreak = $false; LongWorkCulture = $true }
        }
    }

    process {
        # Calculate stealth delay based on type
        $calculatedDelay = 0
        $businessConfigDescription = $null  # Store for delay message
        
        switch ($DelayType) {
            "Random" {
                $calculatedDelay = Get-Random -Minimum $MinDelay -Maximum $MaxDelay
            }
            
            "Progressive" {
                $step = $itemCount + 1
                $calculatedDelay = $MinDelay + ($step * 0.5)
                if ($calculatedDelay -gt $MaxDelay) {
                    $calculatedDelay = $MaxDelay
                }
            }
            
            "BusinessHours" {
                # Determine timezone and business hours configuration
                $targetTimeZone = $null
                $businessConfig = $null
                
                if ($TimeZone) {
                    # When TimeZone is specified, use generic business hours pattern
                    $businessConfig = @{ 
                        Start = 9; 
                        End = 17; 
                        LunchBreak = $false;
                        Description = "Generic"
                    }
                    
                    # Check if TimeZone is UTC offset format (+2, -5, +5.5, etc.)
                    if ($TimeZone -match '^[+-]\d+(?:\.\d+)?$') {
                        try {
                            $offsetHours = [double]$TimeZone
                            $offsetTimeSpan = [TimeSpan]::FromHours($offsetHours)
                            $targetTimeZone = [System.TimeZoneInfo]::CreateCustomTimeZone(
                                "Custom_UTC$TimeZone",
                                $offsetTimeSpan,
                                "Custom UTC$TimeZone",
                                "Custom UTC$TimeZone"
                            )
                            $businessConfig.Description = "UTC$TimeZone"
                            $businessConfigDescription = $businessConfig.Description
                            Write-Verbose " Using custom UTC offset: $TimeZone with generic business hours"
                        }
                        catch {
                            Write-Warning " Invalid UTC offset format '$TimeZone'. Using country default."
                            $businessConfig = $businessHours[$Country]
                            if (-not $businessConfig) { 
                                $businessConfig = $businessHours["US"]
                                $businessConfigDescription = "US"
                            } else {
                                $businessConfigDescription = $Country
                            }
                            $targetTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($businessConfig.TimeZone)
                        }
                    }
                    else {
                        # Try to use as timezone name
                        try {
                            $targetTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($TimeZone)
                            $businessConfig.Description = $TimeZone
                            $businessConfigDescription = $businessConfig.Description
                            Write-Verbose " Using specified timezone: $TimeZone with generic business hours"
                        }
                        catch {
                            Write-Warning " Timezone '$TimeZone' not found. Using country default."
                            $businessConfig = $businessHours[$Country]
                            if (-not $businessConfig) { 
                                $businessConfig = $businessHours["US"]
                                $businessConfigDescription = "US"
                            } else {
                                $businessConfigDescription = $Country
                            }
                            $targetTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($businessConfig.TimeZone)
                        }
                    }
                }
                else {
                    # Use country-specific business hours and timezone
                    $businessConfig = $businessHours[$Country]
                    if (-not $businessConfig) {
                        $businessConfig = $businessHours["US"]  # Default fallback
                        $businessConfigDescription = "US"
                    } else {
                        $businessConfigDescription = $Country
                    }
                    $targetTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($businessConfig.TimeZone)
                    Write-Verbose " Using country-specific configuration: $Country"
                }
                try {
                    $localTime = [System.TimeZoneInfo]::ConvertTimeFromUtc((Get-Date).ToUniversalTime(), $targetTimeZone)
                }
                catch {
                    $localTime = Get-Date  # Fallback to system time
                }
                
                $currentHour = $localTime.Hour
                $isWeekday = $localTime.DayOfWeek -notin @([DayOfWeek]::Saturday, [DayOfWeek]::Sunday)
                
                # Determine if currently in business hours based on configuration
                $isBusinessHours = $false
                
                if ($businessConfig.LunchBreak) {
                    # Countries with lunch break/siesta patterns (ES, IT, FR, MX, CN, BR)
                    $morningHours = $currentHour -ge $businessConfig.Start -and $currentHour -lt $businessConfig.End
                    
                    if ($businessConfig.End2 -and $businessConfig.End2Close) {
                        $afternoonHours = $currentHour -ge $businessConfig.End2 -and $currentHour -lt $businessConfig.End2Close
                        $isBusinessHours = $morningHours -or $afternoonHours
                    } else {
                        $isBusinessHours = $morningHours
                    }
                    
                    # Check if currently in lunch/siesta break
                    $isLunchBreak = $currentHour -ge $businessConfig.LunchStart -and $currentHour -lt $businessConfig.LunchEnd
                } else {
                    # Standard business hours (US, UK, DE, JP, AU, IN, KR, or generic)
                    $isBusinessHours = $currentHour -ge $businessConfig.Start -and $currentHour -lt $businessConfig.End
                }
                
                # Check if we need to wait for business hours (default behavior for BusinessHours timing)
                if (-not $isBusinessHours -or -not $isWeekday) {
                    $waitSeconds = 0
                    
                    if (-not $isWeekday) {
                        # Calculate time until next Monday
                        $daysUntilMonday = (8 - [int]$localTime.DayOfWeek) % 7
                        if ($daysUntilMonday -eq 0) { $daysUntilMonday = 1 } # If it's Sunday, wait until Monday
                        
                        $nextBusinessDay = $localTime.Date.AddDays($daysUntilMonday).AddHours($businessConfig.Start)
                        $waitSeconds = ($nextBusinessDay - $localTime).TotalSeconds
                    }
                    elseif ($businessConfig.LunchBreak -and $isLunchBreak) {
                        # Currently in lunch break - wait until afternoon session
                        if ($businessConfig.End2) {
                            $afternoonStart = $localTime.Date.AddHours($businessConfig.End2)
                            $waitSeconds = ($afternoonStart - $localTime).TotalSeconds
                        }
                    }
                    elseif ($currentHour -lt $businessConfig.Start) {
                        # Wait until business hours start today
                        $businessStart = $localTime.Date.AddHours($businessConfig.Start)
                        $waitSeconds = ($businessStart - $localTime).TotalSeconds
                    }
                    elseif ($businessConfig.LunchBreak -and $businessConfig.End2Close -and $currentHour -ge $businessConfig.End2Close) {
                        # After business hours for lunch break countries
                        $nextDay = $localTime.Date.AddDays(1)
                        if ($nextDay.DayOfWeek -eq [DayOfWeek]::Saturday) {
                            $nextDay = $nextDay.AddDays(2) # Skip to Monday
                        }
                        elseif ($nextDay.DayOfWeek -eq [DayOfWeek]::Sunday) {
                            $nextDay = $nextDay.AddDays(1) # Skip to Monday
                        }
                        
                        $nextBusinessStart = $nextDay.AddHours($businessConfig.Start)
                        $waitSeconds = ($nextBusinessStart - $localTime).TotalSeconds
                    }
                    elseif (-not $businessConfig.LunchBreak -and $currentHour -ge $businessConfig.End) {
                        # After standard business hours
                        $nextDay = $localTime.Date.AddDays(1)
                        if ($nextDay.DayOfWeek -eq [DayOfWeek]::Saturday) {
                            $nextDay = $nextDay.AddDays(2) # Skip to Monday
                        }
                        elseif ($nextDay.DayOfWeek -eq [DayOfWeek]::Sunday) {
                            $nextDay = $nextDay.AddDays(1) # Skip to Monday
                        }
                        
                        $nextBusinessStart = $nextDay.AddHours($businessConfig.Start)
                        $waitSeconds = ($nextBusinessStart - $localTime).TotalSeconds
                    }
                    
                    if ($waitSeconds -gt 0) {
                        $waitHours = [Math]::Floor($waitSeconds / 3600)
                        $waitMinutes = [Math]::Floor(($waitSeconds % 3600) / 60)
                        
                        if (-not $Silent) {
                            $configDescription = if ($TimeZone) { $businessConfig.Description } else { $Country }
                            $waitMessage = if ($isLunchBreak -and $businessConfig.SiestaPattern) {
                                if ($waitHours -gt 0) {
                                    " Waiting {0}h {1}m until {2} siesta/lunch break ends..." -f $waitHours, $waitMinutes, $configDescription
                                } else {
                                    " Waiting {0}m until {1} siesta/lunch break ends..." -f $waitMinutes, $configDescription
                                }
                            } elseif ($waitHours -gt 0) {
                                " Waiting {0}h {1}m until {2} business hours begin..." -f $waitHours, $waitMinutes, $configDescription
                            } else {
                                " Waiting {0}m until {1} business hours begin..." -f $waitMinutes, $configDescription
                            }
                            Write-Host " $waitMessage" -ForegroundColor Magenta
                        }
                        
                        Start-Sleep -Seconds $waitSeconds
                        
                        # Recalculate current time after waiting using the same timezone logic
                        $localTime = [System.TimeZoneInfo]::ConvertTimeFromUtc((Get-Date).ToUniversalTime(), $targetTimeZone)
                        $currentHour = $localTime.Hour
                        $isWeekday = $localTime.DayOfWeek -notin @([DayOfWeek]::Saturday, [DayOfWeek]::Sunday)
                        
                        # Recalculate business hours status
                        if ($businessConfig.LunchBreak) {
                            $morningHours = $currentHour -ge $businessConfig.Start -and $currentHour -lt $businessConfig.End
                            if ($businessConfig.End2 -and $businessConfig.End2Close) {
                                $afternoonHours = $currentHour -ge $businessConfig.End2 -and $currentHour -lt $businessConfig.End2Close
                                $isBusinessHours = $morningHours -or $afternoonHours
                            } else {
                                $isBusinessHours = $morningHours
                            }
                        } else {
                            $isBusinessHours = $currentHour -ge $businessConfig.Start -and $currentHour -lt $businessConfig.End
                        }
                    }
                }
                
                # Apply appropriate delays based on activity level
                if ($isBusinessHours -and $isWeekday) {
                    # Active business hours - shorter delays
                    $calculatedDelay = Get-Random -Minimum $MinDelay -Maximum ([Math]::Min($MaxDelay, $MinDelay + 3))
                }
                elseif ($businessConfig.LunchBreak -and $isLunchBreak -and $isWeekday) {
                    # Lunch/siesta time - moderate delays (some reduced activity)
                    $lunchMin = [Math]::Max($MinDelay, 5)
                    $lunchMax = [Math]::Max([Math]::Min($MaxDelay, 15), $lunchMin + 1)
                    $calculatedDelay = Get-Random -Minimum $lunchMin -Maximum $lunchMax
                }
                else {
                    # Outside business hours - longer delays to simulate reduced activity
                    $outsideHoursMin = [Math]::Max($MinDelay, 10)
                    $outsideHoursMax = [Math]::Max($MaxDelay, $outsideHoursMin + 1)
                    $calculatedDelay = Get-Random -Minimum $outsideHoursMin -Maximum $outsideHoursMax
                }
            }
            
            "Exponential" {
                $retryCount = $itemCount + 1
                $baseDelay = [Math]::Max($MinDelay, 1)
                $calculatedDelay = $baseDelay * [Math]::Pow(2, [Math]::Min($retryCount - 1, 6))  # Cap at 2^6
                if ($calculatedDelay -gt $MaxDelay) {
                    $calculatedDelay = $MaxDelay
                }
            }
        }
        
        # Apply jitter to prevent pattern detection
        if ($Jitter -gt 0) {
            $jitterAmount = $calculatedDelay * $Jitter * (Get-Random -Minimum -1.0 -Maximum 1.0)
            $calculatedDelay = [Math]::Max(0, $calculatedDelay + $jitterAmount)
        }
        
        # Round to reasonable precision
        $calculatedDelay = [Math]::Round($calculatedDelay, 1)
        
        # Apply the delay
        if ($calculatedDelay -gt 0) {
            if (-not $Silent) {
                $delayMessage = switch ($DelayType) {
                    "BusinessHours" {
                        $timeInfo = " ($businessConfigDescription)"
                        # Context-aware emoji based on configuration
                        $emoji = if ($TimeZone) { "" } else { "" }
                        "$emoji Stealth delay: {0}s (BusinessHours{1})" -f $calculatedDelay, $timeInfo
                    }
                    "Progressive" {
                        " Stealth delay: {0}s (Progressive - Step {1})" -f $calculatedDelay, ($itemCount + 1)
                    }
                    "Exponential" {
                        " Stealth delay: {0}s (Exponential - Level {1})" -f $calculatedDelay, ($itemCount + 1)
                    }
                    default {
                        " Stealth delay: {0}s ({1})" -f $calculatedDelay, $DelayType
                    }
                }
                Write-Host " $delayMessage" -ForegroundColor DarkYellow
            }
            
            Start-Sleep -Seconds $calculatedDelay
        }

        # Pass through the input object
        if ($PSBoundParameters.ContainsKey('InputObject')) {
            Write-Output $InputObject
        }

        $itemCount++
    }

    end {
        Write-Verbose " Stealth pipeline completed for $itemCount items"
    }
<#
    .SYNOPSIS
        Executes operations with configurable stealth timing delays.
 
    .DESCRIPTION
        Processes input objects through a pipeline while applying intelligent timing delays to avoid detection patterns and simulate human-like behavior. Supports multiple delay strategies including random intervals, progressive timing, business hours simulation with geographic customization, and exponential backoff. Essential for rate limiting, anti-detection measures, and evading behavioral analysis.
 
    .PARAMETER InputObject
        Objects to process through the stealth pipeline. Accepts pipeline input.
 
    .PARAMETER DelayType
        Specifies the delay pattern strategy:
        - Random: Random delays between min and max values
        - Progressive: Incrementally increasing delays per item
        - BusinessHours: Simulates activity during business hours (automatically waits for business hours)
        - Exponential: Exponential backoff pattern for each item
        Default: Random
 
    .PARAMETER MinDelay
        Minimum delay duration in seconds (0-600). Default: 1
 
    .PARAMETER MaxDelay
        Maximum delay duration in seconds (1-3600). Default: 5
 
    .PARAMETER Silent
        Suppresses delay notification messages when specified.
 
    .PARAMETER Country
        Two-letter country code for business hours timing. Valid values:
        US, UK, EU, JP, AU, ES, IT, FR, MX, CN, BR, IN, KR.
        Only used when DelayType is BusinessHours. Default: "US"
 
    .PARAMETER TimeZone
        Override timezone for business hours calculation. Supports timezone names
        (e.g., "Pacific Standard Time") or UTC offset notation (e.g., "+2", "-5", "+5.5").
        Only used when DelayType is BusinessHours.
 
    .PARAMETER Jitter
        Random jitter percentage to add to delays (0.0-1.0).
        Adds randomness to prevent pattern detection. Default: 0.2
 
    .EXAMPLE
        Invoke-StealthOperation | Find-PublicStorageContainer -StorageAccountName "test" Executes storage discovery with default random stealth timing.
 
    .EXAMPLE
        "example.com", "test.com" | Invoke-StealthOperation -DelayType BusinessHours -Country "UK" | ForEach-Object {
            Find-DnsRecords -Domain $_
        }
 
        Processes domains with UK business hours timing simulation.
 
    .EXAMPLE
        Get-Content domains.txt | Invoke-StealthOperation -MinDelay 30 -MaxDelay 180 -Silent | ForEach-Object {
            Find-SubDomain -Domain $_
        }
 
        Performs subdomain enumeration with extended delays (30-180 seconds) without status messages.
 
    .EXAMPLE
        1..10 | Invoke-StealthOperation -DelayType Exponential | ForEach-Object {
            Test-Connection -Count 1 -ComputerName "server$_"
        }
 
        Tests multiple servers with exponentially increasing delays between operations.
 
    .EXAMPLE
        "target.com" | Invoke-StealthOperation -DelayType BusinessHours -Country "UK" | ForEach-Object {
            Find-DnsRecords -Domain $_
        }
 
        Automatically waits until UK business hours before executing DNS reconnaissance.
 
    .EXAMPLE
        "target.com" | Invoke-StealthOperation -DelayType BusinessHours -Country "EU" -TimeZone "+2" | ForEach-Object {
            Find-DnsRecords -Domain $_
        }
 
        Uses German business culture with UTC+2 timezone offset for precise timing.
 
    .NOTES
        Built-in stealth delay implementation eliminates dependency on external functions.
        Consider network policies and rate limits when configuring delay parameters.
 
    .LINK
        MITRE ATT&CK Tactic: TA0005 - Defense Evasion
        https://attack.mitre.org/tactics/TA0005/
 
    .LINK
        MITRE ATT&CK Technique: T1562.003 - Impair Defenses: Impair Command History Logging
        https://attack.mitre.org/techniques/T1562/003/
 
    #>

}