Public/Export-CitrixSiteDocumentation.ps1
|
function Export-CitrixSiteDocumentation { <# .SYNOPSIS Exports a point-in-time documentation snapshot of a Citrix site to HTML and CSV. .DESCRIPTION Collects site, controllers, hypervisor connections, catalogs, delivery groups, applications, machines, tags and administrators, then writes one CSV per section plus a single HTML file. Read-only. Useful for change records, audits and DR documentation. .PARAMETER AdminAddress Delivery Controller FQDN. Omit for Citrix DaaS. .PARAMETER OutputFolder Folder for the output (created if missing). Default: .\CitrixSiteDoc_<yyyyMMdd-HHmm> .PARAMETER SkipMachines Skip the per-machine table (large sites). .EXAMPLE Export-CitrixSiteDocumentation -AdminAddress ddc01 -OutputFolder C:\Reports\Citrix .NOTES Author : Anthony Buhnerkemper License: MIT Requires the Citrix Virtual Apps and Desktops PowerShell SDK (on-prem) or the Citrix DaaS Remote PowerShell SDK. Tested for syntax only - validate in a lab first. #> [CmdletBinding()] param( [Parameter()] [string]$AdminAddress, [Parameter()] [string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("CitrixSiteDoc_{0:yyyyMMdd-HHmm}" -f (Get-Date))), [Parameter()] [switch]$SkipMachines, [Parameter()] [int]$MaxRecordCount = 10000 ) function Import-CitrixSdk { <# Loads the Citrix SDK: modules (CVAD 2203+ / Remote PowerShell SDK) first, snap-ins as fallback. #> if (Get-Command -Name Get-BrokerSite -ErrorAction SilentlyContinue) { return } $mod = Get-Module -ListAvailable -Name Citrix.Broker.Admin.V2 | Select-Object -First 1 if ($mod) { Import-Module $mod -ErrorAction Stop; return } if (Get-Command -Name Add-PSSnapin -ErrorAction SilentlyContinue) { Add-PSSnapin -Name Citrix* -ErrorAction SilentlyContinue } if (-not (Get-Command -Name Get-BrokerSite -ErrorAction SilentlyContinue)) { throw "Citrix Broker SDK not found. Run on a Delivery Controller, a machine with Studio/the SDK installed, or with the DaaS Remote PowerShell SDK." } } Import-CitrixSdk $ap = @{} if ($AdminAddress) { $ap.AdminAddress = $AdminAddress } $bp = @{ MaxRecordCount = $MaxRecordCount } + $ap New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null # Each section: name -> scriptblock returning selected objects. Failures are caught per section. $sections = [ordered]@{ 'Site' = { Get-BrokerSite @ap | Select-Object Name, LicenseServerName, LicenseServerPort, LicenseEdition, LicenseModel, ConnectionLeasingEnabled, LocalHostCacheEnabled } 'Controllers' = { Get-BrokerController @bp | Select-Object DNSName, State, ControllerVersion, OSType, LastActivityTime } 'HypervisorConnections' = { Get-BrokerHypervisorConnection @bp | Select-Object Name, State, IsReady, PreferredController, MachineCount } 'Catalogs' = { Get-BrokerCatalog @bp | Select-Object Name, AllocationType, ProvisioningType, SessionSupport, PersistUserChanges, MinimumFunctionalLevel, UsedCount, AvailableCount } 'DeliveryGroups' = { Get-BrokerDesktopGroup @bp | Select-Object Name, Enabled, InMaintenanceMode, DeliveryType, DesktopKind, SessionSupport, TotalDesktops, DesktopsAvailable, DesktopsInUse, DesktopsUnregistered } 'Applications' = { Get-BrokerApplication @bp | Select-Object Name, PublishedName, Enabled, Visible, CommandLineExecutable, CommandLineArguments, AdminFolderName } 'Tags' = { Get-BrokerTag @bp | Select-Object Name, Description } 'Administrators' = { if (Get-Command Get-AdminAdministrator -ErrorAction SilentlyContinue) { Get-AdminAdministrator @ap | Select-Object Name, Enabled, @{ n = 'RolesAndScopes'; e = { ($_.Rights | ForEach-Object { "$($_.RoleName) @ $($_.ScopeName)" }) -join '; ' } } } } } if (-not $SkipMachines) { $sections['Machines'] = { Get-BrokerMachine @bp | Select-Object MachineName, DesktopGroupName, CatalogName, RegistrationState, PowerState, InMaintenanceMode, AgentVersion, OSType, @{ n = 'Tags'; e = { $_.Tags -join ';' } } } } $html = New-Object System.Collections.Generic.List[string] foreach ($name in $sections.Keys) { Write-Verbose "Collecting $name" try { $data = @(& $sections[$name]) } catch { Write-Warning "Section $name failed: $($_.Exception.Message)" $data = @() } if ($data.Count -gt 0) { $data | Export-Csv -Path (Join-Path $OutputFolder "$name.csv") -NoTypeInformation $html.Add(($data | ConvertTo-Html -Fragment -PreContent "<h2>$name ($($data.Count))</h2>")) } else { $html.Add("<h2>$name</h2><p><em>No data or not available.</em></p>") } } $style = '<style>body{font-family:Segoe UI,Arial,sans-serif;font-size:12px}table{border-collapse:collapse;margin-bottom:20px}th,td{border:1px solid #ccc;padding:4px 8px}th{background:#eee}</style>' $title = "Citrix Site Documentation - $(Get-Date -Format 'yyyy-MM-dd HH:mm')" $htmlPath = Join-Path $OutputFolder 'CitrixSiteDocumentation.html' ConvertTo-Html -Title $title -Head $style -Body ("<h1>$title</h1>" + ($html -join "`n")) | Set-Content -Path $htmlPath -Encoding UTF8 Write-Host "Documentation written to $OutputFolder" Get-Item $htmlPath } |