LIMS Path Discovery and Generic File System Health

Functions to discover LIMS installation paths and perform health checks without assuming specific directory structures.

LIMS Path Discovery Function


function Find-LIMSInstallationPaths {
    param(
        [string[]]$SearchPatterns = @("*LIMS*", "*LAB*", "*Laboratory*"),
        [string[]]$CommonVendors = @("Thermo", "Waters", "Agilent", "PerkinElmer", "Abbott", "Roche", "Siemens"),
        [switch]$IncludeRegistry = $true,
        [switch]$IncludeServices = $true,
        [switch]$IncludeIIS = $true,
        [switch]$Verbose = $false
    )
    
    Write-Host "Discovering LIMS installation paths..." -ForegroundColor Yellow
    
    $discoveredPaths = @{
        ProgramFiles = @()
        IISPaths = @()
        ServicePaths = @()
        RegistryPaths = @()
        ConfigFiles = @()
        LogDirectories = @()
        DataDirectories = @()
        TempDirectories = @()
        AllUniquePaths = @()
    }
    
    # 1. Search Program Files directories
    Write-Host "Searching Program Files..." -ForegroundColor Cyan
    $programDirs = @("C:\Program Files", "C:\Program Files (x86)", "D:\Program Files")
    
    foreach ($dir in $programDirs) {
        if (Test-Path $dir) {
            # Search by common patterns
            foreach ($pattern in $SearchPatterns) {
                $found = Get-ChildItem $dir -Directory -Name $pattern -ErrorAction SilentlyContinue
                foreach ($folder in $found) {
                    $fullPath = Join-Path $dir $folder
                    $discoveredPaths.ProgramFiles += $fullPath
                    if ($Verbose) { Write-Host "  Found: $fullPath" -ForegroundColor Gray }
                }
            }
            
            # Search by vendor names
            foreach ($vendor in $CommonVendors) {
                $found = Get-ChildItem $dir -Directory -Name "*$vendor*" -ErrorAction SilentlyContinue
                foreach ($folder in $found) {
                    $fullPath = Join-Path $dir $folder
                    # Check if it contains LIMS-related files
                    $hasLIMSFiles = Get-ChildItem $fullPath -Recurse -File -Include "*.exe", "*.dll", "*.config" -ErrorAction SilentlyContinue |
                                   Where-Object { $_.Name -match "LIMS|Lab|Sample|Result|Instrument" } |
                                   Select-Object -First 1
                    if ($hasLIMSFiles) {
                        $discoveredPaths.ProgramFiles += $fullPath
                        if ($Verbose) { Write-Host "  Found vendor dir: $fullPath" -ForegroundColor Gray }
                    }
                }
            }
        }
    }
    
    # 2. Check IIS sites and applications
    if ($IncludeIIS) {
        Write-Host "Checking IIS sites..." -ForegroundColor Cyan
        try {
            Import-Module WebAdministration -ErrorAction SilentlyContinue
            $sites = Get-IISSite -ErrorAction SilentlyContinue
            
            foreach ($site in $sites) {
                if ($site.Name -match ($SearchPatterns -join "|")) {
                    $discoveredPaths.IISPaths += $site.PhysicalPath
                    if ($Verbose) { Write-Host "  Found IIS site: $($site.Name) -> $($site.PhysicalPath)" -ForegroundColor Gray }
                }
                
                # Check applications within sites
                $apps = Get-WebApplication -Site $site.Name -ErrorAction SilentlyContinue
                foreach ($app in $apps) {
                    if ($app.Path -match ($SearchPatterns -join "|")) {
                        $discoveredPaths.IISPaths += $app.PhysicalPath
                        if ($Verbose) { Write-Host "  Found IIS app: $($app.Path) -> $($app.PhysicalPath)" -ForegroundColor Gray }
                    }
                }
            }
        }
        catch {
            Write-Warning "Could not check IIS: $($_.Exception.Message)"
        }
    }
    
    # 3. Check Windows Services
    if ($IncludeServices) {
        Write-Host "Checking Windows services..." -ForegroundColor Cyan
        $services = Get-WmiObject Win32_Service | Where-Object { 
            $_.Name -match ($SearchPatterns -join "|") -or 
            $_.DisplayName -match ($SearchPatterns -join "|") -or
            $_.PathName -match ($SearchPatterns -join "|")
        }
        
        foreach ($service in $services) {
            if ($service.PathName) {
                # Extract directory from service path
                $servicePath = $service.PathName -replace '"', ''
                if (Test-Path $servicePath) {
                    $serviceDir = Split-Path $servicePath -Parent
                    $discoveredPaths.ServicePaths += $serviceDir
                    if ($Verbose) { Write-Host "  Found service: $($service.Name) -> $serviceDir" -ForegroundColor Gray }
                }
            }
        }
    }
    
    # 4. Check Registry for installation paths
    if ($IncludeRegistry) {
        Write-Host "Checking Registry..." -ForegroundColor Cyan
        $regPaths = @(
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
            "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
        )
        
        foreach ($regPath in $regPaths) {
            try {
                $programs = Get-ItemProperty $regPath -ErrorAction SilentlyContinue | 
                           Where-Object { 
                               $_.DisplayName -match ($SearchPatterns -join "|") -or
                               $_.Publisher -match ($CommonVendors -join "|")
                           }
                
                foreach ($program in $programs) {
                    if ($program.InstallLocation -and (Test-Path $program.InstallLocation)) {
                        $discoveredPaths.RegistryPaths += $program.InstallLocation
                        if ($Verbose) { Write-Host "  Found registry: $($program.DisplayName) -> $($program.InstallLocation)" -ForegroundColor Gray }
                    }
                }
            }
            catch {
                # Registry access might fail, continue silently
            }
        }
    }
    
    # 5. Look for common LIMS-related directories
    Write-Host "Searching for common LIMS directories..." -ForegroundColor Cyan
    $searchRoots = @("C:\", "D:\", "E:\")
    
    foreach ($root in $searchRoots) {
        if (Test-Path $root) {
            # Look for data directories
            $dataPatterns = @("*Data*", "*Database*", "*Files*", "*Storage*")
            foreach ($pattern in $dataPatterns) {
                $found = Get-ChildItem $root -Directory -Name $pattern -ErrorAction SilentlyContinue
                foreach ($folder in $found) {
                    $fullPath = Join-Path $root $folder
                    # Check if it contains LIMS-like files
                    $hasDataFiles = Get-ChildItem $fullPath -Recurse -Directory -ErrorAction SilentlyContinue |
                                   Where-Object { $_.Name -match "LIMS|Lab|Sample|Result|Instrument|Reports" } |
                                   Select-Object -First 1
                    if ($hasDataFiles) {
                        $discoveredPaths.DataDirectories += $fullPath
                        if ($Verbose) { Write-Host "  Found data dir: $fullPath" -ForegroundColor Gray }
                    }
                }
            }
        }
    }
    
    # 6. Consolidate and deduplicate all paths
    $allPaths = @()
    $allPaths += $discoveredPaths.ProgramFiles
    $allPaths += $discoveredPaths.IISPaths
    $allPaths += $discoveredPaths.ServicePaths
    $allPaths += $discoveredPaths.RegistryPaths
    $allPaths += $discoveredPaths.DataDirectories
    
    $discoveredPaths.AllUniquePaths = $allPaths | Sort-Object -Unique | Where-Object { $_ -and (Test-Path $_) }
    
    # 7. Find related directories (Config, Logs, Temp) for each discovered path
    foreach ($basePath in $discoveredPaths.AllUniquePaths) {
        # Look for config files
        $configFiles = Get-ChildItem $basePath -Recurse -File -Include "*.config", "*.xml", "*.ini", "*.properties" -ErrorAction SilentlyContinue |
                      Select-Object -First 10
        $discoveredPaths.ConfigFiles += $configFiles.DirectoryName | Sort-Object -Unique
        
        # Look for log directories
        $logDirs = Get-ChildItem $basePath -Recurse -Directory -Name "*log*", "*Log*" -ErrorAction SilentlyContinue
        foreach ($logDir in $logDirs) {
            $discoveredPaths.LogDirectories += Join-Path $basePath $logDir
        }
        
        # Look for temp directories
        $tempDirs = Get-ChildItem $basePath -Recurse -Directory -Name "*temp*", "*tmp*", "*cache*" -ErrorAction SilentlyContinue
        foreach ($tempDir in $tempDirs) {
            $discoveredPaths.TempDirectories += Join-Path $basePath $tempDir
        }
    }
    
    # Remove duplicates
    $discoveredPaths.ConfigFiles = $discoveredPaths.ConfigFiles | Sort-Object -Unique | Where-Object { $_ }
    $discoveredPaths.LogDirectories = $discoveredPaths.LogDirectories | Sort-Object -Unique | Where-Object { $_ -and (Test-Path $_) }
    $discoveredPaths.TempDirectories = $discoveredPaths.TempDirectories | Sort-Object -Unique | Where-Object { $_ -and (Test-Path $_) }
    
    # Display summary
    Write-Host "`nLIMS Path Discovery Summary:" -ForegroundColor Green
    Write-Host "  Program installations: $($discoveredPaths.ProgramFiles.Count)" -ForegroundColor Cyan
    Write-Host "  IIS sites/applications: $($discoveredPaths.IISPaths.Count)" -ForegroundColor Cyan  
    Write-Host "  Service locations: $($discoveredPaths.ServicePaths.Count)" -ForegroundColor Cyan
    Write-Host "  Registry entries: $($discoveredPaths.RegistryPaths.Count)" -ForegroundColor Cyan
    Write-Host "  Log directories: $($discoveredPaths.LogDirectories.Count)" -ForegroundColor Cyan
    Write-Host "  Temp directories: $($discoveredPaths.TempDirectories.Count)" -ForegroundColor Cyan
    Write-Host "  Total unique paths: $($discoveredPaths.AllUniquePaths.Count)" -ForegroundColor Yellow
    
    if ($discoveredPaths.AllUniquePaths.Count -eq 0) {
        Write-Host "  No LIMS installations found. Try running with -Verbose to see search details." -ForegroundColor Yellow
        Write-Host "  You may need to specify paths manually in other functions." -ForegroundColor Yellow
    }
    
    return $discoveredPaths
}

Updated Generic File System Health Function


function Test-LIMSFileSystemHealth {
    param(
        [string[]]$CriticalPaths = @(),
        [switch]$AutoDiscoverPaths = $true,
        [string[]]$AdditionalPaths = @(),
        [int]$LargeFolderSizeMB = 1000,
        [int]$OldFileDays = 30,
        [switch]$IncludeStandardPaths = $true
    )
    
    Write-Host "Analyzing LIMS file system health..." -ForegroundColor Yellow
    
    # Build the list of paths to check
    $pathsToCheck = @()
    
    # Add manually specified paths
    $pathsToCheck += $CriticalPaths
    $pathsToCheck += $AdditionalPaths
    
    # Add standard Windows paths that are commonly used
    if ($IncludeStandardPaths) {
        $standardPaths = @(
            "C:\inetpub\wwwroot",
            "C:\Windows\Temp",
            "C:\Temp"
        )
        $pathsToCheck += $standardPaths | Where-Object { Test-Path $_ }
    }
    
    # Auto-discover LIMS paths if requested
    if ($AutoDiscoverPaths) {
        Write-Host "Auto-discovering LIMS paths..." -ForegroundColor Cyan
        try {
            $discoveredPaths = Find-LIMSInstallationPaths -Verbose:$false
            $pathsToCheck += $discoveredPaths.AllUniquePaths
            $pathsToCheck += $discoveredPaths.LogDirectories
            $pathsToCheck += $discoveredPaths.TempDirectories
            $pathsToCheck += $discoveredPaths.ConfigFiles
        }
        catch {
            Write-Warning "Auto-discovery failed: $($_.Exception.Message)"
        }
    }
    
    # Remove duplicates and empty paths
    $pathsToCheck = $pathsToCheck | Where-Object { $_ } | Sort-Object -Unique
    
    if ($pathsToCheck.Count -eq 0) {
        Write-Warning "No paths to check. Use -CriticalPaths parameter or enable -AutoDiscoverPaths"
        return $null
    }
    
    Write-Host "Checking $($pathsToCheck.Count) discovered paths..." -ForegroundColor Cyan
    
    $results = @{
        PathChecks = @()
        LargeFolders = @()
        OldFiles = @()
        Recommendations = @()
        Summary = @{}
    }
    
    foreach ($path in $pathsToCheck) {
        if (Test-Path $path) {
            try {
                # Basic path info
                $pathInfo = [PSCustomObject]@{
                    Path = $path
                    Exists = $true
                    IsWritable = $false
                    FileCount = 0
                    TotalSizeMB = 0
                    LastModified = $null
                    PathType = "Unknown"
                }
                
                # Determine path type
                if ($path -match "log|Log") { $pathInfo.PathType = "Logs" }
                elseif ($path -match "temp|tmp|cache") { $pathInfo.PathType = "Temporary" }
                elseif ($path -match "config|Config") { $pathInfo.PathType = "Configuration" }
                elseif ($path -match "data|Data|database") { $pathInfo.PathType = "Data" }
                elseif ($path -match "program|Program") { $pathInfo.PathType = "Program" }
                elseif ($path -match "inetpub|wwwroot") { $pathInfo.PathType = "Web" }
                else { $pathInfo.PathType = "Other" }
                
                # Test write access (only for certain types)
                if ($pathInfo.PathType -in @("Temporary", "Data", "Logs")) {
                    $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 $($pathInfo.PathType) directory: $path"
                    }
                }
                
                # Get folder statistics (with size limits for performance)
                $files = Get-ChildItem $path -Recurse -File -ErrorAction SilentlyContinue | Select-Object -First 10000
                $pathInfo.FileCount = ($files | Measure-Object).Count
                
                if ($pathInfo.FileCount -eq 10000) {
                    $pathInfo.FileCount = "10000+"
                    $results.Recommendations += "Large directory detected (>10k files): $path - Consider cleanup"
                }
                
                if ($files) {
                    $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 (especially in temp directories)
                if ($pathInfo.PathType -in @("Temporary", "Logs")) {
                    $oldFiles = $files | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OldFileDays) }
                    if ($oldFiles) {
                        $oldFileInfo = [PSCustomObject]@{
                            Path = $path
                            PathType = $pathInfo.PathType
                            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.OldFiles += $oldFileInfo
                        
                        if ($oldFileInfo.OldFileSizeMB -gt 100) {
                            $results.Recommendations += "Consider cleanup of old files in $($pathInfo.PathType): $path ($($oldFileInfo.OldFileSizeMB)MB, $($oldFileInfo.OldFileCount) files)"
                        }
                    }
                }
                
                $results.PathChecks += $pathInfo
            }
            catch {
                $results.PathChecks += [PSCustomObject]@{
                    Path = $path
                    Exists = $true
                    Error = $_.Exception.Message
                }
            }
        }
        else {
            $results.PathChecks += [PSCustomObject]@{
                Path = $path
                Exists = $false
                PathType = "Missing"
            }
        }
    }
    
    # Generate summary statistics
    $results.Summary = @{
        TotalPaths = $pathsToCheck.Count
        ExistingPaths = ($results.PathChecks | Where-Object { $_.Exists }).Count
        WritablePaths = ($results.PathChecks | Where-Object { $_.IsWritable }).Count
        LargeFolders = $results.LargeFolders.Count
        PathsWithOldFiles = $results.OldFiles.Count
        TotalSizeMB = [math]::Round(($results.PathChecks | Where-Object { $_.TotalSizeMB } | Measure-Object TotalSizeMB -Sum).Sum, 2)
        PathTypes = $results.PathChecks | Group-Object PathType | ForEach-Object { "$($_.Name): $($_.Count)" }
    }
    
    # Display summary
    Write-Host "`nFile System Health Summary:" -ForegroundColor Green
    Write-Host "  Total paths checked: $($results.Summary.TotalPaths)" -ForegroundColor Cyan
    Write-Host "  Existing paths: $($results.Summary.ExistingPaths)" -ForegroundColor Cyan
    Write-Host "  Writable paths: $($results.Summary.WritablePaths)" -ForegroundColor Cyan
    Write-Host "  Total size: $($results.Summary.TotalSizeMB)MB" -ForegroundColor Cyan
    
    Write-Host "`nPath breakdown:" -ForegroundColor Cyan
    $results.Summary.PathTypes | ForEach-Object { Write-Host "  $_" -ForegroundColor Gray }
    
    Write-Host "`nPath Status:" -ForegroundColor Green
    $results.PathChecks | ForEach-Object {
        $icon = switch ($_.PathType) {
            "Program" { "📁" }
            "Data" { "💾" }
            "Logs" { "📄" }
            "Temporary" { "🗂️" }
            "Configuration" { "⚙️" }
            "Web" { "🌐" }
            default { "📂" }
        }
        
        $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 "  $icon $($_.Path) [$($_.PathType)]: $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
        }
    }
    
    if ($results.OldFiles) {
        Write-Host "`nDirectories with old files (>$OldFileDays days):" -ForegroundColor Yellow
        $results.OldFiles | ForEach-Object {
            Write-Host "  $($_.Path): $($_.OldFileSizeMB)MB ($($_.OldFileCount) files, oldest: $($_.OldestFile))" -ForegroundColor Cyan
        }
    }
    
    if ($results.Recommendations) {
        Write-Host "`nRecommendations:" -ForegroundColor Yellow
        $results.Recommendations | ForEach-Object {
            Write-Host "  • $_" -ForegroundColor Cyan
        }
    }
    
    return $results
}

