Centrum skryptów - Microsoft office

Jak odczytać metadane Microsoft Excel?

Udostępnij na: Facebook

Skrypciarze odpowiadają na Wasze pytania

Cześć Skrypciarze!

Witamy w rubryce TechNet, w której Skrypciarze z firmy Microsoft odpowiadają na częste pytania dotyczące używania skryptów w administracji systemu. Jeśli macie jakieś pytania z tej dziedziny, zachęcamy do wysłania e-maila na adres: scripter@microsoft.com. Nie możemy zagwarantować odpowiedzi na każde otrzymane pytanie, ale staramy się jak możemy.

Jak odczytać metadane Microsoft Excel?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Mam skrypt VBScript, który odczytuje metadane Microsoft Office Excel. Chciałbym przekształcić go w skrypt Windows PowerShell. Czy jest to możliwe? Próbowałem użyć cmdletu New-Object do stworzenia obiektu Excel.Application, ale z jakiegoś powodu rozwiązanie to nie działa. O co chodzi? Myślałem, że korzystając z technologii Windows PowerShell, może zrobić wszystko to co w języku VBScript i jeszcze więcej. Czy jestem w błędzie?

- SV

Cześć Skrypciarze! Pytanie

Cześć, SV,

O rany! Tyle pytań w jednym pytaniu - jestem pod wrażeniem! Zobaczmy, czy można odpowiedzieć na wszystkie za jednym zamachem: tak, w dużym stopniu, to złożony problem, zasadniczo nie. Tak chyba powinna brzmieć odpowiedź. Jednak zacznijmy od początku. Chcesz przekształcić działający skrypt VBScript w skrypt Windows PowerShell. Doceniam Twój entuzjazm, ale jeśli masz całkiem dobry skrypt VBScript, po co "tłumaczyć" go na Windows PowerShell? W gruncie rzeczy nawet ja zaktualizowałem do wersji Windows PowerShell jedynie niecałe 100 z ponad 3000 napisanych przeze mnie skryptów VBScript. Prawda jest taka, że jestem tak zachwycony nowymi możliwościami powłoki Windows PowerShell, iż nie mam czasu zająć się konwersją. Jednak słyszałem o kilku firmach, które wydały rozporządzenie "nigdy więcej VBScript" i przydzieliły kilku administratorom sieci wyłączne zadanie tłumaczenia istniejących skryptów VBScript na Windows PowerShell. Jeśli interesuje Was ten proces, dajcie mi znać, wysyłając e-mail na adres scripter@microsoft.com (w języku angielskim, o ile to możliwe) , a z przyjemnością zajmę się tym tematem w sposób bardziej szczegółowy.

W każdym razie SV przejrzałem archiwa i znalazłem następujący skrypt, do którego być może się odwołujesz:

On Error Resume Next

          Set objExcel = CreateObject("Excel.Application")

          objExcel.Visible = True

          Set objWorkbook = objExcel.Workbooks.Open("C:\Scripts\Test.xls")

          For Each strProperty in objWorkbook.BuiltInDocumentProperties

          Wscript.Echo strProperty.Name & " - " & strProperty.Value

          Next

Napisanie tego skryptu prawdopodobnie zajęło Gregowi godzinę (lub mniej). Napisanie skryptu GetExcelMetaData.ps1, który został zaprezentowany na końcu tego akapitu, zajęło mi dwa dni, aż w końcu musiałem poprosić mojego dobrego kolegę Luisa (z lizbońskiego oddziału Microsoft) o pomoc w rozwiązaniu bardzo uciążliwego problemu związanego z zestawami COM interop. Dlatego cieszę się, że napisałeś i zadałeś to pytanie, ponieważ wierzę, że może być to pouczające doświadczenie dla nas wszystkich. Jeśli nie chcecie zagłębiać się w szczegóły, oto skrypt GetExcelMetaData.ps1. Do przeczytania dalszej części zachęcam jedynie osoby będące prawdziwymi maniakami (lub aspirujące do tego tytułu):

$excel = New-Object -ComObject Excel.Application

          $excel.Visible = $false

          $workbook = $excel.Workbooks.Open("C:\test\excel.xls")

          $binding = "System.Reflection.BindingFlags" -as [type]

          Foreach($property in $workbook.BuiltInDocumentProperties)

          {

          $pn = [System.__ComObject].invokemember("name",$binding::GetProperty,$null,$property,$null)

          trap [system.exception]

          {

          write-host -foreground blue "Value not found for $pn"

          continue

          }

          "$pn`: " +

          [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null)

          }

          $excel.quit()

