# encoding: ascii
# api: csharp
# title: Get-FileAllocation
# description: How to obtain file fragmentation data via PowerShell? That’s answer.
# version: 0.1
# type: function
# author: skourlatov
# license: CC0
# function: Get-FileAllocation
# x-poshcode-id: 5398
# x-archived: 2015-01-31T20:32:52
# x-published: 2015-09-04T02:36:00
#
#
Function Get-FileAllocation
{
    param
    (
        [parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)]
        [string]$FilePath
    )

    try
    {
        $file = $FilePath | Get-Item -Force -ea 'Stop'
    }
    catch
    {
        throw "Invalid path"
    }

    if (($file.Attributes -band 0x0400) -eq 0x0400) ## is reparse point
    {
        throw "The file is a reparse point"
    }
    return [PoshCode.FileSystem]::GetFileAllocation($file.FullName)
}

Add-Type -TypeDefinition @"
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace PoshCode
{
    public class FileSystem
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern SafeFileHandle CreateFile(
                                          string lpFileName,
            [MarshalAs(UnmanagedType.U4)] uint dwDesiredAccess,
            [MarshalAs(UnmanagedType.U4)] uint dwShareMode,
                                          IntPtr lpSecurityAttributes,
            [MarshalAs(UnmanagedType.U4)] uint dwCreationDisposition,
            [MarshalAs(UnmanagedType.U4)] uint dwFlagsAndAttributes,
                                          IntPtr hTemplateFile);

        private const int FSCTL_GET_RETRIEVAL_POINTERS = 0x00090073;
        private const uint FileAccess_GenericRead  = 0x80000000;
        private const uint FileAccess_GenericWrite = 0x40000000;
        private const uint FileShare_Read          = 0x00000001;
        private const uint FileShare_Write         = 0x00000002;
        private const uint FileShare_ReadWrite     = FileShare_Read | FileShare_Write;
        private const uint FileMode_OpenExisting   = 0x00000003;
        private const uint FileAttributes_Normal   = 0x00000080;

        private static SafeFileHandle GetSafeHandle(string path, bool allowrite)
        {
            var PreferredAccessMode = FileAccess_GenericRead;
            if (allowrite)
                PreferredAccessMode |= FileAccess_GenericWrite;
            return CreateFile(path, PreferredAccessMode, FileShare_ReadWrite, IntPtr.Zero,
                FileMode_OpenExisting, FileAttributes_Normal, IntPtr.Zero);
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct StartingVcnInputBuffer
        {
            public static readonly int Size;
            static StartingVcnInputBuffer() { Size = Marshal.SizeOf(typeof(StartingVcnInputBuffer)); }
            public long StartingVcn;
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct RetrievalPointersBuffer
        {
            public static readonly int Size;
            static RetrievalPointersBuffer() { Size = Marshal.SizeOf(typeof(RetrievalPointersBuffer)); }
            public int ExtentCount;
            public long StartingVcn;
            // Extents
            public long NextVcn;
            public long Lcn;
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct PhysicalFileFragment
        {
            public long Fragment;
            public long StartCluster;
            public long Length;
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct Win32FileAllocationData
        {
            public uint TotalClusters;
            public uint TotalFragments;
            public List<PhysicalFileFragment> PhysicalAllocation;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hFile,
            uint ioctl,
            ref StartingVcnInputBuffer invalue,
            int InSize,
            out RetrievalPointersBuffer outvalue,
            int OutSize,
            int BytesReturned,
            IntPtr zerovalue
        );

        private const int ERROR_HANDLE_EOF = 0x00000026;
        private const int ERROR_MORE_DATA  = 0x000000EA;
        private const int NO_ERROR         = 0x00000000;
        private static int lpBytesReturned = 0x00000000;

        public static Win32FileAllocationData GetFileAllocation(string path)
        {
            var vcnIn    = new StartingVcnInputBuffer();
            var rpbOut    = new RetrievalPointersBuffer();
            var alloc    = new Win32FileAllocationData();
            var frag    = new PhysicalFileFragment();
            long fragLength;
            bool doInc        = false;
            bool newFrag    = false;
            int extentNumber = 0;
            int ERR;

             vcnIn.StartingVcn    = 0L;
            alloc.TotalClusters    = 0;
            alloc.PhysicalAllocation = new List<PhysicalFileFragment>();
 
            // file open
            using (SafeFileHandle handle = GetSafeHandle(path, false))
            {    do
                {
                    DeviceIoControl(
                        handle, FSCTL_GET_RETRIEVAL_POINTERS,
                        ref vcnIn, StartingVcnInputBuffer.Size, 
                        out rpbOut, RetrievalPointersBuffer.Size,
                        lpBytesReturned, IntPtr.Zero
                    );
 
                    ERR = Marshal.GetLastWin32Error();
                     switch (ERR)
                    {
                        case ERROR_HANDLE_EOF:
                            break;
                        case NO_ERROR:
                            doInc = true;
                            break;
                        case ERROR_MORE_DATA:
                            doInc = true;
                            vcnIn.StartingVcn = rpbOut.NextVcn;
                            break;
                        default:
                            throw new Win32Exception(ERR);
                    }

                    if (doInc && rpbOut.Lcn >= 0) // Some files may have dummy "pieces" - reject them
                    {
                        fragLength = rpbOut.NextVcn - rpbOut.StartingVcn;
                        alloc.TotalClusters += (uint)fragLength;

                        if (extentNumber == 0) // Process started - getting a new fragment
                        {
                            newFrag = true;
                        }
                        else
                        {
                            if (frag.StartCluster + frag.Length == rpbOut.Lcn) // No new fragment - "lengthen" existing
                            {
                                frag.Length += fragLength;
                            }
                            else // There is a new fragment - adding existing to the list and getting new one
                            {
                                alloc.PhysicalAllocation.Add(frag);
                                newFrag = true;
                            }
                        }

                        if (newFrag) // Getting new fragment
                        {
                            frag.Fragment = extentNumber++;
                            frag.StartCluster = rpbOut.Lcn;
                            frag.Length = fragLength;
                            newFrag = false;
                        }

                        doInc = false;
                    }

                } while (ERR == ERROR_MORE_DATA);

                if (frag.Length > 0)
                    alloc.PhysicalAllocation.Add(frag); // Process ended - adding the last fragment to the list
                alloc.TotalFragments = (uint)alloc.PhysicalAllocation.Count;
            }
            // file close

            return alloc;
        }
    }
}
"@
