2016-02-17 22 views
14

In Word/Excel è necessario aggiungere proprietà personalizzate. (Vedi immagine) Custom Properties. Come potete vedere, c'è il campo: "Proprietà:", è possibile aggiungere qualsiasi informazione che si desidera lì. Quando si salva il file e si accede al percorso del file nella cartella, è possibile fare clic con il tasto destro del mouse -> Proprietà e si dispone di tutte le schede: Generale/Sicurezza/Dettagli/Versioni precedenti. con la funzione aggiungi la scheda Personalizzata.Estrazione delle Proprietà file di Windows (Proprietà personalizzate) C#

Ora voglio ottenere queste informazioni tramite la codifica: Custom Properties information. ed estrailo più tardi al blocco note. Finora ho usato il Shell32 ma poi ho solo le informazioni contenute nella scheda Dettagli. Ho fatto qualche ricerca e ho visto alcune possibilità con DSOfile.dll. Ma voglio sapere se c'è la possibilità di farlo senza installare altre DLL? Questo è il mio codice finora con Shell32.

 static void Main(string[] args) 
    { 

     //using (StreamWriter writer = new StreamWriter(@"filepathhere")) 
     //{ 
      //Console.SetOut(writer); 
      ReadProperties(); 
     //} 
    } 
    static void ReadProperties() 
    { 
     List<string> arrHeaders = new List<string>(); 
     Shell shell = new Shell(); 
     Folder objFolder = shell.NameSpace(@"filepathhere"); 
     FolderItem objFolderItem = objFolder.ParseName("filehere.doc"); 

     for (int i = 0; i < short.MaxValue; i++) 
     { 
      string header = objFolder.GetDetailsOf(objFolder, i); 
      if (String.IsNullOrEmpty(header)) 
       break; 
      arrHeaders.Add(header); 
     } 

     for (int i = 0; i < arrHeaders.Count; i++) 
     { 
      Console.WriteLine("{0}\t{1}: {2}", i, arrHeaders[i], objFolder.GetDetailsOf(objFolderItem, i)); 
     } 
     Console.ReadKey(); 
    } 

Grazie in anticipo!

Desu

+0

Che cosa si intende per - l'installazione di altre DLL? Perché qualunque dll che utilizzerai sarà presente in .NetFramework preinstallato. C'è qualche dll che hai in mente al di fuori delle .NetFramework dlls – CarbineCoder

+0

@CarbineCoder come DSOfile.dll, va bene se riesco a installare ogni .dll con il gestore pacchetti NuGet in Visual Studio. Ma con DSOfile.dll non è possibile installarlo con il gestore. – Desutoroiya

+0

Mi dispiace, non sono a conoscenza se questo. Grazie per aver chiarito – CarbineCoder

risposta

8

Storicamente queste proprietà sono state definite dalla tecnologia denominata "Archiviazione strutturata". La prima implementazione di Archiviazione strutturata è l'antica (ma ancora molto viva) Compound File Format

Successivamente, Microsoft ha aggiunto funzionalità di storage strutturato directly into NTFS. Ciò consente di definire proprietà come autore o titolo su qualsiasi file (anche .txt). Sebbene l'interfaccia utente di Explorer non ti consenta più di farlo per qualche motivo, penso che funzioni ancora a livello di programmazione.

E poi, con Vista, Microsoft ha riavviato tutto ciò e ha introdotto un superset di tutto questo: lo Windows Property System.

Sfortunatamente, non ci sono API .NET nel framework per tutto questo. Ma Microsoft ha creato una libreria .NET open source chiamata CodePack API di Windows. Quindi, il modo più semplice per voi per estrarre le proprietà è quello di aggiungere un riferimento al WindowsAPICodePack Nucleo NugetPackage e si può usare in questo modo:

static void Main(string[] args) 
{ 
    foreach (var prop in new ShellPropertyCollection(@"mypath\myfile")) 
    { 
     Console.WriteLine(prop.CanonicalName + "=" + prop.ValueAsObject); 
    } 
} 

Se non si desidera aggiungere DLL in più, allora si può estrarre il codice ShellPropertyCollection dall'origine WindowsAPICodePack (Windows API Code Pack: Where is it?). È piuttosto un lavoro ma fattibile.

Un'altra soluzione nel tuo caso è utilizzare la vecchia API nativa di Structure Storage. Ho fornito un esempio che lo fa. Ecco come è possibile utilizzarlo:

static void Main(string[] args) 
{ 
    foreach (var prop in new StructuredStorage(@"mypath\myfile").Properties) 
    { 
     Console.WriteLine(prop.Name + "=" + prop.Value); 
    }    
} 

public sealed class StructuredStorage 
{ 
    public static readonly Guid SummaryInformationFormatId = new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"); 
    public static readonly Guid DocSummaryInformationFormatId = new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"); 
    public static readonly Guid UserDefinedPropertiesId = new Guid("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"); 

    private List<StructuredProperty> _properties = new List<StructuredProperty>(); 

    public StructuredStorage(string filePath) 
    { 
     if (filePath == null) 
      throw new ArgumentNullException("filePath"); 

     FilePath = filePath; 
     IPropertySetStorage propertySetStorage; 
     int hr = StgOpenStorageEx(FilePath, STGM.STGM_READ | STGM.STGM_SHARE_DENY_NONE | STGM.STGM_DIRECT_SWMR, STGFMT.STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, typeof(IPropertySetStorage).GUID, out propertySetStorage); 
     if (hr == STG_E_FILENOTFOUND || hr == STG_E_PATHNOTFOUND) 
      throw new FileNotFoundException(null, FilePath); 

     if (hr != 0) 
      throw new Win32Exception(hr); 

     try 
     { 
      LoadPropertySet(propertySetStorage, SummaryInformationFormatId); 
      LoadPropertySet(propertySetStorage, DocSummaryInformationFormatId); 
     } 
     finally 
     { 
      Marshal.ReleaseComObject(propertySetStorage); 
     } 

     // for some reason we can't read this one on the same COM ref? 
     LoadProperties(UserDefinedPropertiesId); 
    } 

    public string FilePath { get; private set; } 
    public IReadOnlyList<StructuredProperty> Properties 
    { 
     get 
     { 
      return _properties; 
     } 
    } 

