# getSizeOnDisk-v0.01.ps1

# Set variables
$parentDirectory="C:\"
$depth=4
$topX=10
$excludeDirectories="C:\Windows","C:\Program Files","C:\Program Files (x86)"
$clusterSizeOfNetworkShare=8192

# sanitate
if($depth -le 1){$depth=1}

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

Write-Host -NoNewLine "Running as Administrator..."

function addSystemPrivilege{
param(
[String[]]$privileges=@("SeBackupPrivilege","SeRestorePrivilege")
)

function includeSystemPrivileges{
$win32api = @'
using System;
using System.Runtime.InteropServices;

namespace SystemPrivilege.Win32API
{
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}

[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public UInt32 Attributes;
}

[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
public LUID Luid;
public UInt32 Attributes;
}

public class Privileges
{
public const UInt32 DELETE = 0x00010000;
public const UInt32 READ_CONTROL = 0x00020000;
public const UInt32 WRITE_DAC = 0x00040000;
public const UInt32 WRITE_OWNER = 0x00080000;
public const UInt32 SYNCHRONIZE = 0x00100000;
public const UInt32 STANDARD_RIGHTS_ALL = (
READ_CONTROL |
WRITE_OWNER |
WRITE_DAC |
DELETE |
SYNCHRONIZE
);
public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000u;
public const UInt32 STANDARD_RIGHTS_READ = 0x00020000u;

public const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001u;
public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002u;
public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004u;
public const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000u;

public const UInt32 TOKEN_QUERY = 0x00000008;
public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x00000020;

public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x00000001u;
public const UInt32 TOKEN_DUPLICATE = 0x00000002u;
public const UInt32 TOKEN_IMPERSONATE = 0x00000004u;
public const UInt32 TOKEN_QUERY_SOURCE = 0x00000010u;
public const UInt32 TOKEN_ADJUST_GROUPS = 0x00000040u;
public const UInt32 TOKEN_ADJUST_DEFAULT = 0x00000080u;
public const UInt32 TOKEN_ADJUST_SESSIONID = 0x00000100u;
public const UInt32 TOKEN_READ = (
STANDARD_RIGHTS_READ |
TOKEN_QUERY
);
public const UInt32 TOKEN_ALL_ACCESS = (
STANDARD_RIGHTS_REQUIRED |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID
);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetCurrentProcess();

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetCurrentThread();

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLengthInBytes, IntPtr PreviousStateNull, IntPtr ReturnLengthInBytesNull);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool OpenThreadToken(IntPtr ThreadHandle, UInt32 DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);

[DllImport("ntdll.dll", EntryPoint = "RtlAdjustPrivilege")]
public static extern int RtlAdjustPrivilege(
UInt32 Privilege,
bool Enable,
bool CurrentThread,
ref bool Enabled
);

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);

//
//

private static LUID LookupPrivilege(string privilegeName)
{
LUID privilegeValue = new LUID();

bool res = LookupPrivilegeValue(null, privilegeName, out privilegeValue);

if (!res)
{
throw new Exception("Error: LookupPrivilegeValue()");
}

return privilegeValue;
}

//
//

public static void AdjustPrivilege(string privilegeName, bool enable)
{
IntPtr accessToken = IntPtr.Zero;
bool res = false;

try
{
LUID privilegeValue = LookupPrivilege(privilegeName);

res = OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, out accessToken);

if (!res)
{
res = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out accessToken);

if (!res)
{
throw new Exception("Error: OpenProcessToken()");
}
}

TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES();
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Luid = privilegeValue;

if (enable)
{
tokenPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
}
else
{
tokenPrivileges.Attributes = 0;
}

res = AdjustTokenPrivileges(accessToken, false, ref tokenPrivileges, (uint)System.Runtime.InteropServices.Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero);

if (!res)
{
throw new Exception("Error: AdjustTokenPrivileges()");
}
}

finally
{
if (accessToken != IntPtr.Zero)
{
CloseHandle(accessToken);
accessToken = IntPtr.Zero;
}
}
}
}
}
'@

if ([object]::Equals(('SystemPrivilege.Win32API.Privileges' -as [type]), $null)) {
Add-Type -TypeDefinition $win32api
}
}
includeSystemPrivileges;

$privileges|%{[SystemPrivilege.Win32API.Privileges]::AdjustPrivilege($_, $true)}

# Validation
whoami /priv|?{$_ -match "SeBackupPrivilege|SeRestorePrivilege"}
}
addSystemPrivilege;
################################## Excuting Program as an Administrator ####################################

