Replace VS project files when doing web publishing (Web Deploy) - asp.net

I'm running into a scenario where I need to replace some files in my project during publishing with other custom files. The case is we have several websites that uses the same code base. However, some websites needs a customized version of the deployed files (e.g. favicon.ico, Error.aspx…).
I have been trying to include these files as “extra files” in my projectName.wpp.targets as mentioned Here in Sayed Ibrahim Hashimi blog
but they get overwritten by the original project files during publishing.
I can see from the publishing output that the "extra files" are being included in the package but then they are being replaced by the same files from the project.
Is there a way to tell MS Deploy to include my files after the project content files are included such that my files overwrite?
------------Update------------------
I found out from the below log (thanks to David Martin comment) that the files are not being overwritten but rather they are being skipped because the files in the project are newer than what I am trying to include:
CopyPipelineFiles: Skip copying F:\Extra Files For Sites Deployment\xxx\favicon.ico to obj\xxx\Package\PackageTmp\favicon.ico, File obj\xxx\Package\PackageTmp\favicon.ico is up to date
So is there a way to force including these files even if they are older into the package?

OK, so I have found a fix for this and will post here if someone else ran to this issue but I welcome any other solutions!
The problem as stated in my question is caused by the files I want to include in my package being older than the files in my project. So I needed to find a way to update the last modified date.
I found out that there is a "Touch" task that will do exactly that:
http://msdn.microsoft.com/en-us/library/37fwbyt5.aspx
So this is what I finally did in my script:
<ItemGroup>
<Files Include="F:\Files For Sites Deployment\xxx\**\*" />
</ItemGroup>
<PropertyGroup>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
TouchIncludeFiles;
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForMsdeployDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
<Target Name="TouchIncludeFiles">
<Touch Files="#(Files)" ForceTouch="true"></Touch>
</Target>
<Target Name="CustomCollectFiles">
<ItemGroup>
<_CustomFiles Include="#(Files)" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>

Related

dotnet core nuget package copying content files on restore

