Scripts/compare-blg-report.ps1
|
#Requires -Version 5.1 # Run on FILESERVER (powershell.exe 5.1). Stop the Perfmon set first. # Verifies SMBeat CIM (SMB-Perf + NIC) against Perfmon .blg in the same window. # # powershell.exe -NoProfile -File .\compare-blg-report.ps1 # powershell.exe -NoProfile -File .\compare-blg-report.ps1 -BlgPath 'C:\PerfLogs\Admin\SMB_MBF' # powershell.exe -NoProfile -File .\compare-blg-report.ps1 -Start '2026-08-30 13:49' -End '2026-08-30 14:49' [CmdletBinding()] param( [string]$BlgPath = 'C:\PerfLogs\Admin\SMB_MBF', [datetime]$Start, [datetime]$End ) $ErrorActionPreference = 'Stop' function ConvertTo-SMBeatCompareGB { param([double]$Bytes) [math]::Round(($Bytes / 1GB), 3) } function ConvertFrom-SMBeatCompareNumber { param([string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return $null } $s = $Text.Trim().Trim('"') if ($s.Length -eq 0 -or $s -eq '-' -or $s -eq ' ') { return $null } $n = 0.0 if ([double]::TryParse($s, [System.Globalization.NumberStyles]::Float, [cultureinfo]::GetCultureInfo('de-DE'), [ref]$n)) { return $n } if ([double]::TryParse($s, [System.Globalization.NumberStyles]::Float, [cultureinfo]::InvariantCulture, [ref]$n)) { return $n } return $null } function ConvertFrom-SMBeatCompareWhen { param([string]$Text) $s = $Text.Trim().Trim('"') $styles = [System.Globalization.DateTimeStyles]::AllowWhiteSpaces $dto = [datetime]::MinValue foreach ($cult in @([cultureinfo]::GetCultureInfo('de-DE'), [cultureinfo]::InvariantCulture, [cultureinfo]::GetCultureInfo('en-US'))) { if ([datetime]::TryParse($s, $cult, $styles, [ref]$dto)) { return $dto } } return $null } function Test-SMBeatCompareHeader { param( [string]$Header, [string]$Kind ) $h = $Header.ToLowerInvariant() $isShare = ($h -like '*freigabe*' -or $h -like '*share*') $isSmb = ($h -like '*smb-server*' -or $h -like '*smb server*' -or $h -like '*smbserver*') $isNic = ($h -like '*netzwerkschnittstelle*' -or $h -like '*network interface*') $isSent = ($h -like '*gesendete*' -or $h -like '*bytes sent*' -or $h -like '*bytes gesendet*' -or $h -like '*sent bytes*') $isRecv = ($h -like '*empfangene*' -or $h -like '*bytes received*' -or $h -like '*bytes empfangen*' -or $h -like '*received bytes*') $isPrivat = ($h -like '*privat*') $isAdmin = ($h -like '*admin$*' -or $h -like '*ipc$*') if ($Kind -eq 'smbServerSent') { return ($isSmb -and -not $isShare -and $isSent) } if ($Kind -eq 'smbServerRecv') { return ($isSmb -and -not $isShare -and $isRecv) } if ($Kind -eq 'sharePrivatSent') { return ($isShare -and $isPrivat -and $isSent -and -not $isAdmin) } if ($Kind -eq 'sharePrivatRecv') { return ($isShare -and $isPrivat -and $isRecv -and -not $isAdmin) } if ($Kind -eq 'nicSent') { return ($isNic -and $isSent) } if ($Kind -eq 'nicRecv') { return ($isNic -and $isRecv) } return $false } function Get-SMBeatCompareRowRate { param( [int[]]$Indexes, [string[]]$Cells ) $rate = 0.0 $any = $false if ($null -eq $Indexes) { return $null } foreach ($i in $Indexes) { if ($i -ge $Cells.Count) { continue } $n = ConvertFrom-SMBeatCompareNumber -Text $Cells[$i] if ($null -ne $n) { $rate += [double]$n $any = $true } } if (-not $any) { return $null } $rate } function Get-SMBeatCompareBlgFiles { param( [string]$Path ) if (-not (Test-Path -LiteralPath $Path)) { throw ('BLG path not found: {0}' -f $Path) } $item = Get-Item -LiteralPath $Path if (-not $item.PSIsContainer) { if ($item.Extension -ne '.blg') { throw ('Not a .blg file: {0}' -f $Path) } return @($item) } $list = @(Get-ChildItem -LiteralPath $Path -Filter '*.blg' -File -Recurse | Sort-Object LastWriteTime, Name) if ($list.Count -eq 0) { throw ('No .blg under {0}.' -f $Path) } $list } function ConvertFrom-SMBeatCompareBlg { param( [Parameter(Mandatory = $true)] [string]$Blg, [Parameter(Mandatory = $true)] [string]$Relog ) $empty = [PSCustomObject]@{ File = $Blg Headers = @() Columns = @{} Samples = @() Skipped = $true Note = '' } $csv = Join-Path $env:TEMP ('smbeat-blg-{0}.csv' -f [guid]::NewGuid().ToString('N')) try { Write-Host ('relog {0}' -f $Blg) & $Relog $Blg -f csv -o $csv | Out-Host $csvOk = $false if ((Test-Path -LiteralPath $csv) -and ((Get-Item -LiteralPath $csv).Length -gt 0)) { $csvOk = $true } if (-not $csvOk) { Write-Host ('SKIP (locked or unreadable, often the live BLG): {0}' -f $Blg) $empty.Note = 'relog failed (live or locked)' return $empty } $lines = @(Get-Content -LiteralPath $csv -Encoding Default | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($lines.Count -lt 2) { $empty.Skipped = $false $empty.Note = 'no samples' return $empty } $headerLine = $lines[0] $sep = ',' if ($headerLine.IndexOf(';') -ge 0 -and ($headerLine.Split(';').Count -gt $headerLine.Split(',').Count)) { $sep = ';' } $headers = @($headerLine.Split($sep)) $idx = @{ smbSent = @() smbRecv = @() privSent = @() privRecv = @() nicSent = @() nicRecv = @() } for ($i = 1; $i -lt $headers.Count; $i++) { $name = $headers[$i] if (Test-SMBeatCompareHeader -Header $name -Kind 'smbServerSent') { $idx.smbSent += $i } if (Test-SMBeatCompareHeader -Header $name -Kind 'smbServerRecv') { $idx.smbRecv += $i } if (Test-SMBeatCompareHeader -Header $name -Kind 'sharePrivatSent') { $idx.privSent += $i } if (Test-SMBeatCompareHeader -Header $name -Kind 'sharePrivatRecv') { $idx.privRecv += $i } if (Test-SMBeatCompareHeader -Header $name -Kind 'nicSent') { $idx.nicSent += $i } if (Test-SMBeatCompareHeader -Header $name -Kind 'nicRecv') { $idx.nicRecv += $i } } $samples = New-Object System.Collections.Generic.List[object] foreach ($line in ($lines | Select-Object -Skip 1)) { $cells = @($line.Split($sep)) if ($cells.Count -lt 2) { continue } $when = ConvertFrom-SMBeatCompareWhen -Text $cells[0] if ($null -eq $when) { continue } $samples.Add([PSCustomObject]@{ When = $when File = $Blg smbSent = (Get-SMBeatCompareRowRate -Indexes $idx.smbSent -Cells $cells) smbRecv = (Get-SMBeatCompareRowRate -Indexes $idx.smbRecv -Cells $cells) privSent = (Get-SMBeatCompareRowRate -Indexes $idx.privSent -Cells $cells) privRecv = (Get-SMBeatCompareRowRate -Indexes $idx.privRecv -Cells $cells) nicSent = (Get-SMBeatCompareRowRate -Indexes $idx.nicSent -Cells $cells) nicRecv = (Get-SMBeatCompareRowRate -Indexes $idx.nicRecv -Cells $cells) }) | Out-Null } [PSCustomObject]@{ File = $Blg Headers = $headers Columns = $idx Samples = $samples.ToArray() Skipped = $false Note = '' } } finally { Remove-Item -LiteralPath $csv -Force -ErrorAction SilentlyContinue } } function Show-SMBeatCompareCols { param( [string]$Label, [int[]]$Indexes, [string[]]$Headers ) $text = '(none)' if ($Indexes -and $Indexes.Count -gt 0) { $text = ($Indexes | ForEach-Object { $Headers[$_] }) -join ' | ' } Write-Host ('{0}: {1}' -f $Label, $text) } function Add-SMBeatCompareVolume { param( [hashtable]$Map, [string]$Key, $Rate, [double]$Seconds ) if ($null -eq $Rate) { return } $Map[$Key] = $Map[$Key] + ([double]$Rate * $Seconds) } if ($env:OS -ne 'Windows_NT') { throw 'Needs Windows relog.exe. Run on FILESERVER.' } $relog = Join-Path $env:SystemRoot 'System32\relog.exe' if (-not (Test-Path -LiteralPath $relog)) { throw 'relog.exe not found.' } $files = @(Get-SMBeatCompareBlgFiles -Path $BlgPath) Write-Host ('Path : {0}' -f $BlgPath) Write-Host ('BLGs : {0}' -f $files.Count) foreach ($f in $files) { Write-Host (' {0}' -f $f.FullName) } $parsed = New-Object System.Collections.Generic.List[object] foreach ($f in $files) { $parsed.Add((ConvertFrom-SMBeatCompareBlg -Blg $f.FullName -Relog $relog)) | Out-Null } $firstWithCols = $null foreach ($pack in $parsed) { if ($pack.Headers.Count -gt 0) { $firstWithCols = $pack break } } Write-Host '' Write-Host '=== columns (first blg with samples) ===' if ($null -eq $firstWithCols) { throw 'No samples in any BLG CSV.' } Show-SMBeatCompareCols -Label 'SMB-Server Out' -Indexes $firstWithCols.Columns.smbSent -Headers $firstWithCols.Headers Show-SMBeatCompareCols -Label 'SMB-Server In ' -Indexes $firstWithCols.Columns.smbRecv -Headers $firstWithCols.Headers Show-SMBeatCompareCols -Label 'Privat Out ' -Indexes $firstWithCols.Columns.privSent -Headers $firstWithCols.Headers Show-SMBeatCompareCols -Label 'Privat In ' -Indexes $firstWithCols.Columns.privRecv -Headers $firstWithCols.Headers Show-SMBeatCompareCols -Label 'NIC Out ' -Indexes $firstWithCols.Columns.nicSent -Headers $firstWithCols.Headers Show-SMBeatCompareCols -Label 'NIC In ' -Indexes $firstWithCols.Columns.nicRecv -Headers $firstWithCols.Headers Write-Host '' Write-Host '=== per blg ===' $fileRows = New-Object System.Collections.Generic.List[object] foreach ($pack in $parsed) { $from = $null $to = $null if ($pack.Samples.Count -gt 0) { $from = ($pack.Samples | Measure-Object -Property When -Minimum).Minimum $to = ($pack.Samples | Measure-Object -Property When -Maximum).Maximum } $folder = Split-Path -Leaf (Split-Path -Parent $pack.File) $note = '' if ($pack.PSObject.Properties['Note'] -and $pack.Note) { $note = [string]$pack.Note } $fileRows.Add([PSCustomObject]@{ File = ('{0}\{1}' -f $folder, [System.IO.Path]::GetFileName($pack.File)) Samples = $pack.Samples.Count From = $from To = $to Note = $note }) | Out-Null } $fileRows | Format-Table -AutoSize $merged = New-Object System.Collections.Generic.List[object] $seen = @{} foreach ($pack in $parsed) { foreach ($row in $pack.Samples) { $key = $row.When.ToString('o') if ($seen.ContainsKey($key)) { continue } $seen[$key] = $true $merged.Add($row) | Out-Null } } $ordered = @($merged | Sort-Object When) $sum = @{ smbSent = 0.0 smbRecv = 0.0 privSent = 0.0 privRecv = 0.0 nicSent = 0.0 nicRecv = 0.0 } $used = 0 $first = $null $last = $null $prevWhen = $null $hasStart = $PSBoundParameters.ContainsKey('Start') $hasEnd = $PSBoundParameters.ContainsKey('End') foreach ($row in $ordered) { $when = $row.When if ($hasStart -and $when -lt $Start) { continue } if ($hasEnd -and $when -gt $End) { continue } $dt = 60.0 if ($null -ne $prevWhen) { $gap = ($when - $prevWhen).TotalSeconds if ($gap -gt 0 -and $gap -le 3600) { $dt = $gap } } $prevWhen = $when if ($null -eq $first) { $first = $when } $last = $when $used++ Add-SMBeatCompareVolume -Map $sum -Key 'smbSent' -Rate $row.smbSent -Seconds $dt Add-SMBeatCompareVolume -Map $sum -Key 'smbRecv' -Rate $row.smbRecv -Seconds $dt Add-SMBeatCompareVolume -Map $sum -Key 'privSent' -Rate $row.privSent -Seconds $dt Add-SMBeatCompareVolume -Map $sum -Key 'privRecv' -Rate $row.privRecv -Seconds $dt Add-SMBeatCompareVolume -Map $sum -Key 'nicSent' -Rate $row.nicSent -Seconds $dt Add-SMBeatCompareVolume -Map $sum -Key 'nicRecv' -Rate $row.nicRecv -Seconds $dt } Write-Host '=== window (all blgs) ===' if ($null -eq $first) { Write-Host 'No samples in the selected window.' return } Write-Host ('From : {0}' -f $first) Write-Host ('To : {0}' -f $last) Write-Host ('Samples : {0}' -f $used) Write-Host ('Seconds : {0:N0}' -f (($last - $first).TotalSeconds)) Write-Host '' Write-Host '=== volume (rate * interval, all blgs) ===' @( [PSCustomObject]@{ Source = 'SMB-Server Out'; Bytes = [int64]$sum.smbSent; GB = (ConvertTo-SMBeatCompareGB -Bytes $sum.smbSent) } [PSCustomObject]@{ Source = 'SMB-Server In'; Bytes = [int64]$sum.smbRecv; GB = (ConvertTo-SMBeatCompareGB -Bytes $sum.smbRecv) } [PSCustomObject]@{ Source = 'Privat Out'; Bytes = [int64]$sum.privSent; GB = (ConvertTo-SMBeatCompareGB -Bytes $sum.privSent) } [PSCustomObject]@{ Source = 'Privat In'; Bytes = [int64]$sum.privRecv; GB = (ConvertTo-SMBeatCompareGB -Bytes $sum.privRecv) } [PSCustomObject]@{ Source = 'NIC Out'; Bytes = [int64]$sum.nicSent; GB = (ConvertTo-SMBeatCompareGB -Bytes $sum.nicSent) } [PSCustomObject]@{ Source = 'NIC In'; Bytes = [int64]$sum.nicRecv; GB = (ConvertTo-SMBeatCompareGB -Bytes $sum.nicRecv) } ) | Format-Table -AutoSize Write-Host 'Paste this output. HTML is compared in chat, not in this script.' |