Additional LIMS Debugging Functions

These functions complement your existing Windows LIMS debugging script with additional monitoring capabilities commonly needed in LIMS environments.

Windows Services Monitoring


#region Windows Services Monitoring
function Get-LIMSServiceStatus {
    param(
        [string[]]$ServicePatterns = @("*Oracle*", "*SQL*", "*IIS*", "*W3SVC*", "*LIMS*", "*Print*"),
        [switch]$IncludeAutoStartOnly = $true
    )
    
    Write-Host "Checking LIMS-related Windows services..." -ForegroundColor Yellow
    
    $services = @()
    foreach ($pattern in $ServicePatterns) {
        $services += Get-Service -Name $pattern -ErrorAction SilentlyContinue
    }
    
    $results = $services | Sort-Object Name -Unique | ForEach-Object {
        $startType = (Get-WmiObject -Class Win32_Service -Filter "Name='$($_.Name)'").StartMode
        
        [PSCustomObject]@{
            ServiceName = $_.Name
            DisplayName = $_.DisplayName
            Status = $_.Status
            StartType = $startType
            CanStop = $_.CanStop
            CanRestart = $_.CanPauseAndContinue
            DependentServices = ($_.DependentServices | Measure-Object).Count
            ServicesDependedOn = ($_.ServicesDependedOn | Measure-Object).Count
        }
    }
    
    if ($IncludeAutoStartOnly) {
        $results = $results | Where-Object { $_.StartType -eq "Auto" }
    }
    
    # Display summary
    $stopped = ($results | Where-Object { $_.Status -eq "Stopped" }).Count
    $running = ($results | Where-Object { $_.Status -eq "Running" }).Count
    
    Write-Host "Service Summary: $running running, $stopped stopped" -ForegroundColor Cyan
    
    # Highlight critical stopped services
    $stoppedCritical = $results | Where-Object { $_.Status -eq "Stopped" -and $_.StartType -eq "Auto" }
    if ($stoppedCritical) {
        Write-Host "⚠ Critical services stopped:" -ForegroundColor Red
        $stoppedCritical | ForEach-Object {
            Write-Host "  - $($_.ServiceName) ($($_.DisplayName))" -ForegroundColor Red
        }
    }
    
    return $results
}

File System and Storage Monitoring


