浏览代码

Add native shortcut handling & dashboard shortcut to installer

Eric Reed 12 年之前
父节点
当前提交
a15b6db0dc

+ 276 - 0
MediaBrowser.Installer/Code/ShellLinkNative.cs

@@ -0,0 +1,276 @@
+/**************************************************************************
+*
+* Filename:     ShellLinkNative.cs
+* Author:       Mattias Sjögren (mattias@mvps.org)
+*               http://www.msjogren.net/dotnet/
+*
+* Description:  Defines the native types used to manipulate shell shortcuts.
+*
+* Public types: enum SLR_FLAGS
+*               enum SLGP_FLAGS
+*               struct WIN32_FIND_DATA[A|W]
+*               interface IPersistFile
+*               interface IShellLink[A|W]
+*               class ShellLink
+*
+*
+* Copyright ©2001-2002, Mattias Sjögren
+* 
+**************************************************************************/
+
+using System;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace MediaBrowser.Installer.Code
+{
+  // IShellLink.Resolve fFlags
+  [Flags()]
+  public enum SLR_FLAGS
+  {
+    SLR_NO_UI = 0x1,
+    SLR_ANY_MATCH = 0x2,
+    SLR_UPDATE = 0x4,
+    SLR_NOUPDATE = 0x8,
+    SLR_NOSEARCH = 0x10,
+    SLR_NOTRACK = 0x20,
+    SLR_NOLINKINFO = 0x40,
+    SLR_INVOKE_MSI = 0x80
+  }
+
+  // IShellLink.GetPath fFlags
+  [Flags()]
+  public enum SLGP_FLAGS
+  {
+    SLGP_SHORTPATH = 0x1,
+    SLGP_UNCPRIORITY = 0x2,
+    SLGP_RAWPATH = 0x4
+  }
+
+  [StructLayoutAttribute(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
+  public struct WIN32_FIND_DATAA
+  {
+    public int dwFileAttributes;
+    public FILETIME ftCreationTime;
+    public FILETIME ftLastAccessTime;
+    public FILETIME ftLastWriteTime;
+    public int nFileSizeHigh;
+    public int nFileSizeLow;
+    public int dwReserved0;
+    public int dwReserved1;
+    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)]
+    public string cFileName;
+    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=14)]
+    public string cAlternateFileName;
+    private const int MAX_PATH = 260;
+  }
+
+  [StructLayoutAttribute(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
+  public struct WIN32_FIND_DATAW
+  {
+    public int dwFileAttributes;
+    public FILETIME ftCreationTime;
+    public FILETIME ftLastAccessTime;
+    public FILETIME ftLastWriteTime;
+    public int nFileSizeHigh;
+    public int nFileSizeLow;
+    public int dwReserved0;
+    public int dwReserved1;
+    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)]
+    public string cFileName;
+    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=14)]
+    public string cAlternateFileName;
+    private const int MAX_PATH = 260;
+  }
+
+  [
+    ComImport(),
+    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
+    Guid("0000010B-0000-0000-C000-000000000046")
+  ]
+  public interface IPersistFile
+  {
+    #region Methods inherited from IPersist
+
+    void GetClassID(
+      out Guid pClassID);
+
+    #endregion
+
+    [PreserveSig()]
+    int IsDirty();
+
+    void Load(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
+      int dwMode);
+
+    void Save(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
+      [MarshalAs(UnmanagedType.Bool)] bool fRemember);
+
+    void SaveCompleted(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
+
+    void GetCurFile(
+      out IntPtr ppszFileName);
+
+  }
+
+  [
+    ComImport(),
+    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
+    Guid("000214EE-0000-0000-C000-000000000046")
+  ]
+  public interface IShellLinkA
+  {
+    void GetPath(
+      [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile,
+      int cchMaxPath,
+      out WIN32_FIND_DATAA pfd,
+      SLGP_FLAGS fFlags);
+
+    void GetIDList(
+      out IntPtr ppidl);
+
+    void SetIDList(
+      IntPtr pidl);
+
+    void GetDescription(
+      [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszName,
+      int cchMaxName);
+
+    void SetDescription(
+      [MarshalAs(UnmanagedType.LPStr)] string pszName);
+
+    void GetWorkingDirectory(
+      [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszDir,
+      int cchMaxPath);
+
+    void SetWorkingDirectory(
+      [MarshalAs(UnmanagedType.LPStr)] string pszDir);
+
+    void GetArguments(
+      [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszArgs,
+      int cchMaxPath);
+
+    void SetArguments(
+      [MarshalAs(UnmanagedType.LPStr)] string pszArgs);
+
+    void GetHotkey(
+      out short pwHotkey);
+
+    void SetHotkey(
+      short wHotkey);
+
+    void GetShowCmd(
+      out int piShowCmd);
+
+    void SetShowCmd(
+      int iShowCmd);
+
+    void GetIconLocation(
+      [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszIconPath,
+      int cchIconPath,
+      out int piIcon);
+
+    void SetIconLocation(
+      [MarshalAs(UnmanagedType.LPStr)] string pszIconPath,
+      int iIcon);
+
+    void SetRelativePath(
+      [MarshalAs(UnmanagedType.LPStr)] string pszPathRel,
+      int dwReserved);
+
+    void Resolve(
+      IntPtr hwnd,
+      SLR_FLAGS fFlags);
+
+    void SetPath(
+      [MarshalAs(UnmanagedType.LPStr)] string pszFile);
+
+  }
+
+  [
+    ComImport(),
+    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
+    Guid("000214F9-0000-0000-C000-000000000046")
+  ]
+  public interface IShellLinkW
+  {
+    void GetPath(
+      [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile,
+      int cchMaxPath,
+      out WIN32_FIND_DATAW pfd,
+      SLGP_FLAGS fFlags);
+
+    void GetIDList(
+      out IntPtr ppidl);
+
+    void SetIDList(
+      IntPtr pidl);
+
+    void GetDescription(
+      [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName,
+      int cchMaxName);
+
+    void SetDescription(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszName);
+
+    void GetWorkingDirectory(
+      [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir,
+      int cchMaxPath);
+
+    void SetWorkingDirectory(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszDir);
+
+    void GetArguments(
+      [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs,
+      int cchMaxPath);
+
+    void SetArguments(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
+
+    void GetHotkey(
+      out short pwHotkey);
+
+    void SetHotkey(
+      short wHotkey);
+
+    void GetShowCmd(
+      out int piShowCmd);
+
+    void SetShowCmd(
+      int iShowCmd);
+
+    void GetIconLocation(
+      [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
+      int cchIconPath,
+      out int piIcon);
+
+    void SetIconLocation(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath,
+      int iIcon);
+
+    void SetRelativePath(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel,
+      int dwReserved);
+
+    void Resolve(
+      IntPtr hwnd,
+      SLR_FLAGS fFlags);
+
+    void SetPath(
+      [MarshalAs(UnmanagedType.LPWStr)] string pszFile);
+
+  }
+
+
+  [
+    ComImport(),
+    Guid("00021401-0000-0000-C000-000000000046")
+  ]
+  public class ShellLink  // : IPersistFile, IShellLinkA, IShellLinkW 
+  {
+  }
+
+}

+ 348 - 0
MediaBrowser.Installer/Code/ShellShortcut.cs

@@ -0,0 +1,348 @@
+/**************************************************************************
+*
+* Filename:     ShellShortcut.cs
+* Author:       Mattias Sjögren (mattias@mvps.org)
+*               http://www.msjogren.net/dotnet/
+*
+* Description:  Defines a .NET friendly class, ShellShortcut, for reading
+*               and writing shortcuts.
+*               Define the conditional compilation symbol UNICODE to use
+*               IShellLinkW internally.
+*
+* Public types: class ShellShortcut
+*
+*
+* Dependencies: ShellLinkNative.cs
+*
+*
+* Copyright ©2001-2002, Mattias Sjögren
+* 
+**************************************************************************/
+
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Windows.Forms;
+
+
+namespace MediaBrowser.Installer.Code
+{
+	/// <remarks>
+	///   .NET friendly wrapper for the ShellLink class
+	/// </remarks>
+	public class ShellShortcut : IDisposable
+	{
+    private const int INFOTIPSIZE = 1024;
+    private const int MAX_PATH = 260;
+
+    private const int SW_SHOWNORMAL = 1;
+    private const int SW_SHOWMINIMIZED = 2;
+    private const int SW_SHOWMAXIMIZED = 3;
+    private const int SW_SHOWMINNOACTIVE = 7;
+
+
+  #if UNICODE
+    private IShellLinkW m_Link;
+  #else
+    private IShellLinkA m_Link;
+  #endif
+    private string m_sPath;
+
+    ///
+    /// <param name='linkPath'>
+    ///   Path to new or existing shortcut file (.lnk).
+    /// </param>
+    ///
+    public ShellShortcut(string linkPath)
+    {
+      IPersistFile pf;
+
+      m_sPath = linkPath;
+
+    #if UNICODE
+      m_Link = (IShellLinkW) new ShellLink();
+    #else
+      m_Link = (IShellLinkA) new ShellLink();
+    #endif
+
+      if ( File.Exists( linkPath ) ) {
+        pf = (IPersistFile)m_Link;
+        pf.Load( linkPath, 0 );
+      }
+
+    }
+
+    //
+    //  IDisplosable implementation
+    //
+    public void Dispose()
+    {
+      if ( m_Link != null ) {
+        Marshal.ReleaseComObject( m_Link );
+        m_Link = null;
+      }
+    }
+
+    /// <value>
+    ///   Gets or sets the argument list of the shortcut.
+    /// </value>
+    public string Arguments
+    {
+      get
+      {
+        StringBuilder sb = new StringBuilder( INFOTIPSIZE );
+        m_Link.GetArguments( sb, sb.Capacity );
+        return sb.ToString();
+      }
+      set { m_Link.SetArguments( value ); }
+    }
+
+    /// <value>
+    ///   Gets or sets a description of the shortcut.
+    /// </value>
+    public string Description
+    {
+      get
+      {
+        StringBuilder sb = new StringBuilder( INFOTIPSIZE );
+        m_Link.GetDescription( sb, sb.Capacity );
+        return sb.ToString();
+      }
+      set { m_Link.SetDescription( value ); }
+    }
+
+    /// <value>
+    ///   Gets or sets the working directory (aka start in directory) of the shortcut.
+    /// </value>
+    public string WorkingDirectory
+    {
+      get
+      {
+        StringBuilder sb = new StringBuilder( MAX_PATH );
+        m_Link.GetWorkingDirectory( sb, sb.Capacity );
+        return sb.ToString();
+      }
+      set { m_Link.SetWorkingDirectory( value ); }
+    }
+
+    //
+    // If Path returns an empty string, the shortcut is associated with
+    // a PIDL instead, which can be retrieved with IShellLink.GetIDList().
+    // This is beyond the scope of this wrapper class.
+    //
+    /// <value>
+    ///   Gets or sets the target path of the shortcut.
+    /// </value>
+    public string Path
+    {
+      get
+      {
+      #if UNICODE
+        WIN32_FIND_DATAW wfd = new WIN32_FIND_DATAW();
+      #else
+        WIN32_FIND_DATAA wfd = new WIN32_FIND_DATAA();
+      #endif
+        StringBuilder sb = new StringBuilder( MAX_PATH );
+
+        m_Link.GetPath( sb, sb.Capacity, out wfd, SLGP_FLAGS.SLGP_UNCPRIORITY );
+        return sb.ToString();
+      }
+      set { m_Link.SetPath( value ); }
+    }
+
+    /// <value>
+    ///   Gets or sets the path of the <see cref="Icon"/> assigned to the shortcut.
+    /// </value>
+    /// <summary>
+    ///   <seealso cref="IconIndex"/>
+    /// </summary>
+    public string IconPath
+    {
+      get
+      {
+        StringBuilder sb = new StringBuilder( MAX_PATH );
+        int nIconIdx;
+        m_Link.GetIconLocation( sb, sb.Capacity, out nIconIdx );
+        return sb.ToString();
+      }
+      set { m_Link.SetIconLocation( value, IconIndex ); }
+    }
+
+    /// <value>
+    ///   Gets or sets the index of the <see cref="Icon"/> assigned to the shortcut.
+    ///   Set to zero when the <see cref="IconPath"/> property specifies a .ICO file.
+    /// </value>
+    /// <summary>
+    ///   <seealso cref="IconPath"/>
+    /// </summary>
+    public int IconIndex
+    {
+      get
+      {
+        StringBuilder sb = new StringBuilder( MAX_PATH );
+        int nIconIdx;
+        m_Link.GetIconLocation( sb, sb.Capacity, out nIconIdx );
+        return nIconIdx;
+      }
+      set { m_Link.SetIconLocation( IconPath, value ); }
+    }
+
+    /// <value>
+    ///   Retrieves the Icon of the shortcut as it will appear in Explorer.
+    ///   Use the <see cref="IconPath"/> and <see cref="IconIndex"/>
+    ///   properties to change it.
+    /// </value>
+    public Icon Icon
+    {
+      get
+      {
+        StringBuilder sb = new StringBuilder( MAX_PATH );
+        int nIconIdx;
+        IntPtr hIcon, hInst;
+        Icon ico, clone;
+
+
+        m_Link.GetIconLocation( sb, sb.Capacity, out nIconIdx );
+        hInst = Marshal.GetHINSTANCE( this.GetType().Module );
+        hIcon = Native.ExtractIcon( hInst, sb.ToString(), nIconIdx );
+        if ( hIcon == IntPtr.Zero )
+          return null;
+
+        // Return a cloned Icon, because we have to free the original ourselves.
+        ico = Icon.FromHandle( hIcon );
+        clone = (Icon)ico.Clone();
+        ico.Dispose();
+        Native.DestroyIcon( hIcon );
+        return clone;
+      }
+    }
+
+    /// <value>
+    ///   Gets or sets the System.Diagnostics.ProcessWindowStyle value
+    ///   that decides the initial show state of the shortcut target. Note that
+    ///   ProcessWindowStyle.Hidden is not a valid property value.
+    /// </value>
+    public ProcessWindowStyle WindowStyle
+    {
+      get
+      {
+        int nWS;
+        m_Link.GetShowCmd( out nWS );
+
+        switch ( nWS ) {
+          case SW_SHOWMINIMIZED:
+          case SW_SHOWMINNOACTIVE:
+            return ProcessWindowStyle.Minimized;
+          
+          case SW_SHOWMAXIMIZED:
+            return ProcessWindowStyle.Maximized;
+
+          default:
+            return ProcessWindowStyle.Normal;
+        }
+      }
+      set
+      {
+        int nWS;
+
+        switch ( value ) {
+          case ProcessWindowStyle.Normal:
+            nWS = SW_SHOWNORMAL;
+            break;
+
+          case ProcessWindowStyle.Minimized:
+            nWS = SW_SHOWMINNOACTIVE;
+            break;
+
+          case ProcessWindowStyle.Maximized:
+            nWS = SW_SHOWMAXIMIZED;
+            break;
+
+          default: // ProcessWindowStyle.Hidden
+            throw new ArgumentException("Unsupported ProcessWindowStyle value.");
+        }
+
+        m_Link.SetShowCmd( nWS );
+      
+      }
+    }
+
+    /// <value>
+    ///   Gets or sets the hotkey for the shortcut.
+    /// </value>
+    public Keys Hotkey
+    {
+      get
+      {
+        short wHotkey;
+        int dwHotkey;
+
+        m_Link.GetHotkey( out wHotkey );
+
+        //
+        // Convert from IShellLink 16-bit format to Keys enumeration 32-bit value
+        // IShellLink: 0xMMVK
+        // Keys:  0x00MM00VK        
+        //   MM = Modifier (Alt, Control, Shift)
+        //   VK = Virtual key code
+        //       
+        dwHotkey = ((wHotkey & 0xFF00) << 8) | (wHotkey & 0xFF);
+        return (Keys) dwHotkey;
+      }
+      set
+      {
+        short wHotkey;
+
+        if ( (value & Keys.Modifiers) == 0 )
+          throw new ArgumentException("Hotkey must include a modifier key.");
+
+        //    
+        // Convert from Keys enumeration 32-bit value to IShellLink 16-bit format
+        // IShellLink: 0xMMVK
+        // Keys:  0x00MM00VK        
+        //   MM = Modifier (Alt, Control, Shift)
+        //   VK = Virtual key code
+        //       
+        wHotkey = unchecked((short) ( ((int) (value & Keys.Modifiers) >> 8) | (int) (value & Keys.KeyCode) ));
+        m_Link.SetHotkey( wHotkey );
+
+      }
+    }
+
+    /// <summary>
+    ///   Saves the shortcut to disk.
+    /// </summary>
+    public void Save()
+    {
+      IPersistFile pf = (IPersistFile) m_Link;
+      pf.Save( m_sPath, true );
+    }
+
+    /// <summary>
+    ///   Returns a reference to the internal ShellLink object,
+    ///   which can be used to perform more advanced operations
+    ///   not supported by this wrapper class, by using the
+    ///   IShellLink interface directly.
+    /// </summary>
+    public object ShellLink
+    {
+      get { return m_Link; }
+    }
+
+
+  #region Native Win32 API functions
+    private class Native
+    {
+      [DllImport("shell32.dll", CharSet=CharSet.Auto)]
+      public static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);
+
+      [DllImport("user32.dll")]
+      public static extern bool DestroyIcon(IntPtr hIcon);
+    }
+  #endregion
+
+	}
+}

+ 14 - 12
MediaBrowser.Installer/MainWindow.xaml.cs

@@ -10,7 +10,6 @@ using System.Linq;
 using Ionic.Zip;
 using MediaBrowser.Installer.Code;
 using ServiceStack.Text;
-using IWshRuntimeLibrary;
 
 namespace MediaBrowser.Installer
 {
@@ -124,7 +123,7 @@ namespace MediaBrowser.Installer
             var fullPath = Path.Combine(RootPath, "System", TargetExe);
             try
             {
-                CreateShortcut(fullPath);
+                CreateShortcuts(fullPath);
             }
             catch (Exception e)
             {
@@ -225,21 +224,24 @@ namespace MediaBrowser.Installer
         ///  Only do current user to avoid need for admin elevation
         /// </summary>
         /// <param name="targetExe"></param>
-        protected void CreateShortcut(string targetExe)
+        protected void CreateShortcuts(string targetExe)
         {
             // get path to all users start menu
-            var shell = new WshShell();
-            var startMenu = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),"Media Browser");
+            var startMenu = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),"Media Browser 3");
             if (!Directory.Exists(startMenu)) Directory.CreateDirectory(startMenu);
-            var product = (IWshShortcut)shell.CreateShortcut(Path.Combine(startMenu, FriendlyName+".lnk"));
-            product.TargetPath = targetExe;
-            product.Description = "Run " + FriendlyName;
+            var product = new ShellShortcut(Path.Combine(startMenu, FriendlyName+".lnk")) {Path = targetExe, Description = "Run " + FriendlyName};
             product.Save();
 
-            var uninstall = (IWshShortcut)shell.CreateShortcut(Path.Combine(startMenu, "Uninstall " + FriendlyName + ".lnk"));
-            uninstall.TargetPath = Path.Combine(Path.GetDirectoryName(targetExe),"MediaBrowser.Uninstaller.exe");
-            uninstall.Arguments = (PackageName == "MBServer" ? "server" : "mbt");
-            uninstall.Description = "Uninstall " + FriendlyName;
+            if (PackageName == "MBServer")
+            {
+                var path = Path.Combine(startMenu, "MB Dashboard.lnk");
+                var dashboard = new ShellShortcut(path) 
+                {Path = @"http://localhost:8096/mediabrowser/dashboard/dashboard.html", Description = "Open the Media Browser Server Dashboard (configuration)"};
+                dashboard.Save();
+                
+            }
+            var uninstall = new ShellShortcut(Path.Combine(startMenu, "Uninstall " + FriendlyName + ".lnk")) 
+            {Path = Path.Combine(Path.GetDirectoryName(targetExe), "MediaBrowser.Uninstaller.exe"), Arguments = (PackageName == "MBServer" ? "server" : "mbt"), Description = "Uninstall " + FriendlyName};
             uninstall.Save();
 
         }

+ 11 - 14
MediaBrowser.Installer/MediaBrowser.Installer.csproj

@@ -29,7 +29,7 @@
     <PublisherName>Media Browser Team</PublisherName>
     <SuiteName>Media Browser</SuiteName>
     <OpenBrowserOnPublish>false</OpenBrowserOnPublish>
-    <ApplicationRevision>14</ApplicationRevision>
+    <ApplicationRevision>15</ApplicationRevision>
     <ApplicationVersion>0.1.1.%2a</ApplicationVersion>
     <UseApplicationTrust>false</UseApplicationTrust>
     <PublishWizardCompleted>true</PublishWizardCompleted>
@@ -85,7 +85,9 @@
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Data" />
+    <Reference Include="System.Drawing" />
     <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Core" />
@@ -126,6 +128,8 @@
     <Compile Include="Code\PackageType.cs" />
     <Compile Include="Code\PackageVersionClass.cs" />
     <Compile Include="Code\PackageVersionInfo.cs" />
+    <Compile Include="Code\ShellLinkNative.cs" />
+    <Compile Include="Code\ShellShortcut.cs" />
     <Compile Include="MainWindow.xaml.cs">
       <DependentUpon>MainWindow.xaml</DependentUpon>
       <SubType>Code</SubType>
@@ -149,7 +153,9 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="mbt.config" />
+    <None Include="mbt.config">
+      <SubType>Designer</SubType>
+    </None>
     <None Include="MediaBrowser.Installer_1_TemporaryKey.pfx" />
     <None Include="Properties\app.manifest" />
     <None Include="Properties\Settings.settings">
@@ -159,7 +165,9 @@
     <AppDesigner Include="Properties\" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="App.config" />
+    <None Include="App.config">
+      <SubType>Designer</SubType>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <BootstrapperPackage Include=".NETFramework,Version=v4.5">
@@ -190,17 +198,6 @@
   <ItemGroup>
     <Resource Include="Code\Images\mb3logo800.png" />
   </ItemGroup>
-  <ItemGroup>
-    <COMReference Include="IWshRuntimeLibrary">
-      <Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
-      <VersionMajor>1</VersionMajor>
-      <VersionMinor>0</VersionMinor>
-      <Lcid>0</Lcid>
-      <WrapperTool>tlbimp</WrapperTool>
-      <Isolated>False</Isolated>
-      <EmbedInteropTypes>True</EmbedInteropTypes>
-    </COMReference>
-  </ItemGroup>
   <ItemGroup>
     <Resource Include="Icon.ico" />
   </ItemGroup>

+ 7 - 4
MediaBrowser.Uninstaller.Execute/MainWindow.xaml

@@ -7,10 +7,13 @@
         <Label x:Name="lblHeading" Content="Uninstall " HorizontalAlignment="Left" Margin="51,169,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.478,-2.753" Height="29" Width="423" FontSize="14" FontWeight="Bold"/>
         <Button x:Name="btnUninstall" Content="Uninstall" HorizontalAlignment="Left" Margin="505,341,0,0" VerticalAlignment="Top" Width="75" IsDefault="True" RenderTransformOrigin="0.991,-1.041" Click="btnUninstall_Click"/>
         <Button x:Name="btnCancel" Content="Cancel" HorizontalAlignment="Left" Margin="412,341,0,0" VerticalAlignment="Top" Width="75" IsCancel="True" Click="btnCancel_Click"/>
-        <CheckBox x:Name="cbxRemoveAll" Content="Remove All Traces" HorizontalAlignment="Left" Margin="137,234,0,0" VerticalAlignment="Top" Checked="cbxRemoveAll_Checked" Unchecked="cbxRemoveAll_Checked"/>
-        <CheckBox x:Name="cbxRemoveCache" Content="Delete Cache Files" HorizontalAlignment="Left" Margin="152,255,0,0" VerticalAlignment="Top"/>
-        <CheckBox x:Name="cbxRemoveConfig" Content="Delete Configuration and Log Files" HorizontalAlignment="Left" Margin="152,276,0,0" VerticalAlignment="Top"/>
-        <CheckBox x:Name="cbxRemovePlugins" Content="Delete Plug-ins" HorizontalAlignment="Left" Margin="152,297,0,0" VerticalAlignment="Top"/>
+        <Grid x:Name="grdOptions" HorizontalAlignment="Left" Height="108" Margin="134,213,0,0" VerticalAlignment="Top" Width="261">
+            <CheckBox x:Name="cbxRemoveAll" Content="Remove All Traces" HorizontalAlignment="Left" Margin="0,3,0,0" VerticalAlignment="Top" Checked="cbxRemoveAll_Checked" Unchecked="cbxRemoveAll_Checked"/>
+            <CheckBox x:Name="cbxRemoveCache" Content="Delete Cache Files" HorizontalAlignment="Left" Margin="16,25,0,0" VerticalAlignment="Top"/>
+            <CheckBox x:Name="cbxRemoveConfig" Content="Delete Configuration and Log Files" HorizontalAlignment="Left" Margin="16,47,0,0" VerticalAlignment="Top"/>
+            <CheckBox x:Name="cbxRemovePlugins" Content="Delete Plug-ins" HorizontalAlignment="Left" Margin="16,68,0,0" VerticalAlignment="Top"/>
+            
+        </Grid>
 
     </Grid>
 </Window>

+ 45 - 9
MediaBrowser.Uninstaller.Execute/MainWindow.xaml.cs

@@ -62,21 +62,57 @@ namespace MediaBrowser.Uninstaller.Execute
         private void btnUninstall_Click(object sender, RoutedEventArgs e)
         {
             // First remove our shortcuts
-            var startMenu = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Media Browser");
+            lblHeading.Content = "Removing Shortcuts...";
+            btnCancel.IsEnabled = btnUninstall.IsEnabled = false;
+            grdOptions.Visibility = Visibility.Hidden;
+
+            var startMenu = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Media Browser 3");
             var linkName = "Media Browser " + Product + ".lnk";
-            try 
+            RemoveShortcut(Path.Combine(startMenu, linkName));
+            linkName = "Uninstall " + linkName;
+            RemoveShortcut(Path.Combine(startMenu, linkName));
+            if (Product == "Server")
             {
-                File.Delete(Path.Combine(startMenu,linkName));
+                RemoveShortcut(Path.Combine(startMenu, "Dashboard.lnk"));
             }
-            catch {} // oh well
-
-            linkName = "Uninstall " + linkName;
-            try 
+            // if the startmenu item is empty now - delete it too
+            if (Directory.GetFiles(startMenu).Length == 0)
             {
-                File.Delete(Path.Combine(startMenu,linkName));
+                try
+                {
+                    Directory.Delete(startMenu);
+                }
+                catch (DirectoryNotFoundException)
+                {
+                }
+                catch (Exception ex)
+                {
+                    {
+                        MessageBox.Show(string.Format("Error attempting to remove shortcut folder {0}\n\n {1}", startMenu, ex.Message), "Error");
+                    }
+                }
             }
-            catch {} // oh well
 
+
+            // and done
+            lblHeading.Content = string.Format("Media Browser {0} Uninstalled.", Product);
+            btnUninstall.Content = "Finish";
+        }
+
+        private static void RemoveShortcut(string path)
+        {
+            try
+            {
+                File.Delete(path);
+            }
+            catch (FileNotFoundException)
+            {
+            } // we're trying to get rid of it anyway
+            catch (Exception ex)
+            {
+                MessageBox.Show(string.Format("Error attempting to remove shortcut {0}\n\n {1}", path, ex.Message), "Error");
+            }
+            
         }
     }
 }