/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Windows.Forms; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using IServiceProvider = System.IServiceProvider; using MSBuild = Microsoft.Build.Evaluation; using System.Reflection; using System.Runtime.Versioning; using Microsoft.Build.Utilities; using System.Xml; namespace Microsoft.VisualStudio.Project { public static class VsUtilities { private const string defaultMSBuildVersion = "4.0"; /// /// Look in the registry under the current hive for the path /// of MSBuild /// /// /// /// Is Visual Studio in design mode. /// /// The service provider. /// true if visual studio is in design mode public static bool IsVisualStudioInDesignMode(IServiceProvider site) { VsUtilities.ArgumentNotNull("site", site); IVsMonitorSelection selectionMonitor = site.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection; uint cookie = 0; int active = 0; Guid designContext = VSConstants.UICONTEXT_DesignMode; ErrorHandler.ThrowOnFailure(selectionMonitor.GetCmdUIContextCookie(ref designContext, out cookie)); ErrorHandler.ThrowOnFailure(selectionMonitor.IsCmdUIContextActive(cookie, out active)); return active != 0; } /// /// /// Is an extensibility object executing an automation function. /// /// The service provider. /// true if the extensiblity object is executing an automation function. public static bool IsInAutomationFunction(IServiceProvider serviceProvider) { VsUtilities.ArgumentNotNull("serviceProvider", serviceProvider); IVsExtensibility3 extensibility = serviceProvider.GetService(typeof(EnvDTE.IVsExtensibility)) as IVsExtensibility3; if (extensibility == null) { throw new InvalidOperationException(); } int inAutomation = 0; ErrorHandler.ThrowOnFailure(extensibility.IsInAutomationFunction(out inAutomation)); return inAutomation != 0; } /// /// Creates a semicolon delinited list of strings. This can be used to provide the properties for VSHPROPID_CfgPropertyPagesCLSIDList, VSHPROPID_PropertyPagesCLSIDList, VSHPROPID_PriorityPropertyPagesCLSIDList /// /// An array of Guids. /// A semicolon delimited string, or null public static string CreateSemicolonDelimitedListOfStringFromGuids(Guid[] guids) { if (guids == null || guids.Length == 0) { return String.Empty; } // Create a StringBuilder with a pre-allocated buffer big enough for the // final string. 39 is the length of a GUID in the "B" form plus the final ';' StringBuilder stringList = new StringBuilder(39 * guids.Length); for (int i = 0; i < guids.Length; i++) { stringList.Append(guids[i].ToString("B")); stringList.Append(";"); } return stringList.ToString().TrimEnd(';'); } private static char[] curlyBraces = new char[] { '{', '}' }; /// /// Take list of guids as a single string and generate an array of Guids from it /// /// Semi-colon separated list of Guids /// Array of Guids public static Guid[] GuidsArrayFromSemicolonDelimitedStringOfGuids(string guidList) { if (guidList == null) { return null; } List guids = new List(); string[] guidsStrings = guidList.Split(';'); foreach (string guid in guidsStrings) { if (!String.IsNullOrEmpty(guid)) guids.Add(new Guid(guid.Trim(curlyBraces))); } return guids.ToArray(); } public static void CheckNotNull(object value) { if (value == null) { throw new InvalidOperationException(); } } public static void ArgumentNotNull(string name, object value) { if (value == null) { throw new ArgumentNullException(name); } } public static void ArgumentNotNullOrEmpty(string name, string value) { if (String.IsNullOrEmpty(value)) { throw new ArgumentNullException(name); } } /// /// Validates a file path by validating all file parts. If the /// the file name is invalid it throws an exception if the project is in automation. Otherwise it shows a dialog box with the error message. /// /// The service provider /// A full path to a file name /// In case of failure an InvalidOperationException is thrown. public static void ValidateFileName(IServiceProvider serviceProvider, string filePath) { string errorMessage = String.Empty; if (String.IsNullOrEmpty(filePath)) { errorMessage = String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), filePath); } else if (filePath.Length > NativeMethods.MAX_PATH) { errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.PathTooLong, CultureInfo.CurrentUICulture), filePath); } else if (ContainsInvalidFileNameChars(filePath)) { errorMessage = String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), filePath); } if (errorMessage.Length == 0) { string fileName = Path.GetFileName(filePath); if (String.IsNullOrEmpty(fileName) || IsFileNameInvalid(fileName)) { errorMessage = String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), filePath); } } if (errorMessage.Length > 0) { // If it is not called from an automation method show a dialog box. if (!VsUtilities.IsInAutomationFunction(serviceProvider)) { string title = null; OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL; OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK; OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST; VsShellUtilities.ShowMessageBox(serviceProvider, title, errorMessage, icon, buttons, defaultButton); } else { throw new InvalidOperationException(errorMessage); } } } /// /// Creates a CALPOLESTR from a list of strings /// It is the responsability of the caller to release this memory. /// /// /// A CALPOLESTR that was created from the the list of strings. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CALPOLESTR")] public static CALPOLESTR CreateCALPOLESTR(IList strings) { CALPOLESTR calpolStr = new CALPOLESTR(); if (strings != null) { // Demand unmanaged permissions in order to access unmanaged memory. new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); calpolStr.cElems = (uint)strings.Count; int size = Marshal.SizeOf(typeof(IntPtr)); calpolStr.pElems = Marshal.AllocCoTaskMem(strings.Count * size); IntPtr ptr = calpolStr.pElems; foreach (string aString in strings) { IntPtr tempPtr = Marshal.StringToCoTaskMemUni(aString); Marshal.WriteIntPtr(ptr, tempPtr); ptr = new IntPtr(ptr.ToInt64() + size); } } return calpolStr; } /// /// Creates a CADWORD from a list of tagVsSccFilesFlags. Memory is allocated for the elems. /// It is the responsability of the caller to release this memory. /// /// /// A CADWORD created from the list of tagVsSccFilesFlags. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CADWORD")] public static CADWORD CreateCADWORD(IList flags) { CADWORD cadWord = new CADWORD(); if (flags != null) { // Demand unmanaged permissions in order to access unmanaged memory. new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); cadWord.cElems = (uint)flags.Count; int size = Marshal.SizeOf(typeof(UInt32)); cadWord.pElems = Marshal.AllocCoTaskMem(flags.Count * size); IntPtr ptr = cadWord.pElems; foreach (tagVsSccFilesFlags flag in flags) { Marshal.WriteInt32(ptr, (int)flag); ptr = new IntPtr(ptr.ToInt64() + size); } } return cadWord; } /// /// Splits a bitmap from a Stream into an ImageList /// /// A Stream representing a Bitmap /// An ImageList object representing the images from the given stream [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public static ImageList GetImageList(Stream imageStream) { ImageList ilist = new ImageList(); if (imageStream == null) { return ilist; } ilist.ColorDepth = ColorDepth.Depth24Bit; ilist.ImageSize = new Size(16, 16); Bitmap bitmap = new Bitmap(imageStream); if (bitmap.Height != ilist.ImageSize.Height) { Bitmap newBitmap = new Bitmap((int)Math.Ceiling(bitmap.Width * ((float)ilist.ImageSize.Height / bitmap.Height)), ilist.ImageSize.Height); using (Graphics g = Graphics.FromImage(newBitmap)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.DrawImage(bitmap, 0, 0, newBitmap.Width, newBitmap.Height); } bitmap.Dispose(); bitmap = newBitmap; } ilist.Images.AddStrip(bitmap); ilist.TransparentColor = Color.Magenta; return ilist; } /// /// Gets the active configuration name. /// /// The automation object. /// The name of the active configuartion. public static string GetActiveConfigurationName(EnvDTE.Project automationObject) { VsUtilities.ArgumentNotNull("automationObject", automationObject); string currentConfigName = string.Empty; if (automationObject.ConfigurationManager != null) { EnvDTE.Configuration activeConfig = automationObject.ConfigurationManager.ActiveConfiguration; if (activeConfig != null) { currentConfigName = activeConfig.ConfigurationName; } } return currentConfigName; } /// /// Gets the active platform name. /// /// The automation object. /// The name of the active platform. public static string GetActivePlatformName(EnvDTE.Project automationObject) { VsUtilities.ArgumentNotNull("automationObject", automationObject); string currentPlatformName = string.Empty; if (automationObject.ConfigurationManager != null) { EnvDTE.Configuration activeConfig = automationObject.ConfigurationManager.ActiveConfiguration; if (activeConfig != null) { currentPlatformName = activeConfig.PlatformName; } } return currentPlatformName; } /// /// Verifies that two objects represent the same instance of a COM object. /// This essentially compares the IUnkown pointers of the 2 objects. /// This is needed in scenario where aggregation is involved. /// /// Can be an object, interface or IntPtr /// Can be an object, interface or IntPtr /// True if the 2 items represent the same thing [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj")] public static bool IsSameComObject(object obj1, object obj2) { bool isSame = false; IntPtr unknown1 = IntPtr.Zero; IntPtr unknown2 = IntPtr.Zero; try { // If we have 2 null, then they are not COM objects and as such "it's not the same COM object" if (obj1 != null && obj2 != null) { unknown1 = QueryInterfaceIUnknown(obj1); unknown2 = QueryInterfaceIUnknown(obj2); isSame = IntPtr.Equals(unknown1, unknown2); } } finally { if (unknown1 != IntPtr.Zero) { Marshal.Release(unknown1); } if (unknown2 != IntPtr.Zero) { Marshal.Release(unknown2); } } return isSame; } /// /// Retrieve the IUnknown for the managed or COM object passed in. /// /// Managed or COM object. /// Pointer to the IUnknown interface of the object. public static IntPtr QueryInterfaceIUnknown(object objToQuery) { bool releaseIt = false; IntPtr unknown = IntPtr.Zero; IntPtr result; try { if (objToQuery is IntPtr) { unknown = (IntPtr)objToQuery; } else { // This is a managed object (or RCW) unknown = Marshal.GetIUnknownForObject(objToQuery); releaseIt = true; } // We might already have an IUnknown, but if this is an aggregated // object, it may not be THE IUnknown until we QI for it. Guid IID_IUnknown = VSConstants.IID_IUnknown; ErrorHandler.ThrowOnFailure(Marshal.QueryInterface(unknown, ref IID_IUnknown, out result)); } finally { if (releaseIt && unknown != IntPtr.Zero) { Marshal.Release(unknown); } } return result; } /// /// Returns true if thename that can represent a path, absolut or relative, or a file name contains invalid filename characters. /// /// File name /// true if file name is invalid [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "The name is validated.")] public static bool ContainsInvalidFileNameChars(string name) { if (String.IsNullOrEmpty(name)) { return true; } try { if (Path.IsPathRooted(name) && !name.StartsWith(@"\\", StringComparison.Ordinal)) { string root = Path.GetPathRoot(name); name = name.Substring(root.Length); } } // The Path methods used by ContainsInvalidFileNameChars return argument exception if the filePath contains invalid characters. catch (ArgumentException) { return true; } Microsoft.VisualStudio.Shell.Url uri = new Microsoft.VisualStudio.Shell.Url(name); // This might be confusing bur Url.IsFile means that the uri represented by the name is either absolut or relative. if (uri.IsFile) { string[] segments = uri.Segments; if (segments != null && segments.Length > 0) { foreach (string segment in segments) { if (IsFilePartInValid(segment)) { return true; } } // Now the last segment should be specially taken care, since that cannot be all dots or spaces. string lastSegment = segments[segments.Length - 1]; string filePart = Path.GetFileNameWithoutExtension(lastSegment); // if the file is only an extension (.foo) then it's ok, otherwise we need to do the special checks. if (filePart.Length != 0 && (IsFileNameAllGivenCharacter('.', filePart) || IsFileNameAllGivenCharacter(' ', filePart))) { return true; } } } else { // The assumption here is that we got a file name. string filePart = Path.GetFileNameWithoutExtension(name); if (IsFileNameAllGivenCharacter('.', filePart) || IsFileNameAllGivenCharacter(' ', filePart)) { return true; } return IsFilePartInValid(name); } return false; } /// Cehcks if a file name is valid. /// /// The name of the file /// True if the file is valid. public static bool IsFileNameInvalid(string fileName) { if (String.IsNullOrEmpty(fileName)) { return true; } if (IsFileNameAllGivenCharacter('.', fileName) || IsFileNameAllGivenCharacter(' ', fileName)) { return true; } return IsFilePartInValid(fileName); } /// /// Initializes the in memory project. Sets BuildEnabled on the project to true. /// /// The build engine to use to create a build project. /// The full path of the project. /// A loaded msbuild project. public static MSBuild.Project InitializeMsBuildProject(MSBuild.ProjectCollection buildEngine, string fullProjectPath) { VsUtilities.ArgumentNotNullOrEmpty("fullProjectPath", fullProjectPath); // Call GetFullPath to expand any relative path passed into this method. fullProjectPath = CommonUtils.NormalizePath(fullProjectPath); // Check if the project already has been loaded with the fullProjectPath. If yes return the build project associated to it. List loadedProject = new List(buildEngine.GetLoadedProjects(fullProjectPath)); MSBuild.Project buildProject = loadedProject != null && loadedProject.Count > 0 && loadedProject[0] != null ? loadedProject[0] : null; if (buildProject == null) { buildProject = buildEngine.LoadProject(fullProjectPath); } return buildProject; } /// /// Loads a project file for the file. If the build project exists and it was loaded with a different file then it is unloaded first. /// /// The build engine to use to create a build project. /// The full path of the project. /// An Existing build project that will be reloaded. /// A loaded msbuild project. public static MSBuild.Project ReinitializeMsBuildProject(MSBuild.ProjectCollection buildEngine, string fullProjectPath, MSBuild.Project exitingBuildProject) { // If we have a build project that has been loaded with another file unload it. try { if (exitingBuildProject != null && exitingBuildProject.ProjectCollection != null && !CommonUtils.IsSamePath(exitingBuildProject.FullPath, fullProjectPath)) { buildEngine.UnloadProject(exitingBuildProject); } } // We catch Invalid operation exception because if the project was unloaded while we touch the ParentEngine the msbuild API throws. // Is there a way to figure out that a project was unloaded? catch (InvalidOperationException) { } return VsUtilities.InitializeMsBuildProject(buildEngine, fullProjectPath); } /// /// Initialize the build engine. Sets the build enabled property to true. The engine is initialzed if the passed in engine is null or does not have its bin path set. /// /// An instance of MSBuild.ProjectCollection build engine, that will be checked if initialized. /// The service provider. /// The buildengine to use. public static MSBuild.ProjectCollection InitializeMsBuildEngine(MSBuild.ProjectCollection existingEngine, IServiceProvider serviceProvider) { VsUtilities.ArgumentNotNull("serviceProvider", serviceProvider); if (existingEngine == null) { MSBuild.ProjectCollection buildEngine = MSBuild.ProjectCollection.GlobalProjectCollection; return buildEngine; } return existingEngine; } /// > /// Checks if the file name is all the given character. /// private static bool IsFileNameAllGivenCharacter(char c, string fileName) { // A valid file name cannot be all "c" . int charFound = 0; for (charFound = 0; charFound < fileName.Length && fileName[charFound] == c; ++charFound) ; if (charFound >= fileName.Length) { return true; } return false; } private const string _reservedName = "(\\b(nul|con|aux|prn)\\b)|(\\b((com|lpt)[0-9])\\b)"; private const string _invalidChars = "([\\/:*?\"<>|#%])"; private const string _regexToUseForFileName = _reservedName + "|" + _invalidChars; private static Regex _unsafeFileNameCharactersRegex = new Regex(_regexToUseForFileName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static Regex _unsafeCharactersRegex = new Regex(_invalidChars, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); /// /// Checks whether a file part contains valid characters. The file part can be any part of a non rooted path. /// /// /// private static bool IsFilePartInValid(string filePart) { if (String.IsNullOrEmpty(filePart)) { return true; } String fileNameToVerify = filePart; // Define a regular expression that covers all characters that are not in the safe character sets. // It is compiled for performance. // The filePart might still be a file and extension. If it is like that then we must check them separately, since different rules apply string extension = String.Empty; try { extension = Path.GetExtension(filePart); } // We catch the ArgumentException because we want this method to return true if the filename is not valid. FilePart could be for example #¤&%"¤&"% and that would throw ArgumentException on GetExtension catch (ArgumentException) { return true; } if (!String.IsNullOrEmpty(extension)) { // Check the extension first bool isMatch = _unsafeCharactersRegex.IsMatch(extension); if (isMatch) { return isMatch; } // We want to verify here everything but the extension. // We cannot use GetFileNameWithoutExtension because it might be that for example (..\\filename.txt) is passed in and that should fail, since that is not a valid filename. fileNameToVerify = filePart.Substring(0, filePart.Length - extension.Length); if (String.IsNullOrEmpty(fileNameToVerify)) { // http://pytools.codeplex.com/workitem/497 // .foo is ok return false; } } // We verify CLOCK$ outside the regex since for some reason the regex is not matching the clock\\$ added. if (String.Equals(fileNameToVerify, "CLOCK$", StringComparison.OrdinalIgnoreCase)) { return true; } return _unsafeFileNameCharactersRegex.IsMatch(fileNameToVerify); } /// /// Copy a directory recursively to the specified non-existing directory /// /// Directory to copy from /// Directory to copy to public static void RecursivelyCopyDirectory(string source, string target) { // Make sure it doesn't already exist if (Directory.Exists(target)) throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FileOrFolderAlreadyExists, CultureInfo.CurrentUICulture), target)); Directory.CreateDirectory(target); DirectoryInfo directory = new DirectoryInfo(source); // Copy files foreach (FileInfo file in directory.GetFiles()) { file.CopyTo(Path.Combine(target, file.Name)); } // Now recurse to child directories foreach (DirectoryInfo child in directory.GetDirectories()) { RecursivelyCopyDirectory(child.FullName, Path.Combine(target, child.Name)); } } /// /// Canonicalizes a file name, including: /// - determines the full path to the file /// - casts to upper case /// Canonicalizing a file name makes it possible to compare file names using simple simple string comparison. /// /// Note: this method does not handle shared drives and UNC drives. /// /// A file name, which can be relative/absolute and contain lower-case/upper-case characters. /// Canonicalized file name. public static string CanonicalizeFileName(string anyFileName) { // Get absolute path // Note: this will not handle UNC paths FileInfo fileInfo = new FileInfo(anyFileName); string fullPath = fileInfo.FullName; // Cast to upper-case fullPath = fullPath.ToUpperInvariant(); return fullPath; } /// /// Determines if a file is a template. /// /// The file to check whether it is a template file /// true if the file is a template file public static bool IsTemplateFile(string fileName) { if (String.IsNullOrEmpty(fileName)) { return false; } string extension = Path.GetExtension(fileName); return (String.Equals(extension, ".vstemplate", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".vsz", StringComparison.OrdinalIgnoreCase)); } /// /// Save dirty files /// /// Whether succeeded public static bool SaveDirtyFiles() { var rdt = ServiceProvider.GlobalProvider.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; if (rdt != null) { // Consider using (uint)(__VSRDTSAVEOPTIONS.RDTSAVEOPT_SaveIfDirty | __VSRDTSAVEOPTIONS.RDTSAVEOPT_PromptSave) // when VS settings include prompt for save on build var saveOpt = (uint)__VSRDTSAVEOPTIONS.RDTSAVEOPT_SaveIfDirty; var hr = rdt.SaveDocuments(saveOpt, null, VSConstants.VSITEMID_NIL, VSConstants.VSCOOKIE_NIL); if (hr == VSConstants.E_ABORT) { return false; } } return true; } public static EnvDTE.Project GetProject(IServiceProvider services, string projectPath) { EnvDTE.DTE dte = (EnvDTE.DTE)services.GetService(typeof(EnvDTE.DTE)); return GetProject(dte, projectPath); } public static EnvDTE.Project GetProject(EnvDTE.DTE dte, string projectPath) { if ((null == dte) || (null == dte.Solution)) { return null; } // Search for the project in the collection of the projects in the // current solution. foreach (EnvDTE.Project prj in dte.Solution.Projects) { //Skip this project if it is an umodeled project (unloaded) if (string.Compare(EnvDTE.Constants.vsProjectKindUnmodeled, prj.Kind, StringComparison.OrdinalIgnoreCase) == 0) { continue; } // Get the full path of the current project. EnvDTE.Property pathProperty = null; try { if (prj.Properties == null) { continue; } pathProperty = prj.Properties.Item("FullPath"); if (null == pathProperty) { // The full path should alway be availabe, but if this is not the // case then we have to skip it. continue; } } catch (ArgumentException) { continue; } string prjPath = pathProperty.Value.ToString(); EnvDTE.Property fileNameProperty = null; // Get the name of the project file. try { fileNameProperty = prj.Properties.Item("FileName"); if (null == fileNameProperty) { // Again, this should never be the case, but we handle it anyway. continue; } } catch (ArgumentException) { continue; } prjPath = Path.Combine(prjPath, fileNameProperty.Value.ToString()); // If the full path of this project is the same as the one of this // reference, then we have found the right project. if (CommonUtils.IsSamePath(prjPath, projectPath)) { return prj; } } return null; } /*private static Dictionary> frameworkCache = new Dictionary>(); private static void CacheFrameworkAssemblies(FrameworkName framework) { Dictionary dictionary; if (!frameworkCache.TryGetValue(framework.FullName, out dictionary)) { dictionary = new Dictionary(); var frameworkPaths = ToolLocationHelper.GetPathToReferenceAssemblies(framework); foreach (var path in frameworkPaths) { string frameworkList = Path.Combine(path, "RedistList\\FrameworkList.xml"); if (File.Exists(frameworkList)) { using (XmlReader xmlReader = XmlReader.Create(File.OpenRead(frameworkList))) { while (xmlReader.Read()) { if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.LocalName == "File") { string text = null; Version version = null; if (xmlReader.MoveToAttribute("AssemblyName")) { text = xmlReader.Value; } if (xmlReader.MoveToAttribute("Version")) { Version.TryParse(xmlReader.Value, out version); } if (!string.IsNullOrEmpty(text) && version != null) { string key = Path.Combine(path, text + ".dll"); if (!dictionary.ContainsKey(key)) { dictionary[key] = version; } } } } } } } frameworkCache.Add(framework.FullName, dictionary); } } public static bool IsFrameworkAssembly(string assemblyLocation, FrameworkName framework) { if (assemblyLocation == null) throw new ArgumentNullException("assembly"); if (framework == null) throw new ArgumentNullException("framework"); CacheFrameworkAssemblies(framework); Dictionary assemblies; if (frameworkCache.TryGetValue(framework.FullName, out assemblies)) { Version version; if (assemblies.TryGetValue(assemblyLocation, out version)) { return true; } } return false; }*/ } }