Windows

OfficeIMO – Free cross-platform Microsoft Word .NET library

About 12 years ago, I worked for a company that had their entire client database written in Microsoft Excel (each Client, in a separate file, later on, combined into one large Excel for reporting) and Microsoft Access. Whenever anyone required confirmation on paper about their holdings, the person would generate a Word document manually confirming their portfolio of products to the Client. As you can imagine, this was pretty unmanageable. There was a massive problem with efficiency. A lot of work went into fixing errors in Excel files. Generating Word files by hand was not great either. This was when I was tasked with creating a program that would take over. The only problem was – I had never written the program before. Sure, I wrote some scripts, but nothing major. After struggling to learn C# for weeks, I found this DocX library that would promise to create Word documents without the need for Interop. It was straightforward to use and had all the features I needed. After some time and a bit of helping with the library, I was given it to maintain while the original owner went away.

A few years later, after struggling to support the library (people are kind of demanding), we decided to give it away to a company that would promise to keep it free while establishing some business model. And they did. I could use the DocX library for years without much of a problem. The only downside was it only supports .NET Framework, and the world has moved on. While I could accept the .NET Framework limitation because of my need to interact with DocX using PowerShell primarily, they also changed licensing model recently, making it very difficult to write anything with it and not think about it every single time, whether it breaches a license or not. The NET limitation and licensing model made the DocX library useless for me, so I decided to do something about it. I started exploring, and Office XML SDK seemed a great way to create Word documents. But while it's a great library with many options, it doesn't do a lot of heavy lifting for you. It gives you a hammer and some nails and lets you go on your own. That's why I've decided to simplify things and make it easy to create even complicated documents without knowing all the ins and outs of how Word documents are built.

TL;DR

I've created a cross-platform (Windows, Linux, macOS) Word library based on Open XML SDK that heavily simplifies creating and modifying Word documents. Open XML SDK, while excellent, requires you to do a lot of work to make even simple documents. For example, if you want to use Table styles, you need first to define those styles, put them in a specific place, and assign them to a table. The same goes for lists, images, hyperlinks, bookmarks, and many other Microsoft Word types. Creating sections, managing headers, and footers – all that is possible using Open XML SDK, but it's far from easy. At least for a noob like me. You have to know the order to put them into the document; you must know the places and track IDs to all the elements. And trust me – it's not fun.

OfficeIMO.Word library supports following .NET versions

  • .NET 4.7.2
  • .NET 4.8
  • .NET 5.0
  • .NET 6.0
  • .NET Standard 2.0
  • .NET Standard 2.1

OfficeIMO is free for commercial usage with no limits of any kind. Since it's one of the reasons why I decided to stop using the DocX project, it would make no sense to introduce any limits of any kind. OfficeIMO.Word is a wrapper around Open XML SDK and provides a much easier way to build Word documents using NET. While there is a project called OfficeIMO.Excel I have just played around for a few hours with it and mostly created it to see how it could work, and maybe something I will expand on in the future. However, there are other excellent alternatives for Excel, so I'm not thinking about it for now.

Creating basic Word Document using C#

To show you what's in the OfficeIMO.Word library, let's start with something simple. Creating a bare and empty Word document with three properties such as Title, Creator, or Keywords is as easy as five lines of code.

public static void Example_BasicEmptyWord(string folderPath, bool openWord) {
    Console.WriteLine("[*] Creating standard document (empty)");
    string filePath = System.IO.Path.Combine(folderPath, "EmptyDocument.docx");
    using (WordDocument document = WordDocument.Create(filePath)) {
        document.BuiltinDocumentProperties.Title = "This is my title";
        document.BuiltinDocumentProperties.Creator = "Przemysław Kłys";
        document.BuiltinDocumentProperties.Keywords = "word, docx, test";
        document.Save(openWord);
    }
}

Adding paragraphs with text and some styling to Microsoft Word using C#

Of course, who would create an empty document? Adding some paragraphs with texts and changing its styling, changing alignment, or adding color can be done by changing properties.

