Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically Load Embedded Resource File

i recently implemented the following code in order to programatically build a project/exe. In this exe build, i wanted to store a bunch of "actual files" inside of resources, as streams.

Here's how i'm adding the files into the resource file (singular), and then embedding that resource file into the compiler parameters:

        List<string> UserFiles = new List<string>();
        UserFiles.AddRange(Helpers.GetFilesInFolder(this.txt_Publish_Folder.Text));

        string folder = this.txt_Package_Location.Text;
        folder = folder + "\\Package_" + DateTime.Now.ToLongTimeString().Replace(":", "_").Replace(".", "_");
        Directory.CreateDirectory(folder);

        CompilerParameters parameters = new CompilerParameters();
        parameters.GenerateExecutable = true;
        parameters.IncludeDebugInformation = true;
        parameters.GenerateInMemory = false;
        parameters.WarningLevel = 3;
        parameters.CompilerOptions = "/optimize";
        parameters.OutputAssembly = folder + "\\Install_" + this.txt_AppName.Text + ".exe";
        parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
        parameters.ReferencedAssemblies.Add("System.dll");
        parameters.ReferencedAssemblies.Add("System.Core.dll");
        parameters.ReferencedAssemblies.Add("System.Data.dll");
        parameters.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
        parameters.ReferencedAssemblies.Add("System.Xml.dll");
        parameters.ReferencedAssemblies.Add("System.Xml.Linq.dll");

        CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
        if (codeProvider.Supports(GeneratorSupport.Resources))
        {
            string temp = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
            //create temp file first, because we want to append to it so as to have a single resource file with multiple stream entries...
            File.WriteAllText(temp, null);
            for (int i = 0; i < UserFiles.Count; i++ )
            {
                byte[] FileBytes = File.ReadAllBytes(UserFiles[i]);

                using (FileStream stream = new FileStream(temp, FileMode.Append))
                {
                    using (ResourceWriter writer = new ResourceWriter(stream))
                    {
                        writer.AddResource(Path.GetFileName(UserFiles[i]), FileBytes);
                    }
                }
            }
            parameters.EmbeddedResources.Add(temp);
        }

        CompilerResults res = codeProvider.CompileAssemblyFromFile(parameters, @"C:\HIDDENPATH\Program.cs");

This works great, doesn't yield any errors, and i can actually get the embedded resource file out in the Console Application i built (the one referenced above as Prorgam.cs) through the code below. The problem i am having however is with "Loading" this resource file into the application assembly / getting its values out somehow... here's the code i have so far to do that:

    static void Main(string[] args)
    {
        Console.WriteLine("Checking for resources... please wait...");

        Assembly thisExe;
        thisExe = Assembly.GetExecutingAssembly();
        List<string> resources = thisExe.GetManifestResourceNames().ToList();

        if(resources.Count >= 1)
        {
            try
            {
                string baseName = resources[0];
                ResourceManager mgr = new ResourceManager(baseName, thisExe);
                Console.WriteLine("retrieved manager...");
                Console.ReadLine();
                ResourceSet set = mgr.GetResourceSet(Thread.CurrentThread.CurrentCulture, true, true);
                int count = set.Cast<object>().Count();
                Console.WriteLine("Found [" + count.ToString() + "] embedded resources. Would you like to enumerate them?");
                ConsoleKeyInfo input = Console.ReadKey();
                if (input.Key == ConsoleKey.Y)
                {
                    // Build the string of resources.
                    foreach (string resource in resources)
                        Console.WriteLine(resource);
                }

            }
            catch (Exception ex)
            {
                Console.Write(ex.Message);
                Console.ReadLine();
            }
        }

        Console.ReadLine();
    }

Whenever i run the built exe, i get the following result:


Checking for resources... please wait...
retrieved manager...

Could not find any resources appropriate for the specified culture or the neutral culture.  Make sure "tmpCC59.tmp.resources" was correctly embedded or linked into assembly "Install_testing" at compile time, or that all the satellite assemblies required are loadable and fully signed.

Can someone please tell me why this is happenning? i've tried all different culture sets i could think of and still nothing comes out. Is the problem related to how i'm "Embedding" or how i'm "Loading" ?

like image 966
MaxOvrdrv Avatar asked May 29 '15 17:05

MaxOvrdrv


Video Answer


1 Answers

Ok folks, so this is what i ended up with, which actually fixed all my problems.

  1. Instead of trying to add "Resource Entries" into a single "Resource File", i'm now adding every file as its own resource, directly from the source file (don't know why i thought making a temp copy of the original and using a stream was necessary but it's not) like this:

            for (int i = 0; i < WebAppFiles.Count; i++)
            {
                parameters.EmbeddedResources.Add(WebAppFiles[i]);
            }
    
  2. Then, when comes time to extract the actual files, instead of trying to load a "set" from a single "resource file", i simply extract the data from each embedded resource, like this:

                    for (int i = 0; i < resources.Count; i++)
                    {
                        Console.WriteLine("Extracting file: " + resources[i] + "...");
                        Stream stream = thisExe.GetManifestResourceStream(resources[i]);
                        byte[] bytes = new byte[(int)stream.Length];
                        stream.Read(bytes, 0, bytes.Length);
                        File.WriteAllBytes(dir + resources[i], bytes);
                    }
    

This new way basically means that you tell the compiler that "these files are necessary for the exe and are embedded", so when you call the "build" command, it actually does all the work for you and reads the files in as byte arrays and embeds those into the executable file.

Then, at the other hand, you simply tell the program that you want to save the embedded resources as files, using their own file names.

Hopefully this helps someone else who is trying to do this... as every other post for this type of question had some over complicated answers that were either incomplete (e.g.: how to embed, but not retrieve) or didn't actually work.

like image 84
MaxOvrdrv Avatar answered Sep 17 '22 15:09

MaxOvrdrv