Wednesday, December 1, 2010

XAML and cryptic error message

Got the following cryptic error message when running a WPF application (a XamlParseException):
A 'Binding' cannot be used within a '<usercontrol>' collection. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
At first glance, everything looked good.  It might have been helpful if the error told me which property it was croaking on--but I guess MS can't learn to be specific with this XAML crap.  Fortunately, a quick google search and I stumbled onto a post where they identified thier problem when they got the same error: the owner in the DependencyProperty definition was not the control the property was on--changing it fixed their problem.  A second look at my code and I found I had done the same thing (the problem with cut-and-pasting).  Changing the owner type to the correct class fixed the problem.

Monday, November 29, 2010

Useless SQL Server Errors

Okay, there's nothing more useless than getting an error that doesn't tell you anything about how to fix it or, at the very least, why it occurred.
I had a Transaction Log backup job that was running regularly on SQL Server 2000, and was regularly getting failure, with nothing more than "SQLSTATE 42000, Error 22029".   Googling was not very helpful--seemed this could be caused by a who slew of possibilities, none of which appeared to apply to me.
It wasn't until I opened the text log file that was produced from the job and googled on the specific message ("Backup can not be performed on database <database>. This sub task is ignored.") that I found my answer, at http://support.microsoft.com/kb/303229.  It would have been nice to have something basic in the error message that told me the problem--it would have saved me a few hours of digging.
Basically, the Recovery Model for this database was "Simple".  Apparently you can't back up the transaction log on databases defined as Simple Recovery Model.  So there are two simple choices:  1. Change the Recovery Model to something other than "Simple" ("Bulk-Logged" or "Full" are both valid for backing up the transaction log), or 2. Don't back up the transaction log.
Since, in my case, the database is replicated to another server, it seemed rather redundant to back up the transaction log.  So, I turned off this job altogether.  Problem solved.

Wednesday, October 27, 2010

XAML pages won't generate c# code behind on compile

You'd think Visual Studio would know that if a file ended in ".xaml", then the file.g.cs code that is supposed to be generated from the XAML on compile would always be generated.

Unfortunately for me, I had some significant refactoring to do.  I moved existing XAML files around into new projects, and found that I couldn't compile.  It took me awhile to figure out that the file.g.cs files weren't being generated.  Why they weren't didn't make sense, until a Google search turned up that the properties of these files must be set the Build Action to "page".  Apparently Visual Studio changed the Build Action to something else.  When set to "page", they generated the code correctly.

Tuesday, October 26, 2010

Referencing bitmap image file in XAML.

It was driving me nuts.  I wanted to put a bitmap file into a project as a resource and reference it from XAML in another project.  I Googled the web and followed all the directions, but nothing worked.  Everything always referred to a local bitmap or a XAML file.


I followed all the directions, but nothing worked.  Basically, this is what I had: Image.png was in Resource.csproj.  UI.csproj (in the same solution) had Page.xaml that needed to display Image.png.

I could get the solution to compile, but the image would never display.  I followed all instructions to the letter, but nothing worked.  I think they're all liers, out to get me.

I finally figured it out on my own.  What I figured out even goes against some explicit instruction from one of the sites.

First, in your resource project (Resource.csproj above), add a resource file (a file ending in ".resx"--I called mine "Images.resx").  Do not use the Resources that are part of the project properties, but instead add a separate resource file.

Now add your image to that resource file.  Select the image in the resources and on the properties, change the Persistence to "Embedded in .resx".

Finally, add the ImageSource in the Resources of your XAML. In my case, my example is:

<ImageSource x:key="MyImage">pack://application:,,,/Resource;component/Images/Image.png</ImageSource>

Note that the path to the image is the resource file I added.

[EDIT]
Looks like I spoke too soon.  I still had my image in a folder that matched the same path, located in the UI project ("/Images/Image.png").  When I deleted that image, it failed.  Well, when I figure it out--if I figure it out--I'll update this with the correct information.

Too bad no one else can give good information on this--I'd really like to know.

Tuesday, September 21, 2010

System.ArgumentException: Invalid postback or callback argument

