Any good PowerShell MSBuild tasks? - visual-studio

Anyone know of any good MSBuild tasks that will execute a PowerShell script and pass it different parameters?
I was able to find B# .NET Blog: Invoking PowerShell scripts from MSBuild, but I'm hoping for something that is a little more polished.
If I can't find anything I will of course just go ahead and polish my own using that blog post as a starter.

One could use http://powershellmsbuild.codeplex.com/ for 3.5. It'd be nice if there was a NuGet package for it that one could leverage via NuGet package restore.
4.0 has a Windows Powershell Task Factory which you can get in the code gallery has been rolled into
MSBuild Extension Pack (one of the top task libraries - 400+ Tasks & recommended in Inside MSBuild) has PowerShellTaskFactory (download the help file from the download section of this example release to have a peek).

You might also want to look at Psake - a PowerShell based build environment.

Duplicate Question and Answer I Posted, here for posterity for when it has been vote to closed. The key difference is that this question was constrained to being OOTB and my self-answer stays within that constraint.
Question
Powershell doesn't seem to have an easy way to trigger it with an arbitrary command and then bubble up parse and execution errors in a way that correctly interoperates with callers that are not PowerShell - e.g., cmd.exe, TeamCity etc.
My question is simple. What's the best way for me with OOTB MSBuild v4 and PowerShell v3 (open to suggestions-wouldnt rule out a suitably production ready MSBuild Task, but it would need to be a bit stronger than suggesting "it's easy - taking the PowerShell Task Factory sample and tweak it and/or becoming it's maintainer/parent") to run a command (either a small script segment, or (most commonly) an invocation of a .ps1 script.
I'm thinking it should be something normal like:
<Exec
IgnoreStandardErrorWarningFormat="true"
Command="PowerShell "$(ThingToDo)"" />
That sadly doesn't work:-
if ThingToDo fails to parse, it fails silently
if ThingToDo is a script invocation that doesn't exist, it fails
if you want to propagate an ERRORLEVEL based .cmd result, it gets hairy
if you want to embed " quotes in the ThingToDo, it won't work
So, what is the bullet proof way of running PowerShell from MSBuild supposed to be? Is there something I can PsGet to make everything OK?
Answer
Weeeeelll, you could use something long winded like this until you find a better way:-
<PropertyGroup>
<__PsInvokeCommand>powershell "Invoke-Command</__PsInvokeCommand>
<__BlockBegin>-ScriptBlock { $errorActionPreference='Stop';</__BlockBegin>
<__BlockEnd>; exit $LASTEXITCODE }</__BlockEnd>
<_PsCmdStart>$(__PsInvokeCommand) $(__BlockBegin)</_PsCmdStart>
<_PsCmdEnd>$(__BlockEnd)"</_PsCmdEnd>
</PropertyGroup>
And then 'all' you need to do is:
<Exec
IgnoreStandardErrorWarningFormat="true"
Command="$(_PsCmdStart)$(ThingToDo)$(_PsCmdEnd)" />
The single redeeming feature of this (other than trapping all error types I could think of), is that it works OOTB with any PowerShell version and any MSBuild version.
I'll get my coat.

