JumpList.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Shell / JumpList.cs / 1305600 / JumpList.cs

                            /**************************************************************************\ 
    Copyright Microsoft Corporation. All Rights Reserved.
\**************************************************************************/

namespace System.Windows.Shell 
{
    using System; 
    using System.Collections.Generic; 
    using System.ComponentModel;
    using System.Diagnostics; 
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Security; 
    using System.Security.Permissions;
    using System.Text; 
    using System.Threading; 
    using System.Windows;
    using System.Windows.Markup; 
    using MS.Internal;
    using MS.Internal.AppModel;
    using MS.Internal.PresentationFramework;
    using MS.Internal.Interop; 
    using MS.Win32;
 
    using HRESULT = MS.Internal.Interop.HRESULT; 

    ///  
    /// The list of possible reasons why a JumpItem would be rejected from a JumpList when applied.
    /// 
    public enum JumpItemRejectionReason
    { 
        /// 
        /// Unknown reason.  This should not be used. 
        ///  
        None,
        ///  
        /// The item was rejected because it was invalid for a jump list.  E.g. the file path didn't exist.
        /// 
        /// 
        /// If the application is running on a system where jump lists are not available (like XP or Vista) 
        /// items will get rejected with this reason.
        ///  
        InvalidItem, 
        /// 
        /// The item was rejected because the program was not registered to handle the file extension. 
        /// 
        NoRegisteredHandler,
        /// 
        /// The item was rejected because the user had explicitly removed it since the last time a JumpList was applied. 
        /// 
        RemovedByUser, 
    } 

    ///  
    /// EventArgs for JumpList.JumpItemsRejected event.
    /// 
    public sealed class JumpItemsRejectedEventArgs : EventArgs
    { 
        public JumpItemsRejectedEventArgs()
            : this(null, null) 
        { } 

        public JumpItemsRejectedEventArgs(IList rejectedItems, IList reasons) 
        {
            // If one of the collections is null then the other has to be, too.
            if ((rejectedItems == null && reasons != null)
                || (reasons == null && rejectedItems != null) 
                || (rejectedItems != null && reasons != null && rejectedItems.Count != reasons.Count))
            { 
                throw new ArgumentException(SR.Get(SRID.JumpItemsRejectedEventArgs_CountMismatch)); 
            }
 
            // We don't want the contents of the list getting modified in the event handler,
            // so use a read-only copy
            if (rejectedItems != null)
            { 
                RejectedItems = new List(rejectedItems).AsReadOnly();
                RejectionReasons = new List(reasons).AsReadOnly(); 
            } 
            else
            { 
                RejectedItems = new List().AsReadOnly();
                RejectionReasons = new List().AsReadOnly();
            }
        } 

        public IList RejectedItems { get; private set; } 
        public IList RejectionReasons { get; private set; } 
    }
 
    /// 
    /// EventArgs for JumpList.JumpItemsRemovedByUser event.
    /// 
    public sealed class JumpItemsRemovedEventArgs : EventArgs 
    {
        public JumpItemsRemovedEventArgs() 
            : this(null) 
        { }
 
        public JumpItemsRemovedEventArgs(IList removedItems)
        {
            if (removedItems != null)
            { 
                RemovedItems = new List(removedItems).AsReadOnly();
            } 
            else 
            {
                RemovedItems = new List().AsReadOnly(); 
            }
        }

        public IList RemovedItems { get; private set; } 
    }
 
    ///  
    /// Manage the tasks and files that Shell associates with this application.
    /// This allows modification of the Jump List UI in Windows 7 that appears on the Taskbar and Start Menu. 
    /// 
    /// 
    /// This class is unsafe to use in partial trust.  It modifies the information Shell presents to the user about the application.
    ///  
    [UIPermission(SecurityAction.LinkDemand, Window=UIPermissionWindow.AllWindows)]
    [ContentProperty("JumpItems")] 
    public sealed class JumpList : ISupportInitialize 
    {
        ///  
        /// Critical: Calls critical native methods.  Class-level LinkDemand doesn't apply to static ctor.
        /// TreatAsSafe: Added explicit demand for AllWindows to the static ctor.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        [UIPermission(SecurityAction.Demand, Window=UIPermissionWindow.AllWindows)]
        static JumpList() 
        { 
            // Passing NULL for the HMODULE returns the running executable path.
            _FullName = UnsafeNativeMethods.GetModuleFileName(new HandleRef()); 
        }

        /// 
        /// Add the item at the specified file path to the application's JumpList's recent items. 
        /// 
        ///  
        /// This makes the item eligible for inclusion in the special Recent and Frequent categories. 
        /// 
        ///  
        /// Critical: Calls critical SHAddToRecentDocs.
        /// PublicOK: The class is only available in full trust.
        /// 
        [SecurityCritical] 
        public static void AddToRecentCategory(string itemPath)
        { 
            Verify.FileExists(itemPath, "itemPath"); 
            itemPath = Path.GetFullPath(itemPath);
            NativeMethods2.SHAddToRecentDocs(itemPath); 
        }

