modules/HomeLab.Monitoring/Public/HealthCheck.ps1
<#
.SYNOPSIS Performs a health check on the HomeLab environment. .DESCRIPTION Performs a comprehensive health check on the HomeLab environment, including resource health, connectivity, and configuration. .PARAMETER ResourceGroup The name of the resource group. If not specified, the resource group from the configuration will be used. .EXAMPLE Invoke-HealthCheck -ResourceGroup "HomeLab-RG" #> function Invoke-HealthCheck { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$ResourceGroup ) begin { # Import required modules Import-Module HomeLab.Core Import-Module HomeLab.Azure # Get configuration $config = Get-Configuration # Log function start Write-Log -Message "Starting health check" -Level INFO # If no resource group is specified, use the one from config if (-not $ResourceGroup) { $ResourceGroup = "$($config.projectName)-$($config.env)-$($config.locationCode)-rg" } } process { try { # Check if Azure is connected $azureConnected = Test-AzureConnection if (-not $azureConnected) { Write-Log -Message "Not connected to Azure. Attempting to connect..." -Level WARNING try { Connect-AzureAccount $azureConnected = $true } catch { Write-Log -Message "Failed to connect to Azure: $_" -Level ERROR $azureConnected = $false } } $healthResults = [PSCustomObject]@{ OverallHealth = "Healthy" CheckTime = Get-Date Checks = @() Recommendations = @() } # Check 1: Azure Connection $healthResults.Checks += [PSCustomObject]@{ Name = "Azure Connection" Status = if ($azureConnected) { "Healthy" } else { "Unhealthy" } Details = if ($azureConnected) { "Connected to Azure" } else { "Not connected to Azure" } } if (-not $azureConnected) { $healthResults.OverallHealth = "Unhealthy" $healthResults.Recommendations += "Connect to Azure using Connect-AzureAccount" return $healthResults } # Check 2: Resource Group Existence $rgExists = Get-AzResourceGroup -Name $ResourceGroup -ErrorAction SilentlyContinue $healthResults.Checks += [PSCustomObject]@{ Name = "Resource Group" Status = if ($rgExists) { "Healthy" } else { "Unhealthy" } Details = if ($rgExists) { "Resource group $ResourceGroup exists" } else { "Resource group $ResourceGroup does not exist" } } if (-not $rgExists) { $healthResults.OverallHealth = "Unhealthy" $healthResults.Recommendations += "Create resource group $ResourceGroup" return $healthResults } # Check 3: Virtual Network $vnetName = "$($config.projectName)-$($config.env)-$($config.locationCode)-vnet" $vnet = Get-AzVirtualNetwork -ResourceGroupName $ResourceGroup -Name $vnetName -ErrorAction SilentlyContinue $healthResults.Checks += [PSCustomObject]@{ Name = "Virtual Network" Status = if ($vnet) { "Healthy" } else { "Unhealthy" } Details = if ($vnet) { "Virtual network $vnetName exists" } else { "Virtual network $vnetName does not exist" } } if (-not $vnet) { $healthResults.OverallHealth = "Unhealthy" $healthResults.Recommendations += "Deploy virtual network $vnetName" } # Check 4: VPN Gateway $vpnGatewayName = "$($config.projectName)-$($config.env)-$($config.locationCode)-vpngw" $vpnGateway = Get-AzVirtualNetworkGateway -ResourceGroupName $ResourceGroup -Name $vpnGatewayName -ErrorAction SilentlyContinue $healthResults.Checks += [PSCustomObject]@{ Name = "VPN Gateway" Status = if ($vpnGateway) { "Healthy" } else { "Warning" } Details = if ($vpnGateway) { "VPN Gateway $vpnGatewayName exists" } else { "VPN Gateway $vpnGatewayName does not exist" } } if (-not $vpnGateway) { if ($healthResults.OverallHealth -ne "Unhealthy") { $healthResults.OverallHealth = "Warning" } $healthResults.Recommendations += "Deploy VPN Gateway $vpnGatewayName" } # Check 5: NAT Gateway $natGatewayName = "$($config.projectName)-$($config.env)-$($config.locationCode)-natgw" $natGateway = Get-AzNatGateway -ResourceGroupName $ResourceGroup -Name $natGatewayName -ErrorAction SilentlyContinue $healthResults.Checks += [PSCustomObject]@{ Name = "NAT Gateway" Status = if ($natGateway) { "Healthy" } else { "Warning" } Details = if ($natGateway) { "NAT Gateway $natGatewayName exists" } else { "NAT Gateway $natGatewayName does not exist" } } if (-not $natGateway) { if ($healthResults.OverallHealth -ne "Unhealthy") { $healthResults.OverallHealth = "Warning" } $healthResults.Recommendations += "Deploy NAT Gateway $natGatewayName" } # Check 6: Resource Health if ($vnet -or $vpnGateway -or $natGateway) { $resourceHealth = Test-ResourceHealth -ResourceGroup $ResourceGroup $unhealthyResources = $resourceHealth | Where-Object { $_.HealthStatus -ne "Healthy" -and $_.HealthStatus -ne "Unknown" } $healthResults.Checks += [PSCustomObject]@{ Name = "Resource Health" Status = if ($unhealthyResources.Count -eq 0) { "Healthy" } else { "Warning" } Details = if ($unhealthyResources.Count -eq 0) { "All resources are healthy" } else { "$($unhealthyResources.Count) resources are not healthy" } Resources = $unhealthyResources } if ($unhealthyResources.Count -gt 0) { if ($healthResults.OverallHealth -ne "Unhealthy") { $healthResults.OverallHealth = "Warning" } foreach ($resource in $unhealthyResources) { $healthResults.Recommendations += "Check resource $($resource.ResourceName) with health status $($resource.HealthStatus)" } } } # Check 7: Configuration $configValid = $true $configIssues = @() if (-not $config.projectName) { $configValid = $false $configIssues += "Project name is not set" } if (-not $config.env) { $configValid = $false $configIssues += "Environment is not set" } if (-not $config.locationCode) { $configValid = $false $configIssues += "Location code is not set" } $healthResults.Checks += [PSCustomObject]@{ Name = "Configuration" Status = if ($configValid) { "Healthy" } else { "Warning" } Details = if ($configValid) { "Configuration is valid" } else { "Configuration has issues: $($configIssues -join ', ')" } } if (-not $configValid) { if ($healthResults.OverallHealth -ne "Unhealthy") { $healthResults.OverallHealth = "Warning" } $healthResults.Recommendations += "Update configuration settings" } return $healthResults } catch { Write-Log -Message "Health check failed: $_" -Level ERROR return [PSCustomObject]@{ OverallHealth = "Error" CheckTime = Get-Date Checks = @( [PSCustomObject]@{ Name = "Health Check Execution" Status = "Error" Details = "Health check failed with error: $($_.Exception.Message)" } ) Recommendations = @( "Check logs for more details" ) } } } end { # Log function end Write-Log -Message "Health check completed with status: $($healthResults.OverallHealth)" -Level INFO } } <# .SYNOPSIS Gets the current health status of the HomeLab environment. .DESCRIPTION Gets the current health status of the HomeLab environment from the last health check. .EXAMPLE Get-HealthStatus #> function Get-HealthStatus { [CmdletBinding()] param () begin { # Import required modules Import-Module HomeLab.Core # Get configuration $config = Get-Configuration # Log function start Write-Log -Message "Getting health status" -Level INFO } process { try { # Check if health status file exists $healthStatusPath = [System.IO.Path]::Combine($env:USERPROFILE, "HomeLab", "health_status.json") if (Test-Path -Path $healthStatusPath) { # Get last health check time $healthStatus = Get-Content -Path $healthStatusPath -Raw | ConvertFrom-Json $lastCheckTime = $healthStatus.CheckTime # Check if health check is older than 24 hours $timeSinceLastCheck = (Get-Date) - [datetime]$lastCheckTime if ($timeSinceLastCheck.TotalHours -gt 24) { Write-Log -Message "Health status is more than 24 hours old. Running new health check..." -Level INFO $healthStatus = Invoke-HealthCheck # Save updated health status $healthStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $healthStatusPath -Encoding utf8 } return $healthStatus } else { Write-Log -Message "No health status found. Running health check..." -Level INFO # Run health check $healthStatus = Invoke-HealthCheck # Create directory if it doesn't exist $healthStatusDir = [System.IO.Path]::GetDirectoryName($healthStatusPath) if (-not (Test-Path -Path $healthStatusDir -PathType Container)) { New-Item -Path $healthStatusDir -ItemType Directory -Force | Out-Null } # Save health status $healthStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $healthStatusPath -Encoding utf8 return $healthStatus } } catch { Write-Log -Message "Failed to get health status: $_" -Level ERROR throw $_ } } end { # Log function end Write-Log -Message "Health status retrieved" -Level INFO } } <# .SYNOPSIS Exports a health report to a file. .DESCRIPTION Exports a health report to a file in the specified format. .PARAMETER Path The path where the report will be saved. If not specified, the report will be saved to the user's Documents folder. .PARAMETER Format The format of the report. Valid values are 'HTML', 'JSON', 'TXT'. Default is 'HTML'. .EXAMPLE Export-HealthReport -Path "C:\Reports" -Format "HTML" #> function Export-HealthReport { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$Path, [Parameter(Mandatory = $false)] [ValidateSet('HTML', 'JSON', 'TXT')] [string]$Format = 'HTML' ) begin { # Import required modules Import-Module HomeLab.Core # Get configuration $config = Get-Configuration # Log function start Write-Log -Message "Exporting health report" -Level INFO # Set default path if not specified if (-not $Path) { $Path = [System.IO.Path]::Combine([Environment]::GetFolderPath('MyDocuments'), 'HomeLab', 'Reports') } # Create directory if it doesn't exist if (-not (Test-Path -Path $Path -PathType Container)) { New-Item -Path $Path -ItemType Directory -Force | Out-Null } } process { try { # Get health status $healthStatus = Get-HealthStatus # Generate filename $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $fileName = "HealthReport_${timestamp}" # Export based on format switch ($Format) { 'HTML' { $filePath = [System.IO.Path]::Combine($Path, "$fileName.html") # Create HTML report $html = @" <!DOCTYPE html> <html> <head> <title>HomeLab Health Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1, h2 { color: #0078d4; } table { border-collapse: collapse; width: 100%; margin-bottom: 20px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } tr:nth-child(even) { background-color: #f9f9f9; } .summary { background-color: #e6f2ff; padding: 10px; border-radius: 5px; margin-bottom: 20px; } .healthy { color: green; } .warning { color: orange; } .unhealthy { color: red; } .error { color: darkred; } </style> </head> <body> <h1>HomeLab Health Report</h1> <div class="summary"> <h2>Summary</h2> <p><strong>Overall Health:</strong> <span class="$($healthStatus.OverallHealth.ToLower())">$($healthStatus.OverallHealth)</span></p> <p><strong>Check Time:</strong> $($healthStatus.CheckTime)</p> </div> <h2>Health Checks</h2> <table> <tr> <th>Check</th> <th>Status</th> <th>Details</th> </tr> "@ # Add rows for health checks foreach ($check in $healthStatus.Checks) { $statusClass = $check.Status.ToLower() $html += @" <tr> <td>$($check.Name)</td> <td class="$statusClass">$($check.Status)</td> <td>$($check.Details)</td> </tr> "@ } $html += @" </table> <h2>Recommendations</h2> <ul> "@ # Add recommendations if ($healthStatus.Recommendations.Count -gt 0) { foreach ($recommendation in $healthStatus.Recommendations) { $html += @" <li>$recommendation</li> "@ } } else { $html += @" <li>No recommendations at this time.</li> "@ } $html += @" </ul> <p><em>Report generated on $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")</em></p> </body> </html> "@ # Save HTML to file $html | Out-File -FilePath $filePath -Encoding utf8 } 'JSON' { $filePath = [System.IO.Path]::Combine($Path, "$fileName.json") $healthStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $filePath -Encoding utf8 } 'TXT' { $filePath = [System.IO.Path]::Combine($Path, "$fileName.txt") $txt = @" HomeLab Health Report ===================== Summary ------- Overall Health: $($healthStatus.OverallHealth) Check Time: $($healthStatus.CheckTime) Health Checks ------------ "@ foreach ($check in $healthStatus.Checks) { $txt += @" Check: $($check.Name) Status: $($check.Status) Details: $($check.Details) "@ } $txt += @" Recommendations -------------- "@ if ($healthStatus.Recommendations.Count -gt 0) { foreach ($recommendation in $healthStatus.Recommendations) { $txt += @" - $recommendation "@ } } else { $txt += @" - No recommendations at this time. "@ } $txt += @" Report generated on $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") "@ # Save TXT to file $txt | Out-File -FilePath $filePath -Encoding utf8 } } Write-Log -Message "Health report exported to $filePath" -Level INFO return $filePath } catch { Write-Log -Message "Failed to export health report: $_" -Level ERROR throw $_ } } end { # Log function end Write-Log -Message "Health report export completed" -Level INFO } } |