How to edit a resource assembly in C# .net application - c#

Now, I am working on edit existing string/icon resource assembly which is code in C# in VS. I have search some method to edit the binary file directly, particularly, I use mono.cecil (http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/), almost all the localized resource files work fine. But only the English original resource file does not work. So I think I should give up this way, I hope to edit the file manually, either open source or original .net API is ok. Anyone have this kind experience to edit the resource file, please let me know:
BTW, the English project contains core logic code and references other dlls (OtherDLL.dll), this may cause exceptions when using mono.cecil, the code and exception is below.
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(resourceFileName);
// Some code
assemblyDefinition.Write(newFileName); // This will cause exception: "Failed to resolve assembly: 'OtherDLL, Version=10.1.1.1, Culture=neutral, PublicKeyToken=null'
The english dll is TestDll.dll under deployment directory, and other localized resource dlls are TestDll.resources.dll under localized directory, like /de/TestDll.resources.dll and /zh-CN/TestDll.resources.dll.
Now I need the method to achieve the target(edit and save resource part), so please help me to find a way to achieve the goal. Any comments will be appreciated.
Thanks in advance.

The problem is that you need to set the search directories for the assembly references in the metadata tables to be rewritten correctly:
var assemblyFile = #"c:\myassembly.dll";
var resolver = new DefaultAssemblyResolver();
// add directory of the source assembly for references
resolver.AddSearchDirectory(Path.GetDirectoryName(assemblyFile));
// add .NET runtime directories
var runtimeDir = RuntimeEnvironment.GetRuntimeDirectory();
foreach (var dir in Directory.GetDirectories(RuntimeEnvironment.GetRuntimeDirectory(), "*", SearchOption.AllDirectories))
{
resolver.AddSearchDirectory(dir);
}
var mod = AssemblyDefinition.ReadAssembly(assemblyFile, new ReaderParameters { AssemblyResolver = resolver }).MainModule;
mod.Write(assemblyFile + ".patched");
When you want to change resources with Mono.Cecil:
var strings = mod.Resources.SingleOrDefault(a => a.Name == "English.resources");
mod.Resources.Remove(strings);
using var ms = new MemoryStream();
using var rw = new ResourceWriter(ms);
rw.AddResource("GreetingText", "Hello World");
rw.Generate();
ms.Position = 0;
mod.Resources.Add(new EmbeddedResource("English.resources", ManifestResourceAttributes.Public, ms));
mod.Write(assemblyFile + ".patched");
Regards

Related

Resx files and embedding into assembly

I have a T4 template which generates a DbContext and a migration configuration. During runtime, I use that template to create an assembly, then use that assembly to generate a migration. However, when I want to do an update database, still programmatically. I get an error, however:
Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "EFMigrations._11_01_30.resources" was correctly embedded or linked into assembly "AutomatedMigrations" at compile time, or that all the satellite assemblies required are loadable and fully signed.
The code for creating the assembly:
var configuration = (DbMigrationsConfiguration)icc.CompiledAssembly.CreateInstance("EFMigrations.Configuration");
File.WriteAllText(directory + scaffold.MigrationId + ".designer.cs", scaffold.DesignerCode);
File.WriteAllText(directory + scaffold.MigrationId + ".cs", scaffold.UserCode);
using (var writer = new ResXResourceWriter(directory + scaffold.MigrationId + ".resources"))
{
foreach (var resource in scaffold.Resources)
writer.AddResource(resource.Key, resource.Value);
}
var filesContents = Directory.GetFiles(directory).Where(x => x.EndsWith(".cs")).Select(File.ReadAllText).ToList();
var resources = Directory.GetFiles(directory).Where(x => x.EndsWith(".resources"));
compilerParams.EmbeddedResources.AddRange(resources.ToArray());
var assemblies = provider.CompileAssemblyFromSource(compilerParams, filesContents.ToArray());
configuration.MigrationsAssembly = assemblies.CompiledAssembly;
configuration.MigrationsNamespace = "EFMigrations";
var migrator = new DbMigrator(configuration);
migrator.Update();
The exception is thrown on the Update() line.
Update:
I've performed a small hack to resolve that issue (by naming my resource Namespace.Class.resources, however now I am getting an error:
Stream is not a valid resource file
Update 2:
I have resolved the issue by creating another T4 runtime template and placing the values from the resources directly into it. It's a hackish solution, but it works for my purposes. However I'm still annoyed by the behaviour of the resource file and why it doesn't like the generated resource file.
Just to mark this as answered - I have resolved my particular issue by creating another t4 generator which uses a sample of the designer code as base and fills in MigrationMetadataID and Target with the ones from scaffold resources like this:
var designerGenerator = new MigrationDesignerGenerator();
designerGenerator.Session = new Dictionary<string, object>();
designerGenerator.Session.Add("Target", scaffold.Resources["Target"]);
designerGenerator.Session.Add("MigrationId", scaffold.MigrationId);
designerGenerator.Initialize();
File.WriteAllText(directory + scaffold.MigrationId + ".Designer.cs", designerGenerator.TransformText());
I have excluded the ResourceManager field from the metadata generator, and used this as target instead:
string IMigrationMetadata.Target
{
get { return "<#= Target #>"; }
}
I have come no further to solving the Resx file issue, but I will forget this for now - as hackish as this feels, it works and it works well based on my tests.

