Powershell to find folder / file size

I am trying to write a script using powershell to get folder / file size as mentioned below -

 $StartFolder = "D:\"
$Output = "C:\Temp\test-d.csv"

Add-Content -Value "Folder Path|Size" -path $Output

$colItems = (Get-ChildItem $startFolder -Recurse | Measure-Object -property length -sum)
"$StartFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB" # | out-file $Output

$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
    {
        $subFolderItems = (Get-ChildItem $i.FullName -Recurse | Measure-Object -property length -sum)
        $i.FullName + "|" + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB" | out-file $Output -Append
    } 

I am getting error as mentioned below…

 Measure-Object : The property "length" cannot be found in the input for any objects.
At line:12 char:65
+         $subFolderItems = (Get-ChildItem $i.FullName -Recurse | Measure-Object - ...
+                                                                 
+ CategoryInfo          : InvalidArgument: (:) [Measure-Object], PSArgumentException
+ FullyQualifiedErrorId : GenericMeasurePropertyNotFound,Microsoft.PowerShell.Commands.MeasureObjectCommand

Measure-Object : The property "length" cannot be found in the input for any objects.
At line:12 char:65
+         $subFolderItems = (Get-ChildItem $i.FullName -Recurse | Measure-Object - ...
+                                                             
+ CategoryInfo          : InvalidArgument: (:) [Measure-Object], PSArgumentException
+ FullyQualifiedErrorId : GenericMeasurePropertyNotFound,Microsoft.PowerShell.Commands.MeasureObjectCommand
    } 

Can you pls assist me in that & also when I target C: drive I am getting access denied in some system files -

 Get-ChildItem : Access to the path 'C:\Windows\System32\LogFiles\WMI\RtBackup' is denied.
At line:12 char:28

+         $subFolderItems = (Get-ChildItem $i.FullName -Recurse | Measure-Object - ...
+                            
    + CategoryInfo          : PermissionDenied: (C:\Windows\Syst...es\WMI\RtBackup:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand
  1. First issue.

You’re getting the “Measure-Object : The property “length” cannot be found in the input for any objects.” error because you have at least one folder that has nothing in it but one, or more, sub-folders. You can “get past” this by adding -ErrorAction SilentlyContinue to the end of your Measure-Object command like the following:

$subFolderItems = (Get-ChildItem $i.FullName -Recurse | Measure-Object -property length -sum -ErrorAction SilentlyContinue)

A few comments/suggestions on your code, if I may. Feel free to ignore. :slight_smile:

1. You don't need to put parenthesis around your "Get-ChildItem | Measure-Object" commands.  Everything to the right of the equals sign will be done before the result is assigned to the variable.
2. It's best practice to give your variable appropriately descriptive names to make your code more readable, especially to others.  For example, on your "Get-ChildItem | Measure-Object" commands, it's not clear to me what $colItems is.  On the first "Get-ChildItem | Measure-Object" commands (line 6 of your code), I would probably name the variable something like $StartFolderSize or $StartFolderSizeMB (or sub "RootFolder" in the previous two examples).  On the second one (line 9 of your code), I would probably change it to something like $SubFolders.
3. It's best practice to use variables like $i & $j for things like "For" loops, where $i or $j are used as an incremental counter (Ex: For($i=0;$i -lt 10;$i++) ).  Instead, I would suggest something like the following for your ForEach.
ForEach($SubFolder in $SubFolders)
{
  $SubFolderItemsSum = (Get-ChildItem $SubFolder.FullName -Recurse | Measure-Object -Property length -Sum).Sum
  $SubFolder.FullName + "|" + "{0:N2}" -f ($SubFolderItemsSum / 1MB) + " MB" | Out-File $OutputFile -Append)
} 
4. I would suggest formatting your output differently.  To me, it would look be more readable if the folder path and size/sum of the items in each folder were in separate columns.  When I ran your code as is, the output did weird things.

–What your code outputs into the file when I test with your code as is (I added the “Column 1” & “Column 2” parts). In Excel the data is in two different columns for the 2nd and 3rd entry. It should be “59096.45 MB” & “2874.96 MB” altogether.

Folder Path|Size (Column 1) (Space in here) (Column 2 - no header)
D:\Movies\Movies\Temp\Abc|0.00 MB
D:\Movies|59 (-----------Space in here------------) 096.45 MB
D:\Movies\Movies|2 (---------Space in here------) 874.96 MB
D:\Movies\Movies\Temp|0.00 MB

  1. Second problem.

As for the “Get-ChildItem : Access to the path ‘C:\Windows\System32\LogFiles\WMI\RtBackup’ is denied.” error, I’m searching around to see how to get around that. I’ll post back if I find something.

Thanks for explaining clearly Kevyn, when I added

 -ErrorAction SilentlyContinue) 
its working fine.
Pls let me what you find on the 2nd issue in regards to the “access denied” issue.

Below is a function that I wrote that I use all the time at work. I have the SilentlyContinue instead of Stop right now because once an error occurs it exits the entire script. I"m not sure yet the best approach to loop through each file in $files since I’m using Group-Object. Just haven’t had time to fix it. Other than that though it works great!

Function Get-ExtensionSize {
    [cmdletbinding()]
    param (
        [parameter(ValueFromPipeline=$true)]
        [string[]]
        $Path = $HOME
    )

    Process {
        Try {

            $parentFolder = Get-ChildItem $path -Directory

            if ($parentFolder.count -ne 0) {

                foreach ($folder in $parentFolder.FullName) {

                    $files = Get-ChildItem -Path $folder -Recurse -File -ErrorAction SilentlyContinue

                    if ($files.count -eq 0) {
                        continue
                    }
            
                    $group = $files | Group-Object Extension
                    $group |
                    ForEach-Object {
                        $props = @{
                            ParentFolder = $folder
                            Size = ($_.Group | 
                                Measure-Object -Property Length -Sum).Sum
                            Extension = $_.Name -replace '^\.'
                            Count = $_.Count
                        }
        
                        $obj = New-Object -TypeName PSObject -Property $props
                        Write-Output $obj
                    }
                }
            } else {

                $files = Get-ChildItem -Path $path -ErrorAction SilentlyContinue
                $group = $files | Group-Object Extension
            
                foreach ($ext in $group) {
                    $props = @{
                        ParentFolder = $($path)
                        Size = ($ext.Group | 
                            Measure-Object -Property Length -Sum).Sum
                        Extension = $ext.Name -replace '^\.'
                        Count = $ext.Count
                    }

                    $obj = New-Object -TypeName PSObject -Property $props
                    Write-Output $obj
                }
            }

        } Catch [System.UnauthorizedAccessException] {
            
            "Access to $path is denied."

        } Catch {
            
            "An error has occured."
        }        
    }
}

Thanks John Steele…I am also trying to get file size from non-system drives…how can you do in your script ? Also is there a way we can get hidden file sizes as well along with other file sizes…

VT, you can get hidden files & folders by using the -Force parameter of the Get-ChildItem cmdlet.

-Force
Gets hidden files and folders. By default, hidden files and folder are excluded. You can also get hidden files and folders by using the Hidden parameter or the
Hidden value of the Attributes parameter.

 Required?                    false
 Position?                    named
 Default value
 Accept pipeline input?       false
 Accept wildcard characters?  false

You can get more information by looking at the help file for the cmdlet (Help Get-ChildItem -Full). The help information is always a good place to start.

What non-system drives are you trying to get file sizes on?

Thanks Kevyn…I am trying to get the size of the hidden files, for example “paging file size”

I did try with -Force parameter like mentioned below -

 Get-ChildItem $StartFolder -Recurse -Force 
and
 Get-ChildItem -Force $StartFolder -Recurse 

I am getting error as mentioned below…

 
Get-ChildItem : Access to the path 'C:\Users\.NET v4.5 Classic\Start Menu' is denied.
At line:6 char:13
+ $Folders = (Get-ChildItem $StartFolder -Recurse -Force | Measure-Object -propert ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (C:\Users\.NET v4.5 Classic\Start Menu:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand

VT: I found a solution that got me past the “Access to path” error. Try running the Get-ChildItem command inside an Invoke-Command script block. You have to specify the -ComputerName parameter, even if you’re running it against the local machine, in which case you can either use Localhost or the machine’s actual name (Ex: Server1). The following is an example:

Invoke-Command -ComputerName localhost -ScriptBlock {Get-ChildItem $StartFolder -Recurse -Force}

BTW, where you put the -Force parameter in a cmdlet like this doesn’t matter. Also using either the -Force or -Hidden parameters will not show the page file (c:\pagefile.sys) size. Instead, you have to use either the Get-WmiObject cmdlet [legacy cmdlet] or the Get-CimInstance cmdlet [better way to go], to get the size information you need.

(Get-WmiObject -Class Win32_PageFileUsage).CurrentUsage

OR

(Get-CimInstance -ClassName Win32_PageFileUsage).CurrentUsage

The following MSDN article will give you full details on the Win32_PageFileUsage class.

https://msdn.microsoft.com/en-us/library/aa394246(v=vs.85).aspx

One last thing…I meant to mention this earlier: In your earlier code, you had the following:

"{0:N2}" -f ($colItems.sum / 1MB) + " MB"

If you don’t want any decimal formatting (i.e. you want an integer), you can type-cast the value as follows:

($colItems.sum / 1MB -as [int]) + " MB"

Instead of 56.67, you’d get 57, or instead of 55.22, you’d get 55.

Let us know if you have any further questions.

Thanks for your reply Kevyn. I tried using the Invoke-command -scriptblock -computername as mentioned below & I am not getting any output… & you can see the output for C disk space is 0.00 MB.

 
PS C:\Windows\system32> $StartFolder = "C:\"
$Output = "C:\New Folder\CDriveVMD2.csv"

Add-Content -Value "Folder Path|Size" -path $Output

$Folders = Invoke-Command -computername localhost -scriptblock { Get-ChildItem $StartFolder -Recurse -Force } | Measure-Object -property length -sum -ErrorAction SilentlyContinue 
"$StartFolder -- " + "{0:N2}" -f ($Folders.sum / 1MB) + " MB"  # | out-file $Output

$Folders = Invoke-Command -computername localhost -scriptblock {  Get-ChildItem $startFolder -recurse -Force } | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object 
foreach ($SubFolders in $Folders)
    {
        $subFolderItems = Invoke-Command -computername localhost -scriptblock { Get-ChildItem $SubFolders.FullName -Recurse -Force } | Measure-Object -property length -sum -ErrorAction SilentlyContinue 
        $SubFolders.FullName + "|" + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"  | out-file $Output -Append
    }
C:\ -- 0.00 MB

Hope I can get your assistance…

That’s because I stupidly didn’t give you the whole solution. I was just testing to not get the “Access is denied” errors. Doh! The problem is that just the Get-ChildItem command is in the script block. The entire “Get-ChildItem | Measure-Object” command needs to be in the script block. Here is how lines 7 & 13 should be. I changed the variable name for both lines to make more sense of what is being returned by the Invoke-Command command and tweaked it a bit so that just the folder sum, in bytes, is returned.

Line 7 & 8:

$StartFolderSum = Invoke-Command -ComputerName localhost -ScriptBlock {(Get-ChildItem $StartFolder -Recurse -Force | Measure-Object -Property length -Sum -ErrorAction SilentlyContinue).Sum}
"$StartFolderSum -- " + "{0:N2}" -f ($StartFolderSum / 1MB) + " MB"  # | Out-File $Output

Line 13 & 14:

$SubFolderItemsSum = Invoke-Command -ComputerName localhost -ScriptBlock {*Get-ChildItem $SubFolders.FullName -Recurse -Force | Measure-Object -Property length -Sum -ErrorAction SilentlyContinue).Sum}
$SubFolders.FullName + "|" + "{0:N2}" -f ($SubFolderItemsSum / 1MB) + " MB"  | out-file $Output -Append

FYI: You can make your code faster if you:

  1. Create a PS session(New-PSSession), store it in a variable, and use -Session instead of -ComputerName for the Invoke-Command command. Every time you use Invoke-Command, as you have it in your code, you’re code is making the connection/session to the local computer, running the one command you have in it, and then tearing the connection/session down. It’s doing this 3 times in your code. If you create a session, and use that, then you only have to make the connection/session once, run the 3 Invoke-Command commands against the computer, in the same session, and then close the connection/session when you’re done with it. If you run Help Invoke-Command -Full, you’ll see information on how to create a session and use it in the Invoke-Command cmdlet. You can also get the help on New-PSSession cmdlet. You can use Remove-PSSession cmdlet to remove the session when you’re done with it.

  2. Change line 10 of your code to have the entire "Get-ChildItem | Where {} | Sort-Object inside the -ScriptBlock parameter. That way, you return less information (just the items that are folders). Also, when you do the filtering against a remote computer, it makes the remote computer, rather than your local computer, do the heavy lifting.

$Folders = Invoke-Command -ComputerName localhost -ScriptBlock {Get-ChildItem $StartFolder -Recurse -Force | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object}

Let me know if that helps.

Hmmmm…I went back and just ran line 7, “as is”, and it worked fine for me. I ran it like this, just to test a smaller subset of folders (to save time). Still, what I wrote just before this post with the “Get-ChildItem | Measure-Object” commands, is the better way.

$a = Invoke-Command -ComputerName localhost -ScriptBlock {Get-ChildObject 'c:\windows\system32' -Recurse -Force}
$a | Measure-Object -Property length -Sum -ErrorAction SilentlyContinue

My output showed:

Count : 12345
Average :
Sum : 1234567890
Maximum :
Minimum :
Property: length

Thanks Kevyn…I still get the same 0 MB report…as mentioned below -

 

PS C:\Windows\system32> $StartFolder = "D:\"
$Output = "C:\New Folder\CDrive.csv"

Add-Content -Value "Folder Path|Size" -path $Output

$Folders = Invoke-Command -ComputerName localhost -ScriptBlock {(Get-ChildItem $StartFolder -Recurse -Force | Measure-Object -property length -sum -ErrorAction SilentlyContinue).Sum}
"$StartFolder -- " + "{0:N2}" -f ($startfolder.sum / 1MB) + " MB"  # | out-file $Output

$Folders = Invoke-Command -computername localhost -ScriptBlock {  Get-ChildItem $startFolder -Recurse -Force } | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object 
foreach ($SubFolders in $Folders)
    {
        $subFolderItems = Invoke-Command -ComputerName localhost -scriptblock { (Get-ChildItem $SubFolders.FullName -Recurse -Force | Measure-Object -property length -sum -ErrorAction SilentlyContinue).Sum}
        $SubFolders.FullName + "|" + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"  | out-file $Output -Append
    }
D:\ -- 0.00 MB

But this one mentioned below is working fine…

 $a = Invoke-Command -ComputerName localhost -ScriptBlock {Get-ChildObject 'c:\windows\system32' -Recurse -Force}
$a | Measure-Object -Property length -Sum -ErrorAction SilentlyContinue 

Part of the issue is that you’ve got a mixture of variable names you’ve used and what I’ve used. Another part of the issue is that you still have the .sum after $StartFolderSum & $SubFolderItemsSum on the lines where you output the data to the file. Also, I’ve been hard coding the C:\ drive into the Get-ChildItem commands as I’ve been running them, whereas you’re passing a variable into the Invoke-Command for the Get-ChildItem command. So, I’m going to get the code working and then post what works for me.

Thanks Kevyn…I tried to remove .sum and I didnt get the desired result. Pls let me know which one works for you. My apologies for delayed reply as I was away…

No problem. I’m still working on it. I see where you got your original code from (https://technet.microsoft.com/en-us/library/ff730945.aspx, or maybe stackoverflow.com that referenced the same code). That code looks to have been meant as a simple example where there were no barriers like trying to access system folders, junction points, etc… I wanted to ask a few questions.

  1. What, exactly, are you trying to accomplish (i.e. What’s the goal behind all this?)?
  2. What OS are you planning to run the script on?
  3. What is your need to get the folder size of hidden folders, including system folders?
  4. Is your start folder always going to be the root of a drive, like C:\ & D:?

For Question 4: If you’re trying to see how much space is used up on a particular drive, you can do something like the following:

$CDrive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter “Name = ‘C:’”
($CDrive.Size - $CDrive.FreeSpace) / 1MB -as [int]

Part of the problem with trying to run the Get-ChildItem cmdlet against hidden folders (-Force parameter or -Hidden parameter for Get-ChildItem) is that some of those “folders” are actually what are called Junction Points (https://msdn.microsoft.com/en-us/library/windows/desktop/bb968829(v=vs.85).aspx). There are lots of them, and each one will cause the script to error unless you somehow can filter them out. When I run my code, I’m getting the “PathTooLongException” as mentioned at List All Files Regardless of 260 Character Path Restriction Using PowerShell and Robocopy | Learn Powershell | Achieve More. That page also lists what looks to be a solution for getting around the Junction Point issue. You might want to look at the article and see if it might be what you’re looking for. Or, you might could tweak the code example listed at windows - List all files and dirs without recursion with junctions - Super User to take care of the Junction Points in the script, but I’d have to play with it.

Anyhow, let me know the answers to the above questions. I don’t know if maybe someone else in the forum has any input.

This article proves interesting reading and may offer a new thought Technical Thursdays: Get Directory Sizes Stupidly Fast With PowerShell.

I played around with the Get-DirectorySizeWithCmd function. It’s got a few bugs in it that would need to be worked out first before it could be used, plus cmd.exe doesn’t look to have the ability to look at hidden folders, unless maybe it can if you first unhide hidden folders and possibly system protected folders. One of the articles I previously mentioned does mention using robocopy as part of the solution.

I was checking that MS article and have been updating that one as I am still learning and getting better in PS scripting. For your questions, I have answers mentioned below -

  1. The goal is to find which folder / file is taking too much disk space & we can proceed further to delete based on criticality.
  2. It can be either Server 2012 or 2008 R2
  3. The hidden folder or file size to find the paging file size and also for any hidden files as well.
  4. Start folder location varies (depending on the scenario) & I already have a script that will look to find the disk space or how much disk space have been used.

I am also looking into other resources you provided & trying to update that script. If it works, I will post it here as well.I really appreciate you looking into that.

Get-ChildItem -Force

I understand. I’m curious about your interest in the paging file size and hidden files/folders. Those are all (usually) system related, so you wouldn’t want to go and delete them. Unless you think someone might be putting hidden files and folders on a machine. Anyhow, here is the code I’m trying to test. My machine keeps freezing part way through it running, so I can’t finish the testing. Maybe try it and let me know what you get. Again, if you get the “PathTooLongException” error, that’s expected since powershell is trying to access junction points, which it can’t, by default, distinguish from real folders.

Note: I put in a counter variable ($i) and a few Write-Host commands so I could track the processing of the folders. Also, on the Get-ChildItem cmdlet that’s on the line with “$StartFolderSum = …”, the code on the MSDN site, where the original code you were using is listed, doesn’t have the -Recurse. It’s just getting the size of the top-level of the start folder. Hope that makes sense.

$StartFolder = "C:\"
$Output = "C:\New Folder\CDrive.csv"

Add-Content -Value "Folder Path|Size" -Path $Output

$StartFolderSum = Invoke-Command -ComputerName localhost -ScriptBlock {param($StartFolder) (Get-ChildItem $StartFolder -Force | Measure-Object -Property length -Sum -ErrorAction SilentlyContinue).Sum} -ArgumentList $StartFolder
"$StartFolder -- " + "{0:N2}" -f ($StartFolderSum / 1MB) + " MB"  # | out-file $Output

Write-Host "Getting list of folders" -ForegroundColor Green
$Folders = Invoke-Command -ComputerName localhost -ScriptBlock {param($StartFolder) Get-ChildItem $StartFolder -Recurse -Directory -Force | Sort-Object} -ArgumentList $StartFolder
$i = 1
ForEach($SubFolder in $Folders)
{
  Write-Host "Folder $i of $Folders.count" -ForegroundColor Green
  Write-Host $SubFolder.FullName -ForegroundColor Green
  $SubFolderSum = Invoke-Command -ComputerName localhost -ScriptBlock {param($SubFolder) (Get-ChildItem $SubFolder.FullName -Recurse -Force | Measure-Object Property length -Sum -ErrorAction SilentlyContinue).Sum} -ArgumentList $SubFolder
  $SubFolder.FullName + "|" + "{0:N2}" -f ($SubFolderSum / 1MB) + " MB"  | Out-File $Output -Append
}