The other day I was on Twitter (@CraigToThePoint) having a debate with Joel Olson, Lori Gowin, Wictor WilĂ©n, and others about whether or not STSADM is truly “deprecated” in SharePoint 2010. As you can imagine, I was of the opinion that any you can do with STSADM you can do with PowerShell.
Autocorrect aside, I was pretty confident in this statement. As a result of my confidence I was promptly challenged.
Still I was confident and accepted the challenge.
To keep a long story (took about 6 hours before I gave in) short, I was unable to get ALL of the functionality. What I WAS able to get is the equivalent of:
stsadm -o enumallwebs -includefeatures –includeeventreceivers
For those keeping score at home that is 2 out of the 4 available switches from the 2010 version of the STSADM command. It is important to note that I could have done them all but it would have required querying the database. While this is entirely possible and pretty easy from PowerShell, it is a no no for SharePoint.
The script below is what I came up with.
# Joel Olson Challenge
#Region Function Definition: EnumAllWebs
function EnumAllWebs
{
param
(
[switch]$IncludeEventReceivers,
[switch]$IncludeFeatures,
[switch]$IncludeSetupFiles,
[switch]$IncludeWebParts,
[switch]$IncludeCustomListView,
[string]$DatabaseName,
[string]$DatabaseServer
)
#Region Function Definition: FindSiteTemplateReference
# Checks to see if the specified template is installed in the farm
# Returns null if template is not found, returns the template if it is
function FindSiteTemplateReference
{
param
(
[int]$lcid,
[string]$TemplateName
)
foreach ($template in $templates)
{
if (($template.LCID -eq $lcid) -and ($template.Name -eq $TemplateName))
{
return $template
}
}
return $null
}
#EndRegion Function Definition: FindSiteTemplateReference
#Region Function Definition: ProcessOneContentDatabase
# Runs for each database
function ProcessOneContentDatabase
{
param
(
[System.Xml.XmlTextWriter]$writer,
[Microsoft.SharePoint.Administration.SPContentDatabase]$db,
[bool]$IncludeFeatures,
[bool]$IncludeWebParts,
[bool]$IncludeSetupFiles,
[bool]$IncludeEventReceivers,
[bool]$IncludeCustomListView
)
#Region Function Definition: OutputSiteXml
# Generates the XML for each site
function OutputSiteXml
{
param
(
[System.Xml.XmlTextWriter]$writer,
[Microsoft.SharePoint.SPSite]$site,
[bool]$IncludeFeatures,
[bool]$IncludeWebParts,
[bool]$IncludeSetupFiles,
[bool]$IncludeEventReceivers,
[bool]$IncludeCustomListView
)
#Region Function Definition: OutputFeatureXml
# Generates the XML for each feature
function OutputFeatureXml
{
param
(
[System.Xml.XmlTextWriter]$writer,
[string]$web
)
# Only process features if there are any
if ($web.Features.Count -gt 0)
{
$writer.WriteStartElement("Features")
$features = $web.Features
foreach ($feature in $features)
{
$writer.WriteStartElement("Feature")
$writer.WriteAttributeString("Id", $feature.DefinitionId)
# Check if feature is correctly installed
if ($feature.Definition -ne $null)
{
$definition = $feature.Definition
$writer.WriteAttributeString("DisplayName", $definition.DisplayName)
$writer.WriteAttributeString("InstallPath", $definition.RootDirectory)
$writer.WriteAttributeString("Status", "Installed")
}
else
{
$writer.WriteAttributeString("Status", "Missing")
}
# End element for Feature node
$writer.WriteEndElement()
}
# End element for Features node
$writer.WriteEndElement()
}
}
#EndRegion Function Definition: OutputFeatureXml
#Region Function Definition: OutputEventReceiverXml
# Generates the XML for each Event Receiver Assembly
function OutputEventReceiverXml
{
param
(
[System.Xml.XmlTextWriter]$writer,
[string]$web
)
# Only process event receivers if there are any
if ($web.EventReceivers.Count -gt 0)
{
$writer.WriteStartElement("EventReceiverAssemblies")
# Limit to unique assembly names
$evAssemblies = $web.EventReceivers | Select-Object -Unique Class
foreach ($evAssembly in $evAssemblies)
{
$assemblyString = $evAssembly.Class.ToString()
# Check if event receiver is installed properly
try
{
if ([System.Reflection.Assembly]::LoadWithPartialName($assemblyString) -ne $null)
{
$status = "Installed"
}
}
catch [Exception]
{
$status = "Missing"
}
$writer.WriteStartElement("EventReceiverAssembly")
$writer.WriteAttributeString("Name", $assemblyString)
$writer.WriteAttributeString("Status", $status)
# End element for EventReceiverAssembly
$writer.WriteEndElement()
}
# End element for EventReceiverAssemblies
$writer.WriteEndElement()
}
}
#EndRegion Function Definition: OutputEventReceiverXml
#Region Function Definition: OutputWebPartXml
function OutputWebPartXml
{
param
(
[System.Xml.XmlTextWriter]$writer,
[string]$web
)
## This would require running SQL queries directly against the database
}
#EndRegion Function Definition: OutputWebPartXml
#Region Function Definition: OutputCustomListViewXml
function OutputCustomListViewXml
{
param
(
[System.Xml.XmlTextWriter]$writer,
[string]$web
)
## This would require running SQL queries directly against the database
}
#EndRegion Function Definition: OutputCustomListViewXml
#Region Function Definition: OutputSetupFileXml
function OutputSetupFileXml
{
param
(
[System.Xml.XmlTextWriter]$writer,
[string]$web
)
## This would require running SQL queries directly against the database
}
#EndRegion Function Definition: OutputSetupFileXml
$writer.WriteStartElement("Site")
$writer.WriteAttributeString("Id", $site.ID)
$writer.WriteAttributeString("OwnerLogin", $site.Owner.LoginName)
# Check if it is a host header site collection
if ($site.HostHeaderIsSiteName)
{
$writer.WriteAttributeString("HostHeader", $site.HostName)
}
if ($site.AllWebs.Count -gt 0)
{
$writer.WriteStartElement("Webs")
$writer.WriteAttributeString("Count", $site.AllWebs.Count)
foreach ($web in $site.AllWebs)
{
try
{
$reference = FindSiteTemplateReference -lcid $web.Language -TemplateName "$($web.WebTemplate)#$($web.Configuration)"
# Check if web template is properly installed
if ($reference -eq $null)
{
$str = "Unknown"
}
elseif ($web.Configuration -eq -1)
{
$str = [string]::Empty
}
else
{
$str = "$($web.WebTemplate)#$($web.Configuration)"
}
$writer.WriteStartElement("Web")
$writer.WriteAttributeString("Id", $web.ID)
$writer.WriteAttributeString("Url", $web.Url)
$writer.WriteAttributeString("LanguageId", $web.Language)
# Handle cases where str var not set or set to empty string
if (($str -ne $null) -and ($str -ne [string]::Empty))
{
$writer.WriteAttributeString("TemplateName", $str);
}
if ($web.Configuration -ne -1)
{
$writer.WriteAttributeString("TemplateId", $web.WebTemplate);
}
# Process features
if ($includeFeatures)
{
OutputFeatureXml -writer $writer -web $web
}
# Process event reciever assemblies
if ($includeEventReceivers)
{
OutputEventReceiverXml -writer $writer -web $web
}
# Process web parts NOT USED!!!!
if ($includeWebParts)
{
OutputWebPartXml -writer $writer -web $web
}
# Process custom list views NOT USED!!!!
if ($includeCustomListView)
{
OutputCustomListViewXml -writer $writer -web $web
}
# Process setup files NOT USED!!!!
if ($includeSetupFiles)
{
OutputSetupFileXml -writer $writer -web $web
}
# End element for web
$writer.WriteEndElement()
}
catch [Exception]
{
}
finally
{
$web.Dispose()
}
}
# End element for webs
$writer.WriteEndElement()
}
# End element for site
$writer.WriteEndElement()
}
#EndRegion Function Definition: OutputSiteXml
$writer.WriteStartElement("Database")
$writer.WriteAttributeString("SiteCount", $db.CurrentSiteCount)
$writer.WriteAttributeString("Name", $db.Name)
$writer.WriteAttributeString("DataSource", $db.ServiceInstance.NormalizedDataSource)
# Only process sites if any exist
if ($db.Sites.Count -gt 0)
{
$writer.WriteStartElement("Sites")
foreach ($site in $db.Sites)
{
try
{
OutputSiteXml -writer $writer -site $site -IncludeEventReceivers $includeEventReceivers -IncludeFeatures $includeFeatures -IncludeWebParts $includeWebParts -IncludeSetupFiles $includeSetupFiles -IncludeCustomListView $includeCustomListView
}
catch [Exception]
{
}
finally
{
$site.Dispose()
}
}
# End element for sites
$writer.WriteEndElement()
}
#End element for database
$writer.WriteEndElement()
}
#EndRegion Function Definition: ProcessOneContentDatabase
# Create bool vars for passing to sub functions
if ($IncludeFeatures){ $blIncludeFeatures = $true} else { $blIncludeFeatures = $false}
if ($IncludeSetupFiles){ $blIncludeSetupFiles = $true} else { $blIncludeSetupFiles = $false}
if ($IncludeWebParts){ $blIncludeWebParts = $true} else { $blIncludeWebParts = $false}
if ($IncludeCustomListView){ $blIncludeCustomListView = $true} else { $blIncludeCustomListView = $false}
if ($IncludeEventReceivers){ $blIncludeEventReceivers = $true} else { $blIncludeEventReceivers = $false}
$local = Get-SPFarm
$services = New-Object Microsoft.SharePoint.Administration.SPWebServiceCollection $local
$cs = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$templates = Get-SPWebTemplate
$StringWriter = New-Object System.IO.StringWriter
$writer = New-Object System.XMl.XmlTextWriter $StringWriter
$writer.Formatting = "indented"
$writer.WriteStartElement("Databases")
# if database was specifed process only that database
if ($DatabaseName)
{
# if databaseserver was not specified use default
if ($DatabaseServer -eq $null) { $DatabaseServer = $cs.DefaultDatabaseInstance.DisplayName }
$db = Get-SPContentDatabase -DatabaseName $DatabaseName -DatabaseServer $DatabaseServer
# Process database
ProcessOneContentDatabase -writer $writer -db $db -includeFeatures $blIncludeFeatures -includeWebParts $blIncludeWebParts -includeSetupFiles $blIncludeSetupFiles -includeCustomListView $blIncludeCustomListView -includeEventReceivers $blIncludeEventReceivers
}
# otherwise process all databases
else
{
foreach ($service in $services)
{
foreach ($application in $service.WebApplications)
{
foreach ($database2 in $application.ContentDatabases)
{
# Process database
ProcessOneContentDatabase -writer $writer -db $database2 -includeFeatures $blIncludeFeatures -includeWebParts $blIncludeWebParts -includeSetupFiles $blIncludeSetupFiles -includeCustomListView $blIncludeCustomListView -includeEventReceivers $blIncludeEventReceivers
}
}
}
}
# End element for databases
$writer.WriteEndElement()
# Return output as string
$StringWriter.ToString()
$StringWriter.Flush()
$writer.Flush()
}
#EndRegion Function Definition: EnumAllWebs
# Call function
enumallwebs -IncludeEventReceivers -IncludeFeatures -IncludeSetupFiles -IncludeWebParts -IncludeCustomListView
Note: You will notice that the other parameters are still in there. They do not work but I wanted to show the full structure for those who are interested.
All in all what it comes down to is that until Microsoft decides to give us an API to get to some of these items they are out of reach via PowerShell while following best practices.
It hurts me to say it but: STSADM does still have its purposes.
Let me know what you think or if you have any questions on the script. I hope to dive deeper into some of the elements in later blog posts.
Thanks!