Das Windows-Feature „User Account Control“ (UAC), im deutschen Windows auch Benutzerkontensteuerung genannt, ist seit Windows Vista verbaut und sorgt für sichereres Rechtehandling beim Ausführen von Programmen. Im Hintergrund steckt außerdem ein Sandboxing-Prinzip, wodurch unterschiedliche Programme in unterschiedlichen Nutzer- und Rechtekontexten auf einem Desktop vereint zusammen arbeiten können.
Die Benutzerkontensteuerung kann auf eine von vier unterschiedliche Stufen gestellt werden; eine davon entspricht der Deaktivierung, die anderen drei realisieren unterschiedliche Sicherheitsstufen.
Im (vertrauensvollen) Unternehmenseinsatz ist eine auf Stufe 1 gestellte UAC sinnvoll, damit administrative Aufgaben auch direkt aus der Anmeldung eines eingeschränkten Nutzers per Eingabe von Admin-Daten möglich ist. Diese lässt sich beispielsweise mit Batch und Registry konfigurieren und im Windows AD verteilen, hier der Code:
@echo off & Color 9f & setlocal
set wd=\\server\Deployment\Sonstiges\activate-uac
set log=%wd%\activate-uac-log.txt
set uacconfig=99
REM Clientfilter: nur die Computer aus der allowedPCs.txt dürfen installieren
::for /f %%f in (%wd%\allowedPCs.txt) do if "%computername%"=="%%f" goto check
::goto end
REM Clientfilter: die Computer aus der deniedPCs.txt dürfen nicht installieren
for /f %%f in (%wd%\deniedPCs.txt) do if "%computername%"=="%%f" goto end
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v EnableLUA /t REG_DWORD /d 1 /f
set uacconfig=%errorlevel%
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 5 /f
set uacconfig=%uacconfig%%errorlevel%
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v ConsentPromptBehaviorUser /t REG_DWORD /d 3 /f
set uacconfig=%uacconfig%%errorlevel%
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v PromptOnSecureDesktop /t REG_DWORD /d 0 /f
set uacconfig=%uacconfig%%errorlevel%
echo %date% %time:~0,8% - %computername% hat UAC Settings übernommen: %uacconfig% >> %log%
:end
endlocal
Die unterschiedlichen Werte der einzelnen Registry-Eigenschaften könnt ihr hier übersichtlich nachlesen. Es gibt noch weitere Punkte der UAC, die über die Registry gesteuert werden können. PS: Das Konzept hinter der UAC ist äußerst komplex und es macht u.U. Spaß, sich als fortgeschrittener Windows-Nutzer darin einzulesen.
Einfache Windows-Admin-Frage: Welcher Mitarbeiter ist in diesem Moment und seit wann an welchem PC eingeloggt?
Einfache Antwort, kurz und knackig: Batch! Mit Hilfe von PsLoggedOn, welches sich im selben Verzeichnis befinden muss wie das Skript:
@echo on & Color 9f & setlocal
for /f "Tokens=1" %%c in ('net view /domain:"%USERDOMAIN%"^|Findstr /L /C:"\\"') do (
for /f "Tokens=*" %%u in ('PsLoggedOn -L %%c^|find /i "%USERDOMAIN%\"') do (
echo %%c %%u
)
)
endlocal
pause
goto :EOF
Auch dieser Artikel reiht sich in die Liste der Software-Batch-AD-Deployment-Guides ein. Im Falle von HipChat wird der Artikel recht kurz, denn hier passiert nichts ungewöhnliches.
Vorbereitung
Der aktuellste HipChat-Installer (für Windows) ist als .exe immer unter dieser URL verfügbar. Anschließend wird wieder ein übliches Deployment-Verzeichnis auf einem für die PCs verfügbaren Netzlaufwerk erstellt: Installer, Installer-Batch, allowedPCs.txt und deniedPCs.txt (mehr Informationen zum Clientfilter hier). Im Standardfall (so auch in diesem Deployment-Script) wird die deniedPCs.txt benutzt, um einzelne Clients von der Verteilung auszuschließen. Die Textdatei muss dann Computernamen enthalten, einen pro Zeile. Der HipChat-Installer muss folgendermaßen umbenannt werden: „HipChat_[Version].exe“ und der Versionsstring muss in das Deployment-Script in Zeile 11.
Deployment-Script
19.09.2016: Version 4.27.1.1658 getestet und verteilt.
Hinweis: Wer nicht nur ein Update sondern eine komplette Reinstallation von Hipchat im Netzwerk ausrollen will, kann mein angepasstes Script – hier als Download – nutzen. Die benötigte VersionCompare.exe erhaltet ihr hier.
Hier das Script für ein normales Update:
@echo off && color 9f && setlocal
set wd=\\lea\Deployment\Software\Hipchat
set log=%wd%\hipchat.log
set tools=\\lea\Deployment\Sonstiges\tools
set hipEL=999
set instversion=0.0
set versionEL=9
set exepath=none
set retry=0
REM:: ######## EDIT THIS ####
set newversion=4.1658
REM:: #######################
REM:: Clientfilter: nur die Computer aus der allowedPCs.txt dürfen installieren
::for /f %%f in (%wd%\allowedPCs.txt) do if "%computername%"=="%%f" goto check
::goto end
REM:: Clientfilter: die Computer aus der deniedPCs.txt dürfen nicht installieren
for /f %%f in (%wd%\deniedPCs.txt) do if "%computername%"=="%%f" goto end
:check
if exist "C:\Program Files (x86)\Atlassian\HipChat4\HipChat.exe" set exepath="C:\Program Files (x86)\Atlassian\HipChat4\HipChat.exe"
::if exist "c:\Program Files (x86)\Skype\Phone\skype.exe" set exepath="c:\Program Files (x86)\Skype\Phone\skype.exe"
if %exepath%==none echo %date% %time:~0,8% - %computername% findet das .exe Verzeichnis nicht && goto taskkill
goto checkversion
:checkversion
for /f "tokens=1-3" %%i in ('%tools%\sigcheck %exepath%') do ( if "%%i %%j"=="File version:" set instversion=%%k )
%tools%\VersionCompare.exe %instversion% %newversion%
set versionEL=%errorlevel%
if "%versionEL%"=="-1" goto taskkill
if "%versionEL%"=="0" echo %date% %time:~0,8% - %computername% hat bereits %instversion% installiert >> %log% & goto end
if "%versionEL%"=="1" echo %date% %time:~0,8% - %computername% hat bereits %instversion% (neuer) installiert >> %log% & goto end
goto end
:taskkill
TASKKILL /f /im hipchat.exe
goto install
:install
echo %date% %time:~0,8% - %computername% installiert... >> %log%
::msiexec.exe /i %wd%\deploy\%newversion%\SkypeSetup.msi /qn /norestart FEATURE_IEPLUGIN=0 FEATURE_FFPLUGIN=0
start /w %wd%\HipChat_%newversion%.exe /verysilent /norestart /restartapplications /lang=german /lang=1031 /64 /64bit /x64
set hipEL=%errorlevel%
if %hipEL%==1618 goto retry REM:: msiexec process in use, installation already in progress (eg. windows updates running)
if %hipEL%==1602 goto retry REM:: user canceled installation (eg. taskkill)
if %hipEL%==1603 goto retry REM:: fatal error, some use it for "already installed" (eg. java)
if %hipEL%==1638 goto uninstall REM:: another product is already installed, denies an update
if %hipEL%==1625 goto uninstall REM:: skype installer sometimes threw this one, don't know why
set hipEL=%errorlevel%
echo %date% %time:~0,8% - %computername% hat Version %newversion% mit EL %hipEL% abgeschlossen >> %log%
md %wd%\done\%computername%
goto end
:retry
if %retry%==1 goto retryfailed
echo %date% %time:~0,8% - %computername% hatte den Fehler %hipEL%, retry in 500Sek... >> %log%
set retry=1
REM:: 5 Minuten warten
ping localhost -n 500 > nul
goto taskkill
:retryfailed
echo _!_ %date% %time:~0,8% - %computername% hat die Installation abgebrochen, RETRY FAILED! >> %log%
goto end
:uninstall
if %retry%==1 goto retryfailed
TASKKILL /f /im hipchat.exe
echo %date% %time:~0,8% - %computername% deinstalliert Version %instversion%... >> %log%
start /w "" "C:\Program Files (x86)\Atlassian\HipChat4\unins000.exe" /s /silent /qn
del /q /s "C:\Program Files (x86)\Atlassian"
echo %date% %time:~0,8% - %computername% - %instversion% deinstalliert, retry... >> %log%
set retry=1
goto taskkill
:end
endlocal
exit
Und das war’s auch schon. Bei einem Update muss nur die neue .exe-Datei heruntergeladen und die Version in Zeile 11 angepasst werden. Das Script kommt als Computer-Startscript in das GPO und schon startet die Verteilung: Das Bild zeigt das Deployment der 1648er Version anhand des allnew-Update-Scripts (siehe Hinweis und Download oben). Dabei wird an jedem PC, unabhängig der installierten Version (deswegen wird überall „Version 0.0“ deinstalliert), HipChat komplett deinstalliert und neu installiert. Beim Umstieg auf 1648 würde ich das empfehlen, weitere Update werden ich auch wieder mit dem normalen Script erledigen.
Ich stelle kurz und knackig meinen in AutoIt 3 programmierten aktiven Bot für das Browserspiel Swords and Souls vor. Dieser ist hauptsächlich dafür da, Arena-Stages zu farmen. Außerdem nutzt er aktiv (und clever) alle Skills, leert regelmäßig die Bank, tauscht Kleeblätter ein, kauft EXP und speichert zwischendurch. Er könnte also ohne Probleme über Stunden durchlaufen und den Charakter damit verbessern.
Funktionen
Steuerung durch Shortcuts:
Shift+Alt+A – In der Arena kämpfen
Shift+Alt+M – Bank leeren und Kleeblätter eintauschen
Shift+Alt+O – Zurück zur Übersicht gehen, um andere Funktionen zu nutzen
Shift+Alt+H – Diese Hilfe noch einmal zeigen
Shift+Alt+X / ESC – Programm beenden
Farmingpausen mit:
Speichern
Gold von der Bank einsammeln
Kleeblätter eintauschen
Erfahrung für Gold kaufen (erst nach dem Besiegen des Endgegners möglich)
Cleveres Skillsystem:
Priorität 1: Heilen (Skill 5): nur wenn HP unter 40%
Priorität 2: Schildschlag (Skill 2): in Bosskämpfen immer benutzen, bei normalen Gegnern nur wenn dessen HP > 25%
Priorität 3: Schutzschild (Skill 4): immer wenn möglich
Priorität 4: Gift (Skill 3): in Bosskämpfen immer benutzen, bei normalen Gegnern nur wenn dessen HP > 50%
Priorität 5: Hack’n’Slay (Skill 6): in Bosskämpfen immer benutzen, bei normalen Gegnern nur wenn dessen HP > 50%
Priorität 6: Doppelangriff (Skill 1): in Bosskämpfen immer benutzen, bei normalen Gegnern nur wenn dessen HP > 25%
Screenshots
Die Einrichtung muss direkt nach dem Start geschehen und erfolgt durch einen Klick auf den obersten linkesten Pixel des Spielfensters: Das Browserfenster muss so groß sein, dass das Spiel komplett angezeigt wird. Am Anfang werden in einem Hilfedialog alle Funktionen gelistet: In der Arena wird zuerst das Level für das Farming gewählt, anschließend noch ob das Pausenfeature genutzt werden soll und schon legt der Bot los:
Video
Download und Code
Hier gibts den Bot als Download für 32/64bit Windows und, falls jemand den Bot weiterentwickeln oder anpassen möchte, den kompletten Code 1:1.
Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=swords-and-souls-bot.ico
#AutoIt3Wrapper_Compile_Both=y
#AutoIt3Wrapper_Res_Description=AutoIt Bot for the browsergame "Swords and Souls", developed by Hannes Schurig in 2016
#AutoIt3Wrapper_Res_Fileversion=1.0
#AutoIt3Wrapper_Res_LegalCopyright=Hannes Schurig
#AutoIt3Wrapper_Res_Language=1031
#AutoIt3Wrapper_Add_Constants=n
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <Misc.au3>
#include <Array.au3>
#include <MsgBoxConstants.au3>
AutoItSetOption("SendKeyDelay", 40)
AutoItSetOption("SendKeyDownDelay", 40)
AutoItSetOption("MouseClickDelay", 40)
AutoItSetOption("WinTitleMatchMode", 2)
AutoItSetOption("MouseCoordMode", 1)
$noCoords = True
Local $ol[2]
$i = 0 ; fight counter
$survival = False
Local $levelcoords[2] = [0,0]
$clickCriticals = True
$pauseFarming = True
$pauseFarmingAfterXMatches = 2
HotKeySet("{Esc}", "exitnow") ; ESC
HotKeySet("+!x", "exitnow") ; Shift+Alt+X
HotKeySet("+!a", "arenaFight") ; Shift+Alt+A
HotKeySet("+!m", "museum") ; Shift+Alt+M
HotKeySet("+!o", "overview") ; Shift+Alt+O
HotKeySet("+!h", "help") ; Shift+Alt+H
Func exitnow()
Exit
EndFunc
; prepare
WinActivate("Swords")
Sleep(300)
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONINFORMATION + $MB_OK,"Ecke oben links anklicken","Klicke in den obersten linkesten Pixel des Spiels.")
While $noCoords
If _IsPressed("01") Then
$ol = MouseGetPos()
ConsoleWrite("####### Koords: " & $ol[0] & " " & $ol[1] & @CRLF)
$noCoords = False
EndIf
WEnd
If checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52) Then
ConsoleWrite("####### Location: Map Overview" & @CRLF)
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONINFORMATION + $MB_OK, "Deine Position", "Du befindest dich in der Übersichtskarte.")
EndIf
If checkSingleCoordWithColor($ol[0]+394, $ol[1]+460, 0x745818) Then
ConsoleWrite("####### Location: Museum" & @CRLF)
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONINFORMATION + $MB_OK, "Deine Position", "Du befindest dich im Museum.")
EndIf
If checkSingleCoordWithColor($ol[0]+66, $ol[1]+64, 0xC8B05E) Then
ConsoleWrite("####### Location: Arena" & @CRLF)
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONINFORMATION + $MB_OK, "Deine Position", "Du befindest dich in der Arena.")
EndIf
help()
Func help()
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONINFORMATION + $MB_OK, "Hilfe", "Du kannst folgende Shortcuts nutzen:" & @CRLF & _
"Shift+Alt+A - In der Arena kämpfen" & @CRLF & _
"Shift+Alt+M - Bank leeren und Kleeblätter eintauschen" & @CRLF & _
"Shift+Alt+O - Zurück zur Übersicht gehen, um andere Funktionen zu nutzen" & @CRLF & _
"Shift+Alt+H - Diese Hilfe noch einmal zeigen" & @CRLF & _
"Shift+Alt+X / ESC - Programm beenden" & @CRLF & _
"Dies ist ein Maus/Tastatur-Bot. Der PC ist, während der Bot läuft, nicht direkt benutzbar.")
EndFunc
Func overview()
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
EndFunc
Func arenaFight()
; check for overview map
If checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52) Then
; go to arena
MouseClick("left",$ol[0]+235, $ol[1]+165,1)
; check for arena
ElseIf checkSingleCoordWithColor($ol[0]+66, $ol[1]+64, 0xC8B05E) Then
; continue
Else
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
; go to arena
MouseClick("left",$ol[0]+235, $ol[1]+165,1)
EndIf
; level input
$level = InputBox("Welches Level?", "Bitte gib das Level ein, dass gefarmt werden soll." & @CRLF & _
"Möglich Eingaben sind '1' - '30', 'survival' und 'final'", "", " M", 280, 150)
If $level > 0 And $level <= 10 Then
$levelcoords[0] = $ol[0] + 119 + (($level - 1)*62)
$levelcoords[1] = $ol[1] + 292
ElseIf $level > 10 And $level <= 20 Then
$levelcoords[0] = $ol[0] + 97 + (($level - 11)*66)
$levelcoords[1] = $ol[1] + 334
ElseIf $level > 20 And $level <= 30 Then
$levelcoords[0] = $ol[0] + 75 + (($level - 21)*71)
$levelcoords[1] = $ol[1] + 392
ElseIf $level = "final" Then
$levelcoords[0] = $ol[0] + 489
$levelcoords[1] = $ol[1] + 482
ElseIf $level = "survival" Then
$levelcoords[0] = $ol[0] + 339
$levelcoords[1] = $ol[1] + 456
$survival = True
Else
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONERROR + $MB_OK, "Falsche Eingabe", "Die Eingabe konnte nicht verwertet werden." & @CRLF & _
"Der Bot setzt sich zurück, anschließend kannst Du erneut den Arena-Modus starten.")
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
Sleep(1000)
Return
EndIf
If $levelcoords[0] == 0 Then
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONERROR + $MB_OK, "Unerwarteter Fehler", "Der Bot setzt sich zurück, anschließend kannst Du erneut den Arena-Modus starten.")
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
Sleep(1000)
Return
EndIf
; pause farming regulary after some matches - save, grab income, turn in cloverleafs frequently, get exp for gold?
$save = MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONQUESTION + $MB_YESNO, "Farming mit Pausen?", "Soll der Bot während des Farmens weitere hilfreiche Aufgaben übernehmen? Folgende Aufgaben würde der Bot alle " & $pauseFarmingAfterXMatches & " Matches ebenfalls übernehmen:" & @CRLF & _
"- Speichern" & @CRLF & _
"- Gold von der Bank einsammeln" & @CRLF & _
"- Kleeblätter eintauschen" & @CRLF & _
"- Erfahrung für Gold kaufen (erst nach dem Besiegen des Endgegners möglich)", "", " M")
If $save = $IDYES Then
$pauseFarming = True
Else
$pauseFarming = False
EndIf
; arena:
while 1
WinActivate("Swords")
Sleep(1200)
; ------------- MANAGING MATCHES --------------------------------------------------
; lost/survival done, retry
If checkSingleCoordWithColor($ol[0] + 351, $ol[1] + 326, 0x5A3D1D) Then
MouseClick("left",$ol[0] + 351, $ol[1] + 326, 1)
$i += 1
EndIf
; survival start
If $survival And checkSingleCoordWithColor($ol[0]+292, $ol[1]+175, 0x938D6F) Then
; click survival
MouseClick("left", $levelcoords[0], $levelcoords[1], 1)
Sleep(500)
; click "start"
MouseClick("left", $levelcoords[0], $levelcoords[1], 1)
Sleep(1500)
EndIf
; survival ended, "ok"
If $survival And checkSingleCoordWithColor($ol[0] + 426, $ol[1] + 448, 0x5D3F1D) Then
MouseClick("left", $ol[0] + 426, $ol[1] + 448, 1)
Sleep(1000)
EndIf
; final ended, scarecrow talk
If checkSingleCoordWithColor($ol[0] + 360, $ol[1] + 511, 0xE6DFC7) Then
MouseClick("left", $ol[0] + 360, $ol[1] + 511, 1)
Sleep(1000)
EndIf
; normal match start
If checkSingleCoordWithColor($ol[0]+292, $ol[1]+175, 0x938D6F) Then
MouseClick("left", $levelcoords[0], $levelcoords[1], 1)
EndIf
; normal match finished, continue
If checkSingleCoordWithColor($ol[0]+378, $ol[1]+454, 0xFFFFFF) Then
MouseClick("left", $ol[0]+378, $ol[1]+454, 1)
$i += 1
EndIf
; ------------- FIGHTING --------------------------------------------------
; Skill 5 - Healing
; check if available
If checkSingleCoordWithColor($ol[0]+490, $ol[1]+560, 0xFFE7C1) Then
; just use if u're below 50% health
If checkSingleCoordWithColor($ol[0]+284, $ol[1]+453, 0x730520) Then
; enough health
Else
Send("5")
EndIf
EndIf
; Skill 2
; check if available
If checkSingleCoordWithColor($ol[0]+306, $ol[1]+565, 0xFFE7C1) Then
; if in boss battle - use it anytime
If checkCoordRangeWithColor($ol[0]+404, $ol[1]+451, $ol[0]+442, $ol[1]+469, 0xFFD23E) Then
Send("2")
Else
; if not: only if enemy health 25% or more
If checkSingleCoordWithColor($ol[0]+485, $ol[1]+453, 0x730520) Then
Send("2")
EndIf
EndIf
EndIf
; Skill 4
; if available
If checkSingleCoordWithColor($ol[0]+430, $ol[1]+560, 0xFFE7C1) Then
Send("4")
EndIf
; Skill 3 - Poison
If checkSingleCoordWithColor($ol[0]+369, $ol[1]+552, 0xFFE7C1) Then
; if in boss battle - use it anytime
If checkCoordRangeWithColor($ol[0]+404, $ol[1]+451, $ol[0]+442, $ol[1]+469, 0xFFD23E) Then
Send("3")
Else
; if not: only if enemy health 50% or more
If checkSingleCoordWithColor($ol[0]+515, $ol[1]+453, 0x730520) Then
Send("3")
EndIf
EndIf
EndIf
; Skill 6 - Slashing
If checkSingleCoordWithColor($ol[0]+548, $ol[1]+561, 0xFFE7C1) Then
; if in boss battle - use it anytime
If checkCoordRangeWithColor($ol[0]+404, $ol[1]+451, $ol[0]+442, $ol[1]+469, 0xFFD23E) Then
Send("6")
Else
; if not: only if enemy health 50% or more
If checkSingleCoordWithColor($ol[0]+515, $ol[1]+453, 0x730520) Then
Send("6")
EndIf
EndIf
EndIf
; Skill 1
; if available
If checkSingleCoordWithColor($ol[0]+249, $ol[1]+559, 0xFFE7C1) Then
; if in boss battle - use it anytime
If checkCoordRangeWithColor($ol[0]+404, $ol[1]+451, $ol[0]+442, $ol[1]+469, 0xFFD23E) Then
Send("1")
Else
; if not: only if enemy health 25% or more
If checkSingleCoordWithColor($ol[0]+485, $ol[1]+453, 0x730520) Then
Send("1")
EndIf
EndIf
EndIf
; ------------- REST --------------------------------------------------
; save every 5th fight
If $pauseFarming And $i >= $pauseFarmingAfterXMatches Then
Sleep(1000)
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
museum()
expForGold()
; go to arena
MouseClick("left",$ol[0]+235, $ol[1]+165,1)
$i = 0
Sleep(1000)
EndIf
WEnd
EndFunc
Func museum()
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
; go to museum
MouseClick("left",$ol[0]+329, $ol[1]+459, 1)
Sleep(1000)
; get cash
MouseClick("left",$ol[0]+479, $ol[1]+431, 1)
Sleep(1000)
; auto-invest clovers if u're in the room and have some
While checkSingleCoordWithColor($ol[0]+651, $ol[1]+311, 0xE8E5C2)
MouseClick("left",$ol[0]+651, $ol[1]+311,1)
MouseMove($ol[0]+551, $ol[1]+211, 1)
Sleep(2000)
WEnd
Sleep(1000)
; get cash again
MouseClick("left",$ol[0]+479, $ol[1]+431, 1)
Sleep(1000)
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
Sleep(1000)
EndFunc
Func expForGold()
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
; go to shop
MouseClick("left",$ol[0]+528, $ol[1]+213, 1)
Sleep(1000)
For $x = 1 To 30 Step +1
MouseClick("left",$ol[0]+396, $ol[1]+548,1)
Sleep(300)
Next
; force-go to overview map
While Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52)
MouseClick("left", $ol[0]+25, $ol[1]+20,1)
Sleep(800)
WEnd
Sleep(1000)
EndFunc
Func training()
; this feature is not ready yet
Return
; check for overview map
If Not checkSingleCoordWithColor($ol[0]+578, $ol[1]+400, 0xB65D52) Then
MsgBox($MB_TOPMOST + $MB_SETFOREGROUND + $MB_DEFBUTTON1 + $MB_ICONINFORMATION + $MB_OK, "Übersichtskarte", "Bitte gehe in die Übersichtskarte, um die Botfunktionen zu starten.")
Return
EndIf
;training crit:
While 1
If checkSingleCoordWithColor(685, 382, 0xffffff) Then
MouseClick("left")
Sleep(700)
MouseClick("left")
$i +=1
ContinueLoop
EndIf
If checkSingleCoordWithColor(789, 381, 0xffffff) Then
MouseClick("left")
Sleep(700)
MouseClick("left")
$i +=1
ContinueLoop
EndIf
If $i>50 Then
; quit crit training
MouseClick("left", $ol[0]+25, $ol[1]+20, 1)
Sleep(5000)
; quit training room & go to map
MouseClick("left", 298, 123, 1)
Sleep(3000)
; go to training room
MouseClick("left", 673, 415, 1)
Sleep(3000)
$i = 0
; start crit training
MouseClick("left", 872, 147, 1)
Sleep(3000)
EndIf
WEnd
; training attack:
While 1
If Not checkSingleCoordWithColorV(326, 455, 0xCC9362, 3) Then
Send("{LEFT}")
ContinueLoop
EndIf
If Not checkSingleCoordWithColorV(560, 466, 0xAD794C, 4) Then
Send("{RIGHT}")
ContinueLoop
EndIf
If Not checkSingleCoordWithColorV(563, 541, 0x926743, 3) Then
Send("{Down}")
ContinueLoop
EndIf
If Not checkSingleCoordWithColorV(511, 373, 0xCF996B, 3) Then
Send("{Up}")
ContinueLoop
EndIf
If Not checkSingleCoordWithColorV(322, 424, 0xD4A57D, 3) Then
Send("{LEFT}")
ContinueLoop
EndIf
WEnd
EndFunc
Func checkSingleCoordWithColor($x, $y, $color)
Return IsArray(PixelSearch($x-3, $y-3, $x+3, $y+3, $color, 3))
EndFunc
Func checkSingleCoordWithColorV($x, $y, $color, $variation)
Return IsArray(PixelSearch($x-3, $y-3, $x+3, $y+3, $color, $variation))
EndFunc
Func checkCoordRangeWithColor($x1, $y1, $x2, $y2, $color)
Return IsArray(PixelSearch($x1-3, $y1-3, $x2+3, $y2+3, $color, 3))
EndFunc
; keep-alive for shotcuts
while 1
sleep(100000000)
WEnd
Kurz notiert: Dank PowerShell lassen sich bei Microsoft Exchange – in meinem Fall Exchange Online, also ein hosted Exchange – Änderungen gleich für mehrere oder sogar alle Nutzer ausführen. Vor allem bei Änderungen, die normalerweise nicht über die Adminoberfläche administrierbar sind, sondern über den Nutzer direkt eingestellt werden müssen, lohnt sich das enorm.
Verbindung zu Exchange Online in PowerShell herstellen
In PowerShell folgende Befehle nacheinander eingeben:
$UserCredential = Get-Credential
An dieser Stelle dann die Mail-Credentials eines Exchange Admins eingeben.
Speichert die Verbindung zu Exchange Online mit den Credentials in ein Objekt
Import-PSSession $Session
Lädt das Session Objekt
Anschließend könnt ihr auf dem Exchange Befehle ausführen, beispielsweise
Get-Mailbox
:
In den folgenden Beispielen soll die Abwesenheitsmeldung bzw. Automatische Antwort eingestellt werden. Diese Einstellung eines Nutzers lässt sich mit folgenden Befehl abrufen:
können wieder Ausgaben eines Befehls an den nächsten Befehl zur Weiterverarbeitung übergeben werden. Alle Benutzer:
Get-Mailbox | Set-MailboxAutoReplyConfiguration -AutoReplyState Enabled -ExternalAudience All -ExternalMessage "Guten Tag<br>Bla bla bla, Urlaub bla.<br>Mit freundlichen Grüßen"
Über
Get-Mailbox
werden alle Mailkonten geladen und an den
Set
-Befehl übergeben, der dadurch keinen
Identity
-Parameter mehr braucht. Hinweis: Die Massenverarbeitung dauert natürlich entsprechend lange – für die 30 Nutzer bei uns hat der Befehl 4 Minuten gebraucht. Also nicht ungeduldig werden.
Mit Benutzervorauswahl:
Get-User | where {$_.Department -eq "Sales"} | Get-Mailbox | Set-MailboxAutoReplyConfiguration -AutoReplyState Enabled -ExternalAudience All -ExternalMessage [...]
Somit werden Nutzer erst durch den
where
-Befehl gefiltert, deren Postfächer geladen und weitergegeben. (via)
Typisches Problem – es funktioniert nicht
Wichtig: Damit AutoReply-Regeln tatsächlich auch funktionieren, müssen in den Mailkonten auch wirklich E-Mails eingehen. Bei Konten, die ihre E-Mails nur via SMTP weiterleiten und keine lokale Kopie der Mails in ihrem Postfach empfangen, funktioniert das Auto-Reply deswegen nicht. Neben den AutoReply-Einstellungen muss demnach auch die Einstellung, dass beim Weiterleiten der Mails eine lokale Kopie behalten werden soll, gesetzt werden.
Einen Überblick über die Weiterleitungseinstellungen aller Nutzer bekommt ihr mit diesem Befehl:
Mit diesem schnellen Überblick könnt ihr euch entweder selbst die Nutzer raussuchen, die eine Weiterleitung eingerichtet haben jedoch keine lokalen Kopien in ihr Postfach kriegen (und somit auch keine Automatische Antwort abschicken). Oder ihr nutzt einfach folgenden Befehl. Dieser aktiviert diese Einstellung der lokalen Kopie für alle Benutzer, die eine Weiterleitung (intern sowie extern) eingerichtet haben:
Dieser Beitrag ist eine Ergänzung bzw. Erweiterung des vorherigen Posts „FTP-Backup-Lösung mit PHP“. Die große Neuerung der Version 1.2 ist die Funktion MySQL Datenbanken sichern zu können. Auch hier wird ein Backup erstellt, überschüssige Backups (wenn mehr vorhanden sind als der gewünschte Maximalwert) werden gelöscht und neue Backups ggf. zu einem externen Server übertragen. Mit Version 1.2.1 gibt es zusätzlich die Möglichkeit, ALLE Ordner der Root-Ebene, mit Ausnahmen, zu sichern und Version 1.2.2 ermöglicht detailliertere Ausnahmen. In Version 1.3 wird das Backup mittels eines weiteren Skripts aufgerufen. Mehr dazu weiter unten.
Features
Diese Lösung (v1.3) bietet nun folgenden Funktionsumfang:
beliebig viele Ordner des All-Inkl Accounts in einzelne .tar.gz Archive sichern
oder: alle Ordner der Root-Ebene, mit möglichen Ausnahmen, sichern
Detailliertere Ausnahmen mit Datei- und Ordnermasken wie z.B. „*.tar.gz“
Einschränkung der Anzahl aufgehobener Backups – älteste Backups werden automatisch gelöscht
detaillierte Ausgabe inklusive benötigter Zeit
E-Mail Benachrichtigung
Farbliche Hervorhebung
Verbesserungen des Backup Prozesses, zusätzliche Überprüfungen und Debug Infos bei Fehlern
Verbinden eines externen FTP Server und Kopieren aller neuen Backups
Angabe eines beliebigen FTP Ports
Verbindung über FTPs (SSL FTP) Port 21 wird verwendet, unsicheres FTP nur noch als Fallback
detailliertere Informationen über die Backups in der Benachrichtigungsmail
Backup von beliebig vielen MySQL Datenbanken von localhost, Aufräumen der Backups und Export an externen Server
E-Mail Anpassungen über Parameter möglich – Betreff, Anmerkungen, Details
ausführliche Ausgabe aller Backups im Skript und per Mail
Zwischen den Zeilen 37 und 82 findet ihr alle Variablen, die ihr anpassen müsst/könnt.
Zur Datenbanksicherung ist zu sagen, dass diese auf den Hoster All-Inkl optimiert ist. Sie sichert nur Datenbanken von localhost und benötigt den PHP Befehl „exec()“ sowie die Komponenten „mysqldump“ und „gzip“, die auf All-Inkl Servern erlaubt bzw. installiert sind. Auf anderen Hostern müssen daher ggf. diese Möglichkeiten geschaffen oder die MySQL Sicherung (Zeile 190-191) verändert werden.
Update 03/2020: Blogleser Crunchy hat mich netterweise darauf hingewiesen, dass durch den Wegfall des apache-mode die Skriptausführungszeit nun auf 10 Minuten begrenzt ist. Daher ist es nun sinnvoll, das Backupskript über ein zweites Skript aufzurufen. All-Inkl gibt das Format grob vor und ich habe unten im Download ein solches Aufruf-Skript auch mit eingefügt. Ladet euch also beide Skripte und richtet euch einen Cronjob mit dem Aufruf-Skript init-backup.php ein.
Screenshot
Code
Schaut für Code-Alternativen oder ein weniger komplexes System auch auf die Version 1.1 und 1.0
Das Verzeichnis, in dem die backup.php und die Backups liegen, sollte natürlich mit einer .htpasswd abgesichert werden. Mit einer eingerichteten .htpasswd Datei ist zuerst ein Login nötig, eh man auf bestimmte Bereiche des Webspaces zugreifen darf: Die Datei .htpasswd enthält hierbei die Login Daten und in der .htaccess des Backup Unterordners wird festgelegt, dass eine .htpasswd diesen Ordner schützt. Die .htpasswd generiert ihr euch am besten mit diesem Generator und baut sie dann folgendermaßen in die .htaccess dieses Ordners ein:
Da wir schonmal bei .htaccess sind, erhöhen wir die Sicherheit mit ein paar weiteren grundlegenden Zeilen:
#block access to certain file types
<FilesMatch ".(htaccess|htpasswd|ini|phps|log|sh|tar.gz)
quot;> Order Allow,Deny Deny from all </FilesMatch>
# disable directory browsing Options All -Indexes
# prevent basic url hacking stuff # from: http://www.queness.com/post/5421/17-useful-htaccess-tricks-and-tips RewriteEngine On # proc/self/environ? no way! RewriteCond %{QUERY_STRING} proc/self/environ [OR] # Block out any script trying to set a mosConfig value through the URL RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR] # Block out any script trying to base64_encode crap to send via URL RewriteCond %{QUERY_STRING} base64_encode.*(.*) [OR] # Block out any script that includes a <script> tag in URL RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR] # Block out any script trying to set a PHP GLOBALS variable via URL RewriteCond %{QUERY_STRING} GLOBALS(=|[|\%[0-9A-Z]{0,2}) [OR] # Block out any script trying to modify a _REQUEST variable via URL RewriteCond %{QUERY_STRING} _REQUEST(=|[|\%[0-9A-Z]{0,2}) # Send all blocked request to homepage with 403 Forbidden error! RewriteRule ^(.*)$ /index.htm [F,L]
ErrorDocument 401 /backup/index.htm ErrorDocument 403 /backup/index.htm ErrorDocument 404 /backup/index.htm ErrorDocument 500 /backup/index.htm Dadurch werden Zugriffe auf bestimmte Dateitypen (auch die Backup Dateien), Verzeichnisse und Zugriffe mit sicherheitskritischen Merkmalen unterbunden. Alle diese nicht validen Zugriffe bekommen die index.htm serviert, welches einfach nur eine leere HTML Datei ist. Somit wird den Abfragenden auch kein detaillierter Grund gegeben, warum der Zugriff fehlschlug.
Automatisierung mit All-Inkl Cronjobs
Zu guter Letzt hilft diese Sicherungslösung natürlich nur, wenn sie automatisiert wird. Auch dies ist stark abhängig von eurem Hoster, System, dem Anwendungsbereich usw. Im Falle von All-Inkl als Webhoster, könnt ihr die Cronjob Funktionalität im KAS (KAS -> Tools -> Cronjobs) benutzen:
ACHTUNG: Richtet euch den Cronjob bitte für die init-backup.php ein, nicht für die backup.php. Siehe Update von 03/2020 oben.
Verbesserungen des Backup Prozesses, zusätzliche Überprüfungen und Debug Infos bei Fehlern
Verbinden eines externen FTP Server und Kopieren aller neuen Backups
Angabe eines beliebigen Ports
Verbindung über FTPs (SSL FTP) Port 21 wird verwendet, unsicheres FTP nur noch als Fallback
detailliertere Informationen über die Backups in der Benachrichtigungsmail
Nun werden also von beliebig vielen Ordner des FTP-Root Backups erstellt, gegebenenfalls aufgeräumt wenn mehr Backups existieren als aufgehoben werden sollen und anschließend alle neuen Backups auf einen externen FTP Server kopiert. Für den Upload wird der passive FTP Modus verwendet, da dieser in den seltensten Fällen Probleme macht. Sollte der Wechsel zum passiven FTP fehlschlagen, wird dennoch aktives FTP probiert. Alle nötigen Informationen werden in den Variablen in Zeile 16 bis 25 angegeben. Format und Hilfe steht jeweils dabei, eigentlich sollte da alles klar sein.
Die Erweiterungen machen aus dem Skript eine beispielhafte Backup-Lösung für FTP Inhalte. Auch hier am Beispiel von All-Inkl als Hoster. Wer die Lösung unabhängig von All-Inkl einsetzen möchte, wird das Archive_Tar PHP Modul und irgendeine Art von Cronjob-Funktionalität brauchen.
Update (02.06.2015): Anpassung der Variablennamen zur besseren Lesbarkeit, Fehlerkorrekturen, verbesserte FTP-URI-Verarbeitung, Portangabe möglich, Verbindung über FTPs (FTP über SSL über Port 21) möglich (mit Fallback zu unsicherem FTP wenn FTPs nicht funktioniert), erweiterte Informationen über die erfolgten Backups in der Benachrichtigungsmail, kleine Optimierungen Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.
<?
// PHP-Konfiguration optimieren
// if no errors are shown, please check htaccess restrictions by "php_flag display_errors off"
// in this or parent folders
@error_reporting(E_ALL);
@ini_set("max_execution_time", 300);
@ini_set("memory_limit", "256M");
header('Content-Type: text/html; charset=utf-8');
include "Archive/Tar.php";
$pfad = preg_replace('/(\/www\/htdocs\/\w+\/).*/', '$1', realpath(__FILE__));
$alltime = 0;
$newbackups = array();
$backupinfo = array();
echo "<style>.ok{color:#478F47;}.err{color:#DA3833;}.grey{color:grey;}.warn{color:#f0ad4e;}th,td{border-bottom: 1px solid #aaa;}</style>";
// ########## EDIT THIS VARIABLES ###################
$foldertobackup = array("tools", "reports"); // which root folders should get backed up?
$copytoexternalftp = 1; // copy new backup files to external ftp server? should be 1/"yes"/"ja" or 0/"no"/"nein"
// external (ftp) servers to copy new backups to, format:
// in general: ftp://username:password@url:port/path (port is required!)
// ftp://user:pw@ftp.server.com:21/
// ftp://user:pw@serverurl.com:21/optional/path
// secure sftp connection (port 22) in preparation but not ready yet
$externalftpuri = "ftp://admin:password@firma.dns.com:21/Data/FTP-Backups";
$backupfilemaximum = 2; // how many archives should be stored?
$dir = $pfad."backup/"; // in which subfolder is this backup php file? this would be: "root/backup/"
$sendmail = 1; // send notification mail when all backups are done - should be 1/"yes"/"ja" or 0/"no"/"nein"
$sendmailto = "adminmail@firma.de"; // valid mail address to send the mail to
// ##################################################
echo "<span class='ok'>".date("d.m.Y G:i:s")."</span><br>";
foreach ($foldertobackup as $verzeichnis) {
$jobtime = time();
echo "<br><br>########################################<br>";
echo "<strong>Verzeichnis $verzeichnis wird gesichert...</strong><br>";
flush();
// check if folder exists
if(!file_exists($pfad.$verzeichnis)) {
echo "<span class='err'>Sicherung fehlgeschlagen. Zu sichernder Ordner $pfad$verzeichnis existiert nicht.</span>";
continue;
}
// Name: [verzeichnis]_[Datum]_[Uhrzeit].tar.gz
$archivname = $verzeichnis.date('_Y-m-d_His').".tar.gz";
// Name: [All-Inkl-Accountname]_[Datum]_[Uhrzeit].tar.gz
//$archivname = preg_replace('/.+\/(.+)\/$/', '$1', $pfad).date('_Y-m-d_His').".tar.gz";
// Auszuschließende Ressourcen
$ignorieren = array("*.sql.gz", "*.tar.gz", "usage", "logs");
// ######### create backup
$archiv = new Archive_Tar($archivname, true);
$archiv->setIgnoreList($ignorieren);
$archiv->createModify($pfad.$verzeichnis, "", $pfad);
$backuptime = time() - $jobtime;
$archivsize = round(filesize($dir.$archivname)/1000000);
if (!is_numeric($archivsize)) {
$archivsize = "filesize error";
}
if (is_int($backuptime)) {
echo "<span class='ok'>Backup fertig: $archivname (Größe: $archivsize MB, Dauer: $backuptime Sekunden)</span><br>";
} else {
echo "<span class='ok'>Backup fertig: $archivname</span><br>";
}
// check created archive
if (!is_object($archiv)==1 || !is_numeric($archivsize) || !$archivsize>50) {
// abort process due to wrong type or too small filesize (which likely is an error)
echo "<span class='err'>Sicherung fehlgeschlagen. Erstelltes Archiv ist fehlerhaft.</span><br>Mehr Infos <a href='http://hannes-schurig.de/09/05/2015/ftp-backup-skript-in-php/' target='blank'>hier</a><br>";
// debug
echo "<p class='grey'>Debug:<br>";
echo "Pfad: $dir.$archivname<br>";
echo "Typ: ".gettype($archiv)."<br>";
echo "Größe: $archivsize MB</p>";
continue;
}
$newbackups[$archivname] = $dir.$archivname;
echo "Aufräumen der Backups...<br>";
flush();
// integer starts at 0 before counting
$i = 0;
$backupfiles = array();
// ######### collect valid backup files
if ($handle = opendir($dir)) {
while (($file = readdir($handle)) !== false) {
if ( is_int(strpos($file, $verzeichnis)) == true &&
preg_match('/\.tar.gz$/i', $file) &&
!in_array($file, array('.', '..')) &&
!is_dir($dir.$file)
) {
$backupfiles[$dir.$file] = filectime($dir.$file);
}
}
}
echo count($backupfiles)." valide Backups dieses Ordners gefunden, ";
echo "$backupfilemaximum Backups sollen behalten werden. ";
$backupcountdif = count($backupfiles)-$backupfilemaximum;
if ($backupcountdif<=0) {
echo "Kein Backup wird gelöscht.<br>";
} else if ($backupcountdif==1) {
echo "1 Backup wird gelöscht:<br>";
} else if ($backupcountdif>=2) {
echo "$backupcountdif Backups werden gelöscht:<br>";
}
flush();
// ######### sort and delete oldest backups
// sort backup files by date
arsort($backupfiles);
// reset counter variable
$i = 0;
// delete oldest files
foreach ($backupfiles as $filepath => $value) {
if($i>=$backupfilemaximum) {
echo "$filepath wird gelöscht...<br>";
if (unlink($filepath)) {
echo "<span class='ok'>Datei erfolgreich gelöscht.</span><br>";
} else {
echo "<span class='err'>Fehler beim Löschen der Datei.</span><br>";
}
}
$i++;
}
$jobendtime = time() - $jobtime;
array_push($backupinfo, array($verzeichnis, $archivname, $archivsize." MB", $jobendtime." Sekunden", date("d.m.Y G:i:s")));
echo "<span class='ok'>Backup für Verzeichnis $verzeichnis abgeschlossen.</span><br>";
if (is_int($jobendtime)) {
echo "######################################## (Dauer: $jobendtime Sekunden)<br>";
$alltime += $jobendtime;
} else {
echo "########################################<br>";
}
}
if(count($newbackups)>0) {
echo "<br><span class='ok'>Die automatische Sicherung des Skripts '".pathinfo(__FILE__, PATHINFO_BASENAME)."' hat ".count($newbackups)." Verzeichnisse in insgesamt $alltime Sekunden gesichert.</span><br><br>";
} else {
echo "<br><span class='err'>Es scheint leider so als wenn keine Backups erfolgreich erstellt wurden.</span><br><br>";
}
flush();
// ######### copy backups to external storages
if (!isset($copytoexternalftp) || $copytoexternalftp== 0 && in_array($copytoexternalftp, array("no", "nein"))) {
echo "<span class='warn'>Backups werden nicht auf einen externen FTP kopiert. Option ist deaktiviert.</span><br>";
} else {
$ftptime = time();
echo "########################################<br>";
echo "<strong>".count($newbackups)." Backups werden auf externen FTP kopiert...</strong><br>";
flush();
$ftpcon = getFtpConnectionByURI($externalftpuri);
flush();
if(gettype($ftpcon)=="resource") {
foreach ($newbackups as $filename => $fullpath) {
$uploadtime = time();
echo "Kopiere .$filename. (Größe: $archivsize MB) auf den FTP...<br>";
flush();
if (ftp_put($ftpcon, $filename, $filename, FTP_ASCII)) {
$uploadendtime = time() - $uploadtime;
if (is_int($uploadendtime)) {
echo "<span class='ok'>Backup erfolgreich kopiert. (Dauer: $uploadendtime Sekunden)</span><br>";
} else {
echo "<span class='ok'>Backup erfolgreich kopiert.</span><br>";
}
} else {
echo "<span class='err'>Fehler beim Kopieren des Backups.</span><br>";
}
flush();
}
}
$ftpendtime = time() - $ftptime;
if (is_int($ftptime)) {
echo "######################################## (Dauer: $ftpendtime Sekunden)<br>";
} else {
echo "########################################<br>";
}
}
flush();
// ######### send mail
if (!isset($sendmail) || $sendmail== 0 && in_array($sendmail, array("no", "nein"))) {
echo "<br><span class='warn'>Benachrichtigungsmail wurde nicht verschickt. Option ist deaktiviert.</span><br>";
} else {
if(!preg_match( '/^([a-zA-Z0-9])+([.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-]+)+/' , $sendmailto)) {
echo "<br><span class='err'>FEHLER: Mail konnte nicht versendet werden, da die Adresse ungültig ist!</span><br>";
} else {
$mailsubject = "Automatische FTP Sicherung abgeschlossen";
$mailtext = "Die automatische Sicherung des FTP-PHP-Backup-Skripts ".pathinfo(__FILE__, PATHINFO_BASENAME)." hat ".count($foldertobackup)." Verzeichnisse in insgesamt $alltime Sekunden gesichert.<br><br>";
// add backup informations as table to mailtext
$mailtext .= "<table border=0 style='border-spacing:0;'><thead><tr><th>Verzeichnis</th><th>Dateiname</th><th>Größe</th><th>Dauer</th><th>Timestamp</th></tr></thead><tbody>";
for($i=0;$i<count($backupinfo);$i++) {
$mailtext .= "<tr>";
for($j=0;$j<count($backupinfo[$i]);$j++) {
$mailtext .= "<td>" . $backupinfo[$i][$j] . "</td>";
}
$mailtext .="</tr>";
}
$mailtext .= "</tbody></table>";
mail(
$sendmailto,
$mailsubject,
$mailtext,
"From: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Reply-To: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Content-Type: text/html\r\n"
) or die("<br><span class='err'>FEHLER: Mail konnte wegen eines unbekannten Fehlers nicht versendet werden.</span><br>");
echo "<br><span class='ok'>Benachrichtigungsmail wurde erfolgreich verschickt!</span><br>";
}
}
flush();
// function to get ftp connection object from URI
// basics were from: http://php.net/manual/de/function.ftp-connect.php#89811
function getFtpConnectionByURI($uri)
{
// Split FTP URI into:
// $match[0] = ftp://admin:password@barketing.dns.com:21/Data/FTP-Backups
// $match[1] = ftp
// $match[2] = admin
// $match[3] = password
// $match[4] = barketing.dns.com
// $match[5] = 21
// $match[6] = /Data/FTP-Backups
preg_match("/([a-z]*?):\/\/(.*?):(.*?)@(.*?):(.*?)(\/.*)/i", $uri, $match);
$ftpcon = null;
// check if port is set and uri is formatted correctly
if(is_int(intval($match[5]))) {
$port = intval($match[5]);
// check if ftp(s) or sftp is chosen
if($match[1]=="ftp") {
// set up and ftp(s) connection, login
echo "Stelle (FTPs über SSL - Port $port) Verbindung zu FTP Server $match[4] her...<br>";
// try ftps over ssl, usally through port 21
$ftpcon = ftp_ssl_connect($match[4], $port, 30);
if (!gettype($ftpcon)=="resource") {
echo "<span class='warn'>FTP über SSL - Port $port - Verbindung fehlgeschlagen!</span><br>";
echo "Stelle (unsicheres FTP - Port $port) Verbindung zu FTP Server $match[4] her...<br>";
// try normal insecure ftp
$ftpcon = ftp_connect($match[4], $port, 30);
}
if (gettype($ftpcon)=="resource") {
$login = ftp_login($ftpcon, $match[2], $match[3]);
$pasv = ftp_pasv($ftpcon, true);
}
} else if($match[1]=="sftp") {
echo "<span class='err'>SFTP Unterstützung noch nicht implementiert.</span><br>";
// if(!$port==22) {
// echo "<span class='warn'>SFTP Übertragung aber Port ist nicht 22. Ist der gewählte Port $port korrekt?</span><br>";
// }
// echo "Stelle (sichere sFTP - Port $port) Verbindung zu FTP Server $match[4]$match[5]:$match[6] her...<br>";
// $ftpcon = ssh2_connect($match[4], $port, 30);
// ssh2_auth_password($ftpcon, $match[2], $match[3]);
// $sftp = ssh2_sftp($ftpcon);
} else {
echo "<span class='err'>Kein gültiger Verbindungstyp (ftp/sftp) angegeben.</span><br>";
}
} else {
echo "<span class='err'>Der Port ist fehlerhaft angegeben. Bitte URI prüfen.</span><br>";
}
if ($ftpcon && gettype($ftpcon)=="resource")
{
echo "<span class='ok'>Verbindung hergestellt</span>";
if ($login) {
echo "<span class='ok'>, Login erfolgreich</span>";
if ($pasv) {
echo "<span class='ok'>, passiver Modus aktiviert</span>";
if(!isset($match[6]) || $match[6] == "") {
echo ".<br>";
return $ftpcon;
} else if (ftp_chdir($ftpcon, $match[6])) {
echo "<span class='ok'>, Verzeichniswechsel zu $match[6] erfolgreich.</span><br>";
return $ftpcon;
} else {
echo "<span class='err'>, Verzeichniswechsel zu $match[6] fehlerhaft.</span><br>";
return null;
}
} else {
echo "<span class='err'>, passiver Modus konnte nicht aktiviert werden. Upload wird trotzdem probiert.</span><br>";
return $ftpcon;
}
} else {
echo "<span class='err'>, Login fehlgeschlagen.</span><br>";
return null;
}
}
echo "<span class='err'>Fehler beim Verbinden mit dem FTP Server $match[4]$match[5]$match[6].</span><br>";
echo "<p class='grey'>Debug:<br>";
echo "URI (komplett): $match[0]<br>";
echo "Typ: $match[0]<br>";
echo "URI ohne Typ: $match[4]<br>";
echo "Username: $match[2]<br>";
echo "Passwort: $match[3]<br>";
echo "Port: $match[5]<br>";
echo "Unterordner: $match[6]<br>";
echo "</p>";
// Or retun null
return null;
}
// from: http://stackoverflow.com/a/10473026/516047
function startsWith($haystack, $needle) {
// search backwards starting from haystack length characters from the end
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
}
?>
Sicherheit: Absicherung mit .htpasswd
Das Verzeichnis, in dem die backup.php und die Backups liegen, sollte natürlich mit einer .htpasswd abgesichert werden. Mit einer eingerichteten .htpasswd Datei ist zuerst ein Login nötig, eh man auf bestimmte Bereiche des Webspaces zugreifen darf: Die Datei .htpasswd enthält hierbei die Login Daten und in der .htaccess des Backup Unterordners wird festgelegt, dass eine .htpasswd diesen Ordner schützt. Die .htpasswd generiert ihr euch am besten mit diesem Generator und baut sie dann folgendermaßen in die .htaccess dieses Ordners ein:
Da wir schonmal bei .htaccess sind, erhöhen wir die Sicherheit mit ein paar weiteren grundlegenden Zeilen:
#block access to certain file types
<FilesMatch ".(htaccess|htpasswd|ini|phps|log|sh|tar.gz)$">
Order Allow,Deny
Deny from all
</FilesMatch>
# disable directory browsing
Options All -Indexes
# prevent basic url hacking stuff
# from: http://www.queness.com/post/5421/17-useful-htaccess-tricks-and-tips
RewriteEngine On
# proc/self/environ? no way!
RewriteCond %{QUERY_STRING} proc/self/environ [OR]
# Block out any script trying to set a mosConfig value through the URL
RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR]
# Block out any script trying to base64_encode crap to send via URL
RewriteCond %{QUERY_STRING} base64_encode.*(.*) [OR]
# Block out any script that includes a <script> tag in URL
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
# Block out any script trying to set a PHP GLOBALS variable via URL
RewriteCond %{QUERY_STRING} GLOBALS(=|[|\%[0-9A-Z]{0,2}) [OR]
# Block out any script trying to modify a _REQUEST variable via URL
RewriteCond %{QUERY_STRING} _REQUEST(=|[|\%[0-9A-Z]{0,2})
# Send all blocked request to homepage with 403 Forbidden error!
RewriteRule ^(.*)$ /index.htm [F,L]
ErrorDocument 401 /backup/index.htm
ErrorDocument 403 /backup/index.htm
ErrorDocument 404 /backup/index.htm
ErrorDocument 500 /backup/index.htm
Dadurch werden Zugriffe auf bestimmte Dateitypen (auch die Backup Dateien), Verzeichnisse und Zugriffe mit sicherheitskritischen Merkmalen unterbunden. Alle diese nicht validen Zugriffe bekommen die index.htm serviert, welches einfach nur eine leere HTML Datei ist. Somit wird den Abfragenden auch kein detaillierter Grund gegeben, warum der Zugriff fehlschlug.
Automatisierung mit All-Inkl Cronjobs
Zu guter Letzt hilft diese Sicherungslösung natürlich nur, wenn sie automatisiert wird. Auch dies ist stark abhängig von eurem Hoster, System, dem Anwendungsbereich usw. Im Falle von All-Inkl als Webhoster, könnt ihr die Cronjob Funktionalität im KAS (KAS -> Tools -> Cronjobs) benutzen:
All-Inkl bietet sogar das Zusenden aller Skriptausgaben. Ihr erhaltet also zusätzlich zu dem kurzen E-Mail-Bericht, der im Skript generiert werden kann, noch eine weitere Mail mit allen Ausgaben. Diese sind dann zwar nicht mehr farbig, aber was solls:
Danke an Kenny für das wertvolle Feedback im letzten Artikel, das dazu beigetragen hat, dass ich diese Erweiterung nochmal gepostet habe. Meinst du, dass das jetzt eine Basic Backup Lösung sein könnte? Das einzige, was noch fehlt, ist das Backup Management auf dem externen FTP Server, damit der nicht überläuft. Aber das ist nun wirklich Aufgabe der Admins zu entscheiden und zu verwalten, wie lange Backups aufgehoben werden sollen.