Access denied when attempting to open copied word doc with OpenXML - c#

I'm attempting to automate a mail merge process using c#, a DataSet, and OpenXML. I have a complete working example when running locally. When publishing to our webserver however, I'm getting an Access Denied error despite even going so far as to grant Full Control everywhere.
Here is the code leading up to the error message:
try
{
var strTemplateTestFile = strMergeBuildingLocation.Replace(".docx", "_Test.docx");
// Don't continue if the template file name is not found
if (!File.Exists(strTemplateFileName))
throw new Exception("TemplateFileName (" + strTemplateFileName + ") does not exist");
foreach (var dr in dsData.Tables[0].Rows)
{
string strFileName;
if (doesDestinationExist(strMergeBuildingLocation))
{
File.Copy(strTemplateFileName, strTemplateTestFile, true);
strFileName = strTemplateTestFile;
}
else
{
File.Copy(strTemplateFileName, strMergeBuildingLocation, true);
strFileName = strMergeBuildingLocation;
}
var pkg = Package.Open(strFileName, FileMode.Open, FileAccess.ReadWrite);
using (var docGenerated = WordprocessingDocument.Open(pkg))
The problem falls within the last line upon attempting to open docGenerated.
The error message I'm receiving is:
Access to the path 'docx path' is denied.
The file copies as expected and is able to be opened and modified manually. There's nothing within the folders that would be restricting access to the file. Does anyone have any thoughts as to what the issue could be?

I think the problem is here in this line.
var pkg = Package.Open(strFileName, FileMode.Open, FileAccess.ReadWrite);
using (var docGenerated = WordprocessingDocument.Open(pkg))
Here you are trying to open the document twice.
Try this,
/* Open WordProcessing document package based on filename */
//------------------------------------------------------------------------------------------------Start
public static WordprocessingDocument OpenPackage(WordprocessingDocument package, string inputFileName, bool editable)
{
bool copied = false;
while (!copied)
{
try
{
package = WordprocessingDocument.Open(inputFileName, editable);
copied = true;
}
catch (Exception e)
{
if (e is FileFormatException)
{
package = null;
break;
}
if (e is IOException)
{
copied = false;
}
if (e is ZipException)
{
package = null;
break;
}
}
}
return package;
}
This will give you the WordprocessingDocument package if it exists and available. If file does not exists, null will be returned.If file locked, will open package when file released.
Hope this helps.! Thank you!

Related

How can I debug my add-in when WordEditor's Content property is crashing Outlook?

I have folder full of *.msg files saved from Outlook and I'm trying to convert them to Word.
There is a loop that loads each *.msg as MailItem and saves them.
public static ConversionResult ConvertEmailsToWord(this Outlook.Application app, string source, string target)
{
var word = new Word.Application();
var emailCounter = 0;
var otherCounter = 0;
var directoryTree = new PhysicalDirectoryTree();
foreach (var node in directoryTree.Walk(source))
{
foreach (var fileName in node.FileNames)
{
var currentFile = Path.Combine(node.DirectoryName, fileName);
var branch = Regex.Replace(node.DirectoryName, $"^{Regex.Escape(source)}", string.Empty).Trim('\\');
Debug.Print($"Processing file: {currentFile}");
// This is an email. Convert it to Word.
if (Regex.IsMatch(fileName, #"\.msg$"))
{
if (app.Session.OpenSharedItem(currentFile) is MailItem item)
{
if (item.SaveAs(word, Path.Combine(target, branch), fileName))
{
emailCounter++;
}
item.Close(SaveMode: OlInspectorClose.olDiscard);
}
}
// This is some other file. Copy it as is.
else
{
Directory.CreateDirectory(Path.Combine(target, branch));
File.Copy(currentFile, Path.Combine(target, branch, fileName), true);
otherCounter++;
}
}
}
word.Quit(SaveChanges: false);
return new ConversionResult
{
EmailCount = emailCounter,
OtherCount = otherCounter
};
}
The save method looks likes this:
public static bool SaveAs(this MailItem mail, Word.Application word, string path, string name)
{
Directory.CreateDirectory(path);
name = Path.Combine(path, $"{Path.GetFileNameWithoutExtension(name)}.docx");
if (File.Exists(name))
{
return false;
}
var copy = mail.GetInspector.WordEditor as Word.Document;
copy.Content.Copy();
var doc = word.Documents.Add();
doc.Content.Paste();
doc.SaveAs2(FileName: name);
doc.Close();
return true;
}
It works for most *.msg files but there are some that crash Outlook when I call copy.Content on a Word.Document.
I know you cannot tell me what is wrong with it (or maybe you do?) so I'd like to findit out by myself but the problem is that I am not able to catch the exception. Since a simple try\catch didn't work I tried it with AppDomain.CurrentDomain.UnhandledException this this didn't catch it either.
Are there any other ways to debug it?
The mail that doesn't let me get its content inside a loop doesn't cause any troubles when I open it in a new Outlook window and save it with the same method.
It makes sense to add some delays between Word calls. IO operations takes some time to finish. Also there is no need to create another document in Word for copying the content:
var copy = mail.GetInspector.WordEditor as Word.Document;
copy.Content.Copy();
var doc = word.Documents.Add();
doc.Content.Paste();
doc.SaveAs2(FileName: name);
doc.Close();
Instead, do the required modifications on the original document instance and then save it to the disk. The original mail item will remain unchanged until you call the Save method from the Outlook object model. You may call the Close method passing the olDiscard which discards any changes to the document.
Also consider using the Open XML SDK if you deal with open XML documents only, see Welcome to the Open XML SDK 2.5 for Office for more information.
Do you actually need to use Inspector.WordEditor? You can save the message in a format supported by Word (such as MHTML) using OOM alone by calling MailItem.Save(..., olMHTML) and open the file in Word programmatically to save it in the DOCX format.

Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory

We have code to upload a zip file, at the start of the process if the zip file contains __MACOSX folder in the zip it will strip these folders/files from the zip and pass the file bytes on.
The issue we're having is that further down the chain if we create a new ZipArchive using fileBytes we get the error:
Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
This only happens when the zip doesn't contain any __MAXOSX folders to be removed. Commenting out the below code prevents this error ever occuring.
// Strip MacOSX folder if zip
var fileBytes = uploadController.FileBytes;
if (uploadController.FileName.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase))
{
try
{
using (var data = new MemoryStream(fileBytes))
{
var changed = false;
using (var archive = new ZipArchive(data, ZipArchiveMode.Update))
{
while (true)
{
var osx = archive.Entries.FirstOrDefault(
c => c.FullName.StartsWith("__MACOSX/", StringComparison.CurrentCultureIgnoreCase)
);
if (osx == null) break;
osx.Delete();
changed = true;
}
}
// Archive is updated once disposed
if (changed)
{
fileBytes = data.ToArray();
}
}
}
catch (Exception e)
{
return new ObjectReturnMethodResult<UploadedFileV2>("Uploaded zip appears to be invalid." + e.Message);
}
}
When running the above code on a zip that doesn't contain __MACOSX why might it create this error?
Changing:
using (var data = new MemoryStream(fileBytes))
To:
using (var data = new MemoryStream(fileBytes.ToArray()))
Seems to fix this, although not sure why!

Directory.Delete(path,true) the file is being used by another process

I have a function that should check a directory's files, and if it finds a file with a specific name it should delete the directory (kinda like a cleaning job)
public static void consumeFolderFiles(string path)
{
// get directory info
DirectoryInfo info = new DirectoryInfo(path);
// I want the files to be in order because I am planning to send them via FTP in order in the future.
FileInfo[] files = info.GetFiles().OrderBy(p => p.CreationTime).ToArray();
bool success = false;
if (files.Length > 0)
{
// FTP logic (not implemented yet)
// Check if there's a file's name that contains "balance"
success = files.FirstOrDefault(f => f.Name.ToLower().Contains("balance")) != null;
// Check if it's time to delete the directory
if (success)
{
Directory.Delete(path, true);
}
}
else
{
// Custom exception
throw new NoNewTransactionsInFolderException();
}
}
This function seems to be working, but occasionally I get the exception "the process cannot access the file ****.*** because it's being used by another process".Also, I noticed that all the files before the exception got deleted normally.
The function throws the exception inconsistently. Which means it might delete the folder or stop somewhere in the middle. Thus, I don't think I forgot closing a resource or a handler.
I think it's a good idea to include the file creating function. I might missed something there.
public static void createXMLFile(object o, string path, string fileName)
{
XmlSerializer xml = new XmlSerializer(o.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlTextWriter xtw = new XmlTextWriter(Path.Combine(path, fileName), Encoding.UTF8);
xtw.Formatting = Formatting.Indented;
xml.Serialize(xtw, o, ns);
xtw.Close();
xtw.Dispose();
}
I have seen other answers where people suggested adding a try catch and call Directory.Delete(path,true) in the catch clause, or using .NET Transactional File Manager. However, I would like to know the reason of this exception. Is it something I am doing wrong or an OS bug?

C# error saving Excel file

I'm pretty new to C# and I'm experimenting a lot, I'm trying to make my program a little more user friendly and that is where the problem starts.
At first the location of the excelfile was in a public static string and I had no problems. I've changed it to this:
public string Excellocation()
{
string xlLocation;
if (but_Browse.Text == "Zoek Excel")
{
xlLocation = #"E:\Levi\Documents\Verjaardagen.xlsx";
}
else //Only if I get into this part of my code I get the error
{
xlLocation = but_Browse.Text;
}
return xlLocation;
}
And the button I use so the user can give me a location for the excel file is:
private void but_Browse_Click(object sender, EventArgs e)
{
var FD = new System.Windows.Forms.OpenFileDialog();
if (FD.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
string fileToOpen = FD.FileName;
System.IO.FileInfo File = new System.IO.FileInfo(FD.FileName);
//OR
System.IO.StreamReader reader = new System.IO.StreamReader(fileToOpen);
//etc
but_Browse.Text = fileToOpen;
this.but_Browse.AutoSize = true;
But_Import.Visible = true;
}
}
Reading the Excel-file is no problem, my program finds it and processes it, if and only if the user changed the location by using the "Browse button" I get a message from Windows that there is already an excel file with that name and if I want to replace it, If I click away that message, my code gives an error on the line that tries to save the excel file
xlWorkbook.Save();
xlWorkbook.Close(true);
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
xlWorkbook.Save() gives me this error:
System.Runtime.InteropServices.COMException occurred
HResult=0x800A03EC Message=Verjaardagen.xlsx can not be saved,
because it's read-only.
I have no idea why I don't get an error with the default location while I do get an error if use my button to give me that same location.
Does anyone know what i'm doing wrong?
Thanks in advance
So the problem is that the file is read only when you try to write to it after going through but_Browse_Click? Are you closing the StreamReader? Try using
reader.close();
in but_Browse_Click.
Perhaps a better way would be:
using (StreamReader reader = new StreamReader(fileToOpen))
{
//all code involving the reader in here
}
This automatically closes on completion.

Upload form not working - File is being used by another process

I've got a C# file upload that is meant to extract XML tags from inside a DocX document, the problem I'm facing is that when the file is uploaded, the error "File is being used by another process" comes up. Attempting to delete the document shows it is being used by IIS process manager.
Is there a way to stop my code to get it to continue running?
<script runat="server">
//foreach (DataRow row in table.Rows)
//{
// string dbColumnNames = (selectedData.ToString());
//send files
//}
public string _TempFileLocation = ""; //Used to locate Word Document File Path
//THE USER UPLOAD CONTROL. users use this to upload the document to the server
public void XMLextractor(string _filePath)
{
//XML extraction code
displayFilepath.Text = _filePath;
_TempFileLocation = _filePath;
}
//names the script manager which will be used when the user attempts to upload a form / gives an error if they incorrectly attempt to upload
protected void UploadButton_Click(object sender, EventArgs e)
{
//if file is located
if (FileUploadControl.HasFile)
{
try
{
//allow content type of document / docx
if (FileUploadControl.PostedFile.ContentType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
{
if (FileUploadControl.PostedFile.ContentLength < 10485760) // 10mb)
{
//name the filename, find the path of the name
string filename = Path.GetFileName(FileUploadControl.FileName);
//path of server upload (we just need to save it as a variable to be found on the next page, as it will be made / deleted
FileUploadControl.SaveAs(Server.MapPath("~/") + filename);
//update the label with file uploaded
StatusLabel.Text = "Upload status: File uploaded!";
XMLextractor(Server.MapPath("~/") + filename);
//move onto template wizard page
//Response.Redirect("http://portal.acoura.com/admin/templatewizard.aspx", false);
WordprocessingDocument _TempDoc = WordprocessingDocument.Open(Server.MapPath("~/") + filename, true);
XDocument xdoc = XDocument.Load(Server.MapPath("~/") + filename);
//query to find particular descendants
var lv1s = from document in xdoc.Descendants("table")
select new
{
Header = document.Attribute("name").Value,
Children = document.Descendants("tag")
};
//Loop through results
StringBuilder result = new StringBuilder();
foreach (var lv1 in lv1s)
{
result.AppendLine(lv1.Header);
foreach (var lv2 in lv1.Children)
result.AppendLine(" " + lv2.Attribute("name").Value);
}
//the label should contain the content controls of the document, using the class, XMLfromDocument
labelContentControls.Text = fileUpload_Displayx(XMLfromDocument.GetContentControls(_TempDoc));
}
else
//display the size the file needs to be less than
StatusLabel.Text = "Upload status: The file has to be less than 10mb!";
}
else
//tell the user only docx files are accepted
StatusLabel.Text = "Upload status: Only DOCX files are accepted!";
}
catch (Exception ex)
{
//display the exception message, in which case it would be either size / type / if it's present
StatusLabel.Text = "Upload status: The file could not be uploaded. The following error occured: " + ex.Message;
}
}
}
//needs to be replaced with the variable found in descendants / var tagContent
public string fileUpload_Displayx(XElement _contentcontrol)
{
string str = "";
str = _contentcontrol.Name.ToString();
return str;
}
//public static displayDatabase(object sender, EventArgs e)
// {
//}
//run the validate button on templatewizard, will mark up any problems or give green light
//if red, allow users to replace fields in the left column, from ones in the atabase on the right
//display upload button when validation is succesful. When Upload button runs, Take to new
// / existing page of reports, allow users to download this
</script>
You are opening the file without closing it on this line:
WordprocessingDocument _TempDoc = WordprocessingDocument.Open(Server.MapPath("~/") + filename, true);
Then you are opening it again with xDocument.Load():
XDocument xdoc = XDocument.Load(Server.MapPath("~/") + filename);
I assume that is where the error occurs.
If you handle all of the stuff XDocument needs to do first, and then open and close the WordProcessingDocument.Open() line to get the content controls, you should be fine.
Basically only one process can have a open and read or modify a file at a time, so if two operations from two different sources need to be performed, they must be performed sequentially on the file.
You can also open the file via a FileStream and then load the content into memory and into your XDocument, therefore negating the need to have the file opened twice by XDocument and WordProcessingDocument simultaneously.
Hope this helps!

Categories

Resources