function listDirectories{
param($parentDirectory,$depth,$excludes)
$directories=(dir $parentDirectory| Where {$_.PSIsContainer -eq $True}).FullName | ?{$excludeDirectories -notcontains $_};
$result=$directories;
for ($i=1;$i -lt $depth; $i++){
$directories=$directories|%{if($_ -ne $null){(dir $_| Where {$_.PSIsContainer -eq $True}).FullName}};
$result+=$directories;
}

function removeParents{
param($inputData)
$objectLength=$inputData.length;
$castedArrayList=[System.Collections.ArrayList]$inputData;

for ($i=0;$i -lt $objectLength; $i++){
$currentItem=$castedArrayList[$i];
$nextItem=$castedArrayList[$i+1];
$nextItemSplice=if($nextItem.length -ge $currentItem.length){try{$nextItem.Substring(0,$currentItem.length)}catch{}}

if($currentItem -like $nextItemSplice){
#write-host "Removing $inputData[$i]...";
$castedArrayList.RemoveAt($i);
#$inputData=[Array]$castedArrayList; #reverse the casting and reassign to original Array
$objectLength--;
$i--;
}
}
$outputData=[Array]$castedArrayList; #reverse the casting and reassign to original Array
return $outputData
}

#$result=removeParents -inputData $($result|sort)

return $result;
}

# Discover directories
$directories=listDirectories -parentDirectory $parentDirectory -depth $depth -excludes $excludeDirectories

#$driveLetterAndBlockSize=Get-CimInstance -ClassName Win32_Volume|Select-Object DriveLetter,BlockSize|?{$_.DriveLetter -ne $null -and $_.BlockSize -ne $null}