    private void LoadPropertySet(IPropertySetStorage propertySetStorage, Guid fmtid) 
    { 
     IPropertyStorage propertyStorage; 
     int hr = propertySetStorage.Open(fmtid, STGM.STGM_READ | STGM.STGM_SHARE_EXCLUSIVE, out propertyStorage); 
     if (hr == STG_E_FILENOTFOUND || hr == STG_E_ACCESSDENIED) 
      return; 

     if (hr != 0) 
      throw new Win32Exception(hr); 

     IEnumSTATPROPSTG es; 
     propertyStorage.Enum(out es); 
     if (es == null) 
      return; 

     try 
     { 
      var stg = new STATPROPSTG(); 
      int fetched; 
      do 
      { 
       hr = es.Next(1, ref stg, out fetched); 
       if (hr != 0 && hr != 1) 
        throw new Win32Exception(hr); 

       if (fetched == 1) 
       { 
        string name = GetPropertyName(fmtid, propertyStorage, stg); 

        var propsec = new PROPSPEC[1]; 
        propsec[0] = new PROPSPEC(); 
        propsec[0].ulKind = stg.lpwstrName != null ? PRSPEC.PRSPEC_LPWSTR : PRSPEC.PRSPEC_PROPID; 
        IntPtr lpwstr = IntPtr.Zero; 
        if (stg.lpwstrName != null) 
        { 
         lpwstr = Marshal.StringToCoTaskMemUni(stg.lpwstrName); 
         propsec[0].union.lpwstr = lpwstr; 
        } 
        else 
        { 
         propsec[0].union.propid = stg.propid; 
        } 

        var vars = new PROPVARIANT[1]; 
        vars[0] = new PROPVARIANT(); 
        try 
        { 
         hr = propertyStorage.ReadMultiple(1, propsec, vars); 
         if (hr != 0) 
          throw new Win32Exception(hr); 

        } 
        finally 
        { 
         if (lpwstr != IntPtr.Zero) 
         { 
          Marshal.FreeCoTaskMem(lpwstr); 
         } 
        } 

        object value; 
        try 
        { 
         switch (vars[0].vt) 
         { 
          case VARTYPE.VT_BOOL: 
           value = vars[0].union.boolVal != 0 ? true : false; 
           break; 

          case VARTYPE.VT_BSTR: 
           value = Marshal.PtrToStringUni(vars[0].union.bstrVal); 
           break; 

          case VARTYPE.VT_CY: 
           value = decimal.FromOACurrency(vars[0].union.cyVal); 
           break; 

          case VARTYPE.VT_DATE: 
           value = DateTime.FromOADate(vars[0].union.date); 
           break; 

          case VARTYPE.VT_DECIMAL: 
           IntPtr dec = IntPtr.Zero; 
           Marshal.StructureToPtr(vars[0], dec, false); 
           value = Marshal.PtrToStructure(dec, typeof(decimal)); 
           break; 

          case VARTYPE.VT_DISPATCH: 
           value = Marshal.GetObjectForIUnknown(vars[0].union.pdispVal); 
           break; 

          case VARTYPE.VT_ERROR: 
          case VARTYPE.VT_HRESULT: 
           value = vars[0].union.scode; 
           break; 

          case VARTYPE.VT_FILETIME: 
           value = DateTime.FromFileTime(vars[0].union.filetime); 
           break; 

          case VARTYPE.VT_I1: 
           value = vars[0].union.cVal; 
           break; 

          case VARTYPE.VT_I2: 
           value = vars[0].union.iVal; 
           break; 

          case VARTYPE.VT_I4: 
           value = vars[0].union.lVal; 
           break; 

          case VARTYPE.VT_I8: 
           value = vars[0].union.hVal; 
           break; 

          case VARTYPE.VT_INT: 
           value = vars[0].union.intVal; 
           break; 

          case VARTYPE.VT_LPSTR: 
           value = Marshal.PtrToStringAnsi(vars[0].union.pszVal); 
           break; 

          case VARTYPE.VT_LPWSTR: 
           value = Marshal.PtrToStringUni(vars[0].union.pwszVal); 
           break; 

          case VARTYPE.VT_R4: 
           value = vars[0].union.fltVal; 
           break; 

          case VARTYPE.VT_R8: 
           value = vars[0].union.dblVal; 
           break; 

          case VARTYPE.VT_UI1: 
           value = vars[0].union.bVal; 
           break; 

          case VARTYPE.VT_UI2: 
           value = vars[0].union.uiVal; 
           break; 

          case VARTYPE.VT_UI4: 
           value = vars[0].union.ulVal; 
           break; 

          case VARTYPE.VT_UI8: 
           value = vars[0].union.uhVal; 
           break; 

          case VARTYPE.VT_UINT: 
           value = vars[0].union.uintVal; 
           break; 

          case VARTYPE.VT_UNKNOWN: 
           value = Marshal.GetObjectForIUnknown(vars[0].union.punkVal); 
           break; 

          default: 
           value = null; 
           break; 
         } 
        } 
        finally 
        { 
         PropVariantClear(ref vars[0]); 
        } 

        var property = new StructuredProperty(fmtid, name, stg.propid); 
        property.Value = value; 
        _properties.Add(property); 
       } 
      } 
      while (fetched == 1); 
     } 
     finally 
     { 
      Marshal.ReleaseComObject(es); 
     } 
    } 

