Skip to content

Instantly share code, notes, and snippets.

@rivman
Forked from jcwillox/toolbox-context-menu.ps1
Created July 20, 2022 16:30
Show Gist options
  • Save rivman/b3adc0e0f942c46e3abb27b283f792c6 to your computer and use it in GitHub Desktop.
Save rivman/b3adc0e0f942c46e3abb27b283f792c6 to your computer and use it in GitHub Desktop.

Revisions

  1. @jcwillox jcwillox revised this gist Apr 2, 2022. 1 changed file with 47 additions and 5 deletions.
    52 changes: 47 additions & 5 deletions toolbox-context-menu.ps1
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,9 @@
    Specify the location of the nircmd executable to use, will attempt to locate it from $PATH if not specified.
    .PARAMETER AppDir
    If you are not using Toolbox you can specify the IDE's installation directory.
    .PARAMETER DefaultChannel
    Specify the default channel to use when there are multiple channels found, the user will be prompted to
    choose a channel if the default channel is not found.
    #>

    [CmdletBinding()]
    @@ -32,29 +35,68 @@ param (
    [switch]$List,
    [switch]$UseNircmd,
    [string]$NirCmdPath,
    [string]$AppDir
    [string]$AppDir,
    [ArgumentCompleter({ return @("'Release'", "'Early Access Program'") })]
    [string]$DefaultChannel
    )

    $ErrorActionPreference = "Stop"
    trap { throw $Error[0] }
    $toolbox = if ($BasePath) { $BasePath } else { Join-Path $env:LOCALAPPDATA "JetBrains\Toolbox\apps" }
    $regRoot = if ($Global) { "HKLM" } else { "HKCU" }

    Write-Verbose "Toolbox: '$toolbox'"

    if ($UseNircmd -and -not (Get-Command "nircmd.exe" -ErrorAction Ignore)) {
    Write-Error "nircmd.exe is not installed or missing from the path"
    Write-Error "either install nircmd or specify its location using '-NirCmdPath'"
    return
    }

    function Get-Channel([string]$Path) {
    $channels = Get-ChildItem (Join-Path $Path "ch-*")
    if (-not $channels) {
    Write-Error "no channels found in '$Path'"
    }
    # immediately return if only 1 channel
    if ($channels.Length -eq 1) {
    return $channels[0]
    }
    $channelsOutput = ""
    for ($i = 0; $i -lt $channels.Length; $i++) {
    $channel = $channels[$i]
    # extract channel type
    $channelSettings = (Get-Content (Join-Path $channel.FullName ".channel.settings.json") | ConvertFrom-Json)
    $channelName = $channelSettings.filter.quality_filter.name
    # skip the prompt if we match the default channel
    if ($DefaultChannel -eq $channelName) {
    return $channel
    }
    $channelsOutput += "[$i]: '$($channel.BaseName)' ($channelName)"
    if ($i -lt $channels.Length - 1) {
    $channelsOutput += "`n"
    }
    }
    # prompt user to select which channel to use
    Write-Host (Split-Path -Leaf $Path) -ForegroundColor Green
    Write-Host $channelsOutput
    $i = $null
    while (-not ($i -ge 0 -and $i -lt $channels.Length)) {
    if ($null -ne $i) {
    Write-Host "'$i' is not a valid index." -ForegroundColor Red
    }
    $i = Read-Host "Enter the number of the desired channel"
    }
    return $channels[$i]
    }

    function Get-IDEs {
    $IDEs = @{}
    Write-Verbose "Getting IDEs..."
    Write-Verbose "Toolbox: '$toolbox'"
    foreach ($item in (Get-ChildItem $toolbox -Exclude Toolbox)) {
    $versionPath = (Get-ChildItem -Path (Join-Path $item.FullName "ch-0") -Directory -Exclude "*.plugins" | Sort-Object BaseName -Descending | Select-Object -First 1 | Select-Object -ExpandProperty FullName)
    $channel = Get-Channel -Path $item.FullName
    $versionPath = (Get-ChildItem -Path $channel -Directory -Exclude "*.plugins" | Sort-Object BaseName -Descending | Select-Object -First 1 | Select-Object -ExpandProperty FullName)
    Write-Verbose "Version Path: '$versionPath'"
    $binPath = Join-Path $versionPath "bin"
    Write-Verbose "Bin Path: '$binPath'"
    if (Test-Path $binPath) {
    $IDEs[$item.BaseName] = $versionPath
    }
  2. @jcwillox jcwillox revised this gist Apr 1, 2022. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion toolbox-context-menu.ps1
    Original file line number Diff line number Diff line change
    @@ -35,7 +35,8 @@ param (
    [string]$AppDir
    )

    $ErrorActionPreference = 'Stop'
    $ErrorActionPreference = "Stop"
    trap { throw $Error[0] }
    $toolbox = if ($BasePath) { $BasePath } else { Join-Path $env:LOCALAPPDATA "JetBrains\Toolbox\apps" }
    $regRoot = if ($Global) { "HKLM" } else { "HKCU" }

    @@ -47,9 +48,13 @@ if ($UseNircmd -and -not (Get-Command "nircmd.exe" -ErrorAction Ignore)) {

    function Get-IDEs {
    $IDEs = @{}
    Write-Verbose "Getting IDEs..."
    Write-Verbose "Toolbox: '$toolbox'"
    foreach ($item in (Get-ChildItem $toolbox -Exclude Toolbox)) {
    $versionPath = (Get-ChildItem -Path (Join-Path $item.FullName "ch-0") -Directory -Exclude "*.plugins" | Sort-Object BaseName -Descending | Select-Object -First 1 | Select-Object -ExpandProperty FullName)
    Write-Verbose "Version Path: '$versionPath'"
    $binPath = Join-Path $versionPath "bin"
    Write-Verbose "Bin Path: '$binPath'"
    if (Test-Path $binPath) {
    $IDEs[$item.BaseName] = $versionPath
    }
  3. @jcwillox jcwillox revised this gist Jan 22, 2022. 1 changed file with 10 additions and 3 deletions.
    13 changes: 10 additions & 3 deletions toolbox-context-menu.ps1
    Original file line number Diff line number Diff line change
    @@ -125,16 +125,23 @@ function Add-ShellKeys {
    }
    }

    function Add-RegKey([string]$Path) {
    $Path = Join-Path "${regRoot}:" $Path
    if (-not (Test-Path -LiteralPath $Path)) {
    New-Item -Path $Path -Force -ErrorAction Ignore -Confirm:$false | Out-Null
    }
    }

    function Add-ContextMenu {
    param (
    [string]$Name,
    [string]$ExePath
    )

    # ensure base folders exist
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\`*\shell" -Force -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\shell" -Force -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\Background\shell" -Force -ErrorAction Ignore -Confirm:$false | Out-Null
    Add-RegKey "SOFTWARE\Classes\`*\shell"
    Add-RegKey "SOFTWARE\Classes\Directory\shell"
    Add-RegKey "SOFTWARE\Classes\Directory\Background\shell"

    # add to file context menu
    Write-Output "ACTION: Edit with $Name (Files)"
  4. @jcwillox jcwillox revised this gist Jan 22, 2022. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions toolbox-context-menu.ps1
    Original file line number Diff line number Diff line change
    @@ -132,9 +132,9 @@ function Add-ContextMenu {
    )

    # ensure base folders exist
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\`*\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\Background\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\`*\shell" -Force -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\shell" -Force -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\Background\shell" -Force -ErrorAction Ignore -Confirm:$false | Out-Null

    # add to file context menu
    Write-Output "ACTION: Edit with $Name (Files)"
  5. @jcwillox jcwillox revised this gist Jan 22, 2022. 1 changed file with 15 additions and 22 deletions.
    37 changes: 15 additions & 22 deletions toolbox-context-menu.ps1
    Original file line number Diff line number Diff line change
    @@ -35,6 +35,7 @@ param (
    [string]$AppDir
    )

    $ErrorActionPreference = 'Stop'
    $toolbox = if ($BasePath) { $BasePath } else { Join-Path $env:LOCALAPPDATA "JetBrains\Toolbox\apps" }
    $regRoot = if ($Global) { "HKLM" } else { "HKCU" }

    @@ -88,48 +89,40 @@ function Add-ShellKeys {
    $iconPath = $ExePath
    }

    Push-Location -LiteralPath $Path -ErrorAction Stop

    try {
    if (-not $Force -and (Test-Path -LiteralPath "$Path\$Name")) {
    Write-Host "EXISTS: $Path\$Name" -f Yellow
    } else {
    New-Item `
    -Path ".\$Name" `
    -Path "$Path\$Name" `
    -Value "$Action with $Name" `
    -ErrorAction Stop `
    -Force:$Force `
    -Confirm:$false | Out-Null
    Write-Host "ADDED: $Path\$Name" -f DarkGreen
    } catch {
    Write-Host "EXISTS: $Path\$Name" -f Yellow
    }

    try {
    if (-not $Force -and (Get-ItemProperty -LiteralPath "$Path\$Name" -Name "Icon" -ErrorAction Ignore)) {
    Write-Host "EXISTS: $Path\$Name [Icon]" -f Yellow
    } else {
    New-ItemProperty `
    -LiteralPath ".\$Name" `
    -LiteralPath "$Path\$Name" `
    -PropertyType ExpandString `
    -Name "Icon" `
    -Value $iconPath `
    -ErrorAction Stop `
    -Force:$Force `
    -Confirm:$false | Out-Null
    Write-Host "ADDED: $Path\$Name [Icon]" -f DarkGreen
    } catch {
    Write-Host "EXISTS: $Path\$Name [Icon]" -f Yellow
    }

    try {
    if (-not $Force -and (Get-Item -LiteralPath "$Path\$Name\command" -ErrorAction Ignore)) {
    Write-Host "EXISTS: $Path\$Name\command" -f Yellow
    } else {
    New-Item `
    -Path ".\$Name" `
    -Name "command" `
    -Path "$Path\$Name\command" `
    -Value "`"$exePath`" $LaunchArgs" `
    -ErrorAction Stop `
    -Force:$Force `
    -Confirm:$false | Out-Null
    Write-Host "ADDED: $Path\$Name\command" -f DarkGreen
    } catch {
    Write-Host "EXISTS: $Path\$Name\command" -f Yellow
    }

    Pop-Location
    }

    function Add-ContextMenu {
    @@ -139,7 +132,7 @@ function Add-ContextMenu {
    )

    # ensure base folders exist
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\*\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\`*\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\Background\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\shell" -ErrorAction Ignore -Confirm:$false | Out-Null

    @@ -161,7 +154,7 @@ function Remove-Reg([string]$Path) {
    if (-not (Test-Path -LiteralPath $Path)) {
    Write-Host "MISSING: $Path" -f Yellow
    } else {
    Remove-Item -LiteralPath $Path -Recurse -ErrorAction Stop
    Remove-Item -LiteralPath $Path -Recurse
    Write-Host "REMOVED: $Path" -f Red
    }
    }
  6. @jcwillox jcwillox created this gist Jan 22, 2022.
    201 changes: 201 additions & 0 deletions toolbox-context-menu.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,201 @@
    <#
    .SYNOPSIS
    Automatically add context menu entries for Jetbrains IDEs.
    .PARAMETER Name
    The name or names of the IDEs to add context menus for, use -List to see available IDEs.
    .PARAMETER BasePath
    The path to the Toolbox apps directory, defaults to "$env:LOCALAPPDATA\JetBrains\Toolbox\apps".
    .PARAMETER Global
    Install context menu entries in HKLM registry (machine wide), requires running as administrator.
    .PARAMETER Force
    Overwrite current registry entries, useful when updating existing entries.
    .PARAMETER Remove
    Will remove the context menu entries for specified IDEs
    .PARAMETER List
    List available IDEs installed via Toolbox
    .PARAMETER UseNircmd
    Will use nircmd (if installed) to invisibly run the IDE's batch script, this will avoid you having to
    re-run this script each time an IDE is updated via Toolbox.
    .PARAMETER NirCmdPath
    Specify the location of the nircmd executable to use, will attempt to locate it from $PATH if not specified.
    .PARAMETER AppDir
    If you are not using Toolbox you can specify the IDE's installation directory.
    #>

    [CmdletBinding()]
    param (
    [array]$Name,
    [string]$BasePath,
    [switch]$Global,
    [switch]$Force,
    [switch]$Remove,
    [switch]$List,
    [switch]$UseNircmd,
    [string]$NirCmdPath,
    [string]$AppDir
    )

    $toolbox = if ($BasePath) { $BasePath } else { Join-Path $env:LOCALAPPDATA "JetBrains\Toolbox\apps" }
    $regRoot = if ($Global) { "HKLM" } else { "HKCU" }

    if ($UseNircmd -and -not (Get-Command "nircmd.exe" -ErrorAction Ignore)) {
    Write-Error "nircmd.exe is not installed or missing from the path"
    Write-Error "either install nircmd or specify its location using '-NirCmdPath'"
    return
    }

    function Get-IDEs {
    $IDEs = @{}
    foreach ($item in (Get-ChildItem $toolbox -Exclude Toolbox)) {
    $versionPath = (Get-ChildItem -Path (Join-Path $item.FullName "ch-0") -Directory -Exclude "*.plugins" | Sort-Object BaseName -Descending | Select-Object -First 1 | Select-Object -ExpandProperty FullName)
    $binPath = Join-Path $versionPath "bin"
    if (Test-Path $binPath) {
    $IDEs[$item.BaseName] = $versionPath
    }
    }
    return $IDEs
    }

    if ($List) {
    return Get-IDEs
    }

    function Add-ShellKeys {
    param (
    [string]$Name,
    [string]$ExePath,
    [string]$Path,
    [string]$Action,
    [string]$LaunchArgs
    )

    $Path = Join-Path "${regRoot}:" $Path
    if ($UseNircmd) {
    # extract shell link path
    $scriptPath = Get-Content (Join-Path (Split-Path $ExePath) "../.." ".shellLink")

    # move icon outside of version directory so it will survive updates
    $baseName = (Split-Path $ExePath -LeafBase).Replace("64", "")
    $iconPath = Join-Path (Split-Path $ExePath) "$baseName.ico"
    $destPath = Join-Path (Split-Path (Split-Path (Split-Path $ExePath))) "$baseName.ico"
    Move-Item -Path $iconPath -Destination $destPath -ErrorAction Ignore
    $iconPath = "`"$destPath`",0"

    # contruct args
    $ExePath = if ($NirCmdPath) { $NirCmdPath } else { (Get-Command "nircmd.exe").Path }
    $LaunchArgs = "execmd $scriptPath $LaunchArgs"
    } else {
    $iconPath = $ExePath
    }

    Push-Location -LiteralPath $Path -ErrorAction Stop

    try {
    New-Item `
    -Path ".\$Name" `
    -Value "$Action with $Name" `
    -ErrorAction Stop `
    -Force:$Force `
    -Confirm:$false | Out-Null
    Write-Host "ADDED: $Path\$Name" -f DarkGreen
    } catch {
    Write-Host "EXISTS: $Path\$Name" -f Yellow
    }

    try {
    New-ItemProperty `
    -LiteralPath ".\$Name" `
    -PropertyType ExpandString `
    -Name "Icon" `
    -Value $iconPath `
    -ErrorAction Stop `
    -Force:$Force `
    -Confirm:$false | Out-Null
    Write-Host "ADDED: $Path\$Name [Icon]" -f DarkGreen
    } catch {
    Write-Host "EXISTS: $Path\$Name [Icon]" -f Yellow
    }

    try {
    New-Item `
    -Path ".\$Name" `
    -Name "command" `
    -Value "`"$exePath`" $LaunchArgs" `
    -ErrorAction Stop `
    -Force:$Force `
    -Confirm:$false | Out-Null
    Write-Host "ADDED: $Path\$Name\command" -f DarkGreen
    } catch {
    Write-Host "EXISTS: $Path\$Name\command" -f Yellow
    }

    Pop-Location
    }

    function Add-ContextMenu {
    param (
    [string]$Name,
    [string]$ExePath
    )

    # ensure base folders exist
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\*\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\Background\shell" -ErrorAction Ignore -Confirm:$false | Out-Null
    New-Item -Path "${regRoot}:\SOFTWARE\Classes\Directory\shell" -ErrorAction Ignore -Confirm:$false | Out-Null

    # add to file context menu
    Write-Output "ACTION: Edit with $Name (Files)"
    Add-ShellKeys $Name $ExePath "SOFTWARE\Classes\*\shell" "Edit" "`"%1`""

    # add to directory context menu
    Write-Output "ACTION: Open with $Name (Directory)"
    Add-ShellKeys $Name $ExePath "SOFTWARE\Classes\Directory\shell" "Open" "`"%1`""

    # add to directory background context menu
    Write-Output "ACTION: Open with $Name (Directory Background)"
    Add-ShellKeys $Name $ExePath "SOFTWARE\Classes\Directory\Background\shell" "Open" "`"%V`""
    }

    function Remove-Reg([string]$Path) {
    $Path = Join-Path "${regRoot}:" $Path
    if (-not (Test-Path -LiteralPath $Path)) {
    Write-Host "MISSING: $Path" -f Yellow
    } else {
    Remove-Item -LiteralPath $Path -Recurse -ErrorAction Stop
    Write-Host "REMOVED: $Path" -f Red
    }
    }

    function Remove-ContextMenu([string]$Name) {
    Remove-Reg "SOFTWARE\Classes\*\shell\$Name"
    Remove-Reg "SOFTWARE\Classes\Directory\shell\$Name"
    Remove-Reg "SOFTWARE\Classes\Directory\Background\shell\$Name"
    }

    function Start-ContextMenu([string]$AppDir) {
    $info = (Get-Content (Join-Path $AppDir "product-info.json") | ConvertFrom-Json)
    $friendlyName = $info.Name
    $exePath = Join-Path $AppDir $info.launch[0].launcherPath
    if ($Remove) {
    Write-Host "Removing Context Menu for '$friendlyName'" -ForegroundColor Cyan
    Remove-ContextMenu -Name $friendlyName
    } else {
    Write-Host "Adding Context Menu for '$friendlyName'" -ForegroundColor Cyan
    Add-ContextMenu -Name $friendlyName -ExePath $exePath
    }
    }

    if ($AppDir) {
    Start-ContextMenu $AppDir
    } else {
    $ides = (Get-IDEs)

    foreach ($ide in $ides.Keys) {
    foreach ($match in $Name) {
    if (($match -eq "*") -or ($ide.ToLower().StartsWith($match.ToLower()))) {
    Start-ContextMenu $ides[$ide]
    break
    }
    }
    }
    }