Unable to get local copy using IPersistFile.Save(path, false) in Excel - c#

I implement a vsto plugin for Excel. I need to get local path of the Excel document from Teams channel but Application.ActiveWorkbook.FullName contains url.
I should be able to use IPersistFile.Save(path, false) and create local copy of the document without changing current working document. Unfortunately, it doesn't work in Excel. It creates a local copy, but current working document is replaced with local copy. If user do some changes to the document then Excel won't synchronize it with Teams/Sharepoint.
Is it a bug in API or I need to do something else to fix it?
Is there a different way to get a copy of the file without changing current working document?
I'm able to reproduce issue if I open Excel document from Teams channel and use vsto add-in with below code. Current working document is replaced with local copy but it shouldn't. I cannot get local copy.
this.Application.WorkbookOpen += ( Excel.Workbook wb ) =>
{
if ( !Uri.TryCreate( Application.ActiveWorkbook.FullName, UriKind.Absolute, out Uri uri ) || uri.Scheme == "file" )
{
Debug.WriteLine( $"It's local file: {Application.ActiveWorkbook.FullName}" );
return;
}
Debug.WriteLine( $"It's online file: {Application.ActiveWorkbook.FullName}" );
var fileName = uri.LocalPath.Split( '/' ).Last();
var extension = fileName.Split( '.' ).Last();
var fileNameWithoutExtension = fileName.Substring( 0, fileName.Length - extension.Length - 1 );
var path = Path.Combine( Path.GetTempPath(), $"{fileNameWithoutExtension}_{Guid.NewGuid()}.{extension}" );
if ( wb is IPersistFile persistFile )
{
persistFile.Save( path, false );
//current working document has been replaces with {path}
Debug.WriteLine( $"Local copy has been created: {path}" );
}
else
{
Debug.WriteLine( $"Unable to create local copy" );
}
};

Related

Excel File created as read only

