1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
| '......................................................................................
'... rsyncBackup.vbs 1.01 .............................................................
'... auteurs: Karsten Violka / Noud van Kruysbergen (nvkruysbergen@fnl.nl) ............
'... c't 2006/0607 ....................................................................
'......................................................................................
'
' Versie 1.01 19.04.06 geen extra back-upbestanden meer die op "~" eindigen
' (switch "b" verwijdert, functie removePathLines() toegevoegd)
' Versie 1.00 14.04.06 eerste openbare versie
'--------------------------------------------------------------------------------------
' Bekende problemen:
' -- rsync kopieert geen geopende bestanden
' -- rsync kopieert alleen paden met een lengte tot 260 tekens
' -- rsync kopieert geen specialiteiten van NTFS (junctions, streams, sparse files)
'
' Start het script met lage prioriteit:
' start /min /belownormal cscript.exe rsyncBackup.vbs
'
' Als je het script onder een ander account met beheersrechten wilt starten
' moet je het met rsyncStart.bat aanroepen.
' Het batchbestand zet de omgevingsvariabele "cygwin=nontsec", waardoor
' Cygwin geen ACL's aan de geback-upte bestanden hangt. Daardoor kun je
' de back-ups zonder problemen onder je beperkte gebruikersaccount lezen.
'--------------------------------------------------------------------------------------
Option Explicit
'--------------------------------------------------------------------------------------
'----- BEGIN configuratie -------------------------------------------------------------
'--------------------------------------------------------------------------------------
Dim sourceFolders
' Bronmappen
' Belangrijk: Voeg geen backslash toe aan de bronpaden, omdat rsync voor
' iedere bronmap een aparte submap aanmaakt op de back-upschijf.
'Voorbeeld:
'sourceFolders = Array("c:\Documents and Settings\Monique")
sourceFolders = Array("c:\Documents and Settings\Monique", "c:\Documents and Settings\Chrit", "d:\werk")
Dim excludeFiles
excludeFiles = Array("Cache", "parent.lock", "Temp*")
' De doelmap moet zich bevinden op een schijf die met NTFS geformateerd is
'Voorbeeld:
'const DESTINATION="H:\BC"
const DESTINATION="K:\BC0309"
' Aantal bewaarde back-ups:
const STAGE0_HOURLY = 10000
const STAGE1_DAILY = 14
const STAGE2_WEEKLY = 10
'Uitbreiding: als je de constante COMPARE_CHECKSUMS op true zet,
'roept het script rsync aan met de optie --checksum (zie Manpage). Om achter het aantal
'bestandenUm te kokmen die bij een incrementele bac-kup gekopieerd moeten worden,
'gebruikt rsync normaal gesproken het tijdstip van de laatste wijziging. Als deze switch
'gezet os wordemn daarentegen alle bestanden gelezen, checksums gemaakt en
'worden ze met het origineel vergeleken.
'Deze modus kan echter aanzienlijk meer tijd kosten.
'Deze optie kan als vervanging van de ontbrekende Verify-funcite dienen: als je in het
'logbestand ziet dat rsync bestanden opnieuw kopieert terwijl die sinds de laatste
'back-up niet gewijzigd zijn, kan het zijn dat de bestanden op het back-upmedium
'niet meer goed zijn.
const COMPARE_CHECKSUMS=false
'--------------------------------------------------------------------------------------
'----- EINDE configuratie -------------------------------------------------------------
'--------------------------------------------------------------------------------------
const STAGE1_DAILY_FOLDER = "\_niveau1"
const STAGE2_WEEKLY_FOLDER= "\_niveau2"
const STAGE3_MONTHLY_FOLDER= "\_niveau3"
' constanten voor ADO
const adVarChar = 200
const adDate = 7
' veldnamen voor ecordSet
Dim rsFieldNames
rsFieldNames = Array("name", "date")
'---- globaal gebruikte variabelen
Dim fso, wsh
set fso = CreateObject("Scripting.FileSystemObject")
set wsh = CreateObject("WScript.Shell")
'---- Het logbestand wordt in de profielmap aangemaakt, bijvoorbeeld:
'---- c:\Documents and Settings\Noud\rsyncBackup.log
Dim logFile
logFile = wsh.ExpandEnvironmentStrings("%userprofile%") & "\rsyncBackup.log"
Dim strSourceFolder, recentBackupFolder, strDestinationFolder
Set recentBackupFolder = Nothing
Dim strCmd, cmdResult
logAppend(vbCRLf & "-------- Start: " & Now & " --------------------------------------------")
checkFolders()
strDestinationFolder = DESTINATION & "\" & getDateFolderName()
Set recentBackupFolder = getRecentFolder(DESTINATION)
'-- met Dry-Run testen of de inhoud van de bronmap gewijzigd is
If sourceChanged() Then
strCmd=getRsyncCmd(false)
logAppend("--- rsync-commandoregel:")
logAppend(strCmd)
cmdResult=callCmd(strCmd)
logAppend("--- uitvoervon rsync:" & vbCrLf & toCrLf(removePathLines(cmdResult(1))))
If Len(cmdResult(2)) > 0 Then
logAppend("--- foutmeldingen:" & vbCrLf & toCrLf(cmdResult(2)))
End If
logAppend("--- errorlevel: " & cmdResult(0))
Else
logAppend("--- niets nieuws")
End If
'-- back-ups doorschuiven en oude back-ups verwijderen
rotate getFolderObject(DESTINATION), _
getFolderObject(DESTINATION & STAGE1_DAILY_FOLDER), STAGE0_HOURLY, "d"
rotate getFolderObject(DESTINATION & STAGE1_DAILY_FOLDER), _
getFolderObject(DESTINATION & STAGE2_WEEKLY_FOLDER), STAGE1_DAILY, "ww"
rotate getFolderObject(DESTINATION & STAGE2_WEEKLY_FOLDER), _
getFolderObject(DESTINATION & STAGE3_MONTHLY_FOLDER), STAGE2_WEEKLY, "m"
logAppend("-------- klaar: " & Now & " --------------------------------------------")
'---------------------------------------------------------------------------------------
'--- funkties --------------------------------------------------------------------------
'---------------------------------------------------------------------------------------
'--- checkFolders() --------------------------------------------------------------------
' test of de ingevoerde paden goed zijn
Function checkFolders()
Dim aSourceFolder
For Each aSourceFolder in sourceFolders
If Not fso.FolderExists(aSourceFolder) Then
criticalErrorHandler "checkFolders()", "bronmap '" & aSourceFolder & "' bestaat niet.", 0, ""
End If
Next
If Not fso.DriveExists(fso.getDriveName(DESTINATION)) Then
criticalErrorHandler "checkFolders()", "doelschijf " & fso.getDriveName(DESTINATION) & " niet gevonden", 0, ""
End If
dim d, f
If Not fso.getDrive(fso.getDriveName(DESTINATION)).FileSystem = "NTFS" Then
logAppend("--- waarschuwing: Doelpad " & DESTINATION & " staat niet op een NTFS-schijf!")
logAppend("--- waarschuwing: rsync maakt daar geen hard-Links, maar volledige kopieën")
End If
End Function
'--- sourceChanged() -------------------------------------------------------------------
' geeft "true" terug als een test door rsync oplevert dat er in de bronmappen sinds
' de laatste back-up bestanden gewijzigd zijn.
Function sourceChanged()
dim strCmd, cmdResult, arrayOutput
cmdResult = callCmd(getRsyncCmd(true)) ' commando opbouwen met dryRun
arrayOutput=Split(cmdResult(1), "" & Chr(10) & "", -1, 1)
'-- als er in de vierde regel al "sent" staat is er niets veranderd
If Left(arrayOutput(3), 4) = "sent" Then
sourceChanged=false
Else
sourceChanged=true
End If
End Function
'--- getRsyncCmd() ----------------------------------------------------------------------
' stelt het rsync-commando samen; de parameter "true" schakelt de dryRun-Modus in,
' waarmee een test wordt gestart.
'
' In Version 1.01 is de switch "b" weer verwijderd: deze zorgt er voor dat
' rsync in een nieuwe map de back-upbestanden gewijzigde bestanden voorspiegelt, die
' eindigen op een tilde "~". Zonder deze switch wordt de uitvoer van rsync echter erg
' onoverzichtelijk: rsync toont dan iedere keer alle doorzochte bronmappen,
' of er nou wat nieuws is zit of niet. De funcite removePathLines() filtert deze
' overbodige regels er weer uit.
' bebruikte rsync-parameters:
' a Archiv-Modus Bronnen recursief en geheel kopiëren
' v Verbose Uirgebreide uitvoer, toont alle nieuw gekopieerde bestanden
' c Optioneel, rsync berekent checksums und vergelijkt die met
' de bestandsinhoud, om het aantal te kopiëren bestanden te bepalen
' n Dryrun
Function getRsyncCmd(dryRun)
dim cmd, aSourceFolder, aExcludeFile
cmd = wsh.ExpandEnvironmentStrings("%comspec%") & " /c rsync -av"
If (COMPARE_CHECKSUMS = true) Then
cmd = cmd & "c"
End If
If (dryRun = true) Then
cmd = cmd & "n"
End If
If Not recentBackupFolder Is Nothing Then
cmd = cmd & " --link-dest=""" _
& toCygwinPath(recentBackupFolder.Path) & """"
End If
For Each aExcludeFile in excludeFiles
cmd = cmd & " --exclude """ & aExcludeFile & """"
Next
For Each aSourceFolder in sourceFolders
cmd = cmd & " """ & toCygwinPath(aSourceFolder) & """"
Next
cmd = cmd & " """ & toCygwinPath(strDestinationFolder) & """"
getRsyncCmd = cmd
End Function
'--- getDateFolderName()------------------------------------------------------------
' maakt een mapnaam aan met de huidige datum en tijd
Function getDateFolderName()
Dim nu
nu = Now()
getDateFolderName = Year(nu) & "-" & addLeadingZero(Month(nu))_
& "-" & addLeadingZero(Day(nu))_
& "_" & addLeadingZero(Hour(nu))_
& "~" & addLeadingZero(Minute(nu))
End Function
'--- addLeadingZero(number) -------------------------------------------------------------
' voegt bij getallen < 10 een leidende nul toe
Function addLeadingZero(number)
If number < 10 Then
number = "0" & number
End If
addLeadingZero = number
End Function
'--- getFolderObject(path) -------------------------------------------------------------
' levert voor het meegegeven pad een WSH-objekt van het type Folder
' als deze nog niet bestaat wordt die aangemaakt
Function getFolderObject(path)
If (fso.FolderExists(path)) Then
Set getFolderObject = fso.GetFolder(path)
Else
logAppend("--- maak map: " & path)
On Error Resume Next
Set getFolderObject = fso.CreateFolder(path)
If Err.Number <> 0 Then
On Error Goto 0
criticalErrorHandler "getFolderObject()", "kan doelmap niet aanmaken", Err.Number, Err.Description
End If
On Error Goto 0
End If
End Function
'--- toCygwinPath(String) -----------------------------------------------------------------
' converteert een Windows-pad in het formaat dat Cygwin verwacht
Function toCygwinPath(path)
Dim driveLetter, newPath
driveLetter = Left(fso.GetDriveName(path), 1)
newPath = Replace(path, "\", "/")
newPath = Mid(newPath, 4)
toCygwinPath = "/cygdrive/" & driveLetter & "/" & newPath
End Function
'--- toCrLf(String) -----------------------------------------------------------------------
' vervangt het door rsync gegeven Unix-regeleinde (LF)
' door het gebruikelijke Windows-formaat (CRLF)
Function toCrLf(strText)
toCrLf = Replace(strText, vbLf, vbCrLf)
End Function
'--- removePathLines(String) -----------------------------------------------------------------------
' verwijdert alle regels die op een backslash eindigen
' rsync toont normaal gesproken alle paden, die op nieuwe bestanden gecontroleerd worden,
' ook als er daar niets verandert is; deze routine verwijdert deze regels,
' zodat het logbesatnd overzichtelijk blijft
Function removePathLines(strText)
Dim arrayText, line
arrayText=Split(strText, "" & Chr(10) & "", -1, 1) ' de uitvoer moet in Unix-formaat,
' met LF als regeleinde
For Each line in arrayText
If Not Right(line, 1) = "/" Then
removePathLines = removePathLines & line & vbLF
End If
Next
End Function
'--- logAppend(String) --------------------------------------------------------------------
' voegt de meegegeven tekst aan het logbestand toe
Function logAppend(string)
const forAppend = 8
dim f, errnum
On Error Resume Next
Set f = fso.OpenTextFile(logFile, forAppend, true)
errnum = Err.Number
On Error Goto 0
If errnum = 0 Then
f.WriteLine(string)
f.Close()
Else
Err.Raise 1, "logAppend", "kan logbestand niet openen"
End If
End Function
'--- getRecentFolder(String) ---------------------------------------------------------------
' sorteert de meegegeven map op datum en levert het jongste item terug
Function getRecentFolder(path)
Dim destinationFolder, rs
Set destinationFolder = getFolderObject(path)
Set rs=newFolderRecordSet(destinationFolder)
If Not (rs.Eof) Then
rs.sort = "date DESC" ' aflopend op aanmaakdatum sorteren
rs.MoveFirst
Set getRecentFolder= fso.GetFolder(rs.fields("name"))
Else
Set getRecentFolder = Nothing
End If
rs.Close
Set rs = Nothing
End Function
'--- newFolderRecordSet(Folder-Objekt) -----------------------------------------------------
' zet de submappen van de meegegeven map in de een nieuwe RecordSet,
' die gebruikt wordt voor het sorteren
Function newFolderRecordSet(folder)
Dim aFolder
Set newFolderRecordSet = CreateObject("ADODB.Recordset")
newFolderRecordSet.Fields.Append "name", adVarChar, 255
newFolderRecordSet.Fields.Append "date", adDate
newFolderRecordSet.Open
For Each aFolder in folder.SubFolders
If Left(aFolder.Name, 2) = "20" Then ' alleen de datumma in de lijst opnemen
newFolderRecordSet.addnew rsFieldNames, Array(aFolder.Path, aFolder.DateCreated)
End if
Next
End Function
'--- rotate(fromFolder, toFolder, numberToKeep, diffInterval) ------------------------------
' verplaatst of verwijdert de back-upmap; voor ieder tijdsinterval (dag, week, maand) wordt
' de laatst gemaakte back-up gearchiveerd
Function rotate(fromFolder, toFolder, numberToKeep, diffInterval)
Dim rs, aFolder, lastFolder, i, recentBackup, errNr
Set rs=newFolderRecordSet(fromFolder)
If Not (rs.Eof) Then
rs.Sort = "date DESC"
rs.MoveFirst
i = 0
Do until rs.Eof
If i >= numberToKeep Then
'MsgBox("übrig:" & rs.fields("name"))
' de laatste back-up van deze datum uit toFolder halen; als die nieuwer is dan vervangen
Set recentBackup = getRecentBackupForDate(toFolder, rs.fields("date"), diffInterval)
On Error Resume Next
If Not recentBackup Is Nothing Then
' als de geselecteerde back-up van hetzelfde tijdsinterval is (dag) en
' laer gemaakt is, moet de back-up in de doelmap vervangen worden
If DateDiff("s", recentBackup.DateCreated, rs.fields("date")) > 0 Then
'MsgBox("selber Tag & neuer: bewegen")
logAppend("--- verplaats " & rs.fields("name") & " naar " & toFolder.Path)
fso.MoveFolder fso.GetFolder(rs.fields("name")), toFolder.Path & "\"
If Err.Number <> 0 Then
ErrNr=Err.Number
On Error Goto 0
criticalErrorHandler "rotate()", "kon de map niet verplaatsen", Err.Number, Err.Description
End If
'MsgBox("vorige versie verwijderen")
logAppend("--- vorige versie verwijderen " & recentBackup)
fso.DeleteFolder recentBackup, true
If Err.Number <> 0 Then
On Error Goto 0
criticalErrorHandler "rotate()", "kon de map niet verwijderen", Err.Number, Err.Description
End If
Else
logAppend("--- verwijder " & rs.fields("name"))
'MsgBox("zelfde dag en tijd: weg ermee")
fso.DeleteFolder fso.GetFolder(rs.fields("name")), true
If Err.Number <> 0 Then
On Error Goto 0
criticalErrorHandler "rotate()", "kon de map niet verwijderen", Err.Number, Err.Description
End If
End If
Else
' van deze dag bestaat nog geen back-up
'MsgBox("bestaat nog niet, verplaatsen!")
logAppend("--- verplaats " & rs.fields("name") & " naar " & toFolder.Path)
fso.MoveFolder fso.GetFolder(rs.fields("name")), toFolder.Path & "\"
If Err.Number <> 0 Then
On Error Goto 0
criticalErrorHandler "rotate()", "kon de map niet verplaatsen", Err.Number, Err.Description
End If
End If
On Error Goto 0
End If
i = i + 1
rs.MoveNext
Loop
End If
rs.Close
Set rs = Nothing
End Function
'--- getRecentBackupForDate(folderObj, aDate, diffInterval) -----------------------------
' sorteert de submap met behulp van de ADO RecordSet en geeft de laatste back-up
' de aangegeven dag/week/maand --> diffInterval
Function getRecentBackupForDate(folderObj, aDate, diffInterval)
Dim rs, exitLoop
Set getRecentBackupForDate = Nothing
Set rs=newFolderRecordSet(folderObj)
If Not (rs.Eof) Then
rs.Sort = "date DESC"
rs.MoveFirst
exitLoop=false
Do until rs.Eof Or exitLoop
If DateDiff(diffInterval, rs.fields("date"), aDate) = 0 Then
set getRecentBackupForDate = fso.GetFolder(rs.fields("name"))
exitLoop = true
End If
rs.MoveNext
Loop
End If
rs.Close
Set rs = Nothing
End Function
'--- criticalErrorHandler(source, description, errNumber, errDescription) ---------------
' kritische fouten loggen en programma beëindigen. Voor de aanroep moet de
' foutafhandeling met "On Error Goto 0" weer ingeschakeld worden, zodat het script
' met de nieuw gemaakte fout afbreekt
Function criticalErrorHandler(source, description, errNumber, errDescription)
logAppend("--- fout: functie " & source & ", " & description)
logAppend(" Err.Number: " & errNumber & " Err.Description:" & errDescription)
logAppend("-------- gestopt: " & Now & " --------------------------------------------")
Err.Raise 1, source, description
End Function
'--- callCmd(strCommand) ----------------------------------------------------------------
' voert de commandoregel uit en geef Array terug:
' Index 0: Errorlevel
' Index 1: uitvoer
' Index 2: foutmelding
Function callCmd(strCommand)
Dim strTempFile, strTempFile2, outputFile, result, strOutput, strOutput2
strTempFile = fso.GetSpecialFolder(2) & "\" & fso.GetTempName
strTempFile2 = fso.GetSpecialFolder(2) & "\" & fso.GetTempName
strCommand = strCommand & " 1>" & strTempFile & " 2>" & strTempFile2
result = wsh.Run(strCommand, 0, true)
If fso.FileExists(strTempFile2) And fso.GetFile(strTempFile2).Size > 0 Then
Set outputFile = FSO.OpenTextFile(strTempFile2)
strOutput2 = outputFile.Readall
outputFile.Close
Else
strOutput2 = ""
End If
If fso.FileExists(strTempFile) And fso.GetFile(strTempFile).Size > 0 Then
Set outputFile = FSO.OpenTextFile(strTempFile)
strOutput = outputFile.Readall
outputFile.Close
callCmd = Array(result, strOutput, strOutput2)
deleteInsistently(strTempFile)
deleteInsistently(strTempFile2)
Else
criticalErrorHandler "callCmd()", "commando mislukt: " & strCommand & vbCrLf & "--- foutmelding: " & strOutput2, 0, ""
End If
End Function
'--- deleteInsistently(strFileName) -----------------------------------------------------
' Op sommige testsystemen trad een fout op, omdat de functie callCmd() diens
' tijdelijke bestandn niet kon verwijderen; vermoedelijk blokkeert een virusscanner
' dat bestand op dat moment. De Functi deleteInsistently() onderneemt daarom 10 pogingen
' om het meegegeven bestand te verwijderen; als dat mislukt probeer het script het 5
' seconde later weer
Function deleteInsistently(strFileName)
Dim noOfTries, successful
On Error Resume Next
noOfTries=0
successful=false
While noOfTries < 10 And Not successful
Err.Clear
If fso.FileExists(strFileName) Then
fso.DeleteFile(strFileName)
If Err.Number <> 0 Then
successful=false
noOfTries = noOfTries + 1
logAppend("--- Waarschuwing: kon tijdelijke bestand " & strFileName & " niet verwijderen, poging " & noOfTries)
Wscript.Sleep(5000)
Else
successful=true
End If
Else
successful=true
End If
Wend
On Error Goto 0
If Not successful Then
logAppend("--- waarschuwing: ik geef het op")
End If
End Function |