【问题标题】:How to get the actual size-on-disk of a file from PowerShell?如何从 PowerShell 获取文件的实际磁盘大小?
【发布时间】:2010-10-07 22:17:39
【问题描述】:

我正在编写一个管理脚本,我需要计算磁盘上文件的大小。

这些文件位于压缩的 NTFS 卷上。

我不能使用FileInfo.Length,因为这是文件大小而不是磁盘大小。例如,如果我有一个 100MB 的文件,但由于 NTFS 压缩,它只使用了 25MB,我需要我的脚本返回 25MB。

有没有办法在 Powershell 中做到这一点?

(我知道GetCompressedFileSize() Win32 调用,但我希望这已经在某种程度上进行了包装。)

【问题讨论】:

  • 是否要包括由于未使用的集群空间而浪费的磁盘空间? (由于 MFT 中的内联,这对于小文件来说很棘手)或者只是压缩方面就足够了

标签: .net powershell


【解决方案1】:

(编辑)

我想出了如何为 Fileobject 动态添加一个属性(称为“脚本属性”),所以现在我可以使用语法:$theFileObject.CompressedSize 来读取大小。

(编辑结束)

阅读 Goyuix 的回复,我想“很酷,但 Powershell 中没有某种类型扩展功能吗?”。于是我发现了这个 Scott Hanselman 的帖子:http://www.hanselman.com/blog/MakingJunctionsReparsePointsVisibleInPowerShell.aspx

我为 FileInfo 对象创建了一个脚本属性:CompressedSize。