I have created an Excel Add-in using VSTO in C#. I want to create a new Excel file from a template located on a SharePoint site. A file picker dialog box is used so the user can chose which file to use as a template. Everything works fine exept the new workbook is created as read only. If I copy the template file on my computer, and use the same program to pick that file as a template, the new workbook is not created as read only.
The file on the sharePoint is not being open elseware when creathing the new file. Is there a way to specify the sharePoint library is a safe source? Or to set the newly created workbook readonly property to false?
{
string pathSP= #”\\Business.sharepoint.com#SSL\teams\group\NDC\”;
if (System.IO.Directory.Exists(pathSP))
{
Excel.Application excelObj = Globals.ThisAddIn.Application;
Office.FileDialog fileDialog = excelObj.FileDialog[Office.MsoFileDialogType.msoFileDialogFilePicker];
fileDialog.InitialFileName = initialPath;
fileDialog.AllowMultiSelect = false;
fileDialog.InitialView = Office.MsoFileDialogView.msoFileDialogViewDetails;
fileDialog.Title = "Create Excel file from template";
fileDialog.Filters.Clear();
fileDialog.Filters.Add("Excel template", "*.xls; *.xlsx; *.xlsm; *.xltx; *.xltm; *.xlt", 1);
string TemplatePath;
if (fileDialog.Show() == -1)
{
templatePath = fileDialog.SelectedItems.Item(1);
fileDialog = null;
}
else
{
templatePath=""
fileDialog = null;
}
if (templatePath != "")
{
Excel.Workbook ws= excelObj.Workbooks.Add(templatePath);
}
}
else
{
MessageBox.Show("SharePoint site is not available", "Create Excel file from template", MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
}
}
for the two ways you mentioned:
set the share point library as a safe source - this can be achieved by setting the excel setting: Options -> Trust Centre -> Trust Centre Settings -> Trusted Locations -> add the sharepont location
set the file access (readonly=false) at the end of your process
Actually as a general practice when dealing with template file (not limited to excel), the system should copy the template file to a local temp location (ideally the running user's TEMP folder), use it and then delete it afterwards. this way would avoid all kinds of problems such as file-lock-by-other-user/process problems.
So I think your problem is the Workbooks.Add(). Check out what Microsoft says about it:
[Workbooks.Add()] Determines how the new workbook is created. If this argument is a string specifying the name of an existing Microsoft Excel file, the new workbook is created with the specified file as a template.
I believe the workbook you're creating is a carbon copy of whatever the sharepoint server has, including permissions. The fact it works locally is another clue that sharepoint might be fiddling with permissions.
I suggest try saving a copy of the file locally. You can even use the SaveFileDialog object to give the user a GUI to save their new copy.
SaveFileDialog userSaveFileDialog = new SaveFileDialog();
userSaveFileDialog.Filter = "Excel 2007 and later | *.xlsx, Excel Macro Enabled Worksheet | *.xlsm, I'm guessing this is a 2007 template | *.xltx, Template Macro maybe | *.xltm, I almost definitely think this is a template file | *.xlt";
userSaveFileDialog.Title = "Save an Excel File";
userSaveFileDialog.ShowDialog();
this.Application.ActiveWorkbook.SaveCopyAs(#userSaveFileDialog.FileName);
Once you're done, you can programmatically open the newly saved workbook using userSaveFileDialog.FileName as your filepath.

open specific word doc. depending on user selection c#

i currently have a word doc. file merging program and am attempting to open a specific word doc. depending on user selection.
// sample of code used:
string outputFolder = null;
...
// file selection
...
string outcomeFolder = outputFolder;
string outputFile = "Combined Folder " + fileDate + " # " + fileTime + ".docx";
string outputFileName = Path.Combine(outcomeFolder, outputFile);
in the program, outputFolder is selected by the user via a fileBrowserDialog
currently, the program runs correctly and merges the files in the folder selected by the user however it fails to open Microsoft Word as well as the outcome document merged.
i've attempted to use:
Microsoft.Office.Interop.Word.Application officeApp =
new Microsoft.Office.Interop.Word.Application();
...
// merging code
...
Document documentTest = officeApp.Documents.Open(outputFileName);
i've noticed that though the program fails to launch Word, Task Manager continues to create a new instance of Word. The merged document formed also cannot be deleted as it claims the file is currently in use. It's as if program it's opening in the background however not physically launching. The Task Manager instance of Word must then be killed before the merged file can be edited / deleted
Any suggestions as to remedy this? Am i missing something simplistic or is the issue due to the non-static file path? - if any additional information is required please ask. thank you
Update 1:
Since implementing the officeApp.Visible = true; the program now launches the file created which can then be edited / re-saved etc. However if i immediately run the program again, attempting to create another merged file within the same folder etc. I am presented with "RPC server is unavailable. (Exception from HRESULT: 0x800706BA)"
Update 2:
As listed above, I was getting a generic HRESULT error code which I have now since remedied. I moved the "new officeApp" into the "Merge" handler which seems to be allowing multiple merges in quick succession without throwing errors.
Update 3:
To make things more simplistic, i've experimented with implementing Process.Start(outputFileName); to open the document. This is due to the additional check box I am now introducing which allows the user to decide whether the merged doc. will be launched / presented once created. This new code also prevents the additional Word.exe's from being created if the file visibility is set to false.
thank you all for your suggestions and help.
Have you tried making Word visible?
officeApp.Visible = true;

How to connect to an Excel workbook that lives in SharePoint 2013 and update it

I am creating a command line application and I would like to connect to a SharePoint site that has a workbook that I want to update.
I don't even know where to start but I guess the questions I have are.
What API can I use for this? It has to be something that is already built-in and not a third party API if possible :)
Once the connection is done and I am able to open the spreadsheet, will I be able to also switch from one sheet to another?
I've tried to use some code like this but it did not work.
private static void GetExcelSheetNames(string filename)
{
var xls = new Microsoft.Office.Interop.Excel.Application();
xls.Visible = true;
xls.DisplayAlerts = false;
var workbook = xls.Workbooks.Open(Filename: filename, IgnoreReadOnlyRecommended: true, ReadOnly: false);
try
{
// Refresh the data from data connections
workbook.RefreshAll();
// Wait for the refresh occurs - *wish there was a better way than this.
System.Threading.Thread.Sleep(5000);
// Save the workbook back again
workbook.SaveAs(Filename: filename); // This is when the Exception is thrown
// Close the workbook
workbook.Close(SaveChanges: false);
}
catch (Exception ex)
{
//Exception message is "Cannot save as that name. Document was opened as read-only."
}
finally
{
xls.Application.Quit();
xls = null;
}
}
The Exception that I get was:
Microsoft Excel cannot access the file 'https//url/bla/bla/bla/workbook.xlsx There are several possible reasons:
The file name or path does not exist.
The file is being used by another program.
The workbook you are trying to save has the same name as a currently open workbook.
Actually, it seems like it is trying to open an instance of Excel Desktop in my machine.
Sorry for such an open question but I don't really know how can I connect to a SharePoint site.
Thanks in advance!
If you're using a document in a document library, check to make sure that it is checking out the document before you try to save it. I am working with a similar process but using a report library instead of a document library and am able to save documents without check out/in.

OpenSharedItem for opening .MSG files showing Error in Outlook C#

I am using the following code to open the signed/unsigned
Outlook messages and I display the content in WebBrowser control.
Microsoft.Office.Interop.Outlook.Application app = new Microsoft.Office.Interop.Outlook.Application();
var item = app.Session.OpenSharedItem(msgfile) as Microsoft.Office.Interop.Outlook.MailItem;
string message = item.HTMLBody;
app.Session.Logoff();
It is working fine for the first time the file is opening, but after
closing the Outlook file trying to reopen the file it showing the
following error:
"Cannot open file: C:\tion.msg. The file may not exist, you may not
have permission to open it, or it may be open in another program.
Right-click the folder that contains the file, and then click
Properties to check your permissions for the folder."
After some time later it is opening fine. For this strange behavior
what could be the reason and how to rectify the the error message?
Outlook manages its own cache of items when you are opening and closing messages. Your best bet would be to use a randomly generated filename (i.e. Path.GetRandomFilename) when opening via OpenSharedItem so that you don't get issues. I would also use a temporary path instead of root c:\ (i.e. Path.GetTempPath).
You can try and free the MailItem reference (i.e. setting it to null), but there is no guarantee when Outlook will release the item from its cache.
Would any combination of the Quit[1], Close[2] or ReleaseComObject[3] methods work for you? My code worked better but not perfect after I used them.[4]
using Outlook = Microsoft.Office.Interop.Outlook;
.
.
.
var app = new Outlook.Application();
var item = app.Session.OpenSharedItem(msgfile) as Outlook.MailItem;
//Do stuff with the mail.
item.Close(OlInspectorClose.olDiscard);
app.Quit();
Marshal.ReleaseComObject(item);
Another solution, according to Microsoft - Help and Support[5], is to delay the opening of the file. However, it doesn't sound like a good solution to me since, like #SliverNinja also said, you'll never know when Outlook releases its lock of the file.
Notes and references
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._application.quit.aspx, read 2014-10-14, 16:19.
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._mailitem.close%28v=office.15%29.aspx, read 2014-10-14, 16:19.
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx, read 2014-10-14, 16:19.
For example, if I had opened Outlook for som regular work, the Quit-method would close that window as well.
http://support2.microsoft.com/kb/2633737, read 2014-10-08, 16:19.
Hello you have two options .
set the Read-only attribute to the msg file
or
disable the following permissions for the users or usergroups to the parent folder:
Write Attributes
Write Extended Attributes
the msg file can now open multiple times but is write protect
I had this problem, in my case it was the space in the file name
import win32com.client
import os
path = 'C:/testes/mail'
files = [f for f in os.listdir(path) if '.msg' in f]
for file in files:
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
msg = outlook.OpenSharedItem(os.path.join(path, file))
att=msg.Attachments
for i in att:
i.SaveAsFile(os.path.join('C:/testes/email_download', i.FileName))
I don't know if in your case the OpenSharedItem method can help ...
Firstly, try to release the message as soon as you are done with it using Marshal.ReleaseComObject(). This may or may not help since Outlook likes to cache its last opened item.
Secondly, you are logging off from Outlook while it might still be running and visible to the user - Outlook is a singleton, so you will end up with the existing instance if it was already running. Either don't call Logoff at all, or check that there are no open inspectors and explorers (Application.Explorers.Count == 0 && Application.Inspectors.Count == 0).
Thirdly, reading HTMLBody alone won't work properly if there are embedded images - they are stored as regular attachments. You can save the message as an MHTML file (which most browsers would be happy to show) using MailItem.SaveAs(..., olMHTML).
You can also use Redemption (I am its author) for that - call RDOSession.GetMessageFromMsgFile.
If you need to release the message immediately after you are done, call Marshal.ReleaseComObject()
In case of Redemption, you can also cast RDOMail object to the IDisposable interface and call IDisposable.Dispose(). In addition to the MHTML format, Redemption can also save in the HTML format with image attachments converted into embedded images- use RDOMail.SaveAs(..., olHTMLEmbeddedImages) (olHTMLEmbeddedImages == 1033).

Store metadata outside of file: Any standard approach on modern Windows?

My C# app syncs files from a remote document management system to a filesystem.
The document management system has metadata (date of last audit, secrecy, author...) which is associated with each file but not stored WITHIN each file.
The files can be anything (bmp, xwd, pdf, unknown binary)
I want to make these metadata visible on the local Windows filesystem.
But I can't store metadata WITHIN each file. For instance, changing the secrecy of a file must NOT modify the checksum of the file.
What is the best way to store this metadata?
I have heard about NTFS extended file attributes, is it something that applies to my scenario? This question about setting extended file properties has all answers talking about modifying the files themselves, which I must avoid.
If there is no standard solution, then I will store the metadata in a local SQLite database. But I would really prefer to use a standard solution so that other apps (explorer, gallery apps, etc) can display/modify the properties they understand (like "author")
Alternate data streams is one of NTFS' less-known features. Quote from the page:
C:\test>echo "ADS" > test.txt:hidden.txt
C:\test>dir
Volume in drive C has no label.
Volume Serial Number is B889-75DB
Directory of C:>test
10/22/2003 11:22 AM
. 10/22/2003 11:22 AM
.. 10/22/2003 11:22 AM 0 test.txt
C:\test> notepad test.txt:hidden.txt
This will open the file in notepad and allow you to edit it and save it.
It is similar to the Macintosh resource fork, i.e. it allows associating arbitrary data with files, without it being part of the file itself. Explorer doesn't understand it by default, but you can write a column handler for it.
EDIT
Some metadata (such as Author and Title) can be saved using OLE document properties. I don't know if it modifies the file itself or not, though:
private void button1_Click(object sender, EventArgs e)
{
//This is the PDF file we want to update.
string filename = #"c:\temp\MyFile.pdf";
//Create the OleDocumentProperties object.
DSOFile.OleDocumentProperties dso = new DSOFile.OleDocumentProperties();
//Open the file for writing if we can. If not we will get an exception.
dso.Open(filename, false,
DSOFile.dsoFileOpenOptions.dsoOptionOpenReadOnlyIfNoWriteAccess);
//Set the summary properties that you want.
dso.SummaryProperties.Title = "This is the Title";
dso.SummaryProperties.Subject = "This is the Subject";
dso.SummaryProperties.Company = "RTDev";
dso.SummaryProperties.Author = "Ron T.";
//Save the Summary information.
dso.Save();
//Close the file.
dso.Close(false);
}

Categories

Resources