2009-02-16 5 views
8

J'écris un script administratif, et j'ai besoin de calculer la taille des fichiers sur le disque.Comment obtenir la taille réelle sur disque d'un fichier à partir de PowerShell?

Ces fichiers se trouvent sur un volume NTFS compressé.

Je ne peux pas utiliser FileInfo.Length, parce que c'est la taille du fichier et pas la taille sur le disque. Par exemple, si j'ai un fichier de 100 Mo, mais qu'il n'utilise que 25 Mo à cause de la compression NTFS, j'ai besoin que mon script renvoie 25 Mo.

Existe-t-il un moyen de le faire dans Powershell?

(je sais sur le GetCompressedFileSize() appel Win32, mais j'espérais que cela est déjà wrappered à un certain niveau.)

+0

Voulez-vous inclure l'espace sur le disque gaspillé en raison d'espace de cluster utilisé? (c'est délicat avec de petits fichiers en raison de l'inlining dans le MFT) ou est juste l'aspect compressé suffisant – ShuggyCoUk

Répondre

9

(modifier) ​​

je me suis dit comment ajouter dynamiquement une propriété (appelée « propriété de script ») à la FileObject, maintenant, je peux utiliser la syntaxe: theFileObject.CompressedSize $ pour lire la taille.

(fin modifier) ​​

de réponse de Lire Goyuix, et je pensais que « Cool, mais il est pas là une sorte de capacité de type extension Powershell? ». Alors j'ai trouvé ce message Scott Hanselman: http://www.hanselman.com/blog/MakingJunctionsReparsePointsVisibleInPowerShell.aspx

Et j'ai créé une propriété de script pour l'objet FileInfo: CompressedSize.

Voici ce que je l'ai fait: (note: Je suis tout à fait nouveau pour Powershell, ou du moins je ne l'utilise pas beaucoup ce qui pourrait probablement être beaucoup mieux, mais voici ce que je l'ai fait.

Tout d'abord, je compila les Ntfs.ExtendedFileInfo du poste de Goyuix. Je mets la DLL dans mon répertoire de profil Powershell (Documents \ windowspowershell)

Ensuite, je créé un fichier dans mon répertoire de profil nommé My.Types.ps1xml.

Je mets le code XML suivant dans le fichier:

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

Ce code (une fois fusionné dans le système de type) ajoutera dynamiquement une propriété nommée CompressedSize aux objets FileInfo renvoyés par get-childitem/dir.Mais Powershell ne connaît pas encore le code, et il ne connaît pas encore ma DLL. Nous traitons cela à l'étape suivante:

Modifier le profil.ps1. dans le même répertoire. Maintenant, mon fichier de profil contient déjà des éléments car les Extensions de communauté pour PowerShell sont installées. Heureusement, j'inclus tout ce dont vous avez besoin dans cet extrait de code suivant, afin qu'il fonctionne même sur une machine qui n'a pas les extensions. Ajoutez le code suivant à 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 

Maintenant, la variable $ ProfileDir que je référence est défini plus tôt dans mon script Profile.ps1. Juste au cas où ce n'est pas dans le vôtre, voici la définition:

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

C'est tout. La prochaine fois que vous exécutez Powershell, vous pouvez accéder à la propriété CompressedSize sur l'objet FileInfo comme s'il s'agissait d'une autre propriété. Exemple:

$ myFile = dir c: \ temp \ myfile.txt

$ myFile.CompressedSize

Cela fonctionne (sur ma machine, de toute façon), mais j'aimerais entendre si elle correspond avec les meilleures pratiques. Une chose que je sais que je fais mal: dans le fichier Profile.ps1, je retourne les résultats de LoadFile dans une variable que je ne vais pas utiliser ($ null = bla bla). Je l'ai fait pour supprimer l'affichage du résultat du fichier de chargement à la console. Il y a probablement une meilleure façon de le faire.

8

charge l'API Windows (Managed http://mwinapi.sourceforge.net/) et vérifier la classe ExtendedFileInfo. Il y a une méthode GetPhysicalFileSize() qui retournera la taille qu'un fichier nécessite sur le disque.

public static ulong GetPhysicalFileSize(string filename) 

Aternatively, vous pouvez compiler votre propre DLL et charger l'ensemble pour cette seule fonction:

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; 
     } 
    } 
    } 
} 

Puis compilez:

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

Et enfin, pour charger et exécuter en PowerShell:

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

Si vous ne trouvez pas l'API gérée que vous aimez, dans PowerShell V2, il est beaucoup plus facile d'invoquer une API Win32. Lisez PowerShell P/Invoke Walkthrough pour les instructions.

5

Facile à faire en utilisant V2 Add-Type et 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") 

expérience! Prendre plaisir! Engager!

Jeffrey Snover [MSFT] Windows Management Partner Architecte Visitez le blog de l'équipe Windows PowerShell à: http://blogs.msdn.com/PowerShell Visitez le Windows PowerShell ScriptCenter à: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

+0

J'aime la simplicité de cette solution ... mais pour moi il revient toujours la même valeur que la "longueur", même quand il devrait être différent. Est-ce une limitation de l'appel de l'API Win32? – ewall

+1

@ewall Ceci est un ancien post mais l'API correcte à appeler est ici: http://stackoverflow.com/a/22508299/520612 –

0

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

Questions connexes