Benutzer mit den meisten Antworten
Custom-Object-Array in Custom-Object-Array: Aktualisierung beschreibt alle Arrays im Array gleichzeitig

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
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. -
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
Alle 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. -
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
-
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?
-
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 } -
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
-
$Versuch.Result.Add({$Nummer, $Result})
Hast Du den Aufruf schon mal versucht? ;-)Evgenij Smirnov
-
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
-
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
-
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.