PowerShell function for menu creation revisited

Sometimes when creating blog posts you just get excited to publish a post as soon as it’s finished, but then you realize you can do something better. So yesterday I published a post on how to use a function in PowerShell to create an interactive dynamic menu but in hindsight it could have been more “generic” – so today I’m back with a new post on how you can create that generic function by providing the query in a separate string instead as part of the function itself. This allows you to merely change the query (the input for creating the menu) without having to change anything inside the function. Now it’s a “real” function! ūüôā

I’ve added two mandatory parameters to the function, the first is the query to retrieve data and the second parameter is the task that you’d like to perform on the selected object. I’m calling the parameters -ListItem and -GetItem but the actual query is set in the $MyQuery and $MyTask string and they are then passed on to the function to perform the task. So if I’d like to get a list of VMs I just change the $MyQuery to something like “(Get-VM | Sort-Object)” and if I’d like to get a list of Datastores I set $MyQuery = “(Get-Datastore | Where {$_.Type -eq ‘VMFS’} | Sort-Object -Descending FreeSpaceGB)”. Once the list is presented and you select an object you then pass on what’s going to happen with it with the $MyTask – so if I want to retrieve a specific VM I set it to “Get-VM” but it could be anything (like Remove-VM but there’s no warning so be careful!)

Get a list of VMs
Get a list of VMFS datastores and sort them based on free space

Calling the function is then done using this syntax:
Set-Menu -ListItem $MyQuery -GetItem $MyTask

$VIServer = "vcsa.mydomain.com"
$VIUsername = "administrator@vsphere.local"
$VIPassword = "MyPassword"

Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue

$MyQuery = (Get-Datastore | Where {$_.Type -eq 'VMFS'} |Sort-Object -Descending FreeSpaceGB)
$MyTask = "Get-Datastore"