        /// 
        /// Add the item to the application's JumpList's recent items. 
        /// 
        ///  
        /// This makes the item eligible for inclusion in the special Recent and Frequent categories. 
        /// 
        ///  
        /// Critical: Calls critical SHAddToRecentDocs.
        /// PublicOK: The class is only available in full trust.
        /// 
        [SecurityCritical] 
        public static void AddToRecentCategory(JumpPath jumpPath)
        { 
            Verify.IsNotNull(jumpPath, "jumpPath"); 
            AddToRecentCategory(jumpPath.Path);
        } 

        /// 
        /// Add the task at the specified file path to the application's JumpList's recent items.
        ///  
        /// 
        /// This makes the item eligible for inclusion in the special Recent and Frequent categories. 
        ///  
        /// 
        /// Critical: Calls critical SHAddToRecentDocs. 
        /// PublicOK: The class is only available in full trust.
        /// 
        [SecurityCritical]
        public static void AddToRecentCategory(JumpTask jumpTask) 
        {
            Verify.IsNotNull(jumpTask, "jumpTask"); 
 
            // SHAddToRecentDocs only allows IShellLinks in Windows 7 and later.
            // Silently fail this if that's not the case. 
            // We don't give feedback on success here, so this is okay.
            if (Utilities.IsOSWindows7OrNewer)
            {
                IShellLinkW shellLink = CreateLinkFromJumpTask(jumpTask, false); 
                try
                { 
                    if (shellLink != null) 
                    {
                        NativeMethods2.SHAddToRecentDocs(shellLink); 
                    }
                }
                finally
                { 
                    Utilities.SafeRelease(ref shellLink);
                } 
            } 
        }
 

        private class _RejectedJumpItemPair
        {
            public JumpItem JumpItem { get; set; } 
            public JumpItemRejectionReason Reason { get; set; }
        } 
 
        private class _ShellObjectPair
        { 
            // JumpPath/JumpTask
            public JumpItem JumpItem { get; set; }
            // IShellItem/IShellLink
            public object ShellObject { get; set; } 

            ///  
            /// Releases all native references in a list of _ShellObjectPairs. 
            /// 
            ///  
            /// Critical: Calls critical Utilities.SafeRelease.
            /// 
            /// The list from which to release the resources.
            [SecurityCritical] 
            public static void ReleaseShellObjects(List<_ShellObjectPair> list)
            { 
                if (list != null) 
                {
                    foreach (_ShellObjectPair shellMap in list) 
                    {
                        object o = shellMap.ShellObject;
                        shellMap.ShellObject = null;
                        Utilities.SafeRelease(ref o); 
                    }
                } 
            } 

        } 

        #region Attached Property Methods

        ///  
        /// Set the JumpList attached property on an Application.
        ///  
        public static void SetJumpList(Application application, JumpList value) 
        {
            Verify.IsNotNull(application, "application"); 

            lock (s_lock)
            {
                // If this was associated with a different application, remove the association. 
                JumpList oldValue;
                if (s_applicationMap.TryGetValue(application, out oldValue) && oldValue != null) 
                { 
                    oldValue._application = null;
                } 

                // Associate the jumplist with the application so we can retrieve it later.
                s_applicationMap[application] = value;
 
                if (value != null)
                { 
                    value._application = application; 
                }
            } 

            if (value != null)
            {
                // Changes will only get applied if the list isn't in an ISupportInitialize block. 
                value.ApplyFromApplication();
            } 
        } 

        ///  
        /// Get the JumpList attached property for an Application.
        /// 
        public static JumpList GetJumpList(Application application)
        { 
            Verify.IsNotNull(application, "application");
 
            JumpList value; 
            s_applicationMap.TryGetValue(application, out value);
            return value; 
        }

        #endregion
 
        // static lock to ensure integrity when modifying instances as attached properties on Application.
        private static readonly object s_lock = new object(); 
        private static readonly Dictionary s_applicationMap = new Dictionary(); 

        private Application _application; 

        // Used to enforce the ISupportInitialize contract.  It's not required to BeginInit to use this class,
        // but it is useful for XAML scenarios so we can apply the changes when both EndInit has been called
        // and the Application attached property has been set. 
        private bool? _initializing;
 
        // The internal list of JumpItems in this JumpList 
        private List _jumpItems;
 
        /// 
        /// Critical: This class cannot be used from partial trust.
        /// PublicOK: The class is only available in full trust.
        ///  
        [SecurityCritical]
        public JumpList() 
            : this(null, false, false) 
        {
            // Restore the ability to use ISupportInitialize. 
            _initializing = null;
        }

        ///  
        /// Critical: This class cannot be used from partial trust.
        /// PublicOK: The class is only available in full trust. 
        ///  
        [SecurityCritical]
        public JumpList(IEnumerable items, bool showFrequent, bool showRecent) 
        {
            if (items != null)
            {
                _jumpItems = new List(items); 
            }
            else 
            { 
                _jumpItems = new List();
            } 

            ShowFrequentCategory = showFrequent;
            ShowRecentCategory = showRecent;
 
            // Using this constructor precludes using ISupportInitialize.
            _initializing = false; 
        } 

