SHOW:
|
|
- or go back to the newest paste.
1 | - | :: Purpose: Rotating differential backup using 7-Zip for compression. |
1 | + | :: Purpose: Rotating differential backup using 7-Zip for compression. |
2 | - | :: Requirements: - forfiles.exe from Microsoft |
2 | + | :: Requirements: - forfiles.exe from Microsoft |
3 | - | :: - 7-Zip |
3 | + | :: - 7-Zip |
4 | - | :: Author: vocatus on reddit.com/r/usefulscripts |
4 | + | :: Author: vocatus on reddit.com/r/usefulscripts |
5 | - | :: Version: 1.4.6 + Added two additional "help" flags: "-h" and "--help" |
5 | + | :: Version: 1.4.7 - Removed some redundant %TIME% stamps in the logs, under the Cleanup section |
6 | - | :: 1.4.5 + Added better instructions for what variables can be set to |
6 | + | :: 1.4.6 + Added two additional "help" flags: "-h" and "--help" |
7 | - | :: 1.4.4 + Added quotes around variables that could contain spaces in a few places |
7 | + | :: 1.4.5 + Added better instructions for what variables can be set to |
8 | - | :: * Some comment cleanup, and some logging formatting cleanup. |
8 | + | :: 1.4.4 + Added quotes around variables that could contain spaces in a few places |
9 | - | :: 1.4.3 / Tweaked logging to only display exclamation point if there was an error |
9 | + | :: * Some comment cleanup, and some logging formatting cleanup. |
10 | - | :: / Tweaked logging to display time correctly (no extra spaces) |
10 | + | :: 1.4.3 / Tweaked logging to only display exclamation point if there was an error |
11 | - | :: 1.4.2 / Some logging tweaks |
11 | + | :: / Tweaked logging to display time correctly (no extra spaces) |
12 | - | :: 1.4.1 / Some logging tweaks |
12 | + | :: 1.4.2 / Some logging tweaks |
13 | - | :: 1.4 + Added quotes around SOURCE variable that were missing in a couple places |
13 | + | :: 1.4.1 / Some logging tweaks |
14 | - | :: 1.3 * Fixed backup archiving - wasn't rotating properly on network paths. We use pushd to get around this |
14 | + | :: 1.4 + Added quotes around SOURCE variable that were missing in a couple places |
15 | - | :: - Added "-s" option to show job options that the script WOULD execute with |
15 | + | :: 1.3 * Fixed backup archiving - wasn't rotating properly on network paths. We use pushd to get around this |
16 | - | :: - Major overhaul of code order and logic. Stuff should break less now. |
16 | + | :: - Added "-s" option to show job options that the script WOULD execute with |
17 | - | :: - Many fixes to exclusions file checking |
17 | + | :: - Major overhaul of code order and logic. Stuff should break less now. |
18 | - | :: 1.2 - Fixed problem with cleaning backups. Now cleans staging area and long-term destination area. |
18 | + | :: - Many fixes to exclusions file checking |
19 | - | :: Normally an archive (-a) switch cleans the staging area, but now the -c switch does as well, just in case. |
19 | + | :: 1.2 - Fixed problem with cleaning backups. Now cleans staging area and long-term destination area. |
20 | - | :: - Also fixed issue with forfiles.exe not being able to read UNC paths. We use pushd to get around this. |
20 | + | :: Normally an archive (-a) switch cleans the staging area, but now the -c switch does as well, just in case. |
21 | - | :: Pushd auto-assigns the next available drive letter to a UNC path, then discards it when we use popd. |
21 | + | :: - Also fixed issue with forfiles.exe not being able to read UNC paths. We use pushd to get around this. |
22 | - | :: 1.1 - Added option to use an exclude file to specify files/folders to exclude from the backup |
22 | + | :: Pushd auto-assigns the next available drive letter to a UNC path, then discards it when we use popd. |
23 | - | :: 1.0b Some tweaks to logging |
23 | + | :: 1.1 - Added option to use an exclude file to specify files/folders to exclude from the backup |
24 | - | :: 1.0 Initial write |
24 | + | :: 1.0b Some tweaks to logging |
25 | :: 1.0 Initial write | |
26 | - | :: Notes: My intention for this script was to keep the logic controlling schedules, backup type, etc out of the script and |
26 | + | |
27 | - | :: let an invoking program handle it (e.g. Task Scheduler). You simply run this script with a flag to perform an action. |
27 | + | :: Notes: My intention for this script was to keep the logic controlling schedules, backup type, etc out of the script and |
28 | - | :: If you want to schedule monthly backups, purge old files, etc, just set up task scheduler jobs for those tasks, |
28 | + | :: let an invoking program handle it (e.g. Task Scheduler). You simply run this script with a flag to perform an action. |
29 | - | :: where each job calls the script with a different flag. |
29 | + | :: If you want to schedule monthly backups, purge old files, etc, just set up task scheduler jobs for those tasks, |
30 | :: where each job calls the script with a different flag. | |
31 | - | :: Usage: Run this script without any flags for a list of possible actions. Run it with a flag to perform that action. |
31 | + | |
32 | - | :: Flags: |
32 | + | :: Usage: Run this script without any flags for a list of possible actions. Run it with a flag to perform that action. |
33 | - | :: -f create full backup |
33 | + | :: Flags: |
34 | - | :: -d create differential backup (full backup must already exist) |
34 | + | :: -f create full backup |
35 | - | :: -r restore from a backup (extracts to your staging area) |
35 | + | :: -d create differential backup (full backup must already exist) |
36 | - | :: -a archive (close out/rotate) the current backup set. This: |
36 | + | :: -r restore from a backup (extracts to your staging area) |
37 | - | :: 1. moves all .7z files in the %DESTINATION% into a folder named with the current date |
37 | + | :: -a archive (close out/rotate) the current backup set. This: |
38 | - | :: 2. deletes all .7z files from the staging area |
38 | + | :: 1. moves all .7z files in the %DESTINATION% into a folder named with the current date |
39 | - | :: -c clean up (delete) old backup sets from staging and destination. If you specify a number |
39 | + | :: 2. deletes all .7z files from the staging area |
40 | - | :: of days after the command it will run automatically without any confirmation. Be careful with this! |
40 | + | :: -c clean up (delete) old backup sets from staging and destination. If you specify a number |
41 | - | :: -s show job options (show what the variables are set to) |
41 | + | :: of days after the command it will run automatically without any confirmation. Be careful with this! |
42 | :: -s show job options (show what the variables are set to) | |
43 | - | :: Important: If you want to set this script up in Windows Task Scheduler, be aware that Task Scheduler |
43 | + | |
44 | - | :: can't use mapped network drives (X:\, Z:\, etc) when it is set to "Run even if user isn't logged on." |
44 | + | :: Important: If you want to set this script up in Windows Task Scheduler, be aware that Task Scheduler |
45 | - | :: The task will simply fail to do anything (because Scheduler can't see the drives). To work around this use |
45 | + | :: can't use mapped network drives (X:\, Z:\, etc) when it is set to "Run even if user isn't logged on." |
46 | - | :: UNC paths instead (\\server\backup_folder etc) for your source, destination, and staging areas. |
46 | + | :: The task will simply fail to do anything (because Scheduler can't see the drives). To work around this use |
47 | :: UNC paths instead (\\server\backup_folder etc) for your source, destination, and staging areas. | |
48 | ||
49 | - | :: 1. Add md5sum checksum file in the backup directory (md5sum each full and diff and store in a file) |
49 | + | |
50 | :: 1. Add md5sum checksum file in the backup directory (md5sum each full and diff and store in a file) | |
51 | ||
52 | ||
53 | :: Prep | |
54 | @echo off | |
55 | - | set VERSION=1.4.6 |
55 | + | |
56 | set VERSION=1.4.7 | |
57 | set SCRIPT_NAME=%0% | |
58 | ||
59 | ::::::::::::::: | |
60 | :: VARIABLES :: | |
61 | ::::::::::::::: | |
62 | :: Rules for variables: | |
63 | :: * NO quotes! (bad: "c:\directory\path" ) | |
64 | :: * NO trailing slashes on the path! (bad: c:\directory\ ) | |
65 | :: * Spaces are okay (okay: c:\my folder\with spaces ) | |
66 | - | :: (okay: \\172.16.1.5\share name ) |
66 | + | |
67 | :: ( \\172.16.1.5\share name ) | |
68 | ||
69 | :: Specify the folder you want to back up here. | |
70 | set SOURCE=C:\Users\vocatus\SuperImportantDocuments | |
71 | ||
72 | :: Work area where everything is stored while compressing. Should be a fast drive or something that can handle a lot of writes | |
73 | :: Recommend not using a network share unless it's Gigabit or faster. | |
74 | set STAGING=P:\backup_staging | |
75 | ||
76 | - | set DESTINATION=\\CoolServerName\backup_vocatus\folder with spaces\SuperImportantDocuments |
76 | + | |
77 | set DESTINATION=\\CoolServerName\backups\folder with spaces\SuperImportantDocuments | |
78 | ||
79 | :: If you want to customize the prefix of the backup files, do so here. Don't use any special characters (like underscores) | |
80 | :: The script automatically suffixes an underscore to this name. Recommend not changing this unless you really need to. | |
81 | :: * Spaces are NOT OKAY to use here! | |
82 | set BACKUP_PREFIX=backup | |
83 | ||
84 | :: OPTIONAL: If you want to exclude some files or folders, you can specify your exclude file here. The exclude file is a list of | |
85 | :: files or folders (wildcards in the form of * are allowed and recommended) to exclude. | |
86 | :: If you specify a file here and the script can't find it, it will abort. | |
87 | - | set EXCLUSIONS_FILE=C:\Users\vocatus\root\Scripts\sysadmin\backup_differential_excludes.txt |
87 | + | |
88 | set EXCLUSIONS_FILE=C:\Users\vocatus\Scripts\backup_differential_excludes.txt | |
89 | ||
90 | :: Log settings. Max size is how big (in bytes) the log can be before it is archived. 1048576 bytes is one megabyte | |
91 | set LOGPATH=%SystemDrive%\Logs | |
92 | - | set LOG_MAX_SIZE=2097152 |
92 | + | |
93 | set LOG_MAX_SIZE=104857600 | |
94 | ||
95 | :: Location of 7-Zip and forfiles.exe | |
96 | set SEVENZIP="C:\Program Files\7-Zip\7z.exe" | |
97 | set FORFILES=%WINDIR%\system32\forfiles.exe | |
98 | ||
99 | :: Don't touch anything below this line. If you do, you will break something. | |
100 | set CUR_DATE=%DATE:~-4%-%DATE:~4,2%-%DATE:~7,2% | |
101 | set JOB_TYPE=%1 | |
102 | set JOB_ERROR=0 | |
103 | set DAYS=%2 | |
104 | set RESTORE_TYPE=NUL | |
105 | ||
106 | ||
107 | :::::::::::::::::::::::::::: | |
108 | :: JOB TYPE DETERMINATION :: | |
109 | :::::::::::::::::::::::::::: | |
110 | :job_type_determination | |
111 | if '%JOB_TYPE%'=='' set JOB_TYPE=help | |
112 | if '%JOB_TYPE%'=='/?' set JOB_TYPE=help | |
113 | if '%JOB_TYPE%'=='-?' set JOB_TYPE=help | |
114 | if '%JOB_TYPE%'=='-h' set JOB_TYPE=help | |
115 | if '%JOB_TYPE%'=='--help' set JOB_TYPE=help | |
116 | if /i '%1'=='/f' set JOB_TYPE=full | |
117 | if /i '%1'=='-f' set JOB_TYPE=full | |
118 | if /i '%1'=='/d' set JOB_TYPE=differential | |
119 | if /i '%1'=='-d' set JOB_TYPE=differential | |
120 | if /i '%1'=='/r' set JOB_TYPE=restore | |
121 | if /i '%1'=='-r' set JOB_TYPE=restore | |
122 | if /i '%1'=='/a' set JOB_TYPE=archive_backup_set | |
123 | if /i '%1'=='-a' set JOB_TYPE=archive_backup_set | |
124 | if /i '%1'=='/c' set JOB_TYPE=cleanup_archives | |
125 | if /i '%1'=='-c' set JOB_TYPE=cleanup_archives | |
126 | if /i '%1'=='/s' goto show_options | |
127 | if /i '%1'=='-s' goto show_options | |
128 | :: If none of the above were specified then show the help screen | |
129 | if %JOB_TYPE%==help ( | |
130 | echo. | |
131 | echo %SCRIPT_NAME% v%VERSION% | |
132 | echo. | |
133 | echo Usage: %SCRIPT_NAME% ^< -f ^| -d ^| -r ^| -a ^| -c ^[days^] ^> | |
134 | echo. | |
135 | echo Flags: | |
136 | echo -f: create a full backup | |
137 | echo -d: create a differential backup ^(requires an existing full backup^) | |
138 | echo -r: restore from a backup ^(extracts to %STAGING%\%BACKUP_PREFIX%_restore^) | |
139 | echo -a: archive the current backup set. This will: | |
140 | echo 1. move all .7z files located in: | |
141 | echo %DESTINATION% | |
142 | echo into a dated archive folder. | |
143 | echo 2. purge ^(delete^) all copies in the staging area ^(%STAGING%^) | |
144 | echo -c: clean ^(AKA delete^) archived backup sets from staging and long-term storage. | |
145 | echo Optionally specify number of days to run automatically. Be careful with this! | |
146 | echo Note that this requires a previously-archived backup set ^(-a option^) | |
147 | echo -s: show job options ^(show what parameters the script WOULD execute with^) | |
148 | echo. | |
149 | echo Edit this script before running it to specify your source, destination, and work directories. | |
150 | goto end | |
151 | ) | |
152 | ||
153 | ||
154 | ::::::::::::::::::::::: | |
155 | :: LOG FILE HANDLING :: | |
156 | ::::::::::::::::::::::: | |
157 | :log | |
158 | :: Make the logfile if it doesn't exist | |
159 | if not exist %LOGPATH% mkdir %LOGPATH% | |
160 | if not exist %LOGPATH%\%LOGFILE% echo. > %LOGPATH%\%LOGFILE% | |
161 | ||
162 | :: Check log size. If it's less than our max, then go ahead and get started | |
163 | for %%R in (%LOGPATH%\%LOGFILE%) do if %%~zR LSS %LOG_MAX_SIZE% goto required_files_check | |
164 | ||
165 | :: If the log was too big, go ahead and rotate it. | |
166 | pushd %LOGPATH% 2>&1 | |
167 | del /F %LOGFILE%.ancient 2>NUL | |
168 | rename %LOGFILE%.oldest %LOGFILE%.ancient 2>NUL | |
169 | rename %LOGFILE%.older %LOGFILE%.oldest 2>NUL | |
170 | rename %LOGFILE%.old %LOGFILE%.older 2>NUL | |
171 | rename %LOGFILE% %LOGFILE%.old 2>NUL | |
172 | popd | |
173 | ||
174 | ||
175 | :::::::::::::::::::::::::: | |
176 | :: REQUIRED FILES CHECK :: | |
177 | :::::::::::::::::::::::::: | |
178 | :required_files_check | |
179 | :: Make sure we can find 7-Zip | |
180 | IF NOT EXIST %SEVENZIP% ( | |
181 | echo %TIME% ERROR: Couldn't find 7z.exe when script was invoked.>> %LOGPATH%\%LOGFILE% | |
182 | cls | |
183 | color 0c | |
184 | echo. | |
185 | echo ERROR: | |
186 | echo. | |
187 | echo Cannot find 7z.exe. You must edit this script | |
188 | echo and specify the location of 7-Zip before continuing. | |
189 | echo. | |
190 | echo Script tried to find it here: | |
191 | echo %SEVENZIP% | |
192 | echo. | |
193 | pause | |
194 | color | |
195 | cls | |
196 | goto end | |
197 | ) | |
198 | :: Make sure we can find forfiles.exe | |
199 | IF NOT EXIST %FORFILES% ( | |
200 | echo %TIME% ERROR: Couldn't find forfiles.exe when script was invoked.>> %LOGPATH%\%LOGFILE% | |
201 | cls | |
202 | color 0c | |
203 | echo. | |
204 | echo ERROR: | |
205 | echo. | |
206 | echo Cannot find forfiles.exe. You must edit this script | |
207 | echo and specify the location of forfiles.exe before continuing. | |
208 | echo. | |
209 | echo Script tried to find it here: | |
210 | echo %FORFILES% | |
211 | echo. | |
212 | pause | |
213 | color | |
214 | cls | |
215 | goto end | |
216 | ) | |
217 | ||
218 | ||
219 | :::::::::::::::::::: | |
220 | :: DECISION POINT :: | |
221 | :::::::::::::::::::: | |
222 | :decision_point | |
223 | if '%JOB_TYPE%'=='full' goto %JOB_TYPE% | |
224 | if '%JOB_TYPE%'=='differential' goto %JOB_TYPE% | |
225 | if '%JOB_TYPE%'=='restore' goto %JOB_TYPE% | |
226 | if '%JOB_TYPE%'=='archive_backup_set' goto %JOB_TYPE% | |
227 | if '%JOB_TYPE%'=='cleanup_archives' goto %JOB_TYPE% | |
228 | goto end | |
229 | ||
230 | ||
231 | :::::::::::::::::::::: | |
232 | :: SHOW JOB OPTIONS :: | |
233 | :::::::::::::::::::::: | |
234 | :show_options | |
235 | echo. | |
236 | echo Current configuration: | |
237 | echo. | |
238 | echo Script Version: %VERSION% | |
239 | echo Source: %SOURCE% | |
240 | echo Destination: %DESTINATION% | |
241 | echo Staging area: %STAGING% | |
242 | echo Exclusions file: %EXCLUSIONS_FILE% | |
243 | echo Backup prefix: %BACKUP_PREFIX% | |
244 | echo Restores unpacked to: %STAGING%\%BACKUP_PREFIX%_restore | |
245 | echo Log file: %LOGPATH%\%LOGFILE% | |
246 | echo Log max size: %LOG_MAX_SIZE% bytes | |
247 | echo. | |
248 | echo Edit this script with a text editor to customize these options. | |
249 | echo. | |
250 | goto end | |
251 | ||
252 | ||
253 | :::::::::::::::::::::::: | |
254 | :: CREATE FULL BACKUP :: | |
255 | :::::::::::::::::::::::: | |
256 | :full | |
257 | :: Check for an exclude file and make sure it exists. | |
258 | if '%EXCLUSIONS_FILE%'=='' goto full_go | |
259 | IF NOT EXIST %EXCLUSIONS_FILE% ( | |
260 | echo. | |
261 | echo %TIME% ERROR: An exclusions file was specified but couldn't be found:>> %LOGPATH%\%LOGFILE% | |
262 | echo %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
263 | echo %TIME% ERROR: An exclusions file was specified but couldn't be found: | |
264 | echo %EXCLUSIONS_FILE% | |
265 | goto end | |
266 | ) | |
267 | :full_go | |
268 | echo. | |
269 | echo.>> %LOGPATH%\%LOGFILE% | |
270 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
271 | echo Differential Backup Script v%VERSION% - initialized %CUR_DATE% at%TIME% by %USERDOMAIN%\%USERNAME%>> %LOGPATH%\%LOGFILE% | |
272 | echo.>> %LOGPATH%\%LOGFILE% | |
273 | echo Script location: %~dp0\%SCRIPT_NAME%>> %LOGPATH%\%LOGFILE% | |
274 | echo.>> %LOGPATH%\%LOGFILE% | |
275 | echo Job Options>> %LOGPATH%\%LOGFILE% | |
276 | echo Job type: Full backup>> %LOGPATH%\%LOGFILE% | |
277 | echo Source: %SOURCE%>> %LOGPATH%\%LOGFILE% | |
278 | echo Destination: %DESTINATION%>> %LOGPATH%\%LOGFILE% | |
279 | echo Staging area: %STAGING%>> %LOGPATH%\%LOGFILE% | |
280 | echo Exclusions file: %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
281 | echo Backup prefix: %BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
282 | echo Log location: %LOGPATH%\%LOGFILE%>> %LOGPATH%\%LOGFILE% | |
283 | echo Log max size: %LOG_MAX_SIZE% bytes>> %LOGPATH%\%LOGFILE% | |
284 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
285 | echo.>> %LOGPATH%\%LOGFILE% | |
286 | echo %TIME% Performing full backup of %SOURCE%...>> %LOGPATH%\%LOGFILE% | |
287 | echo %TIME% Performing full backup of %SOURCE%... | |
288 | ||
289 | :: Build archive | |
290 | echo. | |
291 | echo %TIME% Building archive in staging area %STAGING%...>> %LOGPATH%\%LOGFILE% | |
292 | echo %TIME% Building archive in staging area %STAGING%... | |
293 | - | echo [Beginning of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
293 | + | |
294 | echo ------- [ Beginning of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
295 | if not '%EXCLUSIONS_FILE%'=='' %SEVENZIP% a "%STAGING%\%BACKUP_PREFIX%_full.7z" "%SOURCE%" -xr@"%EXCLUSIONS_FILE%" >> %LOGPATH%\%LOGFILE% | |
296 | - | echo [End of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
296 | + | |
297 | echo ------- [ End of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
298 | echo.>> %LOGPATH%\%LOGFILE% | |
299 | echo. | |
300 | ||
301 | :: Report on the build | |
302 | if %ERRORLEVEL%==0 ( | |
303 | echo %TIME% Archive built successfully.>> %LOGPATH%\%LOGFILE% | |
304 | echo %TIME% Archive built successfully. | |
305 | ) | |
306 | if not %ERRORLEVEL%==0 ( | |
307 | set JOB_ERROR=1 | |
308 | echo %TIME% ! Archive built with errors.>> %LOGPATH%\%LOGFILE% | |
309 | echo %TIME% ! Archive built with errors. | |
310 | ) | |
311 | :: Upload to destination | |
312 | echo. | |
313 | echo %TIME% Uploading %BACKUP_PREFIX%_full.7z to %DESTINATION%...>> %LOGPATH%\%LOGFILE% | |
314 | echo %TIME% Uploading %BACKUP_PREFIX%_full.7z to %DESTINATION%... | |
315 | echo. | |
316 | echo.>> %LOGPATH%\%LOGFILE% | |
317 | xcopy "%STAGING%\%BACKUP_PREFIX%_full.7z" "%DESTINATION%\" /Q /J /Y /Z >> %LOGPATH%\%LOGFILE% | |
318 | echo.>> %LOGPATH%\%LOGFILE% | |
319 | ||
320 | :: Report on the upload | |
321 | - | echo %TIME% Uploaded full backup to %DESTINATION% successfully.>> %LOGPATH%\%LOGFILE% |
321 | + | |
322 | - | echo %TIME% Uploaded full backup to %DESTINATION% successfully. |
322 | + | echo %TIME% Uploaded full backup to '%DESTINATION%' successfully.>> %LOGPATH%\%LOGFILE% |
323 | echo %TIME% Uploaded full backup to '%DESTINATION%' successfully. | |
324 | ) ELSE ( | |
325 | - | echo %TIME% ! Upload of full backup to %DESTINATION% failed.>> %LOGPATH%\%LOGFILE% |
325 | + | |
326 | - | echo %TIME% ! Upload of full backup to %DESTINATION% failed. |
326 | + | echo %TIME% ! Upload of full backup to '%DESTINATION%' failed.>> %LOGPATH%\%LOGFILE% |
327 | echo %TIME% ! Upload of full backup to '%DESTINATION%' failed. | |
328 | ) | |
329 | ||
330 | goto done | |
331 | ||
332 | ||
333 | :::::::::::::::::::::::::::::::: | |
334 | :: CREATE DIFFERENTIAL BACKUP :: | |
335 | :::::::::::::::::::::::::::::::: | |
336 | :differential | |
337 | :: Check for an exclude file and make sure it exists. | |
338 | if '%EXCLUSIONS_FILE%'=='' goto differential_go | |
339 | IF NOT EXIST %EXCLUSIONS_FILE% ( | |
340 | echo %TIME% An exclusions file was specified but couldn't be found. Aborting.>> %LOGPATH%\%LOGFILE% | |
341 | echo Looked here: %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
342 | echo %TIME% An exclusions file was specified but couldn't be found. Aborting. | |
343 | echo Looked here: %EXCLUSIONS_FILE% | |
344 | goto end | |
345 | ) | |
346 | :differential_go | |
347 | echo.>> %LOGPATH%\%LOGFILE% | |
348 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
349 | echo Differential Backup Script v%VERSION% - initialized %CUR_DATE% at%TIME% by %USERDOMAIN%\%USERNAME%>> %LOGPATH%\%LOGFILE% | |
350 | echo.>> %LOGPATH%\%LOGFILE% | |
351 | echo Script location: %SCRIPT_NAME%>> %LOGPATH%\%LOGFILE% | |
352 | echo.>> %LOGPATH%\%LOGFILE% | |
353 | echo Job Options>> %LOGPATH%\%LOGFILE% | |
354 | echo Job type: Differential backup>> %LOGPATH%\%LOGFILE% | |
355 | echo Source: %SOURCE%>> %LOGPATH%\%LOGFILE% | |
356 | echo Destination: %DESTINATION%>> %LOGPATH%\%LOGFILE% | |
357 | echo Staging area: %STAGING%>> %LOGPATH%\%LOGFILE% | |
358 | echo Exclusions file: %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
359 | echo Backup prefix: %BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
360 | echo Log location: %LOGPATH%\%LOGFILE%>> %LOGPATH%\%LOGFILE% | |
361 | echo Log max size: %LOG_MAX_SIZE% bytes>> %LOGPATH%\%LOGFILE% | |
362 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
363 | echo.>> %LOGPATH%\%LOGFILE% | |
364 | echo. | |
365 | :: Check for full backup existence | |
366 | if not exist "%STAGING%\%BACKUP_PREFIX%_full.7z" ( | |
367 | set JOB_ERROR=1 | |
368 | echo %TIME% ! ERROR: Couldn't find full backup file ^(%BACKUP_PREFIX%_full.7z^). You must create a full backup before a differential can be created.>> %LOGPATH%\%LOGFILE% | |
369 | echo %TIME% ! ERROR: Couldn't find full backup file ^(%BACKUP_PREFIX%_full.7z^). You must create a full backup before a differential can be created. | |
370 | goto end | |
371 | ) ELSE ( | |
372 | :: Backup existed, so go ahead | |
373 | echo %TIME% Performing differential backup of %SOURCE%...>> %LOGPATH%\%LOGFILE% | |
374 | echo %TIME% Performing differential backup of %SOURCE%... | |
375 | ) | |
376 | ||
377 | :: Build archive | |
378 | :differential_build | |
379 | echo. | |
380 | echo %TIME% Building archive in staging area %STAGING%...>> %LOGPATH%\%LOGFILE% | |
381 | echo %TIME% Building archive in staging area %STAGING%... | |
382 | - | echo [Beginning of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
382 | + | |
383 | echo ------- [ Beginning of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
384 | if not '%EXCLUSIONS_FILE%'=='' %SEVENZIP% u "%STAGING%\%BACKUP_PREFIX%_full.7z" "%SOURCE%" -ms=off -mx=9 -xr@"%EXCLUSIONS_FILE%" -t7z -u- -up0q3r2x2y2z0w2!"%STAGING%\%BACKUP_PREFIX%_differential_%CUR_DATE%.7z" >> %LOGPATH%\%LOGFILE% 2>&1 | |
385 | - | echo [End of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
385 | + | |
386 | echo ------- [ End of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
387 | echo.>> %LOGPATH%\%LOGFILE% | |
388 | echo. | |
389 | :: Report on the build | |
390 | if %ERRORLEVEL%==0 ( | |
391 | echo %TIME% Archive built successfully.>> %LOGPATH%\%LOGFILE% | |
392 | echo %TIME% Archive built successfully. | |
393 | ) | |
394 | if not %ERRORLEVEL%==0 ( | |
395 | set JOB_ERROR=1 | |
396 | echo %TIME% ! Archive built with errors.>> %LOGPATH%\%LOGFILE% | |
397 | echo %TIME% ! Archive built with errors. | |
398 | ) | |
399 | ||
400 | ||
401 | :: Upload to destination | |
402 | echo. | |
403 | echo %TIME% Uploading %BACKUP_PREFIX%_differential_%CUR_DATE%.7z to %DESTINATION%... >> %LOGPATH%\%LOGFILE% | |
404 | echo %TIME% Uploading %BACKUP_PREFIX%_differential_%CUR_DATE%.7z to %DESTINATION%... | |
405 | echo.>> %LOGPATH%\%LOGFILE% | |
406 | xcopy "%STAGING%\%BACKUP_PREFIX%_differential_%CUR_DATE%.7z" "%DESTINATION%\" /Q /J /Y /Z >> %LOGPATH%\%LOGFILE% | |
407 | echo.>> %LOGPATH%\%LOGFILE% | |
408 | :: Report on the upload | |
409 | if %ERRORLEVEL%==0 ( | |
410 | echo %TIME% Uploaded differential file successfully.>> %LOGPATH%\%LOGFILE% | |
411 | echo %TIME% Uploaded differential file successfully. | |
412 | ) | |
413 | ||
414 | if not %ERRORLEVEL%==0 ( | |
415 | set JOB_ERROR=1 | |
416 | echo %TIME% ! Upload of differential file failed.>> %LOGPATH%\%LOGFILE% | |
417 | echo %TIME% ! Upload of differential file failed. | |
418 | ) | |
419 | ||
420 | goto done | |
421 | ||
422 | ||
423 | ::::::::::::::::::::::::::: | |
424 | :: RESTORE FROM A BACKUP :: | |
425 | ::::::::::::::::::::::::::: | |
426 | :restore | |
427 | echo. | |
428 | echo Restoring from a backup set. | |
429 | echo. | |
430 | echo These backups are available: | |
431 | echo. | |
432 | dir /B /A:-D "%STAGING%" 2>NUL | |
433 | echo. | |
434 | echo Enter the filename to restore from exactly as it appears above. | |
435 | echo ^(Note: archived backup sets are not shown^) | |
436 | echo. | |
437 | :restore_menu | |
438 | set BACKUP_FILE= | |
439 | set /p BACKUP_FILE=Filename: | |
440 | if %BACKUP_FILE%==exit goto end | |
441 | echo. | |
442 | :: Make sure user didn't fat-finger the file name | |
443 | if not exist "%STAGING%\%BACKUP_FILE%" ( | |
444 | echo ! ERROR: That file wasn^'t found. Check your typing and try again. && echo. && goto restore_menu | |
445 | goto restore_menu | |
446 | ) | |
447 | ||
448 | set CHOICE=y | |
449 | echo ! Selected file '%BACKUP_FILE%' | |
450 | echo. | |
451 | set /p CHOICE=Is this correct [y]?: | |
452 | if not %CHOICE%==y echo Going back to menu... && goto restore_menu | |
453 | echo. | |
454 | echo Great. Press any key to get started. | |
455 | pause >NUL | |
456 | echo ! Starting restoration at%TIME% on %CUR_DATE% | |
457 | echo This might take a while, be patient... | |
458 | ||
459 | :: Test if we're doing a full or differential restore. | |
460 | if %BACKUP_FILE%==%BACKUP_PREFIX%_full.7z set RESTORE_TYPE=full | |
461 | if not %BACKUP_FILE%==%BACKUP_PREFIX%_full.7z set RESTORE_TYPE=differential | |
462 | ||
463 | ||
464 | :restore_go | |
465 | echo.>> %LOGPATH%\%LOGFILE% | |
466 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
467 | echo Differential Backup Script v%VERSION% - initialized %CUR_DATE% at%TIME% by %USERDOMAIN%\%USERNAME%>> %LOGPATH%\%LOGFILE% | |
468 | echo.>> %LOGPATH%\%LOGFILE% | |
469 | echo Script location: %SCRIPT_NAME%>> %LOGPATH%\%LOGFILE% | |
470 | echo.>> %LOGPATH%\%LOGFILE% | |
471 | echo Job Options>> %LOGPATH%\%LOGFILE% | |
472 | echo Job type: %RESTORE_TYPE% restore>> %LOGPATH%\%LOGFILE% | |
473 | echo Source: %STAGING%\%BACKUP_PREFIX%_full.7z>> %LOGPATH%\%LOGFILE% | |
474 | echo Destination: %STAGING%\%BACKUP_PREFIX%\>> %LOGPATH%\%LOGFILE% | |
475 | echo Staging area: %STAGING%>> %LOGPATH%\%LOGFILE% | |
476 | echo Exclusions file: %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
477 | echo Backup prefix: %BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
478 | echo Log location: %LOGPATH%\%LOGFILE%>> %LOGPATH%\%LOGFILE% | |
479 | echo Log max size: %LOG_MAX_SIZE% bytes>> %LOGPATH%\%LOGFILE% | |
480 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
481 | echo. | |
482 | :: Detect our backup type and inform the user | |
483 | if %RESTORE_TYPE%==differential ( | |
484 | echo %TIME% Restoring from differential backup. Will unpack full backup then differential.>> %LOGPATH%\%LOGFILE% | |
485 | echo %TIME% Restoring from differential backup. Will unpack full backup then differential. | |
486 | ) | |
487 | if %RESTORE_TYPE%==full ( | |
488 | echo %TIME% Restoring from full backup.>> %LOGPATH%\%LOGFILE% | |
489 | echo %TIME% Restoring from full backup. | |
490 | echo %TIME% Unpacking full backup...>> %LOGPATH%\%LOGFILE% | |
491 | echo %TIME% Unpacking full backup... | |
492 | ) | |
493 | ||
494 | :: Start the restoration | |
495 | echo.>> %LOGPATH%\%LOGFILE% | |
496 | - | echo [Beginning of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
496 | + | |
497 | echo ------- [ Beginning of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
498 | - | echo [End of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
498 | + | |
499 | echo ------- [ End of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
500 | :: Report on the unpack | |
501 | if %ERRORLEVEL%==0 ( | |
502 | echo %TIME% Full backup unpacked successfully.>> %LOGPATH%\%LOGFILE% | |
503 | echo %TIME% Full backup unpacked successfully. | |
504 | ) | |
505 | if not %ERRORLEVEL%==0 ( | |
506 | set JOB_ERROR=1 | |
507 | echo %TIME% ! Full backup unpacked with errors.>> %LOGPATH%\%LOGFILE% | |
508 | echo %TIME% ! Full backup unpacked with errors. | |
509 | ) | |
510 | :: If we're just doing a full restore (no differential), then go to the end | |
511 | if %RESTORE_TYPE%==full goto done | |
512 | ||
513 | :: Now we unpack our differential file | |
514 | echo. | |
515 | echo %TIME% Unpacking differential file %BACKUP_FILE%...>> %LOGPATH%\%LOGFILE% | |
516 | echo %TIME% Unpacking differential file %BACKUP_FILE%... | |
517 | - | echo [Beginning of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
517 | + | |
518 | echo ------- [ Beginning of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
519 | - | echo [End of 7zip output]>> %LOGPATH%\%LOGFILE% 2>&1 |
519 | + | |
520 | echo ------- [ End of 7zip output ] ------->> %LOGPATH%\%LOGFILE% 2>&1 | |
521 | echo. | |
522 | :: Report on the unpack | |
523 | if %ERRORLEVEL%==0 ( | |
524 | echo %TIME% Differential file unpacked successfully.>> %LOGPATH%\%LOGFILE% | |
525 | echo %TIME% Differential file unpacked successfully. | |
526 | ) ELSE ( | |
527 | :: Something broke! | |
528 | set JOB_ERROR=1 | |
529 | echo %TIME% ! Differential file unpacked with errors.>> %LOGPATH%\%LOGFILE% | |
530 | echo %TIME% ! Differential file unpacked with errors. | |
531 | ) | |
532 | goto done | |
533 | ||
534 | ||
535 | :::::::::::::::::::::::: | |
536 | :: ARCHIVE BACKUP SET :: aka rotate backups | |
537 | :::::::::::::::::::::::: | |
538 | :archive_backup_set | |
539 | echo.>> %LOGPATH%\%LOGFILE% | |
540 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
541 | echo Differential Backup Script v%VERSION% - initialized %CUR_DATE% at%TIME% by %USERDOMAIN%\%USERNAME%>> %LOGPATH%\%LOGFILE% | |
542 | echo.>> %LOGPATH%\%LOGFILE% | |
543 | echo Script location: %SCRIPT_NAME%>> %LOGPATH%\%LOGFILE% | |
544 | echo.>> %LOGPATH%\%LOGFILE% | |
545 | echo Job Options>> %LOGPATH%\%LOGFILE% | |
546 | echo Job type: Archive/rotate backup set>> %LOGPATH%\%LOGFILE% | |
547 | echo Source: %SOURCE%>> %LOGPATH%\%LOGFILE% | |
548 | echo Destination: %DESTINATION%>> %LOGPATH%\%LOGFILE% | |
549 | echo Staging area: %STAGING%>> %LOGPATH%\%LOGFILE% | |
550 | echo Exclusions file: %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
551 | echo Backup prefix: %BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
552 | echo Log location: %LOGPATH%\%LOGFILE%>> %LOGPATH%\%LOGFILE% | |
553 | echo Log max size: %LOG_MAX_SIZE% bytes>> %LOGPATH%\%LOGFILE% | |
554 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
555 | echo.>> %LOGPATH%\%LOGFILE% | |
556 | echo %TIME% Archiving current backup set to %DESTINATION%\%CUR_DATE%_%BACKUP_PREFIX%_set.>> %LOGPATH%\%LOGFILE% | |
557 | echo %TIME% Archiving current backup set to %DESTINATION%\%CUR_DATE%_%BACKUP_PREFIX%_set. | |
558 | :: Final destination: Make directory, move files | |
559 | pushd "%DESTINATION%" | |
560 | mkdir %CUR_DATE%_%BACKUP_PREFIX%_set >> %LOGPATH%\%LOGFILE% | |
561 | move /Y *.* %CUR_DATE%_%BACKUP_PREFIX%_set >> %LOGPATH%\%LOGFILE% | |
562 | popd | |
563 | echo. | |
564 | echo %TIME% Deleting all copies in the staging area...>> %LOGPATH%\%LOGFILE% | |
565 | echo %TIME% Deleting all copies in the staging area... | |
566 | :: Staging area: Delete old files | |
567 | del /Q /F "%STAGING%\*.7z">> %LOGPATH%\%LOGFILE% | |
568 | echo.>> %LOGPATH%\%LOGFILE% | |
569 | echo. | |
570 | ||
571 | :: Report | |
572 | echo.>> %LOGPATH%\%LOGFILE% | |
573 | echo %TIME% Backup set archived. All unarchived files in staging area were deleted.>> %LOGPATH%\%LOGFILE% | |
574 | echo %TIME% Backup set archived. All unarchived files in staging area were deleted. | |
575 | echo.>> %LOGPATH%\%LOGFILE% | |
576 | goto done | |
577 | ||
578 | ||
579 | ::::::::::::::::::::::::::::::::::: | |
580 | :: CLEAN UP ARCHIVED BACKUP SETS :: aka delete old sets | |
581 | ::::::::::::::::::::::::::::::::::: | |
582 | :cleanup_archives | |
583 | IF NOT '%DAYS%'=='' goto cleanup_archives_go | |
584 | ||
585 | :: List the backup sets | |
586 | :cleanup_archives_list | |
587 | echo. | |
588 | echo CURRENT BACKUP SETS: | |
589 | echo. | |
590 | echo IN STAGING : ^(%STAGING%^) | |
591 | echo --------------------- | |
592 | dir /B /A:D "%STAGING%" 2>&1 | |
593 | echo. | |
594 | echo. | |
595 | echo IN LONG-TERM STORAGE: ^(%DESTINATION%^) | |
596 | echo --------------------- | |
597 | dir /B /A:D "%DESTINATION%" 2>&1 | |
598 | echo. | |
599 | :cleanup_archives_list2 | |
600 | echo. | |
601 | set DAYS=180 | |
602 | echo Delete backup sets older than how many days? ^(you will be prompted for confirmation^) | |
603 | set /p DAYS=[%DAYS%]?: | |
604 | if %DAYS%==exit goto end | |
605 | echo. | |
606 | :: Tell user what will happen | |
607 | echo THESE BACKUP SETS WILL BE DELETED: | |
608 | echo ---------------------------------- | |
609 | :: List files that would match. | |
610 | :: We have to use PushD to get around forfiles.exe not using UNC paths. pushd automatically assigns the next free drive letter | |
611 | echo From staging: | |
612 | pushd "%STAGING%" | |
613 | FORFILES /D -%DAYS% /C "cmd /c IF @isdir == TRUE echo @path" 2>NUL | |
614 | popd | |
615 | echo. | |
616 | echo From long-term storage: | |
617 | pushd "%DESTINATION%" | |
618 | FORFILES /D -%DAYS% /C "cmd /c IF @isdir == TRUE echo @path" 2>NUL | |
619 | popd | |
620 | echo. | |
621 | set HMMM=n | |
622 | set /p HMMM=Is this okay [%HMMM%]?: | |
623 | if /i %HMMM%==n echo. && echo Canceled. Returning to menu. && goto cleanup_archives_list2 | |
624 | if %DAYS%==exit goto end | |
625 | echo. | |
626 | set CHOICE=n | |
627 | set /p CHOICE=Are you absolutely sure [%CHOICE%]?: | |
628 | if not %CHOICE%==y echo. && echo Canceled. Returning to menu. && goto cleanup_archives_list2 | |
629 | echo. | |
630 | echo Okay, starting deletion. | |
631 | ||
632 | :: Go ahead and do the cleanup. | |
633 | :cleanup_archives_go | |
634 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
635 | echo Differential Backup Script v%VERSION% - initialized %CUR_DATE% at%TIME% by %USERDOMAIN%\%USERNAME%>> %LOGPATH%\%LOGFILE% | |
636 | echo.>> %LOGPATH%\%LOGFILE% | |
637 | echo Script location: %SCRIPT_NAME%>> %LOGPATH%\%LOGFILE% | |
638 | echo.>> %LOGPATH%\%LOGFILE% | |
639 | echo Job type: Delete archived backup sets older than %DAYS% days.>> %LOGPATH%\%LOGFILE% | |
640 | echo Source: %SOURCE%>> %LOGPATH%\%LOGFILE% | |
641 | echo Destination: %DESTINATION%>> %LOGPATH%\%LOGFILE% | |
642 | echo Staging area: %STAGING%>> %LOGPATH%\%LOGFILE% | |
643 | echo Exclusions file: %EXCLUSIONS_FILE%>> %LOGPATH%\%LOGFILE% | |
644 | echo Backup prefix: %BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
645 | echo Log location: %LOGPATH%\%LOGFILE%>> %LOGPATH%\%LOGFILE% | |
646 | echo Log max size: %LOG_MAX_SIZE% bytes>> %LOGPATH%\%LOGFILE% | |
647 | echo --------------------------------------------------------------------------------------------------->> %LOGPATH%\%LOGFILE% | |
648 | echo.>> %LOGPATH%\%LOGFILE% | |
649 | echo. | |
650 | echo %TIME% Deleting backup sets that are older than %DAYS% days...>> %LOGPATH%\%LOGFILE% | |
651 | echo %TIME% Deleting backup sets that are older than %DAYS% days... | |
652 | ||
653 | :: This cleans out the staging area. | |
654 | :: First FORFILES command tells the logfile what will get deleted. Second command actually deletes. | |
655 | pushd "%STAGING%" | |
656 | FORFILES /D -%DAYS% /C "cmd /c IF @isdir == TRUE echo @path" >> %LOGPATH%\%LOGFILE% | |
657 | FORFILES /S /D -%DAYS% /C "cmd /c IF @isdir == TRUE rmdir /S /Q @path" | |
658 | popd | |
659 | ||
660 | :: This cleans out the destination / long-term storage area. | |
661 | :: First FORFILES command tells the logfile what will get deleted. Second command actually deletes. | |
662 | pushd "%DESTINATION%" | |
663 | FORFILES /D -%DAYS% /C "cmd /c IF @isdir == TRUE echo @path" >> %LOGPATH%\%LOGFILE% | |
664 | FORFILES /S /D -%DAYS% /C "cmd /c IF @isdir == TRUE rmdir /S /Q @path" | |
665 | popd | |
666 | ||
667 | echo. | |
668 | :: Report on the cleanup | |
669 | if %ERRORLEVEL%==0 ( | |
670 | echo %TIME% Cleanup completed successfully.>> %LOGPATH%\%LOGFILE% | |
671 | echo %TIME% Cleanup completed successfully. | |
672 | ) | |
673 | if not %ERRORLEVEL%==0 ( | |
674 | set JOB_ERROR=1 | |
675 | echo %TIME% ! Cleanup completed with errors.>> %LOGPATH%\%LOGFILE% | |
676 | echo %TIME% ! Cleanup completed with errors. | |
677 | ) | |
678 | goto done | |
679 | ||
680 | ||
681 | ::::::::::::::::::::::: | |
682 | :: COMPLETION REPORT :: | |
683 | ::::::::::::::::::::::: | |
684 | :done | |
685 | :: One of these displays if the operation was a restore operation | |
686 | if %RESTORE_TYPE%==full ( | |
687 | echo %TIME% Restored full backup to %STAGING%\%BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
688 | echo %TIME% Restored full backup to %STAGING%\%BACKUP_PREFIX% | |
689 | ) | |
690 | ||
691 | if %RESTORE_TYPE%==differential ( | |
692 | echo. | |
693 | echo %TIME% Restored full and differential backup to %STAGING%\%BACKUP_PREFIX%>> %LOGPATH%\%LOGFILE% | |
694 | echo %TIME% Restored full and differential backup to %STAGING%\%BACKUP_PREFIX% | |
695 | ) | |
696 | ||
697 | - | echo %TIME% %SCRIPT_NAME% complete on %CUR_DATE% at%TIME%.>> %LOGPATH%\%LOGFILE% |
697 | + | |
698 | - | echo %TIME% %SCRIPT_NAME% complete on %CUR_DATE% at%TIME%. |
698 | + | echo %TIME% %SCRIPT_NAME% complete.>> %LOGPATH%\%LOGFILE% |
699 | echo %TIME% %SCRIPT_NAME% complete. | |
700 | if '%JOB_ERROR%'=='1' echo. && echo %TIME% ! Note: Script exited with errors.>> %LOGPATH%\%LOGFILE% | |
701 | if '%JOB_ERROR%'=='1' echo. && echo %TIME% ! Note: Script exited with errors. Maybe check the log. | |
702 | ||
703 | :end | |
704 | :: Clean up our temp exclude file | |
705 | if exist %TEMP%\DEATH_BY_HAMSTERS.txt del /F /Q %TEMP%\DEATH_BY_HAMSTERS.txt | |
706 | ENDLOCAL |