Skip to content

Instantly share code, notes, and snippets.

@sean-m
Last active October 12, 2016 18:57
Show Gist options
  • Select an option

  • Save sean-m/35099f945d34e558a832 to your computer and use it in GitHub Desktop.

Select an option

Save sean-m/35099f945d34e558a832 to your computer and use it in GitHub Desktop.
Enumerates files in a directory getting both the regular and compressed size. The enumeration happens in a pipeline function so it's memory efficient. The input parameter $Path is checked for type so if you pipe the output of Get-ChildItem into the function it will use the FullName parameter as the path.
<#
.Synopsis
Gets directory sizes quickly.
.DESCRIPTION
Enumerates files in a directory getting both the regular
and compressed size. The enumeration happens in a pipeline
function so it's memory efficient. The input parameter $Path
is checked for type so if you pipe the output of Get-ChildItem
into the function it will use the FullName parameter as the path.
Note, there is no error handling on the path and it only works
on directories. It's up to you to filter out file paths from
directory paths.
You can specify the unit denomination with the $Unit parameter
but it none is specified or it can't resolve file sizes will
be returned in bytes.
Benchmarks:
580.1 GB mixed data
sysinternals du.exe
TotalSeconds : 102.2893017
1x
GetSizeRecurse
TotalSeconds : 319.1673825
3.12x
GetSizeRecurse (NoCompressCheck switch)
TotalSeconds : 239.6403775
2.34x
robocopy.exe measure
TotalSeconds : 72000
703.99x
Sean McArdle, Nov 2015
.EXAMPLE
gci | ? { $_.PSIsContainer } | Get-DirectorySizeRecurse -Unit mb
.EXAMPLE
Get-DirectorySizeRecurse $env:temp
#>
function Get-DirectorySizeRecurse {
[CmdletBinding()]
[OutputType([PSObject])]
param (
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$Path,
# Unit of measure for size, ie: bytes,Kb,Mb,Gb,Tb
# Defaults to bytes.
[Parameter(Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
Position=1)]
$Unit,
[switch]$NoCompressCheck
)
begin {
$denomination = 0
if ([String]::IsNullOrEmpty($Unit)) {
$denomination = 1
}
elseif ($Unit -like "kb") {
$denomination = 1kb
}
elseif ($Unit -like "mb") {
$denomination = 1mb
}
elseif ($Unit -like "gb") {
$denomination = 1gb
}
elseif ($Unit -like "tb") {
$denomination = 1tb
}
elseif ($Unit -like "pb") {
$denomination = 1pb
}
else {
Write-Warning "Unit: $Unit not supported by powershell, using bytes."
$denomination = 1
}
$ntype =@"
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
public class NativeHelper {
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);
public static UInt64 GetCompressedFileSize(string FilePath, bool ErrAsZero=false) {
uint highSize;
uint lowSize = GetCompressedFileSizeW(FilePath, out highSize);
int err = Marshal.GetLastWin32Error();
if (highSize == 0 && lowSize == 0xFFFFFFFF && err != 0) {
if (ErrAsZero) return 0;
throw new System.ComponentModel.Win32Exception(err);
}
return ((ulong)highSize << 32 | lowSize);
}
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
private static extern bool PathCompactPathEx([Out] StringBuilder pszOut, string szPath, int cchMax, int dwFlags);
public static string PathCompactPath(string Path) {
StringBuilder sb = new StringBuilder(247);
PathCompactPathEx(sb, Path, 246, 0);
return sb.ToString();
}
public static IEnumerable<string> GetFiles(string root, string searchPattern) {
var pending = new Stack<string>();
pending.Push(root);
while (pending.Count != 0) {
var path = pending.Pop();
string[] next = null;
try {
next = Directory.GetFiles(path, searchPattern);
}
catch { }
if (next != null && next.Length != 0)
foreach (var file in next) yield return file;
try {
next = Directory.GetDirectories(path);
foreach (var subdir in next) pending.Push(subdir);
}
catch { }
}
}
}
"@
if(-not ([System.Management.Automation.PSTypeName]'NativeHelper').Type) {
Add-Type -TypeDefinition $ntype -Language CSharp -IgnoreWarnings
}
}
process {
## Check if taking in FileInfo objects,
$unwrap = $false
$unwrap = [bool]($Path.GetType() -like [System.IO.DirectoryInfo])
[UInt64]$size = 0
[UInt64]$csize = 0
$filter = "*.*"
$p = [String]::Empty
if ($unwrap) {
$p = $Path.FullName
}
else {
$p = $Path
}
foreach ($fname in [NativeHelper]::GetFiles($p, $filter)) {
## Handle long path name
if ($fname.Length -gt 247) {
$fname = [NativeHelper]::PathCompactPath($fname)
}
## Get FileInfo
$f = New-Object System.IO.FileInfo $fname
## File size bytes
$size = $size + $f.Length
## File size on disk
if (-not $NoCompressCheck) {
$csize = $csize + [NativeHelper]::GetCompressedFileSize($fname, $true)
}
}
[double]$size = $size/$denomination
[double]$csize = $csize/$denomination
if ($NoCompressCheck) {
$csize = "N\A"
}
New-Object PSObject `
| Add-Member -MemberType NoteProperty -PassThru -Name Path -Value $p `
| Add-Member -MemberType NoteProperty -PassThru -Name Size -Value $size `
| Add-Member -MemberType NoteProperty -PassThru -Name CompressedSize -Value $csize `
| Add-Member -MemberType NoteProperty -PassThru -Name Unit -Value $Unit
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment