modules/HomeLab.Azure/HomeLab.Azure.psm1
<# .SYNOPSIS HomeLab Azure Module .DESCRIPTION Module for HomeLab Azure infrastructure deployment with proper dependency handling. .NOTES Author: Jurie Smit Date: March 9, 2025 Version: 1.0.6 #> #region Initialization # Save original preferences to restore later $originalVerbosePreference = $VerbosePreference $originalErrorActionPreference = $ErrorActionPreference $originalWarningPreference = $WarningPreference # Set preferences for module loading $VerbosePreference = 'Continue' # Show verbose output during loading $ErrorActionPreference = 'Continue' # Don't terminate on errors $WarningPreference = 'Continue' # Show all warnings # Get module path for reference $ModulePath = $PSScriptRoot $ModuleName = (Get-Item $PSScriptRoot).BaseName Write-Verbose "Starting $ModuleName module initialization from $ModulePath" #endregion #region Dependency Management # Define required modules $requiredModules = @('HomeLab.Utils', 'HomeLab.Logging', 'HomeLab.Core') $missingModules = @() $loadedModules = @() Write-Host "Checking dependencies for $ModuleName..." -ForegroundColor Cyan foreach ($module in $requiredModules) { Write-Host "Checking module: $module" -ForegroundColor Yellow # Check if module is already loaded if (Get-Module -Name $module) { Write-Host " ✓ Module $module is already loaded" -ForegroundColor Green $loadedModules += $module continue } $moduleLoaded = $false # Try to load from relative path first (sibling directory) $siblingPath = Join-Path -Path (Split-Path -Parent $ModulePath) -ChildPath $module -AdditionalChildPath "$module.psd1" if (Test-Path -Path $siblingPath) { Write-Host " → Found $module at $siblingPath" -ForegroundColor Yellow try { Import-Module -Name $siblingPath -Force -ErrorAction Stop Write-Host " ✓ Successfully loaded $module from sibling path" -ForegroundColor Green $moduleLoaded = $true $loadedModules += $module } catch { Write-Warning " ✗ Failed to load $module from sibling path: $($_.Exception.Message)" } } else { Write-Host " → Module not found at sibling path, checking PSModulePath" -ForegroundColor Yellow } # If not loaded from sibling path, try PSModulePath if (-not $moduleLoaded) { try { Import-Module -Name $module -Force -ErrorAction Stop Write-Host " ✓ Successfully loaded $module from PSModulePath" -ForegroundColor Green $moduleLoaded = $true $loadedModules += $module } catch { Write-Warning " ✗ Failed to load $module from PSModulePath: $($_.Exception.Message)" } } # If module couldn't be loaded, add to missing modules list if (-not $moduleLoaded) { $missingModules += $module } } # Display summary of dependency check Write-Host "Dependency check summary:" -ForegroundColor Cyan Write-Host " - Total required modules: $($requiredModules.Count)" -ForegroundColor Cyan Write-Host " - Successfully loaded: $($loadedModules.Count)" -ForegroundColor $(if ($loadedModules.Count -eq $requiredModules.Count) { 'Green' } else { 'Yellow' }) Write-Host " - Missing modules: $($missingModules.Count)" -ForegroundColor $(if ($missingModules.Count -eq 0) { 'Green' } else { 'Red' }) if ($missingModules.Count -gt 0) { Write-Warning "Missing required modules: $($missingModules -join ', '). Some functionality may be limited." } #endregion #region Function Loading # Load private functions (internal to the module) $privatePath = Join-Path -Path $ModulePath -ChildPath 'Private' $privateCount = 0 if (Test-Path -Path $privatePath) { $privateFiles = Get-ChildItem -Path "$privatePath\*.ps1" -Recurse -ErrorAction SilentlyContinue Write-Host "Loading private functions from $privatePath..." -ForegroundColor Cyan Write-Verbose "Found $($privateFiles.Count) private function files" foreach ($file in $privateFiles) { try { . $file.FullName $privateCount++ Write-Verbose "Loaded private function: $($file.BaseName)" } catch { Write-Warning "Failed to import private function $($file.BaseName): $($_.Exception.Message)" } } Write-Host " ✓ Loaded $privateCount private functions" -ForegroundColor Green } else { Write-Warning "Private directory not found: $privatePath" } # Load public functions (to be exported) $publicPath = Join-Path -Path $ModulePath -ChildPath 'Public' $publicFunctions = @() $publicCount = 0 if (Test-Path -Path $publicPath) { $publicFiles = Get-ChildItem -Path "$publicPath\*.ps1" -Recurse -ErrorAction SilentlyContinue Write-Host "Loading public functions from $publicPath..." -ForegroundColor Cyan Write-Verbose "Found $($publicFiles.Count) public function files" foreach ($file in $publicFiles) { try { # Get function names before loading the file $beforeFunctions = Get-ChildItem function: | Select-Object -ExpandProperty Name # Load the file . $file.FullName $publicCount++ # Get function names after loading the file $afterFunctions = Get-ChildItem function: | Select-Object -ExpandProperty Name # Find new functions that were added by this file $newFunctions = $afterFunctions | Where-Object { $beforeFunctions -notcontains $_ } if ($newFunctions) { foreach ($fn in $newFunctions) { # Verify this is a valid function name (not 'returns' or other keywords) if ($fileContent -match 'function\s+([A-Za-z0-9\-_]+)\s*\{') { $publicFunctions += $fn Write-Verbose "Added function to export list: $fn" } else { Write-Warning "Skipping invalid function name: $fn" } } } else { # Fallback to regex parsing if no new functions were detected $fileContent = Get-Content -Path $file.FullName -Raw if ($fileContent -match 'function\s+([A-Za-z0-9\-_]+)') { $functionName = $matches[1] if ($functionName -ne 'returns') { $publicFunctions += $functionName Write-Verbose "Added function to export list (regex): $functionName" } else { Write-Warning "Skipping invalid function name (regex): $functionName" } } else { Write-Warning "No function definition found in file: $($file.Name)" } } } catch { Write-Warning "Failed to import public function $($file.BaseName): $($_.Exception.Message)" } } Write-Host " ✓ Loaded $publicCount public functions" -ForegroundColor Green } else { Write-Warning "Public directory not found: $publicPath" } # Check for problematic function definitions Write-Host "Checking for problematic function definitions..." -ForegroundColor Cyan foreach ($file in $publicFiles) { $content = Get-Content -Path $file.FullName -Raw if ($content -match 'function\s+returns\b' -or $content -match '\breturns\s*\(' -or $content -match '\breturns\s*\{') { Write-Warning "Potential problematic 'returns' reference found in: $($file.FullName)" Write-Host " Context: $($matches[0])" -ForegroundColor Red } } #endregion #region Export Functions # Get all available functions in the module $availableFunctions = Get-ChildItem function: | Where-Object { $_.ScriptBlock.File -like "*$ModulePath*" } | Select-Object -ExpandProperty Name Write-Verbose "Available functions in module: $($availableFunctions -join ', ')" # Remove any duplicates from the public functions list $publicFunctions = $publicFunctions | Select-Object -Unique # Filter out any problematic function names $validPublicFunctions = $publicFunctions | Where-Object { $_ -match '^[a-zA-Z0-9\-_]+$' -and $_ -ne 'returns' -and $_ -ne 'if' -and $_ -ne 'else' -and $_ -ne 'elseif' -and $_ -ne 'switch' -and $_ -ne 'while' -and $_ -ne 'for' -and $_ -ne 'foreach' -and $_ -ne 'do' -and $_ -ne 'until' -and $_ -ne 'break' -and $_ -ne 'continue' -and $_ -ne 'return' } # Export all valid public functions if ($validPublicFunctions.Count -gt 0) { Export-ModuleMember -Function $validPublicFunctions # Display exported functions for verification Write-Host "Exported functions from $ModuleName module:" -ForegroundColor Cyan foreach ($function in $validPublicFunctions | Sort-Object) { Write-Host " - $function" -ForegroundColor Yellow } } else { Write-Warning "No functions exported from $ModuleName module" } #endregion #region Export vs Available Functions Comparison # Get the list of functions that are available but not exported $availableButNotExported = $availableFunctions | Where-Object { $validPublicFunctions -notcontains $_ } # Display export validation summary Write-Host "`n===== EXPORT VALIDATION SUMMARY =====" -ForegroundColor Cyan Write-Host " - Total available functions: $($availableFunctions.Count)" -ForegroundColor Cyan Write-Host " - Functions being exported: $($validPublicFunctions.Count)" -ForegroundColor Cyan Write-Host " - Available but not exported: $($availableButNotExported.Count)" -ForegroundColor $(if ($availableButNotExported.Count -eq 0) { 'Green' } else { 'Yellow' }) if ($availableButNotExported.Count -gt 0) { Write-Host "`nFunctions available but not exported:" -ForegroundColor Yellow foreach ($fn in $availableButNotExported | Sort-Object) { # Check if this is likely a private function (based on path) $fnInfo = Get-Command $fn -ErrorAction SilentlyContinue $isPrivate = $false if ($fnInfo -and $fnInfo.ScriptBlock.File -like "*\Private\*") { $isPrivate = $true } Write-Host " - $fn" -ForegroundColor $(if ($isPrivate) { 'Gray' } else { 'Yellow' }) -NoNewline if ($isPrivate) { Write-Host " (private function)" -ForegroundColor Gray } else { Write-Host " (consider exporting)" -ForegroundColor Yellow } } } # Check for functions that are being exported but not available $exportedButNotAvailable = $validPublicFunctions | Where-Object { $availableFunctions -notcontains $_ } if ($exportedButNotAvailable.Count -gt 0) { Write-Host "`nWARNING: Attempting to export functions that don't exist:" -ForegroundColor Red foreach ($fn in $exportedButNotAvailable | Sort-Object) { Write-Host " - $fn" -ForegroundColor Red } } #endregion # Display diagnostic information Write-Host "`n===== DIAGNOSTIC INFORMATION =====" -ForegroundColor Magenta Write-Host "Module path: $ModulePath" -ForegroundColor Magenta Write-Host "Functions actually available in the module:" -ForegroundColor Magenta $availableFunctions = Get-ChildItem function: | Where-Object { $_.ScriptBlock.File -like "*$ModulePath*" } | Select-Object -ExpandProperty Name if ($availableFunctions) { foreach ($fn in $availableFunctions) { Write-Host " - $fn" -ForegroundColor Magenta } } else { Write-Host " No functions found with this module path" -ForegroundColor Magenta } # Display loaded modules for verification Write-Host "Currently loaded HomeLab modules:" -ForegroundColor Magenta Get-Module | Where-Object { $_.Name -like "HomeLab.*" } | Format-Table -Property Name, Version, Path -AutoSize # Restore original preferences $VerbosePreference = $originalVerbosePreference $ErrorActionPreference = $originalErrorActionPreference $WarningPreference = $originalWarningPreference |