I've been going nuts on this one, and still don't have a good answer.
Basically, one of our web servers, using identical code (ASP.NET 3.0) as the production server (which works fine), suddenly decided to give me a "System.ArgumentException: Invalid postback or callback argument" every time I'd attempt to log in at the web site.  This was our QA box, used for final testing just before pushing stuff into production.  A quick Google search seemed to give some basic information--I had to used the Page EnableEventValidation="true" (you'd be stupid to set it to false), so the error did make some kind of sense.  But I could not overcome the issue.  There was no binding in the code-behind, so suggestioins of using !IsPostBack were useless.  The StackTrace suggested the error occurred on a TextBox, but there were only two textboxes: one for the username and one for the password.  Our development box worked fine and the production box worked fine. So what was the problem?
I tried all sorts of things--switching to Firefox, rebooting the QA servers, even restoring all production code onto the QA servers, and NOTHING!  The error just would not go away.  I added some code into development to do "ClientScript.RegisterForEventValidation" on ALL visibile controls, but I knew this really couldn't have anything to do with it--and I really didn't want to push development code into QA at this time.  I even checked the certificates (all websites are running using HTTPS) and noticed the QA box was running with an expired certificate--so I updated it, but still nothing.
In desparation, I finally tried logging into the website from a different computer--and lo and behold, it worked.  Okay, must be my PC, then--so I went back to my PC and tried logging in--this time it also worked.
WTF?  Well, at least if it happens again, I know how to clear it up.  But boy, I'd certainly appreciate any comments that could explain what happened and how to ever keep it from happening again.

Thursday, June 24, 2010

Team Systems Project Update

Okay, it's been awhile.  Been busy.



I've done some additional tweaks to my standard Team Systems Build project and have learned some useful tips--which I'm putting here.  I've discovered a number of issues since my original posting, which I've learned how to deal with.



The full project template can be found here



Below, I'm going step-by-step through that project file to explain what's going on and what you can customize for your needs.  This project builds and deploys a Click-Once project, plus updates a Windows Service on a separate Windows server.  The Solution that instigated this used a WPF Click-Once project that connected to a WCF service that ran on a separate machine, both of which were in the same solution, but not referencing each other.  The build project also does not run tests. Finally, there are two different philosophies for dealing with required app.config customizations for different deployments: copying in a unique app.config file for each deployment, or modifying the source app.config file on the fly.  I explore both methods, but found that copying in a unique one is probably the better choice.  I've commented out the code to change app.config as it's not always needed.



I've also added code to a couple of custom tasks that are useful, including a pretty good FTP process (I sometimes need to use FTP to deploy), and a custom RegEx to get information out of the project file (there might have been a good existing one that worked, but I found it easier just to write a custom one).



The custom tasks can be found here.



First, there's your basic setup:



Your standard XML encoding tag:
<?xml version="1.0" encoding="utf-8"?>



Basic project file definition:

<!-- Team Build Project Template: for ClickOnce applications -->



<!-- NOTE: if the app.config file requires modification for deployment variations, go to "!!!" location, uncomment and

       alter as needed

       -->

<!-- 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="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">



  <!-- Do not edit this -->




