Aus Windows heraus schnell Dateien teilen

Also Python habe ich Perplexity nicht vorgegeben, aber das wurde halt gewählt, da es da eben die entsprechenden Module für upnp etc. gibt. Vermutlich fehlt sowas in der Powershell. Wäre ja kein Akt, sich dafür Python zu installieren. Aber ich möchte dir das auch nicht aufdrängen. Es klingt nur so, wie das klassische Szenario für ein Script.
 
@Mojo1987

Hier ist z.B. die Systemd Unit für meinen Opentracker:
Code:
# /etc/systemd/system/opentracker.service
[Unit]
Description=opentracker BitTorrent Tracker
After=network-online.target opentracker-wait.service
Wants=network-online.target

[Service]
Type=simple
ExecStartPre=/usr/bin/upnpc -u http://192.168.100.1:2189/ctl/IPConn -e 'opentracker' -a 192.168.100.78 <port> <port> tcp
ExecStart=/opt/opentracker/opentracker -f /etc/opentracker/opentracker.conf
ExecStopPost=/usr/bin/upnpc -u http://192.168.100.1:2189/ctl/IPConn -d <port> tcp
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ProtectProc=invisible
RestrictAddressFamilies=AF_INET AF_INET6

#RestartSec=10s

[Install]
WantedBy=multi-user.target

Wenn der Dienst startet, wird der Port auf meinen Router auf den Container weitergeleitet. Wenn der Dienst beendet wird, wird die Portweiterleitung wieder entfernt.

Die Portweiterleitung ist so im Router explizit erlaubt.

allow <port>-<port> 192.168.100.78/32 <port>-<port>

Wenn das Bad Practice ist, was wäre dann Best Practice?
 
CoMo schrieb:
Ich habe doch klar beschrieben, was ich haben möchte, oder?
bei diesem unfreundlichen Satz, behalte ich meinen Vorschlag für mich .... Der Ton macht die Musik
 
  • Gefällt mir
Reaktionen: SimmiS, omavoss und redjack1000
Hier, kannst es ja ausprobieren oder nicht. Auf eigene Gefahr versteht sich, geprüft habe ich das nicht:

1. Python für Windows installieren
https://www.computerbase.de/downloads/systemtools/entwicklung/python/

2. Die benötigten Module installieren
Code:
pip install pyperclip miniupnpc

3. Hier das Skript:
Python:
import os
import socket
import threading
from http.server import HTTPServer, SimpleHTTPRequestHandler
import argparse
import pyperclip
import miniupnpc
import ctypes
import platform

# Windows spezifisch: Konsolenfenstertitel setzen
def set_console_title(title):
    if platform.system() == "Windows":
        ctypes.windll.kernel32.SetConsoleTitleW(title)

# Lokale IP-Adresse ermitteln
def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        s.close()
    return ip

# HTTP-Server für eine Datei mit Download-Zählung
class FileHTTPServer:
    def __init__(self, file_path, port=8000):
        self.file_path = file_path
        self.port = port
        self.httpd = None
        self.thread = None

    def start(self):
        os.chdir(os.path.dirname(self.file_path))

        HandlerClass = SimpleHTTPRequestHandler

        class CustomHandler(HandlerClass):
            def do_GET(self):
                if self.path == '/' or self.path == '/' + os.path.basename(self.server.file_path):
                    self.path = '/' + os.path.basename(self.server.file_path)
                self.server.download_count += 1
                print(f"Download #{self.server.download_count} von {self.client_address[0]}")
                return HandlerClass.do_GET(self)

        server_address = ('', self.port)
        self.httpd = HTTPServer(server_address, CustomHandler)
        self.httpd.file_path = self.file_path
        self.httpd.download_count = 0

        def serve():
            set_console_title(f"File sharing server running at port {self.port}")
            print(f"Datei: {self.file_path}")
            ip = get_local_ip()
            print(f"Lokale URL: http://{ip}:{self.port}/{os.path.basename(self.file_path)}")

            # UPnP Portweiterleitung versuchen
            try:
                upnp = miniupnpc.UPnP()
                upnp.discoverdelay = 200
                upnp.discover()
                upnp.selectigd()
                external_ip = upnp.externalipaddress()
                upnp.addportmapping(self.port, 'TCP', upnp.lanaddr, self.port, 'File Share Server', '')
                print(f"Externe URL: http://{external_ip}:{self.port}/{os.path.basename(self.file_path)}")

                url_to_copy = f"http://{external_ip}:{self.port}/{os.path.basename(self.file_path)}"
            except Exception as e:
                print(f"UPnP Portweiterleitung fehlgeschlagen: {e}")
                url_to_copy = f"http://{ip}:{self.port}/{os.path.basename(self.file_path)}"

            # URL in Zwischenablage kopieren
            try:
                pyperclip.copy(url_to_copy)
                print("URL wurde in die Zwischenablage kopiert.")
            except Exception as e:
                print(f"Fehler beim Kopieren in Zwischenablage: {e}")

            print("Strg+C oder Konsole schließen zum Stoppen.")

            try:
                self.httpd.serve_forever()
            except KeyboardInterrupt:
                print("Server wird gestoppt...")
            finally:
                # UPnP Portweiterleitung wieder entfernen
                try:
                    upnp.deleteportmapping(self.port, 'TCP')
                    print("UPnP Portweiterleitung entfernt.")
                except:
                    pass

        self.thread = threading.Thread(target=serve)
        self.thread.start()

    def stop(self):
        if self.httpd:
            self.httpd.shutdown()
        if self.thread:
            self.thread.join()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Teile eine Datei per HTTP mit UPnP Portweiterleitung.')
    parser.add_argument('file', help='Der Pfad zur Datei, die geteilt werden soll.')
    parser.add_argument('--port', type=int, default=8000, help='Port zum Hosten.')
    args = parser.parse_args()

    server = FileHTTPServer(args.file, args.port)
    server.start()
    try:
        while True:
            pass
    except KeyboardInterrupt:
        server.stop()

