As it turns out, PowerShell allows you to create elaborate and useful shortcut menus. In this article, I will show you how it’s done.
The constructor arguments for type System.Management.Automation.Host.ChoiceDescription are:
The label text, which implies the associated choice character, by way of
&preceding desired character; e.g.,Option &AmakesAthe associated character:
$title = "Select an option:"
$options = 'A', 'B', 'C' | ForEach-Object { [System.Management.Automation.Host.ChoiceDescription]::new( "Option &$_", "Help for option $_" )
}
$selection = $Host.UI.PromptForChoice($title, '', $options, 0)
# Handle the user’s selection
switch ($selection) { 0 { "You selected Option A" } 1 { "You selected Option B" } 2 { "You selected Option C" }
}Select an option:
[A] Option A [B] Option B [C] Option C [?] Help (default is "A"): ?
A - Help for option A
B - Help for option B
C - Help for option C
[A] Option A [B] Option B [C] Option C [?] Help (default is "A"): b
You selected Option BBecause it is the label text that determines the choice character, the only way to have numbered choices is to in effect repeat the number in the label text.
Note that you’re fundamentally limited to 10 numbered choices (
1through9+0, if acceptable), given that you can only designate a single character as the choice character.
$title = "Select an option:"
$number = 1
$options = 'A', 'B', 'C' | ForEach-Object { [System.Management.Automation.Host.ChoiceDescription]::new( "Option $_&$number`b", # NOTE: `b on display erases the number. "Help for option $_" ) ++$number
}
$selection = $Host.UI.PromptForChoice($title, '', $options, 0)
# Handle the user’s selection
switch ($selection) { 0 { "You selected Option A" } 1 { "You selected Option B" } 2 { "You selected Option C" }
}Select an option:
[1] Option A [2] Option B [3] Option C [?] Help (default is "1"): 2
You selected Option BThis article shows how Powershell scripting skills have developed over a period of about 12 months, by highlighting the differences between different versions of my Terminal Menu project.
After I started working as an IT Specialist I at Delaware Tech in October of 2022, I quickly became captivated by PowerShell and its ability to increase my efficiency as an IT professional. I had a bit of a background with object-oriented programming in Python 3 and Java including what I’d learned during the Programming I course at Delaware Tech, a large number of CodeCademy () courses, and what I’d managed to teach myself while tutoring for some of the other IT and Programming courses offered at Delaware Tech.
By the start of 2023, I had managed to learn a lot of the absolute ‘bare basics’ of PowerShell scripting. I had begun to realize the power behind the scripting language, and I started to take careful note of issues that seemed to have programmatic resolutions. Especially the issues that would pop up frequently!
For example, many issues experienced with the Microsoft Teams application can be resolved by removing Teams from the system and reinstalling.
Many issues with the Zoom application can be resolved by running the CleanZoom.exe () utility and then reinstalling Zoom using AutoUpdate and any other necessary switches.
Many printer issues are resolved by:
- Scanning the target computer to see which printers are registered as connected.
*Throughout the rest of this article, I’ll refer to the different versions of the menu as Menuv1, Menuv2, and Menuv3 (current).
The last draft of the original version of the menu is from May/June 2023.
: The second version is from September 2023.
is the one I’m still working on today.
For a full listing and explanation of functions currently available in Menuv3 – please scroll to the end of this article.
All versions of the Terminal Menu are essentially a collection of functions that revolve around a central
Some key areas where the three versions differ include: configuration file(s), versatility, and the functions included. As I’ve learned more about PowerShell scripting, one of my main focuses when building the Terminal Menu has been to make sure it’s easy for other people to pick up, understand, and use.
In contrast to the “mess of TXT configuration file(s)” setup used by Menuv1 and Menuv2, uses a single JSON configuration file for category and function selection. Using the single JSON file for configuration makes it easy to understand how to add new categories and function names. It also makes it easy to add new “config options” to the JSON file since it’s already being imported at the beginning of the Menu PS1 script.
The JSON file has a which contains properties which correspond to the categories presented in the first menu. Each property contains a list as a value; each string contained in the list will be presented as a function option, if that category is chosen.
A graphic representation of the configuration files involved in the first two versions of the menu is shown below.
used a text file containing a category name on each line called , in the root of the menu directory to decide which categories to present in the first menu.
These category names had to correspond to the names of other text files, contained in the directory of the menu. These text files contained a function name on each line, which had to correspond to functions listed in the menu_1 file (contained in the root of the menu directory).
uses the same sets of configuration TXT files, but the repository also contains an file. The presence of the JSON file is a clear sign I was in the beginning stages of moving to a JSON configuration file ~September of 2023!
You may be asking yourself why I didn’t just use the JSON configuration file from the start! I was aware of the existence of JSON files, and that they were used for configuration, however I hadn’t learned to import a JSON using PowerShell to create an ‘object’ yet. My knowledge of object manipulation via PowerShell started to emerge around mid 2023.
Terminal menu ‘interface’:
uses the Get-Help command, after dot-sourcing the functions, to access the multi-line string included at each function’s beginning that contains its basic description, along with each parameters’ description.
All functions have been written to be used in the Terminal Menu, but most have also been adapted to be used as standalone functions outside of the Terminal Menu. To decide whether it’s being used in the Terminal Menu – a function checks for the presence of a function dot sourced from the utils folder of the menu, and it also checks that the environment variable that contains the base directory path of the menu exists.
Many functions in Menuv1 are not able to target remote computers, some functions generate HTML reports, while others only output directly to the terminal.
The menu itself repeats in a loop, so you can select a function/execute it, and then select another after that one finishes.
script exists to run installations created using the PS App Deployment Toolkit, which is able to target single remote computers. This was the start of the ‘Install-Application’ function which exists in the latest version of the menu, and can run installations on the local computer, or groups of remote computers.
The second version of the menu also includes many individual/explicitly written functions to install Teams, Zoom, and other software. Most of these individual functions were turned into PSADT deployments, which could then be installed on local/remote computers using the Install-Application function.
Two separate functions exist in Menuv2 to search for files/folders and installed applications on target computers. These functions are combined into the single function, which can target remote computer(s) using a single hostname, hostname TXT file, hostname list, or hostname substring. Scan-ForApporFilepath outputs .csv/.xlsx reports with it’s findings.
TargetComputer parameter (used by most functions):
One of the most important code sections contained in the Terminal Menu is the section that takes the ‘TargetComputer’ parameter value and attempts to turn it into a list of hostnames using one of the methods listed below, if it’s not already a list.
allows for several different types of TargetComputer input, including:
- Path to TEXT file containing a hostname on each line
- Substring, which will be used to generate a list of computer hostnames
- List of computer hostnames
- ‘’ will cause functions to be executed on local computer
This is one of the key re-useable code sections in the Terminal Menu.
The Targetcomputer parameter can also be submitted normally or through the pipeline for most functions in Menuv3. This was just one of the ways I’ve tried to make the functions more versatile.
the TargetComputer parameter existed under the name: ComputerName. It only took a single hostname as a value (I had hopes of it one day being able to take a filepath as a value, which could be turned into a list of hostnames using Get-Content).
, some functions start to allow either a single hostname, or filepath as a value for TargetComputer. These functions use Test-Path to determine if the value was a file, or if it was intended to be a single hostname.
- TargetComputer parameter processing (already covered)
- Valid output file(s) path creation for reports
- CSV/XLSX report generation
You can find three of these in the ‘reuseable’ directory, in Menuv3, located here:
The differences between Menuv1, Menuv2, and Menuv3 are hard to miss, even if you are not familiar with PowerShell scripting. It’s experienced a gradual evolution into something that’s able to act as an aid in many of my daily responsibilities, as well as perform many other tasks in their entirety.
I’m very excited about any opportunities for collaboration or to improve the menu as it continues to be a work in progress. Please do not hesitate to send me a message if you have any ideas or suggestions!
Listing of current functions in the menu on github, and what issues they resolve or help to resolve:
– connects target computers to printer available through PrinterLogic instance
– deletes profiles.ini file from target computer, and optionally deletes all profile data.
- Resolves Firefox profile issues () by manually deleting the profiles.ini file or it’s parent folder.
– converts png file to ico. I’ve used this to create icons for shortcuts when creating installation scripts.
- There is a section in the HTML guide that describes how you can create variables in these canned responses, so the function will prompt you for values and fill them in before copying the response to clipboard.
- Used to quickly and efficiently respond to similar IT Support tickets.
– copies file/folder on target computer(s) back to a folder on the local computer.
- Used for log collection mostly (PrinterLogic client workstation logs, BIOS flashing logs), comparing logs between a ‘working’ machine and a ‘non-working’ one can be an easy way to diagnose the cause of an issue on the ‘non-working’ machine.
– checks the directory for any TXT files and presents them as a menu in the terminal. Once an option is chosen – that file’s content will be copied to clipboard.
- Used to quickly retrieve static information including links, contact information, Powershell one-liners, etc.
– retrieves basic information including computer model, bios version/release date, computer asset tag (from Dell CCTK or using win32_bios CIM class), computer serial number, and multi-monitor information from target computer(s) and outputs to CSV/XLSX or gridview.
- Used to verify asset tag information, check whether monitors are being registered as connected to computers
- Wide range of uses including checking for occupied computers, making sure scheduled reboots are working correctly, and verifying windows builds / OS versions are updated to current organization standard.
This function also checks if the Teams and Zooms processes are running on computers.
– just a wrapper for: , but it has the added advantage of allowing you to submit a hostname substring for target computers not offered by Get-WindowsAutopilot. Get-IntuneHardwareIDs -TargetComputer ‘t-client-‘ to get hardward IDs from all responsive computers that have hostnames starting with ‘t-client-‘.
– attempts to install the Powershell Active Directory module, without having to install RSAT. Salvaged from:
- installations run consecutively on the target machines.
- The function also gives you the option to skip over computers that are occupied, since many PSADT silent installations will force close any processes that are related to the application being installed.
– installs the new Teams client after removing the ‘Classic Microsoft Teams’ client from target machines.
– installs Smart Learning Suite software on target machines – either with board/ink drivers or without depending on function parameter values.
The script creates another Powershell script, that has to be run on each master computer directly to add client machines to master. I believe this is a limitation of the Veyon software, but I’m very interested in any workarounds that may be available to eliminate the extra step in this process.
– checks Windows Registry for evidence of installed .NET versions on target computer(s) and outputs results to gridview.
- Used to determine if machines need certain versions of .NET installed. For ex: at my workplace, one of the applications that needs to be installed on computers requires .NET 3.5. I used this script to ascertain which computers needed to have .NET 3.5 installed.
– create a new canned response file for the directory, which can be used with
– uses IntuneWinAppUtil.exe from: to take a specified folder and compile it into an .intunewin package, that can be uploaded to Intune and used to deploy software.
– opens the file in the folder. This is the ‘landing page’ for the HTML version of the guide, which goes into a lot more detail than the on the main page of the Github repo.
– opens an RDP prompt with specified hostname
– pings target computer(s) specified number of times and generates a CSV/XLSX report logging results including number of pings sent compared to number of responses received and packet loss percentage.
Can be used to pinpoint areas on the network or specific computers that are experiencing issues relating to network connectivity or cabling issues. In addition to networking issues, ping tests can also help identify issues with both in the OS and BIOS*.
I had a situation at work where I was convinced it was a networking issue based on ping tests, until I realized that computers NIC cards were not allowing them to ‘wake up’ from sleep successfully when pinged.
– removes the Microsoft Teams Classic application from target computers (also a part of the Install-NewTeams function).
scan target computer(s) for either a file/folder, or an installed application. Output CSV/XLSX report with findings and computers that are missing the item.
- Used for quick software inventories where a check for only one application is necessary.
- Also used to check remote computer(s) for presence of different log and configuration files.
– if your organization is using Sophos Central, one of the main applications involved in the new client installation is the Sophos Endpoint Self Help application, the presence of which was one item I found to be an indicator of a (probably) successful and working installation of Sophos Central.
– function created to streamline the process of taking inventory of printer-related items, but can be used to generate a CSV/XLSX report of inventory no matter the type of product.
- ‘s free API for UPC code lookup when the UPC code isn’t found in existing inventory.
– scan target computers for Microsoft Office 2021.
- Used to check target computers to make sure the update from Office 2019 went through successfully.
– scan applications installed on target computer(s) using uninstall keys from the Windows Registry, output a CSV report for
- A single .xlsx file is output, with a worksheet for each target computer.
This type of report is especially useful to provide to stakeholders/team members/co-workers, to offer assurances that software has been successfully installed on groups of computers.
– send local file/folder to target remote computer(s).
- Used to send different kinds of shortcuts to target computers, for ex: shortcut to force chrome usage and open specified url for students to take certain type of exam.
- Used for ‘guest’ computers where we had multiple human beings, using the same guest account, to access a computer.
– stop and disable (if specified) Wi-Fi adapters on target remote devices as long as they have an active Ethernet connection.
- Active Wi-Fi adapters on machines that should only have wired connections can cause issues.
– test target computer(s) quickly for network connectivity using the Test-Connection cmdlet, and output to terminal.
- Quick test of connectivity before further investigation of an issue, for a single computer or groups of computers.
- It’s pretty cool that something as simple as a ping test can play a large role in identifying such a wide range of issues. If I had a dollar for every “boot-related” error that I’ve been able to pinpoint by ping testing a group of computers in the past year or so, I would probably be able to buy myself something worth about $50.
Completing the Shortcut Menu
The last thing that I want to point out about the sample script is this line:
As mentioned earlier, the context menu and the individual options displayed on the menu are objects. As such, we need a line of code to establish a relationship between these objects. The line of code shown above accomplishes this.
By adding the objects associated with individual menu options to the menu object as an array, the menu options become associated with the menu itself. Additionally, the menu is associated with another object – a text box in this case.
Building Custom Shortcut Menus in PowerShell
To demonstrate how to build custom shortcut menus, I have added a shortcut menu to the script we saw above. This custom shortcut menu features Copy and Paste commands.
As you can see, the script above closely resembles the previous script, but it includes additional code for generating a shortcut menu. The first thing you will probably notice is that the shortcut menu is treated as an object.
The basic process for building a PowerShell GUI involves creating a form, defining a series of objects (such as text boxes, buttons, or ), and then pinning those objects to the form. While it’s tempting to think of a shortcut menu as an attribute of an object, a shortcut menu is, in fact, an object itself.
Understanding ContextMenu and MenuItem Objects
However, this is not to say that there is no relationship between a shortcut menu and other objects. On the contrary, PowerShell supports an object attribute known as a context menu. Take a look at the third to last line of code in the script:
$Textbox.ContextMenu = $ShortcutMenuThis attribute doesn’t do anything by itself. It must be pointed toward a ContextMenu object to work.
Like other PowerShell GUI objects, the ContextMenu object requires you to create the object and then define various attributes for the object. Context menus support many of the same standard attributes as other GUI objects, but one attribute stands out as both unique and indispensable: MenuItems.
Just as the shortcut menu is a standalone object, so are the individual items within it. In my sample script, for example, there are two menu options – Copy and Paste – both defined as MenuItem objects.
The sample script uses two attributes in conjunction with the MenuItem objects. The first is the Text attribute. The Text attribute controls the text that is displayed for a particular menu option.The shortcut menu I created contains Copy and Paste options. The word “Copy” is the value that is assigned to the Text attribute for the Copy menu option.
The other attribute used with menu items is Add_Click. This attribute controls the action taken when a menu item is clicked. In this case, the Add_Click actions simply interact with the Windows clipboard for copying or pasting items. However, Add_Click attributes offer broader possibilities than demonstrated in this script.
What Normally Happens When You Right-Click Objects?
Before showing you how to build a shortcut menu in PowerShell, let’s look at what normally happens when you right-click objects within a PowerShell GUI. To illustrate the concept, I have prepared a really simple script:
This script creates a basic PowerShell GUI containing a single text box. If you right-click within the text box, PowerShell does indeed display a shortcut menu. However, the menu shown is the default Windows shortcut menu (Figure 1). It’s the same menu that you would encounter if you right-click within Notepad (Figure 2).

This is the shortcut menu that PowerShell displays by default.

Notepad displays the same shortcut menu.
About the Author(s)

Brien Posey is a bestselling technology author, a speaker, and a 20X Microsoft MVP. In addition to his ongoing work in IT, Posey has spent the last several years training as a commercial astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space.

