Search code examples
windowspowershellautomationregistryui-automation

Execute application as context menu action with parameters


I want to programatically start an application in the same manner as done when using a specific context menu action, on Windows 10.

enter image description here

The context menu is provided by the application Proxifier, so from my research I was able to find this in my Registry, which I believe is the related context menu entry.

enter image description here

However there is only one visible parameter and it seems to be the path to the requested user profile to initialize Proxifier settings.

enter image description here

Tried a few different combinations and couldn't really figure anything out there... so the other thing I had thought of was examining the window messages sent to Proxifier when clicking the context menu, with Spy++.

Which I've done, I just can't seem to decipher the wParam or lParam or what info I'd send in order to select the correct sub-menu action.

enter image description here

Any help here would be very much appreciated, thanks!


Solution

  • I was unable to figure out how to get it working by accessing the context menu which I know is technically your question. However, I believe I have a workaround which hopefully could be of use to you.

    This workaround involves copying your proxifier profile (which is just an xml formatted file) and creating a new profile. On the new profile we create a new rule with your specified application and desired action (direct,block,proxy) and insert it at the top of the rule list so that the rule is executed first. The application is then launched. The script will wait for the application to be closed and then it will re-select the old profile.

    For example running the script to block an application would be this:

     .\proxifier.ps1 -Block -filePath "C:\Program Files\VideoLAN\VLC\vlc.exe"
    # the output
    Block C:\Program Files\VideoLAN\VLC\vlc.exe
    Load temp profile    
    # will wait until app is closed
    Reload original profile # once you close the app
    

    The application is added to top of rulelist.

    enter image description here

    You will see proxifier loading the new profile and then re-loading the old profile when the application is closed in the proxifier log window.

    [03.30 11:09:20] Profile test-temp loaded.
    [03.30 11:09:32] Profile test loaded.
    

    --

    The script has these options for rules which are the options for rule actions in proxifier (block,direct,proxy):

    .\proxifier.ps1 -Block -filePath "C:\Program Files\VideoLAN\VLC\vlc.exe"
     .\proxifier.ps1 -Direct -filePath "C:\Program Files\VideoLAN\VLC\vlc.exe"
     .\proxifier.ps1 -Proxy -proxyId yourproxyID -filePath "C:\Program Files\VideoLAN\VLC\vlc.exe"
    

    Proxifier saves your proxy list and has an id for each. So when using the proxy parameter you need the id number for your desired proxy (e.g. 100,101). To get the list run:

    .\proxifier.ps1 -getProxyID
     
     GetID
    
    
    id      : 100
    type    : HTTPS
    Options : 48
    Port    : 8080
    Address : 127.0.0.1
    
    id      : 101
    type    : SOCKS5
    Options : 48
    Port    : 8080
    Address : 10.1.0.1
    

    Change the profilePath and savePath to your desired location. I've tested it and it does successfully set the rules for all actions.

    # add error handling for incorrect file paths
    param(
        [parameter(Mandatory=$false,Position=0,ParameterSetName="Block")]    
        [switch]$Block,
        [parameter(Mandatory=$false,Position=0,ParameterSetName="Direct")]
        [switch]$Direct,
        [parameter(Mandatory=$false,ParameterSetName="ProxyAction",Position=0)]
        [switch]$Proxy,
        [parameter(Mandatory=$true,ParameterSetName="ProxyAction")]
        [string]$proxyId,
        [parameter(Mandatory=$true,Position=1,ParameterSetName="Direct")]
        [parameter(Mandatory=$true,Position=1,ParameterSetName="Block")]
        [parameter(Mandatory=$true,Position=1,ParameterSetName="ProxyAction")]
        [string]$filePath,
        [parameter(Mandatory=$false,ParameterSetName="proxyID")]
        [switch]$getProxyID
    
    )
    
    #path to the proxifer profile you want to copy (change as desired)
    $profilePath = "C:\Users\userName\AppData\Roaming\Proxifier4\Profiles\fielName.ppx"
    #path to existing proxifier exe (change as desired)
    $proxifierPath = "C:\Program Files (x86)\Proxifier\Proxifier.exe"
    #save path for temp proxifier profile (change as desired)
    $savePath = "C:\Users\userName\AppData\Roaming\Proxifier4\Profiles\fileName-temp.ppx"
    
    if ($getProxyID){
    Write-host "GetID"
    [xml]$xmlProxyID = Get-Content -Path $profilePath
    Write-Output $xmlProxyID.ProxifierProfile.ProxyList.Proxy 
    }
    else{
        if ($block){
            $action = "Block" 
        }
        elseif ($direct) {
            $action = "Direct" 
        }
        elseif ($Proxy){
            $action = "Proxy" 
        }
        $ruleName = "My Name" > $null
    
        Write-Host $action, $filePath
        if (!($filePath.Contains("`""))){
            $filePath = '"{0}"' -f $filePath
        }
    # create new element for the rule and set action
    [xml]$xml = Get-Content -Path $profilePath
    $newElement = $xml.CreateElement("Rule")
    $newElement.SetAttribute("enabled","true") 
    $childAction = $xml.CreateElement("Action") 
    $childAction.SetAttribute("type",$action) 
    
    # set proxy setting if using
    if ($proxy){
        $childAction.InnerText = $proxyId 
    }
    
    # set application path
    $childApplication = $xml.CreateElement("Applications")
    $childApplication.InnerText =  $filepath
    
    # $childTargets = $xml.CreateElement("Targets")()
    # $childTargets.InnerText = "Any"
    
    # rule name
    $childName = $xml.CreateElement("Name")
    $childName.InnerText =  $ruleName 
    
    # appened items to element
    $newElement.AppendChild($childAction) | Out-Null
    $newElement.AppendChild($childApplication) | Out-Null
    # $newElement.AppendChild($childTargets)
    $newElement.AppendChild($childName) | Out-Null
    
    # insert new element to top of rule list
    $topRule = $xml.ProxifierProfile.RuleList.Rule[0] 
    $parent = $topRule.ParentNode 
    $parent.InsertBefore($newElement,$topRule) | Out-Null
    $xml.Save($savePath) | Out-Null
    
    Write-Output "Load temp profile"
    Start-Process -FilePath $proxifierPath -ArgumentList $savePath,"silent-load"
    
    # start specified program
    $proc = Start-Process -FilePath $filePath -PassThru
    
    
    # wait until program closes
    while (!($proc.HasExited)) {
        Start-Sleep -Seconds 2
        
    }
    
    # re-load original profile
    Write-Output "Reload original profile"
    Start-Process -FilePath $proxifierPath -ArgumentList $profilePath,"silent-load"
    
    
    
    }
    

    This way won't work with proxifier. But could potentially work in certain use cases like in my example here with 7-zip. So i'll just leave this part.

    It seems very difficult to access certain items in a file's context menu (or maybe even impossible) in powershell. I am not able to currently install Proxifier but I have been testing with 7-zip. I found this blog post which details how to access a file's context menu items in powershell. The issue, which is also mentioned in the post is that some items just can't be accessed easily (items with sub menus don't seem to show up with this method).

    An example using the code from the post outputs the following:

    $o = New-Object -com Shell.APplication
    $folder = $o.NameSpace("C:\Users\myuser\Downloads\")
    $file = $folder.ParseName("test.zip")
    
    # list file's verbs (context menu items)
    $file.Verbs()
    # the output
    Application Parent Name
    ----------- ------ ----
                       &Open
                       &test
    
                       Edit with &Notepad++
                       Share
    
    
                       Scan with Sophos Endpoint
                       Restore previous &versions
    
                       Cu&t
                       &Copy
                       Create &shortcut
                       &Delete
                       Rena&me
                       P&roperties
    

    You can see the difference in that output to the screenshot of the context menu of the same file you'll see the "7-zip" option missing. Which most likely would be the case for Proxifier as well:

    context menu example

    So, my workaround, although not very elegant is create a new context menu item (link from @mklement0 comment on your post). The following will create a new context menu item called customCommand. It uses the similarly formatted command that you mention but with 7-zip C:\Program Files\7-Zip\7zFM.exe "%1" to open a file.

    New-Item -Path  Registry::HKEY_CLASSES_ROOT\*\shell\customCommand
    New-Item -Path  Registry::HKEY_CLASSES_ROOT\*\shell\customCommand\command
    New-ItemProperty -LiteralPath Registry::HKEY_CLASSES_ROOT\*\shell\customCommand\command -name "(default)" -PropertyType String -value "C:\Program Files\7-Zip\7zFM.exe `"%1`""
    

    Running the above will create the following registry entry: registry clip

    You can see the new context menu item, customCommand is added. It will be added for all files:

    context menu example

    Re running the $file.verbs() command from above we can see that our new entry is now listed (partial output):

    Application Parent Name
    ----------- ------ ----
                       &Open
                       &customCommand # our newly added item
                       &testing
    

    Now we can access that item by running:

     $file.InvokeVerb("customCommand")
    

    Now putting it all together we can:

    • create context menu item by adding a registry entry
    • run the commands to access and invoke a file's context menu item
    • remove the registry entry to clean back up the context menu

    The code:

    #change the folder path and file name
    $folderPath = "C:\Users\somepath\Downloads\"
    $fileName = "somefile.exe"
    #I do not have Proxifier installed but looks like it is similar to the 7-zip command. (copied from your screenshot)
    $theCommand = "C:\Program Files (x86)\Proxifier\Proxifier `"%1`""
    
    # example tested working with 7-zip
    #$theCommand = "C:\Program Files\7-Zip\7zFM.exe `"%1`""
    
    if ((Test-Path -LiteralPath $folderPath) -AND (Test-Path -LiteralPath ($folderPath + $fileName))){
    if (!(Test-Path -LiteralPath Registry::HKEY_CLASSES_ROOT\*\shell\customCommand)){
        # create registry entries for the contex menu item
        Write-Host "Creating Registery Entry"
        New-Item -Path  Registry::HKEY_CLASSES_ROOT\*\shell\customCommand | Out-Null
        New-Item -Path  Registry::HKEY_CLASSES_ROOT\*\shell\customCommand\command | Out-Null
        New-ItemProperty -LiteralPath Registry::HKEY_CLASSES_ROOT\*\shell\customCommand\command -name "(default)" -PropertyType String -value $theCommand  | Out-Null
    }
    
    # access and invoke the context menu item
    Write-Host "Opening file with command"
    $o = New-Object -com Shell.APplication
    $folder = $o.NameSpace($folderPath)
    $file = $folder.ParseName($fileName)
    $file.InvokeVerb("customCommand") 
    
    
    
        if ((Test-Path -LiteralPath Registry::HKEY_CLASSES_ROOT\*\shell\customCommand\command)){
            # remove the context menu item
            Write-Host "Removing Registry Entries"
            Remove-Item -LiteralPath Registry::HKEY_CLASSES_ROOT\*\shell\customCommand\command | Out-Null
            Remove-Item -LiteralPath Registry::HKEY_CLASSES_ROOT\*\shell\customCommand | Out-Null
        }
    }
    else{
        Write-Host "Path doesn't exist. Registry entries not created"
    }
    

    Output on command line will be the following (and program should launch):

    example output

    This theoretically should open Proxifier how you want it to. It should also work with other programs in a similar way if they follow the same format as 7-zip.