These functions complement your existing Windows LIMS debugging script with additional monitoring capabilities commonly needed in LIMS environments.
#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
}
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
}
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
}
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
}
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
}
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
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
---
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.