function Test-LIMSFileSystemHealth {
    param(
        [string[]]$CriticalPaths = @(
            "C:\inetpub\wwwroot",
            "C:\Program Files\LIMS",
            "C:\LIMS\Data",
            "C:\LIMS\Temp",
            "C:\LIMS\Uploads",
            "C:\LIMS\Reports"
        ),
        [int]$LargeFolderSizeMB = 1000,
        [int]$OldFileDays = 30
    )
    
    Write-Host "Analyzing LIMS file system health..." -ForegroundColor Yellow
    
    $results = @{
        PathChecks = @()
        LargeFolders = @()
        OldFiles = @()
        DiskSpace = @()
        Recommendations = @()
    }
    
    foreach ($path in $CriticalPaths) {
        if (Test-Path $path) {
            try {
                # Basic path info
                $pathInfo = [PSCustomObject]@{
                    Path = $path
                    Exists = $true
                    IsWritable = $false
                    FileCount = 0
                    TotalSizeMB = 0
                    LastModified = $null
                }
                
                # Test write access
                $testFile = Join-Path $path "lims_test_$(Get-Random).tmp"
                try {
                    "test" | Out-File $testFile -Force
                    Remove-Item $testFile -Force
                    $pathInfo.IsWritable = $true
                }
                catch {
                    $pathInfo.IsWritable = $false
                    $results.Recommendations += "Check write permissions for: $path"
                }
                
                # Get folder statistics
                $files = Get-ChildItem $path -Recurse -File -ErrorAction SilentlyContinue
                $pathInfo.FileCount = ($files | Measure-Object).Count
                $pathInfo.TotalSizeMB = [math]::Round(($files | Measure-Object Length -Sum).Sum / 1MB, 2)
                $pathInfo.LastModified = ($files | Sort-Object LastWriteTime -Descending | Select-Object -First 1).LastWriteTime
                
                # Check for large folders
                if ($pathInfo.TotalSizeMB -gt $LargeFolderSizeMB) {
                    $results.LargeFolders += $pathInfo
                }
                
                # Find old files
                $oldFiles = $files | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OldFileDays) }
                if ($oldFiles) {
                    $results.OldFiles += [PSCustomObject]@{
                        Path = $path
                        OldFileCount = ($oldFiles | Measure-Object).Count
                        OldFileSizeMB = [math]::Round(($oldFiles | Measure-Object Length -Sum).Sum / 1MB, 2)
                        OldestFile = ($oldFiles | Sort-Object LastWriteTime | Select-Object -First 1).LastWriteTime
                    }
                }
                
                $results.PathChecks += $pathInfo
            }
            catch {
                $results.PathChecks += [PSCustomObject]@{
                    Path = $path
                    Exists = $true
                    Error = $_.Exception.Message
                }
            }
        }
        else {
            $results.PathChecks += [PSCustomObject]@{
                Path = $path
                Exists = $false
            }
            $results.Recommendations += "Create missing directory: $path"
        }
    }
    
    # Display summary
    Write-Host "File System Health Summary:" -ForegroundColor Green
    $results.PathChecks | ForEach-Object {
        $color = if ($_.Exists -and $_.IsWritable) { "Green" } elseif ($_.Exists) { "Yellow" } else { "Red" }
        $status = if ($_.Exists -and $_.IsWritable) { "✓ OK" } elseif ($_.Exists) { "⚠ Read-Only" } else { "✗ Missing" }
        Write-Host "  $($_.Path): $status" -ForegroundColor $color
    }
    
    if ($results.LargeFolders) {
        Write-Host "`nLarge folders (>$LargeFolderSizeMB MB):" -ForegroundColor Yellow
        $results.LargeFolders | ForEach-Object {
            Write-Host "  $($_.Path): $($_.TotalSizeMB)MB ($($_.FileCount) files)" -ForegroundColor Cyan
        }
    }
    
    return $results
}

Database Connection Pool Monitoring


