Some of you will recognize this. A recurring pattern in DNN module support is to find out what the customer has running in the first place. Not just your own module (i.e. version), but also ‘the bigger picture’. There are ‘expert’ customers that will write you this in a first email, and there are those that have no clue what you’re asking them. Luckily most of my customers fall toward the expert category. Despite this I still spend a lot of time (emails back and forth) determining the client system. And regularly I have to ‘talk the customer through’ how to get those bits of information that are relevant to me (e.g. is Ajax enabled? Are you using compression? Do you have dll XYZ installed?)
This post is about a solution I’ve been working on. The aim is to let a superuser hit a button and he/she receives a file with all the relevant bits of info I might need. The solution could come in the form of a module (which is how I developed it), but IMO this is not going to be very successful. You need the functionality only when you get stuck with something. And typically people would resist installing modules unnecessarily (I think this is a ‘good’ tendency as you never know what you drag inside of your DNN installation). So my goal is to lift this into the DNN core one day.
The Solution
So what does the current solution (called SysInfo for now) look like? Well, it’s a linkbutton that instantiates a class (called XmlReport for lack of a better name right now) that inherits from XmlDocument. This class builds all the info within itself and the containing module tells it to render itself to the output stream. All this is pretty straightforward stuff.
I have tried to split off as much generic stuff into static methods in a Common class. This allows others to use that class. Why is this useful? Well, the object is that through an interface in the business controller class other modules can add to the report as well. So my document management module could tell something about its configuration. Now that would be neat IMO. Click the magic button and you get an XML report of the core configuration and specifics for any module.
Public Interface ISysInfoProducer
Sub ProduceInfo(ByRef node As XmlNode)
End Interface
The XmlReport class goes through all the installed modules, checks the Business Controller class and if it support this interface, it will be called with the root node of the report as argument. Below you’ll find the other two main classes I mentioned before.
What can you do?
Vote for this enhancement. I have submitted it to the enhancement request section of DNN:
http://www.dotnetnuke.com/Products/Development/Roadmap/tabid/616/ctl/Details/mid/3582/enhancementid/175/Default.aspx
Vote for it and it might be included one day.
Common.vb
Public Class Common
#Region " Generic Data "
Public Shared Sub RunQuery(ByRef node As XmlNode, ByVal tableName As String, ByVal rowName As String, ByVal query As String)
If node Is Nothing Then Exit Sub
AddDataReader(node, tableName, rowName, DotNetNuke.Data.DataProvider.Instance.ExecuteSQL(query))
End Sub
Public Shared Sub AddDataReader(ByRef node As XmlNode, ByVal tableName As String, ByVal rowName As String, ByVal reader As IDataReader)
If node Is Nothing Then Exit Sub
Dim table As XmlNode = node.OwnerDocument.CreateElement(tableName)
node.AppendChild(table)
Dim fields As New ArrayList
Dim intFieldCount As Integer = reader.FieldCount
Dim intCounter As Integer
For intCounter = 0 To intFieldCount - 1
fields.Add(reader.GetName(intCounter))
Next intCounter
While reader.Read()
Dim row As XmlNode = node.OwnerDocument.CreateElement(rowName)
table.AppendChild(row)
For Each field As String In fields
AddElement(row, field, GetAString(reader.Item(field)))
Next
End While
reader.Close()
End Sub
Public Shared Function GetAString(ByVal x As Object) As String
If x Is Nothing Then
Return "NULL"
ElseIf x Is DBNull.Value Then
Return "NULL"
Else
Return Convert.ToString(x)
End If
End Function
#End Region
#Region " File System "
Public Shared Sub AddFileSystemDetails(ByRef node As XmlNode, ByVal path As String, ByVal includeFiles As Boolean, ByVal recurse As Boolean)
If node Is Nothing Then Exit Sub
Dim x As XmlNode = node.OwnerDocument.CreateElement("Folder")
node.AppendChild(x)
AddAttribute(x, "Path", path)
If Not VerifyFileCreate(path & "\Verify.txt") Then
AddAttribute(x, "VerifyFileCreate", "False")
End If
If Not VerifyFileCreate(path & "\Verify.txt") Then
AddAttribute(x, "VerifyFileDelete", "False")
End If
If Not VerifyFolderCreate(path & "\Verify") Then
AddAttribute(x, "VerifyFolderCreate", "False")
End If
If Not VerifyFolderDelete(path & "\Verify") Then
AddAttribute(x, "VerifyFolderDelete", "False")
End If
AddAttribute(x, "Files", IO.Directory.GetFiles(path).Length.ToString)
If includeFiles Then
For Each f As String In IO.Directory.GetFiles(path)
Dim fi As New IO.FileInfo(f)
Dim xFile As XmlNode = node.OwnerDocument.CreateElement("File")
x.AppendChild(xFile)
xFile.InnerText = fi.Name
AddAttribute(xFile, "Size", fi.Length.ToString)
AddAttribute(xFile, "CreationTime", fi.CreationTime.ToString("u"))
If f.EndsWith(".dll") Then
Try
Dim ass As System.Reflection.Assembly = System.Reflection.Assembly.LoadFile(f)
AddAttribute(xFile, "AssemblyVersion", ass.GetName.Version.ToString)
AddAttribute(xFile, "FileVersion", System.Diagnostics.FileVersionInfo.GetVersionInfo(ass.Location).FileVersion)
AddAttribute(xFile, "CompanyName", System.Diagnostics.FileVersionInfo.GetVersionInfo(ass.Location).CompanyName)
AddAttribute(xFile, "ProductVersion", System.Diagnostics.FileVersionInfo.GetVersionInfo(ass.Location).ProductVersion)
AddAttribute(xFile, "ProductName", System.Diagnostics.FileVersionInfo.GetVersionInfo(ass.Location).ProductName)
Catch ex As Exception
End Try
End If
Next
End If
If recurse Then
For Each sDir As String In IO.Directory.GetDirectories(path)
AddFileSystemDetails(x, sDir, includeFiles, recurse)
Next
End If
End Sub
Public Shared Function VerifyFileCreate(ByVal verifyFilePath As String) As Boolean
Dim verified As Boolean = True
'Attempt to create the File
Try
If IO.File.Exists(verifyFilePath) Then
IO.File.Delete(verifyFilePath)
End If
Dim fileStream As IO.Stream = IO.File.Create(verifyFilePath)
fileStream.Close()
Catch ex As Exception
verified = False
End Try
Return verified
End Function
Public Shared Function VerifyFileDelete(ByVal verifyFilePath As String) As Boolean
Dim verified As Boolean = True
'Attempt to delete the File
Try
IO.File.Delete(verifyFilePath)
Catch ex As Exception
verified = False
End Try
Return verified
End Function
Public Shared Function VerifyFolderCreate(ByVal verifyPath As String) As Boolean
Dim verified As Boolean = True
'Attempt to create the Directory
Try
If IO.Directory.Exists(verifyPath) Then
IO.Directory.Delete(verifyPath, True)
End If
IO.Directory.CreateDirectory(verifyPath)
Catch ex As Exception
verified = False
End Try
Return verified
End Function
Public Shared Function VerifyFolderDelete(ByVal verifyPath As String) As Boolean
Dim verified As Boolean = True
If verified Then
'Attempt to delete the Directory
Try
IO.Directory.Delete(verifyPath)
Catch ex As Exception
verified = False
End Try
End If
Return verified
End Function
#End Region
#Region " Add Xml Files "
Public Shared Sub AddXmlFile(ByRef node As XmlNode, ByVal nodeName As String, ByVal filename As String)
If node Is Nothing Then Exit Sub
AddXmlFile(node, nodeName, filename, "", "")
End Sub
Public Shared Sub AddXmlFile(ByRef node As XmlNode, ByVal nodeName As String, ByVal filename As String, ByVal grepReplace As String, ByVal grepReplaceWith As String)
If node Is Nothing Then Exit Sub
Dim xmld As New XmlDocument
xmld.Load(filename)
Dim x As XmlNode = node.OwnerDocument.CreateElement(nodeName)
node.AppendChild(x)
If grepReplace = "" Then
x.InnerXml = xmld.DocumentElement.InnerXml
Else
x.InnerXml = Regex.Replace(xmld.DocumentElement.InnerXml, grepReplace, grepReplaceWith)
End If
End Sub
#End Region
#Region " Host Settings "
Public Shared Sub AddHostSettings(ByRef node As XmlNode)
If node Is Nothing Then Exit Sub
Dim host As XmlNode = node.OwnerDocument.CreateElement("Host")
node.AppendChild(host)
AddElement(host, "AppVersion", glbAppVersion)
AddElement(host, "DataProvider", Providers.ProviderConfiguration.GetProviderConfiguration("data").DefaultProvider)
AddElement(host, "Framework", System.Environment.Version.ToString)
AddElement(host, "MachineName", System.Environment.MachineName)
AddElement(host, "Platform", System.Environment.OSVersion.Platform.ToString)
AddElement(host, "PlatformVersion", System.Environment.OSVersion.Version.ToString)
AddElement(host, "ProcessorCount", System.Environment.ProcessorCount.ToString)
AddElement(host, "UserDomainName", System.Environment.UserDomainName)
AddElement(host, "WorkingSet", System.Environment.WorkingSet.ToString)
AddElement(host, "Identity", System.Security.Principal.WindowsIdentity.GetCurrent.Name)
AddElement(host, "HostName", Net.Dns.GetHostName())
Dim strPermissions As String = ""
If Framework.SecurityPolicy.HasRelectionPermission Then
strPermissions += ", " & Framework.SecurityPolicy.ReflectionPermission
End If
If Framework.SecurityPolicy.HasWebPermission Then
strPermissions += ", " & Framework.SecurityPolicy.WebPermission
End If
AddElement(host, "Permissions", Mid(strPermissions, 3))
AddElement(host, "ApplicationPath", DotNetNuke.Common.ApplicationPath)
AddElement(host, "ApplicationMapPath", DotNetNuke.Common.ApplicationMapPath)
AddElement(host, "WebFarmEnabled", DotNetNuke.Common.Globals.WebFarmEnabled.ToString())
AddHostSetting(host, "GUID", "HostPortalId", "HostTitle", "HostURL", "HostEmail", "ControlPanel")
AddHostSetting(host, "PaymentProcessor", "Currency", "HostCurrency")
AddHostSetting(host, "SchedulerMode", "HostSpace", "PageQuota", "UserQuota")
AddHostSetting(host, "SiteLogStorage", "SiteLogBuffer", "SiteLogHistory")
AddHostSetting(host, "PageStatePersister", "ModuleCaching", "PerformanceSetting", "AuthenticatedCacheability", "HttpCompression", "WhitespaceFilter")
AddHostSetting(host, "DemoPeriod", "DemoSignup", "Copyright", "DisableUsersOnline", "UsersOnlineTime", "EnableAJAX")
AddHostSetting(host, "AutoAccountUnlockDuration", "ProxyServer", "ProxyPort", "WebRequestTimeout")
AddElement(host, "SMTPServerSet", (Convert.ToString(DotNetNuke.Common.Globals.HostSettings("SMTPServer")) <> "").ToString)
AddHostSetting(host, "SMTPAuthentication", "SMTPEnableSSL")
AddHostSetting(host, "FileExtensions", "UseCustomErrorMessages", "UseFriendlyUrls", "EnableRequestFilters")
AddHostSetting(host, "EventLogBuffer", "SkinUpload", "HelpURL", "EnableModuleOnLineHelp", "EnableFileAutoSync")
End Sub
Public Shared Sub AddHostSetting(ByRef node As XmlNode, ByVal ParamArray settings() As String)
For Each setting In settings
If DotNetNuke.Common.Globals.HostSettings(setting) IsNot Nothing Then
AddElement(node, setting, Convert.ToString(DotNetNuke.Common.Globals.HostSettings(setting).ToString))
End If
Next
End Sub
#End Region
#Region " Helper Methods "
Public Shared Sub AddAttribute(ByRef node As XmlNode, ByVal propName As String, ByVal propValue As String)
If node Is Nothing Then Exit Sub
Dim att As XmlAttribute = node.OwnerDocument.CreateAttribute(propName)
att.InnerText = propValue
node.Attributes.Append(att)
End Sub
Public Shared Sub AddElement(ByRef node As XmlNode, ByVal propName As String, ByVal propValue As String)
If node Is Nothing Then Exit Sub
Dim elt As XmlNode = node.OwnerDocument.CreateElement(propName)
elt.InnerText = propValue
node.AppendChild(elt)
End Sub
#End Region
End Class
XmlReport.vb
Public Class XmlReport
Inherits XmlDocument
#Region " Private Members "
Private _root As XmlNode
Private _dataProvider As String = "SqlDataProvider"
#End Region
#Region " Constructors "
Public Sub New()
MyBase.New()
Me.AppendChild(Me.CreateXmlDeclaration("1.0", Nothing, "yes"))
_root = Me.CreateElement("Report")
_dataProvider = Providers.ProviderConfiguration.GetProviderConfiguration("data").DefaultProvider
Me.AppendChild(_root)
AddAttribute(_root, "Created", Now.ToString("u"))
AddHostSettings(_root)
AddXmlFile(_root, "Compression", DotNetNuke.Common.Globals.ApplicationMapPath + "\Compression.config")
AddXmlFile(_root, "WebConfig", DotNetNuke.Common.Globals.ApplicationMapPath + "\web.config", "(?i)(?<=validationkey=""|decryptionkey=""|key=""sitesqlserver"" value=""|connectionstring="")[^""]+(?-i)", "*******")
Dim x As XmlNode = Me.CreateElement("FileSystem")
_root.AppendChild(x)
AddFileSystemDetails(x, DotNetNuke.Common.ApplicationMapPath & "\bin", True, False)
AddFileSystemDetails(x, DotNetNuke.Common.ApplicationMapPath & "\portals", False, True)
AddDataReader(_root, "InstalledModules", "Module", DotNetNuke.Data.DataProvider.Instance.GetDesktopModules)
If _dataProvider.ToLower = "sqldataprovider" Then
RunQuery(_root, "Portals", "Portal", "SELECT p.*, (SELECT COUNT(*) FROM {objectQualifier}Users u INNER JOIN {objectQualifier}UserPortals up ON u.UserId=up.UserId WHERE up.PortalId=p.PortalId) AS NrUsers, (SELECT COUNT(*) FROM {objectQualifier}Tabs t WHERE t.PortalID=p.PortalID) AS NrTabs FROM {objectQualifier}Portals p")
For Each pi As Portals.PortalInfo In (New Portals.PortalController).GetPortals
AddDataReader(_root.SelectSingleNode("Portals/Portal"), "Aliases", "Alias", DotNetNuke.Data.DataProvider.Instance.GetPortalAliasByPortalID(pi.PortalID))
Next
End If
For Each dm As DotNetNuke.Entities.Modules.DesktopModuleInfo In (New DotNetNuke.Entities.Modules.DesktopModuleController).GetDesktopModules
If dm.BusinessControllerClass <> "" Then
Try
Dim objController As Object = Framework.Reflection.CreateObject(dm.BusinessControllerClass, "")
If TypeOf objController Is ISysInfoProducer Then
Dim sip As ISysInfoProducer = CType(objController, ISysInfoProducer)
sip.ProduceInfo(_root)
End If
Catch ex As Exception
End Try
End If
Next
End Sub
#End Region
End Class