SMBpulse.psm1
|
#requires -version 5.1 <# .SYNOPSIS SMBpulse - Messung und Auswertung des SMB-Durchsatzes auf Windows-Fileservern. .DESCRIPTION SMBpulse ist ein PowerShell-Modul und enthaelt vier Cmdlets: Start-SmbPulse Erstellt und startet eine Langzeitmessung als Data Collector Set "SMB_Pulse" (Standard: 7 Tage, 15-Sekunden-Intervall, stuendliche BLG-Segmente). Watch-SmbPulse Zeigt den aktuellen SMB-Durchsatz direkt aus den Performance Countern an - mit farbcodierter Auslastungsanzeige fuer einen 1-Gbit/s-Link. Get-SmbPulseReport Wertet alle bereits abgeschlossenen BLG-Segmente aus und exportiert die Zusammenfassung sowie die groessten Einzelspitzen als CSV. Uninstall-SmbPulse Stoppt und entfernt Collector und Messdaten wieder. Voraussetzungen: - Windows PowerShell 5.1 - Start-SmbPulse und Uninstall-SmbPulse erfordern eine Administratorsitzung Fruehere Versionen des Monitoring-Kits (Publisher-Name "SMB-Throughput-Monitor") mit dem Aelierungen Collector "SMB_Throughput_Monitor", Log "SMB_Throughput-Monitor" oder Collector "SMB_7Tage"/"SMB-Messung" werden automatisch erkannt und beim Start des neuen Collectors aufgeraeumt bzw. vom Uninstaller mitentfernt. .NOTES Autor: Manuel Berfelde #> $ErrorActionPreference = 'Stop' $Script:CollectorName = 'SMB_Pulse' $Script:LegacyCollectors = @( 'SMB_Throughput_Monitor', 'SMB_7Tage' ) $Script:LogDirs = @( 'C:\SMBpulse', 'C:\PerfLogs\SMB-Throughput-Monitor', 'C:\PerfLogs\SMB-Messung' ) $Script:OutputDir = $Script:LogDirs[0] $Script:SummaryCsv = Join-Path $Script:OutputDir 'SMBpulse-Report.csv' $Script:PeaksCsv = Join-Path $Script:OutputDir 'SMBpulse-Peaks.csv' # ---------------------------------------------------------------------------- # Interne Hilfsfunktionen # ---------------------------------------------------------------------------- function Test-Administrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Assert-Administrator { if (-not (Test-Administrator)) { throw 'Dieses Cmdlet muss in einer PowerShell als Administrator ausgefuehrt werden.' } } function Get-SmbCounterSet { param([Object]$AllSets) if (-not $AllSets) { $AllSets = @(Get-Counter -ListSet *) } $set = @($AllSets) | Where-Object { $_.CounterSetName -eq 'SMB-Serverfreigaben' -or $_.CounterSetName -eq 'SMB Server Shares' -or $_.CounterSetName -match '^SMB.*Server.*(Share|Freig)' } | Select-Object -First 1 if (-not $set) { throw 'Der Performance-Counter-Satz fuer SMB-Serverfreigaben wurde nicht gefunden.' } return $set } function Get-SmbCounterKind { param([Parameter(Mandatory = $true)][string]$Path) if ($Path -match '\\(Datenbytes/Sek\.|Data Bytes/sec)$') { return 'Gesamt' } if ($Path -match '\\(Gelesene Bytes/s|Read Bytes/sec)$') { return 'Lesen' } if ($Path -match '\\(Geschriebene Bytes/Sek\.|Write Bytes/sec)$') { return 'Schreiben' } return $null } function Get-ThroughputColor { param([Parameter(Mandatory = $true)][double]$MbitSec) if ($MbitSec -ge 900) { return 'Red' } if ($MbitSec -ge 700) { return 'Yellow' } return 'Green' } function Write-MonitorBanner { param( [Parameter(Mandatory = $true)][string]$Title, [string]$Subtitle, [string]$Color = 'Cyan' ) $width = 62 $top = '+' + ('=' * ($width - 2)) + '+' $empty = '|' + (' ' * ($width - 2)) + '|' $pad = [Math]::Max(0, [int](($width - 2 - $Title.Length) / 2)) $line = '|' + (' ' * $pad) + $Title + (' ' * ($width - 2 - $pad - $Title.Length)) + '|' Write-Host '' Write-Host $top -ForegroundColor $Color Write-Host $empty -ForegroundColor $Color Write-Host $line -ForegroundColor $Color if ($Subtitle) { Write-Host $empty -ForegroundColor $Color $pad = [Math]::Max(0, [int](($width - 2 - $Subtitle.Length) / 2)) $line = '|' + (' ' * $pad) + $Subtitle + (' ' * ($width - 2 - $pad - $Subtitle.Length)) + '|' Write-Host $line -ForegroundColor Gray } Write-Host $empty -ForegroundColor $Color Write-Host $top -ForegroundColor $Color Write-Host '' } function Write-SectionTitle { param([Parameter(Mandatory = $true)][string]$Title) Write-Host '' Write-Host ('--- ' + $Title + ' ---') -ForegroundColor Cyan } function Write-InfoBox { param( [string[]]$Lines, [string]$Title = 'Ueberblick', [string]$Color = 'Cyan' ) if (-not $Lines) { return } $width = 76 $top = '+' + ('=' * ($width - 2)) + '+' $empty = '|' + (' ' * ($width - 2)) + '|' Write-Host '' Write-Host $top -ForegroundColor $Color $pad = [Math]::Max(0, [int](($width - 2 - $Title.Length) / 2)) $line = '|' + (' ' * $pad) + $Title + (' ' * ($width - 2 - $pad - $Title.Length)) + '|' Write-Host $line -ForegroundColor $Color Write-Host $empty -ForegroundColor $Color foreach ($line in $Lines) { $text = $line if ($text -match ':') { $index = $text.IndexOf(':') $label = $text.Substring(0, $index).Trim() $value = $text.Substring($index + 1).Trim() $text = (' {0,-28}: {1}' -f $label, $value) } else { $text = ' ' + $text } if ($text.Length -gt ($width - 3)) { $text = $text.Substring(0, $width - 3) } $pad = $width - 2 - $text.Length Write-Host ('|' + $text + (' ' * $pad) + '|') } Write-Host $empty -ForegroundColor $Color Write-Host $top -ForegroundColor $Color Write-Host '' } function Write-Ok { param([string]$Text) Write-Host $Text -ForegroundColor Green } function Write-Warn { param([string]$Text) Write-Host $Text -ForegroundColor Yellow } function Write-Err { param([string]$Text) Write-Host $Text -ForegroundColor Red } function Write-Muted { param([string]$Text) Write-Host $Text -ForegroundColor DarkGray } function Convert-ToLogmanDuration { param([int]$TotalSeconds) $hours = [Math]::Floor($TotalSeconds / 3600) $minutes = [Math]::Floor(($TotalSeconds % 3600) / 60) $seconds = $TotalSeconds % 60 return ('{0:00}:{1:00}:{2:00}' -f $hours, $minutes, $seconds) } function Invoke-Logman { param( [Parameter(Mandatory = $true)][string[]]$ArgumentList, [switch]$Quiet ) $logman = Join-Path $env:SystemRoot 'System32\logman.exe' if (Test-Path (Join-Path $env:SystemRoot 'Sysnative\logman.exe')) { $logman = Join-Path $env:SystemRoot 'Sysnative\logman.exe' } # logman startet gelegentlich nicht, wenn das aktuelle Arbeitsverzeichnis # fuer den Child-Prozess ungueltig ist ("Der Verzeichnisname ist ungueltig"). # Deshalb immer aus einem garantiert gueltigen Verzeichnis heraus starten. Push-Location $env:SystemRoot try { if ($Quiet) { & $logman @ArgumentList 2>$null | Out-Null } else { $lines = @(& $logman @ArgumentList) foreach ($line in $lines) { Write-Host $line } } return $LASTEXITCODE } finally { Pop-Location } } function Stop-AndDeleteCollector { param([string]$Name) if ((Invoke-Logman -ArgumentList @('query', $Name) -Quiet) -ne 0) { return } $null = Invoke-Logman -ArgumentList @('stop', $Name) -Quiet Start-Sleep -Milliseconds 500 $null = Invoke-Logman -ArgumentList @('delete', $Name) -Quiet # logman/PLA gibt die aktive BLG-Datei ggf. leicht verzoegert frei. for ($i = 0; $i -lt 20; $i++) { if ((Invoke-Logman -ArgumentList @('query', $Name) -Quiet) -ne 0) { break } Start-Sleep -Milliseconds 500 } Write-Ok "Vorhandenen Datensammler '$Name' entfernt." } function Find-SmbTotalCounter { param( [string[]]$Patterns, [string[]]$Paths ) foreach ($pattern in $Patterns) { $match = $Paths | Where-Object { $_ -match '\(_Total\)\\' -and $_ -match $pattern } | Select-Object -First 1 if ($match) { return $match } } return $null } # ---------------------------------------------------------------------------- # Start-SmbPulse # ---------------------------------------------------------------------------- function Start-SmbPulse { <# .SYNOPSIS Erstellt und startet eine Langzeitmessung des SMB- und Netzwerkdurchsatzes. .DESCRIPTION Erstellt den Data Collector Set "SMB_Pulse" ueber logman. Storage: - 7 Tage Laufzeit - 15 Sekunden Abtastintervall - stuendliche BLG-Segmente Die stuendlichen Segmente erlauben bereits waehrend der Messwoche eine Auswertung abgeschlossener BLG-Dateien. Eventuell vorhandene Collector frueherer Versionen ("SMB_Throughput_Monitor", "SMB_7Tage") werden gestoppt und entfernt. Alte Messdaten bleiben erhalten. .PARAMETER Days Laufzeit der Messung in Tagen (1-30, Standard 7). .PARAMETER SampleSeconds Abstand zwischen zwei Messwerten in Sekunden (1-3600, Standard 15). .PARAMETER SegmentMinutes Groesse eines BLG-Zeitsegments in Minuten (5-1440, Standard 60). .EXAMPLE Start-SmbPulse .EXAMPLE Start-SmbPulse -Days 14 -SampleSeconds 10 -SegmentMinutes 30 #> [CmdletBinding()] param( [ValidateRange(1,30)] [int]$Days = 7, [ValidateRange(1,3600)] [int]$SampleSeconds = 15, [ValidateRange(5,1440)] [int]$SegmentMinutes = 60 ) Assert-Administrator $CollectorName = $Script:CollectorName $OutputDir = $Script:OutputDir Write-MonitorBanner -Title 'SMBpulse' -Subtitle 'Langzeitmessung von SMB- und Netzwerkdurchsatz' # Alte bzw. bereits vorhandene Collector sauber entfernen. Write-SectionTitle 'Bestehende Collector entfernen' foreach ($name in @($Script:LegacyCollectors + $CollectorName)) { Stop-AndDeleteCollector -Name $name } New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null Write-SectionTitle 'Performance Counter ermitteln' $AllSets = Get-Counter -ListSet * $SmbSet = Get-SmbCounterSet -AllSets $AllSets $NetSet = @($AllSets) | Where-Object { $_.CounterSetName -eq 'Netzwerkschnittstelle' -or $_.CounterSetName -eq 'Network Interface' } | Select-Object -First 1 if (-not $NetSet) { throw 'Der Performance-Counter-Satz fuer Netzwerkschnittstellen wurde nicht gefunden.' } $SmbPaths = @($SmbSet.PathsWithInstances) $NetPaths = @($NetSet.PathsWithInstances) $SmbData = Find-SmbTotalCounter -Paths $SmbPaths -Patterns @( '\\Datenbytes/Sek\.$', '\\Data Bytes/sec$' ) $SmbRead = Find-SmbTotalCounter -Paths $SmbPaths -Patterns @( '\\Gelesene Bytes/s$', '\\Read Bytes/sec$' ) $SmbWrite = Find-SmbTotalCounter -Paths $SmbPaths -Patterns @( '\\Geschriebene Bytes/Sek\.$', '\\Write Bytes/sec$' ) $SmbCounters = @($SmbData, $SmbRead, $SmbWrite) | Where-Object { $_ } if ($SmbCounters.Count -lt 3) { Write-Host '' Write-Warn 'Gefundene SMB-Counter:' $SmbPaths | Where-Object { $_ -match '\(_Total\)\\' } | ForEach-Object { Write-Host " $_" } throw 'Nicht alle benoetigten SMB-Durchsatzzaehler wurden gefunden.' } $NetCounters = @( $NetPaths | Where-Object { $_ -match '\\(Gesamtanzahl Bytes/s|Bytes Total/sec|Bytes gesendet/s|Bytes Sent/sec|Empfangene Bytes/s|Bytes Received/sec)$' } ) if ($NetCounters.Count -eq 0) { throw 'Keine Netzwerk-Byte/s-Counter wurden gefunden.' } $Counters = @($SmbCounters | Sort-Object -Unique) + @($NetCounters | Sort-Object -Unique) Write-SectionTitle 'Aufzuzeichnende Counter' $Counters | ForEach-Object { Write-Host " $_" } $SampleInterval = Convert-ToLogmanDuration -TotalSeconds $SampleSeconds $RunDuration = Convert-ToLogmanDuration -TotalSeconds ($Days * 24 * 3600) $SegmentDuration = Convert-ToLogmanDuration -TotalSeconds ($SegmentMinutes * 60) $Arguments = @( 'create', 'counter', $CollectorName, '-f', 'bin', '-o', "$OutputDir\SMB", '-si', $SampleInterval, '-rf', $RunDuration, '-cnf', $SegmentDuration, '-v', 'mmddhhmm', '-c' ) $Arguments += $Counters Write-SectionTitle 'Datensammler erstellen' $createExit = Invoke-Logman -ArgumentList $Arguments if ($createExit -ne 0) { throw "logman create ist mit Exitcode $createExit fehlgeschlagen." } Write-SectionTitle 'Messung starten' $startExit = Invoke-Logman -ArgumentList @('start', $CollectorName) if ($startExit -ne 0) { throw "logman start ist mit Exitcode $startExit fehlgeschlagen." } Write-Ok 'Messung laeuft.' Write-InfoBox -Title 'Ueberblick' -Lines @( "Collector: $CollectorName", "Dauer: $Days Tag(e)", "Intervall: $SampleSeconds Sekunden", "BLG-Segment: $SegmentMinutes Minuten", "Ausgabe: $OutputDir" ) Write-Muted 'Status pruefen:' Write-Muted " logman query $CollectorName" Write-Muted '' Write-Muted 'Live-Monitor:' Write-Muted ' Watch-SmbPulse' Write-Muted '' Write-Muted 'Auswertung:' Write-Muted ' Get-SmbPulseReport' Write-Host '' } # ---------------------------------------------------------------------------- # Watch-SmbPulse # ---------------------------------------------------------------------------- function Watch-SmbPulse { <# .SYNOPSIS Live-Anzeige des aktuellen SMB-Durchsatzes. .DESCRIPTION Zeigt den aktuellen SMB-Durchsatz direkt aus den Windows Performance Countern. Es wird keine BLG-Datei benoetigt und der laufende Langzeit-Collector wird nicht beeinflusst. Standardmaessig werden angezeigt: - SMB Gesamt - SMB Lesen - SMB Schreiben Mit -PerShare werden zusaetzlich die einzelnen SMB-Freigaben angezeigt. Die Farben kodieren die Auslastung eines 1-Gbit/s-Links: - gruen: unter 700 Mbit/s - gelb: 700 - 900 Mbit/s - rot: ueber 900 Mbit/s .PARAMETER IntervalSeconds Aktualisierungsintervall in Sekunden (1-60, Standard 2). .PARAMETER PerShare Zeigt zusaetzlich die einzelnen SMB-Freigaben statt nur _Total. .EXAMPLE Watch-SmbPulse .EXAMPLE Watch-SmbPulse -IntervalSeconds 1 -PerShare #> [CmdletBinding()] param( [ValidateRange(1,60)] [int]$IntervalSeconds = 2, [switch]$PerShare ) $SmbSet = Get-SmbCounterSet $AllPaths = @($SmbSet.PathsWithInstances) if ($PerShare) { $CounterPaths = @( $AllPaths | Where-Object { (Get-SmbCounterKind -Path $_) -ne $null } | Sort-Object -Unique ) } else { $CounterPaths = @( $AllPaths | Where-Object { $_ -match '\(_Total\)\\' -and (Get-SmbCounterKind -Path $_) -ne $null } | Sort-Object -Unique ) } if ($CounterPaths.Count -eq 0) { throw 'Keine passenden SMB-Durchsatzzaehler wurden gefunden.' } Write-MonitorBanner -Title 'SMBpulse Live Monitor' -Subtitle 'Aktuelle Durchsatzwerte aller Freigaben' Write-Host "Intervall: $IntervalSeconds Sekunde(n)" Write-Host 'Beenden: STRG+C' Write-Host '' while ($true) { try { $sample = Get-Counter -Counter $CounterPaths -ErrorAction Stop $rows = foreach ($counter in $sample.CounterSamples) { $kind = Get-SmbCounterKind -Path ([string]$counter.Path) if (-not $kind) { continue } $value = [double]$counter.CookedValue if ([double]::IsNaN($value) -or [double]::IsInfinity($value) -or $value -lt 0) { continue } $instance = [string]$counter.InstanceName if (-not $PerShare -or $instance -eq '_Total') { $displayInstance = 'Alle Freigaben' } else { $displayInstance = $instance } [PSCustomObject]@{ Freigabe = $displayInstance Typ = $kind MBPS = $value / 1MB MbitSec = ($value * 8) / 1000000 } } $sorted = @( $rows | Sort-Object Freigabe, @{ Expression = { switch ($_.Typ) { 'Gesamt' { 1 } 'Lesen' { 2 } 'Schreiben' { 3 } default { 9 } } } } ) Clear-Host Write-Host 'SMBpulse Live Monitor' -ForegroundColor Cyan Write-Host ('=' * 76) -ForegroundColor Cyan Write-Host ("Zeit: {0}" -f (Get-Date -Format 'dd.MM.yyyy HH:mm:ss')) Write-Host ("Intervall: {0} Sekunde(n)" -f $IntervalSeconds) Write-Host 'Beenden: STRG+C' Write-Host '' Write-Host (" {0,-22}{1,-10}{2,12}{3,12}" -f 'Freigabe', 'Typ', 'MB/s', 'Mbit/s') -ForegroundColor Cyan if ($sorted.Count -eq 0) { Write-Host ' (momentan keine verwertbaren Messwerte)' -ForegroundColor Yellow } else { foreach ($row in $sorted) { $color = Get-ThroughputColor -MbitSec $row.MbitSec Write-Host (" {0,-22}" -f $row.Freigabe) -NoNewline Write-Host ("{0,-10}" -f $row.Typ) -NoNewline -ForegroundColor DarkCyan Write-Host ("{0,12}" -f ('{0:N2}' -f $row.MBPS)) -NoNewline -ForegroundColor $color Write-Host ("{0,12}" -f ('{0:N2}' -f $row.MbitSec)) -ForegroundColor $color } } Write-Host '' Write-Host (' ' + [char]0x25A0 + ' ') -NoNewline -ForegroundColor Green Write-Host '< 700 Mbit/s' -NoNewline -ForegroundColor DarkGray Write-Host (' ' + [char]0x25A0 + ' ') -NoNewline -ForegroundColor Yellow Write-Host '700-900 Mbit/s' -NoNewline -ForegroundColor DarkGray Write-Host (' ' + [char]0x25A0 + ' ') -NoNewline -ForegroundColor Red Write-Host '> 900 Mbit/s (1-Gbit-Link)' -ForegroundColor DarkGray Start-Sleep -Seconds $IntervalSeconds } catch { Clear-Host Write-Host 'SMBpulse Live Monitor' -ForegroundColor Cyan Write-Host ('=' * 76) -ForegroundColor Cyan Write-Warn "Fehler beim Lesen der SMB-Counter: $($_.Exception.Message)" Write-Host '' Start-Sleep -Seconds $IntervalSeconds } } } # ---------------------------------------------------------------------------- # Get-SmbPulseReport # ---------------------------------------------------------------------------- function Get-SmbPulseReport { <# .SYNOPSIS Wertet die BLG-Dateien von SMBpulse aus. .DESCRIPTION Liest alle bereits abgeschlossenen BLG-Segmente aus dem aktuellen sowie aus den vorherigen Messpfaden ein. Eine momentan noch aktive/unvollstaendige BLG-Datei wird automatisch uebersprungen. Ausgabe: - Konsolenuebersicht mit Durchschnitt, P95 und Maximum - SMBpulse-Report.csv - SMBpulse-Peaks.csv mit den groessten Einzelspitzen .PARAMETER TopPeaks Anzahl der groessten Einzelspitzen in der Peaks-CSV (1-500, Standard 50). .EXAMPLE Get-SmbPulseReport .EXAMPLE Get-SmbPulseReport -TopPeaks 100 #> [CmdletBinding()] param( [ValidateRange(1,500)] [int]$TopPeaks = 50 ) $OutputDir = $Script:OutputDir $SummaryCsv = $Script:SummaryCsv $PeaksCsv = $Script:PeaksCsv function Get-Percentile { param( [double[]]$Values, [ValidateRange(0,100)] [double]$Percentile ) if (-not $Values -or $Values.Count -eq 0) { return 0 } $sorted = @($Values | Sort-Object) $index = [Math]::Ceiling(($Percentile / 100) * $sorted.Count) - 1 if ($index -lt 0) { $index = 0 } return [double]$sorted[$index] } function Get-CounterInfo { param($Counter) $path = [string]$Counter.Path $instance = [string]$Counter.InstanceName if ($path -match 'SMB-Serverfreigaben|SMB Server Shares') { $kind = Get-SmbCounterKind -Path $path if (-not $kind) { return $null } $group = switch ($kind) { 'Gesamt' { 'SMB Gesamt' } 'Lesen' { 'SMB Lesen' } 'Schreiben' { 'SMB Schreiben' } } return @{ Group = $group; Type = 'SMB'; Instance = '_Total' } } if ($path -match 'Netzwerkschnittstelle|Network Interface') { if ($path -match '\\(Gesamtanzahl Bytes/s|Bytes Total/sec)$') { return @{ Group = "Netzwerk Gesamt - $instance"; Type = 'Netzwerk'; Instance = $instance } } if ($path -match '\\(Empfangene Bytes/s|Bytes Received/sec)$') { return @{ Group = "Netzwerk Empfang - $instance"; Type = 'Netzwerk'; Instance = $instance } } if ($path -match '\\(Bytes gesendet/s|Bytes Sent/sec)$') { return @{ Group = "Netzwerk Senden - $instance"; Type = 'Netzwerk'; Instance = $instance } } } return $null } Write-MonitorBanner -Title 'SMBpulse Report' -Subtitle 'Auswertung der abgeschlossenen BLG-Segmente' New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null $files = @() foreach ($dir in $Script:LogDirs) { if (Test-Path $dir) { $files += Get-ChildItem -Path $dir -Filter '*.blg' -File -ErrorAction SilentlyContinue } } $files = @($files | Sort-Object FullName -Unique) if ($files.Count -eq 0) { Write-Warn 'Es wurden noch keine BLG-Dateien gefunden.' return } Write-Muted "Gefundene BLG-Dateien: $($files.Count)" Write-Host '' $samples = New-Object System.Collections.Generic.List[object] $readableFiles = 0 $skippedFiles = 0 foreach ($file in ($files | Sort-Object LastWriteTime)) { Write-Host -NoNewline "Pruefe $($file.FullName) ... " try { $data = @(Import-Counter -Path $file.FullName -ErrorAction Stop) $added = 0 foreach ($sampleSet in $data) { foreach ($counter in $sampleSet.CounterSamples) { $info = Get-CounterInfo -Counter $counter if (-not $info) { continue } $value = [double]$counter.CookedValue if ([double]::IsNaN($value) -or [double]::IsInfinity($value) -or $value -lt 0) { continue } $samples.Add([PSCustomObject]@{ Timestamp = $sampleSet.Timestamp Group = $info.Group Type = $info.Type Instance = $info.Instance BytesSec = $value MBsec = [Math]::Round($value / 1MB, 3) MbitSec = [Math]::Round(($value * 8) / 1000000, 3) Source = $file.Name }) $added++ } } if ($added -gt 0) { Write-Ok "OK ($added Samples)" $readableFiles++ } else { Write-Warn 'keine relevanten Daten' } } catch { Write-Warn 'noch aktiv/unlesbar - uebersprungen' $skippedFiles++ } } if ($samples.Count -eq 0) { Write-Host '' Write-Warn 'Noch keine abgeschlossene BLG-Datei mit auswertbaren Daten vorhanden.' Write-Warn 'Bei der Standardkonfiguration entsteht nach etwa einer Stunde das erste abgeschlossene Segment.' return } $results = foreach ($group in ($samples | Group-Object Group)) { $values = @($group.Group | ForEach-Object { [double]$_.BytesSec }) $avg = [double](($values | Measure-Object -Average).Average) $max = [double](($values | Measure-Object -Maximum).Maximum) $p95 = [double](Get-Percentile -Values $values -Percentile 95) $first = ($group.Group | Sort-Object Timestamp | Select-Object -First 1).Timestamp $last = ($group.Group | Sort-Object Timestamp | Select-Object -Last 1).Timestamp [PSCustomObject]@{ Counter = $group.Name Samples = $values.Count Von = $first Bis = $last 'Durchschnitt MB/s' = [Math]::Round($avg / 1MB, 2) 'P95 MB/s' = [Math]::Round($p95 / 1MB, 2) 'Maximum MB/s' = [Math]::Round($max / 1MB, 2) 'Durchschnitt Mbit/s' = [Math]::Round(($avg * 8) / 1000000, 2) 'P95 Mbit/s' = [Math]::Round(($p95 * 8) / 1000000, 2) 'Maximum Mbit/s' = [Math]::Round(($max * 8) / 1000000, 2) } } $results = @($results | Sort-Object Counter) Write-Host '' Write-Ok "Lesbare BLG-Dateien: $readableFiles" Write-Warn "Uebersprungene BLG-Dateien: $skippedFiles" Write-SectionTitle 'Zusammenfassung' foreach ($result in $results) { Write-Host '' Write-Host $result.Counter -ForegroundColor DarkCyan Write-Muted (" {0} Messwerte ({1} - {2})" -f $result.Samples, $result.Von.ToString('dd.MM.yyyy HH:mm'), $result.Bis.ToString('dd.MM.yyyy HH:mm')) Write-Host (" Durchschnitt: {0,9:N2} MB/s {1,9:N2} Mbit/s" -f $result.'Durchschnitt MB/s', $result.'Durchschnitt Mbit/s') -ForegroundColor Green Write-Host (" P95: {0,9:N2} MB/s {1,9:N2} Mbit/s" -f $result.'P95 MB/s', $result.'P95 Mbit/s') -ForegroundColor Yellow Write-Host (" Maximum: {0,9:N2} MB/s {1,9:N2} Mbit/s" -f $result.'Maximum MB/s', $result.'Maximum Mbit/s') -ForegroundColor Red } $results | Export-Csv -Path $SummaryCsv -Delimiter ';' -NoTypeInformation -Encoding UTF8 $peaks = @( $samples | Sort-Object BytesSec -Descending | Select-Object -First $TopPeaks Timestamp, Group, Instance, MBsec, MbitSec, Source ) $peaks | Export-Csv -Path $PeaksCsv -Delimiter ';' -NoTypeInformation -Encoding UTF8 Write-SectionTitle 'Ergebnisdateien' Write-Ok " $SummaryCsv" Write-Ok " $PeaksCsv" Write-Host '' return $results } # ---------------------------------------------------------------------------- # Uninstall-SmbPulse # ---------------------------------------------------------------------------- function Uninstall-SmbPulse { <# .SYNOPSIS Entfernt alle systemseitigen Aenderungen von SMBpulse. .DESCRIPTION Stoppt und loescht: - SMB_Pulse - den alten Collector SMB_Throughput_Monitor - den alten Collector SMB_7Tage Standardmaessig werden ausserdem die von SMBpulse verwendeten Messdaten-Verzeichnisse geloescht. Vor dem Loeschen der Messdaten fragt das Cmdlet nach; mit -Force wird die Rueckfrage uebersprungen, mit -KeepLogs werden die Messdaten grundsaetzlich behalten. Windows Performance Logs and Alerts (PLA) kann die zuletzt beschriebene BLG-Datei nach "logman stop/delete" noch einige Sekunden geoeffnet halten. Das Cmdlet wartet deshalb gezielt auf die Dateifreigabe und wiederholt die Loeschung. Der Dienst "Performance Logs & Alerts" wird bewusst NICHT global beendet, damit andere Performance-Collector-Sets auf dem Server nicht gestoert werden. .PARAMETER KeepLogs Behaelt die Messdaten, stoppt und entfernt aber die Collector-Sets. .PARAMETER Force Ueberspringt die Rueckfrage vor dem Loeschen der Messdaten. .PARAMETER WaitSeconds Maximale Wartezeit auf die Freigabe geoeffneter BLG-/CSV-Dateien (5-120, Standard 30). .EXAMPLE Uninstall-SmbPulse .EXAMPLE Uninstall-SmbPulse -Force .EXAMPLE Uninstall-SmbPulse -KeepLogs -WaitSeconds 60 #> [CmdletBinding()] param( [switch]$KeepLogs, [switch]$Force, [ValidateRange(5,120)] [int]$WaitSeconds = 30 ) Assert-Administrator $Collectors = $Script:LegacyCollectors + @($Script:CollectorName) $LogDirs = $Script:LogDirs function Test-CollectorExists { param([string]$Name) return ((Invoke-Logman -ArgumentList @('query', $Name) -Quiet) -eq 0) } function Stop-AndRemoveCollector { param([string]$Name) if (-not (Test-CollectorExists -Name $Name)) { Write-Muted "Datensammler '$Name' ist nicht vorhanden." return } Write-Host "Stoppe Datensammler '$Name' ..." $null = Invoke-Logman -ArgumentList @('stop', $Name) -Quiet # PLA arbeitet beim Schliessen der aktiven BLG teilweise asynchron. Start-Sleep -Seconds 1 Write-Host "Loesche Datensammler '$Name' ..." $null = Invoke-Logman -ArgumentList @('delete', $Name) -Quiet for ($i = 0; $i -lt $WaitSeconds; $i++) { if (-not (Test-CollectorExists -Name $Name)) { Write-Ok ' entfernt' return } Start-Sleep -Seconds 1 } throw "Der Datensammler '$Name' konnte innerhalb von $WaitSeconds Sekunden nicht vollstaendig entfernt werden." } function Remove-DirectoryWithRetry { param( [string]$Path, [int]$TimeoutSeconds ) if (-not (Test-Path -LiteralPath $Path)) { Write-Muted "$Path ist nicht vorhanden." return } Write-Host "Loesche $Path ..." $lastError = $null for ($i = 0; $i -lt $TimeoutSeconds; $i++) { try { Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop if (-not (Test-Path -LiteralPath $Path)) { Write-Ok ' entfernt' return } } catch [System.IO.IOException] { $lastError = $_ } catch [System.UnauthorizedAccessException] { $lastError = $_ } if ($i -eq 0) { Write-Warn ' Datei noch durch Windows/PLA geoeffnet - warte auf Freigabe ...' } Start-Sleep -Seconds 1 } # Noch einmal anzeigen, welche Dateien ggf. uebrig sind. $remaining = @( Get-ChildItem -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName ) Write-Host '' Write-Err "Das Verzeichnis konnte nach $TimeoutSeconds Sekunden noch nicht vollstaendig geloescht werden." if ($remaining.Count -gt 0) { Write-Warn 'Noch vorhandene Dateien:' $remaining | ForEach-Object { Write-Host " $_" } } if ($lastError) { throw $lastError } throw "Loeschen von '$Path' fehlgeschlagen." } Write-MonitorBanner -Title 'SMBpulse' -Subtitle 'Deinstallation' $removeLogs = -not $KeepLogs $planLines = @( ('Datensammler: ' + ($Collectors -join ', ')), ('Messpfad: ' + ($LogDirs -join ', ')) ) if ($KeepLogs) { $planLines += 'Messdaten: bleiben erhalten (-KeepLogs)' } Write-InfoBox -Title 'Durchgefuehrte Aenderungen' -Lines $planLines if ($removeLogs -and -not $Force) { Write-Warn 'Die Messdaten-Verzeichnisse werden vollstaendig geloescht.' $answer = Read-Host 'Fortfahren [J/N]' if ($answer -notmatch '^(j|ja|y|yes)$') { $removeLogs = $false Write-Warn 'Messdaten werden behalten.' } } Write-SectionTitle 'Datensammler entfernen' foreach ($collector in $Collectors) { Stop-AndRemoveCollector -Name $collector } if ($removeLogs) { Write-SectionTitle 'Messdaten loeschen' Write-Host 'Warte kurz auf die Freigabe eventuell noch geoeffneter BLG-Dateien ...' Start-Sleep -Seconds 2 foreach ($dir in $LogDirs) { Remove-DirectoryWithRetry -Path $dir -TimeoutSeconds $WaitSeconds } } else { Write-SectionTitle 'Messdaten behalten' Write-Warn 'Die Messdaten werden nicht geloescht.' } Write-Host '' Write-Ok 'Deinstallation abgeschlossen.' Write-Host '' } Export-ModuleMember -Function 'Start-SmbPulse', 'Watch-SmbPulse', 'Get-SmbPulseReport', 'Uninstall-SmbPulse' |