alexsusanu@docs:From web.config appSettings $
alexsusanu@docs
:~$ cat From web.config appSettings.md

HomeLIMS → From web.config appSettings

Get admin email addresses from LIMS config:

# From web.config appSettings
Get-Website | ForEach-Object {
    $webConfigPath = "$($_.PhysicalPath)\web.config"
    if(Test-Path $webConfigPath) {
        try {
            [xml]$webConfig = Get-Content $webConfigPath
            $webConfig.configuration.appSettings.add | 
                Where-Object {$_.key -match "email|mail|admin|alert"} |
                Select-Object key, value
        } catch {}
    }
}

Get website URLs/base URLs:

# Get all website URLs from bindings
Get-Website | ForEach-Object {
    $siteName = $_.Name
    $_.Bindings.Collection | ForEach-Object {
        $bindingParts = $_.bindingInformation -split ':'
        $ip = $bindingParts[0]
        $port = $bindingParts[1]
        $hostname = $bindingParts[2]

        if($hostname) {
            $url = "$($_.Protocol)://$hostname"
            if($port -ne "80" -and $port -ne "443") { $url += ":$port" }
        } else {
            $url = "$($_.Protocol)://localhost"
            if($port -ne "80" -and $port -ne "443") { $url += ":$port" }
        }

        Write-Output "Site: $siteName | URL: $url"
    }
}

Get HTTPS URLs only:

Get-Website | ForEach-Object {
    $siteName = $_.Name
    $_.Bindings.Collection | Where-Object {$_.Protocol -eq "https"} | ForEach-Object {
        $bindingParts = $_.bindingInformation -split ':'
        $hostname = if($bindingParts[2]) { $bindingParts[2] } else { "localhost" }
        $port = $bindingParts[1]
        $url = "https://$hostname"
        if($port -ne "443") { $url += ":$port" }
        Write-Output "Site: $siteName | HTTPS URL: $url"
    }
}

Get website physical paths:

Get-Website | Select-Object Name, PhysicalPath

Get database server and database names from connection strings:

Get-Website | ForEach-Object {
    $webConfigPath = "$($_.PhysicalPath)\web.config"
    if(Test-Path $webConfigPath) {
        try {
            [xml]$webConfig = Get-Content $webConfigPath
            $connections = $webConfig.configuration.connectionStrings.add
            if($connections) {
                $connections | ForEach-Object {
                    $connString = $_.connectionString
                    $server = if($connString -match "Server=([^;]+)") { $matches[1] } else { "Unknown" }
                    $database = if($connString -match "Database=([^;]+)") { $matches[1] } else { "Unknown" }
                    Write-Output "Site: $($_.Name) | Server: $server | Database: $database"
                }
            }
        } catch {}
    }
}

Get printer names:

Get-Printer | Select-Object Name, DriverName, PortName

Get LIMS file processing paths (common locations):

# Common LIMS file processing directories
$commonPaths = @(
    "F:\LIMS\Data",
    "F:\LIMS\Incoming", 
    "F:\LIMS\Processing",
    "F:\LIMS\Archive",
    "C:\LIMS\Data",
    "D:\LIMS\Data"
)

foreach($path in $commonPaths) {
    if(Test-Path $path) {
        Write-Output "LIMS Path Found: $path"
        Get-ChildItem $path -Directory | Select-Object Name, FullName
    }
}

Get server name:

# Computer name
$env:COMPUTERNAME

# FQDN
[System.Net.Dns]::GetHostByName(($env:computerName)).HostName

Get URL patterns for monitoring:

# Extract URL patterns from website bindings
Get-Website | ForEach-Object {
    $_.Bindings.Collection | ForEach-Object {
        $bindingParts = $_.bindingInformation -split ':'
        $hostname = $bindingParts[2]
        if($hostname) {
            Write-Output "URL Pattern: *$hostname*"
        }
    }
} | Sort-Object -Unique

Get service account passwords (NOTE: Cannot retrieve passwords - must be provided by admin):

# Service account passwords cannot be retrieved from system
# These must be obtained from:
# 1. Password manager/vault
# 2. Documentation
# 3. System administrator
Write-Output "Service account passwords must be provided by administrator"
Write-Output "Check: Password vault, documentation, or contact admin"
```# Comprehensive IIS Troubleshooting Guide for LIMS Environments

## Table of Contents
- [Prerequisites and Setup](#prerequisites-and-setup)
- [Complete Discovery Commands Section](#complete-discovery-commands-section)
- [IIS Analysis Phase](#iis-analysis-phase)
- [IIS Debugging Scenarios for LIMS](#iis-debugging-scenarios-for-lims)
- [Advanced Troubleshooting](#advanced-troubleshooting)
- [Prevention and Monitoring](#prevention-and-monitoring)

## Prerequisites and Setup

### Required PowerShell Modules Installation
Before starting any IIS troubleshooting, ensure proper modules are available:

```powershell
# Check available IIS modules
Get-Module -ListAvailable IIS*
Get-Module -ListAvailable WebAdministration

# Install IIS Management Scripts if missing
Install-WindowsFeature web-scripting-tools
Enable-WindowsOptionalFeature -Online -FeatureName IIS-ManagementScriptingTools

# Load required modules (run these commands first)
Import-Module WebAdministration -ErrorAction SilentlyContinue
Import-Module IISAdministration -ErrorAction SilentlyContinue
Add-PSSnapin WebAdministration -ErrorAction SilentlyContinue

Environment Assumptions

  • System Drive: C: (Windows installation)
  • Website Drive: F: (LIMS applications and data)
  • Elevated Privileges: All commands require Administrator rights

Complete Discovery Commands Section

Discovery Commands - Run These First

Get all website names:

(Get-Website).Name

Get all website IDs:

Get-Website | Select-Object Name, ID

Get all application pool names:

(Get-IISAppPool).Name

Get application pool for specific website:

(Get-Website -Name "<WEBSITE_NAME>").ApplicationPool

Get all hostnames:

Get-Website | ForEach-Object { $_.Bindings.Collection | ForEach-Object { ($_.bindingInformation -split ':')[2] } } | Where-Object {$_ -ne ""}

Get log directory for specific site:

$site = Get-Website -Name "<WEBSITE_NAME>"
$logDir = $site.LogFile.Directory -replace '%SystemDrive%', $env:SystemDrive
"$logDir\W3SVC$($site.Id)"

Get worker process IDs:

(Get-Process w3wp -ErrorAction SilentlyContinue).Id

Get which app pool a worker process belongs to:

$pid = <PROCESS_ID>
$wpInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $pid"
($wpInfo.CommandLine -split '"')[1]

Get physical path for website:

(Get-Website -Name "<WEBSITE_NAME>").PhysicalPath

Get server hostname:

$env:COMPUTERNAME

Get websites on F: drive:

Get-Website | Where-Object {$_.PhysicalPath -like "F:\*"} | Select-Object Name

Get SSL certificate thumbprints:

Get-ChildItem IIS:\SslBindings | Select-Object IPPort, Thumbprint

Get current IIS service status:

Get-Service W3SVC, WAS, HTTP | Select-Object Name, Status

Get database connection strings from web.config:

