获得"磁盘大小"对于Powershell中的小文件

时间:2014-03-19 13:32:37

标签: powershell

我正在使用从外部系统进行大量导入的遗留系统,其中大部分都是通过下载文件(根据上下文大小不同),处理它然后将文件存储在SAN卷上的其他位置来实现的。 (格式化为NTFS并安装在WS2008R2盒上)。我们遇到的问题是,由于群集大小,大量小文件最终会浪费大量磁盘空间。

理想情况下,我们会找到最糟糕的导入过程并将文件自动存档到.zip文件或类似文件中。建立一个报告应该是一个相对简单的问题,但我很难在磁盘上获得准确的大小" (如资源管理器中所示)。 (是的,我们可以在X天之后存档所有内容,但它并不理想,并且不一定有助于调整可以在某种程度上进行调整以避免问题的导入过程)

我已经看到了像How to get the actual size-on-disk of a file from PowerShell?这样的答案,但是虽然它们处理压缩文件夹的效果很好,但我得到的文件长度与短文件相同,因此低估了真正的磁盘使用率。

卷上的文件从一些小到足以适应MFT记录,一些只占集群的一小部分而另一些非常大。 NTFS压缩不能在卷上的任何位置启用,尽管可以适应的解决方案将来会更加面向未来,因为我们将来可能会启用它。通常通过UNC共享来访问卷,因此如果可以通过共享确定使用情况(资源管理器似乎能够),这将是很好的,但它并不重要,因为脚本始终可以在服务器本身并直接访问驱动器。

2 个答案:

答案 0 :(得分:4)

你需要一点P / invoke:

add-type -type  @'
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);  
  }
}
'@

像这样使用:

[Win32Functions.ExtendedFileInfo]::GetFileSizeOnDisk( 'C:\ps\examplefile.exe' )
59580416

它返回您在explore中的属性文件中读取的'磁盘大小'。

答案 1 :(得分:0)

通过上面的答案(通过CB),我发现返回的大小总是4127(显然基于我的簇大小 - 4096)高于磁盘上的正确大小或高于实际大小4127。如果它高于实际大小,我测试的文件要么是磁盘上的0字节,要么是磁盘上的大小大于实际大小。

这里可能有一些东西,因为我首先将它转换为VB.Net(使用MindFusion.eu/Code Converter,它提供了有效的代码),但我对此表示怀疑。我在其他一些答案中看到了<< 32这段代码,我不知道为什么会这样,我发现该函数总是返回不正确的值,除非我把它拿出来。

我还发现UInteger.MaxValue(4294967295)上面的文件大小不正确,我还想出了如何在下面的代码中准确获取。这要求我提高可变大小(UInteger和Long to Double)。

我使用以下代码来获得最准确的答案,如果不正确,返回的大小将与实际大小完全相同,如果文件在磁盘上为0字节或大小,则会发生这种情况在磁盘上更大:

Imports System
Imports System.Runtime.InteropServices

Namespace Win32Functions
    Public Class ExtendedFileInfo
        Public Shared Function GetFileSizeOnDisk(file As String) As Double
            Dim hosize As UInteger
            Dim losize As UInteger = GetCompressedFileSizeW(file, hosize)
            Dim size As Double = (UInteger.MaxValue + 1) * hosize + losize
            Return size
        End Function

        <DllImport("kernel32.dll")> _
        Private Shared Function GetCompressedFileSizeW(<[In], MarshalAs(UnmanagedType.LPWStr)> lpFileName As String, <Out, MarshalAs(UnmanagedType.U4)> ByRef lpFileSizeHigh As UInteger) As UInteger
        End Function
    End Class
End Namespace

Converted to C#

using System;
using System.Runtime.InteropServices;

namespace Win32Functions
{
    public class ExtendedFileInfo
    {
        public static double GetFileSizeOnDisk(string file)
        {
            uint hosize;
            uint losize = GetCompressedFileSizeW(file, out hosize);
            double size = (uint.MaxValue + 1) * hosize + losize;
            return size;
        }

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

(请注意,我还没有在C#中对此进行测试,我无法100%确定它的工作原理相同)