Link to home
Start Free TrialLog in
Avatar of dodgerfan
dodgerfanFlag for United States of America

asked on

How can I read custom document properties with C#?

I ant to get the custom document properties of office documents (word, excel, powerpoint) using C#. I'm building a windows desktop application. On one of the forms, I'd like to let the user select a document then click a button that will then bring back for display the custom document properties for that document. Is their a way to read these properties with C#? I am using Visual Studio 2017.
Avatar of Chinmay Patel
Chinmay Patel
Flag of India image

Hi dodgerfan,

You can use VSTO - Visual Studio Tools for Office. Refer to: https://docs.microsoft.com/en-us/visualstudio/vsto/how-to-create-and-modify-custom-document-properties

private string ReadDocumentProperty(string propertyName)
{
    Office.DocumentProperties properties;
    properties = (Office.DocumentProperties)this.CustomDocumentProperties;

    foreach (Office.DocumentProperty prop in properties)
    {
        if (prop.Name == propertyName)
        {
            return prop.Value.ToString();
        }
    }
    return null;
}

Open in new window


Regards,
Chinmay.
Avatar of dodgerfan

ASKER

I apologize for my late response. I got pulled into something else for a time. The link you provide is one fo the things I've been trying to utilize. I also found this one: https://nishantrana.me/2008/07/18/setting-built-in-and-custom-document-properties-using-c/
I used that one to get an app running that lets me update the builtin properties and add custom properties. What I have not yet figured out is how to update a custom property. Any idea how to do that? Thanks.
Refer to the link I shared earlier :) It shows both how to create and modify the properties.

Sample Code:
void TestProperties()
{
    Microsoft.Office.Core.DocumentProperties properties;
    properties = (Office.DocumentProperties)this.CustomDocumentProperties;

    if (ReadDocumentProperty("Project Name") != null)
    {
        properties["Project Name"].Delete();
    }

    properties.Add("Project Name", false,
        Microsoft.Office.Core.MsoDocProperties.msoPropertyTypeString,
        "White Papers");
}

private string ReadDocumentProperty(string propertyName)
{
    Office.DocumentProperties properties;
    properties = (Office.DocumentProperties)this.CustomDocumentProperties;

    foreach (Office.DocumentProperty prop in properties)
    {
        if (prop.Name == propertyName)
        {
            return prop.Value.ToString();
        }
    }
    return null;
}

Open in new window

I've been working with that code you posted from the link. I want to update an existing Custom Property. So would it look something like this? I'm also getting an error on the this.CustomDocumentProperties. Does not contain a definition is the message I see. Not sure what's causing that. Thank again.

private string ReadDocumentProperty(string propertyName)
{
    Microsoft.Office.Core.Office.DocumentProperties properties;
    properties = (Microsoft.Office.Core.DocumentProperties)this.CustomDocumentProperties;  // error on this line

    foreach (Microsoft.Office.Core.DocumentProperty prop in properties)
    {
        if (prop.Name == "MyCustomPropertyName")
        {
            update the custom property? not sure of the line of code here
        }
    }
    return null;
}

Open in new window

Which property name you are using? Does the document contain that property? Did you try to debug your code?
I am using a word document, and I added a custom property called ExpireDate with a  value of today's date. The document has the property and a value. I am trying to update it to a different value. The code you pointed me to plus some other sites I have found have helped. I can update Builtin properties, and I can add new custom properties. But so far no success on updating a custom property.
The technique is delete the existing one and recreate. Did you see the code I posted. It first deletes the existing one and then recreates a new one with new value.
Well, no. I did not notice that at all. I was so focused on finding an Update I failed to notice the delete then recreate part. Sorry about that.
My code looks almost exactly like what you posted above, with just the custom property name changed to ExpireDate. I am still getting the error on this.CustomDocumentProperties; in both pieces of code. Is it related to what document I want to use it on. In this case I just wanted to hard code a file name in, like this:
object filename = @"C:\SampleWord.docx";  But at some point I want to make it a variable that changes.
Hi dodgerfan,