$sitePath = "<WEBSITE_PHYSICAL_PATH>"
$webConfigPath = "$sitePath\web.config"
if(Test-Path $webConfigPath) {
    [xml]$webConfig = Get-Content $webConfigPath
    $webConfig.configuration.connectionStrings.add | Select-Object name, connectionString
}

Get database connection strings from all websites:

Get-Website | ForEach-Object {
    $webConfigPath = "$($_.PhysicalPath)\web.config"
    if(Test-Path $webConfigPath) {
        try {
            [xml]$webConfig = Get-Content $webConfigPath
            $connections = $webConfig.configuration.connectionStrings.add
            if($connections) {
                Write-Output "=== Site: $($_.Name) ==="
                $connections | ForEach-Object { Write-Output "Name: $($_.name) | Connection: $($_.connectionString)" }
            }
        } catch {
            Write-Output "Could not read web.config for $($_.Name)"
        }
    }
}

Get app settings from web.config:

$sitePath = "<WEBSITE_PHYSICAL_PATH>"
$webConfigPath = "$sitePath\web.config"
if(Test-Path $webConfigPath) {
    [xml]$webConfig = Get-Content $webConfigPath
    $webConfig.configuration.appSettings.add | Select-Object key, value
}

Find database server names in connection strings:

Get-Website | ForEach-Object {
    $webConfigPath = "$($_.PhysicalPath)\web.config"
    if(Test-Path $webConfigPath) {
        try {
            [xml]$webConfig = Get-Content $webConfigPath
            $connections = $webConfig.configuration.connectionStrings.add
            if($connections) {
                $connections | ForEach-Object {
                    if($_.connectionString -match "Server=([^;]+)") {
                        Write-Output "Site: $($_.Name) | DB Server: $($matches[1])"
                    }
                }
            }
        } catch {}
    }
}

Get database connection strings from registry (some LIMS store here):

# Common registry locations for LIMS database settings
$regPaths = @(
    "HKLM:\SOFTWARE\*\*\Database",
    "HKLM:\SOFTWARE\WOW6432Node\*\*\Database"
)

foreach($path in $regPaths) {
    Get-ChildItem $path -ErrorAction SilentlyContinue | 
        Get-ItemProperty | 
        Where-Object {$_.PSObject.Properties.Name -match "Server|Database|Connection"} |
        Select-Object PSPath, *
}

Get service account names:

# Application pool service accounts
Get-IISAppPool | Select-Object Name, @{n='ServiceAccount';e={$_.ProcessModel.UserName}}

# Windows services (for LIMS background services)
Get-WmiObject Win32_Service | Where-Object {$_.Name -match "lims|lab|sample"} | Select-Object Name, StartName

Get domain name:

# Current domain
$env:USERDOMAIN

# Full domain name
(Get-WmiObject Win32_ComputerSystem).Domain

Get SSL certificate thumbprints:

# From IIS SSL bindings
Get-ChildItem IIS:\SslBindings | Select-Object IPPort, Thumbprint

# From certificate store
Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Thumbprint, NotAfter

Get SMTP server settings:

# From web.config system.net mailSettings
Get-Website | ForEach-Object {
    $webConfigPath = "$($_.PhysicalPath)\web.config"
    if(Test-Path $webConfigPath) {
        try {
            [xml]$webConfig = Get-Content $webConfigPath
            $smtp = $webConfig.configuration.'system.net'.mailSettings.smtp
            if($smtp) {
                Write-Output "Site: $($_.Name) | SMTP Host: $($smtp.network.host)"
            }
        } catch {}
    }
}

Get admin email addresses from LIMS config:

# From web.config appSettings
Get-Website | ForEach-Object {
    $webConfigPath = "$($_.PhysicalPath)\web.config"
    if(Test-Path $webConfigPath) {
        try {
            [xml]$webConfig = Get-Content $webConfigPath
            $webConfig.configuration.appSettings.add | 
                Where-Object {$_.key -match "email|mail|admin|alert"} |
                Select-Object key, value
        } catch {}
    }
}

Discover application pool physical process information:

# Map worker processes to application pools
foreach($wp in Get-WmiObject -Class Win32_Process -Filter "Name='w3wp.exe'") {
    $appPool = ($wp.CommandLine -split '"')[1]
    [PSCustomObject]@{
        ProcessId = $wp.ProcessId
        AppPoolName = $appPool
        StartTime = $wp.CreationDate
        CommandLine = $wp.CommandLine
    }
}

2. Website Path Discovery

STEP 1: DISCOVER ALL WEBSITE NAMES AND DETAILS:

# Basic website discovery - THIS IS WHERE YOU GET SITE NAMES
Get-Website | Select-Object Name, ID, State, PhysicalPath, ApplicationPool

# Store site names for later use
$allSiteNames = (Get-Website).Name
Write-Output "Discovered website names:"
$allSiteNames | ForEach-Object { Write-Output "  Site Name: '$_'" }

# Find LIMS-specific sites (likely on F: drive)
$limsSites = Get-Website | Where-Object {$_.PhysicalPath -like "F:\*"}
Write-Output "LIMS-related websites (F: drive):"
$limsSites | ForEach-Object { 
    Write-Output "  Name: '$($_.Name)' | Path: '$($_.PhysicalPath)' | Pool: '$($_.ApplicationPool)'"
}

STEP 2: USE DISCOVERED SITE NAMES:

# Comprehensive website path discovery using DISCOVERED names
foreach($site in Get-Website) {
    Write-Output "=== Site: $($site.Name) (ID: $($site.Id)) ==="
    Write-Output "Main Path: $($site.PhysicalPath)"
    Write-Output "Application Pool: $($site.ApplicationPool)"

    # Get applications within the site
    $apps = Get-WebApplication -Site $site.Name
    if($apps) {
        Write-Output "Applications:"
        foreach($app in $apps) {
            Write-Output "  $($app.Path) -> $($app.PhysicalPath) [Pool: $($app.ApplicationPool)]"
        }
    }

    # Get virtual directories
    $vdirs = Get-WebVirtualDirectory -Site $site.Name
    if($vdirs) {
        Write-Output "Virtual Directories:"
        foreach($vdir in $vdirs) {
            Write-Output "  $($vdir.Path) -> $($vdir.PhysicalPath)"
        }
    }
    Write-Output ""
}

Find LIMS-specific paths (F: drive focus):

# Search for LIMS applications on F: drive
Get-Website | Where-Object {$_.PhysicalPath -like "F:\*"} | 
    Select-Object Name, PhysicalPath, ApplicationPool

# Find all IIS-related paths on F: drive
$fDrivePaths = @()
Get-Website | ForEach-Object {
    if($_.PhysicalPath -like "F:\*") { $fDrivePaths += $_.PhysicalPath }
}
Get-WebApplication | ForEach-Object {
    if($_.PhysicalPath -like "F:\*") { $fDrivePaths += $_.PhysicalPath }
}
$fDrivePaths | Sort-Object -Unique

3. Log File Location Discovery

STEP 1: DISCOVER SITE IDs AND NAMES:

# Get all site IDs and names first
Get-Website | Select-Object Name, ID, State | Format-Table -AutoSize

# Store site information for later use
$siteInfo = Get-Website | Select-Object Name, ID
$siteInfo | ForEach-Object { 
    Write-Output "Site Name: '$($_.Name)' has Site ID: '$($_.ID)'" 
}

