none
Foreach-schleife mit if-Statement kombinieren und Array erweitern? RRS feed

  • Frage

  • Hallo an die Gemeinde,

    Ich komme einfach nicht weiter, weshalb ich hier eine Verständnisfrage stellen möchte. Ich möchte erwähnen, dass ich mich erst seit kürzerer Zeit mit Powershell beschäftige und noch so meine Probleme damit habe. Seid also nachsichtig mit mir.

    Ich mache eine WMI-Abfrage an eine Liste von PCs (Onlinestatus wurde vorab geprüft). Denkbar sind dabei also große Mengen an PCs (200-1000).

    Die WMI-Abfrage liefert mir die an den USB-Ports angeschlossenen Geräte zurück. Dabei besteht das Ergebnis aus einen Array von Daten. Zurückgeliefert wird also beispielsweise:

    $Canongeräte - besteht aus $Canongeräte.SystemName,  Manufacturer, DeviceID, Description. Mein Gedanke ist jetzt diese Geräte mittels if/else weiter nach den Modellen zu filtern und diese mit einer Registry-Abfrage zu verbinden, welche mir noch eine Seriennummer dazu ermittelt.

    Wie würde ich jetzt also das Array $Canongeräte weiter filtern und gleich entsprechend dazu die Seriennummer in der Registry ermitteln, welche idealer Weise gleich dem Array hinzugefügt werden sollte.

    Mein Ziel wäre also $Canongeräte.Description, DevicID....,Serial zu erhalten, so dass ich nach Abschluss der gesamten Foreach-Schleife das Ergebnis gleich in CSV exportieren kann.

    Ich wollte keine x-WMI-Abfragen bauen, da ich denke dass die Performance bei vielen PCs dadurch beeinträchtigt ist. Die Abfrage nimmt schon einige Zeit in Anspruch. Und ich habe das Ergebnis ja bereits im Grunde, welches nur durch die Seriennummer ergänzt werden soll. Aber ich bekomme es nicht hin und hoffe ihr könnt mir Ansätze dazu geben, wie ich das umsetzen kann. Danke für die Mühen!!!!!!

    Was habe ich bisher also:

    $Computers = Get-Content "C:\Users\Desktop\livePCs.txt" $inventory = foreach ($Computer in $Computers){ $Can=gwmi -ComputerName $Computer Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Description -Like 'Cano*'} |Select-Object SystemName,Manufacturer,DeviceID,Description,Caption #hier würde ich jetzt gern wie folgt eine if-Anweisung schreiben und hoffe auf eure

    #Hilfe!!!

    if($Can.Description -like 'CanoScan*'){ # erzeuge ein neues Property "Serial" = N/A weil Canonscanner

    # keine Seriennummer in der Registry hinterlegt haben. }#end if if($Can.Description -like 'Canon IP7200 series'){ #registryabfrage, die ich schon habe, und die mir eine Serial zurückliefert,

    #und wie oben auch mit ins Property-Array aufgenommen werden soll.

    }#end if #weitere If-Anweisungen sind möglich, da verschiedenen Geräte vorhanden sind }#end Inventory $inventory |Export-CSV "C:\Users\Desktop\Canon.csv" -NoTypeinformation -Force

    Gruß Pi

    Mittwoch, 21. Februar 2018 07:53