Please do post your entire code and let me have a look at it. Let's close this, it's been dragging for a while. :)

Regards,
Chinmay.
I will post what I have first thing in the morning, Thanks.
This is what I have at the moment.
using System;
using System.IO;
using System.Windows.Forms;
using Microsoft.Office.Core;
using Microsoft.Office.Interop;
using Microsoft.Office.interop.Word;
using Microsoft.Office.Tools;

private void btnUpdateCustom_Click(object sender, EventArgs e)
{
    object filename = @C:\MyFiles\SampleWord.docx";

    Microsoft.Office.Core.DocumentProperties properties;
    properties = (Microsoft.Office.Core.DocumentProperties)this.CustomDocumentProperties;

    if (ReadDocumentProperty("My Text") != null)
    {
        properties["My Text"].Delete();
    }

    properties.Add("My Text", false, Microsoft.Office.Core.MsoDocProperties.msoPropertyTypeString,
        "New Text");
}

private string ReadDocumentProperty(string propertyName)
{
    Office.DocumentProperties properties;
    properties = (Office.DocumentProperties)this.CustomDocumentProperties;

    foreach (Office.DocumentProperty prop in properties)
    {
        if (prop.Name == propertyName)
        {
            return prop.Value.ToString();
        }
    }
    return null;
}

Open in new window

On the line that has this.CustomDocumentProperties in both sections, I get an error: ViewFiles does not contain a definition for CustomDocumentPorperties and no extension method CustomDocumentProperties accepting a first argument of type ViewFiles could be found. Are you missing an assembly reference? ViewFiles is the name of the windows form. I'm also not clear on how to tell it what file to use, which I have in there.
Just one question before I get into this, have you installed VSTO add-in on your system or not?
I thought I did, but now I can;t verify it. How can I check to make sure? And what version? I am using Visual Studio 2017 on a Windows 8 machine. I need it to work with office documents like this, correct? Is there another way? Sorry about this. And thanks again.
I am sorry I was not aware that you are using VS2017. For, VS2017 these add-ins are built-in. You don't have to install anything.

And another solid revelation, I am really sorry for wasting your time but VSTO is not the right fit here. I missed that we are not in an add-in but an independent App.

So please follow this code to list all the properties.

using Microsoft.Office.Interop;
using Microsoft.Office.Interop.Word;

Document doc = null;
            Microsoft.Office.Interop.Word.Application app = new Microsoft.Office.Interop.Word.Application();
            try
            {
                doc = app.Documents.Open(@"c:\temp\doc1.docx");
                dynamic properties = doc.CustomDocumentProperties;
                Console.WriteLine("Name\t\tValue");
                foreach (var item in properties)
                {
                    Console.WriteLine(item.Name + "\t\t" + item.Value + "\t\t");
                }
            }
            catch (Exception exception)
            {
// Handle Errors as per your requirements
            }
            finally
            {
                doc.Close();
                doc = null;
                app = null;
            }

Open in new window


So once you have this, you can use the code you were referring to earlier to add/modify the properties.

Regards,
Chinmay.
Thanks, an no need to apologize. I'm learning, if slowly, so you are not wasting my time.
I have taken your code and put it in my form. I used a listbox instead of console.writeline. The line I changed looks like this now:
lstCustomProperties.Items.Add(item.Name + "\t\t" + item.Value + "\t\t");

Open in new window

I put it in the click event of the button. When I click it, it returns all of the custom properties, names and values. In order to change one, you're saying I can now use the code I referred to earlier? I'm still getting an error on the
properties = (Microsoft.Office.Core.DocumentProperties)this.CustomDocumentProperties;

Open in new window

sections.
I'm trying to use that code now. This section:
object oDocCustomProps = adoc.CustomDocumentProperties;

Type typeDocCustomProps = oDocCustomProps.GetType();

// setting the ProposalSentDate custom date property with current date time

string strIndex1 = “ProposalSentDate”;

string strValue1 = DateTime.Now.ToShortDateString();

