540 lines
17 KiB
Plaintext
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 |