You can read the MSDN article first to understand the different steps needed to develop custom STSADM commands : How to: Extend the STSADM Utility
Let's begin by enumerating what it takes to develop our custom command :
- We need to know what should our command do
- Create a Visual Studio Class Library project and add a references to "Microsoft.SharePoint"
- Create a class that implements the "ISPStsadmCommand" interface
- Write the "GetHelpMessage" method
- Write the "Run" method
- Write the XML file that informs STSADM of our new command
- Sign, build and deploy the DLL of our project to the Global Assembly Cache.
What sould our command do?
The STSADM command we will develop will help us have the content of our SharePoint farm in an XML file. We'll call it "FarmManifest". The command will iterate through the farm content service and write to the XML file the content hierarchy as shown below :
- Web Applications
- Content Databases
- Site Collections
- Webs
- Lists
- Web Features
- Site Features
- Web Application Features
- Solutions
- Farm Features Definition
stsadm -o FarmManifest
-Filename (required)
[-IncludeAll] (optional) : Include all sites, webs and lists
[-IncludeSites] (optional) : Include only sites
[-IncludeWebs] (optional) : Include webs if IncludeSites
[-IncludeLists] (optional) : Include lists if IncludeSites and IncludeWebs
-Filename
[-IncludeAll] (optional) : Include all sites, webs and lists
[-IncludeSites] (optional) : Include only sites
[-IncludeWebs] (optional) : Include webs if IncludeSites
[-IncludeLists] (optional) : Include lists if IncludeSites and IncludeWebs
You may ask what's this command for? Honestly, I think it can be used for many purposes. Let's just say that it can show you all the content of your farm and help you compare the content of two farms, especially when it comes to compare features and solutions. Furthermore, you can enhance this command to include other content such as list items, event handlers, jobs, etc.
The project
Now that we know what should our command do, let's get to work. In Visual Studio we will :
- Create a new Visual Basic Class Library Project. Let's call it "MyStsadmCommands"
- Add a reference to "Windows SharePoint Services"
- Rename the default class "Class1.vb" to "FarmManifest.vb"
- Add the necessary "Imports" to the class :
Imports Microsoft.SharePoint
Imports Microsoft.SharePoint.StsAdmin
Imports Microsoft.SharePoint.Administration
Imports System.Text
Imports System.Xml
Imports System.IO
- Add a "GetHelpMessage" method to our class :
Public Function GetHelpMessage(ByVal command As String) As String Implements Microsoft.SharePoint.StsAdmin.ISPStsadmCommand.GetHelpMessage
Dim strHelpMsg As String = String.Empty
strHelpMsg = "The help message goes here ..."
Return strHelpMsg
End Function
- Add a "Run" method to the class :
Public Function Run(ByVal command As String, ByVal keyValues As System.Collections.Specialized.StringDictionary, ByRef output As String) As Integer Implements Microsoft.SharePoint.StsAdmin.ISPStsadmCommand.Run
' Here goes the code of what sould the command do....
End Function
- For our particular command the "FarmManifest.vb" class when finished, should look like this :
Imports Microsoft.SharePoint
Imports Microsoft.SharePoint.StsAdmin
Imports Microsoft.SharePoint.Administration
Imports System.Text
Imports System.Xml
Imports System.IO
''' <summary>
''' Classe implementing the StsAdm Extension
''' </summary>
''' <remarks></remarks>
Public Class FarmManifest
Implements ISPStsadmCommand
'Private variables
Private ManifestFile As String = String.Empty
Private writer As XmlTextWriter
Private mincludeAll As Boolean = False
Private mincludeSites As Boolean = False
Private mincludeWebs As Boolean = False
Private mincludeLists As Boolean = False
''' <summary>
''' Methode to return the help message
''' </summary>
''' <param name="command"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetHelpMessage(ByVal command As String) As String Implements Microsoft.SharePoint.StsAdmin.ISPStsadmCommand.GetHelpMessage
Dim strHelpMsg As String = String.Empty
strHelpMsg = "stsadm -o FarmManifest" + Environment.NewLine
strHelpMsg = strHelpMsg + " -Filename <filename> (required)" + Environment.NewLine
strHelpMsg = strHelpMsg + " [-IncludeAll] (optional) : Include all sites, webs and lists" + Environment.NewLine
strHelpMsg = strHelpMsg + " [-IncludeSites] (optional) : Include only sites" + Environment.NewLine
strHelpMsg = strHelpMsg + " [-IncludeWebs] (optional) : Include webs if IncludeSites" + Environment.NewLine
strHelpMsg = strHelpMsg + " [-IncludeLists] (optional) : Include lists if IncludeSites and IncludeWebs" + Environment.NewLine
Return strHelpMsg
End Function
''' <summary>
''' Method to execute the STSADM command
''' </summary>
''' <param name="command"></param>
''' <param name="keyValues"></param>
''' <param name="output"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function Run(ByVal command As String, ByVal keyValues As System.Collections.Specialized.StringDictionary, ByRef output As String) As Integer Implements Microsoft.SharePoint.StsAdmin.ISPStsadmCommand.Run
'Read the commad entered parameters
If keyValues.ContainsKey("IncludeAll") Then
mincludeAll = True
Else
If keyValues.ContainsKey("IncludeSites") Then
mincludeSites = True
If keyValues.ContainsKey("IncludeWebs") Then
mincludeWebs = True
If keyValues.ContainsKey("IncludeLists") Then
mincludeLists = True
End If
End If
End If
End If
'Using the farm content service
Dim contentService As SPWebService = SPWebService.ContentService
If keyValues.ContainsKey("FileName") And keyValues("FileName") IsNot Nothing Then
Try
' Open a new XML file stream for writing
Dim stream As IO.FileStream
stream = File.OpenWrite(keyValues("FileName"))
writer = New XmlTextWriter(stream, Encoding.UTF8)
' Causes child elements to be indented
writer.Formatting = Formatting.Indented
writer.WriteProcessingInstruction("xml", "version=""1.0"" encoding=""utf-8""")
writer.WriteStartElement("Manifest")
' Web Applications element
writer.WriteStartElement("WebApplications")
writer.WriteAttributeString("Count", contentService.WebApplications.Count.ToString)
Dim webApp As SPWebApplication
For Each webApp In contentService.WebApplications
If Not webApp.IsAdministrationWebApplication Then
writer.WriteStartElement("WebApplication")
writer.WriteAttributeString("ID", webApp.Id.ToString)
writer.WriteAttributeString("Name", webApp.Name)
writer.WriteAttributeString("AppPool", webApp.ApplicationPool.Name)
'Content databases element
writer.WriteStartElement("ContentDataBases")
writer.WriteAttributeString("Count", webApp.ContentDatabases.Count.ToString)
Dim contentDatabases As SPContentDatabaseCollection = webApp.ContentDatabases
Dim database As SPContentDatabase
For Each database In contentDatabases
writer.WriteStartElement("ContentDataBase")
writer.WriteAttributeString("Name", database.Name)
writer.WriteAttributeString("DataBaseServer", database.Server)
writer.WriteAttributeString("SiteCount", database.Sites.Count.ToString)
writer.WriteAttributeString("WarningSiteCount", database.WarningSiteCount.ToString)
writer.WriteAttributeString("MaximumSiteCount", database.MaximumSiteCount.ToString)
writer.WriteEndElement() ' Content DataBase
Next database
writer.WriteEndElement() ' Content DataBases
'Site Collections
If mincludeAll Or mincludeSites Then
writer.WriteStartElement("SiteCollections")
writer.WriteAttributeString("Count", webApp.Sites.Count.ToString)
For Each site As SPSite In webApp.Sites
writer.WriteStartElement("Site")
writer.WriteAttributeString("ID", site.ID.ToString)
writer.WriteAttributeString("Url", site.Url)
writer.WriteAttributeString("OwnerName", site.Owner.Name)
writer.WriteAttributeString("OwnerEmail", site.Owner.Email)
' Webs
If mincludeAll Or mincludeWebs Then
For Each web As SPWeb In site.AllWebs
If web.IsRootWeb Then
'RootWeb
writer.WriteStartElement("RootWeb")
writer.WriteAttributeString("ID", web.ID.ToString)
writer.WriteAttributeString("Url", web.Url)
writer.WriteAttributeString("Title", web.Title)
writer.WriteAttributeString("Template", web.WebTemplate & "#" & web.WebTemplateId.ToString)
writer.WriteStartElement("SubWebs")
writer.WriteAttributeString("Count", web.Webs.Count.ToString)
'Webs
ProcessWebs(web.Webs)
writer.WriteEndElement() 'SubWebs
writer.WriteEndElement() ' RootWeb
End If
web.Dispose()
Next
End If
'Site features
ProcessFeatures(site.Features, "SiteFeatures")
writer.WriteEndElement() ' Site
site.Dispose()
Next site
writer.WriteEndElement() 'Site Collections
End If
'Web App features
ProcessFeatures(webApp.Features, "WebAppFeatures")
writer.WriteEndElement() ' Web Application
End If
Next webApp
writer.WriteEndElement() ' Web Applications
'Solutions
writer.WriteStartElement("Solutions")
writer.WriteAttributeString("Count", SPFarm.Local.Solutions.Count.ToString)
For Each solution As SPSolution In SPFarm.Local.Solutions
writer.WriteStartElement("Solution")
writer.WriteAttributeString("ID", solution.Id.ToString)
writer.WriteAttributeString("Name", solution.Name)
writer.WriteAttributeString("Deployed", solution.Deployed.ToString)
writer.WriteAttributeString("ContainsGlobalAssembly", solution.ContainsGlobalAssembly.ToString)
writer.WriteAttributeString("ContainsCodeAccessSecurityPolicy", solution.ContainsCasPolicy.ToString)
writer.WriteAttributeString("LastOperationResult", solution.LastOperationResult.ToString)
writer.WriteAttributeString("LastOperationTime", solution.LastOperationEndTime.ToString)
writer.WriteEndElement() ' Solution
Next solution
writer.WriteEndElement() ' Farm features
'Features
writer.WriteStartElement("Features")
writer.WriteAttributeString("Count", SPFarm.Local.FeatureDefinitions.Count.ToString)
For Each feature As SPFeatureDefinition In SPFarm.Local.FeatureDefinitions
writer.WriteStartElement("Feature")
writer.WriteAttributeString("ID", feature.Id.ToString)
writer.WriteAttributeString("Name", feature.DisplayName)
writer.WriteAttributeString("Scope", feature.Scope.ToString)
writer.WriteAttributeString("SolutionID", feature.SolutionId.ToString)
writer.WriteEndElement() ' Feature
Next feature
writer.WriteEndElement() ' Farm features
writer.WriteEndElement() ' Manifest
' Flush the writer and close the stream
writer.Flush()
stream.Close()
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
Else
Throw New ArgumentException("FileName parameter is empty")
End If
End Function
''' <summary>
''' 'Recusive method to process webs and sub webs
''' </summary>
''' <param name="webcollection"></param>
''' <remarks></remarks>
Private Sub ProcessWebs(ByVal webcollection As SPWebCollection)
For Each web As SPWeb In webcollection
writer.WriteStartElement("Web")
writer.WriteAttributeString("ID", web.ID.ToString)
writer.WriteAttributeString("Url", web.Url)
writer.WriteAttributeString("Title", web.Title)
writer.WriteAttributeString("Template", web.WebTemplate & "#" & web.WebTemplateId.ToString)
'Web Lists
If mincludeLists Or mincludeAll Then
writer.WriteStartElement("Lists")
writer.WriteAttributeString("Count", web.Lists.Count.ToString)
For Each list As SPList In web.Lists
writer.WriteStartElement("List")
writer.WriteAttributeString("ID", list.ID.ToString)
writer.WriteAttributeString("Title", list.Title)
writer.WriteAttributeString("Author", list.Author.Name)
writer.WriteAttributeString("Template", list.BaseTemplate.ToString)
writer.WriteAttributeString("ItemsCount", list.ItemCount.ToString)
writer.WriteEndElement() ' List
Next
writer.WriteEndElement() 'Lists
End If
'Web features
ProcessFeatures(web.Features, "WebFeatures")
'Sub Webs
ProcessWebs(web.Webs)
writer.WriteEndElement() ' Web
web.Dispose()
Next
End Sub
''' <summary>
''' Processing features
''' </summary>
''' <param name="features"></param>
''' <param name="element"></param>
''' <remarks></remarks>
Private Sub ProcessFeatures(ByVal features As SPFeatureCollection, ByVal element As String)
writer.WriteStartElement(element)
writer.WriteAttributeString("Count", features.Count.ToString)
For Each feature As SPFeature In features
writer.WriteStartElement("Feature")
writer.WriteAttributeString("ID", feature.Definition.Id.ToString)
writer.WriteAttributeString("Name", feature.Definition.DisplayName)
writer.WriteAttributeString("Scope", feature.Definition.Scope.ToString)
writer.WriteAttributeString("SolutionID", feature.Definition.SolutionId.ToString)
writer.WriteEndElement() ' Feature
Next feature
writer.WriteEndElement() 'Features
End Sub
End Class
- Now that our custom command is ready, it's time to tell STSADM about it. To do that, we have to write a specific xml file and store it in "/config" directory of the "12 hive" (C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG). The name of the xml file must begin with "stsadmcommands." Let's call ours "stsadmcommands.MyStsadmCommands.xml". In this file is a very simple xml, we put a "
<?xml version="1.0" encoding="utf-8" ?>
<commands>
<command name="FarmManifest"
class="MyStsadmCommands.FarmManifest, MyStsadmCommands, Version=1.0.0.0, Culture=neutral, PublicKeyToken=606cd03fbee78308" />
</commands>
As you certainly have noticed, the class attribute of the command is a string containing {the class name, the namespace, the version, the culture, the public key token}. To obtain the public key token, we have to sign our project first. To do so, go to Project properties (right click on the projet name), select the "Signing" tab, select the "Sign the assembly" option and choose
The "MyStsadmCommands.snk" file is now added to the project. Now, build the project. We still do not have our public key token, I know ! Check out this link : Visual Studio tip to get the public key token.
For now, to begin to test our fresh StsAdm command, we only have to copy the
Congratulation! Now in a command line window, try this :
stsadm -o FarmManifest -filename c:\FarmManifest.xml -includeall
Warning : If you have a large farm, this command may take long to execute.
In part 2 of this post, I'll explain how to build a SharePoint solution file (wsp) to deploy our custom command. Until then, if you have any question, feel free to ask.
No comments:
Post a Comment