[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $false,
ValueFromPipeline = $true, Position = 0)] [string[]]$IPAddresses,
[Parameter(Mandatory = $false)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string]$InputFile,
[Parameter(Mandatory = $false)] [string]$OutputPath,
[Parameter(Mandatory = $false)] [ValidateRange(0, 5000)] [int]$RateLimitDelay = 250,
[Parameter(Mandatory = $false)] [string]$LogPath = ".\whois-lookup-$(Get-Date -Format 'yyyyMMdd-HHmmss').log",
[Parameter(Mandatory = $false)] [switch]$IncludeGeoData,
[Parameter(Mandatory = $false)] [switch]$ContinueOnError
)
begin {
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
# Stats tracking
$script:stats = @{
Total = 0 Success = 0 Failed = 0 Skipped = 0 StartTime = Get-Date
}
# Collection for pipeline input
$pipelineIPs = [System.Collections.Generic.List[string]]::new()
# Logging function
function Write-Log {
param(
[string]$Message,
[ValidateSet('INFO', 'WARN', 'ERROR', 'SUCCESS')]
[string]$Level = 'INFO'
)
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$logMessage = "[$timestamp] [$Level] $Message"
# Console output with color
$color = switch
($Level) {
'ERROR' { 'Red' }
'WARN' { 'Yellow' }
'SUCCESS' { 'Green' }
default { 'White' }
}
Write-Host $logMessage -ForegroundColor $color
# File output
try {
Add-Content -Path $LogPath -Value $logMessage -ErrorAction SilentlyContinue
}
catch {
Write-Warning "Failed to write to log: $($_.Exception.Message)"
}
}
# Validate IP format
function Test-IPAddress {
param([string]$IP) if ([string]::IsNullOrWhiteSpace($IP)) {
return $false
}
try {
$octets = $IP.Trim() -split '\.'
if ($octets.Count -ne 4) {
return $false
}
foreach ($octet in $octets) {
$num = [int]$octet
if ($num -lt 0 -or $num -gt 255) {
return $false
}
}
return $true
}
catch {
return $false
}
}
# Walk full exception chain - GetAwaiter wraps in AggregateException hiding the real cause
function Get-ExceptionChain {
param(
[System.Exception]$ex
)
$msgs = [System.Collections.Generic.List[string]]::new()
# Unwrap AggregateException first
if ($ex -is [System.AggregateException]) {
$ex = $ex.GetBaseException()
}
while ($ex) {
if ($msgs -notcontains $ex.Message) {
$msgs.Add($ex.Message)
}
$ex = $ex.InnerException
}
return ($msgs -join ' → ')
}