Centrum skryptów - Systemy operacyjne

Jak zsynchronizować plik tekstowy zawierający nazwy folderów z podfolderami w folderze?

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 zsynchronizować plik tekstowy zawierający nazwy folderów z podfolderami w folderze?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Mam skrypt, który odczytuje plik tekstowy i sprawdza, czy którakolwiek z nazw podfolderów wymieniona w tym pliku istnieje naprawdę (jeżeli na przykład folder główny to C:\Scripts, to skrypt jest w stanie sprawdzić, czy istnieje plik o nazwie C:\Scripts\Test Folder). Ta część skryptu jest dość niezawodna. Czasem jednak istnieje jakiś plik, który nie jest umieszczony w tym pliku tekstowym. Chciałbym wygenerować okno komunikatu, które dawałoby mi wybór, czy dopisać dany folder do pliku tekstowego, czy nie. Jak to zrobić?

-- SW

Cześć Skrypciarze! Pytanie

Cześć, SW! Dzisiejszy dzień (jak każdy w zresztą w mojej skromnej karierze) jest sprawdzeniem na ile dowcipu mogę sobie pozwolić w artykułach i ale potrzeba do tego, żeby przepełnić czarę i zostać po prostu zwolnionym za sprawą czegoś, co wyszło spod mojej klawiatury.

Uwaga: Dostałem info, że raczej nie dzisiaj, możecie więc spokojnie czytać dalej. Mam jednak w kalendarzu jakieś tajemnicze spotkanie z szefem, przewidziane na 8 lutego. Nie wiem w jakiej sprawie dokładnie, choć na podwyżkę bym nie liczył… Jakby co, do ósmego jestem z Wami, zostało więc nam jeszcze morze skryptów do napisania.

Płacą mi też za ogłoszenia (albo i preparują je specjalnie, żebym tylko nie pisał już żadnej anegdoty), ogłaszam więc wszem i wobec: tegoroczna edycja Zimowej Olimpiady Skrypciarskie (j.ang.) (15 lutego – 3 marca) będzie o wiele bardziej urozmaicona od poprzednich i oprócz typowych zadań będzie zawierała wiele bardziej fikuśnych (jeżeli oczywiście wybaczycie mi to sformułowanie).

Nowym wyzwaniem w tym roku jest na pewno User Group Challenge, sponsorowany przez TechNet Magazine (j.ang.). W tegorocznych rozgrywkach będziecie mieli możliwość wskazania, do jakiej grupy użytkowników należycie. Po co wskazywać? Z uwagi na to, że grupa z największą liczbą uczestników (procentowo) będzie ogłoszona zwycięzcą współzawodnictwa grup i oczywiście otrzyma należną sobie nagrodę. Zostanie wymieniona w naszym artykule, ponadto każdy jej członek otrzyma od nas nagrodę indywidualną.

Można oczywiście oszukiwać i jeżeli macie ochotę popracować z kimś nad jakimś skryptem, to proszę bardzo, droga wolna, nie jesteśmy tu od zabraniania, przecież właśnie po to powstały grupy użytkowników. Pamiętajcie jednak, że jeżeli nad skryptem pracuje Was dajmy na to, 13 sztuk, to każdy musi wypełnić oddzielny formularz zgłoszeniowy. Jeżeli tego nie zrobi, to zgodnie z tym, co otrzymaliśmy, uznamy tylko jeden formularz, co z kolei oznacza, że będziecie mogli otrzymać tylko nagrody indywidualne (j.ang.). Tego nie polecamy, widzieliśmy listę tych nagród. To oczywiście żart (wspominałem wcześniej, że sprawdzam, na ile mogę sobie tu na łamach pozwolić).

Nie każdy jednak należy do jakiejś grupy użytkowników. Niektórzy jednak czują się przynależni do jakiegoś kraju, bo albo tam mieszkają, albo tam się urodzili. Jeżeli należycie do takich osób, to na pewno zainteresuje Was współzawodnictwo międzynarodowe. W poprzednich edycjach naszych gier i zabaw dawaliśmy Wam możliwość wyboru i można było być przedstawicielem dowolnego państwa. W tym roku trochę to poszerzyliśmy i państwo, które będzie miało największą liczbę reprezentantów, zostanie ogłoszone zwycięzcą współzawodnictwa międzynarodowego. W kwietniu zamierzamy nawet poświęcić wszystkie skrypty właśnie temu krajowi. Nie wiem jeszcze, jak to zrobimy, ale TechNet pozielenieje z zazdrości.

