Scripts/menu.ps1

$str = @'
<root>
    <Header>
        <Customer Name="Customer1">
            <clientExceptionNotes>Execption delivery address</clientExceptionNotes >
        </Customer>
    </Header>
    <Header>
        <Customer Name="Customer2">
            <clientExceptionNotes></clientExceptionNotes >
        </Customer>
    </Header>
</root>
'@


$script:menuItems = New-Object -TypeName System.Collections.ArrayList

$script:MenuOptions = [pscustomobject]@{
    MenuFillChar    = "*"
    MenuFillColor   = [consolecolor]::White
    Heading         = "" 
    HeadingColor    = [consolecolor]::White
    SubHeading      = ""
    SubHeadingColor = [consolecolor]::White
    FooterText      = ""
    FooterTextColor = [consolecolor]::White
    MenuItemColor   = [consolecolor]::White
    MenuNameColor   = [consolecolor]::White
    MaxWith         = 80
}

$script:Menus = New-Object -TypeName System.Collections.ArrayList

function New-Menu
{
[cmdletbinding()]
Param
(
    [string]
    $Name
    ,
    [string]
    $DisplayName
    ,
    [switch]
    $IsMainMenu
)

BEGIN
{
    $f = $MyInvocation.InvocationName
}

PROCESS
{
    $newMenu = [PSCustomObject]@{
        Id  =0
        Name = "$Name"
        DisplayName = "$DisplayName"
        IsMainMenu = $false
    }

    if ($PSBoundParameters.ContainsKey("IsMainMenu"))
    {
        $currentMainMenu = $script:Menus.Where({$_.IsMainMenu -eq $true})
        if ($currentMainMenu)
        {
            Write-Error -Message "$f - You can only have one Main Menu. Currently [$($currentMainMenu.Name)] is your main menu"    
            break        
        }
        else
        {
            $newMenu.IsMainMenu = $true
        }
    }

    $index = $script:Menus.Add($newMenu)
    $script:Menus[$index].Id = $index

}

END
{

}
}

function Get-Menu
{
[cmdletbinding()]
Param
(
    [switch]
    $MainMenu
)

BEGIN
{}

PROCESS
{
    if ($PSBoundParameters.ContainsKey("MainMenu"))
    {
        $script:Menus.Where({$_.IsMainMenu -eq $true})
    }
    else
    {
        $script:Menus
    }
}

END
{}
}

function Set-Menu
{
[cmdletbinding()]
Param
(
    [string]
    $Name
    ,
    [string]
    $DisplayName
    ,
    [switch]
    $IsMainMenu
)

BEGIN
{}

PROCESS
{
    $menu = $script:Menus.Where({$_.Name -eq "$Name"})
    if ($menu)
    {       
        if ($PSBoundParameters.ContainsKey("DisplayName"))
        {
            $script:Menus[$menu.id].DisplayName = "$DisplayName"
        }

        if ($PSBoundParameters.ContainsKey("IsMainMenu"))
        {
            $script:Menus[$menu.id].IsMainMenu = $true
            # FIXME need to check if we have a main menu
        }
        else
        {
            $script:Menus[$menu.id].IsMainMenu = $false
        }
    }
}

END
{}
}


function New-MenuItem
{
[cmdletbinding()]
Param
(
    [string]
    $Name
    ,
    [string]
    $DisplayName
    ,
    [string]
    $Description
    ,
    [scriptblock]
    $ActionScriptblock
    ,
    [bool]
    $ConfirmBeforeInvoke = $true
    ,
    [int]
    $ParentMenuID
)

BEGIN 
{
    $f = $MyInvocation.InvocationName
    Write-Verbose -Message "$f - START"
}

PROCESS
{
    $menuItem = [PSCustomObject]@{
        Id = 0
        Name = "$Name"
        DisplayName = "$DisplayName"
        Description = "$Description"
        ActionScriptblock = $ActionScriptblock
        ConfirmBeforeInvoke = $ConfirmBeforeInvoke
        ParentMenu = 0
    }

    $nameConflict = $null
    $nameConflict = $script:menuItems.Where({$_.Name -eq "$Name"})

    if ($script:Menus.count -eq 0)
    {
        New-Menu -Name MainMenu -DisplayName "Main Menu" -IsMainMenu
    }

    if (-not $nameConflict)
    {
        $MainMenu = Get-Menu -MainMenu

        if (-not $MainMenu)
        {
            Write-Error -Message "$f - Unable to find Main Menu"
            break
        }

        $menuItem.ParentMenu = $MainMenu.Id

        Write-Verbose -Message "$f - Adding menuItem [$Name]"

        $insertedId = $script:menuItems.Add($menuItem)        
        $script:menuItems[$insertedId].Id = $insertedId
    }
    else
    {
        Write-Warning -Message "$f - Menuitem with [$Name] already exists"
    }
}

END
{
    Write-Verbose -Message "$f- END"
}
}

