Xml Transformation and Variable substitution doesn't work when both selected in Azure DevOps - azure-devops

I have been working on the build and release pipelines for a while now, but ran into this issue lately
I am performing config transform on .net application. The issue i am running into is a bit strange .. Variable substitution doesn’t work if xml transformation is enabled too, but will work ONLY when variable substitution is enabled. I didn't knew this was possible, as per me Variable substitution would work after XML transformation is done. I want to use both the options. Like i want to transform the existing config file and then substitute the remaining values with pipeline variables & variable groups. That way i would have less involvement from other teams to get the transformation for all the values as i have a time crunch. Eventually would have everything transformed but for now i want to follow the above approach. The log does say Xml transformation and Variable substitution completed successful, but variable substitution doesn't happen.
Am i missing something very silly? Has anyone faced this kind of issue.

Based on my test, XML variable substitution and XML transformation could work at the same time.
Here is the original web.config file:
Check the log, the transformation occurred in the _temp folder instead of the $(System.DefaultWorkingDirectory) (e.g. I use deployment group to run the task, so the $(System.DefaultWorkingDirectory) path is C:\azagent\A18\_work\r4\a).
In the temp folder, I noticed that the file has been transformed successfully.
If the object you deploy is a folder, then this transformation can only be found in the temp folder. After the deployment, the contents of this folder will be automatically deleted after deployment.
If the object you deploy is a zip file, in addition to the temp folder, a zip will be automatically generated in the $(System.DefaultWorkingDirectory) path, and the web.config file in this zip is also successfully transformed.
Update:
Here are some details:
Files:(web.config and web.qa.config)
Web.config:
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="PCWSUser" value="TheUserName" />
</appSettings>
Web.qa.config:
<appSettings>
<add xdt:Transform="Replace" xdt:Locator="Match(key)" key="webpages:Enabled" value="true" />
<add key="PCWSUser" value="TheUserNameQA" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
</appSettings>
Release Pipeline variables:
Task settings:
Summary: the webpages:Version is changed by variable substitution. The others are changed by xml transformation.

Related

How to replace value inside XML in tasks pipeline using variables?

How to update the filename using the variables.
Added Replace Transmission task , but how to update the values(filename = y.zip based on variable passing) inside a .xml files using pipe line tasks.
<?xml version="1.0" encoding="utf-16"?>
<configdata>
<solutions>
<configsolutionfile filename="x.zip" />
</solutions>
</configdata>
You have few options:
token replace task
XML transformation
XML variable substitution
It all actually depends at which stage you want to achieve and what you have already. If you want to do it before generating package I would recommend first option - token replace. If you want to do it after (= just before publishing the package) and you have already config transformation files, you can go with XML transformation. Last one is convinient if you don't want to use tranformation files and you want to do it just before publishing the package.
Krzysztof Madej is correct. And I would also like to add the following points.
Here is an extension: XDT Transform. This extension contains the "XDT Transform" task. This task also supports to transform XML file transformation.
You also could try to use "File transform " task.
If you want to deploy the application to Azure or IIS , the Azure App Service
Deploy task and IIS Web App Deploy task support file transforms and variable
substitution.
Hope this helps.

Conditional <exec> based on results of <copy>

In our nant build script for our web-based application, we <copy> a set of files to a target directory and then run aspnet_compiler over them via <exec>.
<copy> only copies files that have changed, however here is no way to pass this information to <exec>, and I want to avoid running aspnet_compiler when nothing has actually changed.
Options I've tried to find are: <copy> setting a property when any file is copied that can then be checked with <if>; or being able to create a file before the copy and doing something like <if test="any-file-newer-than(targetdir, timestampfile)">. Even better would be if <copy> could return a list of copied files that I can then iterate over to avoid having to process the entire tree, but I think that might be asking a bit too much.
So far, I've drawn a blank: is what I'm looking for possible without writing a custom extension?
Why don't you just simply replace copy task with robocopy? (you're on Windows, right?)
Robocopy returns different exit codes on different successful copy situations:
https://ss64.com/nt/robocopy-exit.html
For example:
0 - ok, nothing copied
1 - ok, something copied
You could do something like this:
<exec program="robocopy.exe" commandline="${SourceDir} ${DestDir}" failonerror="false" resultproperty="ExitCode" />
<fail unless="${ExitCode < 8}" message="Failed to copy"/> <!-- Anything between 0..7 is OK for robocopy -->
<exec unless="${ExitCode == 0}" ...

MSDeploy runCommand using relative path