4. "Senden An" Dialog mit entsprechender Option versehen:
  • WIN+R drücken
  • "shell:sendto" dort eingeben, es öffnet sich der Ordner mit den Verknüpfungen für das Menü.
  • Dort neue Verknüpfung erstellen mit dem Ziel "C:\Pfad\zu\python.exe C:\Pfad\zu\deinem_script.py" und entsprechend benennen, z.B. "per Weblink"

Probier's aus, vielleicht klappts ja.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: CoMo
Das funktioniert! 😄

Ich habe Python Python 3.13 über den Windows Store installiert, das Skript noch angepasst, denn ich habe eine statische IP-Adresse und es wird tatsächlich der Port geöffnet, der Link in die Zwischenablage kopiert und die URL ist von außen erreichbar. Genial. Das ist exakt, was ich wollte.

Leider wird der Port nicht geschlossen, wenn ich das Fenster schließe. Da suche ich gerade noch die Lösung.

waterfox_wV8L0Ua4Dg.png


M!cha schrieb:
bei diesem unfreundlichen Satz, behalte ich meinen Vorschlag für mich .... Der Ton macht die Musik

Du darfst musizieren, so viel und wo du möchtest, aber wenn ich klare Anforderungen definiere und schon klarmache, dass ich bisher Nextcloud nutze und dann antwortet jemand mit "OneDrive?", dann ist das sinnloser Spam. Und wenn ich dir zu unfreundlich bin, dann antworte doch einfach nicht auf meine Threads.
 
  • Gefällt mir
Reaktionen: Grimba
CoMo schrieb:
Das funktioniert! 😄
Ja cool. Nicht schlecht für so'n Schuss ins Blaue. Wie gesagt, ab hier musst du alleine weiterbasteln. Komisch, dass das mit dem Zumachen des Ports nicht klappt. Ich wünsche gute Jagd nach der Lösung und viel Erfolg für die letzten Meter :)
 
  • Gefällt mir
Reaktionen: CoMo
Jop, vielen Dank @Grimba. Vielleicht sollte ich mich auch mal Perplexity zuwenden, das scheint ja für solche Aufgaben zielführender zu sein als ChatGPT.

Python war hier auch gar nicht die schlechteste Idee, wenn es miniupnpc direkt als PIP Modul gibt.

Nun muss ich dem Skript noch beibringen, dass es auch bei CTRL-C oder Schließen des Fensters die Portweiterleitung entfernt. Dafür ist wohl das Modul pywin32 nötig. Ich bastel mal etwas weiter 🫠
 
  • Gefällt mir
Reaktionen: Grimba
Ich konnt's nicht lassen, und hab mal perplexity angemeckert wegen deines Problems, und es hat das Skript etwas angepasst (ich hatte ja noch den Kontext offen im Fenster). Ich darf zitierern:
Das Problem, dass der UPnP-Port beim Beenden des Servers nicht korrekt geschlossen wird, kann mehrere Ursachen haben, z.B. dass der UPnP-Client außerhalb des Server-Threads nicht mehr verfügbar ist oder beim Versuch des Löschens ein Fehler auftritt, der nicht richtig behandelt wird.

