How to Build Excel Report Output - COM / Open XML / Templates
· Updated: · Go Komura · Excel, Reporting, Windows Development, Office, COM, Open XML
Revision history (1 updates, last updated Sep 1, 2026)
A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.
- Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.21614525)
- First published
Cite this article(DOI: 10.5281/zenodo.21614524)
This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.
Go Komura (2026). How to Build Excel Report Output - COM / Open XML / Templates. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614524 https://comcomponent.com/en/blog/2026/03/16/010-excel-report-output-how-to-build/
- DOI (latest version)
- 10.5281/zenodo.21614524
- DOI (this version)
- 10.5281/zenodo.22217162
In consultations about Excel report output, the phrase “we want to output to Excel” often turns out to contain several distinct requirements mixed together.
- Users want to edit the output by hand afterwards
- An existing
.xlsmneeds to be kept - Pivot tables, charts, and print settings should carry over as is
- Large volumes need to be produced by a nightly batch
- It must run unattended on a server
- A PDF is also wanted
No single approach solves all of these cleanly. The first thing to look at is not the library name but whether you are driving the Excel application or building an Excel file.
Get this wrong and things may work at first, but maintenance becomes painful later. In this article, assuming Excel report output in Windows apps and business systems, we organize how to choose among COM automation / Open XML / template-based data injection / coexisting with existing VBA.
flowchart TB
accTitle: The first fork to look at
accDescr: A diagram showing that in Excel report output the fork to settle before any library name is whether you drive the Excel application or build an Excel file, and that getting this wrong makes maintenance painful later even when the first version works.
q1{"Drive the Excel app or build a file"}
q1 -->|"Drive the app"| a1["Automate Excel itself"]
q1 -->|"Build a file"| a2["Assemble the xlsx directly"]
q1 -.-> w1["Get it wrong and maintenance turns painful"]
Figure 1: Before picking a library, decide whether you are operating Excel or assembling a file.
Who This Article Is For and What It Assumes
This is written for developers who are about to choose how their business system will produce Excel reports.
The assumed setup is output from a C# / .NET application or batch job running on Windows. Cases where you already carry existing VBA assets are covered too, but even then the article assumes you divide the work with the .NET side rather than keeping everything inside VBA. The code examples are C# / .NET 8.
Terms to Have Straight First
| Term | Meaning |
|---|---|
| Open XML | The file format used from Office 2007 onward. A .xlsx file is really a ZIP archive of XML files, so a program can assemble one without launching Excel |
| COM automation (Office Automation) | An approach that actually launches an Office application such as Excel and drives it from an external program. COM is the Windows mechanism for calls between components, and Excel exposes an entry point through it |
| bitness | Whether the code is built and run as 32-bit or 64-bit. In COM automation, the connection fails when the caller and Excel itself do not have matching bitness |
| Named range | A name you can attach to a cell or cell range in Excel. Instead of an address like Cells[12, 7], you can point at the injection target through this name |
| Table (ListObject) | The structure created by Excel’s Format as Table. Formatting and formulas extend automatically when a row is added, which makes it a good entry point for detail rows |
1. Conclusions First
Let us lay out the conclusions up front.
- If the report is one that users will open and edit in Excel afterwards, the first candidate is a template plus direct
.xlsx/.xlsmgeneration. - If you generate automatically on a server / service / scheduler, it is safer not to base the design on Office automation.
- If you want to leverage existing
.xlsmfiles, VBA, charts, pivot tables, and print settings, the design is more robust if you push layout and Excel-specific features into the template and keep the code focused purely on data injection. - Only when you truly need the behavior of the Excel application itself is it natural to use COM automation, and even then limit it to attended execution on the desktop.
- If you just need a plain list export, CSV / PDF / a web page quite often fits the requirements better from the start.
In short, for many business reports it is more natural to assemble an Excel file than to operate Excel.
flowchart TB
accTitle: The first candidate seen from the requirements
accDescr: A diagram showing that reports users edit afterwards make a template plus direct generation the first candidate, that unattended automatic generation should not be built on Office automation, and that COM automation is limited to attended execution and used only when the behavior of the Excel application itself is genuinely required.
r1["Reports edited afterwards"] --> s1["Template plus direct generation"]
r2["Unattended automatic generation"] --> s2["Do not build on Office automation"]
r3["Excel behavior itself is required"] --> s3["Limit COM automation to attended runs"]
Figure 2: Once you know who runs it and where, the first candidate is almost decided.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (29 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. What to Decide First
Here is a table of the things you want to decide first for Excel report output.
| Item to confirm | Why decide it first |
|---|---|
Is the final deliverable .xlsx / .xlsm / PDF / CSV? |
This alone narrows the options considerably |
| Will users edit the output in Excel afterwards? | If editing is expected, Excel features and layout preservation matter |
| Does it run on the user’s PC, or on a server / service / batch? | This greatly changes where COM automation can be used |
| Will existing VBA / macros / add-ins be kept? | You will need an .xlsm template and a phased-migration design |
| Do charts, pivot tables, print areas, and headers / footers need to be fixed? | Pushing these into the template is more robust than doing them in code |
| How many rows, files, and concurrent executions per run? | For high-volume output, direct generation tends to fit better than COM |
| Who will change the report’s appearance? | If non-developers will touch it too, the template approach is a good fit |
3. Main Implementation Approaches
3.1 Excel COM Automation
This approach launches Excel and manipulates Workbook, Worksheet, and Range via COM.
It is easiest to understand as driving the real Excel.
Its strength is that you can use Excel-specific behavior as is. It plays well with existing workbooks, charts, pivot tables, print settings, macros, and PDF export, and lets you work directly with how Excel will ultimately present the result.
Its weaknesses, however, are equally clear.
- Excel must be installed
- You take on process lifetime, file locks, dialogs, bitness, and user-profile dependencies
- Office Automation from an unattended server or service is something Microsoft itself does not recommend or support
The third point is the strongest claim in this article, so let me make the evidence explicit. The Microsoft support article Considerations for server-side Automation of Office states plainly that server-side Office Automation is neither recommended nor supported. It gives the following five reasons.
| Reason | Details |
|---|---|
| User identity | Office assumes a user is present and reads per-user registry settings. A service running under an account with no user profile fails right there |
| Desktop interactivity | Office assumes an interactive desktop and may raise modal dialogs. In an environment where nobody can dismiss them, the thread stays blocked there indefinitely |
| Reentrancy and scalability | Office applications are single-threaded COM servers and are not reentrant. They are designed for a single client, so they do not hold up under the concurrency a server workload requires |
| Robustness and stability | Install-on-first-use features can raise unexpected dialogs, and Office was never tested for server-side deployment in the first place |
| Server-side security | Office has no security controls designed for distributed components and does not authenticate requests. Cached credentials risk being shared across multiple clients |
Microsoft 365 RPA environments are covered separately in Considerations for unattended automation of Office. When this point becomes the sticking point in choosing an approach, cite these two documents.
flowchart TB
accTitle: Where server-side automation stands
accDescr: A diagram showing that Office Automation from an unattended server or service is neither recommended nor supported by Microsoft itself, and that when the point becomes contentious during approach selection the support article and the article on RPA environments can both be cited as evidence.
sv1["Office Automation from an unattended server"] --> sv2["Microsoft neither recommends nor supports it"]
sv2 -.-> sv3["The support article is the evidence"]
sv2 -.-> sv4["M365 RPA environments are covered separately"]
Figure 3: Whether this approach is viable is settled by official documentation, not by preference.
3.2 Direct .xlsx Generation
Since .xlsx is the Open XML format, you can assemble files directly without launching Excel.
With something like the Open XML SDK, your program can manipulate workbooks, sheets, cells, styles, and tables.
The strength of this approach is that it is easy to run in environments without Excel installed and pairs well with batch jobs and servers.
On the other hand, things get harder when you want to naturally reproduce the UI-oriented behavior that Excel itself provides. Auto-fitting column widths, page breaks, elaborate visuals, and deep edits to existing workbooks all make the line count climb steadily once you try to do every last detail cleanly in code alone.
flowchart TB
accTitle: The character of the direct generation approach
accDescr: A diagram showing that because xlsx is the Open XML format it can be assembled without launching Excel, which pairs well with machines that lack Excel and with batch jobs and servers, while reproducing the UI-oriented behavior of Excel steadily grows the code.
dg1["xlsx is the Open XML format"] --> dg2["Assemble it without launching Excel"]
dg2 --> dg3["Pairs well with batch jobs and servers"]
dg2 -.-> dg4["Reproducing UI behavior grows the code"]
Figure 4: Needing no Excel and struggling to reproduce the look are two sides of the same coin.
Several libraries handle .xlsx from .NET, and the license terms matter in practice. These are the ones that usually come up.
| Library | License | Where it fits |
|---|---|---|
Open XML SDK (DocumentFormat.OpenXml) |
MIT | Made by Microsoft. You work with the Open XML structure almost as it is. It covers the widest ground, at the cost of a fair amount of code even to write a single cell |
| ClosedXML | MIT | A wrapper over the Open XML SDK. Worksheets, cells, named ranges, and tables are all available through a straightforward API. It handles .xlsx and .xlsm, and does not need Excel installed |
| NPOI | Apache License 2.0 | A .NET port of Java’s Apache POI. Its distinguishing feature is that it also handles the older .xls format |
| EPPlus | Polyform Noncommercial or a commercial license from version 5 onward | Very capable, but commercial use requires a paid license. Choosing it from memories of the LGPL-era version 4 line will land you in trouble over licensing |
With the template injection approach the template owns the appearance, so all the code has to do is put values into the agreed entry points. That makes the lighter-weight wrappers the easier fit.
3.3 Template-Based Data Injection
The approach we find easiest to recommend in practice is to create an Excel template first and keep the code focused purely on data injection.
The report’s appearance, formulas, conditional formatting, print areas, headers / footers, logos, and charts live in the template. The code copies the template and writes data into agreed entry points: named ranges, tables, cell ranges, and so on.
Doing this separates layout changes from business-logic changes.
It goes a long way toward avoiding the Cells[37, 9] = ... hell so common in Excel reporting.
flowchart TB
accTitle: How work is split in template injection
accDescr: A diagram showing that the appearance, formulas, and print settings of the report live in the template while the code copies the template and writes data into agreed entry points such as named ranges and tables, which separates layout changes from business logic changes.
tp1["Template side"] --> tp2["Owns appearance, formulas, print settings"]
tc1["Code side"] --> tc2["Only writes values into agreed entry points"]
tp2 --> tw1["Layout changes separate from business changes"]
tc2 --> tw1
Figure 5: Splitting the turf between appearance and logic is the core of this approach.
3.4 Keeping Existing VBA Assets
If existing .xlsm files or VBA are alive and well, it is often more natural not to rebuild everything at once.
Leaving the report UI and final formatting in VBA while moving heavy computation and DB / HTTP / business logic to the C# / .NET side is a very realistic split.
What matters here is not leaving responsibilities ambiguous.
- The VBA side owns behavior inside the workbook
- The .NET side owns data retrieval and business processing
- The boundary between them is fixed via named ranges, tables, public interfaces, and the like
flowchart TB
accTitle: Dividing responsibilities between existing VBA and .NET
accDescr: A diagram showing that when existing xlsm files and VBA are kept, the VBA side owns behavior inside the workbook, the .NET side owns data retrieval and business processing, and the boundary between them is fixed with named ranges, tables, and public interfaces.
vb1["VBA side"] --> vb2["Behavior inside the workbook"]
nt1[".NET side"] --> nt2["Data retrieval and business processing"]
vb2 --> bd1["Boundary fixed by named ranges and the like"]
nt2 --> bd1
Figure 6: The trick to keeping existing assets is to fix the boundary and leave no responsibility ambiguous.
3.5 Cases for Microsoft 365 / Graph
If the Excel files live on OneDrive / SharePoint from the start and you want to share them from web or mobile apps, the Microsoft Graph Excel API is also an option.
It is not, however, a general-purpose answer for casually mass-producing arbitrary files on a local PC. Permissions, storage location, sessions, and operations all assume M365 from the outset.
3.6 Does It Need to Be Excel at All?
If the requirement is a table people will work with afterwards, choosing Excel is natural. But for requirements like these, another format is often the more straightforward choice.
- Printed and filed -> PDF
- Imported into another system -> CSV / TSV / JSON
- Only needs to be viewable in a browser -> HTML / a web page
- Aggregation and visualization are the main goal -> BI or a dashboard
flowchart TB
accTitle: Checking whether it has to be Excel
accDescr: A diagram showing that Excel is a natural choice when the report is a table people work with afterwards, but that another format is often more straightforward when the goal is printing and filing, importing into another system, viewing in a browser, or aggregation and visualization.
ne1{"Is it a table people work with later"}
ne1 -->|"They do"| ne2["Excel is a natural choice"]
ne1 -->|"They do not"| ne3["Consider another format"]
ne3 -.-> ne4["PDF / CSV / web page / BI and so on"]
Figure 7: Work back from the purpose to the output format instead of defaulting to Excel.
4. Comparing the Approaches
Putting the differences side by side in one table gives this.
| Approach | Excel installation | Suitability for unattended execution | Reuse of existing layout | Compatibility with Excel-specific features | Best suited for |
|---|---|---|---|---|---|
| COM automation | Required | Weak | Strong | Very strong | Output on the user’s PC, existing .xlsm, final PDF conversion |
Direct .xlsx generation |
Not required | Strong | Medium | Medium | Batch jobs, servers, high-volume output |
| Template-based injection | Not required (at output time) | Strong | Strong | Medium to strong | First candidate for most business reports |
| Coexisting with existing VBA | Depends on usage | Weak to medium | Very strong | Strong | Phased migration, leveraging existing assets |
| Graph Excel API | Assumes M365 | Medium | Medium | Medium | Shared use on OneDrive / SharePoint |
5. Choosing by Common Requirements
5.1 Output on the User’s PC, Then Edited Directly
In this case, template plus direct generation is a very strong choice. Users open the output in Excel afterwards, so the final editing can simply be left to Excel.
5.2 High-Volume Generation in a Nightly Batch or Service
If a nightly batch is involved, it is safer to start by ruling out COM automation.
Move generation to direct .xlsx creation, and if needed, let users open the files in Excel afterwards.
flowchart TB
accTitle: How to proceed with a nightly batch
accDescr: A diagram showing that for high-volume generation in a nightly batch or service the safe path is to first rule COM automation out of the candidates, move generation to direct xlsx creation, and let users open the files later if needed.
nb1["Generate in volume in a nightly batch"] --> nb2["Rule out COM automation first"]
nb2 --> nb3["Move to direct xlsx generation"]
nb3 -.-> nb4["Users open the file later if needed"]
Figure 8: For unattended execution, it is safer to start the design by subtracting.
5.3 Leveraging Existing .xlsm / VBA
If existing assets are still alive, the realistic path is to keep the .xlsm as the template and perform only the data injection from outside.
5.4 Large Detail Row Counts
The limit for a single Excel sheet is 1,048,576 rows by 16,384 columns. When detail data is large, decide these points first.
- Above how many rows do you split into multiple sheets?
- Above how many records do you split into multiple files?
- Would CSV be the more natural choice in the first place?
flowchart TB
accTitle: What to decide first when detail data is large
accDescr: A diagram showing that because a single Excel sheet has row and column limits, large detail data calls for deciding up front at how many rows to split sheets, at how many records to split files, and whether CSV would be more natural in the first place.
lg1["Detail data is large"] --> lg2["A single sheet has limits"]
lg2 --> lg3["Decide the sheet split threshold"]
lg2 --> lg4["Decide the file split threshold"]
lg2 -.-> lg5["Reconsider whether CSV is more natural"]
Figure 9: Decide the splitting policy up front instead of thinking about it only after hitting the limit.
6. An Architecture That Is Easy to Recommend in Practice
What proves robust in practice is an architecture split into four layers.
| Layer | Responsibility | What it does NOT do |
|---|---|---|
| ReportModel | Shapes the values the report needs | Knows nothing about cell addresses |
| Template | Holds appearance, formulas, print settings, charts | Knows nothing about the DB or business logic |
| Binder | Writes data into named ranges / tables | Brings in no business decisions |
| Finisher | Runs VBA / COM / PDF conversion if needed | Does no source-data retrieval |
The nice thing about this split is that the code becomes much less likely to be dragged around by Excel’s appearance.
6.1 What Flows Between the Layers
What matters is what crosses the layer boundaries. Once that is settled, layout changes and business-logic changes can proceed independently.
flowchart LR
DB[("DB / API / files")] -->|"raw data"| RM["ReportModel<br/>holds only the values<br/>the report needs, already shaped"]
TP["Template<br/>xlsx or xlsm<br/>appearance, formulas, print settings"] -->|"copied workbook"| BD
RM -->|"name and value pairs"| BD["Binder<br/>writes values into<br/>named ranges and tables"]
BD -->|"workbook with values"| FN["Finisher<br/>PDF conversion, VBA calls<br/>only when needed"]
FN -->|"deliverable"| OUT["xlsx / xlsm / PDF"]
BD -.->|"done here when<br/>no Finisher is needed"| OUT
Figure 10: Only name and value pairs flow between the four layers, and cell addresses never leave the Binder.
Only name and value pairs cross the boundary, and cell addresses never travel outside the Binder. Once details of the template start reaching as far as the ReportModel, the design is already beginning to break down.
6.2 A Minimal Implementation
Here is template injection written with ClosedXML. On the template Invoice.xlsx side, define the named ranges Rpt_Title, Rpt_IssuedOn, Rpt_CustomerName, and Rpt_DetailRows in advance.
// C# / .NET 8 + ClosedXML (MIT license)
// dotnet add package ClosedXML
using ClosedXML.Excel;
// The ReportModel equivalent. It holds no cell addresses at all
var rows = new (string Code, string Name, int Qty, decimal UnitPrice)[]
{
("A-100", "Ball bearing", 12, 480m),
("A-205", "Shaft", 3, 12800m),
("B-010", "Mounting bracket", 30, 260m),
};
const string TemplatePath = @"templates\Invoice.xlsx";
string outputDir = "output";
Directory.CreateDirectory(outputDir);
// Do not build the file name from a timestamp down to the second. In a batch that
// processes one record at a time, the moment two records land in the same second
// they get the same name, and the one saved later overwrites the earlier report.
// The nasty part is that nobody notices the loss. Always include a business
// identifier that uniquely determines the report (here, the invoice number).
string invoiceNo = "INV-2026-000123"; // received from the caller
string outputPath = Path.Combine(outputDir, $"Invoice_{invoiceNo}.xlsx");
// 1. Open the template. The save uses a different name, so the template itself is untouched
using var workbook = new XLWorkbook(TemplatePath);
// 2. Header values go into named cells. The point is that no cell address appears in the code
workbook.Cell("Rpt_Title").Value = "Invoice";
workbook.Cell("Rpt_IssuedOn").Value = DateTime.Today; // stored as a value. The display format lives in the template
workbook.Cell("Rpt_CustomerName").Value = "Sample Corporation";
// 3. Use the named range as the entry point for detail rows and write by relative position within it
var detail = workbook.Range("Rpt_DetailRows");
if (rows.Length > detail.RowCount())
{
// More rows than the template provides. Do not truncate silently: stop here
throw new InvalidOperationException(
$"There are {rows.Length} detail rows, but Rpt_DetailRows in the template is {detail.RowCount()} rows. " +
"Increase the row count in the template, or split across sheets.");
}
for (int i = 0; i < rows.Length; i++)
{
var row = rows[i];
detail.Cell(i + 1, 1).Value = row.Code; // 1-based relative position within the range
detail.Cell(i + 1, 2).Value = row.Name;
detail.Cell(i + 1, 3).Value = row.Qty;
detail.Cell(i + 1, 4).Value = row.UnitPrice;
}
// 4. Save under a different name. The template stays a read-only asset.
// Write to a temporary file in the same folder and rename it to the real name only
// once the write completes. Writing straight to outputPath means that a mid-write
// failure (disk full, an inconsistent workbook) leaves a half-written .xlsx sitting
// there under the business file name
string tempPath = Path.Combine(
Path.GetDirectoryName(outputPath) ?? string.Empty, // keep the rename within the same volume
$".{Path.GetFileName(outputPath)}.{Guid.NewGuid():N}.tmp");
try
{
using (var stream = new FileStream(tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
workbook.SaveAs(stream);
}
// IOException if the name already exists. A duplicate name is not overwritten
// silently: you find out on the spot (same policy as FileMode.CreateNew)
File.Move(tempPath, outputPath);
}
catch
{
// Leave no trace on failure. A leftover file blocks the next run from retrying under the same name
try { File.Delete(tempPath); } catch (IOException) { }
throw;
}
Console.WriteLine($"Written: {outputPath}");
Five things this code is careful about.
- No cell address appears in the code. The only injection targets are named ranges. Add one row to the template and this code stays exactly as it is
- The template is never overwritten. The workbook that was opened is always saved under a different name
- Dates and numbers go in as values. Format them into strings on the way in and Excel can no longer sort or aggregate them
- Overflowing detail rows stop with an exception. Truncating silently is the worst way a report can break. This is where the policy from 5.4 lands in the implementation
- The real name is attached only after the write finishes.
SaveAscan fail after it has already started writing. The disk filled up, the share dropped, the workbook contents were invalid - all of these happen. Write straight tooutputPathand what is left behind is a truncated file carrying the perfect nameInvoice_INV-2026-000123.xlsx. People judge by the name, so nobody can tell it apart from a finished report. Worse,FileMode.CreateNewrejects an existing file, so rerunning also stops, this time because the file already exists. Write to a temporary file and rename it withFile.Move, and that name appears only once the contents are complete. The rename target is in the same folder because aMoveacross volumes becomes a copy, which can itself be cut short
Even if you hold amounts as decimal, they turn into double-precision floating point the moment they enter the Excel file format. If the rounding rule matters to the business, it is safer to produce already-rounded values in the ReportModel instead of leaving it to Excel formulas.
Using an .xlsm as the template follows the same flow, with the output extension matched to .xlsm. The arrangement for injecting data while keeping macros is the one in 3.4.
flowchart TB
accTitle: The save flow through a temporary file
accDescr: A diagram showing the flow in which the workbook is written out in full to a temporary file in the same folder, renamed to its real name with File.Move, and the temporary file is deleted on failure, which prevents a truncated file from being left behind under a business file name.
sv1["Write to a temporary file in the same folder"] --> sv2{"Did the write complete"}
sv2 -->|"Success"| sv3["Rename to the real name with File.Move"]
sv2 -->|"Failure"| sv4["Delete the temporary file and propagate the exception"]
sv3 -.-> sv5["The name appears only when the contents are complete"]
Figure 11: A business file name is only ever given to a finished file.
7. Pitfalls
7.1 Do Not Turn Cell Addresses into Business Specifications
Once Cells[12, 7] starts representing a business rule, a layout change becomes a spec change.
Code lasts longer when it touches the report through named ranges and table names.
flowchart TB
accTitle: Do not turn cell addresses into business specifications
accDescr: A diagram showing that once cell addresses start representing business rules a layout change becomes a spec change, so code that touches the report through named ranges and table names lasts longer.
ad1["Cell addresses represent business rules"] --> ad2["A layout change becomes a spec change"]
nm1["Go through named ranges and table names"] --> nm2["The code survives a layout change"]
Figure 12: Touching the report by name rather than by address decides how long the reporting code lives.
7.2 Do Not Use Merged Cells as Data Entry Points
Merged cells are a presentation feature. Using them as injection targets makes adding rows and computing ranges easy to get wrong.
7.3 Do Not Fill Numbers and Dates as Pre-Formatted Strings
It is more natural to store values as values and push appearance into cell formats.
7.4 Do Not Let Template Changes Go Unmanaged
A template is not code, but in practice it is the specification itself. The safe approach is to treat it as subject to version control, diff review, and code review.
7.5 If You Use COM, Do Not Underestimate Bitness and Lifetime Management
With COM automation and VBA integration, 32-bit / 64-bit differences, Excel process cleanup, file locks, and differences across user environments quietly take their toll.
8. Summary
Excel report output looks like a one-line topic - output to Excel - but in reality several decisions need to be made up front.
- Are you driving the Excel application?
- Are you building an Excel file?
- Does it run on the user’s PC, or unattended?
- Will existing VBA or
.xlsmfiles be kept? - Is the final deliverable Excel, or PDF / CSV?
As a first candidate in practice, template plus direct generation is very strong. Adding reuse of existing VBA or final Excel processing on the user’s PC to that as needed tends to come together well.
flowchart TB
accTitle: How to build an architecture that comes together
accDescr: A diagram showing that in practice a template plus direct generation belongs at the center as the first candidate, and that reuse of existing VBA and final Excel processing on the user PC are added on top only as needed.
sm1["Put template plus direct generation at the center"] --> sm2["Add reuse of existing VBA if needed"]
sm1 --> sm3["Add final processing on the user PC if needed"]
Figure 13: Pick one central approach and add only the exceptions, and the design comes together.
9. References
Listed in reading order.
9.1 Read Before Deciding on an Approach
- Considerations for server-side Automation of Office - the source for the statement in 3.1 that server-side Office Automation is neither recommended nor supported. This is the citation used most often to justify the choice of approach
- Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment - the exception to the above: the conditions that apply in an M365 RPA environment
- Excel specifications and limits - the list of limits, including the 1,048,576 rows by 16,384 columns cited in 5.4
9.2 Used for Direct Generation
- About the Open XML SDK for Office - the foundation for assembling
.xlsxwithout Excel - ClosedXML - the wrapper used in the code example in 6.2. MIT license
- NPOI - an option when you also need to handle the old
.xlsformat. Apache License 2.0 - EPPlus - be sure to check the license terms for version 5 and later before adopting it
9.3 When Working on M365 / SharePoint
- Overview of the Excel workbooks and charts API - Microsoft Graph
- Access OneDrive and SharePoint via Microsoft Graph API
9.4 For Going Deeper on Large Workbooks
- How to: Copy a worksheet with SAX (Simple API for XML) - the technique for handling workbooks too large to fit in memory with the Open XML SDK. It is normally unnecessary within the scope of template injection
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
What Is an OLE Object? — How Embedding and Linking Work and the Pitfalls in Business Documents
An OLE object is what embeds an Excel table in Word. Learn embedding vs. linking, compound files, In-Place Activation, broken links, bloa...
Why EXCEL.EXE Processes Remain After C# Excel COM Automation — Reference Release Patterns and the Replacement Decision
A practical look at why EXCEL.EXE processes remain running after automating Excel from C# via Microsoft.Office.Interop.Excel, explained t...
What Is VBA? - Its Constraints, Its Future, When to Replace It, and Realistic Migration Patterns
The basics and constraints of VBA, its future, when it should be replaced, and a realistic way to migrate Excel macros and in-house tools...
WinRT Is COM — IInspectable, .winmd, Language Projections, and Why WinUI Still Rests on a Binary Contract
WinRT is not a managed runtime but an ABI built on COM plus .winmd metadata and language projections. Covers IUnknown vs. IInspectable, H...
How the Clipboard and Drag & Drop Work — Handling OLE Data Transfer Correctly in Business Apps
Why Excel pastes break and paste fails once the source closes: clipboard formats, delayed rendering, OLE drag and drop, and clipboard his...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
How to integrate Excel report output into a Windows app or business system is essentially a Windows app development topic, so it pairs well with Windows App Development.
Technical Consulting & Design Review
If you want to sort out when to use COM automation, Open XML, templates, and existing VBA, taking your runtime environment and operational constraints into account, this works well as a technical consulting / design review engagement.
Frequently Asked Questions
Common questions about the topic of this article.
- Should I choose COM automation or direct file generation for Excel report output?
- The first thing to look at is not the library name but whether you are driving the Excel application or building an Excel file. For most business reports it is more natural to assemble an Excel file than to operate Excel, and if users will edit the report afterwards, a template plus direct .xlsx/.xlsm generation is the first candidate. Only when you truly need the behavior of the Excel application itself does it make sense to use COM automation, limited to attended execution on the desktop.
- Is it acceptable to use Excel COM automation on a server or in a nightly batch?
- It is safer to avoid it. Microsoft itself neither recommends nor supports Office Automation from an unattended server or service. COM automation requires Excel to be installed and brings problems of its own: process lifetime, file locks, dialogs, bitness, and user-profile dependencies. For nightly batches and high-volume output, lean on direct .xlsx generation and, if needed, let users open the files in Excel afterwards.
- Can I build report output while keeping my existing .xlsm files and VBA assets?
- Yes. If the existing assets are still alive, the realistic path is not to rebuild everything at once but to keep the .xlsm as a template and perform only the data injection from outside. A practical split leaves the report UI and final formatting in VBA while moving heavy computation and DB, HTTP, and business logic to the C#/.NET side. The important part is not leaving responsibilities ambiguous, so fix the boundary between the two with named ranges, tables, and public interfaces.
- What pitfalls should I avoid when implementing Excel reports?
- First and foremost, do not turn cell addresses into business specifications: code that touches the report through named ranges and table names, rather than addresses like Cells[12, 7], lasts much longer. It also helps to avoid merged cells as injection targets since they exist for appearance only, to store numbers and dates as real values and push appearance into cell formats, and to treat templates as version-controlled, reviewable specifications. And because a single Excel sheet caps at 1,048,576 rows by 16,384 columns, decide your sheet-split and file-split policy up front when detail data is large.