Programmeren met Visual Basic Script
KB » Computer » Programmeren met Visual Basic Script

Programmeren met Visual Basic Script

    Tweeten

Introductie

Deze pagina is niet bedoeld als programmeercursus, maar meer als handige referentie voor wat wel kan, wat niet kan, welke technieken je kan gebruiken, waar je op moet letten, tegen welke problemen je aan kan lopen, etc.

Ik ga echter (voorlopig in elk geval) niet uitleggen wat variabelen en constanten zijn, en waarom je in het ene geval beter een constante kan gebruiken en in het andere niet.


Scripts uitvoeren

Zet je script in een bestand met extensie .VBS

Voer het script uit vanuit de grafische interface (bv. Verkenner (Explorer)) door er bv. op te dubbelklikken, of vanaf de commandoregel.

Dat laatste doe je via het commando
cscript scriptnaam.vbs

Je kan ook parameters meegeven aan je script, en die vanuit het script benaderen.

Als je het script bv. aanroept met
cscript dinner.vbs 2
dan kan je in het script met
Choice = Wscript.Arguments.Item(0)
de waarde van de 1e parameter (2) in de variabele Choice krijgen.

Script hosts

cscript is een zgn. script host. Het kan scripts interpreteren en uitvoeren.

In de Windows Script Host (WSH) omgeving is er ook nog een andere script host: Wscript

De .VBS-extensie is standaard gekoppeld aan Wscript, en als je dus in Verkenner op zo'n bestand dubbelklikt wordt automatisch Wscript uitgevoerd.

Datzelfde gebeurt als je op de commandoregel 'cscript' zou weglaten, en bv. alleen test.vbs intikt. Ook dan wordt Wscript ingeschakeld.

Je kan de standaard script host veranderen met
cscript //H:cscript of
wscript //H:wscript

Verschil tussen Cscript en Wscript

In principe zijn de verschillen niet groot, behalve dat Cscript de commandoregel-versie is, en Wscript de grafische.

Maar dat maakt alleen verschil als je het Wscript.Echo commando gebruikt. Als Cscript dan je standaard script host is, en je voert het script vanuit Verkenner uit, dan gaat je uitvoer naar het commandoregel venster, dat na afloop van het script ook weer verdwenen is.

Als je het MsgBox-commando gebruikt voor uitvoer, dan maakt het niet uit welke script host je gebruikt.

Opties en help-informatie

Met de commando's cscript /? en wscript /? krijg je help-informatie.

//NOLOGO

Normaal gesproken krijg je aan het begin van het script informatie over de versie van WSH e.d. (kortom, een soort logo).

Als je dat niet wilt kan je het commando als volgt geven: C:\> cscript test.vbs //nologo

//B

De B staat voor Batch mode.

Het gevolg is dat je geen enkele uitvoer meer krijgt: geen logo, geen normale uitvoer en geen foutinformatie.

Er zijn situaties waarin dat handig kan zijn (bv. als je scripts maakt voor leken, en je wilt niet dat ze rare dingen voorbij zien komen).

//T

Soms schrijf je wel eens scripts die in een eindeloze loop terecht komen.

Om dat te vermijden geef je een commando als cscript test.vbs //T:5

Het script houdt dan na 5 seconden gegarandeerd op.

Scripts stoppen

Naast de manier met de //T-optie kan je een script dat gestart is met cscript ook makkelijk stoppen met Ctrl+C

Scripts die gestart zijn met Wscript kan je alleen stoppen met bv. Task Manager

En er is ook een commando om een script van binnenuit te stoppen:
Wscript.Quit


Uitvoer

Met het commando WScript.Echo kan je uitvoer produceren.

Dingen die je tussen dubbele quotes zet worden ongewijzigd afgedrukt, op getallen wordt de geëigende bewerking losgelaten, en je kan zelfs functies (zoals Now) meegeven. In dat laatste geval wordt de datum afgedrukt.

Uitvoer verschijnt in een dialoogvenster als je een script vanuit de grafische interface uitvoert (met WScript).

Het nadeel daarvan kan zijn dat je voor elke regel uitvoer op OK moet klikken voor het script verder gaat.

Vanaf de commandoregel komt alle uitvoer gewoon achter elkaar te staan (als je cscript gebruikt).

Het volgende script

Wscript.Echo "Line One" Wscript.Echo 100 Wscript.Echo 100 + 100 Wscript.Echo "100 + 100" Wscript.Echo Now Wscript.Echo "Now"

produceert de volgende output:

Line One 100 200 100 + 100 28-5-2013 23:44:33 Now

Meer mogelijkheden voor een message box

Naast het Echo-commando is er ook een functie Msgbox

Daarmee kan je bv. ook de titel van je dialoogvenster veranderen, en verschillende knoppen (OK, Cancel, etc.) toevoegen. Voorbeeld:

strMessage = "Here is my message." Msgbox strMessage, 0, "This is my message box"

De 1e parameter is de boodschap die in het venster vertoond wordt, met de 2e kan je de knoppen opgeven (afhankelijk van het getal wat er staat) en met de 3e verander je de titel van het dialoogvenster.


Invoeren van gegevens

InputBox

Een eenvoudige manier om 1 regel tekst in te voeren is de functie InputBox

Voorbeeld: strMessage = InputBox("Voer tekst in: ")

Nadat de gebruiker iets heeft ingevoerd, en op Enter heeft gedrukt, zit de tekst in StrMessage


Variabelen en constanten

Variabelen

Variabelen hoef je niet te declareren.

Je kan dus gewoon zeggen:

i = 1 name = "Henk" j = 3 + 5

Naamgeving

Het is wel handig om variabelen duidelijke (Hongaarse) namen te geven, d.w.z. dat het type vooraf gaat aan de eigenlijke naam.

Voorbeelden:

strName = "Henk" intNumber = 3 arrArray(5) = "Mei" dtToday objOS ' obj = object colDrives ' col = collection

Lokale en globale variabelen

Met het DIM-statement maak je een variabele geldig op dat niveau.

Dat betekent dat als je DIM gebruikt om een variabele op het hoogste niveau te declareren, maar daarna dezelfde naam ook declareert binnen een subroutine, de waarde van binnen de subroutine weer verdwenen is als hij is afgelopen.

Het volgende script levert 13 op:

Dim a a = 4 b = 9 ChangeA Wscript.Echo a + b Sub ChangeA Dim a a = 10 End Sub

Variabelen die alleen in een subroutine gebruikt worden (met of zonder DIM) bestaan niet meer als de subroutine is afgelopen.

Het volgende script levert dus 9 op:

b = 9 ChangeA Wscript.Echo a + b Sub ChangeA a = 10 End Sub

Constanten

Constanten moet je wel declareren, en zijn daarna niet meer van waarde te veranderen (redelijk logisch, gezien de naam). Dat doe je als volgt:

Const i = 2 * 7

Predefined constanten

Een flink aantal constanten zijn al standaard door VBScript gedefiniëerd. Die beginnen allemaal met 'vb', dus bv. vbOKCancel.


Objecten, classes, properties en methods

Objecten aanmaken

Je hebt classes voor bv. processen of services.

Voor elke class kan je nieuwe objecten aanmaken, en je hebt daarvoor het Set-statement nodig.

Het gebruik maken van een bestaande class, bv. in WMI, Active Directory, of ADO, heet binding, connecting, of instantiation. Het object is een instance van de class.

CreateObject

Als je een nieuw object wilt aanmaken gebruik je normaal gesproken CreateObject

Voorbeeld:
Set objExcel = CreateObject("Excel.Application")

GetObject

Je gebruikt GetObject om een referentie te krijgen naar een object dat al bestaat.

Je gebruikt het dus bv. om WMI te benaderen. Het grote verschil is dat Excel normaal niet per definitie draait, maar WMI wel.

Dat betekent dat WMI-objecten altijd beschikbaar zijn als de computer aanstaat.

En je gebruikt de volgende rare regel om die objecten te kunnen benaderen:
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")

'winmgmts' is een soort bijnaam voor het SWbemServices object.
De '.' staat voor de computer waarop je werkt.
En 'root\cimv2' is de namespace in WMI die je wilt gebruiken.

Nu je een referentie naar de WMI service hebt, kan je methodes gebruiken om verdere objecten te benaderen: de method Get of de method ExecQuery

De 1e is meer om 1 specifiek object te krijgen, en de 2e om een verzameling objecten te krijgen, gebaseerd op allerlei criteria.

Een object opruimen

Je kan een object als volgt vrijgeven:
Set objReference = Nothing

Maar doorgaans is het niet nodig, want het gebeurt automatisch aan het eind van het script.

Methods

Een method is een soort functie/subroutine die deel uitmaakt van een class.

Als je een method aanroept zonder het resultaat op te vragen kan je geen haakjes om de parameters zetten.


Loops (in relatie tot arrays en collections)

Collections

Collections zijn verzamelingen van 0, 1 of meer dingen van hetzelfde type.