Um das zuverlässig zu beheben, sollte der UPnP-Client als Objekt im Server gehalten werden und beim Stoppen explizit aufgerufen werden, um die Portweiterleitung zu entfernen.

Hier ein Vorschlag für eine sorgfältigere UPnP-Portfreigabe und das sichere Entfernen beim Serverstopp:
Python:
import os
import socket
import threading
from http.server import HTTPServer, SimpleHTTPRequestHandler
import argparse
import pyperclip
import miniupnpc
import ctypes
import platform
import signal
import sys

def set_console_title(title):
    if platform.system() == "Windows":
        ctypes.windll.kernel32.SetConsoleTitleW(title)

def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        s.close()
    return ip

class FileHTTPServer:
    def __init__(self, file_path, port=8000):
        self.file_path = file_path
        self.port = port
        self.httpd = None
        self.thread = None
        self.upnp = None
        self.running = True
        self.download_count = 0

    def start(self):
        os.chdir(os.path.dirname(self.file_path))

        HandlerClass = SimpleHTTPRequestHandler
        parent = self

        class CustomHandler(HandlerClass):
            def do_GET(self):
                if self.path == '/' or self.path == '/' + os.path.basename(self.server.file_path):
                    self.path = '/' + os.path.basename(self.server.file_path)
                parent.download_count += 1
                print(f"Download #{parent.download_count} von {self.client_address[0]}")
                return HandlerClass.do_GET(self)

        server_address = ('', self.port)
        self.httpd = HTTPServer(server_address, CustomHandler)
        self.httpd.file_path = self.file_path

        # UPnP Port-Mapping setzen
        try:
            self.upnp = miniupnpc.UPnP()
            self.upnp.discoverdelay = 200
            self.upnp.discover()
            self.upnp.selectigd()
            external_ip = self.upnp.externalipaddress()
            self.upnp.addportmapping(self.port, 'TCP', self.upnp.lanaddr, self.port, 'File Share Server', '')
            print(f"Externe URL: http://{external_ip}:{self.port}/{os.path.basename(self.file_path)}")
        except Exception as e:
            print("UPnP Portweiterleitung fehlgeschlagen:", e)
            self.upnp = None

        ip = get_local_ip()
        print(f"Datei: {self.file_path}")
        print(f"Lokale URL: http://{ip}:{self.port}/{os.path.basename(self.file_path)}")

        # URL in Zwischenablage kopieren
        url_to_copy = f"http://{ip}:{self.port}/{os.path.basename(self.file_path)}"
        try:
            pyperclip.copy(url_to_copy)
            print("URL wurde in die Zwischenablage kopiert.")
        except Exception as e:
            print("Fehler beim Kopieren in Zwischenablage:", e)

        def serve():
            set_console_title(f"File sharing server running at port {self.port}")
            print("Strg+C oder Konsole schließen zum Stoppen.")
            while self.running:
                self.httpd.handle_request()

        self.thread = threading.Thread(target=serve)
        self.thread.start()

    def stop(self):
        print("Server wird gestoppt...")
        self.running = False

        # HTTPS-Server herunterfahren
        if self.httpd:
            # Um handle_request() zu lösen, kann man eine Dummy-Anfrage senden, wenn nötig
            # Oder self.httpd.shutdown() verwenden, aber handle_request in Loop verwendet workaround
            try:
                import urllib.request
                urllib.request.urlopen(f"http://localhost:{self.port}/")
            except:
                pass
            self.thread.join()

        # UPnP Portweiterleitung entfernen
        if self.upnp:
            try:
                self.upnp.deleteportmapping(self.port, 'TCP')
                print("UPnP Portweiterleitung entfernt.")
            except Exception as e:
                print("Fehler beim Entfernen der UPnP Portweiterleitung:", e)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Teile eine Datei per HTTP mit UPnP Portweiterleitung.')
    parser.add_argument('file', help='Der Pfad zur Datei, die geteilt werden soll.')
    parser.add_argument('--port', type=int, default=8000, help='Port zum Hosten.')
    args = parser.parse_args()

    server = FileHTTPServer(args.file, args.port)
    server.start()

    def signal_handler(sig, frame):
        server.stop()
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # Warte auf Threadende
    server.thread.join()

Aber wie gesagt, das ganze bitte mit der nötigen Vorsicht verwenden. Direkter Fernzugriff von außen ist immer so eine Sache. Viel Erfolg!
 
Zuletzt bearbeitet:
Ich habs schon mit ChatGPT gelöst 😄

Code:
import http.server
import socketserver
import sys
import os
import pyperclip
import miniupnpc
import threading
import signal