I am trying to run a command as a part of my packaging/deployment process via MSDeploy. In particular, I am trying to create a custom event log by running installutil against one of my DLLs, but I am having trouble with specifying a relative path to the DLL from the deployment directory. To get started, I added the below config to my csproj in order to generate the runCommand provider inside of my Manifest file. Please note the absolute path to the DLL.
<PropertyGroup>
<!-- Extends the AfterAddIisSettingAndFileContentsToSourceManifest action to create Custom Event Log -->
<IncludeEventLogCreation>TRUE</IncludeEventLogCreation>
<AfterAddIisSettingAndFileContentsToSourceManifest Condition="'$(AfterAddIisSettingAndFileContentsToSourceManifest)'==''">
$(AfterAddIisSettingAndFileContentsToSourceManifest);
CreateEventLog;
</AfterAddIisSettingAndFileContentsToSourceManifest>
</PropertyGroup>
<Target Name="CreateEventLog" Condition="'$(IncludeEventLogCreation)'=='TRUE'">
<Message Text="Creating Event Log" />
<ItemGroup>
<MsDeploySourceManifest Include="runCommand">
<path>installutil C:\inetpub\wwwroot\MyTestApp\bin\BusinessLayer.dll</path>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<ItemGroup>
After calling msbuild, this generated my manifest correctly inside of my package.zip. When I ran MyTestApp.deploy.cmd /Y it correctly called msdeploy and deployed my files to inetpub\wwwroot\MyTestApp and ran my command from the manifest below:
<runCommand path="installutil C:\inetpub\wwwroot\MyTestApp\bin\BusinessLayer.dll ... etc
The problem I am having is I do not want to hardcode this DLL path to c:\inetpub\etc. How can I make the above call using the relative path from my deployment directory under Default Web Site? Ideally, I would like MSDeploy to take this path and pass it as a variable to the runCommand statement in order to find the DLL. Then I could write something like: <path>installutil $DeploymentDir\NewTestApp\bin\BusinessLayer.dll</path> without having to worry about hard-coding an absolute path in.
Is there any way to do this without using the absolute path to my DLL every time?
You can add definition of DeploymentDir to the .csproj with the action you wrote above:
<PropertyGroup>
<DeploymentDir Condition="'$(Configuration)'=='Release' AND '$(DeploymentDir)'==''">Release Deployment Dir</DeploymentDir>
<DeploymentDir Condition="'$(Configuration)'=='Debug' AND '$(DeploymentDir)'==''">Debug Deployment Dir</DeploymentDir>
<DeploymentDir Condition="'$(DeploymentDir)'==''">C:\inetpub\wwwroot</DeploymentDir>
<AplicationName Condition="'$(Configuration)'=='Release' AND '$(AplicationName)'==''">NewTestApp</AplicationName>
<AplicationName Condition="'$(Configuration)'=='Debug' AND '$(AplicationName)'==''">MyTestApp</AplicationName>
<ApplicationDeploymentDir Condition="'$(ApplicationDeploymentDir)'==''">$(DeploymentDir)\$(ApplicationName)\bin</ApplicationDeploymentDir>
</PropertyGroup>
Theese conditions will allow to change everything from command line to take full control over the build process in your build system or script.
MSBuild.exe yourproj.proj /p:Configuration=Release /p:DeploymentDir=D:\package /p:ApplivationName=BestAppForever
And inside of your task you can use it
<ItemGroup>
<MsDeploySourceManifest Include="runCommand">
<path>installutil $(ApplicationDeploymentDir)\BusinessLayer.dll</path>
</MsDeploySourceManifest>
</ItemGroup>
I realize this isn't the answer you probably wanted to hear but this is how I got around it.
We created a powershell script on the destination server. So instead of running your command:
installutil C:\inetpub\wwwroot\MyTestApp\bin\BusinessLayer.dll ... etc
We would run:
c:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe d:\powershell\installSites.ps1 siteName <NUL
The "sitename" is being passed in as a param into the powershell script. Inside the script it knows on that destination server which files to install, any commands that need to run, app pools to recycle, etc.
Again, not as easy as finding a relative path, but it does the job.

Nant, SqlCmd -v switch and spaces in nant property fails build with invalid argument

