I ve got a script which get each folder and get name,filescount,size of each folder.
Size with measure-object doesn t work.
My first try using my own object to handle this([pscustomobject]).
May I integrate a command (measure-object) in an object ?
Get-ChildItem d:\ -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue|
ForEach-Object{
[pscustomobject]#{
FullName = $_.Fullname
FileCount = $_.GetFiles().Count
size=measure-object -sum -property length
}
} | sort -Property filecount -Descending
Thks ,)
Unfortunately folders don't actually have a size (Windows is just kind enough to find out for us when we check the properties of it)
So in your script you need to get all child items of the current iterations folder and measure their combined size.
Get-ChildItem d:\ -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue |
ForEach-Object {
[pscustomobject]#{
FullName = $_.Fullname
FileCount = $_.GetFiles().Count
size = (Get-Childitem -Path $_.Fullname -recurse | measure-object -property length -sum).sum
}
} | sort -Property filecount -Descending
You will be using the result from .GetFiles() twice, first for getting the total file count and the second time to get the sum of the file's Length, hence I would advise you to store the result of that method call in a variable for further manipulation.
To get the folder size, you can either use Enumerable.Sum:
Get-ChildItem D:\ -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue | & {
process {
$files = $_.GetFiles()
[pscustomobject]#{
FullName = $_.FullName
FileCount = $files.Count
Size = [Linq.Enumerable]::Sum([int64[]] $files.ForEach('Length'))
}
}
} | Sort-Object -Property Filecount -Descending
Or, if you want to use Measure-Object, the Size property would be:
Size = ($files | Measure-Object Length -Sum).Sum
Lastly, if you want to do a recursive search per folder, you can target the .GetFiles(String, SearchOption) overload using AllDirectories for SearchOption Enum:
$files = $_.GetFiles('*', [IO.SearchOption]::AllDirectories)
I have found several resources that use the following script to get folder sizes
$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
{
$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
}
The problem with that is it also lists the subdirectories ie:
c:\test\1 -- 10mb
c:\test\1\folder -- 10mb
c:\test\1\folder\deep -- 5mb
c:\test\1\folder\tuna -- 5mb
c:\test\2 -- 20bm
c:\test\2\folder -- 20mb
c:\test\2\folder\deep -- 10mb
c:\test\2\folder\tuna -- 10mb
I think you know see where I am going. What I am looking for is just the parent folder's results... SO:
c:\test\1 -- 10mb
c:\test\2 -- 20mb
How can this be accomplished with Powershell?
....
You need to get the total contents size of each directory recursively to output. Also, you need to specify that the contents you're grabbing to measure are not directories, or you risk errors (as directories do not have a Length parameter).
Here's your script modified for the output you're looking for:
$colItems = Get-ChildItem $startFolder | Where-Object {$_.PSIsContainer -eq $true} | Sort-Object
foreach ($i in $colItems)
{
$subFolderItems = Get-ChildItem $i.FullName -recurse -force | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum | Select-Object Sum
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
}
This simple solution worked for me as well.
Get-ChildItem -Recurse 'directory_path' | Measure-Object -Property Length -Sum
The solution posted by #Linga:
"Get-ChildItem -Recurse 'directory_path' | Measure-Object -Property Length -Sum" is nice and short. However, it only computes the size of 'directory_path', without sub-directories.
Here is a simple solution for listing all sub-directory sizes. With a little pretty-printing added.
(Note: use the -File option to avoid errors for empty sub-directories)
foreach ($d in gci -Directory -Force) {
'{0,15:N0}' -f ((gci $d -File -Recurse -Force | measure length -sum).sum) + "`t`t$d"
}
Sorry to reanimate a dead thread, but I have just been dealing with this myself, and after finding all sorts of crazy bloated solutions, I managed to come up with this.
[Long]$actualSize = 0
foreach ($item in (Get-ChildItem $path -recurse | Where {-not $_.PSIsContainer} | ForEach-Object {$_.FullName})) {
$actualSize += (Get-Item $item).length
}
Quickly and in few lines of code gives me a folder size in Bytes, than can easily be converted to any units you want with / 1MB or the like.
Am I missing something? Compared to this overwrought mess it seems rather simple and to the point. Not to mention that code doesn't even work since the called function is not the same name as the defined function. And has been wrong for 6 years. ;)
So, any reasons NOT to use this stripped down approach?
This is similar to https://stackoverflow.com/users/3396598/kohlbrr answer, but I was trying to get the total size of a single folder and found that the script doesn't count the files in the Root of the folder you are searching. This worked for me.
$startFolder = "C:\Users";
$totalSize = 0;
$colItems = Get-ChildItem $startFolder
foreach ($i in $colItems)
{
$subFolderItems = Get-ChildItem $i.FullName -recurse -force | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum | Select-Object Sum
$totalSize = $totalSize + $subFolderItems.sum / 1MB
}
$startFolder + " | " + "{0:N2}" -f ($totalSize) + " MB"
This is something I wind up looking for repeatedly, even though I wrote myself a nice little function a while ago. So, I figured others might benefit from having it and maybe I'll even find it here, myself. hahaha
It's pretty simple to paste into your script and use. Just pass it a folder object.
I think it requires PowerShell 3 just because of the -directory flag on the Get-ChildItem command, but I'm sure it can be easily adapted, if need be.
function Get-TreeSize ($folder = $null)
{
#Function to get recursive folder size
$result = #()
$folderResult = "" | Select-Object FolderPath, FolderName, SizeKB, SizeMB, SizeGB, OverThreshold
$contents = Get-ChildItem $folder.FullName -recurse -force -erroraction SilentlyContinue -Include * | Where-Object {$_.psiscontainer -eq $false} | Measure-Object -Property length -sum | Select-Object sum
$sizeKB = [math]::Round($contents.sum / 1000,3) #.ToString("#.##")
$sizeMB = [math]::Round($contents.sum / 1000000,3) #.ToString("#.##")
$sizeGB = [math]::Round($contents.sum / 1000000000,3) #.ToString("#.###")
$folderResult.FolderPath = $folder.FullName
$folderResult.FolderName = $folder.BaseName
$folderResult.SizeKB = $sizeKB
$folderresult.SizeMB = $sizeMB
$folderresult.SizeGB = $sizeGB
$result += $folderResult
return $result
}
#Use the function like this for a single directory
$topDir = get-item "C:\test"
Get-TreeSize ($topDir)
#Use the function like this for all top level folders within a direcotry
#$topDir = gci -directory "\\server\share\folder"
$topDir = Get-ChildItem -directory "C:\test"
foreach ($folderPath in $topDir) {Get-TreeSize $folderPath}
My proposal:
$dir="C:\temp\"
get-childitem $dir -file -Rec | group Directory | where Name -eq $dir | select Name, #{N='Size';E={(($_.Group.Length | measure -Sum).Sum / 1MB)}}
from sysinternals.com with du.exe or du64.exe -l 1 .
or 2 levels down:
**du -l 2 c:**
Much shorter than Linux though ;)
At the answer from #squicc if you amend this line: $topDir = Get-ChildItem -directory "C:\test" with -force then you will be able to see the hidden directories also. Without this, the size will be different when you run the solution from inside or outside the folder.
I used the great answer of #kohlbrr and improved it a bit. I also turned it into a function you can put in your $PROFILE. This outputs three properties instead of just text: Name, Size (which is a string output of the size converted to MB), and Value which is the raw size value you can use with | sort value
function Get-DirectorySize([string] $rootFolder) {
$colItems = Get-ChildItem $rootFolder | Where-Object {$_.PSIsContainer -eq $true} | Sort-Object
foreach ($i in $colItems) {
$subFolderItems = Get-ChildItem $i.FullName -recurse -force | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum | Select-Object Sum
[PSCustomObject]#{ Name=$i.Fullname; Size="{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"; Value=$subFolderItems.sum }
}
}
The following script provides file sizes from a directory(not going deep) and sizes in bytes.(give your path in $($args[0]) in both the scripts)
Get-ChildItem -Path "$($args[0])" -Recurse -Depth 0 -file| select Length,LastWriteTime,FullName | Export-Csv .\FileAndFolderSizes.csv -NoTypeInformation
The following script provides the folder sizes(depth 0 ) in bytes within the given directory.
$array= #()
Get-ChildItem -Path "$($args[0])" -Recurse -Depth 0 | Where-Object { $_.PSIsContainer } |
ForEach-Object {
$obj = New-Object PSObject
$Size = [Math]::Round((Get-ChildItem -Recurse $_.FullName | Measure-Object Length -Sum -ErrorAction SilentlyContinue).Sum, 2)
$obj |Add-Member -MemberType NoteProperty -Name "FullName" $_.FullName
$obj |Add-Member -MemberType NoteProperty -Name "LastWriteTime" $_.LastWriteTime
$obj |Add-Member -MemberType NoteProperty -Name "Length" $Size
$array +=$obj
}
$array | select Length,LastWriteTime,FullName | Export-Csv .\FileAndFolderSizes.csv -NoTypeInformation -Append
Output is as follows
"Length","LastWriteTime","FullName"
"593408","2/17/2022 10:51:01 PM","C:\a_appli\Partners_Ref\Client64_Rws2\compresslayer.dll"
"286720","2/9/2021 11:52:18 PM","C:\a_appli\Partners_Ref\Client64_Rws2\fdsrtd.dll"
"589312","6/19/2019 10:18:41 PM","C:\a_appli\Partners_Ref\Client64_Rws2\FdswCli.dll"
"13658276","3/18/2022 9:52:16 AM","C:\a_appli\Partners_Ref\Client64_Rws2\PartnersTemplateBuilder"
Interesting how powerful yet how helpless PS can be in the same time, coming from a Nix learning PS. after install crgwin/gitbash, you can do any combination in one commands:
size of current folder:
du -sk .
size of all files and folders under current directory
du -sk *
size of all subfolders (including current folders)
find ./ -type d -exec du -sk {} \;
My company recently moved to outlook365. We are entirely VDI based so our user profiles are stored on a single server. As a result our users all now have 2+ .ost files taking up storage space on the server. I'd like to write a script to find and delete the extraneous .ost files. In addition I'd like to schedule the script to run on a monthly basis to clean up any orphaned .ost's that occur for any other reason.
I've tried a few different solutions but can't seem to find the right syntax to identify just the oldest/original .ost in each subdirectory, all attempts have identified the oldest file from the whole directory or all .ost files in the directory.
$Path = "<path>"
$SubFolders = dir $Path -Recurse | Where-Object {$_.PSIsContainer} | ForEach-Object -Process {$_.FullName}
ForEach ($Folder in $SubFolders)
{
$FullFileName = dir $Folder | Where-Object {!$_.PSIsContainer} | Sort-Object {$_.LastWriteTime} -Descending | Select-Object -First 1
}
Inside of your loop, you could use the following to list the .ost file that has the oldest LastWriteTime value. Just add the -Descending flag to Sort-Object to list the newest file.
$FullFileName = foreach ($folder in $Subfolders) {
$Get-ChildItem -Path $folder -Recurse -File -Filter "*.ost" |
Sort-Object -Property LastWriteTime |
Select-Object -Property FullName -First 1
}
$FullFileName
If there is only one .ost file found in the $folder path, it will still find that file. So you will need logic to not delete when there is only one file. This does not guarantee it is the oldest file. You probably want a combination of the oldest CreationTime and newest LastWriteTime. The following will list the oldest .ost file based on CreationTime.
$FullFileName = foreach ($folder in $Subfolders) {
Get-ChildItem -Path $folder -Recurse -File -Filter "*.ost" |
Sort-Object -Property CreationTime |
Select-Object -Property FullName -First 1
}
$FullFileName
Another issue is setting the $FullFileName variable inside of the foreach loop. This means it will be overwritten through each loop iteration. Therefore, if you retrieve the value after the loop completes, it will only have the last value found. Setting the variable to be the result of the foreach loop output will create an array with multiple values.
To only output an OST file path when there are multiple OST files, you can do something like the following:
$FullFileName = foreach ($folder in $Subfolders) {
$files = Get-ChildItem -Path $folder -Recurse -File -Filter "*.ost" |
Sort-Object -Property LastWriteTime -Descending
if ($files.count -ge 2) {
$files | Select-Object -Property FullName -First 1
}
$FullFileName
This one liner should do the job, keeping the ost file with the newest LastWriteTime
gci -Path $Path -directory | where {(gci -Path $_\*.ost).count -gt 1}|%{gci -Path $_\*.cmd|Sort-Object LastWriteTime -Descending|Select-Object -Skip 1|Remove-Item -WhatIf}
Longer variant follows.
$Path = '<path>'
$Ext = '*.ost'
Get-ChildItem -Path $Path -directory -Recurse |
Where-Object {(Get-ChildItem -Path "$_\$Ext" -File -EA 0).Count -gt 1} |
ForEach-Object {
Get-ChildItem -Path "$_\$Ext" -File -EA 0| Sort-Object LastWriteTime -Descending |
Select-Object -Skip 1 | Remove-Item -WhatIf
}
The first two lines evaluate folders with more than one .ost file
The next lines iterates those folders and sort them descending by LastWriteTime, skips the first (newest) and pipes the other to Remove-Item with the -WhatIf parameter to only show what would be deleted while testing.
You can of course also move them to a backup location instead.
I have this script that searches for files of the same size and if it is of the same size I let one where it is ("C:\folder1") and move the others copies to the "C:\files_compared".
$allfiles = Get-ChildItem -file -recurse "C:\folder1" | Group-Object -Property length
foreach($filegroup in $allfiles)
{
if ($filegroup.Count -ne 1)
{
$fileGroup.Group[1..($fileGroup.Count-1)] | move -Destination 'C:\Files_compared'
}
}
The problem now is that I want to do this but only with folders, not files anymore.
I tried to put the -Directory instead of file but I don't know what to do next.
$allfiles = Get-ChildItem -Directory -recurse "C:\folder1" | Group-Object -Property length
foreach($filegroup in $allfiles)
{
if ($filegroup.Count -ne 1)
{
$fileGroup.Group[1..($fileGroup.Count-1)] | move -Destination 'C:\Files_compared'
}
}
Thanks.
You can use this, It compares a files in a folder and it moves to destinations folder if file length is same.
Get-ChildItem -file "E:\folder" | Group-Object -Property length | ForEach-Object {$_.Group | Select-Object -Skip 1 | Move-Item -Destination "E:\folder\move"}
My ultimate goal is to get a list of top level folders (for a given path) where a file has been modified in the last day.
There are probably a lot of ways to do this. The place where I am having a problem is getting the top level folder only.
Here is what I have so far:
Get-ChildItem -Path "c:\data\*" -recurse |
where-object {$_.lastwritetime -gt (get-date).addDays(-1)} |
where-object {-not $_.PSIsContainer} |
Foreach-Object { $_.DirectoryName} |
sort -unique
It gets all the directories though, not just the top level.
Here's how I would do it
$dirs = dir "sometoplevelpath" |?{ $_.PsIsContainer }
$oneDayAgo = (Get-Date).AddDays(-1)
$dirs |?{ dir $_ -Recurse |?{!$_.PsIsContainer -and $_.LastWriteTime -gt $oneDayAgo } | select -first 1 }
You could take the list of folders that you end up with and compare their full path without their name and see if it matches the directory that contains the folders you're interested in:
$folders | Where-Object {$_.FullName.Replace($_.Name,"") -eq $superDirectory}
Where $superdDirectory is the name of the directory that contains the "top level directories". In this case, that sounds like "C:\".
You could also investigate the PSParentPath property.
Another method would be to make a list of potential backup folders first:
$targetFolders = Get-Item -Path "C:\data*" | Where-Object {$_.PSIsContainer}
And then go through that list to see if they have any items that need backing up, taking action if they do.
$targetFolders | % {
$folderItems = Get-ChildItem $_.FullName | ? {.... use your filter here}
if (($folderItems | Measure-Object).Count -gt 0){
#Backup the folder, or add $_.FullName to the list of folders that should be backed up.
}
}
Try removing the -recurse
Get-ChildItem -Path "c:\data*" | where-object {$_.lastwritetime -gt (get-date).addDays(-1)} | where-object {-not $_.PSIsContainer} | Foreach-Object {$_.DirectoryName} | sort -unique
I also changed the $. to $_.. See if this works. I got it to give me only the top level directory names, but I don't have anything I can run as a pattern like "c\data*"