MSBuild Task syntax for deleting files - msbuild

I'm trying to write a MSBuild Task that deletes the Obj directory and PDBs from my bin folder on my production build scripts and can't seem to get it to work right.
Does anyone have an example where they do this or similar, or a link to a simple example of removing files and a directory with MSBuild?

You can delete the files in those directories first and then the dir itself with
<Target Name="SomeTarget">
<ItemGroup>
<FilesToDelete Include="Path\To\Obj\**\*"/>
</ItemGroup>
<Delete Files="#(FilesToDelete)" />
<RemoveDir Directories="Path\To\Obj\" />
</Target>

If you're looking to delete an entire directory you require the RemoveDir task:
<RemoveDir Directories="Path/To/Obj" />
And if you're wanting to delete the PDB files from bin you'll want the Delete task:
<Delete Files="Path/To/Bin/MyApp.pdb" />
Note that you cannot use wildcards in the Delete task, so if you have multiple pdb files you're have to provide an ItemGroup as an argument.

Posting for others that might have ran into the same problem I was having.
The Delete task cannot delete readonly files, which I needed to be able to do, as when MSBuild gets latest from TFS, the files are marked as readonly. I used the EXEC command to delete readonly files:
<ItemGroup>
<FileToDelete Include="c:\temp\fileToDelete.txt"/>
</ItemGroup>
<Exec Command="del /F /Q "#(FileToDelete)""/>

The posted answers will work as long as you have to deal with a single directory. If you happen to have nested folders, RemoveDir will fail with Directory not empty error.
A slightly generic approach takes care of nested folders as well:
<Target Name="CleanOutDir">
<ItemGroup>
<FilesToClean Include="$(OutDir)\**\*.*" />
<!-- Bit of .Net to get all folders and subfolders -->
<FoldersToClean Include="$([System.IO.Directory]::GetDirectories("$(OutDir)"))" />
</ItemGroup>
<Delete Files="#(FilesToClean)"/>
<RemoveDir Directories="#(FoldersToClean)" />
</Target>

This code is so ugly it should come with an airsickness bag. ;-) But it is fast because it doesn't build a list of files to delete etc.
<Target Name="DeleteBuildFolder">
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
<Exec Command="RmDir /S /Q "$(BuildFolder)"" />
</Target>
How many RmDir commands are needed? Enough so a few RmDir commands return "The system cannot find the file specified" instead of "The directory is not empty." On my machine it seems to take another RmDir if $(BuildFolder) is open in Windows Explorer. The antivirus program may affect RmDir like it occasionally does Subversion but I'd rather have blanket AV protection than (mis)manage an exclusion list.

