Replies: 1 comment
|
While it's not fully finished you can probably use PSWriteOffice and/or OfficeIMO if you prefer the C# route. It's not yet fully announced on PowerShell community but PSWriteOffice can do Word, Excel, PowerPoint, PDF, Markdown or even ODF/RTF/HTML/CSV/Visio and all other things you can imagine doing. It's also materially faster than ImportExcel and a lot more functional. I'm still finalizing the shape of pswriteoffice, and doing some changes but effectively one module to rule them all. I'm open on feedback on pswriteoffice shape if something is missing. OfficeIMO is 96 projects or so, and PSWriteOffice can use all that.
Maybe this will solve your dependency problem and will give you Word development and huge featureset with ability to save as pdf, markdown and excel, csv etc |
Uh oh!
There was an error while loading. Please reload this page.
Adding an Excel export option to AsBuiltReport is something I've wanted to explore for a long time. The PScribo module currently handles all formatting within the framework, but it appears to no longer be actively maintained (I'll leave that for @iainbrighton to confirm). While I'm familiar with ImportExcel and its capabilities, I've been reluctant to introduce another module dependency into the framework.
AI tooling has now made it practical to revisit this idea. I've been using Claude Code heavily over the past 12 months for code development and have used it to assess the feasibility of adding Excel export to ABR. The plan is to build a proof of concept in two stages. First, use ImportExcel to validate that Excel output can be generated from existing report modules, then develop a native PScribo plugin so that PScribo remains the sole formatting tool.
The plan below was drafted with Claude's assistance. A proof of concept will begin soon and feedback on its requirements and direction is welcomed as it progresses.
Important
This is not a commitment to delivering this functionality. This is purely a discussion to explore the idea.
AsBuiltReport — XLSX Export Feasibility & Implementation Plan
Version: 0.1.0
Date: 2026-06-04
Scope: Add Excel (
.xlsx) export to the AsBuiltReport framework using the ImportExcel PowerShell module, where document sections map to worksheets.Modules analysed:
AsBuiltReport.Core(v1.6.2), PScribo (v0.12.0 dev / v0.11.1 min), and report modulesVMware.vSphere,Microsoft.Azure,NetApp.ONTAP,Veeam.VBR.---
1. Verdict
Adding XLSX export is feasible and low-risk to the existing Word/HTML/Text outputs, because every report already builds a fully-populated, traversable PScribo document object model in memory before export. We can walk that object tree and emit a workbook with ImportExcel without touching any report module.
Worksheet mapping is settled: each Heading2 section becomes its own worksheet. This is the correct rule because every report emits exactly one Heading1 section (the target name — vCenter, tenant, cluster, backup server), while the meaningful "chapters" a user expects as tabs (Clusters, Datastores, VMHosts, Networks, Subscriptions, …) are Heading2 sections. Heading1 is carried as a workbook title / "Report Info" sheet. Implemented via a configurable
WorksheetHeadingLeveldefaulting to 2. See §4.Everything else — table extraction, multi-table stacking, health-check cell colouring — maps cleanly.
---
2. How report generation works today (the integration point)
New-AsBuiltReport(AsBuiltReport.Core/Src/Public/New-AsBuiltReport.ps1) does the following:Documentkeyword (lines ~524 and ~572). The scriptblock dot-sources the style script and invokes the report module'sInvoke-AsBuiltReport.<Module>, which emitsSection/Table/Paragraphcalls.Document { … }call returns the in-memory document object into$AsBuiltReport.$AsBuiltReportis the hook. It is a fully-builtPScribo.Documentwith a populated.Sectionstree beforeExport-Documentruns. We can pass that same object to a new Excel exporter. No report module changes are required, ever.The
-Formatparameter is constrained here:---
3. Feasibility findings (verified against source)
3.1 The document object model is traversable and complete
PScribo.Document.Sections(ArrayList of children),.Name,.OptionsPScribo.Section.Name(heading text),.Level(0 = H1, 1 = H2, 2 = H3…),.Number("1.2.3"),.Style,.Sections(recursive children)PScribo.Table.Name,.Columns(ordered headers),.Rows(ArrayList of PSCustomObject),.IsList,.IsKeyedList,.ListKey,.CaptionPScribo.ParagraphPScribo.PageBreak,.BlankLine,.LineBreak,.Image,.TOC,.ListReferenceHeading level is derived (
Level = Number.Split('.').Count - 1), so a recursive walk can classify any node as H1/H2/H3/… reliably. (Source:PScribo/Src/Private/New-PScriboSection.ps1,Invoke-PScriboSectionLevel.ps1.)3.2 Table data is fully preserved as objects
Each
PScribo.Table.Rowsentry is aPSCustomObjectwhose properties are the column values — i.e. exactly the shape ImportExcel'sExport-Excelconsumes. We do not need to parse rendered text. Three layouts exist and each needs handling:IsList = $false): rows × columns → directExport-Excel.IsList = $true): vertical key/value (one source object). → emit as a 2-column Property/Value block, or transpose.IsKeyedList = $true,.ListKey): grouped key/value. → group blocks.(Source:
PScribo/Src/Public/Table.ps1,New-PScriboTableRow.ps1,Plugins/Text/Out-TextTable.ps1.)3.3 Health-check cell styling is available as metadata (verified)
Set-Style -Style Critical/Warning/Info/OK \[-Property Col1,Col2]attaches note-properties to each row object:\_\_Style— row-level style name<ColumnName>\_\_Style— per-cell style nameConfirmed in
PScribo/Src/Private/New-PScriboTableRow.ps1(lines 43–77) andSrc/Public/Set-Style.ps1(lines 44–45). The Word/HTML plugins read these to colour cells; our Excel exporter can read the identical properties and apply background fills via ImportExcel. The Text plugin simply excludes\*\_\_Stylecolumns — we do the same for data, but inspect them for formatting.The style → colour map already exists in
AsBuiltReport.Core/AsBuiltReport.Core.Style.ps1:CriticalFEDDD7WarningFFF4C7InfoE3F5FCOKDFF0D0TableDefaultHeading072E58, textFAFAFAThese hex values can be lifted directly into ImportExcel
-BackgroundColor/ conditional formatting, giving the spreadsheet the same visual health-check cues as the Word report.3.4 Content that does NOT map to cells
Roughly 40–60% of report content is tabular; the rest is paragraphs, blank lines, images/diagrams (NetApp & Veeam are diagram-heavy), and the cover page/TOC. These have no spreadsheet representation. Strategy in §4.4.
---
4. Design
4.1 The worksheet-mapping decision (most important)
Because reports have a single H1, choose one of:
WorksheetHeadingLevel, default 2)Recommendation: implement Option C with a default of level 2 (so out-of-the-box behaviour = Option A). This honours the spirit of the brief ("a section per worksheet") while producing a usable workbook, and lets dense reports push to level 3 if desired.
4.1a Multi-system output: one workbook per system (recommended default, not a hard requirement)
Report modules loop over
-Targetinternally and emit one Heading1 section per system within a single document object. Verified inInvoke-AsBuiltReport.VMware.vSphere.ps1:So a two-vCenter run yields a document whose
.Sectionscontains twoLevel 0sections. Both behaviours are feasible — the choice is cosmetic, not technical:.xlsx, named after that system, with that system's H2 sections as tabs. More logical for per-system analysis and keeps tab lists short.<System> - <H2>).Since it isn't a hard requirement, treat this as a switch (e.g.
-ExcelPerSystem, default on) so either is trivially available. Note Word/HTML/Text always produce a single combined file (PScribo's model); per-system splitting is Excel-only and lives in Core (§4.5).File naming (per-system mode):
<FileName>.xlsx— unchanged from today's naming ($ReportConfig.Report.Name, plus-Timestampsuffix if set).<FileName> - <SystemName>.xlsx,<SystemName>= sanitised H1 text (invalid filename chars stripped, de-duplicated on collision);-Timestampstill applies per file.4.2 Heading-level disposition (H1–H6) — how the tree flattens
Report modules use heading levels 1–6. They map to Excel as follows (default
WorksheetHeadingLevel = 2):.xlsxper H1 (= per system; see §4.1a).H3–H6 never create new tabs or files; they are flattened into the H2 worksheet. For each H2, the exporter recurses the entire subtree, collects every
PScribo.Tabledescendant in document order, and stacks them vertically down the sheet. Depth (H3 vs H4 vs H5 vs H6) is preserved visually, not structurally:.Name, prefixed with its.Number(e.g.1.4.2 SCSI LUN Info, which already encodes full hierarchy).Style.ps1heading palette (Heading 3=395879,Heading 4=958026,Heading 5=009684,Heading 6=009683).NO TOC HeadingNvariants are treated by their numeric level (the "NO TOC" only affected Word/HTML TOC inclusion).\*\_\_Stylehelper columns from the visible data; use them only for cell fill colour (§3.3).Example — vSphere Datastores (H2) tab at InfoLevel 3+:
Optional (Phase 3): map heading depth to Excel row outline levels, making H3–H6 bands collapsible/expandable groups — a natural fit for the hierarchy, straightforward with ImportExcel.
If the knob is changed (
WorksheetHeadingLevel = N): H1 always remains the file split; levelNbecomes the tab boundary; levels between H1 andNqualify (prefix) the tab name; levels belowNbecome the in-sheet banners above. At the defaultN = 2this reduces to the table above.This preserves the report's hierarchy as readable bands within a single tab, rather than exploding into dozens of micro-tabs.
4.3 Worksheet naming & sanitisation
Excel tab constraints must be enforced centrally:
\[ ] : \* ? / \\; cannot be blank; must be unique (append(2),(3)on collision); cannot beHistory.Section.Name; keep a "section number → tab name" map for an optional index/TOC sheet with hyperlinks.4.4 Non-tabular content
Add-ExcelImage-style logic, but out of MVP scope.4.5 Architecture: the exporter lives in Core and uses ImportExcel
A Core-resident exporter that consumes the
$AsBuiltReportdocument object directly and renders with ImportExcel is the right home. It is self-contained, shippable without touching any other repo, and the natural place for AsBuiltReport-specific semantics (H2→worksheet, optional one-workbook-per-system, system-based filenames) that have no equivalent in generic PScribo. This is the basis for the MVP.---
4.6 Recommended MVP scope
Objective: prove the concept end-to-end —
New-AsBuiltReport … -Format Excelproduces a valid, openable.xlsxfrom a real report run, with sections as worksheets and report data in cells, using ImportExcel. Everything beyond "it demonstrably works" is deferred.In scope (minimum to prove functionality):
Exceladded to the-FormatValidateSet, wired intoNew-AsBuiltReportExport-AbrExcelDocumentrendering with ImportExcel\*\_\_Stylehelper columns dropped from data<FileName> - <System>.xlsx-Format Excelis usedExplicitly out of scope for the MVP (deferred to later phases per §7):
\_\_Stylemetadata (Phase 2) — the metadata read is proven feasible (§3.3) but not required to demonstrate export.RequiredModulesdependency / version pinning (decision deferred — §8.2).en-USonly and log the rest as follow-up debt.WorksheetHeadingLevelUI — MVP can hard-default to 2 and expose the knob later.Acceptance criteria (definition of "proven"):
New-AsBuiltReport -Report <X> -Target <t> -Format Excelwrites a.xlsxthat opens cleanly in Excel with no repair prompt.-Format Word,Excelworks in one run; no regression to Word/HTML/Text.-Format Excelfails with a clear, actionable message (not a crash).Suggested validation target:
AsBuiltReport.System.Resources. This is the best MVP report module because it reports on the local machine — no remote infrastructure, no lab, no real credentials — so it runs fast and repeatably on the developer's own box. Verified structure (Invoke-AsBuiltReport.System.Resources.ps1+Src/Private):foreach ($System in $Target) { Section -Style Heading1 "$($System.ToUpper())" { … } }— the standard one H1 per system pattern, so it also exercises the per-system file split with multiple targets.Invoke-Command/CimSession/PSSession/-ComputerNameusage — purely local data gathering.Validate in two layers:
PScribo.Documentfixture in the unit test — one Document → 1–2 H1s → a few H2s, each with a standard table, a list table, and a nested H3 table including\_\_Stylecells — then runExport-AbrExcelDocumentand re-open the result withImport-Excel/Open-ExcelPackageto assert worksheet count, tab names, headers, and values. Needs no live target (§5.6).AsBuiltReport.System.Resourcesagainstlocalhost(single system) and against two hostnames (multi-system) to confirm a real document renders, splits into per-system files, and opens cleanly in Excel.Effort: ~2–3 days (= Phase 1 in §7).
5. Implementation plan (Core-resident, ImportExcel)
5.1 New private function — the exporter
File:
AsBuiltReport.Core/Src/Private/Export-AbrExcelDocument.ps1The function returns an array of
FileInfo— one per system — so the entry point can report and (optionally) email every generated workbook.Helpers (private):
ConvertTo-AbrExcelWorksheetName(sanitise/dedupe),Get-AbrExcelStyleColor(style name → hex from the Style.ps1 palette),Get-AbrPScriboTableDescendant(recursive table collector).5.2 Wire
-Format Excelinto the entry pointFile:
AsBuiltReport.Core/Src/Public/New-AsBuiltReport.ps1Export-Documentcall (~line 622), branch: PScribo handles Word/HTML/Text; ifExcelis in$Format, callExport-AbrExcelDocument -Document $AsBuiltReport -Path $OutputFolderPath -FileName $FileName, which returns one file per system. Append all returned files to$Documentso the existing-SendEmailpath attaches every per-system workbook, and emit oneOutputFoldersuccess line per file.$FormatminusExcelfor the PScribo call so PScribo never receives an unknown format.5.3 Declare the dependency — soft / load-on-demand (decided for MVP)
ImportExcel is a soft dependency for the MVP: it is not added to
AsBuiltReport.Core.psd1'sRequiredModules, so users who never produce Excel are unaffected. Instead, it is loaded only when needed:New-AsBuiltReport(or at the top ofExport-AbrExcelDocument), whenExcelis in$Format, attempt to load it and fail clearly if absent:-Format Excel.5.4 Module loading
AsBuiltReport.Core.psm1already dot-sources everything inSrc/Private/, so the new function loads automatically. No psm1 change unless we choose to promote it to global scope (not needed — it's only called from Core).5.5 Localization
Add strings (export progress, "Excel module not found", sheet-name truncation warning) to all locale files under
Language/\*/New-AsBuiltReport.psd1(minimumen-US), per the project's localization rule.5.6 Tests
Tests/Unit/Export-AbrExcelDocument.Tests.ps1: build a small syntheticPScribo.Document(Document → 1×H1 → 2×H2, each with a table incl.\_\_Stylecells), run the exporter, then re-open withImport-Excel/Open-ExcelPackageand assert: worksheet count = H2 count, tab names sanitised/unique, header row present, data values intact,\_\_Stylecells got the expected fill.Tests/Unit/New-AsBuiltReport.Tests.ps1: assertExcelis now a valid-Formatvalue.Tests/Quality: ensure new file is UTF-8, has comment-based help, passes PSScriptAnalyzer, uses approved verbs (Export-✓).5.7 Docs & changelog
CHANGELOG.md:### Added — Excel (XLSX) export via -Format Excel (ImportExcel). Branch offdev; PR targetsdev.WorksheetHeadingLevelknob, and the "what doesn't export" note (images/prose).---
6. Worked example — what the user gets
Run:
New-AsBuiltReport -Report VMware.vSphere -Target vcenter01 -Credential $c -Format Word,Excel -EnableHealthCheckProduces one Word doc plus
VMware vSphere As Built Report.xlsxwith tabs:Multi-system run:
… -Target vcenter01,vcenter02 -Format Word,Excelproduces one combined Word doc (both vCenters as chapters) but two workbooks:each containing only that vCenter's worksheet tabs.
FEDDD7(Critical), 75–90%FFF4C7(Warning) — identical thresholds to the Word health check, read from the\_\_Stylemetadata.---
7. Effort & phasing
-Format Excel, one workbook per Heading1/system, H2→worksheet (level configurable), standard + list tables, header styling, freeze/autosize, sheet-name + filename sanitisation, Report Info sheet, manifest dependency\_\_Stylemetadata → cell/row fills + Index sheet with hyperlinksOut-ExcelDocumentpluginTotal MVP→shippable: ~1 working week.
---
8. Risks, edge cases & decisions for you
WorksheetHeadingLevel) with the default set to level 2, so out-of-the-box each H2 becomes its own tab and H1 (the target name) is carried as the title / "Report Info" sheet.-Format Excelis requested, with a clear actionable error if missing; it is not added toRequiredModules, so Word-only users are unaffected (§5.3). Promotion to a hard, version-pinnedRequiredModulesentry is deferred to GA.3a. Per-system filenames — system (H1) names used in
<FileName> - <System>.xlsxmust be stripped of invalid path chars (\\ / : \* ? " < > |) and de-duplicated on collision; guard against over-long paths. Single-system runs keep the plain filename..Sections,.Level,.Rows,.Columns,.IsList,\*\_\_Style) of the already-built document object, and adds nothing to PScribo itself.---
9. Bottom line
Export-AbrExcelDocument) invoked fromNew-AsBuiltReport; zero changes to any report module and no fork of PScribo.All reactions