        ///  
        /// Whether to show the special "Frequent" category.
        /// 
        /// 
        /// This category is managed by the Shell and keeps track of items that are frequently accessed by this program. 
        /// Applications can request that specific items are included here by calling JumpList.AddToRecentCategory.
        /// Because of duplication, applications generally should not have both ShowRecentCategory and ShowFrequentCategory set at the same time. 
        ///  
        public bool ShowFrequentCategory { get; set; }
 
        /// 
        /// Whether to show the special "Recent" category.
        /// 
        ///  
        /// This category is managed by the Shell and keeps track of items that have been recently accessed by this program.
        /// Applications can request that specific items are included here by calling JumpList.AddToRecentCategory 
        /// Because of duplication, applications generally should not have both ShowRecentCategory and ShowFrequentCategory set at the same time. 
        /// 
        public bool ShowRecentCategory { get; set; } 

        /// 
        /// The list of JumpItems to be in the JumpList.  After a call to Apply this list will contain only those items that were successfully added.
        ///  
        /// 
        /// This object is not guaranteed to retain its identity after a call to Apply or other implicit setting of the JumpList. 
        /// It should be requeried at such times. 
        /// 
        public List JumpItems 
        {
            get { return _jumpItems; }
        }
 
        private bool IsUnmodified
        { 
            get 
            {
                return _initializing == null 
                    && JumpItems.Count == 0
                    && !ShowRecentCategory
                    && !ShowFrequentCategory;
            } 
        }
        #region ISupportInitialize Members 
 
        /// 
        /// Prepare the JumpList for modification. 
        /// 
        /// 
        /// This works in concert with the Application.JumpList attached property.  The JumpList will automatically be applied
        /// to the current application when attached and a corresponding call to EndInit is made. 
        /// Nested calls to BeginInit are not allowed.
        ///  
        public void BeginInit() 
        {
            if (!IsUnmodified) 
            {
                throw new InvalidOperationException(SR.Get(SRID.JumpList_CantNestBeginInitCalls));
            }
 
            _initializing = true;
        } 
 
        /// 
        /// Signal the end of initialization of this JumpList.  If it is attached to the current Application, apply the contents of the jump list. 
        /// 
        /// 
        /// Calls to EndInit must be paired with calls to BeginInit.
        ///  
        public void EndInit()
        { 
            if (_initializing != true) 
            {
                throw new NotSupportedException(SR.Get(SRID.JumpList_CantCallUnbalancedEndInit)); 
            }

            _initializing = false;
 
            // EndInit only implicitly applies the list if the current Application has been set as an attached property.
            ApplyFromApplication(); 
        } 

        #endregion 

        /// 
        /// Get the AppUserModelId for the running process.
        ///  
        /// 
        /// This is a Shell property that currently is only used as part of a heuristic 
        /// for what taskbar item an HWND should be associated with, e.g. you can put 
        /// windows from multiple processes into the same group, or you can prevent glomming
        /// of HWNDs that would otherwise be shown together. 
        ///
        /// Even though this property isn't exposed on the public WPF OM
        /// we still want to make sure that the jump list gets associated with
        /// the current running app even if the client has explicitly changed the id. 
        ///
        /// It's straightforward to p/invoke to set these for the running application or 
        /// the HWND.  Not so much for this object. 
        /// 
        ///  
        /// Critical: Calls critical GetCurrentProcessExplicitAppUserModelID.
        /// 
        private static string _RuntimeId
        { 
            [SecurityCritical]
            get 
            { 
                string appId;
                HRESULT hr = NativeMethods2.GetCurrentProcessExplicitAppUserModelID(out appId); 
                if (hr == HRESULT.E_FAIL)
                {
                    // This is how Shell signals that the app id hasn't been set.
                    hr = HRESULT.S_OK; 
                    appId = null;
                } 
                hr.ThrowIfFailed(); 
                return appId;
            } 
        }

        /// 
        /// Critical: Calls critical method ApplyList. 
        /// PublicOK: This class is only available in full trust.
        ///  
        [SecurityCritical] 
        public void Apply()
        { 
            if (_initializing == true)
            {
                throw new InvalidOperationException(SR.Get(SRID.JumpList_CantApplyUntilEndInit));
            } 

            // After this attempting to use ISupportInitialize is invalid. 
            _initializing = false; 

            ApplyList(); 
        }

        /// 
        /// Critical: Calls critical method ApplyList. 
        /// TreatAsSafe: This class is only available in full trust.
        ///  
        [SecurityCritical, SecurityTreatAsSafe] 
        private void ApplyFromApplication()
        { 
            // If we're here and the caller has modified the JumpList without using ISupportInitialize
            // we still want to apply the changes.
            if (_initializing != true && !IsUnmodified)
            { 
                _initializing = false;
            } 
 
            if (_application == Application.Current && _initializing == false)
            { 
                // If we're applying due to being an attached property, then don't apply
                // unless we're really on the current application and wait until EndInit
                // has been called, or the list has otherwise been modified.
                ApplyList(); 
            }
        } 
 