# --- Configuration ---
EXTERNAL_IP = "<meine-öffentliche-ip-adresse>"
EXTERNAL_PORT = 80
INTERNAL_PORT = 80
# ----------------------

# --- Check arguments ---
if len(sys.argv) < 2:
    print("Usage: ShareOverHTTP.py <file_path>")
    input("Press Enter to exit...")
    sys.exit(1)

file_path = sys.argv[1]

if not os.path.isfile(file_path):
    print("File not found:", file_path)
    input("Press Enter to exit...")
    sys.exit(1)

file_dir = os.path.dirname(file_path)
file_name = os.path.basename(file_path)

# --- HTTP Server Setup ---
class SingleFileHandler(http.server.SimpleHTTPRequestHandler):
    """Serve only the selected file, return 404 for others"""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=file_dir, **kwargs)

    def do_GET(self):
        if self.path.lstrip("/") != file_name:
            self.send_error(404, "File not found")
            return
        super().do_GET()

httpd = socketserver.TCPServer(("", INTERNAL_PORT), SingleFileHandler)

# --- UPnP Port Forwarding ---
u = miniupnpc.UPnP()
u.discoverdelay = 200
u.discover()
u.selectigd()

local_ip = u.lanaddr

# Remove existing mapping if any
try:
    u.deleteportmapping(EXTERNAL_PORT, "TCP")
except Exception:
    pass

# Add new port mapping
u.addportmapping(EXTERNAL_PORT, "TCP", local_ip, INTERNAL_PORT, "ShareOverHTTP", "")

# --- Copy URL to clipboard ---
url = f"http://{EXTERNAL_IP}:{EXTERNAL_PORT}/{file_name}"
pyperclip.copy(url)
print("File shared at:", url, "(copied to clipboard)")

# --- Cleanup function ---
def cleanup(*args):
    """Stop HTTP server and remove UPnP port mapping"""
    print("\nStopping server and removing port mapping...")
    httpd.shutdown()
    try:
        u.deleteportmapping(EXTERNAL_PORT, "TCP")
    except Exception:
        pass
    sys.exit(0)

# Catch Ctrl+C and termination signals
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)

# Windows: handle console close event
if os.name == 'nt':
    try:
        import win32api
        win32api.SetConsoleCtrlHandler(lambda x: cleanup(), True)
    except ImportError:
        print("pywin32 not installed. Window close cleanup will not work.")

# --- Start server in background thread ---
def serve():
    httpd.serve_forever()

server_thread = threading.Thread(target=serve, daemon=True)
server_thread.start()

# --- Keep the window open ---
print("Press Ctrl+C or close this window to stop sharing.")
try:
    signal.pause()
except AttributeError:
    # On Windows, signal.pause() may not work; fallback
    import time
    while True:
        time.sleep(1)

Wenn ich das Fenster schließe, wird der Port geschlossen. Wenn ich CTRL-C drücke, bleibt es mit Stopping server and removing port mapping... stehen und macht auch den Port zu.

Absolut geil. Exakt die Lösung, die ich haben wollte. Nochmals vielen Dank @Grimba
Ergänzung ()

@Mojo1987 müsste mir jetzt noch erklären, warum ich kein UPnP/PMP nutzen und was ich stattdessen nutzen soll.
Ergänzung ()

So. Der Port wrude nicht geschlossen, wenn das Fenster einfach mit X geschlossen wurde. So funktioniert es nun:


Code:
import http.server
import socketserver
import sys
import os
import pyperclip
import miniupnpc
import threading
import signal

# --- Configuration ---
EXTERNAL_IP = "<meine-öffentliche-ip-adresse"
EXTERNAL_PORT = 80
INTERNAL_PORT = 80
# ----------------------

# --- Check arguments ---
if len(sys.argv) < 2:
    print("Usage: ShareOverHTTP.py <file_path>")
    input("Press Enter to exit...")
    sys.exit(1)

file_path = sys.argv[1]

if not os.path.isfile(file_path):
    print("File not found:", file_path)
    input("Press Enter to exit...")
    sys.exit(1)

file_dir = os.path.dirname(file_path)
file_name = os.path.basename(file_path)

# --- HTTP Server Setup ---
class SingleFileHandler(http.server.SimpleHTTPRequestHandler):
    """Serve only the selected file, return 404 for others"""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=file_dir, **kwargs)

    def do_GET(self):
        if self.path.lstrip("/") != file_name:
            self.send_error(404, "File not found")
            return
        super().do_GET()

httpd = socketserver.TCPServer(("", INTERNAL_PORT), SingleFileHandler)

# --- UPnP Port Forwarding ---
u = miniupnpc.UPnP()
u.discoverdelay = 200
u.discover()
u.selectigd()

