Adding Windows 10 app to the Autoplay detection list fails

0

My WinForms application talks to a piece of hardware via USB, which has embedded in it two disks. I want to prevent Windows from displaying the usual Autoplay (pops up File Explorer in my case) on any of my unit's disks.

I found this question from 2009, which makes reference to this Microsoft article. I implemented the class and called it first from the Programs entry point and then from my main WinForms forms class constructor. It did not work. I debugged and see that I get -1 (65536) from the IRunningObjectTable.Register call.

I am testing inside Visual Studio 2017 (Debug, X86), though I plan to release as X64, neither mode makes a difference. Here is a screenshot of the debug session at the failure point.

Failure at the IRunningObjectTable.Register() call

Here is how I call the code. The presence of the ComVisible makes no difference, just my last debugging attempt included that, as the last post in the article mentions that the calling class should have it.

[ComVisible(true)]
[Guid("9905b600-ee9a-4acf-bad0-5ae09698fec2")]
[ProgId("MyApp.Forms.FrmMain")]
public partial class FrmMain : XtraForm
{
    public FrmMain()
    {
        InitializeComponent();

        Autoplay oAutoplay = new Autoplay();
    }

This event never gets called.

public int AllowAutoPlay(string pszPath, AutorunContent dwContentType, string pszLabel, int dwSerialNumber)
{
    // This test is the name of my volume that should not call autoplay.
    if (true == "whatever1".Equals(pszLabel, StringComparison.CurrentCultureIgnoreCase) ||
        true == "whatever2".Equals(pszLabel, StringComparison.CurrentCultureIgnoreCase))
        return 1;
    else
        return 0;
}

Here is the complete code, for thoroughness sake.

[Flags]
public enum AutorunContent : int
{
    AutorunInf = 2,
    AudioCD = 4,
    DVDMovie = 8,
    BlankCD = 16,
    BlankDVD = 32,
    UnknownContent = 64,
    AutoPlayPictures = 128,
    AutoPlayMusics = 256,
    AutoPlayMovies = 512
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("DDEFE873-6997-4e68-BE26-39B633ADBE12")]
public interface IQueryCancelAutoPlay
{
    [PreserveSig]
    int AllowAutoPlay(
      [MarshalAs(UnmanagedType.LPWStr)] string pszPath,
      [MarshalAs(UnmanagedType.U4)] AutorunContent dwContentType,
      [MarshalAs(UnmanagedType.LPWStr)] string pszLabel,
      [MarshalAs(UnmanagedType.U4)] int dwSerialNumber);
}

enum HRESULT : uint
{
    S_FALSE = 0x0001,
    S_OK = 0x0000,
    E_INVALIDARG = 0x80070057,
    E_OUTOFMEMORY = 0x8007000E
}

/// <summary>
/// https://stackoverflow.com/questions/10831114/how-to-prevent-autoplay-and-run-my-own-app-when-inserting-an-usb-flash-drive
/// </summary>
public class RunningObjectTableEntry : IDisposable
{
    private readonly HRESULT cookie;
    private IRunningObjectTable rot = null;
    private readonly IMoniker monkey = null;

    private RunningObjectTableEntry() { }

    /// <summary>
    /// Creates a new entry for the given object
    /// </summary>
    /// <param name="obj">Object to make an entry for. Only one object per class should ever be registered.</param>
    public RunningObjectTableEntry(object obj)
    {
        int hr = GetRunningObjectTable(0, out rot);
        if (hr != 0)
        {
            throw new COMException("Could not retreive 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);
        }

        cookie = (HRESULT)rot.Register(0x01, obj, monkey);   // Weak reference, but allow any user
        switch (cookie)
        {
            case HRESULT.S_FALSE:
                break;
            case HRESULT.S_OK:
                break;
            case HRESULT.E_INVALIDARG:
                break;
            case HRESULT.E_OUTOFMEMORY:
                break;
            default:
                break;
        }
    }

    [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

    /// <summary>
    /// De-registers the object and class from the Running Object Table
    /// </summary>
    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);
    }

    #region IQueryCancelAutoPlay Members

    public int AllowAutoPlay(string pszPath, AutorunContent dwContentType, string pszLabel, int dwSerialNumber)
    {
        // This test is the name of my volume that should not call autoplay.
        if (true == "whatever1".Equals(pszLabel, StringComparison.CurrentCultureIgnoreCase) ||
            true == "whatever2".Equals(pszLabel, StringComparison.CurrentCultureIgnoreCase))
            return 1;
        else
            return 0;
        //Console.WriteLine("QueryCancelAutoPlay:");
        //Console.WriteLine("   " + pszPath);
        //Console.WriteLine("   " + dwContentType.ToString("x"));
        //Console.WriteLine("   " + pszLabel);
        //Console.WriteLine("   " + dwSerialNumber.ToString());
    }

    #endregion

    #region IDisposable Members

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

    #endregion
}

I added the HRESULT enum just to see the return values during debugging. I took the definition from PinVoke.

NOTES & UPDATES

  1. I came across this question, which talked of IRunningObjectTable.Register returning 65536. I just now verified this behavior that the first call returns 65536, but subsequent calls return what appears to be a cookie handle. Sadly, I do NOT receive a call to the AllowAutoPlay event handler.

  2. The Code Project article said something important, which the SO question did not mention and I almost missed, but which sadly still did not resolve my issue, namely that the developer must:

    a) Sign the .Net assembly

    b) Use regasm.exe to register the .Net assembly as a COM object on the system. The syntax is:

    "C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe" MyNetAssembly.exe /codebase /tlb

Thoughts?

c#
windows
winforms
com
asked on Stack Overflow Jun 21, 2018 by Sarah Weinberger • edited Jun 22, 2018 by Sarah Weinberger

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0