So I feel like I have come to the end of the rope here, but hoping someone knows more than I do here. I have some Typescript files, though that is mostly irrelevant as I am having this problem with all content files.
I am able to generate a nuget, or more precisely dotnet pack, nuget package that includes my content files in the package by using this in the .csproj of my parent project:
<ItemGroup>
<Content Include="Scripts\Utility.ts">
<Pack>true</Pack>
<PackagePath>contentFiles\Scripts\;content\Scripts</PackagePath>
</Content>
</ItemGroup>
I can browse the generated .nupkg and see that indeed the file was added to the package in both the content\Scripts and contentFiles\Scripts locations
The problem is that whenver I consume this package in my 'child' progect, that Typescript never gets copied into any folder of the child project, though I can see it extracted in the .nuget\packages\parent\... folders.
At first I thought it was something with my initial settings in the parent project, and it may be, but after trying what seems like everything in the book, that fails to copy the content files to the child project. I then tried going the dark path of trying to use Init.ps1 in the tools folder of my package, and though it was impossible to debug, it also seemed to run sporatically (I completely unistalled and reinstalled the package and it still failed to run most of the time.) This could be the way but I don't know why I can't get it to output to the Package Manager Console... maybe there's still hope with Init.ps1 but I can't seem to figure it out. Finally I see some potential with a nuget .targets file but I can's seem to grasp how to use it for my purpose either! I would love some feedback as to how to get this done.
From: Announcing NuGet 3.1 with Support for Universal Windows Platform
Importing content from a Nuget package was depreciated for projects using a project.json file in Nuget v3.1. Since then the project.json file has been dropped in favour of the new .csproj format. Importing content from a Nuget package should still work though if you're using the packages.config file instead.
Also mentioned is the fact that there are other package managers available for delivering content.
It looks to me like the answer in the new world is to create a node module containing utility.js and let npm deliver it to your project.
Possible Workaround:
I've looked at .targets to copy files and got this working, but it does run on each build - which may or may not be a problem for you. I can't do what I want with it.
In [PackageId].targets:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Either do this for all scripts in the Scripts/js folder -->
<Target Name="CopyScriptsToProject" BeforeTargets="Build">
<Message Text="Copying scripts to project" />
<ItemGroup>
<SourceScripts Include="$(MSBuildThisFileDirectory)..\..\content\Scripts\js\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(SourceScripts)" DestinationFiles="#(SourceScripts -> '$(MSBuildProjectDirectory)\wwwroot\js\%(RecursiveDir)%(Filename)%(Extension)')" Condition="!Exists('$(MSBuildProjectDirectory)\wwwroot\js\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<!-- Or do this for the individual script -->
<Target Name="CopyUtilityScriptToProject" BeforeTargets="Build">
<Copy SourceFiles="$(MSBuildThisFileDirectory)..\..\content\Scripts\js\Utility.js" DestinationFiles="$(MSBuildProjectDirectory)\wwwroot\js\Utility.js" Condition="!Exists('$(MSBuildProjectDirectory)\wwwroot\js\Utility.js')" />
</Target>
</Project>
<!-- Note: condition can be removed from either if you want it to overwrite each build -->
and in the .csproj file (replacing [PackageId] with the name of your package):
<Project Sdk="Microsoft.NET.Sdk">
... any Globals for source control stuff ...
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>7.0.0</Version>
<PackageId>[PackageId]</PackageId>
</PropertyGroup>
... any PackageReference stuff ...
<ItemGroup Label="Packaging">
<Content Include="build\netcoreapp2.0\[PackageId].targets" PackagePath="build\netcoreapp2.0\[PackageId].targets" />
<!-- Either -->
<Content Include="Scripts\js\**\*.*" PackagePath="content\Scripts\js;contentFiles\Scripts\js" />
<!-- or -->
<Content Include="Scripts\js\Utility.js" PackagePath="content\Scripts\js;contentFiles\Scripts\js" />
</ItemGroup>
</Project>
There seemed to be a bug whereby when the <PackageId>[PackageId]</PackageId> wasn't set explicitly in the .csproj, the build targets didn't work. Although that may well be an issue with my development environment.
Apparently you need the any\any in the path (learn more) as well as to include <PackageCopyToOutput>true</PackageCopyToOutput>, like this:
<ItemGroup>
<Content Include="Scripts\js\Utility.js">
<Pack>true</Pack>
<PackagePath>contentFiles\any\any\wwwroot\js\;content\any\any\wwwroot\js\</PackagePath>
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
</ItemGroup>
You'll also need to precompile your TypeScript before including the .js files in the package
However, this still doesn't create a file there, just some strange reference to it.
In the end, we got it working with a .targets file, you can find a working repo here: https://github.com/NuGet/Home/issues/6743
Serj Sagan's answer got me on the right track, but it wasn't sufficient to deploy the content file to the bin directory (as he noted). I was able to get the file to be deployed by changing the package reference options in the consuming project's .csproj file, as follows:
<PackageReference Include="MyNuGetPackage" Version="0.0.0.1">
<IncludeAssets>all</IncludeAssets>
<PrivateAssets>analyzers;build</PrivateAssets>
</PackageReference>
It seems like the default for PrivateAssets is contentfiles;analyzers;build (documentation), which is not what we want in this case.
Simplified code and explanation from #PurplePiranha
TL;DR:
Basic .NET6 simplified sample code on Github
Step by Step guide
Selection of the files
First we need to select all the files that needs to get into the nuget package.
Add this to the <LibraryPackageName>.csproj:
<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup Label="Packaging">
<Content Include="<Your directory path>\<your file(s)>" />
</ItemGroup>
Multiple content lines are allowed.
Write a target
Make a target file to copy the files before (or after) the build to the bin directory:
The location and name of this file is important:
<root>\build\<LibraryPackageName>.targets
Now, make sure that it will get executed by referencing it in the <LibraryPackageName>.csproj by adding a content line:
<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup Label="Packaging">
<Content Include="build\<LibraryPackageName>.targets" PackagePath="build\<LibraryPackageName>.targets" />
<Content Include="filesToAdd\*.txt">
<Pack>true</Pack>
</Content>
</ItemGroup>
Eg: From the code in github:
<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup Label="Packaging">
<Content Include="build\PackageToGenerateFile.targets" PackagePath="build\PackageToGenerateFile.targets" />
<Content Include="filesToAdd/*.txt">
<Pack>true</Pack>
</Content>
</ItemGroup>
NOTE: By copying the files to the bin directory, the files are not part of your version control, but your package is!
Build and pack
In Visual Studio, right-click on the package name and select "Pack".
A new nuget package should be created in the bin directory of your library.
Use the nuget package
Install the nuget package now in your destination package.
Notice that the files are in the solution explorer, but not in a directory on your disk. They have a shortcut symbol.
Build the destination package
Check the bin directory.
The files should be copied to the location mentioned in the targets.

