Functions to discover LIMS installation paths and perform health checks without assuming specific directory structures.
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
}
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
}
# 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: $_" }
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
Test-LIMSFileSystemHealth -AutoDiscoverPaths
Test-LIMSFileSystemHealth -CriticalPaths @("C:\YourSpecificLIMS", "D:\Data") -AutoDiscoverPaths:$false
Test-LIMSFileSystemHealth -AutoDiscoverPaths -AdditionalPaths @("\\NetworkShare\LIMSData")
$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