CertificateScanner.ps1
|
<#PSScriptInfo .VERSION 3.9.3.0 .GUID 163f0d06-5bef-4d9a-bf8b-0c353b92ffc0 .AUTHOR Faris Malaeb .IMPROVEMENT Alessandro Tanino .COMPANYNAME powershellcenter.com .COPYRIGHT .TAGS SSL, Certificate, Scan, Network, OS Detection, DNS .LICENSEURI .PROJECTURI https://www.powershellcenter.com/2021/12/23/sslexpirationcheck/ .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES - Added -DnsServer parameter by user request. When used with -IncludeReverseDNS, this allows specifying a custom DNS server for all reverse lookup queries. - Created a new robust helper function for reverse DNS lookups that uses Resolve-DnsName with an nslookup fallback. - Integrated the custom DNS functionality across all scan types (Single, File, Network). - Updated help and examples to reflect the new -DnsServer parameter. .PRIVATEDATA #> <# .SYNOPSIS Comprehensive SSL/TLS Certificate Scanner with Network Discovery and OS Detection .DESCRIPTION Scan websites, IP addresses, or entire network ranges for SSL/TLS certificate details including expiration dates, issuer information, and subject details. Supports HTTPS and LDAPS protocols with optional OS detection and reverse DNS lookup. Features: - Single site scanning - Bulk file-based scanning - Network range scanning with CIDR notation - Parallel processing for network scans - HTTPS and LDAPS certificate detection - OS fingerprinting for certificate hosts - Expiration filtering and monitoring - CSV export with append functionality - Email reporting capabilities - Reverse DNS resolution (with optional custom DNS server) .PARAMETER Examples Display detailed usage examples and scenarios .PARAMETER Help Display this help information (alias: -?) .EXAMPLE .\CertificateScanner.ps1 -Examples Display comprehensive usage examples .EXAMPLE .\CertificateScanner.ps1 -Help Display help and syntax information #> #Requires -Version 7.0 [CmdletBinding(DefaultParameterSetName='Help')] param( # Core scanning parameters [Alias("FilePath")] [parameter(mandatory=$true,ParameterSetName="ReadFromFile")]$LoadFromFile, [parameter(mandatory=$true,ParameterSetName="Online")]$SiteToScan, [parameter(mandatory=$true,ParameterSetName="NetworkScan")]$Networks, # Common parameters for all scanning operations [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [validateset("Tls","Tls11","Tls12","Ssl3","Default")]$ProtocolVersion='TLS12', [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$SaveAsTo, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$TimeoutSeconds=5, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [switch]$IncludeReverseDNS, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [string]$DnsServer, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [int]$ExpiresInDays = $null, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [switch]$GetOsType, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [switch]$ExcludeExpired, # Email parameters (all scanning types support email) [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$EmailSendTo, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$EmailFrom, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$EmailSMTPServer, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$EmailSMTPServerPort="25", [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")] [switch]$EmailSMTPServerSSL=$false, [parameter(ParameterSetName="ReadFromFile")] [parameter(ParameterSetName="Online")] [parameter(ParameterSetName="NetworkScan")]$EmailSubject, # Network scan specific parameters [parameter(ParameterSetName="NetworkScan")]$Port=443, [parameter(ParameterSetName="NetworkScan")]$AdditionalHTTPSPorts=@(), [parameter(ParameterSetName="NetworkScan")]$MaxThreads=10, [parameter(ParameterSetName="NetworkScan")][switch]$IncludeLDAPS=$false, [parameter(ParameterSetName="NetworkScan")][switch]$LDAPSOnly=$false, # Help parameters [parameter(ParameterSetName="Help")] [parameter(ParameterSetName="Examples")] [switch]$Examples, [parameter(ParameterSetName="Help")] [Alias("?")] [switch]$Help ) Function Show-Help { Write-Host @" ╔══════════════════════════════════════════════════════════════════════════════════════╗ ║ SSL/TLS Certificate Scanner v3.9.3.0 ║ ║ Network Discovery & Certificate Analysis Tool ║ ╚══════════════════════════════════════════════════════════════════════════════════════╝ SYNOPSIS Comprehensive SSL/TLS certificate scanner with network discovery, OS detection, and expiration monitoring capabilities. SYNTAX # Single Site Scanning .\CertificateScanner.ps1 -SiteToScan <URL> [-ProtocolVersion <Protocol>] [-SaveAsTo <Path>] [-IncludeReverseDNS] [-DnsServer <IP>] [-GetOsType] [-ExpiresInDays <Days>] # Network Range Scanning .\CertificateScanner.ps1 -Networks <CIDR> [-Port <Port>] [-MaxThreads <Count>] [-AdditionalHTTPSPorts <Ports>] [-TimeoutSeconds <Seconds>] [-IncludeLDAPS] [-LDAPSOnly] [-SaveAsTo <Path>] [-IncludeReverseDNS] [-DnsServer <IP>] [-GetOsType] [-ExpiresInDays <Days>] # File-Based Scanning .\CertificateScanner.ps1 -LoadFromFile <FilePath> [-SaveAsTo <Path>] [-IncludeReverseDNS] [-DnsServer <IP>] [-GetOsType] [-ExpiresInDays <Days>] # Email Reporting (any scan type) Add: -EmailSendTo <Email> -EmailFrom <Email> -EmailSMTPServer <Server> -EmailSubject <Subject> [-EmailSMTPServerPort <Port>] [-EmailSMTPServerSSL] PARAMETERS -SiteToScan <String> Target website or IP address (supports port notation) -Networks <String> CIDR notation networks (comma-separated) -LoadFromFile <String> File path containing list of URLs/IPs -Port <Int> Primary HTTPS port (default: 443) -AdditionalHTTPSPorts <Array> Additional HTTPS ports to scan -ProtocolVersion <String> SSL/TLS version (Tls12, Tls11, Tls, Ssl3, Default) -TimeoutSeconds <Int> Connection timeout in seconds (default: 5) -MaxThreads <Int> Parallel scanning threads (default: 10) -ExpiresInDays <Int> Filter certificates expiring in X days (0 = expired only) -ExcludeExpired Exclude already expired certificates from results -SaveAsTo <String> CSV output file (use +filename to append) -IncludeLDAPS Include LDAPS (port 636) scanning -LDAPSOnly Scan only LDAPS certificates -IncludeReverseDNS Perform reverse DNS lookups -DnsServer <String> Optional: Custom DNS server for reverse lookups -GetOsType Detect operating system of certificate hosts -Examples Show detailed usage examples -Help, -? Show this help information QUICK EXAMPLES # Basic website scan .\CertificateScanner.ps1 -SiteToScan www.google.com # Network scan with OS detection and custom DNS for lookups .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -GetOsType -IncludeReverseDNS -DnsServer 192.168.1.1 -SaveAsTo network_scan.csv # Find expiring certificates .\CertificateScanner.ps1 -Networks "10.0.0.0/16" -ExpiresInDays 30 -SaveAsTo +expiring.csv # LDAPS scan with email report .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -LDAPSOnly -EmailSendTo admin@company.com -EmailFrom scanner@company.com -EmailSMTPServer mail.company.com -EmailSubject "LDAPS Certificate Report" For detailed examples and advanced scenarios, run: .\CertificateScanner.ps1 -Examples "@ -ForegroundColor Cyan } Function Show-Examples { Write-Host @" ╔══════════════════════════════════════════════════════════════════════════════════════╗ ║ Certificate Scanner - Usage Examples ║ ╚══════════════════════════════════════════════════════════════════════════════════════╝ 1. SINGLE SITE SCANNING ══════════════════════ # Basic website scan .\CertificateScanner.ps1 -SiteToScan www.powershellcenter.com # Scan with custom port .\CertificateScanner.ps1 -SiteToScan mail.company.com:993 # Scan with older SSL protocol .\CertificateScanner.ps1 -SiteToScan legacy.server.com -ProtocolVersion Ssl3 # Comprehensive single site analysis with a specific internal DNS server .\CertificateScanner.ps1 -SiteToScan www.company.com -IncludeReverseDNS -DnsServer 10.1.1.1 -GetOsType -SaveAsTo site_analysis.csv 2. NETWORK SCANNING ══════════════════ # Basic network scan .\CertificateScanner.ps1 -Networks "192.168.1.0/24" # Multiple networks .\CertificateScanner.ps1 -Networks "10.10.2.0/24,10.2.4.0/24,172.20.3.0/22" # Large network with performance tuning .\CertificateScanner.ps1 -Networks "10.0.0.0/16" -MaxThreads 20 -TimeoutSeconds 3 # Scan multiple HTTPS ports .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -Port 8443 -AdditionalHTTPSPorts 443,8080,9443 # Complete network analysis with custom DNS .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -IncludeLDAPS -IncludeReverseDNS -DnsServer 192.168.1.1 -GetOsType -SaveAsTo full_scan.csv 3. FILE-BASED SCANNING ═══════════════════════ # Create targets file first # echo "www.google.com`nwww.microsoft.com`nmail.company.com:993" > targets.txt # Scan from file .\CertificateScanner.ps1 -LoadFromFile targets.txt # File scan with comprehensive analysis and custom DNS .\CertificateScanner.ps1 -LoadFromFile critical_servers.txt -IncludeReverseDNS -DnsServer 10.5.5.1 -GetOsType -SaveAsTo server_certs.csv 4. EXPIRATION MONITORING ═══════════════════════ # Find already expired certificates .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -ExpiresInDays 0 # Find certificates expiring within 30 days .\CertificateScanner.ps1 -Networks "10.0.0.0/16" -ExpiresInDays 30 -SaveAsTo expiring_soon.csv # Weekly expiration check (90 days) .\CertificateScanner.ps1 -LoadFromFile production_servers.txt -ExpiresInDays 90 -SaveAsTo +weekly_check.csv 5. SAVING AND APPENDING RESULTS ══════════════════════════════ # Save to new file .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -SaveAsTo network_certs.csv # Append to existing file (note the + prefix) .\CertificateScanner.ps1 -Networks "192.168.2.0/24" -SaveAsTo +network_certs.csv # Daily monitoring with date stamps `$date = Get-Date -Format "yyyy-MM-dd" .\CertificateScanner.ps1 -Networks "10.0.0.0/16" -ExpiresInDays 30 -SaveAsTo "daily_scan_`$date.csv" 6. LDAPS CERTIFICATE SCANNING ════════════════════════════ # Include LDAPS in regular scan .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -IncludeLDAPS # Scan only LDAPS certificates .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -LDAPSOnly # LDAPS expiration monitoring .\CertificateScanner.ps1 -Networks "10.0.0.0/16" -LDAPSOnly -ExpiresInDays 60 -SaveAsTo ldaps_expiring.csv 7. ADVANCED SCENARIOS ═══════════════════ # High-speed enterprise scan .\CertificateScanner.ps1 -Networks "10.0.0.0/8" -MaxThreads 50 -TimeoutSeconds 2 -Port 443 -AdditionalHTTPSPorts 8443,9443 # Security audit with OS fingerprinting and custom DNS .\CertificateScanner.ps1 -Networks "192.168.0.0/16" -IncludeLDAPS -GetOsType -IncludeReverseDNS -DnsServer 10.10.10.254 -SaveAsTo security_audit.csv # Legacy system scan .\CertificateScanner.ps1 -LoadFromFile legacy_systems.txt -ProtocolVersion Ssl3 -TimeoutSeconds 10 8. EMAIL REPORTING ═════════════════ # Basic email report .\CertificateScanner.ps1 -Networks "192.168.1.0/24" -ExpiresInDays 30 ` -EmailSendTo admin@company.com ` -EmailFrom certscanner@company.com ` -EmailSMTPServer mail.company.com ` -EmailSubject "Weekly Certificate Expiration Report" # Secure SMTP with SSL .\CertificateScanner.ps1 -SiteToScan critical.company.com -ExpiresInDays 7 ` -EmailSendTo security@company.com ` -EmailFrom alerts@company.com ` -EmailSMTPServer smtp.gmail.com ` -EmailSMTPServerPort 587 ` -EmailSMTPServerSSL ` -EmailSubject "URGENT: Certificate Expiring Soon" 9. MONITORING WORKFLOWS ═════════════════════ # Daily expired certificate check .\CertificateScanner.ps1 -Networks "10.0.0.0/16" -ExpiresInDays 0 -SaveAsTo +expired_certs.csv if ((Import-Csv expired_certs.csv | Measure-Object).Count -gt 0) { # Send alert email } # Weekly comprehensive scan with archiving `$timestamp = Get-Date -Format "yyyy-MM-dd_HHmm" .\CertificateScanner.ps1 -Networks "192.168.0.0/16" -IncludeLDAPS -GetOsType ` -SaveAsTo "weekly_scan_`$timestamp.csv" # Monthly expiration report `$nextMonth = (Get-Date).AddDays(30).ToString("yyyy-MM-dd") .\CertificateScanner.ps1 -LoadFromFile all_servers.txt -ExpiresInDays 30 ` -EmailSendTo management@company.com ` -EmailFrom certificates@company.com ` -EmailSMTPServer internal-mail.company.com ` -EmailSubject "Monthly Certificate Expiration Report - `$nextMonth" 10. PRO TIPS ════════ # Performance optimization for large networks - Use MaxThreads between 10-50 (test optimal value for your network) - Reduce TimeoutSeconds to 2-3 for faster scans of responsive networks - Use specific port lists instead of scanning all default ports # Filtering and organization - Use ExpiresInDays 0 to find expired certificates immediately - Append mode (+filename) is perfect for scheduled monitoring - Combine with PowerShell filtering for advanced analysis: Import-Csv results.csv | Where-Object {`$_.Issuer -like "*Let's Encrypt*"} # Automation integration - Schedule with Task Scheduler or cron - Use exit codes: `$LASTEXITCODE will indicate scan success - Parse CSV results for integration with monitoring systems - Use email reports for immediate alerting # Troubleshooting - Use -ProtocolVersion Ssl3 for very old systems - Increase TimeoutSeconds for slow networks - Enable -IncludeReverseDNS for better host identification (use -DnsServer for internal networks) - Use -GetOsType for security auditing and system classification For more information visit: https://www.powershellcenter.com "@ -ForegroundColor Green } # Help system logic - check if help should be displayed if ($PSCmdlet.ParameterSetName -eq 'Help' -or $Help -or $Examples -or ($PSBoundParameters.Count -eq 0)) { if ($Examples) { Show-Examples } else { Show-Help } return } Function Get-ReverseDnsName { param( [string]$IPAddress, [string]$DnsServer ) try { if (-not ([string]::IsNullOrEmpty($DnsServer))) { # Custom DNS server specified # Use Resolve-DnsName as it's the modern, preferred method if (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue) { $result = Resolve-DnsName -Name $IPAddress -Type PTR -Server $DnsServer -DnsOnly -ErrorAction SilentlyContinue if ($result) { # In case of multiple PTR records, return the first one return ($result.NameHost | Select-Object -First 1) } else { return "No PTR record (via $DnsServer)" } } else { # Fallback to nslookup if Resolve-DnsName is not available (e.g., older PowerShell) $nslookupResult = nslookup -type=PTR $IPAddress $DnsServer $ptrLine = $nslookupResult | Select-String -Pattern "name =" if ($ptrLine) { # Output can be an array, take the first match return (($ptrLine | Select-Object -First 1).ToString() -split '= ')[1].Trim() } else { return "No PTR record (nslookup fallback)" } } } else { # Use system default DNS $hostEntry = [System.Net.Dns]::GetHostEntry($IPAddress) return $hostEntry.HostName } } catch { return "Reverse DNS Error: $($_.Exception.Message)" } } Function ConvertTo-IPRange { param( [string]$CIDR ) $CIDR = $CIDR.Trim() $parts = $CIDR.Split('/') $ip = $parts[0] $subnet = [int]$parts[1] # Convert IP to integer $ipBytes = [System.Net.IPAddress]::Parse($ip).GetAddressBytes() [Array]::Reverse($ipBytes) $ipInt = [System.BitConverter]::ToUInt32($ipBytes, 0) # Calculate network range $mask = [Math]::Pow(2, 32 - $subnet) - 1 $networkInt = $ipInt -band (-bnot $mask) $broadcastInt = $networkInt -bor $mask # Generate all IPs in range (excluding network and broadcast addresses for /24 and larger) $ips = @() $start = if ($subnet -ge 24) { $networkInt + 1 } else { $networkInt } $end = if ($subnet -ge 24) { $broadcastInt - 1 } else { $broadcastInt } for ($i = $start; $i -le $end; $i++) { $bytes = [System.BitConverter]::GetBytes($i) [Array]::Reverse($bytes) $ips += [System.Net.IPAddress]::new($bytes).ToString() } return $ips } Function Test-TCPPort { param( [string]$IPAddress, [int]$Port, [int]$TimeoutMs = 5000 ) try { $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($IPAddress, $Port, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if ($wait) { $tcpClient.EndConnect($asyncResult) $tcpClient.Close() return $true } else { $tcpClient.Close() return $false } } catch { if ($tcpClient) { $tcpClient.Close() } return $false } } Function Get-OSTypeFromHost { param( [string]$IPAddress, [int]$TimeoutMs = 3000 ) $osInfo = [PSCustomObject]@{ OSType = "Unknown" OSDetails = "" DetectionMethod = "" Confidence = "Low" } try { # Method 1: TTL Analysis via ping $pingResult = Test-Connection -ComputerName $IPAddress -Count 1 -Quiet -TimeoutSeconds ($TimeoutMs/1000) -ErrorAction SilentlyContinue if ($pingResult) { # Get detailed ping info for TTL try { $pingDetails = Test-Connection -ComputerName $IPAddress -Count 1 -TimeoutSeconds ($TimeoutMs/1000) -ErrorAction SilentlyContinue if ($pingDetails -and $pingDetails.ResponseTime -ge 0) { # Try to get TTL from ping $ttl = $null if ($pingDetails.PSObject.Properties.Name -contains "TimeToLive") { $ttl = $pingDetails.TimeToLive } if ($ttl) { switch ($ttl) { {$_ -le 64 -and $_ -gt 56} { $osInfo.OSType = "Linux/Unix" $osInfo.OSDetails = "TTL: $ttl (Linux/Unix range)" $osInfo.DetectionMethod = "TTL Analysis" $osInfo.Confidence = "Medium" } {$_ -le 128 -and $_ -gt 120} { $osInfo.OSType = "Windows" $osInfo.OSDetails = "TTL: $ttl (Windows range)" $osInfo.DetectionMethod = "TTL Analysis" $osInfo.Confidence = "Medium" } {$_ -le 255 -and $_ -gt 240} { $osInfo.OSType = "Network Device" $osInfo.OSDetails = "TTL: $ttl (Network equipment range)" $osInfo.DetectionMethod = "TTL Analysis" $osInfo.Confidence = "Medium" } } } } } catch { # TTL detection failed, continue with port analysis } } # Method 2: Port-based OS detection $portTests = @() # Test Windows-specific ports $windowsPorts = @(135, 139, 445, 3389) # RPC, NetBIOS, SMB, RDP $linuxPorts = @(22, 111, 2049) # SSH, RPC, NFS $webPorts = @(80, 8080, 8443) # Additional web ports $windowsPortsOpen = 0 $linuxPortsOpen = 0 $webPortsOpen = 0 foreach ($port in $windowsPorts) { if (Test-TCPPort -IPAddress $IPAddress -Port $port -TimeoutMs 2000) { $windowsPortsOpen++ $portTests += "Windows port $port" } } foreach ($port in $linuxPorts) { if (Test-TCPPort -IPAddress $IPAddress -Port $port -TimeoutMs 2000) { $linuxPortsOpen++ $portTests += "Linux port $port" } } foreach ($port in $webPorts) { if (Test-TCPPort -IPAddress $IPAddress -Port $port -TimeoutMs 2000) { $webPortsOpen++ $portTests += "Web port $port" } } # Determine OS based on port analysis (override TTL if more definitive) if ($windowsPortsOpen -ge 2) { $osInfo.OSType = "Windows" $osInfo.OSDetails = "Ports: " + ($portTests -join ', ') $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "High" } elseif ($linuxPortsOpen -ge 1) { $osInfo.OSType = "Linux/Unix" $osInfo.OSDetails = "Ports: " + ($portTests -join ', ') $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "High" } elseif ($windowsPortsOpen -eq 1 -and $linuxPortsOpen -eq 0) { $osInfo.OSType = "Likely Windows" $osInfo.OSDetails = "Ports: " + ($portTests -join ', ') $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "Medium" } elseif ($webPortsOpen -gt 0 -and $windowsPortsOpen -eq 0 -and $linuxPortsOpen -eq 0) { $osInfo.OSType = "Web Server/Appliance" $osInfo.OSDetails = "Ports: " + ($portTests -join ', ') $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "Medium" } } catch { $osInfo.OSDetails = "Error: $($_.Exception.Message)" } return $osInfo } Function Get-LDAPSCertificate { param( [string]$ServerName, [int]$Port = 636, [int]$TimeoutMs = 5000, [string]$ProtocolVersion = 'TLS12', [switch]$IncludeReverseDNS, [switch]$GetOsType, [string]$DnsServer ) $reverseDNS = "" if ($IncludeReverseDNS) { $reverseDNS = Get-ReverseDnsName -IPAddress $ServerName -DnsServer $DnsServer } $results = [PSCustomObject]@{ URL = $ServerName Port = $Port ServiceType = "LDAPS" StartDate = '' EndDate = '' Issuer = '' Subject = '' Protocol = $ProtocolVersion Status = "Unknown" ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId ReverseDNSName = $reverseDNS OSType = '' OSDetails = '' OSDetectionMethod = '' OSConfidence = '' } try { # Create TCP client with timeout $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($ServerName, $Port, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if (-not $wait) { $tcpClient.Close() throw "Connection timeout after $($TimeoutMs/1000) seconds" } $tcpClient.EndConnect($asyncResult) $networkStream = $tcpClient.GetStream() # Create SSL stream - LDAPS uses direct SSL/TLS connection $sslStream = New-Object System.Net.Security.SslStream($networkStream, $false, { param($sender, $certificate, $chain, $sslPolicyErrors) # Accept all certificates for scanning purposes $true }) # Map protocol version string to enum $protocolEnum = switch ($ProtocolVersion) { 'Ssl3' { [System.Security.Authentication.SslProtocols]::Ssl3 } 'Tls' { [System.Security.Authentication.SslProtocols]::Tls } 'Tls11' { [System.Security.Authentication.SslProtocols]::Tls11 } 'Tls12' { [System.Security.Authentication.SslProtocols]::Tls12 } 'Default' { [System.Security.Authentication.SslProtocols]::Default } default { [System.Security.Authentication.SslProtocols]::Tls12 } } # Authenticate as client (this establishes the SSL/TLS connection and exchanges certificates) $sslStream.AuthenticateAsClient($ServerName, $null, $protocolEnum, $false) # Extract certificate information if ($sslStream.RemoteCertificate) { $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($sslStream.RemoteCertificate) $results.StartDate = $cert.NotBefore $results.EndDate = $cert.NotAfter $results.Issuer = $cert.Issuer $results.Subject = $cert.Subject $results.Status = 'Success' } else { throw "No certificate received from server" } # Clean up $sslStream.Close() $networkStream.Close() $tcpClient.Close() # Add OS detection after successful certificate scan if ($GetOsType -and $results.Status -eq 'Success') { $osInfo = Get-OSTypeFromHost -IPAddress $ServerName -TimeoutMs $TimeoutMs $results.OSType = $osInfo.OSType $results.OSDetails = $osInfo.OSDetails $results.OSDetectionMethod = $osInfo.DetectionMethod $results.OSConfidence = $osInfo.Confidence } } catch { $results.StartDate = $_.Exception.Message $results.EndDate = "LDAPS connection error. Check if LDAPS is enabled and certificate is valid." $results.Status = 'Error' # Clean up on error try { if ($sslStream) { $sslStream.Close() } } catch { } try { if ($networkStream) { $networkStream.Close() } } catch { } try { if ($tcpClient) { $tcpClient.Close() } } catch { } } return $results } Function ScanSiteInformation{ param( $URLScanSiteInfo, $CustomPort = $null, $TimeoutMs = 5000, $ServiceType = "HTTPS", [switch]$IncludeReverseDNS, [switch]$GetOsType, [string]$DnsServer = $null, # Fixed parameter name to match usage [int]$TimeoutSeconds = 5, [int]$ExpiresInDays = $null, [string]$ProtocolVersion = 'TLS12' ) # Convert TimeoutSeconds to milliseconds if provided if ($PSBoundParameters.ContainsKey('TimeoutSeconds')) { $TimeoutMs = $TimeoutSeconds * 1000 } # Clean up URL - remove protocol if present if ($URLScanSiteInfo -match '([a-z]+|[A-Z]+):\/\/'){ $URLScanSiteInfo=$URLScanSiteInfo.Substring($Matches[0].Length) } # Remove trailing slash if present if ($URLScanSiteInfo -match '\/'){ $URLScanSiteInfo=$URLScanSiteInfo.Substring(0,$URLScanSiteInfo.Length-1) } # Check for port notation (hostname:port) - FIXED REGEX if ($URLScanSiteInfo -match "^(.+?):(\d+)$") { $URLScanSite = $Matches[1] $PortToScan = [int]$Matches[2] } else { $URLScanSite = $URLScanSiteInfo $PortToScan = if ($CustomPort) { [int]$CustomPort } else { 443 } } $reverseDNS = "" if ($IncludeReverseDNS) { $reverseDNS = Get-ReverseDnsName -IPAddress $URLScanSite -DnsServer $DnsServer } # For LDAPS, use specialized function if ($ServiceType -eq "LDAPS" -or $PortToScan -eq 636) { return Get-LDAPSCertificate -ServerName $URLScanSite -Port $PortToScan -TimeoutMs $TimeoutMs -ProtocolVersion $ProtocolVersion -IncludeReverseDNS:$IncludeReverseDNS -GetOsType:$GetOsType -DnsServer $DnsServer } # Initialize results object outside the Try/Catch to ensure it's always available $results=[PSCustomObject]@{ URL=$URLScanSiteInfo Port=$PortToScan ServiceType=$ServiceType StartDate='' EndDate='' Issuer='' Subject='' Protocol=$ProtocolVersion Status="Unknown" ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId ReverseDNSName = $reverseDNS DNSServer = if ($DnsServer) { $DnsServer } else { "System DNS" } OSType = '' OSDetails = '' OSDetectionMethod = '' OSConfidence = '' DaysUntilExpiration = $null ExpirationStatus = '' } Try{ # Add timeout for socket connection $socket = New-Object Net.Sockets.TcpClient $asyncResult = $socket.BeginConnect($URLScanSite, $PortToScan, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if (-not $wait) { $socket.Close() throw "Connection timeout after $($TimeoutMs/1000) seconds" } $socket.EndConnect($asyncResult) $stream = $socket.GetStream() $sslStream = New-Object System.Net.Security.SslStream($stream, $false, ({ $True } -as [Net.Security.RemoteCertificateValidationCallback])) # Map protocol version string to enum - FIXED MAPPING $protocolEnum = switch ($ProtocolVersion) { 'Ssl3' { [System.Security.Authentication.SslProtocols]::Ssl3 } 'Tls' { [System.Security.Authentication.SslProtocols]::Tls } 'Tls11' { [System.Security.Authentication.SslProtocols]::Tls11 } 'Tls12' { [System.Security.Authentication.SslProtocols]::Tls12 } 'Default' { [System.Security.Authentication.SslProtocols]::Default } default { [System.Security.Authentication.SslProtocols]::Tls12 } } $sslStream.AuthenticateAsClient($URLScanSite, $null, $protocolEnum, $false) # Populate certificate details $results.StartDate = $sslStream.RemoteCertificate.NotBefore $results.EndDate = $sslStream.RemoteCertificate.NotAfter $results.Issuer=$sslStream.RemoteCertificate.Issuer $results.Subject=$sslStream.RemoteCertificate.Subject $results.Status = "Success" # Calculate days until expiration if ExpiresInDays parameter was provided if ($PSBoundParameters.ContainsKey('ExpiresInDays')) { $today = (Get-Date).Date $certEndDate = [datetime]$sslStream.RemoteCertificate.NotAfter $daysUntilExpiration = ($certEndDate.Date - $today).Days $results.DaysUntilExpiration = $daysUntilExpiration # Set expiration status if ($daysUntilExpiration -lt 0) { $results.ExpirationStatus = "EXPIRED" } elseif ($daysUntilExpiration -le $ExpiresInDays) { $results.ExpirationStatus = "EXPIRING SOON" } else { $results.ExpirationStatus = "OK" } } $socket.close() # Add OS detection after successful certificate scan if ($GetOsType -and $results.Status -eq "Success") { Write-Host "Detecting OS for $URLScanSite..." -ForegroundColor Yellow $osInfo = Get-OSTypeFromHost -IPAddress $URLScanSite -TimeoutMs $TimeoutMs $results.OSType = $osInfo.OSType $results.OSDetails = $osInfo.OSDetails $results.OSDetectionMethod = $osInfo.DetectionMethod $results.OSConfidence = $osInfo.Confidence if ($osInfo.OSType -ne "Unknown") { Write-Host " OS detected: $($osInfo.OSType) ($($osInfo.Confidence) confidence)" -ForegroundColor Green } } } Catch{ Write-Host "$($URLScanSiteInfo):$($PortToScan) - " -NoNewline -ForegroundColor Red; Write-Host "ERROR: $($_.Exception.Message)" $results.StartDate=$_.Exception.Message $results.EndDate="Connection or protocol error. Try using a different -ProtocolVersion." $results.Status = "Error" } # Apply expiration filtering if ExpiresInDays was specified if ($PSBoundParameters.ContainsKey('ExpiresInDays') -and $results.Status -eq "Success") { $today = (Get-Date).Date $certEndDate = [datetime]$results.EndDate $daysUntilExpiration = ($certEndDate.Date - $today).Days # Only return results that meet the expiration criteria if ($ExpiresInDays -eq 0) { # Show only expired certificates if ($daysUntilExpiration -ge 0) { return $null # Certificate not expired, don't return it } } else { # Show certificates expiring within specified days if ($daysUntilExpiration -gt $ExpiresInDays) { return $null # Certificate expires too far in the future, don't return it } } Finally { if ($sslStream) { try { $sslStream.Dispose() } catch { } } if ($stream) { try { $stream.Dispose() } catch { } } if ($socket) { try { $socket.Dispose() } catch { } } } Finally { if ($sslStream) { try { $sslStream.Dispose() } catch { } } if ($networkStream) { try { $networkStream.Dispose() } catch { } } if ($tcpClient) { try { $tcpClient.Dispose() } catch { } } } } Return $results } # Example usage: # ScanSiteInformation -URLScanSiteInfo "example.com" -IncludeReverseDNS -DNSServer "10.10.10.10" -GetOsType -TimeoutSeconds 4 -ExpiresInDays 60 # You'll also need this helper function for reverse DNS lookups: Function Invoke-ParallelScan { param( [array]$IPList, [array]$PortList, [int]$MaxThreads, [int]$TimeoutMs, [string]$ProtocolVersion, [switch]$IncludeReverseDNS, [switch]$GetOsType, [string]$DnsServer ) # Create runspace pool $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads) $RunspacePool.Open() # Create script block for scanning $ScriptBlock = { param($IP, $Port, $TimeoutMs, $ProtocolVersion, $IncludeReverseDNS, $GetOsType, $DnsServer) # Define functions inside the script block since runspaces dont inherit them Function Get-ReverseDnsNameInternal { param( [string]$IPAddress, [string]$DnsServer ) try { if (-not ([string]::IsNullOrEmpty($DnsServer))) { if (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue) { $result = Resolve-DnsName -Name $IPAddress -Type PTR -Server $DnsServer -DnsOnly -ErrorAction SilentlyContinue if ($result) { return ($result.NameHost | Select-Object -First 1) } else { return "No PTR record (via $DnsServer)" } } else { $nslookupResult = nslookup -type=PTR $IPAddress $DnsServer $ptrLine = $nslookupResult | Select-String -Pattern "name =" if ($ptrLine) { return (($ptrLine | Select-Object -First 1).ToString() -split '= ')[1].Trim() } else { return "No PTR record (nslookup fallback)" } } } else { $hostEntry = [System.Net.Dns]::GetHostEntry($IPAddress) return $hostEntry.HostName } } catch { return "Reverse DNS Error: $($_.Exception.Message)" } } Function Test-TCPPortInternal { param([string]$IPAddress, [int]$Port, [int]$TimeoutMs) try { $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($IPAddress, $Port, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if ($wait) { $tcpClient.EndConnect($asyncResult) $tcpClient.Close() return $true } else { $tcpClient.Close() return $false } } catch { if ($tcpClient) { $tcpClient.Close() } return $false } } Function Get-OSTypeFromHostInternal { param([string]$IPAddress, [int]$TimeoutMs = 3000) $osInfo = [PSCustomObject]@{ OSType = "Unknown" OSDetails = "" DetectionMethod = "" Confidence = "Low" } try { # Port-based OS detection (simplified for parallel execution) $windowsPorts = @(135, 139, 445, 3389) $linuxPorts = @(22, 111) $windowsPortsOpen = 0 $linuxPortsOpen = 0 $portTests = @() foreach ($port in $windowsPorts) { if (Test-TCPPortInternal -IPAddress $IPAddress -Port $port -TimeoutMs 1500) { $windowsPortsOpen++ $portTests += "Win:$port" } } foreach ($port in $linuxPorts) { if (Test-TCPPortInternal -IPAddress $IPAddress -Port $port -TimeoutMs 1500) { $linuxPortsOpen++ $portTests += "Nix:$port" } } if ($windowsPortsOpen -ge 2) { $osInfo.OSType = "Windows" $osInfo.OSDetails = $portTests -join ',' $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "High" } elseif ($linuxPortsOpen -ge 1) { $osInfo.OSType = "Linux/Unix" $osInfo.OSDetails = $portTests -join ',' $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "High" } elseif ($windowsPortsOpen -eq 1) { $osInfo.OSType = "Likely Windows" $osInfo.OSDetails = $portTests -join "," $osInfo.DetectionMethod = "Port Analysis" $osInfo.Confidence = "Medium" } } catch { $osInfo.OSDetails = "OS detection error" } return $osInfo } Function Get-LDAPSCertificateInternal { param( [string]$ServerName, [int]$Port = 636, [int]$TimeoutMs = 5000, [string]$ProtocolVersion = "TLS12", [switch]$IncludeReverseDNS, [switch]$GetOsType, [string]$DnsServer ) $reverseDNS = "" if ($IncludeReverseDNS) { $reverseDNS = Get-ReverseDnsNameInternal -IPAddress $ServerName -DnsServer $DnsServer } $results = [PSCustomObject]@{ URL = $ServerName Port = $Port ServiceType = "LDAPS" StartDate = '' EndDate = '' Issuer = '' Subject = '' Protocol = $ProtocolVersion Status = "Unknown" ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId ReverseDNSName = $reverseDNS OSType = '' OSDetails = '' OSDetectionMethod = '' OSConfidence = '' DaysUntilExpiration = $null ExpirationStatus = '' } try { # Create TCP client with timeout $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($ServerName, $Port, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if (-not $wait) { $tcpClient.Close() throw "Connection timeout after $($TimeoutMs/1000) seconds" } $tcpClient.EndConnect($asyncResult) $networkStream = $tcpClient.GetStream() # Create SSL stream - LDAPS uses direct SSL/TLS connection $sslStream = New-Object System.Net.Security.SslStream($networkStream, $false, { param($sender, $certificate, $chain, $sslPolicyErrors) # Accept all certificates for scanning purposes $true }) # Map protocol version string to enum $protocolEnum = switch ($ProtocolVersion) { "Ssl3" { [System.Security.Authentication.SslProtocols]::Ssl3 } "Tls" { [System.Security.Authentication.SslProtocols]::Tls } "Tls11" { [System.Security.Authentication.SslProtocols]::Tls11 } "Tls12" { [System.Security.Authentication.SslProtocols]::Tls12 } "Default" { [System.Security.Authentication.SslProtocols]::Default } default { [System.Security.Authentication.SslProtocols]::Tls12 } } # Authenticate as client (this establishes the SSL/TLS connection and exchanges certificates) $sslStream.AuthenticateAsClient($ServerName, $null, $protocolEnum, $false) # Extract certificate information if ($sslStream.RemoteCertificate) { $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($sslStream.RemoteCertificate) $results.StartDate = $cert.NotBefore $results.EndDate = $cert.NotAfter $results.Issuer = $cert.Issuer $results.Subject = $cert.Subject $results.Status = "Success" # Calculate expiration information $today = (Get-Date).Date $certEndDate = [datetime]$cert.NotAfter $daysUntilExpiration = ($certEndDate.Date - $today).Days $results.DaysUntilExpiration = $daysUntilExpiration if ($daysUntilExpiration -lt 0) { $results.ExpirationStatus = "EXPIRED" } else { $results.ExpirationStatus = "OK" } } else { throw "No certificate received from server" } # Clean up $sslStream.Close() $networkStream.Close() $tcpClient.Close() # Add OS detection after successful certificate scan if ($GetOsType -and $results.Status -eq "Success") { $osInfo = Get-OSTypeFromHostInternal -IPAddress $ServerName -TimeoutMs $TimeoutMs $results.OSType = $osInfo.OSType $results.OSDetails = $osInfo.OSDetails $results.OSDetectionMethod = $osInfo.DetectionMethod $results.OSConfidence = $osInfo.Confidence } } catch { $results.StartDate = $_.Exception.Message $results.EndDate = "LDAPS connection error. Check if LDAPS is enabled and certificate is valid." $results.Status = "Error" # Clean up on error try { if ($sslStream) { $sslStream.Close() } } catch { } try { if ($networkStream) { $networkStream.Close() } } catch { } try { if ($tcpClient) { $tcpClient.Close() } } catch { } } return $results } Function ScanSiteInformationInternal { param($URLScanSiteInfo, $CustomPort, $TimeoutMs, $ServiceType, $ProtocolVersion, [switch]$IncludeReverseDNS, [switch]$GetOsType, [string]$DnsServer) $reverseDNS = "" if ($IncludeReverseDNS) { $reverseDNS = Get-ReverseDnsNameInternal -IPAddress $URLScanSiteInfo -DnsServer $DnsServer } # For LDAPS, use specialized function if ($ServiceType -eq "LDAPS" -or $CustomPort -eq 636) { return Get-LDAPSCertificateInternal -ServerName $URLScanSiteInfo -Port $CustomPort -TimeoutMs $TimeoutMs -ProtocolVersion $ProtocolVersion -IncludeReverseDNS:$IncludeReverseDNS -GetOsType:$GetOsType -DnsServer $DnsServer } $results = [PSCustomObject]@{ URL = $URLScanSiteInfo Port = $CustomPort ServiceType = $ServiceType StartDate = '' EndDate = '' Issuer = '' Subject = '' Protocol = $ProtocolVersion Status = "Unknown" ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId ReverseDNSName = $reverseDNS OSType = '' OSDetails = '' OSDetectionMethod = '' OSConfidence = '' DaysUntilExpiration = $null ExpirationStatus = '' } Try { $socket = New-Object Net.Sockets.TcpClient $asyncResult = $socket.BeginConnect($URLScanSiteInfo, $CustomPort, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if (-not $wait) { $socket.Close() throw "Connection timeout after $($TimeoutMs/1000) seconds" } $socket.EndConnect($asyncResult) $stream = $socket.GetStream() $sslStream = New-Object System.Net.Security.SslStream($stream, $false, ({ $True } -as [Net.Security.RemoteCertificateValidationCallback])) # Map protocol version correctly $protocolEnum = switch ($ProtocolVersion) { 'Ssl3' { [System.Security.Authentication.SslProtocols]::Ssl3 } 'Tls' { [System.Security.Authentication.SslProtocols]::Tls } 'Tls11' { [System.Security.Authentication.SslProtocols]::Tls11 } 'Tls12' { [System.Security.Authentication.SslProtocols]::Tls12 } 'Default' { [System.Security.Authentication.SslProtocols]::Default } default { [System.Security.Authentication.SslProtocols]::Tls12 } } $sslStream.AuthenticateAsClient($URLScanSiteInfo, $null, $protocolEnum, $false) $results.StartDate = $sslStream.RemoteCertificate.NotBefore $results.EndDate = $sslStream.RemoteCertificate.NotAfter $results.Issuer = $sslStream.RemoteCertificate.Issuer $results.Subject = $sslStream.RemoteCertificate.Subject $results.Status = "Success" # Calculate expiration information $today = (Get-Date).Date $certEndDate = [datetime]$sslStream.RemoteCertificate.NotAfter $daysUntilExpiration = ($certEndDate.Date - $today).Days $results.DaysUntilExpiration = $daysUntilExpiration if ($daysUntilExpiration -lt 0) { $results.ExpirationStatus = "EXPIRED" } else { $results.ExpirationStatus = "OK" } $socket.close() # Add OS detection after successful certificate scan if ($GetOsType -and $results.Status -eq "Success") { $osInfo = Get-OSTypeFromHostInternal -IPAddress $URLScanSiteInfo -TimeoutMs $TimeoutMs $results.OSType = $osInfo.OSType $results.OSDetails = $osInfo.OSDetails $results.OSDetectionMethod = $osInfo.DetectionMethod $results.OSConfidence = $osInfo.Confidence } } Catch { $results.StartDate = $_.Exception.Message $results.EndDate = "Connection or protocol error. Try using a different -ProtocolVersion." $results.Status = "Error" Finally { if ($sslStream) { try { $sslStream.Dispose() } catch { } } if ($stream) { try { $stream.Dispose() } catch { } } if ($socket) { try { $socket.Dispose() } catch { } } } } Return $results } # Main scanning logic $serviceType = if ($Port -eq 636) { "LDAPS" } else { "HTTPS" } if (Test-TCPPortInternal -IPAddress $IP -Port $Port -TimeoutMs $TimeoutMs) { return ScanSiteInformationInternal -URLScanSiteInfo $IP -CustomPort $Port -TimeoutMs $TimeoutMs -ServiceType $serviceType -ProtocolVersion $ProtocolVersion -IncludeReverseDNS:$IncludeReverseDNS -GetOsType:$GetOsType -DnsServer $DnsServer } else { $reverseDNS = "" if ($IncludeReverseDNS) { $reverseDNS = Get-ReverseDnsNameInternal -IPAddress $IP -DnsServer $DnsServer } return [PSCustomObject]@{ URL = $IP Port = $Port ServiceType = $serviceType StartDate = "Port Closed" EndDate = "Port closed or filtered" Issuer = '' Subject = '' Protocol = $ProtocolVersion Status = "Port Closed" ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId ReverseDNSName = $reverseDNS OSType = '' OSDetails = '' OSDetectionMethod = '' OSConfidence = '' } } } # Create jobs for each IP/Port combination $Jobs = @() foreach ($ip in $IPList) { foreach ($port in $PortList) { $PowerShell = [powershell]::Create() $PowerShell.RunspacePool = $RunspacePool [void]$PowerShell.AddScript($ScriptBlock) [void]$PowerShell.AddParameter("IP", $ip) [void]$PowerShell.AddParameter("Port", $port) [void]$PowerShell.AddParameter("TimeoutMs", $TimeoutMs) [void]$PowerShell.AddParameter("ProtocolVersion", $ProtocolVersion) [void]$PowerShell.AddParameter("IncludeReverseDNS", $IncludeReverseDNS) [void]$PowerShell.AddParameter("GetOsType", $GetOsType) [void]$PowerShell.AddParameter("DnsServer", $DnsServer) $Jobs += [PSCustomObject]@{ PowerShell = $PowerShell Handle = $PowerShell.BeginInvoke() IP = $ip Port = $port } } } # Wait for jobs to complete and collect results $Results = @() $CompletedJobs = 0 $TotalJobs = $Jobs.Count Write-Host "Started $TotalJobs parallel scanning jobs using $MaxThreads threads..." -ForegroundColor Green do { Start-Sleep -Milliseconds 500 $CompletedCount = ($Jobs | Where-Object { $_.Handle.IsCompleted }).Count if ($CompletedCount -gt $CompletedJobs) { $CompletedJobs = $CompletedCount Write-Progress -Activity "Parallel Network Scanning" -Status "Completed $CompletedJobs of $TotalJobs scans" -PercentComplete (($CompletedJobs / $TotalJobs) * 100) } # Collect completed results $CompletedJobs_ToProcess = $Jobs | Where-Object { $_.Handle.IsCompleted -and $_.PowerShell.InvocationStateInfo.State -eq "Completed" } foreach ($job in $CompletedJobs_ToProcess) { try { $result = $job.PowerShell.EndInvoke($job.Handle) if ($result) { $Results += $result if ($result.Status -eq "Success") { $osInfo = if ($GetOsType -and $result.OSType) { " [OS: $($result.OSType)]" } else { "" } Write-Host "✓ $($result.URL):$($result.Port) ($($result.ServiceType)) - Certificate found$osInfo" -ForegroundColor Green if ($IncludeReverseDNS -and $result.ReverseDNSName) { Write-Host " Reverse DNS: $($result.ReverseDNSName)" -ForegroundColor DarkGreen } } } } catch { Write-Host "✗ $($job.IP):$($job.Port) - Job error: $($_.Exception.Message)" -ForegroundColor Red } finally { $job.PowerShell.Dispose() } } # Remove completed jobs from the list $Jobs = $Jobs | Where-Object { -not $_.Handle.IsCompleted -or $_.PowerShell.InvocationStateInfo.State -ne "Completed" } } while ($Jobs.Count -gt 0) Write-Progress -Activity "Parallel Network Scanning" -Completed $RunspacePool.Close() $RunspacePool.Dispose() return $Results } Function Apply-ExpirationFilter { param( [array]$Results, [int]$ExpiresInDays, [switch]$ExcludeExpired ) if ($null -eq $Results -or $Results.Count -eq 0) { return @() } $Today = (Get-Date).Date $filteredResults = @() foreach ($result in $Results) { # Skip non-successful results if ($result.Status -ne "Success") { continue } # Parse EndDate - it might be a string or datetime $endDate = $null try { if ($result.EndDate -is [datetime]) { $endDate = $result.EndDate } elseif ($result.EndDate -is [string] -and $result.EndDate -ne "") { $endDate = [datetime]::Parse($result.EndDate) } else { continue # Skip if we can't parse the date } } catch { Write-Warning "Could not parse EndDate '$($result.EndDate)' for $($result.URL):$($result.Port)" continue } # Calculate days until expiration $daysUntilExpiration = ($endDate.Date - $Today).Days # Add expiration info to the result object $result | Add-Member -Name "DaysUntilExpiration" -Value $daysUntilExpiration -MemberType NoteProperty -Force if ($daysUntilExpiration -lt 0) { $result | Add-Member -Name "ExpirationStatus" -Value "EXPIRED" -MemberType NoteProperty -Force } elseif ($daysUntilExpiration -le $ExpiresInDays) { $result | Add-Member -Name "ExpirationStatus" -Value "EXPIRING SOON" -MemberType NoteProperty -Force } else { $result | Add-Member -Name "ExpirationStatus" -Value "OK" -MemberType NoteProperty -Force } # Apply filtering logic $shouldInclude = $false if ($ExcludeExpired -and $daysUntilExpiration -lt 0) { # Exclude expired certificates $shouldInclude = $false } elseif ($ExpiresInDays -eq 0) { # Show only expired certificates $shouldInclude = ($daysUntilExpiration -lt 0) } else { # Show certificates expiring within specified days (including expired if not excluded) if ($ExcludeExpired) { $shouldInclude = ($daysUntilExpiration -ge 0 -and $daysUntilExpiration -le $ExpiresInDays) } else { $shouldInclude = ($daysUntilExpiration -le $ExpiresInDays) } } if ($shouldInclude) { $filteredResults += $result } } # Display filtering summary if ($ExcludeExpired) { Write-Host "Excluded already expired certificates from results." -ForegroundColor Yellow } if ($ExpiresInDays -eq 0) { Write-Host "Filtered to show only already expired certificates: $($filteredResults.Count) found." -ForegroundColor Yellow } else { Write-Host "Filtered to show certificates expiring within $ExpiresInDays days: $($filteredResults.Count) found." -ForegroundColor Yellow } return $filteredResults } Function Save-Results { param( [array]$Results, [string]$SaveAsTo ) # Check if Results is null or empty - do nothing if so if ($null -eq $Results -or $Results.Count -eq 0) { Write-Host "No results to save (Results is null or empty)" -ForegroundColor Yellow return } try { # Check if filename starts with + for append mode $appendMode = $SaveAsTo.StartsWith("+") $actualFileName = if ($appendMode) { $SaveAsTo.Substring(1) } else { $SaveAsTo } if ($appendMode -and (Test-Path $actualFileName)) { # Append mode: add to existing file with -Force to handle column mismatches $Results | Export-Csv -Path $actualFileName -NoTypeInformation -Append -Force Write-Host "Results appended to: $actualFileName" -ForegroundColor Green } else { # Normal mode: create new file or overwrite existing $Results | Export-Csv -Path $actualFileName -NoTypeInformation if ($appendMode) { Write-Host "Results saved to new file: $actualFileName (file didn't exist for append)" -ForegroundColor Green } else { Write-Host "Results saved to: $actualFileName" -ForegroundColor Green } } } catch { Throw $_.Exception.Message } } Function SendMailToTheInternet { param( $Fullresult, $EmailFrom, $EmailSendTo, $EmailSubject, $EmailSMTPServer, $EmailSMTPServerPort, [switch]$EmailSMTPServerSSL ) try{ $SendMail=@{ From=$EmailFrom To =$EmailSendTo Subject =$EmailSubject Body =($Fullresult | Out-String) SmtpServer =$EmailSMTPServer Credential =(Get-Credential) Port= $EmailSMTPServerPort UseSsl = $EmailSMTPServerSSL } Write-Host "Sending Email ...[][][]" Send-MailMessage @sendmail Write-Host "Email Sent ...>>>>" } Catch{ Throw $_.exception.message } } ## Start for Network Scan if ($PSCmdlet.ParameterSetName -eq "NetworkScan") { Write-Host "Starting network scan..." -ForegroundColor Green $Fullresult = @() $allIPs = @() # Determine which ports to scan $portsToScan = @() $httpsPortsToScan = @() # Build HTTPS ports list $httpsPortsToScan += $Port # Always include the main port if ($AdditionalHTTPSPorts.Count -gt 0) { $httpsPortsToScan += $AdditionalHTTPSPorts } # Remove duplicates $httpsPortsToScan = $httpsPortsToScan | Sort-Object | Get-Unique if ($LDAPSOnly) { $portsToScan = @(636) Write-Host "Scanning LDAPS certificates only (port 636)" -ForegroundColor Cyan } elseif ($IncludeLDAPS) { $portsToScan = @($httpsPortsToScan) + 636 Write-Host "Scanning HTTPS ports ($($httpsPortsToScan -join ", ")) and LDAPS (port 636)" -ForegroundColor Cyan } else { $portsToScan = $httpsPortsToScan Write-Host "Scanning HTTPS ports: $($httpsPortsToScan -join ", ")" -ForegroundColor Cyan } # Parse multiple networks $networkList = $Networks.Split(",").Trim() foreach ($network in $networkList) { Write-Host "Processing network: $network" -ForegroundColor Yellow try { $ips = ConvertTo-IPRange -CIDR $network $allIPs += $ips Write-Host "Generated $($ips.Count) IP addresses from $network" -ForegroundColor Cyan } catch { Write-Host "Error processing network $network : $($_.Exception.Message)" -ForegroundColor Red } } Write-Host "Total IP addresses to scan: $($allIPs.Count)" -ForegroundColor Green Write-Host "Total ports per IP: $($portsToScan.Count) with timeout $TimeoutSeconds seconds..." -ForegroundColor Green Write-Host "Using $MaxThreads concurrent threads for parallel scanning..." -ForegroundColor Green if ($IncludeReverseDNS) { if($DnsServer) { Write-Host "Reverse DNS lookup enabled (using custom server: $DnsServer)." -ForegroundColor Green } else { Write-Host "Reverse DNS lookup enabled (using system DNS)." -ForegroundColor Green } } if ($GetOsType) { Write-Host "OS detection enabled for hosts with certificates." -ForegroundColor Green } if ($PSBoundParameters.ContainsKey("ExpiresInDays")) { if ($ExpiresInDays -eq 0) { Write-Host "Will show only already expired certificates." -ForegroundColor Yellow } else { Write-Host "Will show certificates expiring in $ExpiresInDays days or less." -ForegroundColor Yellow } } else { Write-Host "Will show all certificates found (no expiration filtering)." -ForegroundColor Green } # Use parallel scanning $Fullresult = Invoke-ParallelScan -IPList $allIPs -PortList $portsToScan -MaxThreads $MaxThreads -TimeoutMs ($TimeoutSeconds * 1000) -ProtocolVersion $ProtocolVersion -IncludeReverseDNS:$IncludeReverseDNS -GetOsType:$GetOsType -DnsServer $DnsServer # Apply expiration filter only if ExpiresInDays parameter was explicitly provided if ($PSBoundParameters.ContainsKey("ExpiresInDays") -or $ExcludeExpired) { $Fullresult = Apply-ExpirationFilter -Results $Fullresult -ExpiresInDays $ExpiresInDays -ExcludeExpired:$ExcludeExpired } # Filter out port closed results for cleaner output $SuccessfulResults = $Fullresult | Where-Object { $_.Status -eq "Success" } $ErrorResults = $Fullresult | Where-Object { $_.Status -ne "Success" -and $_.Status -ne "Port Closed" } $PortClosedResults = $Fullresult | Where-Object { $_.Status -eq "Port Closed" } Write-Host "Network scan completed in parallel!" -ForegroundColor Green # Display summary by service type and port $httpsCount = ($SuccessfulResults | Where-Object { $_.ServiceType -eq "HTTPS" }).Count $ldapsCount = ($SuccessfulResults | Where-Object { $_.ServiceType -eq "LDAPS" }).Count $portClosedCount = $PortClosedResults.Count $errorCount = $ErrorResults.Count Write-Host "Summary: $httpsCount HTTPS certificates, $ldapsCount LDAPS certificates found, $portClosedCount ports closed, $errorCount other errors." -ForegroundColor Green # Show breakdown by port $portSummary = $SuccessfulResults | Group-Object Port | Sort-Object Name foreach ($portGroup in $portSummary) { $serviceType = if ($portGroup.Name -eq "636") { "LDAPS" } else { "HTTPS" } Write-Host " Port $($portGroup.Name) ($serviceType): $($portGroup.Count) certificates" -ForegroundColor Cyan } # Show OS detection summary if enabled if ($GetOsType) { $osResults = $SuccessfulResults | Where-Object { $_.OSType -and $_.OSType -ne "Unknown" } if ($osResults.Count -gt 0) { Write-Host "OS Detection Summary:" -ForegroundColor Yellow $osSummary = $osResults | Group-Object OSType | Sort-Object Count -Descending foreach ($osGroup in $osSummary) { Write-Host " $($osGroup.Name): $($osGroup.Count) hosts" -ForegroundColor Cyan } } } # Show thread usage summary $threadSummary = $Fullresult | Where-Object { $_.ThreadId } | Group-Object ThreadId | Measure-Object if ($threadSummary.Count -gt 0) { Write-Host "Utilized $($threadSummary.Count) threads for parallel processing" -ForegroundColor Yellow } if ($PSBoundParameters.Keys -like "SaveAsTo"){ Save-Results -Results $Fullresult -SaveAsTo $SaveAsTo } if (($PSBoundParameters.Keys -like "*email*")){ SendMailToTheInternet ` -Fullresult $Fullresult ` -EmailFrom $EmailFrom ` -EmailSendTo $EmailSendTo ` -EmailSubject $EmailSubject ` -EmailSMTPServer $EmailSMTPServer ` -EmailSMTPServerPort $EmailSMTPServerPort ` -EmailSMTPServerSSL $EmailSMTPServerSSL } return $Fullresult } ## Start for File Load and Scan if ($PSCmdlet.ParameterSetName -eq "ReadFromFile") { if (!(Test-Path $LoadFromFile)){Throw "Incorrect Source Path."} $Fullresult=@() $CertificateList=Get-Content -Path $LoadFromFile Foreach($url in $CertificateList){ $siteresults=ScanSiteInformation -URLScanSiteInfo $url -IncludeReverseDNS:$IncludeReverseDNS -GetOsType:$GetOsType -DnsServer $DnsServer $Fullresult+=$siteresults } # Apply expiration filter only if ExpiresInDays parameter was explicitly provided if ($PSBoundParameters.ContainsKey("ExpiresInDays") -or $ExcludeExpired) { $Fullresult = Apply-ExpirationFilter -Results $Fullresult -ExpiresInDays $ExpiresInDays -ExcludeExpired:$ExcludeExpired } if ($PSBoundParameters.Keys -like "SaveAsTo"){ Save-Results -Results $Fullresult -SaveAsTo $SaveAsTo } if (($PSBoundParameters.Keys -like "*email*")){ SendMailToTheInternet ` -Fullresult $Fullresult ` -EmailFrom $EmailFrom ` -EmailSendTo $EmailSendTo ` -EmailSubject $EmailSubject ` -EmailSMTPServer $EmailSMTPServer ` -EmailSMTPServerPort $EmailSMTPServerPort ` -EmailSMTPServerSSL $EmailSMTPServerSSL } return $Fullresult } if ($pscmdlet.ParameterSetName -eq "Online") { $Fullresult=ScanSiteInformation -URLScanSiteInfo $SiteToScan -IncludeReverseDNS:$IncludeReverseDNS -GetOsType:$GetOsType -DnsServer $DnsServer # Apply expiration filter only if ExpiresInDays parameter was explicitly provided if ($PSBoundParameters.ContainsKey("ExpiresInDays") -or $ExcludeExpired) { $Fullresult = Apply-ExpirationFilter -Results $Fullresult -ExpiresInDays $ExpiresInDays -ExcludeExpired:$ExcludeExpired } if ($PSBoundParameters.Keys -like "SaveAsTo"){ Save-Results -Results $Fullresult -SaveAsTo $SaveAsTo } if (($PSBoundParameters.Keys -like "*email*")){ SendMailToTheInternet ` -Fullresult $Fullresult ` -EmailFrom $EmailFrom ` -EmailSendTo $EmailSendTo ` -EmailSubject $EmailSubject ` -EmailSMTPServer $EmailSMTPServer ` -EmailSMTPServerPort $EmailSMTPServerPort ` -EmailSMTPServerSSL $EmailSMTPServerSSL } return $Fullresult } |