Search code examples
c#winformsautoplayusb-driverunning-object-table

AllowAutoPlay Never Gets Called


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?


Solution

  • 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,
    }