Skripterstellung mit PowerShell und „Direkte Speicherplätze“-Leistungsverlauf

Gilt für: Windows Server 2022, Windows Server 2019

In Windows Server 2019 zeichnen Direkte Speicherplätze einen umfassenden Leistungsverlauf u. a. für virtuelle Computer, Server, Laufwerke, Volumes, Netzwerkadapter auf und speichern ihn. Der Leistungsverlauf lässt sich in PowerShell einfach abfragen und verarbeiten, sodass Sie schnell von Rohdaten zu tatsächlichen Antworten auf Fragen wie die folgenden gelangen:

  1. Traten in der letzten Woche CPU-Spitzen auf?
  2. Weist ein physischer Datenträger eine ungewöhnliche Latenz auf?
  3. Welche VMs verbrauchen derzeit die meisten Speicher-IOPS?
  4. Ist meine Netzwerkbandbreite gesättigt?
  5. Wann steht auf diesem Volume kein Speicherplatz mehr zur Verfügung?
  6. Welche VMs haben im letzten Monat den meisten Arbeitsspeicher genutzt?

Das Get-ClusterPerf-Cmdlet wurde für die Skripterstellung erstellt. Es akzeptiert Eingaben von Cmdlets wie Get-VM oder Get-PhysicalDisk durch die Pipeline, um die Zuordnung zu verarbeiten, und Sie können die Ausgabe an Hilfsprogramm-Cmdlets wie Sort-Object, Where-Object und Measure-Object weiterreichen, um schnell leistungsstarke Abfragen zu erstellen.

In diesem Thema sind 6 Beispielskripts enthalten, die hier erläutert werden und die 6 oben genannten Fragen beantworten. Sie bieten Muster, die Sie anwenden können, um anhand zahlreicher Daten und Zeitrahmen u. a. Spitzen und Durchschnittswerte zu finden, Trendlinien zu zeichnen und Ausreißer zu erkennen. Sie werden Ihnen als kostenloser Startcode zum Kopieren, Erweitern und Wiederverwenden bereitgestellt.

Hinweis

Um der Prägnanz willen wird in den Beispielskripts z. B. die Fehlerbehandlung ausgelassen, die Sie von hochwertigem PowerShell-Code erwarten können. Sie dienen in erster Linie der Inspiration und Schulung und nicht der Verwendung in der Produktion.

Beispiel 1: CPU, ich sehe dich!

In diesem Beispiel wird die ClusterNode.Cpu.Usage-Reihe aus dem LastWeek-Zeitrahmen verwendet, um das Maximum (obere Grenze), Minimum und die durchschnittliche CPU-Auslastung für jeden Server im Cluster zu zeigen. Außerdem wird eine einfache Quartilanalyse durchgeführt, um zu zeigen, wie viele Stunden die CPU-Auslastung in den letzten 8 Tagen über 25 %, 50 % und 75 % lag.

Screenshot

Im folgenden Screenshot sehen wir, dass Server-02 in der letzten Woche eine ungeklärte Spitze aufwies:

Screenshot that shows that Server-02 had an unexplained spike last week.

Funktionsweise

Die Ausgabe von Get-ClusterPerf wird an das integrierte Cmdlet Measure-Object weitergereicht. Wir geben nur die Value-Eigenschaft an. Mit dem -Maximum-, -Minimum- und -Average-Flag liefert Measure-Object uns fast ohne unser Zutun die ersten drei Spalten. Um die Quartilanalyse durchzuführen, können wir sie an Where-Object weiterreichen und zählen, wie viele Werte größer als (-Gt) 25, 50 oder 75 waren. Der letzte Schritt ist die Verschönerung mit den Hilfsfunktionen Format-Hours und Format-Percent – sicherlich optional.

Skript

Hier ist das Skript:

Function Format-Hours {
    Param (
        $RawValue
    )
    # Weekly timeframe has frequency 15 minutes = 4 points per hour
    [Math]::Round($RawValue/4)
}

