Last active
October 12, 2016 18:57
-
-
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.
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
| <# | |
| .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