Tutaj można oczywiście także oszukiwać. Jeżeli wszyscy Finowie mają ochotę zebrać się i główkować razem nad naszymi zadaniami, proszę bardzo. Nikt Was nie będzie powstrzymywał, tym bardziej że jesteście Finami – ich powstrzymać się nie da. Śmieszne, ale nadal nie jesteśmy w stanie odnaleźć kilku państw na naszej mapie (j.ang.) takich, jak: Targetopia, VandyLand, Bmraarykduesn i wielu innych. Jeżeli gdzieś je odnajdziecie, proszę, dajcie nam znać.

Następnie mamy współzawodnictwo nagłej śmierci. Fajna nazwa, no nie? Nam też się podoba, chociaż tak naprawdę samo współzawodnictwo nie ma nic wspólnego ze śmiercią. Mam nadzieję, że piękno tego zwrotu rekompensuje Wam oszustwo, jakiego się dopuściliśmy. Aby się o tym całkiem upewnić, powtórzymy go jeszcze dwa razy: współzawodnictwo nagłej śmierci, współzawodnictwo nagłej śmierci. Pięknie. W ramach tego współzawodnictwa będziemy publikować 10 minikonkurencji, z których każda będzie ważna tylko przez dzień albo dwa. Pod koniec olimpiady podsumujemy punkty i wyłonimy zwycięzcę nagłej śmierci (uwaga: wbrew pozorom nie oznacza to, że nagrodą będzie nagła śmierć... może trzeba będzie przemyśleć tę nazwę...).

To chyba tyle z informacji, 15 lutego zaczynamy, pamiętajcie!

Pozostaje jednak pytanie, co robić z czasem do 15 lutego, który wydaje się tak odległy? Można oczywiście popisać skrypty, na przykład taki, który informuje nas, czy plik tekstowy zawierający nazwy folderów i folder z podfolderami są z sobą zsynchronizowane:

Const ForReading = 1

Const ForAppending = 8 

Const FOLDER_PATH = ("C:\Scripts\") 



Set objFSO = CreateObject("Scripting.FileSystemObject") 

Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForReading)



Set objFolderList = CreateObject("Scripting.Dictionary")

objFolderList.CompareMode = vbTextCompare



Do Until objFile.AtEndOfStream 

    strName = objFile.ReadLine 

    objFolderList.Add strName, strName

Loop



objFile.Close

 

Set objExistingFolders = CreateObject("Scripting.Dictionary")

objExistingFolders.CompareMode = vbTextCompare



strComputer = "."



Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")



Set colSubfolders = objWMIService.ExecQuery _

    ("Associators of {Win32_Directory.Name='C:\Scripts'} " _

        & "Where AssocClass = Win32_Subdirectory " _

            & "ResultRole = PartComponent")



For Each objSubfolder in colSubfolders

    strName = objSubfolder.FileName

    objExistingFolders.Add strName, strName

Next



For Each strKey in objFolderList.Keys

    strFolderName = strKey

    If Not objExistingFolders.Exists(strFolderName) Then

        Wscript.Echo "The folder " & strFolderName & " does not exist."

    End If

Next



For Each strKey in objExistingFolders.Keys

    strFolderName = strKey

    If Not objFolderList.Exists(strFolderName) Then

        strMessage = "The folder " & strFolderName & " is not in the text file. "

        strMessage = strMessage & "Would you like to add this folder to the text file?"

        intReturn = Msgbox(strMessage, vbYesNo, "Add to Text File")

        If intReturn = vbYes Then

            strNewItems = strNewItems & strFolderName & vbCrLf

        End If

    End If

Next



If strNewItems <> "" Then

    Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForAppending)

    objFile.Write strNewItems

    objFile.Close

End If

Wiemy, trochę przydługi, ale krócej naprawdę się nie dało. Spróbujmy wytłumaczyć, jak to wszystko działa.

Zaczynamy od zdefiniowania trzech stałych: ForReading i For Appending (będziemy ich potrzebowali w trakcie czytania z pliku tekstowego i zapisywania w nim czegoś) oraz FOLDER_PATH, czyli stałej zawierającej ścieżkę pliku, który będziemy sprawdzać pod kątem podfolderów (skopiowaliśmy ją bezpośrednio od SW). W tym wypadku to C:\Scripts\.

