-1

I've asked the same question before now I'll try to make my question a little more specific. My goal is to create a md file from a bom (xml) file using Powershell. From the bom file three values should be read out (name, version, license). Here is the following code:

[XML]$xml = Get-Content $XMLfile


$xml.components.component | ForEach-Object {
    [PSCustomObject]@{
        'Name'          = $_.name
        'Version'         = $_.version
        'License'      = $_.license
    }
}

The code should be correct in my opinion to read out the desired data or not? Now I get the following error code:

Cannot convert value "System.Object[]" to type "System.Xml.XmlDocument". Error: "'=' is an unexpected token. The expected token is ';'. Line 1820, position 60."

+ [xml]$xml = Get-Content $xmlFile

Could it be that an error code occurs because the bom file uses a different Unicode(UTF-16-LE)? Here is a part of the XML file:

<bom xmlns="http://cyclonedx.org/schema/bom/1.2" serialNumber="urn:uuid:cdd205c6-beb8-49db-998d-884cfeef678c" version="1">
<metadata>
<timestamp>2021-08-20T23:41:11.390Z</timestamp>
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>Node.js module</name>
<version>2.0.0</version>
</tool>
</tools>
</metadata>
<components>
<component type="library" bom-ref="pkg:npm/%40mdi/font@4.9.95">
<group>@mdi</group>
<name>font</name>
<version>4.9.95</version>
<description>
<![CDATA[ Dist for Material Design Webfont. This includes the Stock and Community icons in a single webfont collection. ]]>
</description>
<hashes>
<hash alg="SHA-512">9b6b1b02cf923304675a990cae207112e970ba19aa47287a5fe85d39996a4b161950cd82d936839d0e20722973768020aeed97627595899ff13ee4831a045757</hash>
</hashes>
<purl>pkg:npm/%40mdi/font@4.9.95</purl>
<externalReferences>
<reference type="website">
<url>https://materialdesignicons.com</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/Templarian/MaterialDesign/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/Templarian/MaterialDesign-Webfont.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/%40storybook/addon-a11y@6.2.9">
<group>@storybook</group>
<name>addon-a11y</name>
<version>6.2.9</version>
<description>
<![CDATA[ Test component compliance with web accessibility standards ]]>
</description>
<hashes>
<hash alg="SHA-512">c28ee716912a11e8870ec44a9e1a9eda020767d67bfc079fc39ef4901811794e6d2a59ab6f9ac501f4d50561816652c759e24cb05b11b16ad6ae691fd41e7639</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/%40storybook/addon-a11y@6.2.9</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/storybookjs/storybook#readme</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/storybookjs/storybook/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/storybookjs/storybook.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/%40storybook/addons@6.2.9">
<group>@storybook</group>
<name>addons</name>
<version>6.2.9</version>
<description>
<![CDATA[ Storybook addons store ]]>
</description>
<hashes>
<hash alg="SHA-512">1a798429b27088dd639dc37d35203c0ae4758b65c095ab0f725fd99f48e47d5f568ad41e73355c2423f0f3a4861fce000feb53042c85da2f540b429a395d5805</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/%40storybook/addons@6.2.9</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/storybookjs/storybook/tree/master/lib/addons</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/storybookjs/storybook/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/storybookjs/storybook.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/%40storybook/api@6.2.9">
<group>@storybook</group>
<name>api</name>
<version>6.2.9</version>
<description>
<![CDATA[ Core Storybook API & Context ]]>
</description>
<hashes>
<hash alg="SHA-512">a24900dc7012704f6d1a7601ae34ce720cd3f8bd65447368121dd97c6821d6efd7344c8718d923e20aef91dea75fb07345c610fe5d9591c282aa63a35274a455</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/%40storybook/api@6.2.9</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/storybookjs/storybook/tree/master/lib/api</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/storybookjs/storybook/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/storybookjs/storybook.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/%40reach/router@1.3.4">
<group>@reach</group>
<name>router</name>
<version>1.3.4</version>
<description>
<![CDATA[ Next generation Routing for React. ]]>
</description>
<hashes>
<hash alg="SHA-512">fa6b67f708e507d34dd823679c2fc1458b7074a05f4b2c9ab0f62b68d032bda575a1c72bff9367078095ce31198a9347c1879b430727746526a45cf1014a2a48</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/%40reach/router@1.3.4</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/reach/router#readme</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/reach/router/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/reach/router.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/create-react-context@0.3.0">
<name>create-react-context</name>
<version>0.3.0</version>
<description>
<![CDATA[ Polyfill for the proposed React context API ]]>
</description>
<hashes>
<hash alg="SHA-512">74d95d2284ae352be54c9eec948282fd914629ec41301aeb71cf934ded4d76644e9da012b8b3efaa9c23f6fe174b8b9767cf983eed2c3664a15f6ad72392cdb3</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/create-react-context@0.3.0</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/thejameskyle/create-react-context#readme</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/thejameskyle/create-react-context/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/thejameskyle/create-react-context.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/gud@1.0.0">
<name>gud</name>
<version>1.0.0</version>
<description>
<![CDATA[ Create a 'gud nuff' (not cryptographically secure) globally unique id ]]>
</description>
<hashes>
<hash alg="SHA-512">cc610e54a14ce6c54f3eb62cec9e7f858130d8fa1ff0a0b23b0ca11bcb00176ea60807941407183eed66c01ee186920fdbdcce201683ab0b8774ba748ef6578f</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/gud@1.0.0</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/jamiebuilds/global-unique-id#readme</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/jamiebuilds/global-unique-id/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/jamiebuilds/global-unique-id.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/warning@4.0.3">
<name>warning</name>
<version>4.0.3</version>
<description>
<![CDATA[ A mirror of Facebook's Warning ]]>
</description>
<hashes>
<hash alg="SHA-512">ae9272376db629622f1c9fc5e775d266fd1997f69c72a1d1f1eb7592968c4c3fdf2c2471b55f225fc73333363bb1566ea53237cdc51383c7b2712da4345f65eb</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/warning@4.0.3</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/BerkeleyTrue/warning</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/BerkeleyTrue/warning/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/BerkeleyTrue/warning.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/loose-envify@1.4.0">
<name>loose-envify</name>
<version>1.4.0</version>
<description>
<![CDATA[ Fast (and loose) selective `process.env` replacer using js-tokens instead of an AST ]]>
</description>
<hashes>
<hash alg="SHA-512">972bb13c6aff59f86b95e9b608bfd472751cd7372a280226043cee918ed8e45ff242235d928ebe7d12debe5c351e03324b0edfeb5d54218e34f04b71452a0add</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/loose-envify@1.4.0</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/zertosh/loose-envify</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/zertosh/loose-envify/issues</url>
</reference>
<reference type="vcs">
<url>git://github.com/zertosh/loose-envify.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/js-tokens@4.0.0">
<name>js-tokens</name>
<version>4.0.0</version>
<description>
<![CDATA[ A regex that tokenizes JavaScript. ]]>
</description>
<hashes>
<hash alg="SHA-512">45d2547e5704ddc5332a232a420b02bb4e853eef5474824ed1b7986cf84737893a6a9809b627dca02b53f5b7313a9601b690f690233a49bce0e026aeb16fcf29</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/js-tokens@4.0.0</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/lydell/js-tokens#readme</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/lydell/js-tokens/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/lydell/js-tokens.git</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:npm/invariant@2.2.4">
<name>invariant</name>
<version>2.2.4</version>
<description>
<![CDATA[ invariant ]]>
</description>
<hashes>
<hash alg="SHA-512">a6125f41506e689339ada3a926349f9220fa0696c213836cfff2da5e5eb0198b54058f379d64ba45ff6d5e6d9ef1568aeb42448d895d6cf89ffc0d81d42da034</hash>
</hashes>
<licenses>
<license>
<id>MIT</id>
</license>
</licenses>
<purl>pkg:npm/invariant@2.2.4</purl>
<externalReferences>
<reference type="website">
<url>https://github.com/zertosh/invariant#readme</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/zertosh/invariant/issues</url>
</reference>
<reference type="vcs">
<url>git+https://github.com/zertosh/invariant.git</url>
</reference>
</externalReferences>
</component>
</components>
</bom>

I was able to format the xml file correctly and it should work now. My code now looks like this:

$xml        = 'C:\Users\jonb\Desktop\bom.xml'
$ns = @{a = 'http://cyclonedx.org/schema/bom/1.2'} 
$xPath = '//a:component'
$components = Select-Xml -Xml $xml -XPath $xPath -Namespace $ns

foreach ($component in $components) {    
  $name = Select-Xml -Xml $component.Node -XPath './/a:name/text()'-Namespace $ns 
  $version = Select-Xml -Xml $component.Node -XPath './/a:version/text()'-Namespace $ns
  $lic_cond = Select-Xml -Xml $component.Node -XPath './/a:license/a:id/text()'-Namespace $ns

  $license = $(If ($lic_cond) {$lic_cond} Else {"NA"}) 

  $finalObject = [pscustomobject]@{
        'Name'          = $name
        'Version'       = $version
        'License'      = $license
    }
   Write-Output $finalObject
}

I now have the following error message:

Select-Xml : Cannot bind parameter 'Xml'. Cannot convert the "C:\Users\jonb\Desktop\bom.xml" value of type "System.String" to type "System.Xml.XmlNode".
At C:\Users\jonb\Bobby Jones\Programme\Powershell\Untitled-1.ps1:4 char:31
+ $components = Select-Xml -Xml $xml -XPath $xPath -Namespace $ns
+                               ~~~~
    + CategoryInfo          : InvalidArgument: (:) [Select-Xml], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SelectXmlCommand

Sorry for the inconvenience, I don't know Powershell at all.

bobby17jones
  • 191
  • 1
  • 1
  • 9
  • 3
    Remove `| ConvertFrom-String` – Mathias R. Jessen Aug 25 '21 at 12:22
  • I have already removed | ConvertFrom-String – bobby17jones Aug 25 '21 at 12:48
  • 4
    It's not possible to replicate this because the XML file is incomplete (`Cannot convert value "System.Object[]" to type "System.Xml.XmlDocument". Error: "Unexpected end of file has occurred.`). – Robin Aug 25 '21 at 13:00
  • I have now extended the xml file now it should work to replicate it. The whole xml file is more than 10'000 lines big. – bobby17jones Aug 25 '21 at 13:41
  • I added comments to your original question without realising you created a duplicate - see there for a possible fix involving unescaped urls in your xml data. https://stackoverflow.com/questions/68893566/cannot-convert-value-system-object-to-type-system-xml-xmldocument#comment121803759_68893566 – mclayton Aug 25 '21 at 17:48

2 Answers2

3

Using your - well-formed - sample XML, the only problem with your code is that you're missing parts of the property paths needed to get the information of interest, using PowerShell's dot-notation adaptation of the XML DOM.

# Note the `.bom` before `.components.component` 
# and `.licenses.license.id` instead of `.license`
$xml.bom.components.component | ForEach-Object {
    [PSCustomObject]@{
        'Name'         = $_.name
        'Version'      = $_.version
        'License'      = $_.licenses.license.id
    }
}

As for the error message referenced in the title that appears to happen with your real XML:

The error message implies that your XML is not well-formed, which means that you need to diagnose the problems in your XML text and fix them.

The error message pinpoints the (first) problem (there may be more):

'=' is an unexpected token. The expected token is ';'. Line 1820, position 60.

Therefore, check line 1820, column 60 in your input file.

The problem appears to be a malformed XML entity (a correct instance of which is &amp;, for example) or something that is interpreted as such due to lack of escaping, such as in an unescaped URL containing &, as mcclayton points out. For instance, you can provoke the same error as follows:

# Note the malformed `&amp` entity, which has a trailing "=" instead of ";"
[xml] '<foo>A &amp= B</foo>'

There are online XML validators that can help you troubleshoot XML interactively, iteratively, such as xmlvalidation.com and liquid-technologies.com/online-xml-validator.


As an aside:

You can speed up reading XML files by adding the -Raw switch to the Get-Content call, given that the file must be read in full anyway:

# Faster, but not fully robust.
[xml] $xml = Get-Content -Raw $XMLfile

However, the Get-Content technique isn't robust, because the XML document's character encoding may be misinterpreted - see the bottom section of this answer for details.

To avoid this problem, use the following idiom:

# Note: In PowerShell version 4 and below, 
#       replace `[xml]::new()` with `New-Object xml`
($xml = [xml]::new()).Load((Convert-Path -LiteralPath $XMLfile))

Note: Convert-Path is necessary to ensure that a full path is passed to the .Load() method, given that .NET's working directory usually differs from PowerShell's. If you know $XMLfile to contain a full path already, you can omit the Convert-Path call and pass it directly.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Now that your xml is well formed, you should be able to get to where (I believe) you want to get, using xpath and taking care of your namespaces:

$ns = @{a = 'http://cyclonedx.org/schema/bom/1.2'} 
$xPath = '//a:component'
$components = Select-Xml -Xml $xml -XPath $xPath -Namespace $ns

foreach ($component in $components) {    
  $name = Select-Xml -Xml $component.Node -XPath './/a:name/text()'-Namespace $ns 
  $version = Select-Xml -Xml $component.Node -XPath './/a:version/text()'-Namespace $ns
  $lic_cond = Select-Xml -Xml $component.Node -XPath './/a:license/a:id/text()'-Namespace $ns

  #the use of the ternary operator in the next line is required because not all your components have a license
  $license = $(If ($lic_cond) {$lic_cond} Else {"NA"}) 

  $finalObject = [pscustomobject]@{
        'Name'          = $name
        'Version'       = $version
        'License'      = $license
    }
   echo $finalObject
}

Output:

Name                 Version License
----                 ------- -------
font                 4.9.95  NA     
addon-a11y           6.2.9   MIT    
addons               6.2.9   MIT    
api                  6.2.9   MIT    
router               1.3.4   MIT    
create-react-context 0.3.0   MIT    
gud                  1.0.0   MIT    
warning              4.0.3   MIT    
loose-envify         1.4.0   MIT    
js-tokens            4.0.0   MIT    
invariant            2.2.4   MIT    
Jack Fleeting
  • 24,385
  • 6
  • 23
  • 45
  • While XPath and `Select-Xml` are good techniques to showcase in general, note that it only takes minor tweaks to the approach shown in the question to make it work, which relies on PowerShell's adaptation of the XML DOM (where child elements and attributes are surfaced as _properties_), which makes for a more concise and conceptually simpler solution. – mklement0 Aug 26 '21 at 13:42
  • @mklement0 I guess I'll have to respectfully disagree. While I like your answer (and upvoted it), to me - maybe as a matter of temperament or experience - xml needs to be parsed with xpath. The dot notation system with elements treated as properties fails me, especially when the xml is deeply nested and conditionals need to be inserted half way through the tree. For some reason, `$xml.bom.components.component.group.this.that` rubs me the wrong way... – Jack Fleeting Aug 26 '21 at 13:54
  • Understood, @JackFleeting (and thanks for up-voting). To me it's not an either-or situation: With simple "drill-down operations", if you don't need to worry about namespaces, PowerShell's dot notation can be very handy. By contrast, for more complex operations and when performance matters, XPath is your friend - but it comes at the expense of introducing an entirely new, non-trivial knowledge domain. – mklement0 Aug 26 '21 at 13:57
  • 1
    @mklement0 "it comes at the expense of introducing an entirely new, non-trivial knowledge domain" Here, I agree with you 100% - it's non-trivial and largely non-intuitive; that's a fundamental problem across the board (i.e., not just with PS) with both xpath and xquery (with the quagmire of namespaces adding fuel to the fire (pardon the mixed metaphors)) and I'm certain that's one main reason for the limited adoption of xpath among users. – Jack Fleeting Aug 26 '21 at 14:04