It is also possible to first remove the readonly property from the file and to execute the msbuild delete Task.
Like so:
<Target Name="DeleteFiles">
<Message Text="Delete File" Importance="high"/>
<Attrib Files="$(FileToDelete)" ReadOnly="false" />
<Delete Files="$(FileToDelete)" />
</Target>`

In Visual Studio 2013, added this to the end of my .csproj file just before the </Project> closing tag
<Target Name = "clean_folders" AfterTargets="Clean">
<Exec Command = "rd /S /Q obj" />
<Exec Command = "rd /S /Q bin" />
</Target>
At first it didn't appear to work but I noticed that Visual Studio (or R#, not sure) re-re-added DesignTimeResolveAssemblyReferencesInput.cache to the obj folder and it also re-added the current \bin folder (I have different builds in different subfolders under \bin). It cleaned away everything else, including the 25 other build configs I have from imported .csproj files (yes, I know).
Be careful if you Batch Rebuild more than one config as it just wipes all previous efforts on each rebuild leaving you with only the last one. Whups.

Just to add one more wrinkle that I've discovered. I'm using Visual Studio 2015. The posted answers that are deleting via wildcard are still troublesome for me. I suspect that the wildcards are evaluated before the build, not after. That means the deletions won't happen if the files you want to delete are created during the build. It also leads to wonderful behavior where the delete works every second time you build, which makes this all very enjoyable to test.
I'm giving up on wildcards. For what I'm doing I know the files that are causing trouble and I'm hard-coding (if it can be called that inside of a project file) the actual file names.

Related

How to copy downloaded file to publish directory with msbuild?

I'd like to download and copy files to the publish directory with MSBuild. I've tried the following which seems pretty obvious:
<Target Name="DownloadContentFiles" BeforeTargets="BeforePublish">
<DownloadFile SourceUrl="$(LatestValidatorUrl)" DestinationFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="DownloadedFile" ItemName="Content" />
</DownloadFile>
</Target>
<ItemGroup>
<Content Include="org.hl7.fhir.validator.jar" CopyToPublishDirectory="PreserveNewest" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
However CopyToPublishDirectory is running before BeforeTargets="BeforePublish" runs, so it fails to find the file.
I've tried many other variations as well, but nothing is working.
How can I download a file and copy it to the publish directory before the Publish step runs?

MSBuild fails to find directory with huge sub-directory

I want to create an MSBuild item that includes all files in a sub-directory, recursively, except the "node_modules" directory and a few others.
<MyItem Include="MyDir\**\*.*" Exclude="MyDir\node_modules\**\*.*;MyDir\tmp\**\*.*" />
In the build log I see this:
Input file "MyDir\**\*.*" does not exist.
When I delete the "node_modules" directory it works. I suspect MSBuild fails to process it because it contains over 30,000 files and a deep sub-directory structure. Is there a way I can work around this without listing all the sub-directories I want to include, which may change from time to time?
OK, I figured out a way using another target with CreateItems:
<ItemGroup>
<InputDirs Include="MyDir" />
<InputExcludedSubDirs Include="MyDir\tmp;MyDir\node_modules" />
</ItemGroup>
<Target Name="PrepareForMyTarget">
<ItemGroup>
<InputSubDirs Include="$([System.IO.Directory]::GetDirectories("%(InputDirs.Identity)"))" Exclude="#(InputExcludedSubDirs)" />
</ItemGroup>
<CreateItem Include="%(InputSubDirs.Identity)\**\*.*">
<Output TaskParameter="Include" ItemName="Inputs" />
</CreateItem>
</Target>
<Target Name="MyRealTarget" DependsOnTargets="PrepareForMyTarget">
... use Inputs item here as normal ...
</Target>

Msbuild - how to delete folder contents but not folder itself?

Right now in my msbuild script is a task to delete a folder
<RemoveDir Directories="$(Bin)"/>
However I'd rather delete the contents of the folder but leave the folder be (in case someone has the folder open in Windows Explorer). How can I do that?
This will remove all files and subfolders:
<Target Name="CleanFolder">
<PropertyGroup>
<TargetFolder>c:\clean</TargetFolder>
</PropertyGroup>
<ItemGroup>
<FilesToClean Include="$(TargetFolder)\**\*"/>
<Directories Include="$([System.IO.Directory]::GetDirectories('$(TargetFolder)', '*', System.IO.SearchOption.AllDirectories))"
Exclude="$(TargetFolder)"/>
</ItemGroup>
<Delete Files="#(FilesToClean)" ContinueOnError="true"/>
<RemoveDir Directories="#(Directories)" />
</Target>
It would also be good to drop open connections using the openfiles tool:
openfiles /disconnect /ID *
Download and install the msbuild extension pack then use
<MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent" Path="$(Bin)" />

How to delete all file and folders with msbuild

How can I delete all files and folders from a given path?
I tried this, but I'm unable to select the directories.
<Target Name="CleanSource" Condition="$(path)!=''">
<Message Text="path=$(path)"/>
<ItemGroup>
<fileToDelete Include="$(path)\**\*.*" />
<directoryToDelete Include="$(path)\**\" /><!these doest not select any directory at all-->
</ItemGroup>
<Message Text="file to delete:#(fileToDelete)"/>
<Message Text="directory to delete:#(directoryToDelete)"/>
<Delete Files="#(fileToDelete)" />
<Message Text="file effectively deleted:#(DeletedFiles)"/>
<RemoveDir Directories="#(directoryToDelete)" />
<Message Text="Directory effectively deleted:#(RemovedDirectories)"/>
</Target>
The RemoveDir task removes the specified directories and all of its files and subdirectories. You don't have to remove the files and subdirectories first. Just pass the directory name to RemoveDir.
<ItemGroup>
<DirsToClean Include="work" />
</ItemGroup>
<Target Name="CleanWork">
<RemoveDir Directories="#(DirsToClean)" />
</Target>
While there are ways to construct this using just MSBuild, I'd highly recommend the MSBuild Extension pack.
http://msbuildextensionpack.codeplex.com/ [has been moved]
GitHub: MSBuildExtensionPack
Using the pack, you get a RemoveContent task that does exactly what you are needing. Once you install, you'd just do something like:
<MSBuild.ExtensionPack.FileSystem.Folder
TaskAction="RemoveContent" Path="$(PathtoEmpty)"/>
I'm arriving to this conversation a little late, but I found the easiest way to accomplish this was to use the Exec task to execute the batch command given by lain in response to a similar question (with minor edits by yours truly):
<Exec Command="FOR /D %%p IN ("$(path)*.*") DO rmdir "%%p" /s /q" />
Finally I did use powershell wich is much more fast:
<exec>
<executable>powershell.exe</executable>
<buildArgs><![CDATA[-command "& {if( [System.IO.Directory]::Exists($pwd) ){dir $pwd | ri -recurse
-force}}"]]></buildArgs>
</exec>

How do I select all read-only files with msbuild?

I'm trying to write an MsBuild script to zip some files up. I need to select all of the read-only files recursively from a folder into an ItemGroup to add to the zip.
I'm using the community tasks Zip task, but am struggling with selecting files based on their attributes.
Is there anything around to do this out of the box, or do I need to write a custom task?
Thanks for you help.
You can use Property Functions (added to msbuild 4) to figure out if a file is read-only like so:
<ItemGroup>
<MyFiles Include="Testing\*.*" >
<ReadOnly Condition='1 == $([MSBuild]::BitwiseAnd(1, $([System.IO.File]::GetAttributes("%(Identity)"))))'>True</ReadOnly>
</MyFiles>
</ItemGroup>
<Target Name="Run" Outputs="%(MyFiles.Identity)">
<Message Text="%(MyFiles.Identity)" Condition="%(MyFiles.ReadOnly) != True"/>
<Message Text="%(MyFiles.Identity) ReadOnly" Condition="%(MyFiles.ReadOnly) == True" />
</Target>
Have you looked at the community build tasks site?
It has a zip task and an attribute change task - they should get you most of they way there.
This seems to do the job with a bit of dirty command line usage.
<Exec Command="dir .\RelPath\ToFolder\ToSearchIn /S /AR /B > readonlyfiles.temp.txt"/>
<ReadLinesFromFile File="readonlyfiles.temp.txt">
<Output TaskParameter="Lines" ItemName="ReadOnlyFiles"/>
</ReadLinesFromFile>
<Delete Files="readonlyfiles.temp.txt"/>
That gives absolute paths to the files.
To get relative paths, try something like this:
<Exec Command="dir .\RelPath\ToFolder\ToSearchIn /S /AR /B > readonlyfiles.temp.txt"/>
<FileUpdate Files="readonlyfiles.temp.txt"
Multiline="True"
Regex="^.*\\RelPath\\ToFolder\\ToSearchIn"
ReplacementText="RelPath\ToFolder\ToSearchIn"
/>
<ReadLinesFromFile File="readonlyfiles.temp.txt">
<Output TaskParameter="Lines" ItemName="ReadOnlyZipFiles"/>
</ReadLinesFromFile>
<Delete Files="readonlyfiles.temp.txt"/>