Uwaga. Pamiętajcie o znaku \ na końcu nazwy ścieżki, dzięki temu ukośnikowi możemy ustalić kompletne ścieżki folderów dla podfolderów, dodając po prostu nazwy tego podfoldera na końcu tej wartości.

Po zdefiniowaniu tych stałych tworzymy wystąpienie obiektu Scripting.Dictionary, a następnie korzystamy z metody OpenTextFile, za pomocą której otwieramy plik C:\Scripts\Test.txt do odczytu:

Set objFSO = CreateObject("Scripting.FileSystemObject") 

Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForReading)

Następnie tworzymy wystąpienie obiektu Scripting.Dictionary i nadajemy mu nawiązanie do obiektu objFolderList:

Set objFolderList = CreateObject("Scripting.Dictionary")

Po co nam takie wystąpienie obiektu Dictionary? No w sumie do niczego, potrzebujemy bowiem dwóch takich wystąpień. Nasze zadanie, jak pamiętacie, składa się z dwóch części: po pierwsze musimy odczytać plik tekstowy i sprawdzić, czy foldery tam umieszczone istnieją naprawdę, a po drugie przejść pętlą przez kolekcję podfolderów i sprawdzić, czy wszystkie one są uwzględnione w naszym pliku. Można to robić na wiele sposobów, najprościej jednak będzie umieścić nazwy folderów zapisane w pliku w obiekcie Dictionary, a następnie w drugim obiekcie Dictionary umieścić nazwy istniejących naprawdę podfolderów. Dzięki metodzie Exists można łatwo sprawdzić, czy obiekty w Dictionary1 pojawiają się także w Dictionary 2 i odwrotnie.

Właściwość CompareMode obiektu Dictionary ustawiliśmy na vbCompareText. Dzięki temu nasze słowniki nie będą brały pod uwagę wielkości czcionki, a zatem folder Test Folder oraz folder test folder będą uznane za ten sam obiekt.

Jak pobrać nazwy folderów z pliku tekstowego do słownika? Właśnie w następujący sposób:

Do Until objFile.AtEndOfStream 

    strName = objFile.ReadLine 

    objFolderList.Add strName, strName

Loop

Ustawiamy tu pętlę Do Until, która działa do momentu, kiedy dotrze do końca pliku tekstowego (to jest do momentu, w którym właściwość AtEndOfStream nie uzyska wartości TRUE). Wewnątrz pętli używamy metody ReadLine, która odczytuje pierwszą nazwę folderu, a następnie metody Add, która dodaje tę nazwę do słownika:

strName = objFile.ReadLine 

objFolderList.Add strName, strName

Po dodaniu wszystkich folderów wywołujemy metodę Close i tym samym zamykamy plik tekstowy.

Jeden obiekt Dictionary z głowy, pozostał nam drugi.

W celu godnego zajęcia się obiektem Dictionary 2, zaczynamy, jak przystało od utworzenia kolejnego wystąpienia obiektu Scripting.Dictionary i nadania mu nawiązania do obiektu objExistingFolders:

Set objExistingFolders = CreateObject("Scripting.Dictionary")

Czas teraz pobrać podfoldery z C:\Scripts. W pierwotnej wersji skryptu autorstwa SW, w celu pobrania podfolderów skorzystał on z obiektu FileSystemObject. Miał rację, taki wybór działa wyśmienicie, ma jednak jeden poważny minus – ogranicza działanie skryptu do komputera lokalnego. Jeżeli więc chcecie, aby skrypt został uruchomiony na komputerze zdalnym, to musimy rozważyć jeszcze kilka spraw. Jednak jak wiecie, Skrypciarze nie lubią rozważać spraw, dlatego w celu pobrania wszystkich podfolderów z C:\Scripts postanowili skorzystać z WMI:

 

strComputer = "."



Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")



Set colSubfolders = objWMIService.ExecQuery _

    ("Associators of {Win32_Directory.Name='C:\Scripts'} " _

        & "Where AssocClass = Win32_Subdirectory " _

            & "ResultRole = PartComponent")

Uwaga: Macie rację, powyższy fragment również działa wyłącznie na komputerze lokalnym. Przepraszamy za usterki. Jeżeli jednak jesteście uparci i koniecznie chcecie pobrać informację z komputera zdalnego, to musicie po prostu przypisać nazwę tego komputera zmiennej strComputer. Innymi słowy:

strComputer = "atl-fs-001"

