Public/Start-AtomicGUI.ps1

function Start-AtomicGUI {
    param (
        [Int] $port = 8487
    )
    # Install-Module UniversalDashboard if not already installed
    $UDcommunityInstalled = Get-InstalledModule -Name "UniversalDashboard.Community" -ErrorAction:SilentlyContinue
    $UDinstalled = Get-InstalledModule -Name "UniversalDashboard" -ErrorAction:SilentlyContinue
    if (-not $UDcommunityInstalled -and -not $UDinstalled) { 
        Write-Host "Installing UniversalDashboard.Community"
        Install-Module -Name UniversalDashboard.Community -Scope CurrentUser -Force
    }

    ############## Function Definitions Made Available to EndPoints
    function New-UDTextAreaX ($ID, $PlaceHolder) {
        New-UDElement -Tag div -Attributes @{class = "input-field col" } -Content {
            New-UDElement -Tag "textarea" -id  $ID -Attributes @{ class = "materialize-textarea ud-input" }
            New-UDElement -Tag Label -Attributes @{for = $ID } -Content { $PlaceHolder }
        }
    }

    function New-UDTextBoxX ($ID, $PlaceHolder) {
        New-UDElement -Tag div -Attributes @{class = "input-field col" } -Content {
            New-UDElement -Tag "input" -id $ID -Attributes @{ class = "ud-input"; type = "text" }
            New-UDElement -Tag Label -Attributes @{for = $ID } -Content { $PlaceHolder }
        }
    }

    $InputArgCards = @{ }
    function New-InputArgCard {
        $cardNumber = $InputArgCards.count + 1
        $newCard = New-UDCard -ID "InputArgCard$cardNumber" -Content {
            New-UDTextBoxX "InputArgCard$cardNumber-InputArgName" "Input Argument Name"
            New-UDTextAreaX "InputArgCard$cardNumber-InputArgDescription" "Description"        
            New-UDTextBoxX "InputArgCard$cardNumber-InputArgDefault" "Default Value" 
            New-UDLayout -columns 4 {
                New-UDSelect -ID "InputArgCard$cardNumber-InputArgType" -Label "Type" -Option {
                    New-UDSelectOption -Name "Path" -Value "path"
                    New-UDSelectOption -Name "String" -Value "string"
                    New-UDSelectOption -Name "Url" -Value "url"
                    New-UDSelectOption -Name "Integer" -Value "integer"
                }
            }
            New-UDButton -Text "Remove this Input Argument"  -OnClick (
                New-UDEndpoint -Endpoint {
                    Remove-UDElement -Id "InputArgCard$cardNumber"
                    $inputArgCards["InputArgCard$cardNumber"] = $true
                } -ArgumentList @($cardNumber, $inputArgCards)
            )
        }
        $InputArgCards.Add("InputArgCard$cardNumber", $false) | Out-Null
        $newCard
    }

    $depCards = @{ }
    function New-depCard {
        $cardNumber = $depCards.count + 1
        $newCard = New-UDCard -ID "depCard$cardNumber" -Content {
            New-UDTextBoxX "depCard$cardNumber-depDescription" "Prereq Description"
            New-UDTextAreaX "depCard$cardNumber-prereqCommand" "Check prereqs Command"        
            New-UDTextAreaX "depCard$cardNumber-getPrereqCommand" "Get Prereqs Command"        
            New-UDButton -Text "Remove this Prereq"  -OnClick (
                New-UDEndpoint -Endpoint {
                    Remove-UDElement -Id "depCard$cardNumber"
                    $depCards["depCard$cardNumber"] = $true
                } -ArgumentList @($cardNumber, $depCards)
            )
        }
        $depCards.Add("depCard$cardNumber", $false) | Out-Null
        $newCard
    }

    function New-UDSelectX ($Id, $Label) {
        New-UDSelect -Label $Label -Id $Id -Option {
            New-UDSelectOption -Name "PowerShell" -Value "PowerShell" -Selected
            New-UDSelectOption -Name "Command Prompt" -Value "CommandPrompt" 
            New-UDSelectOption -Name "Bash" -Value "Bash"
            New-UDSelectOption -Name "Sh" -Value "Sh"
        }
    }

    ############## End Function Definitions Made Available to EndPoints

    # EndpointInitialization defining which methods, modules, and variables will be available for use within an endpoint
    $ei = New-UDEndpointInitialization `
        -Function @("New-InputArgCard", "New-depCard", "New-UDTextAreaX", "New-UDTextBoxX", "New-UDSelectX") `
        -Variable @("InputArgCards", "depCards", "yaml") `
        -Module @("..\Invoke-AtomicRedTeam.psd1")

    ############## EndPoint (ep) Definitions: Dynamic code called to generate content for an element or perfrom onClick actions
    $BuildAndDisplayYamlScriptBlock = {   
        $testName = (Get-UDElement -Id atomicName).Attributes['value']
        $testDesc = (Get-UDElement -Id atomicDescription).Attributes['value']
        $platforms = @()
        if ((Get-UDElement -Id spWindows).Attributes['checked']) { $platforms += "Windows" }
        if ((Get-UDElement -Id spLinux).Attributes['checked']) { $platforms += "Linux" }
        if ((Get-UDElement -Id spMacOS).Attributes['checked']) { $platforms += "macOS" }
        $attackCommands = (Get-UDElement -Id attackCommands).Attributes['value']
        $executor = (Get-UDElement -Id executorSelector).Attributes['value']
        $elevationRequired = (Get-UDElement -Id elevationRequired).Attributes['checked']
        $cleanupCommands = (Get-UDElement -Id cleanupCommands).Attributes['value']
        if ("" -eq $executor) { $executor = "PowerShell" }
        # input args
        $inputArgs = @()
        $InputArgCards.GetEnumerator() | ForEach-Object {
            if ($_.Value -eq $false) {
                # this was not deleted
                $prefix = $_.key
                $InputArgName = (Get-UDElement -Id "$prefix-InputArgName").Attributes['value']
                $InputArgDescription = (Get-UDElement -Id "$prefix-InputArgDescription").Attributes['value']
                $InputArgDefault = (Get-UDElement -Id "$prefix-InputArgDefault").Attributes['value']
                $InputArgType = (Get-UDElement -Id "$prefix-InputArgType").Attributes['value']
                if ("" -eq $InputArgType) { $InputArgType = "String" }
                $NewInputArg = New-AtomicTestInputArgument -Name $InputArgName -Description $InputArgDescription -Type $InputArgType -Default $InputArgDefault -WarningVariable +warnings
                $inputArgs += $NewInputArg
            }
        }
        # dependencies
        $dependencies = @()
        $preReqEx = ""
        $depCards.GetEnumerator() | ForEach-Object {
            if ($_.Value -eq $false) {
                # a value of true means the card was deleted, so only add dependencies from non-deleted cards
                $prefix = $_.key
                $depDescription = (Get-UDElement -Id "$prefix-depDescription").Attributes['value']
                $prereqCommand = (Get-UDElement -Id "$prefix-prereqCommand").Attributes['value']
                $getPrereqCommand = (Get-UDElement -Id "$prefix-getPrereqCommand").Attributes['value']
                $preReqEx = (Get-UDElement -Id "preReqEx").Attributes['value']
                if ("" -eq $preReqEx) { $preReqEx = "PowerShell" }
                $NewDep = New-AtomicTestDependency -Description $depDescription -PrereqCommand $prereqCommand -GetPrereqCommand $getPrereqCommand -WarningVariable +warnings
                $dependencies += $NewDep
            }
        }
        $depParams = @{ }
        if ($dependencies.count -gt 0) {
            $depParams.add("DependencyExecutorType", $preReqEx)
            $depParams.add("Dependencies", $dependencies)
        }
        if (($cleanupCommands -ne "") -and ($null -ne $cleanupCommands)) { $depParams.add("ExecutorCleanupCommand", $cleanupCommands) }
        $depParams.add("ExecutorElevationRequired", $elevationRequired)

        $AtomicTest = New-AtomicTest -Name $testName -Description $testDesc -SupportedPlatforms $platforms -InputArguments $inputArgs -ExecutorType $executor -ExecutorCommand $attackCommands -WarningVariable +warnings @depParams                                           
        $yaml = ($AtomicTest | ConvertTo-Yaml) -replace "^", "- " -replace "`n", "`n "
        foreach ($warning in $warnings) { Show-UDToast $warning -BackgroundColor LightYellow -Duration 10000 }
        New-UDElement -ID yaml -Tag pre -Content { $yaml }
    } 

    $epYamlModal = New-UDEndpoint -Endpoint {
        Show-UDModal -Header { New-UDHeading -Size 3 -Text "Test Definition YAML" } -Content {
            new-udrow -endpoint $BuildAndDisplayYamlScriptBlock
            # Left arrow button (decrease indentation)
            New-UDButton -Icon arrow_circle_left -OnClick (
                New-UDEndpoint -Endpoint {
                    $yaml = (Get-UDElement -Id "yaml").Content[0]
                    if (-not $yaml.startsWith("- ")) {
                        Set-UDElement -Id "yaml" -Content {
                            $yaml -replace "^ ", "" -replace "`n ", "`n"
                        }
                    }
                }
            )
            # Right arrow button (increase indentation)
            New-UDButton -Icon arrow_circle_right -OnClick (
                New-UDEndpoint -Endpoint {
                    $yaml = (Get-UDElement -Id "yaml").Content[0]
                    Set-UDElement -Id "yaml" -Content {
                        $yaml -replace "^", " " -replace "`n", "`n "
                    }
                }
            )
            # Copy Yaml to clipboard
            New-UDButton -Text "Copy" -OnClick (
                New-UDEndpoint -Endpoint {
                    $yaml = (Get-UDElement -Id "yaml").Content[0]
                    Set-UDClipboard -Data $yaml
                    Show-UDToast -Message "Copied YAML to the Clipboard" -BackgroundColor YellowGreen 
                }
            )
        }
    }

    $epFillTestData = New-UDEndpoint -Endpoint {
        Add-UDElement -ParentId "inputCard" -Content { New-InputArgCard }        
        Add-UDElement -ParentId "depCard"   -Content { New-depCard }
        Start-Sleep 1
        Set-UDElement -Id atomicName -Attributes @{value = "My new atomic" }
        Set-UDElement -Id atomicDescription -Attributes @{value = "This is the atomic description" }
        Set-UDElement -Id attackCommands -Attributes @{value = "echo this`necho that" }
        Set-UDElement -Id cleanupCommands -Attributes @{value = "cleanup commands here`nand here..." }
        # InputArgs
        $cardNumber = 1
        Set-UDElement -Id "InputArgCard$cardNumber-InputArgName" -Attributes @{value = "input_arg_1" }
        Set-UDElement -Id "InputArgCard$cardNumber-InputArgDescription" -Attributes @{value = "InputArg1 description" }        
        Set-UDElement -Id "InputArgCard$cardNumber-InputArgDefault" -Attributes @{value = "this is the default value" }        
        # dependencies
        Set-UDElement -Id "depCard$cardNumber-depDescription" -Attributes @{value = "This file must exist" }
        Set-UDElement -Id "depCard$cardNumber-prereqCommand" -Attributes @{value = "if (this) then that" }       
        Set-UDElement -Id "depCard$cardNumber-getPrereqCommand" -Attributes @{value = "iwr" }       
        
    }
    ############## End EndPoint (ep) Definitions

    ############## Static Definitions
    $supportedPlatforms = New-UDLayout -Columns 4 {
        New-UDElement -Tag Label -Attributes @{ style = @{"font-size" = "15px" } } -Content { "Supported Platforms:" } 
        New-UDCheckbox -FilledIn -Label "Windows" -Checked -Id spWindows
        New-UDCheckbox -FilledIn -Label "Linux" -Id spLinux
        New-UDCheckbox -FilledIn -Label "macOS"-Id spMacOS
    }

    $executorRow = New-UDLayout -Columns 4 {
        New-UDSelectX 'executorSelector' "Executor for Attack Commands"
        New-UDCheckbox -ID elevationRequired -FilledIn -Label "Requires Elevation to Execute Successfully?" 
    }

    $genarateYamlButton = New-UDRow -Columns {
        New-UDColumn -Size 8 -Content { }
        New-UDColumn -Size 4 -Content {
            New-UDButton -Text "Generate Test Definition YAML" -OnClick ( $epYamlModal )
        }
    }

    ############## End Static Definitions

    ############## The Dashboard
    $idleTimeOut = New-TimeSpan -Minutes 10080
    $db = New-UDDashboard -Title "Atomic Test Creation" -IdleTimeout $idleTimeOut -EndpointInitialization $ei -Content {
        New-UDCard -Id "mainCard" -Content {
            New-UDCard -Content {
                New-UDTextBoxX 'atomicName' "Atomic Test Name"
                New-UDTextAreaX "atomicDescription" "Atomic Test Description"
                $supportedPlatforms
                New-UDTextAreaX "attackCommands" "Attack Commands"
                $executorRow
                New-UDTextAreaX "cleanupCommands" "Cleanup Commands (Optional)"
                $genarateYamlButton  
            }

            # input args
            New-UDCard -Id "inputCard" -Endpoint {
                New-UDButton -Text "Add Input Argument (Optional)" -OnClick (
                    New-UDEndpoint -Endpoint { Add-UDElement -ParentId "inputCard" -Content { New-InputArgCard } }
                )
            }

            # prereqs
            New-UDCard -Id "depCard" -Endpoint {
                New-UDLayout -columns 4 {
                    New-UDButton -Text "Add Prerequisite (Optional)" -OnClick (
                        New-UDEndpoint -Endpoint { Add-UDElement -ParentId "depCard" -Content { New-depCard } }
                    )
                    New-UDSelectX 'preReqEx' "Executor for Prereq Commands" 
                }
            }   
        }

        # button to fill form with test data for development purposes
        if ($false) { New-UDButton -Text "Fill Test Data" -OnClick ( $epFillTestData ) }
    }
    ############## End of the Dashboard

    Stop-AtomicGUI
    Start-UDDashboard -port $port -Dashboard $db -Name "AtomicGUI" -ListenAddress 127.0.0.1
    start-process http://localhost:$port
}

function Stop-AtomicGUI {
    Get-UDDashboard -Name 'AtomicGUI' | Stop-UDDashboard
    Write-Host "Stopped all AtomicGUI Dashboards"
}