XPath expression for parsing WiX Processing-Instructions as MsBuild properties - xpath

I have the following wix include VersionFile.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define ProductVersionMajor = "1" ?>
<?define ProductVersionMinor = "00" ?>
<?define ProductName= "MyProduct" ?>
<?define UpgradeCode = "myUpgradeCode" ?>
</Include>
Now I want to get e.g. the ProductVersionMajor as "1" or ProductName "MyProduct" (without quotes) using XmlPeek and a XPath query. With following code
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<XmlPeek XmlInputPath="VersionFile.wxi"
Query="//processing-instruction('define')[starts-with(., "ProductVersionMajor =")]">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<XmlPeek XmlInputPath="VersionFile.wxi"
Query="//processing-instruction('define')[starts-with(., "ProductVersionMajor=")]">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<Message Text="#(Peeked)"/>
</Target>
</Project>
I already got it down to
<?define ProductVersionMajor = "1" ?>
But goal would be
1
Any help how to tweak the XPath query highly appreciated. Also it'll be great to have a placeholder "ProductVersionMajor*=" instead using XmlPeek twice.
<XmlPeek XmlInputPath="ProductVersion.wxi"
Query="substring-before(substring-after(//processing-instruction("define")[starts-with(., "ProductVersionMajor=")],"),")">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
unfortunately only produces an
error MSB3734: XPath Query "substring-before(substring-after(//processing-instruction("define")[starts-with(., "ProductVersionMajor=")],"),")" cannot be loaded. 'substring-before(substring-after(//processing-instruction("define")[starts-with(., "ProductVersionMajor=")],"),")' has an invalid token.
Assume that XmlPeek needs some more custom XPath syntax possibly?
Yes. Tried it as well. Now also tried
Query="substring-before(substring-after(//processing-instruction('define')[starts-with(., 'ProductVersionMajor =')],&apos;"&apos;),&apos;"&apos;) ">
Also no success. Error is
error MSB4018: The "XmlPeek" task failed unexpectedly.\r
error MSB4018: System.Xml.XPath.XPathException: Expression must evaluate to a node-set.\r
error MSB4018: at System.Xml.XPath.XPathNavigator.Select(XPathExpression expr)\r
error MSB4018: at Microsoft.Build.Tasks.XmlPeek.Execute()\r
error MSB4018: at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()\r
error MSB4018: at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, Task
LoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Boolean& taskResult)

Form the xpath point of view the following should do:
Query='substring-before(
substring-after(
//processing-instruction("define")[starts-with(., "ProductVersionMajor =")]
,
&apos;"&apos;
)
,
&apos;"&apos;
)'

I solved this problem with this approach. Hope it helps:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="GetWixDefine"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<File ParameterType="System.String" Required="true" Output="false" />
<DefineName ParameterType="System.String" Required="true" Output="false" />
<Value ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Using Namespace="System" />
<Using Namespace="System.Text.RegularExpressions" />
<Using Namespace="System.Xml" />
<Code Type="Fragment" Language="cs">
<![CDATA[
this.Value = string.Empty;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(this.File);
string selector = string.Format("//processing-instruction('define')[starts-with(., '{0}')]", this.DefineName);
XmlNode defineNode = xmlDoc.SelectSingleNode(selector);
if (defineNode == null)
throw new Exception("define not found");
string regex = string.Format("{0}[ ]*=[ ]*\"(?<value>.*)\"", DefineName);
Match match = Regex.Match(defineNode.InnerText, regex);
if (!match.Success)
throw new Exception("cannot match correctly");
this.Value = match.Groups["value"].Value;
]]>
</Code>
</Task>
</UsingTask>
<Target Name="BeforeBuild">
<GetWixDefine File="VersionFile.wxi" DefineName="ProductVersion">
<Output TaskParameter="Value" ItemName="ProductVersionValue"/>
</GetWixDefine>
<Message Importance="High" Text="ProductVersion: #(ProductVersionValue)"/>
</Target>
</Project>

Related

MSBuild: How to access a property value set by a Target during the Post Build event in Visual Studio

