Toptorials Tutorials WeblogoLogobild kleinLogobild kleinToptorials Tutorials Weblogo
  • ALLE THEMEN IM ÜBERBLICK
  • YouTube
  • INFOS
    • SITEMAP
    • ÜBER TOPTORIALS
✕
            No results See all results

            Python Grundkurs Teil 009: Projektordner mit Datum und Protokolldatei sichern

            • Startseite
            • Tutorials Windows Automatisierung Python Grundkurs
            • Python Grundkurs Teil 009: Projektordner mit Datum und Protokolldatei sichern

            Python Grundkurs Teil 009: Projektordner mit Datum und Protokolldatei sichern

            Worum geht es in diesem Teil?

            In diesem Teil des Python-Grundkurses sichern wir vollständige Projektordner in neue, eindeutig datierte Backupordner. Bei jedem echten Sicherungslauf entsteht zusätzlich eine eigene Protokolldatei. Darin wird festgehalten, welche Ordner angelegt, welche Dateien kopiert und welche Elemente ausgeschlossen wurden.

            Aus einem Projektordner wie:

            C:\Projekte\MeinProjekt

            entsteht beispielsweise diese Struktur:

            C:\Projekte\MeinProjekt
            └── Backups
                └── Backup_2026-09-12_18-30-45
                    ├── Dokumente
                    ├── Bilder
                    └── backup_protokoll_2026-09-12_18-30-45.log
            
            
            Ordner Backups mit Backup

            Ordner Backups mit Backup

            Der vorhandene Ordner Backups wird beim Einlesen der Quelle ausdrücklich ausgeschlossen. Dadurch gelangen ältere Sicherungen nicht erneut in das nächste Backup.

            Wir erstellen zwei Varianten:

            1. Die normale Variante sichert den Ordner, in dem das Python-Skript liegt.
            2. Die SendTo-Variante übernimmt einen oder mehrere Projektordner aus dem Windows-Explorer.

            Was ist das Ziel?

            Mit wenigen Schritten soll eine neue Momentaufnahme eines Projektordners entstehen. Datum und Uhrzeit im Ordnernamen machen jeden Sicherungslauf eindeutig und zeitlich nachvollziehbar.

            Vor dem Kopieren zeigt das Skript eine vollständige Vorschau. Im Testmodus werden keine Ordner, Dateien oder Protokolle angelegt. Erst im Echtmodus und nach der genauen Eingabe JA beginnt der Kopiervorgang.

            Warum ist dieser Teil wichtig?

            Dieses Tutorial verbindet mehrere wichtige Python-Grundlagen mit einer praktischen Aufgabe:

            • datetime.now().strftime() erzeugt den Zeitstempel.
            • Path erstellt und verarbeitet Dateisystempfade.
            • os.walk() durchläuft einen Projektordner einschließlich seiner Unterordner.
            • fnmatchcase() prüft Dateinamen gegen Ausschlussmuster.
            • casefold() ermöglicht Vergleiche ohne Beachtung der Groß- und Kleinschreibung.
            • Listen und Dictionaries speichern den vorbereiteten Backupplan.
            • mkdir() legt die Zielstruktur an.
            • shutil.copy2() kopiert Dateien und übernimmt dabei möglichst auch ihre Metadaten.
            • with sorgt dafür, dass die Protokolldatei zuverlässig geschlossen wird.
            • sys.argv übernimmt Testmodus und SendTo-Pfade.

            Hinweis zur Kursreihe

            Dieses Tutorial gehört zum Python-Grundkurs. Der Aufbau orientiert sich an den entsprechenden Aufgaben im Batch- und PowerShell-7-Grundkurs. So lassen sich die unterschiedlichen Schreibweisen und Arbeitsweisen der drei Sprachen direkt miteinander vergleichen.

            Die Python-Version verwendet ausschließlich Module aus der Standardbibliothek. Zusätzliche Pakete müssen daher nicht installiert werden.

            Downloads zu diesem Teil

            Dateien für Python Grundkurs Teil 009:
            Download Python Grundkurs: 009_Backup_mit_Datum als ZIP-Datei.Entpacken nach:

            C:\TOPTORIALS_CODE\020_Python_Grundkurs

            Der Hauptordner für diesen Teil lautet:

            C:\TOPTORIALS_CODE\020_Python_Grundkurs\009_Backup_mit_Datum

            Testumgebung zum Mitmachen

            Verwende für die ersten Durchläufe ausschließlich Kopien unwichtiger Dateien. Ein kleiner Test-Projektordner mit einigen Dateien, leeren Unterordnern sowie den Ordnern Temp und Cache eignet sich besonders gut. So kannst du Vorschau, Ausschlüsse, Ordnerstruktur und Protokoll gefahrlos prüfen.

            Testumgebung für Windows-Automatisierung:Download TOPTORIALS_Testdaten_Windows_Automatisierung_V001.zip

            Entpacken nach:

            C:\TOPTORIALS_CODE\1000_TESTBEREICH

            Sicherheitshinweis

            Wichtig:Prüfe vor dem Start den tatsächlichen Namen der Starter-Batchdatei. Nur wenn darin _test vorkommt, wird --test an Python übergeben.

            Im Fenster muss Testmodus: JA erscheinen. In diesem Modus werden die geplanten Ordner und Dateien nur angezeigt. Es entstehen weder ein Backups-Ordner noch kopierte Dateien oder eine Protokolldatei.

            Ohne _test beginnt nach der Vorschau der Echtmodus. Die Kopien werden erst nach der exakten Eingabe JA erstellt.

            Ein Backup im selben Projektordner und auf demselben Datenträger schützt nicht vor einem Defekt oder Verlust dieses Datenträgers. Wichtige Daten sollten zusätzlich auf einem anderen Datenträger oder an einem anderen sicheren Ort gesichert werden.

            Die Nutzung des Codes erfolgt auf eigene Verantwortung. Es wird keine Haftung für Datenverluste, unbeabsichtigte Änderungen oder andere Schäden übernommen.

            Voraussetzungen

            • Windows 10 oder Windows 11.
            • Python 3, erreichbar über den Python Launcher py.
            • Ausreichende Lese- und Schreibrechte in den Projektordnern.
            • Python-Skript und passende Starter-Batchdatei liegen jeweils gemeinsam in ihrem Kursordner.
            • Für SendTo wird eine Verknüpfung zur Starter-Batchdatei im Windows-Ordner Senden an angelegt.
            • Externe Python-Pakete werden nicht benötigt.

            Enthaltene Ordner und Dateien

            Die beiden Varianten liegen in diesen Kursordnern:

            C:\TOPTORIALS_CODE\020_Python_Grundkurs\009_Backup_mit_Datum\02_Projektordner_sichern_mit_Log
            
            C:\TOPTORIALS_CODE\020_Python_Grundkurs\009_Backup_mit_Datum\03_SendTo_Projektordner_sichern_mit_Log
            Ordner Python-Skript Starter im Testmodus
            02_Projektordner_sichern_mit_Log 009_backup_mit_datum_mit_log.py 009_backup_mit_datum_mit_log_starter_batch_test.bat
            03_SendTo_Projektordner_sichern_mit_Log 009_sendto_backup_mit_datum_mit_log.py 009_sendto_backup_mit_datum_mit_log_starter_batch_test.bat

            Für den Echtmodus wird nur _test aus dem Namen des jeweiligen Starters entfernt. Der Name der Python-Datei bleibt unverändert, weil die Batchdatei genau diesen Namen aufruft.

            Wo werden die Backups gespeichert?

            Normale Variante

            Die normale Variante verwendet den Ordner des Python-Skripts als Quelle:

            quelle = Path(__file__).resolve().parent

            __file__ enthält den Pfad der ausgeführten Python-Datei. resolve() bildet daraus einen absoluten, aufgelösten Pfad und parent liefert den übergeordneten Ordner. Der Ordner Backups entsteht deshalb direkt innerhalb dieses Projektordners.

            02_Projektordner_sichern_mit_Log
            ├── 009_backup_mit_datum_mit_log.py
            ├── 009_backup_mit_datum_mit_log_starter_batch.bat
            └── Backups
                └── Backup_2026-09-12_18-30-45

            SendTo-Variante

            Bei SendTo ist nicht der Skriptordner die Quelle. Der Windows-Explorer übergibt die ausgewählten Projektordner als Argumente. In jedem gültigen Quellordner entsteht ein eigener Ordner Backups.

            Ausgewählter Projektordner
            └── Backups
                └── Backup_2026-09-12_18-30-45

            Wichtig für SendTo:Python-Skript und Starter-Batchdatei bleiben gemeinsam im Kursordner. In Senden an gehört nur eine Verknüpfung zur Starter-Batchdatei. Eine Kopie der Batchdatei würde das daneben erwartete Python-Skript dort nicht finden.

            Zeitstempel, Backupordner und Protokolldatei

            Python erzeugt den Zeitstempel einmal pro Skriptlauf:

            zeitstempel = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
            Platzhalter Bedeutung Beispiel
            %Y Jahr mit vier Ziffern 2026
            %m Monat mit zwei Ziffern 09
            %d Tag mit zwei Ziffern 12
            %H Stunde im 24-Stunden-Format 18
            %M Minute 30
            %S Sekunde 45

            Daraus entstehen zum Beispiel:

            Backup_2026-09-12_18-30-45
            backup_protokoll_2026-09-12_18-30-45.log

            Alle in einem SendTo-Durchlauf ausgewählten Quellen erhalten denselben Zeitstempel. Da jedes Projekt seinen eigenen Backups-Ordner besitzt, entstehen dadurch keine Konflikte zwischen den Quellen.

            Welche Ordner und Dateien werden ausgeschlossen?

            Diese Ordnernamen sind vorbereitet:

            Temp
            Cache
            Alte Dateien

            Zusätzlich wird immer der jeweilige Hauptordner Backups ausgeschlossen. Sonst könnten bereits vorhandene Sicherungen beim nächsten Lauf erneut kopiert werden.

            Diese Dateien und Dateimuster werden ausgelassen:

            desktop.ini
            thumbs.db
            *.tmp
            *.log
            *.bak

            Die Einträge stehen am Anfang beider Python-Skripte in AUSGESCHLOSSENE_ORDNER und AUSGESCHLOSSENE_DATEIEN. Sie können dort angepasst oder erweitert werden.

            casefold() sorgt bei diesen Prüfungen dafür, dass Groß- und Kleinschreibung keine Rolle spielen. Deshalb passen beispielsweise BERICHT.LOG und bericht.log beide auf *.log.

            Wie entsteht der Backupplan?

            Die Funktion backup_planen() durchläuft die Quelle mit:

            os.walk(
                quelle,
                topdown=True,
                onerror=walk_fehler_ausloesen,
                followlinks=False,
            )
            Argument Aufgabe
            quelle Startordner für den Durchlauf.
            topdown=True Bearbeitet einen Ordner, bevor dessen Unterordner betreten werden.
            onerror=walk_fehler_ausloesen Gibt Lesefehler an den aufrufenden Code weiter.
            followlinks=False Folgt symbolischen Verzeichnislinks nicht.

            Besonders wichtig ist diese Zuweisung:

            unterordner[:] = erlaubte_unterordner

            os.walk() stellt in unterordner die als Nächstes zu besuchenden Ordner bereit. Durch die Slice-Zuweisung wird genau diese vorhandene Liste verändert. Entfernte Ordner werden anschließend nicht mehr betreten. So können Temp, Cache, Alte Dateien, Backups sowie Verzeichnisverknüpfungen vollständig vom Durchlauf ausgeschlossen werden.

            Das Ergebnis ist ein Dictionary mit vier Listen:

            {
                "ordner": ordner,
                "dateien": dateien,
                "ausgeschlossene_ordner": ausgeschlossene_ordner,
                "ausgeschlossene_dateien": ausgeschlossene_dateien,
            }

            Damit kann das Skript zuerst alles anzeigen und erst danach entscheiden, ob der Plan nur getestet oder wirklich ausgeführt wird.

            Symbolische Links und Windows-Junctions

            Die Funktion ist_verzeichnisverknuepfung() prüft mit is_symlink() symbolische Links. Falls die verwendete Python-Version is_junction() anbietet, werden unter Windows zusätzlich Junctions erkannt.

            Solche Verknüpfungen werden nicht verfolgt. Dadurch kann der Backupdurchlauf nicht unbeabsichtigt in andere Verzeichnisbereiche wechseln oder durch eine Verknüpfung immer wieder dieselben Ordner erreichen.

            Testmodus und Echtmodus

            Der Starter erkennt den Modus an seinem eigenen Dateinamen:

            • Enthält der Name _test, wird Python mit --test gestartet.
            • Fehlt _test, startet das Python-Skript im Echtmodus.

            Python wertet den Schalter mit sys.argv aus:

            testmodus = "--test" in sys.argv[1:]

            Im Testmodus wird der komplette Plan angezeigt, anschließend endet das Programm mit sys.exit(0). Das Dateisystem bleibt unverändert.

            Im Echtmodus erscheint die Sicherheitsabfrage:

            Backup jetzt erstellen? Bitte JA eingeben:

            Die SendTo-Version fragt entsprechend nach der gemeinsamen Freigabe für alle angezeigten Backups.

            Optional auch ein kleines ja akzeptieren

            Die getesteten Skripte verwenden:

            if antwort.strip() != "JA":

            strip() entfernt Leerzeichen vor und hinter der Eingabe. Der normale Zeichenkettenvergleich unterscheidet in Python Groß- und Kleinschreibung. Deshalb wird ausschließlich JA akzeptiert.

            Sollen auch ja, Ja und jA gelten, kann die Zeile in beiden Python-Skripten so geändert werden:

            if antwort.strip().casefold() != "ja":

            Der Unterschied:casefold() erzeugt für den Vergleich eine normalisierte Kleinschreibweise. Der ursprüngliche Eingabetext wird dabei nicht verändert.

            Wie werden Ordner und Dateien kopiert?

            Der Zielordner wird erst nach der Bestätigung angelegt:

            zielordner.mkdir(parents=True)

            parents=True erlaubt Python, bei Bedarf auch den übergeordneten Ordner Backups zu erstellen. Ein bereits vorhandener Zielordner wird nicht wiederverwendet; das Skript meldet in diesem Fall einen Fehler.

            Die vorbereiteten Unterordner entstehen mit mkdir(). Anschließend wird jede Quelldatei kopiert:

            shutil.copy2(quelldatei, zieldatei)

            copy2() kopiert den Dateiinhalt und versucht zusätzlich, Metadaten wie Änderungs- und Zugriffszeit zu übernehmen. Betriebssystemabhängig können jedoch nicht sämtliche Metadaten erhalten bleiben.

            Auch leere Unterordner werden angelegt, weil Ordner und Dateien getrennt im Plan gespeichert sind.

            Wie entsteht die Protokolldatei?

            Die Logdatei wird im neuen Backupordner als UTF-8-Datei geöffnet:

            protokoll = logdatei.open("w", encoding="utf-8")

            Der Modus "w" steht für Schreiben. Da der Zielordner für jeden Durchlauf neu ist, entsteht dort auch eine neue Protokolldatei.

            Mit:

            with protokoll:

            wird ein Kontextmanager verwendet. Beim Verlassen dieses Blocks schließt Python die Datei automatisch, auch wenn innerhalb des Blocks ein Fehler auftreten sollte.

            Die Hilfsfunktion zeile_ausgeben_und_protokollieren() gibt jede Meldung im Konsolenfenster aus und schreibt dieselbe Zeile in das Protokoll. flush() übergibt die gepufferten Daten unmittelbar an das Betriebssystem.

            Am Ende enthält das Protokoll unter anderem:

            • Zeitstempel, Quelle und Ziel,
            • die eingestellten Ausschlüsse,
            • angelegte Ordner und kopierte Dateien,
            • aufgetretene Fehler,
            • eine Zusammenfassung mit Zählerständen.

            Variante 1: Projektordner mit Logdatei sichern

            So wird die Variante angewendet

            Lege das Python-Skript und die Starter-Batchdatei aus 02_Projektordner_sichern_mit_Log gemeinsam in den Projektordner, der gesichert werden soll.

            Starte zuerst:

            009_backup_mit_datum_mit_log_starter_batch_test.bat

            Prüfe die Anzeige Testmodus: JA, den Zielpfad, alle aufgelisteten Dateien und die Zähler. Im Testmodus darf noch kein Ordner Backups entstehen.

            Für den Echtlauf entfernst du _test aus dem Namen der Starter-Batchdatei. Starte sie erneut und bestätige erst nach Kontrolle der neuen Vorschau mit JA.

            Vollständiges Python-Skript

            Dateiname: 009_backup_mit_datum_mit_log.py

            # ============================================================
            # TOPTORIALS OFFICE - Python Grundkurs
            # Teil: 009 - Backup mit Datum und Protokolldatei
            # Script: 009_backup_mit_datum_mit_log.py
            # Beschreibung:
            # Sichert den Ordner dieses Python-Skripts in einem datierten
            # Unterordner und erstellt beim Echtlauf eine Protokolldatei.
            # Autor: Chris
            # YouTube: https://www.youtube.com/@TOPTORIALS-OFFICE
            # Webseite: https://www.toptorials.com
            # Version: Grundkurs 1.0
            # Stand: 2026-09-12
            #
            # Hinweise:
            # - Benötigt Python 3.
            # - Mit --test wird nur angezeigt, was gesichert würde.
            # - Im Testmodus werden keine Ordner oder Dateien angelegt.
            # - Ohne --test erfolgt die Sicherung nach Bestätigung mit JA.
            # - Der Ordner "Backups" wird nie in ein Backup aufgenommen.
            # - Festgelegte Ordner, Dateien und Dateimuster werden ausgeschlossen.
            # - Symbolische Links und Windows-Junctions werden nicht verfolgt.
            # - Zuerst mit Testdaten arbeiten und wichtige Daten zusätzlich sichern.
            # - Die Nutzung erfolgt auf eigene Verantwortung.
            # ============================================================
            
            from datetime import datetime
            from fnmatch import fnmatchcase
            import os
            from pathlib import Path
            import shutil
            import sys
            
            
            # ------------------------------------------------------------
            # Einstellungen
            # ------------------------------------------------------------
            
            BACKUP_HAUPTORDNER = "Backups"
            
            AUSGESCHLOSSENE_ORDNER = (
                "Temp",
                "Cache",
                "Alte Dateien",
            )
            
            AUSGESCHLOSSENE_DATEIEN = (
                "desktop.ini",
                "thumbs.db",
                "*.tmp",
                "*.log",
                "*.bak",
            )
            
            
            # ------------------------------------------------------------
            # Hilfsfunktionen
            # ------------------------------------------------------------
            
            def pfad_schluessel(pfad):
                """Erzeugt einen Vergleichswert für einen vollständigen Pfad."""
                return os.path.normcase(os.path.abspath(str(pfad))).casefold()
            
            
            def ist_verzeichnisverknuepfung(pfad):
                """Erkennt symbolische Links und, falls verfügbar, Junctions."""
                try:
                    if pfad.is_symlink():
                        return True
            
                    if hasattr(pfad, "is_junction") and pfad.is_junction():
                        return True
                except OSError:
                    return True
            
                return False
            
            
            def ist_datei_ausgeschlossen(dateiname):
                """Prüft einen Dateinamen gegen alle ausgeschlossenen Muster."""
                name = dateiname.casefold()
            
                return any(
                    fnmatchcase(name, muster.casefold())
                    for muster in AUSGESCHLOSSENE_DATEIEN
                )
            
            
            def walk_fehler_ausloesen(fehler):
                """Gibt einen Lesefehler von os.walk() an den Aufrufer weiter."""
                raise fehler
            
            
            def backup_planen(quelle, backup_pfad):
                """Ermittelt alle Ordner und Dateien, die gesichert werden sollen."""
                ordner = []
                dateien = []
                ausgeschlossene_ordner = []
                ausgeschlossene_dateien = []
                ausgeschlossene_ordnernamen = {
                    name.casefold() for name in AUSGESCHLOSSENE_ORDNER
                }
                backup_schluessel = pfad_schluessel(backup_pfad)
            
                for aktueller_text, unterordner, dateinamen in os.walk(
                    quelle,
                    topdown=True,
                    onerror=walk_fehler_ausloesen,
                    followlinks=False,
                ):
                    aktueller_ordner = Path(aktueller_text)
                    relativer_ordner = aktueller_ordner.relative_to(quelle)
            
                    if relativer_ordner != Path("."):
                        ordner.append(relativer_ordner)
            
                    erlaubte_unterordner = []
            
                    for ordnername in sorted(unterordner, key=str.casefold):
                        ordnerpfad = aktueller_ordner / ordnername
            
                        ausgeschlossen = (
                            ordnername.casefold() in ausgeschlossene_ordnernamen
                            or pfad_schluessel(ordnerpfad) == backup_schluessel
                            or ist_verzeichnisverknuepfung(ordnerpfad)
                        )
            
                        if ausgeschlossen:
                            ausgeschlossene_ordner.append(ordnerpfad)
                        else:
                            erlaubte_unterordner.append(ordnername)
            
                    unterordner[:] = erlaubte_unterordner
            
                    for dateiname in sorted(dateinamen, key=str.casefold):
                        dateipfad = aktueller_ordner / dateiname
            
                        if ist_datei_ausgeschlossen(dateiname) or dateipfad.is_symlink():
                            ausgeschlossene_dateien.append(dateipfad)
                        else:
                            dateien.append(dateipfad)
            
                return {
                    "ordner": ordner,
                    "dateien": dateien,
                    "ausgeschlossene_ordner": ausgeschlossene_ordner,
                    "ausgeschlossene_dateien": ausgeschlossene_dateien,
                }
            
            
            def zeile_ausgeben_und_protokollieren(protokoll, text=""):
                """Gibt eine Zeile aus und schreibt sie zusätzlich ins Protokoll."""
                print(text)
                protokoll.write(text + "\n")
                protokoll.flush()
            
            
            # ------------------------------------------------------------
            # Testmodus und Pfade vorbereiten
            # ------------------------------------------------------------
            
            testmodus = "--test" in sys.argv[1:]
            quelle = Path(__file__).resolve().parent
            zeitstempel = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
            backup_pfad = quelle / BACKUP_HAUPTORDNER
            zielordner = backup_pfad / f"Backup_{zeitstempel}"
            logdatei = zielordner / f"backup_protokoll_{zeitstempel}.log"
            
            
            # ------------------------------------------------------------
            # Backup planen und Vorschau anzeigen
            # ------------------------------------------------------------
            
            try:
                plan = backup_planen(quelle, backup_pfad)
            except OSError as fehler:
                print(f"FEHLER beim Lesen des Quellordners: {fehler}")
                sys.exit(1)
            
            print()
            print("Backup mit Datum und Protokolldatei")
            print("------------------------------------")
            print(f"Testmodus:              {'JA' if testmodus else 'NEIN'}")
            print(f"Quelle:                 {quelle}")
            print(f"Backup-Hauptordner:     {backup_pfad}")
            print(f"Neuer Backupordner:     {zielordner}")
            print(f"Protokolldatei:         {logdatei}")
            print(f"Ausgeschlossene Ordner: {', '.join(AUSGESCHLOSSENE_ORDNER)}")
            print(f"Ausgeschlossene Dateien: {', '.join(AUSGESCHLOSSENE_DATEIEN)}")
            print()
            
            for relativer_ordner in plan["ordner"]:
                print(f"ORDNER: {relativer_ordner}")
            
            for datei in plan["dateien"]:
                print(f"DATEI:  {datei.relative_to(quelle)}")
            
            print()
            print(f"Zu sichernde Ordner:       {len(plan['ordner'])}")
            print(f"Zu sichernde Dateien:      {len(plan['dateien'])}")
            print(f"Ausgeschlossene Ordner:    {len(plan['ausgeschlossene_ordner'])}")
            print(f"Ausgeschlossene Dateien:   {len(plan['ausgeschlossene_dateien'])}")
            
            
            # ------------------------------------------------------------
            # Testmodus beenden oder Echtlauf bestätigen
            # ------------------------------------------------------------
            
            if testmodus:
                print()
                print("TESTMODUS: Es wurden keine Ordner oder Dateien angelegt.")
                print("Für den Echtmodus _test aus dem Namen der Starterdatei entfernen.")
                sys.exit(0)
            
            print()
            antwort = input("Backup jetzt erstellen? Bitte JA eingeben: ")
            
            if antwort.strip() != "JA":
                print("Abgebrochen. Es wurden keine Dateien kopiert.")
                sys.exit(0)
            
            
            # ------------------------------------------------------------
            # Backupordner anlegen und Dateien kopieren
            # ------------------------------------------------------------
            
            if zielordner.exists():
                print(f"FEHLER: Der Zielordner existiert bereits: {zielordner}")
                sys.exit(1)
            
            try:
                zielordner.mkdir(parents=True)
                protokoll = logdatei.open("w", encoding="utf-8")
            except OSError as fehler:
                print(f"FEHLER: Der Backupordner konnte nicht angelegt werden: {fehler}")
                sys.exit(1)
            
            anzahl_kopiert = 0
            anzahl_fehler = 0
            
            with protokoll:
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    "TOPTORIALS OFFICE - Backup-Protokoll",
                )
                zeile_ausgeben_und_protokollieren(protokoll, f"Zeitstempel: {zeitstempel}")
                zeile_ausgeben_und_protokollieren(protokoll, f"Quelle:      {quelle}")
                zeile_ausgeben_und_protokollieren(protokoll, f"Ziel:        {zielordner}")
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    "Ausgeschlossene Ordner: "
                    + ", ".join((BACKUP_HAUPTORDNER,) + AUSGESCHLOSSENE_ORDNER),
                )
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    "Ausgeschlossene Dateien: " + ", ".join(AUSGESCHLOSSENE_DATEIEN),
                )
                zeile_ausgeben_und_protokollieren(protokoll)
            
                for relativer_ordner in plan["ordner"]:
                    try:
                        (zielordner / relativer_ordner).mkdir(parents=True, exist_ok=True)
                        zeile_ausgeben_und_protokollieren(
                            protokoll,
                            f"ORDNER ANGELEGT: {relativer_ordner}",
                        )
                    except OSError as fehler:
                        anzahl_fehler += 1
                        zeile_ausgeben_und_protokollieren(
                            protokoll,
                            f"FEHLER BEIM ORDNER: {relativer_ordner} - {fehler}",
                        )
            
                for quelldatei in plan["dateien"]:
                    relativer_pfad = quelldatei.relative_to(quelle)
                    zieldatei = zielordner / relativer_pfad
            
                    try:
                        zieldatei.parent.mkdir(parents=True, exist_ok=True)
                        shutil.copy2(quelldatei, zieldatei)
                        anzahl_kopiert += 1
                        zeile_ausgeben_und_protokollieren(
                            protokoll,
                            f"KOPIERT: {relativer_pfad}",
                        )
                    except OSError as fehler:
                        anzahl_fehler += 1
                        zeile_ausgeben_und_protokollieren(
                            protokoll,
                            f"FEHLER BEIM KOPIEREN: {relativer_pfad} - {fehler}",
                        )
            
                zeile_ausgeben_und_protokollieren(protokoll)
                zeile_ausgeben_und_protokollieren(protokoll, "Zusammenfassung")
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    f"Kopierte Dateien:        {anzahl_kopiert}",
                )
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    f"Ausgeschlossene Ordner:  {len(plan['ausgeschlossene_ordner'])}",
                )
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    f"Ausgeschlossene Dateien: {len(plan['ausgeschlossene_dateien'])}",
                )
                zeile_ausgeben_und_protokollieren(
                    protokoll,
                    f"Fehler:                  {anzahl_fehler}",
                )
            
            print()
            
            if anzahl_fehler > 0:
                print("Das Backup wurde mit Fehlern abgeschlossen.")
                print(f"Bitte das Protokoll kontrollieren: {logdatei}")
                sys.exit(1)
            
            print("Backup erfolgreich abgeschlossen.")
            print(f"Backupordner: {zielordner}")
            print(f"Protokoll:    {logdatei}")

            So funktioniert die normale Variante

            Nach den Einstellungen folgen die Hilfsfunktionen. Danach werden Testmodus, Quelle, Zeitstempel, Backupordner und Protokollpfad vorbereitet. backup_planen() sammelt alle erlaubten Ordner und Dateien, ohne bereits etwas zu kopieren.

            Die Vorschau zeigt jedes geplante Element und die vier Zähler. Der Testmodus endet danach sofort. Im Echtmodus prüft Python zuerst die Eingabe und danach, ob der Zielordner bereits existiert.

            Beim Kopieren werden Fehler pro Ordner oder Datei aufgefangen und protokolliert. Dadurch kann der Lauf die übrigen Elemente weiter bearbeiten. Sind Fehler aufgetreten, endet das Skript mit dem Rückgabecode 1; bei einem vollständig erfolgreichen Lauf bleibt es bei 0.

            Starter-Batchdatei für Variante 1

            Dateiname im Testmodus: 009_backup_mit_datum_mit_log_starter_batch_test.bat

            @echo off
            setlocal DisableDelayedExpansion
            
            REM ============================================================
            REM TOPTORIALS OFFICE - Python Grundkurs
            REM Teil: 009 - Backup mit Datum und Protokolldatei
            REM Script: 009_backup_mit_datum_mit_log_starter_batch_test.bat
            REM Beschreibung:
            REM Startet das Python-Skript, das den Ordner dieser Dateien in
            REM einem datierten Backupordner mit Protokolldatei sichert.
            REM Autor: Chris
            REM YouTube: https://www.youtube.com/@TOPTORIALS-OFFICE
            REM Webseite: https://www.toptorials.com
            REM Version: Grundkurs 1.0
            REM Stand: 2026-09-12
            REM
            REM Hinweise:
            REM - Benoetigt Python 3, erreichbar ueber den Python Launcher py.
            REM - Starter und Python-Datei muessen im gleichen Ordner liegen.
            REM - Solange _test im Starternamen steht, wird --test uebergeben.
            REM - Im Testmodus werden keine Ordner oder Dateien angelegt.
            REM - Fuer den Echtmodus _test aus dem Starternamen entfernen.
            REM - Das Python-Skript verlangt dann die Bestaetigung mit JA.
            REM - pause haelt das Fenster nach der Ausfuehrung offen.
            REM - Zuerst mit Testdaten arbeiten und wichtige Daten sichern.
            REM - Die Nutzung erfolgt auf eigene Verantwortung.
            REM ============================================================
            
            chcp 65001 >nul
            
            REM In den Ordner wechseln, in dem Starter und Python-Skript liegen.
            cd /d "%~dp0"
            if errorlevel 1 (
                echo FEHLER: Der Skriptordner konnte nicht geoeffnet werden.
                pause
                exit /b 1
            )
            
            REM Pruefen, ob der eigene Dateiname den Text "_test" enthaelt.
            echo("%~n0" | find /i "_test" >nul
            
            if not errorlevel 1 (
                py "009_backup_mit_datum_mit_log.py" --test
                REM Alternative, falls py nicht funktioniert:
                REM python "009_backup_mit_datum_mit_log.py" --test
            ) else (
                py "009_backup_mit_datum_mit_log.py"
                REM Alternative, falls py nicht funktioniert:
                REM python "009_backup_mit_datum_mit_log.py"
            )
            
            pause

            So funktioniert der Starter

            setlocal DisableDelayedExpansion begrenzt Änderungen an der Batch-Umgebung auf den aktuellen Starter und deaktiviert die verzögerte Variablenerweiterung. Dadurch werden Ausrufezeichen in übergebenen Texten nicht als verzögerte Variablen interpretiert.

            chcp 65001 schaltet die Konsole auf UTF-8. cd /d "%~dp0" wechselt in den Ordner der Batchdatei. %~n0 liefert ihren Namen ohne Dateiendung. find /i "_test" sucht darin ohne Beachtung der Groß- und Kleinschreibung nach dem Testzusatz.

            Wurde _test gefunden, startet der Launcher das Python-Skript mit --test. Andernfalls fehlt dieser Schalter und Python arbeitet nach der Sicherheitsabfrage im Echtmodus. pause hält das Fenster am Ende offen.

            Variante 2: Projektordner über „Senden an“ sichern

            SendTo-Verknüpfung einrichten

            1. Drücke Windows-Taste + R.
            2. Gib shell:sendto ein und bestätige.
            3. Erstelle dort eine Verknüpfung zur SendTo-Starter-Batchdatei.
            4. Gib der Verknüpfung einen verständlichen Namen, beispielsweise Python 009 – Backup testen.

            Für den Echtmodus entfernst du _test aus dem tatsächlichen Namen der Batchdatei. Aktualisiere die vorhandene Verknüpfung oder lege eine zweite Verknüpfung zur Echtmodus-Datei an.

            So wird die SendTo-Variante angewendet

            Markiere im Windows-Explorer einen oder mehrere Projektordner. Öffne das Kontextmenü und wähle über Senden an die eingerichtete Verknüpfung. Dateien und ungültige Pfade werden mit einer Meldung übersprungen.

            Jeder gültige Projektordner erhält einen eigenen Backupplan und später einen eigenen datierten Backupordner mit Protokolldatei.

            Vollständiges Python-Skript

            Dateiname: 009_sendto_backup_mit_datum_mit_log.py

            # ============================================================
            # TOPTORIALS OFFICE - Python Grundkurs
            # Teil: 009 - Backup mit Datum und Protokolldatei, SendTo
            # Script: 009_sendto_backup_mit_datum_mit_log.py
            # Beschreibung:
            # Sichert einen oder mehrere übergebene Ordner jeweils in einem
            # datierten Backupordner und erstellt eigene Protokolldateien.
            # Autor: Chris
            # YouTube: https://www.youtube.com/@TOPTORIALS-OFFICE
            # Webseite: https://www.toptorials.com
            # Version: Grundkurs 1.0
            # Stand: 2026-09-12
            #
            # Hinweise:
            # - Benötigt Python 3.
            # - Die Ordner werden über "Senden an" aus dem Explorer übergeben.
            # - Mit --test wird nur angezeigt, was gesichert würde.
            # - Im Testmodus werden keine Ordner oder Dateien angelegt.
            # - Ohne --test erfolgt die Sicherung nach Bestätigung mit JA.
            # - Der Ordner "Backups" wird nie in ein Backup aufgenommen.
            # - Mehrfach übergebene Quellordner werden nur einmal verarbeitet.
            # - Dateien und ungültige Pfade werden übersprungen.
            # - Symbolische Links und Windows-Junctions werden nicht verfolgt.
            # - Zuerst mit Testdaten arbeiten und wichtige Daten zusätzlich sichern.
            # - Die Nutzung erfolgt auf eigene Verantwortung.
            # ============================================================
            
            from datetime import datetime
            from fnmatch import fnmatchcase
            import os
            from pathlib import Path
            import shutil
            import sys
            
            
            # ------------------------------------------------------------
            # Einstellungen
            # ------------------------------------------------------------
            
            BACKUP_HAUPTORDNER = "Backups"
            
            AUSGESCHLOSSENE_ORDNER = (
                "Temp",
                "Cache",
                "Alte Dateien",
            )
            
            AUSGESCHLOSSENE_DATEIEN = (
                "desktop.ini",
                "thumbs.db",
                "*.tmp",
                "*.log",
                "*.bak",
            )
            
            
            # ------------------------------------------------------------
            # Hilfsfunktionen
            # ------------------------------------------------------------
            
            def pfad_schluessel(pfad):
                """Erzeugt einen Vergleichswert für einen vollständigen Pfad."""
                return os.path.normcase(os.path.abspath(str(pfad))).casefold()
            
            
            def ist_verzeichnisverknuepfung(pfad):
                """Erkennt symbolische Links und, falls verfügbar, Junctions."""
                try:
                    if pfad.is_symlink():
                        return True
            
                    if hasattr(pfad, "is_junction") and pfad.is_junction():
                        return True
                except OSError:
                    return True
            
                return False
            
            
            def ist_datei_ausgeschlossen(dateiname):
                """Prüft einen Dateinamen gegen alle ausgeschlossenen Muster."""
                name = dateiname.casefold()
            
                return any(
                    fnmatchcase(name, muster.casefold())
                    for muster in AUSGESCHLOSSENE_DATEIEN
                )
            
            
            def walk_fehler_ausloesen(fehler):
                """Gibt einen Lesefehler von os.walk() an den Aufrufer weiter."""
                raise fehler
            
            
            def backup_planen(quelle, backup_pfad):
                """Ermittelt alle Ordner und Dateien, die gesichert werden sollen."""
                ordner = []
                dateien = []
                ausgeschlossene_ordner = []
                ausgeschlossene_dateien = []
                ausgeschlossene_ordnernamen = {
                    name.casefold() for name in AUSGESCHLOSSENE_ORDNER
                }
                backup_schluessel = pfad_schluessel(backup_pfad)
            
                for aktueller_text, unterordner, dateinamen in os.walk(
                    quelle,
                    topdown=True,
                    onerror=walk_fehler_ausloesen,
                    followlinks=False,
                ):
                    aktueller_ordner = Path(aktueller_text)
                    relativer_ordner = aktueller_ordner.relative_to(quelle)
            
                    if relativer_ordner != Path("."):
                        ordner.append(relativer_ordner)
            
                    erlaubte_unterordner = []
            
                    for ordnername in sorted(unterordner, key=str.casefold):
                        ordnerpfad = aktueller_ordner / ordnername
            
                        ausgeschlossen = (
                            ordnername.casefold() in ausgeschlossene_ordnernamen
                            or pfad_schluessel(ordnerpfad) == backup_schluessel
                            or ist_verzeichnisverknuepfung(ordnerpfad)
                        )
            
                        if ausgeschlossen:
                            ausgeschlossene_ordner.append(ordnerpfad)
                        else:
                            erlaubte_unterordner.append(ordnername)
            
                    unterordner[:] = erlaubte_unterordner
            
                    for dateiname in sorted(dateinamen, key=str.casefold):
                        dateipfad = aktueller_ordner / dateiname
            
                        if ist_datei_ausgeschlossen(dateiname) or dateipfad.is_symlink():
                            ausgeschlossene_dateien.append(dateipfad)
                        else:
                            dateien.append(dateipfad)
            
                return {
                    "ordner": ordner,
                    "dateien": dateien,
                    "ausgeschlossene_ordner": ausgeschlossene_ordner,
                    "ausgeschlossene_dateien": ausgeschlossene_dateien,
                }
            
            
            def zeile_ausgeben_und_protokollieren(protokoll, text=""):
                """Gibt eine Zeile aus und schreibt sie zusätzlich ins Protokoll."""
                print(text)
                protokoll.write(text + "\n")
                protokoll.flush()
            
            
            def backup_ausfuehren(plan):
                """Führt einen vorbereiteten Backupplan aus."""
                quelle = plan["quelle"]
                zielordner = plan["ziel"]
                logdatei = plan["protokoll"]
                inhalt = plan["inhalt"]
            
                if zielordner.exists():
                    print(f"FEHLER: Der Zielordner existiert bereits: {zielordner}")
                    return False
            
                try:
                    zielordner.mkdir(parents=True)
                    protokoll = logdatei.open("w", encoding="utf-8")
                except OSError as fehler:
                    print(f"FEHLER: Der Backupordner konnte nicht angelegt werden: {fehler}")
                    return False
            
                anzahl_kopiert = 0
                anzahl_fehler = 0
            
                with protokoll:
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        "TOPTORIALS OFFICE - Backup-Protokoll",
                    )
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        f"Zeitstempel: {plan['zeitstempel']}",
                    )
                    zeile_ausgeben_und_protokollieren(protokoll, f"Quelle:      {quelle}")
                    zeile_ausgeben_und_protokollieren(protokoll, f"Ziel:        {zielordner}")
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        "Ausgeschlossene Ordner: "
                        + ", ".join((BACKUP_HAUPTORDNER,) + AUSGESCHLOSSENE_ORDNER),
                    )
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        "Ausgeschlossene Dateien: " + ", ".join(AUSGESCHLOSSENE_DATEIEN),
                    )
                    zeile_ausgeben_und_protokollieren(protokoll)
            
                    for relativer_ordner in inhalt["ordner"]:
                        try:
                            (zielordner / relativer_ordner).mkdir(
                                parents=True,
                                exist_ok=True,
                            )
                            zeile_ausgeben_und_protokollieren(
                                protokoll,
                                f"ORDNER ANGELEGT: {relativer_ordner}",
                            )
                        except OSError as fehler:
                            anzahl_fehler += 1
                            zeile_ausgeben_und_protokollieren(
                                protokoll,
                                f"FEHLER BEIM ORDNER: {relativer_ordner} - {fehler}",
                            )
            
                    for quelldatei in inhalt["dateien"]:
                        relativer_pfad = quelldatei.relative_to(quelle)
                        zieldatei = zielordner / relativer_pfad
            
                        try:
                            zieldatei.parent.mkdir(parents=True, exist_ok=True)
                            shutil.copy2(quelldatei, zieldatei)
                            anzahl_kopiert += 1
                            zeile_ausgeben_und_protokollieren(
                                protokoll,
                                f"KOPIERT: {relativer_pfad}",
                            )
                        except OSError as fehler:
                            anzahl_fehler += 1
                            zeile_ausgeben_und_protokollieren(
                                protokoll,
                                f"FEHLER BEIM KOPIEREN: {relativer_pfad} - {fehler}",
                            )
            
                    zeile_ausgeben_und_protokollieren(protokoll)
                    zeile_ausgeben_und_protokollieren(protokoll, "Zusammenfassung")
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        f"Kopierte Dateien:        {anzahl_kopiert}",
                    )
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        f"Ausgeschlossene Ordner:  {len(inhalt['ausgeschlossene_ordner'])}",
                    )
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        f"Ausgeschlossene Dateien: {len(inhalt['ausgeschlossene_dateien'])}",
                    )
                    zeile_ausgeben_und_protokollieren(
                        protokoll,
                        f"Fehler:                  {anzahl_fehler}",
                    )
            
                if anzahl_fehler > 0:
                    print(f"Backup mit Fehlern abgeschlossen: {quelle}")
                    print(f"Bitte das Protokoll kontrollieren: {logdatei}")
                    return False
            
                print(f"Backup erfolgreich abgeschlossen: {quelle}")
                print(f"Backupordner: {zielordner}")
                print(f"Protokoll:    {logdatei}")
                return True
            
            
            # ------------------------------------------------------------
            # Argumente und Testmodus auswerten
            # ------------------------------------------------------------
            
            argumente = sys.argv[1:]
            testmodus = "--test" in argumente
            pfad_argumente = [argument for argument in argumente if argument != "--test"]
            
            if not pfad_argumente:
                print("FEHLER: Es wurden keine Ordner übergeben.")
                print("Bitte einen oder mehrere Ordner über 'Senden an' starten.")
                sys.exit(1)
            
            
            # ------------------------------------------------------------
            # Gültige Quellordner sammeln
            # ------------------------------------------------------------
            
            quellordner = []
            bereits_erfasste_ordner = set()
            
            for pfad_text in pfad_argumente:
                try:
                    element = Path(pfad_text)
            
                    if not element.exists():
                        print(f"ÜBERSPRUNGEN: Pfad nicht gefunden: {pfad_text}")
                        continue
            
                    if ist_verzeichnisverknuepfung(element):
                        print(f"ÜBERSPRUNGEN: Verzeichnisverknüpfung: {element}")
                        continue
            
                    element = element.resolve()
            
                    if not element.is_dir():
                        print(f"ÜBERSPRUNGEN: Kein Ordner: {element}")
                        continue
            
                    schluessel = pfad_schluessel(element)
            
                    if schluessel not in bereits_erfasste_ordner:
                        bereits_erfasste_ordner.add(schluessel)
                        quellordner.append(element)
                except OSError as fehler:
                    print(f"ÜBERSPRUNGEN: {pfad_text} - {fehler}")
            
            if not quellordner:
                print("FEHLER: Es wurde kein gültiger Quellordner gefunden.")
                sys.exit(1)
            
            
            # ------------------------------------------------------------
            # Für jeden Quellordner einen Backupplan erstellen
            # ------------------------------------------------------------
            
            zeitstempel = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
            plaene = []
            anzahl_planungsfehler = 0
            
            for quelle in quellordner:
                backup_pfad = quelle / BACKUP_HAUPTORDNER
                zielordner = backup_pfad / f"Backup_{zeitstempel}"
                logdatei = zielordner / f"backup_protokoll_{zeitstempel}.log"
            
                try:
                    inhalt = backup_planen(quelle, backup_pfad)
                    plaene.append(
                        {
                            "quelle": quelle,
                            "backup_pfad": backup_pfad,
                            "ziel": zielordner,
                            "protokoll": logdatei,
                            "zeitstempel": zeitstempel,
                            "inhalt": inhalt,
                        }
                    )
                except OSError as fehler:
                    print(f"FEHLER beim Lesen von '{quelle}': {fehler}")
                    anzahl_planungsfehler += 1
            
            if not plaene:
                print("FEHLER: Es konnte kein Backupplan erstellt werden.")
                sys.exit(1)
            
            
            # ------------------------------------------------------------
            # Alle Backuppläne anzeigen
            # ------------------------------------------------------------
            
            print()
            print("SendTo-Backup mit Datum und Protokolldatei")
            print("------------------------------------------")
            print(f"Testmodus:               {'JA' if testmodus else 'NEIN'}")
            print(f"Anzahl der Quellordner:  {len(plaene)}")
            print(f"Ausgeschlossene Ordner:  {', '.join(AUSGESCHLOSSENE_ORDNER)}")
            print(f"Ausgeschlossene Dateien: {', '.join(AUSGESCHLOSSENE_DATEIEN)}")
            print()
            
            for nummer, plan in enumerate(plaene, start=1):
                inhalt = plan["inhalt"]
                print(f"Backup {nummer} von {len(plaene)}")
                print(f"Quelle:    {plan['quelle']}")
                print(f"Ziel:      {plan['ziel']}")
                print(f"Protokoll: {plan['protokoll']}")
                print(f"Ordner:    {len(inhalt['ordner'])}")
                print(f"Dateien:   {len(inhalt['dateien'])}")
            
                for relativer_ordner in inhalt["ordner"]:
                    print(f"  ORDNER: {relativer_ordner}")
            
                for datei in inhalt["dateien"]:
                    print(f"  DATEI:  {datei.relative_to(plan['quelle'])}")
            
                print()
            
            
            # ------------------------------------------------------------
            # Testmodus beenden oder Echtlauf bestätigen
            # ------------------------------------------------------------
            
            if testmodus:
                print("TESTMODUS: Es wurden keine Ordner oder Dateien angelegt.")
                print("Für den Echtmodus _test aus dem Namen der Starterdatei entfernen.")
                sys.exit(1 if anzahl_planungsfehler > 0 else 0)
            
            antwort = input("Alle angezeigten Backups jetzt erstellen? Bitte JA eingeben: ")
            
            if antwort.strip() != "JA":
                print("Abgebrochen. Es wurden keine Dateien kopiert.")
                sys.exit(0)
            
            
            # ------------------------------------------------------------
            # Backuppläne ausführen und Ergebnis zusammenfassen
            # ------------------------------------------------------------
            
            erfolgreich = 0
            fehlgeschlagen = anzahl_planungsfehler
            
            for plan in plaene:
                print()
                print(f"Bearbeite: {plan['quelle']}")
            
                if backup_ausfuehren(plan):
                    erfolgreich += 1
                else:
                    fehlgeschlagen += 1
            
            print()
            print("Zusammenfassung")
            print("---------------")
            print(f"Erfolgreich:    {erfolgreich}")
            print(f"Fehlgeschlagen: {fehlgeschlagen}")
            
            if fehlgeschlagen > 0:
                sys.exit(1)
            
            sys.exit(0)

            So funktioniert die SendTo-Variante

            sys.argv[1:] enthält alle Argumente nach dem Namen des Python-Skripts. Der Testschalter wird zunächst erkannt und anschließend aus den eigentlichen Pfadargumenten entfernt.

            argumente = sys.argv[1:]
            testmodus = "--test" in argumente
            pfad_argumente = [argument for argument in argumente if argument != "--test"]

            Die Pfade werden auf Existenz, Verzeichnisverknüpfung und Ordnertyp geprüft. Das Set bereits_erfasste_ordner merkt sich einen normalisierten Vergleichsschlüssel. Derselbe Projektordner wird dadurch auch dann nur einmal verarbeitet, wenn er mehrfach übergeben wurde.

            Für jede gültige Quelle entsteht ein Dictionary mit Quelle, Backup-Hauptordner, Ziel, Protokollpfad, Zeitstempel und Inhalt. Die Liste plaene sammelt diese Dictionaries.

            enumerate(plaene, start=1) liefert beim Anzeigen gleichzeitig jeden Plan und eine laufende Nummer ab 1. Erst nachdem alle Pläne sichtbar sind, folgt im Echtmodus eine einzige Sicherheitsabfrage für den gesamten Durchlauf.

            backup_ausfuehren() übernimmt anschließend jeden Plan. Die Zähler erfolgreich und fehlgeschlagen ergeben am Ende eine gemeinsame Zusammenfassung.

            Starter-Batchdatei für Variante 2

            Dateiname im Testmodus: 009_sendto_backup_mit_datum_mit_log_starter_batch_test.bat

            @echo off
            setlocal DisableDelayedExpansion
            
            REM ============================================================
            REM TOPTORIALS OFFICE - Python Grundkurs
            REM Teil: 009 - Backup mit Datum und Protokolldatei, SendTo
            REM Script: 009_sendto_backup_mit_datum_mit_log_starter_batch_test.bat
            REM Beschreibung:
            REM Startet die SendTo-Variante und reicht alle im Explorer
            REM ausgewaehlten Ordner an das zugehoerige Python-Skript weiter.
            REM Autor: Chris
            REM YouTube: https://www.youtube.com/@TOPTORIALS-OFFICE
            REM Webseite: https://www.toptorials.com
            REM Version: Grundkurs 1.0
            REM Stand: 2026-09-12
            REM
            REM Hinweise:
            REM - Benoetigt Python 3, erreichbar ueber den Python Launcher py.
            REM - Starter und Python-Datei muessen im gleichen Ordner liegen.
            REM - Eine Verknuepfung zu diesem Starter in "Senden an" ablegen.
            REM - Einen oder mehrere Ordner markieren und dorthin senden.
            REM - Solange _test im Starternamen steht, wird --test uebergeben.
            REM - Der Anzeigename der Verknuepfung steuert den Testmodus nicht.
            REM - Fuer den Echtmodus _test aus dem Starternamen entfernen.
            REM - Die SendTo-Verknuepfung danach gegebenenfalls aktualisieren.
            REM - Das Python-Skript verlangt dann die Bestaetigung mit JA.
            REM - pause haelt das Fenster nach der Ausfuehrung offen.
            REM - Zuerst mit Testdaten arbeiten und wichtige Daten sichern.
            REM - Die Nutzung erfolgt auf eigene Verantwortung.
            REM ============================================================
            
            chcp 65001 >nul
            
            REM In den Ordner wechseln, in dem Starter und Python-Skript liegen.
            cd /d "%~dp0"
            if errorlevel 1 (
                echo FEHLER: Der Skriptordner konnte nicht geoeffnet werden.
                pause
                exit /b 1
            )
            
            REM Pruefen, ob der eigene Dateiname den Text "_test" enthaelt.
            echo("%~n0" | find /i "_test" >nul
            
            if not errorlevel 1 (
                py "009_sendto_backup_mit_datum_mit_log.py" --test %*
                REM Alternative, falls py nicht funktioniert:
                REM python "009_sendto_backup_mit_datum_mit_log.py" --test %*
            ) else (
                py "009_sendto_backup_mit_datum_mit_log.py" %*
                REM Alternative, falls py nicht funktioniert:
                REM python "009_sendto_backup_mit_datum_mit_log.py" %*
            )
            
            pause

            Der entscheidende Unterschied im SendTo-Starter

            Der Python-Aufruf endet zusätzlich mit:

            %*

            %* steht in einer Batchdatei für sämtliche übergebenen Argumente. Der Windows-Explorer übergibt hier die markierten Pfade. Der Starter reicht sie unverändert an Python weiter. Dort erscheinen sie in sys.argv.

            Typische Meldungen

            Testmodus

            TESTMODUS: Es wurden keine Ordner oder Dateien angelegt.

            Erfolgreiches Backup

            Backup erfolgreich abgeschlossen.
            Backupordner: C:\Projekte\MeinProjekt\Backups\Backup_2026-09-12_18-30-45
            Protokoll:    C:\Projekte\MeinProjekt\Backups\Backup_2026-09-12_18-30-45\backup_protokoll_2026-09-12_18-30-45.log

            Datei wurde an SendTo übergeben

            ÜBERSPRUNGEN: Kein Ordner: C:\Projekte\Hinweis.txt

            Pfad wurde nicht gefunden

            ÜBERSPRUNGEN: Pfad nicht gefunden: C:\NichtVorhanden

            Backup mit Fehlern

            Das Backup wurde mit Fehlern abgeschlossen.
            Bitte das Protokoll kontrollieren: ...

            Typische Fehler und mögliche Ursachen

            Der Befehl py wurde nicht gefunden

            Prüfe mit py --version, ob der Python Launcher verfügbar ist. Falls Python nur über python gestartet werden kann, stehen die alternativen Aufrufe bereits als Kommentare in den Batchdateien.

            Das Python-Skript wird nicht gefunden

            Prüfe, ob Starter und Python-Datei im selben Ordner liegen und der aufgerufene Dateiname exakt stimmt. Zusätze wie (1) oder (2), die beim mehrfachen Herunterladen entstehen können, gehören nicht zu den vorgesehenen Namen.

            Der Starter zeigt Testmodus: NEIN

            Im tatsächlichen Namen der Starter-Batchdatei fehlt _test. Der Anzeigename einer SendTo-Verknüpfung aktiviert den Testmodus nicht.

            Bei SendTo entsteht das Backup im Skriptordner

            Prüfe, ob wirklich die SendTo-Version des Python-Skripts und der zugehörige SendTo-Starter verwendet werden. Die normale Version sichert absichtlich ihren eigenen Skriptordner.

            Im SendTo-Menü startet gar nichts oder Python wird nicht gefunden

            Lege im Ordner Senden an eine Verknüpfung zur Batchdatei ab. Kopiere die Batchdatei nicht allein dorthin, denn dann fehlt ihr das daneben erwartete Python-Skript.

            Bestimmte Dateien fehlen im Backup

            Prüfe AUSGESCHLOSSENE_DATEIEN. Die Muster *.tmp, *.log und *.bak lassen entsprechende Dateien unabhängig von ihrer Groß- und Kleinschreibung aus.

            Bestimmte Ordner fehlen im Backup

            Prüfe AUSGESCHLOSSENE_ORDNER. Zusätzlich werden der Backup-Hauptordner sowie symbolische Links und Windows-Junctions grundsätzlich nicht durchlaufen.

            Ein Zielordner mit demselben Zeitstempel existiert bereits

            Da der Zeitstempel Sekunden enthält, ist dies selten. Das Skript verwendet einen vorhandenen Zielordner nicht erneut und überschreibt ihn nicht, sondern bricht den betreffenden Backupplan mit einer Fehlermeldung ab.

            Einzelne Dateien lassen sich nicht kopieren

            Mögliche Ursachen sind fehlende Rechte, eine Dateisperre, ein zu langer Pfad oder ein Datenträgerfehler. Das Skript protokolliert den Fehler, bearbeitet die übrigen Elemente weiter und meldet den Lauf anschließend als fehlerhaft.

            Wichtige Python-Begriffe aus diesem Tutorial

            datetime.now()
            Liefert das aktuelle lokale Datum und die aktuelle Uhrzeit.
            strftime()
            Formatiert Datum und Uhrzeit nach einem vorgegebenen Muster als Text.
            Path(__file__).resolve().parent
            Liefert den aufgelösten Ordnerpfad des ausgeführten Python-Skripts.
            os.walk()
            Durchläuft einen Verzeichnisbaum und liefert den aktuellen Ordner, seine Unterordner und seine Dateien.
            topdown=True
            Ermöglicht es, Unterordner vor dem Betreten aus der Liste von os.walk() zu entfernen.
            fnmatchcase()
            Vergleicht einen Dateinamen mit einem Muster wie *.log.
            casefold()
            Erzeugt einen Vergleichswert, bei dem Groß- und Kleinschreibung keine Rolle spielen.
            set()
            Speichert eindeutige Vergleichsschlüssel und verhindert in SendTo eine doppelte Verarbeitung.
            Dictionary
            Speichert zusammengehörige Werte unter Namen wie quelle, ziel und protokoll.
            relative_to()
            Ermittelt den Pfad eines Elements relativ zum Quellordner.
            mkdir(parents=True)
            Legt einen Ordner und bei Bedarf auch noch fehlende übergeordnete Ordner an.
            shutil.copy2()
            Kopiert eine Datei und versucht, ihre Metadaten zu übernehmen.
            with
            Verwaltet hier die geöffnete Protokolldatei und schließt sie automatisch.
            try und except OSError
            Fangen Fehler bei Datei- und Ordnerzugriffen kontrolliert ab.
            sys.argv[1:]
            Enthält die Argumente, die nach dem Skriptnamen an Python übergeben wurden.
            enumerate(..., start=1)
            Liefert Elemente einer Schleife zusammen mit einer laufenden Nummer ab eins.
            sys.exit(0) und sys.exit(1)
            Beenden das Programm mit einem Erfolgs- beziehungsweise Fehlercode.

            Wichtige Batch-Begriffe aus diesem Tutorial

            setlocal DisableDelayedExpansion
            Begrenzt Umgebungsänderungen auf den Starter und verhindert die verzögerte Auswertung von Ausrufezeichen.
            %~dp0
            Liefert Laufwerk und Ordner der ausgeführten Starter-Batchdatei.
            cd /d
            Wechselt in diesen Ordner und bei Bedarf auch auf das passende Laufwerk.
            %~n0
            Liefert den Namen der Batchdatei ohne Ordner und Dateiendung.
            find /i
            Sucht ohne Beachtung der Groß- und Kleinschreibung nach _test.
            if not errorlevel 1
            Prüft hier, ob find den gesuchten Text gefunden hat.
            %*
            Reicht alle an den SendTo-Starter übergebenen Pfade an Python weiter.
            pause
            Hält das Konsolenfenster bis zum nächsten Tastendruck geöffnet.

            Was zeichnet diese Grundkurs-Version aus?

            • Sie benötigt nur Python 3 und keine zusätzlichen Pakete.
            • Sie plant den gesamten Lauf, bevor etwas kopiert wird.
            • Sie bietet einen echten Testmodus ohne Schreibzugriffe.
            • Sie verlangt im Echtmodus eine zusätzliche Bestätigung.
            • Sie übernimmt Dateien, Unterordner und leere Ordner.
            • Sie schließt vorbereitete Ordner, Dateimuster und den Backup-Hauptordner aus.
            • Sie verfolgt keine symbolischen Verzeichnislinks oder Junctions.
            • Sie schreibt für jedes Backup eine eigene UTF-8-Protokolldatei.
            • Sie verarbeitet über SendTo mehrere Projektordner und dedupliziert sie.

            Was ist bewusst nicht enthalten?

            Die Skripte erstellen vollständige, unkomprimierte Kopien. Sie komprimieren oder verschlüsseln keine Dateien, löschen keine alten Sicherungen und vergleichen keine Prüfsummen. Die Protokolldatei dokumentiert den Ablauf, ersetzt aber keine zusätzliche Integritätsprüfung.

            Eine automatische Aufbewahrungsstrategie und ein separates Backupziel auf einem anderen Datenträger können in späteren, weiterführenden Lösungen ergänzt werden.

            Was haben wir in diesem Teil gelernt?

            Mit datetime.now().strftime() erzeugen wir einen einheitlichen Zeitstempel für Backupordner und Protokolldatei. Path setzt die benötigten Pfade übersichtlich zusammen.

            os.walk() durchläuft den gesamten Projektordner. Durch topdown=True und die Änderung von unterordner[:] werden ausgeschlossene Verzeichnisse nicht betreten. fnmatchcase() und casefold() prüfen Dateimuster ohne Beachtung der Groß- und Kleinschreibung.

            Der Backupplan speichert erlaubte Ordner und Dateien zunächst in Listen. Im Testmodus wird dieser Plan nur angezeigt. Im Echtmodus legt mkdir() die Zielstruktur an und shutil.copy2() kopiert die Dateien nach der Bestätigung mit JA.

            Die Protokolldatei hält Quelle, Ziel, Ausschlüsse, Kopiervorgänge und Fehler fest. Die normale Variante sichert den Ordner des Python-Skripts. Die SendTo-Version übernimmt dagegen einen oder mehrere Projektordner aus dem Explorer und erstellt in jeder Quelle eine eigene datierte Sicherung.

             

            Das könnte Dich auch interessieren

            Keine Einträge gefunden

            Schreibe einen Kommentar Antwort abbrechen

            Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

            INTERESSANTE SEITEN

            • TUTORIALS SCHNELLER FINDEN
            • SCHNELLER RECHNEN LERNEN
            • MATHE AUFGABEN GENERATOR
            • LINIEN UND KARO GENERATOR
            • VORLAGEN ZUM AUSDRUCKEN
            • BINGO-SCHEIN-GENERATOR
            • MEINE WUNSCHLISTE
            • Symbole zum Kopieren

            Content

            • Microsoft Excel Tutorials
            • Microsoft Excel VBA Tutorials
            • Microsoft Word Tutorials
            • Microsoft Powerpoint Tutorials
            • Microsoft Windows 10 Tutorials
            • Google Tabellen Tutorials
            • Google Tabellen Script Tutorials
            • Google Docs Tutorials
            • LibreOffice Calc Tutorials
            • Libre Office Writer Tutorials
            • LibreOffice Impress Tutorials
            • OpenOffice Calc Tutorials
            • OpenOffice Writer Tutorials
            • OpenOffice Impress Tutorials
            • WPS Spreadsheets Tutorials
            • WPS Spreadsheets Tutorials

            Neue Beiträge

            • Python Grundkurs Teil 009: Projektordner mit Datum und Protokolldatei sichern
            • PowerShell 7 Grundkurs Teil 009: Projektordner mit Datum und Logdatei sichern
            • Batch Grundkurs 009: Backup mit Zeitstempel und robocopy erstellen
            • Python Grundkurs Teil 008: Text in Dateinamen ersetzen
            • PowerShell 7 Grundkurs Teil 008: Text in Dateinamen ersetzen

            Neue Kommentare

            • Chris zu Zeilen, Spalten, Fenster fixieren (FreeOffice PlanMaker)
            • Herbert Krüger zu Zeilen, Spalten, Fenster fixieren (FreeOffice PlanMaker)
            • Chris zu Tutorial Wunsch Liste
            • Rupert zu Tutorial Wunsch Liste
            • Axel zu Datumsformat anpassen (LibreOffice Calc)
            • Klaus zu Dynamischen Monatskalender erstellen + Download (LibreOffice Calc)

            Schlagwörter-Wolke

            anfänger Anleitung anpassen ausrechnen auswahlliste Bedingte Formatierung berechnen Calc Datum Download Dropdownliste einfügen entfernen erstellen Excel FreeOffice Funktion google hinzufügen Jahresplan Kalender Lernvideo LibreOffice löschen Makro markieren Microsoft OpenOffice PlanMaker programmierung script spreadsheets Sverweis Tabelle tabellen Tutorial VBA Video Wenn Wenn Funktion Windows Automatisierung wps spreadsheets Zeilen Zelle Zellen
            KONTAKT   IMPRESSUM    DATENSCHUTZERKLÄRUNG    HAFTUNGSAUSSCHLUSS (Disclaimer)   © copyright TOPTORIALS