Teraz możemy już spokojnie przejść pętlą przez całą kolekcję podfolderów, pobrać wartość właściwości FileName i dodać tę wartość do słownika:

For Each objSubfolder in colSubfolders

    strName = objSubfolder.FileName

    objExistingFolders.Add strName, strName

Next

Super. Teraz mamy już do dyspozycji dwa słowniki pełne nazw, pytanie jednak, co zamierzamy z nimi zrobić? Zaczniemy chyba od czegoś takiego:

For Each strKey in objFolderList.Keys

    strFolderName = strKey

    If Not objExistingFolders.Exists(strFolderName) Then

        Wscript.Echo "The folder " & strFolderName & " does not exist."

    End If

Next

Przechodzimy tutaj pętlą przez pierwszy obiekt Dictionary, czyli ten zawierający nazwy folderów (tylko nazwy, nie ścieżki), które odczytaliśmy z pliku tekstowego. Przechowujemy nazwę każdego klucza w zmiennej o nazwie strFolderName, następnie używamy metody Exists i sprawdzamy, czy taka sama wartość jest umieszczona w drugim słowniku (czyli tym, który zawiera podfoldery z C:\Scripts). Jeżeli taka wartość nie istnieje w drugim słowniku, to oznacza to, że ten folder jest umieszczony w pliku tekstowym, ale tak naprawdę nie istnieje. Wywołujemy zatem następujące echo:

The folder Test Folder does not exist.

Z tym bez wątpienia uporał się SW.

Teraz robimy rzecz podobną, tyle tylko, że z drugim słownikiem – sprawdzamy, czy wszystkie istniejące podfoldery z C:\Scripts są zapisane w pliku tekstowym:

For Each strKey in objExistingFolders.Keys

    strFolderName = strKey

    If Not objFolderList.Exists(strFolderName) Then

        strMessage = "The folder " & strFolderName & " is not in the text file. "

        strMessage = strMessage & "Would you like to add this folder to the text file?"

        intReturn = Msgbox(strMessage, vbYesNo, "Add to Text File")

        If intReturn = vbYes Then

            strNewItems = strNewItems & strFolderName & vbCrLf

        End If

    End If

Next

Co robimy, jeżeli podfolder nie jest umieszczony w pliku tekstowym? Po pierwsze używamy poniższych dwóch wierszy kodu, które generują komunikat, że dany folder nie jest umieszczony w pliku tekstowym, a następnie pytają, czy chcemy dodać tę nazwę do pliku:

strMessage = "The folder " & strFolderName & " is not in the text file. "

strMessage = strMessage & "Would you like to add this folder to the text file?"

Następujący kod wyświetla nam okno komunikatu zawierające przycisk Yes oraz No (stała skryptu VBScript vbYesNo):

intReturn = Msgbox(strMessage, vbYesNo, "Add to Text File")

Jeżeli użytkownik kliknie Yes (czyli jeżeli If intReturn = vbYes), dodajemy tę nazwę foldera oraz znak powrotu karetki i nowego wiersza (vbCrLf) do zmiennej strNewItems. Jeżeli zaś użytkownik kliknie No, to po prostu przechodzimy dalej pętlą i powtarzamy cały proces dla kolejnego klucza w słowniku.

Kiedy pętla przejdzie już przez wszystkie klucze w drugim słowniku, sprawdzamy, czy strNewItems nie jest pustym ciągiem:

If strNewItems <> "" Then

Jeżeli jest, oznacza to, że istnieją nowe nazwy folderów, które musimy dodać do pliku tekstowego. Używamy więc następującego wiersza kodu:

Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForAppending)

objFile.Write strNewItems

objFile.Close

Otwieramy tutaj po prostu plik tekstowy (C:\Scripts\Test.txt) w celu dodania nowych obiektów. Przywołujemy metodę Write, która dodaje wartość strNewItems do pliku, a następnie zamykamy plik tekstowy za pomocą metody Close. Tyle o tym na dzisiaj.

Całkiem niezły skrypt, SW, mamy nadzieję, że przystąpisz do Zimowej Olimpiady Skrypciarskiej 2008. Do zobaczenia zatem 15. lutego, no chyba, że znów będziesz miał jakieś pytania dotyczące skryptów. My też się przygotowujemy do Olimpiady, żeby nie było – mamy już całą kratę piwa w piwnicy. A Wy?

 Do początku strony Do początku strony

Centrum skryptów - Systemy operacyjne