Click here to Skip to main content
15,887,822 members
Articles / Programming Languages / C#
Tip/Trick

DirTree: A Simplified Directory Tree Tool

Rate me:
Please Sign up or sign in to vote.
3.90/5 (6 votes)
29 Jun 2022CPOL 8.6K   158   11   13
Console app to scan a VS directory and create a simplified directory tree
This is a quick and dirty Console app written in VS2019 and .NET 4.8 to scan a VS directory and create a simplified directory tree.

Introduction

This quick and dirty Console app can be used to produce a simplified tree representation similar to the DOS Tree command, but with an option to limited the tree depth.

Directories starting with '.' are excluded, and also other directories like bin and obj.

Below is an example tree from the open-source Budoco VS Code solution (Bug Tracker in ASP.NET Core available on GitHub).

Image 1

Using the Code

Command line usage:

DirTree [path] [depth]

If no path is given, the current directory is used.
Depth must be >= 0, default value is 2.

So without further ado, here is the source code:

C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace DirTree
{
    /// <summary>
    /// Console app to scan a VS directory and create a simplified directory tree.
    /// Command line usage: 
    ///   DirTree [path] [depth]
    /// If no path is given, the current directory is used.
    /// Depth must be >= 0, default value is 2.
    /// </summary>
    class Program
    {
        static string filter = "*.*";
        static string startPath;
        static List<string> excludeList = new List<string>();
        static int folderLevel;
        static int treeDepth = 2;
        static int folderLevelStart;
        static string lastFolder;
        static bool lastDirFlag;
        static int tabSize = 3;
        static StringBuilder sbTree;        // Optionally write to DirTree.txt

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                startPath = args[0];

                if (args.Length > 1)
                {
                    treeDepth = int.Parse(args[1]);
                }
            }

            if (string.IsNullOrEmpty(startPath))
            {
                startPath = Environment.CurrentDirectory;
            }

            if (!startPath.EndsWith(@"\"))
            {
                startPath += @"\";
            }

            sbTree = new StringBuilder(50);
            sbTree.AppendLine(startPath);
            sbTree.AppendLine();

            // Add directories to exclude
            excludeList.Add("bin");
            excludeList.Add("obj");
            excludeList.Add("x64");
            excludeList.Add("x86");
            excludeList.Add("Properties");

            folderLevelStart = startPath.Count(x => x == '\\');
            var dirs = GetFoldersFiltered(startPath, filter);

            if (dirs.Length > 0)
            {
                lastFolder = dirs[dirs.Length - 1];
                ScanFolders(startPath);
                //File.WriteAllText("DirTree.txt", sbTree.ToString()); // Enable this 
                                                          // to write DirTree.txt
            }
            else
            {
                Console.WriteLine("No directories found.");
            }

            Console.ReadKey();
        }

        /// <summary>
        /// Get folders array, exclude folders with excludeList and 
        /// starting with '.' like .git .vs.
        /// </summary>
        /// <param name="path">The path</param>
        /// <param name="filter">Wildcard filter</param>
        /// <returns>A string array</returns>
        static string[] GetFoldersFiltered(string path, string filter)
        {
            var folders = Directory.GetDirectories(path, filter).ToList();

            for (int i = folders.Count - 1; i >= 0; i--)
            {
                string dirFullname = folders[i];
                string dirname = dirFullname.Replace(path, "");

                if (dirname.StartsWith(".") || excludeList.Contains(dirname))
                {
                    folders.RemoveAt(i);
                }
            }

            return folders.ToArray();
        }

        /// <summary>
        /// Recursively scan folders with a depth of <see cref="treeDepth"/>.
        /// </summary>
        /// <param name="path">The start path</param>
        /// <param name="continueLine">The folder level to continue vertical line
        /// </param>
        /// <param name="previous_indent">The previous indentation</param>
        static void ScanFolders(string path, int continueLine = -1, 
                                string previous_indent = "")
        {
            int i = 0;

            if (!path.EndsWith(@"\"))
            {
                path += @"\";
            }

            var dirs = GetFoldersFiltered(path, filter);
            int lastNode = dirs.Length - 1;

            while (i < dirs.Length)
            {
                string dirFullname = dirs[i];
                string dirname = dirFullname.Replace(path, "");
                string indent = "  ";
                string indent2 = string.Empty;

                if (dirFullname.StartsWith(lastFolder))
                {
                    lastDirFlag = true;
                }

                folderLevel = dirFullname.Count(x => x == '\\') - folderLevelStart;

                for (int j = 0; j < folderLevel; j++)
                {
                    if (lastDirFlag && continueLine >= j)
                    {
                        indent += " " + new string(' ', tabSize);
                    }
                    else
                    {
                        indent += "│" + new string(' ', tabSize);
                    }
                }

                if (previous_indent.Length > 0)
                {
                    indent = previous_indent + indent.Substring(previous_indent.Length);
                }

                if (i == lastNode)
                {
                    indent2 = "└" + new string('─', tabSize);
                    continueLine = folderLevel;
                }
                else
                {
                    indent2 = "├" + new string('─', tabSize);
                }

                Console.WriteLine($"{indent}{indent2}{dirname}");
                sbTree.AppendLine($"{indent}{indent2}{dirname}");

                if (folderLevel < treeDepth)
                {
                    if (i == lastNode)
                    {
                        indent += " " + new string(' ', tabSize);
                    }

                    // Recursive call
                    ScanFolders(dirFullname, continueLine, indent);
                }

                i++;
            }
        }
    }
}

History

  • 29th June, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Netherlands Netherlands
Software developer in the Netherlands, currently working on Video Surveillance applications.
Experience: C#, C++, VB, ASP, SQL Server, PostgreSQL, Gitea, TeamCity.
It all started with Black&White, no not the whiskey but the Sinclair ZX81 followed by several Atari's and PC's. The journey continues ...

Comments and Discussions

 
QuestionPowerShell Conversion Pin
Brooks Vaughn (US)2-Jul-22 7:36
Brooks Vaughn (US)2-Jul-22 7:36 
PraiseRe: PowerShell Conversion Pin
RickZeeland2-Jul-22 8:00
mveRickZeeland2-Jul-22 8:00 
GeneralRe: PowerShell Conversion Pin
Brooks Vaughn (US)3-Jul-22 4:52
Brooks Vaughn (US)3-Jul-22 4:52 
Hi Rick,

I had that same thought "Wouldn't be nice to have a C# to Powershell converter". 1/2 way through converting your code manually, I thought if I should try writing one. It was just a thought.

If you do come across a good C# to PowerShell language converter, please let me know.

To write a PS to C# converter would require having a better understanding of the C# language syntax. I'd look at using the Abstract Syntax Tree (AST) Classes (System.Management.Automation.Language) but just haven't had time. Was easier to just manually code the conversion.

I've been using PowerShell almost exclusively for the past decade. I have on occasion, had to convert PowerShell WPF GUI based applications to .Net WPF C#. I never really liked C++ or C#.

My IT experiences started in 1981 with IBM 370, UNIX, CP/M, Cromix, HP-3000, Primos, OS/2, Windows, and Linux for OS, and BAL Assembler, PL/1, Z-80 Assembly, C, x-Base, ADA, Relate-3000, and then moved to Delphi (Object PASCAL), Java, PL/SQL & T/SQL and used C-Shell, Borne Shell, TCL/TK, REXX, DOS Batch, perl, and PowerShell for scripting languages. My expertise was in technology development, systems design, development, and integration, reverse engineering, API development, ETL, Oracle, Directory Services, IdAM, Document Management, and automation.

I enjoyed C & Delphi but most organizations don't know the Power and Productivity gains using Delphi, I had to embrace Visual Studio & C#. When I moved into a operations support role, I embraced PowerShell for everything due to its similarity to TCL and its flexibility and have never looked backed. Now I do Azure Security and Policy management using PowerShell & Azure RM, Graph, Office365 PS Modules & APIs.

I still love using the PowerShell_ISE to edit code which is PS Version 5.x because of the Intellisense. I would use VS Code to develop Powershell 7.x code but usually try to avoid it.

The new Powershell is version 7.2.x which supports .Net Core and has other advanced features such as the ability to run in Windows, Linux, and Mac OS. When writing Linux / OS Code, you always have to remember when it comes to file & folder paths, it's CASE Sensitive and requires '/' (Windows PS supports both \ and /) as the Path Separator.

One suggestion from the comments about your DirTree was to use $FSO.Directory.EnumerateFileSystemInfos() which is faster than using GetDirectories() but requires using the additional options for case & scope such as:
PowerShell
$startingPath = [System.IO.DirectoryInfo]'c:\'
$EnumerationOptions = [System.IO.EnumerationOptions]::new()
$EnumerationOptions.RecurseSubdirectories = $true
$EnumerationOptions.MatchCasing = [System.IO.MatchCasing]::CaseInsensitive
$searchFilter = $Filter
$Directories = $startingPath.EnumerateDirectories($searchFilter, $EnumerationOptions)


modified 4-Jul-22 16:30pm.

GeneralRe: PowerShell Conversion Pin
Andreas Saurwein4-Jul-22 1:07
Andreas Saurwein4-Jul-22 1:07 
QuestionDeferred Execution Pin
George Swan30-Jun-22 9:29
mveGeorge Swan30-Jun-22 9:29 
AnswerRe: Deferred Execution Pin
RickZeeland30-Jun-22 9:36
mveRickZeeland30-Jun-22 9:36 
GeneralRe: Deferred Execution Pin
George Swan30-Jun-22 9:46
mveGeorge Swan30-Jun-22 9:46 
GeneralRe: Deferred Execution Pin
RickZeeland30-Jun-22 21:00
mveRickZeeland30-Jun-22 21:00 
QuestionOutput Example? Pin
DSchopp29-Jun-22 6:56
professionalDSchopp29-Jun-22 6:56 
PraiseRe: Output Example? Pin
RickZeeland29-Jun-22 7:29
mveRickZeeland29-Jun-22 7:29 
QuestionDownload Pin
George Swan29-Jun-22 3:06
mveGeorge Swan29-Jun-22 3:06 
AnswerRe: Download Pin
RickZeeland29-Jun-22 3:29
mveRickZeeland29-Jun-22 3:29 
GeneralRe: Download Pin
George Swan29-Jun-22 6:10
mveGeorge Swan29-Jun-22 6:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.