这是我所做的:(注意:我对 Powershell 还很陌生,或者至少我不怎么使用它。这可能会变得更好,但这是我所做的:

首先,我从 Goyuix 的帖子中编译了 Ntfs.ExtendedFileInfo。我将 DLL 放在我的 Powershell 配置文件目录 (Documents\WindowsPowershell)

接下来,我在我的配置文件目录中创建了一个名为 My.Types.ps1xml 的文件。

我将以下 XML 放入文件中:

<Types>
<Type>
    <Name>System.IO.FileInfo</Name>
    <Members>
        <ScriptProperty>
          <Name>CompressedSize</Name>
          <GetScriptBlock>
            [Ntfs.ExtendedFileInfo]::GetCompressedFileSize($this.FullName)
          </GetScriptBlock>
        </ScriptProperty>
    </Members>
</Type>
</Types>

该代码(一旦合并到类型系统中)将动态地将名为 CompressedSize 的属性添加到由 get-childitem/dir 返回的 FileInfo 对象。但是 Powershell 还不知道代码,也不知道我的 DLL。我们在下一步中处理它:

编辑 Profile.ps1。在同一目录中。现在,我的个人资料文件中恰好已经有一些东西,因为我安装了 Powershell 的社区扩展。希望我在下一个代码 sn-p 中包含了您需要的所有内容,以便它甚至可以在没有扩展的机器上工作。将以下代码添加到 Profile.ps1:

#This will load the ExtendedfileInfo assembly to enable the GetCompressedFileSize method.  this method is used by the
#PSCompressedSize Script Property attached to the FileInfo object.
$null = [System.Reflection.Assembly]::LoadFile("$ProfileDir\ntfs.extendedfileinfo.dll") 

#merge in my extended types
$profileTypes = $ProfileDir | join-path -childpath "My.Types.ps1xml"
Update-TypeData $profileTypes

现在,我引用的 $ProfileDir 变量已在前面的 Profile.ps1 脚本中定义。以防万一它不在你的,这里是定义:

$ProfileDir = split-path $MyInvocation.MyCommand.Path -Parent

就是这样。下次运行 Powershell 时,您可以访问 FileInfo 对象上的 CompressedSize 属性,就像它是任何其他属性一样。 示例:

$myFile = 目录 c:\temp\myfile.txt

$myFile.CompressedSize

这可行(无论如何在我的机器上),但我很想知道它是否符合最佳实践。我知道我做错了一件事:在 Profile.ps1 文件中,我将 LoadFile 的结果返回到我不会使用的变量中 ($null = blah blah)。我这样做是为了抑制将加载文件的结果显示到控制台。可能有更好的方法。

【讨论】:

    【解决方案2】:

    加载托管 Windows API (http://mwinapi.sourceforge.net/) 并查看 ExtendedFileInfo 类。有一种方法GetPhysicalFileSize() 将返回文件在磁盘上所需的大小。

    public static ulong GetPhysicalFileSize(string filename)
    

    或者,您可以编译自己的 DLL 并为该函数加载程序集:

    using System;
    using System.Runtime.InteropServices;
    
    namespace NTFS {
      public class ExtendedFileInfo
      {
        [DllImport("kernel32.dll", SetLastError=true, EntryPoint="GetCompressedFileSize")]
        static extern uint GetCompressedFileSizeAPI(string lpFileName, out uint lpFileSizeHigh);
        public static ulong GetCompressedFileSize(string filename)
        {
          uint high;
          uint low;
          low = GetCompressedFileSizeAPI(filename, out high);
          int error = Marshal.GetLastWin32Error();
          if (high == 0 && low == 0xFFFFFFFF && error != 0)
          {
            throw new System.ComponentModel.Win32Exception(error);
          }
          else
          {
            return ((ulong)high << 32) + low;
          }
        }
      }
    }
    

    然后编译:

    csc /target:library /out:ntfs.extendedfileinfo.dll ntfs.extendedfileinfo.cs 
    

    最后,在 PowerShell 中加载和运行:

    PS C:\> [System.Reflection.Assembly]::LoadFile("C:\ntfs.extendedfileinfo.dll")
    PS C:\> [NTFS.ExtendedFileInfo]::GetCompressedFileSize("C:\sample.txt")
    

    【讨论】:

    • 如果您希望它与所有(Unicode 编码的)文件名兼容,您需要使用 GetCompressedFileSizeW:[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "GetCompressedFileSizeW")] static extern uint GetCompressedFileSizeAPI([MarshalAs(UnmanagedType.LPWStr)] string lpFileName, out uint lpFileSizeHigh);
    【解决方案3】:

    使用 V2 Add-Type 和 Pinvoke.NET 轻松实现:

    add-type -type  @'
    using System;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    
    namespace Win32Functions
    {
        public class ExtendedFileInfo
        {
            [DllImport("kernel32.dll", SetLastError=true, EntryPoint="GetCompressedFileSize")]
            static extern uint GetCompressedFileSizeAPI(string lpFileName, out uint lpFileSizeHigh);
    
            public static ulong GetCompressedFileSize(string filename)
            {
                uint high;
                uint low;
                low = GetCompressedFileSizeAPI(filename, out high);
                int error = Marshal.GetLastWin32Error();
                if (high == 0 && low == 0xFFFFFFFF && error != 0)
                throw new Win32Exception(error);
                else
                return ((ulong)high << 32) + low;
            }
        }
    }
    '@
    
    [Win32Functions.ExtendedFileInfo]::GetCompressedFileSize( "C:\autoexec.bat")
    

    实验!享受!参与!

    杰弗里·斯诺弗 [MSFT] Windows 管理合作伙伴架构师 访问 Windows PowerShell 团队博客:http://blogs.msdn.com/PowerShell 访问 Windows PowerShell 脚本中心:http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

    【讨论】:

    • 我喜欢这个解决方案的简单性......但对我来说,它总是返回与“长度”相同的值,即使它应该不同。这是 Win32 API 调用的限制吗?
    • @ewall 这是一篇旧帖子,但要调用的正确 API 在这里:stackoverflow.com/a/22508299/520612
    【解决方案4】:

    如果找不到喜欢的托管 API,在 PowerShell V2 中,P/Invoke Win32 API 会容易得多。阅读PowerShell P/Invoke Walkthrough 获取说明。

    【讨论】:

      【解决方案5】:

      请注意,这不会返回 Windows 资源管理器显示的“磁盘大小”,尤其是。对于小文件。

      Getting "size on disk" for small files in Powershell 描述了获取该信息的(几乎)正确方法

      【讨论】:

        【解决方案6】:

        $s=(compact /q C:\whatever.dat | where-object {$_.contains('total bytes')}).split()};$s[8].padleft(20)+ $s[0].padleft(20)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-10-17
          • 2011-05-15
          • 2018-12-04
          • 2011-04-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多