With a bit of fun, I managed to come up with a fairly clean way of making this work:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- #1 Place this line at the top of any msbuild script (ie, csproj, etc) -->
<PropertyGroup><PowerShell># 2>nul || type %~df0|find /v "setlocal"|find /v "errorlevel"|powershell.exe -noninteractive -& exit %errorlevel% || #</PowerShell></PropertyGroup>
<!-- #2 in any target you want to run a script -->
<Target Name="default" >
<PropertyGroup> <!-- #3 prefix your powershell script with the $(PowerShell) variable, then code as normal! -->
<myscript>$(PowerShell)
#
# powershell script can do whatever you need.
#
dir ".\*.cs" -recurse |% {
write-host Examining file named: $_.FullName
# do other stuff here...
}
$answer = 2+5
write-host Answer is $answer !
</myscript>
</PropertyGroup>
<!-- #4 and execute the script like this -->
<Exec Command="$(myscript)" EchoOff="true" />
</Target>
</Project>
Notes:
You can still use the standard Exec Task features! (see: https://msdn.microsoft.com/en-us/library/x8zx72cd.aspx)
if your powershell script needs to use < > or & characters, just place the contents in a CDATA wrapper:
<script2><![CDATA[ $(PowerShell)
# your powershell code goes here!
write-host "<<Hi mom!>>"
]]></script2>
if you want return items to the msbuild script you can get them:
<script3>$(PowerShell)
# your powershell code goes here!
(dir "*.cs" -recurse).FullName
</script3>
<Exec Command="$(script3)" EchoOff="true" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="items" />
</Exec>
<Touch Files="$(items)" />
See! then you can use those items with another msbuild Task :D

Related

How to use an msbuild CopyTask to copy a list of directories recursively

I would like to copy a list of directories recursively using a CopyTask.
The list is defined by a macro like so;
<ConanBinaryDirectories>some/path/;another/path/;</ConanBinaryDirectories>
I know a CopyTask can copy a single directory recursively, but how to deal with the specified format.
The ConanBinaryDirectories seems to be a MSBuild Property. If so, I assume you can use Msbuild Property Functions to get the single path.
Something like this:
<PropertyGroup>
<ConanBinaryDirectories>C:\Users\xxx\Desktop\Path1;C:\Users\xxx\Desktop\Path2;</ConanBinaryDirectories>
</PropertyGroup>
<PropertyGroup>
<SourcePath1>$(ConanBinaryDirectories.Split(";")[0])</SourcePath1> //C:\Users\xxx\Desktop\Path1
<SourcePath2>$(ConanBinaryDirectories.Split(";")[1])</SourcePath2> //C:\Users\xxx\Desktop\Path2
</PropertyGroup>
After you get the property which represents the single directory, you can use either 1.Copy task or 2.Exec task with xcopy command in it like this to copy the single directory to destination path.
All you need to do is to call the corresponding task twice in your custom target.
I know maybe what you want when you ask this question is a way like turn the MSBuild property to an MSBuild Item as the input of a task and do the copy job. But after my check: 1. The msbuild copy task actually doesn't support the input format like this some/path/ 2.We can use something like some/path/**/*.*, but it doesn't work well when our input could be something like #(...)/**/*.*.
So I suggest you split the macro to several paths and then use them into copy job.
Update:
The msbuild property doesn't support wildcard well. So to use something like **/*.*, you need to use Item instead of Property. You can have a look at this similar issue.
For a Property whose value is Path/*.*, it only represents a string Path/*.* most of the time while for an Item <MyItem Include="Path/*.*"/>, it represents all the files in the specified path. So no matter which way(copy task or xcopy command) we choose to do the copy job,the input should be an Item.
The script which works after test:
<PropertyGroup>
C:\Users\xxx\Desktop\Path1;C:\Users\xxx\Desktop\Path2
<PropertyGroup>
<SourcePath1>$(ConanBinaryDirectories.Split(";")[0])</SourcePath1>
<SourcePath2>$(ConanBinaryDirectories.Split(";")[1])</SourcePath2>
</PropertyGroup>
<ItemGroup>
<MySourceFiles Include="$(SourcePath1)\**\*.*" />
<MySourceFiles Include="$(SourcePath2)\**\*.*" />
</ItemGroup>
<Target Name="TestItem" AfterTargets="build">
<Copy SourceFiles="#(MySourceFiles)" DestinationFolder="$(OutputPath)"/>
</Target>
$(OutputPath) for C#, $(OutDir) for C++.

Displaying Metarunner parameters for build steps

I've created a metarunner in TeamCity, but I can't figure out how to display information from it on the list of build steps, which leads to several identical rows. Other (built-in) runners have the ability to display some basic information to help clarify what the step is doing. For example, in the image below, I have:
A metarunner with no description
The built-in Command Line task, with a "Command" displayed
Five instances of the same metarunner with different parameters, which all look the same.
The built-in SMB Upload task, with a "Target SMB share" displayed
One parameter from the "Copy config" metarunner looks like:
<param name="FileName" spec="text display='normal' label='File Name' description='Name of the file to be copied'" />
Is this something that can be edited via the metarunner XML? Or is this a feature that's only available to built-in runners?
I see parameters/param tags before the build-runners/runner section containing parameters/param (the latter being inputs to the wrapped runner, I guess). The former allow editable inputs. I am new so I feel uncomfortable with the multiple inheritance. I would rather see the parameters configuration from the runner to show up in the Parameters section of the build. I would also prefer if the runner could configure the artifact mapping as well. So far I am going to configure parameters in the root project separately from my custom runner. I did not look at custom plugins.
https://github.com/endjin/NewRelicDeploymentNotifierMetaRunner/blob/master/Solutions/SimpleRunner/server/metaRunners/MRPLUGIN_NewRelicDeploymentNotifier.xml
I figured I do not have to HTML-escape special characters in the runner's custom script, I could use the CDATA marker-wrapped clear text of the script as the param tag's text.
<build-runners>
<runner name="Run my runner" type="simpleRunner">
<parameters>
<param name="use.custom.script" value="true" />
<param name="script.content"><![CDATA[#echo on
if not exist %env.CYGBINSLASH%python2.7.exe (
powershell "$r = New-Object System.Net.WebClient; $r.DownloadFile('%env.scriptweb%fixcygwin.bat', 'fixcygwin.bat')"
call fixcygwin.bat /f
) 1>&2
#rem CMD.EXE would destroy leading double quotes in continuation lines, so sticking white space.
%env.CYGBINSLASH%bash -exc '^
if [[ "${my_param}" != "true" ]] ; then ^
exit; ^
fi; ^
/usr/bin/curl -o myscript.sh "${MY_SCRIPT}"; ^
source myscript.sh "PROJ_%Project%" "%Project%/%Project%.csproj"; ^
' 1>&2]]></param>
<param name="teamcity.step.mode" value="default" />
</parameters>
</runner>
</build-runners>

code reuse for file existence test in a Visual Studio vcxproj file

I have a vcxproj file that contains explicit Windows shell commands in the NMakeBuildCommandLine section:
<NMakeBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
move file1 file2
</NMakeBuildCommandLine>
I'm using MSBuild to execute the vcxproj, either directly or via a sln file. The problem is that when file1 does not exist, the output is very unhelpful and doesn't even list the file's name:
The system cannot find the file specified.
My naive solution is to replace move file1 file2 with:
if exist file1 (move file1 file2) else (echo file1 does not exist && exit 1)
(Note that I need to write && instead of &&.)
This works, but it's error-prone because I need to type file1 three times per line and make sure they all match. file1 is only one of many files that need to be moved. Furthermore, the series of commands is virtually identical across the various build configurations.
How can I avoid repeating myself both within a command line and across build configurations? I thought that the UserMacros property group might help me, but I couldn't figure out how to write such a macro via the IDE. (Right-clicking on the project in Visual Studio doesn't show a field for entering user macros.) Nor could I find any discussion of the syntax of this section on the Internet, so I didn't know how to write macros with a text editor (which I would actually prefer).
There may be yet a better way within a vcxproj file to identify files that need to exist, so I'm open to any alternatives.
MsBuild has 'variables' like most other programming languages called properties. You declare one in a PropertyGroup element in the project file xml and then reuse it using the $(PropertyName) syntax. Example for your case:
<PropertyGroup>
<Src>/path/to/my/src</Src>
<Dst>/path/to/my/dst</Dst>
</PropertyGroup>
<NMakeBuildCommandLine>
if exist $(Src) (move $(Src) $(Dst)) else (echo $(Src) does not exist && exit 1)
</NMakeBuildCommandLine>
If you want to use the IDE, which might get tedious if you have lots of values, you can indeed use so-called UserMacros but you have to declare those in a proprty sheet. Go to View->Property Manager, right-click on your project and select 'Add new Property Sheet'. Doubleclick on it, go to 'User Macros' and add key/value pairs there. If you save everything and look in the generated files you'll see the vcxproj now Imports the propertysheet, and the propertysheet itself has a PropertyGroup just like shown above - but editable thgough the IDE.
As an alternative which might be better (less duplication, easier to automate) in the long run you can use MsBuild code for checking file existence and moving files which has the benefit you only have to write the move command once as you can have MsBuild loop over items. Those are declared in an ItemGroup. Explaining everything here is a bit out of scope but an example should make things clear:
<Target Name="BatchMove">
<ItemGroup>
<SrcFiles Include="file1">
<Dest>file2</Dest>
</SrcFiles>
<SrcFiles Include="file3">
<Dest>file4</Dest>
</SrcFiles>
</ItemGroup>
<Warning Text="Source file %(SrcFiles.Identity) does not exist" Condition="!Exists(%(SrcFiles.Identity))" />
<Move SourceFiles="%(SrcFiles.Identity)" DestinationFiles="%(SrcFiles.Dest)" Condition="Exists(%(SrcFiles.Identity))" />
</Target>
This declares 2 source files file1/file3 and their respective destination files file2/file4. If the source does not exists (using standard MsBuild Exists check) a message is shown, else it is moved to the destination. Those % characters will make the line they occur in loop over each element of the SrcFiles collection. To add more files, just add to the ItemGroup. Last step is to get this target invoked from the nmake command line which is done simply by calling msbuild on the file itself and telling it to run the target:
<NMakeBuildCommandLine>
msbuild $(MSBuildThisFile) /t:BatchMove
</NMakeBuildCommandLine>

How can I force files in my project so that they always copy to output directory

I am using a visual studio c# library project to contain static resources that are needed as deployment artifacts. (in my case SQL files that are run with a combination of RoundhousE and Octopus deploy). By convention all files in the project must have their properties set so that the "Build action" is "Content" and "Copy to output directory" is "Copy always".
If someone on the team adds a file but forgets to set these properties we see deployment errors. This is usually picked up in an internal environment, but I was hoping to find a way to enforce this in the CI build.
So is there a way to either fail the build or better still override these properties during the build with an MS Build task? Am I tackling this the wrong way? Any suggestions welcomed.
You are going to have to parse the project files and check for Content without CopyToOutputDirectory set to Always, I doubt there is another way.
That can be done using whatever scripting language you want, or you could even write a small C# tool that uses the classes from the Microsoft.Build.Evaluation namespace. Here is a possible PowerShell implementation - the hardest part is getting the regexes right. First one checks for Content without any metadata, second one for Content where CopyToOutputDirectory does not start with "A" (which I assume should be "Always", no idea how to match that whole word).
FindBadContentNodes.ps1 :
param([String]$inputDir)
Function FindBadContent()
{
$lines = Get-Content $input
$text = [string]::Join( "`n", $lines )
if( $text -match "<Content Include.*/>" -Or
$text -match "<Content Include.*`n\s*<CopyToOutputDirectory>[^A]\w*<.*" )
{
"Found file with bad content node"
exit 1
}
}
Get-ChildItem -Recurse -Include *.csproj -Path $inputDir | FindBadContent
Call this from MsBuild:
<Target Name="FindBadContentNodes">
<Exec Command="Powershell FindBadContentNodes.ps1 -inputDir path\to\sourceDir"/>
</Target>
Note you mention or better still override these properties during the build. I'd stay away from such a solution: you're just burying the problem and relying on the CI to produce correct builds, so local builds using just VS would not be the same. Imo making the build fail is better, especially since most CI systems have a way of notifying the developper that is responsible anyway so the fix should be applied quickly.
Another possibility would be to have the CI apply the fix and then commit the changes so at least everyone has the correct version.
IIRC there is a way in Visual Studio to set a file extension to do certain things on default, much like .config files will always set to content and copy to output directory.
So one could do the same with .sql files (and other files that they would want to be set up this way). A quick search brought me to this: http://blog.andreloker.de/post/2010/07/02/Visual-Studio-default-build-action-for-non-default-file-types.aspx
The relevant parts:
The default build action of a file type can be configured in the
registry. However, instead of hacking the registry manually, we use a
much better approach: pkgdef files (a good article about pkgdef
files). In essence, pkdef are configuration files similar to .reg
files that define registry keys and values that are automatically
merged into the correct location in the real registry. If the pkgfile
is removed, the changes are automatically undone. Thus, you can safely
modify the registry without the danger of breaking anything – or at
least, it’s easy to undo the damage.
Finally, here’s an example of how to change the default build action
of a file type:
1: [$RootKey$\Projects{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\FileExtensions.spark]
2: "DefaultBuildAction"="Content" The Guid in the key refers to project type. In this case, “{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}” means “C# projects”. A rather comprehensive list of project type guids can be found here. Although it does not cover Visual Studio 2010 explicitly, the Guids apply to the current version as well. By the way, we can use C# as the project type here, because C# based MVC projects are in fact C# projects (and web application projects). For Visual Basic, you’d use “{F184B08F-C81C-45F6-A57F-5ABD9991F28F}” instead.
$RootKey$ is in abstraction of the real registry key that Visual
Studio stores the configuration under:
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config (Note:
Do not try to manually edit anything under this key as it can be
overwritten at any time by Visual Studio).
The rest should be self explanatory: this option sets the default
build action of .spark files to “Content”, so those files are included
in the publishing process.
All you need to do now is to put this piece of text into a file with
the extension pkgdef, put it somewhere under
%PROGRAMFILES(x86)%\Microsoft Visual Studio
10.0\Common7\IDE\Extensions (on 64-bit systems) or %PROGRAMFILES(x86)%\Microsoft Visual Studio
10.0\Common7\IDE\Extensions (on 32-bit systems) and Visual Studio will load and apply the settings automatically the next time it starts. To
undo the changes, simply remove the files.
Finally, I’ve attached a bunch of pkgdef files that are use in
production that define the “Content” default Build Action for C# and
VB projects for .spark, .brail, .brailjs and .less files respectively.
Download them, save them somewhere in the Extensions folder and you’re
good to go.
The author also says that he built a utility to help do all of this for you:
http://tools.andreloker.de/dbag
Expanding on #stijn answer, instead of using regex it is far easier to use native xml parsing.
Here is my proposed file, it also supports the ability to customize which files are evaluated by using a regex on the filename only.
param([String]$Path, [string]$IncludeMatch, [switch]$AllowPreserve)
Function Test-BadContentExists
{
param (
[parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string[]]$Path,
[string]$IncludeMatch,
[switch]$AllowPreserve
)
[xml]$proj = Get-Content -Path $Path
$ContentNodes = ($proj | Select-Xml "//Content|//n:Content" -Namespace #{n='http://schemas.microsoft.com/developer/msbuild/2003'}).Node
if (![string]::IsNullOrEmpty($IncludeMatch)) {
$ContentNodes = $ContentNodes | Where-Object -Property Include -Match $IncludeMatch
}
#remove the always nodes
$ContentNodes = $ContentNodes | Where-Object -Property CopyToOutputDirectory -ne 'Always'
#optionally remove the preserve nodes
if ($AllowPreserve) {
$ContentNodes = $ContentNodes | Where-Object -Property CopyToOutputDirectory -ne 'PreserveNewest'
}
if($ContentNodes)
{
write-output "Found file with bad content node:"
write-output ($ContentNodes | Select-Object Include,CopyToOutputDirectory | sort Include | Out-String)
exit 1
}
}
[hashtable]$Options = $PSBoundParameters
[void]$Options.Remove("Path")
Get-ChildItem -Recurse -Include *.csproj -Path $Path | Test-BadContentExists #Options
and calling it, with parameter:
<Target Name="FindBadContentNodes">
<Exec Command="Powershell FindBadContentNodes.ps1 -inputDir path\to\sourceDir -IncludeMatch '^Upgrade.*\.(sql|xml)$'"/>
</Target>
I ended up using a pre-build event instead and put this ps1 file in my solution directory so i could use it with multiple projects.
echo "Build Dir: %cd%"
echo "Sol Dir: $(SolutionDir)"
echo "Proj Dir: '$(ProjectDir)"
echo.
Powershell -NoProfile -Command "& '$(SolutionDir)\FindBadContentNodes.ps1' -Path '$(ProjectDir)' -IncludeMatch '^Upgrade.*\.(sql|xml)$'"
example build output:
1> "Build Dir: C:\Source\RPS\MRM BI\MRMBI-Setup\MRMBI-Schema\bin\Debug"
1> "Sol Dir: C:\Source\RPS\MRM BI\MRMBI-Setup\"
1> "Proj Dir: 'C:\Source\RPS\MRM BI\MRMBI-Setup\MRMBI-Schema\"
1>
1> Found file with bad content node:
1>
1> Include CopyToOutputDirectory
1> ------- ----------------------
1> Upgrades\V17.09\myfile1.sql
1> Upgrades\V20.05\myfile2.sql PreserveNewest
1>

How to fully clean bin and obj folders within Visual Studio?

If you right click on a folder, you will see a "Clean" menu item. I assumed this would clean (remove) the obj and bin directory.
However, as far as I can see, it does nothing.
Is there another way?
(please don't tell me to go to Windows Explorer or the cmd.exe)
I'd like to remove the obj and bin folder so that I can easily zip the whole thing.
As others have responded already Clean will remove all artifacts that are generated by the build. But it will leave behind everything else.
If you have some customizations in your MSBuild project this could spell trouble and leave behind stuff you would think it should have deleted.
You can circumvent this problem with a simple change to your .*proj by adding this somewhere near the end :
<Target Name="SpicNSpan"
AfterTargets="Clean">
<RemoveDir Directories="$(OUTDIR)"/>
</Target>
Which will remove everything in your bin folder of the current platform/configuration.
------ Edit
Slight evolution based on Shaman's answer below (share the votes and give him some too)
<Target Name="SpicNSpan" AfterTargets="Clean">
<!-- Remove obj folder -->
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<!-- Remove bin folder -->
<RemoveDir Directories="$(BaseOutputPath)" />
</Target>
---- Edit again with parts from xDisruptor but I removed the .vs deletion as this would be better served in a .gitignore (or equivalent)
Updated for VS 2015.
<Target Name="SpicNSpan" AfterTargets="Clean"> <!-- common vars https://msdn.microsoft.com/en-us/library/c02as0cs.aspx?f=255&MSPPError=-2147217396 -->
<RemoveDir Directories="$(TargetDir)" /> <!-- bin -->
<RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" /> <!-- obj -->
</Target>
He also provides a good suggestion on making the task easier to deploy and maintain if you have multiple projects to push this into.
If you vote this answer be sure to vote them both as well.
If you are using git and have a correct .gitignore in your project, you can
git clean -xdf --dry-run
to remove absolutely every file on the .gitignore list, i.e. it will clean obj, and bin folders (the x triggers this behavior)
Note: The parameter --dry-run will only simulate the operation ("Would remove ...") and show you what git would delete. Try it with dry-run, then remove the parameter and it will really delete the files+folders.
Optionally, after that clean command, you can use dotnet restore mySolution.sln to get all the NUGET packages restored. And if you have a developer console open anyway, you can quickly run msbuild -m mySolution.sln afterwards (without having Visual Studio open) to see if it was successful.
For Visual Studio 2015 the MSBuild variables have changed a bit:
<Target Name="SpicNSpan" AfterTargets="Clean"> <!-- common vars https://msdn.microsoft.com/en-us/library/c02as0cs.aspx?f=255&MSPPError=-2147217396 -->
<RemoveDir Directories="$(TargetDir)" /> <!-- bin -->
<RemoveDir Directories="$(SolutionDir).vs" /> <!-- .vs -->
<RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" /> <!-- obj -->
</Target>
Notice that this snippet also wipes out the .vs folder from the root directory of your solution. You may want to comment out the associated line if you feel that removing the .vs folder is an overkill. I have it enabled because I noticed that in some third party projects it causes issues when files ala application.config exist inside the .vs folder.
Addendum:
If you are into optimizing the maintainability of your solutions you might want to take things one step further and place the above snippet into a separate file like so:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="SpicNSpan" AfterTargets="Clean"> <!-- common vars https://msdn.microsoft.com/en-us/library/c02as0cs.aspx?f=255&MSPPError=-2147217396 -->
<RemoveDir Directories="$(TargetDir)" /> <!-- bin -->
<RemoveDir Directories="$(SolutionDir).vs" /> <!-- .vs -->
<RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" /> <!-- obj -->
</Target>
</Project>
And then include this file at the very end of each and every one of your *.csproj files like so:
[...]
<Import Project="..\..\Tools\ExtraCleanup.targets"/>
</Project>
This way you can enrich or fine-tune your extra-cleanup-logic centrally, in one place without going through the pains of manually editing each and every *.csproj file by hand every time you want to make an improvement.
To delete bin and obj before build add to project file:
<Target Name="BeforeBuild">
<!-- Remove obj folder -->
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<!-- Remove bin folder -->
<RemoveDir Directories="$(BaseOutputPath)" />
</Target>
Here is article: How to remove bin and/or obj folder before the build or deploy
This site: https://sachabarbs.wordpress.com/2014/10/24/powershell-to-clean-visual-studio-binobj-folders/ uses William Kempf's powershell commands to remove any bin and obj folders from the current directory and sub directories. It should be possible to run it from the root of the drive.
Here is William's version
gci -inc bin,obj -rec | rm -rec -force
In William's own words:
That wipes out all of the “bin” and “obj” directories in the current
directory and every subdirectory. Super useful to run in your
workspace directory to get to a “clean” state, especially when someone
messes up and there’s something that a Clean or Rebuild inside the IDE
doesn’t catch.
For those of you reading that may not know, PowerShell supports
command aliases, here it is rewritten again not using the aliases
Get-ChildItem -inc bin,obj -rec | Remove-Item -rec -force
NOTE : You should have this stored in a PowerShell file and place that
file at the root of your solution (where the .sln file resides), and
then run it when you want a proper clean (not the micky mouse one that
VisualStudio does, and reports success too).
Check out Ron Jacobs fantastic open source CleanProject It even takes care of the zipping if you like.
Here is the CodePlex link
Visual Studio Extension
Right Click Solution - Select "Delete bin and obj folders"
You can easily find and remove bin and obj folders in Far Manager.
Navigate to you solution and press Alt+F7
In search setting dialog:
Type "bin,obj" in field "A file mask or several file masks"
Check option "Search for folders"
Press Enter
After the search is done, switch view to "Panel".
Select all files (with Ctrl+A) and delete folders (press "Shift+Del")
Hope it helps someone.
Based on Joe answer, I've converted the VB code into C# :
/// <summary>
/// Based on code of VSProjCleaner tool (C) 2005 Francesco Balena, Code Archirects
/// </summary>
static class VisualStudioCleaner
{
public static void Process(string rootDir)
{
// Read all the folder names in the specified directory tree
string[] dirNames = Directory.GetDirectories(rootDir, "*.*", SearchOption.AllDirectories);
List<string> errorsList = new List<string>();
// delete any .suo and csproj.user file
foreach (string dir in dirNames) {
var files = new List<string>();
files.AddRange(Directory.GetFiles(dir, "*.suo"));
files.AddRange(Directory.GetFiles(dir, "*.user"));
foreach (string fileName in files) {
try {
Console.Write("Deleting {0} ...", fileName);
File.Delete(fileName);
Console.WriteLine("DONE");
} catch (Exception ex) {
Console.WriteLine();
Console.WriteLine(" ERROR: {0}", ex.Message);
errorsList.Add(fileName + ": " + ex.Message);
}
}
}
// Delete all the BIN and OBJ subdirectories
foreach (string dir in dirNames) {
string dirName = Path.GetFileName(dir).ToLower();
if (dirName == "bin" || dirName == "obj") {
try {
Console.Write("Deleting {0} ...", dir);
Directory.Delete(dir, true);
Console.WriteLine("DONE");
} catch (Exception ex) {
Console.WriteLine();
Console.WriteLine(" ERROR: {0}", ex.Message);
errorsList.Add(dir + ": " + ex.Message);
}
}
}
Console.WriteLine(new string('-', 60));
if (errorsList.Count == 0) {
Console.WriteLine("All directories and files were removed successfully");
} else {
Console.WriteLine("{0} directories or directories couldn't be removed", errorsList.Count);
Console.WriteLine(new string('-', 60));
foreach (string msg in errorsList) {
Console.WriteLine(msg);
}
}
}
}
In windows just open the explorer
navigate to your SLN folder
click into search field and type kind:=folder;obj --> for obj folders
use CTRL+A and delete 'em - same for bin
Done
No need for any tool or extra software ;)
Clean will remove all intermediate and final files created by the build process, such as .obj files and .exe or .dll files.
It does not, however, remove the directories where those files get built.
I don't see a compelling reason why you need the directories to be removed.
Can you explain further?
If you look inside these directories before and after a "Clean", you should see your compiled output get cleaned up.
I use VisualStudioClean which is easy to understand and predictable. Knowing how it works and what files it is going to delete relieves me.
Previously I tried VSClean (note VisualStudioClean is not VSClean), VSClean is more advanced, it has many configurations that sometimes makes me wondering what files it is going to delete? One mis-configuration will result in lose of my source codes. Testing how the configuration will work need backing up all my projects which take a lot of times, so in the end I choose VisualStudioClean instead.
Conclusion : VisualStudioClean if you want basic cleaning, VSClean for more complex scenario.
I can't add a comment yet (no minimal reputation reached)
so I leave this reply to underline that:
the "BeforeBuild" action with <RemoveDir Directories="$(BaseIntermediateOutputPath)" /> is great but, for me, is conflicting with an Entity Framework model included into the same project.
The error I receive is:
Error reading resource '{mymodel}.csdl' -- 'Could not find a part of the path '{myprojectpath}\obj\Release\edmxResourcesToEmbed\{mymodel}.csdl
I suppose, the "edmxResourcesToembed" is created before the "BeforeBuild" target action is executed.
This is how I do with a batch file to delete all BIN and OBJ folders recursively.
Create an empty file and name it DeleteBinObjFolders.bat
Copy-paste code the below code into the DeleteBinObjFolders.bat
Move the DeleteBinObjFolders.bat file in the same folder with your solution (*.sln) file.
#echo off
#echo Deleting all BIN and OBJ folders...
for /d /r . %%d in (bin,obj) do #if exist "%%d" rd /s/q "%%d"
#echo BIN and OBJ folders successfully deleted :) Close the window.
pause > nul
Update: Visual Studio 2019 (Clean [bin] and [obj] before release). However I am not sure if [obj] needs to be deleted. Be aware there is nuget package configuration placed too. You can remove the second line if you think so.
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)' == 'Release'">
<!--remove bin-->
<Exec Command="rd /s /q "$(ProjectDir)$(BaseOutputPath)" && ^" />
<!--remove obj-->
<Exec Command="rd /s /q "$(BaseIntermediateOutputPath)Release"" />
</Target>
I store my finished VS projects by saving only source code.
I delete BIN, DEBUG, RELEASE, OBJ, ARM and .vs folders from all projects.
This reduces the size of the project considerably. The project
must be rebuilt when pulled out of storage.
Just an addendum to all the fine answers above in case someone doesn't realize how easy it is in VB/C# to automate the entire process down to the zip archive.
So you just grab a simple Forms app from the templates (if you don't already have a housekeeping app) and add a button to it and then ClickOnce install it to your desktop without worrying about special settings or much of anything. This is all the code you need to attach to the button:
Imports System.IO.Compression
Private Sub btnArchive_Click(sender As Object, e As EventArgs) Handles btnArchive.Click
Dim src As String = "C:\Project"
Dim dest As String = Path.Combine("D:\Archive", "Stub" & Now.ToString("yyyyMMddHHmmss") & ".zip")
If IsProjectOpen() Then 'You don't want Visual Studio holding a lock on anything while you're deleting folders
MsgBox("Close projects first, (expletive deleted)", vbOKOnly)
Exit Sub
End If
If MsgBox("Are you sure you want to delete bin and obj folders?", vbOKCancel) = DialogResult.Cancel Then Exit Sub
If ClearBinAndObj(src) Then ZipFile.CreateFromDirectory(src, dest)
End Sub
Public Function ClearBinAndObj(targetDir As String) As Boolean
Dim dirstodelete As New List(Of String)
For Each d As String In My.Computer.FileSystem.GetDirectories(targetDir, FileIO.SearchOption.SearchAllSubDirectories, "bin")
dirstodelete.Add(d)
Next
For Each d As String In My.Computer.FileSystem.GetDirectories(targetDir, FileIO.SearchOption.SearchAllSubDirectories, "obj")
dirstodelete.Add(d)
Next
For Each d In dirstodelete
Try
Directory.Delete(d, True)
Catch ex As Exception
If MsgBox("Error: " & ex.Message & " - OK to continue?", vbOKCancel) = MsgBoxResult.Cancel Then Return False
End Try
Next
Return True
End Function
Public Function IsProjectOpen()
For Each clsProcess As Process In Process.GetProcesses()
If clsProcess.ProcessName.Equals("devenv") Then Return True
Next
Return False
End Function
One thing to remember is that file system deletes can go wrong easily. One of my favorites was when I realized that I couldn't delete a folder because it contained items created by Visual Studio while running with elevated privileges (so that I could debug a service).
I needed to manually give permission or, I suppose, run the app with elevated privileges also. Either way, I think there is some value in using an interactive GUI-based approach over a script, specially since this is likely something that is done at the end of a long day and you don't want to find out later that your backup doesn't actually exist...
this answer is great I just want to comment on the last part of the answer
NOTE : You should have this stored in a PowerShell file and place that
file at the root of your solution (where the .sln file resides), and
then run it when you want a proper clean (not the micky mouse one that
VisualStudio does, and reports success too).
Alternatively, you can add the following to your profile.ps1
function CleanSolution {
Get-ChildItem -inc bin,obj -rec | Remove-Item -rec -force
}
Set-Alias cs CleanSolution
Then you can use either CleanSolution or cs to run. That way you can use it for any project and without the ./ prefix of the filename
Complete one-liner you can invoke from within Visual Studio
In your solution root folder create a file called "CleanBin.bat" and add the following one-liner:
Powershell.exe -ExecutionPolicy Bypass -NoExit -Command "Get-ChildItem -inc bin,obj -rec | Remove-Item -rec -force"
Run the .bat file. Enjoy.
Original creds to the answer here: https://stackoverflow.com/a/43267730/1402498
The original answer shows the powershell command, but I had a lot of trouble making it work smoothly on my system. I finally arrived at the one-liner above, which should work nicely for most folks.
Caveat:
Microsoft seems to be great at making Windows security cause stranger and stranger behavior. On my machine, when I run the script, all obj and bin folders are deleted but then reappear 2 seconds later! Running the script a second time causes permanent deletion. If anyone knows what would cause this behavior, please let me know a fix and I'll update the answer.
for visual studio 2022
you can use:
https://marketplace.visualstudio.com/items?itemName=MadsKristensen.CleanSolution
If you need to delete bin and obj folders from ALL of your projects...
Launch git Bash and enter the following command:
find . -iname "bin" -o -iname "obj" | xargs rm -rf
For C# projects, I recommend appending $(Configuration) to obj folder, so-as to avoid deleting nuget files which are stored on obj base directory.
<Target Name="CleanAndDelete" AfterTargets="Clean">
<!-- Remove obj folder -->
<RemoveDir Directories="$(BaseIntermediateOutputPath)$(Configuration)" />
<!-- Remove bin folder -->
<RemoveDir Directories="$(BaseOutputPath)" />
</Target>
If you delete the nuget files, it can be problematic to recreate them. Moreover, I've never seen a case where "Restore NuGet Packages" fixes this issue after these files have been deleted.

Resources