How do you update an environment variable from inside an Exec task? - msbuild

I am trying to pipe the output from a command to an Environment variable thus:
<Exec Command="for /f "tokens=*" %%i in ('svn info') do SET SVNINFO=%%i" />
and then use SVNINFO as a property in MSBuild.
While the command line counterpart:
for /f "tokens=*" %i in ('svn info') do SET SVNINFO=%i
works, the change in the value of the Environment variable when called from the Exec does not persist. (I am not able to obtain its value as a property.) Am I missing something here? Is there any better way to achieve this?

Starting with .NET 4.5 you can capture output of the Exec task using its ConsoleOutput parameter by setting ConsoleToMsBuild="true" (documentation). For example, the following target captures %TIME% value into the Time MSBuild property:
<Project>
<Target Name="Build">
<Exec Command="echo %TIME%" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="Time" />
</Exec>
<Message Text="Current time is $(Time)" />
</Target>
</Project>

Maybe using the Exec Task Output is a better way:
<Project DefaultTargets="DefaultTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Exe">
<Exec Command="echo %PATH%">
<Output TaskParameter="Outputs" PropertyName="ExecOutput" />
</Exec>
</Target>
<Target Name="DefaultTarget" DependsOnTargets="Exe">
<Message Text="Result from Exec is $(ExecOutput)" />
</Target>
</Project>

Related

Transform a delimited string to array or ItemGroup

I'm trying to create a MSBuild target to post-process assemblies through a custom executable (e.g. convert.exe).
The target receives a semi-colon ; separated list of assemblies as Input and I would like to batch Exec.
<Target Name="_CollectAssemblies" DependsOnTargets="ResolveReferences">
<ItemGroup>
<_Assemblies Include="#(ReferencePath);#(CopyLocalFiles);#(ResolvedDependencyFiles);#(ReferenceDependencyPaths);$(TargetPath)" />
</ItemGroup>
</Target>
<Target Name="_ConvertFiles" DependsOnTargets="_CollectAssemblies"
Inputs="#(_Assemblies)">
<Exec Command="echo #(_Assemblies)" />
<!--<Exec Command="$(MSBuildThisFileDirectory)convert.exe #(_Assemblies)" />-->
</Target>
The Exec command outputs, echo Assembly1.dll;Assembly2.dll;Assembly3.dll;.
How do I transform the Input so that I can process each assembly individually?
e.g.
echo Assembly1.dll
echo Assembly2.dll
echo Assembly3.dll
So far I have tried:
<Target Name="_ConvertFiles" DependsOnTargets="_CollectAssemblies"
Inputs="#(_Assemblies)">
<ItemGroup>
<_SplitAssemblies Include="$(_Assemblies.Split(';'))" />
</ItemGroup>
<Exec Command="echo %(_SplitAssemblies.Identity)" />
</Target>
Answering my own question. MSBuild already had a way of batching the items (as I thought!).
<Target Name="_ConvertFiles" DependsOnTargets="_CollectAssemblies"
Inputs="#(_Assemblies)">
<ItemGroup>
<_ConvertAssemblies Include="#(_Assemblies)"/>
</ItemGroup>
<Exec Command="$(MSBuildThisFileDirectory)convert.exe %(_ConvertAssemblies.Identity)" />
<Error Text="Stop" />
</Target>

How to pass parameter to Wix patch?

We are creating patch using Wix. It is given below
<Family DiskId="5000"
MediaSrcProp="Sample"
Name="Sample"
SequenceStart="5000">
<UpgradeImage SourceFile="Z:\MyViewName\Latest_UnCompressed\EmailTrans.msi" Id="Latest">
<TargetImage SourceFile="Z:\MyViewName\Prev_Uncompressed\EmailTrans.msi" Order="2" Id="Previous" IgnoreMissingFiles="no"/>
</UpgradeImage>
</Family>
I do not want to use <UpgradeImage SourceFile="Z:\MyViewName , Because this may change often.
I am using Msbuild target like below to build it
<Target Name="CreateUncompressFolder">
<RemoveDir Condition="Exists('$(OldUncompressedMsiPath)')" Directories="$(OldUncompressedMsiPath)" />
<MakeDir Condition="!Exists('$(OldUncompressedMsiPath)')" Directories="$(OldUncompressedMsiPath)" />
<RemoveDir Condition="Exists('$(NewUncompressedMsiPath)')" Directories="$(NewUncompressedMsiPath)" />
<MakeDir Condition="!Exists('$(NewUncompressedMsiPath)')" Directories="$(NewUncompressedMsiPath)" />
</Target>
<Target Name="UnCompressMsi" DependsOnTargets="CreateUncompressFolder">
<Exec Command="msiexec.exe /a "$(NewMsiPath)" /qb TARGETDIR="$(NewUncompressedMsiPath)""/>
<Exec Command="msiexec.exe /a "$(OldMsiPath)" /qb TARGETDIR="$(OldUncompressedMsiPath)""/>
</Target>
<Target Name="BuildMsp">
<Exec Command="candle.exe "$(PatchWxsName)""/>
<Exec Command="light.exe "$(WixObj)" -out "$(PCPName)""/>
<Exec Command="msimsp.exe -s "$(PCPName)" -p "$(MspName)" -l "Patch.log" "/>
</Target>
Is it possible to pass Z:\MyViewName as parameter via Msbuild ?
You need to use the DefineConstants and have msbuild pass in parameters to your .wixproj as parameters. In Automating WiX with MSBuild, you'll see he's passing in PRODUCTVERSION as a MSBUILD parameter to the wixproj and he can then reference the value of PRODUCTVERSION using $(var.PRODUCTVERSION) in his WXS file.
Another approach I've seen done, which is pretty much the same in the link i mentioned is to instead add the XML element DefineConstants as a property to the PropertyGroup in the .wixproj file instead of creating the property on the fly in the BeforeBuild target.
E.x.:
<Project>
<PropertyGroup>
<Configuration/>
<Platform/>
<DefineConstants>
BuildVersion=$(ProductVersion);
WixVarName=$(MSBuildPropertyName);
WixVarName1=$(MSBuildPropertyName1);
WixVarName2=$(MSBuildPropertyName2);
</DefineConstants>
<OutputPath/>
<OutputName/>

