Add support for inline XML format data in Update-FormatData#27713
Add support for inline XML format data in Update-FormatData#27713eevaal wants to merge 8 commits into
Conversation
Addresses PowerShell#7845 by allowing Update-FormatData to accept format data directly as XML strings using the -PrependFormatData and -AppendFormatData parameters. - Adds FormatDataXml property and constructor to SessionStateFormatEntry. - Updates TypeInfoDataBaseLoader to parse format data XML from strings without disk I/O. - Plumbs the inline XML capabilities into UpdateFormatDataCommand through TypeInfoDataBaseManager. - Adds unit tests covering new parameters.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
- Add blank lines before doc comments - Group fields before properties - Add Justification to SuppressMessage attributes - Use 'Gets or sets' in property summaries
There was a problem hiding this comment.
Pull request overview
This PR extends Update-FormatData to accept PS1XML formatting configuration as in-memory XML strings, enabling module authors to embed format data without shipping external .ps1xml files (addressing #7845).
Changes:
- Adds new
Update-FormatDataparameters-PrependFormatData/-AppendFormatDatafor inline format XML input. - Introduces
SessionStateFormatEntry.FormatDataXmland plumbing throughTypeInfoDataBaseManager/loader to load format data from XML strings. - Adds Pester coverage intended to validate loading views from inline XML.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 |
Adds tests for loading format views from inline XML strings. |
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs |
Adds cmdlet parameters and populates InitialSessionState.Formats with inline XML entries. |
src/System.Management.Automation/engine/InitialSessionState.cs |
Adds FormatDataXml storage on SessionStateFormatEntry and cloning support. |
src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs |
Adds support for carrying inline format XML through PSSnapInTypeAndFormatErrors. |
src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs |
Adds LoadXmlString(...) to parse/load formatting data from an XML string. |
src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs |
Hooks inline XML loading into the format database load path. |
| var doc = new System.Xml.XmlDocument(); | ||
| doc.LoadXml(PrependFormatData[i]); | ||
| newFormats.Add(new SessionStateFormatEntry(doc)); |
| var doc = new System.Xml.XmlDocument(); | ||
| doc.LoadXml(appendFormatDataItem); | ||
| newFormats.Add(new SessionStateFormatEntry(doc)); |
| else if (FormatDataXml != null) | ||
| { | ||
| var doc = new XmlDocument(); | ||
| doc.LoadXml(FormatDataXml); | ||
| entry = new SessionStateFormatEntry(doc); |
| internal PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml) | ||
| { | ||
| this.psSnapinName = psSnapinName; | ||
| FormatDataXml = formatDataXml; | ||
| Errors = new ConcurrentBag<string>(); | ||
| } |
Security (High): Replace unsafe XmlDocument.LoadXml() with XmlReader using DtdProcessing.Prohibit and XmlResolver=null to prevent XXE and DTD entity expansion attacks. Fixed in Update-TypeData.cs (Prepend + Append) and InitialSessionState.Clone(). Tests (Medium): Fix variable scoping in Pester tests by passing xmlContent via param()/AddArgument() instead of string interpolation.
ad795c7 to
9b555f4
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs:52
- The new PSSnapInTypeAndFormatErrors constructor takes a boolean parameter (isXml) that is not used. Consider either removing the parameter (and updating call sites) or renaming it to '_' to make the intentional discard explicit.
internal PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml)
{
this.psSnapinName = psSnapinName;
FormatDataXml = formatDataXml;
Errors = new ConcurrentBag<string>();
| public SessionStateFormatEntry(XmlDocument xmlDocument) | ||
| : base("*") | ||
| { | ||
| if (xmlDocument == null) | ||
| { |
| It "Should load inline XML using AppendFormatData" { | ||
| $null = $ps.AddScript('param($xml) Update-FormatData -AppendFormatData $xml').AddArgument($xmlContent) | ||
| $ps.Invoke() | ||
| $ps.HadErrors | Should -BeFalse | ||
| $ps.Commands.Clear() | ||
| $null = $ps.AddScript("Get-FormatData InlineTestType") | ||
| $formatData = $ps.Invoke() | ||
| $formatData | Should -HaveCount 1 | ||
| $formatData.TypeNames | Should -BeExactly "InlineTestType" | ||
| $formatData.FormatViewDefinition.Name | Should -BeExactly "InlineTest" | ||
| } | ||
| } | ||
| } |
| if (ShouldProcess("Inline XML", action)) | ||
| { | ||
| var doc = new System.Xml.XmlDocument() { XmlResolver = null }; | ||
| var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null }; | ||
| using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(PrependFormatData[i]), settings)) | ||
| { | ||
| doc.Load(reader); | ||
| } | ||
|
|
||
| newFormats.Add(new SessionStateFormatEntry(doc)); | ||
| } |
| if (ShouldProcess("Inline XML", action)) | ||
| { | ||
| var doc = new System.Xml.XmlDocument() { XmlResolver = null }; | ||
| var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null }; | ||
| using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(appendFormatDataItem), settings)) | ||
| { | ||
| doc.Load(reader); | ||
| } | ||
|
|
||
| newFormats.Add(new SessionStateFormatEntry(doc)); | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:989
Update-FormatData -PrependFormatDataparses the XML viaXmlDocument.Load(...)without handlingXmlException. A malformed XML string will throw a terminating .NET exception before the cmdlet reaches its normalFormatXmlUpdateExceptionhandling, producing inconsistent errors (and bypassing-ErrorActionsemantics). Catch parse failures and emit a cmdlet error record (and either continue or return).
if (ShouldProcess("Inline XML", action))
{
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(PrependFormatData[i]), settings))
{
doc.Load(reader);
}
newFormats.Add(new SessionStateFormatEntry(doc));
}
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:1034
Update-FormatData -AppendFormatDatahas the same unhandled XML parse failure path as prepend: malformed XML throws out ofProcessRecord()as a rawXmlExceptioninstead of producing aFormatXmlUpdateExceptionerror record. HandleXmlExceptionduring parsing.
if (ShouldProcess("Inline XML", action))
{
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(appendFormatDataItem), settings))
{
doc.Load(reader);
}
newFormats.Add(new SessionStateFormatEntry(doc));
}
src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs:54
- The new
PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml)overload has an unused parameter (_ = isXml;). This makes the API misleading and suggests incomplete implementation. Either remove the parameter and update call sites, or store/consume it meaningfully.
internal PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml)
{
this.psSnapinName = psSnapinName;
FormatDataXml = formatDataXml;
_ = isXml;
Errors = new ConcurrentBag<string>();
}
test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1:108
- The new inline XML feature is only tested for the happy path. Add a negative test that verifies malformed XML passed to
-PrependFormatData/-AppendFormatDataproduces aFormatXmlUpdateExceptionerror record (consistent with the existing invalid.ps1xmlfile test).
It "Should load inline XML using AppendFormatData" {
$null = $ps.AddScript('param($xml) Update-FormatData -AppendFormatData $xml').AddArgument($xmlContent)
$ps.Invoke()
$ps.HadErrors | Should -BeFalse
$ps.Commands.Clear()
$null = $ps.AddScript("Get-FormatData InlineTestType")
$formatData = $ps.Invoke()
$formatData | Should -HaveCount 1
$formatData.TypeNames | Should -BeExactly "InlineTestType"
$formatData.FormatViewDefinition.Name | Should -BeExactly "InlineTest"
}
| if (file.FormatDataXml != null) | ||
| { | ||
| XmlFileLoadInfo info = | ||
| new XmlFileLoadInfo("Inline", "Inline XML", file.Errors, file.PSSnapinName); | ||
| using (TypeInfoDataBaseLoader loader = new TypeInfoDataBaseLoader()) | ||
| { | ||
| if (!loader.LoadXmlString(file.FormatDataXml, info, db, expressionFactory, preValidated)) | ||
| success = false; | ||
|
|
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:985
- Inline XML is parsed with XmlDocument.Load() without handling XmlException. If the XML is invalid, the XmlException will escape ProcessRecord() (it is not a RuntimeException), so the cmdlet won't revert InitialSessionState.Formats and won't emit the standard FormatXmlUpdateException error record (unlike the file-based path). Wrap the parse/load in a try/catch and rethrow as a PowerShell RuntimeException-derived type so existing error handling runs consistently.
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(PrependFormatData[i]), settings))
{
doc.Load(reader);
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:1030
- AppendFormatData has the same issue as PrependFormatData: invalid inline XML will throw XmlException directly (not caught by the existing RuntimeException catch), bypassing the normal FormatXmlUpdateException error path and leaving InitialSessionState.Formats mid-update. Catch XmlException and rethrow as PSInvalidOperationException so the existing revert-and-WriteError logic runs.
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(appendFormatDataItem), settings))
{
doc.Load(reader);
src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs:52
- The new PSSnapInTypeAndFormatErrors overload uses a boolean parameter only to disambiguate the (string, string) overloads, but the parameter is otherwise meaningless and is only referenced via
_ = isXml;. Rename the parameter to_and remove the dummy assignment to make the intent explicit and avoid the no-op statement.
internal PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml)
{
this.psSnapinName = psSnapinName;
FormatDataXml = formatDataXml;
_ = isXml;
test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1:102
- The new inline XML feature is only tested for the success path. Add a negative test to verify that invalid inline XML produces the same error surface as invalid file-based format XML (i.e., a FormatXmlUpdateException error record), so regressions like uncaught XmlException are caught.
It "Should load inline XML using AppendFormatData" {
$null = $ps.AddScript('param($xml) Update-FormatData -AppendFormatData $xml').AddArgument($xmlContent)
$ps.Invoke()
$ps.HadErrors | Should -BeFalse
$ps.Commands.Clear()
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:985
- Inline XML is pre-parsed via XmlReaderSettings (DtdProcessing.Prohibit) and XmlDocument.Load without handling XmlException. This can (1) make -PrependFormatData reject XML that would load via the existing hardened loader settings and (2) surface as an unhandled terminating XmlException instead of a cmdlet error.
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(PrependFormatData[i]), settings))
{
doc.Load(reader);
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:1030
- Inline XML is parsed with XmlDocument.Load without bounds from the shared hardened settings, and XmlException will currently terminate the cmdlet unexpectedly. Aligning parsing with InternalDeserializer.LoadUnsafeXmlDocument also keeps behavior consistent with file-based format data loading.
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(appendFormatDataItem), settings))
{
doc.Load(reader);
src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs:52
- The new PSSnapInTypeAndFormatErrors constructor takes a bool that is never used ("_ = isXml;"). Since the bool exists only to disambiguate the (string,string) overload, consider naming it "_" and removing the dead assignment for clarity.
internal PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml)
{
this.psSnapinName = psSnapinName;
FormatDataXml = formatDataXml;
_ = isXml;
src/System.Management.Automation/engine/InitialSessionState.cs:411
- SessionStateFormatEntry now carries FormatDataXml, but InitialSessionState format loading doesn’t handle FormatDataXml entries (it only handles Formattable / FormatData / FileName). A SessionStateFormatEntry created with the XmlDocument constructor will likely be treated like a file entry with a null FileName when creating a new runspace, causing load failures.
/// <summary>
/// The FormatData XML string specified with constructor.
/// This can be null if other constructors are used.
/// </summary>
public string FormatDataXml { get; }
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:985
Update-FormatData -PrependFormatDataparses the XML withXmlDocument.Load(...), but anyXmlException(malformed XML) will currently escapeProcessRecord()unhandled. That produces an inconsistent error type/ID compared to file-based failures (which surface asFormatXmlUpdateException) and can bypass the cmdlet’s normal error-reporting behavior.
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(PrependFormatData[i]), settings))
{
doc.Load(reader);
src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs:1030
Update-FormatData -AppendFormatDatahas the same issue as the prepend loop: malformed XML throwsXmlExceptionfromXmlDocument.Load(...)and bypasses the cmdlet’s normalFormatXmlUpdateExceptionerror reporting.
var doc = new System.Xml.XmlDocument() { XmlResolver = null };
var settings = new System.Xml.XmlReaderSettings { DtdProcessing = System.Xml.DtdProcessing.Prohibit, XmlResolver = null };
using (var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(appendFormatDataItem), settings))
{
doc.Load(reader);
src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs:54
- The
isXmlparameter name implies it affects behavior, but it’s intentionally unused (the_ = isXml;line is just a discard). Renaming the parameter to_makes the intent explicit and avoids suggesting a behavior switch that doesn’t exist.
internal PSSnapInTypeAndFormatErrors(string psSnapinName, string formatDataXml, bool isXml)
{
this.psSnapinName = psSnapinName;
FormatDataXml = formatDataXml;
_ = isXml;
Errors = new ConcurrentBag<string>();
}
test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1:102
- Inline XML support is only tested for successful loads. There’s no test coverage for malformed inline XML (well-formedness errors) to ensure the cmdlet reports the expected
FormatXmlUpdateExceptionerror ID (matching file-based behavior) instead of surfacing a rawXmlException.
It "Should load inline XML using AppendFormatData" {
$null = $ps.AddScript('param($xml) Update-FormatData -AppendFormatData $xml').AddArgument($xmlContent)
$ps.Invoke()
$ps.HadErrors | Should -BeFalse
$ps.Commands.Clear()
PR Summary
This PR allows the
Update-FormatDatacmdlet to accept formatting configuration (PS1XML) directly from an in-memory string. It introduces two new string array parameters:-PrependFormatDataand-AppendFormatData.Under the hood:
FormatDataXmlproperty and anXmlDocumentconstructor toSessionStateFormatEntryfor secure encapsulation of the raw string.TypeInfoDataBaseLoaderto parse inline XML strings without requiring physical disk I/O.UpdateFormatDataCommandvia theTypeInfoDataBaseManager.-PrependFormatDataand-AppendFormatDatacorrectly load views.PR Context
Resolves #7845
Currently, module authors who use tools like
ModuleBuilderto pack their modules into a single.psm1file for better performance cannot easily embed their format data (.ps1xmlfile contents). They are forced to ship external.ps1xmlfiles alongside the module, which also complicates code signing (since the XML file must be signed separately).By allowing
Update-FormatDatato accept raw XML strings, module authors can now natively store and apply formatting rules entirely in memory.PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header