functions/Export-D365LBDConfigReport.ps1
function Export-D365LBDConfigReport { <# .SYNOPSIS Gathers data from the D365 environment for easy diagnostics and quicker onboarding for support tickets. .DESCRIPTION Gathers data from the D365 environment for easy diagnostics and quicker onboarding for support tickets. .EXAMPLE Export-D365LBDConfigReport -exportlocation "C:\ConfigreportBasicForLCSTicketsEnvironment.html" -CustomModuleName 'MOD' Creates a basic report at C:\ConfigreportBasicForLCSTicketsEnvironment.html based on local servers environment and a custom module name mod .EXAMPLE Export-D365LBDConfigReport -computername 'AXSFServer01' -detailed -exportlocation "C:\ConfigreportEnvironment.html" -CustomModuleName 'MOD' Creates a detailed report at C:\ConfigreportEnvironment.html based on server AXSFServer01's environment and a custom module name mod .PARAMETER ComputerName String The name of the D365 LBD Server to grab the environment details; needed if a config is not specified and will default to local machine. .PARAMETER Config Custom PSObject Config Object created by either the Get-D365LBDConfig or Get-D365TestConfigData function inside this module .PARAMETER Detailed Switch if not selected the report will be basic with only details on issues. Less information for high level architecture without divulging any unneeded could be confidential config data. .PARAMETER ExportLocation String Mandatory the file path to export the report to should end with a file extension of HTML .PARAMETER CustomModuleName String the name of the custom module name to gather more information of the environment such as build version #> [alias("Export-D365ConfigReport")] [CmdletBinding()] param([Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $false, HelpMessage = 'D365FO Local Business Data Server Name')] [PSFComputer]$ComputerName = "$env:COMPUTERNAME", [Parameter(ValueFromPipeline = $True)] [psobject]$Config, [switch]$Detailed, [switch]$QueriesandEvents, [Parameter(Mandatory = $True)] [string]$ExportLocation, ##mandatory should end in html [string]$CustomModuleName ) ##Gather Information from the Dynamics 365 Orchestrator Server Config BEGIN { } PROCESS { if (!$Config) { if ($CustomModuleName) { $Config = Get-D365LBDConfig -ComputerName $ComputerName -CustomModuleName $CustomModuleName } else { $Config = Get-D365LBDConfig -ComputerName $ComputerName } } Write-PSFMessage -Level VeryVerbose -Message "Running Environment Health Check" $Health = Get-D365LBDEnvironmentHealth -config $config $HealthGroups = $($Health.Group | Select-Object -unique) Write-PSFMessage -Level VeryVerbose -Message "Running Dependency Health Check" $DependencyCheck = Get-D365LBDDependencyHealth -Config $config $DependencyGroups = $($DependencyCheck.Group | Select-Object -unique) $HealthText = "<p class=""Success""><b>D365 Health looks great</b></p>" if ($Health.Status -contains "Down") { $HealthText = "<p class=""issue""><b>D365 Health issues:</b></p>" $healthissues = $Health | Where-Object { $_.Status -eq "Down" } } $html = "<html> <body>" if ($Detailed) { $html += "<h1><a href = ""$($Config.ClientURL)"">$($Config.LCSEnvironmentName)</a> </h1>" } else { $html += "<h1>$($Config.LCSEnvironmentName)</h1>" } if (!$CustomModuleName) { $CustomModuleName = $Config.CustomModuleName } if ($CustomModuleName) { $html += "<p><b>Custom Code $CustomModuleName Version:</b></p> $($Config.CustomModuleVersion)" } $html += "<p><b>AX Kernel Version:</b></p> $($Config.AOSKernelVersion)" $html += "<p><b>LCS Project and Environment ID:</b></p> $($config.LCSProjectID) - <a href=""$($config.LCSEnvironmentURL)""> $($config.LCSEnvironmentID)</a> " if ($Config.AXDatabaseRestoreDate) { $html += "<p><b>Database Refresh/Restore Date:</b></p> $($Config.AXDatabaseRestoreDate) " if ($Detailed) { $html += "<p><b>Database Refresh/Restore file:</b></p> $($Config.AXDatabaseBackupFileUsedForRestore) " } } $html += "<p><b>Number of Apps in Healthy State:</b></p> <a href=""$($config.SFExplorerURL)""> $($Config.NumberOfAppsinServiceFabric) </a> " $html += "$HealthText" if ($healthissues) { foreach ($healthissue in $healthissues) { $html += "<p><b>Check:</b> $($healthissue.Name) <b>Source:</b> $($healthissue.Source) <b>Details:</b> $($healthissue.Details) <b>Additional Info:</b> $($healthissue.ExtraInfo) </p>" } } $html += "<b><p>Health Check Groups: </b></p> <ul>" foreach ($HealthGroup in $HealthGroups) { $html += "<li>$HealthGroup</li>" } $html += "</ul>" if ($DependencyCheck.Count -gt 0) { $DependencyCheckText = "<p class=""Success""><b>D365 Environment Dependencies Health looks great.</b></p>" if ($DependencyCheck.Status -contains "Down") { $DependencyCheckText = "<p class=""issue""><b>D365 Health issues:</b></p>" $DependencyCheckissues = $DependencyCheck | Where-Object { $_.Status -eq "Down" } } $html += "$DependencyCheckText" if ($DependencyCheckissues) { foreach ($DependencyCheckissue in $DependencyCheckissues) { $html += "<p><b>Check:</b> $($DependencyCheckissue.Name) <b>Source:</b> $($DependencyCheckissue.Source) <b>Details:</b> $($DependencyCheckissue.Details) <b>Additional Info:</b> $($DependencyCheckissue.ExtraInfo) </p></p> " } } $html += "<b><p>Dependency Groups: </b></p> <ul>" foreach ($DependencyGroup in $DependencyGroups) { $html += "<li>$DependencyGroup</li>" } $html += "</ul>" } $html += "<p><b>Orchestrator Job State:</b> $($Config.OrchestratorJobState) <b>Last Ran Orchestrator Job ID:</b> $($Config.LastOrchJobId) </p>" $html += "<p><b>Run Book Task State:</b> $($Config.OrchestratorJobRunBookState) <b>Last Ran Run Book Task ID:</b> $($Config.LastRunbookTaskId) </p>" $CountofAXServerNames = $Config.AXSFServerNames.Count $html += "<p><b>Number of AX SF Servers:</b></p> $($CountofAXServerNames)" if ($Detailed) { $html += "<b><p>AX SF Servers: </b></p> <ul>" foreach ($axsfserver in $Config.AXSFServerNames) { $html += "<li>$axsfserver</li>" } $html += "</ul>" } $html += "<p><b>Number of Orchestrator Servers: </b> </p>$($Config.OrchestratorServerNames.Count)</p>" if ($Detailed) { $html += "<p><b>Orchestrator Servers:</b></p> <ul>" foreach ($AXOrchServerName in $Config.OrchestratorServerNames) { $html += "<li>$AXOrchServerName</li>" } $html += "</ul>" } if ($Detailed) { $html += "<p><b>AX Database Connection Endpoint:</b></p> $($Config.AXDatabaseServer)" } $html += "<p><b>AX database is $($Config.DatabaseClusteredStatus)</b></p>" $DBCount = $Config.DatabaseClusterServerNames.Count if ($DBCount -gt 1) { $html += "<p><b>Number of Database servers:</b></p> $($Config.DatabaseClusterServerNames.Count)" if ($Detailed) { $html += "<p><b>Database server(s):</b></p> <ul>" foreach ($AXDBServerName in $($Config.DatabaseClusterServerNames)) { $html += "<li>$AXDBServerName</li>" } $html += "</ul>" } } $html += "<p><b>Number of SSRS Report Servers: </b> </p>$($Config.SSRSClusterServerNames.Count)</p>" if ($Detailed) { $html += "<p><b>SSRS Servers:</b></p> <ul>" foreach ($SSRSClusterServerName in $Config.SSRSClusterServerNames) { $html += "<li>$SSRSClusterServerName</li>" } $html += "</ul>" } if ($Config.ComponentsinSetupModule -contains "financialreporting") { $html += "<p><b>Number of Management Reporter Servers: </b> </p>$($Config.ManagementReporterServers.Count)</p>" if ($Detailed) { $html += "<p><b>Management Reporter Servers:</b></p> <ul>" foreach ($ManagementReporterServer in $Config.ManagementReporterServers) { $html += "<li>$ManagementReporterServer</li>" } $html += "</ul>" } } $html += "<p><b>Local Agent Version:</b></p> $($Config.OrchServiceLocalAgentVersionNumber)</p>" $html += '<table style="width:100%" class="Thumbprints"><tr><th>Certificate</th>' if ($Detailed) { $html += '<th>Thumbprint</th>' } $html += '<th>Expiration Date</th></tr>' $html += '<tr><td>SF Client</td> ' if ($Detailed) { $html += "<td> $($Config.SFClientCertificate)</td>" } $html += "<td> $($Config.SFClientCertificateExpiresAfter)</td></tr>" $html += '<tr><td>SF Server</td> ' if ($Detailed) { $html += "<td>$($Config.SFServerCertificate)</td>" } $html += "<td> $($Config.SFServerCertificateExpiresAfter)</td></tr>" $html += '<tr><td>Data Encryption</td> ' if ($Detailed) { $html += "<td> $($Config.DataEncryptionCertificate)</td>" } $html += "<td> $($Config.DataEncryptionCertificateExpiresAfter)</td></tr>" $html += '<tr><td>Data Signing</td> ' if ($Detailed) { $html += "<td>$($Config.DataSigningCertificate)</td>" } $html += "<td>$($Config.DataSigningCertificateExpiresAfter)</td></tr>" $html += '<tr><td>Session Authentication</td> ' if ($Detailed) { $html += "<td>$($Config.SessionAuthenticationCertificate)</td>" } $html += "<td>$($Config.SessionAuthenticationCertificateExpiresAfter)</td></tr>" $html += '<tr><td>Shared Access</td> ' if ($Detailed) { $html += "<td>$($Config.SharedAccessSMBCertificate)</td>" } $html += "<td>$($Config.SharedAccessSMBCertificateExpiresAfter)</td></tr>" if ($Config.DataEnciphermentCertificateExpiresAfter) { $html += '<tr><td>Data Encipherment</td> ' if ($Detailed) { $html += "<td>$($Config.DataEnciphermentCertificate)</td>" } $html += "<td>$($Config.DataEnciphermentCertificateExpiresAfter)</td></tr>" } else { Write-PSFMessage -Level Warning -Message "DataEncipherment likely not configured in xml" } $html += '<tr><td>Financial Reporting (MR)</td> ' if ($Detailed) { $html += "<td>$($Config.FinancialReportingCertificate)</td>" } $html += "<td>$($Config.FinancialReportingCertificateExpiresAfter)</td></tr>" $html += '<tr><td>Reporting SSRS </td> ' if ($Detailed) { $html += "<td>$($Config.ReportingSSRSCertificate)</td>" } $html += "<td>$($Config.ReportingSSRSCertificateExpiresAfter)</td></tr>" if ($Config.DatabaseEncryptionCertificates) { foreach ($DatabaseEncryptionCertificate in $Config.DatabaseEncryptionCertificates) { $html += '<tr><td>Database Connection Configured Encryption </td> ' if ($Detailed) { $html += "<td>$($DatabaseEncryptionCertificate)</td>" } if ($Config.DatabaseEncryptionCertificatesExpiresAfter) { $html += "<td>$($Config.DatabaseEncryptionCertificatesExpiresAfter)</td></tr>" } else { $html += "<td>Can't get Expiration (Permissions on SQL OS level)</td></tr>" } } } else { Write-PSFMessage -Level Warning -Message "Database Encryption not configured in xml or access issues to Database server" } $html += '<tr><td>Local Agent</td> ' if ($Detailed) { $html += "<td>$($Config.LocalAgentCertificate)</td>" } $html += "<td>$($Config.LocalAgentCertificateExpiresAfter)</td></tr>" $html += "</table>" if ($Detailed) { $guids = Get-D365LBDAXSFGUIDS -Config $Config $html += "<p></p><table style=""width:100%"" class=""GUIDS""> <tr> <th>Server</th> <th>GUID</th> <th>Endpoint</th> </tr>" foreach ($guid in $guids) { $html += "<tr><td>$($guid.Source)</td><td>$($guid.Details)</td><td><a href=""$($guid.ExtraInfo)"">$($guid.ExtraInfo)</a></td></tr>" } $html += "</table>" } if ($QueriesandEvents) { <# Source: https://stackoverflow.com/questions/8423541/how-do-you-run-a-sql-server-query-from-powershell #> function Invoke-SQL { param( [string] $dataSource = ".\SQLEXPRESS", [string] $database = "MasterData", [string] $sqlCommand = $(throw "Please specify a query.") ) $connectionString = "Data Source=$dataSource; " + "Integrated Security=SSPI; " + "Initial Catalog=$database" $connection = new-object system.data.SqlClient.SQLConnection($connectionString) $command = new-object system.data.sqlclient.sqlcommand($sqlCommand, $connection) $connection.Open() $adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command $dataset = New-Object System.Data.DataSet $adapter.Fill($dataSet) | Out-Null $connection.Close() $dataSet.Tables } $SqlQueryToGetRunningSQL = "SELECT stext.TEXT,req.total_elapsed_time,req.session_id,req.status,req.command FROM sys.dm_exec_requests req CROSS APPLY sys.dm_exec_sql_text(sql_handle) stext" $AXDatabaseName = $Config.AXDatabaseName $AXDatabaseServer = $Config.AXDatabaseServer $SqlresultsToGetRunningSQL = invoke-sql -datasource $AXDatabaseServer -database $AXDatabaseName -sqlcommand $SqlQueryToGetRunningSQL $html += "<h2>Running Queries </h2>" $html += "$SqlresultsToGetRunningSQL" $html += "<h2>Last Orchestrator Events: </h2>" $orchevents = Get-D365OrchestrationLogs -config $Config -NumberofEvents 10 $html += "<p></p><table style=""width:100%"" class=""OrchLogs""> <tr> <th>Server</th> <th>MessageType</th> <th>DateTime</th> <th>EventMessage</th><th>EventDetails</th> </tr>" foreach ($orchevent in $orchevents) { $html += "<tr><td>$($orchevent.MachineName)</td><td>$($orchevent.Message)</td><td>$($orchevent.TimeCreated)</td><td>$($orchevent.EventMessage)</td><td>$($orchevent.EventDetails)</td></tr>" } $html += "</table>" $html += "<h2>Last Database Synchronize Events: </h2>" $dbevents = Get-D365DBEvents -config $Config -NumberofEvents 10 $html += "<p></p><table style=""width:100%"" class=""OrchLogs""> <tr> <th>Server</th> <th>MessageType</th> <th>DateTime</th> <th>EventMessage</th><th>EventDetails</th> </tr>" foreach ($dbevent in $dbevents) { $html += "<tr><td>$($dbevent.MachineName)</td><td>$($dbevent.Message)</td><td>$($dbevent.TimeCreated)</td><td>$($dbevent.EventMessage)</td><td>$($dbevent.EventDetails)</td></tr>" } $html += "</table>" } $html += "</body></html>" $html | Out-File "$ExportLocation" -Verbose } END {} } |