public static void Example_BasicWord(string folderPath, bool openWord) {
    Console.WriteLine("[*] Creating standard document with paragraph");
    string filePath = System.IO.Path.Combine(folderPath, "BasicDocumentWithParagraphs.docx");
    using (WordDocument document = WordDocument.Create(filePath)) {
        var paragraph = document.AddParagraph("Adding paragraph with some text");
        paragraph.ParagraphAlignment = JustificationValues.Center;
        Console.WriteLine(SixLabors.ImageSharp.Color.Blue.ToHexColor());
        Console.WriteLine(SixLabors.ImageSharp.Color.Crimson.ToHexColor());
        Console.WriteLine(SixLabors.ImageSharp.Color.Aquamarine.ToHexColor());

        paragraph.Color = SixLabors.ImageSharp.Color.Red;

        paragraph = document.AddParagraph("Adding another paragraph with some more text");
        paragraph.Bold = true;
        paragraph = paragraph.AddText(" , but now we also decided to add more text to this paragraph using different style");
        paragraph.Underline = UnderlineValues.DashLong;
        paragraph = paragraph.AddText(" , and we still continue adding more text to existing paragraph.");
        paragraph.Color = SixLabors.ImageSharp.Color.CornflowerBlue;

        document.Save(openWord);
    }
}

Adding sections, changing page size and page orientation using C#

OfficeIMO isn't all about basic stuff tho. It can create and modify sections, add and remove paragraphs, add and change comments, and adjust page size and orientation. With minimal effort, as shown in the example below, you can define a pretty complicated Word Document without knowing the document's structure that would be otherwise required when playing with Open XML SDK.

public static void Example_BasicWord2(string folderPath, bool openWord) {
    Console.WriteLine("[*] Creating standard document with paragraph (2)");
    string filePath = System.IO.Path.Combine(folderPath, "BasicDocumentWithParagraphs2.docx");
    using (WordDocument document = WordDocument.Create(filePath)) {

        document.Settings.ZoomPercentage = 50;
        var paragraph = document.AddParagraph("Basic paragraph");

        var section1 = document.AddSection();
        section1.AddParagraph("Test Middle Section - 1");

        var section2 = document.AddSection();
        section2.AddParagraph("Test Last Section - 1");
        section1.AddParagraph("Test Middle Section - 2").AddComment("Adam Kłys", "AK", "Another test");
        var test = document.AddParagraph("Test 1 - to delete");
        test.Remove();
        section1.PageSettings.PageSize = WordPageSize.A5;
        section2.PageOrientation = PageOrientationValues.Landscape;

        document.Sections[2].AddParagraph("Test 0 - Section Last");
        document.Sections[1].AddParagraph("Test 1").AddComment("Przemysław Kłys", "PK", " This is just a test");

        Console.WriteLine("----");
        Console.WriteLine("Sections: " + document.Sections.Count);
        Console.WriteLine("----");
        Console.WriteLine(document.Sections[0].Paragraphs.Count);
        Console.WriteLine(document.Sections[1].Paragraphs.Count);
        Console.WriteLine(document.Sections[2].Paragraphs.Count);

        Console.WriteLine(document.Comments.Count);

        document.Comments[0].Text = "Lets change it";
        document.Save(false);
    }

    using (WordDocument document = WordDocument.Load(filePath)) {
        Console.WriteLine("----");
        Console.WriteLine(document.Sections.Count);
        Console.WriteLine("----");
        Console.WriteLine(document.Sections[0].Paragraphs.Count);
        Console.WriteLine(document.Sections[0].Paragraphs.Count);
        Console.WriteLine(document.Sections[0].Paragraphs.Count);

        Console.WriteLine(document.Sections[0].HyperLinks.Count);
        Console.WriteLine(document.HyperLinks.Count);
        Console.WriteLine(document.Fields.Count);
        document.Save(true);
    }
}

Adding tables, lists, table of content and more using C#

While the above examples are pretty simple OfficeIMO feature set for Word has a lot more to offer. Adding sections, headers, and footers, changing page size, and page orientation. Adding watermark, adding a cover page, adding a table of content, adding lists, adding tables, and so much more. You can see in the below example a lot of those features shown, and how most of them are basically one line of code.

