snapcontrol/snapcontrol.pb
Michael H.G. Schmidt e674d75a20 .
2021-05-26 00:54:46 +02:00

540 lines
17 KiB
Plaintext

; snapcontrol.pb
; https://github.com/mhgschmidt/snapcontrol
; ------------------------------------------------------------
; Drive snapshot backup controller
; - starts backups using drive snapshot
; - sends reports via email
; LICENSE : MIT License
; AUTHOR : Michael H.G. Schmidt
; EMAIL : michael@schmidt2.de
; DATE : 20210526
; ------------------------------------------------------------
;
; This tool creates an image backups of windows machines,
; it uses the Drive Snapshot tool by Tom Ehlert Software.
; Please support this great tool and buy a license.
; http://www.drivesnapshot.de/en/order.htm
;
; Copyright (c) 2019-2021 Michael H.G. Schmidt
; Released under the MIT license.
;
;
; INIT (set vars, read args, read inifile etc. pp.)
;
OpenConsole()
EnableGraphicalConsole(0)
Dim filelist$(0)
Global VERSION$="V1.02"
Global installme = 0
Global updatesched = 0
Global dryrun = 0
Global hostname$ = GetEnvironmentVariable("COMPUTERNAME")
Global jobname$ = "snapcontrol"
Global month$ = FormatDate("%mm", Date())
Global day$ = FormatDate("%dd", Date())
Global DriveSnapshotVersionOk = 0
; valid versions for Drive Snapshot we support ...
NewList DriveSnapshotVersion$()
AddElement(DriveSnapshotVersion$())
DriveSnapshotVersion$() = "V1.46"
AddElement(DriveSnapshotVersion$())
DriveSnapshotVersion$() = "V1.47"
AddElement(DriveSnapshotVersion$())
DriveSnapshotVersion$() = "V1.48"
Procedure Usage()
PrintN ("usage: snapcontrol.exe </I | /U | /D | /V>")
PrintN (" /I = install program and config")
PrintN (" /U = update scheduler job")
PrintN (" /D = dry run")
PrintN (" /V = show version")
End 99
EndProcedure
; check commandline...
If ( CountProgramParameters() >1 )
Usage()
EndIf
; set path for inifile ...
inifile$ = RTrim(GetPathPart(ProgramFilename()),"\") + "\snapcontrol.ini"
; install job, update job, show version, dry run ...
If ( UCase(ProgramParameter(0)) = "/I" )
installme = 1
ElseIf ( UCase(ProgramParameter(0)) = "/U" )
updatesched = 1
ElseIf ( UCase(ProgramParameter(0)) = "/V" )
PrintN ("Version = " + VERSION$)
End 0
ElseIf ( UCase(ProgramParameter(0)) = "/D" )
dryrun = 1
ElseIf ( UCase(ProgramParameter(0)) = "/?" )
Usage()
EndIf
; Procedure "RunProgram" aborts after writing approx. 8GB data with snapshot64...
; so we will use native "system" command...
ImportC "msvcrt.lib"
system(str.p-ascii)
EndImport
If (Not OpenPreferences(inifile$))
PrintN ("FATAL: cannot read inifile [ " + inifile$ + " ] ---> EXIT.")
End 98
EndIf
PreferenceGroup("install")
Global InstallTo$ = ReadPreferenceString("InstallTo","c:\snapshot")
Global BinPath$ = ReadPreferenceString("BinPath","c:\tools")
PreferenceGroup("backup")
Global TargetPath$ = ReadPreferenceString("TargetPath","\\server\share")
Global FtpBackup$ = ReadPreferenceString("FtpBackup","no")
Global FtpServer$ = ReadPreferenceString("FtpServer","none")
Global TargetUser$ = ReadPreferenceString("TargetUser","guest")
Global TargetPassword$ = ReadPreferenceString("TargetPassword","guest")
Global BackupschedMode$ = ReadPreferenceString("BackupschedMode","LOGIN")
Global BackupStart$ = ReadPreferenceString("BackupStart","0005:00")
Global Disks2Dump$ = ReadPreferenceString("Disks2Dump","HD1:*")
Global DumpSize$ = ReadPreferenceString("DumpSize","4095")
Global Verify$ = ReadPreferenceString("Verify","yes")
Global BurnTrash$ = ReadPreferenceString("BurnTrash","no")
Global Encrypt$ = ReadPreferenceString("Encrypt","no")
Global EncryptPW$ = ReadPreferenceString("EncryptPW","")
Global LimitIO$ = ReadPreferenceString("LimitIO","")
Global EjectMedia$ = ReadPreferenceString("EjectMedia","yes")
PreferenceGroup("logging")
Global LogFile$ = ReadPreferenceString("LogFile","backup.log")
Global HistLog$ = ReadPreferenceString("HistLog","history.log")
; add PATH to logfiles ...
LogFile$ = InstallTo$ + "\" + LogFile$
HistLog$ = InstallTo$ + "\" + HistLog$
PreferenceGroup("mail")
Global MailReport$ = ReadPreferenceString("MailReport","no")
Global MailTo$ = ReadPreferenceString("MailTo","")
Global MailServer$ = ReadPreferenceString("MailServer","")
Global MailPort = Val(ReadPreferenceString("MailPort",""))
Global MailUser$ = ReadPreferenceString("MailUser","")
Global MailPass$ = ReadPreferenceString("MailPass","")
; which Drive Snapshot version should be used ?
arch$ = GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")
If ( arch$ = "x86" )
SnapshotBin$ = BinPath$ + "\snapshot.exe"
Else
SnapshotBin$ = BinPath$ + "\snapshot64.exe"
EndIf
; Drive Snapshot found ?
If ( FileSize ( SnapshotBin$ ) < 0 )
PrintN("ERROR: [ " + SnapshotBin$ + " ] NOT found.")
End 1
EndIf
; Version check for Drive Snapshot ...
DriveSnapshot = RunProgram(SnapshotBin$, "/?", "", #PB_Program_Open | #PB_Program_Read)
If DriveSnapshot
While ProgramRunning(DriveSnapshot)
Out$ = Out$ + ReadProgramString(DriveSnapshot)
Wend
CloseProgram(DriveSnapshot)
EndIf
ForEach DriveSnapshotVersion$()
If ( FindString(Out$, DriveSnapshotVersion$()) >0 )
DriveSnapshotVersionOk = 1
Break
EndIf
Next
If ( DriveSnapshotVersionOk = 0 )
PrintN("ERROR: Drive Snapshot version is NOT supported!")
PrintN(" Supported versions are:")
ForEach DriveSnapshotVersion$()
PrintN(" - " + DriveSnapshotVersion$())
Next
End 1
EndIf
;
; PROCEDURES
;
Procedure LogMe(Message$)
Protected h = OpenFile(#PB_Any, LogFile$,
#PB_File_Append | #PB_File_NoBuffering | #PB_Ascii | #PB_File_SharedRead)
If h
WriteStringN(h, FormatDate("[%yyyy.%mm.%dd (%hh:%ii:%ss)] ", Date()) + Message$)
PrintN(FormatDate("[%yyyy.%mm.%dd (%hh:%ii:%ss)] ", Date()) + Message$)
CloseFile(h)
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
Procedure LogMeRaw(Message$)
Protected h = OpenFile(#PB_Any, LogFile$,
#PB_File_Append | #PB_File_NoBuffering | #PB_Ascii | #PB_File_SharedRead)
If h
WriteStringN(h, Message$)
PrintN(Message$)
CloseFile(h)
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
Procedure IsAdmin()
ProcedureReturn(system("reg.exe ADD HKLM /F >nul 2>&1"))
EndProcedure
Procedure LogSend(subject$, filename$)
InitNetwork()
Protected e = 0
Protected MailText$ = ""
If CreateMail(0, "system@" + hostname$ , subject$)
AddMailRecipient(0, MailTo$, #PB_Mail_To)
LogMe("INFO: MailServer = " + Mailserver$)
If ReadFile(0, filename$, #PB_File_SharedRead)
While Eof(0) = 0
MailText$ + ReadString(0) + Chr($0d) + Chr($0a)
Wend
CloseFile(0)
SetMailBody(0, MailText$)
EndIf
; send mails ALWAYS with SSL...
e = SendMail(0, MailServer$, MailPort, #PB_Mail_UseSSL, MailUser$, MailPass$)
EndIf
ProcedureReturn e
EndProcedure
Procedure EndProg(err)
Protected s$ = ""
If ( err = 0 )
LogMe("INFO: END of BACKUP with result SUCCESS !")
s$ = "BACKUP report - { SUCCESS } @" + hostname$
Else
LogMe("ERROR: END of BACKUP with result FAILED !")
s$ = "BACKUP report - { ERROR ! } @" + hostname$
EndIf
; write return code of last command (snapshot.exe) to logfile
LogMe("return=" + err)
; send a report via mail ?
If ( LCase(MailReport$) = "yes" )
LogMe("INFO: sending mail to: " + MailTo$)
If ( LogSend(s$, Logfile$) )
LogMe("INFO: mail sent")
Else
LogMe("FATAL: could not send backup report via email...")
LogMeRaw(" please inform your Administrator !")
EndIf
EndIf
; adding actual logfile to histlog...
If ReadFile(0, Logfile$, #PB_File_SharedRead)
While Eof(0) = 0
LogText$ + ReadString(0) + Chr($0d) + Chr($0a)
Wend
CloseFile(0)
EndIf
If OpenFile(0, HistLog$, #PB_File_Append | #PB_File_NoBuffering | #PB_File_SharedRead )
FileSeek(0, Lof(0))
WriteString(0,LogText$)
CloseFile(0)
EndIf
; remove backup share...
dummy = system("net use " + TargetPath$ + " /DELETE >nul 2>&1")
ClosePreferences()
CloseConsole()
End err
EndProcedure
Procedure.s mkpass(password_length)
chars$ = "abcdef-" ; possible characters, keep these lcase
For a = 1 To password_length
Select Random(1) ; 0 = char, 1 = digit
Case 1 ; is digit
pass$ + Str(Random(9))
Case 0 ; is character
position = Random(Len(chars$)) ; random character selector
pass$ + Mid(chars$,position,1)
EndSelect
Next
ProcedureReturn pass$
EndProcedure
;
; MAIN
;
; installation requested ?
If ( installme = 1 )
; delete old logfiles ...
LogMe("INFO: deleting old logfiles ...")
dummy = system("del /F /Q " + LogFile$ + " 2>nul")
dummy = system("del /F /Q " + HistLog$ + " 2>nul")
; create directory ...
LogMe("INFO: creating directory [ " + InstallTo$ + " ] ...")
dummy = system("mkdir " + InstallTo$ + " >nul 2>&1")
; copy binary and inifile ...
LogMe("INFO: copy binary and inifile to folder [ " + InstallTo$ + " ] ...")
dummy = system("copy /Y " + inifile$ + " " + InstallTo$ + " 2>nul")
dummy = system("copy /Y " + ProgramFilename() + " " + InstallTo$ + " 2>nul")
; update jobscheduler (selfcall) ...
dummy = system(InstallTo$ + "\snapcontrol.exe /U 2>nul")
ClosePreferences()
CloseConsole()
End
EndIf
; update for jobscheduler requested ?
If ( updatesched = 1 )
username$ = GetEnvironmentVariable("USERNAME")
; generate call string for task...
jobcmd$ = ProgramFilename()
; check jobtype ...
If ( LCase(BackupschedMode$) <> "login" And LCase(BackupschedMode$) <> "time" )
PrintN("WARNING: unknown BackupschedMode [ " + BackupschedMode$ + " ] ---> IGNORING. Type will be set to LOGIN.")
BackupschedMode$ = "LOGIN"
EndIf
; update the job ...
PrintN ("Updating windows jobscheduler ...")
If ( LCase(BackupschedMode$) = "time" )
dummy = system("schtasks /create /F /RL HIGHEST /SC daily /ST " + BackupStart$ + " /TN " + jobname$ + " /TR " + jobcmd$)
ElseIf ( LCase(BackupschedMode$) = "login" )
dummy = system("schtasks /create /F /RL HIGHEST /SC onlogon /DELAY " + BackupStart$ + " /TN " + jobname$ + " /TR " + jobcmd$)
EndIf
; show job and print some status messages...
dummy = system("schtasks /query /FO List /V /TN " + jobname$)
PrintN (Chr(13) + "READY." + Chr(13))
ClosePreferences()
CloseConsole()
End
EndIf
;
; ASK the user for permission to start ...
;
; FTP backup requested ?
If ( FtpBackup$ = "yes" )
TargetPath$ = "ftp://" + TargetUser$ + "@" + FtpServer$ + TargetPath$
EndIf
Result = MessageRequester("SnapControl",
"Start BACKUP now?" + Chr(13) +
"Targetpath => " + TargetPath$,
#PB_MessageRequester_YesNo | #PB_MessageRequester_Info)
If Result = #PB_MessageRequester_No
Result = MessageRequester("SnapControl",
"backup ABORTED.",
#PB_MessageRequester_Ok | #PB_MessageRequester_Warning)
CloseConsole()
End 0
EndIf
; cleanup: delete old Logfile, remove old drive letter...
dummy = DeleteFile(LogFile$, #PB_FileSystem_Force)
LogMe("============== starting BACKUP ==============")
LogMe("snapcontrol.exe version = [ " + VERSION$ + " ]")
LogMe(" snapshot.exe version = [ " + DriveSnapshotVersion$() + " ]")
If ( dryrun = 1 )
LogMe("DRYRUN - (simulating a backup run) !!!")
EndIf
e = IsAdmin()
If ( e = 0 )
LogMe("OK. Running as Admin ...")
Else
PrintN("ERROR: Please run as Administrator !")
PrintN(" cannot continue ---> EXIT")
End 97
EndIf
;
; GENERATE the BACKUP command...
;
; encryption enabled ?
If ( LCase(Encrypt$) = "yes" )
params$ = "-PW=" + EncryptPW$ + " "
If ( EncryptPW$ = "" )
LogMe("WARNING: ENCRYPTED backup requested but PASSWORD is not set in infile !")
Else
LogMe("WARNING: encryption requested. The backup will be encrypted.")
LogMeRaw(" --> PLEASE WRITE DOWN YOUR PASSWORD AND STORE IT IN A SAFE PLACE !")
EndIf
EndIf
If ( LCase(Encrypt$) = "dynamic" )
If ( EncryptPW$ = "" )
EncryptPW$ = mkpass(20)
LogMe("WARNING: ENCRYPTED backup requested and DYNAMIC password was requested !")
LogMeRaw(" Generated password is: --->>> " + EncryptPW$ + " <<<---")
LogMe("INFO: writing password to inifile...")
PreferenceGroup("Global")
dummy = WritePreferenceString("EncryptPW", EncryptPW$)
Else
LogMe("WARNING: ENCRYPTED backup requested. Using DYNAMIC password in inifile !")
LogMeRaw(" The password is: --->>> " + EncryptPW$ + " <<<---")
EndIf
params$ = "-PW=" + EncryptPW$ + " "
EndIf
; limit the write rate ?
If ( LimitIO$ <> "" )
params$ + "--LimitIORate:" + LimitIO$ + " "
EndIf
; verify the backup ?
If ( LCase(Verify$) = "yes" )
params$ + "-T "
EndIf
; burn all the trash in the recyclebin ?
If ( LCase(BurnTrash$) = "yes" )
params$ + "-R "
EndIf
; eject media ?
If ( EjectMedia$ = "yes" )
params$ + "--EjectDriveAfterBackup "
EndIf
; FTP backup requested ?
If ( FtpBackup$ = "yes" )
LogMe("INFO: FTP backup, using server [ " + FtpServer$ + " ] for backup ...")
; add ftp account ...
e = system(SnapshotBin$ + " --AddFTPAccount:" + TargetUser$ + "," + FtpServer$ + "," + TargetPassword$)
; generate filenames for backup ...
DumpFile$ = TargetPath$ + "/" + hostname$ + "_snapshot_" + month$ + "_$type_$disk.sna"
HashFile$ = "-h" + InstallTo$ + "\" + hostname$ + "_snapshot_" + month$ + "_$type_$disk.hsh"
Else
; network share or local drive ?
colonpos = FindString(TargetPath$, ":")
If ( colonpos = 0 )
; no colon found in path this means we are using a network share ...
params$ + "--NetUse:" + TargetPath$ + "," + TargetUser$ + "," + TargetPassword$ + " "
LogMe("INFO: using a NETWORK share for backup ...")
Else
; found a ':' at some position means we are using a drive letter ...
DRIVE$ = Left(TargetPath$, colonpos)
LogMe("INFO: using DRIVE [ " + DRIVE$ + " ] for backup ...")
EndIf
; check if there is a full backup file ... in case it was deleted ...
If ( FileSize ( TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_ful*.sna" ) < 0 )
LogMe("WARNING: no full backup found.")
If ( FileSize ( TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_ful*.hsh" ) >= 0 )
dummy = system("del /F /Q " + TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_ful*.hsh")
LogMe("INFO: [ " + TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_ful*.hsh ] deleted.")
EndIf
EndIf
; check the actual FULL backup file for errors ...
; ( must be done BEFORE we run a backup because we don't know - for sure
; - wether the last backup was interrupted or not... )
i = 1
If ExamineDirectory(0, TargetPath$, hostname$ + "_snapshot_" + month$ + "_ful*.sna")
While NextDirectoryEntry(0)
If DirectoryEntryType(0) = #PB_DirectoryEntry_File
ReDim filelist$(i)
filelist$(i-1) = DirectoryEntryName(0)
LogMe("INFO: found [ " + filelist$(i-1) + " ]")
i + 1
EndIf
Wend
FinishDirectory(0)
EndIf
e = 0
For i = 1 To ArraySize(filelist$())
LogMe("INFO: Starting QUICKCHECK for [ " + filelist$(i-1) + " ]")
e = system(SnapshotBin$ + " --Logfile:" + LogFile$ + " --QuickCheck:" + TargetPath$ + "\" + filelist$(i-1))
If ( e <> 0 )
LogMe("ERROR: DELETING last full backup [ REASON: corrupt file! ]")
dummy = system("del /F /Q " + TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_ful*.*")
Break
EndIf
Next i
; generate filenames for backup ...
DumpFile$ = TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_$type_$disk.sna"
HashFile$ = "-h" + TargetPath$ + "\" + hostname$ + "_snapshot_" + month$ + "_$type_$disk.hsh"
EndIf
;
; run BACKUP...
;
; extend params ...
params$ + " --FullIfHashIsMissing --CreateDir -W -L" + DumpSize$ + " " + Disks2Dump$
LogMe("INFO: executing command [ " + SnapshotBin$ + " ]")
LogMe(" DumpFile: [ " + DumpFile$ + " ]")
LogMe(" HashFile: [ " + HashFile$ + " ]")
LogMe(" LogFile: [ " + LogFile$ + " ]")
If ( dryrun = 0 )
e = system(SnapshotBin$ + " --Logfile:" + LogFile$ + " " + params$ + " " + DumpFile$ + " " + HashFile$)
EndIf
; end with return code...
EndProg(e)
; IDE Options = PureBasic 5.73 LTS (Windows - x64)
; ExecutableFormat = Console
; CursorPosition = 58
; FirstLine = 20
; Folding = --
; EnableXP
; Executable = snapcontrol.exe
; DisableDebugger
; Warnings = Display