modules/HomeLab.UI/Public/Handlers/2-VpnCertHandler.ps1
<#
.SYNOPSIS VPN Certificate menu handler for HomeLab setup .DESCRIPTION Processes user selections in the VPN certificate menu using the new VPN certificate management functions. Displays progress bars for certificate operations. .PARAMETER ShowProgress If specified, shows a progress bar while loading the menu. .EXAMPLE Invoke-VpnCertMenu .EXAMPLE Invoke-VpnCertMenu -ShowProgress .NOTES Author: Jurie Smit Date: March 9, 2025 #> function Invoke-VpnCertMenu { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [switch]$ShowProgress ) # Check if required functions exist $requiredFunctions = @( "Show-VpnCertMenu", "Get-Configuration", "Pause" ) foreach ($function in $requiredFunctions) { if (-not (Get-Command -Name $function -ErrorAction SilentlyContinue)) { Write-Error "Required function '$function' not found. Make sure all required modules are imported." return } } # Check if logging is available $canLog = Get-Command -Name "Write-Log" -ErrorAction SilentlyContinue if ($canLog) { Write-Log -Message "Entering VPN Certificate Menu" -Level INFO } # Validate configuration try { $config = Get-Configuration # Verify required configuration properties $requiredProps = @('env', 'project', 'loc') $missingProps = $requiredProps | Where-Object { -not $config.PSObject.Properties.Name.Contains($_) -or [string]::IsNullOrWhiteSpace($config.$_) } if ($missingProps.Count -gt 0) { $message = "Configuration is missing required properties: $($missingProps -join ', ')" Write-Host $message -ForegroundColor Red if ($canLog) { Write-Log -Message $message -Level ERROR } $confirmContinue = Get-UserConfirmation -Message "Continue anyway?" -DefaultYes:$false if (-not $confirmContinue) { return } } } catch { Write-Host "Failed to load configuration: $_" -ForegroundColor Red if ($canLog) { Write-Log -Message "Failed to load configuration: $_" -Level ERROR } $confirmContinue = Get-UserConfirmation -Message "Continue without configuration?" -DefaultYes:$false if (-not $confirmContinue) { return } # Create minimal default configuration $config = [PSCustomObject]@{ env = "dev" project = "homelab" loc = "westus" } Write-Host "Using default configuration values." -ForegroundColor Yellow if ($canLog) { Write-Log -Message "Using default configuration values" -Level Warning } } # Define certificate store path with a default that can be overridden $certStorePath = "Cert:\CurrentUser\My" if ($config.PSObject.Properties.Name.Contains('CertStorePath') -and -not [string]::IsNullOrWhiteSpace($config.CertStorePath)) { $certStorePath = $config.CertStorePath } # Helper function to validate certificate name function Test-CertificateName { param ( [Parameter(Mandatory = $true)] [string]$Name ) # Basic validation - no special characters except hyphen and underscore return $Name -match '^[a-zA-Z0-9\-_]+$' } # Main menu loop $exitMenu = $false do { # Show menu and get result $result = Show-VpnCertMenu -ShowProgress:$ShowProgress if ($result.IsExit) { $exitMenu = $true Write-Host "Returning to main menu..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User exited VPN Certificate Menu" -Level INFO } continue } # Process the user's choice switch ($result.Choice) { "1" { Write-Host "Creating new root certificate..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User selected: Create new root certificate" -Level INFO } $rootCertName = "$($config.env)-$($config.project)-vpn-root" $clientCertName = "$($config.env)-$($config.project)-vpn-client" # Validate certificate names if (-not (Test-CertificateName -Name $rootCertName)) { Write-Host "Invalid root certificate name: $rootCertName" -ForegroundColor Red if ($canLog) { Write-Log -Message "Invalid root certificate name: $rootCertName" -Level ERROR } Pause continue } if (-not (Test-CertificateName -Name $clientCertName)) { Write-Host "Invalid client certificate name: $clientCertName" -ForegroundColor Red if ($canLog) { Write-Log -Message "Invalid client certificate name: $clientCertName" -Level ERROR } Pause continue } # Check if function exists if (Get-Command New-VpnRootCertificate -ErrorAction SilentlyContinue) { # Create a progress task for certificate creation $task = Start-ProgressTask -Activity "Creating Root Certificate" -TotalSteps 3 -ScriptBlock { # Step 1: Creating root certificate $syncHash.Status = "Creating root certificate..." $syncHash.CurrentStep = 1 # Step 2: Configuring certificate properties $syncHash.Status = "Configuring certificate properties..." $syncHash.CurrentStep = 2 # Step 3: Finalizing certificate $syncHash.Status = "Finalizing certificate..." $syncHash.CurrentStep = 3 try { # Call the actual function New-VpnRootCertificate -RootCertName $rootCertName -ClientCertName $clientCertName -CreateNewRoot return "Root certificate created successfully." } catch { return "Error creating root certificate: $_" } } $result = $task.Complete() if ($result -like "Error*") { Write-Host $result -ForegroundColor Red if ($canLog) { Write-Log -Message $result -Level ERROR } } else { Write-Host $result -ForegroundColor Green if ($canLog) { Write-Log -Message "Root certificate created: $rootCertName" -Level INFO } } } else { Write-Host "Function New-VpnRootCertificate not found. Make sure the required module is imported." -ForegroundColor Red if ($canLog) { Write-Log -Message "Function New-VpnRootCertificate not found" -Level ERROR } } Pause } "2" { Write-Host "Creating client certificate..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User selected: Create client certificate" -Level INFO } $rootCertName = "$($config.env)-$($config.project)-vpn-root" $clientCertName = Read-Host "Enter client certificate name" if ([string]::IsNullOrWhiteSpace($clientCertName)) { $clientCertName = "$($config.env)-$($config.project)-vpn-client" Write-Host "Using default client certificate name: $clientCertName" -ForegroundColor Yellow if ($canLog) { Write-Log -Message "Using default client certificate name: $clientCertName" -Level INFO } } # Validate certificate names if (-not (Test-CertificateName -Name $rootCertName)) { Write-Host "Invalid root certificate name: $rootCertName" -ForegroundColor Red if ($canLog) { Write-Log -Message "Invalid root certificate name: $rootCertName" -Level ERROR } Pause continue } if (-not (Test-CertificateName -Name $clientCertName)) { Write-Host "Invalid client certificate name: $clientCertName" -ForegroundColor Red if ($canLog) { Write-Log -Message "Invalid client certificate name: $clientCertName" -Level ERROR } Pause continue } # Check if function exists if (Get-Command New-VpnClientCertificate -ErrorAction SilentlyContinue) { # Create a progress task for client certificate creation $task = Start-ProgressTask -Activity "Creating Client Certificate" -TotalSteps 4 -ScriptBlock { # Step 1: Locating root certificate $syncHash.Status = "Locating root certificate..." $syncHash.CurrentStep = 1 # Step 2: Validating root certificate $syncHash.Status = "Validating root certificate..." $syncHash.CurrentStep = 2 # Step 3: Creating client certificate $syncHash.Status = "Creating client certificate..." $syncHash.CurrentStep = 3 # Step 4: Finalizing client certificate $syncHash.Status = "Finalizing client certificate..." $syncHash.CurrentStep = 4 try { # Call the actual function New-VpnClientCertificate -RootCertName $rootCertName -ClientCertName $clientCertName return "Client certificate created successfully." } catch { return "Error creating client certificate: $_" } } $result = $task.Complete() if ($result -like "Error*") { Write-Host $result -ForegroundColor Red if ($canLog) { Write-Log -Message $result -Level ERROR } } else { Write-Host $result -ForegroundColor Green if ($canLog) { Write-Log -Message "Client certificate created: $clientCertName" -Level INFO } } } else { Write-Host "Function New-VpnClientCertificate not found. Make sure the required module is imported." -ForegroundColor Red if ($canLog) { Write-Log -Message "Function New-VpnClientCertificate not found" -Level ERROR } } Pause } "3" { Write-Host "Adding client certificate to existing root..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User selected: Add client certificate to existing root" -Level INFO } $newClientName = Read-Host "Enter new client name" if ([string]::IsNullOrWhiteSpace($newClientName)) { Write-Host "Client name cannot be empty." -ForegroundColor Red if ($canLog) { Write-Log -Message "User provided empty client name" -Level Warning } Pause continue } # Validate client name if (-not (Test-CertificateName -Name $newClientName)) { Write-Host "Invalid client name: $newClientName" -ForegroundColor Red if ($canLog) { Write-Log -Message "Invalid client name: $newClientName" -Level ERROR } Pause continue } # Check if function exists if (Get-Command Add-AdditionalClientCertificate -ErrorAction SilentlyContinue) { # Create a progress task for adding client certificate $task = Start-ProgressTask -Activity "Adding Client Certificate" -TotalSteps 3 -ScriptBlock { # Step 1: Finding existing root certificate $syncHash.Status = "Finding existing root certificate..." $syncHash.CurrentStep = 1 # Step 2: Creating new client certificate $syncHash.Status = "Creating new client certificate..." $syncHash.CurrentStep = 2 # Step 3: Linking to root certificate $syncHash.Status = "Linking to root certificate..." $syncHash.CurrentStep = 3 try { # Call the actual function Add-AdditionalClientCertificate -NewClientName $newClientName return "Additional client certificate added successfully." } catch { return "Error adding additional client certificate: $_" } } $result = $task.Complete() if ($result -like "Error*") { Write-Host $result -ForegroundColor Red if ($canLog) { Write-Log -Message $result -Level ERROR } } else { Write-Host $result -ForegroundColor Green if ($canLog) { Write-Log -Message "Additional client certificate added: $newClientName" -Level INFO } } } else { Write-Host "Function Add-AdditionalClientCertificate not found. Make sure the required module is imported." -ForegroundColor Red if ($canLog) { Write-Log -Message "Function Add-AdditionalClientCertificate not found" -Level ERROR } } Pause } "4" { Write-Host "Uploading certificate to VPN Gateway..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User selected: Upload certificate to VPN Gateway" -Level INFO } $resourceGroup = "$($config.env)-$($config.loc)-rg-$($config.project)" $gatewayName = "$($config.env)-$($config.loc)-vpng-$($config.project)" $certName = "$($config.env)-$($config.project)-vpn-root" Write-Host "Select the Base64 encoded certificate file (.txt)..." -ForegroundColor Yellow $certFile = Read-Host "Enter path to certificate file" if ([string]::IsNullOrWhiteSpace($certFile)) { Write-Host "Certificate file path cannot be empty." -ForegroundColor Red if ($canLog) { Write-Log -Message "User provided empty certificate file path" -Level Warning } Pause continue } # Validate and read certificate file if (Test-Path $certFile) { # Check if it's a text file $extension = [System.IO.Path]::GetExtension($certFile) if ($extension -ne ".txt" -and $extension -ne ".cer" -and $extension -ne ".pem") { Write-Host "Warning: File does not have a standard certificate extension (.txt, .cer, .pem)" -ForegroundColor Yellow if ($canLog) { Write-Log -Message "Non-standard certificate file extension: $extension" -Level Warning } $confirmContinue = Get-UserConfirmation -Message "Continue anyway?" -DefaultYes:$false if (-not $confirmContinue) { Pause continue } } try { $certData = Get-Content $certFile -Raw -ErrorAction Stop # Basic validation that it looks like a Base64 certificate if (-not ($certData -match "-----BEGIN CERTIFICATE-----" -or $certData -match "^[A-Za-z0-9+/=]+$")) { Write-Host "Warning: File does not appear to contain a valid Base64 certificate" -ForegroundColor Yellow if ($canLog) { Write-Log -Message "File does not appear to contain a valid Base64 certificate" -Level Warning } $confirmContinue = Get-UserConfirmation -Message "Continue anyway?" -DefaultYes:$false if (-not $confirmContinue) { Pause continue } } # Check if function exists if (Get-Command Add-VpnGatewayCertificate -ErrorAction SilentlyContinue) { # Create a progress task for certificate upload $task = Start-ProgressTask -Activity "Uploading Certificate to VPN Gateway" -TotalSteps 4 -ScriptBlock { # Step 1: Validating certificate data $syncHash.Status = "Validating certificate data..." $syncHash.CurrentStep = 1 # Step 2: Connecting to Azure $syncHash.Status = "Connecting to Azure..." $syncHash.CurrentStep = 2 # Step 3: Locating VPN Gateway $syncHash.Status = "Locating VPN Gateway..." $syncHash.CurrentStep = 3 # Step 4: Uploading certificate $syncHash.Status = "Uploading certificate..." $syncHash.CurrentStep = 4 try { # Call the actual function Add-VpnGatewayCertificate -ResourceGroupName $resourceGroup -GatewayName $gatewayName -CertificateName $certName -CertificateData $certData return "Certificate uploaded to VPN Gateway successfully." } catch { return "Error uploading certificate to VPN Gateway: $_" } } $result = $task.Complete() if ($result -like "Error*") { Write-Host $result -ForegroundColor Red if ($canLog) { Write-Log -Message $result -Level ERROR } } else { Write-Host $result -ForegroundColor Green if ($canLog) { Write-Log -Message "Certificate uploaded to VPN Gateway: $gatewayName" -Level INFO } } } else { Write-Host "Function Add-VpnGatewayCertificate not found. Make sure the required module is imported." -ForegroundColor Red if ($canLog) { Write-Log -Message "Function Add-VpnGatewayCertificate not found" -Level ERROR } } } catch { Write-Host "Error reading certificate file: $_" -ForegroundColor Red if ($canLog) { Write-Log -Message "Error reading certificate file: $_" -Level ERROR } } } else { Write-Host "Certificate file not found: $certFile" -ForegroundColor Red if ($canLog) { Write-Log -Message "Certificate file not found: $certFile" -Level ERROR } } Pause } "5" { Write-Host "Listing all certificates..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User selected: List all certificates" -Level INFO } $rootCertName = "$($config.env)-$($config.project)-vpn-root" $clientCertPrefix = "$($config.env)-$($config.project)-vpn-client" # Check if certificate store exists if (-not (Test-Path -Path $certStorePath)) { Write-Host "Certificate store path not found: $certStorePath" -ForegroundColor Red if ($canLog) { Write-Log -Message "Certificate store path not found: $certStorePath" -Level ERROR } Pause continue } # Create a progress task for listing certificates $task = Start-ProgressTask -Activity "Listing Certificates" -TotalSteps 2 -ScriptBlock { # Step 1: Finding root certificates $syncHash.Status = "Finding root certificates..." $syncHash.CurrentStep = 1 $rootCerts = Get-ChildItem -Path $certStorePath | Where-Object { $_.Subject -like "CN=$rootCertName*" } # Step 2: Finding client certificates $syncHash.Status = "Finding client certificates..." $syncHash.CurrentStep = 2 $clientCerts = Get-ChildItem -Path $certStorePath | Where-Object { $_.Subject -like "CN=$clientCertPrefix*" } # Return the results return @{ RootCerts = $rootCerts ClientCerts = $clientCerts } } try { $results = $task.Complete() Write-Host "Root Certificates:" -ForegroundColor Yellow if ($results.RootCerts.Count -eq 0) { Write-Host "No root certificates found." -ForegroundColor Yellow if ($canLog) { Write-Log -Message "No root certificates found" -Level INFO } } else { $results.RootCerts | Format-Table -Property Subject, Thumbprint, NotBefore, NotAfter if ($canLog) { Write-Log -Message "Found $($results.RootCerts.Count) root certificates" -Level INFO } } Write-Host "Client Certificates:" -ForegroundColor Yellow if ($results.ClientCerts.Count -eq 0) { Write-Host "No client certificates found." -ForegroundColor Yellow if ($canLog) { Write-Log -Message "No client certificates found" -Level INFO } } else { $results.ClientCerts | Format-Table -Property Subject, Thumbprint, NotBefore, NotAfter if ($canLog) { Write-Log -Message "Found $($results.ClientCerts.Count) client certificates" -Level INFO } } } catch { Write-Host "Error listing certificates: $_" -ForegroundColor Red if ($canLog) { Write-Log -Message "Error listing certificates: $_" -Level ERROR } } Pause } "0" { Write-Host "Returning to main menu..." -ForegroundColor Cyan if ($canLog) { Write-Log -Message "User exited VPN Certificate Menu" -Level INFO } } default { Write-Host "Invalid option. Please try again." -ForegroundColor Red if ($canLog) { Write-Log -Message "User selected invalid option: $($result.Choice)" -Level Warning } Start-Sleep -Seconds 2 } } # Only show progress on first display $ShowProgress = $false } while (-not $exitMenu) } |