# listBigFilesAndFolders-v0.01.ps1

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

# Sanitize
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 getFolderSizes{
    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
$x=10
$topXFolders=getFolderSizes -folders $directories|select-object -first $x|Format-Table -AutoSize
write-host $topXFolders;

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

function getTopFiles($directory,$x=10){
    $files=Invoke-Expression -Command:"cmd.exe /C dir '$directory' /S /B /W /A:-D"|`
            select-object @{name="FileName";expression={$_}},@{name="FileSize";e={(gi $_).Length}}
    $topXFiles=$files| Sort-Object FileSize -Descending |select-object -first $x|Format-Table -AutoSize
    return $topXFiles
    }
getTopFiles $parentDirectory