Using Visual Studio 2013
I have a set of 8 SSDT projects that can all be deployed to several distinct environments. The advanced publish settings for each project, however, are meant to be identical. At present I have created a distinct publish profile for each environment, meaning I have ~20 publish profiles all using the exact same settings but different connection strings.
Tweaking the publish settings (which happens with some regularity as I am still a bit new to SSDT) for my databases is most annoying, but I have yet to find a way around this as I am unable to apply transforms to publish profiles like I can to web.config files in an ASP.NET project. I even tried installing the Visual Studio SlowCheetah plugin, but it doesn't appear to work with SSDT projects as the option to apply transform does not appear when right-clicking on a publish profile.
I don't want my team to have to think about entering connection details manually when deploying a DB to dev or QA environments. Is there any way to set a master publish profile or otherwise specify a group of shared settings so that I don't have to manage 20 nearly identical publish profiles??
EDIT:
Using SAS' answer I was able to cobble together the following XML for my .sqlproj file:
<PropertyGroup>
<PublishProfileDir>$(ProjectDir)Publish Profiles\</PublishProfileDir>
<TemplatePublishProfile>$(PublishProfileDir)Baseline\publish.xml</TemplatePublishProfile>
</PropertyGroup>
<Target Name="CopyXml" AfterTargets="Build">
<Copy SourceFiles="$(TemplatePublishProfile)" DestinationFolder="$(PublishProfileDir)Dev"/>
<Copy SourceFiles="$(TemplatePublishProfile)" DestinationFolder="$(PublishProfileDir)Qa"/>
</Target>
<ItemGroup>
<DevPublishUpdates Include="ConfigUpdates">
<XPath>/msb:Project/msb:PropertyGroup/msb:TargetDatabaseName</XPath>
<NewValue>CountyRecordsDev</NewValue>
</DevPublishUpdates>
<DevPublishUpdates Include="ConfigUpdates">
<XPath>/msb:Project/msb:PropertyGroup/msb:DeployScriptFileName</XPath>
<NewValue>CountyRecords.Dev.Sql</NewValue>
</DevPublishUpdates>
</ItemGroup>
<Target Name="UpdateXml" AfterTargets="CopyXml">
<Message Text="Editing Derived Xml Publish Profiles" Importance="high" />
<XmlPoke Namespaces="<NamespacePrefix='msb'Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputath="$(PublishProfileDir)Dev\publish.xml"
Query="%(DevPublishUpdates.XPath)"
Value="%(DevPublishUpdates.NewValue)" />
</Target>
The only downside is that I seem to need a separate folder for all my publish profiles in order to prevent one transform from overwriting another, I could not seem to find a way to simply overwrite an existing file. For XmlPoke, the namespaces attribute is critical to operation. I learned more about this process from this blog post by Sayed Ibrahim Hashimi.
We are using a template xml file that is copied automagically as a pre-step in the publish, for all our targets, so any changes need only be mande in the template. The target server name is replaced dynamically as the publish xml files are created. We also had to modify the xaml for this. We use Copy and XMLPoke tags in common proj-file thar is included in our proj-files. It takes some work, but works fine.
Edit: I have pasted in some code below to try to explain, it is only part of the original but I hope it is enough to get everyone started:
This part of what is in our common file (SQLCommonInclude.proj):
<Target Name="CreatePublishXMLFile">
<PropertyGroup>
<VersionNumber Condition="'$(VersionNumber)'==''">Local Build</VersionNumber>
<CurrentDate>$([System.DateTime]::Now.ToString(yyyy-MM-dd HH:mm:ss))</CurrentDate>
<SqlPublishProfilePath Condition="'$(SqlPublishProfilePath)'==''">Publish\$(TargetServerParam).publish.xml</SqlPublishProfilePath>
<TargetXMLFile>$(ProjectDir)Publish\$(TargetServerParam).publish.xml</TargetXMLFile>
<ChangeSets Condition="'$(ChangeSets)'==''">Unknown</ChangeSets>
</PropertyGroup>
<XmlPoke XmlInputPath="$(TargetXMLFile)" Query="/*[local-name()='Project']/*[local-name()='PropertyGroup']/*[local-name()='TargetConnectionString']" Value="Data Source=$(TargetServerParam)%3BIntegrated Security=True%3BPooling=False" />
<XmlPoke XmlInputPath="$(TargetXMLFile)" Query="/*[local-name()='Project']/*[local-name()='PropertyGroup']/*[local-name()='TargetDatabaseName']" Value="$(ProjectName)" />
<XmlPoke XmlInputPath="$(TargetXMLFile)" Query="/*[local-name()='Project']/*[local-name()='ItemGroup']/*[local-name()='SqlCmdVariable'][#Include='ChangeSets']/*[local-name()='Value']" Value="$(ChangeSets)" />
</Target>
Then call this repeatedly, for each target server:
<Target Name="CreateAllPublishXMLFiles">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CreatePublishXMLFile" Properties="TargetServerParam=OURSERVER1" />
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CreatePublishXMLFile" Properties="TargetServerParam=OURSERVER2" />
</Target>
In each Project file we include and call the common code:
<Import Project="$(SolutionDir)SQLCommonInclude.proj" />
<Target Name="BeforeBuild" DependsOnTargets="CreateAllPublishXMLFiles">
Then, In a Post-deployment Script we set the Extended Properties like this:
IF NOT EXISTS (SELECT NULL FROM SYS.EXTENDED_PROPERTIES WHERE class_desc = 'DATABASE' AND name = 'SSDT ChangeSets')
EXEC sp_addextendedproperty #name = N'SSDT ChangeSets', #value = '';
EXEC sp_updateextendedproperty #name = N'SSDT ChangeSets', #value = '$(ChangeSets)';
Related
Would really like help with this.
I'm running Team City, have setup a build config, it builds fine. I added a deploy parameter, and that functions almost correctly. The thing is it's trying to deploy my entire c-drive!
This is the config for it:
<Target Name="DeployApp">
<Message Text="Copying application files..." />
<ItemGroup>
<ApplicationFiles Include="$(ApplicationOutputDirectory)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(ApplicationFiles)" DestinationFolder="$(DestinationPath)\% (RecursiveDir)"/>
</Target>
Any help on how to make it only copy the built project to the server?
If $(ApplicationOutputDirectory) isn't set, the Include statement will resolve to \**\*.*, which is the entire current drive (in this case, your C drive)
I am deploying some files on the server. But when I am doing this, build is deleting all the files and folder which are residing at that location. I don't want to delete all the files from the server. I want to exclude one folder (folder name is Temp) from the destination folder. Temp folder should not get deleted while deleting other files. How to do that?
Here is TFS Build Definition
<PropertyGroup Condition=" '$(DeployEnvironment)' == 'Dev' ">
<DeployPath>\\server1\D$\temp\reports</DeployPath>
</PropertyGroup>
<Target Name="CoreCompileSolution" />
<Target Name="AfterCompile">
<Message Importance ="high" Text="Solution Root: $(SolutionRoot)" />
<Message Importance ="high" Text="Out Dir: $(OutDir)" />
<Copy SourceFiles="#(RPTFiles)" DestinationFolder="$(OutDir)_PublishedWebsites\Reports\" />
</Target>
<Target Name="AfterDropBuild" >
<CreateItem Exclude="$(DeployPath)\Temp*.*">
<Output ItemName="PreviousDeployment" TaskParameter="Include" />
</CreateItem>
</Target>
Why are you using a Copy task? I think it is intended to be used for local manipulations during build, rather than deployment (because it does not give you a chance to easily configure behaviour).
I suggest that instead of copy tsak you use one of the following options
Non-web applications - use Robocopy:
/XD dirs [dirs]... : eXclude Directories matching given names/paths.
XF and XD can be used in combination e.g.
ROBOCOPY c:\source d:\dest /XF *.doc *.xls /XD c:\unwanted /S
see this link for usage guide. You either run it from the command line (using <Exec Command="" > task, or employ MBuiild Community Tasksproject which has a nice wrapper.
Web applications: you should use Web Deploy for your deployments. You an either use MSBuild integration (VS 2010 and later, see this blog series for guidance on setup and configure on VS2010 NB: it has been much simplified in VS 2012, but I don't have a link to share at the moment) or run it from command line (prior to VS 2010):
<Exec Command=""$(WebDeployToolPath)" -verb:sync - source:dirPath='$(MSBuildProjectDirectory)\Published\' -dest:dirPath='$(DeployDirectoryLocalPath)',computerName=$(DeployTargetURL),userName='$(DeployUserName)',password='$(Password)',authType='Basic' -skip:skipaction='Delete',objectname='filePath',absolutepath='app_offline.htm' -skip:skipaction='Delete',objectname='filePath',absolutepath='logs\\.*' -skip:skipaction='Delete',objectname='dirPath',absolutepath='logs\\.*' -skip:skipaction='Delete',objectname='filePath',absolutepath='UserFiles\\.*' -skip:skipaction='Delete',objectname='dirPath',absolutepath='UserFiles\\.*' -verbose -allowUntrusted" />
NB using skip:skipaction='Delete.. to skip removing files and folders.
Update
It looks like I've undestood this a bit incorrect (I supposed, deployment happenned in AfterCompile target, however, as I see now, TFS uses CoreDropBuild target to do the deployment.
So I think, what you need is to override CoreDropBuild target as described: here. (although, I've never tried this).
You can either use Copy task as the author of the thread, or go with Robocopy/webdeploy based on your personal preference.
I'm trying to make a batch file to publish the few ClickOnce application we have in one click. I'm using msbuild for that, and as an example the below command line shows how I'm doing it:
msbuild
MyApp.sln
/t:Publish
/p:Configuration=Release
/p:PublishUrl="C:\Apps\"
/v:normal > Log.txt
(wrapped for easier reading)
when I run the above command it builds and publish the application in the release directory, i.e. bin\release! Any idea why msbuild doesn't respect PublishUrl property in my example above?
PS: I tried also different combinations including remove 'Configuration', use 'Rebuild' and 'PublishOnly' as targets, and remove the the quotation marks but without any success.
You are setting the wrong property. Try PublishDir instead.
You can pass it into MSBuild as you are or you can set it in the project file (or maybe the sln file too, not sure I always use the project file.) like this
<PropertyGroup>
<PublishDir>C:\Dev\Release\$(BuildEnvironment)\</PublishDir>
</PropertyGroup>
I've just done a few blog posts on MsBuild and ClickOnce stuff, check it out you 'should' find them useful...
Some features are done by Visual-Studio and not by the MSBuild-script. So the click-once-deployment behaves differently when it's executed from the command-line.
The ApplicationRevision isn't increased with every build. This works only when is exectued from Visual Studio
In in somecases, the PublishUrl isn't used. Quote from MSDN:
For example, you could set the PublishURL to an FTP path and set the InstallURL to a Web URL. In this case, the PublishURL is only used in the IDE to transfer the files, but not used in the command-line builds. Finally, you can use UpdateUrl if you want to publish a ClickOnce application that updates itself from a separate location from which it is installed.
I've created a special MSBuild-file which does this things. It runs the publish-target and copies then the files to the right location.
An example of the build-file, as requested by alhambraeidos. It basically runs the regular VisualStudio-build and then copies the click-once data to the real release folder. Note that removed some project-specific stuff, so it's maybe broken. Furthermore it doesn't increase the build-number. Thats done by our Continues-Build-Server:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Publish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- the folder of the project to build -->
<ProjLocation>..\YourProjectFolder</ProjLocation>
<ProjLocationReleaseDir>$(ProjLocation)\bin\Release</ProjLocationReleaseDir>
<ProjPublishLocation>$(ProjLocationReleaseDir)\app.publish</ProjPublishLocation>
<!-- This is the web-folder, which provides the artefacts for click-once. After this
build the project is actually deployed on the server -->
<DeploymentFolder>D:\server\releases\</DeploymentFolder>
</PropertyGroup>
<Target Name="Publish" DependsOnTargets="Clean">
<Message Text="Publish-Build started for build no $(ApplicationRevision)" />
<MSBuild Projects="$(ProjLocation)/YourProject.csproj" Properties="Configuration=Release" Targets="Publish"/>
<ItemGroup>
<SchoolPlannerSetupFiles Include="$(ProjPublishLocation)\*.*"/>
<SchoolPlannerUpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
</ItemGroup>
<Copy
SourceFiles="#(SchoolPlannerSetupFiles)"
DestinationFolder="$(DeploymentFolder)\"
/>
<Copy
SourceFiles="#(SchoolPlannerUpdateFiles)"
DestinationFolder="$(DeploymentFolder)\Application Files\%(RecursiveDir)"
/>
<CallTarget Targets="RestoreLog"/>
</Target>
<Target Name="Clean">
<Message Text="Clean project:" />
<MSBuild Projects="$(ProjLocation)/YourProject.csproj" Properties="Configuration=Release" Targets="Clean"/>
</Target>
</Project>
I'll put in my 2 cents, this syntax seems to work (right or wrong):
/p:publishUrl="C:\\_\\Projects\\Samples\\artifacts\\Web\\"
For me, the soultion was to escape the path.
Instead of:
/p:PublishUrl="C:\Apps\"
Put:
/p:PublishUrl="C:\\Apps\\"
I got a directory I want to copy to a number of locations.
Say I have
home.aspx
I want to copy it to
abc/home.aspx
def/home.aspx
ghi/home.aspx
so two questions for me:
How do I define the list abc, def, ghi?
How do I execute my Copy task with each element of this list?
Here is an actual example that I put together that shows what you were looking for:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Test" ToolsVersion="3.5">
<!--Declare an ItemGroup that points to your file you want to copy.-->
<ItemGroup>
<ItemToCopy Include=".\Home.aspx" />
</ItemGroup>
<!--Declare an ItemGroup that points to your destination Locations-->
<ItemGroup>
<DestLocations Include=".\abc\home.aspx" />
<DestLocations Include=".\def\home.aspx" />
<DestLocations Include=".\ghi\home.aspx" />
</ItemGroup>
<Target Name="CopyFiles">
<!--Run the copy command to copy the item to your dest locations-->
<!--This is where the magic happens. The % sign before the DestLocations reference says to use
Batching. So Copy will be run for each unique FullPath MetaData in the DestLocations ItemGroup.-->
<Copy SourceFiles="#(ItemToCopy)" DestinationFolder="%(DestLocations.FullPath)" />
</Target>
</Project>
The concept that you should be interested in is known as Batching.
I've covered this exact scenario on my blog at http://www.sedodream.com/PermaLink,guid,5f1e0445-ce3d-4052-ba80-42fd19512d42.aspx
Here is the text of that blog entry, you can download the mentioned files at the link above.
Today someone was telling me about a co-worker who was having issues with MSBuild. He told me that he was trying to copy a set of files to a set of different servers. But the issue was that he didn’t know how to achieve this without performing multiple Copy task invocations. I told him that he could achieve this using MSBuild Batching. Batching is a process of performing a task (or target) on a set of items (batches) at a time. A batch can also include a single item. So in this scenario we need to perform the copy one time for each server that he wanted to deploy to. I’ve created a simple msbuild file which demonstrates this in two different ways. The first way uses task batching, which can bee seen in the Test target. And the other uses Target batching which can be seen in the DoItCore target. I've also created a clean target, which has nothing to do with batching.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Test">
<ItemGroup>
<SourceFiles Include="*.txt"/>
<Dest Include="One;Two;Three;Four;Five"/>
</ItemGroup>
<Target Name="Test">
<Copy SourceFiles ="#(SourceFiles)" DestinationFolder="%(Dest.FullPath)"/>
<Message Text="Fullpath: %(Dest.FullPath)"/>
</Target>
<!-- These targets demonstrate target batching -->
<Target Name="DoIt" DependsOnTargets="DoItCore"/>
<Target Name="DoItCore" Inputs="#(SourceFiles)" Outputs="%(Dest.FullPath)">
<Copy SourceFiles="#(SourceFiles)" DestinationFolder="%(Dest.FullPath)"/>
</Target>
<!-- This will clean up the files -->
<Target Name="Clean">
<CreateItem Include="%(Dest.FullPath)\**\*">
<Output ItemName="FilesToDelete" TaskParameter="Include"/>
</CreateItem>
<Delete Files="#(FilesToDelete)"/>
</Target>
</Project>
Batching is an advanced topic of MSBuild, and is defintely neglected. I have to admit I’m guilty of not writing about it enough myself. There are some good batching resources, they are listed below.
Here are some other batching related blog entries that I've posted.
MSBuild Batching Part 1
MSBuild Batching Part 2
MSBuild Batching Part 3
MSBuild RE: Enforcing the Build Agent in a Team Build
Thanks,
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
You really are best off doing this yourself as a learning exercise, rather than treating MSBUILD as a magic box. This article from Patrick Smacchia gives you most of the techniques involved.
Have an itemgroup where you build up this list of destinations ("<Destination>abc</Destionation>..., etc). Then invoke the copy task with this list (#Destination).
I'm sure you'll find plenty of examples if you search for it. http://keithhill.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3dMSBuild
After MSbuild has built my solution (with an asp.net website), and the webdeployment project has built and put the website in the directory _PublishedWebsites:
c:\mybuilds\buildName\Daily_20090519.3\Release_PublishedWebsites\MyWebsite.
How do I copy this to the fixed directory where IIS points to for the test website?
I have found loads of code snippets, but I cannot seem to find one that will take into account the fact that this directory name changes.
This is pretty easy. You can edit the project and insert something similar to the following.
<PropertyGroup>
<OutputDest>$(MSBuildProjectDirectory)\..\OutputCopy\</OutputDest>
</PropertyGroup>
<Target Name="AfterBuild">
<!-- Create an item with all the output files -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*" Exclude="$(OutputPath)obj\**\*" />
</ItemGroup>
<!-- You probably don't want to include the files in the obj folder so exclude them. -->
<Message Text="OutputDest : $(OutputDest)" />
<Copy SourceFiles="#(_OutputFiles)"
DestinationFiles="#(_OutputFiles->'$(OutputDest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
Is this what you are looking for?
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
I'm using different technique.
<PropertyGroup>
<BinariesRoot>c:\BinariesForIis\</BinariesRoot>
</PropertyGroup>
The c:\BinariesForIis\ will be used for direct output compiled binaries (before copy to ...\Daily_20090519.3\Release_ ...).