PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/Tools/IronStudio/IronStudio/VisualStudio/Project/Utilities.cs

http://github.com/IronLanguages/main
C# | 995 lines | 630 code | 135 blank | 230 comment | 137 complexity | c0c54c7d9859c0a393aeeb0cc582bf5b MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. * ***************************************************************************/
  14. using System;
  15. using System.Collections.Generic;
  16. using System.ComponentModel;
  17. using System.Diagnostics;
  18. using System.Diagnostics.CodeAnalysis;
  19. using System.Drawing;
  20. using System.Globalization;
  21. using System.IO;
  22. using System.Runtime.InteropServices;
  23. using System.Security.Permissions;
  24. using System.Security.Policy;
  25. using System.Text;
  26. using System.Text.RegularExpressions;
  27. using System.Windows.Forms;
  28. using Microsoft.VisualStudio;
  29. using Microsoft.VisualStudio.OLE.Interop;
  30. using Microsoft.VisualStudio.Shell;
  31. using Microsoft.VisualStudio.Shell.Interop;
  32. using Microsoft.Win32;
  33. using IServiceProvider = System.IServiceProvider;
  34. using MSBuild = Microsoft.Build.Evaluation;
  35. using VSRegistry = Microsoft.VisualStudio.Shell.VSRegistry;
  36. namespace Microsoft.VisualStudio.Project
  37. {
  38. public static class Utilities
  39. {
  40. private const string defaultMSBuildVersion = "4.0";
  41. /// <summary>
  42. /// Look in the registry under the current hive for the path
  43. /// of MSBuild
  44. /// </summary>
  45. /// <returns></returns>
  46. [CLSCompliant(false)]
  47. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")]
  48. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "msbuild")]
  49. public static string GetMsBuildPath(IServiceProvider serviceProvider)
  50. {
  51. return GetMsBuildPath(serviceProvider, defaultMSBuildVersion);
  52. }
  53. /// <summary>
  54. /// Search the registry for the tools path for MSBuild.
  55. /// </summary>
  56. /// <param name="serviceProvider">The service provider.</param>
  57. /// <param name="version">Msbuild version.</param>
  58. /// <returns>The msbuild tools path</returns>
  59. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")]
  60. public static string GetMsBuildPath(IServiceProvider serviceProvider, string version)
  61. {
  62. string msBuildPath = null;
  63. using(RegistryKey root = VSRegistry.RegistryRoot(serviceProvider, __VsLocalRegistryType.RegType_Configuration, false))
  64. {
  65. // Get the value from the registry
  66. using(RegistryKey vsKey = root.OpenSubKey("MSBuild", false))
  67. {
  68. msBuildPath = (string)vsKey.GetValue("MSBuildBinPath", null);
  69. }
  70. }
  71. if(!string.IsNullOrEmpty(msBuildPath))
  72. {
  73. return msBuildPath;
  74. }
  75. // The path to MSBuild was not found in the VisualStudio's registry hive, so try to
  76. // find it in the new MSBuild hive.
  77. string registryPath = string.Format(CultureInfo.InvariantCulture, "Software\\Microsoft\\MSBuild\\ToolsVersions\\{0}", version);
  78. using(Microsoft.Win32.RegistryKey msbuildKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(registryPath, false))
  79. {
  80. msBuildPath = (string)msbuildKey.GetValue("MSBuildToolsPath", null);
  81. }
  82. if(string.IsNullOrEmpty(msBuildPath))
  83. {
  84. string error = SR.GetString(SR.ErrorMsBuildRegistration, CultureInfo.CurrentUICulture);
  85. throw new FileLoadException(error);
  86. }
  87. return msBuildPath;
  88. }
  89. /// <summary>
  90. /// Is Visual Studio in design mode.
  91. /// </summary>
  92. /// <param name="serviceProvider">The service provider.</param>
  93. /// <returns>true if visual studio is in design mode</returns>
  94. public static bool IsVisualStudioInDesignMode(IServiceProvider site)
  95. {
  96. if (site == null)
  97. {
  98. throw new ArgumentNullException("site");
  99. }
  100. IVsMonitorSelection selectionMonitor = site.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection;
  101. uint cookie = 0;
  102. int active = 0;
  103. Guid designContext = VSConstants.UICONTEXT_DesignMode;
  104. ErrorHandler.ThrowOnFailure(selectionMonitor.GetCmdUIContextCookie(ref designContext, out cookie));
  105. ErrorHandler.ThrowOnFailure(selectionMonitor.IsCmdUIContextActive(cookie, out active));
  106. return active != 0;
  107. }
  108. /// <include file='doc\VsShellUtilities.uex' path='docs/doc[@for="Utilities.IsInAutomationFunction"]/*' />
  109. /// <devdoc>
  110. /// Is an extensibility object executing an automation function.
  111. /// </devdoc>
  112. /// <param name="serviceProvider">The service provider.</param>
  113. /// <returns>true if the extensiblity object is executing an automation function.</returns>
  114. public static bool IsInAutomationFunction(IServiceProvider serviceProvider)
  115. {
  116. if(serviceProvider == null)
  117. {
  118. throw new ArgumentNullException("serviceProvider");
  119. }
  120. IVsExtensibility3 extensibility = serviceProvider.GetService(typeof(EnvDTE.IVsExtensibility)) as IVsExtensibility3;
  121. if(extensibility == null)
  122. {
  123. throw new InvalidOperationException();
  124. }
  125. int inAutomation = 0;
  126. ErrorHandler.ThrowOnFailure(extensibility.IsInAutomationFunction(out inAutomation));
  127. return inAutomation != 0;
  128. }
  129. /// <summary>
  130. /// Creates a semicolon delinited list of strings. This can be used to provide the properties for VSHPROPID_CfgPropertyPagesCLSIDList, VSHPROPID_PropertyPagesCLSIDList, VSHPROPID_PriorityPropertyPagesCLSIDList
  131. /// </summary>
  132. /// <param name="guids">An array of Guids.</param>
  133. /// <returns>A semicolon delimited string, or null</returns>
  134. [CLSCompliant(false)]
  135. public static string CreateSemicolonDelimitedListOfStringFromGuids(Guid[] guids)
  136. {
  137. if(guids == null || guids.Length == 0)
  138. {
  139. return null;
  140. }
  141. // Create a StringBuilder with a pre-allocated buffer big enough for the
  142. // final string. 39 is the length of a GUID in the "B" form plus the final ';'
  143. StringBuilder stringList = new StringBuilder(39 * guids.Length);
  144. for(int i = 0; i < guids.Length; i++)
  145. {
  146. stringList.Append(guids[i].ToString("B"));
  147. stringList.Append(";");
  148. }
  149. return stringList.ToString().TrimEnd(';');
  150. }
  151. private static char[] curlyBraces = new char[] { '{', '}' };
  152. /// <summary>
  153. /// Take list of guids as a single string and generate an array of Guids from it
  154. /// </summary>
  155. /// <param name="guidList">Semi-colon separated list of Guids</param>
  156. /// <returns>Array of Guids</returns>
  157. [CLSCompliant(false)]
  158. public static Guid[] GuidsArrayFromSemicolonDelimitedStringOfGuids(string guidList)
  159. {
  160. if(guidList == null)
  161. {
  162. return null;
  163. }
  164. List<Guid> guids = new List<Guid>();
  165. string[] guidsStrings = guidList.Split(';');
  166. foreach(string guid in guidsStrings)
  167. {
  168. if(!String.IsNullOrEmpty(guid))
  169. guids.Add(new Guid(guid.Trim(curlyBraces)));
  170. }
  171. return guids.ToArray();
  172. }
  173. /// <summary>
  174. /// Validates a file path by validating all file parts. If the
  175. /// 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.
  176. /// </summary>
  177. /// <param name="serviceProvider">The service provider</param>
  178. /// <param name="filePath">A full path to a file name</param>
  179. /// <exception cref="InvalidOperationException">In case of failure an InvalidOperationException is thrown.</exception>
  180. public static void ValidateFileName(IServiceProvider serviceProvider, string filePath)
  181. {
  182. string errorMessage = String.Empty;
  183. if(String.IsNullOrEmpty(filePath))
  184. {
  185. errorMessage = SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture);
  186. }
  187. else if(filePath.Length > NativeMethods.MAX_PATH)
  188. {
  189. errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.PathTooLong, CultureInfo.CurrentUICulture), filePath);
  190. }
  191. else if(ContainsInvalidFileNameChars(filePath))
  192. {
  193. errorMessage = SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture);
  194. }
  195. if(errorMessage.Length == 0)
  196. {
  197. string fileName = Path.GetFileName(filePath);
  198. if(String.IsNullOrEmpty(fileName) || IsFileNameInvalid(fileName))
  199. {
  200. errorMessage = SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture);
  201. }
  202. else
  203. {
  204. string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
  205. // If there is no filename or it starts with a leading dot issue an error message and quit.
  206. if(String.IsNullOrEmpty(fileNameWithoutExtension) || fileNameWithoutExtension[0] == '.')
  207. {
  208. errorMessage = SR.GetString(SR.FileNameCannotContainALeadingPeriod, CultureInfo.CurrentUICulture);
  209. }
  210. }
  211. }
  212. if(errorMessage.Length > 0)
  213. {
  214. // If it is not called from an automation method show a dialog box.
  215. if(!Utilities.IsInAutomationFunction(serviceProvider))
  216. {
  217. string title = null;
  218. OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
  219. OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
  220. OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
  221. VsShellUtilities.ShowMessageBox(serviceProvider, title, errorMessage, icon, buttons, defaultButton);
  222. }
  223. else
  224. {
  225. throw new InvalidOperationException(errorMessage);
  226. }
  227. }
  228. }
  229. /// <summary>
  230. /// Creates a CALPOLESTR from a list of strings
  231. /// It is the responsability of the caller to release this memory.
  232. /// </summary>
  233. /// <param name="guids"></param>
  234. /// <returns>A CALPOLESTR that was created from the the list of strings.</returns>
  235. [CLSCompliant(false)]
  236. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CALPOLESTR")]
  237. public static CALPOLESTR CreateCALPOLESTR(IList<string> strings)
  238. {
  239. CALPOLESTR calpolStr = new CALPOLESTR();
  240. if(strings != null)
  241. {
  242. // Demand unmanaged permissions in order to access unmanaged memory.
  243. new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
  244. calpolStr.cElems = (uint)strings.Count;
  245. int size = Marshal.SizeOf(typeof(IntPtr));
  246. calpolStr.pElems = Marshal.AllocCoTaskMem(strings.Count * size);
  247. IntPtr ptr = calpolStr.pElems;
  248. foreach(string aString in strings)
  249. {
  250. IntPtr tempPtr = Marshal.StringToCoTaskMemUni(aString);
  251. Marshal.WriteIntPtr(ptr, tempPtr);
  252. ptr = new IntPtr(ptr.ToInt64() + size);
  253. }
  254. }
  255. return calpolStr;
  256. }
  257. /// <summary>
  258. /// Creates a CADWORD from a list of tagVsSccFilesFlags. Memory is allocated for the elems.
  259. /// It is the responsability of the caller to release this memory.
  260. /// </summary>
  261. /// <param name="guids"></param>
  262. /// <returns>A CADWORD created from the list of tagVsSccFilesFlags.</returns>
  263. [CLSCompliant(false)]
  264. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CADWORD")]
  265. public static CADWORD CreateCADWORD(IList<tagVsSccFilesFlags> flags)
  266. {
  267. CADWORD cadWord = new CADWORD();
  268. if(flags != null)
  269. {
  270. // Demand unmanaged permissions in order to access unmanaged memory.
  271. new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
  272. cadWord.cElems = (uint)flags.Count;
  273. int size = Marshal.SizeOf(typeof(UInt32));
  274. cadWord.pElems = Marshal.AllocCoTaskMem(flags.Count * size);
  275. IntPtr ptr = cadWord.pElems;
  276. foreach(tagVsSccFilesFlags flag in flags)
  277. {
  278. Marshal.WriteInt32(ptr, (int)flag);
  279. ptr = new IntPtr(ptr.ToInt64() + size);
  280. }
  281. }
  282. return cadWord;
  283. }
  284. /// <summary>
  285. /// Splits a bitmap from a Stream into an ImageList
  286. /// </summary>
  287. /// <param name="imageStream">A Stream representing a Bitmap</param>
  288. /// <returns>An ImageList object representing the images from the given stream</returns>
  289. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
  290. public static ImageList GetImageList(Stream imageStream)
  291. {
  292. ImageList ilist = new ImageList();
  293. if(imageStream == null)
  294. {
  295. return ilist;
  296. }
  297. ilist.ColorDepth = ColorDepth.Depth24Bit;
  298. ilist.ImageSize = new Size(16, 16);
  299. Bitmap bitmap = new Bitmap(imageStream);
  300. ilist.Images.AddStrip(bitmap);
  301. ilist.TransparentColor = Color.Magenta;
  302. return ilist;
  303. }
  304. /// <summary>
  305. /// Splits a bitmap from a pointer to an ImageList
  306. /// </summary>
  307. /// <param name="imageListAsPointer">A pointer to a bitmap of images to split</param>
  308. /// <returns>An ImageList object representing the images from the given stream</returns>
  309. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
  310. public static ImageList GetImageList(object imageListAsPointer)
  311. {
  312. ImageList images = null;
  313. IntPtr intPtr = new IntPtr((int)imageListAsPointer);
  314. HandleRef hImageList = new HandleRef(null, intPtr);
  315. int count = UnsafeNativeMethods.ImageList_GetImageCount(hImageList);
  316. if(count > 0)
  317. {
  318. // Create a bitmap big enough to hold all the images
  319. Bitmap b = new Bitmap(16 * count, 16);
  320. Graphics g = Graphics.FromImage(b);
  321. // Loop through and extract each image from the imagelist into our own bitmap
  322. IntPtr hDC = IntPtr.Zero;
  323. try
  324. {
  325. hDC = g.GetHdc();
  326. HandleRef handleRefDC = new HandleRef(null, hDC);
  327. for(int i = 0; i < count; i++)
  328. {
  329. UnsafeNativeMethods.ImageList_Draw(hImageList, i, handleRefDC, i * 16, 0, NativeMethods.ILD_NORMAL);
  330. }
  331. }
  332. finally
  333. {
  334. if(g != null && hDC != IntPtr.Zero)
  335. {
  336. g.ReleaseHdc(hDC);
  337. }
  338. }
  339. // Create a new imagelist based on our stolen images
  340. images = new ImageList();
  341. images.ColorDepth = ColorDepth.Depth24Bit;
  342. images.ImageSize = new Size(16, 16);
  343. images.Images.AddStrip(b);
  344. }
  345. return images;
  346. }
  347. /// <summary>
  348. /// Gets the active configuration name.
  349. /// </summary>
  350. /// <param name="automationObject">The automation object.</param>
  351. /// <returns>The name of the active configuartion.</returns>
  352. internal static string GetActiveConfigurationName(EnvDTE.Project automationObject)
  353. {
  354. if(automationObject == null)
  355. {
  356. throw new ArgumentNullException("automationObject");
  357. }
  358. string currentConfigName = string.Empty;
  359. if(automationObject.ConfigurationManager != null)
  360. {
  361. EnvDTE.Configuration activeConfig = automationObject.ConfigurationManager.ActiveConfiguration;
  362. if(activeConfig != null)
  363. {
  364. currentConfigName = activeConfig.ConfigurationName;
  365. }
  366. }
  367. return currentConfigName;
  368. }
  369. /// <summary>
  370. /// Verifies that two objects represent the same instance of a COM object.
  371. /// This essentially compares the IUnkown pointers of the 2 objects.
  372. /// This is needed in scenario where aggregation is involved.
  373. /// </summary>
  374. /// <param name="obj1">Can be an object, interface or IntPtr</param>
  375. /// <param name="obj2">Can be an object, interface or IntPtr</param>
  376. /// <returns>True if the 2 items represent the same thing</returns>
  377. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj")]
  378. public static bool IsSameComObject(object obj1, object obj2)
  379. {
  380. bool isSame = false;
  381. IntPtr unknown1 = IntPtr.Zero;
  382. IntPtr unknown2 = IntPtr.Zero;
  383. try
  384. {
  385. // If we have 2 null, then they are not COM objects and as such "it's not the same COM object"
  386. if(obj1 != null && obj2 != null)
  387. {
  388. unknown1 = QueryInterfaceIUnknown(obj1);
  389. unknown2 = QueryInterfaceIUnknown(obj2);
  390. isSame = IntPtr.Equals(unknown1, unknown2);
  391. }
  392. }
  393. finally
  394. {
  395. if(unknown1 != IntPtr.Zero)
  396. {
  397. Marshal.Release(unknown1);
  398. }
  399. if(unknown2 != IntPtr.Zero)
  400. {
  401. Marshal.Release(unknown2);
  402. }
  403. }
  404. return isSame;
  405. }
  406. /// <summary>
  407. /// Retrieve the IUnknown for the managed or COM object passed in.
  408. /// </summary>
  409. /// <param name="objToQuery">Managed or COM object.</param>
  410. /// <returns>Pointer to the IUnknown interface of the object.</returns>
  411. internal static IntPtr QueryInterfaceIUnknown(object objToQuery)
  412. {
  413. bool releaseIt = false;
  414. IntPtr unknown = IntPtr.Zero;
  415. IntPtr result;
  416. try
  417. {
  418. if(objToQuery is IntPtr)
  419. {
  420. unknown = (IntPtr)objToQuery;
  421. }
  422. else
  423. {
  424. // This is a managed object (or RCW)
  425. unknown = Marshal.GetIUnknownForObject(objToQuery);
  426. releaseIt = true;
  427. }
  428. // We might already have an IUnknown, but if this is an aggregated
  429. // object, it may not be THE IUnknown until we QI for it.
  430. Guid IID_IUnknown = VSConstants.IID_IUnknown;
  431. ErrorHandler.ThrowOnFailure(Marshal.QueryInterface(unknown, ref IID_IUnknown, out result));
  432. }
  433. finally
  434. {
  435. if(releaseIt && unknown != IntPtr.Zero)
  436. {
  437. Marshal.Release(unknown);
  438. }
  439. }
  440. return result;
  441. }
  442. /// <summary>
  443. /// Returns true if thename that can represent a path, absolut or relative, or a file name contains invalid filename characters.
  444. /// </summary>
  445. /// <param name="name">File name</param>
  446. /// <returns>true if file name is invalid</returns>
  447. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
  448. Justification="The name is validated.")]
  449. public static bool ContainsInvalidFileNameChars(string name)
  450. {
  451. if(String.IsNullOrEmpty(name))
  452. {
  453. return true;
  454. }
  455. try
  456. {
  457. if(Path.IsPathRooted(name) && !name.StartsWith(@"\\", StringComparison.Ordinal))
  458. {
  459. string root = Path.GetPathRoot(name);
  460. name = name.Substring(root.Length);
  461. }
  462. }
  463. // The Path methods used by ContainsInvalidFileNameChars return argument exception if the filePath contains invalid characters.
  464. catch(ArgumentException)
  465. {
  466. return true;
  467. }
  468. Microsoft.VisualStudio.Shell.Url uri = new Microsoft.VisualStudio.Shell.Url(name);
  469. // This might be confusing bur Url.IsFile means that the uri represented by the name is either absolut or relative.
  470. if(uri.IsFile)
  471. {
  472. string[] segments = uri.Segments;
  473. if(segments != null && segments.Length > 0)
  474. {
  475. foreach(string segment in segments)
  476. {
  477. if(IsFilePartInValid(segment))
  478. {
  479. return true;
  480. }
  481. }
  482. // Now the last segment should be specially taken care, since that cannot be all dots or spaces.
  483. string lastSegment = segments[segments.Length - 1];
  484. string filePart = Path.GetFileNameWithoutExtension(lastSegment);
  485. if(IsFileNameAllGivenCharacter('.', filePart) || IsFileNameAllGivenCharacter(' ', filePart))
  486. {
  487. return true;
  488. }
  489. }
  490. }
  491. else
  492. {
  493. // The assumption here is that we got a file name.
  494. string filePart = Path.GetFileNameWithoutExtension(name);
  495. if(IsFileNameAllGivenCharacter('.', filePart) || IsFileNameAllGivenCharacter(' ', filePart))
  496. {
  497. return true;
  498. }
  499. return IsFilePartInValid(name);
  500. }
  501. return false;
  502. }
  503. /// Cehcks if a file name is valid.
  504. /// </devdoc>
  505. /// <param name="fileName">The name of the file</param>
  506. /// <returns>True if the file is valid.</returns>
  507. public static bool IsFileNameInvalid(string fileName)
  508. {
  509. if(String.IsNullOrEmpty(fileName))
  510. {
  511. return true;
  512. }
  513. if(IsFileNameAllGivenCharacter('.', fileName) || IsFileNameAllGivenCharacter(' ', fileName))
  514. {
  515. return true;
  516. }
  517. return IsFilePartInValid(fileName);
  518. }
  519. /// <summary>
  520. /// Helper method to call a converter explicitely to convert to an enum type
  521. /// </summary>
  522. /// <typeparam name="T">THe enum to convert to</typeparam>
  523. /// <typeparam name="V">The converter that will be created</typeparam>
  524. /// <param name="value">The enum value to be converted to</param>
  525. /// <param name="typeToConvert">The type to convert</param>
  526. /// <param name="culture">The culture to use to read the localized strings</param>
  527. /// <returns></returns>
  528. [CLSCompliant(false)]
  529. public static object ConvertToType<T>(T value, Type typeToConvert, CultureInfo culture)
  530. where T : struct
  531. {
  532. EnumConverter converter = GetEnumConverter<T>();
  533. if(converter == null)
  534. {
  535. return null;
  536. }
  537. if(converter.CanConvertTo(typeToConvert))
  538. {
  539. return converter.ConvertTo(null, culture, value, typeToConvert);
  540. }
  541. return null;
  542. }
  543. /// <summary>
  544. /// Helper method for converting from a string to an enum using a converter.
  545. /// </summary>
  546. /// <typeparam name="T"></typeparam>
  547. /// <param name="value"></param>
  548. /// <param name="culture">The culture to use to read the localized strings</param>
  549. /// <returns></returns>
  550. [CLSCompliant(false)]
  551. public static Nullable<T> ConvertFromType<T>(string value, CultureInfo culture)
  552. where T : struct
  553. {
  554. Nullable<T> returnValue = new Nullable<T>();
  555. returnValue = returnValue.GetValueOrDefault();
  556. if(value == null)
  557. {
  558. return returnValue;
  559. }
  560. EnumConverter converter = GetEnumConverter<T>();
  561. if(converter == null)
  562. {
  563. return returnValue;
  564. }
  565. if(converter.CanConvertFrom(value.GetType()))
  566. {
  567. object converted = converter.ConvertFrom(null, culture, value);
  568. if(converted != null && (converted is T))
  569. {
  570. returnValue = (T)converted;
  571. }
  572. }
  573. return returnValue;
  574. }
  575. /// <summary>
  576. /// Sets a string value from an enum
  577. /// </summary>
  578. /// <typeparam name="T">The enum type</typeparam>
  579. /// <param name="enumValue">The value of teh enum.</param>
  580. /// <returns></returns>
  581. [CLSCompliant(false)]
  582. public static string SetStringValueFromConvertedEnum<T>(T enumValue, CultureInfo culture)
  583. where T : struct
  584. {
  585. string convertToType = ConvertToType<T>(enumValue, typeof(string), culture) as string;
  586. if(convertToType == null)
  587. {
  588. return String.Empty;
  589. }
  590. return convertToType;
  591. }
  592. /// <summary>
  593. /// Initializes the in memory project. Sets BuildEnabled on the project to true.
  594. /// </summary>
  595. /// <param name="engine">The build engine to use to create a build project.</param>
  596. /// <param name="fullProjectPath">The full path of the project.</param>
  597. /// <returns>A loaded msbuild project.</returns>
  598. internal static MSBuild.Project InitializeMsBuildProject(MSBuild.ProjectCollection buildEngine, string fullProjectPath)
  599. {
  600. if(String.IsNullOrEmpty(fullProjectPath))
  601. {
  602. throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "fullProjectPath");
  603. }
  604. // Call GetFullPath to expand any relative path passed into this method.
  605. fullProjectPath = Path.GetFullPath(fullProjectPath);
  606. // Check if the project already has been loaded with the fullProjectPath. If yes return the build project associated to it.
  607. List<MSBuild.Project> loadedProject = new List<MSBuild.Project>(buildEngine.GetLoadedProjects(fullProjectPath));
  608. MSBuild.Project buildProject = loadedProject != null && loadedProject.Count > 0 && loadedProject[0] != null ? loadedProject[0] : null;
  609. if(buildProject == null)
  610. {
  611. buildProject = buildEngine.LoadProject(fullProjectPath);
  612. }
  613. return buildProject;
  614. }
  615. /// <summary>
  616. /// 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.
  617. /// </summary>
  618. /// <param name="engine">The build engine to use to create a build project.</param>
  619. /// <param name="fullProjectPath">The full path of the project.</param>
  620. /// <param name="exitingBuildProject">An Existing build project that will be reloaded.</param>
  621. /// <returns>A loaded msbuild project.</returns>
  622. internal static MSBuild.Project ReinitializeMsBuildProject(MSBuild.ProjectCollection buildEngine, string fullProjectPath, MSBuild.Project exitingBuildProject)
  623. {
  624. // If we have a build project that has been loaded with another file unload it.
  625. try
  626. {
  627. if(exitingBuildProject != null && exitingBuildProject.ProjectCollection != null && !NativeMethods.IsSamePath(exitingBuildProject.FullPath, fullProjectPath))
  628. {
  629. buildEngine.UnloadProject(exitingBuildProject);
  630. }
  631. }
  632. // We catch Invalid operation exception because if the project was unloaded while we touch the ParentEngine the msbuild API throws.
  633. // Is there a way to figure out that a project was unloaded?
  634. catch(InvalidOperationException)
  635. {
  636. }
  637. return Utilities.InitializeMsBuildProject(buildEngine, fullProjectPath);
  638. }
  639. /// <summary>
  640. /// 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.
  641. /// </summary>
  642. /// <param name="engine">An instance of MSBuild.ProjectCollection build engine, that will be checked if initialized.</param>
  643. /// <param name="engine">The service provider.</param>
  644. /// <returns>The buildengine to use.</returns>
  645. internal static MSBuild.ProjectCollection InitializeMsBuildEngine(MSBuild.ProjectCollection existingEngine, IServiceProvider serviceProvider)
  646. {
  647. if(serviceProvider == null)
  648. {
  649. throw new ArgumentNullException("serviceProvider");
  650. }
  651. if(existingEngine == null)
  652. {
  653. MSBuild.ProjectCollection buildEngine = MSBuild.ProjectCollection.GlobalProjectCollection;
  654. return buildEngine;
  655. }
  656. return existingEngine;
  657. }
  658. /// <summary>
  659. /// Gets an instance of an EnumConverter for enums that have PropertyPageTypeConverter attribute
  660. /// </summary>
  661. /// <typeparam name="T">The type to search for the PropertyPageTypeConverter attribute.</typeparam>
  662. /// <returns>An instance of an enum converter, or null if none found.</returns>
  663. private static EnumConverter GetEnumConverter<T>()
  664. where T : struct
  665. {
  666. object[] attributes = typeof(T).GetCustomAttributes(typeof(PropertyPageTypeConverterAttribute), true);
  667. // There should be only one PropertyPageTypeConverterAttribute defined on T
  668. if(attributes != null && attributes.Length == 1)
  669. {
  670. Debug.Assert(attributes[0] is PropertyPageTypeConverterAttribute, "The returned attribute must be an attribute is PropertyPageTypeConverterAttribute");
  671. PropertyPageTypeConverterAttribute converterAttribute = (PropertyPageTypeConverterAttribute)attributes[0];
  672. if(converterAttribute.ConverterType.IsSubclassOf(typeof(EnumConverter)))
  673. {
  674. return Activator.CreateInstance(converterAttribute.ConverterType) as EnumConverter;
  675. }
  676. }
  677. return null;
  678. }
  679. /// <summary>>
  680. /// Checks if the file name is all the given character.
  681. /// </summary>
  682. private static bool IsFileNameAllGivenCharacter(char c, string fileName)
  683. {
  684. // A valid file name cannot be all "c" .
  685. int charFound = 0;
  686. for(charFound = 0; charFound < fileName.Length && fileName[charFound] == c; ++charFound) ;
  687. if(charFound >= fileName.Length)
  688. {
  689. return true;
  690. }
  691. return false;
  692. }
  693. /// <summary>
  694. /// Checks whether a file part contains valid characters. The file part can be any part of a non rooted path.
  695. /// </summary>
  696. /// <param name="filePart"></param>
  697. /// <returns></returns>
  698. private static bool IsFilePartInValid(string filePart)
  699. {
  700. if(String.IsNullOrEmpty(filePart))
  701. {
  702. return true;
  703. }
  704. String reservedName = "(\\b(nul|con|aux|prn)\\b)|(\\b((com|lpt)[0-9])\\b)";
  705. String invalidChars = @"([/?:&\\*<>|#%" + '\"' + "])";
  706. String regexToUseForFileName = reservedName + "|" + invalidChars;
  707. String fileNameToVerify = filePart;
  708. // Define a regular expression that covers all characters that are not in the safe character sets.
  709. // It is compiled for performance.
  710. // The filePart might still be a file and extension. If it is like that then we must check them separately, since different rules apply
  711. string extension = String.Empty;
  712. try
  713. {
  714. extension = Path.GetExtension(filePart);
  715. }
  716. // 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
  717. catch(ArgumentException)
  718. {
  719. return true;
  720. }
  721. if(!String.IsNullOrEmpty(extension))
  722. {
  723. // Check the extension first
  724. String regexToUseForExtension = invalidChars;
  725. Regex unsafeCharactersRegex = new Regex(regexToUseForExtension, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
  726. bool isMatch = unsafeCharactersRegex.IsMatch(extension);
  727. if(isMatch)
  728. {
  729. return isMatch;
  730. }
  731. // We want to verify here everything but the extension.
  732. // We cannot use GetFileNameWithoutExtension because it might be that for example (..\\filename.txt) is passed in asnd that should fail, since that is not a valid filename.
  733. fileNameToVerify = filePart.Substring(0, filePart.Length - extension.Length);
  734. if(String.IsNullOrEmpty(fileNameToVerify))
  735. {
  736. return true;
  737. }
  738. }
  739. // We verify CLOCK$ outside the regex since for some reason the regex is not matching the clock\\$ added.
  740. if(String.Compare(fileNameToVerify, "CLOCK$", StringComparison.OrdinalIgnoreCase) == 0)
  741. {
  742. return true;
  743. }
  744. Regex unsafeFileNameCharactersRegex = new Regex(regexToUseForFileName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
  745. return unsafeFileNameCharactersRegex.IsMatch(fileNameToVerify);
  746. }
  747. /// <summary>
  748. /// Copy a directory recursively to the specified non-existing directory
  749. /// </summary>
  750. /// <param name="source">Directory to copy from</param>
  751. /// <param name="target">Directory to copy to</param>
  752. public static void RecursivelyCopyDirectory(string source, string target)
  753. {
  754. // Make sure it doesn't already exist
  755. if(Directory.Exists(target))
  756. throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FileOrFolderAlreadyExists, CultureInfo.CurrentUICulture), target));
  757. Directory.CreateDirectory(target);
  758. DirectoryInfo directory = new DirectoryInfo(source);
  759. // Copy files
  760. foreach(FileInfo file in directory.GetFiles())
  761. {
  762. file.CopyTo(Path.Combine(target, file.Name));
  763. }
  764. // Now recurse to child directories
  765. foreach(DirectoryInfo child in directory.GetDirectories())
  766. {
  767. RecursivelyCopyDirectory(child.FullName, Path.Combine(target, child.Name));
  768. }
  769. }
  770. /// <summary>
  771. /// Canonicalizes a file name, including:
  772. /// - determines the full path to the file
  773. /// - casts to upper case
  774. /// Canonicalizing a file name makes it possible to compare file names using simple simple string comparison.
  775. ///
  776. /// Note: this method does not handle shared drives and UNC drives.
  777. /// </summary>
  778. /// <param name="anyFileName">A file name, which can be relative/absolute and contain lower-case/upper-case characters.</param>
  779. /// <returns>Canonicalized file name.</returns>
  780. internal static string CanonicalizeFileName(string anyFileName)
  781. {
  782. // Get absolute path
  783. // Note: this will not handle UNC paths
  784. FileInfo fileInfo = new FileInfo(anyFileName);
  785. string fullPath = fileInfo.FullName;
  786. // Cast to upper-case
  787. fullPath = fullPath.ToUpper(CultureInfo.CurrentCulture);
  788. return fullPath;
  789. }
  790. /// <summary>
  791. /// Determines if a file is a template.
  792. /// </summary>
  793. /// <param name="fileName">The file to check whether it is a template file</param>
  794. /// <returns>true if the file is a template file</returns>
  795. internal static bool IsTemplateFile(string fileName)
  796. {
  797. if(String.IsNullOrEmpty(fileName))
  798. {
  799. return false;
  800. }
  801. string extension = Path.GetExtension(fileName);
  802. return (String.Compare(extension, ".vstemplate", StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(extension, ".vsz", StringComparison.OrdinalIgnoreCase) == 0);
  803. }
  804. /// <summary>
  805. /// Retrives the configuration and the platform using the IVsSolutionBuildManager2 interface.
  806. /// </summary>
  807. /// <param name="serviceProvider">A service provider.</param>
  808. /// <param name="hierarchy">The hierrachy whose configuration is requested.</param>
  809. /// <param name="configuration">The name of the active configuration.</param>
  810. /// <param name="platform">The name of the platform.</param>
  811. /// <returns>true if successfull.</returns>
  812. internal static bool TryGetActiveConfigurationAndPlatform(System.IServiceProvider serviceProvider, IVsHierarchy hierarchy, out string configuration, out string platform)
  813. {
  814. if(serviceProvider == null)
  815. {
  816. throw new ArgumentNullException("serviceProvider");
  817. }
  818. if(hierarchy == null)
  819. {
  820. throw new ArgumentNullException("hierarchy");
  821. }
  822. configuration = String.Empty;
  823. platform = String.Empty;
  824. IVsSolutionBuildManager2 solutionBuildManager = serviceProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2;
  825. if(solutionBuildManager == null)
  826. {
  827. return false;
  828. }
  829. IVsProjectCfg[] activeConfigs = new IVsProjectCfg[1];
  830. ErrorHandler.ThrowOnFailure(solutionBuildManager.FindActiveProjectCfg(IntPtr.Zero, IntPtr.Zero, hierarchy, activeConfigs));
  831. IVsProjectCfg activeCfg = activeConfigs[0];
  832. // Can it be that the activeCfg is null?
  833. System.Diagnostics.Debug.Assert(activeCfg != null, "Cannot find the active configuration");
  834. string canonicalName;
  835. ErrorHandler.ThrowOnFailure(activeCfg.get_CanonicalName(out canonicalName));
  836. return ProjectConfig.TrySplitConfigurationCanonicalName(canonicalName, out configuration, out platform);
  837. }
  838. /// <summary>
  839. /// Determines whether the shell is in command line mode.
  840. /// </summary>
  841. /// <param name="serviceProvider">A reference to a Service Provider.</param>
  842. /// <returns>true if the shell is in command line mode. false otherwise.</returns>
  843. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
  844. internal static bool IsShellInCommandLineMode(System.IServiceProvider serviceProvider)
  845. {
  846. if(serviceProvider == null)
  847. {
  848. throw new ArgumentNullException("serviceProvider");
  849. }
  850. IVsShell shell = serviceProvider.GetService(typeof(SVsShell)) as IVsShell;
  851. if(shell == null)
  852. {
  853. throw new InvalidOperationException();
  854. }
  855. object isInCommandLineModeAsObject;
  856. ErrorHandler.ThrowOnFailure(shell.GetProperty((int)__VSSPROPID.VSSPROPID_IsInCommandLineMode, out isInCommandLineModeAsObject));
  857. return ((bool)isInCommandLineModeAsObject);
  858. }
  859. }
  860. }