function Test-LIMSDatabaseConnections {
    param(
        [string]$ConnectionString,
        [int]$ConnectionPoolSize = 10,
        [int]$TestDurationSeconds = 30
    )
    
    Write-Host "Testing LIMS database connection pool..." -ForegroundColor Yellow
    
    if (-not $ConnectionString) {
        Write-Warning "No connection string provided. Example for Oracle:"
        Write-Host 'Test-LIMSDatabaseConnections -ConnectionString "Data Source=server:1521/service;User Id=user;Password=pass;"'
        return
    }
    
    $results = @{
        ConnectionTests = @()
        PoolPerformance = @()
        Errors = @()
        Summary = @{}
    }
    
    $startTime = Get-Date
    $endTime = $startTime.AddSeconds($TestDurationSeconds)
    $testNumber = 0
    
    while ((Get-Date) -lt $endTime) {
        $testNumber++
        $connections = @()
        
        try {
            # Test multiple concurrent connections
            for ($i = 1; $i -le $ConnectionPoolSize; $i++) {
                $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                
                if ($ConnectionString -like "*Oracle*" -or $ConnectionString -like "*Data Source*") {
                    # Oracle connection
                    $connection = New-Object Oracle.ManagedDataAccess.Client.OracleConnection($ConnectionString)
                } else {
                    # SQL Server connection
                    $connection = New-Object System.Data.SqlClient.SqlConnection($ConnectionString)
                }
                
                $connection.Open()
                $stopwatch.Stop()
                
                $connections += $connection
                
                $results.ConnectionTests += [PSCustomObject]@{
                    TestNumber = $testNumber
                    ConnectionNumber = $i
                    OpenTimeMs = $stopwatch.ElapsedMilliseconds
                    Timestamp = Get-Date
                    Status = "Success"
                }
            }
            
            # Close all connections
            $connections | ForEach-Object { $_.Close(); $_.Dispose() }
            
            Write-Host "Test $testNumber`: Successfully opened $ConnectionPoolSize connections" -ForegroundColor Green
        }
        catch {
            $results.Errors += [PSCustomObject]@{
                TestNumber = $testNumber
                Error = $_.Exception.Message
                Timestamp = Get-Date
            }
            Write-Host "Test $testNumber`: Error - $($_.Exception.Message)" -ForegroundColor Red
            
            # Clean up any open connections
            $connections | ForEach-Object { 
                try { $_.Close(); $_.Dispose() } catch { }
            }
        }
        
        Start-Sleep 2
    }
    
    # Calculate summary statistics
    $successfulTests = $results.ConnectionTests | Where-Object { $_.Status -eq "Success" }
    $results.Summary = @{
        TotalTests = $testNumber
        SuccessfulConnections = ($successfulTests | Measure-Object).Count
        FailedTests = ($results.Errors | Measure-Object).Count
        AverageOpenTimeMs = if ($successfulTests) { [math]::Round(($successfulTests | Measure-Object OpenTimeMs -Average).Average, 2) } else { 0 }
        MaxOpenTimeMs = if ($successfulTests) { ($successfulTests | Measure-Object OpenTimeMs -Maximum).Maximum } else { 0 }
        MinOpenTimeMs = if ($successfulTests) { ($successfulTests | Measure-Object OpenTimeMs -Minimum).Minimum } else { 0 }
    }
    
    Write-Host "`nDatabase Connection Pool Summary:" -ForegroundColor Green
    Write-Host "  Total tests: $($results.Summary.TotalTests)" -ForegroundColor Cyan
    Write-Host "  Successful connections: $($results.Summary.SuccessfulConnections)" -ForegroundColor Cyan
    Write-Host "  Failed tests: $($results.Summary.FailedTests)" -ForegroundColor Cyan
    Write-Host "  Average open time: $($results.Summary.AverageOpenTimeMs)ms" -ForegroundColor Cyan
    Write-Host "  Min/Max open time: $($results.Summary.MinOpenTimeMs)ms / $($results.Summary.MaxOpenTimeMs)ms" -ForegroundColor Cyan
    
    return $results
}

Print Queue and Document Monitoring


function Get-LIMSPrintStatus {
    param(
        [string[]]$PrinterPatterns = @("*LIMS*", "*LAB*", "*LABEL*"),
        [int]$MaxQueueAge = 30
    )
    
    Write-Host "Checking LIMS print queues..." -ForegroundColor Yellow
    
    $results = @{
        Printers = @()
        QueuedJobs = @()
        Errors = @()
    }
    
    # Get all printers
    $allPrinters = Get-Printer -ErrorAction SilentlyContinue
    
    # Filter for LIMS-related printers
    $limsPrinters = @()
    foreach ($pattern in $PrinterPatterns) {
        $limsPrinters += $allPrinters | Where-Object { $_.Name -like $pattern }
    }
    
    if (-not $limsPrinters) {
        $limsPrinters = $allPrinters
    }
    
    foreach ($printer in $limsPrinters) {
        try {
            $printerInfo = [PSCustomObject]@{
                Name = $printer.Name
                Status = $printer.PrinterStatus
                JobCount = $printer.JobCount
                IsDefault = $printer.IsDefault
                IsShared = $printer.Shared
                PortName = $printer.PortName
                DriverName = $printer.DriverName
                Location = $printer.Location
                Comment = $printer.Comment
            }
            
            $results.Printers += $printerInfo
            
            # Check print jobs
            $printJobs = Get-PrintJob -PrinterName $printer.Name -ErrorAction SilentlyContinue
            foreach ($job in $printJobs) {
                $jobAge = ((Get-Date) - $job.SubmittedTime).TotalMinutes
                
                $jobInfo = [PSCustomObject]@{
                    PrinterName = $printer.Name
                    JobId = $job.Id
                    DocumentName = $job.DocumentName
                    UserName = $job.UserName
                    Status = $job.JobStatus
                    Pages = $job.TotalPages
                    Size = $job.Size
                    SubmittedTime = $job.SubmittedTime
                    AgeMinutes = [math]::Round($jobAge, 1)
                    Priority = $job.Priority
                }
                
                $results.QueuedJobs += $jobInfo
                
                if ($jobAge -gt $MaxQueueAge) {
                    Write-Host "⚠ Old print job: $($job.DocumentName) ($([math]::Round($jobAge,1)) minutes old)" -ForegroundColor Yellow
                }
            }
        }
        catch {
            $results.Errors += [PSCustomObject]@{
                PrinterName = $printer.Name
                Error = $_.Exception.Message
            }
        }
    }
    
    # Display summary
    Write-Host "Print System Summary:" -ForegroundColor Green
    Write-Host "  Total printers: $($results.Printers.Count)" -ForegroundColor Cyan
    Write-Host "  Queued jobs: $($results.QueuedJobs.Count)" -ForegroundColor Cyan
    
    $offlinePrinters = $results.Printers | Where-Object { $_.Status -ne "Normal" }
    if ($offlinePrinters) {
        Write-Host "  ⚠ Printers with issues:" -ForegroundColor Yellow
        $offlinePrinters | ForEach-Object {
            Write-Host "    $($_.Name): $($_.Status)" -ForegroundColor Red
        }
    }
    
    return $results
}