public static void Example_AdvancedWord(string folderPath, bool openWord) {
    Console.WriteLine("[*] Creating advanced document");
    string filePath = System.IO.Path.Combine(folderPath, "AdvancedDocument.docx");
    using (WordDocument document = WordDocument.Create(filePath)) {
        // lets add some properties to the document
        document.BuiltinDocumentProperties.Title = "Cover Page Templates";
        document.BuiltinDocumentProperties.Subject = "How to use Cover Pages with TOC";
        document.ApplicationProperties.Company = "Evotec Services";

        // we force document to update fields on open, this will be used by TOC
        document.Settings.UpdateFieldsOnOpen = true;

        // lets add one of multiple added Cover Pages
        document.AddCoverPage(CoverPageTemplate.IonDark);

        // lets add Table of Content (1 of 2)
        document.AddTableOfContent(TableOfContentStyle.Template1);

        // lets add page break
        document.AddPageBreak();

        // lets create a list that will be binded to TOC
        var wordListToc = document.AddTableOfContentList(WordListStyle.Headings111);

        wordListToc.AddItem("How to add a table to document?");

        document.AddParagraph("In the first paragraph I would like to show you how to add a table to the document using one of the 105 built-in styles:");

        // adding a table and modifying content
        var table = document.AddTable(5, 4, WordTableStyle.GridTable5DarkAccent5);
        table.Rows[3].Cells[2].Paragraphs[0].Text = "Adding text to cell";
        table.Rows[3].Cells[2].Paragraphs[0].Color = Color.Blue; ;
        table.Rows[3].Cells[3].Paragraphs[0].Text = "Different cell";

        document.AddParagraph("As you can see adding a table with some style, and adding content to it ").SetBold().SetUnderline(UnderlineValues.Dotted).AddText("is not really complicated").SetColor(Color.OrangeRed);

        wordListToc.AddItem("How to add a list to document?");

        var paragraph = document.AddParagraph("Adding lists is similar to ading a table. Just define a list and add list items to it. ").SetText("Remember that you can add anything between list items! ");
        paragraph.SetColor(Color.Blue).SetText("For example TOC List is just another list, but defining a specific style.");

        var list = document.AddList(WordListStyle.Bulleted);
        list.AddItem("First element of list", 0);
        list.AddItem("Second element of list", 1);

        var paragraphWithHyperlink = document.AddHyperLink("Go to Evotec Blogs", new Uri("https://evotec.xyz"), true, "URL with tooltip");
        // you can also change the hyperlink text, uri later on using properties
        paragraphWithHyperlink.Hyperlink.Uri = new Uri("https://evotec.xyz/hub");
        paragraphWithHyperlink.ParagraphAlignment = JustificationValues.Center;

        list.AddItem("3rd element of list, but added after hyperlink", 0);
        list.AddItem("4th element with hyperlink ").AddHyperLink("included.", new Uri("https://evotec.xyz/hub"), addStyle: true);

        document.AddParagraph();

        var listNumbered = document.AddList(WordListStyle.Heading1ai);
        listNumbered.AddItem("Different list number 1");
        listNumbered.AddItem("Different list number 2", 1);
        listNumbered.AddItem("Different list number 3", 1);
        listNumbered.AddItem("Different list number 4", 1);

        var section = document.AddSection();
        section.PageOrientation = PageOrientationValues.Landscape;
        section.PageSettings.PageSize = WordPageSize.A4;

        wordListToc.AddItem("Adding headers / footers");

        // lets add headers and footers
        document.AddHeadersAndFooters();

        // adding text to default header
        document.Header.Default.AddParagraph("Text added to header - Default");

        var section1 = document.AddSection();
        section1.PageOrientation = PageOrientationValues.Portrait;
        section1.PageSettings.PageSize = WordPageSize.A5;

        wordListToc.AddItem("Adding custom properties and page numbers to document");

        document.CustomDocumentProperties.Add("TestProperty", new WordCustomProperty { Value = DateTime.Today });
        document.CustomDocumentProperties.Add("MyName", new WordCustomProperty("Some text"));
        document.CustomDocumentProperties.Add("IsTodayGreatDay", new WordCustomProperty(true));

        // add page numbers
        document.Footer.Default.AddPageNumber(WordPageNumberStyle.PlainNumber);

        // add watermark
        document.Sections[0].AddWatermark(WordWatermarkStyle.Text, "Draft");

        document.Save(openWord);
    }
}

Of course, the OfficeIMO Word library isn't complete. There are bugs, missing features, and things I've not thought about. I am not a developer, and I mainly created this for my own needs but decided to share it with everyone. Having said that:

  • If you see bad practice, please open an issue/submit PR.
  • If you know how to do something in OpenXML that could help this project – please open an issue/submit a PR
  • If you see something that could work better – please open an issue/submit a PR
  • If you see something that I made a fool of myself – please open an issue/submit a PR
  • If you see something that works not the way I think it works – please open an issue/submit a PR

I hope you get the drift? If it's terrible – open an issue/fix it! I don't know what I'm doing! OfficeIMO is hosted on GitHub and is open source.

OfficeIMO - How do I get it?

How do you install it? The easiest and most optimal way is to use Nuget.org. This will get you up and running in no time.

  • Code is published as a nuget on Nuget.org
  • Issues should be reported on GitHub
  • Code is published on GitHub

On GitHub, you can also find multiple other examples and a multitude of tests that go thru different functionality.

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

5 days ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

5 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

7 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

8 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

8 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

9 months ago