    private static string GetPropertyName(Guid fmtid, IPropertyStorage propertyStorage, STATPROPSTG stg) 
    { 
     if (!string.IsNullOrEmpty(stg.lpwstrName)) 
      return stg.lpwstrName; 

     var propids = new int[1]; 
     propids[0] = stg.propid; 
     var names = new string[1]; 
     names[0] = null; 
     int hr = propertyStorage.ReadPropertyNames(1, propids, names); 
     if (hr == 0) 
      return names[0]; 

     return null; 
    } 

    public void LoadProperties(Guid formatId) 
    { 
     IPropertySetStorage propertySetStorage; 
     int hr = StgOpenStorageEx(FilePath, STGM.STGM_READ | STGM.STGM_SHARE_DENY_NONE | STGM.STGM_DIRECT_SWMR, STGFMT.STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, typeof(IPropertySetStorage).GUID, out propertySetStorage); 
     if (hr == STG_E_FILENOTFOUND || hr == STG_E_PATHNOTFOUND) 
      throw new FileNotFoundException(null, FilePath); 

     if (hr != 0) 
      throw new Win32Exception(hr); 

     try 
     { 
      LoadPropertySet(propertySetStorage, formatId); 
     } 
     finally 
     { 
      Marshal.ReleaseComObject(propertySetStorage); 
     } 
    } 

    private const int STG_E_FILENOTFOUND = unchecked((int)0x80030002); 
    private const int STG_E_PATHNOTFOUND = unchecked((int)0x80030003); 
    private const int STG_E_ACCESSDENIED = unchecked((int)0x80030005); 

    private enum PRSPEC 
    { 
     PRSPEC_LPWSTR = 0, 
     PRSPEC_PROPID = 1 
    } 

    private enum STGFMT 
    { 
     STGFMT_ANY = 4, 
    } 

    [Flags] 
    private enum STGM 
    { 
     STGM_READ = 0x00000000, 
     STGM_READWRITE = 0x00000002, 
     STGM_SHARE_DENY_NONE = 0x00000040, 
     STGM_SHARE_DENY_WRITE = 0x00000020, 
     STGM_SHARE_EXCLUSIVE = 0x00000010, 
     STGM_DIRECT_SWMR = 0x00400000 
    } 

