1515// </remarks>
1616// ---------------------------------------------------------------------------------------------
1717using System ;
18+ using System . Collections . Generic ;
1819using System . Collections . Specialized ;
1920using System . Diagnostics ;
2021using System . IO ;
@@ -103,6 +104,15 @@ public RegFree()
103104 /// ------------------------------------------------------------------------------------
104105 public ITaskItem [ ] NoTypeLib { get ; set ; }
105106
107+ /// ------------------------------------------------------------------------------------
108+ /// <summary>
109+ /// Gets or sets the CLSIDs to exclude from the manifest.
110+ /// This is useful when a CLSID is defined in a TypeLib but implemented in a managed assembly
111+ /// that provides its own manifest entry.
112+ /// </summary>
113+ /// ------------------------------------------------------------------------------------
114+ public ITaskItem [ ] ExcludedClsids { get ; set ; }
115+
106116 /// ------------------------------------------------------------------------------------
107117 /// <summary>
108118 /// Gets or sets manifest fragment files that will be included in the resulting manifest
@@ -153,16 +163,20 @@ public override bool Execute()
153163 Path . GetFileName ( Executable )
154164 ) ;
155165
156- StringCollection dllPaths = GetFilesFrom ( Dlls ) ;
157- if ( dllPaths . Count == 0 )
166+ var itemsToProcess = new List < ITaskItem > ( Dlls ) ;
167+ if ( itemsToProcess . Count == 0 )
158168 {
159169 string ext = Path . GetExtension ( Executable ) ;
160170 if (
161171 ext != null
162172 && ext . Equals ( ".dll" , StringComparison . InvariantCultureIgnoreCase )
173+ && ( ManagedAssemblies == null || ! ManagedAssemblies . Any ( m => m . ItemSpec . Equals ( Executable , StringComparison . OrdinalIgnoreCase ) ) )
163174 )
164- dllPaths . Add ( Executable ) ;
175+ {
176+ itemsToProcess . Add ( new TaskItem ( Executable ) ) ;
177+ }
165178 }
179+
166180 string manifestFile = string . IsNullOrEmpty ( Output )
167181 ? Executable + ".manifest"
168182 : Output ;
@@ -192,14 +206,19 @@ public override bool Execute()
192206
193207 // Process all DLLs using direct type library parsing (no registry redirection needed)
194208 var creator = new RegFreeCreator ( doc , Log ) ;
195- var filesToRemove = dllPaths
196- . Cast < string > ( )
197- . Where ( fileName => ! File . Exists ( fileName ) )
198- . ToList ( ) ;
199- foreach ( var file in filesToRemove )
200- dllPaths . Remove ( file ) ;
209+ if ( ExcludedClsids != null )
210+ {
211+ creator . AddExcludedClsids ( GetFilesFrom ( ExcludedClsids ) ) ;
212+ }
213+
214+ // Remove non-existing files from the list
215+ itemsToProcess . RemoveAll ( item => ! File . Exists ( item . ItemSpec ) ) ;
201216
202217 string assemblyName = Path . GetFileNameWithoutExtension ( manifestFile ) ;
218+ if ( assemblyName . EndsWith ( ".dll" , StringComparison . OrdinalIgnoreCase ) )
219+ {
220+ assemblyName = Path . GetFileNameWithoutExtension ( assemblyName ) ;
221+ }
203222 Debug . Assert ( assemblyName != null ) ;
204223 // The C++ test programs won't run if an assemblyIdentity element exists.
205224 //if (assemblyName.StartsWith("test"))
@@ -214,33 +233,65 @@ public override bool Execute()
214233 // just ignore
215234 }
216235 if ( string . IsNullOrEmpty ( assemblyVersion ) )
236+ {
217237 assemblyVersion = "1.0.0.0" ;
238+ }
239+ else
240+ {
241+ // Ensure version has 4 parts for manifest compliance (Major.Minor.Build.Revision)
242+ // Some assemblies might have 3-part versions (e.g. 1.1.0) which are invalid in manifests.
243+ // We also strip any non-numeric suffix if present, though FileVersion is usually clean.
244+ var parts = assemblyVersion . Split ( '.' ) ;
245+ if ( parts . Length != 4 )
246+ {
247+ var newParts = new string [ 4 ] ;
248+ for ( int i = 0 ; i < 4 ; i ++ )
249+ {
250+ // Simple parsing to ensure we only get numbers
251+ string part = "0" ;
252+ if ( i < parts . Length )
253+ {
254+ // Take only the leading digits
255+ var digits = new string ( parts [ i ] . TakeWhile ( char . IsDigit ) . ToArray ( ) ) ;
256+ if ( ! string . IsNullOrEmpty ( digits ) )
257+ part = digits ;
258+ }
259+ newParts [ i ] = part ;
260+ }
261+ assemblyVersion = string . Join ( "." , newParts ) ;
262+ }
263+ }
218264
219265 XmlElement root = creator . CreateExeInfo ( assemblyName , assemblyVersion , Platform ) ;
220266
221- foreach ( string fileName in dllPaths )
267+ foreach ( string fileName in GetFilesFrom ( ManagedAssemblies ) )
222268 {
223- if ( NoTypeLib . Count ( f => f . ItemSpec == fileName ) != 0 )
224- continue ;
225-
226269 Log . LogMessage (
227270 MessageImportance . Low ,
228- "\t Processing library {0}" ,
271+ "\t Processing managed assembly {0}" ,
229272 Path . GetFileName ( fileName )
230273 ) ;
231-
232- // Process type library directly (no registry redirection needed)
233- creator . ProcessTypeLibrary ( root , fileName ) ;
274+ creator . ProcessManagedAssembly ( root , fileName ) ;
234275 }
235276
236- foreach ( string fileName in GetFilesFrom ( ManagedAssemblies ) )
277+ foreach ( var item in itemsToProcess )
237278 {
279+ string fileName = item . ItemSpec ;
280+ if ( NoTypeLib . Count ( f => f . ItemSpec == fileName ) != 0 )
281+ continue ;
282+
283+ string server = item . GetMetadata ( "Server" ) ;
284+ if ( string . IsNullOrEmpty ( server ) )
285+ server = null ;
286+
238287 Log . LogMessage (
239288 MessageImportance . Low ,
240- "\t Processing managed assembly {0}" ,
289+ "\t Processing library {0}" ,
241290 Path . GetFileName ( fileName )
242291 ) ;
243- creator . ProcessManagedAssembly ( root , fileName ) ;
292+
293+ // Process type library directly (no registry redirection needed)
294+ creator . ProcessTypeLibrary ( root , fileName , server ) ;
244295 }
245296
246297 // Process classes and interfaces from HKCR (where COM is already registered)
@@ -277,6 +328,18 @@ public override bool Execute()
277328 creator . AddDependentAssembly ( root , assemblyFileName ) ;
278329 }
279330
331+ if ( ! HasRegFreeContent ( doc ) )
332+ {
333+ Log . LogMessage (
334+ MessageImportance . Low ,
335+ "\t No registration-free content found for {0}; manifest will not be emitted." ,
336+ Path . GetFileName ( manifestFile )
337+ ) ;
338+ if ( File . Exists ( manifestFile ) )
339+ File . Delete ( manifestFile ) ;
340+ return true ;
341+ }
342+
280343 var settings = new XmlWriterSettings
281344 {
282345 OmitXmlDeclaration = false ,
@@ -301,9 +364,25 @@ public override bool Execute()
301364 return true ;
302365 }
303366
304- private static StringCollection GetFilesFrom ( ITaskItem [ ] source )
367+ private static bool HasRegFreeContent ( XmlDocument doc )
368+ {
369+ if ( doc . DocumentElement == null )
370+ return false ;
371+
372+ var namespaceManager = new XmlNamespaceManager ( doc . NameTable ) ;
373+ namespaceManager . AddNamespace ( "asmv1" , "urn:schemas-microsoft-com:asm.v1" ) ;
374+
375+ bool HasNode ( string xpath ) => doc . SelectSingleNode ( xpath , namespaceManager ) != null ;
376+
377+ return HasNode ( "//asmv1:clrClass" )
378+ || HasNode ( "//asmv1:comClass" )
379+ || HasNode ( "//asmv1:typelib" )
380+ || HasNode ( "//asmv1:dependentAssembly" ) ;
381+ }
382+
383+ private static List < string > GetFilesFrom ( ITaskItem [ ] source )
305384 {
306- var result = new StringCollection ( ) ;
385+ var result = new List < string > ( ) ;
307386 if ( source == null )
308387 return result ;
309388 foreach ( var item in source )
0 commit comments