The Zune Experience Part 2

by Administrator 28. February 2008 15:50

I downloaded the update package and installed from that, no luck.  I started searching for other people with the same problem and found that it is quite common.  I passed over some of the insane "fixes" in favor of trying some of the easier approaches.  Apparently, the Zune installer requires that the Windows Firewall service be running during installation.  What a bizarre dependency.  I'm all Zuned up now and liking it.  Now my wife wants one.

Tags:

The Zune Experience Part 1

by Administrator 27. February 2008 20:37

Got the Zune today.  So far I am underwhelmed.  My very fast and very up to date Vista Laptop failed to install the Zune software the first time.  The error code explains this could be one of a number of thigns: .net 2, encoders and a lot of other developer-sounding nonsense.  Since the Zune installer checks for updates etc. this is unacceptable.  I am directed to a page with a svelte 173mb download to get all of the correct versions at once. 

Downloading...

Tags:

Invoking a .NET program on Device Insert

by Administrator 26. February 2008 17:32

Invoking a .NET program on Device Insert

Given the popularity of the .NET Platform it seems odd that Microsoft does not provide easier means of doing certain incredibly useful and easy to imagine tasks from the .NET framework.  It took what seems like a ridiculous amount of research to accomplish a simple task:

Lots of programs come up as ‘launch’ options when I insert a storage card/camera/video camera/PDA/Mp3Player/younameit device into a USB port.  I’d like my .NET program to be in that list and to run if the user selects it.

File Types & WIA

The most useful thing you get in Visual Studio towards this goal is file types.  You can build an MSI for your desktop application and make your “.damon” files open with this program and have right clickàOpen appear in the shell context menu for “.damon” files.  I’ve written before about how to do multiple-select and some useless and goofy things with this scenario, but this doesn’t get us anywhere near where we want to be. 

WIA is very useful for media-specific actions like this, and handles the “something was plugged in” scenario in specific cases.  There is a WIA Automation Layer and managed wrappers for this.  Obviously if you care about something besides JPGs and AVIs WIA may not work for you.  Also, WIA requires that the device you’re speaking to has a WIA driver.  WIA drivers are certainly very common for the appropriate device types, but when you are distributing desktop software that depends on an OEM’s drivers you’re stepping into a world of pain (Not a world of Payne, which is actually a wonderful place).    Can the OEM give you merge-modules for the install?  Are their drivers buggy?  Be sure to treat your Support Group very well if you go down this path.

The Problem Solution: Shell Extensions and Managed Code

I’m going to focus on a simple subset of the overall problem.  Despite the perceived simplicity of the final solution, I couldn’t find a single working example of this online so if you got here via Goolge you’ve hit the jackpot.   Suppose you have an SD card, an SD card reader, and some family photos on said SD card.  You would like a .NET program to automatically create thumbnails of these when the SD card is inserted into your card reader.

Step 1

Step 1 is to read some obscure documentation found here: http://msdn.microsoft.com/msdnmag/issues/01/11/autoplay/

The first part of this is easy to follow. The Windows Autoplay functionality mostly resides in the sub keys of HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\AutoplayHandlers\EventHandlers\

Oddly enough, the names of these sub-keys are almost meaningful and you can do some Googling Windows Live Searching to get an idea of what exactly will cause this or that handler to be invoked.  In our case, the key we want for XP and Vista is ShowPicturesOnArrival.  Windows scans the SD card after it’s inserted and finds JPGs or whatever and checks this sub key to determine what choices the Shell should offer the user.  What can be gleaned from the MSDN article in Step 1 is that we can make up a Handler name, so I’ll call mine TestPhotoHandler.  This “TestPhotoHandler” must correspond to a sub key of HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\AutoplayHandlers\Handlers (../../Handlers from ShowPicturesOnArrival), which will in turn contain some values that describe what TestPhotoHandler is to the Windows Shell.

Step 2

Step two is to populate our Handler.

·         Action=what will appear to the user they are doing with this handler. 

·         DefaultIcon=Pretty self explanatory.  You can also use the Foo.exe,-1 syntax to specify an embedded resource. 

·         Provider=Name of your program

