MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Friday, December 18, 2009

Creating a SharePoint 2010 Site Structure Using PowerShell

In a previous post I detailed how to use PowerShell to perform what would be otherwise done using PSConfig to create an initial SharePoint Farm. In this post I will continue the example and show how to create your web applications using a simple XML configuration file and a reusable script.

Like the previous example I have a very basic XML file that defines my web application structure. In this example I've included not only the web application and application pool but also the content databases and site collections, along with the SharePoint Designer settings. Consider the XML and corresponding PowerShell a starting place to extend further if needed by adding elements for managed paths, quota templates, sites and even lists. Here's the XML which I store in a file called WebAppConfigurations.xml:

<WebApplications>
<WebApplication Name="SharePoint Portal (80)"
DefaultTimeZone="12"
DefaultQuotaTemplate="Portal"
AllowAnonymous="false"
AuthenticationMethod="NTLM"
HostHeader="portal"
Path="c:\sharepoint\webs\portal"
Port="80"
LoadBalancedUrl="http://portal"
Ssl="false">
<ApplicationPool Name="SharePoint Portal App Pool"
Account="sp2010\spportalapppool" />
<SPDesigner AllowDesigner="true" AllowRevertFromTemplate="true"
AllowMasterPageEditing="true" ShowURLStructure="true" />
<ContentDatabases>
<ContentDatabase Server="spsql1"
Name="SharePoint_Content_Portal1"
MaxSiteCount="100" WarningSiteCount="80"
Default="true">
<SiteCollections>
<SiteCollection Name="Portal"
Description=""
Url="http://portal"
LCID="1033"
Template="SPSPORTAL#0"
OwnerLogin="sp2010\siteowner1"
OwnerEmail="siteowner1@sp2010.com"
SecondaryLogin="sp2010\spadmin"
SecondaryEmail="spadmin@sp2010.com">
</SiteCollection>
</SiteCollections>
</ContentDatabase>
</ContentDatabases>
</WebApplication>
</WebApplications>

Note that you could easily adapt the file by having the <WebApplications /> element be a child of the <Farm /> element shown in my previous post resulting in a single configuration file rather than multiple files. One thing to note is that I'm not storing the password for the application pool account which I assume exists - the password will be asked for when the script runs.

Let's take a look at the script that does all the work:

function Start-WebApplicationsBuild(
    [string]$settingsFile = "Configurations.xml") {
    [xml]$config = Get-Content $settingsFile
        
    #Creating individual web applications
    $config.WebApplications.WebApplication | ForEach-Object {
        $webAppConfig = $_
        $webApp = New-WebApplication $webAppConfig
    
        #Configuring SharePoint Designer Settings
        $spd = $webAppConfig.SPDesigner
        $allowRevert = ([bool]::Parse($spd.AllowRevertFromTemplate))
        $allowMasterEdit = ([bool]::Parse($spd.AllowMasterPageEditing))
        Write-Host "Setting SP Designer settings..."
        $webApp | Set-SPDesignerSettings `
            -AllowDesigner:([bool]::Parse($spd.AllowDesigner)) `
            -AllowRevertFromTemplate:$allowRevert `
            -AllowMasterPageEditing:$allowMasterEdit `
            -ShowURLStructure:([bool]::Parse($spd.ShowURLStructure))
        
        $webAppConfig.ContentDatabases.ContentDatabase | ForEach-Object {
            #Creating content database
            Write-Host "Creating content database $($_.Name)..."
            $db = New-SPContentDatabase -Name $_.Name `
                -WebApplication $webApp `
                -DatabaseServer $_.Server `
                -MaxSiteCount $_.MaxSiteCount `
                -WarningSiteCount $_.WarningSiteCount
            
            $_.SiteCollections.SiteCollection | ForEach-Object {
                #Creating site collection
                Write-Host "Creating site collection $($_.Url)..."
                $gc = Start-SPAssignment
                $site = $gc | New-SPSite `
                    -Url $_.Url `
                    -ContentDatabase $db `
                    -Description $_.Description `
                    -Language $_.LCID `
                    -Name $_.Name `
                    -Template $_.Template `
                    -OwnerAlias $_.OwnerLogin `
                    -OwnerEmail $_.OwnerEmail `
                    -SecondaryOwnerAlias $_.SecondaryLogin `
                    -SecondaryEmail $_.SecondaryEmail
                Stop-SPAssignment -SemiGlobal $gc
            }
        }
    }
}