xcopy with MsBuild

I have a really simple build script that looks like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Bundle">
<ItemGroup>
<BuildArtifacts Include="..\_buildartifacts" />
<Application Include="..\_application" />
</ItemGroup>
<Target Name="Clean">
<RemoveDir Directories="#(BuildArtifacts)" />
<RemoveDir Directories="#(Application)" />
</Target>
<Target Name="Init" DependsOnTargets="Clean">
<MakeDir Directories="#(BuildArtifacts)" />
<MakeDir Directories="#(Application)" />
</Target>
<Target Name="Bundle" DependsOnTargets="Compile">
<Exec Command="xcopy.exe %(BuildArtifacts.FullPath) %(Application.FullPath) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" WorkingDirectory="C:\Windows\" />
</Target>
The problem is the Bundle target, only the %(BuildArtifacts.FullPath) gets extracted, %(BuildArtifacts.FullPath) is ignored when the scripts executes.
The command looks like this when executing:
xcopy.exe C:\#Code\blaj_buildartifacts /e /EXCLUDE:C:\#Code\blaj\files_to_ignore_when_bundling.txt" exited with code 4
As you can see, the destination path is not there, if I hard code the paths or just the destination path it all works. Any suggestion on what I am doing wrong here?
Update
I managed to solve the problem, I removed the last part WorkingDirectory="C:\Windows\"
And changed the script into this:
<Exec Command="xcopy.exe #(BuildArtifacts) #(Application) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" />
and now it's working :)
I managed to solve this. I've updated the question with the solution.
I removed the last part WorkingDirectory="C:\Windows\" And changed the script into this:
<Exec Command="xcopy.exe #(BuildArtifacts) #(Application) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" />
and now it's working :)
You need to execute xcopy twice. You are trying to use task batching for two different item arrays in the same invocation, it doesn't work like that. Try this:
<Target Name="Bundle" DependsOnTargets="Compile">
<Exec Command="xcopy.exe %(BuildArtifacts.FullPath) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" WorkingDirectory="C:\Windows\" />
<Exec Command="xcopy.exe %(Application.FullPath) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" WorkingDirectory="C:\Windows\" />
</Target>

How get exec task output with msbuild

I'm trying to get simple output by exec task with msbuild:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<Exec Command="echo test output">
<Output TaskParameter="Outputs" ItemName="Test1" />
</Exec>
<Exec Command="echo test output">
<Output TaskParameter="Outputs" PropertyName="Test2" />
</Exec>
<Message Text="----------------------------------------"/>
<Message Text="#(Test1)"/>
<Message Text="----------------------------------------"/>
<Message Text="$(Test2)"/>
<Message Text="----------------------------------------"/>
</Target>
</Project>
But get next output:
echo test output
test output
echo test output
test output
----------------------------------------
----------------------------------------
----------------------------------------
How can I get output by my script?
Good news everyone! You can now capture output from <Exec> as of .NET 4.5.
Like this:
<Exec ... ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
</Exec>
Simply:
Add ConsoleToMsBuild="true" to your <Exec> tag
Capture the output using the ConsoleOutput parameter in an <Output> tag
Finally!
Documentation here
If you want to capture output to an array-like structure and not to a plain string where the output lines are separated by a semicolon, use ItemName instead of PropertyName:
<Exec ... ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" ItemName="OutputOfExec" />
</Exec>
I've gotten to the point where I'm so frustrated with the limitations of MSBuild, and the stuff that's supposed to work but doesn't (at least not in every context), that pretty much anytime I need to do anything with MSBuild, I create a custom build task in C#.
If none of the other suggestions are working, then you could certainly do it that way.
You can pipe the output to a file so to speak, and read it back.
echo test output > somefile.txt

Gathering outputs from an MSBuild exec task

I have a batch script that I want to call from an MSBuild project, and the documentation says I can't use output from the batch (either console / environment variables) in the MSBuild project.
Is there a workaround?
You can redirect the output of the command to a file using "> output.txt" and read that into a variable.
<PropertyGroup>
<OutputFile>$(DropLocation)\$(BuildNumber)\Output.txt</OutputFile>
</PropertyGroup>
<Exec Command="dir > "$(OutputFile)"" />
<ReadLinesFromFile File="$(OutputFile)">
<Output TaskParameter="Lines" ItemName="OutputLines"/>
</ReadLinesFromFile>
<Message Text="#(OutputLines->'%(Identity)', '%0a%0d')" />