object[] oArg = { strIndex1, false, Microsoft.Office.Core.MsoDocProperties.msoPropertyTypeDate, strValue1 };

typeDocCustomProps.InvokeMember(“Add”,

BindingFlags.Default |

BindingFlags.InvokeMethod, null,

oDocCustomProps,oArg);

Open in new window

lets me add new custom properties to the document. But I have not been able to update one. As you said, I need to delete the property then put it back in with the new value. But I do not see how to remove it first. I tried "Delete" as part of invokeMember, but that threw an error.  Also, when I run the code to add a property, it opens the form and inserts it, then I have to save it then exit it. Can I do this in code? I've tried making it not visible and trying to save the document after insert, then closing word altogether. But that does not work.
Read that article carefully it has shown how to set the property.
I got this code to work:
public void DeleteCustom()
{
	object missing = System.Reflection.Missing.Value;
 	object filename = @C:\MyFiles\SampleWord.docx";

	ApplicationClass WordApp = new ApplicationClass();
	Document adoc = WordApp.Documents.Open(ref fileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
	
object oDocCustomProps = object oDocCustomProps = adoc.CustomDocumentProperties;
	Type typeDocCustomProps = oDocCustomProps.GetType();
 
	string strIndex = "PropertyName";
	try
	{
		object oKBProp = typeDocCustomProps.InvokeMember("Item",
															 BindingFlags.Default |
															 BindingFlags.GetProperty,
															 null, oDocCustomProps,
															 new object[] { strIndex });
		Type typeKBProp = oKBProp.GetType();
		typeKBProp.InvokeMember("Delete", BindingFlags.Default | BindingFlags.InvokeMethod, null, oKBProp, null);
	}
	catch { }
	WordApp.Visible=true;
	adoc.Save();
}

Open in new window

The problem is that the word has to open. If I take out WordApp.Visible=true; it does not work. And the save does not always work properly. I would like to keep word closed and make the change (delete or add). Is that possible? Thanks again.
I do not think that word has to open. I have not tried it myself on delete part yet. I could keep word hidden - you can see in my code that I have not made it visible - and it still gave the desired results. Also you can just use app.Documents.Open(filename), no need to provide those optional attributes. Also no need to use ref keyword as well. I will give it a try tomorrow and let you know.
I made the change you mentioned on the optional attributes. It looks like this now:
public void DeleteCustom()
{
 	object filename = @C:\MyFiles\SampleWord.docx";

	ApplicationClass WordApp = new ApplicationClass();
	Document adoc = WordApp.Documents.Open(fileName);
	
	object oDocCustomProps = object oDocCustomProps = adoc.CustomDocumentProperties;
	Type typeDocCustomProps = oDocCustomProps.GetType();
 
	string strIndex = "PropertyName";
	try
	{
		object oKBProp = typeDocCustomProps.InvokeMember("Item", BindingFlags.Default | BindingFlags.GetProperty, null, oDocCustomProps, new object[] { strIndex });
		Type typeKBProp = oKBProp.GetType();
		typeKBProp.InvokeMember("Delete", BindingFlags.Default | BindingFlags.InvokeMethod, null, oKBProp, null);
	}
	catch { }
	WordApp.Visible=true;
	adoc.Save();
}

Open in new window

If I close the app then open then open the word document and check for the just deleted custom property, it is removed. But when i close word it asks if I want to save changes. If I do not, then the custom property I just deleted goes back in. Opening the document to save changes is not what I want to do. I hope this makes sense. I can remove the WordApp.Visible = true line and I get the same behavior, so it does not appear it has to open. But I need it to save the changes. The adoc.Save() line does not appear to work.
Hi Dodgerfan,

A question - Apart from modifying the properties is there anything else that you have to do with this App?

Regards,
Chinmay.
Chinmay,
I've been trying to get one thing at a time for the most part, but eventually a uer will open the app, and browse to select a document. I've been focused on Word right now (that's 95% of the documents), but it will eventually be Excel and Powerpoint, too. Once the document is selected, then a list will display all of the Custom Properties (you got that part earlier). I need the user to then be able to select a custom property from the list and either delete it or update it. They will also be able to add a new one. And all of this without actually opening the document. I've been working on trying to get it to work without opening Word, but so far I'm hitting the wall. Thanks again.
It is already done but using a different technique. I was wondering if you are OK to wait a bit more then I can write a complete article on this. If you are in a hurry I'll post it in couple of minutes.
No problem, I can wait for a bit more.
And which version of Microsoft Office it should target? Are you planning to process any older versions (.Doc, .ppt, .xls) etc?
Office 2010 is the focus right now. Moving to newer versions down the line could happen (long time out). And it is possible to have to deal with older versions, but it's not a lot.
One other thing I thought of. If any of the processing takes a some time, show a progress bar of some type then a message when the process is complete.
:D I am not going to do that. I am going to provide you a library you call to the library it doe the processing in a split second, if you think it is taking time, you will provide a progressbar. But I do request you to hold off till you see it in action. Also, one question, do you need to process BuiltIn Document properties as well? or just custom ones?
Thanks, sounds good. While not as important, processing the builtin properties would be useful too.
How about once you see my code, build your own. I promise you it will be easy and straightforward. That way you will learn something new as well.
Sounds great. There's really no point if I don't learn to work this myself.
ASKER CERTIFIED SOLUTION
Avatar of Chinmay Patel
Chinmay Patel
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I was able to get the code in and take a look. I cut and pasted the helper class into a class I created in my solution. But when I try to change the namespace to the namespace I have from OblakConsulting, I get an error on here:
Properties customProperties = customProps.Properties;
if (customProperties != null)
{
CustomDocumentProperty property = GetProperty(customProperties, propertyName);

Open in new window

It does not like Properties or customProperties. When I change the namespace to OblakConsulting, it clears up. Any idea how to change the namespace without getting the errors?
Hi dodgerfan,

It should not give you an error as long as you make changes in code wherever you are referencing this class. I think you have not corrected the namespace after changing OblakConsulting. Which version of Visual Studio you are using? It generally gives hints for trivial issues like this and just by pressing CTRL + SPACE or CTRL + . you should be able to fix these errors.

Regards,
Chinmay.
I'm not able to fix it so far. The error I get when I highlight the red line is 'Properties' is a namespace' but is being used like a type. The suggestions from intellisense have not worked. If I make the namespace OblakConstulting, then the error clears up.
1.Just double check if OpenXML SDK is installed for your project.
2. Have you included proper Using statements?
3. Share your entire project.
I mean all the code files you are using.
It looks like OpenXML is not installed properly. Then why would it compile if the namespace is not changed? Anyway, I cannot post the entire project. It exists on a different network, with no way fro me to pull off a copy. The code I do post I retype, which is why have typos, too. I had to download the OPenXML nuget package from the Nuget site then moved had it moved over. When I try to install it, I get the error: Unable to resolve the dependency System.IO.Packaging'. Sources(s) used: 'Package Source'. Microsoft Visual Studio Offline Packages.' Sorry about this. If you have an idea about what I have wrong, please let me know. But I will try to resolve this.
Hi dodgerfan,

You don't have to post everything just the failing files. Also if OpenXML is not installed properly I don't think it will compile at all.
How did you try to install the nuget package?

Another thing that hit me, you might have conflicting namespace, could you please post the name spaces you have included? I generally remove unused namespaces.

Also change : Properties customProperties = customProps.Properties; to this:
                DocumentFormat.OpenXml.CustomProperties.Properties customProperties = customProps.Properties;

Open in new window


PS: I understand security and restrictions BUT I absolutely loathe, security by obscurity.

Regards,
Chinmay.
Once I changed that line of code you pointed out, it all compiled and I have n o errors. OpenXML is installed properly, too. Thank you!
Finally a WIN :D... I actually lost my cool :P for a couple of minutes when I read about that namespace thing. Namespaces generally don't interfere I think there are some other Properties your code was running into. Glad this one got closed finally.
Yes, thank you again. I will close this one out.
Extraordinarily helpful.