Softwareinventarisierung

  • Hallo an alle begeisterten AutoIt-Freunde,

    ich befasse mich mit der Inventarisierung aller Rechner in unserem Unternehmen und habe dafür mal ein Skript geschrieben.

    Dieses Skript öffnet ein Konsole-Fenster und gibt dort die einzelnen Schritte aus.

    Als Quelle Dient derzeit noch eine *.txt-Datei in der alle Rechner eingetragen werden.

    Habe das Skript in zwei Hauptfunktionen unterteilt, eine Funktion fragt alle Rechner ab und sucht nach den installierten Betriebssystemen.

    Die zweite Funktion fragt alle Rechner ab und sucht nach der gesamten installierten Software.

    Im Anschluss an jede Funktion werden die Daten zusammengefasst und per HTTP-POST Methode an einen Webservice gesendet.

    Als Frontend dient eine Webansicht in der man übersichtlich den jeweiligen Rechner auswählen kann und die Installierte Software sehen kann.

    Ich würde mich freuen, wenn die Profis unter euch mal einen Blick auf den Code werfen könnten und Verbesserungsvorschläge liefern.

    Die Laufzeit der Funktion zur Abfrage der installierten Betriebssysteme ist zufriedenstellend.

    Leider ist die Laufzeit der Funktion zur Abfrage der installierten Software noch sehr hoch.

    Schaut euch den Code mal an.

    Ich freue mich auf eure Kommentare.

    • Offizieller Beitrag

    Tipp: Pfadangaben nicht hardcoden.

    Deine *.txt und deine Logfiles sind fest im Skript codiert. Ich würde das flexibel gestalten (mit INI oder Programmparameter)

    - wenn INI vorhanden --> verwende Pfade von dort -->sonst: @ScriptDir & "\..."

    Oder

    - wenn Startparameter übergeben ( /txt='Pfad\Datei.txt' /log1='Pfad\Log1Datei.log' /log2='Pfad\Log2Datei.log' - oder ähnlich) --> verwende diese Pfade, sonst: s.o.

  • Hallo Lashandan

    Mir sind zwei Dinge aufgefallen (wobei dich wohl eines nur interessieren wird :D).

    Das erste ist das:

    AutoIt
    ConsoleWrite("Falsche Eingabe! Erwartet wird 1 oder 2! Sie haben " & $input & " eingegeben!" & @CRLF)
    Sleep(1000)
    ConsoleWrite("Skript schliesst in 3" & @CRLF)
    Sleep(1000)
    ConsoleWrite("Skript schliesst in 2" & @CRLF)
    Sleep(1000)
    ConsoleWrite("Skript schliesst in 1" & @CRLF)
    Sleep(1000)

    Das könnte man in eine Schleife packen - auch wenn das hier noch keinen großen Unterschied macht:

    Code
    ConsoleWrite("Falsche Eingabe! Erwartet wird 1 oder 2! Sie haben " & $input & " eingegeben!" & @CRLF)
    Sleep(1000)
    For $i = 3 To 1 Step -1
        ConsoleWrite("Skript schliesst in " & $i & @CRLF)
        Sleep(1000)
    Next

    Das nächste betrifft deine Funktionen, OSList und Softwarelist. Hier hast du jeweils ein FileClose am Ende der Funktion. Aber zuvor hast du die Datei nie mit FileOpen geöffnet und du arbeitest daher auch nicht mit dem Filehandle, das von dieser Funktion zurückgegeben wird, sondern mit dem Dateipfad. Daher werden die Schritte FileOpen/FileClose bei jeder Anwendung der Funktion FileWriteLine durchgeführt. Je mehr Einträge, desto länger dauert das...

    Ich bin sicher, die Korrektur dieses kleinen Fehlers macht dein Skript schon schneller. Darüber hinaus würde ich den langen, sich wiederholenden Teil der Ausgabe, am Anfang in eine Variable schreiben und diese dann benutzen. Dann wirkt der Code nicht so erschlagend.

    Hier am Bsp deiner OSList-Funktion (mehr Auswirkungen hat das sicher bei Softwarelist. Da ist es aber genau gleich zu ändern.):

    (Achtung: Bei FileOpen benutze ich die Konstante $FO_OVERWRITE. Entweder du schreibst stattdessen 2 oder du fügst oben im Skript #include <FileConstants.au3> ein.)

    Grüße autoiter

    Einmal editiert, zuletzt von autoiter (20. Juli 2018 um 18:20)

  • Da dein Webservice POST Daten entgegennimmt ohne vorherige Authentifizierung (hab jedenfalls beim Überfliegen nichts davon gesehn) ließe sich das ganze massiv paralelisieren.

    Anstatt das Script zentral von einer Adminstation auszuführen könnte auch jeder Clientrechner im Netzwerk seine eigenen Scriptinstanz ausführen und seine Daten selbsständig an den Webservice melden. So machen das auch verbreitete "professionelle" Lösungen wie z.B. OCS Inventory und GLPI

    Vorteil:

    - Alle Clients melden paralell, somit extreme Zeitersparnis

    - Alle Clients könnten periodisch Updates über ihre derzeitige Konfiguration versenden (z.B. über täglichen Windows Task), somit hast du stets aktuelle Daten über die Softwarekonfiguration

    Nachteil bzw. Mehraufwand:

    - Das Script muss einmalig auf dem Clientrechner platziert und entsprechend als Task eingerichtet werden

    - Der Webservice bzw. dessen API muss im kompletten Netzwerk verfügbar sein (evtl. Probleme mit Clients die mobil und nicht immer im lokalen Netzwerk angemeldet sind)

    - Missbrauchspotential, da Webservice ohne Authentifizierung ansprechbar ist -> evtl. richtige API mit Authentifizierung einsetzen

    - Client Script sollte über Selbstupdate Funktion verfügen, damit du einfach und schnell alle Clients austauschen kannst wenn du eine Änderung des Clients benötigst

    - Fehlermeldungen vielleicht besser unsichtbar per Email versenden anstatt per msgbox ausgeben, um Benutzer nicht zu verwirren

    Einmal editiert, zuletzt von misterspeed (20. Juli 2018 um 18:58)

  • Tipp: Pfadangaben nicht hardcoden.

    Deine *.txt und deine Logfiles sind fest im Skript codiert. Ich würde das flexibel gestalten (mit INI oder Programmparameter)

    - wenn INI vorhanden --> verwende Pfade von dort -->sonst: @ScriptDir & "\..."

    Oder

    - wenn Startparameter übergeben ( /txt='Pfad\Datei.txt' /log1='Pfad\Log1Datei.log' /log2='Pfad\Log2Datei.log' - oder ähnlich) --> verwende diese Pfade, sonst: s.o.

    Hey BugFix - vielen Dank für deine Antwort.

    Danke für den Hinweis. Habe es berücksichtigt und verwende künftig keine harten Pfadangaben ;)

    Das nächste betrifft deine Funktionen, OSList und Softwarelist. Hier hast du jeweils ein FileClose am Ende der Funktion. Aber zuvor hast du die Datei nie mit FileOpen geöffnet und du arbeitest daher auch nicht mit dem Filehandle, das von dieser Funktion zurückgegeben wird, sondern mit dem Dateipfad. Daher werden die Schritte FileOpen/FileClose bei jeder Anwendung der Funktion FileWriteLine durchgeführt. Je mehr Einträge, desto länger dauert das...

    Ich bin sicher, die Korrektur dieses kleinen Fehlers macht dein Skript schon schneller. Darüber hinaus würde ich den langen, sich wiederholenden Teil der Ausgabe, am Anfang in eine Variable schreiben und diese dann benutzen. Dann wirkt der Code nicht so erschlagend.

    Hier am Bsp deiner OSList-Funktion (mehr Auswirkungen hat das sicher bei Softwarelist. Da ist es aber genau gleich zu ändern.):

    (Achtung: Bei FileOpen benutze ich die Konstante $FO_OVERWRITE. Entweder du schreibst stattdessen 2 oder du fügst oben im Skript #include <FileConstants.au3> ein.)

    Auch diesen Hinweis von dir habe ich berücksichtigt und angewandt.

    Ich ging wohl einfach davon aus, dass ein FileOpen gar nicht mehr notwendig wäre, wenn man die Variable zum Pfad bzw. zum Logfile angibt.

    Aber es macht alles nur Sinn was du schreibst:

    Funktion starten, File öffnen, offen lassen, Schreibvorgänge vornehmen und am Ende erst wieder schließen.

    Ich habe die neue Funktion auch direkt mal getestet...allerdings habe ich immer noch Schwierigkeiten mit der Performance.

    Also hat es nicht unbedingt zu einer Besserung geführt, den Code aber wesentlich ordentlicher und richtiger gemacht.

    Die Performance bei der OSList()-Funktion läuft problemlos und sehr schnell. Innerhalb von 3 Minuten waren 190 Rechner abgefragt.

    Weiterhin habe ich weitere 28 Rechner abgefragt und eine Softwareliste erstellen lassen, hier mal die Zeiten dazu:

    Uhrzeit Status Dauer
    10:45:54 NDEST003 Erfolg : Eintraege wurden vorgenommen 00:00:28
    10:47:18 NDEST005 Erfolg : Eintraege wurden vorgenommen 00:01:24
    10:47:20 NDEST009 Erfolg : Eintraege wurden vorgenommen 00:00:02
    10:47:48 NDEST044 Erfolg : Eintraege wurden vorgenommen 00:00:28
    10:50:36 NDEST054 Erfolg : Eintraege wurden vorgenommen 00:02:48
    10:51:00 NDEST055 Erfolg : Eintraege wurden vorgenommen 00:00:24
    10:51:29 NDEST056 Erfolg : Eintraege wurden vorgenommen 00:00:29
    10:53:34 NDEST058 Erfolg : Eintraege wurden vorgenommen 00:02:05
    10:55:24 NDEST059 Erfolg : Eintraege wurden vorgenommen 00:01:50
    10:56:01 NDEST061 Erfolg : Eintraege wurden vorgenommen 00:00:37
    10:57:29 NDEST066 Erfolg : Eintraege wurden vorgenommen 00:01:28
    10:58:04 NDEST071 Erfolg : Eintraege wurden vorgenommen 00:00:35
    10:58:30 NDEST078 Erfolg : Eintraege wurden vorgenommen 00:00:26
    10:59:23 SDESTAPP3 Erfolg : Eintraege wurden vorgenommen 00:00:53
    10:59:57 SDESTAPP4 Erfolg : Eintraege wurden vorgenommen 00:00:34
    11:01:33 SDESTFELIX Erfolg : Eintraege wurden vorgenommen 00:01:36
    11:02:14 SDESTFIL03 Erfolg : Eintraege wurden vorgenommen 00:00:41
    11:02:59 SDESTIZDQ Erfolg : Eintraege wurden vorgenommen 00:00:45
    11:03:37 SDESTLSA Erfolg : Eintraege wurden vorgenommen 00:00:38
    11:04:20 SDESTMXS02 Erfolg : Eintraege wurden vorgenommen 00:00:43
    11:04:50 SDESTPAAPP1 Erfolg : Eintraege wurden vorgenommen 00:00:30
    11:05:08 SDESTPAD1 Erfolg : Eintraege wurden vorgenommen 00:00:18
    11:10:00 SDESTSQL02 Erfolg : Eintraege wurden vorgenommen 00:04:52
    11:11:44 SDESTSQL03 Erfolg : Eintraege wurden vorgenommen 00:01:44
    11:12:47 SDESTSQL04 Erfolg : Eintraege wurden vorgenommen 00:01:03
    11:15:46 VDEST1001 Erfolg : Eintraege wurden vorgenommen 00:02:59
    11:17:50 VDEST1003 Erfolg : Eintraege wurden vorgenommen 00:02:04
    11:19:10 VDEST1004 Erfolg : Eintraege wurden vorgenommen 00:01:20

    Ich kann mir einfach noch nicht so recht erklären, wodurch diese extrem unterschiedlichen Zeiten zustande kommen.

    Aber vllt. hat ja jemand von euch noch eine Idee hinsichtlich der Optimierung.

    Hey misterspeed - dir auch vielen Dank für deine Bemühungen.

    Hierzu muss ich aber noch erwähnen, dass ich nur für die Teststellung keine Authentifizierung nutze.

    Leider überwiegen die Nachteile bei der von dir vorgeschlagenen Lösung.

    Allerdings habe ich noch folgendes vor...dadurch, dass ja ein Webservice und eine SQL-Datenbank die ganze Geschichte unterstützt, habe ich später keine *.txt-Datei mehr in der die abzufragenden Rechner stehen, sondern eine SQL-Tabelle in der alle Rechner mittels IP-Range ermittelt werden.

    Dann starte ich mein Skript als Dienst in 6-10 verschiedene Instanzen (die Anzahl muss ich mir ausprobieren) und jedesmal, wenn sich das Skript einen Rechner holt, setzt der Webservice einen Eintrag in der Datenbank, dass dieser Rechner bereits abgefragt wird. Hat die erste Instanz dann die benötigten Informationen, wird die Softwareliste in die Datenbank geschrieben und der Webservice löscht den abgefragten Rechner.

    Somit erhöht sich die Performance eben um die Anzahl der Instanzen/Dienste die gestartet werden.

    • Offizieller Beitrag

    hallo,

    ich würde bei der Vielzahl von Datumsangaben das einmal in eine Variable packen und die verwenden, statt das immer und immer wieder zu tippen.

  • IHallo admin

    ganz so sollte man es aber nicht machen. Der TE lässt sich ja unter anderem auch Konsolenausgaben mit Zeitstempel ausgeben. Wenn man die Variable einmal vor der Schleife füllt, hat man auch immer die gleiche Zeit. Das macht eine sekundengenaue Zeitangabe an dieser Stelle sinnlos.

    Grüße autoiter

    • Offizieller Beitrag

    Naja an der Stelle ist das, denke ich, kein Problem, da die Funktion ja je Rechner einmal aufgerufen wird und die Logeinträge am Ende nicht mehr erzeugt werden sollen. Am Ende gibt es ziemlich genau 2 Einträge, die das Datum verwenden, abseits des Names der Logdatei. Und die einfache Abfrage des Betriebssystems wird wohl keine 5 Minuten dauern, sondern eher im Millisekundenbereich liegen.

    Von daher sehe ich in der Funktion kein Problem darin, das fürs Debugging in der Funktion einmal zu definieren.

  • hallo,

    ich würde bei der Vielzahl von Datumsangaben das einmal in eine Variable packen und die verwenden, statt das immer und immer wieder zu tippen.

    Das werde ich auf jeden Fall noch berücksichtigen,

    vielen Dank für den Hinweis.

  • <push>

    Hat noch jemand von euch Ideen zur Optimierung der Performance, ganz speziell in der Funktion der Softwareliste?!

    Das Abfragen der installierten Betriebssysteme ist innerhalb weniger Minuten erledigt und kein Problem.
    Nur das Abfragen der installierten Software ist einfach unglaublich lang.

    Selbst wenn die Quelldatei vorher schon nur mit "online" Rechnern gefüllt ist, dauert es mehrere Stunden.

    <push>

  • Hallo Lashandan

    Nur das Abfragen der installierten Software ist einfach unglaublich lang.

    Selbst wenn die Quelldatei vorher schon nur mit "online" Rechnern gefüllt ist, dauert es mehrere Stunden.

    In Beitrag 5 sieht man ja, dass es auch mal fast 5 Minuten dauert, bevor ein Rechner abgefragt ist. Diese enormen Schwankungen könntest du mal untersuchen. Das erklärt sich doch nicht mit enorm viel Software, die aufzulisten wäre, oder?

    An deiner Stelle würde ich mal testen, die Funktion Softwarelist in eine eigene Anwendung zu packen. Die Funktion sollte dann auch keine Schleife abarbeiten, sondern immer einen Rechner abfragen (IP kannst du ja per Startparameter mitgeben). Aus deinem Hauptskript rufst du dann immer wieder diese Anwendung auf. Vielleicht wartest du dann blockweise etwas ab und startest dann die Auflistung eines neuen Blocks (einfach damit die Anwendung nicht 130 mal läuft). Um den Arbeitsstatus im Hauptskript zu prüfen, musst du dir noch eine Kommunikation überlegen.

    Grüße autoiter

  • Hallo Lashandan

    In Beitrag 5 sieht man ja, dass es auch mal fast 5 Minuten dauert, bevor ein Rechner abgefragt ist. Diese enormen Schwankungen könntest du mal untersuchen. Das erklärt sich doch nicht mit enorm viel Software, die aufzulisten wäre, oder?

    An deiner Stelle würde ich mal testen, die Funktion Softwarelist in eine eigene Anwendung zu packen. Die Funktion sollte dann auch keine Schleife abarbeiten, sondern immer einen Rechner abfragen (IP kannst du ja per Startparameter mitgeben). Aus deinem Hauptskript rufst du dann immer wieder diese Anwendung auf. Vielleicht wartest du dann blockweise etwas ab und startest dann die Auflistung eines neuen Blocks (einfach damit die Anwendung nicht 130 mal läuft). Um den Arbeitsstatus im Hauptskript zu prüfen, musst du dir noch eine Kommunikation überlegen.

    Moin autoiter

    kannst du mir deinen Vorschlag noch etwas genauer beschreiben wie ich vorgehen muss?
    Habe noch nie eine Anwendung ausgelagert. Läuft das ganze dann mit includes?

  • Hallo Lashandan,

    das Performance Problem liegt an der Abfrage der Installierten Software via wmi query der Win32_product Klasse. Diese dauert immer sehr lange! Aufgepasst es kann unter umständer auch passieren, dass die Software sich repariert, da mit einer query auf Win32_product auch ein consistency check gemacht wird.

    Hier ein auszug von Microsoft:

    Warning  Win32_Product is not query optimized. Queries such as "select * from Win32_Product where (name like 'Sniffer%')" require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause. This process also initiates a consistency check of packages installed, verifying and repairing the install. With an account with only user privileges, as the user account may not have access to quite a few locations, may cause delay in application launch and an event 11708 stating an installation failure. For more information, see KB Article 794524.

    Eine andere Methode, die ich bei der Software Paketierung verwende ist, die uninstall registry keys der installierten Software abzufragen:

    (x86)

    HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\

    (x64)

    HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\


    Hier einmal ein Auszug aus einem Powershell-Script, vielleicht kannst du ja etwas von gebrauchen:

    Viele Grüße,

    Digitalkarl

    • Offizieller Beitrag

    Eine andere Methode, die ich bei der Software Paketierung verwende ist, die uninstall registry keys der installierten Software abzufragen:

    Dafür hätte ich auch eine AutoIt-Funktion:

    Aber das muss dann auf jedem Rechner laufen.

    Allerdings könnte man das als Hintergrundprogramm auf jedem Rechner installieren und es wartet dann auf eine TCP-Nachricht, um die Funktion auszuführen und an das Serverscript zu senden.

  • psinfo... gute Idee... und das bei der Hitze... lol... hm, zeigt bei mir einige Einträge doppelt an?!

    Hallo Bitnugger ,

    ich habe versucht das nachzuvollziehen. Allerdings konnte ich das bei mir (nur auf meinem lokalen Rechner) nicht nachvollziehen. Hier sieht alles gut aus.

    Grüße autoiter

  • Unter Programme und Features werden sie nur 1x angezeigt...

    ...

    RealSpeak Solo Direct Steffi 1.0.84.101

    RealSpeak Solo Direct Steffi 1.0.84.101

    ...

    Xperia Companion 2.1.12.0

    Xperia Companion 2.1.12.0

    ...

  • Hehe, keine Ahnung. Aber das wäre ja nicht das erste Mal, dass sich auf deinem System besondere Effekte zeigen :P

    Ne Quatsch. Keine Ahnung. Bei mir gibt es jedenfalls keine Dubletten. Am wichtigsten ist wohl, dass nichts fehlt. Sollte der TE das auch beobachten können, kann er aus der Ausgabe ja ein Array erstellen und die doppelten Einträge mit _ArrayUnique entfernen.

    Grüße autoiter

  • Besondere Effekte treten bei mir nur auf, was die Darstellung angeht, aber das hier hat nichts damit zu tun.

    Auf meinem Mini-Server mit Windows 7, und da ist alles auf Standard, also keinen eigenen Skin, habe ich auch einen doppelten Eintrag.

    ...

    Renesas Electronics USB 3.0 Host Controller Driver 3.0.23.0

    Renesas Electronics USB 3.0 Host Controller Driver 3.0.23.0

    ...