Programmatically Load Embedded Resource File - c#

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" ?

Ok folks, so this is what i ended up with, which actually fixed all my problems.
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]);
}
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.

Related

IOException thrown from Image class or byte array

I'm extracting a ZIP file. This ZIP contains image files and an Excel file with a product list. When articles of different sizes are listed the article refers to the same image. I copy the image file to a local folder and write the (compressed) binary data to SQL server database.
So when it gets to the point where a JPG file shall be processed a second time, I get this exception, although I dispose the image object.
Worksheet ws;
string root = "C:\\images\\";
string file;
string importFolder = "C:\\import\\;
Dictionary <string, object> ins;
Image im;
Image th;
//Worksheet has been opened before
//ZIP has been extracted before to C:\import\
for (i = 2; i <= ws.Dimension.End.Row; i++) {
ins = new Dictionary<string, object>(); //Dictionary to write data to database
file = ws.Cells[i, 4].Text;
System.IO.File.Copy(importFolder + "\\" + file, root + "\\" + file, true); // <-- Here the exception is thrown in the second iteration
im = Image.FromFile(root + "\\" + file);
im = im.GetBetterThumbnail(1024);
byte[] im_data = im.GetJpgByteArray(85);
ins.Add("url", "www.test.de/images/" + file);
ins.Add("image_data", im_data);
ins.Add("image_size", im_data.Length);
//image will be written to database
im.Dispose();
im = null;
im_data = null;
//With these initializations there shouldn't be thrown an exception
} // end for
What am I missing? With resetting the Image object and byte array, there shouldn't be another reference to the image file.
I had a look on this
IOException: The process cannot access the file 'file path' because it is being used by another process
but I couldn't figure out, how to adept to my topic.
Yes, I could store all file names just to copy them once, but I think that's the lazy way.
Kind regards
You assign a value to the variable im two times.
One time you use im = Image.FromFile(root + "\\" + file) and the other time you use im = im.GetBetterThumbnail(1024). Could it be that this opens two handles that need to be closed?
Besides, it's better to use the using statement. Then you don't have to take care of the disposing by yourself.
For example like this:
for (i = 2; i <= ws.Dimension.End.Row; i++)
{
ins = new Dictionary<string, object>(); //Dictionary to write data to database
file = ws.Cells[i, 4].Text;
System.IO.File.Copy(importFolder + "\\" + file, root + "\\" + file, true);
using (im = Image.FromFile(root + "\\" + file))
{
// I guess that this method creates its own handle
// and therefore also needs to be disposed.
using (thumbnail = im.GetBetterThumbnail(1024))
{
byte[] im_data = thumbnail.GetJpgByteArray(85);
ins.Add("url", "www.test.de/images/" + file);
ins.Add("image_data", im_data);
ins.Add("image_size", im_data.Length);
//image will be written to database
}
}
} // end for
I got the issue solved by using a stream. The memory management works really better now.
New code:
//im = Image.FromFile(root + "\\" + file);
im = Image.FromStream(File.Open(root + "\\" + file, FileMode.Open));
So could it be that this is another 'Microsoft feature'?

Attach file to Winform and copy the file to local while running exe c#