I have a nant script that ...
1. takes the content of disc-file
2. assigns that content to a nant property
3. and then calls sqlcmd with a -v passing in that property containg the content of the disc file
4. inside the sql script the contents of the file should be used by a stored proc.
The problem is that when the content of the file contains a space the nant build stops with a "Invalid argument" issue
Anone know a way around this ?
The top part of the nant script is ...
<?xml version="1.0"?>
<!-- the main name of this project -->
<project name="Hops" default="all">
<!-- BuildHistory -->
<property name="buildHistoryContents" value="" />
<xmlpeek xpath="/" file="BuildNotes.xml" property="buildHistoryContents"></xmlpeek>
<!-- <echo message="${buildHistoryContents}" /> -->
<!-- ***************** -->
<target name="ExecSql">
<echo message="running sql script : ${SqlBuildScriptsDir}${sqlBuildFileName}" />
<exec program="${SqlCmd}" commandline="-S ${SqlServerInstanceName} -E -d HBus -i ${SqlBuildScriptsDir}${sqlBuildFileName} -v vSchemaVersion=${buildHistoryContents} " />
</target>
The sql script contains the line ...
exec lsp_SchemaVersionUpsert '1.4', N'$(vSchemaVersion)'
A disc file content that works is ...
<BuildNotes>
<Note>
<buildVer>HasNotSpace</buildVer>
</Note>
</BuildNotes>
A disc file content that does not works is ...
<BuildNotes>
<Note>
<buildVer>Has Space</buildVer>
</Note>
</BuildNotes>
The use of all this is pass xml build comments to a table logging version build history for the db schema.
Does anyone know an alternate method or know a way through this ?
The next part, added after Phillip Keeley correcty solved first part (the SPACE Problem)
I simplified the original task to simplify the question.
There is also a Quoted Attribute Problem ; xml quoted attributes cause the nant build to fail with "Invalid Argument".
eg this will cause nant to choke but removing the dt attribute will enable the nant build to succeed ...
<BuildNotes>
<Note>
<buildVer>1.4</buildVer>
<dateStarted>09/24/2009 11:25:42</dateStarted>
<Item dt="20091008" >SpacesAndNoQuotedAttribute</Item>
</Note>
</BuildNotes>
Any ideas ... ?
Your problem is (of course) in line
<exec program="${SqlCmd}" commandline="-S ${SqlServerInstanceName} -E -d HBus -i ${SqlBuildScriptsDir}${sqlBuildFileName} -v vSchemaVersion=${buildHistoryContents} " />
specifically, in
-v vSchemaVersion=${buildHistoryContents}
The NAnt expression replaces property ${buildHistoryContents} with the stored value--which will include any embedded spaces. Problem is, when calling SQLCMD (I"m assuming that's what ${SqlCmd} resolves to) from a command window, the values for any and all -v parameters are space delimited -- that is, the parser hits -v, reads the next characters through the "=" as the variable name, then reads all characters after the = and through the next space (or end of line) as the value to assign to the variable, and that embedded space will mess you up bigtime.
On the command line, the work-around is to wrap the variable value in quotes:
- v MyVariable=Hello World
becomes
- v MyVariable="Hello World"
That doesn't work here, because it's XML and you have to wrap the commandline attribute of the exec element with quotes... and embedded quotes will, once again, mess you up bigtime.
I believe the work-around here is to use XML macro substitution (I quite possibly have the formal titles of these concepts wrong) for those embedded quotes. This value should be
"
Which means that the following should work:
<exec program="${SqlCmd}" commandline="-S ${SqlServerInstanceName} -E -d HBus -i ${SqlBuildScriptsDir}${sqlBuildFileName} -v vSchemaVersion="${buildHistoryContents}" " />
Please try this and see -- I may have to do something like this myself some day soon.

Nant cmd.exe redirection creating file called 'program' on c:\ drive

I have NAnt script which as part of its project calls a batch file using the following task:
<target name="makeplane">
<exec program="C:\WINDOWS\system32\CMD.EXE"
commandline="/C ${make.file} > ${make.log}"
verbose="false"
workingdir="${make.dir}"
basedir="${make.dir}">
</exec>
<delete>
<fileset basedir="c:\">
<include name="program" />
</fileset>
</delete>
</target>
Unfortunately i dont have control over the contents on the batch file and it spews out a lot of garbage onto the screen which is of no use in the log. So to get around this im redirecting the output from the bat file to a text file using the
> ${make.log}
part which equates to "> log.txt".
This redirection seems to create a file called "program" on the C drive and messes up all sorts of services and windows generally doesnt like it. To get around this Im manually deleting this file after the bat file has executed.
The problem is i now need to run a similar task for another project entirely and if they run at the same time then the first will lock the file called "program" and the second will fail. Not exactly a great situation for Continuous integration.
I searched on the net but because the file is called program i get all sorts of rubbish results. Anyone got any ideas on a work around. I tried the output parameter on the exec task but the issue remains the same.
If the file path to the log contains spaces, one generally would want to surround the path in quotes. In order to do this in nant one can use the " entity.
It sounds like this is what's happening in your particular situation. Therefore, if you change your example to the following I think things should work as expected.
<target name="makeplane">
<exec program="C:\WINDOWS\system32\CMD.EXE"
commandline="/C ${make.file} > "${make.log}""
verbose="false"
workingdir="${make.dir}"
basedir="${make.dir}">
</exec>
</target>
Usually this happens because the script is trying to create a file with a long file name with space in it (c:\program files in your case), but it is not using quotes around the long file name.
Here is what I did. I think it is a bit cleaner for complex commands.
<property name="cmd.label" value="\${ss.previous.label}#$Project.SSPath" />
<echo message="Getting $Project.Name source code with label \${cmd.label}" />
<property name="cmd" value=""\${tfs.root}\tf.exe" get $Project.SSPath "/version:L\${cmd.label}" /force /recursive /noprompt"/>
<exec program="cmd.exe"
workingdir="\${shadow.dir}"
failonerror="true"
verbose="true">
<arg value="/c" />
<arg value=""\${cmd}"" />
<arg value="> nul" />
</exec>