How to combine into single attribute if metadata is same?
-
28-10-2019 - |
Question
I am having Msbuild XML file like this.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Notifications Include="someone@gmail.com"/>
</ItemGroup>
<ItemGroup>
<Xcopy Include="..\..\..\Release\UtilitiesAssembly.dll">
<Destination>"..\..\..\Mycomponent\depl\BOM\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\Core.assembly.dll">
<Destination>"..\..\..\Mycomponent\depl\BOM\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\UIAssembly.dll">
<Destination>"..\..\..\Anothercomponent\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\Core.assembly.dll">
<Destination>"..\..\..\Anothercomponent\depl\BOM</Destination>
</Xcopy>
</Itemgroup>
</Project>
Actually i would like to group XCopy Itemgroup like this
<Xcopy Include="..\..\..\Release\UtilitiesAssembly.dll;
..\..\..\Release\\Core.assembly.dll;">
<Destination>"..\..\..\Mycomponent\depl\BOM\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\UIAssembly.dll;">
<Destination>"..\..\..\Anothercomponent\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\Core.assembly.dll">
<Destination>"..\..\..\Anothercomponent\depl\BOM</Destination>
</Xcopy>
How to achieve it using Powershell or Msbuild or by some other mechanism
Solution
Here is an example how to do this using the Group-Object
cmdlet. It will group the xcopy elements by Destination
and combine the Include
paths into a semi-colon separated string. The output is stored in a new XML document and saved to disk. This doesn't programatically update the original. You can just copy paste the new consolidated XML over you original XML.
Update Now that I see your whole file, I see it has a default namespace which causes problems with XPath. There are two ways to get around this problem.
- Use namespace agnostic xpath
- Use a namespace manager
Here is an updated example using namespace agnostic xpath. Also in order to get the indentation you are looking for we'll need to do some text file processing after the XML object processing.
$xml="Myinput.xml"
$tempXmlFile = "C:\New.xml"
$outXml = New-Object System.Xml.XmlDocument
$outXml.AppendChild($outXml.CreateElement("root")) > $null
$x = [xml] (Get-Content $xml)
$x.SelectNodes("//*[local-name()='Xcopy']") | Group-Object Destination | % {
$includePaths = ($_.Group | Select-Object -ExpandProperty Include) -join ";"
$element = $outXml.CreateElement("Xcopy")
$element2 = $outXml.CreateElement("Destination")
$element2.InnerText = $_.Name
$element.AppendChild($element2) > $null
$element.SetAttribute("Include", $includePaths)
$outXml.DocumentElement.AppendChild($element) > $null
}
$outXml.Save($tempXmlFile)
$data = Get-Content $tempXmlFile | % {
$search = 'Include="'
$index = $_.IndexOf('Include="')
if ($index -gt 0) {
$spaces = ( ' ' * ($index + $search.length) )
}
$_ -replace ';', ";`n${spaces}"
}
$data | Set-Content $tempXmlFile
& notepad $tempXmlFile
Creates output:
<root>
<Xcopy Include="..\..\..\Release\UtilitiesAssembly.dll
..\..\..\Release\Core.assembly.dll">
<Destination>"..\..\..\Mycomponent\depl\BOM\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\UIAssembly.dll">
<Destination>"..\..\..\Anothercomponent\Folder1</Destination>
</Xcopy>
<Xcopy Include="..\..\..\Release\Core.assembly.dll">
<Destination>"..\..\..\Anothercomponent\depl\BOM</Destination>
</Xcopy>
</root>
OTHER TIPS
Starting from MSBuild 3.5 you can use ItemDefinitionGroup.
<ItemDefinitionGroup>
<Xcopy>
<Destination>"..\..\..\Mycomponent\depl\BOM\Folder1</Destination>
</Xcopy>
</ItemDefinitionGroup>
<ItemGroup>
<Xcopy Include="..\..\..\Release\UtilitiesAssembly.dll" />
<Xcopy Include="..\..\..\Release\Core.assembly.dll" />
<Xcopy Include="..\..\..\Release\UIAssembly.dll;">
<Destination>"..\..\..\Anothercomponent\Folder1</Destination>
</Xcopy>
</ItemGroup>