LIMS Application Log Parser


function Get-LIMSApplicationLogs {
    param(
        [string[]]$LogPaths = @(
            "C:\LIMS\Logs\*.log",
            "C:\Program Files\LIMS\Logs\*.log",
            "C:\ProgramData\LIMS\Logs\*.log"
        ),
        [int]$Hours = 24,
        [string[]]$ErrorPatterns = @("ERROR", "FATAL", "EXCEPTION", "FAIL", "TIMEOUT", "DEADLOCK"),
        [string[]]$WarningPatterns = @("WARN", "WARNING", "SLOW", "RETRY"),
        [string]$OutputPath = "C:\temp\lims_app_log_analysis.txt"
    )
    
    Write-Host "Analyzing LIMS application logs..." -ForegroundColor Yellow
    
    $startTime = (Get-Date).AddHours(-$Hours)
    $results = @{
        LogFiles = @()
        Errors = @()
        Warnings = @()
        Statistics = @{}
    }
    
    foreach ($logPattern in $LogPaths) {
        $logFiles = Get-ChildItem $logPattern -ErrorAction SilentlyContinue | 
                   Where-Object { $_.LastWriteTime -ge $startTime }
        
        foreach ($logFile in $logFiles) {
            Write-Host "Processing: $($logFile.Name)" -ForegroundColor Cyan
            
            $fileInfo = [PSCustomObject]@{
                FileName = $logFile.Name
                FullPath = $logFile.FullName
                SizeMB = [math]::Round($logFile.Length / 1MB, 2)
                LastModified = $logFile.LastWriteTime
                LineCount = 0
                ErrorCount = 0
                WarningCount = 0
            }
            
            try {
                $content = Get-Content $logFile.FullName
                $fileInfo.LineCount = ($content | Measure-Object).Count
                
                foreach ($line in $content) {
                    # Extract timestamp if possible (common formats)
                    $timestamp = $null
                    if ($line -match '^\d{4}-\d{2}-\d{2}[\s\T]\d{2}:\d{2}:\d{2}') {
                        try {
                            $timestamp = [DateTime]::Parse($matches[0])
                        } catch { }
                    }
                    
                    # Skip old entries if we have a timestamp
                    if ($timestamp -and $timestamp -lt $startTime) {
                        continue
                    }
                    
                    # Check for errors
                    foreach ($errorPattern in $ErrorPatterns) {
                        if ($line -match $errorPattern) {
                            $results.Errors += [PSCustomObject]@{
                                FileName = $logFile.Name
                                Timestamp = $timestamp
                                Pattern = $errorPattern
                                Line = $line.Trim()
                            }
                            $fileInfo.ErrorCount++
                            break
                        }
                    }
                    
                    # Check for warnings
                    foreach ($warningPattern in $WarningPatterns) {
                        if ($line -match $warningPattern) {
                            $results.Warnings += [PSCustomObject]@{
                                FileName = $logFile.Name
                                Timestamp = $timestamp
                                Pattern = $warningPattern
                                Line = $line.Trim()
                            }
                            $fileInfo.WarningCount++
                            break
                        }
                    }
                }
                
                $results.LogFiles += $fileInfo
            }
            catch {
                Write-Warning "Could not process $($logFile.Name): $($_.Exception.Message)"
            }
        }
    }
    
    # Generate statistics
    $results.Statistics = @{
        TotalLogFiles = ($results.LogFiles | Measure-Object).Count
        TotalErrors = ($results.Errors | Measure-Object).Count
        TotalWarnings = ($results.Warnings | Measure-Object).Count
        ErrorsByPattern = $results.Errors | Group-Object Pattern | Sort-Object Count -Descending
        TopErrorFiles = $results.LogFiles | Sort-Object ErrorCount -Descending | Select-Object -First 5
    }
    
    # Generate report
    $report = @"
LIMS Application Log Analysis - Last $Hours Hours
Generated: $(Get-Date)
================================================

SUMMARY:
Total log files processed: $($results.Statistics.TotalLogFiles)
Total errors found: $($results.Statistics.TotalErrors)
Total warnings found: $($results.Statistics.TotalWarnings)

TOP ERROR PATTERNS:
"@
    
    $results.Statistics.ErrorsByPattern | ForEach-Object {
        $report += "`n$($_.Name): $($_.Count) occurrences"
    }
    
    $report += "`n`nRECENT ERRORS (Last 20):`n"
    $results.Errors | Sort-Object Timestamp -Descending | Select-Object -First 20 | ForEach-Object {
        $timeStr = if ($_.Timestamp) { $_.Timestamp.ToString("yyyy-MM-dd HH:mm:ss") } else { "Unknown" }
        $report += "$timeStr [$($_.FileName)] $($_.Line)`n"
    }
    
    $report | Out-File $OutputPath
    Write-Host "Application log analysis saved to: $OutputPath" -ForegroundColor Green
    
    return $results
}