Custom Task imports (these are important for doing things that aren't a standard part of MSBuild):


  <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />



 This one defines our custom tasks. Be sure to define the path to where you dropped the BuildCodeBehind.Targets file in the zip file linked to above.


  <Import Project="$(MSBuildExtensionsPath)\BuildCodeBehind\BuildCodeBehind.Targets" />






Below is a set of common tasks that you can get from http://sdctasks.codeplex.com/:


   <!-- Microsoft.Sdc.Common.tasks from http://sdctasks.codeplex.com/ -->

  <Import Project="$(MSBuildExtensionsPath)\Microsoft\Sdc\Microsoft.Sdc.Common.tasks" />





Below is a set of common tasks that you can get from http://msbuildtasks.tigris.org/:
  <!-- MSBuild.Community.Tasks from http://msbuildtasks.tigris.org/ -->

  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>








Basic Configuration:


  <ProjectExtensions>

    <!-- Team Foundation Build Version - DO NOT CHANGE -->

    <ProjectFileVersion>2</ProjectFileVersion>

  </ProjectExtensions>



  Now here's where I put the various things that change from project to project.  I put it all together so that it's easy to find:


  <!-- *************************************************************  -->

  <!-- Solution specific Modifiable stuff begins here -->

  <PropertyGroup>



This defines the folder where the Click-Once project can be found.  This is not the full path, but only the subfolder under the solution:
       <ClickOnceProjectFolder>projectFolder</ClickOnceProjectFolder>





This is your solution name:
       <SolutionName>solutionName</SolutionName>



And the relative folder for the solution:
    <SolutionFolder>solutionFolder</SolutionFolder>



This is where any referenced files can be found.  Your click-once landing page should be in here:
    <ReferencedFilesFolder>referencedFilesFolder</ReferencedFilesFolder>



Your Click-Once application name as it will appear on the Land Page.  It will be compiled with this name followed by .exe:  <ClickOnceApplicationName>applicationName</ClickOnceApplicationName>



This parameter defines the project where the code can be found in Team Systems Source Control:


       <SourceCodeProject>ProjectNameInTeamSystemsSourceControl</SourceCodeProject>





    <!-- Deployment-Dependent Properties -->







  <PurposeName>Purpose name for Landing page</PurposeName>





  <!-- Final drop locations -->




Be sure to change WebServerURL to the URL of your web server:
  <ClickOnceUrl>http://WebServerURL/$(ClickOnceApplicationName)/</ClickOnceUrl>





Two deployment servers are assumed (our deployment is on a cluster), but you can repeat as many time as needed--if more needed just cut-and-paste for the extras.  If only one needed, then comment out RemoteServer2 references:
    <RemoteServer1>DeploymentServer1</RemoteServer1>

    <RemoteServer2>DeploymentServer2</RemoteServer2>






The deployment location for deploying by copying over the network:
<RemoteTarget1>\\$(RemoteServer1)\RootShare\$(ClickOnceApplicationName)</RemoteTarget1>

  <RemoteTarget2>\\$(RemoteServer2)\RootShare\$(ClickOnceApplicationName)</RemoteTarget2>






If updating a service, this is the service name that will need stopped and started for updating:
    <RemoteService>ServiceNameToStopAndStart</RemoteService>



And the is the full path to the Remote Service to copy to:    <RemoteServiceFolder>PathToServiceApplicationFolder</RemoteServiceFolder>



  <!-- Use "Environment" property to assist with differentiating with code branches -->

  <Environment></Environment>





  <!-- Standard Properties: These show up on the Landing Page. -->

  <ClickOnceAppTitle>ApplicationTitleForLandingPage - For $(PurposeName)</ClickOnceAppTitle>

  <AppDescription>ApplicationDescriptionForLandingPage.</AppDescription>



  <!-- Standard Company-level Properties: For the Landing Page -->

  <SupportUrl>SupportURLForLandingPage</SupportUrl>

  <Company>CompanyNameForLandingPage</Company>






If Deploying by FTP, these parameters define FTP deployment:
<FTPDeployServerUsername>FTPUsernameToDeploymentServer</FTPDeployServerUsername>

<FTPDeployServerPassword>FTPPasswordToDeploymentServer</FTPDeployServerPassword>

<RemoteDISTFolder>FTPFolderOnDeploymentServer</RemoteDISTFolder>





  <!-- General, unchanging Properties -->

  <AppConfigFile>app.config</AppConfigFile>





  <ConfigDir>$(SolutionRoot)\$(SolutionFolder)$(Environment)\$(ClickOnceProjectFolder)\</ConfigDir>




This parameter defines the drop location on the build server for the Click-Once project:
  <LocalDropLocation>e:\$(SourceCodeProject)\$(SolutionFolder)$(Environment)\ClickOnceDeploy\</LocalDropLocation>




This parameter defines the drop location of your secondary project (the services project):
<WCFServicesDropLocation>e:\$(SourceCodeProject)\$(SolutionFolder)$(Environment)\WCFServices\</WCFServicesDropLocation>

   

   

  <ClickOnceApplicationUrl>$(ClickOnceUrl)$(ClickOnceApplicationName).application</ClickOnceApplicationUrl>



  <!-- SigningCert:  May need altered -->




This defines the signing certificate:
  <SigningCert>$(ClickOnceProjectFolder)_TemporaryKey.pfx</SigningCert>

   



Your secondary project (service) to build: <SecondaryProject>$(SolutionRoot)/$(SolutionName)$(Environment)/SecondaryProjectFolder/SecondaryProjectFile.csproj</SecondaryProject>

  </PropertyGroup>

  <ItemGroup>

    <!-- Alter Landing Page if source location is different. -->



The location to find the Landing Page file:
    <ClickOnceLandingPage Include="$(SolutionRoot)\$(SolutionName)$(Environment)\$(ReferencedFilesFolder)\LandingPageFile.htm"/>

  </ItemGroup>



  <!-- Solution specific Modifiable stuff ends here -->

  <!-- *************************************************************  -->





Below are standard Microsoft parameters:


  <PropertyGroup>



    <!--  TESTING

     Set this flag to enable/disable running tests as a post-compilation build step.

    -->

    <RunTest>false</RunTest>



    <!--  CODE ANALYSIS

     Set this property to enable/disable running code analysis. Valid values for this property are

     Default, Always and Never.



         Default - Perform code analysis as per the individual project settings

         Always  - Always perform code analysis irrespective of project settings

         Never   - Never perform code analysis irrespective of project settings

     -->

    <RunCodeAnalysis>Default</RunCodeAnalysis>



    <!-- Additional Properties -->



    <!--  WorkItemType

     The type of the work item created on a build failure.

     -->

    <WorkItemType>Bug</WorkItemType>



    <!--  WorkItemFieldValues

     Fields and values of the work item created on a build failure.

    

     Note: Use reference names for fields if you want the build to be resistant to field name

     changes. Reference names are language independent while friendly names are changed depending

     on the installed language. For example, "System.Reason" is the reference name for the "Reason"

     field.

     -->

    <WorkItemFieldValues>System.Reason=Build Failure;System.Description=Start the build using Team Build</WorkItemFieldValues>



    <!--  WorkItemTitle

     Title of the work item created on build failure.

     -->

    <WorkItemTitle>Build failure in build:</WorkItemTitle>



    <!--  DescriptionText

     History comment of the work item created on a build failure.

     -->

    <DescriptionText>This work item was created by Team Build on a build failure.</DescriptionText>



    <!--  BuildLogText

     Additional comment text for the work item created on a build failure.

     -->

    <BuildlogText>The build log file is at:</BuildlogText>



    <!--  ErrorWarningLogText

     Additional comment text for the work item created on a build failure.

     This text will only be added if there were errors or warnings.

     -->

    <ErrorWarningLogText>The errors/warnings log file is at:</ErrorWarningLogText>



    <!--  UpdateAssociatedWorkItems

     Set this flag to enable/disable updating associated workitems on a successful build.

     -->

    <UpdateAssociatedWorkItems>true</UpdateAssociatedWorkItems>



    <!--  AdditionalVCOverrides

     Additional text for the VCOverrides file generated for VC++ projects.

     -->

    <AdditionalVCOverrides></AdditionalVCOverrides>



    <!--  CustomPropertiesForClean

     Custom properties to pass to the MSBuild task while calling the "Clean" target for all solutions.

     The format should be: PropertyName1=value1;PropertyName2=value2;...

     -->

    <CustomPropertiesForClean></CustomPropertiesForClean>



    <!--  CustomPropertiesForBuild

     Custom properties to pass to the MSBuild task while calling the default targets for all solutions.

     The format should be: Property1=value1;Property2=value2;...  To pass custom properties to

     individual solutions, use the Properties metadata item of the SolutionToBuild ItemGroup.

     -->

    <CustomPropertiesForBuild></CustomPropertiesForBuild>



  </PropertyGroup>



  <ItemGroup>

    <!--  SOLUTIONS

     The paths of the solutions to build. Paths can be server paths or local paths, but server paths

     relative to the location of this file are highly recommended.  To add/delete solutions, edit this

     ItemGroup. For example, to add a solution MySolution.sln, add the following line:

        

         <SolutionToBuild Include="$(BuildProjectFolderPath)/path/MySolution.sln" />



     To change the order in which the solutions are built, modify the order in which the solutions

     appear below.

    

     To call a target (or targets) other than the default, add a metadata item named Targets.  To pass

     custom properties to the solution, add a metadata item named Properties.  For example, to call

     the targets MyCustomTarget1 and MyCustomTarget2, passing in properties Property1 and Property2,

     add the following:

        

         <SolutionToBuild Include="$(BuildProjectFolderPath)/path/MySolution.sln">

             <Targets>MyCustomTarget1;MyCustomTarget2</Targets>

             <Properties>Property1=Value1;Property2=Value2</Properties>

         </SolutionToBuild>

    -->



    <SolutionToBuild Include="$(SolutionRoot)/$(SolutionName)$(Environment)/$(SolutionName).sln">

      <Targets></Targets>

      <Properties></Properties>

    </SolutionToBuild>



    <SolutionToPublish Include="$(SolutionToBuild)" />

  </ItemGroup>



  <ItemGroup>

    <!--  CONFIGURATIONS

     The list of configurations to build. To add/delete configurations, edit this value. For example,

     to add a new configuration, add the following lines:

        

         <ConfigurationToBuild Include="Debug|x86">

             <FlavorToBuild>Debug</FlavorToBuild>

             <PlatformToBuild>x86</PlatformToBuild>

         </ConfigurationToBuild>



     The Include attribute value should be unique for each ConfigurationToBuild node.

    -->

    <ConfigurationToBuild Include="Release|Any CPU">

      <FlavorToBuild>Release</FlavorToBuild>

      <PlatformToBuild>Any CPU</PlatformToBuild>

    </ConfigurationToBuild>



  </ItemGroup>



  <ItemGroup>

    <!--  TEST ARGUMENTS

     If the RunTest property is set to true then the following test arguments will be used to run

     tests. Tests can be run by specifying one or more test lists and/or one or more test containers.

    

     To run tests using test lists, add MetaDataFile items and associated TestLists here.  Paths can

     be server paths or local paths, but server paths relative to the location of this file are highly

     recommended:

    

        <MetaDataFile Include="$(BuildProjectFolderPath)/HelloWorld/HelloWorld.vsmdi">

            <TestList>BVT1;BVT2</TestList>

        </MetaDataFile>



     To run tests using test containers, add TestContainer items here:

    

        <TestContainer Include="$(OutDir)\HelloWorldTests.dll" />

        <TestContainer Include="$(SolutionRoot)\TestProject\WebTest1.webtest" />

        <TestContainer Include="$(SolutionRoot)\TestProject\LoadTest1.loadtest" />



     Use %2a instead of * and %3f instead of ? to prevent expansion before test assemblies are built

    -->



  </ItemGroup>



  <PropertyGroup>

    <!-- TEST ARGUMENTS

     If the RunTest property is set to true, then particular tests within a

     metadata file or test container may be specified here.  This is

     equivalent to the /test switch on mstest.exe.



     <TestNames>BVT;HighPriority</TestNames>

    -->



  </PropertyGroup>

  <ItemGroup>



    <!--  ADDITIONAL REFERENCE PATH

     The list of additional reference paths to use while resolving references. For example:

    

         <AdditionalReferencePath Include="C:\MyFolder\" />

         <AdditionalReferencePath Include="C:\MyFolder2\" />

    -->

  </ItemGroup>



These are the base files required for Click-Once:

  <ItemGroup>

    <ClickOnceInstallationFiles Include="$(LocalDropLocation)$(ClickOnceApplicationName).application"/>

    <ClickOnceInstallationFiles Include="$(LocalDropLocation)$(ClickOnceApplicationName).exe.manifest"/>

    <ClickOnceInstallationFiles Include="$(LocalDropLocation)setup.exe"/>

    <ClickOnceInstallationFiles Include="$(LocalDropLocation)default.htm"/>



  </ItemGroup>



  <Target Name="AfterGet">



    <Message Text="  ~~~ Doing After Get"/>



Here's where you set up custom app.config files based on deployment:

    <!-- *************************************************************  -->

       <!-- Custom Config file for differing deployments.  Option 1: modify on fly -->

    <!-- !!! Edit Config File here.-->

   

    <!--

    <Message Text="  ~~~ Modifying Config file: AppConfigFile=$(ConfigDir)app.config"/>

    -->



    <!-- ModifyFile is out of Microsoft.Sdc.Common.tasks -->

    <!--

    <ModifyFile

        Path="$(ConfigDir)$(AppConfigFile)"

        XPath="/configuration/userSettings/$(ClickOnceApplicationName).Properties.Settings/setting[@name='SettingToModify']/value"

     NewValue="$(NewSetting)"

     Force="true" />

    -->



    <!-- End Config File modification here  -->

       <!-- Custom Config file for differing deployments.  Option 2 (better): copy unique in from source -->

<!--  !!! Edit Config File here.  -->

<!--

    <Message Text="  ~~~ Deleting app.config file"/>

    <File.DeleteFiles

            Files="$(ConfigDir)app.config"

            TreatErrorsAsWarnings="false"

            Force="true" />



    <Message Text="  ~~~ Copying in Config file from $(CUCustomStuff)$(AppConfigFile)"/>



    <Copy SourceFiles="$(CUCustomStuff)$(AppConfigFile)" DestinationFiles="$(ConfigDir)app.config"/>



-->



    <!--  End Config File modification here    -->

    <!-- *************************************************************  -->







Now we get the latest changeset out of source control to use as our "Revision" number:

    <!-- Getting version data -->

    <!-- TfsVersion is out of  MSBuild.Community.Tasks -->

    <TfsVersion LocalPath="$(SolutionRoot)">

      <Output TaskParameter="Changeset" PropertyName="Revision" />

    </TfsVersion>



    <Message Text="  ~~~ Revision is: $(Revision)" />





    <!--  Setting the Application (Publish) version for ClickOnce by modifying the C# Project file. -->

    <CreateProperty Value="\&lt;ApplicationRevision\&gt;[0-9]+\&lt;\/ApplicationRevision\&gt;">

      <Output PropertyName="RegExMatch" TaskParameter="Value" />

    </CreateProperty>

    <CreateProperty Value="&lt;ApplicationRevision&gt;$(Revision)&lt;/ApplicationRevision&gt;">

      <Output PropertyName="RegExReplace" TaskParameter="Value" />

    </CreateProperty>



    <!-- RegEx is out of Microsoft.Sdc.Common.tasks -->

    <Regex Path="$(ConfigDir)$(ClickOnceApplicationName).csproj"

            RegularExpression="$(RegExMatch)"

            NewValue="$(RegExReplace)"

            Force="true"/>



    <Message Text="  ~~~ Getting ApplicationVersion for $(ConfigDir)$(ClickOnceApplicationName).csproj" />







    <!-- ReadLinesFromFile is out of Microsoft.Sdc.Common.tasks -->

    <ReadLinesFromFile File="$(ConfigDir)$(ClickOnceApplicationName).csproj">

      <Output TaskParameter="Lines"

      ItemName="ItemsFromFile" />

    </ReadLinesFromFile>





    <CreateProperty Value="@(ItemsFromFile)">

      <Output PropertyName="wrkItemsFromFile" TaskParameter="Value" />

    </CreateProperty>



    <!-- RegexCapture is a custom task, out of the RCO BuildCodeBehind.dll -->

    <RegexCapture Input="@(ItemsFromFile)" Expression="\&lt;ApplicationVersion\&gt;[0-9]+\.[0-9]+\.[0-9]+">

      <Output ItemName="VersionInfo" TaskParameter="Output"></Output>

    </RegexCapture>



    <CreateProperty Value="@(VersionInfo)">

      <Output PropertyName="VersionQuestion" TaskParameter="Value" />

    </CreateProperty>





    <!-- RegexCapture is a custom task, out of the RCO BuildCodeBehind.dll -->

    <RegexCapture Input="@(VersionInfo)" Expression="[0-9]+\.[0-9]+\.[0-9]+">

      <Output ItemName="VersionInfo2" TaskParameter="Output"></Output>

    </RegexCapture>



    <CreateProperty Value="@(VersionInfo2)">

      <Output PropertyName="VersionData" TaskParameter="Value" />

    </CreateProperty>



    <Message Text="  ~~~ Publish Version = $(VersionData).$(Revision)" />



    <!-- end getting version data -->



By setting the "Publish" version, an update can now be forced without having to manually change the publish version in the project.



  </Target>





  <Target Name="AfterCompile">

    <Message Text="  ~~~ BuildProjectFolderPath=$(BuildProjectFolderPath)"/>

    <Message Text="  ~~~ SolutionRoot=$(SolutionRoot)"/>





    <Message Text="  ~~~ copying @(ClickOnceLandingPage) to $(LocalDropLocation)\default.htm"/>

    <Copy  SourceFiles="@(ClickOnceLandingPage)"

            DestinationFiles="$(LocalDropLocation)\default.htm"/>



    <Message Text="  ~~~ Modifying landing page for app name, title, and version"/>



    <!-- RegEx is out of Microsoft.Sdc.Common.tasks -->

    <Regex

          Path="$(LocalDropLocation)\default.htm"

          RegularExpression="#APPLICATION_NAME#"

          NewValue="$(SolutionName)"

          Force="true"/>



    <!-- RegEx is out of Microsoft.Sdc.Common.tasks -->

    <Regex

        Path="$(LocalDropLocation)\default.htm"

        RegularExpression="#TITLE#"

        NewValue="$(ClickOnceAppTitle)"

        Force="true"/>





    <!-- RegEx is out of Microsoft.Sdc.Common.tasks -->

    <Regex

        Path="$(LocalDropLocation)\default.htm"

        RegularExpression="#VERSION#"

        NewValue="$(VersionData).$(Revision)"

        Force="true"/>



    <!-- RegEx is out of Microsoft.Sdc.Common.tasks -->

    <Regex

        Path="$(LocalDropLocation)\default.htm"

        RegularExpression="#APPDESCRIPTION#"

        NewValue="$(AppDescription)"

        Force="true"/>



    <Message Text="  ~~~ Building $(VersionData).$(Revision)"/>





Build the Click-Once project:



    <MSBuild

      Projects="$(SolutionRoot)/$(SolutionName)$(Environment)/$(ClickOnceProjectFolder)/$(ClickOnceApplicationName).csproj"

      Properties="Configuration=%(ConfigurationToBuild.FlavorToBuild);PublishDir=$(LocalDropLocation);OutDir=$(LocalDropLocation);ApplicationVersion=$(VersionData).$(Revision);Satellite_FileVersion=$(VersionData).$(Revision);Satellite_ProductVersion=$(VersionData).$(Revision);Satellite_Version=$(VersionData).$(Revision);ApplicationRevision=$(Revision);"

      Targets="Publish" />



Build your secondary (service) project:

    <MSBuild

      Projects="$(SecondaryProject)"

      Properties="Configuration=%(ConfigurationToBuild.FlavorToBuild);PublishDir=$(WCFServicesDropLocation);OutDir=$(WCFServicesDropLocation);ApplicationVersion=$(VersionData).$(Revision);Satellite_FileVersion=$(VersionData).$(Revision);Satellite_ProductVersion=$(VersionData).$(Revision);Satellite_Version=$(VersionData).$(Revision);ApplicationRevision=$(Revision);"

      Targets="Build" />



  </Target>







  <Target Name="AfterDropBuild">



    <Message Text="Deploying files..." />





    <CreateItem Include="$(VersionData).$(Revision)">

      <Output TaskParameter="Include"

              ItemName="TestGroup"/>

    </CreateItem>





    <!-- RegexReplace is out of  MSBuild.Community.Tasks -->

    <RegexReplace Input="@(TestGroup)" Expression="\." Replacement="_" Count="-1">

      <Output PropertyName ="BuildNumberFolder" TaskParameter="Output" />

    </RegexReplace>







    <Message Text="  ~~~ BuildNumberFolder = $(BuildNumberFolder)" />



    <CreateItem

                      Include="$(LocalDropLocation)Application Files\$(ClickOnceApplicationName)_$(BuildNumberFolder)\*.*">

      <Output

                        TaskParameter ="Include"

                        ItemName ="DroppedApplicationItems"/>

    </CreateItem>



    <CreateItem

         Include="$(WCFServicesDropLocation)\*.*">

      <Output

                        TaskParameter ="Include"

                        ItemName ="DroppedWCFItems"/>

    </CreateItem>

   

   

    <CreateProperty Value="@(DroppedApplicationItems)">

      <Output PropertyName="ApplicationFiles" TaskParameter="Value" />

    </CreateProperty>





    <Message Text="  ~~~ Looking for Application Files in: $(ApplicationFiles)" />

    <Message Text="  ~~~ ApplicationFiles=$(ApplicationFiles)" />







    <Message Text="  ~~~ Copying ClickOnceInstallation files to $(RemoteTarget1)" />



    <Copy

      SourceFiles="@(ClickOnceInstallationFiles)"

      DestinationFiles="@(ClickOnceInstallationFiles->'$(RemoteTarget1)\%(Filename)%(Extension)')"

      />







    <Message Text="  ~~~ Copying Application files to $(RemoteTarget1)" />

    <Copy

  SourceFiles="@(DroppedApplicationItems)"

  DestinationFiles="@(DroppedApplicationItems->'$(RemoteTarget1)\Application Files\$(ClickOnceApplicationName)_$(BuildNumberFolder)\%(Filename)%(Extension)')"

      />

    <Message Text="  ~~~ Copying ClickOnceInstallation files to $(RemoteTarget2)" />

    <Copy

   SourceFiles="@(ClickOnceInstallationFiles)"

   DestinationFiles="@(ClickOnceInstallationFiles->'$(RemoteTarget2)\%(Filename)%(Extension)')"

     

      />

    <Message Text="  ~~~ Copying Application files to $(RemoteTarget2)" />

    <Copy

  SourceFiles="@(DroppedApplicationItems)"

    DestinationFiles="@(DroppedApplicationItems->'$(RemoteTarget2)\Application Files\$(ClickOnceApplicationName)_$(BuildNumberFolder)\%(Filename)%(Extension)')"

      />











    <Message Text="~~~~Deploying WCF files...to $(RemoteServer1)" />

 <!-- ServiceController is out of  MSBuild.Community.Tasks -->

    <ServiceController MachineName="$(RemoteServer1)" Action="Stop" ServiceName="$(RemoteService)"></ServiceController>

    <Copy

      SourceFiles="@(DroppedWCFItems)"

      DestinationFiles="@(DroppedWCFItems->'\\$(RemoteServer1)\$(RemoteServiceFolder)\%(Filename)%(Extension)')"

     

      />



 <!-- ServiceController is out of  MSBuild.Community.Tasks -->

    <ServiceController MachineName="$(RemoteServer1)" Action="Start" ServiceName="$(RemoteService)"></ServiceController>

   

   

    <Message Text="~~~~Deploying WCF files...to $(RemoteServer2)" />

 <!-- ServiceController is out of  MSBuild.Community.Tasks -->

    <ServiceController MachineName="$(RemoteServer2)" Action="Stop" ServiceName="$(RemoteService)"></ServiceController>

    <Copy

      SourceFiles="@(DroppedWCFItems)"

      DestinationFiles="@(DroppedWCFItems->'\\$(RemoteServer2)\$(RemoteServiceFolder)\%(Filename)%(Extension)')"

      />



 <!-- ServiceController is out of  MSBuild.Community.Tasks -->

    <ServiceController MachineName="$(RemoteServer2)" Action="Start" ServiceName="$(RemoteService)"></ServiceController>



 <!-- FTP to Target Starts here -->

<!--

    <Message Text="~~~ Uploading files " />

    <FTP

      LocalRootPath="e:\$(SourceCodeProject)\$(SolutionFolder)$(Environment)\"

      Task="upload"

      Files="@(ClickOnceInstallationFiles)"

      RemotePath="$(RemoteDISTFolder)"

      Host="$(RemoteServer1)" Username="$(FTPDeployServerUsername)" Password="$(FTPDeployServerPassword)" Binary="true" >



    </FTP>

-->

    <!-- FTP to target ends here -->



Clean up the build folders.  Once deployed, you generally don't need the stuff on the build machines.  So remove all unnecessary stuff to save on disk space.
    <!-- Clean up -->

    <Message Text="~~~ Deleting files (cleanup)" />



  

    <Exec ContinueOnError="true"

          Command="rmdir /s/q &quot;e:\$(SourceCodeProject)\$(SolutionFolder)$(Environment)&quot;" />

   

    <Exec ContinueOnError="true"

               Command="rmdir /s/q &quot;$(DropLocation)\$(BuildNumber)\Release&quot;" />

  </Target>





</Project>