STEP 2: DISCOVER LOG LOCATIONS USING DISCOVERED SITE IDs:

# Standard log location discovery using actual discovered values
foreach($site in Get-Website) {
    $logDir = $site.LogFile.Directory -replace '%SystemDrive%', $env:SystemDrive
    $logPath = "$logDir\W3SVC$($site.Id)"

    [PSCustomObject]@{
        SiteName = $site.Name
        SiteID = $site.Id
        LogDirectory = $logPath
        LogFormat = $site.LogFile.LogFormat
        LogEnabled = $site.LogFile.Enabled
        ActualLogPath = if(Test-Path $logPath) { $logPath } else { "Path Not Found" }
    }
}

# Find all log files with recent activity
$logRoot = "$env:SystemDrive\inetpub\logs\LogFiles"
if(Test-Path $logRoot) {
    Get-ChildItem $logRoot -Recurse -File | 
        Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-7)} |
        Select-Object Name, Directory, LastWriteTime, 
        @{n='SizeMB';e={[math]::Round($_.Length / 1MB, 2)}} |
        Sort-Object LastWriteTime -Descending
}

Discover HTTPERR logs (critical for connection issues):

# HTTP.SYS error logs location
$httpErrPath = "$env:SystemRoot\System32\LogFiles\HTTPERR"
if(Test-Path $httpErrPath) {
    Write-Output "HTTPERR Log Location: $httpErrPath"
    Get-ChildItem $httpErrPath -File | 
        Select-Object Name, LastWriteTime, 
        @{n='SizeMB';e={[math]::Round($_.Length / 1MB, 2)}}
}

4. Service ID and Hostname Discovery

Discover website IDs and binding information:

# Complete binding discovery with hostname extraction
foreach($site in Get-Website) {
    foreach($binding in $site.Bindings.Collection) {
        $bindingParts = $binding.bindingInformation -split ':'
        $ip = if($bindingParts[0] -eq '*') { 'All IPs' } else { $bindingParts[0] }
        $port = $bindingParts[1]
        $hostname = if($bindingParts[2]) { $bindingParts[2] } else { 'Any hostname' }

        [PSCustomObject]@{
            SiteName = $site.Name
            SiteID = $site.Id
            Protocol = $binding.Protocol
            IPAddress = $ip
            Port = $port
            Hostname = $hostname
            SSLFlags = $binding.sslFlags
            PhysicalPath = $site.PhysicalPath
        }
    }
}

Extract unique hostnames for LIMS systems:

# Get all unique hostnames configured in IIS
$hostnames = @()
foreach($site in Get-Website) {
    foreach($binding in $site.Bindings.Collection) {
        $hostname = ($binding.bindingInformation -split ':')[2]
        if($hostname -and $hostname -ne '') {
            $hostnames += [PSCustomObject]@{
                Hostname = $hostname
                SiteName = $site.Name
                Protocol = $binding.Protocol
            }
        }
    }
}
$hostnames | Sort-Object Hostname -Unique

5. Website Configuration Discovery

Discover authentication settings:

# Authentication configuration for each site
foreach($site in Get-Website) {
    Write-Output "=== Authentication Settings for $($site.Name) ==="

    # Anonymous Authentication
    $anonymous = Get-WebConfigurationProperty -Filter "system.webServer/security/authentication/anonymousAuthentication" -Name "enabled" -Location $site.Name
    Write-Output "Anonymous Authentication: $($anonymous.Value)"

    # Windows Authentication
    $windows = Get-WebConfigurationProperty -Filter "system.webServer/security/authentication/windowsAuthentication" -Name "enabled" -Location $site.Name
    Write-Output "Windows Authentication: $($windows.Value)"

    # Basic Authentication
    $basic = Get-WebConfigurationProperty -Filter "system.webServer/security/authentication/basicAuthentication" -Name "enabled" -Location $site.Name
    Write-Output "Basic Authentication: $($basic.Value)"

    Write-Output ""
}

Discover SSL certificate bindings:

# SSL certificate discovery
Get-ChildItem IIS:\SslBindings | ForEach-Object {
    $binding = $_
    $cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $binding.Thumbprint}

    [PSCustomObject]@{
        IPPort = $binding.IPPort
        Hostname = $binding.Host
        CertificateSubject = $cert.Subject
        CertificateIssuer = $cert.Issuer
        ExpirationDate = $cert.NotAfter
        Thumbprint = $binding.Thumbprint
    }
}

6. Complete System Discovery Script

Master discovery script for LIMS environments:

# Complete IIS Discovery Script for LIMS Environments
# Save as Discover-IISEnvironment.ps1
param(
    [switch]$IncludePerformanceData,
    [switch]$ExportToFile
)

$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$reportPath = "C:\temp\IIS_Discovery_$timestamp.txt"

function Write-Section {
    param([string]$Title)
    $line = "=" * 50
    $output = "`n$line`n$Title`n$line"
    Write-Output $output
    if($ExportToFile) { $output | Out-File $reportPath -Append }
}

function Write-Data {
    param([string]$Data)
    Write-Output $Data
    if($ExportToFile) { $Data | Out-File $reportPath -Append }
}

# Ensure required modules are loaded
Import-Module WebAdministration -ErrorAction SilentlyContinue

Write-Section "IIS DISCOVERY REPORT - $(Get-Date)"
Write-Data "Server: $env:COMPUTERNAME"
Write-Data "Generated by: $env:USERNAME"

# System Information
Write-Section "SYSTEM INFORMATION"
$osInfo = Get-WmiObject Win32_OperatingSystem
Write-Data "OS: $($osInfo.Caption)"
Write-Data "Version: $($osInfo.Version)"
Write-Data "Architecture: $($osInfo.OSArchitecture)"

$iisVersion = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\InetStp' -ErrorAction SilentlyContinue)
if($iisVersion) {
    Write-Data "IIS Version: $($iisVersion.VersionString)"
}

# Service Status
Write-Section "IIS SERVICE STATUS"
$services = @('HTTP', 'WAS', 'W3SVC', 'IISADMIN')
foreach($service in $services) {
    $svc = Get-Service $service -ErrorAction SilentlyContinue
    if($svc) {
        Write-Data "$($svc.Name): $($svc.Status) [$($svc.StartType)]"
    } else {
        Write-Data "$service: Not Found"
    }
}

# Websites Discovery
Write-Section "WEBSITES"
$websites = Get-Website
foreach($site in $websites) {
    Write-Data "Site: $($site.Name) [ID: $($site.Id)]"
    Write-Data "  State: $($site.State)"
    Write-Data "  Physical Path: $($site.PhysicalPath)"
    Write-Data "  Application Pool: $($site.ApplicationPool)"

    # Log information
    $logDir = $site.LogFile.Directory -replace '%SystemDrive%', $env:SystemDrive
    Write-Data "  Log Directory: $logDir\W3SVC$($site.Id)"
    Write-Data "  Log Enabled: $($site.LogFile.Enabled)"

    # Bindings
    Write-Data "  Bindings:"
    foreach($binding in $site.Bindings.Collection) {
        Write-Data "    $($binding.Protocol): $($binding.bindingInformation)"
    }

    # Applications
    $apps = Get-WebApplication -Site $site.Name
    if($apps) {
        Write-Data "  Applications:"
        foreach($app in $apps) {
            Write-Data "    $($app.Path) -> $($app.PhysicalPath) [Pool: $($app.ApplicationPool)]"
        }
    }
    Write-Data ""
}