function New-WebApplication([System.Xml.XmlElement]$webAppConfig) {
    $poolAccount = $null
    $tempAppPool = $null
    $poolName = $webAppConfig.ApplicationPool.Name
    if ([Microsoft.SharePoint.Administration.SPWebService]::ContentService.ApplicationPools.Count -gt 0) {
        $tempAppPool = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.ApplicationPools | ? {$_.Name -eq $poolName}
    }
    if ($tempAppPool -eq $null) {
        Write-Host "Getting $($webAppConfig.ApplicationPool.Account) account for application pool..."
        $accountCred = Get-Credential $webAppConfig.ApplicationPool.Account
        $poolAccount = (Get-SPManagedAccount -Identity $accountCred.Username -ErrorVariable err -ErrorAction SilentlyContinue)
        if ($err) {
            $poolAccount = New-SPManagedAccount -Credential $accountCred
        }
    }

    $allowAnon = [bool]::Parse($webAppConfig.AllowAnonymous.ToString())
    $ssl = [bool]::Parse($webAppConfig.Ssl.ToString())

    $db = $null
    if ($webAppConfig.ContentDatabases.ChildNodes.Count -gt 1) {
        $db = $webAppConfig.ContentDatabases.ContentDatabase | `
            where {$_.Default -eq "true"}
        if ($db -is [array]) {
            $db = $db[0]
        }
    } else {
        $db = $webAppConfig.ContentDatabases.ContentDatabase
    }

    #Create the web application
    Write-Host "Creating web application $($webAppConfig.Name)..."
    $webApp = New-SPWebApplication -SecureSocketsLayer:$ssl `
        -AllowAnonymousAccess:$allowAnon `
        -ApplicationPool $poolName `
        -ApplicationPoolAccount $poolAccount `
        -Name $webAppConfig.Name `
        -AuthenticationMethod $webAppConfig.AuthenticationMethod `
        -DatabaseServer $db.DatabaseServer `
        -DatabaseName $db.DatabaseName `
        -HostHeader $webAppConfig.HostHeader `
        -Path $webAppConfig.Path `
        -Port $webAppConfig.Port `
        -Url $webAppConfig.LoadBalancedUrl `
        -ErrorVariable err

    return $webApp
}

I've put the script in two different functions with Start-WebApplicationsBuild being the primary function that is called by the logged in user. The other function, New-WebApplication, is just there for readability (I wanted to separate out the code that created the application pool and web application itself). Note that, like in my previous post, I use a more complex version of this script which has the various elements broken out into many different shared helper functions and considerably more tracing and error handling added - this script is a fairly simplistic version which lets you focus on the core SharePoint 2010 PowerShell stuff without polluting the code with lots of plumbing.

With this script and XML file structure you can create as many web applications, content databases, and site collections as needed by only modifying the XML file - the script will support any number of each. One thing to be careful of - make sure you have only one <ContentDatabase /> element with a Default attribute set to "true" (this is the database that will be created when the web application is created - you may have as many <ContentDatabase /> elements as needed but you need at least one with a Default value of true).

Hopefully this script proves useful to anyone who needs to automatically create their SharePoint 2010 site structure. Stay tuned for the next piece of the scripts which will cover provisioning service applications.

7 comments:

MS said...

how do you run this script and also can you have tags as child to site collection

MS said...

I get this errors when i run the script


PS C:\work\SiteCreation> ..\script.ps1
The term '..\script.ps1' is not recognized as the name of a cmdlet, function, s
cript file, or operable program. Check the spelling of the name, or if a path w
as included, verify that the path is correct and try again.
At line:1 char:14
+ ..\script.ps1 <<<<
+ CategoryInfo : ObjectNotFound: (..\script.ps1:String) [], Comma
ndNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

PS C:\work\SiteCreation> Start-WebApplicationsBuild "WebAppConfigurations.xml "
The term 'Start-WebApplicationsBuild' is not recognized as the name of a cmdlet
, function, script file, or operable program. Check the spelling of the name, o
r if a path was included, verify that the path is correct and try again.
At line:1 char:27
+ Start-WebApplicationsBuild <<<< "WebAppConfigurations.xml "
+ CategoryInfo : ObjectNotFound: (Start-WebApplicationsBuild:Stri
ng) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

Gary Lapointe said...

Make sure you have a space between your two periods: ". .\script.ps1"

delegator said...

Hi Gary, Great work! I'm new to powershell and what you do here is perfect to learn it the right way.

In your fallowing script i found a trivial error:
$webApp = New-SPWebApplication -SecureSocketsLayer:$ssl ` -AllowAnonymousAccess:$allowAnon ` -ApplicationPool $pool.Name ` -ApplicationPoolAccount $pool.ProcessAccount ` -Name $webAppConfig.Name ` -AuthenticationMethod $webAppConfig.AuthenticationMethod ` -DatabaseServer $db.DatabaseServer ` -DatabaseName $db.DatabaseName ` -HostHeader $webAppConfig.HostHeader ` -Path $webAppConfig.Path ` -Port $webAppConfig.Port ` -Url $webAppConfig.LoadBalancedUrl ` -ErrorVariable err

Should be:
$webApp = New-SPWebApplication -SecureSocketsLayer:$ssl -AllowAnonymousAccess:$allowAnon -ApplicationPool $pool.Name -ApplicationPoolAccount $pool.ProcessAccount -Name $webAppConfig.Name -AuthenticationMethod $webAppConfig.AuthenticationMethod -DatabaseServer $db.Server -DatabaseName $db.Name -HostHeader $webAppConfig.HostHeader -Path $webAppConfig.Path -Port $webAppConfig.Port -Url $webAppConfig.LoadBalancedUrl -ErrorVariable err


Well...

-DatabaseServer $db.Server -DatabaseName $db.Name

Instead of

DatabaseServer $db.DatabaseServer ` -DatabaseName $db.DatabaseName

-----
I would like to know if it's possible to call a function in multi-line for readability the ` char doesn't work for me...

Also is it possible to re-initialize all variable in a shell because re-running the script doesn't for me. I have to open a new shell.

Tank You
ex:

Gary Lapointe said...

Unfortunately if you want to call a cmdlet and break it onto multiple lines then you have to use the tick character. To reload the script just re-dot source it ". .\filename.ps1".

Thanks for the catch on the script - I thought I'd fixed that but must have missed it in the version I posted here.

Rich Finn said...

Hey Gary,

What do we do now that the New-SPIisWebServiceApplicationPool cmdlet is marked as internal sealed in RTM?

Rich Finn

Gary Lapointe said...

Rich - I haven't had time to update my post yet but the cmdlet was simply renamed to New-SPServiceApplicationPool - use that instead and it will work just fine.