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
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.
@Dan Jones Thank you for input, after doing some research, I found this script from the script guys (Browse code samples | Microsoft Docs)
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 }
}
}
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:
Do a recursive search for all files from my root directory
Group those by directory
For each grouping get the directory name and the sum of all lengths (files in those directories)
Store results in a variable
*At this point you will have the same as what Don provided
For each directory in the variable, Get all other directories in the variable who’s name begins with the same directory name
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 :^)
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
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.
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:
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.