# Application Pools
Write-Section "APPLICATION POOLS"
$appPools = Get-ChildItem IIS:\AppPools
foreach($pool in $appPools) {
    Write-Data "Pool: $($pool.Name)"
    Write-Data "  State: $($pool.State)"
    Write-Data "  .NET Version: $($pool.managedRuntimeVersion)"
    Write-Data "  Pipeline Mode: $($pool.managedPipelineMode)"
    Write-Data "  Identity: $($pool.processModel.identityType)"
    Write-Data "  Auto Start: $($pool.autoStart)"
    Write-Data ""
}

# Worker Processes
Write-Section "WORKER PROCESSES"
$workerProcesses = Get-Process w3wp -ErrorAction SilentlyContinue
if($workerProcesses) {
    foreach($wp in $workerProcesses) {
        $appPool = "Unknown"
        $wpInfo = Get-WmiObject -Class Win32_Process -Filter "ProcessId = $($wp.Id)" -ErrorAction SilentlyContinue
        if($wpInfo) {
            $appPool = ($wpInfo.CommandLine -split '"')[1]
        }

        Write-Data "PID: $($wp.Id) | App Pool: $appPool | CPU: $($wp.CPU) | Memory: $([math]::Round($wp.WorkingSet / 1MB, 2)) MB"
    }
} else {
    Write-Data "No worker processes currently running"
}

# Performance Data (if requested)
if($IncludePerformanceData) {
    Write-Section "PERFORMANCE COUNTERS"
    try {
        $counters = Get-Counter -Counter @(
            "\Processor(_Total)\% Processor Time",
            "\Memory\Available MBytes",
            "\Web Service(_Total)\Current Connections"
        ) -SampleInterval 1 -MaxSamples 1 -ErrorAction SilentlyContinue

        foreach($counter in $counters.CounterSamples) {
            Write-Data "$($counter.Path): $($counter.CookedValue)"
        }
    } catch {
        Write-Data "Could not retrieve performance counters"
    }
}

# Log File Analysis
Write-Section "LOG FILE ANALYSIS"
$logRoot = "$env:SystemDrive\inetpub\logs\LogFiles"
if(Test-Path $logRoot) {
    $logStats = Get-ChildItem $logRoot -Recurse -File | Measure-Object -Property Length -Sum
    Write-Data "Total log files: $($logStats.Count)"
    Write-Data "Total log size: $([math]::Round($logStats.Sum / 1MB, 2)) MB"

    # Recent activity
    $recentLogs = Get-ChildItem $logRoot -Recurse -File | 
        Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-1)} |
        Sort-Object LastWriteTime -Descending |
        Select-Object -First 5

    Write-Data "Recent log files (last 24 hours):"
    foreach($log in $recentLogs) {
        Write-Data "  $($log.Name) - Modified: $($log.LastWriteTime)"
    }
}

# SSL Certificates
Write-Section "SSL CERTIFICATES"
$sslBindings = Get-ChildItem IIS:\SslBindings -ErrorAction SilentlyContinue
if($sslBindings) {
    foreach($binding in $sslBindings) {
        $cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $binding.Thumbprint}
        if($cert) {
            Write-Data "Binding: $($binding.IPPort)"
            Write-Data "  Subject: $($cert.Subject)"
            Write-Data "  Expires: $($cert.NotAfter)"
            Write-Data "  Thumbprint: $($binding.Thumbprint)"
        }
    }
} else {
    Write-Data "No SSL bindings configured"
}

Write-Section "DISCOVERY COMPLETE"
if($ExportToFile) {
    Write-Output "Report saved to: $reportPath"
}

# Usage Examples:
# .\Discover-IISEnvironment.ps1
# .\Discover-IISEnvironment.ps1 -IncludePerformanceData
# .\Discover-IISEnvironment.ps1 -ExportToFile

IIS Analysis Phase

1. Log Analysis Commands

Analyze IIS access logs for patterns:

# Find recent errors in IIS logs
$logPath = "<LOG_DIRECTORY_PATH>"  # Example: C:\inetpub\logs\LogFiles\W3SVC1
$yesterday = (Get-Date).AddDays(-1)

Get-ChildItem "$logPath\*.log" | 
    Where-Object {$_.LastWriteTime -gt $yesterday} |
    ForEach-Object {
        $content = Get-Content $_.FullName | 
            Where-Object {$_ -notlike "#*" -and $_ -match " (4[0-9][0-9]|5[0-9][0-9]) "}

        foreach($line in $content) {
            $fields = $line -split ' '
            if($fields.Count -ge 9) {
                [PSCustomObject]@{
                    DateTime = "$($fields[0]) $($fields[1])"
                    ClientIP = $fields[2]
                    Method = $fields[3]
                    UriStem = $fields[4]
                    StatusCode = $fields[8]
                    TimeTaken = $fields[14]
                    LogFile = $_.Name
                }
            }
        }
    } | Sort-Object DateTime -Descending | Select-Object -First 50

Analyze HTTPERR logs for connection issues:

# Parse HTTPERR logs for connection resets (common in LIMS)
$httpErrPath = "$env:SystemRoot\System32\LogFiles\HTTPERR"
$yesterday = (Get-Date).AddDays(-1)

if(Test-Path $httpErrPath) {
    Get-ChildItem "$httpErrPath\httperr*.log" |
        Where-Object {$_.LastWriteTime -gt $yesterday} |
        ForEach-Object {
            Get-Content $_.FullName | Where-Object {$_ -match "Connection_Abandoned_By_ReqQueue|Timer_ConnectionIdle"}
        } | Select-Object -First 20
}

2. Performance Analysis

Analyze application pool performance:

# Get application pool performance metrics
$appPoolName = "<APPLICATION_POOL_NAME>"

# Check if application pool is healthy
$appPool = Get-IISAppPool -Name $appPoolName
Write-Output "Application Pool '$appPoolName' State: $($appPool.State)"

# Get worker process information
$workerProcess = Get-Process w3wp -ErrorAction SilentlyContinue | 
    Where-Object {
        $wpInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $($_.Id)"
        ($wpInfo.CommandLine -split '"')[1] -eq $appPoolName
    }

if($workerProcess) {
    [PSCustomObject]@{
        AppPool = $appPoolName
        ProcessId = $workerProcess.Id
        CPUTime = $workerProcess.CPU
        WorkingSetMB = [math]::Round($workerProcess.WorkingSet / 1MB, 2)
        StartTime = $workerProcess.StartTime
        ThreadCount = $workerProcess.Threads.Count
    }
}

STEP 1: DISCOVER APPLICATION POOL NAMES FIRST:

# Get all application pool names
$appPoolNames = (Get-IISAppPool).Name
Write-Output "Available Application Pools:"
$appPoolNames | ForEach-Object { Write-Output "  - $_" }

# Filter for LIMS-related application pools (common naming patterns)
$limsAppPools = $appPoolNames | Where-Object {$_ -match "lims|lab|laboratory|sample"}
Write-Output "Potential LIMS Application Pools:"
$limsAppPools | ForEach-Object { Write-Output "  - $_" }

STEP 2: MONITOR DISCOVERED APPLICATION POOLS:

# Monitor application pool resource usage over time
# TIME UNITS: $samples = number of measurements, $interval = SECONDS between measurements
$samples = 5        # Take 5 measurements
$interval = 10      # Wait 10 SECONDS between each measurement (total monitoring time = 50 seconds)

# Choose application pool to monitor from discovered list
foreach($appPoolName in (Get-IISAppPool).Name) {
    Write-Output "=== Monitoring Application Pool: $appPoolName ==="

    for($i = 1; $i -le $samples; $i++) {
        $wp = Get-Process w3wp -ErrorAction SilentlyContinue | 
            Where-Object {
                $wpInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $($_.Id)"
                ($wpInfo.CommandLine -split '"')[1] -eq $appPoolName
            }

        if($wp) {
            Write-Output "Sample $i - Memory: $([math]::Round($wp.WorkingSet / 1MB, 2)) MB | Threads: $($wp.Threads.Count)"
            if($i -lt $samples) { Start-Sleep $interval }  # Sleep for $interval SECONDS
        } else {
            Write-Output "Sample $i - No worker process found for app pool: $appPoolName"
        }
    }
    Write-Output ""
}

### 3. Configuration Analysis

**Compare current configuration with baseline:**
```powershell
# Export current IIS configuration for comparison
$siteName = "<SITE_NAME>"
$configPath = "C:\temp\IIS_Config_$(Get-Date -Format 'yyyy-MM-dd').xml"

# Export site configuration
$siteConfig = Get-WebConfiguration -Filter "system.applicationHost/sites/site[@name='$siteName']"
$siteConfig | Export-Clixml $configPath

# Export application pool configuration
$appPoolName = (Get-Website -Name $siteName).ApplicationPool
$appPoolConfig = Get-WebConfiguration -Filter "system.applicationHost/applicationPools/add[@name='$appPoolName']"
$appPoolConfig | Export-Clixml "C:\temp\AppPool_Config_$(Get-Date -Format 'yyyy-MM-dd').xml"

Write-Output "Configuration exported to $configPath"

IIS Debugging Scenarios for LIMS

1. Web Service Failures

Scenario: LIMS API Connection Resets

Symptoms:
- HTTP API calls fail intermittently
- "Connection_Abandoned_By_ReqQueue" in HTTPERR logs
- Requires server restart to resolve temporarily

Discovery Commands:

# Check HTTPERR logs for connection issues
$httpErrPath = "$env:SystemRoot\System32\LogFiles\HTTPERR"
Get-ChildItem "$httpErrPath\httperr*.log" | 
    ForEach-Object {
        Get-Content $_.FullName | 
        Where-Object {$_ -match "Connection_Abandoned_By_ReqQueue"} |
        Select-Object -Last 10
    }

# Check application pool identity
$appPoolName = "<APPPOOL_NAME>"
Get-IISAppPool -Name $appPoolName | Select-Object Name, ProcessModel

# Check request queue limits
netsh http show servicestate view=requestq verbose=yes

Diagnosis Commands:

# Check for blocked threads
$siteName = "<WEBSITE_NAME>"
appcmd list requests /site.name:"$siteName" /elapsed:10000

# Monitor executing requests
$appPoolName = "<APPPOOL_NAME>"
Get-Counter "\ASP.NET Applications($appPoolName)\Requests Executing" -MaxSamples 5 -SampleInterval 2

Resolution Steps:

# 1. Increase request queue limit
appcmd set config -section:system.webServer/httpErrors /+"[statusCode='503',subStatusCode='2',prefixLanguageFilePath='',path='C:\inetpub\custerr\en-US\503.htm']"

# 2. Configure application pool identity with network permissions
$appPoolName = "<APPPOOL_NAME>"
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.identityType -Value SpecificUser
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.userName -Value "<DOMAIN>\<SERVICE_ACCOUNT>"
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.password -Value "<SERVICE_ACCOUNT_PASSWORD>"

# 3. Configure keep-alive settings
$siteName = "<WEBSITE_NAME>"
Set-WebConfigurationProperty -Filter "system.webServer/httpProtocol" -Name "allowKeepAlive" -Value $true -Location $siteName

Scenario: Web Service Hangs and Timeouts

Symptoms:
- Pages taking >30 seconds to load
- 503 Service Unavailable errors
- High CPU usage in w3wp.exe

Discovery Commands:

# Find long-running requests
$siteName = "<WEBSITE_NAME>"
appcmd list requests /site.name:"$siteName" /elapsed:30000

# Check thread pool exhaustion
$appPoolName = "<APPPOOL_NAME>"
Get-Counter "\ASP.NET Applications($appPoolName)\Requests in Application Queue" -MaxSamples 3

# Monitor worker process CPU
$appPoolName = "<APPPOOL_NAME>"
Get-Process w3wp | Where-Object {
    $wpInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $($_.Id)"
    ($wpInfo.CommandLine -split '"')[1] -eq $appPoolName
} | Measure-Object CPU -Average

Resolution Commands:

# Configure proper timeout values
$siteName = "<WEBSITE_NAME>"
Set-WebConfigurationProperty -Filter "system.web/httpRuntime" -Name "executionTimeout" -Value 300 -Location $siteName
Set-WebConfigurationProperty -Filter "system.web/httpRuntime" -Name "maxRequestLength" -Value 102400 -Location $siteName

# Configure application pool recycling
$appPoolName = "<APPPOOL_NAME>"
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name recycling.periodicRestart.requests -Value 10000
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name recycling.periodicRestart.privateMemory -Value 1048576  # 1GB in KB

2. API Connectivity Problems

Scenario: LIMS Database Integration Failures

Symptoms:
- "Cannot locate master LIM now" error messages
- Database timeout exceptions
- Intermittent data access failures

Discovery Commands:

# Test database connectivity from IIS server
$connectionString = "<DATABASE_CONNECTION_STRING>"
try {
    $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
    $connection.Open()
    Write-Output "Database connection successful"
    $connection.Close()
} catch {
    Write-Output "Database connection failed: $($_.Exception.Message)"
}

# Check application pool identity permissions
$appPoolName = "<APPPOOL_NAME>"
$appPool = Get-IISAppPool -Name $appPoolName
Write-Output "Application Pool Identity: $($appPool.ProcessModel.IdentityType)"

Resolution Commands:

# Configure connection pooling
$webConfigPath = "<WEBSITE_PHYSICAL_PATH>\web.config"
$webConfig = [xml](Get-Content $webConfigPath)

# Add connection string with proper pooling settings
$connectionString = "Server=<DATABASE_SERVER>;Database=<DATABASE_NAME>;Integrated Security=true;Pooling=true;Max Pool Size=100;Min Pool Size=5;Connection Timeout=30;"

# Set in web.config programmatically (backup first)
Copy-Item $webConfigPath "$webConfigPath.backup"
# Manual web.config editing required for connection strings
Write-Output "Update connection string in $webConfigPath with proper pooling settings"

3. File Receiving Issues

Scenario: LIMS File Processing Service Failures

Symptoms:
- Files accumulating in watch folders
- "System resource exceeded" errors
- File system access denied errors

Discovery Commands:

# Check file system permissions on LIMS directories
$limsPath = "<LIMS_FILE_PROCESSING_PATH>"  # Example: F:\LIMS\Data\Incoming
$appPoolName = "<APPPOOL_NAME>"

# Get application pool identity
$identity = "IIS AppPool\$appPoolName"
Write-Output "Checking permissions for: $identity"

# Check NTFS permissions
$acl = Get-Acl $limsPath
$acl.AccessToString

# Test file creation permissions
$testFile = Join-Path $limsPath "test_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
try {
    "Test" | Out-File $testFile
    Remove-Item $testFile
    Write-Output "File system permissions: OK"
} catch {
    Write-Output "File system permissions: FAILED - $($_.Exception.Message)"
}

Resolution Commands:

# Grant proper permissions to application pool identity
$limsPath = "<LIMS_FILE_PROCESSING_PATH>"
$appPoolName = "<APPPOOL_NAME>"
$identity = "IIS AppPool\$appPoolName"

# Grant Full Control to LIMS processing directories
icacls "$limsPath" /grant "$identity:(OI)(CI)F" /T

# Configure file system monitoring exclusions (for antivirus)
# This is environment-specific - add LIMS directories to antivirus exclusions
Write-Output "Add the following paths to antivirus exclusions:"
Write-Output "- $limsPath"
Write-Output "- F:\LIMS\*"  # Adjust based on your LIMS installation

4. Printer Agent Problems

Scenario: LIMS Label Printing Service Issues

Symptoms:
- Print jobs stuck in queue
- "ApplicationPoolIdentity" printer access errors
- Registry key missing printer information

Discovery Commands:

# Check printer access for application pool identity
$appPoolName = "<APPPOOL_NAME>"
$identity = "IIS AppPool\$appPoolName"

# List available printers
Get-Printer | Select-Object Name, DriverName, PortName, PrinterStatus

# Check if application pool can access printers
$printerName = "<PRINTER_NAME>"
try {
    $printer = Get-Printer -Name $printerName
    Write-Output "Printer '$printerName' found: $($printer.PrinterStatus)"
} catch {
    Write-Output "Cannot access printer '$printerName': $($_.Exception.Message)"
}

Resolution Commands:

# Configure application pool to load user profile (required for printer access)
$appPoolName = "<APPPOOL_NAME>"
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.loadUserProfile -Value $true

# Configure application pool to run under account with printer permissions
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.identityType -Value SpecificUser
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.userName -Value "<DOMAIN>\<PRINT_SERVICE_ACCOUNT>"
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.password -Value "<PRINT_SERVICE_ACCOUNT_PASSWORD>"

# Increase request size limits for large print jobs
$siteName = "<WEBSITE_NAME>"
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/requestLimits" -Name maxAllowedContentLength -Value 104857600 -Location $siteName  # 100MB

5. Authentication and Authorization Issues

Scenario: LIMS Single Sign-On Failures

Symptoms:
- 401.2 authentication errors
- Kerberos double-hop issues
- Users unable to access LIMS modules

Discovery Commands:

# Check authentication configuration
$siteName = "<WEBSITE_NAME>"

# Windows Authentication settings
Get-WebConfigurationProperty -Filter "system.webServer/security/authentication/windowsAuthentication" -Name "enabled" -Location $siteName
Get-WebConfigurationProperty -Filter "system.webServer/security/authentication/windowsAuthentication/providers" -Location $siteName

# Check for SPN registration
$serviceName = "<SERVICE_ACCOUNT>"
$serverName = "<SERVER_NAME>"
setspn -L $serviceName

# Check delegation settings
setspn -Q HTTP/$serverName

Resolution Commands:

# Enable Windows Authentication
$siteName = "<WEBSITE_NAME>"
Set-WebConfigurationProperty -Filter "system.webServer/security/authentication/windowsAuthentication" -Name "enabled" -Value $true -Location $siteName

# Configure authentication providers (Kerberos first)
Add-WebConfigurationProperty -Filter "system.webServer/security/authentication/windowsAuthentication/providers" -Name "." -Value @{value="Negotiate"} -Location $siteName

# Register SPN for service account
$serviceName = "<SERVICE_ACCOUNT>"
$serverName = "<SERVER_NAME>"
setspn -A HTTP/$serverName $serviceName
setspn -A HTTP/$serverName.domain.com $serviceName

# Configure constrained delegation (run on domain controller)
# Enable delegation in Active Directory Users and Computers for the service account
Write-Output "Configure constrained delegation in Active Directory for account: $serviceName"

Advanced Troubleshooting

1. Memory Leaks and Performance Issues

Detect memory leaks in LIMS applications:

# Monitor application pool memory usage over time
$appPoolName = "<APPPOOL_NAME>"
$monitorMinutes = 30
$intervalSeconds = 60

$results = @()
for($i = 0; $i -lt $monitorMinutes; $i++) {
    $wp = Get-Process w3wp -ErrorAction SilentlyContinue | 
        Where-Object {
            $wpInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $($_.Id)"
            ($wpInfo.CommandLine -split '"')[1] -eq $appPoolName
        }

    if($wp) {
        $results += [PSCustomObject]@{
            Time = Get-Date
            MemoryMB = [math]::Round($wp.WorkingSet / 1MB, 2)
            ThreadCount = $wp.Threads.Count
            HandleCount = $wp.HandleCount
            CPUTime = $wp.CPU
        }
    }

    if($i -lt ($monitorMinutes - 1)) { Start-Sleep $intervalSeconds }
}

# Analyze memory growth
$results | Format-Table -AutoSize
$memoryGrowth = ($results[-1].MemoryMB - $results[0].MemoryMB)
Write-Output "Memory growth over $monitorMinutes minutes: $memoryGrowth MB"

Configure memory-based recycling:

# Set memory limits for application pool
$appPoolName = "<APPPOOL_NAME>"
$memoryLimitMB = 2048  # 2GB

Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name recycling.periodicRestart.privateMemory -Value ($memoryLimitMB * 1024)  # Convert to KB
Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name recycling.periodicRestart.virtualMemory -Value ($memoryLimitMB * 1024 * 2)  # 4GB virtual

2. SSL/TLS Configuration Problems

Diagnose SSL certificate issues:

# Check SSL certificate details
$hostname = "<HOSTNAME>"
$port = 443

$tcpClient = New-Object System.Net.Sockets.TcpClient
try {
    $tcpClient.Connect($hostname, $port)
    $sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream())
    $sslStream.AuthenticateAsClient($hostname)

    $cert = $sslStream.RemoteCertificate
    [PSCustomObject]@{
        Subject = $cert.Subject
        Issuer = $cert.Issuer
        ValidFrom = [datetime]$cert.GetEffectiveDateString()
        ValidTo = [datetime]$cert.GetExpirationDateString()
        DaysUntilExpiry = ([datetime]$cert.GetExpirationDateString() - (Get-Date)).Days
        Thumbprint = $cert.GetCertHashString()
    }

    $sslStream.Close()
} catch {
    Write-Output "SSL connection failed: $($_.Exception.Message)"
} finally {
    $tcpClient.Close()
}

Fix SSL binding issues:

# Remove and recreate SSL binding
$siteName = "<WEBSITE_NAME>"
$certificateThumbprint = "<SSL_CERTIFICATE_THUMBPRINT>"
$ipAddress = "*"  # or specific IP
$port = 443

# Remove existing binding
Remove-WebBinding -Name $siteName -Protocol https -Port $port -IPAddress $ipAddress

# Add new SSL binding
New-WebBinding -Name $siteName -Protocol https -Port $port -IPAddress $ipAddress
New-Item -Path "IIS:\SslBindings\$ipAddress!$port" -Value (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $certificateThumbprint})

3. Network Connectivity Debugging

Test LIMS API endpoints:

# Comprehensive API connectivity test
function Test-LIMSConnectivity {
    param(
        [string]$BaseUrl = "<BASE_URL>",
        [string[]]$Endpoints = @("api/status", "api/health", "api/version"),
        [int]$TimeoutSeconds = 30
    )

    $results = @()
    foreach($endpoint in $endpoints) {
        $fullUrl = "$BaseUrl/$endpoint"
        try {
            $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
            $response = Invoke-WebRequest -Uri $fullUrl -TimeoutSec $TimeoutSeconds -UseBasicParsing
            $stopwatch.Stop()

            $results += [PSCustomObject]@{
                Endpoint = $endpoint
                Status = "Success"
                StatusCode = $response.StatusCode
                ResponseTime = $stopwatch.ElapsedMilliseconds
                ContentLength = $response.Content.Length
            }
        } catch {
            $results += [PSCustomObject]@{
                Endpoint = $endpoint
                Status = "Failed"
                StatusCode = $_.Exception.Response.StatusCode.value__
                Error = $_.Exception.Message
                ResponseTime = $null
                ContentLength = $null
            }
        }
    }
    return $results
}

# Run connectivity test
Test-LIMSConnectivity -BaseUrl "<BASE_URL>"

Trace network issues:

# Start network trace for HTTP traffic
$traceFile = "C:\temp\lims_network_trace.etl"
netsh trace start capture=yes provider=Microsoft-Windows-HttpService tracefile=$traceFile maxsize=100

Write-Output "Network trace started. Reproduce the issue, then run:"
Write-Output "netsh trace stop"
Write-Output "Trace file: $traceFile"

# Check URL reservations
netsh http show urlacl | Where-Object {$_ -match "<URL_PATTERN>"}

# Check SSL certificate bindings
netsh http show sslcert | Where-Object {$_ -match "<HOSTNAME>"}

4. Complete Health Check Script

Comprehensive LIMS-IIS health monitoring:

# Complete LIMS-IIS Health Check Script
# Save as Check-LIMSHealth.ps1
param(
    [string]$LIMSSiteName = "<WEBSITE_NAME>",
    [string]$LIMSAppPoolName = "<APPPOOL_NAME>",
    [string]$LIMSBaseUrl = "<BASE_URL>",
    [switch]$Detailed
)

function Write-Status {
    param([string]$Component, [string]$Status, [string]$Details = "")
    $color = switch($Status) {
        "OK" { "Green" }
        "WARNING" { "Yellow" }
        "ERROR" { "Red" }
        default { "White" }
    }
    Write-Host "[$Status] $Component" -ForegroundColor $color
    if($Details) { Write-Host "    $Details" -ForegroundColor Gray }
}

Write-Host "=== LIMS-IIS Health Check ===" -ForegroundColor Cyan
Write-Host "Site: $LIMSSiteName" -ForegroundColor Cyan
Write-Host "App Pool: $LIMSAppPoolName" -ForegroundColor Cyan
Write-Host "URL: $LIMSBaseUrl" -ForegroundColor Cyan
Write-Host ""

# 1. IIS Services
$services = @('HTTP', 'WAS', 'W3SVC')
foreach($service in $services) {
    $svc = Get-Service $service -ErrorAction SilentlyContinue
    if($svc -and $svc.Status -eq 'Running') {
        Write-Status "$service Service" "OK" "Running"
    } else {
        Write-Status "$service Service" "ERROR" "Not running or not found"
    }
}

# 2. Website Status
try {
    $website = Get-Website -Name $LIMSSiteName
    if($website.State -eq 'Started') {
        Write-Status "Website $LIMSSiteName" "OK" "Started"
    } else {
        Write-Status "Website $LIMSSiteName" "ERROR" "State: $($website.State)"
    }
} catch {
    Write-Status "Website $LIMSSiteName" "ERROR" "Not found"
}

# 3. Application Pool Status
try {
    $appPool = Get-IISAppPool -Name $LIMSAppPoolName
    if($appPool.State -eq 'Started') {
        Write-Status "App Pool $LIMSAppPoolName" "OK" "Started"
    } else {
        Write-Status "App Pool $LIMSAppPoolName" "ERROR" "State: $($appPool.State)"
    }
} catch {
    Write-Status "App Pool $LIMSAppPoolName" "ERROR" "Not found"
}

# 4. Worker Process Health
$wp = Get-Process w3wp -ErrorAction SilentlyContinue | 
    Where-Object {
        $wpInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $($_.Id)"
        ($wpInfo.CommandLine -split '"')[1] -eq $LIMSAppPoolName
    }

if($wp) {
    $memoryMB = [math]::Round($wp.WorkingSet / 1MB, 2)
    if($memoryMB -lt 1000) {
        Write-Status "Worker Process Memory" "OK" "$memoryMB MB"
    } elseif($memoryMB -lt 2000) {
        Write-Status "Worker Process Memory" "WARNING" "$memoryMB MB"
    } else {
        Write-Status "Worker Process Memory" "ERROR" "$memoryMB MB (High)"
    }
} else {
    Write-Status "Worker Process" "WARNING" "No worker process found"
}

# 5. Web Connectivity
try {
    $response = Invoke-WebRequest -Uri $LIMSBaseUrl -TimeoutSec 10 -UseBasicParsing
    if($response.StatusCode -eq 200) {
        Write-Status "Web Connectivity" "OK" "HTTP $($response.StatusCode)"
    } else {
        Write-Status "Web Connectivity" "WARNING" "HTTP $($response.StatusCode)"
    }
} catch {
    Write-Status "Web Connectivity" "ERROR" $_.Exception.Message
}

# 6. Recent Errors (if detailed)
if($Detailed) {
    Write-Host "`n=== Recent Errors (Last Hour) ===" -ForegroundColor Cyan
    $recentErrors = Get-WinEvent -FilterHashtable @{
        LogName='Application'
        StartTime=(Get-Date).AddHours(-1)
        Level=2
    } -MaxEvents 5 -ErrorAction SilentlyContinue |
    Where-Object {$_.ProviderName -match 'IIS|ASP.NET|W3SVC'}

    if($recentErrors) {
        foreach($error in $recentErrors) {
            Write-Host "$($error.TimeCreated): $($error.LevelDisplayName) - $($error.Message.Substring(0, [Math]::Min(100, $error.Message.Length)))..." -ForegroundColor Red
        }
    } else {
        Write-Host "No recent errors found" -ForegroundColor Green
    }
}

