1

I am looking for a Windows/Powershell command that, for each folder, lists in one line the size, number of files, and number of folders (similar to what one gets when right clicking a folder in explorer and inquiring about Properties). It will work on the subfolders in tree up to a given depth. For instance, if one wants to work with C:\root, up to depth 2, the output would be like

C:\root\dir1  32.6MB  153 files  26 folders
.C:\root\dir1\tan  12.0MB  37 files  12 folders
.C:\root\dir1\win  3.5MB  15 files  2 folders
C:\root\dir2  134.5KB  13 files  0 folders
...

This is similar to Unix du. The output would be similar to the output of tree, but adding the requested totals for each item.

How can that be accomplished?

I found that Get Folder Size from Windows Command Line gets the total size, but does not recurse up to a selected depth. Moreover, the solutions in native cmd are not reliable, and powershell appears to be needed.
Note: DU from the Sysinternals Suite is reliable. One could write a function that uses its output and recurses the subtree, but it will inquire each branch per depth level requested, so it might be very inefficient. For a first shot, I would try it. I am currently working on it, and elaborations on this are welcome. Efficient solutions would be most desirable.

Set Recursive-Depth for dir command in dos recurses folders, but only shows the folder names. And it is in native Windows, so there would be no chance of reliably extracting the total size (see note above).