function getSizeOnDisk{
param (
[string]$path='.',
$clusterSizeOfNetworkShare=4096
)

# snippet to obtain size (not size on disk)
# ((gci –force $path –Recurse -ErrorAction SilentlyContinue| measure Length -s).sum) / 1G
# GCI $dir -Recurse | Group-Object -Property Directory | % {New-Object psobject -Property @{Name =$_.Name;Size = ($_.Group | ? {!($_.PSIsContainer)} | Measure-Object Length -Sum).Sum}} | Sort-Object -Property size -Descending

<# deprecated
$typeSource = @"
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;

namespace Win32
{
public class Disk {
[DllImport("kernel32.dll")]
static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
[Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

public static ulong GetSizeOnDisk(string filename)
{
uint HighOrderSize;
uint LowOrderSize;
ulong size;

FileInfo file = new FileInfo(filename);
LowOrderSize = GetCompressedFileSizeW(file.FullName, out HighOrderSize);

if (HighOrderSize == 0 && LowOrderSize == 0xffffffff)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else {
size = ((ulong)HighOrderSize << 32) + LowOrderSize;
return size;
}
}
}
}

"@

try{Add-Type -TypeDefinition $typeSource -ErrorAction SilentlyContinue}catch{}

$dir="C:\temp"
[Win32Functions.ExtendedFileInfo]::GetFileSizeOnDisk( $dir )

$totalSize=0;
#$count=0;
$driveLetter=(Get-Item $path).PSDrive.Root
$clusterSize=(Get-WmiObject -Class Win32_Volume | Where-Object {$_.Name -eq $driveLetter}).BlockSize
if(!($clusterSize)){$clusterSize=$clusterSizeOfNetworkShare}

Get-ChildItem $path -Recurse -Force -ErrorAction SilentlyContinue| where { !$_.PSisContainer }|`
% {
$size = $(try{[Win32.Disk]::GetSizeOnDisk([string]$_.FullName)}catch{})
#$AFZ=[MidpointRounding]::AwayFromZero
#$roundedSize=[math]::Round($size/$ClusterSize+0.5,$AFZ)*$ClusterSize
if ($size){
if ($size -gt $clusterSize){$roundedSize=[math]::ceiling($size/$clusterSize)*$clusterSize}
if ($size -le $clusterSize){$roundedSize=$clusterSize;}
}else{
$roundedSize=0;
}
$count+=1;
#"$count`. $($_.FullName): $size vs $roundedSize"
$totalSize+=$roundedSize;
}


#>

# this part is obtained from https://stackoverflow.com/questions/22507523/getting-size-on-disk-for-small-files-in-powershell
$typeSource = @'
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;

namespace Win32Functions
{
public class ExtendedFileInfo
{
public static long GetFileSizeOnDisk(string file)
{
FileInfo info = new FileInfo(file);
uint dummy, sectorsPerCluster, bytesPerSector;
int result = GetDiskFreeSpaceW(info.Directory.Root.FullName, out sectorsPerCluster, out bytesPerSector, out dummy, out dummy);
if (result == 0) throw new Win32Exception();
uint clusterSize = sectorsPerCluster * bytesPerSector;
uint hosize;
uint losize = GetCompressedFileSizeW(file, out hosize);
long size;
size = (long)hosize << 32 | losize;
return ((size + clusterSize - 1) / clusterSize) * clusterSize;
}

[DllImport("kernel32.dll")]
static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
[Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

[DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters,
out uint lpTotalNumberOfClusters);
}
}
'@
try{Add-Type -TypeDefinition $typeSource -ErrorAction SilentlyContinue}catch{}

$totalSize=0;
$files=Get-ChildItem $path -Recurse -Force -ErrorAction SilentlyContinue| where { !$_.PSisContainer }|select fullname
foreach ($file in $files){
$size=[Win32Functions.ExtendedFileInfo]::GetFileSizeOnDisk('$file');
$totalSize+=$size;
}
return $totalSize
}

# List top 10 files in all folders
function getLargestFiles{
param($folders,$limit)
$topFiles=$folders|%{get-childitem $_ -recurse}| ?{!$_.PSisContainer}|sort -Property Length -Descending | select-object -first $limit|select FullName,Length
return $topFiles
}
#$top10Files=getLargestFiles -folders $directories -limit $topX

function discoverFolderSizes{
param($folders)

function getFolderSize{
param($folder)
return (gci –force $folder –Recurse -ErrorAction SilentlyContinue| measure Length -s).sum
}

$folderAndSizes=$folders|select @{Name="Path";Expression={$_}},
@{Name="SizeOnDiskGb";Expression={[math]::round((getFolderSize $_)/1GB,2)}}`
|sort -Property SizeOnDiskGb -Descending
return $folderAndSizes;
}

# Find largest folder
# This code is currently unoptimized: needed to rewrite with roll-up size summarization between children and parent directories
$folderSizes=discoverFolderSizes -folders $directories
$topXFolders=$folderSizes|select-object -first 10|Format-Table -AutoSize

# List 10 biggest files in the biggest folder
$topXFiles=get-childitem $parentDirectory -recurse | ?{!$_.PSisContainer}|sort -Property Length -Descending | select-object -first $topX|`
select @{Name="Path";Expression={$_.FullName}},
@{Name="SizeOnDiskGb";Expression={[math]::round($_.Length/1GB,2)}}`
|Format-Table -AutoSize

# Display output
$topXFolders;
$topXFiles;
pause;

Sample Output

PS C:\Windows\system32> $topXFolders;

Path SizeOnDiskGb
---- ------------
C:\Users 251.27
C:\Users\flyingcows 97.57
C:\Users\Kim 92.48
C:\Users\Kim\Downloads 7.88
C:\Users\Public\Documents\Hyper-V 1.54
C:\Users\Public 1.54
C:\Users\Public\Documents 1.54
C:\Users\flyingcows\Ubiquiti UniFi 1.36
C:\Users\flyingcows\Ubiquiti UniFi\data 1.18
C:\Users\Kim\Desktop 0.49


PS C:\Windows\system32> $topXFiles;

Path SizeOnDiskGb
---- ------------
C:\Users\Kim\Downloads\CentOS-7-x86_64-DVD-1810.iso 4.27
C:\Windows\LiveKernelReports\NDIS-20191010-1931.dmp 1.72
C:\Users\Public\Documents\Hyper-V\Virtual hard disks\Nimble-Storage-Witness.vhdx 1.54
C:\Users\Kim\Downloads\CentOS-7-x86_64-Minimal-1908.iso 0.92
C:\Users\flyingcows\Ubiquiti UniFi\data\db\journal\j._2 0.81
C:\Users\Kim\Downloads\ophcrack-vista-livecd-3.6.0.iso 0.63
C:\Users\Kim\Downloads\CentOS-7-x86_64-Minimal-1511.iso 0.59
C:\Users\Kim\Downloads\CentOS-8-x86_64-1905-boot.iso 0.52
C:\Users\Kim\Downloads\CentOS-7-x86_64-NetInstall-1511.iso 0.37
C:\Program Files (x86)\Google\Chrome\Application\79.0.3945.79\Installer\chrome.7z 0.22