Click here to Skip to main content
14,666,474 members
Articles » Enterprise Systems » Office Development » Microsoft Word
Posted 14 Jun 2010

Tagged as


52 bookmarked

Manipulate Docx with C# without Microsoft Word installed with OpenXML SDK

Rate this:
4.69 (9 votes)
Please Sign up or sign in to vote.
4.69 (9 votes)
15 Jun 2010CPOL
Use C# and the OpenXML SDK to manipulate docx without MSO.


With the OpenXML SDK, you can edit docx files without having Microsoft Word installed.

In this particular situation, I'm editing the custom properties of a docx file, which are commonly used to store some application's info for further use, or even for some add-in that we developed as well.

Using the code

This article is really simple; its purpose is to spread the word, just showing you what to do based on MSDN.

For starters, the discovery was when I found the OpenXML SDK that allows me to manipulate Word documents without having Office installed on the server that runs my application, which is a major breakthrough! The code that I use which adds custom properties was taken from MSDN, and I will show it to you here:

The PropertyTypes enum:

public enum PropertyTypes

The method:

public bool WDSetCustomProperty(string docName, string propertyName, 
            object propertyValue, PropertyTypes propertyType)
    const string documentRelationshipType =
        "" +
    const string customPropertiesRelationshipType =
        "" +
    const string customPropertiesSchema =
        "" +
    const string customVTypesSchema =
        "" +

    bool retVal = false;
    PackagePart documentPart = null;
    string propertyTypeName = "vt:lpwstr";
    string propertyValueString = null;

    //  Calculate the correct type.
    switch (propertyType)
        case PropertyTypes.DateTime:
          propertyTypeName = "vt:filetime";
          //  Make sure you were passed a real date, 
          //  and if so, format in the correct way. The date/time 
          //  value passed in should represent a UTC date/time.
          if (propertyValue.GetType() == typeof(System.DateTime))
            propertyValueString = string.Format("{0:s}Z",

        case PropertyTypes.NumberInteger:
          propertyTypeName = "vt:i4";
          if (propertyValue.GetType() == typeof(System.Int32))
            propertyValueString =

        case PropertyTypes.NumberDouble:
          propertyTypeName = "vt:r8";
          if (propertyValue.GetType() == typeof(System.Double))
            propertyValueString =

        case PropertyTypes.Text:
          propertyTypeName = "vt:lpwstr";
          propertyValueString = Convert.ToString(propertyValue);

        case PropertyTypes.YesNo:
          propertyTypeName = "vt:bool";
          if (propertyValue.GetType() == typeof(System.Boolean))
            //  Must be lower case!
            propertyValueString =

    if (propertyValueString == null)
        //  If the code cannot convert the 
        //  property to a valid value, throw an exception.
        throw new InvalidDataException("Invalid parameter value.");

    using (Package wdPackage = Package.Open(
           docName, FileMode.Open, FileAccess.ReadWrite))
        //  Get the main document part (document.xml).
        foreach (PackageRelationship relationship in
            Uri documentUri = PackUriHelper.ResolvePartUri(
                new Uri("/", UriKind.Relative), relationship.TargetUri);
            documentPart = wdPackage.GetPart(documentUri);
            //  There is only one document.
        //  Work with the custom properties part.
        PackagePart customPropsPart = null;

        //  Get the custom part (custom.xml). It may not exist.
        foreach (PackageRelationship relationship in
            Uri documentUri = PackUriHelper.ResolvePartUri(
                new Uri("/", UriKind.Relative), relationship.TargetUri);
            customPropsPart = wdPackage.GetPart(documentUri);
            //  There is only one custom properties part, 
            // if it exists at all.

        //  Manage namespaces to perform Xml XPath queries.
        NameTable nt = new NameTable();
        XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
        nsManager.AddNamespace("d", customPropertiesSchema);
        nsManager.AddNamespace("vt", customVTypesSchema);

        Uri customPropsUri =
          new Uri("/docProps/custom.xml", UriKind.Relative);
        XmlDocument customPropsDoc = null;
        XmlNode rootNode = null;

        if (customPropsPart == null)
          customPropsDoc = new XmlDocument(nt);

          //  The part does not exist. Create it now.
          customPropsPart = wdPackage.CreatePart(

          //  Set up the rudimentary custom part.
          rootNode = customPropsDoc.
            CreateElement("Properties", customPropertiesSchema);
          rootNode.Attributes["xmlns:vt"].Value = customVTypesSchema;


          //  Create the document's relationship to the 
          //  new custom properties part.
            TargetMode.Internal, customPropertiesRelationshipType);
          //  Load the contents of the custom properties part 
          //  into an XML document.
          customPropsDoc = new XmlDocument(nt);
          rootNode = customPropsDoc.DocumentElement;

        string searchString =
        XmlNode node = customPropsDoc.SelectSingleNode(
          searchString, nsManager);

        XmlNode valueNode = null;

        if (node != null)
            //  You found the node. Now check its type.
            if (node.HasChildNodes)
                valueNode = node.ChildNodes[0];
                if (valueNode != null)
                    string typeName = valueNode.Name;
                    if (propertyTypeName == typeName)
                        //  The types are the same. 
                        //  Replace the value of the node.
                        valueNode.InnerText = propertyValueString;
                        //  If the property existed, and its type
                        //  has not changed, you are finished.
                        retVal = true;
                        //  Types are different. Delete the node
                        //  and clear the node variable.
                        node = null;

        if (node == null)
            string pidValue = "2";

            XmlNode propertiesNode = customPropsDoc.DocumentElement;
            if (propertiesNode.HasChildNodes)
                XmlNode lastNode = propertiesNode.LastChild;
                if (lastNode != null)
                    XmlAttribute pidAttr = lastNode.Attributes["pid"];
                    if (!(pidAttr == null))
                        pidValue = pidAttr.Value;
                        //  Increment pidValue, so that the new property
                        //  gets a pid value one higher. This value should be 
                        //  numeric, but it never hurt so to confirm.
                        int value = 0;
                        if (int.TryParse(pidValue, out value))
                            pidValue = Convert.ToString(value + 1);

            node = customPropsDoc.CreateElement("property", customPropertiesSchema);
            node.Attributes["name"].Value = propertyName;

            node.Attributes["fmtid"].Value = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";

            node.Attributes["pid"].Value = pidValue;

            valueNode = customPropsDoc.
            CreateElement(propertyTypeName, customVTypesSchema);
            valueNode.InnerText = propertyValueString;
            retVal = true;

        //  Save the properties XML back to its part.
                            FileMode.Create, FileAccess.Write));

    return retVal;


// Change an existing property's value or create a new one with the supplied value
WDSetCustomProperty("C:\\demo.docx", "Completed", 
  false, PropertyTypes.YesNo);

// Change an existing property's value or create a new one with the supplied value
WDSetCustomProperty("C:\\demo.docx", "Completed", 
  new DateTime(2008, 1, 1), PropertyTypes.DateTime);

The purpose of this code is to write custom properties, for my Word add-in to work properly, which is quite handy in most situations.

Hope this was as much value to you as it was to me! Thank you very much.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Software Developer (Senior) Truphone
Portugal Portugal
No Biography provided

Comments and Discussions

QuestionEasy to use alternative Pin
Robert Hutch16-Feb-12 3:09
MemberRobert Hutch16-Feb-12 3:09 
OpenXML SDK is really powerful, but very hard to use for some simple things.

This C# Word library is an easy to use alternative.

Here is a sample C# DOCX code how to create and save a Word document with custom properties, and open it and read custom properties:
// Use the component in free mode.

// Create new empty document.
var document = new DocumentModel();

// Add various custom document properties.
document.DocumentProperties.Custom.Add("BooleanProperty", true);
document.DocumentProperties.Custom.Add("DateTimeProperty", DateTime.Now);
document.DocumentProperties.Custom.Add("StringProperty", "some text");
document.DocumentProperties.Custom.Add("NumberProperty", Math.PI);

// Save the document to a file.
document.Save("Document.docx", SaveOptions.DocxDefault);

// Load a document from the file.
document = DocumentModel.Load("Document.docx", LoadOptions.DocxDefault);

// Output all custom document properties to a console.
foreach (var customProperty in document.DocumentProperties.Custom)
	Console.WriteLine("{0}: {1} ({2})", customProperty.Key, customProperty.Value, customProperty.Value.GetType());

AnswerRe: Easy to use alternative Pin
ricmrodrigues17-Feb-12 0:07
Memberricmrodrigues17-Feb-12 0:07 
GeneralRe: Easy to use alternative Pin
Robert Hutch17-Feb-12 4:35
MemberRobert Hutch17-Feb-12 4:35 
GeneralRe: Easy to use alternative Pin
ricmrodrigues17-Feb-12 5:01
Memberricmrodrigues17-Feb-12 5:01 
GeneralRe: Easy to use alternative Pin
Robert Hutch19-Feb-12 22:42
MemberRobert Hutch19-Feb-12 22:42 
GeneralRe: Easy to use alternative Pin
atulonweb@gmail.com23-Dec-15 20:44
Memberatulonweb@gmail.com23-Dec-15 20:44 
GeneralfleXdoc Pin
hmmhazeb15-Jun-10 10:41
Memberhmmhazeb15-Jun-10 10:41 
Generalcode dump Pin
Jaime Olivares14-Jun-10 16:35
MemberJaime Olivares14-Jun-10 16:35 
GeneralRe: code dump Pin
ricmrodrigues14-Jun-10 22:08
Memberricmrodrigues14-Jun-10 22:08 
GeneralRe: code dump Pin
deBaires15-Jun-10 3:12
MemberdeBaires15-Jun-10 3:12 
GeneralRe: code dump Pin
ricmrodrigues15-Jun-10 23:18
Memberricmrodrigues15-Jun-10 23:18 
GeneralRe: code dump Pin
ricmrodrigues15-Jun-10 23:33
Memberricmrodrigues15-Jun-10 23:33 
GeneralRe: code dump Pin
ricmrodrigues15-Jun-10 23:33
Memberricmrodrigues15-Jun-10 23:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.