How to query for installed "packaged COM" extension points - com

I work on a plugin-based application that is currently scanning the Windows registry for compatible COM servers that expose certain "Implemented Categories" entries. This works well for "regular" COM servers installed through MSI installers.
However, I'm now facing a problem with COM servers installed through MSIX installers that expose COM extension points through the "Packaged COM" catalog as described in https://blogs.windows.com/windowsdeveloper/2017/04/13/com-server-ole-document-support-desktop-bridge/ . These COM servers can still be instantiated through CoCreateInstance, but RegOpenKey/RegEnumKey searches aren't able to detect their presence.
I'm not sure how to approach this problem. The best outcome would be some sort of Windows API for querying the "Packaged COM" catalog for installed COM servers that I can run in addition to the registry search. However, I don't know if that even exist? I'm also open for other suggestions, as long as they still allows my application to dynamically detect the presence of new COM-based plugins.

PLEASE DISREGARD THIS ANSWER. There's a better answer based on ICatInformation::EnumClassesOfCategories below.
Answering myself with sample code to query the "Packaged COM" catalog for installed COM servers. Based on suggestion from #SimonMourier.
using System.Collections.Generic;
using System.IO;
/** Use Target Framework Moniker as described in https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance */
class PackagedComScan {
static void Main(string[] args) {
var packageManager = new Windows.Management.Deployment.PackageManager();
// this call require the "packageQuery" capability if called from a UWP app (add <rescap:Capability Name="packageQuery" /> to the appxmanifest)
IEnumerable<Windows.ApplicationModel.Package> my_packages = packageManager.FindPackagesForUser("");
foreach (var package in my_packages) {
try {
ParseAppxManifest(package.InstalledLocation.Path + #"\AppxManifest.xml");
} catch (FileNotFoundException) {
// Installed package missing from disk. Can happen after deploying UWP builds from Visual Studio.
}
}
}
static void ParseAppxManifest(string manifest_path) {
var doc = new System.Xml.XmlDocument();
using (var fs = new FileStream(manifest_path, FileMode.Open, FileAccess.Read, FileShare.Read))
doc.Load(fs);
var nsmgr = new System.Xml.XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("a", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // default namespace
nsmgr.AddNamespace("com", "http://schemas.microsoft.com/appx/manifest/com/windows10");
// detect exported COM servers
var nodes = doc.SelectNodes("/a:Package/a:Applications/a:Application/a:Extensions/com:Extension/com:ComServer/com:ExeServer/com:Class/#Id", nsmgr);
foreach (System.Xml.XmlNode node in nodes)
System.Console.WriteLine("Exported COM CLSID: {0}", node.Value);
}
}
This is admittedly a bit ad-hoc since it relies on parsing the AppxManifest.xml files. Still, it seems to get the job done. Please note that UWP applications that runs within sandboxed AppContainer processes only seem to have read access to some of the AppxManifest.xml files, and not all. The code therefore only works for "regular" Win32 or .Net processes.

Answering myself with sample code to query all installed COM servers, including the "Packaged COM" catalog, using ICatInformation::EnumClassesOfCategories. Based on suggestion by Aditi_Narvekar:
#include <atlstr.h>
#include <vector>
static void CHECK(HRESULT hr) {
if (FAILED(hr))
abort(); // TODO: More graceful error handling
}
/** Return COM classes that implement any of the provided "Implemented Categories". */
inline std::vector<CLSID> GetClassesWithAnyOfCategories(std::vector<CATID> impl_categories) {
CComPtr<ICatInformation> cat_search;
CHECK(cat_search.CoCreateInstance(CLSID_StdComponentCategoriesMgr));
CComPtr<IEnumGUID> class_list;
CHECK(cat_search->EnumClassesOfCategories((ULONG)impl_categories.size(), impl_categories.data(), -1, nullptr, &class_list));
std::vector<CLSID> app_clsids;
app_clsids.reserve(64);
for (;;) {
CLSID cur_cls = {};
ULONG num_read = 0;
CHECK(class_list->Next(1, &cur_cls, &num_read));
if (num_read == 0)
break;
// can also call ProgIDFromCLSID to get the ProgID
// can also call OleRegGetUserType to get the COM class name
app_clsids.push_back(cur_cls);
}
return app_clsids;
}

Related

I would like to intercept the MS Teams notifications about poor network quality, during calls

In order to diagnose a network condition I am trying to intercept (at least on Windows) the notifications about poor network quality that Teams pops-up sometimes, during a call.
For testing, I am using "clumsy", in order to generate iffiness in the network, while in a teams audiovideo call to my phone.
I was wondering if an API would provide a local interface to teams an allow me to subscribe to those events.
So far if found a type library in C:\Users\xxx\AppData\Local\Microsoft\TeamsPresenceAddin\Uc.tlb that looks promising because of the idl file that the oleviewer generates from it:
ModalityProperty in uc.tlb is an enum and contains these two values:
ucAVModalityAudioNetworkQuality = 0x300b001d,
ucAVModalityVideoNetworkQuality = 0x300b0021,
That enum is used by the interface _IAVModalityEvents and that looks to be fired by the coclass AVModality:
[
uuid(BA2BD6F3-7676-42E3-89C6-10CEB3F7E106),
helpstring("AVModality class defines the audio video modality.This class handles the events defined in the interface IAVModalityEvents."),
noncreatable,
custom(5047D0E3-86FD-4EB4-A500-AC4F5B4E17E1, "relns=Conversation.AudioVideo")
]
coclass AVModality {
[default] interface IAVModality;
interface IAVModality2;
**[default, source] dispinterface _IAVModalityEvents;**
};
Does anyone have an idea on how to connect to the running Teams instance (tried ROT but it doesn't seem to be registered) and how to enum modalities in order to get to the AVModality and subscribe to its events ?
Update 1:
I actually got some partial results with the Uc.tlb.
First I ran: TlbImp.exe Uc.tlb to create a C# assembly from it.
I then built the following C# app to intercept conversation changes:
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Xml;
using UCCollaborationLib;
class Program
{
//Teams CLSID harvested from registry
// Take a looksie at: https://stackoverflow.com/questions/56865704/com-object-for-teams-client-microsoft-office-uc
[ComImport, Guid("00425F68-FFC1-445F-8EDF-EF78B84BA1C7")]
public class TeamsOfficeIntegration
{
}
static void Main(string[] args)
{
var version = "15.0.0.0";
IUCOfficeIntegration teamsOfficeIntegration = (IUCOfficeIntegration) new TeamsOfficeIntegration();
if (teamsOfficeIntegration == null) {
Console.WriteLine("Cannot instantiate UCOfficeIntegration for Teams");
return;
}
IClient teamsClient = (IClient) teamsOfficeIntegration.GetInterface(version, OIInterface.oiInterfaceILyncClient);
if (teamsClient == null) {
Console.WriteLine("Cannot retrieve Teams Client interface");
return;
}
var teamsConversationManager = teamsClient.ConversationManager;
if (teamsConversationManager == null) {
Console.WriteLine("Cannot retrieve Teams ConversationManager interface");
return;
}
// Handle Teams Conversation Collection events
teamsConversationManager.OnConversationAdded += (ConversationManager _eventSource, ConversationManagerEventData _eventData) => { Console.WriteLine("Conversation added");};
teamsConversationManager.OnConversationRemoved += (ConversationManager _eventSource, ConversationManagerEventData _eventData) => { Console.WriteLine("Conversation removed");};
//iterate SelfParticipant or Modalities -> iterate IAVModality -> IConnectionPoint
}
}
The good news is that, when Teams is stopped it tries to start it so it does CoCreateInstance on the Teams LocalServer so it should be able to interact with it. The bad news is that even if the Teams app starts or is being attached to the creation fails with the following entry in the EventViewer/AdministrativeEvents:
"The server {00425F68-FFC1-445F-8EDF-EF78B84BA1C7} did not register with DCOM within the required timeout."

.Net Core Image Manipulation (Crop & Resize) / File Handling

I have spent the last 5 hours trying to find a feasible way to accomplish what seems to me quite an easy task if it was a previous version of the .NET family that I was working with:
Uploading a picture
Resizing & Cropping the picture
Saving the new picture into a directory
I have come accross to couple of libraries that are either in pre-release stage or in a not-complete stage.
Has anyone at all accomplished the above tasks without specifically including the System.Drawing namespace and/or adding a dependency for an earlier version of the .NET framework?
UPDATE on 08 / 08 / 2016
I ended up using System.Drawing something which is very annoying and disappointing. If you are developing a software used by thousands of developers, and if all these developers are relying on the components of this software, I believe, one cannot just come up with a new version, "sweet talk" about it during the conferences to show off your public speaking skills rather than giving a shit about your work and on one hand proudly hold in high esteem of it, and on the other, strip away the mostly used and demanded portions of it.
I do understand and appreciate with great excitement myself - of the new era of .net with the core framework - being a loyal asp dev since the first days of classic asp - however, to me, it is just an uncomplete product causing more frustrations and dissappointment than pleasure. When there are millions of websites in today's content-driven world, completely relying on content management, you can't just come up and say, "Hey, I have this brilliant, techonology, leaner, faster blah blah" but errr, you will have some problems with "managing" your content..
It should not be forgotten that, although, MS (and us) is very excited about this new core framework, with now being open source etc, there are other languages and frameworks out there that are doing what MS is promising to do, for a very very long time now.
ImageSharp
ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics API.
Designed to democratize image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API.
Compared to System.Drawing we have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.
Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
You can use the Microsoft ASP.NET Core JavaScript Services to invoke arbitrary NPM packages at runtime from .NET code which means you can choose any npm package that provide image scaling and invoke it.
The following example shows how to use JavaScriptServices to resize image
https://github.com/aspnet/JavaScriptServices/tree/dev/samples/misc/NodeServicesExamples
Hope that Helps
.NET Core Image Processing blog post (January 19, 2017) compares 6 libraries:
CoreCompat.System.Drawing
ImageSharp
Magick.NET (Win only)
SkiaSharp
FreeImage-dotnet-core
MagicScaler
Feb 26 update: post was updated, two new packages added
To complete the #Hossam Barakat answer, you can use the Microsoft ASP.NET Core JavaScript Services to invoke arbitrary NPM packages at runtime from .NET code which means you can choose any npm package that provide image scaling and invoke it.
The sample use the sharp module, which has a lot of dependecies. If you prefere, like me, to use jimp which is pure javascript:
Startup.cs
public class Startup
{
...
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Enable Node Services
services.AddNodeServices();
...
}
...
}
ImageController.cs
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.StaticFiles;
using System.Security.Cryptography;
using System.Text;
namespace NodeServicesExamples.Controllers
{
public class ResizeImageController : Controller
{
private const int MaxDimension = 1000;
private static string[] AllowedMimeTypes = new[] { "image/jpeg", "image/png", "image/gif" };
private IHostingEnvironment _environment;
private INodeServices _nodeServices;
public ResizeImageController(IHostingEnvironment environment, INodeServices nodeServices)
{
_environment = environment;
_nodeServices = nodeServices;
}
[Route("resize/{*imagePath}")]
[ResponseCache(Duration = 3600)]
public async Task<IActionResult> Index(string imagePath, double maxWidth, double maxHeight)
{
// Validate incoming params
if (maxWidth < 0 || maxHeight < 0 || maxWidth > MaxDimension || maxHeight > MaxDimension
|| (maxWidth + maxHeight) == 0)
{
return BadRequest("Invalid dimensions");
}
var mimeType = GetContentType(imagePath);
if (Array.IndexOf(AllowedMimeTypes, mimeType) < 0)
{
return BadRequest("Disallowed image format");
}
// Locate source image on disk
var fileInfo = _environment.WebRootFileProvider.GetFileInfo(imagePath);
if (!fileInfo.Exists)
{
return NotFound();
}
var eTag = GenerateETag(Encoding.UTF8.GetBytes($"{fileInfo.LastModified.ToString("s")}-{fileInfo.Length}"));
HttpContext.Response.Headers["ETag"] = eTag;
var match = HttpContext.Request.Headers["If-None-Match"].FirstOrDefault();
if (eTag == match)
{
return StatusCode(304);
}
// Invoke Node and pipe the result to the response
var imageStream = await _nodeServices.InvokeAsync<Stream>(
"./Node/resizeImage",
fileInfo.PhysicalPath,
mimeType,
maxWidth,
maxHeight);
return File(imageStream, mimeType, fileInfo.Name);
}
private string GetContentType(string path)
{
string result;
return new FileExtensionContentTypeProvider().TryGetContentType(path, out result) ? result : null;
}
private string GenerateETag(byte[] data)
{
string ret = string.Empty;
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(data);
string hex = BitConverter.ToString(hash);
ret = hex.Replace("-", "");
}
return ret;
}
}
}
Node\resizeImage.js
var jimp = require("jimp");
module.exports = function (result, physicalPath, mimeType, maxWidth, maxHeight) {
// Invoke the 'jimp' NPM module, and have it pipe the resulting image data back to .NET
jimp.read(physicalPath).then(function (file) {
var width = maxWidth || jimp.AUTO;
var height = maxHeight || jimp.AUTO;
file.resize(maxWidth, height)
.getBuffer(mimeType, function (err, buffer) {
var stream = result.stream;
stream.write(buffer);
stream.end();
});
}).catch(function (err) {
console.error(err);
});
};
install jimp: npm install jimp --save
Short answer is no, not yet. Most if not all current libraries rely on System.Drawing. If you need this now, I would go that route and add System.Drawing.
The .NET team is currently working through the features that are missing on the Core 1.0 stack, but this one isn't high enough on their priority list: Link
This is a library to watch as they're getting very close to a releasable API without System.Drawing. : ImageSharp
use SkiSharp, it is on the official microsoft documentation also:
https://learn.microsoft.com/en-us/dotnet/api/skiasharp?view=skiasharp-1.68.0

Deploying SSRS RDL files from VB.Net - Issue with shared datasources

I am currently developing a utility to help automate our report deployment process. Multiple files, in multiple folders, to multiple servers.
I am using the reportservice2010.asmx web service, and I am deploying my files to the server - so most of the way there.
My issue is that I have shared data sets and shared data sources, which are deployed to individual folders, separate to the report folders. When the deployment occurs the web service looks locally for the data source rather than in the data source folder, giving an error like:
The dataset ‘CostReduction’ refers to the shared data source ‘CostReduction’, which is not
published on the report server. The shared data source ‘CostReduction’ must be published
before this report can run.
The data source/set has been deployed and the report functions correctly but I need to suppress these error messages as they may be hiding other actual errors.
I can hard code a lookup that checks if the data source/set exists and manually filter them via that, but it seems very in-efficient. Is there any way I can tell the web service where to look for these files or another approach that other people have used?
I'm not looking at changing the reports so the data source is read from
/DataSources/DataSourceName
as there are lots of reports and that's not how our existing projects are configured.
Many thanks in advance.
I realize you are using VB, but perhaps this will give you a clue if you convert it from C# to VB, using one of the translators on the web.
Hopefully this will give you a lead in the right direction.
When All the reports in a particular folder, referred to here as the 'parent folder', all use the same Shared Data source, I use this to set all the reports to the same shared Data Source (in this case "/DataSources/Shared_New")
using GetPropertiesSample.ReportService2010;
using System.Diagnostics;
using System.Collections.Generic; //<== required for LISTS
using System.Reflection;
namespace GetPropertiesSample
{
class Program
{
static void Main(string[] args)
{
GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource("0_Contacts"); //<=== This is the parent folder
}
private static void GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource(string sParentFolder)
{
// Create a Web service proxy object and set credentials
ReportingService2010 rs = new ReportingService2010();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
CatalogItem[] reportList = rs.ListChildren(#"/" + sParentFolder, true);
int iCounter = 0;
foreach (CatalogItem item in reportList)
{
iCounter += 1;
Debug.Print(iCounter.ToString() + "]#########################################");
if (item.TypeName == "Report")
{
Debug.Print("Report: " + item.Name);
ResetTheDataSource_for_a_Report(item.Path, "/DataSources/Shared_New"); //<=== This is the DataSource that I want them to use
}
}
}
private static void ResetTheDataSource_for_a_Report(string sPathAndFileNameOfTheReport, string sPathAndFileNameForDataSource)
{
//from: http://stackoverflow.com/questions/13144604/ssrs-reportingservice2010-change-embedded-datasource-to-shared-datasource
ReportingService2010 rs = new ReportingService2010();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
string reportPathAndName = sPathAndFileNameOfTheReport;
//example of sPathAndFileNameOfTheReport "/0_Contacts/207_Practices_County_CareManager_Role_ContactInfo";
List<ReportService2010.ItemReference> itemRefs = new List<ReportService2010.ItemReference>();
ReportService2010.DataSource[] itemDataSources = rs.GetItemDataSources(reportPathAndName);
foreach (ReportService2010.DataSource itemDataSource in itemDataSources)
{
ReportService2010.ItemReference itemRef = new ReportService2010.ItemReference();
itemRef.Name = itemDataSource.Name;
//example of DataSource i.e. 'itemRef.Reference': "/DataSources/SharedDataSource_DB2_CRM";
itemRef.Reference = sPathAndFileNameForDataSource;
itemRefs.Add(itemRef);
}
rs.SetItemReferences(reportPathAndName, itemRefs.ToArray());
}
}
To Call it I use this in the 'Main' Method:
GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource("0_Contacts");
In this case "0_Contacts" is the parent folder, itself located in the root directory, that contains all the reports for which I want to reset their DataSources to the new Shared DataSource. Then that Method calls the other method "ResetTheDataSource_for_a_Report" which actually sets the DataSource for the report.

How to extract class IL code from loaded assembly and save to disk?

How would I go about extracting the IL code for classes that are generated at runtime by reflection so I can save it to disk? If at all possible. I don't have control of the piece of code that generates these classes.
Eventually, I would like to load this IL code from disk into another assembly.
I know I could serialise/deserialise classes but I wish to use purely IL code. I'm not fussed with the security implications.
Running Mono 2.10.1
Or better yet, use Mono.Cecil.
It will allow you to get at the individual instructions, even manipulating them and disassembling them (with the mono decompiler addition).
Note that the decompiler is a work in progress (last time I checked it did not fully support lambda expressions and Visual Basic exception blocks), but you can have pretty decompiled output in C# pretty easily as far as you don't hit these boundary conditions. Also, work has progressed since.
Mono Cecil in general let's you write the IL to a new assembly, as well, which you can then subsequently load into your appdomain if you like to play with bleeding edge.
Update I came round to trying this. Unfortunately I think I found what problem you run into. It turns out there is seems to be no way to get at the IL bytes for a generated type unless the assembly happened to get written out somewhere you can load it from.
I assumed you could just get the bits via reflection (since the classes support the required methods), however the related methods just raise an exception The invoked member is not supported in a dynamic module. on invocation. You can try this with the code below, but in short I suppose it means that it ain't gonna happen unless you want to f*ck with Marshal::GetFunctionPointerForDelegate(). You'd have to binary dump the instructions and manually disassemble them as IL opcodes. There be dragons.
Code snippet:
using System;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Reflection.Emit;
using System.Reflection;
namespace REFLECT
{
class Program
{
private static Type EmitType()
{
var dyn = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Emitted"), AssemblyBuilderAccess.RunAndSave);
var mod = dyn.DefineDynamicModule("Emitted", "Emitted.dll");
var typ = mod.DefineType("EmittedNS.EmittedType", System.Reflection.TypeAttributes.Public);
var mth = typ.DefineMethod("SuperSecretEncryption", System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.Static, typeof(String), new [] {typeof(String)});
var il = mth.GetILGenerator();
il.EmitWriteLine("Emit was here");
il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
il.Emit(System.Reflection.Emit.OpCodes.Ret);
var result = typ.CreateType();
dyn.Save("Emitted.dll");
return result;
}
private static Type TestEmit()
{
var result = EmitType();
var instance = Activator.CreateInstance(result);
var encrypted = instance.GetType().GetMethod("SuperSecretEncryption").Invoke(null, new [] { "Hello world" });
Console.WriteLine(encrypted); // This works happily, print "Emit was here" first
return result;
}
public static void Main (string[] args)
{
Type emitted = TestEmit();
// CRASH HERE: even if the assembly was actually for SaveAndRun _and_ it
// has actually been saved, there seems to be no way to get at the image
// directly:
var ass = AssemblyFactory.GetAssembly(emitted.Assembly.GetFiles(false)[0]);
// the rest was intended as mockup on how to isolate the interesting bits
// but I didn't get much chance to test that :)
var types = ass.Modules.Cast<ModuleDefinition>().SelectMany(m => m.Types.Cast<TypeDefinition>()).ToList();
var typ = types.FirstOrDefault(t => t.Name == emitted.Name);
var operands = typ.Methods.Cast<MethodDefinition>()
.SelectMany(m => m.Body.Instructions.Cast<Instruction>())
.Select(i => i.Operand);
var requiredTypes = operands.OfType<TypeReference>()
.Concat(operands.OfType<MethodReference>().Select(mr => mr.DeclaringType))
.Select(tr => tr.Resolve()).OfType<TypeDefinition>()
.Distinct();
var requiredAssemblies = requiredTypes
.Select(tr => tr.Module).OfType<ModuleDefinition>()
.Select(md => md.Assembly.Name as AssemblyNameReference);
foreach (var t in types.Except(requiredTypes))
ass.MainModule.Types.Remove(t);
foreach (var unused in ass.MainModule
.AssemblyReferences.Cast<AssemblyNameReference>().ToList()
.Except(requiredAssemblies))
ass.MainModule.AssemblyReferences.Remove(unused);
AssemblyFactory.SaveAssembly(ass, "/tmp/TestCecil.dll");
}
}
}
If all you want is the IL for your User class, you already have it. It's in the dll that you compiled it to.
From your other assembly, you can load the dll with the User class dynamically and use it through reflection.
UPDATE:
If what you have is a dynamic class created with Reflection.Emit, you have an AssemblyBuilder that you can use to save it to disk.
If your dynamic type was instead created with Mono.Cecil, you have an AssemblyDefinition that you can save to disk with myAssemblyDefinition.Write("MyAssembly.dll") (in Mono.Cecil 0.9).

How to programmatically get DLL dependencies

How can I get the list of all DLL dependencies of a given DLL or EXE file?
In other words, I'd like to do the same as the "Dependency walker" tool, but programmatically.
What is the Windows (ideally .NET) API for that?
You can use EnumProcessModules function. Managed API like kaanbardak suggested won't give you a list of native modules.
For example see this page on MSDN
If you need to statically analyze your dll you have to dig into PE format and learn about import tables. See this excellent tutorial for details.
NOTE: Based on the comments from the post below, I suppose this might miss unmanaged dependencies as well because it relies on reflection.
Here is a small c# program written by Jon Skeet from bytes.com on a .NET Dependency Walker
using System;
using System.Reflection;
using System.Collections;
public class DependencyReporter
{
static void Main(string[] args)
{
//change this line if you only need to run the code one:
string dllToCheck = #"";
try
{
if (args.Length == 0)
{
if (!String.IsNullOrEmpty(dllToCheck))
{
args = new string[] { dllToCheck };
}
else
{
Console.WriteLine
("Usage: DependencyReporter <assembly1> [assembly2 ...]");
}
}
Hashtable alreadyLoaded = new Hashtable();
foreach (string name in args)
{
Assembly assm = Assembly.LoadFrom(name);
DumpAssembly(assm, alreadyLoaded, 0);
}
}
catch (Exception e)
{
DumpError(e);
}
Console.WriteLine("\nPress any key to continue...");
Console.ReadKey();
}
static void DumpAssembly(Assembly assm, Hashtable alreadyLoaded, int indent)
{
Console.Write(new String(' ', indent));
AssemblyName fqn = assm.GetName();
if (alreadyLoaded.Contains(fqn.FullName))
{
Console.WriteLine("[{0}:{1}]", fqn.Name, fqn.Version);
return;
}
alreadyLoaded[fqn.FullName] = fqn.FullName;
Console.WriteLine(fqn.Name + ":" + fqn.Version);
foreach (AssemblyName name in assm.GetReferencedAssemblies())
{
try
{
Assembly referenced = Assembly.Load(name);
DumpAssembly(referenced, alreadyLoaded, indent + 2);
}
catch (Exception e)
{
DumpError(e);
}
}
}
static void DumpError(Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error: {0}", e.Message);
Console.WriteLine();
Console.ResetColor();
}
}
To get native module dependencies, I believe it should be ok to get it from the PE file's import table, here are 2 links which explain that in-depth:
http://msdn.microsoft.com/en-us/magazine/bb985992.aspx
http://msdn.microsoft.com/en-us/magazine/cc301808.aspx
To get .NET dependencies, we can use .NET's API, like Assembly.Load.
To get a .NET module's all dependencies, How about combine the 2 ways - .NET assemblies are just PE file with meta data.
While this question already has an accepted answer, the documentation referenced in the other answers, where not broken, is old. Rather than reading through all of it only to find it doesn't cover differences between Win32 and x64, or other differences, my approach was this:
C:\UnxUtils\usr\local\wbin>strings.exe E:\the-directory-I-wanted-the-info-from\*.dll > E:\TEMP\dll_strings.txt
This allowed me to use Notepad++ or gvim or whatever to search for dlls that were still depending on MS dlls with 120.dll at the end of the dll name so I could find the ones that needed updating.
This could easily be scripted in your favorite language.
Given that my search for this info was with VS 2015 in mind, and this question was the top result for a Google search, I supply this answer that it may perhaps be of use to someone else who comes along looking for the same thing.
To read the DLL's (modules) loaded by a running exe, use the ToolHelp32 functions
Tool help Documentation on MSDN.
Not sure what it will show for a .Net running exe (I've never tried it). But, it does show the full path from where the DLL's were loaded. Often, this was the information I needed when trying to sort out DLL problems. .Net is supposed to have removed the need to use these functions (look up DLL Hell for more information).
If you don't want to load the assembly in your program, you can use DnSpy (https://www.nuget.org/packages/dnSpyLibs):
var assemblyDef = dnlib.DotNet.AssemblyDef.Load("yourDllName.dll");
var dependencies = assemblyDef.ManifestModule.GetAssemblyRefs();
Notice that you have all the infos you can want in the "ManifestModule" property.