Function Format-Percent {
    Param (
        $RawValue
    )
    [String][Math]::Round($RawValue) + " " + "%"
}

$Output = Get-ClusterNode | ForEach-Object {
    $Data = $_ | Get-ClusterPerf -ClusterNodeSeriesName "ClusterNode.Cpu.Usage" -TimeFrame "LastWeek"

    $Measure = $Data | Measure-Object -Property Value -Minimum -Maximum -Average
    $Min = $Measure.Minimum
    $Max = $Measure.Maximum
    $Avg = $Measure.Average

    [PsCustomObject]@{
        "ClusterNode"    = $_.Name
        "MinCpuObserved" = Format-Percent $Min
        "MaxCpuObserved" = Format-Percent $Max
        "AvgCpuObserved" = Format-Percent $Avg
        "HrsOver25%"     = Format-Hours ($Data | Where-Object Value -Gt 25).Length
        "HrsOver50%"     = Format-Hours ($Data | Where-Object Value -Gt 50).Length
        "HrsOver75%"     = Format-Hours ($Data | Where-Object Value -Gt 75).Length
    }
}

$Output | Sort-Object ClusterNode | Format-Table

Beispiel 2: Der Latenzausreißer

In diesem Beispiel wird mit der PhysicalDisk.Latency.Average-Reihe aus dem Zeitrahmen LastHour nach statistischen Ausreißern gesucht, die als Laufwerke definiert sind, deren durchschnittliche stündliche Latenz +3σ (mehr als drei Standardabweichungen) über dem Durchschnitt der Population liegt.

Wichtig

Um der Prägnanz willen implementiert dieses Skript keine Schutzmaßnahmen gegen geringe Varianz, behandelt keine teilweise fehlenden Daten, unterscheidet nicht nach Modell oder Firmware usw. Bitte verlassen Sie sich nicht allein auf dieses Skript, um zu bestimmen, ob eine Festplatte ersetzt werden soll. Es wird hier nur zu Schulungszwecken vorgestellt.

Screenshot

Im folgenden Screenshot sehen wir, dass keine Ausreißer vorhanden sind:

Screenshot that shows there are no outliers.

Funktionsweise

Zunächst schließen wir im Leerlauf oder nahezu im Leerlauf befindliche Laufwerke aus, indem wir überprüfen, ob PhysicalDisk.Iops.Total konsistent -Gt 1 ist. Für jede aktive HDD reichen wir den LastHour-Zeitrahmen, der aus 360 Messungen in Intervallen von 10 Sekunden besteht, an Measure-Object -Average weiter, um die durchschnittliche Latenz in der letzten Stunde zu erhalten. Dadurch wird unsere Population eingerichtet.

Wir implementieren die allgemein bekannte Formel, um die mittlere (μ) und Standardabweichung (σ) der Population zu ermitteln. Für jede aktive HDD vergleichen wir die durchschnittliche Latenz mit dem Populationsdurchschnitt und dividieren durch die Standardabweichung. Wir behalten die Rohwerte bei, damit wir unsere Ergebnisse sortieren können (Sort-Object), verwenden jedoch die Hilfsfunktionen Format-Latency und Format-StandardDeviation, um zu verschönern, was wir anzeigen – sicherlich optional.

Wenn ein Laufwerk über +3σ liegt, erfolgt die Anzeige (Write-Host) in Rot, andernfalls in Grün.

Skript

Hier ist das Skript:

Function Format-Latency {
    Param (
        $RawValue
    )
    $i = 0 ; $Labels = ("s", "ms", "μs", "ns") # Petabits, just in case!
    Do { $RawValue *= 1000 ; $i++ } While ( $RawValue -Lt 1 )
    # Return
    [String][Math]::Round($RawValue, 2) + " " + $Labels[$i]
}