Usage Examples


# 1. Discover LIMS paths first
$limsPaths = Find-LIMSInstallationPaths -Verbose

# 2. Use discovered paths for health check
Test-LIMSFileSystemHealth -AutoDiscoverPaths

# 3. Manual paths if auto-discovery doesn't work
Test-LIMSFileSystemHealth -CriticalPaths @("C:\YourLIMS", "D:\Data") -AutoDiscoverPaths:$false

# 4. Combine auto-discovery with additional specific paths
Test-LIMSFileSystemHealth -AutoDiscoverPaths -AdditionalPaths @("\\NetworkShare\LIMSData")

# 5. Just check standard Windows paths (no LIMS discovery)
Test-LIMSFileSystemHealth -AutoDiscoverPaths:$false -IncludeStandardPaths

# 6. Discover paths and show what was found
$paths = Find-LIMSInstallationPaths -Verbose
$paths.AllUniquePaths | ForEach-Object { Write-Host "Found: $_" }

Key Improvements:

1. Path Discovery First: Find-LIMSInstallationPaths searches multiple sources (Program Files, IIS, Services, Registry)

2. No Hardcoded Paths: Discovers actual LIMS installation locations

3. Multiple Search Methods: Program files, IIS sites, Windows services, Registry entries

4. Path Type Classification: Identifies Logs, Temp, Data, Config, Program directories

