I'm in the process of developing several custom build scripts for TFS and I'd like to know if there are any best practices for developing, testing and deploying TFS build scripts.
Do you setup development and QC environments that are seperate from the production build server? Are there other ways to isolate the process of developing the scripts from the rest of the build process so that builds scripts under development don't interfere with "production" builds?
Team Build likes to create work items, update work items and add labels as part of the build process which I'd rather not have happen for a "test" build.
jMM
Check out my answer here: Modular TeamBuilds
You can keep core functionality factored out into a common MSBuild file that's included across all builds. Furthermore, all of these files are part of your broader branch structure, so they participate directly in your preexisting SDLC without any extra work. Thus:
If you're making risky changes to your build scripts, make them in a "dev" or "private" branch, just as you would with any other risky changes.
If you want a build definition that's just for quick validation, set properties like SkipLabel, SkipWorkItemCreation, etc to False in the *.targets file imported by that build definition.
To expand on #2 a bit, let's take your example of "production" vs "test" builds. You only want to turn on features like labeling in production builds. So you would remove the SkipLabel property from TFSBuild.proj (and also TFSBuild.Common.targets if it's defined there) and instead set it in TFSBuild.Production.targets and TFSBuild.Test.targets -- using two different values, of course.
As mentioned in the earlier question, TFSBuild.proj is the master msbuild file that controls how the rest of the build will operate. Here's what mine looks like:
<?xml version="1.0" encoding="utf-8"?>
<!-- DO NOT EDIT the project element - the ToolsVersion specified here does not prevent the solutions
and projects in the SolutionToBuild item group from targeting other versions of the .NET framework.
-->
<Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<!-- Import configuration for all MyCompany team builds -->
<Import Project="MyCompany.TeamBuild.Common.targets"/>
<!-- Import build-specific configurations -->
<Import Condition="'$(BuildDefinition)'=='Dev - quick'" Project="MyCompany.TeamBuild.Quick.targets" />
<Import Condition="'$(BuildDefinition)'=='Main - full'" Project="MyCompany.TeamBuild.Full.targets" />
<Import Condition="'$(BuildDefinition)'=='Main - quick'" Project="MyCompany.TeamBuild.Quick.targets" />
<Import Condition="'$(BuildDefinition)'=='Release - full'" Project="MyCompany.TeamBuild.Full.targets" />
<!-- This would be much cleaner as we add more branches, but msbuild doesn't support it :(
Imports are evaluated declaratively at parse-time, before any tasks execute
<Target Name="BeforeEndToEndIteration">
<RegexReplace Input="$(BuildDefinition)" Expression=".*\s-\s" Replacement="">
<Output TaskParameter="Output" PropertyName="BuildType" />
</RegexReplace>
</Target>
<Import Condition="$(BuildType)==full" Project="MyCompany.TeamBuild.Full.targets" />
<Import Condition="$(BuildType)==quick" Project="MyCompany.TeamBuild.Quick.targets" />
-->
</Project>
By doing something similar, you can ensure that all builds from the Dev branch are "quick" builds (which for you means no labeling, etc), all builds from the Release branch are "full" builds, and builds from the Main branch can be either depending on which build definition the user launches from Visual Studio / TSWA. Myself, I have "quick" builds set up with Continuous Integration and "full" builds running nightly.
Related
I have a azure webjob project that uses config transforms to create dev/test/release configuration. We are using TFS for CI/CD deployment to Azure. I want to have MSBuild apply the transforms for dev so we can debug locally. However, when we are building in TFS in the CI/CD pipeline I need to disable the config transforms during the build step.
TFS has an "apply XML transformations" checkbox in the release step, which is where we want the transforms applied since we have the environment variable set during release. Unfortunately, this is not working because the transforms are already applied during build so the release artifact only has the finished output file, not the separate transform files.
I have tried editing the .csproj file to disable the transforms. I assume the transforms are being performed by the following section of the project file:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="Exists('App.$(Configuration).config')">
<!--Generate transformed app config in the intermediate directory-->
<TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" />
<!--Force build process to use the transformed configuration file from now on.-->
<ItemGroup>
<AppConfigWithTargetPath Remove="App.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
<!--Override After Publish to support ClickOnce AfterPublish. Target replaces the untransformed config file copied to the deployment directory with the transformed one.-->
<Target Name="AfterPublish">
<PropertyGroup>
<DeployedConfig>$(_DeploymentApplicationDir)$(TargetName)$(TargetExt).config$(_DeploymentFileMappingExtension)</DeployedConfig>
</PropertyGroup>
<!--Publish copies the untransformed App.config to deployment directory so overwrite it-->
<Copy Condition="Exists('$(DeployedConfig)')" SourceFiles="$(IntermediateOutputPath)$(TargetFileName).config" DestinationFiles="$(DeployedConfig)" />
</Target>
I tried adding conditions like "$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" to these sections, and it did not help (the transforms still got applied in all three environments). I even commented this section out completely, and I still got the transforms. This leaves me with three questions:
How can the config transforms be disabled?
How can I conditionally
disable them so they are still applied when debugging in VS?
Is this
the correct approach, or is there a better way to get the correct
transform applied when using CI/CD in TFS 2017?
To disable the config transform during the build, you just need to add argument /p:TransformWebConfigEnabled=False in MSBuild Arguments section of your Build task. You also need to add /p:AutoParameterizationWebConfigConnectionStrings=False if you want to update the connection string during the release.
Besides, you need to update your project file so that the Web.XXX.Config file will be included in the package if you are generating msdeploy package for deployment.
Change the "Build Action" of the config file from "None" to "Content".
Unload your project file and remove <DependentUpon> tag for the config file.
With these "MSBuild Arguments" it works for me:
/p:AutoParameterizationWebConfigConnectionStrings=false
/p:DeployOnBuild=true
/p:MarkWebConfigAssistFilesAsExclude=false
/p:PackageAsSingleFile=false
/p:PackageLocation="$(build.artifactstagingdirectory)\\"
/p:ProfileTransformWebConfigEnabled=false
/p:SkipInvalidConfigurations=true
/p:TransformWebConfigEnabled=false
/p:WebPublishMethod=FileSystem
The important ones:
/p:AutoParameterizationWebConfigConnectionStrings=false
/p:MarkWebConfigAssistFilesAsExclude=false
/p:ProfileTransformWebConfigEnabled=false
/p:TransformWebConfigEnabled=false
It started to work when I also added:
/p:ProfileTransformWebConfigEnabled=false
Thanks to #Eddie Chen - MSFT & #Wessel T.
Regards Hans
We store various design documents within TFS in multimarkdown format. We also have an EXE process that can run to take those MMD files and generate PDF's from them - but just by getting the files from a local folder.
What we'd like to do is to have a process run "on-checkin", just as if you'd run an automatic build on checkin (i.e., ultimately calling msbuild to compile an application) but in our case we'd like it to be able to get a list of the checked in files and to process and generate an output of them. The result doesn't need to be in TFS because they're a build output, not the source.
I'm sure this should be somehow possible by taking the same approach as must be taken by the workflow for a "normal" build.
Has anybody done anything like this or can point me in a suitable direction please ?
You could use the exec task in MSBuild to invoke the exe and "build" your output. Create a file called something like buildDocs.proj and check it in to TFS possibly in a folder under the things you want to build. Use the MSbuild below as a guide.
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build">
<Exec Command='"My.exe" -My Paramiters' />
<ItemGroup>
<CopyItems Include="[path to output]\*.*" />
</ItemGroup>
<Copy SourceFiles="#(CopyItems)" DestinationFolder="$(OutDir)\SomeDir" />
</Target>
</Project>
The trick will be in identifying the various paths involved.
Use the default template to build the proj, just as you would a c# project. If you need to pass in additional Parameters to MSBuild you can do this from within the advanced section of the build definition process tab.
I have found other posts here on StackOverflow that deal with my issue I am experiencing, for example:
MSBuild: Deploying files that are not included in the project as well as Include Files in MSBuild that are not a part of the project
I wanted to share the code that I was able to create after reading these posts and ask for some help as to why it might not be working?
To elaborate on what exactly is not wrong and what I intend to do. I am using Visual Studio 2012, and TFS 2012.
I have a batch file called CreateMyFiles.bat, and what I would like to do is execute this and then take the files it outputs (it outputs them to my Includes/Javascript/Bundled folder) and include them in part of the build in MSBuild (so that they are deployed to the target IIS server).
When I edited my local .csproj in my local Visual Studio and added the code below to the bottom of the file and reloaded, I was able to right click on my web projbect, select 'publish', and then select my local file-based publishing profile which did indeed deploy my files to the correct location. It worked!
I then checked in my code to TFS, and went to 'builds' on TFS, and queued a new build. Sure enough, I was able to see the files output to the same directory on the build server. Now, i'm not 100% sure about MSBuild but I noticed that just like when I hit publish locally, it created a _publishedWebsite folder on the build server as well (a directory above the source). The thing is, within this publishedwebsite folder, my manually created files were not present. Furthermore, going to the target web server after the build was done unfortunately did not have the files I wanted.
So it seems like if I were to manually select publish, the code below works, but if I were to queue a build with TFS, it does not work. Does MSBuild use publish? Could that be the reason it does not work below?
Here is the code I've placed in my .csproj file:
<Target Name="CustomCollectFiles">
<Exec Command="CreateMyFiles.bat" /> <!-- Generate Files -->
<ItemGroup>
<!-- Create an identity called _CustomFiles, and associate it to the files I created -->
<_CustomFiles Include="Includes\JavaScript\Bundled\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>Includes\JavaScript\Bundled\*%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<!-- Hook into the pipeline responsible for gathering files and tell it to add my files -->
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
I'm really stuck on this and wanted to ask for some help as to why the files might not be going. I suspect MSBuild doesn't use publish and that's why it works locally (because i'm selecting publish)?
Thanks so much for your help
UPDATE
Tried this as per comments below, but this time the files didn't even appear (so it seemed to not even run the tasks now). Any idea why? Did I type this right?
<Target Name="CustomCollectFiles">
<Exec Command="CreateMyFiles.bat" />
<!-- Generate Files -->
<ItemGroup>
<!-- Create an identity called _CustomFiles, and associate it to the files I created -->
<_CustomFiles Include="Includes\JavaScript\Bundled\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>Includes\JavaScript\Bundled\*%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<!-- Hook into the pipeline responsible for gathering files and tell it to add my files -->
<PropertyGroup>
<PipelineCollectFilesPhaseDependsOn>
CustomCollectFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>
UPDATE 2
When I take the above code, and place it into my pubxml file and then execute an actual publish, it works, but as far as I know our process is to just queue a build from TFS. Is it possible to hook into the above code block when simply queuing a build? Or do I need to publish?
to do a publish from TFS build you need to add the following arguments
/p:DeployOnBuild=true;PublishProfile=Release
obviously using your own PublishProfile name
In VS2012, the target was renamed from:
CopyAllFilesToSingleFolderForPackageDependsOn
to:
CopyAllFilesToSingleFolderForMsdeployDependsOn
Update: looks like the above Targets are not getting called from within VS2012 targets, can you replace it with a call to the Target "PipelineCollectFilesPhaseDependsOn"? That should fix it.
<PropertyGroup>
<PipelineCollectFilesPhaseDependsOn>
CustomCollectFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>
I'm in the midst of doing a test upgrade from TFS2010 to TFS2012. They're using the Upgrade Template to run an extensive set of custom MSBuild tasks to automate their build and deployment. The MSBuild projects were originally created for TFS2005, and were upgraded directly to TFS2010 at some point. The solutions they're building are mostly targeted at .NET 2.0, and they're still using VS2005 for most development.
So far, I've installed VS2005, VS2010, and TFS2012 with Update 1 (in that order), and upgraded their TFS2010 databases to TFS2012. The build controller lives on the same machine as the app tier and database, just because this is a "proof of concept" upgrade to identify any issues that will need to be addressed with the build process prior to the real upgrade.
When I run any of their MSBuild-based builds, I get the following error:
C:\Builds\18\Web\ES-INTEGRATION-WebTest\BuildType\TFSBuild.proj (75):
An extension of type
'Microsoft.TeamFoundation.Build.Client.IBuildDetail' must be
configured in order to run this workflow.
C:\Builds\18\Web\ES-INTEGRATION-WebTest\BuildType\TFSBuild.proj (75):
The "Get" task failed unexpectedly.
System.Activities.ValidationException: An extension of type
'Microsoft.TeamFoundation.Build.Client.IBuildDetail' must be
configured in order to run this workflow.
at
System.Activities.Hosting.WorkflowInstanceExtensionCollection..ctor(Activity
workflowDefinition, WorkflowInstanceExtensionManager extensionManager)
at
System.Activities.Hosting.WorkflowInstanceExtensionManager.CreateInstanceExtensions(Activity
workflowDefinition, WorkflowInstanceExtensionManager extensionManager)
at
System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager
extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at
System.Activities.WorkflowApplication.RunInstance(WorkflowApplication
instance)
at System.Activities.WorkflowApplication.Invoke(Activity activity,
IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions,
TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow,
IDictionary`2 inputs, TimeSpan timeout,
WorkflowInstanceExtensionManager extensions)
at
Microsoft.TeamFoundation.Build.Tasks.WorkflowTask.ExecuteInternal()
at Microsoft.TeamFoundation.Build.Tasks.Task.Execute()
at
Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
at
Microsoft.Build.BackEnd.TaskBuilder.d__20.MoveNext()
I've gone through and pared the build down to a very small case that reproduces the issue. The complete .proj file is as follows:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<!-- Do not edit this -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<ProjectExtensions>
<!-- DESCRIPTION
The description is associated with a build type. Edit the value for making changes.
-->
<Description>Builds and Deploys the BE site to the integration environment</Description>
<!-- BUILD MACHINE
Name of the machine which will be used to build the solutions selected.
-->
<BuildMachine>[redacted]</BuildMachine>
</ProjectExtensions>
<PropertyGroup>
<!-- TEAM PROJECT
The team project which will be built using this build type.
-->
<TeamProject>Web</TeamProject>
<!-- BUILD DIRECTORY
The directory on the build machine that will be used to build the
selected solutions. The directory must be a local path on the build
machine (e.g. c:\build).
-->
<BuildDirectoryPath>c:\build</BuildDirectoryPath>
<!-- DROP LOCATION
The location to drop (copy) the built binaries and the log files after
the build is complete. This location has to be a valid UNC path of the
form \\Server\Share. The build machine service account and application
tier account need to have read write permission on this share.
-->
<DropLocation>\\[redacted]\BuildDrop</DropLocation>
<!-- TESTING
Set this flag to enable/disable running tests as a post build step.
-->
<RunTest>True</RunTest>
<!-- WorkItemFieldValues
Add/edit key value pairs to set values for fields in the work item created
during the build process. Please make sure the field names are valid
for the work item type being used.
-->
<WorkItemFieldValues>Symptom=build break;Steps To Reproduce=Start the build using Team Build</WorkItemFieldValues>
<!-- CODE ANALYSIS
To change CodeAnalysis behavior edit this value. Valid values for this
can be Default,Always or Never.
Default - To perform code analysis as per the individual project settings
Always - To always perform code analysis irrespective of project settings
Never - To never perform code analysis irrespective of project settings
-->
<RunCodeAnalysis>Default</RunCodeAnalysis>
<!-- UPDATE ASSOCIATED WORK ITEMS
Set this flag to enable/disable updating associated workitems on a successful build
-->
<UpdateAssociatedWorkItems>false</UpdateAssociatedWorkItems>
<!-- Title for the work item created on build failure -->
<WorkItemTitle>Build failure in build:</WorkItemTitle>
<!-- Description for the work item created on build failure -->
<DescriptionText>This work item was created by Team Build on a build failure.</DescriptionText>
<!-- Text pointing to log file location on build failure -->
<BuildlogText>The build log file is at:</BuildlogText>
<!-- Text pointing to error/warnings file location on build failure -->
<ErrorWarningLogText>The errors/warnings log file is at:</ErrorWarningLogText>
</PropertyGroup>
<PropertyGroup>
<SourceBranchPath>Main</SourceBranchPath>
</PropertyGroup>
<!-- Does some basic validation of the environment before the build starts -->
<Target Name="PreBuildValidations" >
<Get FileSpec="$/Web/$(SourceBranchPath)/BuildFiles/Tools/PSExec.exe" Workspace="$(WorkspaceName)" Recursive="false" Force="true" TeamFoundationServerUrl="$(TeamFoundationServerUrl)"/>
</Target>
<Target Name="BeforeGet">
<CallTarget Targets="PreBuildValidations" />
</Target>
</Project>
I figured it out. Apparently, the BuildUri parameter is required for the Get task. I added BuildUri="$(BuildURI)" and all is well.
Your build is failing on the "Get Task" in your msbuild script. You are only pulling down one file which is an .exe file in the BeforeGet target. In order to build a project you need a project file.
It looks like the error may be because you missing a parameter or passing in an invalid parameter in the Get task.
Why the need for a prebuildvalidation step when the build can just pull the file down from the build definition workspace?
I've tried a number of different configurations with this and I haven't achieved my result.
TL;DR
I'm trying to add config transforms into my build process and am looking for the right way to do it from MSBuild so that it shows up in my deployments via MSDeploy.
Background
I have an WebApp (MVC3), a Core app (CS Class Lib), and two test class libs, one for each.
I have a build script in my solution that uses MSBuild to compile.
One of those MSBuild targets deploys to an IIS server using MSDeploy
This process is working so far both manually and via CruiseControl.NET
Goal
I would like to add Web.Config transforms to this process. I figured I would do something simple at first, like an app setting called "PEAppsEnvironmentName", which I would make Dev, Test, or Prod based on the current environment.
Theory So Far
To me, it appears that when packaging with MSDeploy, I'm not transforming the config file.
When I run MSBuild with the DeployOnBuild option set to true, it creates another package that has the appropriately transformed config. It just seems like somehow I can't get it all to match up. The end result is that the web page displays "None" (the initial setting) instead of the transformed "Development" string.
I think if I could find out how to use MSDeploy during the packaging phase to transform the MSConfig, I'd be good to go.
Code
My web.config file
<appSettings>
<add key ="PEAppsEnvironmentName" value="None"/>
...
</appSettings>
My Web.Dev.config file
<appSettings>
<add key ="PEAppsEnvironmentName" xdt:Transform="Replace" xdt:Locator="Match(key)" value="Development" />
</appSettings>
My MSBuild Targets
Property group showing default config is "Dev"
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Dev</Configuration>
</PropertyGroup>
My MSBuild "Compile" Target
<Target Name="Compile" DependsOnTargets="Init">
<MSBuild Projects="#(SolutionFile)" Targets="Rebuild" Properties="OutDir=%(BuildArtifacts.FullPath);DeployOnBuild=True"/>
</Target>
My MSBuild "Package" Target
<Target Name="Package" DependsOnTargets="Compile;Test">
<PropertyGroup>
<PackageDir>%(PackageFile.RootDir)%(PackageFile.Directory)</PackageDir>
<Source>%(WebSite.FullPath)</Source>
<Destination>%(PackageFile.FullPath)</Destination>
</PropertyGroup>
<MakeDir Directories="$(PackageDir)"/>
<Exec Command='"#(MSDeploy)" -verb:sync -source:iisApp="$(Source)" -dest:package="$(Destination)" '/>
</Target>
My MSBuild "Deploy" Target
(scrubbed for PWs, etc.)
<Target Name='Deploy' DependsOnTargets='Package'>
<PropertyGroup>
<Source>%(PackageFile.FullPath)</Source>
</PropertyGroup>
<Exec Command ='"#(MsDeploy)" -verb:sync -source:package="$(Source)" -dest:iisApp=PEApps,computerName=$(WebServerName),username=[User],password=[Password]'/>
</Target>
There was a lot to this question, I'm not sure if I'm fully on the same page as you but I'll summarize my impression of what you are asking. You have an existing web project which is in a solution with other projects. You need to be able to package the web project so that you can publish it to multiple destinations.
I have created a NuGet package which can be used for this exact purpose. It's called package-web. When you add it to your web project it will update the packaging process. When you create a package a few additional files will be included in the package, including all the web.config transform files. A .ps1 file will be created next to the package as well. You can use this script to publish the package. It will prompt you for which transform to run and for all the Web Deploy parameters. You can also save the responses to a file and then just pass them to the .ps1 file so that you can perform non-interactive publishes. I created a 5 minute video on it at http://nuget.org/packages/PackageWeb
package web: http://sedodream.com/2012/03/14/PackageWebUpdatedAndVideoBelow.aspx. FYI this is not yet working with VS 2012 but I'm working on the fix and should have it updated by the time VS 2012 is released.
If you don't find that useful you can see how I implemented the solution at https://github.com/sayedihashimi/package-web and you should see examples of everything that you need to do to roll your own.
FYI if you need to transform any files besides web.config on package create then you should take a look at my VS extension SlowCheetah. Here is a blog about how to integrate it into a build server.