Antworten

  • Hallo,

    ich habe mal ein wenig aufgeräumt und umstrukturiert, allerdings habe ich es nicht getestet.

    Zum besseren Debuggen empfiehlt es sich, nicht zu komplexe Anweisungen in einer foreach-Pipeline zu haben. Hier ist es besser, eine foreach-Schleife mit Lauf-Variable zu nehmen. Die Übergabe $var = foreach{} funktioniert zwar, kann aber Probleme bereiten, wenn noch unerwartete Dinge im Stream unterwegs sind. Man sollte lieber ein Ausgabe-Array benutzen, das man gezielt befüllt.

    Neue Properties können einem Objekt oder einem Array aus Objekten sehr leicht mit select hinzugefügt werden. Wenn man dort Felder angibt, die bisher nicht existieren, werden leere Felder an alle Objekte im Array angefügt. Ein ganz leeres Objekt mit mehreren Feldern lässt sich mit 

    "" | select Feld1, Feld2, Feld3

    generieren.

    Außerdem kannst du elseif benutzen, da sich deine If-Blöcke nicht überschneiden, d.h. es wird immer nur einer ausgeführt.

    $Computers = Get-Content "C:\Users\Desktop\livePCs.txt"
    
    #Ausgabe-Array
    #Die List ist schneller als das Standard-PowerShell-Array, wenn es viele Einträge werden
    $inventory = New-Object System.Collections.Generic.List
    
    foreach ($Computer in $Computers)
    {
        #hinter der Pipeline kann ein Zeilenumbruch kommen, für mehr Übersicht
        $Can = gwmi -ComputerName $Computer Win32_USBControllerDevice | 
                foreach{[wmi]($_.Dependent)} | where{$_.Description -Like 'Cano*'} |
                select SystemName,Manufacturer,DeviceID,Description,Caption
    
        #Neues Feld für Seriennummer hinzufügen (und alle anderen übernehmen mit *)
        $Can = $Can | select *, Serial
    
        #$Can ist wahrscheinlich ein Array, daher muss hier eine Schleife sein
        foreach($c in $Can)
        {
            if($c.Description -like 'CanoScan*'){
                $c.Serial = "N/A"
            } elseif ($Can.Description -like 'Canon IP7200 series'){
               #registryabfrage, die ich schon habe, und die mir eine Serial zurückliefert, 
               $c.Serial = "Ergebnis deiner Abfrage"
            }
    
            #Datensatz zur Ausgabe-Liste hinzufügen
            $inventory.Add($c)
        }
    }
    $inventory | Export-CSV "C:\Users\Desktop\Canon.csv" -NoTypeinformation -Force




    • Bearbeitet hpotsirhc Mittwoch, 21. Februar 2018 10:23
    • Als Antwort markiert Pikus1977 Mittwoch, 21. Februar 2018 12:08
    Mittwoch, 21. Februar 2018 10:13
  • Wie schaut das so aus?

    $Inventory = Import-CSV "C:\Users\Desktop\livePCs.csv"| foreach {
         
         $Can = gwmi -ComputerName $_.ComputerName Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Description -Like 'Cano*'} |Select-Object SystemName,Manufacturer,DeviceID,Description,Caption
    
         # Erstellen einer Hastable
         $inventoryData = @{}
    
         # Füllen der Hastable
         $inventoryData.add("SystemName", $Can.SystemName)
         $inventoryData.add("Manufacturer", $Can.Manufacturer)
         $inventoryData.add("DeviceID", $Can.DeviceID)
         $inventoryData.add("Description", $Can.Description)
         $inventoryData.add("Caption", $Can.Caption)
    
         if($Can.Description -like 'CanoScan*'){
            $inventoryData.add("Serial", "N/A")
         }
    
         # Erstellen eines psobjects aus der Hastable
         $inventoryObject = New-Object psobject -Property $inventoryData
    
         # Schreiben des psobjects in die $Inventory Variable
         $inventoryObject
    }
    
    $Inventory #spiegelt entweder $Can oder $_ wieder auch in der CSV

    Gibt sicherlich elegantere Wege und testen konnte ich leider auch nicht.

    • Als Antwort markiert Pikus1977 Mittwoch, 21. Februar 2018 12:31
    Mittwoch, 21. Februar 2018 11:51

Alle Antworten

  • Hi,

    So könnte das klappen (ist ungetestet):

    $inventory = Import-CSV "C:\Users\Desktop\livePCs.csv" | foreach {
    
        $Can = gwmi -ComputerName $_.ComputerName Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Description -Like 'Cano*'} |Select-Object SystemName,Manufacturer,DeviceID,Description,Caption
    
        #hier würde ich jetzt gern wie folgt eine if-Anweisung schreiben und hoffe auf eure 
    
        #Hilfe!!!
    
    
        if($Can.Description -like 'CanoScan*'){
            # erzeuge ein neues Property "Serial" = N/A weil Canonscanner 
    
            $_ | Add-Member -MemberType NoteProperty -Name "Serial" -Value "N/A" -Force 
    
            # keine Seriennummer in der Registry hinterlegt haben.
            }#end if
    
        if($Can.Description -like 'Canon IP7200 series'){
           #registryabfrage, die ich schon habe, und die mir eine Serial zurückliefert, 
    
           $_ | Add-Member -MemberType NoteProperty -Name "Serial" -Value "Serial aus anderem Query" -Force 
    
           #und wie oben auch mit ins Property-Array aufgenommen werden soll.
    
        }#end if
    
        #weitere If-Anweisungen sind möglich, da verschiedenen Geräte vorhanden sind
    
    }#end Inventory
    
    $inventory |Export-CSV "C:\Users\Desktop\Canon.csv" -NoTypeinformation -Force

    Anstelle einer Textdatei wird hier ein CSV Importiert, das eine Spalte mit dem Header "ComputerName" hat. Durch den CSV Import steht ein Objekt zur Verfügung, dem mit $_ | Add-Member weitere Attribute hinzugefügt werden können. Das Ganze wird zurück in $inventory geschrieben und kann so als CSV exportiert werden. "ComputerName" wird so ebenfalls im Export vorhanden sein.

    Grüsse
    Tobias


    Mittwoch, 21. Februar 2018 09:20
  • Hallo,

    ich habe mal ein wenig aufgeräumt und umstrukturiert, allerdings habe ich es nicht getestet.

    Zum besseren Debuggen empfiehlt es sich, nicht zu komplexe Anweisungen in einer foreach-Pipeline zu haben. Hier ist es besser, eine foreach-Schleife mit Lauf-Variable zu nehmen. Die Übergabe $var = foreach{} funktioniert zwar, kann aber Probleme bereiten, wenn noch unerwartete Dinge im Stream unterwegs sind. Man sollte lieber ein Ausgabe-Array benutzen, das man gezielt befüllt.

    Neue Properties können einem Objekt oder einem Array aus Objekten sehr leicht mit select hinzugefügt werden. Wenn man dort Felder angibt, die bisher nicht existieren, werden leere Felder an alle Objekte im Array angefügt. Ein ganz leeres Objekt mit mehreren Feldern lässt sich mit 

    "" | select Feld1, Feld2, Feld3

    generieren.

    Außerdem kannst du elseif benutzen, da sich deine If-Blöcke nicht überschneiden, d.h. es wird immer nur einer ausgeführt.

    $Computers = Get-Content "C:\Users\Desktop\livePCs.txt"
    
    #Ausgabe-Array
    #Die List ist schneller als das Standard-PowerShell-Array, wenn es viele Einträge werden
    $inventory = New-Object System.Collections.Generic.List
    
    foreach ($Computer in $Computers)
    {
        #hinter der Pipeline kann ein Zeilenumbruch kommen, für mehr Übersicht
        $Can = gwmi -ComputerName $Computer Win32_USBControllerDevice | 
                foreach{[wmi]($_.Dependent)} | where{$_.Description -Like 'Cano*'} |
                select SystemName,Manufacturer,DeviceID,Description,Caption
    
        #Neues Feld für Seriennummer hinzufügen (und alle anderen übernehmen mit *)
        $Can = $Can | select *, Serial
    
        #$Can ist wahrscheinlich ein Array, daher muss hier eine Schleife sein
        foreach($c in $Can)
        {
            if($c.Description -like 'CanoScan*'){
                $c.Serial = "N/A"
            } elseif ($Can.Description -like 'Canon IP7200 series'){
               #registryabfrage, die ich schon habe, und die mir eine Serial zurückliefert, 
               $c.Serial = "Ergebnis deiner Abfrage"
            }
    
            #Datensatz zur Ausgabe-Liste hinzufügen
            $inventory.Add($c)
        }
    }
    $inventory | Export-CSV "C:\Users\Desktop\Canon.csv" -NoTypeinformation -Force




    • Bearbeitet hpotsirhc Mittwoch, 21. Februar 2018 10:23
    • Als Antwort markiert Pikus1977 Mittwoch, 21. Februar 2018 12:08
    Mittwoch, 21. Februar 2018 10:13
  • @TobyU

    Vielen Dank erstmal für diesen Ansatz....

    Das klappt so leider noch nicht ganz.

    Die Inventory ohne die if-Anweisung klappt.!

    Du siehst den Fehler sicherlich sofort....

    Wenn ich die If-Anweisung hinzufüge, kann ich über $_ am Host dann entweder nur Computername und Serial sehen, oder über $Can eben die Ergebnisse der WMI-Abfrage = SystemName, Manufacturer etc.

    Ausgabe $Can:

    Systemname  : Heiko-PC

    Manufacturer  : Canon

    DeviceID        : USB\VID_04....

    Description     : CanoScan Lide 100

    Caption          : CanoScan Lide 100

    usw.

    Ausgabe $_

    ComputerName     Serial

    Heiko-PC               N/A

    usw.

    Schau selbst

    $Inventory=Import-CSV "C:\Users\Desktop\livePCs.csv"| foreach {
         $Can=gwmi -ComputerName $_.ComputerName Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Description -Like 'Cano*'} |Select-Object SystemName,Manufacturer,DeviceID,Description,Caption
    
         if($Can.Description -like 'CanoScan*'){
         $_ | Add-Member -MemberType NoteProperty -Name "Serial" -Value "N/A" -Force
         }
         $Can #Ergebnis der WMI-Abfrage ohne Serial
         $_   #Ergebnis der IF-Anweisung ComputerName,Serial
         }
         $Inventory #spiegelt entweder $Can oder $_ wieder auch in der CSV

    Gruß Pi.


    • Bearbeitet Pikus1977 Mittwoch, 21. Februar 2018 10:43 Hinzugefügt
    Mittwoch, 21. Februar 2018 10:36
  • Wie schaut das so aus?

    $Inventory = Import-CSV "C:\Users\Desktop\livePCs.csv"| foreach {
         
         $Can = gwmi -ComputerName $_.ComputerName Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Description -Like 'Cano*'} |Select-Object SystemName,Manufacturer,DeviceID,Description,Caption
    
         # Erstellen einer Hastable
         $inventoryData = @{}
    
         # Füllen der Hastable
         $inventoryData.add("SystemName", $Can.SystemName)
         $inventoryData.add("Manufacturer", $Can.Manufacturer)
         $inventoryData.add("DeviceID", $Can.DeviceID)
         $inventoryData.add("Description", $Can.Description)
         $inventoryData.add("Caption", $Can.Caption)
    
         if($Can.Description -like 'CanoScan*'){
            $inventoryData.add("Serial", "N/A")
         }
    
         # Erstellen eines psobjects aus der Hastable
         $inventoryObject = New-Object psobject -Property $inventoryData
    
         # Schreiben des psobjects in die $Inventory Variable
         $inventoryObject
    }
    
    $Inventory #spiegelt entweder $Can oder $_ wieder auch in der CSV

    Gibt sicherlich elegantere Wege und testen konnte ich leider auch nicht.

    • Als Antwort markiert Pikus1977 Mittwoch, 21. Februar 2018 12:31
    Mittwoch, 21. Februar 2018 11:51
  • @hpotsirhc

    Dein Vorschlag funktioniert schon gleich sehr gut. Am Host sehe ich auch was ich sehen will. Nur beim Export muss ich noch schauen wie ich die Propertys als (SystemName, DeviceID und Co als Header hinbekomme.

    Das ist aber sicher eine Einstellung beim Export, weshalb ich auf jeden Fall deinen Weg als Antwort markiert habe. Das klappt in der Tat recht schnell und liefert alle Ergebnisse entsprechend zurück. Die Erweiterbarkeit ist definitiv gegeben, was in diesem Fall für weitere und neue Geräte wichtig ist.

    Vielen Vielen Dank für die Hilfe!!!! Wie immer seid ihr schnell und extrem hilfreich für mich "Beginner"!

    Gruß Pi

    Mittwoch, 21. Februar 2018 12:12
  • Hallo TobyU...

    Ich probiere es jetzt gleich mal aus und komme neu inwiefern dein Ansatz seinen Dienst tut. Vielen vielen Dank auf jeden Fall für die Mühe. Ich weiß es sehr zu schätzen.!!!!!Gruß Pi

    Mittwoch, 21. Februar 2018 12:13
  • Ja dein Vorschlag hat funktioniert und bietet mir auf jeden Fall eine Möglichkeit der Umsetzung.

    Ich muss mal schauen ob ich das jetzt erweitern kann, so dass ich schlussendlich alle weiteren möglichen Canon-Geräte mit eingebunden bekomme.

    Einziges Manko ist der Export in die CSV, wo die Hashtable scheinbar zufällig sortiert ausgegeben wird. Der Systemname ist leider mitten drin statt der erste Wert. Da gibt's bestimmt auch noch eine Möglichkeit mit Sort-Object oder sowas nehme ich mal an.

    Auf jeden Fall vielen lieben Dank. Jetzt probiere ich mich weiter aus und vertiefe mal eure Vorschläge!!!

    Danke und Gruß Pi

    Mittwoch, 21. Februar 2018 12:35
  • Es gibt in dem Sinne keine Einstellungen für Cmdlets, das wird alles über die Parameter gemacht. Was kommt denn bei dir als Export raus?

    Versuch mal

    Export-CSV "C:\Users\Desktop\Canon.csv" -NoTypeinformation -Force -delimiter ";"

    • Bearbeitet hpotsirhc Mittwoch, 21. Februar 2018 12:38
    Mittwoch, 21. Februar 2018 12:37
  • Also es kommt raus:

    Header der CSV sind

    PSPath, PSParentPath,PSChildName,PSDrive,PSProvider,ReadCount,Length

    Daten sind dann die Pfadangaben C:\Users\Desktop\LivePCs.txt etc.

    Im Host oder im Gridview siéht man aber die Daten, wobei zuerst die PC-Namen alleine kommen

    GridView

    Index 0 PCName1

    Index 1 PCName2

    Index 2 PCName3

    Index 3 @{Systemname=PCName1;Manufacturer=Canon;DeviceID=.....usw. incl. Serial

    Index 4 @{Systemname=PCName2;Manufacturer=Canon;DeviceID=.....usw. incl. Serial

    Ich hab genau diese 3 PC zum testen genommen, damit ich die Ergebnisse entsprechend einordnen kann beim testen.

    am Host

    3

    4

    PCName1

    PCName2

    PCName3

    SystemName  : PCName1

    Manufacturer  : Canon

    DeviceID        :

    etc.

    SystemName  : PCName2

    Manufacturer  : Canon

    etc......

    Also vorhanden sind die Daten ja.

    Eine Frage dazu habe ich noch. Ich würde dieses Script, sofern es dann wie gewünscht läuft, erweitern.ich möchte gern 3 WMI-Abfragen nach deinem Vorschlag durchführen, welche dann entsprechend die Serial dazu liefern. Wäre das so denkbar, oder sollte ich das ganze anders angehen?

    also gedankliches Beispiel ist:

    $Computers = Get-Content "C:\Users\Desktop\livePCs.txt"
    #####################################################################################################################
    #Ausgabe-Array, Die List ist schneller als das Standard-PowerShell-Array, wenn es viele Einträge werden
    $inventory = [System.Collections.ArrayList]@($Computers)
    foreach ($Computer in $Computers)
    {
        #hinter der Pipeline kann ein Zeilenumbruch kommen, für mehr Übersicht
        $Can = gwmi -ComputerName $Computer Win32_USBControllerDevice | foreach{[wmi]($_.Dependent)} | where{$_.Description -Like 'Cano*'} |
               select SystemName,Manufacturer,DeviceID,Description,Caption
        $Sam = gwmi -ComputerName $Computer Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Manufacturer -Like 'Samsung*'} |
               Select-Object SystemName,Manufacturer,DeviceID,Description,Caption |ForEach-Object -Process {$_.DeviceID = ([string]$_.DeviceID).Split("\")|
               Select-String -Pattern "SAM*" -AllMatches;$_}
        $HP  = gwmi -ComputerName $Computer Win32_USBControllerDevice | %{[wmi]($_.Dependent)} | Where-Object {$_.Description -Like 'HP*' -and $_.Description -notlike '*(*)'} | 
               Select-Object SystemName,Manufacturer,DeviceID,Description,Caption
    
        #Neues Feld für Seriennummer hinzufügen (und alle anderen übernehmen mit *)
        $Can = $Can | select *, Serial
        $Sam = $Sam | select *, Serial
        $HP  = $HP  | select *, Serial
    #jetzt wieder wie gehabt die foreach/if/elsif-Abfragen dazu einbetten.
    

    Das versteht sich als grundsätzliche Frage, oder sollte man von vornherein einen anderen Ansatz zur Umsetzung finden? Bin da für alles offen.

    Sonst würde ich in der Tat pro Hersteller ein Script erschaffen wollen.

    Gruß Pi

    Mittwoch, 21. Februar 2018 13:51
  • Ich verstehe nicht, was am Host "3" und "4" sein sollen. Der Header der csv sollte eigentlich auch 

    SystemName,Manufacturer,DeviceID,Description,Caption, Serial

    sein. Schau dir das Array noch einmal genauer an, aus der Ferne ist das leider schwierig.

    Die neuen Gerätetypen würde ich nicht in separaten Arrays lagern, sondern in einem Array speichern, wenn die Datenstruktur und der weitere Ablauf gleich sind.

    Du könntest die Modelle auch zusammen über einen regulären Ausdruck filtern:

    where{$_.Description -match '^(Cano|Samsung|HP)'}
    Dies steht für einen String, der entweder mit Cano, Samsung oder HB beginnt.

    Donnerstag, 22. Februar 2018 07:09
  • Eventuell habe ich noch einen Fehler bei mir gefunden. Man muss den Datentyp der Liste angeben:
    $inventory = New-Object System.Collections.Generic.List[System.Object]

    Montag, 26. Februar 2018 13:12