Is it possible to use an MSBuild target to create a new msys.bat instance and run commands?
MSBuild has an Exec task you can use to call a batch file or any executable:
<Exec Command="msys.bat param1 param2 param3" />
Related
I have a .NET 6 project that includes some Exec nodes, and those commands are failing because (as in this discussion on the msbuild repo) the paths of the generated tmp<blah>.exec.cmd files are not whitelisted.
The suggested fix in there is
The location of this file is controlled by the environment variable
TEMP. Can you set that to a custom value before invoking MSBuild?
Which I'm sure would work - but I don't know how to do that. According to this question (which is for C++ not C#, but it's the best I can find) you can use EnvironmentVariables="<blah>" in that same node, but the files are still generated in %LOCALAPPDATA% despite my trying to set TEMP to something else. A failing example is below - what am I doing wrong?
<Target Name="ToolRestore" BeforeTargets="PreBuildEvent">
<Exec Command="dotnet tool restore" StandardOutputImportance="high" EnvironmentVariables="TEMP=C:\MSBuildTemp" />
</Target>
An answer should ideally be valid for building/debugging in Visual Studio and via dotnet build/test/publish. Even better would be a method of making the value of TEMP be variable per-user, but that's not necessary.
According to the code for the Exec task it does use the standard GetTempPath function, which means it really should react to user-level environment variables. However that function is documented like:
This method checks for the existence of environment variables in the
following order and uses the first path found:
The path specified by the TMP environment variable.
The path specified by the TEMP environment variable.
...
so the suggested fix you found is not entirely correct: you might need TMP not TEMP. And indeed on my machine I have TMP set and msbuild uses it for its temporary batch files which can be seen using a target which prints the path of the batch file Exec uses:
<Target Name="TempTest">
<Exec Command="echo %~dp0"/>
</Target>
Running on cmd.exe:
>set TMP=c:\temp
>msbuild temptest.proj /t:TempTest /v:m /nologo
c:\temp\
>set TMP=c:\Users
>msbuild temptest.proj /t:TempTest /v:m /nologo
temptest.proj(7,5): error MSB6003: The specified task executable "
cmd.exe" could not be run. Failed to create a temporary file. Temporary files folder is full or its path is incorrect.
Access to the path 'c:\Users\tmpb31f9faffaab49e9b3bd5479a6823550.exec.cmd' is denied.
I'm trying to execute msbuild on Azure Devops. Because of that I cannot use the MSBuild task provided.
When I use a Command Line task the command is not recognised. On my local machine I load vcvarsall.bat before I use msbuild. But I've not been unable to work out how to obtain that path in Azure Devops. Doesn't appear to be a Develop Command Prompt task for Azue Devops either.
Any ideas on how I can use msbuild from a Command Line task or Batch Script task? Using their Hosted VS agent.
The best way to do this in a supported way is to use vswhere. The following bit of script will install vswhere (using chocolatey) and then query the installer registry where msbuild can be found. Replace -latest with a more specific version if you need that:
choco install vswhere
for /f "tokens=*" %%i in ('vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe -nologo') do set msbuildpath="%%i"
echo "##vso[task.setvariable variable=msbuildpath]%msbuildpath%"
This will save the path to msbuild to the environment variable %msbuildpath% as well as the pipeline variable (for this stage) $(msbuildpath).
You can then either use a second run commandline task and pass in $(msbuildpath) or you can simply call MsBuild from the same piece of script mentioned above by calling:
%msbuildpath%
This will make sure your script will remain working, even if Microsoft upgrades their images and moves some things around (which does happen).
You can also get vswhere using wget or invoke-webrequest -outfile from the following location:
https://github.com/Microsoft/vswhere/releases/latest/download/vswhere.exe
Other samples for vswhere syntax can be found on the project wiki, including the syntax for PowerShell.
If you use Hosted Agent 2017 you can run the msbuild.exe from the Command Line task in this way:
Command Line version 1:
Command Line version 2:
Results:
If you are interested in seeing how the built-in Microsoft task resolves the path, all the Azure Devops tasks are provided open-source. These are the path functions you probably care to review.
Here is the solution I came up with using only built-in pipeline tasks which makes the MSBuild bin directory available on the path environment variable.
Create a PowerShell task to generate an MSBuild project to capture and output to a file the variables you are interested in (ex. MSBuildBinPath)
PowerShell script
"<Project DefaultTargets=`"DetectMsBuild`">
<ItemGroup>
<OutFile Include=`"`$(MsBuildDetectionFile)`" />
<OutFile Condition=`"'`$(OutFile)' == ''`" Include=`"msbuildInfo.json`" />
</ItemGroup>
<Target Name=`"DetectMsBuild`">
<PropertyGroup>
<MsBuildPaths>
[{
`"Name`": `"BinPath`",
`"Value`": `"`$(MSBuildBinPath.Replace('\', '\\'))`"
}]
</MsBuildPaths>
</PropertyGroup>
<WriteLinesToFile
File=`"#(Outfile)`"
Lines=`"`$(MsBuildPaths)`"
Overwrite=`"true`"
Encoding=`"UTF-8`" />
</Target>
</Project>" | Out-File -FilePath "msbuilddetect.proj" -Encoding utf8
Set the working directory and any variables accordingly.
PowerShell task settings screenshot:
Create an MSBuild task to run the project file generated by the previous task. Ensure the MSBuild version is set to the version you want to use.
MSBuild task settings screenshot:
Last, create another PowerShell task that will parse the outputted JSON file of the extracted variables and sets environment variables accordingly.
PowerShell script
Write-Host "Current path: $($env.Path)`n`n"
$msBuildVariables = Get-Content -Path msbuildInfo.json | ConvertFrom-Json
$Path = "$($msBuildVariables[0].Value);$($env:Path)"
Write-Host "##vso[task.setvariable variable=Path;]$Path"
PowerShell task settings screenshot:
Here is a screenshot of the task order in the build pipeline.
I can see that MSBuild makes use of the /t:Package on the command line, but what's the euivalent for including this option in an msbuild script file.
The equivalent in a build script file is to use the MsBuild task. See link below.
Msdn Build Task
Are you invoking an msbuild script from another script using the MsBuild task? If so, the parameter you want to use is Targets http://msdn.microsoft.com/en-us/library/z7f65y0d.aspx.
I'm using Team Build (2010) to call an msbuild script with an Exec task that calls a batch file that in turn calls msbuild. Like this:
<Exec Command="BatchFileThatCallsMSBuild.bat" />
Of course the batch file does a bunch of other junk or I'd just use the MSBuild task.
The problem is that when the batch file tries to call msbuild it can't find it.
'msbuild' is not recognized as an internal or external command, operable program or batch file.
How do I get the necessary environment set up in the exec task?
I tried changing the command to:
<Exec Command="%22$(VS100COMNTOOLS)..\..\VC\vcvarsall.bat%22&BatchFileThatCallsMSBuild.bat" />
but no dice, still msbuild is not found.
The answer I came up with was to take advantage of the seldom-demonstrated-online multi-line Command string to the Exec task.
<Exec Command="call "%VS100COMNTOOLS%..\..\VC\vcvarsall.bat" x86
set AnotherEnvVar=$(RandomMSBuildProperty)
call BatchFileThatCallsMSBuild.bat
type file_with_output_from_the_msbuilds_in_the_batchfile.log" />
This let me set up the basic build environment (call to vcvarsall), push an msbuild property out to the Exec's environment where the batched msbuilds could see it, call the batch file, and even pull the hidden msbuild output up to the level of the Exec task for clearer logging in Team Build.
I'm not thrilled with having to embed yet another reference to this specific VS version in my code, but it works for now.
I would like explorer opened up on a certain folder, after the deployment has happened, and I'm using the following to attemp it:
<Target Name="AfterBuild">
<Exec Command="..."></Exec>
</Target>
However, a simple "explorer \somewhere" causes the build process to block on explorer, and it wont finish until you close explorer. Prefixing it with start explorer \\somewhere doesn't change that either.
Is there a simple way to do this? Thing is that we only deploy to a intermediate stage, and want the last step done manually, and opening an explorer on the relevant folder is a nicety. The Exec command actually calls a BAT file if that matters, using VS.NET 2008, on Server 2008 Standard.
What I ended up doing was have a
<Exec Command="..." Timeout="2000"></Exec>
That is, launch Explorer from a different Exec element then the copy-element, and then add a somewhat short timeout on this element. This means VS starts up Explorer, and after 2 seconds, returns.
What I ended up doing was starting a powershell process to the pre-build events. It executes the command without blocking the rest of the build process.
powershell start-process -workingdirectory "..." "cmd " """/k ..."""
In the DOS shell you can use cmd /c to call another process and not wait for it's return. You this works fine as a post-build event.
<Target Name="AfterBuild">
<Exec Command="cmd /c start explorer"></Exec>
</Target>