·         InvokeProgID, InvokeVerb=the interesting part, keep reading.

o   Note that it would be FAR too easy to just let you include the path to your .exe or .dll here…

With just the above work done, you can see the following behavior in Windows:

Step 3

Telling Windows where to go from here is far more difficult than it needs to be, and I could not find a single working example of getting this stuff to work with managed code.  In addition to telling Windows how to find our .NET program, Windows must be able to communicate with our program.  There are three interfaces that are interesting for these purposes:

1.       IHWEventHandler

2.       IHWEventHandler2

3.       IDropTarget

These are COM interfaces.  Because I keep meaning to get back into C++ development I always install C++ accessories in Visual Studio.  This means I have the various COM header and IDL files installed, and I can dig up some useful information.  Recall that .NET classes can implement COM interfaces as long as you know the GUID of the COM interface.  Here, then, is my IDropTarget interface definition for .NET objects.

    [ComImport,

     InterfaceType(ComInterfaceType.InterfaceIsIUnknown),

     Guid("00000122-0000-0000-C000-000000000046")]

    public interface IDropTarget

    {

        [PreserveSig()]

        int DragEnter(IntPtr pDataObj,               //  IDataObject

                      ulong grfKeyState,             //  DWORD

                      POINTL pt,

                      ref ulong pdwEffect);          //  DWORD*

 

        [PreserveSig()]

        int DragOver(ulong grfKeyState,              //  DWORD

                     POINTL pt,

                     ref ulong pdwEffect);           //  DWORD*

 

        [PreserveSig()]

        int DragLeave();

 

        [PreserveSig()]

        int Drop(IntPtr pDataObj,                    //  IDataObject

                 ulong grfKeyState,                  //  DWORD

                 POINTL pt,

                 ref ulong pdwEffect);               //  DWORD*

 

    }

 

    [StructLayout(LayoutKind.Sequential)]

    public struct POINTL

    {

        public POINTL(long xx, long yy) { x = xx; y = yy; }

        public long x;

        public long y;

        public override string ToString()

        {

            String s = String.Format("({0},{1})", x, y);

            return s;

        }

    }

 

PointL was copied from Dino Esposito’s excellent article on Shell Extensions on The Server Side.  At any rate, these three attributes on IDropTarget basically tells COM that we can speak the COM lingo and have the behaviors specified by the Interface identified by this GUID.

Step 4

