PowerShellUniversal.Apps.AutomatedLab.psm1

function New-UDAutomatedLabApp {
    <#
    .SYNOPSIS
    Creates a new AutomatedLab management app.
    
    .DESCRIPTION
    Creates a new AutomatedLab management app for PowerShell Universal.
    #>


    # Load all page scripts
    Write-Output $PSScriptRoot
    $DashboardPath = Join-Path $PSScriptRoot -ChildPath 'dashboards\AutomatedLab'
    Get-ChildItem (Join-Path $DashboardPath -ChildPath 'pages') -Recurse -Filter *.ps1 | Foreach-Object {
        . $_.FullName
    }

    # Execute the main app script and return the app
    $AppScript = Join-Path $DashboardPath 'AutomatedLab.ps1'
    & $AppScript
}

function Get-PSULabConfiguration {
    <#
    .SYNOPSIS
    Returns lab configuration objects
    
    .DESCRIPTION
    Returns all lab configurations when no Name is specified, or a specific configuration when Name is provided.
       
    .PARAMETER Name
    The name of the specific configuration to return. If not specified, all configurations are returned.
    
    .EXAMPLE
    Get-AllLabConfigurations
    
    Returns all available lab configurations.
    
    .EXAMPLE
    Get-AllLabConfigurations -Name Example
    
    Returns the specific configuration named 'Example'.
    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

                $configPath = Join-Path $env:LocalAppData -ChildPath "powershell\$env:USERNAME"
                if (Test-Path $configPath) {
                    Get-ChildItem -Path $configPath -Directory | Where-Object {
                        $_.Name -like "$wordToComplete*"
                    } | ForEach-Object {
                        [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Name)
                    }
                }
            })]
        [String]
        $Name
    )

    end {
        if ($Name) {
            $config = Import-Configuration -Name $Name -CompanyName $env:USERNAME
            $config['Lab'] = $Name
            $config | Split-Configuration
        }
        else {
            $configPath = Join-Path $env:LocalAppData -ChildPath "powershell\$env:USERNAME"
            if (Test-Path $configPath) {
                Get-ChildItem -Path $configPath -Directory | ForEach-Object {
                    $config = Import-Configuration -Name $_.Name -CompanyName $env:USERNAME
                    $config.Add('Lab', $_.Name)
                    $config | Split-Configuration
                }
            }
        }
    }
}

function Split-Configuration {
    <#
    .SYNOPSIS
    Expands parameter hashtable from a configuration and adds indiviual properties to the parent object
    
    .PARAMETER InputObject
    The configuration hashtable to split
    
    .EXAMPLE
    Split-Configuration -InputObject $configuration

    .EXAMPLE
    Get-LabConfiguration | Split-Configuration

    .EXAMPLE
    Get-PSULabConfiguration -Name Demo | Split-Configuration
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Hashtable]
        $InputObject
    )

    process {
        $newHash = @{}

        $newHash.Definition = $InputObject['Definition']
        $newHash.Lab = $InputObject['Lab']
        $parameters = $InputObject['Parameters']
        $parameters.GetEnumerator() | ForEach-Object {
            if ($_.Key -ne 'Name') {
                $newHash.Add($_.Key, $_.Value)
            }
        }

        [PSCustomObject]$newHash
    }
}

function Get-PSULabInfo {
    <#
    .SYNOPSIS
    Gets virtual machine information for a specific lab using PowerShell Universal context
    
    .DESCRIPTION
    This function imports an AutomatedLab by name and returns information about each machine
    including the name, processor count, memory, and operating system. This version is optimized
    for use within PowerShell Universal dashboards.
    
    .PARAMETER LabName
    The name of the lab to import and analyze.
    
    .EXAMPLE
    Get-PSULabInfo -LabName "MyTestLab"
    
    .NOTES
    This function requires the AutomatedLab module to be installed and available.
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$LabName
    )
    
    try {
        Write-Verbose "Importing lab: $LabName"
        # Import the lab without validation to speed up the process
        Import-Lab -Name $LabName -NoValidation -NoDisplay | Out-Null

        $machines = Get-LabVM
        $status = Get-LabVMStatus -AsHashTable
        
        if (-not $machines) {
            Write-Warning "No machines found in lab '$LabName'"
            return @()
        }
        
        $labInfo = foreach ($machine in $machines) {
            [PSCustomObject]@{
                Name            = $machine.Name
                ProcessorCount  = $machine.Processors
                Memory          = $machine.Memory
                OperatingSystem = $machine.OperatingSystem.OperatingSystemName
                MemoryGB        = [Math]::Round($machine.Memory / 1GB, 2)
                Status          = $status[$machine.Name]
            }
        }
        
        Write-Verbose "Retrieved information for $($labInfo.Count) machines"
        return $labInfo
    }
    catch {
        Write-Error "Failed to import lab '$LabName': $($_.Exception.Message)"
        return @()
    }
}

