Last active
May 31, 2021 09:46
-
-
Save XPlantefeve/e1de3e1eaae5b0f8c6c5d409dfb4a946 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Get-HumanReadableSize { | |
<# | |
.SYNOPSIS | |
Outputs a human readable version of a file size | |
.DESCRIPTION | |
This function rounds the value of the given file size to the nearest relevant | |
multiplier prefix (Gb, Mb, or Kb) | |
.INPUTS | |
long | |
.OUTPUTS | |
string | |
#> | |
param( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[long]$InputObject, | |
[double]$Precision = 0.9 | |
) | |
process { | |
if (($result = $_ / 1Gb) -gt $Precision) { | |
'{0:n2}G' -f $result | |
} elseif (($result = $_ / 1Mb) -gt $Precision) { | |
'{0:n2}M' -f $result | |
} elseif (($result = $_ / 1Kb) -gt $Precision) { | |
'{0:n2}K' -f $result | |
} else { | |
$_ | |
} | |
} | |
} | |
function Get-FolderSize { | |
<# | |
.SYNOPSIS | |
Gets information of subfolders content | |
.DESCRIPTION | |
The Get-SubfolderSize function will test every subfolder of the provided path for size, | |
number of folders, and number of files. | |
.PARAMETER Path | |
The folder whose subfolders should be measured | |
.EXAMPLE | |
Get-SubfolderSize -Path C:\Localdata | |
Gets the size of every immediate subfolder of C:\Localdata | |
.NOTES | |
This function uses Robocopy internally, as Robocopy is build for speed whereas | |
Powershell needs to construct the whole .NET object for every folder and file | |
and is way slower. | |
Reparse points (IE. hard links) are excluded to avoid loops. | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$Path | |
) | |
function Get-ResultFromRobocopy { | |
param( | |
[string]$Name, | |
[string[]]$robocopyInfo | |
) | |
$properties = @{Name = $Name } | |
foreach ($line in $robocopyInfo) { | |
if ($line -match '(?<infoname>Dirs|Files|Bytes) :\s+(?<infovalue>\d+)') { | |
$properties[$Matches.infoname] = $Matches.infovalue | |
} | |
} | |
$output = [PSCustomObject]$properties | | |
Select-Object -Property @( | |
'Name' | |
@{l = 'Size'; e = { $_.Bytes | Get-HumanReadableSize } } | |
'Bytes' | |
'Dirs' | |
'Files' | |
) | |
# We build a default display property set so as not to clutter the output. | |
# The Byte property is hidden but still available if needed. | |
$DefaultDisplayPropertySet = 'DefaultDisplayPropertySet', [string[]]('Name', 'Size', 'Dirs', 'Files') | |
$PSMemberInfo = New-Object -TypeName Management.Automation.PSPropertySet -ArgumentList $DefaultDisplayPropertySet | |
$PSStandardMembers = [Management.Automation.PSMemberInfo[]]$PSMemberInfo | |
$output | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers -PassThru | |
} | |
$robocopyParameters = @( | |
'C:\DUMMY' # Non-existant destination folder | |
'/l' # List only (blank run): we only pretend to copy to get the size information | |
'/bytes' # Robocopy rounds the size information. We need this to get an accurate number | |
'/np','/nfl','/ndl','/njh' # We remove all output but the footer | |
'/r:0','/w:0' # There's no point in retrying a failed copy, it's not a network problem | |
'/xjf','/xjd' # We skip over junctions (hard links) to avoid loops. | |
) | |
$i = 0 | |
$subfolders = Get-ChildItem -Path $Path -Directory -Force | ? Attributes -NotMatch ReparsePoint | |
$percenttotal = (1+$subfolders.count)/100 | |
$activity = 'Analyzing {0} folders' -f (1+$subfolders.count) | |
# We start with a pecial case: the folder itself. | |
Write-Progress -Activity $activity -PercentComplete ($i++/$percenttotal) -CurrentOperation 'root folder' | |
$robocopyInfo = Robocopy.exe $Path $robocopyParameters | |
Get-ResultFromRobocopy -Name . -robocopyInfo $robocopyInfo | |
# We only read one level of files for the root folder, but we need the whole tree for every | |
# subfolder, so we add the /mir parameter for recursive processing | |
$robocopyParameters += '/mir' | |
foreach ($folder in $subfolders) { | |
Write-Progress -Activity $activity -PercentComplete ($i++/$percenttotal) -CurrentOperation $folder.Name | |
$robocopyInfo = Robocopy.exe $folder.FullName $robocopyParameters | |
Get-ResultFromRobocopy -Name $folder.Name -robocopyInfo $robocopyInfo | |
} | |
} | |
function Get-FolderSizeReport { | |
<# | |
.SYNOPSIS | |
Outputs a report of subfolders content | |
.DESCRIPTION | |
The Get-SubfolderReport function will output a table information on every | |
subfolder of the provided path for size, and percentage of the size of the | |
whole folder. | |
.PARAMETER Path | |
The folder whose subfolders should be measured | |
.EXAMPLE | |
Get-SubfolderReport -Path C:\Localdata | |
Gives information about C:\Localdata subfolders | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$Path | |
) | |
$table = Get-FolderSize -Path $Path | |
$sum = $table | Measure-Object -Sum -Property bytes | Select-Object -ExpandProperty Sum | |
$table | Select-Object -Property @( | |
'Name' | |
'Size' | |
@{l = 'Percent'; e = { [math]::Round((($_.Bytes) / $sum * 100), 2) } } | |
) | Sort-Object -Descending -Property percent | |
# We build a footer | |
[PSCustomObject]@{ | |
Name = '----' | |
Size = '----' | |
Percent = '-------' | |
} | |
[PSCustomObject]@{ | |
Name = 'Total' | |
Size = $sum | Get-HumanReadableSize | |
Percent = 100 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment