Prompting before building a Release configuration? - visual-studio-2013

I have some specific post-build actions tied to the Release configuration, which deploys to a test environment. Is there a way to enable a pop-up warning before someone builds locally using the Release configuration for a specific project? If not, I can make my post-build actions smarter.

You'll need to use a custom MSBuild task. Here's an inline task to get you started.
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">
<UsingTask TaskName="ShowConfirmationPopup" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<Message ParameterType="System.String" Required="true" />
<Title ParameterType="System.String" Required="false" />
<Result ParameterType="System.Boolean" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Windows.Forms"/>
<Using Namespace="System.Windows.Forms" />
<Code Type="Fragment" Language="cs"><![CDATA[
Result = MessageBox.Show(Message, Title ?? "MSBuild Confirmation",
MessageBoxButtons.YesNo, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1) == DialogResult.Yes;
]]></Code>
</Task>
</UsingTask>
<Target Name="PromptReleaseBuild" BeforeTargets="PrepareForBuild" Condition="'$(Configuration)' == 'Release'">
<ShowConfirmationPopup Message="Do Release Build?">
<Output TaskParameter="Result" PropertyName="DoReleaseBuild" />
</ShowConfirmationPopup>
<Error Text="Prompt refused" Condition="'$(DoReleaseBuild)' != 'true'" />
</Target>
</Project>

Related

Building web project causes web config to be updated setting 'environmentVariable' to 'Development'

Time back I created a .net core with react SPA project from a template, ever since I have been unable to identify what is causing the web.config to be update each time I build.
Well, to be precise it is updated only adding back the environment variables tags, setting the environment to Development.
Can anyone please tell me how to prevent this?
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
Here is the csproj file content:
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OData" Version="8.0.12" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.13" />
<PackageReference Include="Minded" Version="0.1.3" />
[...]
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<ItemGroup>
<Folder Include="ClientApp\src\components\" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="#(DistFiles->'%(FullPath)')" Exclude="#(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

AjaxMin not running on CopyAllFilesToSingleFolderForPackage when using MSDeploy to remote server

I'm trying to get my JS and CSS files to be automatically minified when publishing our Web App, but it only seems to work on a File System publish not a web deploy; using the target CopyAllFilesToSingleFolderForPackage in a .pubxml file.
I've tried running on different targets but the same result.
<UsingTask TaskName="AjaxMin" AssemblyFile="$(MSBuildProjectDirectory)\lib\AjaxMinTask.dll" />
<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage">
<ItemGroup>
<JS Include="$(_PackageTempDir)\**\*.js" Exclude="$(_PackageTempDir)\**\*.min.js" />
<CSS Include="$(_PackageTempDir)\**\*.css" Exclude="$(_PackageTempDir)\**\*.min.css" />
</ItemGroup>
<Message Text="Compressing JavaScript and CSS files in $(PublishDir)..." Importance="high" />
<AjaxMin JsKnownGlobalNames="jQuery,$" JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".js" CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".css" />
</Target>
I'd expect it to minify the files in the temporary package folder, but nothing happens.
So I've managed to solve the problem.
I had to find the right target and folders to do this on:
<UsingTask TaskName="AjaxMin" AssemblyFile="$(MSBuildProjectDirectory)\lib\AjaxMinTask.dll" />
<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForAspNetCompileMerge">
<ItemGroup>
<JS Include="$(IntermediateOutputPath)\AspnetCompileMerge\Source\**\*.js" Exclude="$(IntermediateOutputPath)\AspnetCompileMerge\Source\**\*.min.js" />
<CSS Include="$(IntermediateOutputPath)\AspnetCompileMerge\Source\**\*.css" Exclude="$(IntermediateOutputPath)\AspnetCompileMerge\Source\**\*.min.css" />
</ItemGroup>
<Message Text="Compressing JavaScript and CSS files in $(IntermediateOutputPath)..." Importance="high" />
<AjaxMin JsKnownGlobalNames="jQuery,$" JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".js" CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".css" />
</Target>
Now after the first copy, it will minify the files and use those going forward

Is there an indexer available in MSBuild ItemGroups?

I'm using this stack overflow answer to return the built assembly's #(Version) as an ItemGroup for use in the post-build event.
However, I want to have Major and Minor separately, as I only want vMajor.Minor in the post build. To do this, I'd like to index inside the Include attribute.
The following works, but will break if the Major or Minor numbers go over 9 (single digits only):
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<ItemGroup>
<Version Include="#(Targets->'%(Version)')" />
<Major Include="#(Version->Substring(0,1))" />
<Minor Include="#(Version->Substring(2,1))" />
</ItemGroup>
</Target>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
<PostBuildEvent>
if $(ConfigurationName) == Release copy /y $(TargetPath) $(SolutionDir)..\Builds\$(TargetName)_v#(Major).#(Minor)$(TargetExt)
</PostBuildEvent>
</PropertyGroup>
What I'm looking for is something more along the lines of:
<Major Include="#(Version->Split('.')[0])" />
<Minor Include="#(Version->Split('.')[1])" />
or, only if an indexer is really not possible,
<Major Include="#(Targets->Version->Major)" />
<Minor Include="#(Targets->Version->Minor)" />
<Major Include="#(Targets->'%(Version)'->'%(Major)')" />
<Minor Include="#(Targets->'%(Version)'->'%(Minor)')" />
<Major Include="#(Targets->'%(Version.Major)')" />
<Minor Include="#(Targets->'%(Version.Minor)')" />
What options, if any, do I have to index an array or otherwise pick apart a version number like 1.0.2838.24877 into 1 and 0?
Edit
This now works, by first using a <PropertyGroup> I can utilitize Split('.')[n] like in this post, but the $(Major) Properties ($()) are not available in the Post-Build event (why not? Am I missing something?). This would be more elegant if I could just directly use $(Major) in the post-build.
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<PropertyGroup>
<Version>#(Targets->'%(Version)')</Version>
<Major>$(Version.Split('.')[0])</Major>
<Minor>$(Version.Split('.')[1])</Minor>
<Build>$(Version.Split('.')[2])</Build>
<Revision>$(Version.Split('.')[3])</Revision>
</PropertyGroup>
<ItemGroup>
<Version Include="$(Version)" />
<Major Include="$(Major)" />
<Minor Include="$(Minor)" />
<Build Include="$(Build)" />
<Revision Include="$(Revision)" />
</ItemGroup>
<Message Text="Version: #(Version)" />
<Message Text="Major.Minor.Build.Revision: #(Major).#(Minor).#(Build).#(Revision)" />
</Target>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
<PostBuildEvent>
if $(ConfigurationName) == Release copy /y $(TargetPath) $(SolutionDir)..\Builds\$(TargetName)_v#(Major).#(Minor)$(TargetExt)
</PostBuildEvent>
</PropertyGroup>
Why are ItemGroups #() available and PropertyGroups $() not?
This is the best I've come to, with little understanding of what else would be available or how to use indexers within ItemGroups.
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<PropertyGroup>
<Version>#(Targets->'%(Version)')</Version>
</PropertyGroup>
<ItemGroup>
<Version Include="$(Version)" />
<Major Include="$(Version.Split('.')[0])" />
<Minor Include="$(Version.Split('.')[1])" />
<Build Include="$(Version.Split('.')[2])" />
<Revision Include="$(Version.Split('.')[3])" />
</ItemGroup>
<Message Text="Version: #(Version)" />
<Message Text="Major.Minor.Build.Revision: #(Major).#(Minor).#(Build).#(Revision)" />
</Target>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
<PostBuildEvent>
if $(ConfigurationName) == Release copy /y $(TargetPath) $(SolutionDir)..\Builds\$(TargetName)_v#(Major).#(Minor)$(TargetExt)
</PostBuildEvent>
</PropertyGroup>
I would like to be able to accomplish this without the <PropertyGroup> section, if possible.

Publishing ClickOnce application with NANT script

