1

I have a WinForms application. Just before creating the actual form in Program.cs, I instantiate an Autoplay class. Registration is successful, after the obligatory first return value of 65536, but I never get any calls to AllowAutoPlay().

Am I missing something?

Here is the code:

public class RunningObjectTableEntry : IDisposable
{
    private const int ROTFLAGS_REGISTRATIONKEEPSALIVE = 1;

    private HRESULT cookie;
    private IRunningObjectTable rot = null;
    private IMoniker monkey = null;

    private RunningObjectTableEntry() { }

    public RunningObjectTableEntry(object obj)
    {
        this.AddToROT(obj);
    }

    public void AddToROT(object obj)
    {
        int hr = GetRunningObjectTable(0, out rot);
        if (hr != 0)
        {
            throw new COMException("Could not retrieve running object table!", hr);
        }

        Guid clsid = obj.GetType().GUID;

        hr = CreateClassMoniker(ref clsid, out monkey);

        if (hr != 0)
        {
            Marshal.ReleaseComObject(rot);
            throw new COMException("Could not create moniker for CLSID/IID \"" + clsid + "\"!", hr);
        }

        UInt32 iResult = (UInt32)rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, obj, monkey);   // Weak reference, but allow any user

        if (65536 == iResult)
            iResult = (UInt32)rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, obj, monkey);

        cookie = (HRESULT)iResult;
    }

    public void RemoveFromROT()
    {
        if (cookie != 0)
        {
            try
            {
                // Get the running object table and revoke the cookie
                rot.Revoke((int)cookie);
                cookie = 0;
            }
            finally
            {
                if (rot != null) while (Marshal.ReleaseComObject(rot) > 0) ;
            }
        }
    }

    [DllImport("ole32.dll", ExactSpelling = true)]
    private static extern int GetRunningObjectTable([MarshalAs(UnmanagedType.U4)] int reserved, out IRunningObjectTable pprot);

    [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    private static extern int CreateClassMoniker([In] ref Guid g, [Out] out IMoniker ppmk);

    #region IDisposable Members

    public void Dispose()
    {
        if (null != monkey)
            Marshal.ReleaseComObject(monkey);
        rot.Revoke((int)cookie);
        Marshal.ReleaseComObject(rot);
    }

    #endregion
}

[ComVisible(true)]
[Guid("331F1768-05A9-4ddd-B86E-DAE34DDC998A")]
[ClassInterface(ClassInterfaceType.None)]
public class Autoplay : IQueryCancelAutoPlay, IDisposable
{
    private RunningObjectTableEntry rotEntry;

    public Autoplay()
    {
        rotEntry = new RunningObjectTableEntry(this);
    }

    public void RemoveFromROT()
    {
        this.rotEntry?.RemoveFromROT();
    }
    #region IQueryCancelAutoPlay Members

    public int AllowAutoPlay(string pszPath, AutorunContent dwContentType, string pszLabel, int dwSerialNumber)
    {
        String msgUser = $"AllowAutoPlay: Path={pszPath}, ContentType={dwContentType.ToString()}, Label={pszLabel}, SerialNumber={dwSerialNumber.ToString()}";
        System.Diagnostics.Debug.WriteLine(msgUser);
        MessageBox.Show(msgUser);
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
        rotEntry.Dispose();
    }

    #endregion
}

The cookie on the second call is fine, consistent, but fine at 131073 or 0x00020001.

I used the following articles: Prevent Autoplay, 65536 error, and CodeProject.

Neither a breakpoint or the message box shows.

I am running on Windows 10 using Visual Studio 2017.