Get All Configuration Files Across Application

I have a modularized application where each module is it's own project. Each module can then be packaged into a single dll.
I have added my own custom configuration file for each module called "Scripts.config" within the root of the project. I was wondering if there was a way to get all the Scripts.config files in the application.
The following code is used to get all implementations of a particular type:
private IList<Type> GetTypes<T>() {
return BuildManager.GetReferencedAssemblies().Cast<Assembly>().SelectMany(a => a.GetExportedTypes().Where(t => typeof(T).IsAssignableFrom(t) && !t.IsAbstract)).ToList();
}
I guess i'm looking for the equivalent but to get all the "Script.config" files. I guess I could embed the Scripts.config file but I was wondering if there was a better solution.
Sorry if I haven't explained this clearly. I'd really appreciate any help. Thanks
As I thought embedding the resource was the best way to go. I simply get the required assemblies and then use the following code for each assembly to retrieve the content of the Scripts.config file.
using (var stream = assembly.GetManifestResourceStream(assembly.GetManifestResourceNames().Single(n => n.EndsWith("Scripts.config")))) {
using (var sr = new StreamReader(stream)) {
var content = sr.ReadToEnd();
...
}
}

Reading the Version number from a AssemblyInfo.cs file

I'm trying to extract the version number from a AssemblyInfo.cs file!
And I'm trying to use System.Reflection.Assembly.LoadFile(path); But while doing this I get a BadImageFormatException; "The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)". So now I wounder, is that not a possible way to go about it? And should I use RegEx instead?
I have read many examples with GetExecutingAssembly() but I do want to get the version from an other project.
Clarification: I want to read the version info from the AssemblyInfo.cs file! And not from a compiled file. I'm trying to make a tool to update my version numbers before I make a new release.
You can get Assembly version without loading it as:
using System.Reflection;
using System.IO;
...
// Get assembly
AssemblyName currentAssembly = AssemblyName.GetAssemblyName(path);
Version assemblyVersion = currentAssembly.Version;
Edit:
If you want to read file then you can do it like this:
string path = #"d:\AssemblyInfo.cs";
if (File.Exists(path))
{
// Open the file to read from.
string[] readText = File.ReadAllLines(path);
var versionInfoLines = readText.Where(t => t.Contains("[assembly: AssemblyVersion"));
foreach (string item in versionInfoLines)
{
string version = item.Substring(item.IndexOf('(') + 2, item.LastIndexOf(')') - item.IndexOf('(') - 3);
//Console.WriteLine(Regex.Replace(version, #"\P{S}", string.Empty));
Console.WriteLine(version);
}
}
//Output
1.0.*
1.0.0.0
Hope this help...
You can specify the target assembly path in AssemblyName.GetAssemblyName
AssemblyName.GetAssemblyName("ProjectB.exe").Version
AssemblyInfo.cs file gets compiled to IL assembly.
If you load that assembly you can read the version with all the examples that you have already seen. Which is reading an embedded version information from a compiled assembly file, and it may be overwritten by compilation process to a value different from what is in AssemblyInfo.cs
However it sounds like what you want instead is to read a version number from AssemblyInfo.cs text file, without compiling it down.
If this is the case you really just have to use regex with a format appropriate for your project, or even come up with a convention that will keep it simple.
This could be as simple as
var versionMatch = Regex.Match(File.ReadAllText(filename), #"AssemblyVersion\s*\(\s*""([0-9\.\*]*?)""\s*\)");
if (versionMatch.Success)
{
Console.WriteLine(versionMatch.Groups[1].Value);
}
You would have to consider convention around what goes there, since 1.0.* is a valid version string that translates to timestamp values of form 1.0.nnn.mmm at compile time, and nnn and mmm part closely guessable but not precisely guessable.
It sounds like you're trying to load an assembly compiled for x86 in an x64 environment or vice-versa.
Ensure the assembly this code resides in is built for the same environment as the target and you can get it with the examples it sounds like you've read.
You can proceed with Assembly.GetName().Version where your assembly could be the type of your class
public class Test
{
public static void Main()
{
Console.WriteLine("Current assembly : " + typeof(Test).Assembly.GetName().Version);
}
}
For the test application I have working on, shows me below details using above code:

CodeDom add reference to existing file

In visual studio, I can click on "Reference" > "Add reference" and browse to an existing .dll file from my computer. I can then use the referenced dll as follows:
dllNameSpace.dllClassName myReference = new dllNameSpace.dllClassName();
myReference.someVoid();
I know how to add a referenced assembly using codedom (will show this below), but the actual dll file is not being added to the project as it is when done through Visual Studio. Again, I need to be able to call some function in the dll file I'd like to reference.
What I'm doing now:
// Configure a CompilerParameters that links the system.dll and produces the specified executable file.
string[] referenceAssemblies = {
"System.dll",
"System.Drawing.dll",
"System.Windows.Forms.dll",
"System.Data.dll",
"System.Xml.dll",
"System.Management.dll",
Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + #"\myDllFile.dll"
};
CompilerParameters cp = new CompilerParameters(referenceAssemblies, exeFile, false);
I'm assuming that I will need to do something different in order to have CodeDom add the dll to the output executable file. What more needs to be done here?
Thanks for the help everyone!
Following code may help you in loading the assembly and invoke method.
Assembly asmbly = Assembly.LoadFile("assembly.test.dll");
var myclass = asmbly.GetType("MyClass"); // use FullName i.e. Namespace.Classname
var myobj = Activator.CreateInstance(myclass);
myclass.GetMethod("MyMethod").Invoke(myobj,new object[]{"param1","param2"});

File not found error when reflecting assembly types

Iterating through a directory for *.dll files, find them and create an Assembly reference for each file.
Once I have a reflected object, I iterate through all the types available in each, from which I'd like to get the custom attributes for each type in the collection:
string[] files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory + "Methods", "*.dll");
foreach (string s in files)
{
Assembly asm = Assembly.LoadFile(s);
Type[] asmTypes = asm.GetTypes();
bool isCorrect = false;
foreach (Type type in asmTypes)
{
1. var customAttribs = type.GetCustomAttributes(typeof(BaseModelAttribute), false);
}
}
[Update] : exception raised at line # 1
This code works all the way up to the foreach...loop when I get an exception saying that the file could not be found, which is weird as I created an Assembly reference from the file higher up in the code block (not mentioned in code).
[Update]: Erno was correct in in assuming a reference could not be established. Base, for some reason needs to be defined outside of the reference pool (being in the bin directory) even though it's not actually needed by the application. Does not makes sense to me, but it works. Thanks.
When .NET is not able to find a file it probably is trying to load an assembly that the currently reflected assembly is dependent on.
You can use Fuslogvw.exe (SDK) to find out what assembly is being searched for.

Categories

Resources