Je kan collections geen waarde geven, alleen de inhoud ervan opvragen.

Voorbeeld:

Set objFSO = CreateObject("Scripting.FileSystemObject") Set colDrives = objFSO.Drives

De verzameling colDrives is nu een object met de namen van alle disks op je PC.

Arrays

Je kan arrays op 2 manieren vullen: door waarden aan de elementen toe te kennen, alsof elk element een variabele is, of door de Array-method te gebruiken.

In het 1e geval moet je arrays declareren als volgt:

Dim arrArray(aantal)

In dit geval heb je een array gekregen van aantal+1 elementen, omdat het 1e element index 0 heeft.

De reden voor het declareren met Dim is dat VBScript dan weet dat het om een array gaat. Methods worden nl. ook aangeroepen met ronde haakjes om de parameters.

Als je op het moment van declaratie van de array nog niet weet hoeveel elementen er in zullen komen geef je gewoon geen aantal op. Zodra je het wel weet gebruik je een
ReDim
statement.

Array method

In dit geval heb je geen Dim-statement nodig, omdat VBScript nu automatisch weet dat het om een array gaat. Voorbeeld (compleet script):

arrArray = Array("H","e","n","k") Wscript.Echo arrArray(0) & arrArray(1) & arrArray(2) & arrArray(3)

Loops

Er komen 2 soort loops aan de orde: For Each ... Next en For Next
Verder een manier om loops voortijdig te beëindigen.

For Each ... Next

We willen nu iets doen met elke disk op onze PC. We hadden al een verzameling met alle disks.

For Each objDrive in colDrives ' do something Next

De naam objDrive is een vrije keuze.

Een compleet voorbeeld (probeer maar op je PC):

Set objFSO = CreateObject("Scripting.FileSystemObject") Set colDrives = objFSO.Drives For Each objDrive in colDrives Wscript.Echo "Drive letter: " & objDrive.DriveLetter Next Wscript.Echo "Done"

In mijn geval is de uitvoer (de laatste 2 zijn resp. de DVD-speler en een USB-stick):

Drive letter: C Drive letter: D Drive letter: E Drive letter: G Done

For Next

Deze vorm wordt meer gebruikt voor arrays (maar je kan For Each ook gebruiken voor arrays). Een voorbeeld (ook dit is een compleet script):

Dim arrCount() size = 3 ReDim arrCount(size) For i = 0 to size arrCount(i) = i Wscript.Echo arrCount(i) Next Wscript.Echo "Done "

i is een door jezelf te kiezen variabele, en de begin- en eindwaarde (in dit geval 0 en size kan je ook zelf kiezen). Ze moeten natuurlijk wel binnen de grenzen van de array vallen.

De uitvoer is dan:

0 1 2 3 Done

In dit script ging je met stappen van 1 omhoog (dus alle elementen bij langs). Je kan met een kleine toevoeging ook grotere stappen vooruit of zelfs achteruit maken (zoals in onderstaand voorbeeld):

Dim arrCount() size = 3 ReDim arrCount(size) For i = size to 0 Step -1 arrCount(i) = i Wscript.Echo arrCount(i) Next Wscript.Echo "Done "

Voortijdig de loop verlaten

Dat doe je met het statement
Exit For


Het IF-statement

Om te beginnen een voorbeeld, omdat dat de zaken al aardig duidelijk maakt:

A = 5 B = 10 If A > B Then Wscript.Echo "A is greater than B" ElseIf A = B Then Wscript.Echo "A is equal to B" Else Wscript.Echo "A is less than B" End If

De ElseIf- en Else-takken zijn (vanzelfsprekend) niet verplicht.

Verder kan je in elk van de 3 situaties meer dan 1 commando geven.


Het case-select-statement

Een voorbeeldscript:

Choice = 1 Select Case Choice Case 2 Wscript.Echo "Eten bestellen" Case 3 Wscript.Echo "Koken" Case 1 Wscript.Echo "Buitenshuis eten" Case Else Wscript.Echo "Niet eten" End Select

Je hebt een variabele met een bepaalde waarde (in dit geval Choice), en vervolgens ga je in een aantal takken testen wat voor waarde die variabele heeft. Bij verschillende waardes kan je verschillende acties ondernemen.

De Case Else-tak moet altijd als laatste staan. De acties die erachter staan worden uitgevoerd als de variabele geen van de andere waardes heeft. Je mag deze tak ook helemaal weglaten. Dan gebeurt er gewoon niets als de variabele een andere waarde heeft.


Subroutines, functions en bibliotheekbestanden

Subroutines

Parameters worden in VBScript default by reference ( ByRef) doorgegeven. Als je ze by value wilt doorgeven moet je ByVal gebruiken.

Een voorbeeldscript van by reference:

x = 7 Wscript.Echo "Before subroutine call x = " & x TestSub x Wscript.Echo "After subroutine call x = " & x Sub TestSub(a) Wscript.Echo "In subroutine, original value of a = " & a a = 10 Wscript.Echo "In subroutine, changed value of a = " & a End Sub

De uitvoer van dit script is:

Before subroutine call x = 7 In subroutine, original value of a = 7 In subroutine, changed value of a = 10 After subroutine call x = 10

Als je de 1e regel van de subroutine verandert in
Sub TestSub(ByVal a)
krijg je de volgende uitvoer:

Before subroutine call x = 7 In subroutine, original value of a = 7 In subroutine, changed value of a = 10 After subroutine call x = 7

Functions

Het idee is hetzelfde als subroutines, alleen heeft een functie een resultaat. In de functie geef je een waarde aan dat resultaat door hem toe te kennen aan de naam van de functie (alsof het een variabele was):

x = 4 y = TestFunc(x) Wscript.Echo y Function TestFunc(a) Wscript.Echo a TestFunc = a + 2 End Function

Uitvoer is:

4 6

Bibliotheekbestanden

We willen procedures en functies graag kunnen delen tussen verschillende scripts, en dat kan op de volgende manier:

Const ForReading = 1 Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.OpenTextFile("procedures.txt", ForReading) Execute objFile.ReadAll()

De naam procedures.txt is willekeurig (extensie maakt ook niet uit), en bevat een verzameling subroutines en functions.

Het Execute-statement leest het bestand, zet alles in het geheugen, en daarna kunnen de subroutines e.d. door andere scripts worden aangeroepen.

Onderstaand een beetje een ingewikkeld voorbeeld van een script dat van een subroutine in zo'n bibliotheek gebruik maakt:

strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colFiles = objWMIService.ExecQuery _ ("ASSOCIATORS OF {Win32_Directory.Name='C:\Scripts'} Where " _ & "ResultClass = CIM_DataFile") For Each objFile in colFiles If objFile.Extension = "txt" Then Wscript.Echo objFile.Name strText = strText & ReadFile(objFile) & vbCrLf End If Next Wscript.Echo strText

En dit is de bijbehorende bibliotheek-file:

Function ReadFile(oFile) Const ForReading = 1 Set objFSO = CreateObject("Scripting.FileSystemObject") Set objTextFile = objFSO.OpenTextFile _ (oFile.Name, ForReading) ReadFile = objTextFile.ReadLine objTextFile.Close End Function

Standaardfuncties

Getallen

IsNumeric

Soms is het belangrijk om te weten of een variabele een getal bevat:

a = 5 b = "A" If IsNumeric(a) And IsNumeric(b) Then c = a + b Wscript.Echo c Else Wscript.Echo "Beide waardes moeten numeriek zijn. a = " & a & " and b = " & b End If

FormatNumber

Deze functie heeft 5 parameters:

  1. Number
    Het getal dat je wilt formatteren.
  2. NumberOfDigits
    Het aantal cijfers dat er achter de komma moet komen.
  3. LeadingZeroes
    Als deze parameter -1 is, dan betekent het dat er een 0 voor de komma komt te staan. Het getal zou dus bv. 0,13 zijn en niet ,13.
    Een waarde van 0 betekent dat er geen 0 voor de komma komt.
    Een waarde van -2 tenslotte betekent dat de instellingen op de computer leidend zijn.
  4. NegativesInParens
    Als deze parameter -1 is, dan betekent het dat bv. -4 wordt weergegeven als (4).
    Een waarde van 0 betekent dat er geen haakjes worden gebruikt.
    Een waarde van -2 tenslotte betekent dat de instellingen op de computer leidend zijn.
  5. GroupDigits
    Als deze parameter -1 is, dan betekent het dat duizendtallen worden gegroepeerd m.b.v. de waarde die op je computer is gedefinieerd. 5000000 zou dus kunnen worden weergegeven als 5.000.000 of als 5,000,000 (laatste is meer Amerikaans).
    Een waarde van 0 betekent dat je geen groepering wilt.
    Een waarde van -2 tenslotte betekent dat de instellingen op de computer leidend zijn.

Alleen de 1e parameter is verplicht. Als je de andere weglaat worden de instellingen van de computer gebruikt.

Een voorbeeld:

a = 2.5793 b = "400" c = FormatNumber(a, 2) d = FormatNumber(b, 4) Wscript.Echo c Wscript.Echo d

Dit zou de volgende uitvoer opleveren:
2.58
400.0000

Int

Deze functie levert je het gedeelte van het getal voor de komma, dus bv. Int(6,3) levert de waarde 6 op.

Rnd en Randomize

Rnd produceert een random getal tussen 0 en 1.

Voordat je Rnd gebruikt moet je altijd eerst een keer Randomize aanroepen. De systeemtijd wordt dan gebruikt als basis voor een willekeurige waarde.

Dus, als je een random getal tussen 1 en 100 wilt produceren, doe je:

upper = 100 lower = 1 Randomize For i = 1 to 5 a = Int((upper - lower + 1) * Rnd + lower) Wscript.Echo a Next

Conversie

CInt

Doet hetzelfde als Int, maar rond ook af naar de dichtstbijzijnde gehele waarde.

CInt(5,7) levert dus 6 op.

Hex

Zet een getal om in een hexadecimaal getal.

Je kan aan VBScript aangeven dat een getal hexadecimaal is door er '&H' voor te zetten. Dus 25 = &H19

Asc

Geeft je de numerieke waarde van een ASCII-teken.

letter = "B" Wscript.Echo Asc(letter)

geeft als uitvoer 66

CDate

Deze method zet een datum in string-formaat om in een echt datum-formaat.

Strings

Je hebt een aantal handige functies die met strings kunnen werken.

Right

De 1e is string Right(string, integer)

Voorbeeld:

str = "voetbal" intCharacters = 3 strNew = Right(str, intCharacters) Wscript.Echo strNew

Als het goed is zal er "bal" afgedrukt worden, omdat de teller begint bij 1.

Je had ook in 1 keer het volgende kunnen doen, met hetzelfde resultaat:
Wscript.Echo Right("voetbal", 3)

Left

Het principe is hetzelfde. De volgende code

str = "voetbal" intCharacters = 4 strNew = Left(str, intCharacters) Wscript.Echo strNew

levert "voet" op.

Mid

De definitie van deze functie is string Mid(string, integer [, integer])

De 2e parameter is nu de beginpositie, en de 3e het aantal tekens dat je wilt hebben.

De volgende functie-aanroep
Wscript.Echo Mid("voetbal", 3, 4)
levert "etba" op.

Als je de laatste parameter weglaat krijg je alles vanaf de beginpositie. In het bovenstaande voorbeeld zou dat "etbal" zijn.

Voorbeeld met Left, Right en Mid

Het volgende script pakt een datum in het formaat 'mmddyyyy', en zet hem in het formaat 'mm/dd/yyyy':

strDate = "08112006" intDayMonthCharacters = 2 intYearCharacters = 4 intStart = 3 strNew = Left(strDate, intDayMonthCharacters) strNew = strNew & "/" strNew = strNew & Mid(strDate, intStart, intDayMonthCharacters) strNew = strNew & "/" strNew = strNew & Right(strDate, intYearCharacters) Wscript.Echo strNew

Lengte van een string

Met de functie integer Len(string)
kom je de lengte van een string te weten.

String naar uppercase omzetten

Met de functie UCase kan je een string naar uppercase omzetten. Dus bv.:
Wscript.Echo UCase(strMessage)
drukt de string in strMessage in uppercase af.

Datums en tijden

Date en Now

De method Date geeft je de datum, in het formaat zoals dat in je regionale instellingen staat.

De method Now geeft datum en tijd.

DateAdd

Met de method DateAdd kan je vooruit of achteruit gaan in de tijd, door bv. het aantal jaren te verhogen, het aantal maanden te verlagen, etc. Een voorbeeld:

dt = DateAdd("m", 2, today) Wscript.Echo "Twee maanden na vandaag: " & dt

'm' staat voor maand (month), en staat tussen dubbele quotes omdat het anders zou worden opgevat als een variabele.

I.p.v. 'm' kan je ook gebruik maken van bv. 'yyyy' (jaar), 'q' (kwartaal), 'h' (uur), 'n' (minuten) en 's' (seconden).

DateDiff

Net als bij DateAdd is de 1e parameter de tijdseenheid, zoals dagen of maanden.
De 2e parameter is de 1e datum, en 3e parameter de 2e datum.

Het verschil in tijd tussen beide tijden wordt berekend.