Ale o co właściwie chodzi z tymi metadanymi Excel? To brzmi jak fragment jakiejś epopei kosmicznej:

"Alarm! Buntownicy atakują metadane!"

"Uruchomić powłokę PowerShell!"

(lub inny tego typu scenariusz)

Ale wróćmy do głównego tematu. Na Rysunku zaprezentowanym na końcu tego akapitu znaleźć można pewne metadane powiązane z arkuszem kalkulacyjnym Excel. W systemie Windows Vista istnieją dwa główne sposoby wyświetlania metadanych. Gdy zaznaczymy plik w Eksploratorze, wybrane właściwości metadanych zostaną zaprezentowane w postaci podsumowania w dolnej części ekranu. Natomiast bardziej obszerną listę można znaleźć po kliknięciu pliku prawym klawiszem myszy, wybraniu opcji Properties (Właściwości), a następnie kliknięciu karty Details (Szczegóły). Jak widać, niektóre informacje mogą być niezwykle przydatne. Uwielbiam dodawać pola komentarzy oraz znaczniki, ponieważ można je później wykorzystać w funkcji wyszukiwania systemu Windows Vista, dzięki czemu mogę szybko odnajdować określone dokumenty wśród miliardów plików znajdujących się na moim 200 GB dysku:

Rysunek 1.

Wracając do pytania zadanego przez SV, czy jest to możliwe? Oczywiście, że jest. Na początku skryptu GetExcelMetaData.ps1 tworzymy instancję obiektu Excel.Application. W języku VBScript do tego celu służy polecenie CreateObject, natomiast w Windows PowerShell wykorzystujemy cmdlet New-Object:

$excel = New-Object -ComObject Excel.Application

Jak na razie wszystko idzie gładko. W stworzonym przez Grega skrypcie VBScript instancja Excel została uwidoczniona. W naszym przykładzie nie jest to konieczne. W końcu interesują nas jedynie metadane arkusza, a nie właściwe dane, które się w nim znajdują. Aby arkusz pozostał niewidoczny, przypisujemy właściwości visible obiektu Excel.Application wartość false w następujący sposób:

$excel.Visible = $false

Okay, jeszcze jedna łatwa linia kodu i sprawy zaczną się komplikować (jeśli macie wątpliwości, możecie się jeszcze wycofać). Kolejne wyzwanie stanowi otwarcie arkusza Excel. Do tego celu używamy metody open obiektu workbook. Metoda open wymaga podania ścieżki do skoroszytu Microsoft Excel, który mamy zamiar wykorzystać. Oto linia kodu odpowiedzialna za otwarcie skoroszytu:

$workbook = $excel.Workbooks.Open("C:\test\excel.xls")

A teraz trudniejsza część (w rzeczywistości nie jest tak źle). Kolejne zadanie polega na przekształceniu ciągu w typ danych. Za takim podejściem przemawiają dwa podstawowe argumenty. Po pierwsze chciałem zademonstrować, w jaki sposób można użyć tej techniki. Po drugie to rozwiązanie nieco zwiększa czytelność kodu. Tak naprawdę tworzymy alias dla typu danych, czyli w tym przypadku klasy System.Reflection.BindingFlags. Gdybyśmy po prostu przypisali wartość "System.Reflection.BindingFlags" do zmiennej $binding, otrzymalibyśmy jedynie ciąg, a nie instancję klasy System.Reflection.BindingFlags. Wartości BindingFlags służą do kontrolowania powiązań wielu klas w przestrzeni nazw System.Reflection oraz innych przestrzeniach nazw Microsoft .NET Framework. Umożliwiają one uzyskanie dostępu do metody InvokeMember, o czym można się będzie przekonać za chwilę. Aby przekształcić ciąg w typ danych, należy użyć operatora -as w ramach konstrukcji -as [type] w następujący sposób:

$binding = "System.Reflection.BindingFlags" -as [type]

Teraz potrzebujemy instrukcji ForEach do przejścia przez kolekcję BuiltInDocumentProperties obiektu Excel Workbook. Zmienna $property służy do przechodzenia po kolekcji, co zaprezentowano poniżej:

Foreach($property in $workbook.BuiltInDocumentProperties)