Thoughts?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Sarah Weinberger
  • 15,041
  • 25
  • 83
  • 130
  • Have you tried this editing the registry or overriding `WndProc`? See here: [Enabling and Disabling AutoRun](https://learn.microsoft.com/en-us/windows/desktop/shell/autoplay-reg), two modes are listed. About `WndProc`, I've posted (work in progress) some code that just happens to [include this feature](https://stackoverflow.com/questions/52598371/catch-usb-plug-and-unplug-event-system-invalidcastexception?answertab=active#tab-top). Include a [RegisterWindowMessageW](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerwindowmessagew) call. If it fits here. – Jimi Oct 02 '18 at 06:39
  • @Jimi The code you recommended does indeed show USB drive insertion and removal messages perfectly, HOWEVER, I do not receive the QueryCancelAutoPlay messages. Yes, I saw the note on Expert-Exchange, which says that Windows sends that message to the foreground window. Maybe because I have multiple child forms showing on my main form. – Sarah Weinberger Oct 02 '18 at 17:17
  • @Jimi I verified using a test application that Windows sends only the top layer form in an application, not every form in an application, the 50419 QueryCancelAutoPlay message. So if an application has multiple forms that make one big form, then all of them will need to respond to this code or redirect to the bottom form. That is annoying. – Sarah Weinberger Oct 02 '18 at 17:33

2 Answers2

1

The Exchange Expert response is the answer, namely

dbtoth Author Commented: 2003-07-30 The above is working fine except for one small glitch... because the code only works when the window has focus,

A key element worth noting is "window". The original that I gave in my question works great with only one form. My main applications has several forms packed together, so if any of them have the focus, then the code will not work.

The code above and the WndProc variant rely on Windows sending the QueryCancelAutoPlay message, which only occurs to the top-layer form, which may NOT be the form that you think.

My applications creates firsts first a FrmMain, but on top of that I have various child forms. Only the top-most form (window) gets the message, which means that to be safe that all child forms need one of the forms of QueryCancelAutoPlay.

Sarah Weinberger
  • 15,041
  • 25
  • 83
  • 130
  • You need to register `CancelAutoPlay` using your Main window (the starting one). Pass the value returned in `WndProc` as `m.Msg = [CancelAutoPlay]` to each window that needs to answer to any further message received in **their own** `WndProc`. The windows that process the message should return `m.Result = (IntPtr)1;`. This way, AutoPlay will be disabled for each windows that processes the message (= to the initial value of CancelAutoPlay as received by the first window that registered "CancelAutoPlay" with `RegisterWindowMessage`). – Jimi Oct 02 '18 at 19:39
  • @Jimi That is exactly what I said, though each form can register to receive its own messages and clean up in the Form_Closing. Either way, the concept is that each new Form() in the application has to trap the QueryCancelAutoPlay in order for the application to properly detect the message. – Sarah Weinberger Oct 02 '18 at 19:42
  • 1
    Yes, that's right. What I was pointing out is that overriding `WndProc`, using a common (shared) value of the initial `CancelAutoplay` returned vaue, is a much more straightforward method. Because that registration is application-wide, event though the message is sent, as usual, to the foreground window. You probably have already overriden `WndProc` in your `Forms`. You just need to add a check like: `if ((int)m.Msg == [TheSharedCancelAutoPlayValue]) { m.Result = (IntPtr)1; }` and that's it. AutoPlay disabled for that window. – Jimi Oct 02 '18 at 19:48
  • 1
    @Jimi Having a common shared `CancelAutoply` message is a bit of a time and memory saving. That just means that each form in the application needs to override the `WndProc`. Heck, each `WndProc override` can call a shared `handler`, which has that if-statement you and `pinvoke.net` mentioned. I like it. Thanks. – Sarah Weinberger Oct 02 '18 at 19:54
0

My first answer is the technical answer, which answers the specific question, however the first answer does NOT address the problem.

I struggled and finally found a real solution, which I wanted to share.

My test application with the solution DOES receive the QueryCancelAutoPlay message, however my real application does NOT. I used the Windows SDK Inspect utility, added the WndProc() to every form and nothing.

I also do not like the only the active window gets QueryCancelAutoPlay message. If the user happens to shift to another application momentarily, then this talked method will not work.

I once started down the path of the answer mentioned here and for whatever reason abandoned it.

I now have 2 ComboBox controls in the setup area. One holds the Windows default, while the other is for the application. I then set the application upon launch to the application version, and upon application exit I reset to the Windows default option, which I stored in the ComboBox.

Works great.

private const String RegKey_UserChosen_StorageOnArrival = @"Software\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers\UserChosenExecuteHandlers\StorageOnArrival";
private const String RegKey_Event_StorageOnArrival = @"Software\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers\EventHandlersDefaultSelection\StorageOnArrival";
private const String RegValue_NoAction = @"MSTakeNoAction";
private const String RegValue_OpenFolder = @"MSOpenFolder";

public static Boolean SetExplorerAutoplay(String regValue)
{
    try
    {
        // Open first key needed.
        using (RegistryKey oKey = Registry.CurrentUser.OpenSubKey(ExplorerAutoplay.RegKey_UserChosen_StorageOnArrival, true))
        {
            // Set the default value. To set the default value do not use "(Default)", but rather leave blank.
            oKey.SetValue(String.Empty, regValue);
        }

        // Open second key needed.
        using (RegistryKey oKey = Registry.CurrentUser.OpenSubKey(ExplorerAutoplay.RegKey_Event_StorageOnArrival, true))
        {
            // Set the default value. To set the default value do not use "(Default)", but rather leave blank.
            oKey.SetValue(String.Empty, regValue);
        }

        return true;
    }

    catch (Exception)
    {
    }

    return false;
}

public static Boolean GetExplorerAutoplay(out AutoPlayDriveAction action, out String regValue)
{
    action = AutoPlayDriveAction.Invalid;
    regValue = null;
    try
    {
        // Only one of the keys is necessary, as both are the same.
        using (RegistryKey oKey = Registry.CurrentUser.OpenSubKey(ExplorerAutoplay.RegKey_UserChosen_StorageOnArrival, true))
        {
            // Get the default value.
            object oRegValue = oKey.GetValue(String.Empty);
            regValue = oRegValue?.ToString();
            if (true == regValue.Equals(ExplorerAutoplay.RegValue_NoAction))
                action = AutoPlayDriveAction.TakeNoAction;
            else if (true == regValue.Equals(ExplorerAutoplay.RegValue_OpenFolder))
                action = AutoPlayDriveAction.OpenFolder;
        }

        return true;
    }

    catch (Exception)
    {
    }

    return false;
}

public enum AutoPlayDriveAction
{
    Invalid,
    TakeNoAction,
    OpenFolder,
}
Sarah Weinberger
  • 15,041
  • 25
  • 83
  • 130