My WPF application is deployed with ClickOnce.
In Visual Studio I open "Project properties / Publish".
There I have:
Publish location
Publish URL
Version
Signature
The problem is, that I have to publish every version for test and production.
The difference between them are properties publish location and publish URL. Currently I have to execute the process twice, while changing the values before publishing for production.
So the result of pressing publish is a folder containing folder "ApplicationFiles", the application manifest file and a setup.exe.
Then i decided to automate this process using NANT.
I build/publish the application first for testing (here i set the .csproj file location, publish folder and application varsion)
<target name="BuildTestApplication" depends="Clean" description="Build">
<echo message="Building..." />
<exec program="${msbuildExe}" workingdir="." verbose="true">
<arg value="${projectFile}" />
<arg value="/target:Clean;Publish" />
<arg value="/p:PublishDir=${testPublishFolder}" />
<arg value="/p:ApplicationVersion=${version}" />
<arg value="/p:Publisher="${publisherName}"" />
</exec>
<echo message="Built" />
</target>
With this I found out that build does not set the publisher. Plus I need to change the provider URL, since the application is also installed via internet (different URLs for test and production). So i did:
<target name="UpdateTestApplication" depends="BuildTestApplication" description="Update">
<echo message="Updating..." />
<exec program="${mageExe}" workingdir="." verbose="true">
<arg value="-Update" />
<arg value="${testPublishFolder}/EdpClient.application" />
<arg value="-ProviderUrl" />
<arg value=""${testPublishUrl}"" />
<arg value="-Publisher" />
<arg value=""${publisherName}"" />
</exec>
<echo message="Updated" />
</target>
With this I have updated the application manifest file with correct values (Publisher and ProviderUrl)...
I do the same for production build, meaning i build the application to another folder and update it with different ProviderUrl and add Publisher, since it has to be included in every mage update...
Now the problem is with setup.exe file.
Setup.exe is generated at build and it takes the values from the .csproj file.
Considering all of the above I have three issues:
1.
Is there a way of building the application with the correct parameters, so the setup.exe would contain the correct values?
2.
Also how would I update Assembly information (parameter version) before build? When publishing from VS i need to update it on "Probject properties / Application / Assembly Information"
3.
I noticed that when Publishing from VS the application manifest file is also generated in the "Application Files" folder, while publishing with MSBUILD it is not. Why is that?
Thank you in advance and best regards, no9
EDIT:
I fixed the problem #2 like so:
<!--UPDATE ASSEMBLY INFORMATION BEFORE BUILD-->
<target name="UpdateAssemblyInfo">
<asminfo output="${assemblyInfoFile}" language="CSharp">
<imports>
<import namespace="System" />
<import namespace="System.Reflection" />
<import namespace="System.Resources" />
<import namespace="System.Runtime.CompilerServices" />
<import namespace="System.Runtime.InteropServices" />
<import namespace="System.Windows" />
</imports>
<attributes>
<attribute type="AssemblyTitleAttribute" value="some value" />
<attribute type="AssemblyDescriptionAttribute" value="some value" />
<attribute type="AssemblyConfigurationAttribute" value="some value" />
<attribute type="AssemblyCompanyAttribute" value="some value" />
<attribute type="AssemblyProductAttribute" value="some value" />
<attribute type="AssemblyVersionAttribute" value="some value" />
<attribute type="AssemblyFileVersionAttribute" value="some value" />
<attribute type="AssemblyCopyrightAttribute" value="some value" />
<attribute type="AssemblyTrademarkAttribute" value="some value" />
<attribute type="AssemblyCultureAttribute" value="some value" />
<attribute type="CLSCompliantAttribute" value="boolean value" />
<attribute type="ComVisibleAttribute" value="boolean value" />
</attributes>
</asminfo>
<echo file="${assemblyInfoFile}" append="true">
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
</echo>
<echo message="Updated" />
</target>
Meaning I override Assembly.info file before build and add relevant values.
And the problem #3 like so:
<!--COPY APPLICATION MANIFEST TO APPLICATIONFILES FOLDER-->
<target name="CopyTestApplicationManifestToApplicationFilesFolder" depends="Dependency target name" description="Update">
<echo message="Copying..." />
<copy
file="source file"
tofile="target file" />
<echo message="Copied" />
</target>
I was able to create tasks that properly generated the assets you mentioned in your question (setup.exe,application folder, etc.) without having to explicitly sign manifest.
the "clean_and_publish_application" task does the following
Clean the project
Rebuild the project
Publish the project **Here i provide several parameters specifying my publish url, BootstrapperSDKPath,Application Version, and Application Revision
<target name="clean_and_publish_application" description="Build the application.">
<echo message="Clean the build directory"/>
<msbuild project ="${src.dir}\${target.assembly.name}.csproj">
<arg value="/property:Configuration=Debug;outdir=bin\Debug" />
<arg value="/t:clean" />
</msbuild>
<echo message="Rebuild the application"/>
<msbuild project ="${src.dir}\${target.assembly.name}.csproj">
<arg value="/property:Configuration=Debug;outdir=bin\Debug" />
<arg value="/t:rebuild" />
</msbuild>
<echo message="Publish the application"/>
<msbuild project ="${src.dir}\${target.assembly.name}.csproj">
<arg value="/p:publishurl=${publish.url};
GenerateBootstrapperSdkPath=
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\Bootstrapper\;
ApplicationVersion=${app.version};
ApplicationRevision=${app.revision}" />
<arg value="/t:publish" />
</msbuild>
</target>
The BootstrapperSdkPath property is required for the creation of the setup.exe file. I hard coded the location because it doesn't change. MSBuild is looking for a setup.bin file within that directory.
The ApplicationVersion property is formatted as for example 2.0.0.%2a
The ApplicationRevision property is formatted as a number for example 24 (these values are the Publish Version we see in visual studio. I never actually update the AssemplyInfo.cs file at all.)
Any property you see listed in the .csproj file can be passed as an argument for msbuild. I found this documentation very helpful MSBuild Command-Line Reference
The above task does everything you need EXCEPT actually copy the files to your publish url (Still looking for the answer for this). So I just manually copy all the files from the app.config directory the publish creates
<target name="copy_src">
<echo message="Copying app.config folder"/>
<copy todir="${publish.url}" overwrite="true" failonerror="true">
<fileset basedir="${src.dir}\${app.config.location}">
<include name="**" />
</fileset>
</copy>
</target>
I'm running these scripts in Team City. Because I used the publish target, I didn't have to worry about signing any manifests. Also, I use the msbuild task from the NAnt.Contrib.Tasks.dll which you can download here NAntContrib

