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: EnumAllWebsfunction 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 anyif ($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 installedif ($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 anyif ($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 properlytry
{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 installedif ($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 stringif (($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 existif ($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 functionsif ($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 defaultif ($DatabaseServer -eq $null) { $DatabaseServer = $cs.DefaultDatabaseInstance.DisplayName }
$db = Get-SPContentDatabase -DatabaseName $DatabaseName -DatabaseServer $DatabaseServer
# Process databaseProcessOneContentDatabase -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 databaseProcessOneContentDatabase -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 functionenumallwebs -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!