Function Format-StandardDeviation {
    Param (
        $RawValue
    )
    If ($RawValue -Gt 0) {
        $Sign = "+"
    }
    Else {
        $Sign = "-"
    }
    # Return
    $Sign + [String][Math]::Round([Math]::Abs($RawValue), 2) + "σ"
}

$HDD = Get-StorageSubSystem Cluster* | Get-PhysicalDisk | Where-Object MediaType -Eq HDD

$Output = $HDD | ForEach-Object {

    $Iops = $_ | Get-ClusterPerf -PhysicalDiskSeriesName "PhysicalDisk.Iops.Total" -TimeFrame "LastHour"
    $AvgIops = ($Iops | Measure-Object -Property Value -Average).Average

    If ($AvgIops -Gt 1) { # Exclude idle or nearly idle drives

        $Latency = $_ | Get-ClusterPerf -PhysicalDiskSeriesName "PhysicalDisk.Latency.Average" -TimeFrame "LastHour"
        $AvgLatency = ($Latency | Measure-Object -Property Value -Average).Average

        [PsCustomObject]@{
            "FriendlyName"  = $_.FriendlyName
            "SerialNumber"  = $_.SerialNumber
            "MediaType"     = $_.MediaType
            "AvgLatencyPopulation" = $null # Set below
            "AvgLatencyThisHDD"    = Format-Latency $AvgLatency
            "RawAvgLatencyThisHDD" = $AvgLatency
            "Deviation"            = $null # Set below
            "RawDeviation"         = $null # Set below
        }
    }
}

If ($Output.Length -Ge 3) { # Minimum population requirement

    # Find mean μ and standard deviation σ
    $μ = ($Output | Measure-Object -Property RawAvgLatencyThisHDD -Average).Average
    $d = $Output | ForEach-Object { ($_.RawAvgLatencyThisHDD - $μ) * ($_.RawAvgLatencyThisHDD - $μ) }
    $σ = [Math]::Sqrt(($d | Measure-Object -Sum).Sum / $Output.Length)

    $FoundOutlier = $False

    $Output | ForEach-Object {
        $Deviation = ($_.RawAvgLatencyThisHDD - $μ) / $σ
        $_.AvgLatencyPopulation = Format-Latency $μ
        $_.Deviation = Format-StandardDeviation $Deviation
        $_.RawDeviation = $Deviation
        # If distribution is Normal, expect >99% within 3σ
        If ($Deviation -Gt 3) {
            $FoundOutlier = $True
        }
    }

    If ($FoundOutlier) {
        Write-Host -BackgroundColor Black -ForegroundColor Red "Oh no! There's an HDD significantly slower than the others."
    }
    Else {
        Write-Host -BackgroundColor Black -ForegroundColor Green "Good news! No outlier found."
    }

    $Output | Sort-Object RawDeviation -Descending | Format-Table FriendlyName, SerialNumber, MediaType, AvgLatencyPopulation, AvgLatencyThisHDD, Deviation

}
Else {
    Write-Warning "There aren't enough active drives to look for outliers right now."
}

Beispiel 3: Lauter Nachbar? Wir schreiben alles auf!

Der Leistungsverlauf kann Fragen auch direkt beantworten. Neue Messungen sind alle 10 Sekunden in Echtzeit verfügbar. In diesem Beispiel wird die VHD.Iops.Total-Reihe aus dem MostRecent-Zeitrahmen verwendet, um die am meisten ausgelasteten (einige sagen vielleicht „lautesten“) virtuellen Computer zu identifizieren, die die meisten Speicher-IOPS auf allen Hosts im Cluster verbrauchen, und um die Aufschlüsselung ihrer Aktivität in Lese-/Schreibzugriffe zu zeigen.

Screenshot

Im folgenden Screenshot sehen wir die 10 virtuellen Computer mit der meisten Speicheraktivität:

Screenshot that shows the Top 10 virtual machines by storage activity.

Funktionsweise

Im Gegensatz zu Get-PhysicalDisk ist das Cmdlet Get-VM nicht clusterbewusst – es gibt nur VMs auf dem lokalen Server zurück. Um von jedem Server parallel abfragen zu können, schließen wir unseren Aufruf in Invoke-Command (Get-ClusterNode).Name { ... } ein. Für jeden virtuellen Computer erhalten wir VHD.Iops.Total-, VHD.Iops.Read- und VHD.Iops.Write-Messungen. Indem wir den Parameter -TimeFrame nicht angeben, erhalten wir jeweils den einzelnen MostRecent-Datenpunkt.

Tipp

Diese Reihen spiegeln die Summe der Aktivitäten dieses virtuellen Computers in allen VHD-/VHDX-Dateien wider. Dies ist ein Beispiel, wie der Leistungsverlauf automatisch für uns aggregiert wird. Um die VHD-/VHDX-Aufschlüsselung zu erhalten, können Sie eine einzelne Get-VHD anstatt des virtuellen Computers an Get-ClusterPerf weiterreichen.

Die Ergebnisse von jedem Server ergeben zusammen den $Output, den wir zuerst mit Sort-Object und dann mit Select-Object -First 10 verarbeiten können. Beachten Sie, dass Invoke-Command Ergebnisse mit einer PsComputerName-Eigenschaft versieht, die angibt, woher sie kommen. Wir können sie ausgeben, um zu wissen, wo die VM ausgeführt wird.

Skript

Hier ist das Skript:

$Output = Invoke-Command (Get-ClusterNode).Name {
    Function Format-Iops {
        Param (
            $RawValue
        )
        $i = 0 ; $Labels = (" ", "K", "M", "B", "T") # Thousands, millions, billions, trillions...
        Do { if($RawValue -Gt 1000){$RawValue /= 1000 ; $i++ } } While ( $RawValue -Gt 1000 )
        # Return
        [String][Math]::Round($RawValue) + " " + $Labels[$i]
    }

    Get-VM | ForEach-Object {
        $IopsTotal = $_ | Get-ClusterPerf -VMSeriesName "VHD.Iops.Total"
        $IopsRead  = $_ | Get-ClusterPerf -VMSeriesName "VHD.Iops.Read"
        $IopsWrite = $_ | Get-ClusterPerf -VMSeriesName "VHD.Iops.Write"
        [PsCustomObject]@{
            "VM" = $_.Name
            "IopsTotal" = Format-Iops $IopsTotal.Value
            "IopsRead"  = Format-Iops $IopsRead.Value
            "IopsWrite" = Format-Iops $IopsWrite.Value
            "RawIopsTotal" = $IopsTotal.Value # For sorting...
        }
    }
}

$Output | Sort-Object RawIopsTotal -Descending | Select-Object -First 10 | Format-Table PsComputerName, VM, IopsTotal, IopsRead, IopsWrite

Beispiel 4: Es heißt: „25-gig ist das neue 10-gig"

In diesem Beispiel dient die Reihe NetAdapter.Bandwidth.Total aus dem LastDay-Zeitrahmen zum Suchen nach Anzeichen einer Netzwerksättigung, die als >90 % der theoretischen maximalen Bandbreite definiert wurde. Für jeden Netzwerkadapter im Cluster wird die höchste beobachtete Bandbreitennutzung am letzten Tag mit der angegebenen Verbindungsgeschwindigkeit verglichen.

Screenshot

Im folgenden Screenshot sehen wir, dass ein Fabrikam NX-4 Pro #2 am letzten Tag einen Spitzenwert erreicht hat:

Screenshot that shows that Fabrikam NX-4 Pro #2 peaked in the last day.

Funktionsweise

Wir wiederholen unseren Invoke-Command-Trick von oben, um Get-NetAdapter auf jedem Server auszuführen und das Ergebnis an Get-ClusterPerf weiterzureichen. Nebenbei werden zwei relevante Eigenschaften erfasst: die LinkSpeed-Zeichenfolge wie „10 Gbps“ und die Speed-Rohdatenzahl wie „10000000000“. Mit Measure-Object rufen wir den Durchschnitts- und Spitzenwert vom letzten Tag ab (zur Erinnerung: jede Messung im LastDay-Zeitrahmen stellt 5 Minuten dar) und multiplizieren ihn mit 8 Bits pro Byte, um einen Äpfel-mit-Äpfeln-Vergleich zu erhalten.

Hinweis

Einige Anbieter, z. B. Chelsio, schließen RDMA-Aktivitäten (Remote Direct Memory Access, Remotezugriff auf den direkten Speicher) in ihre Netzwerkadapter-Leistungsindikatoren ein, sodass sie in der NetAdapter.Bandwidth.Total-Reihe enthalten sind. Andere, wie Mellanox, möglicherweise nicht. Wenn Ihr Anbieter es nicht tut, fügen Sie einfach die NetAdapter.Bandwidth.RDMA.Total-Reihe Ihrer Version dieses Skripts hinzu.

Skript

Hier ist das Skript:

$Output = Invoke-Command (Get-ClusterNode).Name {

    Function Format-BitsPerSec {
        Param (
            $RawValue
        )
        $i = 0 ; $Labels = ("bps", "kbps", "Mbps", "Gbps", "Tbps", "Pbps") # Petabits, just in case!
        Do { $RawValue /= 1000 ; $i++ } While ( $RawValue -Gt 1000 )
        # Return
        [String][Math]::Round($RawValue) + " " + $Labels[$i]
    }

    Get-NetAdapter | ForEach-Object {

        $Inbound = $_ | Get-ClusterPerf -NetAdapterSeriesName "NetAdapter.Bandwidth.Inbound" -TimeFrame "LastDay"
        $Outbound = $_ | Get-ClusterPerf -NetAdapterSeriesName "NetAdapter.Bandwidth.Outbound" -TimeFrame "LastDay"

        If ($Inbound -Or $Outbound) {

            $InterfaceDescription = $_.InterfaceDescription
            $LinkSpeed = $_.LinkSpeed

            $MeasureInbound = $Inbound | Measure-Object -Property Value -Maximum
            $MaxInbound = $MeasureInbound.Maximum * 8 # Multiply to bits/sec

            $MeasureOutbound = $Outbound | Measure-Object -Property Value -Maximum
            $MaxOutbound = $MeasureOutbound.Maximum * 8 # Multiply to bits/sec

            $Saturated = $False

            # Speed property is Int, e.g. 10000000000
            If (($MaxInbound -Gt (0.90 * $_.Speed)) -Or ($MaxOutbound -Gt (0.90 * $_.Speed))) {
                $Saturated = $True
                Write-Warning "In the last day, adapter '$InterfaceDescription' on server '$Env:ComputerName' exceeded 90% of its '$LinkSpeed' theoretical maximum bandwidth. In general, network saturation leads to higher latency and diminished reliability. Not good!"
            }

            [PsCustomObject]@{
                "NetAdapter"  = $InterfaceDescription
                "LinkSpeed"   = $LinkSpeed
                "MaxInbound"  = Format-BitsPerSec $MaxInbound
                "MaxOutbound" = Format-BitsPerSec $MaxOutbound
                "Saturated"   = $Saturated
            }
        }
    }
}

$Output | Sort-Object PsComputerName, InterfaceDescription | Format-Table PsComputerName, NetAdapter, LinkSpeed, MaxInbound, MaxOutbound, Saturated

Beispiel 5: Speicher muss wieder trendy werden!

Um Makrotrends zu untersuchen, wird der Leistungsverlauf bis zu einem Jahr lang beibehalten. In diesem Beispiel wird die Volume.Size.Available-Reihe aus dem LastYear-Zeitrahmen verwendet, um die Rate zu bestimmen, mit der der Speicher aufgefüllt wird, und um zu schätzen, wann er voll ist.

Screenshot

Im folgenden Screenshot sehen wir, dass dem Backup-Volume etwa 15 GB pro Tag hinzufügt werden:

Screenshot that shows that the Backup volume is adding about 15 GB per day.

Mit dieser Rate ist seine Kapazität in weiteren 42 Tagen ausgereizt.

Funktionsweise

Der LastYear-Zeitrahmen verfügt über einen Datenpunkt pro Tag. Obwohl Sie genau genommen nur zwei Punkte für eine Trendlinie benötigen, sollten in der Praxis besser mehr vorausgesetzt werden, z. B. 14 Tage. Mit Select-Object -Last 14 richten wir ein Array von (x, y) Punkten für x im Bereich [1, 14] ein. Mit diesen Punkten implementieren wir den einfachen linearen Algorithmus für die kleinsten Quadrate, um $A und $B zu finden, die die Anpassungslinie y = ax + b am besten parametrisieren. Willkommen zurück an der Uni.

Wenn wir die SizeRemaining-Eigenschaft des Volumes durch den Trend (die Steigung $A) dividieren, können wir grob schätzen, wie viele Tage es bei der aktuellen Speicherwachstumsrate dauert, bis das Volume voll ist. Die Hilfsfunktionen Format-Bytes, Format-Trend und Format-Days verschönern die Ausgabe.

Wichtig

Diese Schätzung ist linear und basiert nur auf den Messungen der letzten 14 Tage. Es gibt komplexere und genauere Techniken. Bitte verlassen Sie sich nicht allein auf dieses Skript, um zu entscheiden, ob Sie in die Erweiterung Ihres Speichers investieren möchten. Es wird hier nur zu Schulungszwecken vorgestellt.

Skript

Hier ist das Skript:


Function Format-Bytes {
    Param (
        $RawValue
    )
    $i = 0 ; $Labels = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
    Do { $RawValue /= 1024 ; $i++ } While ( $RawValue -Gt 1024 )
    # Return
    [String][Math]::Round($RawValue) + " " + $Labels[$i]
}

Function Format-Trend {
    Param (
        $RawValue
    )
    If ($RawValue -Eq 0) {
        "0"
    }
    Else {
        If ($RawValue -Gt 0) {
            $Sign = "+"
        }
        Else {
            $Sign = "-"
        }
        # Return
        $Sign + $(Format-Bytes ([Math]::Abs($RawValue))) + "/day"
    }
}

Function Format-Days {
    Param (
        $RawValue
    )
    [Math]::Round($RawValue)
}

$CSV = Get-Volume | Where-Object FileSystem -Like "*CSV*"

$Output = $CSV | ForEach-Object {

    $N = 14 # Require 14 days of history

    $Data = $_ | Get-ClusterPerf -VolumeSeriesName "Volume.Size.Available" -TimeFrame "LastYear" | Sort-Object Time | Select-Object -Last $N

    If ($Data.Length -Ge $N) {

        # Last N days as (x, y) points
        $PointsXY = @()
        1..$N | ForEach-Object {
            $PointsXY += [PsCustomObject]@{ "X" = $_ ; "Y" = $Data[$_-1].Value }
        }

        # Linear (y = ax + b) least squares algorithm
        $MeanX = ($PointsXY | Measure-Object -Property X -Average).Average
        $MeanY = ($PointsXY | Measure-Object -Property Y -Average).Average
        $XX = $PointsXY | ForEach-Object { $_.X * $_.X }
        $XY = $PointsXY | ForEach-Object { $_.X * $_.Y }
        $SSXX = ($XX | Measure-Object -Sum).Sum - $N * $MeanX * $MeanX
        $SSXY = ($XY | Measure-Object -Sum).Sum - $N * $MeanX * $MeanY
        $A = ($SSXY / $SSXX)
        $B = ($MeanY - $A * $MeanX)
        $RawTrend = -$A # Flip to get daily increase in Used (vs decrease in Remaining)
        $Trend = Format-Trend $RawTrend

        If ($RawTrend -Gt 0) {
            $DaysToFull = Format-Days ($_.SizeRemaining / $RawTrend)
        }
        Else {
            $DaysToFull = "-"
        }
    }
    Else {
        $Trend = "InsufficientHistory"
        $DaysToFull = "-"
    }

    [PsCustomObject]@{
        "Volume"     = $_.FileSystemLabel
        "Size"       = Format-Bytes ($_.Size)
        "Used"       = Format-Bytes ($_.Size - $_.SizeRemaining)
        "Trend"      = $Trend
        "DaysToFull" = $DaysToFull
    }
}

$Output | Format-Table

Beispiel 6: Arbeitsspeicherfresser, du kannst weglaufen, aber dich nicht verstecken.

Da der Leistungsverlauf zentral für den gesamten Cluster erfasst und gespeichert wird, müssen Sie niemals Daten von verschiedenen Computern zusammenheften, unabhängig davon, wie oft virtuelle Computer zwischen Hosts verschoben werden. In diesem Beispiel wird die VM.Memory.Assigned-Reihe aus dem LastMonth-Zeitrahmen verwendet, um die virtuellen Computer zu identifizieren, die in den letzten 35 Tagen den meisten Arbeitsspeicher verbraucht haben.

Screenshot

Im folgenden Screenshot sehen wir die 10 virtuellen Computer, die im letzten Monat den meisten Arbeitsspeicher genutzt haben:

Screenshot of PowerShell

Funktionsweise

Wir wiederholen unseren oben vorgestellten Invoke-Command-Trick, um Get-VM auf jedem Server auszuführen. Wir verwenden Measure-Object -Average, um den monatlichen Durchschnitt für jeden virtuellen Computer zu erhalten, und dann Sort-Object gefolgt von Select-Object -First 10, um unsere Bestenliste zu erhalten. (Oder ist es unsere Meistgesuchten-Liste?)

Skript

Hier ist das Skript:

$Output = Invoke-Command (Get-ClusterNode).Name {
    Function Format-Bytes {
        Param (
            $RawValue
        )
        $i = 0 ; $Labels = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
        Do { if( $RawValue -Gt 1024 ){ $RawValue /= 1024 ; $i++ } } While ( $RawValue -Gt 1024 )
        # Return
        [String][Math]::Round($RawValue) + " " + $Labels[$i]
    }

    Get-VM | ForEach-Object {
        $Data = $_ | Get-ClusterPerf -VMSeriesName "VM.Memory.Assigned" -TimeFrame "LastMonth"
        If ($Data) {
            $AvgMemoryUsage = ($Data | Measure-Object -Property Value -Average).Average
            [PsCustomObject]@{
                "VM" = $_.Name
                "AvgMemoryUsage" = Format-Bytes $AvgMemoryUsage.Value
                "RawAvgMemoryUsage" = $AvgMemoryUsage.Value # For sorting...
            }
        }
    }
}

$Output | Sort-Object RawAvgMemoryUsage -Descending | Select-Object -First 10 | Format-Table PsComputerName, VM, AvgMemoryUsage

Das ist alles! Hoffentlich werden Sie von diesen Beispielen inspiriert, und sie helfen Ihnen bei den ersten Schritten. Mit dem „Direkte Speicherplätze“-Leistungsverlauf und dem leistungsstarken, skriptfreundlichen Get-ClusterPerf-Cmdlet können Sie komplexe Fragen zum Verwalten und Überwachen Ihrer Windows Server 2019-Infrastruktur stellen – und beantworten!

Zusätzliche Referenzen