    // we only define what we handle 
    private enum VARTYPE : short 
    { 
     VT_I2 = 2, 
     VT_I4 = 3, 
     VT_R4 = 4, 
     VT_R8 = 5, 
     VT_CY = 6, 
     VT_DATE = 7, 
     VT_BSTR = 8, 
     VT_DISPATCH = 9, 
     VT_ERROR = 10, 
     VT_BOOL = 11, 
     VT_UNKNOWN = 13, 
     VT_DECIMAL = 14, 
     VT_I1 = 16, 
     VT_UI1 = 17, 
     VT_UI2 = 18, 
     VT_UI4 = 19, 
     VT_I8 = 20, 
     VT_UI8 = 21, 
     VT_INT = 22, 
     VT_UINT = 23, 
     VT_HRESULT = 25, 
     VT_LPSTR = 30, 
     VT_LPWSTR = 31, 
     VT_FILETIME = 64, 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    private struct PROPVARIANTunion 
    { 
     [FieldOffset(0)] 
     public sbyte cVal; 
     [FieldOffset(0)] 
     public byte bVal; 
     [FieldOffset(0)] 
     public short iVal; 
     [FieldOffset(0)] 
     public ushort uiVal; 
     [FieldOffset(0)] 
     public int lVal; 
     [FieldOffset(0)] 
     public uint ulVal; 
     [FieldOffset(0)] 
     public int intVal; 
     [FieldOffset(0)] 
     public uint uintVal; 
     [FieldOffset(0)] 
     public long hVal; 
     [FieldOffset(0)] 
     public ulong uhVal; 
     [FieldOffset(0)] 
     public float fltVal; 
     [FieldOffset(0)] 
     public double dblVal; 
     [FieldOffset(0)] 
     public short boolVal; 
     [FieldOffset(0)] 
     public int scode; 
     [FieldOffset(0)] 
     public long cyVal; 
     [FieldOffset(0)] 
     public double date; 
     [FieldOffset(0)] 
     public long filetime; 
     [FieldOffset(0)] 
     public IntPtr bstrVal; 
     [FieldOffset(0)] 
     public IntPtr pszVal; 
     [FieldOffset(0)] 
     public IntPtr pwszVal; 
     [FieldOffset(0)] 
     public IntPtr punkVal; 
     [FieldOffset(0)] 
     public IntPtr pdispVal; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct PROPSPEC 
    { 
     public PRSPEC ulKind; 
     public PROPSPECunion union; 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    private struct PROPSPECunion 
    { 
     [FieldOffset(0)] 
     public int propid; 
     [FieldOffset(0)] 
     public IntPtr lpwstr; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct PROPVARIANT 
    { 
     public VARTYPE vt; 
     public ushort wReserved1; 
     public ushort wReserved2; 
     public ushort wReserved3; 
     public PROPVARIANTunion union; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct STATPROPSTG 
    { 
     [MarshalAs(UnmanagedType.LPWStr)] 
     public string lpwstrName; 
     public int propid; 
     public VARTYPE vt; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct STATPROPSETSTG 
    { 
     public Guid fmtid; 
     public Guid clsid; 
     public uint grfFlags; 
     public System.Runtime.InteropServices.ComTypes.FILETIME mtime; 
     public System.Runtime.InteropServices.ComTypes.FILETIME ctime; 
     public System.Runtime.InteropServices.ComTypes.FILETIME atime; 
     public uint dwOSVersion; 
    } 

    [DllImport("ole32.dll")] 
    private static extern int StgOpenStorageEx([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, STGM grfMode, STGFMT stgfmt, int grfAttrs, IntPtr pStgOptions, IntPtr reserved2, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IPropertySetStorage ppObjectOpen); 

    [DllImport("ole32.dll")] 
    private static extern int PropVariantClear(ref PROPVARIANT pvar); 

    [Guid("0000013B-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
    private interface IEnumSTATPROPSETSTG 
    { 
     [PreserveSig] 
     int Next(int celt, ref STATPROPSETSTG rgelt, out int pceltFetched); 
     // rest ommited 
    } 

    [Guid("00000139-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
    private interface IEnumSTATPROPSTG 
    { 
     [PreserveSig] 
     int Next(int celt, ref STATPROPSTG rgelt, out int pceltFetched); 
     // rest ommited 
    } 

    [Guid("00000138-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
    private interface IPropertyStorage 
    { 
     [PreserveSig] 
     int ReadMultiple(uint cpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPSPEC[] rgpspec, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPVARIANT[] rgpropvar); 
     [PreserveSig] 
     int WriteMultiple(uint cpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPSPEC[] rgpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPVARIANT[] rgpropvar, uint propidNameFirst); 
     [PreserveSig] 
     int DeleteMultiple(uint cpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPSPEC[] rgpspec); 
     [PreserveSig] 
     int ReadPropertyNames(uint cpropid, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgpropid, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 0)] string[] rglpwstrName); 
     [PreserveSig] 
     int NotDeclared1(); 
     [PreserveSig] 
     int NotDeclared2(); 
     [PreserveSig] 
     int Commit(uint grfCommitFlags); 
     [PreserveSig] 
     int NotDeclared3(); 
     [PreserveSig] 
     int Enum(out IEnumSTATPROPSTG ppenum); 
     // rest ommited 
    } 

    [Guid("0000013A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
    private interface IPropertySetStorage 
    { 
     [PreserveSig] 
     int Create([MarshalAs(UnmanagedType.LPStruct)] Guid rfmtid, [MarshalAs(UnmanagedType.LPStruct)] Guid pclsid, uint grfFlags, STGM grfMode, out IPropertyStorage ppprstg); 
     [PreserveSig] 
     int Open([MarshalAs(UnmanagedType.LPStruct)] Guid rfmtid, STGM grfMode, out IPropertyStorage ppprstg); 
     [PreserveSig] 
     int NotDeclared3(); 
     [PreserveSig] 
     int Enum(out IEnumSTATPROPSETSTG ppenum); 
    } 
} 

public sealed class StructuredProperty 
{ 
    public StructuredProperty(Guid formatId, string name, int id) 
    { 
     FormatId = formatId; 
     Name = name; 
     Id = id; 
    } 

    public Guid FormatId { get; private set; } 
    public string Name { get; private set; } 
    public int Id { get; private set; } 
    public object Value { get; set; } 

    public override string ToString() 
    { 
     return Name; 
    } 
} 
+0

Funziona anche per impostare il valore di una proprietà personalizzata esistente? – baru

+0

@baru - WindowsAPICodePack può farlo –

+0

Sei sicuro? Quando provo a impostare il campo prop.ValueAsObject, IntelliSense dice "Errore: proprietà o indicizzatore" Microsoft.WindowsAPICodePack.Shell.PropertySystem.IShellProperty.ValueAsObject "non può essere assegnato - è di sola lettura". – baru

1

C'è un insieme di pacchetti Nuget chiamati NetOffice che è possibile utilizzare. Esistono pacchetti per ciascuna delle applicazioni di Office, oltre ad alcuni core assembly. Ottieni NetOffice.Word e NetOffice.Excel e installali nella tua soluzione. C'è una piccola documentazione allo Codeplex site, ma ho dovuto sfogliare il codice sorgente per capire veramente cosa stava succedendo. Ecco un esempio di programma:

using NetOffice.OfficeApi; 
using System; 

namespace Office_Doc_Reader 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      using (var wordApp = new NetOffice.WordApi.Application()) 
      using (var excelApp = new NetOffice.ExcelApi.Application()) 
      { 
       var doc = wordApp.Documents.Open("C:\\Users\\John\\Desktop\\test.docx"); 
       var xls = excelApp.Workbooks.Open("C:\\Users\\John\\Desktop\\test.xlsx"); 

       var customProperties = (DocumentProperties)doc.CustomDocumentProperties; 

       foreach (var property in customProperties) 
       { 
        Console.WriteLine(String.Format("Name: {0}, Value: {1}, Type: {2}", property.Name, property.Value, property.Type)); 
       } 

       customProperties = (DocumentProperties)xls.CustomDocumentProperties; 

       foreach (var property in customProperties) 
       { 
        Console.WriteLine(String.Format("Name: {0}, Value: {1}, Type: {2}", property.Name, property.Value, property.Type)); 
       } 
      } 

      Console.ReadKey(); 
     } 
    } 
} 

che mostra i seguenti risultati:

Name: Custom prop 1, Value: Text Value, Type: msoPropertyTypeString 
Name: Custom prop 2, Value: 2/21/2016 12:00:00 AM, Type: msoPropertyTypeDate 
Name: Custom prop 3, Value: 42, Type: msoPropertyTypeNumber 
Name: Custom prop 4, Value: True, Type: msoPropertyTypeBoolean 
Name: Foo, Value: abc, Type: msoPropertyTypeString 
Name: Bar, Value: 1/1/1970 12:00:00 AM, Type: msoPropertyTypeDate 
Name: Baz, Value: 3.14159, Type: msoPropertyTypeFloat 
Name: Qux, Value: False, Type: msoPropertyTypeBoolean 

Per un Word ed Excel file con queste proprietà:

Word PropertiesExcel Properties

io non ho lavorato con tutto questo, quindi non potrò andare molto più in profondità di questo. Spero che sia d'aiuto.

1

È possibile provare il motore NPOI per estrarre le proprietà da diversi file di Office (doc, xls, xlsx, docx, ecc.). Questo componente non ha dipendenze di terze parti e non è necessario che Office lo utilizzi.

Tuttavia, questa libreria è un po 'complicata perché è necessario utilizzare diversi tipi di estrattori di proprietà per diversi tipi di file. Un buon esempio di codice può essere trovato nel repository ufficiale Git Hub TestHPSFPropertiesExtractor.

Il pacchetto NuGet è disponibile here.