I have a PostBuild event which invokes a batch file and I need to pass in a particular parameter into the batch file. This parameter is populated through another task which is invoked through a Target configured to run before the PostBuildEvent.
I can see that it gets successfully displayed when displayed using the element as part of the section.
But $(TargetFrameworkToolsFolderPath) under the PostBuildEvent has an "" value. Is there a way to access this custom property in the post build event?
Example:
<UsingTask TaskName="GetTargetFrameworkToolsFolderName" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<SDKFolderPath ParameterType="System.String" Required="true" />
<TargetFrameworkVersionStr ParameterType="System.String" Required="true" />
<TargetFrameworkToolsFolder ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
TargetFrameworkToolsFolder = SDKFolderPath + "\\" + "bin\\NETFX " + TargetFrameworkVersionStr.Substring(1) + " Tools\\";
</Code>
</Task>
</UsingTask>
<Target Name="FindTargetFrameworkToolsFolderPath" BeforeTargets="PostBuildEvent">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="SdkPath" />
</GetFrameworkSdkPath>
<GetTargetFrameworkToolsFolderName SDKFolderPath="$(SdkPath)" TargetFrameworkVersionStr="$(TargetFrameworkVersion)">
<Output PropertyName="TargetFrameworkToolsFolderPath" TaskParameter="TargetFrameworkToolsFolder"/>
</GetTargetFrameworkToolsFolderName>
<Message Text="$(TargetFrameworkToolsFolderPath)" Importance="normal" /> --> Displayed correctly here
</Target>
<PropertyGroup>
<PostBuildEvent>
call $(ProjectDir)AfterBuildCommands.bat $(TargetFrameworkToolsFolderPath) --> The TargetFrameworkToolsFolderPath property value here seems to be empty.
</PostBuildEvent>
</PropertyGroup>
But $(TargetFrameworkToolsFolderPath) under the PostBuildEvent has an
"" value. Is there a way to access this custom property in the post
build event?
In fact, <PostBuildEvent> is a property and MSBuild reads all the properties first and then executes all targets.
If you put these below outside the target which defines the property TargetFrameworkToolsFolderPath, these below will always execute first, as expected, the values of TargetFrameworkToolsFolderPath will be empty.
To avoid it, you should put the PostBuildEvent and TargetFrameworkToolsFolderPath properties in the same target and make sure the target is executed early enough, such as, run after PrepareForBuild target.
<PropertyGroup>
<PostBuildEvent>
call $(ProjectDir)AfterBuildCommands.bat $(TargetFrameworkToolsFolderPath) --> The TargetFrameworkToolsFolderPath property value here seems to be empty.
</PostBuildEvent>
</PropertyGroup>
Solution
Try this below:
<UsingTask TaskName="GetTargetFrameworkToolsFolderName" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<SDKFolderPath ParameterType="System.String" Required="true" />
<TargetFrameworkVersionStr ParameterType="System.String" Required="true" />
<TargetFrameworkToolsFolder ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
TargetFrameworkToolsFolder = SDKFolderPath + "\\" + "bin\\NETFX " + TargetFrameworkVersionStr.Substring(1) + " Tools\\";
</Code>
</Task>
</UsingTask>
<Target Name="MyFindTargetFrameworkToolsFolderPath" AfterTargets="PrepareForBuild">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="SdkPath" />
</GetFrameworkSdkPath>
<GetTargetFrameworkToolsFolderName SDKFolderPath="$(SdkPath)" TargetFrameworkVersionStr="$(TargetFrameworkVersion)">
<Output PropertyName="TargetFrameworkToolsFolderPath" TaskParameter="TargetFrameworkToolsFolder" />
</GetTargetFrameworkToolsFolderName>
<PropertyGroup>
<PostBuildEvent> call $(ProjectDir)AfterBuildCommands.bat $(TargetFrameworkToolsFolderPath) --> The TargetFrameworkToolsFolderPath property value here seems to be empty.</PostBuildEvent>
</PropertyGroup>
<Message Text="$(TargetFrameworkToolsFolderPath)" Importance="normal" />
</Target>
Hope it could help you.

Validating XML documents against schemas nested inside wsdl:types