function Get-LabInfo {
    <#
    .SYNOPSIS
    Imports a lab by name and returns basic information about the lab machines.
    
    .DESCRIPTION
    This function imports an AutomatedLab by name and returns information about each machine
    including the name, processor count, memory, and operating system.
    
    .PARAMETER LabName
    The name of the lab to import and analyze.
    
    .EXAMPLE
    Get-LabInfo -LabName "MyTestLab"
    
    .EXAMPLE
    Get-LabInfo "MyTestLab" | Format-Table -AutoSize
    
    .NOTES
    This function requires the AutomatedLab module to be installed and available.
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                try {
                    $availableLabs = Get-Lab -List
                    $availableLabs | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
                        [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
                    }
                }
                catch {
                    @()
                }
            })]
        [string]$LabName
    )
    
    try {

        Write-Verbose "Importing lab: $LabName"
        # Nothing nulls this output. Resistence is futile. Thankfully this usecase will never see it so whatever.
        Import-Lab -Name $LabName -NoValidation | Out-Null

        $machines = Get-LabVM
        $status = Get-LabVMStatus -AsHashTable
        if (-not $machines) {
            Write-Warning "No machines found in lab '$LabName'"
            return
        }
        
        $labInfo = foreach ($machine in $machines) {
            [PSCustomObject]@{
                Name            = $machine.Name
                ProcessorCount  = $machine.Processors
                Memory          = $machine.Memory
                OperatingSystem = $machine.OperatingSystem.OperatingSystemName
                MemoryGB        = [Math]::Round($machine.Memory / 1GB, 2)
                Status          = $status[$machine.Name]
            }
        }
        
        Write-Verbose "Retrieved information for $($labInfo.Count) machines"
        return $labInfo
    }
    catch {
        Write-Error "Failed to import lab '$LabName': $($_.Exception.Message)"
        throw
    }
}

function New-AutomatedLabDefinitionScript {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]
        $LabData
    )
    
    process {
        $script = @"
# AutomatedLab Definition: $($LabData.LabName)
# Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')

Import-Module AutomatedLab

New-LabDefinition -Name '$($LabData.LabName)' -DefaultVirtualizationEngine HyperV

"@


        # Add virtual switches
        foreach ($network in $LabData.Networks) {
            switch ($network.SwitchType) {
                'DefaultSwitch' {
                    $script += "Add-LabVirtualNetworkDefinition -Name 'Default Switch'`n"
                }
                'Internal' {
                    $script += "Add-LabVirtualNetworkDefinition -Name '$($network.Name)' -AddressSpace '$($network.Subnet)'"
                    if ($network.Gateway) { 
                        $script += " -HyperVProperties @{ SwitchType = 'Internal' }" 
                    }
                    $script += "`n"
                }
                'External' {
                    $script += "Add-LabVirtualNetworkDefinition -Name '$($network.Name)' -HyperVProperties @{ SwitchType = 'External'; AdapterName = '$($network.PhysicalAdapter)' }`n"
                }
            }
        }

        $script += "`n"

    # Add VMs
    foreach ($vm in $LabData.VMs) {
        $script += "Add-LabMachineDefinition -Name '$($vm.Name)' -OperatingSystem '$($vm.OS)' -Memory $($vm.RAM)GB -Processors $($vm.CPU)"
        
        if ($vm.NetworkAdapters -and $vm.NetworkAdapters.Count -gt 0) {
            $adapters = $vm.NetworkAdapters | ForEach-Object {
                $adapterDef = "New-LabNetworkAdapterDefinition -VirtualSwitch '$($_.VirtualSwitch)'"
                if ($_.InterfaceName) {
                    $adapterDef += " -InterfaceName '$($_.InterfaceName)'"
                }
                if ($_.IpAddress -and ![string]::IsNullOrEmpty($_.IpAddress)) {
                    $adapterDef += " -IpAddress '$($_.IpAddress)'"
                }
                if ($_.UseDhcp -eq $false -and $_.IpAddress) {
                    # Static IP configuration - don't add UseDhcp parameter as it defaults to false when IpAddress is specified
                } else {
                    # DHCP configuration
                    $adapterDef += " -UseDhcp"
                }
                $adapterDef
            }
            $script += " -NetworkAdapter @($($adapters -join ', '))"
        }
        $script += "`n"
    }        $script += @"

Install-Lab

"@


        return $script
    }
}