How to use the Web Publishing Pipeline and Web Deploy (MSDEPLOY) to Publish a Console Application? - console-application

I would like to use web deploy to publish a Visual Studio "Console" application to a folder on the target system.
I have had some luck, and have been able to produce something similar to what I need, but not quite.
I've added the following to the console .csproj:
added the following projectName.wpp.targets file
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
and I've added the following projectName.wpp.targets:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<DeployAsIisApp>false</DeployAsIisApp>
<IncludeSetAclProviderOnDestination>false</IncludeSetAclProviderOnDestination>
</PropertyGroup>
<ItemGroup>
<FilesForPackagingFromProject Include="$(IntermediateOutputPath)$(TargetFileName).config">
<DestinationRelativePath>bin\%(RecursiveDir)%(FileName)%(Extension)</DestinationRelativePath>
<FromTarget>projectName.wpp.targets</FromTarget>
</FilesForPackagingFromProject>
</ItemGroup>
</Project>
I then edit the .SetParameters.xml file as follows:
<parameters>
<setParameter name="IIS Web Application Name" value="c:\company\project" />
</parameters>
When I then deploy using the generated .cmd file, I get all the files deployed to C:\company\project\bin.
That's not bad, but I'd like to do better. In particular, I'd like to omit the "bin" folder and put all files in the "C:\company\project" folder, and I'd like to be able to specify the ACLs
Has anybody been able to work around these problems?

Ok, so here's the way how to omit the 'bin' folder.
First of all, I'd like to emphasize that all this msdeploy-related stuff is for web apps deployment, and 'bin' folder seems for me to be almost hardcoded deeply inside. So if you want to get rid of it - you have to do some dirty things. Which I did.
We'll have to change $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets project a little bit, so it's better to change not it, but it's copy.
Steps:
1.Backup $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets(alternatively, you could install MSBuild.Microsoft.VisualStudio.Web.targets package, redirect your csproj file to Microsoft.WebApplication.targets file obtained from package and work with it).
2. In the $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplicaton.targets find the xml node which looks like <CopyPipelineFiles PipelineItems="#(FilesForPackagingFromProject)"(there are several ones of them, take the one from the line ~2570).
3. Comment the node out, replace with the custom one, so eventually it will look like:
<!--
<CopyPipelineFiles PipelineItems="#(FilesForPackagingFromProject)"
SourceDirectory="$(WebPublishPipelineProjectDirectory)"
TargetDirectory="$(WPPAllFilesInSingleFolder)"
SkipMetadataExcludeTrueItems="True"
UpdateItemSpec="True"
DeleteItemsMarkAsExcludeTrue ="True"
Condition="'#(FilesForPackagingFromProject)' != ''">
<Output TaskParameter="ResultPipelineItems" ItemName="_FilesForPackagingFromProjectTempory"/>
</CopyPipelineFiles>-->
<!-- Copying files to package folder in 'custom'(dirty) way -->
<CreateItem Include="$(OutputPath)\**\*.*">
<Output TaskParameter="Include" ItemName="YourFilesToCopy" />
</CreateItem>
<Copy SourceFiles="#(YourFilesToCopy)"
DestinationFiles="#(YourFilesToCopy->'$(WPPAllFilesInSingleFolder)\%(RecursiveDir)%(Filename)%(Extension)')" />
Then
4. Your projectName.wpp.targets don't have to have FilesForPackagingFromProject, so it will look like:
<!-- targets -->
<PropertyGroup>
<DeployAsIisApp>false</DeployAsIisApp>
<IncludeSetAclProviderOnDestination>false</IncludeSetAclProviderOnDestination>
</PropertyGroup>
<ItemGroup>
<!-- intentionally left blank -->
</ItemGroup>
</Project>
That's it. Worked for me(tm), tested. Let me be honest, I don't like this approach, but that was the only way I made it working in the needed way. It's up to you whether you'll use it in your project or not.
My opinion is not to use msdeploy here - it was not for you task.
Better to write msbuild-scripts from scratch or accept the 'bin' folder, and fight against the framework again once next customization is required.

Related

How do I get a console project to group my appsettings.json files?

