locked
VS 2012, powershell, and creating a custom menu RRS feed

  • Question

  • Using VS 2012, I'm trying to have some powershell script create a custom menu for me and fire off commands from the click events, but they don't seem to function. I thought I had this working successfully, but upon creating a new VS session and closing my old one, it no longer functions and I can't remember what I'm missing..

    Why isn't the click event getting triggered?

    $menubar = $dte.CommandBars["MenuBar"]
    $cmenu = $menubar.Controls.Add([Microsoft.VisualStudio.CommandBars.MsoControlType]::msoControlPopup, 98765432, "CUSTOM", 38, $false)
    $cmenu.Caption = "CUSTOM"
    $cmenu.Enabled = $true
    $item = $cmenu.Controls.Add([Microsoft.VisualStudio.CommandBars.MsoControlType]::msoControlButton, 987654321, "CUSTOM", 1, $false)
    $item.Enabled = $true
    $item.Caption = "CUSTOM"
    $itemEvents = $dte.Events.CommandBarEvents($item)
    $itemEventInterface = Get-Interface $itemEvents ([EnvDTE._dispCommandBarControlEvents_Event])
    $itemClickEvent = [EnvDTE._dispCommandBarControlEvents_ClickEventHandler]{ $dte.ExecuteCommand("Help.Samples") }
    $itemEventInterface.add_Click($itemClickEvent)

    I know the event is working because the Invoke fires correctly -- But I'm missing some registration somewhere I suppose.  Your help would be greatly appreciated

    $itemClickEvent.Invoke($menubar, [ref]$true, [ref]$false)

    Sunday, January 6, 2013 8:27 AM

Answers

  • You woud need to re-apply the click handler in every launched instance of VS doesn't (and can't) serialize/persist click handler as they consist of arbitrary code  and are a black box to VS. Doing things via PowerShell isn't really a supported extensibility mechanism and likely won't work well for reasons like this. If you  have an AddIn or Package there is a place VS goes to load these things and you can run any code you like in those instances. In PowerShell you are invoking into VS 'from the outside' and we have no idea where you are calling from or any way to 'get back in touch with you' in the future (say on next launch) if we needed you to run some code again.

    I assume the sample above is just a repro and not your real case? If it is your real case it almost never makes sense in VS to create a custom button that just calls an existing command, you can just get that Command object and call AddControl to make a new button that will execute that command.

    • Marked as answer by Ego Jiang Tuesday, January 15, 2013 5:09 AM
    Sunday, January 6, 2013 4:50 PM
  • Are you somehow rooting the events object? Due to how the CLR does COM interop connection point containers (the COM equiavalent of an event) cause temporary .NET objects to be spun up and that temporary object is what holds your managed object callback. If the temporary gets GCed your event will never be invoked. I would store both the Events and the CommandBarEvents objects in variables to ensure theyare rooted. It may also depend on what Powershell is doing here since you are talking about cross-process invocation (i.e. the CLR object is a COM object and the object given to Powershell is a proxy), when VS tries to invoke the handler it would be  cross-process call back into Powershell.
    Sunday, January 6, 2013 6:28 PM
  • Yes, that is what I meant, though to be extra sure you would probably want to say $cbe = allEvents.CommandBarEvents.

    There is no such thing as a property in COM and dte is a proxy. 

    Ryan

    • Marked as answer by Ego Jiang Tuesday, January 15, 2013 5:09 AM
    Monday, January 7, 2013 3:22 AM

All replies

  • You woud need to re-apply the click handler in every launched instance of VS doesn't (and can't) serialize/persist click handler as they consist of arbitrary code  and are a black box to VS. Doing things via PowerShell isn't really a supported extensibility mechanism and likely won't work well for reasons like this. If you  have an AddIn or Package there is a place VS goes to load these things and you can run any code you like in those instances. In PowerShell you are invoking into VS 'from the outside' and we have no idea where you are calling from or any way to 'get back in touch with you' in the future (say on next launch) if we needed you to run some code again.

    I assume the sample above is just a repro and not your real case? If it is your real case it almost never makes sense in VS to create a custom button that just calls an existing command, you can just get that Command object and call AddControl to make a new button that will execute that command.

    • Marked as answer by Ego Jiang Tuesday, January 15, 2013 5:09 AM
    Sunday, January 6, 2013 4:50 PM
  • Correct, it is just a test command inside the event handler to execute a command that I know works to test the firing of the click event, which for some reason is not happening anymore. I did have a similar piece of code working, but it is no longer triggering and I'm not sure what I'm leaving out, or doing differently this time around.

    Yeah It would need reapplication on each new launch but I'm okay with that



    Sunday, January 6, 2013 5:25 PM
  • I can't recall which things in DTE are presisted and which aren't, I think named commands are persisted but 'anonymous' controls like you are adding above are not. Are you sure none of the steps before you add the click handler are throwing an exception on runs after the first?


    EDIT: Also when you say the click handler 'isn't firing' how are you inivoking it? Via physically clicking on the item in the menu/toolbar you created it on?

    Sunday, January 6, 2013 6:03 PM
  • Yeah actually clicking on it inside the popup menu does not trigger anything.   I'm not aware of any exceptions occuring from the above piece of code. But the Invoke on the event works:

    $itemClickEvent.Invoke($menubar, [ref]$true, [ref]$false)

    , so I'm not certain why the actual menu click isn't getting hooked up correctly. I'm missing something somewhere

    Sunday, January 6, 2013 6:12 PM
  • Are you somehow rooting the events object? Due to how the CLR does COM interop connection point containers (the COM equiavalent of an event) cause temporary .NET objects to be spun up and that temporary object is what holds your managed object callback. If the temporary gets GCed your event will never be invoked. I would store both the Events and the CommandBarEvents objects in variables to ensure theyare rooted. It may also depend on what Powershell is doing here since you are talking about cross-process invocation (i.e. the CLR object is a COM object and the object given to Powershell is a proxy), when VS tries to invoke the handler it would be  cross-process call back into Powershell.
    Sunday, January 6, 2013 6:28 PM
  • So you mean something like 

    $allEvents = $dte.Events
    $cbe = $dte.Events.CommandBarEvents 

    or maybe I'm misintepreting?

    Sunday, January 6, 2013 7:29 PM
  • Yes, that is what I meant, though to be extra sure you would probably want to say $cbe = allEvents.CommandBarEvents.

    There is no such thing as a property in COM and dte is a proxy. 

    Ryan

    • Marked as answer by Ego Jiang Tuesday, January 15, 2013 5:09 AM
    Monday, January 7, 2013 3:22 AM