Performance Counter Monitoring


function Get-LIMSPerformanceCounters {
    param(
        [int]$SampleCount = 10,
        [int]$SampleIntervalSeconds = 5
    )
    
    Write-Host "Collecting LIMS performance counters..." -ForegroundColor Yellow
    
    $counters = @(
        "\Processor(_Total)\% Processor Time",
        "\Memory\Available MBytes",
        "\Memory\Pages/sec",
        "\PhysicalDisk(_Total)\% Disk Time",
        "\PhysicalDisk(_Total)\Disk Queue Length",
        "\Network Interface(*)\Bytes Total/sec",
        "\Process(w3wp)\% Processor Time",
        "\Process(w3wp)\Working Set",
        "\Process(w3wp)\Handle Count",
        "\Process(oracle)\% Processor Time",
        "\Process(oracle)\Working Set",
        "\ASP.NET\Requests Queued",
        "\ASP.NET\Request Execution Time",
        "\ASP.NET Applications(__Total__)\Requests/Sec"
    )
    
    $results = @()
    
    for ($i = 1; $i -le $SampleCount; $i++) {
        Write-Host "Sample $i of $SampleCount..." -ForegroundColor Cyan
        
        $sample = @{
            Timestamp = Get-Date
            Counters = @{}
        }
        
        foreach ($counter in $counters) {
            try {
                $value = (Get-Counter $counter -SampleInterval 1 -MaxSamples 1 -ErrorAction SilentlyContinue).CounterSamples.CookedValue
                $sample.Counters[$counter] = [math]::Round($value, 2)
            }
            catch {
                $sample.Counters[$counter] = $null
            }
        }
        
        $results += [PSCustomObject]$sample
        
        if ($i -lt $SampleCount) {
            Start-Sleep $SampleIntervalSeconds
        }
    }
    
    # Calculate averages and trends
    $analysis = @{
        AverageValues = @{}
        MaxValues = @{}
        Trends = @{}
    }
    
    foreach ($counter in $counters) {
        $values = $results | ForEach-Object { $_.Counters[$counter] } | Where-Object { $_ -ne $null }
        
        if ($values) {
            $analysis.AverageValues[$counter] = [math]::Round(($values | Measure-Object -Average).Average, 2)
            $analysis.MaxValues[$counter] = ($values | Measure-Object -Maximum).Maximum
            
            # Simple trend calculation (last value vs first value)
            if ($values.Count -gt 1) {
                $trend = $values[-1] - $values[0]
                $analysis.Trends[$counter] = [math]::Round($trend, 2)
            }
        }
    }
    
    Write-Host "`nPerformance Summary:" -ForegroundColor Green
    Write-Host "  CPU Average: $($analysis.AverageValues['\Processor(_Total)\% Processor Time'])%" -ForegroundColor Cyan
    Write-Host "  Memory Available: $($analysis.AverageValues['\Memory\Available MBytes'])MB" -ForegroundColor Cyan
    Write-Host "  Disk Queue: $($analysis.AverageValues['\PhysicalDisk(_Total)\Disk Queue Length'])" -ForegroundColor Cyan
    
    return @{
        RawData = $results
        Analysis = $analysis
    }
}
#endregion