I have XML files that I need to validate against XSDs that are nested inside a <wsdl:types></wsdl:types> in a WSDL file retrieved from a web service.
Inside the <wsdl:types></wsdl:types> there are several <xs:schema>s. I am using the ruby gem nokogiri to load the XML files and validate them against said XSDs, however, I am getting following error when I run the program:
Element '{http://schemas.xmlsoap.org/soap/envelope/}Envelope': No
matching global declaration available for the validation root.
So far I have extracted out the <xs:schema>s (all 4 of them) and copied them into a schema.xsd file.
Code:
require 'rubygems'
require 'nokogiri'
def validate(document_path, schema_path)
schema = Nokogiri::XML::Schema(File.read(schema_path))
document = Nokogiri::XML(File.read(document_path))
schema.validate(document)
end
validate('data.xml', 'schema.xsd').each do |error|
puts error.message
end
So basically my schema.xsd has multiple <xs:schema>s in there, which I do not think in and of itself is a problem because nokogiri didn't throw errors when I instantiated the schema object.
schema.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/">
<xs:element name="anyType" nillable="true" type="xs:anyType"/>
<xs:element name="anyURI" nillable="true" type="xs:anyURI"/>
<!-- data in here -->
</xs:schema>
<!-- three more xs:schema tags removed for brevity -->
data.xml
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header />
<env:Body>
<CreatePerson xmlns="https://person.example.com/">
<oMessageType xmlns:epa="http://schemas.datacontract.org/2004/07/whatever" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:array="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<person:bodyField>
<!-- data in here -->
</person:bodyField>
<!-- more data in here -->
</oMessageType>
</CreatePerson>
</env:Body>
</env:Envelope>
Yes, WSDL not the XSD scheam so you need to extract the schema partial manually or automatic by programming.
Here is the sample code, you need to refactor it and using in your codes.
str = <<EOF
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.examples.com/wsdl/HelloService.wsdl" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="HelloService" targetNamespace="http://www.examples.com/wsdl/HelloService.wsdl">
<types>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/">
<element name="anyType" nillable="true" type="anyType"/>
<element name="anyURI" nillable="true" type="anyURI"/>
<!-- data in here -->
</schema>
</types>
<message name="SayHelloRequest">
<part name="firstName" type="xsd:string"/>
</message>
<message name="SayHelloResponse">
<part name="greeting" type="xsd:string"/>
</message>
<portType name="Hello_PortType">
<operation name="sayHello">
<input message="tns:SayHelloRequest"/>
<output message="tns:SayHelloResponse"/>
</operation>
</portType>
<binding name="Hello_Binding" type="tns:Hello_PortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="sayHello">
<soap:operation soapAction="sayHello"/>
<input>
<soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:examples:helloservice"/>
</input>
<output>
<soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:examples:helloservice"/>
</output>
</operation>
</binding>
<service name="Hello_Service">
<documentation>WSDL File for HelloService</documentation>
<port name="Hello_Port" binding="tns:Hello_Binding">
<soap:address location="http://www.examples.com/SayHello/"/>
</port>
</service>
</definitions>
EOF
require 'rubygems'
require 'nokogiri'
doc = Nokogiri::XML(str)
doc.root.children.each do |child|
if child.node_name == 'types'
types = child
# p types.inner_html
xsd_doc = Nokogiri::XML(types.inner_html)
# p xsd_doc.root
xsd = Nokogiri::XML::Schema.from_document xsd_doc.root
end
end

How to get property value of a project file using msbuild

Using MSBuild I include a solution>
<ItemGroup>
<ProjectToBuild Include="$(SVNLocalPath)\$(SolutionName)"> </ProjectToBuild>
</ItemGroup>
I need to include all the *.csproj file from the solution with the condition of the proj file contain or define a property; for example if x.csproj contain a defined property "TestProjectType" would like to include the project into my itemGroup
something like this
<Target Name = "TestProperties">
<Message Text="TestProperties"/>
<ItemGroup>
<AllProj Include="$(SVNLocalPath)\*.csproj"/>
<AllTestProj Include="%(AllProj.Identity)" Condition="%(AllProj.ProjectTypeGuids)=={3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"/>
</ItemGroup>
<Message Text="#(AllTestProj )"/>
</Target>
Thanks
You can achieve that through custom task.
A simple sample to check a property (test) in all projects exclude current project of current solution:
<UsingTask TaskName="GetPropertyTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<ProjectFile ParameterType="System.String" Required="true" />
<BuildOutput ParameterType="System.String[]" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml"/>
<Reference Include="Microsoft.Build"/>
<Using Namespace="Microsoft.Build" />
<Using Namespace="Microsoft.Build.Evaluation" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var properties = new Dictionary<string, string>
{
{ "Configuration", "$(Configuration)" },
{ "Platform", "$(Platform)" }
};
//Log.LogMessage(MessageImportance.High, "customLog");
// Log.LogMessage(MessageImportance.High, ProjectFile);
var collection = new ProjectCollection(properties);
var project = collection.LoadProject(ProjectFile);
ProjectProperty pp = project.Properties.Where(p => p.Name == "MyCustomProperty").FirstOrDefault();
string customValue = pp==null?"empty":pp.EvaluatedValue;
BuildOutput = new String[] { customValue };
]]></Code>
</Task>
</UsingTask>
<Target Name="AfterBuild">
<GetPropertyTask ProjectFile="%(ProjectToScan.FullPath)">
<Output ItemName="ProjectToScanOutput" TaskParameter="BuildOutput"/>
</GetPropertyTask>
<Message Text="ClassLibrary1" Importance="high" Condition="'%(ProjectToScanOutput.Identity)' == 'test'" />
</Target>
More information, please refer to this article.

