none
Custom-Object-Array in Custom-Object-Array: Aktualisierung beschreibt alle Arrays im Array gleichzeitig RRS feed

  • Frage

  • Hallo zusammen,

    ich stehe vor der Herausforderung ein Script zur Abfrage von Servern zu erweitern. Dafür möchte ich das erst etwas effizienter gestalten, bevor ich neue Funktionen hinzufüge. Dabei bin ich auf ein Phänomen gestoßen, das ich auch in einem wesentlich kleineren Script nachvollziehen, aber nicht lösen kann. Der Code: 

    #Kommandos, die ausgeführt werden sollen
    $Cmds = @(
              [pscustomobject]@{Nummer = '1'   ;Cmd = {Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'};Result = ''}
              [pscustomobject]@{Nummer = '2'   ;Cmd = {Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'};Result = ''}
    )
    
    #Platzhalter, der im Original für die abzufragenden Server steht
    $Versuche = @(
              [pscustomobject]@{Versuch = '1'    ;Cmds = $Cmds}
              [pscustomobject]@{Versuch = '2'    ;Cmds = $Cmds}
              [pscustomobject]@{Versuch = '3'    ;Cmds = $Cmds}
    )
    
    #Die drei Versuche werden nacheinander abgearbeitet.
    $Versuche | %{
        $Versuch = $_.Versuch
        $Cmds    = $_.Cmds
    
        #Bei jedem Versuch werden beide Kommandos (Cmds.Cmd) abgearbeitet
        $Cmds | %{
            $Nummer = $_.Nummer
            $Cmd    = $_.Cmd
            $Result = &$Cmd
            Write-Host "Versuch $Versuch, Nummer $Nummer, Result $Result"
            $_.Result = $Result
            Start-Sleep 1
        }
        Start-Sleep 1
    }
    
    $Versuche.Cmds | ft -autosize

    Das Ergebnis verwirrt mich:

    Versuch 1, Nummer 1, Result 2019-11-06 08:29:43.602
    Versuch 1, Nummer 2, Result 2019-11-06 08:29:44.618
    Versuch 2, Nummer 1, Result 2019-11-06 08:29:46.618
    Versuch 2, Nummer 2, Result 2019-11-06 08:29:47.618
    Versuch 3, Nummer 1, Result 2019-11-06 08:29:49.618
    Versuch 3, Nummer 2, Result 2019-11-06 08:29:50.618
    
    Nummer Cmd                                        Result                 
    ------ ---                                        ------                 
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 08:29:49.618
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 08:29:50.618
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 08:29:49.618
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 08:29:50.618
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 08:29:49.618
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 08:29:50.618

    Wie man sieht, hat Write-Host nach jedem Kommando und Versuch jeweils eine Sekunde Pause gelassen. Wenn ich das Ergebnis anschließend in Versuche.Cmds betrachte, scheint es, als würden der aktuelle Versuch alle Custom Objects (Cmds) gleichzeitig zu überschreiben. Weiß jemand eine Lösung, wie ich dasselbe Ergebnis erhalte, das mit auch durch Write-Host angezeigt wird?

    Schon mal vielen Dank für die Mühe

     Heiko Sattler

    Mittwoch, 6. November 2019 07:43

Antworten

  • Betrachte mal dein Datenmodell:

    $cmds ist ein Objekt, dass ein Array enthält.
    In $Versuche weist du jedem Array-Eintrag dasselbe Objekt als Verweis zu und keine Kopie.

    Wenn du nun in deiner Schleife die Versuche durchgehst, änderst du den Inhalt des Objektes.
    Da alle Verweise dasselbe Objekt betreffen, hast du den Inhalt für alle Versuche geändert.

    Du musst in deiner Init-Routine den Versuchen jeweils neue Objekte zuweisen.
    Mache aus der Zuweisung "Cmds = $Cmds" einen Funktionufruf der dir jeweils ein neues $Cmds liefert.

    • Als Antwort markiert hsattler Mittwoch, 6. November 2019 09:38
    • Tag als Antwort aufgehoben hsattler Mittwoch, 6. November 2019 09:38
    • Als Antwort markiert hsattler Mittwoch, 6. November 2019 10:54
    Mittwoch, 6. November 2019 09:23
  • Ich habe es gefunden, vielen Dank für den Tipp. In PowerShell Arrays und Listen zu kopieren scheint schon vielen Kopfzerbrechen bereitet zu haben. Da hat aber jemand eine einfache Lösung gefunden: https://www.reddit.com/r/PowerShell/comments/76471s/array_copying/

    Ich brauchte die Zuweisung der Arrays nur um "| select *" erweitern. Der vollständige Code sieht so aus:

    #Kommandos, die ausgeführt werden sollen
    $Cmds = @(
              [pscustomobject]@{Nummer = '1'   ;Cmd = {Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'};Result = ''}
              [pscustomobject]@{Nummer = '2'   ;Cmd = {Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'};Result = ''}
    )
    
    #Platzhalter, der im Original für die abzufragenden Server steht
    $Versuche = @(
              [pscustomobject]@{Versuch = '1'    ;Cmds = $Cmds | select *}
              [pscustomobject]@{Versuch = '2'    ;Cmds = $Cmds | select *}
              [pscustomobject]@{Versuch = '3'    ;Cmds = $Cmds | select *}
    )
    
    #Die drei Versuche werden nacheinander abgearbeitet.
    $Versuche | %{
        $Versuch = $_.Versuch
        $Cmds    = $_.Cmds
    
        #Bei jedem Versuch werden beide Kommandos (Cmds.Cmd) abgearbeitet
        $Cmds | %{
            $Nummer = $_.Nummer
            $Cmd    = $_.Cmd
            $Result = &$Cmd
            Write-Host "Versuch $Versuch, Nummer $Nummer, Result $Result"
            $_.Result = $Result
            Start-Sleep 1
        }
        Start-Sleep 1
    }
    
    $Versuche.Cmds | ft -AutoSize

    Jetzt passt auch das Ergebnis:

    Versuch 1, Nummer 1, Result 2019-11-06 11:32:29.681
    Versuch 1, Nummer 2, Result 2019-11-06 11:32:30.697
    Versuch 2, Nummer 1, Result 2019-11-06 11:32:32.697
    Versuch 2, Nummer 2, Result 2019-11-06 11:32:33.697
    Versuch 3, Nummer 1, Result 2019-11-06 11:32:35.697
    Versuch 3, Nummer 2, Result 2019-11-06 11:32:36.697
    
    Nummer Cmd                                        Result                 
    ------ ---                                        ------                 
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:29.681
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:30.697
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:32.697
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:33.697
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:35.697
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:36.697

    Vielen Dank für die Unterstützung und eine angenehme Restwoche...

    • Als Antwort markiert hsattler Mittwoch, 6. November 2019 10:54
    Mittwoch, 6. November 2019 10:40

Alle Antworten

  • Hmmm ...

    darf ich fragen, was Du eigentlich machen möchtest? Dein Konstrukt sieht unnötig umständlich aus. Vielelicht gibt es ja einen effizienteren Weg, Dein Ziel zu erreichen.


    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Mittwoch, 6. November 2019 08:49
  • Betrachte mal dein Datenmodell:

    $cmds ist ein Objekt, dass ein Array enthält.
    In $Versuche weist du jedem Array-Eintrag dasselbe Objekt als Verweis zu und keine Kopie.

    Wenn du nun in deiner Schleife die Versuche durchgehst, änderst du den Inhalt des Objektes.
    Da alle Verweise dasselbe Objekt betreffen, hast du den Inhalt für alle Versuche geändert.

    Du musst in deiner Init-Routine den Versuchen jeweils neue Objekte zuweisen.
    Mache aus der Zuweisung "Cmds = $Cmds" einen Funktionufruf der dir jeweils ein neues $Cmds liefert.

    • Als Antwort markiert hsattler Mittwoch, 6. November 2019 09:38
    • Tag als Antwort aufgehoben hsattler Mittwoch, 6. November 2019 09:38
    • Als Antwort markiert hsattler Mittwoch, 6. November 2019 10:54
    Mittwoch, 6. November 2019 09:23
  • Hallo,

    ich habe eine Liste von derzeit 46 Servern, die als Custom Objects im Script hinterlegt sind und verschiedene Eigenschaften beinhalten. Alle Server sollen durch PSRemoting abgefragt werden. Da die Abfragen für alle Server gleich sind und ich am liebsten automatisch pro Server HTML-, XML- oder JSON-Berichte ausgeben lassen möchte, speichere ich die abzufragenden WMI-Klassen und PS-Befehle in einem anderen Custom-Object-Array, das auch die abgefragten WMI-Objekte und .net-Objekte speichern soll. Ich beabsichtige, in jedem Server-Objekt eine "Kopie" des Abfrage-Objekts zu speichern, damit ich mit einer äußeren For-Schleife die Liste der Server durchlaufen kann und mit einer inneren For-Schleife die Liste der Befehle durchlaufen kann.

    Das sollte so funktionieren, dass ich in dem Objekt mit dem Befehlen nur weitere PS-Befehle oder abzufragende WMI-Objekte hinzufügen brauche, damit diese auf alle Server angewendet werden. Durch Functions sollen anschließend aus den Daten die Berichte generiert werden können.

    Vielen Dank schon mal und Grüße aus Mainz

    Mittwoch, 6. November 2019 09:24
  • Hallo bfuerchau,

    das klingt logisch und nach einem Umstand, den ich bei anderen Programmiersprachen mal kennengelernt habe. Während ich sowas noch schnell bei Google gefunden hatte, habe ich keine Idee, wie ich das in der PowerShell umsetze. Hast du einen Tipp für mich?

    Mittwoch, 6. November 2019 09:36
  • Ich habe deinen Fehler gefunden:

     $_.Result = $Result

    Mit dieser Zuweisung speicherst du das Ergebnis im CMD-Objekt.
    Du benötigst ein Ergebnis-Array im Versuch-Objekt.

    #Die drei Versuche werden nacheinander abgearbeitet. $Versuche | %{ $Versuch = $_.Versuch
    $Versuch.Result = @{} $Cmds = $_.Cmds #Bei jedem Versuch werden beide Kommandos (Cmds.Cmd) abgearbeitet $Cmds | %{ $Nummer = $_.Nummer $Cmd = $_.Cmd $Result = &$Cmd Write-Host "Versuch $Versuch, Nummer $Nummer, Result $Result" $Versuch.Result[$Nummer] = $Result Start-Sleep 1 } Start-Sleep 1 }

    Mittwoch, 6. November 2019 09:39
  • Das hatte ich schon versucht. Führe ich den Code aus, kommt es bei jeder Loop-Ausführung zu folgendem Fehler:

    Cannot index into a null array.
    At line:12 char:9
    +         $Versuch.Result[$Nummer] = $Result
    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
        + FullyQualifiedErrorId : NullArray


    • Bearbeitet hsattler Mittwoch, 6. November 2019 09:52
    Mittwoch, 6. November 2019 09:44
  • $Nummer wird hier leider als Zahl und nicht als Zeichenkette interpretiert, daher der Fehler.
    Lege das Array per $Versuch.Result = @() an und verwende dann einfach die Add-Methode.
    $Versuch.Result.Add({$Nummer, $Result})
    Mittwoch, 6. November 2019 10:20
  • $Versuch.Result.Add({$Nummer, $Result})
    Hast Du den Aufruf schon mal versucht? ;-)

    Evgenij Smirnov

    http://evgenij.smirnov.de

    Mittwoch, 6. November 2019 10:28
  • Ich habe es gefunden, vielen Dank für den Tipp. In PowerShell Arrays und Listen zu kopieren scheint schon vielen Kopfzerbrechen bereitet zu haben. Da hat aber jemand eine einfache Lösung gefunden: https://www.reddit.com/r/PowerShell/comments/76471s/array_copying/

    Ich brauchte die Zuweisung der Arrays nur um "| select *" erweitern. Der vollständige Code sieht so aus:

    #Kommandos, die ausgeführt werden sollen
    $Cmds = @(
              [pscustomobject]@{Nummer = '1'   ;Cmd = {Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'};Result = ''}
              [pscustomobject]@{Nummer = '2'   ;Cmd = {Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'};Result = ''}
    )
    
    #Platzhalter, der im Original für die abzufragenden Server steht
    $Versuche = @(
              [pscustomobject]@{Versuch = '1'    ;Cmds = $Cmds | select *}
              [pscustomobject]@{Versuch = '2'    ;Cmds = $Cmds | select *}
              [pscustomobject]@{Versuch = '3'    ;Cmds = $Cmds | select *}
    )
    
    #Die drei Versuche werden nacheinander abgearbeitet.
    $Versuche | %{
        $Versuch = $_.Versuch
        $Cmds    = $_.Cmds
    
        #Bei jedem Versuch werden beide Kommandos (Cmds.Cmd) abgearbeitet
        $Cmds | %{
            $Nummer = $_.Nummer
            $Cmd    = $_.Cmd
            $Result = &$Cmd
            Write-Host "Versuch $Versuch, Nummer $Nummer, Result $Result"
            $_.Result = $Result
            Start-Sleep 1
        }
        Start-Sleep 1
    }
    
    $Versuche.Cmds | ft -AutoSize

    Jetzt passt auch das Ergebnis:

    Versuch 1, Nummer 1, Result 2019-11-06 11:32:29.681
    Versuch 1, Nummer 2, Result 2019-11-06 11:32:30.697
    Versuch 2, Nummer 1, Result 2019-11-06 11:32:32.697
    Versuch 2, Nummer 2, Result 2019-11-06 11:32:33.697
    Versuch 3, Nummer 1, Result 2019-11-06 11:32:35.697
    Versuch 3, Nummer 2, Result 2019-11-06 11:32:36.697
    
    Nummer Cmd                                        Result                 
    ------ ---                                        ------                 
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:29.681
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:30.697
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:32.697
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:33.697
    1      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:35.697
    2      Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' 2019-11-06 11:32:36.697

    Vielen Dank für die Unterstützung und eine angenehme Restwoche...

    • Als Antwort markiert hsattler Mittwoch, 6. November 2019 10:54
    Mittwoch, 6. November 2019 10:40
  • Hast Du den Aufruf schon mal versucht? ;-)

    Ich würde sagen nein :-) Ich hätte da auch vorher eine ArrayList initialisiert - oder einfach += verwendet.

    Greetings/Grüße, Martin - https://mvp.microsoft.com/en-us/PublicProfile/5000017 Mal ein gutes Buch über GPOs lesen? - http://www.amazon.de/Windows-Server-2012--8-Gruppenrichtlinien/dp/3866456956 Good or bad GPOs? My blog - http://evilgpo.blogspot.com And if IT bothers me? Coke bottle design refreshment - http://sdrv.ms/14t35cq

    Mittwoch, 6. November 2019 10:55
  • Der Select * liest das Array ja aus und somit erhältst du deine Kopie.

    Und man kann ja mal ein Array mit einer ArryList verwechseln:
    https://learn-powershell.net/2014/09/21/quick-hits-adding-items-to-an-array-and-a-look-at-performance/

    Daher eine kleine Korrektur:
    $Versuch.Result += {$Nummer, $Result}

    Damit gehts dann auch ohne den Select *. $Nummer noch mal zu speichern macht dann Sinn, wenn man auf das CMD-Objekt noch mal zugreifen will.
    Außerdem kann man sich die Zuweisung des CMD's an jeden Versuch sparen, da es immer dasselbe ist und bleiben kann.

    Aber wie immer, es gibt viele Wege in der Programmiererwelt.

    Mittwoch, 6. November 2019 11:56