5. Performance Optimized: Limits file scanning to prevent hanging on huge directories

6. Flexible Usage: Can use auto-discovery, manual paths, or combination

7. Better Recommendations: Context-aware suggestions based on path types

This approach works for any LIMS installation without assuming specific directory structures!

Key Improvements in the Updated Version:

1. Path Discovery Function (Find-LIMSInstallationPaths)

Searches multiple sources:

Program Files - Looks for LIMS patterns and vendor names (Thermo, Waters, Agilent, etc.)

IIS Sites - Finds web applications with LIMS-related names

Windows Services - Discovers services pointing to LIMS executables

Registry - Checks uninstall entries for LIMS software

File System - Scans for directories with LIMS-like file structures

2. Generic File System Health (Test-LIMSFileSystemHealth)

No hardcoded assumptions:

Auto-discovery by default - Uses the discovery function automatically

Manual override - Can specify exact paths if needed

Path type classification - Identifies Logs, Temp, Data, Config, Program directories

Performance optimized - Limits scanning to prevent hanging on huge directories

3. Flexible Usage Patterns

Auto-discover everything (recommended)

Test-LIMSFileSystemHealth -AutoDiscoverPaths

If auto-discovery fails, specify manually

Test-LIMSFileSystemHealth -CriticalPaths @("C:\YourSpecificLIMS", "D:\Data") -AutoDiscoverPaths:$false

Combine both approaches

Test-LIMSFileSystemHealth -AutoDiscoverPaths -AdditionalPaths @("\\NetworkShare\LIMSData")

See what paths were discovered first

$paths = Find-LIMSInstallationPaths -Verbose

$paths.AllUniquePaths

4. Real-World Examples

This approach will find LIMS installations like:

C:\Program Files\Thermo Fisher\SampleManager

C:\Program Files (x86)\Waters\LIMS

D:\Applications\YourCustomLIMS

C:\inetpub\wwwroot\StarLIMS

Vendor-specific locations discovered via services/registry

The discovery function is intelligent enough to find LIMS installations regardless of vendor, version, or custom installation paths