Updating a appsettings.json file from Teamcity - msbuild

I have a appsettings.json file in my dotnet core project. From Teamcity I need to update the content of the file. File looks something like this
{
"keyofjson":"valuetobeupdated"
}
I need to update the text "valuetobeupdated" based on "keyofjson".
In traditional .net projects we had .config file which could be updated using msbuild file based using the target from xmlpeek and xmlpoke tasks. Do we have something same in dotnet core?

In step build use PowerShell script:
$filePath = "yourPath"
(GC $filePath).Replace("valuetobeupdated", "keyofjson") | Set-Content $filePath

You've not specified that this value is different on a build-by-build basis, if the value you want to change is the same every time you can simply add a build feature and select 'File content replacer' and find and replace 'valuetobeupdated' with the value you want.
If the value is a value that is calculated during the build then you will need to use a build step, e.g. powershell/commandline step to do the said find and replace.

Related

Set ClickOnce ApplicationVersion and MinimumRequiredVersion to date in VSTS Build

I have a visual studio build step in a CI build that creates the clickonce files of a desktop application using the MSBuild arguments below:
/target:publish /p:ApplicationVersion=$(Year:yyyy).$(Month).$(DayOfMonth).$(Build.BuildId) /p:MinimumRequiredVersion=$(Year:yyyy).$(Month).$(DayOfMonth).$(Build.BuildId) /p:InstallUrl=$(InstallUrl)
The $(Build.BuildId) and $(InstallUrl) variables get replaced with their correct values but the $(Year:yyyy), $(Month) and $(DayOfMonth) variables do not get replaced. I am using the same variables to set the Build number format on the General tab and they get replaced correctly. Is it not possible to use the date based variables in a build step in VSTS?
Edit: It appears using $(Build.BuildNumber) would work but I like to include the build definition name in the build number format, which obviously won't work for the version.
$(Year:yyyy), $(Month), $(DayOfMonth) are tokens you could use only in the Build Number Format field, not anywhere else.
I would suggest you to create yourself those variables on the fly, leveraging the following script run by the PowerShell task (with an Inline Script) just before your Visual Studio Build task:
$date = get-date
write-host "##vso[task.setvariable variable=Year;]$(($date).year)"
write-host "##vso[task.setvariable variable=Month;]$(($date).month)"
write-host "##vso[task.setvariable variable=Day;]$(($date).day)"
Then you could use $(Year), $(Month) and $(Day) in place of the tokens you currently use as additional MSBuild arguments.

Can Liquibase Autopick new changesets files like Flyway?

