<# .SYNOPSIS Collects a DocuWare Ivy Engine support bundle. .DESCRIPTION Gathers the information DocuWare support asks for first - engine version, logs, configuration and basic host facts - into a single zip file. Configuration files are copied, then scrubbed of anything that looks like a secret before being added to the bundle. Review the archive before sending it: automated redaction is a safety net, not a guarantee. This script is published with the DocuWare Ivy documentation and is downloadable from /files/scripts/collect-engine-diagnostics.ps1. .PARAMETER EngineDir The engine installation directory, i.e. the folder containing 'configuration' and 'logs'. .PARAMETER OutFile Path of the zip file to create. Defaults to a timestamped name in the current directory. .PARAMETER LogDays How many days of log files to include. Defaults to 7. .EXAMPLE .\collect-engine-diagnostics.ps1 -EngineDir 'C:\Program Files\DocuWare Ivy' .EXAMPLE .\collect-engine-diagnostics.ps1 -EngineDir /opt/docuware-ivy -LogDays 2 -OutFile bundle.zip .NOTES Requires PowerShell 5.1 or later. Read-only with respect to the engine: it never writes to or restarts the installation. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $EngineDir, [string] $OutFile, [ValidateRange(1, 90)] [int] $LogDays = 7 ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if (-not (Test-Path -LiteralPath $EngineDir)) { throw "Engine directory not found: $EngineDir" } if (-not $OutFile) { $OutFile = Join-Path (Get-Location) ("docuware-ivy-support-{0:yyyyMMdd-HHmmss}.zip" -f (Get-Date)) } # Staging area outside the engine directory, so nothing is ever written there. $staging = Join-Path ([System.IO.Path]::GetTempPath()) ("dwivy-diag-" + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $staging -Force | Out-Null # Anything matching these gets its value replaced. Add to the list rather than # relying on it being complete. $secretPattern = '(?i)(password|passwd|secret|token|apikey|api-key|credential|privatekey|connectionstring)(\s*[:=]\s*)(.+)' function Write-Section { param([string] $Name, [scriptblock] $Body) Write-Host " collecting $Name ..." -NoNewline try { & $Body Write-Host ' ok' } catch { Write-Host ' failed' # A missing optional item must not abort the whole collection - the # bundle is more useful incomplete than not produced at all. "$Name : $($_.Exception.Message)" | Add-Content -LiteralPath (Join-Path $staging 'collection-errors.txt') -Encoding utf8 } } try { Write-Host "Collecting DocuWare Ivy diagnostics from $EngineDir" Write-Section 'host information' { $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue [PSCustomObject]@{ CollectedUtc = (Get-Date).ToUniversalTime().ToString('o') ComputerName = $env:COMPUTERNAME OperatingSystem = if ($os) { $os.Caption } else { [System.Environment]::OSVersion.VersionString } OSVersion = [System.Environment]::OSVersion.VersionString PowerShell = $PSVersionTable.PSVersion.ToString() ProcessorCount = [System.Environment]::ProcessorCount TotalMemoryGB = if ($os) { [math]::Round($os.TotalVisibleMemorySize / 1MB, 1) } else { $null } EngineDirectory = (Resolve-Path -LiteralPath $EngineDir).Path JavaHome = $env:JAVA_HOME } | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (Join-Path $staging 'host.json') -Encoding utf8 } Write-Section 'disk space' { Get-PSDrive -PSProvider FileSystem | Select-Object Name, @{n = 'UsedGB'; e = { [math]::Round($_.Used / 1GB, 1) } }, @{n = 'FreeGB'; e = { [math]::Round($_.Free / 1GB, 1) } } | Format-Table -AutoSize | Out-File -LiteralPath (Join-Path $staging 'disk.txt') -Encoding utf8 } Write-Section 'engine version' { $candidates = @( (Join-Path $EngineDir 'version'), (Join-Path $EngineDir 'VERSION'), (Join-Path $EngineDir 'configuration/version') ) $found = $candidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 if ($found) { Copy-Item -LiteralPath $found -Destination (Join-Path $staging 'engine-version.txt') } else { 'Engine version file not found. Read it from Engine Cockpit > Info instead.' | Set-Content -LiteralPath (Join-Path $staging 'engine-version.txt') -Encoding utf8 } } Write-Section "logs (last $LogDays day(s))" { $logSource = Join-Path $EngineDir 'logs' if (-not (Test-Path -LiteralPath $logSource)) { return } $logTarget = Join-Path $staging 'logs' New-Item -ItemType Directory -Path $logTarget -Force | Out-Null $cutoff = (Get-Date).AddDays(-$LogDays) Get-ChildItem -LiteralPath $logSource -File -Recurse | Where-Object { $_.LastWriteTime -ge $cutoff } | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $logTarget -Force } } Write-Section 'configuration (secrets redacted)' { $configSource = Join-Path $EngineDir 'configuration' if (-not (Test-Path -LiteralPath $configSource)) { return } $configTarget = Join-Path $staging 'configuration' New-Item -ItemType Directory -Path $configTarget -Force | Out-Null Get-ChildItem -LiteralPath $configSource -File -Recurse -Include *.yaml, *.yml, *.xml, *.properties, *.conf | ForEach-Object { $relative = $_.FullName.Substring($configSource.Length).TrimStart('\', '/') $destination = Join-Path $configTarget $relative New-Item -ItemType Directory -Path (Split-Path -Parent $destination) -Force | Out-Null (Get-Content -LiteralPath $_.FullName) -replace $secretPattern, '$1$2***REDACTED***' | Set-Content -LiteralPath $destination -Encoding utf8 } } Write-Section 'running java processes' { Get-Process -Name java -ErrorAction SilentlyContinue | Select-Object Id, StartTime, @{n = 'WorkingSetMB'; e = { [math]::Round($_.WorkingSet64 / 1MB) } }, Path | Format-List | Out-File -LiteralPath (Join-Path $staging 'java-processes.txt') -Encoding utf8 } if (Test-Path -LiteralPath $OutFile) { Remove-Item -LiteralPath $OutFile -Force } Compress-Archive -Path (Join-Path $staging '*') -DestinationPath $OutFile -Force $sizeKb = [math]::Round((Get-Item -LiteralPath $OutFile).Length / 1KB, 1) Write-Host '' Write-Host "Support bundle written to $OutFile ($sizeKb KB)" Write-Host 'Review its contents before sending: redaction is best-effort.' -ForegroundColor Yellow } finally { if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue } }