Files
Modules/Base/Map_Drive.vb
2026-04-30 16:04:31 +02:00

560 lines
24 KiB
VB.net
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Imports System.IO
Imports System.Runtime.InteropServices
Imports DigitalData.Modules.Logging
Public Class Map_Drive
Private ReadOnly _Logger As Logger
Private ReadOnly _LogConfig As LogConfig
#Region "Windows API Deklarationen"
<DllImport("mpr.dll", CharSet:=CharSet.Auto)>
Private Shared Function WNetAddConnection2(ByRef lpNetResource As NETRESOURCE,
ByVal lpPassword As String,
ByVal lpUsername As String,
ByVal dwFlags As Integer) As Integer
End Function
<DllImport("mpr.dll", CharSet:=CharSet.Auto)>
Private Shared Function WNetCancelConnection2(ByVal lpName As String,
ByVal dwFlags As Integer,
ByVal fForce As Boolean) As Integer
End Function
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Private Structure NETRESOURCE
Public dwScope As Integer
Public dwType As Integer
Public dwDisplayType As Integer
Public dwUsage As Integer
Public lpLocalName As String
Public lpRemoteName As String
Public lpComment As String
Public lpProvider As String
End Structure
Private Const RESOURCETYPE_DISK As Integer = 1
Private Const CONNECT_UPDATE_PROFILE As Integer = 1
Private Const ERROR_SUCCESS As Integer = 0
Private Const ERROR_ALREADY_ASSIGNED As Integer = 85
#End Region
Public Sub New(LogConfig As LogConfig)
_LogConfig = LogConfig
_Logger = LogConfig.GetLogger()
End Sub
''' <summary>
''' Struktur für Netzlaufwerk-Informationen
''' </summary>
Public Structure NetworkDriveInfo
Public DriveLetter As String
Public NetworkPath As String
Public DriveType As IO.DriveType
Public IsReady As Boolean
Public TotalSize As Long
Public FreeSpace As Long
End Structure
''' <summary>
''' Ermittelt den nächsten freien Laufwerksbuchstaben (alphabetisch absteigend von Z bis A)
''' </summary>
''' <param name="blacklist">Liste der nicht erlaubten Laufwerksbuchstaben (z.B. "Y,M,V")</param>
''' <returns>Nächster freier Laufwerksbuchstabe mit Doppelpunkt (z.B. "Z:") oder String.Empty wenn keiner frei</returns>
Public Function GetNextFreeDriveLetter(Optional blacklist As String = "") As String
Try
' Blacklist verarbeiten (Großbuchstaben ohne Doppelpunkte)
Dim blacklistArray As New List(Of Char)
If Not String.IsNullOrEmpty(blacklist) Then
For Each item In blacklist.Split(","c)
Dim letter = item.Trim().ToUpper().Replace(":", "")
If letter.Length = 1 AndAlso Char.IsLetter(letter(0)) Then
blacklistArray.Add(letter(0))
End If
Next
End If
' Alle aktuell verwendeten Laufwerksbuchstaben ermitteln
Dim usedDrives As New List(Of Char)
For Each drive As IO.DriveInfo In IO.DriveInfo.GetDrives()
Dim letter As Char = drive.Name(0)
usedDrives.Add(Char.ToUpper(letter))
Next
' Alphabetisch absteigend von Z bis A durchgehen
For i As Integer = Asc("Z"c) To Asc("A"c) Step -1
Dim currentLetter As Char = Chr(i)
' Prüfen ob Buchstabe verfügbar ist
If Not usedDrives.Contains(currentLetter) AndAlso Not blacklistArray.Contains(currentLetter) Then
_Logger.Debug($"Nächster freier Laufwerksbuchstabe gefunden: {currentLetter}:")
Return currentLetter & ":"
End If
Next
_Logger.Warn("Kein freier Laufwerksbuchstabe gefunden!")
Return String.Empty
Catch ex As Exception
_Logger.Error($"Fehler beim Ermitteln des nächsten freien Laufwerksbuchstabens: {ex.Message}")
_Logger.Error(ex)
Return String.Empty
End Try
End Function
''' <summary>
''' Prüft ob ein Laufwerksbuchstabe verfügbar ist (nicht verwendet und nicht in Blacklist)
''' </summary>
''' <param name="driveLetter">Zu prüfender Laufwerksbuchstabe</param>
''' <param name="blacklist">Liste der nicht erlaubten Laufwerksbuchstaben</param>
''' <returns>True wenn verfügbar, False wenn bereits verwendet oder in Blacklist</returns>
Public Function IsDriveLetterAvailable(driveLetter As String, Optional blacklist As String = "") As Boolean
Try
' Formatierung sicherstellen
driveLetter = driveLetter.Trim().ToUpper().Replace(":", "")
If driveLetter.Length <> 1 OrElse Not Char.IsLetter(driveLetter(0)) Then
_Logger.Warn($"Ungültiger Laufwerksbuchstabe: {driveLetter}")
Return False
End If
Dim letter As Char = driveLetter(0)
' Blacklist prüfen
If Not String.IsNullOrEmpty(blacklist) Then
For Each item In blacklist.Split(","c)
Dim blacklistedLetter = item.Trim().ToUpper().Replace(":", "")
If blacklistedLetter.Length = 1 AndAlso blacklistedLetter(0) = letter Then
_Logger.Debug($"Laufwerk {letter}: ist in der Blacklist")
Return False
End If
Next
End If
' Prüfen ob bereits verwendet
For Each drive As IO.DriveInfo In IO.DriveInfo.GetDrives()
If Char.ToUpper(drive.Name(0)) = letter Then
_Logger.Debug($"Laufwerk {letter}: ist bereits verwendet")
Return False
End If
Next
Return True
Catch ex As Exception
_Logger.Error($"Fehler beim Prüfen der Laufwerksverfügbarkeit: {ex.Message}")
_Logger.Error(ex)
Return False
End Try
End Function
''' <summary>
''' Mappt ein Netzlaufwerk mit automatischer Laufwerksbuchstabenwahl oder spezifischem Buchstaben
''' </summary>
''' <param name="driveLetter">Gewünschter Laufwerksbuchstabe (leer = automatisch den nächsten freien wählen)</param>
''' <param name="networkPath">UNC-Pfad des Netzwerkshares</param>
''' <param name="blacklist">Komma-getrennte Liste verbotener Laufwerksbuchstaben (z.B. "Y,M,V")</param>
''' <param name="userName">Optionaler Benutzername für Authentifizierung</param>
''' <param name="password">Optionales Passwort für Authentifizierung</param>
''' <param name="persistent">Soll das Mapping persistent sein?</param>
''' <returns>Verwendeter Laufwerksbuchstabe bei Erfolg, String.Empty bei Fehler</returns>
Public Function MapNetworkDrive(driveLetter As String,
networkPath As String,
Optional blacklist As String = "",
Optional userName As String = Nothing,
Optional password As String = Nothing,
Optional persistent As Boolean = True) As String
Try
Dim targetDriveLetter As String = ""
' Szenario 1: Kein Laufwerksbuchstabe angegeben -> Automatische Auswahl
If String.IsNullOrEmpty(driveLetter) Then
_Logger.Info("Kein Laufwerksbuchstabe angegeben - suche nächsten freien Buchstaben...")
targetDriveLetter = GetNextFreeDriveLetter(blacklist)
If String.IsNullOrEmpty(targetDriveLetter) Then
_Logger.Error("❌ Kein freier Laufwerksbuchstabe verfügbar!")
Return String.Empty
End If
_Logger.Info($"Automatisch gewählter Laufwerksbuchstabe: {targetDriveLetter}")
Else
' Szenario 2: Spezifischer Laufwerksbuchstabe angegeben
targetDriveLetter = driveLetter.Trim().ToUpper()
If Not targetDriveLetter.EndsWith(":") Then
targetDriveLetter &= ":"
End If
' ========== NEU: Prüfen ob Laufwerk verfügbar ist ==========
If Not IsDriveLetterAvailable(targetDriveLetter, blacklist) Then
_Logger.Warn($"⚠️ Laufwerk {targetDriveLetter} ist nicht verfügbar (bereits verwendet oder in Blacklist)")
' NICHT abbrechen - weiter versuchen (alte Logik beibehalten)
End If
' ========== ENDE NEU ==========
End If
' ========== NEU: Prüfung ob Laufwerk bereits existiert ==========
Dim driveExists As Boolean = False
Try
Dim driveInfo As New System.IO.DriveInfo(targetDriveLetter)
driveExists = driveInfo.IsReady
Catch
' Laufwerk existiert nicht - das ist OK
driveExists = False
End Try
' Nur trennen wenn Laufwerk wirklich existiert
If driveExists Then
_Logger.Debug($" Laufwerk {targetDriveLetter} existiert bereits - wird getrennt")
DisconnectNetworkDrive(targetDriveLetter, force:=True)
Else
_Logger.Debug($"✓ Laufwerk {targetDriveLetter} existiert noch nicht - kein Disconnect nötig")
End If
' ========== ENDE NEU ==========
' Laufwerk mappen (bestehende Logik)
If MapNetworkDriveInternal(targetDriveLetter, networkPath, userName, password, persistent) Then
_Logger.Info($"✓ Netzlaufwerk {targetDriveLetter} erfolgreich gemappt zu {networkPath}")
Return targetDriveLetter
Else
_Logger.Error($"❌ Fehler beim Mappen von {targetDriveLetter}")
Return String.Empty
End If
Catch ex As Exception
_Logger.Error($"❌ Fehler in MapNetworkDrive: {ex.Message}")
_Logger.Error(ex)
Return String.Empty
End Try
End Function
''' <summary>
''' Interne Methode zum tatsächlichen Mappen eines Netzlaufwerks
''' </summary>
Private Function MapNetworkDriveInternal(driveLetter As String,
networkPath As String,
userName As String,
password As String,
persistent As Boolean) As Boolean
Try
' Erst trennen falls vorhanden (ohne Fehler wenn nicht vorhanden)
DisconnectNetworkDrive(driveLetter, True)
' NETRESOURCE-Struktur vorbereiten
Dim netResource As New NETRESOURCE With {
.dwType = RESOURCETYPE_DISK,
.lpLocalName = driveLetter,
.lpRemoteName = networkPath
}
Dim flags As Integer = If(persistent, CONNECT_UPDATE_PROFILE, 0)
' WICHTIG: Credentials als Nothing übergeben = Verwende aktuelle Windows-Credentials
' Wenn der Share öffentlich oder mit aktuellen Credentials erreichbar ist, funktioniert es
Dim result As Integer = WNetAddConnection2(netResource, password, userName, flags)
Select Case result
Case ERROR_SUCCESS
_Logger.Debug($"✓ Laufwerk {driveLetter} erfolgreich gemappt")
Return True
Case 1326 ' ERROR_LOGON_FAILURE
_Logger.Error($"❌ Authentifizierungsfehler (1326): Anmeldung fehlgeschlagen für [{networkPath}]")
_Logger.Error($" → Der UNC-Pfad erfordert möglicherweise spezielle Credentials")
_Logger.Error($" → Oder der aktuelle Benutzer hat keine Berechtigung")
Return False
Case 53 ' ERROR_BAD_NETPATH
_Logger.Error($"❌ Netzwerkpfad nicht gefunden (53): [{networkPath}]")
Return False
Case 67 ' ERROR_BAD_NET_NAME
_Logger.Error($"❌ Netzwerkname ungültig (67): [{networkPath}]")
Return False
Case 85 ' ERROR_ALREADY_ASSIGNED
_Logger.Warn($"⚠️ Laufwerk {driveLetter} ist bereits zugewiesen (85)")
' Versuche es zu trennen und erneut zu verbinden
DisconnectNetworkDrive(driveLetter, force:=True)
System.Threading.Thread.Sleep(500) ' Kurze Pause
result = WNetAddConnection2(netResource, password, userName, flags)
If result = ERROR_SUCCESS Then
_Logger.Info($"✓ Laufwerk {driveLetter} nach erneutem Versuch erfolgreich gemappt")
Return True
Else
Return False
End If
Case Else
_Logger.Error($"❌ WNetAddConnection2 Error Code: {result}")
Return False
End Select
Catch ex As Exception
_Logger.Error($"Fehler in MapNetworkDriveInternal: {ex.Message}")
_Logger.Error(ex)
Return False
End Try
End Function
''' <summary>
''' Test-Funktion um UNC-Pfad-Zugriff zu prüfen
''' </summary>
Public Function TestUNCAccess(uncPath As String) As Boolean
Try
_Logger.Info($"🔍 Teste Zugriff auf UNC-Pfad: [{uncPath}]")
' Teste ob Pfad existiert und erreichbar ist
If System.IO.Directory.Exists(uncPath) Then
_Logger.Info($"✓ UNC-Pfad ist direkt erreichbar ohne Mapping")
' Teste Lese-Berechtigung
Try
Dim files = System.IO.Directory.GetFiles(uncPath)
_Logger.Info($"✓ Lese-Berechtigung vorhanden ({files.Length} Dateien gefunden)")
Return True
Catch permEx As UnauthorizedAccessException
_Logger.Error($"❌ Keine Lese-Berechtigung: {permEx.Message}")
Return False
End Try
Else
_Logger.Error($"❌ UNC-Pfad nicht erreichbar oder existiert nicht")
Return False
End If
Catch ex As Exception
_Logger.Error($"❌ Fehler beim Testen des UNC-Zugriffs: {ex.Message}")
_Logger.Error(ex)
Return False
End Try
End Function
''' <summary>
''' Trennt ein Netzlaufwerk mit Windows-API
''' </summary>
Public Function DisconnectNetworkDrive(driveLetter As String, Optional force As Boolean = True) As Boolean
Try
' Formatierung sicherstellen
driveLetter = driveLetter.Trim().ToUpper()
If Not driveLetter.EndsWith(":") Then
driveLetter &= ":"
End If
Dim driveExists As Boolean = False
Try
Dim driveInfo As New System.IO.DriveInfo(driveLetter)
driveExists = driveInfo.IsReady
Catch
driveExists = False
End Try
If Not driveExists Then
_Logger.Debug($" Laufwerk {driveLetter} existiert nicht - Disconnect übersprungen")
Return True ' Kein Fehler, da das gewünschte Ergebnis erreicht ist (Laufwerk ist nicht verbunden)
End If
Dim flags As Integer = CONNECT_UPDATE_PROFILE
Dim result As Integer = WNetCancelConnection2(driveLetter, flags, force)
If result = ERROR_SUCCESS Then
_Logger.Debug($"✓ Netzlaufwerk {driveLetter} erfolgreich getrennt")
Return True
ElseIf result = 2250 Then ' ERROR_NOT_CONNECTED
' Von WARN auf DEBUG herabgestuft, da es kein echter Fehler ist
_Logger.Debug($" Laufwerk {driveLetter} war nicht verbunden (Code 2250)")
Return True
ElseIf result = ERROR_ALREADY_ASSIGNED Then
_Logger.Debug($" Netzlaufwerk {driveLetter} war nicht verbunden")
Return True
Else
_Logger.Warn($"⚠️ Warnung beim Trennen von {driveLetter}: Error Code {result}")
Return False
End If
Catch ex As Exception
_Logger.Debug($"⚠️ Fehler beim Trennen von {driveLetter} (ignoriert): {ex.Message}")
Return False
End Try
End Function
''' <summary>
''' Ermittelt alle gemappten Netzlaufwerke
''' </summary>
Public Function GetMappedNetworkDrives() As List(Of NetworkDriveInfo)
Dim mappedDrives As New List(Of NetworkDriveInfo)
Try
For Each drive As IO.DriveInfo In IO.DriveInfo.GetDrives()
If drive.DriveType = IO.DriveType.Network Then
Dim driveInfo As New NetworkDriveInfo With {
.DriveLetter = drive.Name,
.NetworkPath = GetNetworkPath(drive.Name),
.DriveType = drive.DriveType,
.IsReady = drive.IsReady
}
If drive.IsReady Then
Try
driveInfo.TotalSize = drive.TotalSize
driveInfo.FreeSpace = drive.AvailableFreeSpace
Catch ex As Exception
_Logger.Debug($"Konnte Größeninformationen für {drive.Name} nicht ermitteln: {ex.Message}")
End Try
End If
mappedDrives.Add(driveInfo)
End If
Next
_Logger.Debug($"Insgesamt {mappedDrives.Count} Netzlaufwerk(e) gefunden")
Return mappedDrives
Catch ex As Exception
_Logger.Error($"Fehler beim Ermitteln der Netzlaufwerke: {ex.Message}")
_Logger.Error(ex)
Return mappedDrives
End Try
End Function
''' <summary>
''' Ermittelt den UNC-Pfad eines gemappten Laufwerks
''' </summary>
Public Function GetNetworkPath(driveLetter As String) As String
Try
driveLetter = driveLetter.Trim().ToUpper()
If Not driveLetter.EndsWith(":") Then
driveLetter &= ":"
End If
Dim network As Object = CreateObject("WScript.Network")
Dim enumDrives As Object = network.EnumNetworkDrives()
For i As Integer = 0 To enumDrives.Count - 1 Step 2
If enumDrives.Item(i).ToString.Equals(driveLetter, StringComparison.OrdinalIgnoreCase) Then
Return enumDrives.Item(i + 1).ToString()
End If
Next
Return String.Empty
Catch ex As Exception
_Logger.Debug($"Fehler beim Ermitteln des Netzwerkpfads für {driveLetter}: {ex.Message}")
Return String.Empty
End Try
End Function
''' <summary>
''' Prüft ob ein bestimmtes Laufwerk als Netzlaufwerk gemappt ist
''' </summary>
Public Shared Function IsDriveMapped(driveLetter As String) As Boolean
Try
driveLetter = driveLetter.Trim().ToUpper()
If Not driveLetter.EndsWith(":") Then
driveLetter &= ":"
End If
If Not driveLetter.EndsWith("\") Then
driveLetter &= "\"
End If
Dim driveInfo As New IO.DriveInfo(driveLetter)
Return driveInfo.DriveType = IO.DriveType.Network AndAlso driveInfo.IsReady
Catch ex As Exception
Return False
End Try
End Function
''' <summary>
''' Gibt eine formatierte Übersicht aller gemappten Netzlaufwerke zurück
''' </summary>
Public Function GetMappedDrivesInfo() As String
Dim result As New System.Text.StringBuilder()
Dim drives = GetMappedNetworkDrives()
If drives.Count = 0 Then
Return "Keine Netzlaufwerke gefunden."
End If
result.AppendLine($"Gemappte Netzlaufwerke ({drives.Count}):")
result.AppendLine(New String("-"c, 60))
For Each drive In drives
result.AppendLine($"Laufwerk: {drive.DriveLetter}")
result.AppendLine($" Pfad: {drive.NetworkPath}")
result.AppendLine($" Status: {If(drive.IsReady, "Verfügbar", "Nicht verfügbar")}")
If drive.IsReady AndAlso drive.TotalSize > 0 Then
Dim totalGB As Double = drive.TotalSize / (1024.0 ^ 3)
Dim freeGB As Double = drive.FreeSpace / (1024.0 ^ 3)
result.AppendLine($" Größe: {totalGB:N2} GB (Frei: {freeGB:N2} GB)")
End If
result.AppendLine()
Next
Return result.ToString()
End Function
''' <summary>
''' Mappt ein spezifisches Laufwerk (z.B. "V") mit Blacklist-Prüfung
''' </summary>
Public Function MapSpecificDrive(driveLetter As String, blacklist As String, networkPath As String) As Boolean
Try
' Formatierung sicherstellen
driveLetter = driveLetter.Trim().ToUpper().Replace(":", "")
If String.IsNullOrEmpty(driveLetter) OrElse driveLetter.Length <> 1 Then
_Logger.Warn($"⚠️ Ungültiger Laufwerksbuchstabe: [{driveLetter}]")
Return False
End If
Dim driveWithColon As String = driveLetter & ":"
' Prüfen ob Laufwerk verfügbar ist
If Not IsDriveLetterAvailable(driveWithColon, blacklist) Then
_Logger.Warn($"⚠️ Laufwerk {driveWithColon} ist nicht verfügbar (bereits verwendet oder in Blacklist)")
Return False
End If
' UNC-Pfad vorbereiten (ohne abschließenden Backslash)
Dim uncPath As String = networkPath.TrimEnd("\"c)
' Laufwerk mappen (OHNE Credentials, persistent=False für temporäres Mapping)
Dim result = MapNetworkDrive(driveWithColon, uncPath, blacklist, userName:=Nothing, password:=Nothing, persistent:=False)
If Not String.IsNullOrEmpty(result) Then
_Logger.Debug($"✓ Laufwerk {driveWithColon} erfolgreich gemappt zu [{uncPath}]")
Return True
Else
_Logger.Error($"❌ Fehler beim Mappen von {driveWithColon}")
Return False
End If
Catch ex As Exception
_Logger.Error($"Fehler in MapSpecificDrive: {ex.Message}")
_Logger.Error(ex)
Return False
End Try
End Function
''' <summary>
''' Mappt automatisch den nächsten freien Laufwerksbuchstaben (Z→A)
''' </summary>
Public Function MapDriveAutomatic(blacklist As String, networkPath As String) As String
Try
' UNC-Pfad vorbereiten (ohne abschließenden Backslash)
Dim uncPath As String = networkPath.TrimEnd("\"c)
_Logger.Debug($"🔍 Suche automatisch freien Laufwerksbuchstaben...")
_Logger.Debug($" Blacklist: [{blacklist}]")
_Logger.Debug($" Netzwerkpfad: [{uncPath}]")
' Automatisches Mapping (leer = automatische Auswahl, persistent=False)
Dim result = MapNetworkDrive("", uncPath, blacklist, userName:=Nothing, password:=Nothing, persistent:=False)
If Not String.IsNullOrEmpty(result) Then
_Logger.Debug($"✓ Automatisch gewähltes Laufwerk: {result}")
Return result
Else
_Logger.Error($"❌ Kein freier Laufwerksbuchstabe verfügbar")
Return String.Empty
End If
Catch ex As Exception
_Logger.Error($"Fehler in MapDriveAutomatic: {ex.Message}")
_Logger.Error(ex)
Return String.Empty
End Try
End Function
End Class