I am new to both Liquibase and Flyway. Was trying to do some Hello Worlds. I successfully ran basic SQL ( create insert etc ) using both Liquibase and Flyway. Was interested in running them from command line.
Flyway :
was kind of easy to start with
I had to just Put sql file in correct naming format 'V1_xxxx.sql' in correct folder 'flyway/sql' & Run 'flyway migrate'
the best part was it automatically picked up any new sql file given the correct file name.
LiquiBase :
had to spend some time to understand and use it
Need to give correct file name each time
liquibase --driver=com.mysql.jdbc.Driver --classpath=/path/to/classes --changeLogFile=com/example/db.changelog1.xml --url="jdbc:mysql://localhost/example" --username=dev migrate
liquibase --driver=com.mysql.jdbc.Driver --classpath=/path/to/classes --changeLogFile=com/example/db.changelog2.xml --url="jdbc:mysql://localhost/example" --username=dev migrate
Is there a way in liquibase to automatically pick the new xml files ? like Flyway i could just give folder name and Liquibase could use its table DATABASECHANGELOG to find the deltas and execute same.
Second Question for Liquibase only
In windows in order to run command successfully i had to change the changeLogFile parameter ... from ...
liquibase --driver=com.mysql.jdbc.Driver --classpath=/path/to/classes --changeLogFile=com/example/db.changelog1.xml --url="jdbc:mysql://localhost/example" --username=dev migrate
to
liquibase --driver=com.mysql.jdbc.Driver --classpath=/path/to/classes --changeLogFile=./db.changelog1.xml --url="jdbc:mysql://localhost/example" --username=dev migrate
i.e. i changed my present working directory to com/example and then modified the changeLogFile param to point to a file in current folder and execute command.
Is there a way i can point to changeLogFile in another folder (apart from current folder)
One thing you can do to make liquibase a bit easier to use from the commandline is to create a file named liquibase.properties and save that in the directory where you are running the command. If I remember correctly, the command line will look for files with that name and use the properties in that file rather than requiring all the options on the command line. See http://www.liquibase.org/documentation/liquibase.properties.html and http://www.liquibase.org/documentation/command_line.html#using_a_liquibase.properties_file for more details. The docs there have this:
If you do not want to always specify options on the command line, you
can create a properties file that contains default values. By default,
Liquibase will look for a file called “liquibase.properties” in the
current working directory, but you can specify an alternate location
with the --defaultsFile flag. If you have specified an option in a
properties file and specify the same option on the command line, the
value on the command line will override the properties file value.
Yes, you can have liquibase automatically load files from a directory. You have to have a simple changelog.xml that is referenced from the command line or your properties file, but then that changelog can just reference another directory that contains more changelog files. The <includeAll> tag is used for this purpose (see http://www.liquibase.org/documentation/includeall.html) for more details.
Also, yes, you can put the changelog file wherever you like.

How can I conditionally include large scripts in my ssdt post deployment script?

In our SSDT project we have a script that is huge and contains a lot of INSERT statements for importing data from an old system. Using sqlcmd variables, I'd like to be able to conditionally include the file into the post deployment script.
We're currently using the :r syntax which includes the script inline:
IF '$(ImportData)' = 'true'
BEGIN
:r .\Import\OldSystem.sql
END
This is a problem because the script is being included inline regardless of whether $(ImportData) is true or false and the file is so big that it's slowing the build down by about 15 minutes.
Is there another way to conditionally include this script file so it doesn't slow down the build?
Rather than muddy up my prior answer with another. There is a special case with a VERY simple option.
Create separate SQLCMD input files for each execution possibility.
The key here is to name the execution input files using the value of your control variable.
So, for example, your publish script defines variable 'Config' which may have one of these values: 'Dev','QA', or 'Prod'.
Create 3 post deployment scripts named 'DevPostDeploy.sql', 'QAPostDeploy.sql' and 'ProdPostDeploy.sql'.
Code your actual post deploy file like this:
:r ."\"$(Config)PostDeploy.sql
This is very much like the build event mechanism where you overwrite scripts with appropriate ones except you don't need a build event. But you are dependent upon naming your scripts very specifically.
The scripts referenced using :r are always included. You have a couple of options but I would first verify that if you take the script out it improves the performance to where you want it to get to.
The simplest approach is to just keep it outside of the whole build process and change your deploy process so it becomes a two step thing (deploy DAC then deploy script). The positives of this are you can do things outside of the ssdt process but the negatives are you don't get things like auto disabling of constraints on tables changing in the deployment.
The second way is to not include the script in the deploy when you build but create an AfterBuild msbuild task that adds the script as a post deploy script in the dacpac. The dacpac is a zip file so you can use the .net packaging Api to add a part called postdeploy.sql which will then be included in the deployment process.
Both of these ways mean you lose verification so you might want to keep it in a separate ssdt project which has a "same database" reference to your main project, it will slow down the build when it changes but should be quick the rest of the time.
Here is the way I had to do it.
1) Create a dummy post-deploy script.
2) Create build configurations in your project for each deploy scenario.
3) Use a pre-build event to determine which post deploy configuration to use.
You can either create separate scripts for each configuration or dynamically build the post-deploy script in your pre-build event. Either way you base what you do on the value of $(configuration) which always exists in a build event.
If you use separate static scripts, your build event only needs to copy the appropriate static file, overwriting the dummy post-deploy with whichever script is useful in that deploy scenario.
In my case I had to use dynamic generation because the decision about which scripts to include required knowing the current state of the database being deployed to. So I used the configuration variable to tell me which environment was being deployed to and then used an SQLCMD script with :OUT set to my Post-Deploy script location. Thus my pre-build script would then write the post-deploy script dynamically.
Either way, once build completed and the normal deploy process started the Post-Deploy script contained exactly the :r commands that I wanted.
Here's an example of the SQLCMD script I invoke in pre-build.
:OUT .\Script.DynamicPostDeployment.sql
PRINT ' /*';
PRINT ' DO NOT MANUALLY MODIFY THIS SCRIPT. ';
PRINT ' ';
PRINT ' It is overwritten during build. ';
PRINT ' Content IS based on the Configuration variable (Debug, Dev, Sit, UAT, Release...) ';
PRINT ' ';
PRINT ' Modify Script.PostDeployment.sql to effect changes in executable content. ';
PRINT ' */';
PRINT 'PRINT ''PostDeployment script starting at''+CAST(GETDATE() AS nvarchar)+'' with Configuration = $(Configuration)'';';
PRINT 'GO';
IF '$(Configuration)' IN ('Debug','Dev','Sit')
BEGIN
IF (SELECT IsNeeded FROM rESxStage.StageRebuildNeeded)=1
BEGIN
-- These get a GO statement after every file because most are really HUGE
PRINT 'PRINT ''ETL data was needed and started at''+CAST(GETDATE() AS nvarchar);';
PRINT ' ';
PRINT 'EXEC iESxETL.DeleteAllSchemaData ''pExternalETL'';';
PRINT 'GO';
PRINT ':r .\PopulateExternalData.sql ';
....
I ended up using a mixture of our build tool (Jenkins) and SSDT to accomplish this. This is what I did:
Added a build step to each environment-specific Jenkins job that writes to a text file. I either write a SQLCMD command that includes the import file or else I leave it blank depending on the build parameters the user chooses.
Include the new text file in the Post Deployment script via :r.
That's it! I also use this same approach to choose which pre and post deploy scripts to include in the project based on the application version, except that I grab the version number from the code and write it to the file using a pre-build event in VS instead of in the build tool. (I also added the text file name to .gitignore so it doesn't get committed)