A teraz pora na omówienie sedna dwudniowego problemu! Teoretycznie powinienem móc po prostu użyć następującego kodu do pobrania poszczególnych nazw właściwości z kolekcji BuiltInDocumentProperties:

$property.name

Jednak tu pojawia się problem, który wynika ze sposobu działania zestawu interop dla tego określonego obiektu COM. Obiekt wraca i informuje, że jest [System.__ComObject]. Gdy próbujemy wywołać metodę $property.name, otrzymujemy błąd o treści " [System.__ComObject] does not have a property named name ”. W związku z tym musimy użyć refleksji:

$pn = [System.__ComObject].invokemember("name",$binding::GetProperty,$null,$property,$null)

Metodę InvokeMember można wykorzystywać na trzy różne sposoby (zwane przeładowaniami). Oto poszczególne składowe wywołania metody:

Wartość Znaczenie
name Nazwa właściwości do wywołania w kwerendzie.
$binding::GetProperty Statyczna metoda GetProperty utworzonej przez nas wcześniej klasy System.Reflection.BindingFlagss, znajdującej się w zmiennej $binding.
$ null Obiekt klasy Binder, który określa zestaw właściwości i umożliwia wiązanie. W tym przykładzie wykorzystujemy $null, aby użyć domyślnej wartości defaultBinder.
$property Obiekt docelowy. W tym przykładzie instancja właściwości dokumentu z kolekcji BuiltInDocumentProperties.
$ null Tablica obiektów przekazywanych w roli argumentów do metody. W tym przykładzie nie jest potrzebna, ponieważ badamy właściwość.

Jeśli będziemy usiłowali zbadać właściwość dokumentu, która nie istnieje, obiekt wygeneruje błąd (odnosi się to również do skryptów VBScript). Ponieważ zadaliśmy sobie tyle trudu, aby odczytać właściwości dokumentu, zdecydowaliśmy się pójść o krok dalej i zaprezentować sposób przechwytywania błędów przy pomocy programu Windows PowerShell. Oryginalny skrypt VBScript wykorzystywał instrukcję On Error Resume Next i w przypadku wystąpienia błędu przechodził do kolejnej właściwości w kolekcji. Mogliśmy zrobić dokładnie to samo, wykorzystując kod $ErrorActionPreference = "SilentlyContinue" . Jednak zadecydowaliśmy, że ciekawiej będzie użyć instrukcji Trap, ponieważ naśladuje ona sposób obsługiwania błędów w języku C#. W przypadku zastosowania instrukcji Trap należy określić, jakiego typu błędy nas interesują. W tym przykładzie przechwytujemy stosunkowo uniwersalny typ system.exception. Po wykryciu wyjątku wyświetlamy nazwę właściwości dokumentu, która nie istnieje (przy pomocy niebieskiego koloru czcionki, który jest znacznie bardziej przyjazny niż czerwony), a następnie przechodzimy do kolejnej właściwości:

trap [system.exception]

          {

          write-host -foreground blue "Value not found for $pn"

          continue

          }

Gdy właściwość istnieje, prezentujemy jej nazwę oraz wartość. Warto zauważyć, że w tym celu ponownie wywołujemy metodę invokemember. Jednak tym razem wyświetlamy wartość właściwości, a także jej nazwę znajdującą się w zmiennej $pn:

"$pn`: " +

[System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null)

Po zaprezentowaniu listy właściwości dokumentu oraz ich wartości pozostaje nam jedynie opuścić skoroszyt Excel (jeśli tego nie zrobimy, będzie on niepotrzebnie zużywał zasoby pamięciowe). Do tego celu służy następująca linia kodu:

$excel.quit()

Uruchomienie skryptu skutkuje wyświetleniem poniższych danych wyjściowych:

Rysunek 2.

A zatem jak widzisz SV, nie wszystkie rozwiązania Windows PowerShell są łatwiejsze niż skrypty VBScript. Jak wspomniałem na początku, jeśli macie prawidłowo działający skrypt, zostawcie go w spokoju. Chyba że istnieje wystarczający powód przemawiający za jego przekształceniem (taki jak oficjalne, odgórne rozporządzenie). Niniejszy przykład pokazuje również, dlaczego wolę wykorzystywać klasy .NET Framework, gdy pracuję z powłoką Windows PowerShell – zasadniczo są one łatwiejsze w użyciu i efektywniejsze.

Ed Wilson i Craig Liebendorfer, Skrypciarze z firmy Microsoft

 Do początku strony Do początku strony

Centrum skryptów - Microsoft office