Updated Usage Commands


Write-Host @"

EXTENDED LIMS DEBUGGING COMMANDS:
=================================

8. Windows Services Check:
   Get-LIMSServiceStatus -ServicePatterns @("*Oracle*", "*SQL*", "*LIMS*")

9. File System Health:
   Test-LIMSFileSystemHealth -CriticalPaths @("C:\LIMS\Data", "C:\LIMS\Temp")

10. Database Connection Pool Test:
    Test-LIMSDatabaseConnections -ConnectionString "your_connection_string"

11. Print Queue Status:
    Get-LIMSPrintStatus -PrinterPatterns @("*LIMS*", "*LAB*")

12. Application Log Analysis:
    Get-LIMSApplicationLogs -LogPaths @("C:\LIMS\Logs\*.log")

13. Performance Counter Monitoring:
    Get-LIMSPerformanceCounters -SampleCount 20 -SampleIntervalSeconds 10

"@ -ForegroundColor Green

---

Key Additions Summary:

1. Windows Services Monitoring - Critical LIMS services status

2. File System Health - Check LIMS directories, permissions, and cleanup needs

3. Database Connection Pool Testing - Test multiple concurrent connections

4. Print Queue Monitoring - Essential for LIMS environments with label/report printing

5. Application Log Parser - Beyond IIS logs, parse actual LIMS application logs

6. Performance Counter Monitoring - Detailed system and application metrics

These additions address common LIMS pain points like:

1. Windows Services Monitoring

LIMS environments depend on many services (Oracle, IIS, print services). The Get-LIMSServiceStatus function identifies stopped critical services quickly.

2. File System Health Checks

LIMS generate tons of files. Test-LIMSFileSystemHealth checks permissions, finds large folders, and identifies cleanup opportunities.

3. Database Connection Pool Testing

One of the most common LIMS issues! Test-LIMSDatabaseConnections stress-tests concurrent connections to catch pool exhaustion.

4. Print Queue Monitoring

Labs have complex printing needs (labels, reports, certificates). Get-LIMSPrintStatus catches common print queue issues.

5. Application Log Parser

Your script focuses on Windows/IIS logs, but LIMS apps have their own logs. Get-LIMSApplicationLogs parses application-specific errors.

6. Performance Counter Monitoring

More detailed than basic CPU/memory - Get-LIMSPerformanceCounters provides comprehensive system metrics.