How to discover .js, .css files, what are in the filesystem (and repo) but not in .csproj?

Context
I regularly make the mistake to forget include a vendor .css or .js to the Asp Mvc project. I just copy/download with a tool, or from a theme, and referencing them. All works locally because the files are in the virtual directory so IIS Express will server them.
When publish times come, and I publish the new version, those files which are not in the .csproj will not be deployed.
Question
Although some tools or itself the IDE creates warning in some cases if in a syntax construct I refer to a resource what is not in the .csproj, this is not all working (for example: when using BundleConfig)
It seems to be pretty simple prevent this source of errors: Just check the file system with a well picked filter and list all files what are not included in the .csproj. (the filter could be: (*.css, .js, ...) or (assets/.*)
How can I accomplish this task?
If you switch to the new .csproj format supported by Visual Studio 2017, you no longer need to add references to files in the file system, they are picked up by default and you have to exclude files that you don't want.
Migration to the new .csproj format is pretty straightforward - you can use the dotnet migrate tool to make the conversion.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net47</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MyProj\MyProj.csproj" />
</ItemGroup>
<ItemGroup>
<!-- /* Exclude files you don't want */ -->
<Compile Remove="Text\AnyTransliterator.cs" />
<Compile Remove="Text\BreakTransliterator.cs" />
</ItemGroup>
</Project>
If you have files outside of your project directory that you want to include, you can create a link to a file or directory.
<!-- /* Link to an individual file outside of the project */ -->
<Content Include="..\..\..\Assets\something.css" Link="Assets\something.css" />
<!-- /* Create a virtual directory in Visual Studio named Assets
and link to external Assets directory. All files in that
directory will be included in the project */ -->
<Content Include="..\..\..\Assets\**\*" LinkBase="Assets" />
<!-- /* Create a virtual directory in Visual Studio named Assets
and link to external Assets directory. Only .css files in that
directory will be included in the project */ -->
<Content Include="..\..\..\Assets\**\*.css" LinkBase="Assets" />
This works with .NET Framework, but do note that you need to install the .NET Core SDK 2.0.0 in addition to VS 2017 15.3 (and ensure no global.json selects a lower SDK version) for the LinkBase option to work.
Reference: New .csproj format - How to specify entire directory as "linked file" to a subdirectory?

How do I get msdeploy to create App_Data if it doesn't exist, but not delete any contents of the remote directory?

I have an application setup with the following Package/Publish Web settings:
Only files needed to run this application
(unchecked) Exclude generated debug symbols
(checked) Exclude files from the App_Data folder
(checked) Include all databases configured in Package/Publish SQL tab - note I do not have any databases configured
(unchecked) include IIS settings as configured in IIS Express
In the project, I have an App_Data folder setup, primarily to handle application logs.
The behavior I'd like to see (and expect) is the following:
On initial deploy to a brand new server, the application is copied and an App_Data folder is created with write permissions assigned for the application.
On subsequent deployments, the App_Data folder is ignored because it already exists and the "Exclude files from the App_Data folder" is checked.
However, msdeploy does not appear to do step #1 (step 2 is fine if I create the folder manually). I've been unable to find any documentation on the web besides this unanswered so question that seems to confirm the behavior I see.
How do I get msdeploy to create App_Data and assign permissions on initial deployment in this scenario?
Getting App_Data deployed when starting from scratch
#tdykstra got this part right. To get App_Data out there (and ACLs set automatically), I did the following:
Adding a placeholder file in App_Data
Set the build action to content on the placeholder (my placeholder file has text in it to let people stumbling across it know why it's there).
Unchecked "Exclude files from the App_Data folder" on the Package/Publish Web tab of the project properties in VS 2010
This gets my App_Data folder created and ready for use on the server. However, it will result in all my files getting deleted whenever I republish. This is problem #2 in my question above, and pretty closely resembles this other SO question/answer.
Preventing data on the server from being deleted on subsequent publish events
There are two mechanisms in MsDeploy that can get confused (at least I confused them):
Excluding files
MsDeploy skip rules
These can both be used to solve the problem, depending on the scenario:
#tdykstra's solution will likely work if you:
Know the names of the files in App_Data in advance (e.g. a sqllite database)
Have the files included in the App_Data folder in your project
The use MsDeploy skip rules to tell MsDeploy to completely skip all deletes on the server for that directory and files in that directory. This solves the problem in all cases, but is much more involved.
Implementing MsDeploy skip rules
To implement skip rules you'll have to abandon the right-click, Deploy option in VS 2010 in favor of right-click, Package, go into a command line, re-jigger a batch file and run a command line). If you're willing to put up with this experience (I am, because I'm automating it all through a CI process), here are the details:
Edit the project file and add the following. Note that the AbsolutePath argument is a regular expression, so you can get way fancy:
<Target Name="AddCustomSkipRules">
<ItemGroup>
<MsDeploySkipRules Include="SkipDeleteAppData">
<SkipAction>Delete</SkipAction>
<ObjectName>filePath</ObjectName>
<AbsolutePath>$(_Escaped_PackageTempDir)\\App_Data\\.*</AbsolutePath>
<XPath>
</XPath>
</MsDeploySkipRules>
<MsDeploySkipRules Include="SkipDeleteAppData">
<SkipAction>Delete</SkipAction>
<ObjectName>dirPath</ObjectName>
<AbsolutePath>$(_Escaped_PackageTempDir)\\App_Data\\.*</AbsolutePath>
<XPath>
</XPath>
</MsDeploySkipRules>
</ItemGroup>
</Target>
Package, do not deploy the project. This will create a zip file and .cmd file in the target directory (defined by "Location where package will be created" on the Package/Publish Web Tab). By default, this is obj\Debug\Package (or obj\Release\Package)
Deploy the site using the the resulting command file
In my testing, you must package and run the command file. The project file tweaks will tell msbuild to put the necessary -skip rule into the command file. However, using the "publish" feature straight from VS 2010 doesn't seem to run the command file (see the warning on this walkthrough)...it calls msdeploy directly and doesn't seem to honor the project file skip rules. I believe this is the difference between VS using msbuild -T:Package and msbuild -T:MsDeployPublish to build the project, but I have not tested this.
Finally, the command file isn't quite correct, at least in VS 2010 SP1. There's a great description of what goes wrong in this SO answer, but basically, VS (or maybe the /t:Package target is a better culprit) sets up the command file to publish to the machine without specifying a site. To fix that, you'll need to somehow get "?site=sitename" (probably this is ?site=Default+Web+Site, for a full URL of https://machine:8172/MsDeploy.axd?site=Default+Web+Site) onto the end of the computerName argument.
The problem I had was that the command file (batch file) has a hard time with using site= anything on the command line since it mis-parses the command line argument (even if escaped). I don't see a way around this problem other than modifying the cmd file directly, but for testing I copied the msdeploy.exe output I saw from my failed test run and modified that to call msdeploy.exe directly without the script.
Now that it's working, my intention is to work this into my CI build processes. What I'll be doing for the final solution is:
Change my build script to use /T:Package (right now it's /T:MsDeploy)
Have a scripted search/replace routine alter the generated cmd deployment script
Run the altered deployment script
This really should be easier.
Update
Here's the scripted search/replace routine I've come up with in PowerShell:
(Get-Content "project.deploy.cmd")
-replace('^set _ArgComputerName=$'
,"set ArgComputerName=https://server:8172/MsDeploy.axd?Site=Default+Web+Site")
| Out-File -Encoding ascii deploy.cmd
Once that is run, deploy.cmd can be called (without the /M option) and it will work as expected.
Web Deploy won't create a folder if there are no files to copy to it. One workaround in your scenario would be to not use the Exclude files from the App_Data folder check box, put a dummy file in App_Data (such as a .txt file with nothing in it), and specify file exclusion rules for whatever else you have in the App_Data folder (such as your .sdf file).
On excluding individual files (you can use wildcards), see the first question in the deployment FAQ on MSDN:
http://msdn.microsoft.com/en-us/library/ee942158.aspx#can_i_exclude_specific_files_or_folders_from_deployment
On using the dummy file method for causing a folder to be created, see Making Sure that the Elmah Folder gets Deployed in this tutorial:
http://www.asp.net/web-forms/tutorials/deployment-to-a-hosting-provider/deployment-to-a-hosting-provider-configuring-project-properties-4-of-12
I managed to get it working when using the Publish Web dialog from within Visual Studio. Note: it works for any folder and not only App_Data.
This is the basic .pubxml profile:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>AddCustomSkipRules</AfterAddIisSettingAndFileContentsToSourceManifest>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LastUsedBuildConfiguration>Local</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<ExcludeApp_Data>False</ExcludeApp_Data>
<MSDeployServiceURL>localhost</MSDeployServiceURL>
<DeployIisAppPath>SuperCoolAwesomeAppName</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>False</SkipExtraFilesOnServer>
<MSDeployPublishMethod>InProc</MSDeployPublishMethod>
<EnableMSDeployBackup>False</EnableMSDeployBackup>
<UserName />
<_SavePWD>False</_SavePWD>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
</PropertyGroup>
<PropertyGroup>
<UseMsDeployExe>true</UseMsDeployExe>
</PropertyGroup>
<Target Name="CreateEmptyFolders">
<Message Text="Adding empty folders to Files" />
<MakeDir Directories="$(_MSDeployDirPath_FullPath)\Files\Folder 1" />
<MakeDir Directories="$(_MSDeployDirPath_FullPath)\Files\Folder 2" />
<MakeDir Directories="$(_MSDeployDirPath_FullPath)\Files\Folder 3\Test"/>
</Target>
<Target Name="AddCustomSkipRules" DependsOnTargets="CreateEmptyFolders">
<Message Text="Adding Custom Skip Rules" />
<ItemGroup>
<MsDeploySkipRules Include="SkipFilesInFilesFolder">
<SkipAction>Delete</SkipAction>
<ObjectName>filePath</ObjectName>
<AbsolutePath>$(_DestinationContentPath)\\Files\\.*</AbsolutePath>
<Apply>Destination</Apply>
</MsDeploySkipRules>
<MsDeploySkipRules Include="SkipFoldersInFilesFolders">
<SkipAction></SkipAction>
<ObjectName>dirPath</ObjectName>
<AbsolutePath>$(_DestinationContentPath)\\Files\\.*\\*</AbsolutePath>
<Apply>Destination</Apply>
</MsDeploySkipRules>
</ItemGroup>
</Target>
</Project>
Here's a detailed post explaining it:
Using MsDeploy publish profile .pubxml to create an empty folder structure on IIS and skip deleting it with MsDeploySkipRules
Summarizing and simplifying Emil and Leniel answers in a concise one, if you just want to allow App_Data deploy for adds and updates, but prevents deletes, add this to your .pubxml.
<Project>
...
<PropertyGroup>
<UseMSDeployExe>true</UseMSDeployExe>
<ExcludeApp_Data>False</ExcludeApp_Data>
</PropertyGroup>
<Target Name="AddCustomSkipRules"
AfterTargets="AddIisSettingAndFileContentsToSourceManifest">
<Message Text="Adding Custom Skip Rules" />
<ItemGroup>
<MsDeploySkipRules Include="SkipDeleteAppData">
<SkipAction>Delete</SkipAction>
<ObjectName>filePath</ObjectName>
<AbsolutePath>App_Data\\.*</AbsolutePath>
</MsDeploySkipRules>
<MsDeploySkipRules Include="SkipDeleteAppData">
<SkipAction>Delete</SkipAction>
<ObjectName>dirPath</ObjectName>
<AbsolutePath>App_Data</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
</Target>
</Project>
<UseMSDeployExe>true</UseMSDeployExe> is really needed or it will fail complaining Unrecognized skip directive 'skipaction'.

Minify CSS and JS in Asp.Net when Publish a WebApplication

I use Asp.net 4 C# and MicrosoftAjax Minifier.
Please look at my code here; Using <Target Name="AfterBuild"> I'm able to minify all files .js and .css. The problem is that this code minify even the original files in my project solution, so would be almost impossible to edit theme once again.
I need instead minify all .js and .css on my solution after publishing it to a local folder.
In this way I can keep my original files in my project folder intact and have instead the site compiled and minified into another local folder.
I change my scripting using <Target Name="Publish">, I do not receive any error but It does not work.
Could you tell me what I'm missing here, and if there is a better approach to solve this problem? Thanks
<!-- Minify all JavaScript files that were embedded as resources -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="Publish">
<ItemGroup>
<JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" />
</ItemGroup>
<ItemGroup>
<CSS Include="**\*.css" Exclude="**\*.min.css" />
</ItemGroup>
<AjaxMin
JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".js"
CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".css" />
</Target>
(copied from questions)
To deal with losing the original js/css files, ensure the target filenames include .min, eg. JsTargetExtension=".min.js" and CssTargetExtension=".min.css". See article on using MsAjax minifier: http://www.asp.net/ajaxlibrary/ajaxminquickstart.ashx
To then be able to publish the minified files, see this article: Using Microsoft AJAX Minifier with Visual Studio 2010 1-click publish
At my workplace, we are solving the minifying issue quite differently by using Chirpy addin for Visual Studio. It automatically minifies files to a .min version upon saving. See http://chirpy.codeplex.com/

How to publish a full website without compiling and without copying SVN files

Apparently, DNN installations do not like to be precompiled (they won't be able to find any localized strings then). Our installation is safely put in SVN, which means I cannot just copy the whole directory. To publish everything, I need to copy the whole website directory without the SVN files and directories. So far, I've been messing with good old DOS commands, which is time consuming and error prone.
Can someone help me to an MS-Built script or step to do just this? Or can I do this using default Visual Studio 2010 commands?
Note: this is a website, not a web application.
Just svn export the directory from source control, which will give you a clean copy without the .svn stuff in it.
Visual Studio -> Solution Explorer -> <web site> -> <right click> -> Publish Web Site or Copy Web Site
If you are ever interested in automating this with MSBuild then you can do that with something like the following.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<FilesToCopy Include="**\*"
Exclude="**\.svn\**"/>
</ItemGroup>
<PropertyGroup>
<Dest>C:\temp\dest\</Dest>
</PropertyGroup>
<Target Name="CopyFiles">
<Message Text="FilesToCopy: #(FilesToCopy)"/>
<MakeDir Directories="$(Dest)"/>
<Copy SourceFiles="#(FilesToCopy)"
DestinationFiles="#(FilesToCopy->'$(Dest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
</Project>
So when I create the FilesToCopy item I exclude all the files under any .svn folder. Then I just perform the copy inside the CopyFiles target.

Resources