<# .SYNOPSIS Read-only dump of what the Veeam Backup for Microsoft 365 cmdlets return, for troubleshooting the collector. .DESCRIPTION The collector reads properties of Get-VBOJob / Get-VBOJobSession results whose names aren't fully documented. This prints those objects' real structure (property names, types, values and each date's Kind, a few levels deep) for a few jobs and their latest sessions. Nothing is changed. Output is also saved to a text file. .EXAMPLE .\Get-Vb365CollectorDiagnostics.ps1 #> [CmdletBinding()] param( [int]$Jobs = 3, [int]$SessionsPerJob = 2, [string]$OutFile = (Join-Path $env:TEMP 'vb365-collector-diagnostics.txt') ) $ErrorActionPreference = 'Stop' $MaxDepth = 4 if (-not (Get-Command Get-VBOJob -ErrorAction SilentlyContinue)) { if (Get-Module -ListAvailable -Name Veeam.Archiver.PowerShell) { Import-Module Veeam.Archiver.PowerShell } else { Import-Module (Join-Path $env:ProgramFiles 'Veeam\Backup365\Veeam.Archiver.PowerShell\Veeam.Archiver.PowerShell.psd1') } } function Test-Scalar($Value) { return $null -eq $Value -or $Value -is [string] -or $Value -is [ValueType] -or $Value -is [guid] } # Recursive "name (type) = value" listing. Collections show their first 3 items. function Show-Object($Value, [string]$Indent = ' ', [int]$Depth = 0) { if (Test-Scalar $Value) { return } if ($Depth -ge $MaxDepth) { "$Indent..."; return } if ($Value -is [Collections.IEnumerable]) { $items = @($Value) "$Indent[$($items.Count) item(s)]" $i = 0 foreach ($item in $items | Select-Object -First 3) { "$Indent[$i] ($($item.GetType().FullName))$(if (Test-Scalar $item) { " = $item" })" Show-Object $item "$Indent " ($Depth + 1) $i++ } return } foreach ($p in $Value.PSObject.Properties) { try { $v = $p.Value } catch { "$Indent$($p.Name) = "; continue } $type = if ($null -eq $v) { 'null' } else { $v.GetType().FullName } if (Test-Scalar $v) { $kind = if ($v -is [datetime]) { " [Kind=$($v.Kind)]" } else { '' } "$Indent$($p.Name) ($type) = $v$kind" } else { $text = [string]$v if ($text.Length -gt 100) { $text = $text.Substring(0, 97) + '...' } "$Indent$($p.Name) ($type) = $text" Show-Object $v "$Indent " ($Depth + 1) } } } function Show-Section([string]$Title, [scriptblock]$Get) { "== $Title" try { $result = @(& $Get) if ($result.Count -eq 0 -or ($result.Count -eq 1 -and $null -eq $result[0])) { ' (nothing returned)' } $n = 0 foreach ($item in $result) { if ($null -eq $item) { continue } " [$n] ($($item.GetType().FullName))" Show-Object $item ' ' $n++ } } catch { " error: $($_.Exception.Message)" } '' } $report = & { $module = Get-Module Veeam.Archiver.PowerShell "Generated $(Get-Date -Format o) on $env:COMPUTERNAME, PowerShell $($PSVersionTable.PSVersion), module $($module.Version)" "Local time zone: $([TimeZoneInfo]::Local.Id), UTC offset now $([TimeZoneInfo]::Local.GetUtcOffset((Get-Date))), culture $([Globalization.CultureInfo]::CurrentCulture.Name)" '' "== Organizations" try { Get-VBOOrganization | ForEach-Object { " $($_.Name) [$($_.Type)]" } } catch { " error: $($_.Exception.Message)" } '' $allJobs = @(Get-VBOJob) "== Jobs: $($allJobs.Count) in total" $allJobs | ForEach-Object { " $($_.Name) [$($_.Id)] enabled=$($_.IsEnabled) next=$($_.NextRun)" } '' Show-Section "Get-VBOJob (first $Jobs, full detail)" { $allJobs | Select-Object -First $Jobs } foreach ($j in $allJobs | Select-Object -First $Jobs) { Show-Section "Get-VBOJobSession -Job '$($j.Name)' (latest $SessionsPerJob)" { Get-VBOJobSession -Job $j | Sort-Object CreationTime -Descending | Select-Object -First $SessionsPerJob } } if (Get-Command Get-VBOCopyJob -ErrorAction SilentlyContinue) { Show-Section 'Get-VBOCopyJob (first 2)' { Get-VBOCopyJob | Select-Object -First 2 } if (Get-Command Get-VBOCopyJobSession -ErrorAction SilentlyContinue) { Show-Section 'Get-VBOCopyJobSession (latest 2)' { Get-VBOCopyJobSession | Sort-Object CreationTime -Descending | Select-Object -First 2 } } } else { '== Get-VBOCopyJob: not available' } } | Out-String -Width 300 $report $report | Set-Content -Path $OutFile -Encoding UTF8 Write-Host "Saved to $OutFile"