Write-Host "`n=== Health Check Complete ===" -ForegroundColor Cyan

Prevention and Monitoring

1. Proactive Monitoring Setup

Configure performance counter monitoring:

# Create monitoring script for LIMS performance counters
$monitoringScript = @"
# LIMS Performance Monitor Script
`$counters = @(
    "\Web Service(_Total)\Current Connections",
    "\ASP.NET Applications(<APPPOOL_NAME>)\Requests/Sec",
    "\ASP.NET Applications(<APPPOOL_NAME>)\Requests in Application Queue",
    "\Process(w3wp*)\% Processor Time",
    "\Process(w3wp*)\Working Set"
)

Get-Counter -Counter `$counters -SampleInterval 30 -MaxSamples 120 | 
    Export-Counter -Path "C:\temp\LIMS_Performance_`$(Get-Date -Format 'yyyy-MM-dd_HH-mm').blg"
"@

$monitoringScript | Out-File "C:\scripts\Monitor-LIMS-Performance.ps1"

# Create scheduled task for monitoring
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\scripts\Monitor-LIMS-Performance.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00AM"
Register-ScheduledTask -TaskName "LIMS Performance Monitoring" -Action $action -Trigger $trigger -Description "Monitor LIMS IIS performance counters"

2. Automated Health Checks