If I start a new web api project, the appsettings files are grouped together. However, I'm creating a working project from the console app template and when I create the appsettings files manually, the do not group together. I think back in older versions, there was something I'd put in the csproj file, but I don't know how to do it in .net core and I'm not seeing anything in properties or configurations
In the project file of your solution you can edit or add an <ItemGroup> element within the <Project> element. This worked for me:
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<DependentUpon>appsettings.json</DependentUpon>
</Content>
</ItemGroup>
Please Note that my console project targets .Net Core 2.0 and is running in Visual Studio Pro 2017 Version 15.7.5.
Also, if your Solution Explorer doesn't immediately refresh try unloading and reloading the project.
Using an <ItemGroup> with <Content> as suggested gave me an error (in Visual Studio 2019) about "Duplicate 'Content' items included". It turns out the .NET SDK includes 'Content' items from your project directory by default. Setting the EnableDefaultContentItems property to false seems a bit rigid, so now I include the items as <None>.
<ItemGroup>
<!-- Group AppSettings in Console project. Use None to prevent "Duplicate 'Content' items were included" when using (default) EnableDefaultContentItems=true -->
<None Include="appsettings.*.json">
<DependentUpon>appsettings.json</DependentUpon>
</None>
</ItemGroup>
This does show the files grouped, but shows their properties with Build Action 'None' and 'Do Not Copy' in the Solution-explorer, so I guess that's the price for wanting them to group?
FWIW: a file-nesting rule as suggested in appsettings-json-not-in-hierarchy
will not show the files as grouped/nested, but it will make it collapse if the solution-explorer collapse-button is pressed.
You just need to click the File Nesting icon, and choose "Web"

Custom ProjectSchemaDefinitions in Project Property Sheet

I finally managed to configure my Visual Studo C++ Project to show my Custom ProjectSchemaDefinition if I take a look at a Single Project->Property.
My Question:
Is there any way to show this in a Shared Project Property (eg external.props) ?
My current shared.targets configuration
<PropertyGroup Label="Import Settings">
<UseDefaultProjectTools>true</UseDefaultProjectTools>
<UseDefaultPropertyPageSchemas>true</UseDefaultPropertyPageSchemas>
<UseDefaultGeneralPropertyPageSchema>true</UseDefaultGeneralPropertyPageSchema>
</PropertyGroup>
<ItemGroup>
<PropertyPageSchema Include="$(SolutionDir)\props\PublishConfig.xml">
<Context>Project</Context>
</PropertyPageSchema>
<ProjectTools Include="PublishConfig" />
</ItemGroup>
I think I miss just the right Context in this definition but cannot find any reliable source at msdn where different Context Targets are correctly described.
Thank you in advance

Msbuild resolve project reference to custom project type

I've created a custom project type (cljproj instead of csproj) for a programming language called clojure. When the project compiles it outputs multiple .dll files as well as some dependent .clj files.
This is done by overriding the default CoreCompile target in the cljproj file. Which basically copies all files needing compilation to the bin directory and then executes a separate app to compile them.
<Target Name="CoreCompile">
<PropertyGroup>
<ClojureNamespaces>#(Compile -> '%(RelativeDir)%(Filename)', ' ')</ClojureNamespaces>
</PropertyGroup>
<Copy SourceFiles="#(Compile)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" DestinationFiles="#(Compile -> '$(OutDir)%(RelativeDir)%(Filename)%(Extension)')" />
<Exec WorkingDirectory="$(OutDir)" Command=""$(ClojureRuntimesDirectory)\$(ClojureVersion)\Clojure.Compile" $(ClojureNamespaces.Replace('\', '.'))" />
</Target>
I've added a reference from a c# project (csproj) to my clojure project (cljproj).
<ProjectReference Include="..\Clojure ASP.Net MVC Controller Library1\Clojure ASP.Net MVC Controller Library1.cljproj">
<Project>{8fe1995b-4b6d-4911-b563-a759467fdf53}</Project>
<Name>Clojure ASP.Net MVC Controller Library1</Name>
</ProjectReference>
Visual Studio by default doesn't resolve the project reference correctly, because it assumes there will only be one output, Clojure ASP.Net MVC Controller Library1.dll.
Examples of the actual output files are MvcApplication1.Controllers.HomeController.dll and HomeController.clj
I'd like to make this work without making any changes to the C# .csproj file, so that a .cljproj can easily be referenced from any .csproj file.
My attempt to resolve the project reference is by overriding the GetTargetPath target.
<Target Name="GetTargetPath" DependsOnTargets="$(GetTargetPathDependsOn)" Returns="#(TargetPath)">
<ItemGroup>
<TargetPath Include="$(TargetDir)\**\*.dll" />
<!-- <TargetPath Include="$(TargetDir)\**\*.clj" /> -->
</ItemGroup>
</Target>
If I set the TargetPath using *.dll, it works and copies the .dll files to the c#.csproj output directory. It even copies the .pdb files to that directory, although I didn't add them to the TargetPath. However, if I uncomment the *.clj TargetPath, CSC complains that the .clj files are corrupt (probably because they are plain text, not .net assemblies).
I'm happy to use a copy command instead of overriding TargetPath, however I'm not sure which variable to use for the directory to output them to, because $(outdir) gives me the bin of my custom project (.cljproj) not the bin of the c# project trying to resolve the project reference (.csproj). I'm not sure what other target to override besides GetTargetPath, because most clojure project (.cljproj) targets are not called when compiling the c# (.csjproj) project, for example: .cljproj:AfterBuild is only called when compiling cljproj directly, not when compiling .csproj which has a project reference to .cljproj.
I was able to get it to work by overriding the GetCopyToOutputDirectoryItems Target.
<Target Name="GetCopyToOutputDirectoryItems" Returns="#(CopyToOutputDirectoryItemsWithTargetPath)">
<ItemGroup>
<CopyToOutputDirectoryItems Include="$(TargetDir)\**\*.clj">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</CopyToOutputDirectoryItems>
</ItemGroup>
<AssignTargetPath Files="#(CopyToOutputDirectoryItems)" RootFolder="$(TargetDir)">
<Output TaskParameter="AssignedFiles" ItemName="CopyToOutputDirectoryItemsWithTargetPath" />
</AssignTargetPath>
</Target>