Function Set-Menu {
    Param (


    Try {
    $menu = @{}
    For ($i=1;$i -le $ListItem.count; $i++)
        { Write-Host "$i. $($ListItem[$i-1].Name)"
        $menu.Add($i,($ListItem[$i-1].Name)) }
    [int]$ans = if(($ans = Read-Host "Select item or press enter to accept default value [1]") -eq '') {"1"} else {$ans}
    $Selection = $menu.Item($ans); Invoke-Expression $($GetItem +  " " + $Selection)

    Catch {
    Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..."


Set-Menu -ListItem $MyQuery -GetItem $MyTask

Disconnect-VIServer $VIServer -Confirm:$false
Set-Menu waiting for user input
Set-Menu has executed the task defined in $MyTask

The way you can use the function is up to you but I use it in William Lams vghetto-vsphere-automated-lab-deployment script

Simplify your life with dynamic menus in PowerShell

If you are anything like me you’re setting up test or demo environments and then tearing them down a few hours later when you’re done with what ever testing you were doing. While setting up a VMware vSphere test environment is super easy using powershell/powercli (if you haven’t already visited William Lams web page, I highly recommend it and while you’re there grab the PowerShell scripts to deploy vSphere/vSAN/NSX environments. Kudos to William for everything you do for the vCommunity!!), I use his powershell scripts almost on a daily basis!

Now while setting up a single lab environment is usually not a big problem using the scripts provided by Willam but when you start setting up multiple labs (I typically have like 3 or 4 labs set up on any given time for different purposes) you might run out of some resources statically configured in the script, for instance your datastore configured doesn’t have enough capacity. So why not enhance the experience with dynamic selection of for instance datastore (or in my example: DatastoreCluster). Turns out it’s really easy to build an interactive dynamic menu of your datastoresclusters and use the menu to select where to install the lab environment.

I’m using a function to call the datastoreclusters I can use. I have a basic error handling included that you might need to extend. The code below includes 2 examples, the first function is calling available DatastoreClusters from a vCenter server and ordering them based on available free space and the second example gets a list of port groups available on my distributed switch. I’ve also assigned a default selection/value for faster deployments, just press enter to select the default value.

$VIServer = "vcsa.mydomain.com"
$VIUsername = "administrator@vsphere.local"
$VIPassword = "MyPassword"

Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue

Function Show-DatastoreClusters ($Title = 'My Datastores'){
Try {
$AvailDatastores = (Get-Datastore | Where {$_.Type -eq "VMFS"} |Sort-Object -Descending FreeSpaceGB)
$menuds = @{}
For ($i=1;$i -le $AvailDatastores.count; $i++)
{ Write-Host "$i. $($AvailDatastores[$i-1].Name) - $([math]::Round($AvailDatastores[$i-1].FreespaceGB,1)) GB free space"
$menuds.Add($i,($AvailDatastores[$i-1].Name)) }
[int]$ansds = if(($ansds = Read-Host "Select Datastore or press enter to accept default value [1]") -eq ''){"1"} else {$ansds}
$DatastoreSelection = $menuds.Item($ansds) ; Get-Datastore $DatastoreSelection
Catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." Start-Sleep 2 }

Function Show-PortGroups ($Title = 'My Portgroups'){
Try {
$AvailPortgroups = (Get-VDSwitch -Name "DSwitch" | Get-VDPortgroup)
$menupg = @{}
For ($i=1;$i -le $AvailPortgroups.count; $i++)
{ Write-Host "$i. $($AvailPortgroups[$i-1].Name)"
$menupg.Add($i,($AvailPortgroups[$i-1].Name)) }
[int]$anspg = if(($anspg = Read-Host "Select portgroup or press enter to accept default value [7]") -eq ''){"7"}else{$anspg}
$PortgroupSelection = $menupg.Item($anspg) ; Get-VDPortgroup $PortgroupSelection
Catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." Start-Sleep 2 }
Write-Host `n
$VMDatastore = Show-DatastoreClusters
Write-Host `n
$VMNetwork = Show-Portgroups
Write-Host `n

Disconnect-VIServer $VIServer -Confirm:$false

The function above can then be included in the script from William Lam. When calling the function the output will look something like this (the menus being produced are highlighted in red):

Function to build dynamic menu

If you’d like you can add some extra granularity when enumerating the datastore, expand the “Where” statement to just get the datastores with a specific name:

{$_.Type -eq "VMFS" -and $_.Name -like "VMFS-ESXi7*"}

Once you got you script fetching the desired data you can add the function to any script, I’ve added it to William’s script for some added flexibility.

PowerShell for the win!

Who doesn’t love PowerShell and PoweCLI? I use it to automate as much as I can. Building demo environments, upgrading stuff or just playing around. The list is just a few examples of available modules from the Microsoft PowerShell Gallery, you can spend hours exploring interesting modules there. The list below is mostly a reminder for myself but feel free to explore!





















Log Insight






Pure Storage



Amazon Web Services
















The power of PowerCLI ‚Äď Labautomation

Har du någonsin behövt skapa identiska labbmiljöer till flera deltagare, i till exempel utbildningssyfte eller för en workshop? Då vet du att det inte bara är att högerklicka på en template och välja deploy from template. Det behövs kanske flera olika templates som ska rullas ut, separata virtuella switchar och portgrupper behöver skapas för varje deltagare osv. Det blir fort en väldigt stor administrativ belastning. Det finns lite olika sätta att lösa problemet, exempelvis via vApps i vCloud Director.

Men om du nu inte har vCloud Director i din miljö, du kanske bara har en eller två hostar i miljön. Vad göra? Då kommer VMware PowerCLI till din räddning! PowerCLI är VMwares utökning av Microsoft Powershell som låter dig scripta i stort sätt allt du kan tänkas vilja göra. Du behöver en windows maskin samt hämta PowerCLI som finns här.

I mitt dagliga jobb beh√∂ver jag ofta s√§tta upp f√§rdiga milj√∂er som inneh√•ller mellan 5-10 virtuella maskiner som jag¬†har tillg√§ngliga¬†som templates. F√∂r att enkelt kunna skapa unika labbmilj√∂er f√∂r flera personer/deltagare s√• har jag skapat ett skript som automatiserar¬†utrullningen √•t mig. Det enda jag beh√∂ver g√∂ra √§r att ange hur m√•nga kit jag vill skapa samt var jag vill skapa dom (p√• vilken host och vilket datastore). Sen har jag lagt till m√∂jligheten att ange vilket ‚ÄĚstartnummer‚ÄĚ jag vill anv√§nda. Exempelvis om jag vill skapa 10 kit men det datastore jag angett finns det bara plats f√∂r 5 stycken kit. D√• kan jag dela upp 5 kit p√• datastore1 med startummer 1 d√§refter skapar jag ytterligare 5¬†kit p√• datastore 2 fast med startnummer 6. D√• har jag 10 kit med en logisk namnstandard ‚Äď fr√•n 1 till 10.

En kort förklaring vad scriptet gör (förutom att skapa en meny där man kan välja lite olika alternativ som att skapa vApps, starta / stänga vApps osv):
1. Skapa en vApp för varje deltagare
2. Rulla ut VMs från templates till vAppen
3. Skapa isolerat nätverk kopplat till varje deltagare
4. √Ąndra s√§kerhetsniv√•n p√• utvalda portgrupper till promiscous mode eftersom det √§r nested ESXi
5. Koppla de virtuella maskinerna i en vApp till studentens egna nätverk
6. √Ąndra s√• att¬†de virtuella maskinerna st√§ngs ner gracefully n√§r¬†vAppen st√§ngs (fr√•n Power off till Guest Shutdown)

När kiten är skapade kan jag via skriptet starta samtliga kit eller stänga ner dom om jag vill det. I mitt exempel har jag även en VM som är tillgänglig från klientnätet för att ge deltagarna tillgång till den skyddade labbmiljön, en Control Center. Jag kan via skriptet hämta in samtliga IP adresser till dessa Control Center maskinerna så jag kan dela ut ett kit till varje deltagare som därefter loggar in på maskinen via RDP och kommer åt resten av labbkitet.

Det är ett basic skript utan felhantering och så vidare men använd det om du vill men använd det på egen risk och endast i en skydad miljö.

# ===============================================================
#     NAME: VMware_lab_setup.ps1
#     AUTHOR: Niclas Borgström, Viridis IT
#     DATE: 2014-03-12 #
#     VERSION: 0.9
# ===============================================================

# Explain what the script does
Write-Host "This script will allow for set up and configure"
Write-Host "a lab environment for VMware vSphere 5.5 using"
Write-Host "existing templates."

# Ask for the Host name or IP
$VCENTER = Read-Host "Enter vCenter Server Name or IP"
Connect-VIServer -server $VCENTER

# Setup Our MENU
do {
     # Select environment MENU
     Write-Host "*** Config Menu for VMW55 ***"
     Write-Host "-----------------------------"
     # Print out the MENU
     Write-Host "What task would you like to perform?"
     Write-Host " "
     Write-Host " 1. Set up a new lab environment"
     Write-Host " 2. Power on vApps"
     Write-Host " 3. Get IP addresses of Desktop and ControlCenter VMs"
     Write-Host " 4. Get IP addresses of all VMs in vApps"
     Write-Host " 5. Power off (gracefully) vApps"
     Write-Host " "
     Write-Host " 0. Quit"
     Write-Host " "
     $response = Read-Host "Select 1-5"
     Write-Host " "

     switch ($response)
1 {

    # Show a welcome message
    Write-Host "VMware vSphere 5.5 lab environment set up v1.03"
    Write-Host "-----------------------------------------------"
    Write-Host " "

    # Select amount of Labkits to set up and what number the studentkit wil be assigned
    [int]$labkit = Read-Host "How many student kits will be needed? (1-12)"
    [int]$start = Read-Host "What startnumber would you like to use? (1-100)"
    [int]$end = $start + $labkit -1

    # Select host to install Lab kits on
    Write-Host " "
    Write-Host "Choose one of the hosts below from the Rescoures_Cluster:"
    Get-Cluster "Resources_Cluster" | Get-VMhost | Select Name
    $labhost = Read-Host "Type FQDN/IP address of host do you want to use"
    Get-VMhost $labhost | select Name

    # Select what datastore to use with enough capacity for thin vmdk and vswp
    Get-VMhost $labhost | Get-Datastore | Where-Object {$_.freespaceGB -gt ($labkit*(40+30))} | Select Name
    $datastore = Read-Host "What datastore would you like to use? (only showing datastores with enough free space)"
    Get-VMhost $labhost | Get-Datastore $datastore | Select Name
    Write-Host "Datastore" $datastore "will be used to install the vApp(s)"

    # Print summary
    Write-Host "Summary of lab environment set up:"
    Write-Host "---------------------------------"
    Write-Host "Lab kits to set up: " $labkit
    Write-Host "First kit name:     " "VMW55_Student$start"
    Write-Host "Last kit name:      " "VMW55_Student$end"
    Write-Host "Startnumber:        " $start
    Write-Host "Host:               " $labhost
    Write-Host "Datastore:          " $datastore
    Write-Host "                    "
    Write-Host "Required resources: "
    Write-Host "RAM:                " ($labkit*30) "GB"
    Write-Host "CPU:                " ($labkit*11) "vCPU"
    Write-Host "Storage (intial):   " ($labkit*40) "GB"
    Write-Host "Storage (max):      " ($labkit*316) "GB"
    $choice = @()
    $choice = Read-Host "Is this correct (Y/N)?"
    if ($choice -eq "y")
        # Start setting up the lab environment for each student
        $start..$end | Foreach {
        Write-Host "Setting up environment for student" $start "..."
        $studentid = "VMW55_Student$start"

        # Create a vSwitch for each student
        Write-Host "Creating Network environment..."
        New-VirtualSwitch -VMHost $labhost -Name $studentid

        # Create Portgroups for 3 use cases
        New-VirtualPortGroup -Name $studentid"_Production" -VirtualSwitch $studentid
        New-VirtualPortGroup -Name $studentid"_Management" -VirtualSwitch $studentid
        New-VirtualPortGroup -Name $studentid"_vMotion" -VirtualSwitch $studentid

        # Set the Management Portgroup to allow PromiscousMode
        $netsys = Get-View (Get-VMHost $labhost | Get-View).configmanager.networksystem
        $portgroupspec = New-Object VMWare.Vim.HostPortGroupSpec
        $portgroupspec.vswitchname = $studentid
        $portgroupname = $studentid
        $portgroupname = ($portgroupname += "_Management")
        $portgroupspec.Name = $portgroupname
        $portgroupspec.policy = New-object vmware.vim.HostNetworkPolicy
        $portgroupspec.policy.Security = New-object vmware.vim.HostNetworkSecurityPolicy
        $portgroupspec.policy.Security.AllowPromiscuous = $true

        # Create a vApp for each student
        Write-Host "Creating vApp..."
        New-VApp -Name $studentid -Location "Resources_Cluster"

        # Start deploying virtual machines
        Write-Host "Deploying virtual machines..."
        New-VM -VMHost $labhost -Name ControlCenter -Template VMW55_ControlCenter -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name iscsi -Template VMW55_iscsi -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name nfs -Template VMW55_nfs -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name esxi01 -Template VMW55_esxi01 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name esxi02 -Template VMW55_esxi02 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name vcva01 -Template VMW55_vcva01 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name Desktop01 -Template VMW55_Desktop01 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid

        # Change networking to dedicated portgroups
        Write-Host "Change portgroups on all virtual machines in the vApp..."
        Get-VApp $studentid | Get-VM | Get-NetworkAdapter | Where {$_.NetworkName -eq "Production_template" } | Set-NetworkAdapter -NetworkName $studentid"_Production" -Confirm:$false
        Get-VApp $studentid | Get-VM | Get-NetworkAdapter | Where {$_.NetworkName -eq "vMotion_template" } | Set-NetworkAdapter -NetworkName $studentid"_vMotion" -Confirm:$false
        Get-VApp $studentid | Get-VM | Get-NetworkAdapter | Where {$_.NetworkName -eq "Management_template" } | Set-NetworkAdapter -NetworkName $studentid"_Management" -Confirm:$false

        # Change StopAction for all VMs in the vApp
        Write-Host "Change settings for vApp" $studentid "..."
        $AppView = Get-VApp $studentid | Get-View
        Foreach ($Entity in $AppView.VAppconfig.EntityConfig)
            If ($Entity.Stop.Action -ne "guestShutdown")
                $VAppConfigSpec = New-Object VMware.Vim.VAppConfigSpec
                $EntityConfig = New-Object VMware.Vim.VAppEntityConfigInfo
                $EntityConfig.Key = (Get-View $Entity.Key).MoRef
                #$EntityConfig.startDelay = 120
                $EntityConfig.waitingForGuest = $true
                #$EntityConfig.stopDelay = 120
                $EntityConfig.StopAction = "guestShutdown"
                $VAppConfigSpec.EntityConfig = $EntityConfig

    [int]$start = $start + 1
2 {

    # Power on all VMs in vApp
    # To power on all lab kits just type the first letters, i.e to power on
    # all lab kits called VMW55_student1, VMW55_student2, and so on you only
    # need to typ "VMW55"
    Write-Host "Power on all vApps for VMW55 lab"
    Write-Host "--------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name
    $vappinput = Read-Host "What lab should power on? (case sensitive)"
    Get-VApp | Where {$_.Name -like "$vappinput*"} | Start-VApp
3 {

    # List Ip address of all Desktop01 & ControlCenter VMs in selected vApps
    Write-Host "IP address of all Desktop01 & ControlCenter VMs for VMW55 class"
    Write-Host "-------------------------------------------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name
    $vappinput = Read-Host "What Class should we list? (case sensitive)"
    Write-Host "Saving result to file 'IPAddress_of_Desktop.txt'"
    $FencedVMIP = Get-VApp | Where {$_.Name -like "$vappinput*"} | Get-VM | Where {$_.Name -like "Desktop*" -or $_.Name -eq "ControlCenter"} | Select {$_.VApp},Name,{$_.Guest.IpAddress[0]}
    $FencedVMIP | Out-File C:\PS_Scripts\VMware\VMW55\IPAddress_Of_Desktop.txt
4 {

    # List Ip address of all VMs in selected vApps
    Write-Host "IP address of all VMs for VMW55 lab"
    Write-Host "-------------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name
    $vappinput = Read-Host "What Class should we list? (case sensitive)"
    Write-Host "Saving result to file 'IPAddress_All_VMs.txt'"
    $AllVMIP = Get-VApp | Where {$_.Name -like "$vappinput*"} | Get-VM | Select Name,{$_.VApp},{$_.Guest.IpAddress}
    $AllVMIP | Out-File C:\PS_Scripts\VMware\VMW55\IPAddress_All_VMs.txt

5 {

    # Shutdown all VMs in the selected vApps
    Write-Host "Power off all vApps for VMW55 class"
    Write-Host "-----------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name

$vappinput = Read-Host "What Class should power off? (case sensitive)"
    $choice = Read-Host "Are you sure you want to stop all vApps (Y/N)?"
    if ($choice -eq "y")
        Get-VApp | Where {$_.Name -like "$vappinput*"} | Stop-VApp


0 {
    "** Exiting Script **";
    Write-Host "Quit";
    Disconnect-viserver -server $VCENTER -confirm:$false

default {
    "** The selection could not be determined **";
    Start-Sleep 1

while ($response -ne "0")