TFS 2015 Can build variables access other build variables?

When I define a custom variable in the new TFS 2015 team build as follows:
Name: SomeOutput
Value: $(System.DefaultWorkingDirectory)\Some
...it doesn't seems to expand $(System.DefaultWorkingDirectory).
Is there a way around this?
EDIT:
At least it seems it's not expanded everywhere.
For example, in MSBuild-Arguments, /p:OUTPUT="$(SomeOutput)" is expanded to /p:OUTPUT="C:\TfsData\BuildAgents\_work\3\s\Some" but when i add a cmd line build task with tool set to cmd and parameter set to /k set, it prints
SOMEOUTPUT=$(System.DefaultWorkingDirectory)\Some
EDIT 2:
Here are my variables
This is my workflow step
And this is what the build prints
You can use the VSTS Variable Tasks extension from the Visual Studio Marketplace.
When you define a variable in the Variables screen and use other variables as value, they won't be expanded (as you may have expected). Instead the literal text is passed to the tasks in the workflow. Without this little task the following configuration won't work:
Variable Value
Build.DropLocation \\share\drops\$(Build.DefinitionName)\$(Build.BuildNumber)
By adding the Expand variable(s) task to the top of your workflow, it will take care of the expansion, so any task below it will receive the value you're after.
https://github.com/jessehouwing/vsts-variable-tasks/wiki/Expand-Variable
PS: The new agent (version 2.x) auto-expands variables now.
It can be achieved.
You may need use % % instead of $ to call the variables in cmd to print the result. It is also necessary to add call in the front of the command. Here is a simple example:
Note: System.DefaultWorkingDirectory is not available in cmd (not sure why); you need use System_DefaultWorkingDirectory instead. Details can be viewed in the logs.
I had the same problem - wanted to piece together a path made up of several built-in variables and pass it to a PS script.
Workaround:
I ended up combining the variables in the actual script through the corresponding generated environment variables (for example $env:BUILD_SOURCESDIRECTORY).
Not what I had in mind originally, but it works at least. Drawback - if I need to change the path, I always have to change the PS script instead of a build variable.

Why won't MSBuild build a project with a dot in the name?

The Story So Far
I've got a nice solution with a desktop application project, a few library projects, and a couple of development tools projects (also desktop applications). At the moment, my build server outputs all of the code into one OutputPath. So we end up with
drop-x.y.z\
Company.MainApplication.exe <-- main application
Company.MainApplicationCore.dll <-- libraries
Helper.exe <-- developer tools
Grapher.exe
Parser.exe
... <-- the rest of the output
But, we're growing up and people outside of our team want access to our tools. So I want to organize the output. I decided that what we would want is a different OutputPath per executable project
drop-x.y.z\
Company.MainApplication\
Company.MainApplication.exe <-- main application
Company.MainApplicationCore.dll <-- libraries
... <-- application specific output
Helper\
Helper.exe <-- developer tools
... <-- tool specific output
Grapher\
Grapher.exe
...
Parser\
Parser.exe
...
What I Did
I found this simple command. I like it because it retains all the Solution working-dir context that makes msbuild a pain.
msbuild /target:<ProjectName>
For example, from my solution root as a working directory, I would call
PS> msbuild /target:Helper /property:OutputPath="$pwd\out\Helper"
I'm testing this from PowerShell, so that $pwd resolves to the full path to my working directory, or the Solution root in this case. I get the output I desire.
However, when I run this command
PS> msbuild /target:Company.MainApplication /property:OutputPath="$pwd\out\Company.MainApplication"
I get the following error output (there's no more information, I ran with /verbosity:diagnostic)
The target "Company.MainApplication" does not exist in the project.
What I Need
The command fails on any project with a dot or dots in the name. I tried with many combinations of working directories and properties. I tried several ways of escaping the property values. I also tried running the command from a <Task> in a targets file.
I need to know either
A) How to fix this command to work property
B) How to achieve the same output with minimal friction
Try using an underscore as an escape character for the dot in the target parameter, e.g.
msbuild /target:Company_MainApplication /property:OutputPath="$pwd\out\Company.MainApplication"
Specify the target after the -target: switch in the format :. If the project name contains any of the characters %, $, #, ;, ., (, ), or ', replace them with an _ in the specified target name.
https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-build-specific-targets-in-solutions-by-using-msbuild-exe?view=vs-2019
Dan Nolan's answer and comments are correct. Just want to supplement the Microsoft documentation.
The /targets: switch is to identify a <Target to run in the project file. You need to supply your .csproj file as a an argument that is not prefixed by a /xx option marker.
You might also want to work based on the .sln file. In that case, you still dont specify the project in the .sln to build in this manner. I'll leave you to search up the correct syntax in case that's what you end up doing.