local_ip = u.lanaddr

# Remove existing mapping if any
try:
    u.deleteportmapping(EXTERNAL_PORT, "TCP")
except Exception:
    pass

# Add new port mapping
u.addportmapping(EXTERNAL_PORT, "TCP", local_ip, INTERNAL_PORT, "ShareOverHTTP", "")

# --- Copy URL to clipboard ---
url = f"http://{EXTERNAL_IP}:{EXTERNAL_PORT}/{file_name}"
pyperclip.copy(url)
print("File shared at:", url, "(copied to clipboard)")

# --- Cleanup function ---
def cleanup(*args):
    """Stop HTTP server and remove UPnP port mapping"""
    print("\nStopping server and removing port mapping...")
    try:
        httpd.shutdown()           # stop server
        httpd.server_close()       # close socket
    except Exception:
        pass
    try:
        u.deleteportmapping(EXTERNAL_PORT, "TCP")
    except Exception:
        pass
    server_thread.join(timeout=1)   # wait for thread
    print("Server stopped, port mapping removed. Exiting.")
    sys.exit(0)

# --- Signal handlers ---
signal.signal(signal.SIGINT, cleanup)   # Ctrl+C
signal.signal(signal.SIGTERM, cleanup)  # Terminate signal

# Windows: catch window close event (requires pywin32)
if os.name == 'nt':
    try:
        import win32api
        win32api.SetConsoleCtrlHandler(lambda x: cleanup(), True)
    except ImportError:
        print("pywin32 not installed. Cleanup on window close will not work!")

# --- Start server in a background thread ---
def serve():
    httpd.serve_forever()

server_thread = threading.Thread(target=serve, daemon=True)
server_thread.start()

# --- Keep console open ---
print("Press Ctrl+C or close this window to stop sharing.")
try:
    signal.pause()
except AttributeError:
    # fallback for Windows if signal.pause() unavailable
    import time
    while True:
        time.sleep(1)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Grimba
Ich muss zugeben, dass ich aus diesem Beispiel auch eine Erkenntnis mitnehme. Und zwar, dass es sich lohnt, häufiger mal außerhalb gewohnter Bahnen zu denken und statt viel Zeit für die Suche nach einer passenden Software aufzuwenden, vielleicht mal den direkten Weg zu gehen. Macht man ja in Windows tendenziell eher seltener als in Linux, was etwas mehr dazu einlädt. So AI Tools senken die Hürde jedenfalls ordentlich, aber blind drauf verlassen sollte man sich natürlich auch nicht. Dennoch cool, wie flott das jetzt dann doch gelöst war.
 
  • Gefällt mir
Reaktionen: CoMo
Jap. Im Grunde ist das die perfekte Lösung. Wenn die Aufgabe trivial ist und es keine geeignete Software gibt, diese einfach selbst schreiben (lassen).

Nun habe ich ja schon fast eine Stunde mit ChatGPT rumdiskutiert und darauf bestanden, das ganze nativ in Powershell abzubilden. Das hat nicht funktioniert. Vermutlich hätte ich in Python auch schneller funktionierenden Code bekommen 🫠
 
Also, ich weiß nicht, ob das wirklich stimmt, aber ich könnte mir vorstellen, dass durch die große Verbreitung von Python der Code Generator in der AI dafür einfach etwas ausgereifter ist, oder Python vielleicht per se mehr Module für alles Mögliche bietet. Aber im Prinzip ist es ja auch Wurscht womit man das löst, solange es funktioniert.
 
Noch das Problem gehabt, dass zwar die Portweiterleitung gestoppt wurde, der python httpd Prozess aber nicht. Fix für die cleanup Funktion:

Code:
# --- Cleanup ---
def cleanup(*args):
    print("\nStopping server and removing port mapping...")
    try:
        httpd.shutdown()
        httpd.server_close()
    except Exception:
        pass
    try:
        u.deleteportmapping(EXTERNAL_PORT, "TCP")
    except Exception:
        pass
    if server_thread.is_alive():
        server_thread.join(timeout=2)
    print("Server stopped, port mapping removed. Exiting.")
    sys.exit(0)

Beim Schließen des Fensters ohne Beenden bleibt er immer noch hängen, aber das ist mir relativ egal. CTRL-C funktioniert jetzt.
 
Was man jetzt noch machen könnte, wären ein paar Zugriffsinformationen zur Laufzeit anzeigen. Also z.B. "Download von <Deine Datei> durch <Externe IP>" Vielleicht mit Progress Bar. Das wären so Sachen, wo ich jetzt dran basteln würde.
 

Ähnliche Themen

Zurück
Oben