Setting Visual Studio to automatically minifying using Ajax Minifier

I use VS 2010 I would like at the pressing of Build Button have all CSS and .JS files minified.
Following this article all is working great for JS... adding some extra code for the CSS (pasted below) I'm not able to Minifying the CSS. What I'm doing wrong here?
Thanks for your time.
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="AfterBuild">
<ItemGroup>
<JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" />
<JS Include="**\*.css" Exclude="**\*.min.css;Scripts\*.css" />
</ItemGroup>
<AjaxMin SourceFiles="#(JS)" SourceExtensionPattern="\.js$" TargetExtension=".min.js" />
<AjaxMin SourceFiles="#(CSS)" SourceExtensionPattern="\.css$" TargetExtension=".min.css" />
</Target>
I found out the solution to my problem, more resource here
Unable to run Ajax Minifier as post-build in Visual Studio
http://ajaxmin.codeplex.com/wikipage?title=AjaxMinTask
Also very interesting:
http://www.codeproject.com/Articles/81317/Automatically-compress-embedded-JavaScript-resourc.aspx?msg=3802401&display=Mobile
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="AfterBuild">
<ItemGroup>
<JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" />
</ItemGroup>
<ItemGroup>
<CSS Include="**\*.css" Exclude="**\*.min.css" />
</ItemGroup>
<AjaxMin
JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js"
CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" />
</Target>
On my project file I used "UsingTask" instead of "Import" and it's working:
<UsingTask TaskName="AjaxMin" AssemblyFile="$(MSBuildProjectDirectory)\..\..\packages\AjaxMin.5.14.5506.26202\tools\net40\AjaxMinTask.dll" />
<Target Name="AfterBuild">
<ItemGroup>
<JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" />
</ItemGroup>
<ItemGroup>
<CSS Include="**\*.css" Exclude="**\*.min.css" />
</ItemGroup>
<AjaxMin
JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js"
CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" />
</Target>

Resources