Hardware Auslastung ermitteln

  • Hab mal eine kleine UDF zusammengestellt die die Auslastung von CPU, Ram und Netwerk ermittelt.

    Stichwörter für Forensuche: Prozessor, Prozessoren, Memory, Netzwerkauslastung, Usage ;)

    Update: 7.4.09

    usage.au3 :

    Spoiler anzeigen
    [autoit]

    #include-once
    ;#####################################################################
    ;#
    ;# Funtion _GetCPUUsage()
    ;# Ermittelt die Auslastung der CPU, Multicore Kompatibel
    ;#
    ;# Rückgabewert: Array
    ;# Array[0] Anzahl der CPUs, Fehler wenn negativ
    ;# Array[n] Auslastung der n-ten CPU in Prozent
    ;#
    ;# Fehler: -1 Fehler beim 1. Dll Aufruf
    ;# -2 Fehler beim 2. Dll Aufruf
    ;# -3 Fehler beim 3. Dll Aufruf
    ;#
    ;# Autor: Bitboy
    ;#####################################################################
    Func _GetCPUUsage()
    Local Const $SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = 8
    Local Const $SYSTEM_TIME_INFO = 3
    Local Const $tagS_SPPI = "int64 IdleTime;int64 KernelTime;int64 UserTime;int64 DpcTime;int64 InterruptTime;long InterruptCount"

    [/autoit] [autoit][/autoit] [autoit]

    Local $CpuNum, $IdleOldArr[1],$IdleNewArr[1], $tmpStruct
    Local $timediff = 0, $starttime = 0
    Local $S_SYSTEM_TIME_INFORMATION, $S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
    Local $RetArr[1]

    [/autoit] [autoit][/autoit] [autoit]

    Local $S_SYSTEM_INFO = DllStructCreate("ushort dwOemId;short wProcessorArchitecture;dword dwPageSize;ptr lpMinimumApplicationAddress;" & _
    "ptr lpMaximumApplicationAddress;long_ptr dwActiveProcessorMask;dword dwNumberOfProcessors;dword dwProcessorType;dword dwAllocationGranularity;" & _
    "short wProcessorLevel;short wProcessorRevision")

    [/autoit] [autoit][/autoit] [autoit]

    ;Anzahl der CPUs bestimmen
    $err = DllCall("Kernel32.dll", "none", "GetSystemInfo", "ptr",DllStructGetPtr($S_SYSTEM_INFO))

    [/autoit] [autoit][/autoit] [autoit]

    If @error Or Not IsArray($err) Then ;Fehler beim 1. Dll Aufruf
    Return $RetArr[0] = -1
    Else
    $CpuNum = DllStructGetData($S_SYSTEM_INFO, "dwNumberOfProcessors")
    ReDim $RetArr[$CpuNum+1]
    $RetArr[0] = $CpuNum
    EndIf
    $S_SYSTEM_INFO = 0

    [/autoit] [autoit][/autoit] [autoit]

    While 1
    ;Erstellen der Struktur für die Prozessor Performance
    $S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = DllStructCreate($tagS_SPPI)
    ;Größe Struktur um später vielfache (MultiCore) der Structur lesen zu können
    $StructSize = DllStructGetSize($S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)
    ;Erstellen einer Pufferstruktur
    $S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = DllStructCreate("byte puffer[" & $StructSize * $CpuNum & "]")
    ;Pointer zum wiederfinden
    $pointer = DllStructGetPtr($S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)

    [/autoit] [autoit][/autoit] [autoit]

    $err = DllCall("ntdll.dll", "int", "NtQuerySystemInformation", _
    "int", $SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION, _
    "ptr", DllStructGetPtr($S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), _
    "int", DllStructGetSize($S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), _
    "int", 0)

    [/autoit] [autoit][/autoit] [autoit]

    ;Fehler beim 2. Dll Aufruf
    If $err[0] Then
    Return $RetArr[0] = -2
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    ;Struktur und Dll-Aufruf für Zeitmessung
    Local $S_SYSTEM_TIME_INFORMATION = DllStructCreate("int64;int64;int64;uint;int")
    $err = DllCall("ntdll.dll", "int", "NtQuerySystemInformation", _
    "int", $SYSTEM_TIME_INFO, _
    "ptr", DllStructGetPtr($S_SYSTEM_TIME_INFORMATION), _
    "int", DllStructGetSize($S_SYSTEM_TIME_INFORMATION), _
    "int", 0)

    ;Fehler beim 3. Dll Aufruf
    If $err[0] Then
    Return $RetArr[0] = -3
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    If $starttime = 0 Then ;Startwerte Ermitteln
    ReDim $IdleOldArr[$CpuNum]
    ;Start-Informationen Auslesen
    For $i = 0 to $CpuNum -1
    $tmpStruct = DllStructCreate($tagS_SPPI, $Pointer + $i*$StructSize)
    $IdleOldArr[$i] = DllStructGetData($tmpStruct,"IdleTime")
    Next
    ;Startzeit ermitteln
    $starttime = DllStructGetData($S_SYSTEM_TIME_INFORMATION, 2)
    ;Verzögerung um einen Zeitunterschied sicher zu stellen
    Sleep(100)
    Else
    ReDim $IdleNewArr[$CpuNum]
    ;Neue Informationen auslesen
    For $i = 0 to $CpuNum -1
    $tmpStruct = DllStructCreate($tagS_SPPI, $Pointer + $i*$StructSize)
    $IdleNewArr[$i] = DllStructGetData($tmpStruct,"IdleTime")
    Next

    ;Zeitdifferenze in 100 nanosekunden
    $timediff = DllStructGetData($S_SYSTEM_TIME_INFORMATION, 2) - $starttime

    ;Auslastung errechnen
    For $i=0 to $CpuNum -1
    $RetArr[$i+1] = Round(100-(($IdleNewArr[$i] - $IdleOldArr[$i]) * 100 / $timediff))
    Next

    Return $RetArr
    EndIf

    ;Speicher wieder frei geben
    $S_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = 0
    $S_SYSTEM_TIME_INFORMATION = 0
    $tmpStruct = 0
    WEnd
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ;#####################################################################
    ;#
    ;# Funtion _GetCPUUsageOld()
    ;# Ermittelt die Auslastung der CPU (Wie im Taskmanager angezeigt)
    ;#
    ;# Rückgabewert: Auslastung der CPU in Prozent, Fehler wenn negativ
    ;# Fehler: -1 Fehler beim 1. Dll Aufruf
    ;# -2 Fehler beim 2. Dll Aufruf
    ;# -3 Fehler beim 3. Dll Aufruf
    ;#
    ;# Hinweis: Mit zukünftigen Windowsversion eventuell nicht kompatibel,
    ;# Ungenau bei Multi-Prozessorsystemen
    ;#
    ;# Autor: ???
    ;#####################################################################
    Func _GetCPUUsageOld()
    Local Const $SYSTEM_BASIC_INFO = 0
    Local Const $SYSTEM_PERFORMANCE_INFO = 2
    Local Const $SYSTEM_TIME_INFO = 3

    [/autoit] [autoit][/autoit] [autoit]

    Local $idletimeold = 0,$idletimediff=0, $idle
    Local $starttime = 0, $timediff = 0, $CPUCount=0

    ;Struktur für Basis Informationen
    $S_SYSTEM_BASIC_INFORMATION = DllStructCreate("int;uint;uint;uint;uint;uint;uint;ptr;ptr;uint;byte;byte;short")
    $err = DllCall("ntdll.dll", _
    "int", "NtQuerySystemInformation", _
    "int", $SYSTEM_BASIC_INFO, _
    "ptr", DllStructGetPtr($S_SYSTEM_BASIC_INFORMATION), _
    "int", DllStructGetSize($S_SYSTEM_BASIC_INFORMATION), _
    "int", 0)

    If $err[0] Then Return -1

    $CPUCount = DllStructGetData($S_SYSTEM_BASIC_INFORMATION, 11)
    $S_SYSTEM_BASIC_INFORMATION = 0

    While 1
    ;Struktur für CPU Zeit
    Local $S_SYSTEM_PERFORMANCE_INFORMATION = DllStructCreate("int64;int[76]")
    $err = DllCall("ntdll.dll", "int", "NtQuerySystemInformation", "int", $SYSTEM_PERFORMANCE_INFO, _
    "ptr", DllStructGetPtr($S_SYSTEM_PERFORMANCE_INFORMATION), _
    "int", DllStructGetSize($S_SYSTEM_PERFORMANCE_INFORMATION), _
    "int", 0)
    If $err[0] Then Return -2

    ;Struktur für Zeitmessung
    Local $S_SYSTEM_TIME_INFORMATION = DllStructCreate("int64;int64;int64;uint;int")
    $err = DllCall("ntdll.dll", "int", "NtQuerySystemInformation", _
    "int", $SYSTEM_TIME_INFO, _
    "ptr", DllStructGetPtr($S_SYSTEM_TIME_INFORMATION), _
    "int", DllStructGetSize($S_SYSTEM_TIME_INFORMATION), _
    "int", 0)

    If $err[0] Then Return -3

    ;Startwerte ermitteln
    If $starttime = 0 Then
    $idletimeold = DllStructGetData($S_SYSTEM_PERFORMANCE_INFORMATION, 1)
    $starttime = DllStructGetData($S_SYSTEM_TIME_INFORMATION, 2)
    Sleep(100)
    Else
    ; "Aktuelle" Werte ermitteln
    $idletimediff = DllStructGetData($S_SYSTEM_PERFORMANCE_INFORMATION, 1) - $idletimeold
    $timediff = DllStructGetData($S_SYSTEM_TIME_INFORMATION, 2) - $starttime

    ;Berechnung
    $idle = Round(100.0 - ($idletimediff * 100.0 / $timediff)/$CPUCount,0)
    Return $idle
    EndIf
    $S_SYSTEM_PERFORMANCE_INFORMATION = 0
    $S_SYSTEM_TIME_INFORMATION = 0
    WEnd
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ;####################################################
    ;#
    ;# Funtion _GetMemUsage()
    ;# Ermittelt die Ram-Auslastung in Prozent
    ;#
    ;# Rückgabewert: Array [0] Physikalischer Speicher
    ;# [1] Auslagerungsdatei
    ;# [2] Virtueller Speicher
    ;#
    ;# Autor: Bitboy
    ;####################################################
    Func _GetMemUsage()
    Local $memstat, $retArr[3]
    $memstat = MemGetStats ()

    [/autoit] [autoit][/autoit] [autoit]

    $retArr[0] = $memstat[0] ;Physikalischer Speicher
    $retArr[1] = Round(100 - ($memstat[4] / $memstat[3] * 100)) ;Auslagerungsdatei
    $retArr[2] = Round(100 - ($memstat[6] / $memstat[5] * 100)) ;Virtueller Speicher

    [/autoit] [autoit][/autoit] [autoit]

    Return $retArr
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ;####################################################
    ;#
    ;# Funtion _GetNetUsage()
    ;# Ermittelt die Auslastung aller _Ethernet_ Adapter
    ;#
    ;# Rückgabewert: Array [n][0] Name des Adapters
    ;# [n][1] Auslastung in Prozent
    ;#
    ;# Autor: Bitboy
    ;####################################################
    Func _GetNetUsage()
    Local $netarrold[1], $netarr[1], $tmparr, $retarr[1]
    Local $AdapterNum, $i, $x, $timediff,$netcounter=0, $InOutBytes

    [/autoit] [autoit][/autoit] [autoit]

    For $i =1 to 2
    $tmparr = _GetNetAdapterInfo($AdapterNum)
    If $i = 1 Then
    ReDim $netarrold[$AdapterNum][2]
    $timediff = TimerInit()
    For $x = 0 to $AdapterNum -1
    If $tmparr[$x][2] = 6 Then ;Ethernet Adapter
    ;InOctets
    $netarrold[$netcounter][0] = $tmparr[$x][10]
    ;OutOctets
    $netarrold[$netcounter][1] = $tmparr[$x][16]
    $netcounter = $netcounter + 1
    EndIf
    Next
    Sleep(100)
    Else
    $timediff = TimerDiff($timediff)
    ReDim $retarr[$netcounter][2]
    $netcounter = 0
    For $x = 0 to $AdapterNum -1
    If $tmparr[$x][2] = 6 Then ;Ethernet Adapter
    ;Adapternamen speichern
    $retarr[$netcounter][0] = $tmparr[$x][23]
    ;Auslastung = ((InOctets + Outoctets) - (InOctetsOld + OutoctetsOld) / timediff/1000) / Netspeed * 8 * 100
    $InOutBytes = ($tmparr[$x][10] + $tmparr[$x][16])-($netarrold[$netcounter][0] + $netarrold[$netcounter][1])
    $retarr[$netcounter][1] = Round(($InOutBytes / ($timediff /1000)) / $tmparr[$x][4] * 8 *100)
    $netcounter = $netcounter + 1
    EndIf
    Next
    Return $retarr
    EndIf
    Next
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ;################################################################################################################
    ;#
    ;# Funktion _GetNetAdapterInfo (ByRef $AdapterNum)
    ;# Interne Funktion zum Ermitteln von Netzwerkadapter Informationen
    ;#
    ;# Rückgabewerte: Array [n][0] wszName, meist (immer?) leer
    ;# Array [n][1] dwIndex, Indexnummer des Adapters
    ;# Array [n][2] dwType, Typ des Adapters, Siehe Konstanten
    ;# Array [n][3] dwMTU, Maximale Paketgröße
    ;# Array [n][4] dwSpeed, Geschwindigkeit in Bit/Sekunde
    ;# Array [n][5] dwPhysAddrLen, Länge der Physikalischen (MAC) Adresse (Immer 8)
    ;# Array [n][6] bPhysAddr, Physikalische (MAC) Adresse
    ;# Array [n][7] dwAdminStatus, Verbindung aktiviert? 1 oder 0
    ;# Array [n][8] dwOperStatus,???
    ;# Array [n][9] dwLastChange, Letzte Veränderung seit Systemstart in hunderstel-sekunden
    ;# Array [n][10] dwInOctets, Empfangene Oktetts (1 Oktett = 8 Bit = 1 Byte)
    ;# Array [n][11] dwInUcastPkts, Empfangene Unicast Pakete
    ;# Array [n][12] dwInNUcastPkts, Empfangene Non-Unicast Pakete
    ;# Array [n][13] dwInDiscards, Empfangene und verworfene Pakete
    ;# Array [n][14] dwInErrors, Empfangene und verworfene fehlerhafte Pakete
    ;# Array [n][15] dwInUnknownProtos, Empfangene und verworfene Pakete wegen unbekanntem Protokoll
    ;# Array [n][16] dwOutOctets, Gesendete Oktetts (1 Oktett = 8 Bit = 1 Byte)
    ;# Array [n][17] dwOutUcastPkts, Gesendete Unicast Pakete
    ;# Array [n][18] dwOutNUcastPkts, Gesendete Non-Unicast Pakete
    ;# Array [n][19] dwOutDiscards, Gesendete und verworfene Pakete
    ;# Array [n][20] dwOutErrors, Gesendete und verworfene fehlerhafte Pakete
    ;# Array [n][21] dwOutQLen, Übertragungswarteschlange (derzeit unbenutzt)
    ;# Array [n][22] dwDescrLen, Zeichenlänge des Adapternamens (Konstant 256)
    ;# Array [n][23] bDescr, Adaptername
    ;# $AdapterNum enthält die Anzahl der Adapter [n]
    ;#
    ;# Autor: Progandy (autoit.de), Bitboy
    ;################################################################################################################
    Func _GetNetAdapterInfo(ByRef $AdapterNum)
    ;Konstantendeklaration
    ;Netzwerk Adapter Typen
    Local Const $MIB_IF_TYPE_OTHER = 1 ;Some other type of network interface.
    Local Const $MIB_IF_TYPE_ETHERNET = 6 ;An Ethernet network interface.
    Local Const $MIB_IF_TYPE_TOKENRING = 9 ;A token ring network interface.
    Local Const $MIB_IF_TYPE_PPP = 23 ;A PPP network interface.
    Local Const $MIB_IF_TYPE_LOOPBACK = 24 ;A software loopback network interface.
    Local Const $MIB_IF_TYPE_ATM = 37 ;An ATM network interface.
    Local Const $MIB_IF_TYPE_IEEE80211 = 73 ;An IEEE 802.11 wireless network interface.
    Local Const $MIB_IF_TYPE_TUNNEL = 131 ;A tunnel type encapsulation network interface.
    Local Const $MIB_IF_TYPE_IEEE1394 = 144 ;An IEEE 1394 (Firewire) high performance serial bus network interface.

    [/autoit] [autoit][/autoit] [autoit]

    Local Const $MAX_INTERFACE_NAME_LEN = 256
    Local Const $MAXLEN_IFDESCR = 256
    Local Const $MAXLEN_PHYSADDR = 8

    [/autoit] [autoit][/autoit] [autoit]

    ;Struktur für Tabellenzeile
    Local Const $tagMIB_IFROW = _
    "WCHAR wszName[" & $MAX_INTERFACE_NAME_LEN & "]; " & _
    "DWORD dwIndex;" & _
    "DWORD dwType;" & _
    "DWORD dwMtu;" & _
    "DWORD dwSpeed;" & _
    "DWORD dwPhysAddrLen;" & _
    "BYTE bPhysAddr[" & $MAXLEN_PHYSADDR & "];" & _
    "DWORD dwAdminStatus;" & _
    "DWORD dwOperStatus;" & _
    "DWORD dwLastChange;" & _
    "DWORD dwInOctets;" & _
    "DWORD dwInUcastPkts;" & _
    "DWORD dwInNUcastPkts;" & _
    "DWORD dwInDiscards;" & _
    "DWORD dwInErrors;" & _
    "DWORD dwInUnknownProtos;" & _
    "DWORD dwOutOctets;" & _
    "DWORD dwOutUcastPkts;" & _
    "DWORD dwOutNUcastPkts;" & _
    "DWORD dwOutDiscards;" & _
    "DWORD dwOutErrors;" & _
    "DWORD dwOutQLen;" & _
    "DWORD dwDescrLen;" & _
    "CHAR bDescr[" & $MAXLEN_IFDESCR & "];"

    ;Deklaration der Struktur MIB_IFTABLE
    Local Const $tagMIB_IFTABLE_HEADER = "DWORD dwNumEntries;"

    [/autoit] [autoit][/autoit] [autoit]

    ;Variablendeklaration
    Local $puffersize = 0, $StructSize = 0, $i=0, $j=0, $AdapterCount = 0
    Local $puffer, $Pointer, $retArr[1][1]
    Local $MIB_IFROW = DllStructCreate($tagMIB_IFROW)

    [/autoit] [autoit][/autoit] [autoit]

    ;Erster Aufruf (wird fehlschlagen) liefert dabei aber die benötigte Puffergröße
    Local $err = DllCall("Iphlpapi.dll", "dword","GetIfTable","ptr", 0, "ulong*", 0,"long",1)
    If Not @error Then
    If $err[0] = 122 Then ;Fehler: Puffer zu klein
    $puffersize = $err[2]
    EndIf
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    ;Erstellen der Struktur MIB_IFTABLE mit passender Puffergröße
    Local $MIB_IFTABLE = DllStructCreate($tagMIB_IFTABLE_HEADER & "byte puffer[" & $puffersize &"]")

    [/autoit] [autoit][/autoit] [autoit]

    ;Zweiter Aufruf
    $err = DllCall("Iphlpapi.dll", "dword","GetIfTable","ptr", DllStructGetPtr($MIB_IFTABLE), "ulong*", DllStructGetSize($MIB_IFTABLE),"long",1)

    [/autoit] [autoit][/autoit] [autoit]

    ;Größe der Struktur und die Anzahl der Adapter speichern
    $AdapterCount = DllStructGetData($MIB_IFTABLE, "dwNumEntries")
    $AdapterNum = $AdapterCount
    $StructSize = DllStructGetSize($MIB_IFROW)

    [/autoit] [autoit][/autoit] [autoit]

    $Pointer = DllStructGetPtr($MIB_IFTABLE, "puffer")
    ReDim $retArr[$AdapterCount][24]
    For $i = 0 To $AdapterCount-1
    $MIB_IFROW = DllStructCreate($tagMIB_IFROW, $Pointer + $i*$StructSize)
    For $j = 0 To 23
    If $j = 6 Then
    $retArr[$i][$j] = StringMid(DllStructGetData($MIB_IFROW, $j+1),3,12)
    Else
    $retArr[$i][$j] = DllStructGetData($MIB_IFROW, $j+1)
    EndIf
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $MIB_IFROW = 0
    $MIB_IFTABLE = 0

    [/autoit] [autoit][/autoit] [autoit]

    Return $retArr
    EndFunc

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    #cs
    ;Folgender Code dient nur zum Testen und kann gelöscht werden

    [/autoit] [autoit][/autoit] [autoit]

    #include<Array.au3>
    Local $Count

    [/autoit] [autoit][/autoit] [autoit]

    ;CPU-Auslastung
    $test = _GetCPUUsage()
    _ArrayDisplay ($test)

    [/autoit] [autoit][/autoit] [autoit]

    ;Alte CPU-Auslastung
    MsgBox(0,"CPU Auslastung", _GetCPUUsageOld()&"%")

    [/autoit] [autoit][/autoit] [autoit]

    ;Ram-Auslastung
    $array = _GetMemUsage()
    _ArrayDisplay($array)

    [/autoit] [autoit][/autoit] [autoit]

    ;Netzwerkauslastung
    $array = _GetNetUsage()
    _ArrayDisplay($array)

    [/autoit] [autoit][/autoit] [autoit]

    ;Netwerkinfo
    $array = _GetNetAdapterInfo($Count)
    ; Namen zur Info einfügen
    ReDim $array[$Count+1][UBound($array,2)]
    $array[$Count][0] = "wszName"
    $array[$Count][1] = "dwIndex"
    $array[$Count][2] = "dwType"
    $array[$Count][3] = "dwMtu"
    $array[$Count][4] = "dwSpeed"
    $array[$Count][5] = "dwPhysAddrLen"
    $array[$Count][6] = "bPhysAddr"
    $array[$Count][7] = "dwAdminStatus"
    $array[$Count][8] = "dwOperStatus"
    $array[$Count][9] = "dwLastChange"
    $array[$Count][10] = "dwInOctets"
    $array[$Count][11] = "dwInUcastPkts"
    $array[$Count][12] = "dwInNUcastPkts"
    $array[$Count][13] = "dwInDiscards"
    $array[$Count][14] = "dwInErrors"
    $array[$Count][15] = "dwInUnknownProtos"
    $array[$Count][16] = "dwOutOctets"
    $array[$Count][17] = "dwOutUcastPkts"
    $array[$Count][18] = "dwOutNUcastPkts"
    $array[$Count][19] = "dwOutDiscards"
    $array[$Count][20] = "dwOutErrors"
    $array[$Count][21] = "dwOutQLen"
    $array[$Count][22] = "dwDescrLen"
    $array[$Count][23] = "bDescr"

    [/autoit] [autoit][/autoit] [autoit]

    _ArrayDisplay($array)

    [/autoit] [autoit][/autoit] [autoit]

    #ce

    [/autoit] [autoit][/autoit] [autoit][/autoit]

    Edit: Jetzt MIT Rahmen :P

    Edit: Tippfehler korrigiert.

    Hinweis: Bei einem Dualcore geht die Auslastung nicht über 50 %, die Gesamtauslastung wird nämlich durch die Anzahl der Prozessoren gerechnet. Quadcore ist ungetestet.

    6 Mal editiert, zuletzt von Bitboy (7. April 2009 um 14:11)

  • sehr schöne UDF großes lob von mir hänge sich aber pls noch als datei an :) dann muss man nicht immer alles kopieren :P

  • Den Rahmen machst du, indem du den nciht in den Beitrag mit einfügst, einfach im Anhang lassen ;)

    Sieht gut aus :D