Recursive does not get Empty Directories

When using the following does not return empty directories.

<ItemGroup>

<DirectorySet Include="$(ThisDir)\**"/>

</ItemGroup>

My question is why does a call like this not create a collection set off all objects Is there a way to have the collection return the empty directories as well

I am trying use collection sets that need to have all the specified files and directories included in them for proper processing whether it be an empty directory or not. The strange thing I noticed is if I do a call something like:

<DirectorySet Include="$(ThisDir)"/>

It will return that directory alone as a collection item.

Please help, I am on a deadline for a project and this is going to set me back quite a bit.

Thanks in advance!!!




Answer this question

Recursive does not get Empty Directories

  • Mindking

    Here is a task that I wrote that will return either files or directories (empty or not) under a given path:

    namespace Sedodream.MSBuild.Tasks

    {

    /// <summary>

    /// This task will locate all the directories under a given path.

    /// </summary>

    public class FindUnder : Task

    {

    #region Fields

    public ITaskItem path;

    public ITaskItem [] directories;

    public bool findFiles;

    public bool findDirectories;

    #endregion

    public FindUnder()

    {

    this.findDirectories = false;

    this.findFiles = false;

    }

    #region Properties

    /// <summary>

    /// This is the directory to search under for sub directories.

    /// This directory will not be included in the <c>Directories</c> item.

    /// </summary>

    [Required]

    public ITaskItem Path

    {

    get { return this.path; }

    set { this.path = value; }

    }

    public bool FindFiles

    {

    get { return this.findFiles; }

    set { this.findFiles = value; }

    }

    public bool FindDirectories

    {

    get { return this.findDirectories; }

    set { this.findDirectories = value; }

    }

    /// <summary>

    /// An <c>ITaskItem</c> containing all of the sub directories under <c>Path</c>.

    /// </summary>

    [Output]

    public ITaskItem[] FoundItems

    {

    get { return this.directories; }

    set { this.directories = value; }

    }

    #endregion

    public override bool Execute()

    {

    string fullPath = this.Path.GetMetadata("Fullpath");

    if (string.IsNullOrEmpty(fullPath) || !Directory.Exists(fullPath))

    {

    string message = string.Format("Path specified {0} doesn't exist", fullPath);

    throw new Exception(message);

    }

    if (!findFiles && !findDirectories)

    {

    string message = "Either FindFiles or FindDirectories must be true";

    throw new Exception(message);

    }

    DirectoryInfo dir = new DirectoryInfo(fullPath);

    FileInfo [] files = new FileInfo[0];

    DirectoryInfo [] subDirs = new DirectoryInfo[0];

    if( findFiles)

    {

    files = dir.GetFiles("*",SearchOption.AllDirectories);

    }

    if( findDirectories )

    {

    subDirs = dir.GetDirectories("*",SearchOption.AllDirectories);

    }

    List<ITaskItem> items = new List<ITaskItem>();

    foreach(FileInfo fInfo in files)

    {

    items.Add(new TaskItem(fInfo.FullName));

    }

    foreach(DirectoryInfo dInfo in subDirs)

    {

    items.Add(new TaskItem(dInfo.FullName));

    }

    this.directories = items.ToArray();

    return true;

    }

    }

    }

    Here is a sample of its use:

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Find">

    <UsingTask AssemblyFile="C:\MSBuild\Tasks\Sedodream.MSBuild.Tasks.dll" TaskName="FindUnder"/>

    <PropertyGroup>

    <SearchPath>.</SearchPath>

    </PropertyGroup>

    <ItemGroup>

    <Path Include="$(SearchPath)"/>

    </ItemGroup>

    <Target Name="Find">

    <FindUnder Path="@(Path)" FindFiles="true" FindDirectories="true">

    <Output ItemName="Items" TaskParameter="FoundItems"/>

    </FindUnder>

    <Message Text="Found items: @(Items,'%0d%0a')" Importance="high"/>

    </Target>

    </Project>

    Sayed Ibrahim Hashimi
    www.sedodream.com


  • Oscar Marquez

    Hi Brian,

    I gave the code you listed above a spin and indeed, when you use Include="$(ThisDir)" it does list the folder even though it is empty, and as soon as you start using wildcards, only files are listed (no folders at all are listed in the item, not even non-empty ones, just files).

    There is a way around this, but it is a bit of a hack. Use the 'Exec' task to execute the following command:

    dir c:\temp2 /s /b &gt; c:\temp2.txt

    - c:\temp2 is the root folder from where you want to start
    - /s means recursive, /b means bae format (no headers)
    - &gt; is the xml notation for > to send the output of the dir command to a file

    The reason I write this to a file is because at present there is no other way of getting the output of an Exec task back into msbuild.

    Next, you use the ReadLinesFromFile task to read the contents of the file into an item like this:

    <ReadLinesFromFile File="c:\temp2.txt">
    <Output TaskParameter="Lines" ItemName="FolderNames" />
    </ReadLinesFromFile>

    My whole target looks like this:

    <Target Name="Build">
    <Exec Command="dir c:\temp2 /s /b &gt; c:\temp2.txt" />
    <ReadLinesFromFile File="c:\temp2.txt">
    <Output TaskParameter="Lines" ItemName="LinesReadFromFile" />
    </ReadLinesFromFile>
    <Message Text="@(LinesReadFromFile)" Importance="low" />
    </Target>

    @(LinesReadFromFile) contains all the files and folders (empty or not) under the root folder you specify.

    This is the only way of doing this I can think of right now.

    cheers,

    Stephane



  • AquaLunger

    Wow, thanks for the great responses. The solution I was looking for had to allow for Excludes as well. Plus I wanted to be able to attach ItemMetadata to directories if it is necessary. All of what I am posting below relies on the sequence of how the task is called. Also, you can't mind have dummy files written to all your directories. Another thing is you need to use CreateItem to generate your ItemGroups. Again, not the greatest solution but allows for some advance directory manipulation and inclusion. I look forward to any comments or suggestions. Thanks again for all the work!!! I hope that all of our postings help someone out that was in my situation.
      /// <summary>
      /// Exposes methods to add directories to <see cref="ITaskItem"/> collection.
      /// </summary>
      public class AddDirectories: Task
      {
        private string directory;
        private bool delete = false;
        private ITaskItem[] itemGroup;
        private bool returnOnlyDirectories = false;
        private string placeholder;
        /// <summary>
        /// Deletes placeholder files in the given directory.
        /// </summary>
        public bool DeletePlaceholders
        {
          get { return delete; }
          set { delete = value; }
        }
     
        /// <summary>
        /// Gets or sets a value of the directory.
        /// </summary>
        public string Directory
        {
          get { return this.directory; }
          set { this.directory = value; }
        }
     
        /// <summary>
        /// The item group that will be cleaned and returned.
        /// </summary>
        [Output]
        public ITaskItem[] ItemGroup
        {
          get { return this.itemGroup; }
          set { this.itemGroup = value; }
        }
     
        /// <summary>
        /// The name of the placeholder to create, delete and search for.
        /// </summary>
        /// <remarks>The default placeholder is "file.placeholder".</remarks>
        public string Placeholder
        {
          get { return this.placeholder; }
          set { this.placeholder = value; }
        }
     
        /// <summary>
        /// Gets or sets a value to determine if the <c>ItemGroup</c> will have only directories listed.
        /// </summary>
        public bool ReturnOnlyDirectories
        {
          get { return this.returnOnlyDirectories; }
          set { this.returnOnlyDirectories = value; }
        }
     
        /// <summary>
        /// Executes the task.
        /// </summary>
        /// <returns>Returns true if the task completed sucessfully.</returns>
        public override bool Execute ( )
        {
          if ( this.placeholder == null )
            this.placeholder = "file.placeholder";
     
          if ( this.Directory != null )
          {
            if ( !System.IO.Directory.Exists ( this.Directory ) )
              throw new DirectoryNotFoundException ( string.Format ( 
              "The directory must exist to be processed. {0} was not found.", this.Directory ) );
     
            DirectoryInfo dir = new DirectoryInfo ( this.Directory );
     
            if ( !string.IsNullOrEmpty ( this.Directory ) && !this.DeletePlaceholders )
              this.WalkDirectory ( dir );
     
            if ( this.DeletePlaceholders )
              this.WalkDirectoryDeleting ( new DirectoryInfo ( this.Directory ) );
          }
     
          if ( this.ItemGroup != null )
          {
            List<ITaskItem> allItems = new List<ITaskItem> ( );
            this.AppendDirectories ( allItems );
            this.ItemGroup = allItems.ToArray ( );
          }
     
          return !Log.HasLoggedErrors;
        }
     
        private void WalkDirectory ( DirectoryInfo dir )
        {
          if ( !File.Exists ( dir.FullName + @"\" + this.placeholder ) )
            File.CreateText ( dir.FullName + @"\" + this.placeholder );
     
          foreach ( DirectoryInfo subDir in dir.GetDirectories ( ) )
            this.WalkDirectory ( subDir );
        }
     
        private void WalkDirectoryDeleting ( DirectoryInfo dir )
        {
          try
          {
            if ( dir.GetFiles ( this.placeholder ).Length > 0 )
              File.Delete ( string.Format (@"{0}\{1}", dir.FullName, this.Placeholder ) );
          }
          catch ( System.IO.IOException )
          {
            Log.LogError ( 
             "The placeholder ({0}) in the {1} directory has not been released yet.", this.Placeholder, dir.FullName );
            Log.LogError ( "Placeholders might be getting created and deleted frequently for this directory." );
          }
     
          foreach ( DirectoryInfo subDir in dir.GetDirectories ( ) )
            this.WalkDirectoryDeleting ( subDir );
        }
     
        private void AppendDirectories ( List<ITaskItem> allItems)
        {
          foreach ( ITaskItem item in this.ItemGroup )
          {
            bool isPlaceholder = item.GetMetadata ( "Filename" ) + item.GetMetadata ( "Extension" ) == this.placeholder;
            string dirName = this.GetIdentityDirectory ( item );
     
            if ( isPlaceholder )
            {
              TaskItem dir = new TaskItem ( dirName );
              
              // If the directory should have additional metadata it will be set on the placeholder item.
              if ( isPlaceholder )
                item.CopyMetadataTo ( dir );
     
              allItems.Add ( dir );
            }
     
            if ( !isPlaceholder && !this.returnOnlyDirectories )
              allItems.Add ( item );
          }
        }
     
        private string GetIdentityDirectory ( ITaskItem item )
        {
          string id = "Identity";
     
          if ( item.GetMetadata ( id ).LastIndexOf ( '\\' ) != -1 )
          {
            return item.GetMetadata ( id ).Substring ( 0, item.GetMetadata ( id ).LastIndexOf ( '\\' ) );
          }
     
          return ".";
        }
      }
    
    The follow is an example as to how the class can be used.
    <!--Placeholder for creating directories.-->
    <PropertyGroup>
      <PlaceholderExt>placeholder</PlaceholderExt>
      <Placeholder>folder.$(PlaceholderExt)</Placeholder>
    </PropertyGroup>
    <Target Name="Testing">
      <AddDirectories Directory="$(Prog)\SomeFolder\" Placeholder="$(Placeholder)"/>
      <CreateItem Include="$(Prog)\SomeFolder\**"
       Exclude="$(Prog)\SomeFolder\Products\notme\static;$(Prog)\SomeFolder\Forget\about\it.htm;$(Prog)\SomeFolder\**\*.$(PlaceholderExt)"
       AdditionalMetadata="FileSystemInfo=FileInfo">
       <Output TaskParameter="Include" ItemName="FullSearch"/>
      </CreateItem>
      <CreateItem Include="$(Prog)\SomeFolder\**\*.$(PlaceholderExt)"
       Exclude="@(FullSearch);$(Prog)\SomeFolder\Products\notme\static;$(Prog)\SomeFolder\Forget\about\it.htm;"
       AdditionalMetadata="FileSystemInfo=DirectoryInfo">
       <Output TaskParameter="Include" ItemName="AllFolders"/>
      </CreateItem>
      <CreateItem Include="@(FullSearch);@(AllFolders)">
       <Output TaskParameter="Include" ItemName="Everything"/>
      </CreateItem>
      <AddDirectories
       Directory="$(Prog)\SomeFolder\"
       DeletePlaceholders="true"
       ItemGroup="@(Everything)"
       Placeholder="$(placeholder)">
       <Output TaskParameter="ItemGroup" ItemName="NoPlaceholders"/>
      </AddDirectories>
      <Message Text="Found items: @(NoPlaceholders,' - %(FileSystemInfo)%0d%0a')" Importance="high"/>
    </Target>
    


  • Vojtech

    We talked about this in the team too, and unfortunately Stephane's approach is the only one you can do right now. It's kinda ugly, but it will work.

    Neil



  • Recursive does not get Empty Directories