function Set-MenuItem
{
[cmdletbinding()]
Param
(
    [string]
    $Name
    ,
    [string]
    $DisplayName
    ,
    [string]
    $Description
    ,
    [scriptblock]
    $ActionScriptblock
    ,
    [bool]
    $ConfirmBeforeInvoke
)

BEGIN
{
    $f = $MyInvocation.InvocationName
    Write-Verbose -Message "$f - START"
}

PROCESS
{
    $menuItem = $script:menuItems.GetEnumerator().Where({$_.Name -eq "$Name"})
        
    if(-not $menuItem)
    {
        Write-Error -Message "$f - Unable to find menuItem with name [$Name]"
        break
    }

    $menuItem = $script:menuItems[$menuItem.id]

    if($PSBoundParameters.ContainsKey("DisplayName"))
    {
        $menuItem.DisplayName = "$DisplayName"
    }
    
    if($PSBoundParameters.ContainsKey("Description"))
    {
        $menuItem.Description = "$Description"
    }
    
    if($PSBoundParameters.ContainsKey("ActionScriptblock"))
    {
        $menuItem.ActionScriptblock = $ActionScriptblock
    }

    if($PSBoundParameters.ContainsKey("ConfirmBeforeInvoke"))
    {
        $menuItem.ConfirmBeforeInvoke = $ConfirmBeforeInvoke
    }

}

END
{
    Write-Verbose -Message "$f- END"
}
}

function Get-MenuItem
{
[cmdletbinding(DefaultParameterSetName='none')]
Param
(
    [Parameter(ParameterSetName="ByName")]
    [string[]]
    $Name = "*"
    ,
    [Parameter(ValueFromPipeline,ParameterSetName="ById")]
    [int[]]
    $Id 
)

BEGIN
{
    $f = $MyInvocation.InvocationName
    Write-Verbose -Message "$f - START"
}

PROCESS
{
    if (-not $PSBoundParameters.ContainsKey("Name") -and -not $PSBoundParameters.ContainsKey("Id"))
    {
        $script:menuItems
    }

    if ($PSBoundParameters.ContainsKey("Name"))
    {
        foreach ($menuName in $Name)
        {
            $script:menuItems.Where({$_.Name -like $Name})
        }
    }

    if ($PSBoundParameters.ContainsKey("Id"))
    {
        $script:menuItems.Where({$_.Id -in $id})
    }
}

END
{
    Write-Verbose -Message "$f- END"
}
}