Next we’ll implement the IDropTarget Interface.  The implementation can supposedly reside either in an .exe or .dll, but for our purposes it only works and is only safe when the implementation is on the Entry Point of an .exe project.  So,

    [Guid("C1D0D2F6-5F45-4462-9835-C463AFE367E3")]

    [ComVisible(true)]

    public class Program : IDropTarget

    {

        /// <summary>

        /// The main entry point for the application.

        /// </summary>

        static void Main()

        {

There are some things to take note of here.  Since we wish to enter the world of COM (and COM is love…) we put a ComVisible attribute on the class.  The default Program created for you in VS2008 has Program as a static class, which cannot have attributes so we’ve removed that.  We also have used the Tools in Visual Studio to create a GUID for this class.  We are using an API known as “HackingtheRegistrySinceNoRealRegistrationAPIExists” and guids are the coin of the realm.  I happen to hate this API but options are scarce.

Step 5

The next step is to register our code in such a way that it is findable via the appropriate magic.  For .NET programs RegAsm.exe is our registrar of choice.  Note that RegAsm will pout if your Assembly is not strongly named.  If we now “Regasm.exe /codebase LaunchTestProg.exe” we get some interesting registry settings created for us.  Under HKEY_CLASSES_ROOT\CLSID we find our Program’s GUID, along with the following:

A sub key named “ProgId” containing the fully qualified class name of the .NET program we’d like to run is nestled beneath our class id guid.  This is part of the explanation for the values in Step 2.  Continuing to search for our GUID in the registry we find that HKEY_CLASSES_ROOT\LaunchTestProg.Program has been created for us. 

Step 6

Rather than be clever I’ll just show the final piece we need to create.  The Shell depends on various lookups and naming conventions to determine what it needs to do. 

Below HKEY_CLASSES_ROOT\LaunchTestProg.Program, we need to create some registry keys and values ourselves.  We create sub keys shell, open, DropTarget, and put the GUID of our .NET program as a clsid attribute of the DropTarget key.  The entire process can now ask the user, find, and Invoke our .NET program when a USB device is inserted and it meets the original Handler criteria.

Further Investigation

Upon testing this, I found some interesting behaviors.  It appears that while the .NET project must be an .exe, what Windows is really doing is instantiating the Program object and invoking the members of IDropTarget on it.  I have not tried too hard to get Main to run.  In normal cases, IDropTarget.DragEnter and then IDropTarget.Drop will be called.  For my particular needs, the arguments to either method are throw-away, but using these should be normal P/Invoke & Marshalling exercises.  If you just want to start some other .NET code you can now do so from the your Drop()  implementation.

I will also warn that you want to read this and understand what it means: http://blogs.msdn.com/junfeng/archive/2005/11/18/494572.aspx

I will also warn that the class library Assembly that contains my IDropTarget interface seems to be file-locked once I actually invoke my program.  Restarting Explorer.exe fixes this, which may be a consideration if you are going to be auto-updating the program in some way.  It seems odd that the .exe containing the actual program is not locked.  I plan on investigating the registry areas of Windows\CurrentVersion\ShellExt\Cached to see if clearing that and firing the appropriate Registry Changed event will unlock the assembly. 

Other Avenues...

You will notice DeviceGroups and DeviceHandlers keys as siblings of the “EventHandlers” subkey.  I also mentioned IHWEventHandler and IHWEventHandler2.  The latter is merely the Vista version of the former that adds an extra method, presumably for UAC.  It seems that allowing a .NET program to reconfigure itself if a new input device is plugged in is certainly not out of the range of possibilities.

Would you like some thrown-together code that does all of this for you?  I’ve been meaning to get better about posting more code, so here we are:

    public class DropTargetInstaller

    {

 

        public bool Install(Assembly exe, string entryPointTypeName, string desiredHandlerName, string iconPath, string actionText, string usingText)

        {           

            Type t = exe.GetType(entryPointTypeName);

            object[] attributes = t.GetCustomAttributes(false);

            GuidAttribute guid = GetGUID(attributes);

 

            //Throws ArgumentExceptions to caller if the Assembly/class does not meet requirements

            VerifySuitability(entryPointTypeName, t, attributes, guid);

            string guidVal = guid.Value;

 

            Regasm(exe);

            WriteRegistryValues(desiredHandlerName, actionText, iconPath, entryPointTypeName, usingText,guidVal);

            

            return true;

        }

 

        /// <summary>

        /// Suitable classes must Implement the COM IDropTarget interface, be marked COMVisible, and contain a valid GUID attribute

        /// </summary>

        /// <param name="entryPointTypeName"></param>

        /// <param name="t"></param>

        /// <param name="attributes"></param>

        /// <param name="guid"></param>

        private void VerifySuitability(string entryPointTypeName, Type t, object[] attributes, GuidAttribute guid)

        {

            if (!VerifyDropTarget(t))

            {

                string msg = string.Format("Type {0} does not implement IDropTarget", entryPointTypeName);

                throw new ArgumentException(msg);

            }

            //

            if (!VerifyCOMVisible(attributes))

            {

                string msg = string.Format("Type {0} is missing COMVisble attribute", entryPointTypeName);

                throw new ArgumentException(msg);

            }

            //                        

            if (null == guid)

            {

                string msg = string.Format("Type {0} is missing Guid attribute", entryPointTypeName);

                throw new ArgumentException(msg);

            }

        }

 

        /// <summary>

        ///

        /// </summary>

        /// <param name="exe"></param>

        protected void Regasm(Assembly exe)

        {

            string regasmExe = @"Microsoft.NET\Framework\v2.0.50727\RegAsm.exe";           

            string regPath = Path.Combine(@"c:\windows\", regasmExe);

 

            //RegAsm does not support URI format (file:////c:blahblah

            ProcessStartInfo info = new ProcessStartInfo(regPath, "/codebase " + exe.CodeBase.Replace("file:///",string.Empty));

            info.RedirectStandardOutput = true;

            info.RedirectStandardError = true;

            info.UseShellExecute = false;

           

            Process p = Process.Start(info);

            using (StreamReader stdOut = p.StandardOutput)

            {

                using (StreamReader stdErr = p.StandardError)

                {

                    p.WaitForExit();

                    string output = stdOut.ReadToEnd();

                    string errput = stdErr.ReadToEnd();

                    //Do error logging here if stdErr is not blank, etc.

                    int code = p.ExitCode;

                }

            }                      

        }

 

        /// <summary>

        ///

        /// </summary>

        /// <param name="desiredHandlerName"></param>

        /// <param name="actionName"></param>

        /// <param name="iconPath"></param>

        /// <param name="entryPointName"></param>

        /// <param name="usingString"></param>

        /// <param name="guid"></param>

        protected void WriteRegistryValues(string desiredHandlerName, string actionName, string iconPath,

            string entryPointName, string usingString, string guid)

        {

            WriteHandlerRoot(desiredHandlerName, actionName, iconPath, entryPointName, usingString);

 

            WritePhotosOnArrival(desiredHandlerName);

 

            WriteDropTarget(entryPointName, guid);

        }

 

        private static void WriteDropTarget(string entryPointName, string guid)

        {

            RegistryKey classesRoot = Registry.ClassesRoot;

            if (classesRoot.GetSubKeyNames().Contains<string>(entryPointName))

            {

                RegistryKey invokeProgId = classesRoot.OpenSubKey(entryPointName, true);

                if (invokeProgId.GetSubKeyNames().Contains<string>("shell"))//Start over

                {

                    invokeProgId.DeleteSubKeyTree("shell");

                }

                RegistryKey shell = invokeProgId.CreateSubKey("shell");

                RegistryKey open = shell.CreateSubKey("open");

                RegistryKey dropTarget = open.CreateSubKey("DropTarget");

                dropTarget.SetValue("clsid", "{" + guid + "}");

                dropTarget.Close();

                open.Close();

                shell.Close();

                invokeProgId.Close();

            }

            else

            {

                //Regasm did not run

            }

            //

            classesRoot.Close();

        }

 

        private static void WritePhotosOnArrival(string desiredHandlerName)

        {

            RegistryKey photosOnArrival =

                Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\AutoPlayHandlers\EventHandlers\ShowPicturesOnArrival", true);

            if (!photosOnArrival.GetValueNames().Contains<string>(desiredHandlerName))

            {

                photosOnArrival.SetValue(desiredHandlerName, string.Empty);

            }

            photosOnArrival.Close();

        }

 

        private static void WriteHandlerRoot(string desiredHandlerName, string actionName, string iconPath, string entryPointName, string usingString)

        {

            RegistryKey handlerRoot = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\AutoPlayHandlers\Handlers", true);

            if (handlerRoot.GetSubKeyNames().Contains<string>(desiredHandlerName))

            {

                handlerRoot.DeleteSubKey(desiredHandlerName);

            }

            RegistryKey handler = handlerRoot.CreateSubKey(desiredHandlerName);

            handler.SetValue("Action", actionName);

            handler.SetValue("DefaultIcon", iconPath);

            handler.SetValue("InvokeProgID", entryPointName);

            handler.SetValue("InvokeVerb", "open");

            handler.SetValue("Provider", usingString);

            handler.Close();

            handlerRoot.Close();

        }

 

 

        protected bool VerifyDropTarget(Type t)

        {

            Type[] interfaceTypes = t.GetInterfaces();

            bool dropTarg = interfaceTypes.Contains<Type>(typeof(IDropTarget));

            return dropTarg;

        }

 

        protected bool VerifyCOMVisible(object[] attributes)

        {

            for (int i = 0; i < attributes.Length; ++i)

            {

                if (attributes[i] is ComVisibleAttribute)

                {

                    return true;

                }

            }

            return false;

        }

 

        protected GuidAttribute GetGUID(object[] attributes)

        {

            GuidAttribute g = null;

 

            for (int i = 0; i < attributes.Length; ++i)

            {

                if (attributes[i] is GuidAttribute)

                {

                    return (GuidAttribute)attributes[i];

                }

            }

 

            return g;

        }

    }

To use this class just do the following from Main:

            DropTargetInstaller dti = new DropTargetInstaller();

            string iconPath = Path.Combine(Application.StartupPath, "LaunchTest.ico");

            dti.Install(System.Reflection.Assembly.GetExecutingAssembly(), "LaunchTestProg.Program", "TestHandler", iconPath, "Do something cool", "Launch Test Prog");

 

And there you have it: you can invoke .NET programs when certain Windows Shell events occur.  I plan on doing some basic Zune testing when it arrives and exploring uses for the IHWEventHandler code. As far as I know this is the only example of its type online which is a shame.  Enjoy.

Tags:

Zune

by Administrator 25. February 2008 01:08
At some point recently I noticed the tremendous stack of CDs I continually burn to stay on top of Dot Net Rocks, Hanselminutes, and AVRant.  I have to admit (maybe I'm old?) I completely did not get the whole iPod/Mp3 player thing for the longest time; I'm a dinosaur who likes owning physical media and my ancient car does not have a line-in.   I do my serious music listening on a two-channel system if that tells you anything. For music and podcasts, though, I've been wanting a portable media device.  An evaluation of the reviews of the 2nd Generation Zune and the GDC2008 announcement that some form of XNA would be coming to all the Zune editions and I decided I needed to have a Zune. I had originally decided on the 80gb zune since it looked cool online and it would hold the entire history of .NET rocks and my entire digital music collection, but seeing them in person, the 8gb is so much sleeker and smaller and portable feeling I ordered that one instead.  The 80gb Zune would be like carrying a second Tilt.

Tags:

Smart device debugging

by Administrator 23. February 2008 15:49

What's wrong with this picture?  (I wish I had found this during RC0)

 

Tags:

RIP: HD-DVD

by Administrator 17. February 2008 22:50

It's over folks.  Take your pick of any one of a dozen news outlets, or read this nice summary on Ars: http://arstechnica.com/news.ars/post/20080217-official-hd-dvd-obituary-a-matter-of-days-not-weeks.html

Sadly there's not that many titles that I'll be picking up after the inevitable announcements by Paramount and Universal.  Pitch Black and Chronicles of Riddick are guilty pleasures; King-Kong shall be a rental, as will Sweeny Todd and Transformers.  I won't buy the whole "Jack Ryan Collection" for my beloved Hunt for Red October but I'll netflix them.

This is good news for lovers of the high definition experience.

Tags:

Dirty secrets of CF Designer Support

by Administrator 14. February 2008 18:58

After being a one man shop for far too long, my new cohort and myself have experienced the joy of actually validating that the Compact Framework projects in Subversion are acually correct.  The Solution has a Lib solution folder which contains all of the external dependencies needed for the project to build and run.  On a clean machine it appears that the Compact Framework 2.0 is not installed when you install VS2008.  This led me to put mscorlib, System.Windows.Forms, etc. into the Lib folder to save Dan from needing to install VS 2005 (which he has blogged about) in order to get the assemblies we need.  Oddly enough, all UserControl and Form classes in the solution do not appear to have designer support when the solution is checked out, getting a clean local copy shows the same behavior for me.  Controls just display all their designer-added children (Label, Button, etc) in a giant Control tray.

The only thing that seemed to resolve this issue for us is removing our Lib assembly references and re-adding the fundamental dependencies from the GAC.  This makes one wonder: what bizarre implementation detail of the designer support causes this issue?

Tags:

Introducing Dan Vanderboom

by Administrator 11. February 2008 17:12

Starting in my department today is Dan Vanderboom, programmer and software architect extraordinaire.  You can check out Dan's blog here: http://dvanderboom.wordpress.com/

Dan will be rocking the CF and WPF world with me at CarSpot, but I'm still hiring!

Tags:

About the author

Damon Payne is a Microsoft MVP specializing in Smart Client solution architecture. 

INETA Community Speakers Program

Month List

Page List

flickr photostream