Een voorbeeld:
Wscript.Echo "Days left in the year: " & DateDiff("d", today, #1/1/2014#)

Er van uitgaande dat het nu 2013 is, berekent deze method het resterende aantal dagen van dit jaar, nl. tussen vandaag (today) en 1-1-2014.

Om aan te geven dat de 3e parameter een echte datum is, moet je hem tussen hekjes zetten.

Day, Year en Month

Deze 3 methods doen wat je van ze verwacht: ze geven de dag (nummer), het jaar en de maand (nummer) van een datum weer.

Het is vandaag 27-8-2013, dus Month(today) zou als uitkomst 8 hebben.

Weekday en WeekdayName

Deze method levert het nummer van de dag in de week, waarbij de nummering begint bij zondag, met 1.

Dus als vandaag vrijdag is, en je voert Weekday(today) uit, dan is het resultaat 6.

Als je de dag van de week wilt weten in letters, dan gebruik je WeekdayName. Zie bv. het volgende script:

dy = Weekday(today) Wscript.Echo "Day of the week, readable: " & WeekdayName(dy)

WMI datums

UTC datumformaat

WMI slaat de datums in UTC formaat op. Dus als je bv. de datum wilt weten waarop het OS geinstalleerd is zou je als resultaat
20130731185109.000000+120
kunnen krijgen.

Eerst zie je jaar, maand, dag, dan de tijd (de 6 tekens voor en na de punt) en tenslotte de offset t.o.v. GMT.

Er bestaan kant-en-klare functies om dit rare formaat om te zetten naar een normale datum en tijd.



Dictionaries (woordenboeken)

Index en item

Arrays bestaan uit series van gelijksoortige dingen, die je kan benaderen met een index (getal).

Bij dictionaries heb je ook een index, alleen kan dat van alles zijn (bv. namen van personen). De index noem je de key, de waarde die erbij hoort het item. De index moet uniek zijn, en is case-sensitive (d.w.z. "Henk" is niet hetzelfde als "HENK").

Het dictionary object

Om überhaupt iets met dictionaries te kunnen doen moet je een dictionary object aanmaken, m.b.v. een regel als deze (de naam voor het '='-teken is vrij):
Set objDictionary = CreateObject("Scripting.Dictionary")

Wijzigingen aanbrengen in de dictionary

M.b.v. de method Add kan je key-item paren aan de dictionary toevoegen.

Voorbeeld

Hieronder een goed voorbeeld van hoe je een dictionary kunt gebruiken, bv. om alle gestopte services op het systeem af te drukken.

strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colServices = objWMIService.ExecQuery _ ("Select * from Win32_Service") Set objDictionary = CreateObject("Scripting.Dictionary") For Each objService in colServices objDictionary.Add objService.Name, objService.State Next colKeys = objDictionary.Keys For Each strKey in colKeys If objDictionary.Item(strKey) = "Stopped" Then Wscript.Echo strKey End If Next

Eerst maak je een verzameling services (colServices), dan voeg je de Name en State als keys en items aan de dictionary toe.

Tenslotte zet je alle keys in de verzameling colKeys, en druk je alleen de servicenamen af van de services die gestopt zijn.

Testen of een key al bestaat

Als je niet-unieke keys toevoegt krijg je een foutmelding en crasht je script. Je kan met de method Exists ook van tevoren testen:

Set objDictionary = CreateObject("Scripting.Dictionary") objDictionary.Add "Key1", "Item" objDictionary.Add "Key2", "Item" If objDictionary.Exists("Key1") Then Wscript.Echo "Key1 exists" Else Wscript.Echo "Adding Key1" objDictionary.Add "Key1", "Item" End If

Dit script geeft keurig een melding, omdat Key1 al bestond.

Case sensitive

Een dictionary object heeft een property CompareMode

De waarde 1 is bedoeld voor texts (case insensitive), de waarde 0 voor binaire vergelijkingen (case sensitive).


Programma's starten vanuit een script

Met objShell.Run

Gewone grafische programma's kan je starten met een script als het volgende:

Set objShell = CreateObject("WScript.Shell") objShell.Run "Calc.exe"

Om een commandoregel programma uit te voeren moet je een andere truc uithalen:

Set objShell = CreateObject("WScript.Shell") objShell.Run "%COMSPEC% /k ping 127.0.0.1"

Als je als 2e regel alleen dit zou doen:
objShell.Run "ping 127.0.0.1"
zou het commando wel uitgevoerd worden, maar buiten de commandoregel om. (En je zou geen uitvoer te zien krijgen.)

(Terzijde: Soms krijg je trouwens een foutmelding als je op bovenstaande manier een commando, bv. het dir-commando uitvoert:
I:\test.vbs(2, 1) (null): The system cannot find the file specified.
De verklaring is dat commando's als dir zitten ingebakken in de commandoregel-verwerker, tegenwoordig meestal het programma CMD.EXE
Er bestaat dus werkelijk geen bestand DIR.EXE op het systeem.)

Vandaar de omgevingsvariabele %COMSPEC%, die een commandoregel-venster opent (door op de meeste systemen C:\Windows\system32\cmd.exe aan te roepen).

Maar dat zou nog niet voldoende zijn, omdat het commandoregel venster standaard ook na afloop automatisch zou sluiten als het op deze manier wordt aangeroepen. Daarom geef je de K-optie mee.

Het zou kunnen zijn dat dit script niet werkt onder Windows 7 als je bv. ipconfig wil starten, waarschijnlijk omdat je dan meer privileges nodig hebt. Maar vanaf een elevated commandoregel draait het bij mij prima.

Parameters van de Run method

De 2e parameter van de Run method heeft betrekking op het type venster dat geopend wordt (bv. gemaximaliseerd).

De 3e parameter is standaard False, en betekent dat het script onmiddellijk doorgaat met het volgende commando. Als je dus wilt dat het ene commando wacht op het volgende, moet je zoiets doen:

Set objShell = CreateObject("WScript.Shell") objShell.Run "%COMSPEC% /k ping 127.0.0.1",,True objShell.Run "%COMSPEC% /k nslookup 127.0.0.1"

Je krijgt een venster met de uitvoer van het ping-commando, en daarna gebeurt er niets meer. Want de commandoregel die erbij hoort is nog steeds actief. Zodra je die beëindigt wordt een nieuw commandoregel-venster met nslookup gestart.

Resultaat van de Run method

Deze method geeft een status code terug, en die kan je opvragen. Je moet dan wel de parameters tussen haakjes zetten. Dus:

Set objShell = CreateObject("WScript.Shell") iErrorCode = objShell.Run("ping 127.0.0.1",,True) WScript.Echo iErrorCode

Met objShell.Exec

Deze method is handig voor commandoregel programma's, zoals ipconfig.

Het resultaat van objShell.Exec is nl. een WshScriptExec-object, dat status- en foutinformatie bevat.

Maar via het object kan je ook toegang krijgen tot STDOUT, de "stream" waar de uitvoer van het programma heengeschreven wordt.

Het werkt als volgt:

Set objShell = CreateObject("Wscript.Shell") Set objWshScriptExec = objShell.Exec("ipconfig.exe") WScript.Echo objWshScriptExec.StdOut.ReadAll

Afhankelijke van de scripting host die je gebruikt komt de uitvoer op het scherm of in een dialoogvenster.

Het gebruik van speciale tekens in het commando

Het volgende script levert een fout:

Set objShell = CreateObject("Wscript.Shell") objShell.Run "notepad.exe "c:\my scripts\logfile.txt""

Na de 2e dubbele quote denkt WSH dat het commando is afgelopen.

In Windows 7 kan je de binnenste dubbele quotes vervangen door dubbele dubbele quotes, dus:

Set objShell = CreateObject("Wscript.Shell") objShell.Run "notepad.exe ""c:\my scripts\logfile.txt"""

Maar er is nog een truc. Met de standdaardfunctie Chr kan je een code omzetten in een speciaal teken. Op die manier wordt bovenstaand script:

strArgument = "notepad.exe " & chr(34) & _ "c:\my scripts\logfile.txt" & chr(34) Wscript.Echo strArgument

chr(34) is gelijk aan de dubbele quote, en met de & worden de strings aan elkaar geknoopt.

Programma's op andere machines starten

Dat kan met het WshController-object, maar het werkt nogal moeizaam. Je kan beter WMI gebruiken.

WMI Create

De WMI-klasse Win32_Process heeft een method Create, waarmee je programma's (grafisch en commandoregel) lokaal en remote kunt uitvoeren, en die als resultaat het proces ID(entificatie) teruggeeft.

Voorbeeeld:

strCommand = "notepad.exe" Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process") intReturn = objProcess.Create _ (strCommand, Null, Null, intProcessID)

De Create-method heeft 4 parameters:

Om een proces op een remote computer te starten gebruik je bv. de volgende code:

strComputer = "server1" strCommand = "notepad.exe" Set objProcess = GetObject("winmgmts:\\" & strComputer & _ "\root\cimv2:Win32_Process") intReturn = objProcess.Create _ (strCommand, Null, Null, intProcessID)
Win32_ProcessStartup

De class Win32_ProcessStartup heeft 14 properties, bv. de prioriteit waarop het proces moet lopen en het type venster.

Om redenen die ik nog niet helemaal doorgrond moet je na het aanmaken van een object van deze class nog een object aanmaken (in deze regel: Set objConfig = objStartup.SpawnInstance_), en pas daarna kan je er de eigenschappen aan toekennen.

In dit geval willen we kladblok (notepad) starten in een normaal venster (NORMAL_WINDOW) en aan het eind ook nog het resultaat (intReturn) testen. Als alles goed is gegaan is de waarde 0.

Const NORMAL_WINDOW = 1 strComputer = "." strCommand = "notepad.exe" Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set objStartup = objWMIService.Get("Win32_ProcessStartup") Set objConfig = objStartup.SpawnInstance_ objConfig.ShowWindow = NORMAL_WINDOW Set objProcess = objWMIService.Get("Win32_Process") intReturn = objProcess.Create _ (strCommand, Null, objConfig, intProcessID) If intReturn = 0 Then Wscript.Echo "Process Created." & _ vbCrLf & "Command line: " & strCommand & _ vbCrLf & "Process ID: " & intProcessID Else Wscript.Echo "Process could not be created." & _ vbCrLf & "Command line: " & strCommand & _ vbCrLf & "Return value: " & intReturn End If

Je kan er ook een loop van maken, als volgt:

strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colMonitorProcess = objWMIService.ExecNotificationQuery _ ("SELECT * FROM __InstanceOperationEvent " _ & " Within 1 WHERE TargetInstance ISA 'Win32_Process'") WScript.Echo "Waiting for process to start or stop ..." Do Set objLatestEvent = colMonitorProcess.NextEvent WScript.Echo VbCrLf & objLatestEvent.Path_.Class Wscript.Echo "Process Name: " & objLatestEvent.TargetInstance.Name Wscript.Echo "Process ID: " & objLatestEvent.TargetInstance.ProcessId WScript.Echo "Time: " & Now Loop

Dan krijg je dit soort output (en het gaat eindeloos door tot je Ctrl+C indrukt):

__InstanceModificationEvent Process Name: csrss.exe Process ID: 624 Time: 3-12-2013 15:08:23 __InstanceModificationEvent Process Name: iexplore.exe Process ID: 7280 Time: 3-12-2013 15:08:23 __InstanceModificationEvent Process Name: FlashPlayerPlugin_11_9_900_117.exe Process ID: 7376 Time: 3-12-2013 15:08:23 __InstanceModificationEvent Process Name: plugin-container.exe Process ID: 7456 Time: 3-12-2013 15:08:23 __InstanceModificationEvent Process Name: svchost.exe Process ID: 752 Time: 3-12-2013 15:08:23

Als je alleen wilt selecteren op het starten en stoppen van processen doe je:

If objLatestEvent.Path_.Class = "__InstanceCreationEvent" _ Or objLatestEvent.Path_.Class = "__InstanceDeletionEvent" Then WScript.Echo VbCrLf & objLatestEvent.Path_.Class Wscript.Echo "Process Name: " & objLatestEvent.TargetInstance.Name Wscript.Echo "Process ID: " & _ objLatestEvent.TargetInstance.ProcessId WScript.Echo "Time: " & Now End If

WMI heeft nog 3 classes die te maken hebben met proces events: Win32_ProcessTrace, Win32_ProcessStartTrace en Win32_ProcessStopTrace

Voorbeeldscript:

On Error Resume Next strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colProcessStopTrace = objWMIService.ExecNotificationQuery _ ("SELECT * FROM Win32_ProcessStopTrace") WScript.Echo "Waiting for process to stop ..." Do Set objLatestEvent = colProcessStopTrace.NextEvent WScript.Echo Wscript.Echo "Process Name: " & objLatestEvent.ProcessName Wscript.Echo "Process ID: " & objLatestEvent.ProcessId Wscript.Echo "Time: " & objLatestEvent.TIME_CREATED 'Property exists only on Windows Server 2003. Wscript.Echo "Exit Code: " & objLatestEvent.ExitStatus Loop

De tijd die je terugkrijgt is van het type uint64 en de inhoud is het aantal 100 nano-seconde-intervallen sinds 1 januari 1601. Niet zo handig, dus.

Sinds Server 2003 levert de WMI scripting API een nieuw object, SWbemDateTime, dat functionaliteit heeft om met 3 datum/tijd-formaten om te gaan: DATETIME (de bovenstaande uitvoer), FILETIME, en een voor mensen iets aangenamer formaat, VT_DATE


Tekstbestanden

Om met files te kunnen werken maak je gebruik van het voorgedefinieerde FileSystemObject

Een (nieuw) bestand aanmaken

Zo maak je bv. een bestand aan:

Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.CreateTextFile("C:\Scripts\FreeSpace.log")

De extensie van het bestand maakt niet uit, maar het wordt als een tekstbestand behandeld.
CreateTextFile is 1 van de methods van het file system object.

Als je niet wilt dat een al bestaande file wordt overschreven, moet je het statement als volgt veranderen:
Set objFile = objFSO.CreateTextFile("C:\Scripts\FreeSpace.log", False)

Je kan ook (bv.) elke dag een nieuwe file aanmaken, en de datum in de filenaam opnemen:

dt = Date dt = Replace(dt,"/","-") strFileName = "C:\Scripts\" & dt & "-FreeSpace.log" Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.CreateTextFile(strFileName)

In de 2e regel gebruik je de Replace method om voor de zekerheid (hangt af van je regionale instellingen) evt. '/' in de datum te vervangen door hyphens ('-').

Een bestand sluiten

Eenvoudiger is bijna niet mogelijk: objFile.Close

Schrijven naar een bestand

Dat doe je met het statement: objFile.WriteLine (wat_je_wilt_schrijven)

Een voorbeeldscript, waarin je de vrije ruimte en de totale ruimte op disk C opvraagt en naar een bestand schrijft:

strComputer = "." dt = Replace(Date,"/","-") strFileName = "C:\Scripts\" & dt & "-FreeSpace.log" Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.CreateTextFile(strFileName) Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colDisks = objWMIService.ExecQuery _ ("Select * from Win32_LogicalDisk Where DeviceID = 'C:'") For Each objDisk in colDisks objFile.WriteLine ("Free space (in bytes): " & objDisk.FreeSpace) objFile.WriteLine ("Total space (in bytes): " & objDisk.Size) Next objFile.Close

Een file openen

Je kan een tekstbestand openen met de method OpenTextFile

Naast de bestandsnaam moet je echter een extra parameter meegeven, nl. of je alleen maar wilt lezen (ForReading), of je wilt schrijven (ForWriting) of dat je tekst aan het eind van het bestand wilt toevoegen (ForAppending).
Deze 3 parameters hebben resp. de waardes 1, 2 en 8.

Dus om een file te openen voor lezen:

Const ForReading = 1 Set objFile = objFSO.OpenTextFile(strFileName, ForReading)

Je kan OpenTextFile ook gebruiken om een nog niet bestaande file onmiddellijk te openen voor schrijven (dit is dus identiek aan CreateTextFile):
Set objFile = objFSO.OpenTextFile(strFileName, ForWriting, True)

Lezen uit een file

In het volgende script lees je 1 voor 1 de regels uit het bestand Servers.txt tot je aan het einde van de file bent, en je drukt ze af (op het scherm).
De eigenschap AtEndOfStream heeft de waarde True als je het einde van de file hebt bereikt.

Const ForReading = 1 Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.OpenTextFile("C:\Scripts\Servers.txt", ForReading) Do Until objFile.AtEndOfStream strLine = objFile.ReadLine Wscript.Echo strLine Loop objFile.Close

ReadLine leest regel voor regel uit een bestand. Als je het hele bestand in 1 keer wil lezen gebruik je ReadAll

Lezen en schrijven naar dezelfde file

Het volgende script leest een bestand, controleert of een regel bestaat uit het woord 'Apple', en verandert het dan door 'Samsung':

Const ForReading = 1 Const ForWriting = 2 Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.OpenTextFile("C:\Scripts\Companies.txt", ForReading) Do Until objFile.AtEndOfStream strLine = objFile.ReadLine If strLine = "Apple" Then strLine = "Samsung" End If strContents = strContents & strLine & vbCrLf Loop objFile.Close Set objFile = objFSO.OpenTextFile("C:\scripts\Companies.txt", ForWriting) objFile.Write(strContents) objFile.Close

De truc is dat het bestand eerst alleen voor lezen wordt geopend, en dat de veranderde tekst in een string (strContents) wordt opgeslagen.

Daarna wordt het bestand gesloten en onmiddellijk weer geopend, maar nu voor schrijven. En de hele opgebouwde string wordt er met de Write method naartoe geschreven.

Je hebt in dit geval geen WriteLine nodig, omdat er bij het opbouwen van de string aan het eind al steeds een Carriage Return (Cr) en LineFeed (Lf) zijn toegevoegd. De string is dus al onderverdeeld in aparte regels.


Bestanden en mappen

Je kan op 2 manieren met files en folders werken, via het FileSystemObject object, en via WMI.

Het voordeel van WMI is dat het ook gebruikt kan worden voor remote computers.
Het voordeel van FileSystemObject is dat het wat makkelijker is om mee te werken, i.h.b. als het om datums gaat.

FileExists en FolderExists

Deze 2 methods controleren of een bestand of map bestaat.

Bv. als volgt voor een bestand:
If objFSO.FileExists("C:\scripts\test.txt") Then ...

GetFile

Deze method dient om een object te krijgen dat naar de file verwijst, als je weet dat hij bestaat.

Voorbeeld:
Set objFile = objFSO.GetFile("C:\scripts\test.txt")

Copy

Bestanden

Met deze method kan je een bestand copiëren, waarbij de standaard is dat het doelbestand, als het al bestaat, wordt overschreven. Door een 2e parameter, False, op te geven kan je dat verhinderen.

Dus met het volgende statement wordt de file alleen gecopiëerd als hij nog niet bestaat:
objFile.Copy "C:\scripts\temp\", False

De backslash aan het eind is wel belangrijk, omdat je wilt dat de file in de map temp terecht komt.

CopyFile en CopyFolder

Je kan GetFile of GetFolder en Copy ook combineren tot de method's uit de titel:

Set objFSO = CreateObject("Scripting.FileSystemObject") objFSO.CopyFile "C:\scripts\test.txt","C:\scripts\temp\" objFSO.CopyFolder "C:\scripts","C:\scriptstest"

Deze 2 methods kennen ook een extra (in dit geval 3e) parameter, die aangeeft of evt. bestaande files en folders overschreven moeten worden.

Maar ze kunnen ook iets wat de normale Copy niet kan, nl. meerdere bestanden of mappen copiëren. Je moet dan met wildcards werken.

Set objFSO = CreateObject("Scripting.FileSystemObject") objFSO.CopyFile "C:\scripts\*.txt","C:\scripts\temp\"

Bovenstaand script copiëert alle bestanden met extensie txt naar de doelmap.

In geval van mappen is het even oppassen geblazen. De regel
objFSO.CopyFolder "C:\s*","C:\temp"
copiëert alle mappen die beginnen met 's' in zijn geheel *binnen* de map c:\temp

Dus in dit geval wordt niet de inhoud van de mappen die met 's' beginnen gecopiëerd, maar de map als geheel. Anders zou het binnen de doelmap een ongeorganiseerde chaos worden.

Mappen

Met mappen werkt het vrijwel hetzelfde als met bestanden, alleen moet je nu geen backslash aan het einde van het doel zetten.

Verder is het belangrijk om te beseffen dat ook de inhoud van submappen e.d. wordt meegenomen.

Een voorbeeldscript:

Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFolder = objFSO.GetFolder("C:\scripts") objFolder.Copy "C:\temp"

Move, MoveFile en MoveFolder

Alles werkt vrijwel identiek aan de copiëer-operaties die hierboven beschreven zijn, met 1 verschil.

De standaard is in dit geval dat een bestand niet overschreven wordt, en je kan ook geen extra parameter meegeven om dat wel te bewerkstelligen.

Delete, DeleteFile en DeleteFolder

Alles werkt weer gelijk aan de Copy-methods, met opnieuw 1 belangrijk verschil.

In dit geval is er wel een extra parameter. Die heeft als standaardwaarde False, en dat betekent dat files die read-only zijn niet worden weggegooid.

Om dus een hele map gegarandeerd kwijt te raken moet je het volgende statement uitvoeren:
objFolder.Delete True

Copiëren op basis van datum

Stel je hebt een file object, en je wilt afdrukken op welke datum het bestand aangemaakt is. Dat kan als volgt:
Wscript.Echo objFile.DateCreated

Je hebt ook nog 2 andere eigenschappen van een file object, DateLastAccessed (wanneer is de file voor het laatst benaderd), en DateLastModified (wanneer is de file voor het laatst gewijzigd).

De bovenstaande 3 eigenschappen van het file object zie je ook als je met rechts klikt op een bestand in Verkenner en dan op Eigenschappen (Properties)

Door nu gebruik te maken van de datum-functies kan je bv. alle files copiëren die meer dan een maand geleden zijn aangemaakt:

Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFolder = objFSO.GetFolder("C:\scripts") Set colFiles = objFolder.Files dtmMonthAgo = DateAdd("m", -1, Now) For Each objFile in colFiles If objFile.DateCreated < dtmMonthAgo Then objFSO.CopyFile objFile.Path, "C:\scripts\old\" End If Next

In de 6e regel bepaal je het tijdstip dat een maand voor het huidige ligt. En daarna ga je gewoon alle files bij langs om te kijken of DateCreated voor dat tijdstip ligt.


Bestanden en mappen via WMI

Misschien moet je eerst de introductie over WMI lezen.

Het verschil met werken met het FileSystemObject is dat je nu met 2 classes te maken hebt, 1 voor files (CIM_DataFile) en 1 voor mappen (Win32_Directory).

Kijken of een file of map bestaat

Map

Gebruik het volgende script:

strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colFolders = _ objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'") If colFolders.Count < 1 Then Wscript.Echo "Folder does not exist" Else Wscript.Echo "Folder does exist" End If

Met het Select-statement haal je alle bestanden uit de map C:\Scripts
De reden voor de 2 backslashes is dat de '\' een zgn. escape-character is (om aan te geven dat het volgende teken anders geinterpreteerd moet worden dan normaal).

De eigenschap Count geeft aan hoeveel objecten (folders in dit geval) aan de voorwaarde voldoen, en omdat we om een specifieke map gevraagd hebben zijn er maar 2 mogelijkheden: 0 of 1.

File

Voor bestanden werkt het eigenlijk net als met mappen, behalve, zoals ik in het begin al zei, dat je nu een andere class gebruikt:
Set colFiles = _ objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'")

Copy

Folder

Stel dat je de folder C:\Scripts naar C:\Temp wilt copiëren.

Het script verandert niet veel t.o.v. dat in de vorige sectie (ik heb de 1e 5 regels weggelaten):

Set colFolders = _ objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'") For Each objFolder in colFolders objFolder.Copy("C:\Temp") Next

Belangrijk hier is dat als de map waar je naartoe copiëert al bestaat, er niets gebeurt, maar je ook geen foutmelding krijgt.

Je kan wel controleren op een foutmelding door het resultaat van de method op te vragen:
errResult = objFolder.Copy("C:\Temp")
Foutcodes staan in de beschrijvingen van de WMI classes (per method).

Verder heeft de Copy method maar 1 parameter, en worden eventuele submappen niet meegecopiëerd. Als je dat wel wilt moet je de method CopyEx gebruiken.

File

Als je een file wilt copiëren:

Set colFiles = _ objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'") For Each objFile in colFiles objFile.Copy ("C:\Temp\NewTest.txt") Next

Wat als je meerdere bestanden wilt copiëren? Dan gebruik je een volgend soort syntax:

Set colFiles = _ objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'") For Each objFile in colFiles strNewFile = "C:\Temp\" & objFile.FileName & ".txt" objFile.Copy strNewFile Next

Als je zoekt op een andere schijf dan de huidige, moet je ook de Drive-property gebruiken:
Where Drive = 'C:' and Path = '\\Test\\' and Extension = 'txt'

Verder is het goed om te beseffen dat de FileName-property niet de extensie bevat.

Bestanden verhuizen (Move)

Er is geen Move method in WMI.

Als je mappen of bestanden wilt verhuizen zal je ze eerst moeten copiëren en dan verwijderen van de plek waar ze vandaan komen.

Delete

Mappen

Voorbeeld:

Set colFolders = _ objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'") For Each objFolder in colFolders ' objFolder.Delete Next

Read-only files in de map worden ook weggegooid!!

Het echte Delete-statement is uitgecommentariëerd, om te voorkomen dat je per ongeluk dit script uitvoert en echt een map weggooit.

Bestanden

Voorbeeld:

Set colFiles = _ objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'") For Each objFile in colFiles objFile.Delete Next

Het weggooien van alle bestanden met extensie TXT in een map:

Set colFiles = _ objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'") For Each objFile in colFiles objFile.Delete Next

Bestanden met deze extensie in submappen worden niet weggegooid.

Copiëren op basis van datum

Dit is een beetje ingewikkelder dan met het FileSystemObject-object. Dat komt doordat WMI werkt met datums in UTC-formaat.

Je moet de datum in UTC-formaat dus eerst naar een "normaal" formaat omzetten:

Set colFiles = _ objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'") dtMonthAgo = DateAdd("m", -1, Now) For Each objFile in colFiles dtCreationDate = WMIDateStringToDate(objFile.CreationDate) If dtCreationDate < dtMonthAgo then strNewFile = "C:\Scripts\old\" & objFile.FileName & ".txt" objFile.Copy strNewFile End If Next Function WMIDateStringToDate(dtmInstallDate) WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) & "/" & _ Mid(dtmInstallDate, 7, 2) & "/" & Left(dtmInstallDate, 4) _ & " " & Mid (dtmInstallDate, 9, 2) & ":" & _ Mid(dtmInstallDate, 11, 2) & ":" & Mid(dtmInstallDate, _ 13, 2)) End Function

Register

Je kan zonder WMI werken (zie het vervolg) of met.

De Windows Scripting Host levert een class, WshShell, die je lokale toegang geeft tot het register.

Je maakt als volgt een object van de class aan:
Set objShell = WScript.CreateObject("WScript.Shell")

Je begint elke script (dat met het register werkt) met dit statement, dus die noem ik vanaf hier niet meer.

De class heeft 3 methods, RegRead, RegWrite en RegDelete

Lezen uit het register

Simpel voorbeeld:

iWordWrap = objShell.RegRead _ ("HKCU\Software\Microsoft\Notepad\fWrap") If iWordWrap = 0 Then Wscript.Echo "Word wrap is turned off" Else Wscript.Echo "Word wrap is turned on" End If

Van de 5 hoofdkeys in het register moet je alleen HKEY_USERS en HKEY_CURRENT_CONFIG voluit schrijven, de andere 3 kan je afkorten tot 4 letters.

Schrijven naar het register

Het volgende script schrijft naar dezelfde waarde die ook al bij het leescommando werd gebruikt:
objShell.RegWrite "HKCU\Software\Microsoft\Notepad\fWrap", 1, "REG_DWORD"

Het meest opvallende hier is dat je als 3e parameter het data-type moet opgeven. De meest belangrijke types staan op mijn pagina over het register.

Om het schrijf-statement wat overzichtelijker te maken kan je het pad in het register ook van tevoren in een variabele zetten. Onderstaand script draait de waarde van de wrap-instelling in Kladblok (Notepad) om:

strPath = "HKCU\Software\Microsoft\Notepad\fWrap" Set objShell = WScript.CreateObject("WScript.Shell") iWordWrap = objShell.RegRead(strPath) If iWordWrap = 0 Then objShell.RegWrite strPath, 1, "REG_DWORD" Else objShell.RegWrite strPath, 0, "REG_DWORD" End If

Stel dat je een compleet nieuwe waarde binnen een key wilt aanmaken, dan zou je de volgende regel kunnen uitvoeren:
objShell.RegWrite "HKCU\Software\Microsoft\Notepad\NewValue", 1, "REG_DWORD"

Verwijderen uit het register

Met onderstaande statement verwijder je de nieuwe waarde weer die we hierboven aangemaakt hebben:
objShell.RegDelete "HKCU\Software\Microsoft\Notepad\NewValue"

Registerbewerkingen m.b.v. WMI

Lezen

Het volgende script leest dezelfde waarde uit het register die we al in deze hele sectie (over het register) gebruiken:

Const HKEY_CURRENT_USER = &H80000001 strComputer = "." strKeyPath = "Software\Microsoft\Notepad" strEntryName = "fWrap" Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv") objReg.GetDWordValue HKEY_CURRENT_USER, strKeyPath, strEntryName, strValue Wscript.Echo strValue

In de 1e regel wordt 1 van de 5 keys in het register in een constante gezet. Je kan hier niet de afkortingen van de keys gebruiken, zoals HKCU, maar moet een hexadecimale representatie ervan gebruiken (HKLM = &H80000002 en HKCR = &H80000000).

Als je de punt in de 3e regel vervangt door de naam van een remote computer, kan je (alleen met WMI) ook op die computer werken.

Op bijna deze hele pagina (waar het over WMI gaat) wordt vrijwel steeds gebruik gemaakt van de \root\cimv2 namespace. Hier is het \root\default

Met WMI moet je een verschillende method gebruiken, afhankelijk van het data-type dat je gaat lezen:

Schrijven

Dat is nauwelijks anders dan lezen, alleen moet je nu natuurlijk opgeven welke waarde je gaat schrijven. De enige 2 regels die dus anders zijn, zijn:

strValue = 1 ... objReg.SetDWordValue HKEY_CURRENT_USER, strKeyPath, strEntryName, strValue

Verwijderen

Een nog niet bestaande waarde aanmaken, en die vervolgens verwijderen, is ook a piece of cake als het voorgaande duidelijk is:

Const HKEY_CURRENT_USER = &H80000001 strComputer = "." strKeyPath = "Software\Microsoft\Notepad" strEntryName = "NewKey" strValue = 10 Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv") objReg.SetDWordValue HKEY_CURRENT_USER, strKeyPath, strEntryName, strValue objReg.DeleteValue HKEY_CURRENT_USER, strKeyPath, strEntryName

Gebruik maken van voorgedefinieerde informatie (zoals WMI)

Aanroep

Om van WMI gebruik te kunnen maken moet een regel als de volgende in je script hebben staan:
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Maar het kan ook ingewikkelder:
Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv")

En het kan simpeler als je met de lokale computer werkt:
Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")

Het object dat je terugkrijgt als je werkt met de naam winmgmts is van het type SWbemServices

Meestal gebruik je de method ExecQuery van dit object, omdat je een bepaalde collectie gegevens wilt verzamelen (query).

Maar als je met events wilt werken gaat het anders.

Voorbeeld

Als je van bv. Verkenner (het bijbehorende bestand heet explorer.exe) zou willen zien wanneer hij gestart is, zou je het volgende (complete) script kunnen uitvoeren:

strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colProcessList = objWMIService.ExecQuery _ ("Select * from Win32_Process Where Name = 'explorer.exe'") For Each objProcess in colProcessList Wscript.Echo objProcess.CreationDate Next

In mijn geval was de uitvoer (op 31 mei): 20130524165937.110020+120

Blijkbaar draaide mijn PC dus als sinds 24 mei onafgebroken.

Hoe vind je informatie over wat er in WMI beschikbaar is?

Je gebruikt hier de Win32_Process-class en de property Name

Hoe weet je dat die er zijn? Via de WMI Reference. Links daarnaar heb ik op mijn pagina over programmeren staan.

De bovengenoemde class ziet er als volgt uit (ik heb een groot stuk er uit gesloopt, maar je ziet de properties Name en CreationDate):

class Win32_Process : CIM_Process { datetime CreationDate; string CSCreationClassName; string CSName; string Description; string ExecutablePath; uint16 ExecutionState; uint32 MaximumWorkingSetSize; uint32 MinimumWorkingSetSize; string Name; string OSCreationClassName; string OSName; };

Hier een script (compleet) om de service SCardSvr te starten (kan geen kwaad om uit te voeren), en als uitvoer de statuscode af te drukken (0 = goed):

strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colServiceList = objWMIService.ExecQuery _ ("Select * from Win32_Service where Name='SCardSvr'") For Each objService in colServiceList errReturn = objService.StartService() Wscript.Echo errReturn Next

Events

Om WMI events te kunnen ontvangen gebruik je niet de method ExecQuery van het object SWbemServices, maar de method ExecNotificationQuery

Dus bv.:

Set colMonitorProcess = objWMIService.ExecNotificationQuery _ ("SELECT * FROM __InstanceOperationEvent " & _ "WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'")

Het getal achter 'within' (in dit geval 1) is het aantal seconden tussen de momenten dat er gekeken wordt naar nieuwe events (het 'polling interval').

De class __InstanceOperationEvent is een ingebouwde WMI event class, die elke wijziging, verwijdering of aanmaak van een object van een WMI class registreert.

Er zijn 3 classes afgeleid van bovenstaande, nl. __InstanceCreationEvent, __InstanceModificationEvent en __InstanceDeletionEvent

Als je, zoals in bovenstaande query, zoekt op de class __InstanceOperationEvent, dan krijg je ze alle 3.

Wanneer je een query als bovenstaande uitvoert krijg je als resultaat een SWbemEventSource-object.

Het object SWbemEventSource heeft een method NextEvent, die informatie ophaalt over het eerstvolgende event dat voldoet aan de query.

Het resultaat is een object, met de naam van de event class die opgetreden is in de property Path_.Class van dat object.

En als je een query doet die met processen te maken heeft (en dus de class Win32_Process), dan bevatten de eigenschappen TargetInstance.Name en TargetInstance.ProcessId van het object uit de vorige paragraaf de naam en het proces ID van het proces.

Het volgende stukje code:

strComputer ="." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colMonitorProcess = objWMIService.ExecNotificationQuery _ ("SELECT * FROM __InstanceOperationEvent " _ & " WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'") WScript.Echo "Waiting for process change event ..." Set objLatestEvent = colMonitorProcess.NextEvent WScript.Echo VbCrLf & objLatestEvent.Path_.Class Wscript.Echo "Process Name: " & objLatestEvent.TargetInstance.Name Wscript.Echo "Process ID: " & objLatestEvent.TargetInstance.ProcessId WScript.Echo "Time: " & Now

zou dus de volgende uitvoer op kunnen leveren:

Waiting for process change event ... __InstanceModificationEvent Process Name: System Idle Process Process ID: 0 Time: 25-11-2013 22:15:22

WMI Query Language

Select statement

Je kan zeggen
Select * from Win32_PnPEntity
wat betekent dat je alle (daar staat de '*' voor) eigenschappen van het object Win32_PnPEntity opvraagt.

Je kan ook 1 specifieke eigenschap opvragen:
Select Description from Win32_PnPEntity

Of meerdere eigenschappen:
Select Description, Manufacturer from Win32_PnPEntity

En als je wilt dat je de waarde van je eigenschap aan een bepaalde voorwaarde voldoet, gebruik je de where-clause:
Select * from Win32_PnPEntity Where Manufacturer = 'Microsoft'

Maar je kan natuurlijk ook meer voorwaarden opgeven:
Where Manufacturer = 'Microsoft' and Name = 'wan miniport (ip)'

En tenslotte kan je ook in de where-clause een wildcard-teken opgeven, de '%':
Select * from Win32_PnPEntity Where Manufacturer = 'Microsoft' and Name Like 'wan miniport%'
betekent dus dat als de waarde van eigenschap Name begint met wan miniport, hij voldoet aan de voorwaarde.

In plaats van and kan je ook or gebruiken om 2 voorwaarden met elkaar te verbinden.


Foutzoeken en fouten voorkomen

Waar kan je fouten verwachten?

Veel voorkomende plekken waar fouten optreden:

Option explicit

Omdat VBScript loosely typed is, worden tikfouten in namen van variabelen niet gemerkt. Dat kan leiden tot moeilijk oplosbare fouten. Zie bv. het volgende script:

FirstNumber = 4 SecondNumber = 9 Wscript.Echo FirstNumber + SeconNumber

Het script drukt 4 af als antwoord, omdat er een 'd' mist in SeconNumber, en je krijgt geen foutmelding (een niet gedeclareerde variabele krijgt standaard als waarde 0).

Als je aan het begin van het script
Option Explicit
zet, zou dit script wel een foutmelding geven.

Je moet de variabelen dan wel van tevoren declareren:

Option Explicit Dim FirstNumber Dim SecondNumber FirstNumber = 4 SecondNumber = 9 Wscript.Echo FirstNumber + SeconNumber

Controleren op syntax-fouten

Een syntax-fout is wanneer je bv. een spelfout maakt in een keyword, zoals Else

VBScript controleert de syntax van het hele script voordat het het uitvoert.

Een slimme manier om dus zelf te controleren of de syntax OK is, is het script (tijdelijk) te laten beginnen met Wscript.Quit

Vooral als je script gevaarlijke dingen zou kunnen doen is dit een manier om er voor te zorgen dat je script niets kan beschadigen, want het stop per definitie na de 1e regel.

On Error Resume Next

Je kan alles over dit commando op de site van Microsoft vinden.

Met dit commando in je script doet het alsof fouten niet bestaan. Als een statement een fout oplevert slaat het dat statement gewoon over, en gaat verder met het volgende. En je zult het nooit te weten komen.

Tenzij je dit statement op zijn minst tijdelijk uitschakelt. En dat is dus ook wat je moet doen als je onverwachte resultaten krijgt.

Ook al staat dit commando in je hoofdscript, dan nog werkt het niet in functies of subroutines. Binnen elke functie of subroutine moet je het opnieuw uitvoeren.

Je kan het commando uitschakelen met On Error GoTo 0

Err object

Je kan alles over dit object op de site van Microsoft vinden.

Number property

VBScript bevat standaard het object Err, met de property Number

Elke VBScript method zet een resultaat in deze property. En dat resultaat kan je bv. als volgt opvragen en er iets mee doen:

If Err.Number Then ... Err.Clear End If

Andere properties

Err heeft ook nog de properties Source (bron) en Description (beschrijving), beide strings.

De properties HelpFile en HelpContext zijn alleen van belang als de applicatie eigen foutcodes heeft gedefiniëerd, en die aan helpbestanden heeft gekoppeld.

Methods

Clear

Clear is een zeer belangrijke method.

Als je waarde van bv. Err.Number hebt gecontroleerd moet je Err.Clear uitvoeren.

Als je de waarde nl. later weer een keer controleert, en de commando's tot dan toe hebben geen fout opgeleverd, dan zal toch nog steeds de oude waarde er in zitten.

Raise

De andere method is Raise(lngNumber, strSource, strDescription)

Hiermee kan je zelf een fout produceren als VBScript het niet doet.

Alleen de 1e parameter, een getal tussen 0 en 65536, is verplicht.

IsObject en Is Nothing

Deze 2 tests kan je gebruiken na een commando waarin je met objecten werkt, zoals GetObject en CreateObject

Ze leveren geen specifieke foutinformatie op, alleen of je een geldig object hebt gekregen of niet.

Is Nothing

Nothing staat gelijk aan Null, m.a.w. een pointer naar niets (en in elk geval geen object).

Je kan dus bv. het volgende script gebruiken om te testen of je een geldige referentie naar de WMI-provider hebt gekregen:

On Error Resume Next strComputer = "fictional" Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") If objWMIService Is Nothing Then WScript.Echo "Unable to bind to WMI on " & strComputer Else WScript.Echo "Successfully bound to WMI on " & strComputer End If

IsObject

Deze functie doet eigenlijk het omgekeerde van de vergelijking Is Nothing

Als je een geldige referentie naar een object (maakt niet uit wat voor) aan IsObject geeft krijg je de waarde true terug, anders false.

Het volgende script test of je succesvol een (printer) object hebt aangemaakt:

On Error Resume Next strPrinter = "TestPrinter" Set objPrinter = GetObject _ ("winmgmts:root\cimv2:Win32_Printer.Name='" & strPrinter & "'") If IsObject(objPrinter) Then WScript.Echo "Connected to printer " & strPrinter Else WScript.Echo "Unable to connect to printer " & strPrinter End If

Resultaten van methods

Methods geven altijd een code 0 terug als het goed is gegaan.

Als er iets mis gaat komt er een geheel positief getal terug, maar de waardes daarvan variëren per method.

Je zou bv. een functie kunnen schrijven die als parameter een object van het type Win32_Process meekrijgt, en vervolgens probeert dat proces te beëindigen.

Afhankelijk van de code die het oplevert (in variable intReturn) kan de functie een andere actie ondernemen (de onderstaande functie drukt steeds alleen maar de status af).

Function TerminateProcess(objProcess) On Error Resume Next intReturn = objProcess.Terminate Select Case intReturn Case 0 Wscript.Echo "Return code " & intReturn & " - Terminated" Case 2 Wscript.Echo "Return code " & intReturn & " - Access denied" Case 3 Wscript.Echo "Return code " & intReturn & " - Insufficient privilege" Case 8 Wscript.Echo "Return code " & intReturn & " - Unknown failure" Case 9 Wscript.Echo "Return code " & intReturn & " - Path not found" Case 21 Wscript.Echo "Return code " & intReturn & " - Invalid parameter" Case Else Wscript.Echo "Return code " & intReturn & _ " - Unable to terminate for undetermined reason" End Select TerminateProcess = intReturn End Function

WMI-fouten

WbemErrorEnum

In geval van een WMI-fout komt er informatie terug in WbemErrorEnum

Je moet echter waardes verwachten als wbemErrInvalidFlavor, en deze zelfde informatie zit ook in de property Description van het Err-object.

Bovendien is de waarde in WbemErrorEnum moeilijk te benaderen.

SWbemLastError

Door het object SWbemLastError te creëren kunnen we ook meer informatie over de laatste fout krijgen.

Dat doe je met het volgende commando:
Set WMI_Error = CreateObject("WbemScripting.SwbemLastError")

SWbemLastError heeft een aantal parameters die interessant zouden kunnen zijn in troubleshooting: Operation, ParameterInfo en ProviderName

Je moet het object pas aanmaken na het punt in het script waar een fout zou kunnen optreden.

De benodigde subroutine en een voorbeeld kan je hier vinden.

Fouten bij werken met het register

De klasse StdRegProv die je nodig hebt om met het register te werken, bevat alleen methods. Deze kunnen een waarde 0 (succes) of iets anders teruggeven.

Aangezien dat 'iets anders' niet (goed) gedocumenteerd is, is het het beste gewoon te controleren op succes of niet.

In onderstaande code wordt eerst verbinding gemaakt met de registerklasse op de lokale computer, en dan wordt de waarde van een niet bestaande sleutel (hnetmonh) in het register opgehaald.

On Error Resume Next strComputer = "." 'Change to non-existent host to create binding error. Const HKLM = &H80000002 strSubKeyName = "SOFTWARE\Microsoft\NetSh" strEntryName = "hnetmonh" 'Connect to WMI and StdRegProv class. Set objReg = GetObject("winmgmts:\\" & strComputer & _ "\root\default:StdRegProv") If Err = 0 Then WScript.Echo vbCrLf & "Bind success" WScript.Echo vbCrLf & "Computer: " & strComputer Else WScript.Echo "ERROR: Unable to bind to WMI provider on " & strComputer & "." WScript.Quit End If 'Get string value from entry and check return value. intRet = objReg.GetStringValue(HKLM, strSubKeyName, strEntryName, strValue) If intRet = 0 Then WScript.Echo vbCrLf & "Registry success" WScript.Echo "Registry Path: HKLM\" & strSubKeyName & "\" & strEntryName WScript.Echo "Entry Value: " & strValue Else WScript.Echo vbCrLf & "ERROR: Unable to retrieve value of registry " & _ "entry HKLM\" & strSubKeyName & "\" & strEntryName & vbCrLf & _ "Return value: " & intRet End If

Gek genoeg, wanneer je de subroutine gebruikt waarmee je foutinformatie kunt weergeven, dan geeft bovenstaand script geen fout, omdat Err kennelijk de waarde 0 heeft.

Maar wanneer je de waardes van strSubKeyName en strEntryName uitcommentariëert, dan krijg je ineens wel een fout:

ERROR: Unable to retrieve value of registry entry HKLM\\ Return value: Number (dec) : -2147217400 Number (hex) : &H80041008 Description : Invalid parameter Source : SWbemObjectEx Operation : ExecMethod ParameterInfo: StdRegProv ProviderName : WinMgmt

ADSI-fouten

Deze sectie is voor de meeste thuisgebruikers niet van belang, want het gaat hier om Active Directory, en dat wordt bijna alleen in (grotere) bedrijven gebruikt.

Op de site van Microsoft is deze speciale pagina gewijd aan ADSI-fouten.

ADsGetLastError

Deze waarde, die vergelijkbaar is met SWbemLastError voor WMI, is voor scripting-talen niet toegankelijk, dus vanuit VBScript moet je andere methodes gebruiken.

4 soorten fouten

Generic COM-fouten

5 van de 6 zien eruit als 0xH8000400x, en de laatste is een soort doomsday-fout: 0xH8000FFFF (catastophic failure).

Generic ADSI-fouten

3 zien eruit als 0xH000050xx en de andere 18 als 0xH800050xx

Win32-foutcodes

Volgt later.

LDAP-foutcodes

Volgt later.

Verdere informatie

Op de site van Microsoft een serie artikelen ("To Err is VBScript") over geavanceerde foutzoektechnieken.


Learn Beginning Scripting
Deze pagina bevat links naar een aantal columns, geschreven in de jaren 2005-2007, om de beginner te leren programmeren in VBScript. De linkerkolom is voor de *absolute* beginner, de rechter voor de beginner+. De artikelen zijn vaak heel humoristisch geschreven. Zeer de moeite waard, hoewel VBScript meer en meer zal worden vervangen door PowerShell.
Tales from the Script
Artikelen geschreven in de periode 2002-2005, bedoeld voor beginnende scripting gebruikers.
Hey, Scripting Guy! Blog - Learn about Windows PowerShell
124 pagina's (mei 2013) met tientallen links naar blogs van de Scripting Guy waarin hij vragen van gebuikers beantwoordt. Deze link is naar de oudste pagina (op dit moment), en daar gaan de blogs nog over VBScript. Op de meest recente pagina's gaan de blogs over PowerShell.
Doctor Scripto's Script Shop
Vast en zeker interessante artikelen over scripting, maar ik heb er nog niet naar gekeken.
Scripting Clinic
Ik denk dat dit een verzameling wat oudere artikelen is (maar daarom niet noodzakelijk minder interessant).
VBScript Language Reference
Een complete beschrijving van de taal VBScript, onderverdeeld in secties als Constants, Functions, Keywords, enz.
Functions (VBScript)
Een overzicht van alle functies in VBScript.
Windows Script Components Have a COM-ing Effect
Een voorbeeld van het gebruik van Windows Script Components.
Scripting
Uitvoerige documentatie over scripting, i.h.b. over Windows Script Components.
Separating lines from multi-line Excel cell
Een script om regels in een Excel-cel op te splitsen.


    Tweeten

© Henk Dalmolen
Reageer via E-mail (dalmolen@xs4all.nl)

Deze pagina is voor het laatst gewijzigd op: 12-02-23 13:29:26