        /// 
        /// Critical: Uses native methods to apply JumpItem data to the Shell's JumpList for the current application. 
        /// 
        [SecurityCritical]
        private void ApplyList()
        { 
            Debug.Assert(_initializing == false);
            Verify.IsApartmentState(ApartmentState.STA); 
 
            // We don't want to force applications to conditionally check this before constructing a JumpList,
            // but if we're not on 7 then this isn't going to work.  Fail fast. 
            if (!Utilities.IsOSWindows7OrNewer)
            {
                RejectEverything();
                return; 
            }
 
            List successList; 
            List<_RejectedJumpItemPair> rejectedList;
 
            // Declare these outside the try block so we can cleanup native resources in the _ShellObjectPairs.
            List> categories = null;
            List<_ShellObjectPair> removedList = null;
            var destinationList = (ICustomDestinationList)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid(CLSID.DestinationList))); 
            try
            { 
                // Even though we're not exposing Shell's AppModelId concept, we'll still respect it 
                // since it's easy to clients to p/invoke to set it for Application and Window, but not for JumpLists
                string appId = _RuntimeId; 
                if (!string.IsNullOrEmpty(appId))
                {
                    destinationList.SetAppID(appId);
                } 

                // The number ot items visible on a jump list is a user setting.  Shell doesn't reject items based on overflow. 
                // We don't bother checking against it because the app can query the setting and manage overflow based on it 
                // if they really care.  We'll happily add too many items with the hope that if the user changes the setting
                // items will be recovered from the overflow. 
                uint slotsVisible;
                Guid removedIid = new Guid(IID.ObjectArray);
                var objectsRemoved = (IObjectArray)destinationList.BeginList(out slotsVisible, ref removedIid);
 
                // Keep track of the items that were previously removed by the user.
                // We don't want to pend any items that are contained in this list. 
                // It's possible that this contains null JumpItems when we were unable to do the conversion. 
                removedList = GenerateJumpItems(objectsRemoved);
 
                // Keep track of the items that have been successfully pended.
                // IMPORTANT: Ensure at the end of this that if the list is applied again that it would
                //     result in items being added to the JumpList in the same order.
                //     This doesn't mean that they'll be ordered the same as how the user added them 
                //     (e.g. categories will be coalesced), but the categories should appear in the same order.
                //     Since when we call AddCategory we're doing it in reverse order, AddCategory augments 
                //     the items in the list in reverse as well.  At the end the final list is reversed. 
                successList = new List(JumpItems.Count);
 
                // Keep track of the items that we couldn't pend, and why.
                rejectedList = new List<_RejectedJumpItemPair>(JumpItems.Count);

                // Need to group the JumpItems based on their categories. 
                // The special "Tasks" category doesn't actually have a name and should be first so it's unconditionally added.
                categories = new List>() { new List<_ShellObjectPair>() }; 
 
                // This is not thread-safe.
                // We're traversing the original list so we're vulnerable to another thread modifying it during the enumeration. 
                foreach (var jumpItem in JumpItems)
                {
                    if (jumpItem == null)
                    { 
                        // App added a null jump item?  Just go through the normal failure mechanisms.
                        rejectedList.Add(new _RejectedJumpItemPair{ JumpItem = jumpItem, Reason = JumpItemRejectionReason.InvalidItem }); 
                        continue; 
                    }
 
                    object shellObject = null;
                    try
                    {
                        shellObject = GetShellObjectForJumpItem(jumpItem); 
                        // If for some reason we couldn't create the item add it to the rejected list.
                        if (shellObject == null) 
                        { 
                            rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.InvalidItem, JumpItem = jumpItem });
                            continue; 
                        }

                        // Don't add this item if it's in the list of items previously removed by the user.
                        if (ListContainsShellObject(removedList, shellObject)) 
                        {
                            rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.RemovedByUser, JumpItem = jumpItem }); 
                            continue; 
                        }
 
                        var shellMap = new _ShellObjectPair { JumpItem = jumpItem, ShellObject = shellObject };
                        if (string.IsNullOrEmpty(jumpItem.CustomCategory))
                        {
                            // No custom category, so add to the Tasks list. 
                            categories[0].Add(shellMap);
                        } 
                        else 
                        {
                            // Find the appropriate category and add to that list. 
                            // If it doesn't exist, add a new category for it.
                            bool categoryExists = false;
                            foreach (var list in categories)
                            { 
                                // The first item in the category list can be used to check the name.
                                if (list.Count > 0 && list[0].JumpItem.CustomCategory == jumpItem.CustomCategory) 
                                { 
                                    list.Add(shellMap);
                                    categoryExists = true; 
                                    break;
                                }
                            }
                            if (!categoryExists) 
                            {
                                categories.Add(new List<_ShellObjectPair>() { shellMap }); 
                            } 
                        }
 
                        // Shell interface is now owned by the category list.
                        shellObject = null;
                    }
                    finally 
                    {
                        Utilities.SafeRelease(ref shellObject); 
                    } 
                }
 
                // Jump List categories get added top-down, except for "Tasks" which is special and always at the bottom.
                // We want the Recent/Frequent to always be at the top so they get added first.
                // Logically the categories are added bottom-up, but their contents are top-down,
                // so we reverse the order we add the categories to the destinationList. 
                // To preserve the item ordering AddCategory also adds items in reverse.
                // We need to reverse the final list when everything is done. 
                categories.Reverse(); 

                if (ShowFrequentCategory) 
                {
                    destinationList.AppendKnownCategory(KDC.FREQUENT);
                }
 
                if (ShowRecentCategory)
                { 
                    destinationList.AppendKnownCategory(KDC.RECENT); 
                }
 
                // Now that all the JumpItems are grouped add them to the custom destinations list.
                foreach (List<_ShellObjectPair> categoryList in categories)
                {
                    if (categoryList.Count > 0) 
                    {
                        string categoryHeader = categoryList[0].JumpItem.CustomCategory; 
                        AddCategory(destinationList, categoryHeader, categoryList, successList, rejectedList); 
                    }
                } 

                destinationList.CommitList();
            }
            catch 
            {
                // It's not okay to throw an exception here.  If Shell is rejecting the JumpList for some reason 
                // we don't want to be responsible for the app throwing an exception in its startup path. 
                // For common use patterns there isn't really any user code on the stack, so there isn't
                // an opportunity to catch this in the app. 
                // We can instead handle this.

                // Try to notify the developer if they're hitting this.
                if (TraceShell.IsEnabled) 
                {
                    TraceShell.Trace(TraceEventType.Error, TraceShell.RejectingJumpItemsBecauseCatastrophicFailure); 
                } 
                RejectEverything();
                return; 
            }
            finally
            {
                // Deterministically release native resources. 

                Utilities.SafeRelease(ref destinationList); 
 
                if (categories != null)
                { 
                    foreach (List<_ShellObjectPair> list in categories)
                    {
                        _ShellObjectPair.ReleaseShellObjects(list);
                    } 
                }
 
                // Note that this only clears the ShellObjects, not the JumpItems. 
                // We still need the JumpItems out of this list for the JumpItemsRemovedByUser event.
                _ShellObjectPair.ReleaseShellObjects(removedList); 
            }

            // Swap the current list with what we were able to successfully place into the JumpList.
            // Reverse it first to ensure that the items are in a repeatable order. 
            successList.Reverse();
            _jumpItems = successList; 
 
            // Raise the events for rejected and removed.
            EventHandler rejectedHandler = JumpItemsRejected; 
            EventHandler removedHandler = JumpItemsRemovedByUser;

            if (rejectedList.Count > 0 && rejectedHandler != null)
            { 
                var items = new List(rejectedList.Count);
                var reasons = new List(rejectedList.Count); 
 
                foreach (_RejectedJumpItemPair rejectionPair in rejectedList)
                { 
                    items.Add(rejectionPair.JumpItem);
                    reasons.Add(rejectionPair.Reason);
                }
 
                rejectedHandler(this, new JumpItemsRejectedEventArgs(items, reasons));
            } 
 
            if (removedList.Count > 0 && removedHandler != null)
            { 
                var items = new List(removedList.Count);
                foreach (_ShellObjectPair shellMap in removedList)
                {
                    // It's possible that not every shell object could be converted to a JumpItem. 
                    if (shellMap.JumpItem != null)
                    { 
                        items.Add(shellMap.JumpItem); 
                    }
                } 

                if (items.Count > 0)
                {
                    removedHandler(this, new JumpItemsRemovedEventArgs(items)); 
                }
            } 
        } 

        ///  
        /// Critical: This accesses methods on native interfaces IShellLink and IShellItem.
        /// TreatAsSafe: This is checking for equality in a provided list.  This information is safe.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private static bool ListContainsShellObject(List<_ShellObjectPair> removedList, object shellObject)
        { 
            Debug.Assert(removedList != null); 
            Debug.Assert(shellObject != null);
 
            if (removedList.Count == 0)
            {
                return false;
            } 

            // Casts in .Net don't AddRef.  Don't need to release these. 
            var shellItem = shellObject as IShellItem; 
            if (shellItem != null)
            { 
                foreach (var shellMap in removedList)
                {
                    var removedItem = shellMap.ShellObject as IShellItem;
                    if (removedItem != null) 
                    {
                        if (0 == shellItem.Compare(removedItem, SICHINT.CANONICAL | SICHINT.TEST_FILESYSPATH_IF_NOT_EQUAL)) 
                        { 
                            return true;
                        } 
                    }
                }
                return false;
            } 

            var shellLink = shellObject as IShellLinkW; 
            if (shellLink != null) 
            {
                foreach (var shellMap in removedList) 
                {
                    var removedLink = shellMap.ShellObject as IShellLinkW;
                    if (removedLink != null)
                    { 
                        // There's no intrinsic comparison function for ShellLinks.
                        // Talking to the Shell guys, the way they compare these is to catenate a string with 
                        // a normalized version of the app path and the unmodified args. 
                        // If the two strings ordinally compare, they're the same.
                        string removedLinkString = ShellLinkToString(removedLink); 
                        string linkString = ShellLinkToString(shellLink);

                        if (removedLinkString == linkString)
                        { 
                            return true;
                        } 
                    } 
                }
                return false; 
            }

            // Unlikely.  It's not a supported shell interface?
            return false; 
        }
 
        ///  
        /// 
        ///  
        /// This returns a native COM object that should be deterministically released by the caller, when possible.
        /// 
        /// 
        /// Critical: Calls critical methods CreateItemForJumpPath and CreateLinkforJumpTask. 
        /// 
        [SecurityCritical] 
        private static object GetShellObjectForJumpItem(JumpItem jumpItem) 
        {
            var jumpPath = jumpItem as JumpPath; 
            var jumpTask = jumpItem as JumpTask;

            // Either of these create functions could return null if the item is invalid but they shouldn't throw.
            if (jumpPath != null) 
            {
                return CreateItemFromJumpPath(jumpPath); 
            } 
            else if (jumpTask != null)
            { 
                return CreateLinkFromJumpTask(jumpTask, true);
            }

            // Unsupported type? 
            Debug.Assert(false);
            return null; 
        } 

        ///  
        /// Critical: Creates instances of native interfaces IShellItem and IShellLink.
        /// 
        [SecurityCritical]
        private static List<_ShellObjectPair> GenerateJumpItems(IObjectArray shellObjects) 
        {
            Debug.Assert(shellObjects != null); 
 
            var retList = new List<_ShellObjectPair>();
 
            Guid unknownIid = new Guid(IID.Unknown);
            uint count = shellObjects.GetCount();
            for (uint i = 0; i < count; ++i)
            { 
                // This is potentially a heterogenous list, so get as an IUnknown and QI afterwards.
                object unk = shellObjects.GetAt(i, ref unknownIid); 
                JumpItem item = null; 
                try
                { 
                    item = GetJumpItemForShellObject(unk);
                }
                catch (Exception e)
                { 
                    if (e is NullReferenceException || e is System.Runtime.InteropServices.SEHException)
                    { 
                        throw; 
                    }
                    // If we failed the conversion we still want to keep the shell interface for comparision. 
                    // Just leave the JumpItem property as null.
                }
                retList.Add(new _ShellObjectPair { ShellObject = unk, JumpItem = item });
            } 

            return retList; 
        } 

        ///  
        /// Critical: Calls critical AddCategory.
        /// 
        [SecurityCritical]
        private static void AddCategory(ICustomDestinationList cdl, string category, List<_ShellObjectPair> jumpItems, List successList, List<_RejectedJumpItemPair> rejectionList) 
        {
            AddCategory(cdl, category, jumpItems, successList, rejectionList, true); 
        } 

        ///  
        /// Critical: Calls methods on native interfaces ICustomDestinationList, IShellItem, and IShellLink.
        /// 
        [SecurityCritical]
        private static void AddCategory(ICustomDestinationList cdl, string category, List<_ShellObjectPair> jumpItems, List successList, List<_RejectedJumpItemPair> rejectionList, bool isHeterogenous) 
        {
            Debug.Assert(jumpItems.Count != 0); 
            Debug.Assert(cdl != null); 

            HRESULT hr; 
            var shellObjectCollection = (IObjectCollection)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid(CLSID.EnumerableObjectCollection)));

            foreach (var itemMap in jumpItems)
            { 
                shellObjectCollection.AddObject(itemMap.ShellObject);
            } 
 
            if (string.IsNullOrEmpty(category))
            { 
                hr = cdl.AddUserTasks((IObjectArray)shellObjectCollection);
            }
            else
            { 
                hr = cdl.AppendCategory(category, (IObjectArray)shellObjectCollection);
            } 
 
            if (hr.Succeeded)
            { 
                // Woot! Add these items to the list.
                // Do it in reverse order so Apply has the items in the correct order.
                for (int i = jumpItems.Count; --i >= 0;)
                { 
                    successList.Add(jumpItems[i].JumpItem);
                } 
            } 
            else
            { 
                // If the list contained items that could not be added because this object isn't a handler
                // then drop all ShellItems and retry without them.
                if (isHeterogenous && hr == HRESULT.DESTS_E_NO_MATCHING_ASSOC_HANDLER)
                { 
                    if (TraceShell.IsEnabled)
                    { 
                        TraceShell.Trace(TraceEventType.Error, TraceShell.RejectingJumpListCategoryBecauseNoRegisteredHandler(category)); 
                    }
 
                    Utilities.SafeRelease(ref shellObjectCollection);
                    var linksOnlyList = new List<_ShellObjectPair>();
                    foreach (var itemMap in jumpItems)
                    { 
                        if (itemMap.JumpItem is JumpPath)
                        { 
                            rejectionList.Add(new _RejectedJumpItemPair { JumpItem = itemMap.JumpItem, Reason = JumpItemRejectionReason.NoRegisteredHandler }); 
                        }
                        else 
                        {
                            linksOnlyList.Add(itemMap);
                        }
                    } 
                    if (linksOnlyList.Count > 0)
                    { 
                        // There's not a reason I know of that we should reject a list of only links... 
                        Debug.Assert(jumpItems.Count != linksOnlyList.Count);
                        AddCategory(cdl, category, linksOnlyList, successList, rejectionList, false); 
                    }
                }
                else
                { 
                    Debug.Assert(HRESULT.DESTS_E_NO_MATCHING_ASSOC_HANDLER != hr);
                    // If we failed for some other reason, just reject everything. 
                    foreach (var item in jumpItems) 
                    {
                        rejectionList.Add(new _RejectedJumpItemPair { JumpItem = item.JumpItem, Reason = JumpItemRejectionReason.InvalidItem }); 
                    }
                }
            }
        } 

        ///  
        /// Critical: Assigned in an elevated context. 
        /// 
        [SecurityCritical] 
        private static readonly string _FullName;

        #region Converter methods
 
        /// 
        /// Critical: Creates instance of native interface IShellLinkW 
        ///  
        [SecurityCritical]
        private static IShellLinkW CreateLinkFromJumpTask(JumpTask jumpTask, bool allowSeparators) 
        {
            Debug.Assert(jumpTask != null);

            // Title is generally required.  If it's missing we need to treat this like a separator. 
            // Everything else can still appear on separator elements,
            // but separators can only exist in the Tasks category. 
            if (string.IsNullOrEmpty(jumpTask.Title)) 
            {
                if (!allowSeparators || !string.IsNullOrEmpty(jumpTask.CustomCategory)) 
                {
                    // Just treat this situation as an InvalidItem.
                    return null;
                } 
            }
 
            var link = (IShellLinkW)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid(CLSID.ShellLink))); 
            try
            { 
                string appPath = _FullName;
                if (!string.IsNullOrEmpty(jumpTask.ApplicationPath))
                {
                    appPath = jumpTask.ApplicationPath; 
                }
 
                link.SetPath(appPath); 

                // This is optional.  Don't set it if the app hasn't explicitly requested it. 
                if (!string.IsNullOrEmpty(jumpTask.WorkingDirectory))
                {
                    // Don't verify this.  It's possible that the directory doesn't exist now, but it will later.
                    // Shell handles this fine when we try to set an improperly formatted path. 
                    link.SetWorkingDirectory(jumpTask.WorkingDirectory);
                } 
 
                if (!string.IsNullOrEmpty(jumpTask.Arguments))
                { 
                    link.SetArguments(jumpTask.Arguments);
                }

                // -1 is a sentinel value indicating not to use the icon. 
                if (jumpTask.IconResourceIndex != -1)
                { 
                    string resourcePath = _FullName; 
                    if (!string.IsNullOrEmpty(jumpTask.IconResourcePath))
                    { 
                        // Shell bug (Windows 7 595770): IShellLink doesn't correctly limit icon location path to MAX_PATH.
                        // It's really too bad we have to enforce this here.  When the shortcut gets
                        // serialized it streams the full string.  On deserialization it only retrieves
                        // MAX_PATH for this field leaving junk behind for subsequent gets, leading to data corruption. 
                        // Because we don't want to allow the app to do create something that we know may
                        // be corrupt we have to enforce this ourselves.  If Shell fixes this later then 
                        // we need to remove this check to let them handle this as they see fit. 
                        // If they fix it by supporting longer paths, then we're artificially constraining this value...
                        if (jumpTask.IconResourcePath.Length >= Win32Constant.MAX_PATH) 
                        {
                            // we could throw the exception here, but we're already globally catching everything.
                            return null;
                        } 
                        resourcePath = jumpTask.IconResourcePath;
                    } 
                    link.SetIconLocation(resourcePath, jumpTask.IconResourceIndex); 
                }
 
                if (!string.IsNullOrEmpty(jumpTask.Description))
                {
                    link.SetDescription(jumpTask.Description);
                } 

                IPropertyStore propStore = (IPropertyStore)link; 
                var pv = new PROPVARIANT(); 
                try
                { 
                    PKEY pkey = default(PKEY);

                    if (!string.IsNullOrEmpty(jumpTask.Title))
                    { 
                        pv.SetValue(jumpTask.Title);
                        pkey = PKEY.Title; 
                    } 
                    else
                    { 
                        pv.SetValue(true);
                        pkey = PKEY.AppUserModel_IsDestListSeparator;
                    }
 
                    propStore.SetValue(ref pkey, pv);
                } 
                finally 
                {
                    Utilities.SafeDispose(ref pv); 
                }

                propStore.Commit();
 
                IShellLinkW retLink = link;
                link = null; 
                return retLink; 
            }
            catch (Exception) 
            {
                // IShellLinkW::Set* methods tend to return E_FAIL when trying to set invalid data.
                // The create methods don't explicitly check for these kinds of errors.
 
                // If we aren't able to create the item for any reason just return null to indicate an invalid item.
                return null; 
            } 
            finally
            { 
                Utilities.SafeRelease(ref link);
            }
        }
 
        /// 
        /// Critical: Creates instance of native interface IShellItem2. 
        ///  
        [SecurityCritical]
        private static IShellItem2 CreateItemFromJumpPath(JumpPath jumpPath) 
        {
            Debug.Assert(jumpPath != null);

            try 
            {
                // This will return null if the path doesn't exist. 
                return ShellUtil.GetShellItemForPath(Path.GetFullPath(jumpPath.Path)); 
            }
            catch (Exception) 
            {
                // Don't propagate exceptions here.  If we couldn't create the item, it's just invalid.
            }
 
            return null;
        } 
 
        /// 
        /// Critical: Accesses methods on native interfaces IShellItem and IShellLink. 
        /// 
        [SecurityCritical]
        private static JumpItem GetJumpItemForShellObject(object shellObject)
        { 
            var shellItem = shellObject as IShellItem2;
            var shellLink = shellObject as IShellLinkW; 
 
            if (shellItem != null)
            { 
                JumpPath path = new JumpPath
                {
                    Path = shellItem.GetDisplayName(SIGDN.DESKTOPABSOLUTEPARSING),
                }; 
                return path;
            } 
 
            if (shellLink != null)
            { 
                var pathBuilder = new StringBuilder(Win32Constant.MAX_PATH);
                shellLink.GetPath(pathBuilder, pathBuilder.Capacity, null, SLGP.RAWPATH);
                var argsBuilder = new StringBuilder(Win32Constant.INFOTIPSIZE);
                shellLink.GetArguments(argsBuilder, argsBuilder.Capacity); 
                var descBuilder = new StringBuilder(Win32Constant.INFOTIPSIZE);
                shellLink.GetDescription(descBuilder, descBuilder.Capacity); 
                var iconBuilder = new StringBuilder(Win32Constant.MAX_PATH); 
                int iconIndex;
                shellLink.GetIconLocation(iconBuilder, iconBuilder.Capacity, out iconIndex); 
                var dirBuilder = new StringBuilder(Win32Constant.MAX_PATH);
                shellLink.GetWorkingDirectory(dirBuilder, dirBuilder.Capacity);

                JumpTask task = new JumpTask 
                {
                    // Set ApplicationPath and IconResources, even if they're from the current application. 
                    // This means that equivalent JumpTasks won't necessarily compare property-for-property. 
                    ApplicationPath = pathBuilder.ToString(),
                    Arguments = argsBuilder.ToString(), 
                    Description = descBuilder.ToString(),
                    IconResourceIndex = iconIndex,
                    IconResourcePath = iconBuilder.ToString(),
                    WorkingDirectory = dirBuilder.ToString(), 
                };
 
                using (PROPVARIANT pv = new PROPVARIANT()) 
                {
                    var propStore = (IPropertyStore)shellLink; 
                    PKEY pkeyTitle = PKEY.Title;

                    propStore.GetValue(ref pkeyTitle, pv);
 
                    // PKEY_Title should be an LPWSTR if it's not empty.
                    task.Title = pv.GetValue() ?? ""; 
                } 

                return task; 
            }

            // Unsupported type?
            Debug.Assert(false); 
            return null;
        } 
 
        /// 
        /// Generate a unique string for the ShellLink that can be used for equality checks. 
        /// 
        [SecurityCritical]
        private static string ShellLinkToString(IShellLinkW shellLink)
        { 
            var pathBuilder = new StringBuilder(Win32Constant.MAX_PATH);
            shellLink.GetPath(pathBuilder, pathBuilder.Capacity, null, SLGP.RAWPATH); 
 
            string title = null;
            // Need to use the property store to get the title for the link. 
            using (PROPVARIANT pv = new PROPVARIANT())
            {
                var propStore = (IPropertyStore)shellLink;
                PKEY pkeyTitle = PKEY.Title; 

                propStore.GetValue(ref pkeyTitle, pv); 
 
                // PKEY_Title should be an LPWSTR if it's not empty.
                title = pv.GetValue() ?? ""; 
            }

            var argsBuilder = new StringBuilder(Win32Constant.INFOTIPSIZE);
            shellLink.GetArguments(argsBuilder, argsBuilder.Capacity); 

            // Path and title should be case insensitive. 
            // Shell treats arguments as case sensitive because apps can handle those differently. 
            return pathBuilder.ToString().ToUpperInvariant() + title.ToUpperInvariant() + argsBuilder.ToString();
        } 

        #endregion

        private void RejectEverything() 
        {
            EventHandler handler = JumpItemsRejected; 
            if (handler == null) 
            {
                _jumpItems.Clear(); 
                return;
            }

            if (_jumpItems.Count > 0) 
            {
                var reasons = new List(JumpItems.Count); 
                for (int i = 0; i < JumpItems.Count; ++i) 
                {
                    reasons.Add(JumpItemRejectionReason.InvalidItem); 
                }
                // We're rejecting everything,
                // so create an event args with the original list and then clear it.
                var args = new JumpItemsRejectedEventArgs(JumpItems, reasons); 
                _jumpItems.Clear();
 
                handler(this, args); 
            }
        } 

        public event EventHandler JumpItemsRejected;

        public event EventHandler JumpItemsRemovedByUser; 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK