Public/Get-SMBeatReport.ps1

#Requires -Version 5.1

function Get-SMBeatReport {
    <#
    .SYNOPSIS
        Aggregates SMBeat samples for an arbitrary window by hour, day, and/or week.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Local')]
    param(
        [Parameter(ParameterSetName = 'ComputerName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]$ComputerName,
        [Parameter(ParameterSetName = 'Session')]
        [System.Management.Automation.Runspaces.PSSession[]]$Session,
        [pscredential]$Credential,
        [System.Management.Automation.Runspaces.AuthenticationMechanism]$Authentication,
        [switch]$UseSSL,
        [int]$Port,
        [System.Management.Automation.Remoting.PSSessionOption]$SessionOption,
        [int]$ThrottleLimit = 32,

        [datetime]$Start,
        [datetime]$End,
        [int]$LastHours,
        [int]$LastDays,
        [int]$LastWeeks,
        [ValidateSet('Hour', 'Day', 'Week')]
        [string[]]$Granularity = @('Hour', 'Day', 'Week'),
        [object]$TimeZone,
        [string]$Path
    )

    begin {
        Assert-SMBeatRemotingExclusive -ComputerName $ComputerName -CimSession $null -Session $Session
        $tz = Resolve-SMBeatTimeZone -TimeZone $TimeZone

        $endUtc = Get-SMBeatUtcNow
        if ($PSBoundParameters.ContainsKey('End')) {
            $endUtc = $End.ToUniversalTime()
        }
        $startUtc = $null
        if ($PSBoundParameters.ContainsKey('Start')) {
            $startUtc = $Start.ToUniversalTime()
        }
        elseif ($LastHours -gt 0) {
            $startUtc = $endUtc.AddHours(-1 * $LastHours)
        }
        elseif ($LastDays -gt 0) {
            $startUtc = $endUtc.AddDays(-1 * $LastDays)
        }
        elseif ($LastWeeks -gt 0) {
            $startUtc = $endUtc.AddDays(-7 * $LastWeeks)
        }
        else {
            $startUtc = $endUtc.AddDays(-7)
        }
        if ($startUtc -ge $endUtc) {
            throw 'Start must be earlier than End.'
        }

        $duration = $endUtc - $startUtc
        $prevEnd = $startUtc
        $prevStart = $startUtc - $duration
        $gran = @($Granularity)
        $computers = New-Object System.Collections.Generic.List[string]
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Local') {
            $root = Get-SMBeatDataRoot -Path $Path
            New-SMBeatReportFromDataRoot -DataRoot $root -WindowStart $startUtc -WindowEnd $endUtc -PrevStart $prevStart -PrevEnd $prevEnd -TimeZone $tz -Granularity $gran
            return
        }

        if ($PSCmdlet.ParameterSetName -eq 'ComputerName') {
            foreach ($name in @($ComputerName)) {
                if (-not [string]::IsNullOrWhiteSpace($name)) {
                    $computers.Add($name) | Out-Null
                }
            }
            return
        }

        $payload = @{
            Path        = $Path
            StartTicks  = $startUtc.Ticks
            EndTicks    = $endUtc.Ticks
            PrevSTicks  = $prevStart.Ticks
            PrevETicks  = $prevEnd.Ticks
            TzId        = $tz.Id
            Granularity = $gran
        }

        $sb = {
            param($P)
            Import-Module SMBeat -ErrorAction Stop
            $tz = [TimeZoneInfo]::FindSystemTimeZoneById($P.TzId)
            $start = [datetime]::SpecifyKind((New-Object datetime $P.StartTicks), [DateTimeKind]::Utc)
            $end = [datetime]::SpecifyKind((New-Object datetime $P.EndTicks), [DateTimeKind]::Utc)
            $ps = [datetime]::SpecifyKind((New-Object datetime $P.PrevSTicks), [DateTimeKind]::Utc)
            $pe = [datetime]::SpecifyKind((New-Object datetime $P.PrevETicks), [DateTimeKind]::Utc)
            $splat = @{
                Start       = $start
                End         = $end
                Granularity = $P.Granularity
                TimeZone    = $tz
            }
            if ($P.Path) { $splat['Path'] = $P.Path }
            Get-SMBeatReport @splat
        }

        if ($PSCmdlet.ParameterSetName -eq 'Session') {
            $invoke = @{ ScriptBlock = $sb; ArgumentList = @($payload); ThrottleLimit = $ThrottleLimit; Session = $Session }
            $reports = @(Invoke-SMBeatRemoteCommand @invoke | Where-Object { $_ })
            if ($reports.Count -eq 0) {
                New-SMBeatReportFromSamples -Records @() -StartUtc $startUtc -EndUtc $endUtc -TimeZone $tz -Granularity $gran -IntervalSec 60
                return
            }

            $prevTotals = $null
            $prevList = @($reports | ForEach-Object { $_.PreviousTotals } | Where-Object { $_ })
            if ($prevList.Count -gt 0) {
                $pr = [int64]0; $pw = [int64]0; $psn = [int64]0; $pv = [int64]0; $pi = [int64]0; $po = [int64]0; $pret = [int64]0; $pwri = [int64]0; $psmbS = [int64]0; $psmbR = [int64]0
                foreach ($t in $prevList) {
                    $pr += [int64]$t.ReadBytes
                    $pw += [int64]$t.WriteBytes
                    $psn += [int64]$t.SentBytes
                    $pv += [int64]$t.ReceivedBytes
                    $pi += [int64]$t.NicBytesIn
                    $po += [int64]$t.NicBytesOut
                    if ($t.RetrievedBytes) { $pret += [int64]$t.RetrievedBytes } else { $pret += [int64]$t.NicBytesOut }
                    if ($t.PSObject.Properties['WrittenBytes'] -and $t.WrittenBytes) { $pwri += [int64]$t.WrittenBytes } else { $pwri += [int64]$t.ReceivedBytes }
                    if ($t.PSObject.Properties['SmbPerfSentBytes'] -and $t.SmbPerfSentBytes) { $psmbS += [int64]$t.SmbPerfSentBytes }
                    if ($t.PSObject.Properties['SmbPerfReceivedBytes'] -and $t.SmbPerfReceivedBytes) { $psmbR += [int64]$t.SmbPerfReceivedBytes }
                }
                $prevTotals = ConvertTo-SMBeatTotalsObject -Read $pr -Write $pw -Sent $psn -Received $pv -NicIn $pi -NicOut $po -Retrieved $pret -Written $pwri -SmbPerfSent $psmbS -SmbPerfReceived $psmbR
            }

            Merge-SMBeatReports -Reports $reports -PreviousTotals $prevTotals
        }
    }

    end {
        if ($computers.Count -eq 0) {
            return
        }

        $payload = @{
            Path        = $Path
            StartTicks  = $startUtc.Ticks
            EndTicks    = $endUtc.Ticks
            PrevSTicks  = $prevStart.Ticks
            PrevETicks  = $prevEnd.Ticks
            TzId        = $tz.Id
            Granularity = $gran
        }
        $sb = {
            param($P)
            Import-Module SMBeat -ErrorAction Stop
            $tz = [TimeZoneInfo]::FindSystemTimeZoneById($P.TzId)
            $start = [datetime]::SpecifyKind((New-Object datetime $P.StartTicks), [DateTimeKind]::Utc)
            $end = [datetime]::SpecifyKind((New-Object datetime $P.EndTicks), [DateTimeKind]::Utc)
            $splat = @{
                Start       = $start
                End         = $end
                Granularity = $P.Granularity
                TimeZone    = $tz
            }
            if ($P.Path) { $splat['Path'] = $P.Path }
            Get-SMBeatReport @splat
        }
        $invoke = @{
            ScriptBlock   = $sb
            ArgumentList  = @($payload)
            ThrottleLimit = $ThrottleLimit
            ComputerName  = @($computers)
        }
        if ($Credential) { $invoke['Credential'] = $Credential }
        if ($PSBoundParameters.ContainsKey('Authentication')) { $invoke['Authentication'] = $Authentication }
        if ($UseSSL) { $invoke['UseSSL'] = $true }
        if ($Port) { $invoke['Port'] = $Port }
        if ($SessionOption) { $invoke['SessionOption'] = $SessionOption }

        $reports = @(Invoke-SMBeatRemoteCommand @invoke | Where-Object { $_ })
        if ($reports.Count -eq 0) {
            New-SMBeatReportFromSamples -Records @() -StartUtc $startUtc -EndUtc $endUtc -TimeZone $tz -Granularity $gran -IntervalSec 60
            return
        }

        $prevTotals = $null
        $prevList = @($reports | ForEach-Object { $_.PreviousTotals } | Where-Object { $_ })
        if ($prevList.Count -gt 0) {
            $pr = [int64]0; $pw = [int64]0; $psn = [int64]0; $pv = [int64]0; $pi = [int64]0; $po = [int64]0; $pret = [int64]0; $pwri = [int64]0; $psmbS = [int64]0; $psmbR = [int64]0
            foreach ($t in $prevList) {
                $pr += [int64]$t.ReadBytes
                $pw += [int64]$t.WriteBytes
                $psn += [int64]$t.SentBytes
                $pv += [int64]$t.ReceivedBytes
                $pi += [int64]$t.NicBytesIn
                $po += [int64]$t.NicBytesOut
                if ($t.RetrievedBytes) { $pret += [int64]$t.RetrievedBytes } else { $pret += [int64]$t.NicBytesOut }
                if ($t.PSObject.Properties['WrittenBytes'] -and $t.WrittenBytes) { $pwri += [int64]$t.WrittenBytes } else { $pwri += [int64]$t.ReceivedBytes }
                if ($t.PSObject.Properties['SmbPerfSentBytes'] -and $t.SmbPerfSentBytes) { $psmbS += [int64]$t.SmbPerfSentBytes }
                if ($t.PSObject.Properties['SmbPerfReceivedBytes'] -and $t.SmbPerfReceivedBytes) { $psmbR += [int64]$t.SmbPerfReceivedBytes }
            }
            $prevTotals = ConvertTo-SMBeatTotalsObject -Read $pr -Write $pw -Sent $psn -Received $pv -NicIn $pi -NicOut $po -Retrieved $pret -Written $pwri -SmbPerfSent $psmbS -SmbPerfReceived $psmbR
        }

        Merge-SMBeatReports -Reports $reports -PreviousTotals $prevTotals
    }
}

function Export-SMBeatReport {
    <#
    .SYNOPSIS
        Writes an HTML management report and CSV files (Excel-friendly de/en).
    #>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        $Report,
        [datetime]$Start,
        [datetime]$End,
        [int]$LastHours,
        [int]$LastDays,
        [int]$LastWeeks,
        [ValidateSet('Hour', 'Day', 'Week')]
        [string[]]$Granularity = @('Hour', 'Day', 'Week'),
        [object]$TimeZone,
        [string]$Path,
        [string[]]$ComputerName,
        [System.Management.Automation.Runspaces.PSSession[]]$Session,
        [pscredential]$Credential,
        [Parameter(Mandatory = $true)]
        [string]$OutputPath,
        [ValidateSet('de', 'en')]
        [string]$Language,
        [ValidateSet('de', 'en')]
        [string]$CsvCulture
    )

    begin {
        $lang = Get-SMBeatLanguage -Language $Language
        if ([string]::IsNullOrWhiteSpace($CsvCulture)) {
            $CsvCulture = $lang
        }
    }

    process {
        if ($null -eq $Report) {
            $splat = @{}
            if ($PSBoundParameters.ContainsKey('Start')) { $splat['Start'] = $Start }
            if ($PSBoundParameters.ContainsKey('End')) { $splat['End'] = $End }
            if ($LastHours) { $splat['LastHours'] = $LastHours }
            if ($LastDays) { $splat['LastDays'] = $LastDays }
            if ($LastWeeks) { $splat['LastWeeks'] = $LastWeeks }
            if ($Granularity) { $splat['Granularity'] = $Granularity }
            if ($TimeZone) { $splat['TimeZone'] = $TimeZone }
            if ($Path) { $splat['Path'] = $Path }
            if ($ComputerName) { $splat['ComputerName'] = $ComputerName }
            if ($Session) { $splat['Session'] = $Session }
            if ($Credential) { $splat['Credential'] = $Credential }
            $Report = Get-SMBeatReport @splat
        }

        if (-not (Test-Path -LiteralPath $OutputPath)) {
            New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
        }

        $html = ConvertTo-SMBeatHtmlReport -Report $Report -Language $lang
        $htmlFile = Join-Path $OutputPath 'SMBeat-Report.html'
        $utf8Bom = New-Object System.Text.UTF8Encoding $true
        [System.IO.File]::WriteAllText($htmlFile, $html, $utf8Bom)

        $stamp = $Report.StartUtc.ToString('yyyyMMdd', $script:SMBeatInvariant)
        Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-shares-$stamp.csv") -InputObject @($Report.ByShare) -CsvCulture $CsvCulture
        if ($Report.PSObject.Properties['BySmbPerf']) {
            Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-smbperf-$stamp.csv") -InputObject @($Report.BySmbPerf) -CsvCulture $CsvCulture
        }
        Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-clients-$stamp.csv") -InputObject @($Report.ByClient) -CsvCulture $CsvCulture
        Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-users-$stamp.csv") -InputObject @($Report.ByUser) -CsvCulture $CsvCulture
        Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-servers-$stamp.csv") -InputObject @($Report.ByServer) -CsvCulture $CsvCulture
        if ($Report.Hourly -and $Report.Hourly.Count -gt 0) {
            Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-hourly-$stamp.csv") -InputObject @($Report.Hourly) -CsvCulture $CsvCulture
        }
        if ($Report.Daily -and $Report.Daily.Count -gt 0) {
            Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-daily-$stamp.csv") -InputObject @($Report.Daily) -CsvCulture $CsvCulture
        }
        if ($Report.Weekly -and $Report.Weekly.Count -gt 0) {
            Write-SMBeatCsvFile -Path (Join-Path $OutputPath "SMBeat-weekly-$stamp.csv") -InputObject @($Report.Weekly) -CsvCulture $CsvCulture
        }

        [PSCustomObject]@{
            Html     = $htmlFile
            Language = $lang
            CsvCulture = $CsvCulture
            OutputPath = $OutputPath
        }
    }
}