**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"

Table of Contents

Prerequisites and Setup

Required PowerShell Modules Installation

Before starting any IIS troubleshooting, ensure proper modules are available:


# 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

---

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:**

Export current IIS configuration for comparison

$siteName = ""

$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 = ""

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 = ""

appcmd list requests /site.name:"$siteName" /elapsed:10000

Monitor executing requests

$appPoolName = ""

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 = ""

Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.identityType -Value SpecificUser

Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.userName -Value "\"

Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.password -Value ""

3. Configure keep-alive settings

$siteName = ""

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 = ""

appcmd list requests /site.name:"$siteName" /elapsed:30000

Check thread pool exhaustion

$appPoolName = ""

Get-Counter "\ASP.NET Applications($appPoolName)\Requests in Application Queue" -MaxSamples 3

Monitor worker process CPU

$appPoolName = ""

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 = ""

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 = ""

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 = ""

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 = Get-IISAppPool -Name $appPoolName

Write-Output "Application Pool Identity: $($appPool.ProcessModel.IdentityType)"



**Resolution Commands:**

Configure connection pooling

$webConfigPath = "\web.config"

$webConfig = [xml](Get-Content $webConfigPath)

Add connection string with proper pooling settings

$connectionString = "Server=;Database=;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 = "" # Example: F:\LIMS\Data\Incoming

$appPoolName = ""

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 = ""

$appPoolName = ""

$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 = ""

$identity = "IIS AppPool\$appPoolName"

List available printers

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

Check if application pool can access printers

$printerName = ""

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 = ""

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 "\"

Set-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name processModel.password -Value ""

Increase request size limits for large print jobs

$siteName = ""

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 = ""

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 = ""

$serverName = ""

setspn -L $serviceName

Check delegation settings

setspn -Q HTTP/$serverName



**Resolution Commands:**

Enable Windows Authentication

$siteName = ""

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 = ""

$serverName = ""

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 = ""

$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 = ""

$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 = ""

$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 = ""

$certificateThumbprint = ""

$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 = "",

[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 ""



**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 ""}

Check SSL certificate bindings

netsh http show sslcert | Where-Object {$_ -match ""}



### 4. Complete Health Check Script

**Comprehensive LIMS-IIS health monitoring:**

Complete LIMS-IIS Health Check Script

Save as Check-LIMSHealth.ps1

param(

[string]$LIMSSiteName = "",

[string]$LIMSAppPoolName = "",

[string]$LIMSBaseUrl = "",

[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()\Requests/Sec",

"\ASP.NET Applications()\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 = ""

`$appPoolName = ""

`$baseUrl = ""

`$alertEmail = ""

`$smtpServer = ""

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 ""

Stop/Start specific website

Stop-Website -Name ""

Start-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.