Call Publish after Build in MSBuild

I am trying to call the Publish target every time I build my WPF app. I have tweaked the .csproj file to include this:
<Target Name="AfterBuild">
<Message Text="Running AfterBuild..." />
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="Configuration=$(Configuration); PublishDependsOn=" Targets="Publish" />
</Target>
When I run this from the command line, I see the message that it is 'Running AfterBuild...' but nothing happens. If I remove the '; PublishDependsOn=' from the Properties of the MSBuild task, I get a circular reference error.
What magic am I missing here?
OK, I figured out how to do what I want to do. Instead of trying to explicitly call Publish in AfterBuild, I just added it to the DefaultTargets of the project. Now it calls Build then Publish.
<Project ToolsVersion="4.0" DefaultTargets="Build;Publish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

multiple build rules using vc2010 and msbuild

I'm managing a C++ project in VS2010 and want to have ALL .cpp files run through an external tool before going to the C++ compiler. All signs seem to indicate this is possible. See, for example, here.
Since this will happen over multiple projects, it makes sense to put this functionality in a property sheet and then just importing this property sheet everywhere. Before I touched the property sheet, it looked like this in its entirety:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
<ExtensionsToDeleteOnClean>...</ExtensionsToDeleteOnClean>
<CustomBuildBeforeTargets>ClCompile</CustomBuildBeforeTargets>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>...</AdditionalIncludeDirectories>
<ForcedIncludeFiles>%(ForcedIncludeFiles)</ForcedIncludeFiles>
</ClCompile>
<Link>
<AdditionalDependencies>...</AdditionalDependencies>
</Link>
<Outputs>...</Outputs>
</CustomBuildStep>
</ItemDefinitionGroup>
</Project>
As per the above linked document, I have added the following lines:
<Project>
<PropertyGroup>
...
<CustomBuildBeforeTargets>ClCompile</CustomBuildBeforeTargets>
</PropertyGroup>
...
<ItemGroup>
<CustomBuild Include="*.cpp">
<Message>Running Custom Build Step</Message>
<Command>dummy</Command>
<Outputs>dummy</Outputs>
</CustomBuild>
</ItemGroup>
</Project>
This appears to have no effect, and my custom build tool never runs before ClCompile. I have tried various ways of moving things around and renaming tags (the Xml editor complains that CustomBuild isn't even valid according to the schema, for example), but nothing works.
What am I doing wrong?
Although this is a very old question, I will attempt to answer it.
The Include attribute expects to find *.cpp files in the Project directory. If there are no .cpp files in the Project directory, then this CustomBuild "Task" will never kick in.
What the OP could have done was
<CustomBuild Include="**\*.cpp">
<Message>Custombuild kicking in</Message>
<Command>echo %(Identity)</Command>
<Outputs>dummy</Outputs>
</CustomBuild>
instead of
<CustomBuild Include="*.cpp">
...
and he would have performed the CustomBuild action on all the .cpp files in all the subdirectories of the project directory, and not just the .cpp files waiting to be compiled.
If the .cpp files are not availble under the Project directory (can happen when such a project directory structure is used) then the OP must explicitly point to the right "root" directory.
Alternatively you can save yourself wildcard usage by simply running an Exec task on every included ClCompile.
<Target Name="ProcessClCompileFiles" BeforeTargets="ClCompile" Condition="'#(ClCompile)'!=''">
<Message Text="== Starting processing cpp files ==" Importance="High"/>
<Exec Command="echo processing %(ClCompile.filename)%(ClCompile.extension) & ////YOUR COMMAND HERE////" />
</Target>
As a note. This requires the ClCompile itemgroup to be initialised before this Target. if you were to create a target that dynamically adds ClCompile, you'd have to ensure that this happens before this Target is called.
Another note is that these Exec tasks will always run if there are any items in the ClCompile itemgroup. If you have a condition where it should skip them (eg, already processed), use Condition="A=B"
For easy reference, here's how to validate it hasn't created a file and if it created a file; if the file is out of date:
Condition="!Exists('$(OutDir)%(Filename)%(Extension)') OR ($([System.DateTime]::Parse('%(ClCompile.ModifiedTime)').Ticks) > $([System.IO.File]::GetLastWriteTime('$(OutDir)%(Filename)%(Extension)').Ticks))"