function Show-Menu
{
[cmdletbinding()]
Param
(
    [int]
    $InvokeItem
    ,
    [switch]
    $Force
    ,
    [int]
    $MenuID
)

BEGIN
{
    $f = $MyInvocation.InvocationName
    Write-Verbose -Message "$f - START"

    $mainMenu = Get-Menu -MainMenu

    if (-not $mainMenu)
    {
        Write-Warning -Message "Please add a menu first"
        break
    }
}

PROCESS
{
    if ($PSBoundParameters.ContainsKey("InvokeItem"))
    {
        $menuSelected = $script:menuItems.Where({$_.ID -eq $InvokeItem})

        if($menuSelected)
        {
            if ($menuSelected.ConfirmBeforeInvoke)
            {
                if (-not $Force)
                {                    
                    $Continue = Read-Host -Prompt "Are you sure you want to execute [$($menuSelected.Name)] Y/N?"
                    If ($Continue -ne "y")
                    {
                        Write-Host -Object "Execution aborted" -ForegroundColor DarkYellow
                        break
                    }
                }                
            }

            if ($menuSelected.ActionScriptblock)
            {
                Write-Host -Object "Invoking [$($menuSelected.Name)]" -ForegroundColor DarkYellow
                $menuSelected.ActionScriptblock.Invoke()
                Write-Host -Object "Invoke DONE!" -ForegroundColor DarkYellow
                break
            }            
        }                     
    }
}

END
{
    $menuLines = New-Object -TypeName System.Collections.ArrayList
    $maxWith = $script:MenuOptions.MaxWith

    function Get-MenuLine
    {
    Param
    (
        [string]
        $Text
        ,
        [consolecolor]
        $Color = [System.ConsoleColor]::White
    )
        [pscustomobject]@{
            Text = "$Text"
            Color = $color
        }
    }

    if ($PSBoundParameters.ContainsKey("MenuID"))
    {
        $mainMenu = Get-Menu | where Id -eq $MenuID
        if (-not $mainMenu)
        {
            Write-Error -Exception "$f - Could not find menu with ID [$MenuID]"
        }
    }
    
    $lineFeed = ""#[System.Environment]::NewLine
    $menuString = ($script:MenuOptions.MenuFillChar * ($maxWith - 2))
    $null = $menuLines.Add((Get-MenuLine -Text $menuString -color $script:MenuOptions.MenuFillColor))

    $menuNameLength = $mainMenu.DisplayName.length
    $menuNameBlanks = (($maxWith - 2) - $menuNameLength) / 2
    $menuName = " " * $menuNameBlanks + $mainMenu.DisplayName
    $menuName += " " * (($maxWith - 1) - $menuName.Length - 1)
    $null = $menuLines.Add((Get-MenuLine -Text $menuName -Color $script:MenuOptions.MenuNameColor))


    #$menuEmptyLine = $script:MenuOptions.MenuFillChar + (" " * 78) + $script:MenuOptions.MenuFillChar #+ $lineFeed
    $menuEmptyLine = (" " * ($maxWith - 2)) #+ $script:MenuOptions.MenuFillChar
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    $menuHeadingLength = $script:MenuOptions.Heading.Length
    $menuHeadingBlanks = (($maxWith - 2) - $menuHeadingLength) / 2
    
    $menuHeading = (" " * $menuHeadingBlanks) + $script:MenuOptions.Heading #+ (" " * $menuHeadingBlanks) + $script:MenuOptions.MenuFillChar #+ $lineFeed
    $menuHeading += (" " * (($maxWith - 1) - $menuHeading.length - 1))
    $null = $menuLines.Add((Get-MenuLine -Text $menuHeading -color $script:MenuOptions.HeadingColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    $menuSubHeadingLength = $script:MenuOptions.SubHeading.Length
    $menuSubHeadingBlanks = (($maxWith - 2) - $menuSubHeadingLength) / 2    
    $menuSubHeading = (" " * $menuSubHeadingBlanks) + $script:MenuOptions.SubHeading #+ (" " * $menuSubHeadingBlanks) + $script:MenuOptions.MenuFillChar# + $lineFeed
    $menuSubHeading += (" " * (($maxWith - 1) - $menuSubHeading.length - 1))
    $null = $menuLines.Add((Get-MenuLine -Text $menuSubHeading -color $script:MenuOptions.SubHeadingColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    $menuString = ($script:MenuOptions.MenuFillChar * ($maxWith - 2))
    $null = $menuLines.Add((Get-MenuLine -Text $menuString -color $script:MenuOptions.MenuFillColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    foreach($item in $script:menuItems)
    {
        $menuItemLength = $item.Displayname.length
        #$menuItemBlanks = (70 - $menuItemLength) / 2
        #$menuItem = $script:MenuOptions.MenuFillChar + (" " * $menuItemBlanks) + $item.Displayname + (" " * $menuItemBlanks) + $script:MenuOptions.MenuFillChar #+ $lineFeed
        $menuItem = " " + "$($item.Id). $($item.DisplayName)"
        $menuitem += " " * (($maxWith - 1) - $menuItem.length - 1)
        $null = $menuLines.Add((Get-MenuLine -Text $menuItem -Color $script:MenuOptions.MenuItemColor))
    }

    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    $menuFooterLength = $script:MenuOptions.FooterText.Length
    $menuFooterBlanks = (($maxWith - 2) - $menuFooterLength) / 2
    $menuFooter = (" " * $menuFooterBlanks) + $script:MenuOptions.FooterText #+ (" " * $menuFooterBlanks)
    $menuFooter += (" " * (($maxWith - 1) - $menuFooter.length - 1))
    $null = $menuLines.Add((Get-MenuLine -Text $menuFooter -color $script:MenuOptions.FooterTextColor))
    $null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    $menuEndString = ($script:MenuOptions.MenuFillChar * ($maxWith - 2)) + $lineFeed
    $null = $menuLines.Add((Get-MenuLine -Text $menuEndString -color $script:MenuOptions.MenuFillColor))
    #$null = $menuLines.Add((Get-MenuLine -Text $menuEmptyLine -color $script:MenuOptions.MenuFillColor))

    foreach ($line in $menuLines)
    {
        Write-Host -Object $script:MenuOptions.MenuFillChar -ForegroundColor $script:MenuOptions.MenuFillColor -NoNewline
        Write-Host -Object $line.Text -ForegroundColor $line.Color -NoNewline
        Write-Host -Object $script:MenuOptions.MenuFillChar -ForegroundColor $script:MenuOptions.MenuFillColor
    }
    $userSelection = (Read-Host -Prompt "Please enter number to execute action")

    try
    {
        $actionItemSelectionIndex = [int]$userSelection
    }
    catch
    {
        Write-Host -Object "Menuitem not found [$userSelection]" -ForegroundColor DarkYellow
        break
    }

    $menuSelected = $script:menuItems.Where({$_.ID -eq $actionItemSelectionIndex})

    if ($menuSelected)
    {
        if ($menuSelected.ConfirmBeforeInvoke)
        {
            $Continue = Read-Host -Prompt "Are you sure you want to execute [$($menuSelected.Name)] Y/N?"
            If ($Continue -ne "y")
            {
                Write-Host -Object "Execution aborted" -ForegroundColor DarkYellow
                break
            }
        }
        if ($menuSelected.ActionScriptblock)
        {
            Write-Host -Object "Invoking [$($menuSelected.Name)]" -ForegroundColor DarkYellow
            $menuSelected.ActionScriptblock.Invoke()
            Write-Host -Object "Invoke DONE!" -ForegroundColor DarkYellow
        }
    }
    else
    {
        Write-Host -Object "Menuitem not found [$userSelection]" -ForegroundColor DarkYellow
    }
    Write-Verbose -Message "$f- END"
}
}

function Test-Invoke1
{
    $F = $MyInvocation.InvocationName
    write-verbose -Message "$f - START" -Verbose
}



function Set-MenuOption
{
[cmdletbinding()]
Param
(
    [string]
    $MenuFillChar = "*"
    ,
    [ConsoleColor]
    $MenuFillColor = [consolecolor]::white
    ,
    [string]
    $Heading = "[Heading not set]"
    ,
    [ConsoleColor]
    $HeadingColor = [consolecolor]::white
    ,
    [string]
    $SubHeading = "[SubHeading not set ]"
    ,
    [ConsoleColor]
    $SubHeadingColor = [consolecolor]::white
    ,
    [string]
    $FooterText = "[FooterText not set]"
    ,
    [ConsoleColor]
    $FooterTextColor = [consolecolor]::white
    ,
    [consolecolor]
    $MenuItemColor = [consolecolor]::white
    ,
    [consolecolor]
    $MenuNameColor = [consolecolor]::white
    ,
    [int]
    $MaxWith = 80
)

BEGIN
{
    $f = $MyInvocation.InvocationName
    Write-Verbose -Message "$f - START"

    foreach ($key in $PSBoundParameters.Keys)
    {
        $script:MenuOptions.$key = $PSBoundParameters.$key
    }

    if([string]::IsNullOrEmpty($script:MenuOptions.FooterText))
    {
        
        $script:MenuOptions.FooterText = "$(Get-date) - Running as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
    }
}

END
{
    Write-Verbose -Message "$f- END"
}
}

function Get-MenuOption
{
[cmdletbinding()]
Param
()

    $script:MenuOptions
}

$function = "" ; Remove-Module -Name tmp -ErrorAction SilentlyContinue
foreach($action in $script:menuItems)
{
    $funcs = New-Object -TypeName System.Collections.ArrayList
    
    $function += "function $($action.id) {$($action.ActionScriptblock.ToString())};"
    Write-Verbose $function -Verbose
    
}

$sb = [scriptblock]::Create($function)
New-Module -Name tmp -ScriptBlock $sb | Import-Module