Managing Windows Explorer context menu with Powershell
Abstract
Managing Windows Exporer context menu has always been somewhat of a challenge. At least since retirement of Win95 Power Toys with their ‘cmd prompt here’ back in 1990x, Windows instrmentation has always been lacking standard tools for doing this kind of tasks. While there currently are a number of 3rd party tools available, mostly compiled programs, the need to download and install a 3rd part binary executable may appear an unfeasible overkill and unnecessary risk, and thus inappropriate in this situation. Manually editing Windows Registry via regedit
is tricky and prone to typos and other kinds of errors, let alone not reproducible.
On the other hand, Poweshell scripting is a very good candidate. A script is easily customizable; it can be replayed on any number of workstations, or on the same worktation to restore or alter customized menus. The only requrement is Powershell itself, which has become an integral part of Win10 OS.
Description
The script creates the cascading context submenu Shells
containing a number of popular shells: cmd
, powershell
, Windows Subsystem for Linux shell, Windows Terminal, plus some development shell environments.
All shells except cmd
are added conditionally (once found).
The cascading menu is attached to folders (context #2), folder backgrounds (context #3) and Library folder backgrounds (context #4). A shell is launched in the directory the menu was invoked upon. The submenu is registered in the HKCU
registry tree (thus no admin rights are required and the submenu is user-specific).
cmd
is added unconditionally unless Windows Terminal is found.powershell
andwsl
are added if found.- If Windows Terminal is found, it is added, and adding separate items for
cmd
,powershell
andwsl
is suppressed. - If MS Visual Studio 2019 (or just its Build Tools) is found, an additional entry is added that launches
cmd
withx64
build environment activated. - The script looks up a
conda
environment nameddev
(may be overriden by a command line argument). If found, an additional entry is added that launchescmd
with this env activated. If MS VS/Build Tools is also present, thex64
environment is also activated in that shell.
Requirements
Windows 10 with all recent updates and powershell
5.1 or later.
How to run
- Save code to a file (e.g.
directory-menu.ps1
) - Digitally sign if necessary
- Run it from
powershell
TODO
- Add ‘remove menu’
Code
<#
.SYNOPSIS
Creates cascading submenus to Explorer directory context menu with commonly used shells.
.DESCRIPTION
This script creates a submenu "Shells", containing cmd, powershell and wsl entries.
Shells are launched in the directory the context menu is invoked upon.
#>
#To print out this help in PS: get-help .\directory-menu.ps1 -detailed
param(
[string]
#Conda environment name
$CondaEnv = "dev",
[Int[]]
#Context numbers, array of numbers from 1 to 4
$Contexts = (2,3,4)
)
#should be followed by Create-Menu-Item calls
function Create-Cascading-Menu(
[Int]$Context,
[String]$Key,
[String]$Name,
[String]$Icon
) {
switch($Context) {
1 { $BasePath = "HKCU:\Software\Classes\Folder\shell"}
2 { $BasePath = "HKCU:\Software\Classes\Directory\shell"}
3 { $BasePath = "HKCU:\Software\Classes\Directory\Background\shell" }
4 { $BasePath = "HKCU:\Software\Classes\LibraryFolder\background\shell" }
default {
Write-Error "Invalid context value: $Context"
return
}
}
Set-Location $BasePath
New-Item -Name $Key -Force | Out-Null
Set-Location $Key
New-ItemProperty -Path . -Name "MUIVerb" -Value $Name -PropertyType STRING `
| Out-Null
New-ItemProperty -Path . -Name "subcommands" -Value "" -PropertyType STRING `
| Out-Null
New-ItemProperty -Path . -Name "Icon" -Value $Icon -PropertyType STRING `
| Out-Null
New-Item -Name "shell" `
| Out-Null
Set-Location "shell"
}
#Must be called in the context of 'shell' registry key
function Create-Menu-Item(
[String]$Key,
[String]$Name,
[String]$Icon,
[String]$Command
) {
if (-not (@(Get-Location).Path.StartsWith("HKCU:"))) {
Write-Error "Current location is not in HKCU:"
return
}
New-Item -Name $Key | Out-Null
Push-Location $Key
Set-Item -Path . -Value $Name
New-ItemProperty -Path . -Name "Icon" -Value $Icon | Out-Null
New-Item -Name "Command" | Out-Null
Push-Location "Command"
Set-Item -Path . -Value $Command
Pop-Location
Pop-Location
}
$CmdPath = (Get-Command cmd.exe).Path
$GotoAndRunPfx = "$CmdPath /k pushd `"%V`" && "
$WTPath = (Get-Command -ErrorAction Ignore wt.exe).Path
if ($WTPath) { $GotoAndRunPfx = "$WTPath -d `"%V`" -p `"cmd`" cmd /k " }
$WSLPath = (Get-Command -ErrorAction Ignore wsl.exe).Path
$PSPath = (Get-Command -ErrorAction Ignore powershell.exe).Path
$VSActivate = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019" + `
"\BuildTools\VC\Auxiliary\Build\vcvarsall.bat"
$HasVS = (Test-Path $VSActivate)
if (-not ($CondaEnv)) { $CondaEnv = "dev" }
$CondaRoot = "$env:USERPROFILE\Miniconda3"
$CondaActivate = "$CondaRoot\condabin\activate.bat"
$CondaEnvRoot = "$CondaRoot\envs\$CondaEnv"
$HasCondaEnv = (Test-Path $CondaEnvRoot)
function Create-Menu([Int]$Context) {
Create-Cascading-Menu -Context $Context -Key Shells -Name Shells -Icon "$CmdPath,0"
#If Windows Terminal is present, we add it alone instead of adding cmd + ps + wsl
#(as those can be launched from WT)
if ($WTPath) {
Create-Menu-Item -Key "wt" -Name "Terminal" -Icon "$CmdPath,0" `
-Command "$WTPath -d `"%V`""
} else {
Create-Menu-Item -Key "cmd" -Name "cmd" -Icon "$CmdPath,0" `
-Command "$CmdPath /s /k pushd `"%V`""
if ($PSPath) {
Create-Menu-Item -Key "ps" -Name "PowerShell" -Icon "$PSPath,0" `
-Command "$PSPath -NoExit -Command `"cd %V `""
}
if ($WSLPath) {
Create-Menu-Item -Key "wsl" -Name "WSL" -Icon "$WSLPath,0" `
-Command "$WSLPath --cd `"%V`""
}
}
if ($HasCondaEnv) {
if ($HasVS) {
$c = "$GotoAndRunPfx `"$CondaActivate`" $CondaEnv && `"$VSActivate`" x64"
} else {
$c = "$GotoAndRunPfx `"$CondaActivate`" $CondaEnv"
}
Create-Menu-Item -Key $CondaEnv -Name $CondaEnv `
-Icon "$CondaRoot\Menu\Iconleak-Atrous-Console.ico" -Command $c
}
if ($HasVS) {
Create-Menu-Item -Key "vscmd" -Name "VStudioTools" -Icon "$CmdPath,0" `
-Command "$GotoAndRunPfx `"$VSActivate`" x64"
}
}
Push-Location
foreach ($cx in $Contexts) {
Write-Host "Creating menu for context $cx"
Create-Menu -Context $cx
}
Pop-Location