Get size of each folder inside a function

This topic contains 13 replies, has 3 voices, and was last updated by Profile photo of Curtis Smith Curtis Smith 3 weeks, 6 days ago.

  • Author
    Posts
  • #73898
    Profile photo of russell karr
    russell karr
    Participant

    I am trying to obtain the size of each folder and its sub-folders along with the owner, path, and last modified date – also up to a depth of 5. I have everything except for the size of the folder completed; I am trying to get the size in MB. I am using PowerShell 4.

    The structure I am trying to obtain:
    
     Date Modified Owner FullName                                            Size                                                                                                     
     ------------- ----- --------                                            ------                                                                
    06/26/2017          /Users/demo/main/1slevel                             12.0MB
    06/26/2017          /Users/demo/main/1slevel/2nlvel                       8.0MB
    06/26/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel               5.0MB
    06/26/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel/4thlev
    06/26/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel/4thlev/5thlevl
    06/26/2017          /Users/demo/main/uns
    06/26/2017          /Users/demo/main/uns/swan
    06/26/2017          /Users/demo/main/uns/swan/drins
    06/26/2017          /Users/demo/main/uns/swan/drins/furth
    06/26/2017          /Users/demo/main/uns/swan/drins/furth/firf
    

    The Code I have:

    Function Get-Depth {
       Param(
            [String]$Path = '/Users/demo/main',
            [String]$Filter = "*",
            [Int]$ToDepth = 4,
            [Int]$CurrentDepth = 0
        )
        #incrimintation
        $CurrentDepth++
    
      #obtains the path and passes the filter values. KEEP in mind that level 1 is 0.
         Get-ChildItem $Path | %{
         $_ | ?{ $_.Name -Like $Filter }
         #if thier is a folder, use the depth and run function until to depth value is 4
         If ($_.PsIsContainer) {
         If ($CurrentDepth -le $ToDepth) {
    
           # Call to function
           #adds the filter values and depth to the path..
           Get-Depth -Path $_.FullName -Filter $Filter `
            -ToDepth $ToDepth -CurrentDepth $CurrentDepth
               }
             }
          }
    
      }
    
    
    #just calling the function and and adding what we want!
    
    Get-Depth|? {$_.PsIsContainer}| select @{Name='Date Modified'; 
    Expression={$_.LastWriteTime.ToString('MM/dd/yyyy')}},
    @{Name='Owner'; E={(($_.GetAccessControl().Owner.Split('\'))[1])}}, Fullname 
    

    Thank you!

  • #73900
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Folders don't have a size; you'd need to get all the files within and add up their Length properties. Measure-Object can help with that.

  • #73904
    Profile photo of Don Jones
    Don Jones
    Keymaster

    What that's doing is getting all the _files_ and adding up their Length property. Directories don't have a Length property, because they don't have a size in and of themselves.

    • #73939
      Profile photo of russell karr
      russell karr
      Participant

      @Dan Jones Thank you for input, after doing some research, I found this script from the script guys (https://gallery.technet.microsoft.com/scriptcenter/Outputs-directory-size-964d07ff)
      After doing some sample runs, it appears to do exactly what you say before (Adds lengths of all files), however; this script only lists out the first level directory folders and does not list out the size of its subfolders with fullpath, etc – is there anyway to expand this scripts functionality to list out more than the first level folders? (essentially trying to get the structure originally posted in the question)
      Thank you!

      # Get-DirStats.ps1
      # Written by Bill Stewart (bstewart@iname.com)
      # Outputs file system directory statistics.
      
      #requires -version 2
      
      
      
      [CmdletBinding(DefaultParameterSetName="Path")]
      param(
        [parameter(Position=0,Mandatory=$false,ParameterSetName="Path",ValueFromPipeline=$true)]
          $Path='/Users/demo/main',
        [parameter(Position=0,Mandatory=$true,ParameterSetName="LiteralPath")]
          [String[]] $LiteralPath,
          [Switch] $Only,
          [Switch] $Every,
          [Switch] $FormatNumbers,
          [Switch] $Total
      )
      
      begin {
        $ParamSetName = $PSCmdlet.ParameterSetName
        if ( $ParamSetName -eq "Path" ) {
          $PipelineInput = ( -not $PSBoundParameters.ContainsKey("Path") ) -and ( -not $Path )
        }
        elseif ( $ParamSetName -eq "LiteralPath" ) {
          $PipelineInput = $false
        }
      
        # Script-level variables used with -Total.
        [UInt64] $script:totalcount = 0
        [UInt64] $script:totalbytes = 0
      
        # Returns a [System.IO.DirectoryInfo] object if it exists.
        function Get-Directory {
          param( $item )
      
          if ( $ParamSetName -eq "Path" ) {
            if ( Test-Path -Path $item -PathType Container ) {
              $item = Get-Item -Path $item -Force
            }
          }
          elseif ( $ParamSetName -eq "LiteralPath" ) {
            if ( Test-Path -LiteralPath $item -PathType Container ) {
              $item = Get-Item -LiteralPath $item -Force
            }
          }
          if ( $item -and ($item -is [System.IO.DirectoryInfo]) ) {
            return $item
          }
        }
      
        # Filter that outputs the custom object with formatted numbers.
        function Format-Output {
          process {
            $_ | Select-Object Path,
              @{Name="Files"; Expression={"{0:N0}" -f $_.Files}},
              @{Name="Size"; Expression={"{0:N0}" -f $_.Size}}
          }
        }
      
        # Outputs directory statistics for the specified directory. With -recurse,
        # the function includes files in all subdirectories of the specified
        # directory. With -format, numbers in the output objects are formatted with
        # the Format-Output filter.
        function Get-DirectoryStats {
          param( $directory, $recurse, $format )
      
          Write-Progress -Activity "Get-DirStats.ps1" -Status "Reading '$($directory.FullName)'"
          $files = $directory | Get-ChildItem -Force -Recurse:$recurse | Where-Object { -not $_.PSIsContainer }
          if ( $files ) {
            Write-Progress -Activity "Get-DirStats.ps1" -Status "Calculating '$($directory.FullName)'"
            $output = $files | Measure-Object -Sum -Property Length | Select-Object `
              @{Name="Path"; Expression={$directory.FullName}},
              @{Name="Files"; Expression={$_.Count; $script:totalcount += $_.Count}},
              @{Name="Size"; Expression={$_.Sum; $script:totalbytes += $_.Sum}}
          }
          else {
            $output = "" | Select-Object `
              @{Name="Path"; Expression={$directory.FullName}},
              @{Name="Files"; Expression={0}},
              @{Name="Size"; Expression={0}}
          }
          if ( -not $format ) { $output } else { $output | Format-Output }
        }
      }
      
      process {
        # Get the item to process, no matter whether the input comes from the
        # pipeline or not.
        if ( $PipelineInput ) {
          $item = $_
        }
        else {
          if ( $ParamSetName -eq "Path" ) {
            $item = $Path
          }
          elseif ( $ParamSetName -eq "LiteralPath" ) {
            $item = $LiteralPath
          }
        }
      
        # Write an error if the item is not a directory in the file system.
        $directory = Get-Directory -item $item
        if ( -not $directory ) {
          Write-Error -Message "Path '$item' is not a directory in the file system." -Category InvalidType
          return
        }
      
        # Get the statistics for the first-level directory.
        Get-DirectoryStats -directory $directory -recurse:$false -format:$FormatNumbers
        # -Only means no further processing past the first-level directory.
        if ( $Only ) { return }
      
        # Get the subdirectories of the first-level directory and get the statistics
        # for each of them.
        $directory | Get-ChildItem -Force -Recurse:$Every |
          Where-Object { $_.PSIsContainer } | ForEach-Object {
            Get-DirectoryStats -directory $_ -recurse:(-not $Every) -format:$FormatNumbers
          }
      }
      
      end {
        # If -Total specified, output summary object.
        if ( $Total ) {
          $output = "" | Select-Object `
            @{Name="Path"; Expression={""}},
            @{Name="Files"; Expression={$script:totalcount}},
            @{Name="Size"; Expression={$script:totalbytes}}
          if ( -not $FormatNumbers ) { $output } else { $output | Format-Output }
        }
      }
      
      
       
  • #73942
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Your process is:

    1. use Get-ChildItem and -recurse and -directory to get a list of JUST THE DIRECTORIES.

    2. ForEach directory, Get-ChildItem all the files and add up their Length, usually using Measure-Object.

    3. Construct an output object containing the directory full name and the total length for that directory.

    You don't need anything nearly as complex as what Bill wrote.

    Get-ChildItem -Path /start/here -Recurse -Directory |
    ForEach-Object {
     $size = Get-ChildItem -Path $_.FullName | Measure -Prop Length -Sum | Select -Expand Sum
     New-Object -Type PSObject -Prop @{'Path'=$_.FullName;'Size'=$size}
    }
    

    Something vaguely like that, I think. That'll give you bytes; you'll have to do whatever math you want if you want MB.

  • #73960
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    What Don provided will work for getting the total sum of all files in each directory at each level, but it will not the the size of the files and all sub directories.

    If it were me, I would:
    1) Do a recursive search for all files from my root directory
    2) Group those by directory
    3) For each grouping get the directory name and the sum of all lengths (files in those directories)
    4) Store results in a variable
    *At this point you will have the same as what Don provided
    4) For each directory in the variable, Get all other directories in the variable who's name begins with the same directory name
    5) Output the directory name being searched for and a sum of the length of the results
    *At this point every parent directory will contain the size of all file in it and in all sub directories

    Again you don't need anything near as complex as what Bill wrote.
    (Hint: I was able to do the above in 15 lines of code, or 2 one liners if I didn't care about readability :^)

    Directory                                                     Size Size (KB) Size (MB) Size (GB)
    ---------                                                     ---- --------- --------- ---------
    C:\Temp                                                  352320814 344063.29       336      0.33
    C:\Temp\CheckLCStatus                                    140497837 137204.92    133.99      0.13
    C:\Temp\CheckLCStatus\prerequisites\.NET Framework 4.5.2  69999448  68358.84     66.76      0.07
    C:\Temp\CheckLCStatus\prerequisites\VS2013 vcredist x86    6503984   6351.55       6.2      0.01
    C:\Temp\Old                                                9191936    8976.5      8.77      0.01
    C:\Temp\SDelete                                             324234    316.63      0.31         0
    C:\Temp\VF                                                       0         0         0         0

    Give it a shot and see what you can come up with.

    • #73984
      Profile photo of russell karr
      russell karr
      Participant

      @Curtis Smith , @Don Jones

      Thank you for your input.
      I don't believe I need that much detail just yet, I am still trying to get the size to work with my function.
      Can you take a look at what I have:

       Function Get-Depth {
          Param(
              [String]$Path = 'H:\Demo',
              [String]$Filter = "*",
              [Int]$ToDepth = 5,
              [Int]$CurrentDepth = 0
          )
          #incrimintation
          $CurrentDepth++
      
      #obtains the path and passes the filter values. KEEP in mind that level 1 is 0.
          Get-ChildItem $Path | %{
              $_ | ?{ $_.Name -Like $Filter }
      #if thier is a folder, use the depth and run function until to depth value is 4
              If ($_.PsIsContainer) {
                  If ($CurrentDepth -le $ToDepth) {
      
                      # Call to function
                      #adds the filter values and depth to the path..
                      Get-Depth -Path $_.FullName -Filter $Filter `
                        -ToDepth $ToDepth -CurrentDepth $CurrentDepth
                  }
      
           (Get-ChildItem -Path 'H:\Demo' -Directory -Recurse| ForEach-Object {
           $size= (Get-ChildItem $_.FullName -File -Recurse| Measure-Object -property length -sum).Sum
           if ($size) {[Math]::Round($size / 1MB,2,[MidPointRounding]::AwayFromZero)} else {0}
      })
      
              }
          }
      }
      
      
      #just calling the function and and adding what we want!
      Get-Depth|? {$_.PsIsContainer}| select @{Name='Date Modified'; Expression={$_.LastWriteTime.ToString('MM/dd/yyyy')}}, @{Name='Owner'; E={(($_.GetAccessControl().Owner.Split('\'))[1])}}, @{Name='File Path'; Expression={$_.FullName -replace '.*?(\\Demo*)','$1'}},size 
      

      Thank you!

  • #73999
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    I assume you mean this section of your code

        (Get-ChildItem -Path 'H:\Demo' -Directory -Recurse| ForEach-Object {
         $size= (Get-ChildItem $_.FullName -File -Recurse| Measure-Object -property length -sum).Sum
         if ($size) {[Math]::Round($size / 1MB,2,[MidPointRounding]::AwayFromZero)} else {0}
    })

    Besides being inefficient because it has to query the same files for their size over and over again, there is nothing wrong here. It works.

    I added $_.FullName to the output just so I could see what directory it was on and compare to my version and the numbers are correct.

    (Get-ChildItem -Path 'C:\Temp' -Directory -Recurse| ForEach-Object {
        $_.FullName
         $size= (Get-ChildItem $_.FullName -File -Recurse| Measure-Object -property length -sum).Sum
         if ($size) {[Math]::Round($size / 1MB,2,[MidPointRounding]::AwayFromZero)} else {0}
    })

    Results

    C:\Temp\1
    0
    C:\Temp\CheckLCStatus
    133.99
    C:\Temp\Old
    8.77
    C:\Temp\SDelete
    0.31
    C:\Temp\VF
    0
    C:\Temp\CheckLCStatus\prerequisites
    72.96
    C:\Temp\CheckLCStatus\prerequisites\.NET Framework 4.5.2
    66.76
    C:\Temp\CheckLCStatus\prerequisites\VS2013 vcredist x86
    6.2

    I guess part of the problem is that you have this in your ForEach-Object loop, so it is doing this piece of code for every folder it finds. With my sample, it would output the above results 8 times because their are 8 folders causing the full output to be generated 8 times.

    Additionally, I'm not sure what you are intending to do here

    $_ | ?{ $_.Name -Like $Filter }

    It appears that you are attempting to only include those objects that match your filter, but then you are not doing anything with the output. It is just being sent to the Default Output.

    • #74003
      Profile photo of russell karr
      russell karr
      Participant

      @Curtis Smith,

      Thank you for that, however; I am trying to have the sizes displayed in a table format along with the rest of the data; I was looking into achieving this sort of structure:

      Date Modified Owner File Path                                                              size
      ------------- ----- ---------                                                              ----
      06/26/2017     jh   /Users/demo/main/1slevel                                               2.3MB
      06/29/2017          /Users/demo/main/1slevel/2nlvel                                        5.0MB ...
      06/26/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel
      06/29/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel/4thlev
      06/26/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel/4thlev/5thlevl
      06/29/2017          /Users/demo/main/1slevel/2nlvel/3rdlvel/4thlev/5thlevl/6thlvl
      06/26/2017          /Users/demo/main/uns
      06/29/2017          /Users/demo/main/uns/swan
      06/29/2017          /Users/demo/main/uns/swan/drins
      06/29/2017          /Users/demo/main/uns/swan/drins/furth
      06/26/2017          /Users/demo/main/uns/swan/drins/furth/firf
      06/26/2017          /Users/demo/main/uns/swan/drins/furth/firf/zexs
      
  • #74005
    Profile photo of Curtis Smith
    Curtis Smith
    Participant
    (Get-ChildItem -Path 'C:\Temp' -Directory -Recurse| ForEach-Object {
        [pscustomobject]@{
            "Date Modified" = $_.LastWriteTime
            "Owner" = $_.GetAccessControl().Owner
            "File Path" = $_.FullName
            "Size" = [Math]::Round((Get-ChildItem $_.FullName -File -Recurse| Measure-Object -property length -sum).Sum / 1MB,2,[MidPointRounding]::AwayFromZero)
        }
    })

    Results:

    Date Modified         Owner                  File Path                                                  Size
    -------------         -----                  ---------                                                  ----
    6/22/2016 12:06:27 AM domain\user            C:\Temp\1                                                     0
    5/30/2017 8:55:14 AM  domain\user            C:\Temp\CheckLCStatus                                    133.99
    1/11/2016 10:46:27 PM domain\user            C:\Temp\Old                                                8.77
    7/14/2016 9:55:52 AM  domain\user            C:\Temp\SDelete                                            0.31
    12/5/2016 8:29:43 AM  BUILTIN\Administrators C:\Temp\VF                                                    0
    5/30/2017 8:55:19 AM  domain\user            C:\Temp\CheckLCStatus\prerequisites                       72.96
    5/30/2017 8:55:14 AM  domain\user            C:\Temp\CheckLCStatus\prerequisites\.NET Framework 4.5.2  66.76
    5/30/2017 8:55:19 AM  domain\user            C:\Temp\CheckLCStatus\prerequisites\VS2013 vcredist x86     6.2
    
    • #74014
      Profile photo of russell karr
      russell karr
      Participant

      thank you!
      I've made the adjustments you asked, and the script runs a lot faster now.

      Out of curiosity, for a beginner user of PowerShell like me, what resources should I utilize in order to further my understanding of Powershell?

      Thanks!

  • #74017
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    One that I hear mentioned in this forum a lot is "Learn Windows PowerShell in a Month of Lunches", written by one of the co-founders of this very site Mr. Don Jones. (Second Edition is now available)

    For myself, it was a matter of practice and challenge. I find it very useful to be active in a forum such as this one to see what challenges people are facing, and what types of projects they are working on. Then I see how I would solve their issue. Quite frequently I come across ideas or challenges I've never faced and my capabilities are expanded by solving the problems of others or seeing how someone else solved the problem. Personally, experience is the best teacher, but a good solid foundation can be found in good solid resources like the one mentioned above. There are also several eBooks available for free in this site that cover many aspects of PowerShell including some of the gotchyas you may run into. Definitely worth a review.

  • #74020
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    Just for giggles, here is my method of doing it, and since it only does the Get-ChildItem once, it should run quicker.

    $Directories = Get-ChildItem -Path C:\Temp -Recurse |
    Group-Object -Property Directory |
    Where-Object {$_.Name} |
    Select-Object Name,
                  @{label = "Size"; Expression={$_.group.length | Measure-Object -Sum | Select-Object -ExpandProperty Sum}}
    
    $Directories |
    ForEach-Object {
        $directory = Get-Item -Path $_.name
        $totalsize = ($Directories | Where-Object {$_.Name -like "$directory*"} | Measure-Object -Property Size -Sum).Sum / 1mb
        [PSCustomObject]@{"Directory" = $directory.FullName
                          "Date Modified" = $directory.LastWriteTime
                          "Owner" = $directory.GetAccessControl().Owner
                          "Size" = [math]::Round($totalsize,2)
                         }
    } | Sort-Object Directory | FT -AutoSize

    Results:

    Directory                                                Date Modified         Owner                    Size
    ---------                                                -------------         -----                    ----
    C:\Temp                                                  6/28/2017 9:23:04 AM  domain\user                 336
    C:\Temp\CheckLCStatus                                    5/30/2017 8:55:14 AM  domain\user              133.99
    C:\Temp\CheckLCStatus\prerequisites\.NET Framework 4.5.2 5/30/2017 8:55:14 AM  domain\user               66.76
    C:\Temp\CheckLCStatus\prerequisites\VS2013 vcredist x86  5/30/2017 8:55:19 AM  domain\user                 6.2
    C:\Temp\Old                                              1/11/2016 10:46:27 PM domain\user                8.77
    C:\Temp\SDelete                                          7/14/2016 9:55:52 AM  domain\user                0.31
    C:\Temp\VF                                               12/5/2016 8:29:43 AM  BUILTIN\Administrators      0

You must be logged in to reply to this topic.