UnitTask error trying to use Microsoft.Build.Evaluation

have the following code inside my CSProj file:
<UsingTask TaskName="HelloWorld" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<Task>
<!--Microsoft.Build.dll-->
<Using Namespace="Microsoft.Build.Evaluation" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var p = new Project("$(MSBuildProjectFullPath)");
]]>
</Code>
</Task>
</UsingTask>
in my AfterBuild I call it like this:
<Target Name="AfterBuild">
<HelloWorld />
</Target>
The error I am getting is:
error CS0246: The type or namespace name 'Project' could not be found
(are you missing a using directive or an assembly reference?)
Referring to var p = new Project
Include Microsoft.Build reference in the task can fix that issue. (The Microsoft.Build.dll reference need to be added to your project firstly)
<Task>
<Reference Include="Microsoft.Build"/>
<Using Namespace="Microsoft.Build.Evaluation" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var p = new Project("$(MSBuildProjectFullPath)");
]]>
</Code>
</Task>

How to customize the schema inlined inside an imported WSDL

I have a.wsdl & b.wsdl where a.wsdl imports b.wsdl.
Now I have to customize the schema inside b.wsdl using wsimport and JAXB. but using below customization is giving error that "XPath evaluation of "wsdl:definitions/wsdl:types/xsd:schema[#targetNamespace='b']" results in an empty target node
I am not able to find a way to customize the inlined schema in imported b.wsdl when generating the client code using wsimport.
<jaxws:bindings node="wsdl:definitions/wsdl:types/xsd:schema[#targetNamespace='b']"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings>
<jaxb:javaType name="java.util.Calendar" xmlType="xsd:dateTime"
parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />
</jaxb:globalBindings>
</jaxws:bindings>
A.wsdl
<definitions targetNamespace="a"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:interface="b">
<import location="b.wsdl" namespace="b"/>
<service name="Service">
<port binding="interface:Binding" name="Port">
<soap:address location="https://localhost/sdk/vpxdService" />
</port>
</service>
</definitions>
B.wsdl
<definitions targetNamespace="b"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:b="b"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<types>
<schema
targetNamespace="b"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:b="b"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<complexType name="XYZ">
<sequence>
<element name="dynamicType" type="xsd:string" minOccurs="0" />
<element name="val" type="xsd:anyType" maxOccurs="unbounded" />
</sequence>
</complexType>
</types>
</schema>
</definitions>
After going through given website I modified the external binding file to use wsdlLocation="b.wsdl" instead of node="wsdl:definitions/wsdl:types/xsd:schema[#targetNamespace='b']" which did the magic.
This will make sure that the inline schema defined in WSDL will customized as required.
<bindings
xmlns="http://java.sun.com/xml/ns/jaxb"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl"
version="2.0">
<bindings wsdlLocation="b.wsdl">
<globalBindings>
<javaType name="java.util.Calendar" xmlType="xsd:dateTime"
parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
printMethod="javax.xml.bind.DatatypeConverter.printDate"
/>
</globalBindings>
</bindings>
</bindings>
http://fusesource.com/docs/framework/2.1/jaxws/JAXWSCustomTypeMappingOverview.html
Have you tried adding the following attributes to the <jaxws:bindings> element?
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
and
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
You're referencing the xsd and wsdl namespaces in your xpath expression, but until you define the URI's for them, they won't match up with the URI's in the target documents.

Resources