Daily health check automation:

# Create automated health check with email alerts
$healthCheckScript = @"
# Import required modules
Import-Module WebAdministration

# Configuration
`$siteName = "<WEBSITE_NAME>"
`$appPoolName = "<APPPOOL_NAME>"
`$baseUrl = "<BASE_URL>"
`$alertEmail = "<ADMIN_EMAIL_ADDRESS>"
`$smtpServer = "<SMTP_SERVER>"

# Health check function
function Send-Alert {
    param([string]`$Subject, [string]`$Body)
    Send-MailMessage -To `$alertEmail -From "lims-monitor@company.com" -Subject `$Subject -Body `$Body -SmtpServer `$smtpServer
}

# Check application pool status
`$appPool = Get-IISAppPool -Name `$appPoolName
if(`$appPool.State -ne 'Started') {
    Send-Alert "LIMS Alert: Application Pool Stopped" "Application pool `$appPoolName is not running. State: `$(`$appPool.State)"
}

# Check website status
`$website = Get-Website -Name `$siteName
if(`$website.State -ne 'Started') {
    Send-Alert "LIMS Alert: Website Stopped" "Website `$siteName is not running. State: `$(`$website.State)"
}

# Check web connectivity
try {
    `$response = Invoke-WebRequest -Uri `$baseUrl -TimeoutSec 30 -UseBasicParsing
    if(`$response.StatusCode -ne 200) {
        Send-Alert "LIMS Alert: Web Connectivity Issue" "LIMS website returned HTTP `$(`$response.StatusCode)"
    }
} catch {
    Send-Alert "LIMS Alert: Web Connectivity Failed" "Cannot connect to LIMS website: `$(`$_.Exception.Message)"
}

# Check for recent errors
`$recentErrors = Get-WinEvent -FilterHashtable @{
    LogName='Application'
    StartTime=(Get-Date).AddMinutes(-30)
    Level=2
} -MaxEvents 1 -ErrorAction SilentlyContinue |
Where-Object {`$_.ProviderName -match 'IIS|ASP.NET|W3SVC'}

if(`$recentErrors) {
    Send-Alert "LIMS Alert: Application Errors Detected" "Recent application errors detected in event log"
}
"@

$healthCheckScript | Out-File "C:\scripts\LIMS-Health-Check.ps1"

# Schedule health check to run every 15 minutes
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\scripts\LIMS-Health-Check.ps1"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 15) -RepetitionDuration (New-TimeSpan -Days 365)
Register-ScheduledTask -TaskName "LIMS Health Check" -Action $action -Trigger $trigger -Description "Automated LIMS health monitoring with alerts"

3. Log Management and Cleanup

Automated log cleanup for LIMS environments:

# Log cleanup script for LIMS environments
$logCleanupScript = @"
# LIMS Log Cleanup Script
`$retentionDays = 30
`$cutoffDate = (Get-Date).AddDays(-`$retentionDays)

# IIS Logs
`$iisLogPath = "`$env:SystemDrive\inetpub\logs\LogFiles"
if(Test-Path `$iisLogPath) {
    Get-ChildItem `$iisLogPath -Recurse -File | 
        Where-Object {`$_.LastWriteTime -lt `$cutoffDate} | 
        Remove-Item -Force
    Write-Output "Cleaned up IIS logs older than `$retentionDays days"
}

# HTTPERR Logs
`$httpErrPath = "`$env:SystemRoot\System32\LogFiles\HTTPERR"
if(Test-Path `$httpErrPath) {
    Get-ChildItem `$httpErrPath -File | 
        Where-Object {`$_.LastWriteTime -lt `$cutoffDate} | 
        Remove-Item -Force
    Write-Output "Cleaned up HTTPERR logs older than `$retentionDays days"
}

# Application-specific logs (adjust path as needed)
`$appLogPath = "F:\LIMS\Logs"
if(Test-Path `$appLogPath) {
    Get-ChildItem `$appLogPath -Recurse -File | 
        Where-Object {`$_.LastWriteTime -lt `$cutoffDate -and `$_.Extension -eq '.log'} | 
        Remove-Item -Force
    Write-Output "Cleaned up application logs older than `$retentionDays days"
}
"@

$logCleanupScript | Out-File "C:\scripts\LIMS-Log-Cleanup.ps1"

# Schedule cleanup to run weekly
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\scripts\LIMS-Log-Cleanup.ps1"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At "02:00AM"
Register-ScheduledTask -TaskName "LIMS Log Cleanup" -Action $action -Trigger $trigger -Description "Weekly cleanup of LIMS and IIS log files"

Quick Reference Commands

Emergency Commands

# Restart IIS completely
iisreset /restart

# Restart specific application pool
Restart-WebAppPool -Name "<APPPOOL_NAME>"

# Stop/Start specific website
Stop-Website -Name "<WEBSITE_NAME>"
Start-Website -Name "<WEBSITE_NAME>"

# Check application pool status
Get-IISAppPool | Where-Object {$_.State -ne "Started"}

# Check website status
Get-Website | Where-Object {$_.State -ne "Started"}

# Find all running worker processes
Get-Process w3wp | Select-Object Id, ProcessName, StartTime, WorkingSet64

# Check recent IIS errors
Get-WinEvent -FilterHashtable @{LogName='Application'; Level=2; StartTime=(Get-Date).AddHours(-1)} | 
    Where-Object {$_.ProviderName -match 'IIS|ASP.NET|W3SVC'} | 
    Select-Object TimeCreated, Id, LevelDisplayName, Message

Discovery One-Liners

# Get all website bindings
Get-Website | ForEach-Object { $_.Bindings.Collection | Select-Object @{n='Site';e={$_.Name}}, protocol, bindingInformation }

# Find websites on F: drive
Get-Website | Where-Object {$_.PhysicalPath -like "F:\*"}

# Get log locations for all sites
Get-Website | ForEach-Object { "$($_.logFile.Directory -replace '%SystemDrive%', $env:SystemDrive)\W3SVC$($_.id)" }

# Check SSL certificates expiring soon
Get-ChildItem IIS:\SslBindings | ForEach-Object { Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $_.Thumbprint -and $_.NotAfter -lt (Get-Date).AddDays(30)} }

This comprehensive guide provides complete discovery, analysis, and debugging capabilities for IIS troubleshooting in LIMS environments, with emphasis on PowerShell automation and structured problem-solving approaches. All placeholders use the <PLACEHOLDER_NAME> format as specified.

Last updated: 2025-08-26 20:00 UTC