Community
  • 1
  • 1
  • 1
    The properties of a folder shows the recursive total number of files and folders within the folder, along with the total size. I'm not sure what you expect to report for any given folder when you limit the depth of recursion. Should the count/slze still recursively include all descendants, or should it exclude files/folders that lay beyond the recursion depth? – dbenham May 03 '15 at 15:20
  • @dbenham - It seems you misunderstood the question. I want to get the totals (size, # files, # folders) for each folder, without limiting the recursion depth for obtaining the totals. The recursion limit is for the folders to be listed. See the link provided http://stackoverflow.com/questions/12479250/set-recursive-depth-for-dir-command-in-dos/30012985 and an edited question. – sancho.s ReinstateMonicaCellio May 04 '15 at 00:29
  • I'm very familiar with that link - I wrote the accepted answer. And I did understand the question, except I needed a bit of clarification as to your requirements - and your response provided the requested clarification. – dbenham May 04 '15 at 02:53
  • @dbenham - Please see answer edited again. Hopefully it clarifies your points. – sancho.s ReinstateMonicaCellio May 04 '15 at 09:02

2 Answers2

1

I haven't learned powershell, but I did solve the problem with JScript.

It would be really simple if all you needed was the total folder size (including size of all subfolders, recursive), because the FileSystemObject folder.size property gives that value directly. But there are no properties to get the corresponding file and folder count, so you must write your own recursive code to get the counts.

folderSummary.js

var fso = new ActiveXObject("Scripting.FileSystemObject");
var maxDepth = parseInt(WScript.Arguments.Item(1));
var files;

procFolder( fso.GetFolder( WScript.Arguments.Item(0) ), 0, new Summary() );

function procFolder( folder, depth, parent ) {
  var summary = new Summary();
  for (files = new Enumerator(folder.Files); !files.atEnd(); files.moveNext()) {
    summary.fileCnt++;
    summary.size += files.item().size;
  }
  for (var folders = new Enumerator(folder.SubFolders); !folders.atEnd(); folders.moveNext()) {
    summary.folderCnt++;
    procFolder( folders.item(), depth+1, summary );
  }  
  if (depth<=maxDepth) WScript.Echo( lpad( summary.folderCnt, '         ' ) + ' folders ' +
                                     lpad( summary.fileCnt,   '         ' ) + ' files '   +
                                     lpad( summary.size, '               ') + ' bytes   ' +
                                     folder.Path
                                   );
  parent.size      += summary.size;
  parent.fileCnt   += summary.fileCnt;
  parent.folderCnt += summary.folderCnt;
}                 

function Summary() {
  this.size = 0;
  this.fileCnt = 0;
  this.folderCnt = 0;
}

function lpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

Usage - It expects the root folder as the first argument, and the display recursion depth as the second argument. Here is an example for the current directory, 3 levels deep:

cscript //nologo folderSummary.js . 3

It is simple to package the JScript into a hybrid batch script to make it easier to call from the command line. The script below defaults the root to the current folder, and the depth to 0.

folderSummary.bat

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
:: folderSummary.bat  [rootPath]  [recursionDepth]
::
::   default rootPath is current folder
::   default recursionDepth is 0
::

::************* Batch portion **********

@echo off
setlocal disableDelayedExpansion
set "root=%~1"
if "%root%" equ "" set "root=."
set "depth=%~2"
if "%depth%" equ "" set "depth=0"
cscript //nologo //E:JScript "%~f0" "%root%" "%depth%"
exit /b


************* JScript portion **********/

var fso = new ActiveXObject("Scripting.FileSystemObject");
var maxDepth = parseInt(WScript.Arguments.Item(1));
var files;

procFolder( fso.GetFolder( WScript.Arguments.Item(0) ), 0, new Summary() );

function procFolder( folder, depth, parent ) {
  var summary = new Summary();
  for (files = new Enumerator(folder.Files); !files.atEnd(); files.moveNext()) {
    summary.fileCnt++;
    summary.size += files.item().size;
  }
  for (var folders = new Enumerator(folder.SubFolders); !folders.atEnd(); folders.moveNext()) {
    summary.folderCnt++;
    procFolder( folders.item(), depth+1, summary );
  }  
  if (depth<=maxDepth) WScript.Echo( lpad( summary.folderCnt, '         ' ) + ' folders ' +
                                     lpad( summary.fileCnt,   '         ' ) + ' files '   +
                                     lpad( summary.size, '               ') + ' bytes   ' +
                                     folder.Path
                                   );
  parent.size      += summary.size;
  parent.fileCnt   += summary.fileCnt;
  parent.folderCnt += summary.folderCnt;
}                 

function Summary() {
  this.size = 0;
  this.fileCnt = 0;
  this.folderCnt = 0;
}

function lpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

Sample usage:

rem root = Current folder, depth = 0
folderSummary

rem root = c:\test, depth = 3
folderSummary c:\test 3



The above can be fairly slow because they recursively iterate every file and folder within the root path. Here is a much faster version that only iterates the folders - the total size is gotten from folder.Size, and file/folder collection item counts are gotten from the Count property. But, this fast version fails if it runs across a file/folder to which you do not have access.

fastFolderSummary.bat

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
:: fastFolderSummary.bat  [rootPath]  [recursionDepth]
::
::   default rootPath is current folder
::   default recursionDepth is 0
::

::************* Batch portion **********

@echo off
setlocal disableDelayedExpansion
set "root=%~1"
if "%root%" equ "" set "root=."
set "depth=%~2"
if "%depth%" equ "" set "depth=0"
cscript //nologo //E:JScript "%~f0" "%root%" "%depth%"
exit /b


************* JScript portion **********/

var fso = new ActiveXObject("Scripting.FileSystemObject");
var maxDepth = parseInt(WScript.Arguments.Item(1));

procFolder( fso.GetFolder( WScript.Arguments.Item(0) ), 0, new Summary() );

function procFolder( folder, depth, parent ) {
  var summary = new Summary();
  summary.fileCnt += folder.Files.Count;
  summary.folderCnt += folder.SubFolders.Count;
  for (var folders = new Enumerator(folder.SubFolders); !folders.atEnd(); folders.moveNext()) {
    procFolder( folders.item(), depth+1, summary );
  }  
  if (depth<=maxDepth) WScript.Echo( lpad( summary.folderCnt, '         ' ) + ' folders ' +
                                     lpad( summary.fileCnt,   '         ' ) + ' files '   +
                                     lpad( folder.size, '               ' ) + ' bytes   ' +
                                     folder.Path
                                   );
  parent.fileCnt   += summary.fileCnt;
  parent.folderCnt += summary.folderCnt;
}                 

function Summary() {
  this.fileCnt = 0;
  this.folderCnt = 0;
}

function lpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}
dbenham
  • 127,446
  • 28
  • 251
  • 390
0

You could try something like this? It isn't very efficient, and I only calculated the size, but it's fairly easy to extend.

function Measure-DirectorySize([string] $directory, [int] $depth)
{
    if ($depth -lt 0) { return 0; }
    $files = get-childitem $directory -file -force -ea silentlycontinue | 
                measure-object -Property Length -Sum;
    $dirs = get-childitem $directory -Directory -ea silentlycontinue -force |
                % { Measure-DirectorySize ($_.FullName) ($depth - 1) } |
                measure-object -sum;
    return $files.Sum + $dirs.Sum;
}
Rob
  • 4,327
  • 6
  • 29
  • 55