1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.Globalization;
6 using System.Runtime.InteropServices.ComTypes;
7 using System.Text;
8 
9 using Microsoft.Build.Shared;
10 using Microsoft.Build.Utilities;
11 
12 using Marshal = System.Runtime.InteropServices.Marshal;
13 using COMException = System.Runtime.InteropServices.COMException;
14 
15 namespace Microsoft.Build.Tasks
16 {
17     /*
18      * Class:   ComReference
19      *
20      * Abstract base class for COM reference wrappers providing common functionality.
21      * This class hierarchy is used by the ResolveComReference task. Every class deriving from ComReference
22      * provides functionality for wrapping Com type libraries in a given way (for example AxReference, or PiaReference).
23      *
24      */
25     internal abstract class ComReference
26     {
27         #region Constructors
28 
29         /// <summary>
30         /// Internal constructor
31         /// </summary>
32         /// <param name="taskLoggingHelper">task logger instance used for logging</param>
33         /// <param name="silent">true if this task should log only errors, no warnings or messages; false otherwise</param>
34         /// <param name="referenceInfo">cached reference information (typelib pointer, original task item, typelib name etc.)</param>
35         /// <param name="itemName">reference name (for better logging experience)</param>
ComReference(TaskLoggingHelper taskLoggingHelper, bool silent, ComReferenceInfo referenceInfo, string itemName)36         internal ComReference(TaskLoggingHelper taskLoggingHelper, bool silent, ComReferenceInfo referenceInfo, string itemName)
37         {
38             _referenceInfo = referenceInfo;
39             _itemName = itemName;
40             _log = taskLoggingHelper;
41             _silent = silent;
42         }
43 
44         #endregion
45 
46         #region Properties
47 
48         /// <summary>
49         /// various data for this reference (type lib attrs, name, path, ITypeLib pointer etc)
50         /// </summary>
51         internal virtual ComReferenceInfo ReferenceInfo
52         {
53             get
54             {
55                 return _referenceInfo;
56             }
57         }
58 
59         private ComReferenceInfo _referenceInfo;
60 
61         /// <summary>
62         /// item name as it appears in the project file
63         /// (used for logging purposes, we use the actual typelib name for interesting operations)
64         /// </summary>
65         internal virtual string ItemName
66         {
67             get
68             {
69                 return _itemName;
70             }
71         }
72 
73         private string _itemName;
74 
75         /// <summary>
76         /// task used for logging messages
77         /// </summary>
78         protected internal TaskLoggingHelper Log
79         {
80             get
81             {
82                 return _log;
83             }
84         }
85 
86         private TaskLoggingHelper _log;
87 
88         /// <summary>
89         /// True if this class should only log errors, but no messages or warnings.
90         /// </summary>
91         protected internal bool Silent
92         {
93             get
94             {
95                 return _silent;
96             }
97         }
98 
99         private bool _silent;
100 
101         /// <summary>
102         /// lazy-init property, returns true if ADO 2.7 is installed on the machine
103         /// </summary>
104         internal static bool Ado27Installed
105         {
106             get
107             {
108                 // if we already know the answer, return it
109                 if (ado27PropertyInitialized)
110                     return ado27Installed;
111 
112                 // not initialized? Find out if ADO 2.7 is installed
113                 ado27Installed = true;
114                 ado27PropertyInitialized = true;
115 
116                 ITypeLib ado27 = null;
117 
118                 try
119                 {
120                     // see if ADO 2.7 is registered.
121                     ado27 = (ITypeLib)NativeMethods.LoadRegTypeLib(ref s_guidADO27, 2, 7, 0);
122                 }
123                 catch (COMException ex)
124                 {
125                     // it's not registered.
126                     ado27Installed = false;
127                     ado27ErrorMessage = ex.Message;
128                 }
129                 finally
130                 {
131                     if (ado27 != null)
132                         Marshal.ReleaseComObject(ado27);
133                 }
134 
135                 return ado27Installed;
136             }
137         }
138 
139         internal static bool ado27PropertyInitialized = false;
140         internal static bool ado27Installed;
141 
142         /// <summary>
143         /// Error message if Ado27 is not installed on the machine (usually something like "type lib not registered")
144         /// Only contains valid data if ADO 2.7 is not installed and Ado27Installed was called before
145         /// </summary>
146         internal static string Ado27ErrorMessage
147         {
148             get
149             {
150                 return ado27ErrorMessage;
151             }
152         }
153 
154         internal static string ado27ErrorMessage;
155 
156         #endregion
157 
158         #region Methods
159 
160         /*
161          * Method:  UniqueKeyFromTypeLibAttr
162          *
163          * Given a TYPELIBATTR structure, generates a key that can be used in hashtables to identify it.
164          */
UniqueKeyFromTypeLibAttr(TYPELIBATTR attr)165         internal static string UniqueKeyFromTypeLibAttr(TYPELIBATTR attr)
166         {
167             return String.Format(CultureInfo.InvariantCulture, @"{0}|{1}.{2}|{3}", attr.guid, attr.wMajorVerNum, attr.wMinorVerNum, attr.lcid);
168         }
169 
170         /*
171          * Method:  AreTypeLibAttrEqual
172          *
173          * Compares two TYPELIBATTR structures
174          */
AreTypeLibAttrEqual(TYPELIBATTR attr1, TYPELIBATTR attr2)175         internal static bool AreTypeLibAttrEqual(TYPELIBATTR attr1, TYPELIBATTR attr2)
176         {
177             return attr1.wMajorVerNum == attr2.wMajorVerNum &&
178                 attr1.wMinorVerNum == attr2.wMinorVerNum &&
179                 attr1.lcid == attr2.lcid &&
180                 attr1.guid == attr2.guid;
181         }
182 
183         /// <summary>
184         /// Helper method for retrieving type lib attributes for the given type lib
185         /// </summary>
186         /// <param name="typeLib"></param>
187         /// <param name="typeLibAttr"></param>
188         /// <returns></returns>
GetTypeLibAttrForTypeLib(ref ITypeLib typeLib, out TYPELIBATTR typeLibAttr)189         internal static void GetTypeLibAttrForTypeLib(ref ITypeLib typeLib, out TYPELIBATTR typeLibAttr)
190         {
191             IntPtr pAttrs = IntPtr.Zero;
192             typeLib.GetLibAttr(out pAttrs);
193 
194             // GetLibAttr should never return null, this is just to be safe
195             if (pAttrs == IntPtr.Zero)
196             {
197                 throw new COMException(
198                     ResourceUtilities.GetResourceString("ResolveComReference.CannotGetTypeLibAttrForTypeLib"));
199             }
200 
201             try
202             {
203                 typeLibAttr = (TYPELIBATTR)Marshal.PtrToStructure(pAttrs, typeof(TYPELIBATTR));
204             }
205             finally
206             {
207                 typeLib.ReleaseTLibAttr(pAttrs);
208             }
209         }
210 
211         /// <summary>
212         /// Helper method for retrieving type attributes for a given type info
213         /// </summary>
214         /// <param name="typeInfo"></param>
215         /// <param name="typeAttr"></param>
216         /// <returns></returns>
GetTypeAttrForTypeInfo(ITypeInfo typeInfo, out TYPEATTR typeAttr)217         internal static void GetTypeAttrForTypeInfo(ITypeInfo typeInfo, out TYPEATTR typeAttr)
218         {
219             IntPtr pAttrs = IntPtr.Zero;
220             typeInfo.GetTypeAttr(out pAttrs);
221 
222             // GetTypeAttr should never return null, this is just to be safe
223             if (pAttrs == IntPtr.Zero)
224             {
225                 throw new COMException(
226                     ResourceUtilities.GetResourceString("ResolveComReference.CannotRetrieveTypeInformation"));
227             }
228 
229             try
230             {
231                 typeAttr = (TYPEATTR)Marshal.PtrToStructure(pAttrs, typeof(TYPEATTR));
232             }
233             finally
234             {
235                 typeInfo.ReleaseTypeAttr(pAttrs);
236             }
237         }
238 
239         /// <summary>
240         /// Helper method for retrieving type attributes for a given type info
241         /// This method needs to also return the native pointer to be released when we're done with our VARDESC.
242         /// It's not really possible to copy everything to a managed struct and then release the ptr immediately
243         /// here, since VARDESCs contain other native pointers we may need to access.
244         /// </summary>
245         /// <param name="typeInfo"></param>
246         /// <param name="varIndex"></param>
247         /// <param name="typeAttr"></param>
248         /// <param name="varDesc"></param>
249         /// <param name="varDescHandle"></param>
250         /// <returns></returns>
GetVarDescForVarIndex(ITypeInfo typeInfo, int varIndex, out VARDESC varDesc, out IntPtr varDescHandle)251         internal static void GetVarDescForVarIndex(ITypeInfo typeInfo, int varIndex, out VARDESC varDesc, out IntPtr varDescHandle)
252         {
253             IntPtr pVarDesc = IntPtr.Zero;
254             typeInfo.GetVarDesc(varIndex, out pVarDesc);
255 
256             // GetVarDesc should never return null, this is just to be safe
257             if (pVarDesc == IntPtr.Zero)
258             {
259                 throw new COMException(
260                     ResourceUtilities.GetResourceString("ResolveComReference.CannotRetrieveTypeInformation"));
261             }
262 
263             varDesc = (VARDESC)Marshal.PtrToStructure(pVarDesc, typeof(VARDESC));
264             varDescHandle = pVarDesc;
265         }
266 
267         /// <summary>
268         /// Helper method for retrieving the function description structure for the given function index.
269         /// This method needs to also return the native pointer to be released when we're done with our FUNCDESC.
270         /// It's not really possible to copy everything to a managed struct and then release the ptr immediately
271         /// here, since FUNCDESCs contain other native pointers we may need to access.
272         /// </summary>
273         /// <param name="typeInfo"></param>
274         /// <param name="funcIndex"></param>
275         /// <param name="funcDesc"></param>
276         /// <param name="funcDescHandle"></param>
277         /// <returns></returns>
GetFuncDescForDescIndex(ITypeInfo typeInfo, int funcIndex, out FUNCDESC funcDesc, out IntPtr funcDescHandle)278         internal static void GetFuncDescForDescIndex(ITypeInfo typeInfo, int funcIndex, out FUNCDESC funcDesc, out IntPtr funcDescHandle)
279         {
280             IntPtr pFuncDesc = IntPtr.Zero;
281             typeInfo.GetFuncDesc(funcIndex, out pFuncDesc);
282 
283             // GetFuncDesc should never return null, this is just to be safe
284             if (pFuncDesc == IntPtr.Zero)
285             {
286                 throw new COMException(
287                     ResourceUtilities.GetResourceString("ResolveComReference.CannotRetrieveTypeInformation"));
288             }
289 
290             funcDesc = (FUNCDESC)Marshal.PtrToStructure(pFuncDesc, typeof(FUNCDESC));
291             funcDescHandle = pFuncDesc;
292         }
293 
294         /*
295          * Method:  GetTypeLibNameForITypeLib
296          *
297          * Gets the name of given type library.
298          */
GetTypeLibNameForITypeLib(TaskLoggingHelper log, bool silent, ITypeLib typeLib, string typeLibId, out string typeLibName)299         internal static bool GetTypeLibNameForITypeLib(TaskLoggingHelper log, bool silent, ITypeLib typeLib, string typeLibId, out string typeLibName)
300         {
301             typeLibName = "";
302 
303             // see if the type library supports ITypeLib2
304             ITypeLib2 typeLib2 = typeLib as ITypeLib2;
305 
306             if (typeLib2 == null)
307             {
308                 // Looks like the type lib doesn't support it. Let's use the Marshal method.
309                 typeLibName = Marshal.GetTypeLibName(typeLib);
310                 return true;
311             }
312 
313             // Get the custom attribute.  If anything fails then just return the
314             // type library name.
315             try
316             {
317                 object data = null;
318 
319                 typeLib2.GetCustData(ref NativeMethods.GUID_TYPELIB_NAMESPACE, out data);
320 
321                 // if returned namespace is null or its type is not System.String, fall back to the default
322                 // way of getting the type lib name (just to be safe)
323                 if (data == null || string.Compare(data.GetType().ToString(), "system.string", StringComparison.OrdinalIgnoreCase) != 0)
324                 {
325                     typeLibName = Marshal.GetTypeLibName(typeLib);
326                     return true;
327                 }
328 
329                 // Strip off the DLL extension if it's there
330                 typeLibName = (string)data;
331 
332                 if (typeLibName.Length >= 4)
333                 {
334                     if (string.Compare(typeLibName.Substring(typeLibName.Length - 4), ".dll", StringComparison.OrdinalIgnoreCase) == 0)
335                     {
336                         typeLibName = typeLibName.Substring(0, typeLibName.Length - 4);
337                     }
338                 }
339             }
340             catch (COMException ex)
341             {
342                 // If anything fails log a warning and just return the type library name.
343                 if (!silent)
344                 {
345                     log.LogWarningWithCodeFromResources("ResolveComReference.CannotAccessTypeLibName", typeLibId, ex.Message);
346                 }
347                 typeLibName = Marshal.GetTypeLibName(typeLib);
348                 return true;
349             }
350 
351             return true;
352         }
353 
354         /*
355          * Method:  GetTypeLibNameForTypeLibAttrs
356          *
357          * Gets the name of given type library.
358          */
GetTypeLibNameForTypeLibAttrs(TaskLoggingHelper log, bool silent, TYPELIBATTR typeLibAttr, out string typeLibName)359         internal static bool GetTypeLibNameForTypeLibAttrs(TaskLoggingHelper log, bool silent, TYPELIBATTR typeLibAttr, out string typeLibName)
360         {
361             typeLibName = "";
362             ITypeLib typeLib = null;
363 
364             try
365             {
366                 // load our type library
367                 try
368                 {
369                     TYPELIBATTR attr = typeLibAttr;
370                     typeLib = (ITypeLib)NativeMethods.LoadRegTypeLib(ref attr.guid, attr.wMajorVerNum, attr.wMinorVerNum, attr.lcid);
371                 }
372                 catch (COMException ex)
373                 {
374                     if (!silent)
375                     {
376                         log.LogWarningWithCodeFromResources("ResolveComReference.CannotLoadTypeLib", typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, ex.Message);
377                     }
378 
379                     return false;
380                 }
381 
382                 string typeLibId = log.FormatResourceString("ResolveComReference.TypeLibAttrId", typeLibAttr.guid.ToString(), typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum);
383 
384                 return GetTypeLibNameForITypeLib(log, silent, typeLib, typeLibId, out typeLibName);
385             }
386             finally
387             {
388                 if (typeLib != null)
389                     Marshal.ReleaseComObject(typeLib);
390             }
391         }
392 
393         /// <summary>
394         /// Strips type library number from a type library path (for example, "ref.dll\2" becomes "ref.dll")
395         /// </summary>
396         /// <param name="typeLibPath">type library path with possible typelib number appended to it</param>
397         /// <returns>proper file path to the type library</returns>
StripTypeLibNumberFromPath(string typeLibPath, FileExists fileExists)398         internal static string StripTypeLibNumberFromPath(string typeLibPath, FileExists fileExists)
399         {
400             bool lastChance = false;
401             if (typeLibPath != null && typeLibPath.Length > 0)
402             {
403                 if (!fileExists(typeLibPath))
404                 {
405                     // Strip the type library number
406                     int lastSlash = typeLibPath.LastIndexOf('\\');
407 
408                     if (lastSlash != -1)
409                     {
410                         bool allNumbers = true;
411 
412                         for (int i = lastSlash + 1; i < typeLibPath.Length; i++)
413                         {
414                             if (!Char.IsDigit(typeLibPath[i]))
415                             {
416                                 allNumbers = false;
417                                 break;
418                             }
419                         }
420 
421                         // If we had all numbers past the last slash then we're OK to strip
422                         // the type library number
423                         if (allNumbers)
424                         {
425                             typeLibPath = typeLibPath.Substring(0, lastSlash);
426                             if (!fileExists(typeLibPath))
427                             {
428                                 lastChance = true;
429                             }
430                         }
431                         else
432                         {
433                             lastChance = true;
434                         }
435                     }
436                     else
437                     {
438                         lastChance = true;
439                     }
440                 }
441             }
442 
443             // If we couldn't find the path directly, we'll use the same mechanism Windows uses to find
444             // libraries.  LoadLibrary() will search all of the correct paths to find this module.  We can then
445             // use GetModuleFileName() to determine the actual path from which the module was loaded.  This problem
446             // was exposed in Vista where certain libraries are registered but are lacking paths in the registry,
447             // so the old code would fail to find them on disk using the simplistic checks above.
448             if (lastChance)
449             {
450                 IntPtr libraryHandle = NativeMethodsShared.LoadLibrary(typeLibPath);
451                 if (IntPtr.Zero != libraryHandle)
452                 {
453                     try
454                     {
455                         StringBuilder sb = new StringBuilder(NativeMethodsShared.MAX_PATH);
456                         System.Runtime.InteropServices.HandleRef handleRef = new System.Runtime.InteropServices.HandleRef(sb, libraryHandle);
457                         int len = NativeMethodsShared.GetModuleFileName(handleRef, sb, sb.Capacity);
458                         if ((len != 0) &&
459                             ((uint)Marshal.GetLastWin32Error() != NativeMethodsShared.ERROR_INSUFFICIENT_BUFFER))
460                         {
461                             typeLibPath = sb.ToString();
462                         }
463                         else
464                         {
465                             typeLibPath = "";
466                         }
467                     }
468                     finally
469                     {
470                         NativeMethodsShared.FreeLibrary(libraryHandle);
471                     }
472                 }
473                 else
474                 {
475                     typeLibPath = "";
476                 }
477             }
478 
479             return typeLibPath;
480         }
481 
482         /*
483          * Method:  GetPathOfTypeLib
484          *
485          * Gets the type lib path for given type lib attributes (reused almost verbatim from vsdesigner utils code)
486          * NOTE:  If there's a typelib number at the end of the path, does NOT strip it.
487          */
GetPathOfTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr, out string typeLibPath)488         internal static bool GetPathOfTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr, out string typeLibPath)
489         {
490             // Get which file the type library resides in.  If the appropriate
491             // file cannot be found then a blank string is returned.
492             typeLibPath = "";
493 
494             try
495             {
496                 // Get the path from the registry
497                 // This call has known issues. See http://msdn.microsoft.com/en-us/library/ms221436.aspx for the method and
498                 // here for the fix http://support.microsoft.com/kb/982110. Most users from Win7 or Win2008R2 should have already received this post Win7SP1.
499                 // In Summary: The issue is about calls to The QueryPathOfRegTypeLib function not returning the correct path for a 32-bit version of a
500                 // registered type library in a 64-bit edition of Windows 7 or in Windows Server 2008 R2. It either returns the 64bit path or null.
501                 typeLibPath = NativeMethods.QueryPathOfRegTypeLib(ref typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, typeLibAttr.lcid);
502                 typeLibPath = Environment.ExpandEnvironmentVariables(typeLibPath);
503             }
504             catch (COMException ex)
505             {
506                 if (!silent)
507                 {
508                     log.LogWarningWithCodeFromResources("ResolveComReference.CannotGetPathForTypeLib", typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, ex.Message);
509                 }
510 
511                 return false;
512             }
513 
514             if (typeLibPath != null && typeLibPath.Length > 0)
515             {
516                 // We have to check for NULL here because QueryPathOfRegTypeLib() returns
517                 // a BSTR with a NULL character appended to it.
518                 if (typeLibPath[typeLibPath.Length - 1] == '\0')
519                 {
520                     typeLibPath = typeLibPath.Substring(0, typeLibPath.Length - 1);
521                 }
522             }
523 
524             if (typeLibPath != null && typeLibPath.Length > 0)
525             {
526                 return true;
527             }
528             else
529             {
530                 if (!silent)
531                 {
532                     log.LogWarningWithCodeFromResources("ResolveComReference.CannotGetPathForTypeLib", typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, "");
533                 }
534 
535                 return false;
536             }
537         }
538 
539         #region RemapAdoTypeLib guids
540 
541         // guids for RemapAdoTypeLib
542         private readonly static Guid s_guidADO20 = new Guid("{00000200-0000-0010-8000-00AA006D2EA4}");
543         private readonly static Guid s_guidADO21 = new Guid("{00000201-0000-0010-8000-00AA006D2EA4}");
544         private readonly static Guid s_guidADO25 = new Guid("{00000205-0000-0010-8000-00AA006D2EA4}");
545         private readonly static Guid s_guidADO26 = new Guid("{00000206-0000-0010-8000-00AA006D2EA4}");
546         // unfortunately this cannot be readonly, since it's being passed by reference to LoadRegTypeLib
547         private static Guid s_guidADO27 = new Guid("{EF53050B-882E-4776-B643-EDA472E8E3F2}");
548 
549         #endregion
550 
551         /*
552          * Method:  RemapAdoTypeLib
553          *
554          * Tries to remap an ADO type library to ADO 2.7. If the type library passed in is an older ADO tlb,
555          * then remap it to ADO 2.7 if it's registered on the machine (!). Otherwise don't modify the typelib.
556          * Returns true if the type library passed in was successfully remapped.
557          */
RemapAdoTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr)558         internal static bool RemapAdoTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr)
559         {
560             // we only care about ADO 2.0, 2.1, 2.5 or 2.6 here.
561             if (typeLibAttr.wMajorVerNum == 2)
562             {
563                 if ((typeLibAttr.wMinorVerNum == 0 && typeLibAttr.guid == s_guidADO20) ||
564                     (typeLibAttr.wMinorVerNum == 1 && typeLibAttr.guid == s_guidADO21) ||
565                     (typeLibAttr.wMinorVerNum == 5 && typeLibAttr.guid == s_guidADO25) ||
566                     (typeLibAttr.wMinorVerNum == 6 && typeLibAttr.guid == s_guidADO26))
567                 {
568                     // see if ADO 2.7 is registered.
569                     if (!Ado27Installed)
570                     {
571                         if (!silent)
572                         {
573                             // it's not registered. Don't change the original typelib then.
574                             log.LogWarningWithCodeFromResources("ResolveComReference.FailedToRemapAdoTypeLib", typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, Ado27ErrorMessage);
575                         }
576 
577                         return false;
578                     }
579 
580                     typeLibAttr.guid = s_guidADO27;
581                     typeLibAttr.wMajorVerNum = 2;
582                     typeLibAttr.wMinorVerNum = 7;
583                     typeLibAttr.lcid = 0;
584 
585                     return true;
586                 }
587             }
588 
589             return false;
590         }
591 
592         /// <summary>
593         /// Finds an existing wrapper for the specified component
594         /// </summary>
595         /// <param name="wrapperInfo"></param>
596         /// <param name="componentTimestamp"></param>
597         /// <returns></returns>
FindExistingWrapper(out ComReferenceWrapperInfo wrapperInfo, DateTime componentTimestamp)598         internal abstract bool FindExistingWrapper(out ComReferenceWrapperInfo wrapperInfo, DateTime componentTimestamp);
599 
600         #endregion
601     }
602 }
603