Is it possible to attach a text file resource to my Winform exe. So when I run the "Form.exe" in another computer then it copy the text file to a specified folder. Please suggest a method to achieve the same. Thanks
If the resource name is a string:
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream(resourceName))
using (var reader = new StreamReader(stream))
{
string text = reader.ReadToEnd();
File.WriteAllText(fileName, text);
}
else:
File.WriteAllText(fileName, Properties.Resources.TextFile1);
And also make sure that you have set the Build Action of the resource file to "Embedded Resource".
First you need to add your file as a resource in your project.
This explains what to do
Then select your file and in the properties change the "Build Action" to "Embedded Resource". This will now embed your file in your output (.exe).
To extract the file you need to do the following;
String myProject = "Name of your project";
String file = "Name of your file to extract";
String outputPath = #"c:\path\to\your\output";
using (System.IO.Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(myProject + ".Resources." + file))
{
using (System.IO.FileStream fileStream = new System.IO.FileStream(outputPath + "\\" + file, System.IO.FileMode.Create))
{
for (int i = 0; i < stream.Length; i++)
{
fileStream.WriteByte((byte)stream.ReadByte());
}
fileStream.Close();
}
}
Ideally you should check that the file does not already exist before you do this. Don't forget also to catch exceptions. Which can be very common when dealing with the file system.
Add the text file to your project resources
Properties -> Resources -> Add Resource
Read the data from the resource using
var text = Properties.Resources.textFile;
Write to the file with
File.WriteAllText(#"C:\test\testOut.txt", text);

Extract embedded resources in C#

I have four resource files embedded in my C# executable, 1 python script and 3 perl scripts. I could extract all three perl scripts successfully. But I am not able to extract the python script. I tried so many ways. Could someone please have a look ? Thank you.
public static string ExtractResource(string resourceName)
{
string destFile = "";
//look for the resource name
foreach (string currentResource in System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames() )
if (currentResource.LastIndexOf(resourceName) != -1)
{
string subPath = Common_Utilities.GetTempPath() + "SCRIPTS";
bool isExists = System.IO.Directory.Exists(subPath);
if (!isExists)
System.IO.Directory.CreateDirectory(subPath);
string strFile = subPath + "\\" + resourceName;
string path = System.IO.Path.GetDirectoryName(strFile);
string rootName = System.IO.Path.GetFileNameWithoutExtension(strFile);
destFile = path + #"\" + rootName + System.IO.Path.GetExtension(currentResource);
System.IO.Stream fs = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream( currentResource ) ;
byte[] buff = new byte[fs.Length];
fs.Read(buff, 0, (int)fs.Length);
fs.Close();
System.IO.FileStream destStream = new System.IO.FileStream(destFile, FileMode.Create);
destStream.Write(buff, 0, buff.Length);
destStream.Close();
}
return destFile;
// throw new Exception("Resource not found : " + resourceName);
}
Not sure why the Python script can't be extracted.... couple points though:
I would recommend to use Path.Combine() to stick together path and file name - don't do this yourself, too many chances for error!
Since those are (text-based) scripts, you could do the whole copying much simpler:
System.IO.Stream fs = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(currentResource);
string scriptContents = new StreamReader(fs).ReadToEnd();
File.WriteAllText(destFile, scriptContents);
With this approach, you should be easily able to see in debugging whether or not the script is properly loaded from resources. If not - check your resource name etc. (is the script really set to "embedded" resource?). If you have subdirectories for your resources, be aware that the resource name will contain those subdirectories as part of the fully qualified name - but separated by a dot (.), not a backslash like a physical path!
easy way you can mod your code with
Binary files -> File.WriteAllBytes(Path, Properties.Resources.filename);
Text files -> File.WriteAllText(Path, Properties.Resources.filename);

Write from Resource to file where resource could be text or image

I'm having a problem trying to write my resource files to disk (all resource files part of the same project and assembly).
If I add
var temp = Assembly.GetExecutingAssembly().GetManifestResourceNames();
This returns a string[] in the following format
Gener.OptionsDialogForm.resources
Gener.ProgressDialog.resources
Gener.Properties.Resources.resources
Gener.g.resources
Gener.Resources.reusable.css
Gener.Resources.other.jpg
The last 2 of the array are the only 2 files I want but I assume it's not a guarantee that this will always be the case. The array could come through in another order as code is changed so I cannot explicity call the item at a given index (temp[4])
So, I could do
foreach (string item in Assembly
.GetExecutingAssembly()
.GetManifestResourceNames())
{
if (!item.Contains("Gener.Resources."))
continue;
//Do whatever I need to do
}
But this is just horrible! I face another problem with this approach; This doesn't return the file name with the extension, just the Name and as such, I have no idea what the extension is.
This is the code as it currently is
public void CopyAllFiles()
{
var files = Resources.ResourceManager.GetResourceSet(System.Globalization.CultureInfo.CurrentUICulture, true, true);
//var temp = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (DictionaryEntry item in files)
{
using (var resourceFileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Gener.Resources." + item.Key.ToString() + ".css")) // this won't work, I can't hard code .css as the extension could be different
{
Stream stream = new FileStream(this.DirPath, FileMode.Create, FileAccess.Write);
resourceFileStream.CopyTo(stream);
stream.Dispose();
}
}
files.Dispose();
}
But this seems... wrong... Is this how any one else would do this, I'm sure I'm missing something and such a task is common that there is a better solution?
The resource names are predictable, you could just pass the name to the Assembly.GetManifestResourceStream() method.
More productively, Visual Studio supports a designer for this so you don't have to guess at the string you need to pass. Use Project + Properties, Resources tab. Click on the dropdown arrow of the Add Resource button and select your file. You can now refer to the resource in your code with a variable name. Like:
File.WriteAllText(path, Properties.Resources.reusable);
Do consider the so-so wisdom of copying resources to files at runtime. You get the exact same outcome by just using an installer or XCopy to copy the files just once. With the significant advantage is that those resources won't eat memory address space anymore and that you won't get in trouble when you don't have write access to the directory. Which is common with UAC enabled.
This was what I used! Hopefully it will help others. It feels some what hacking, but it works!
/// <summary>
/// Copies all the files from the Resource Manifest to the relevant folders.
/// </summary>
internal void CopyAllFiles()
{
var resourceFiles = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (var item in resourceFiles)
{
string basePath = Resources.ResourceManager.BaseName.Replace("Properties.", "");
if (!item.Contains(basePath))
continue;
var destination = this._rootFolder + "\\" + this._javaScriptFolder + "\\" + item.Replace(basePath + ".", "");
using (Stream resouceFile = Assembly.GetExecutingAssembly().GetManifestResourceStream(item))
using (Stream output = File.Create(destination))
{
resouceFile.CopyTo(output);
}
}
}

Use Image without adding it in WPF Project/Solution Resources

I am building a Gallery for Floors in WPF application, I am reading Image Names from csv file and User will directly copy Images into Resources folder.
StreamReader streamReader = new StreamReader(
(Application.Current as PlayOnSurface.App).ProjectAmenitiesCSVPath);
// browse the csv file line by line until the end of the file
while (!streamReader.EndOfStream)
{
// for each line, split it with the split caractere (that may no be ';')
var splitLine = streamReader.ReadLine().Split('~');
if (int.Parse(splitLine[0].Trim())
== (Application.Current as PlayOnSurface.App).SelectedFloorID)
{
for (int i = 2; i < splitLine.Length; i++)
{
ImageList.Add(splitLine[i].Trim());
}
}
// map the splitted line with an entity
}
streamReader.Close();
btnPrev.IsEnabled = false;
if (ImageList.Count <= 1)
btnNext.IsEnabled = false;
imgGallery.Source = Utilities.LoadBitmapFromResource(
"Resources/Amenities/" + ImageList[0],
null);
Utilities code
public static BitmapImage LoadBitmapFromResource(string pathInApplication, Assembly assembly = null)
{
if (assembly == null)
{
assembly = Assembly.GetCallingAssembly();
}
if (pathInApplication[0] == '/')
{
pathInApplication = pathInApplication.Substring(1);
}
return new BitmapImage(new Uri(#"pack://application:,,,/" + assembly.GetName().Name + ";component/" + pathInApplication, UriKind.Absolute));
}
I can't add it in Resources folder in project and I can't embed resources because I want user to copy their file in Resources folder and update csv file after deployment.
My code will read it and display gallery but in that case WPF throws "Cannot locate resource" exception when loading the image on imgGallery.Source line in above code.
my csv file format:
1~FirstFloor~img1.png~img2.png~img3.png
2~SecondFloor~img1.png~img2.png~img3.png
3~ThirdFloor~img1.png~img2.png~img3.png
4~FourthFloor~img1.png~img2.png~img3.png
Maybe use the file absolute path, simply change the last line of the LoadBitmapFromResource function to:
return new BitmapImage(new Uri(assembly.GetName().CodeBase + pathInApplication, UriKind.Absolute));
this should do the fix.
Also you can try using the UriKind.Relative.
Good day
You're using an incorrect authority in your URI scheme. The application authority is to be used for data files which are known at compile time (aka static resources). You have to replace it with the siteoforigin authority.
I'd also recommend replacing your home-rolled text file format with a standard CSV file. Many programs such as Excel support generating comma-separated fields by default. While custom separators can be put, they usually require additional effort. Reading and parsing comma-separated files is also easier because many libraries are available to do these tasks.

Categories