SharePoint Automation Gary Lapointe – Founding Partner, Aptillon, Inc.

24Feb/130

SharePoint Evolution Conference 2013

Today I finally got around to booking my travel for my favorite SharePoint conference – the SharePoint Evolution Conference in London England. For the third year in a row I have the honor of being able to present, and once again I’ll be covering the PowerShell side of things at the conference. Normally I tend to do more of a deep dive when I present but this time I’ll be stepping back a bit and providing more of an introductory type session to perhaps help those who have yet to realize the power of PowerShell get up to speed. This time I’ll only be doing the one session so I’ll get plenty of time to sit in on what I expect to be some great sessions (I originally was going to present on the SharePoint Education features but as those features are going away it was decided to scrub the session rather than present on something people shouldn’t be using).

Here’s the abstract for my session if your planning on attending:

The Power that is PowerShell – learn all about using PowerShell in SharePoint.
Audience: Developer / IT Pro

With the release of SharePoint 2013 Microsoft has nearly doubled the number of PowerShell cmdlets that are available for managing the product. This increase coupled with the fact that many things can now only be managed using PowerShell (such as search topology) means that administrators and developers alike must embrace PowerShell in order to be successful with SharePoint. In this session you will learn the basics of how to use PowerShell, from simple syntax to creating scripts for common or complex tasks; additionally, we’ll look at some of the new features available with PowerShell V3, the required version for SharePoint 2013.

Steve Smith and his crew at Combined Knowledge know how to put on an incredible show with fantastic speakers, sessions, and after-hours events. If you’re only given a budget for one conference each year you may not initially think about flying to London (assuming you’re not local) for that conference but I highly suggest you consider it. I’ve only been out of the States twice before in my life and both times were for this conference and it was totally worth it. From a purely educational standpoint the sessions are top notch with speakers who truly know their stuff so you can be assured that you’re getting quality information that stems from real world experience and not just playing around in a lab environment.

If you do make it to the conference please stop by my session and say hi!

Speaker_Evo-2013-Banner

24Sep/120

I’m Speaking at SharePoint Live!, December 10-14 in Orlando

I’ll be speaking at a brand new SharePoint conference for SharePoint administrators, developers and planners – SharePoint Live!, December 10-14 in Orlando, FL. http://bit.ly/SPSPK14

I’ll be presenting the following sessions:

  • Getting Started with PowerShell for SharePoint

      This session will provide an introduction to PowerShell and how to use it with SharePoint 2010 and 2013. We’ll cover basic PowerShell syntax, discovery, and scripting as well as new features introduced with PowerShell v3 and SharePoint 2013. Throughout the session we’ll be walking through real-world scripts and scenarios to help you understand not just how to use PowerShell but why you would use it.

      You will learn:

      • Learn the basic syntax of PowerShell
      • Understand how to find what you’re looking for
      • Learn how to create simple PowerShell scripts
      • Discover what’s new with PowerShell v3 and SharePoint 2013
  • Remote SharePoint Management Using PowerShell

      This session will explore the SharePoint Online Management Shell and provide real-world examples of how to manage an Office 365 SharePoint environment using PowerShell 3.0 and the new Microsoft.Online.SharePoint.PowerShell Module. Additionally, we will cover how to use PowerShell remoting to connect to your on-premises SharePoint 2010 or 2013 environment.

      You will learn:

      • Explore what is available within the SharePoint Online Management Shell
      • Learn how to execute cmdlets against Office 365
      • Understand PowerShell Remoting and how to connect to your on-premise environment

This event will have something for everyone (IT Pros, Developers, Business Users, Decision Makers), and will provide leading-edge training on customizing, deploying and maintaining SharePoint Server and SharePoint Foundation to maximize the business value.

SPECIAL OFFER: As a speaker, I can extend $500 savings on the 5-day package. Register here: http://bit.ly/SPSPK14Reg  and use code SPSPK14.

The extra-cool part about SharePoint Live!: four events in one! This year, the event will be co-located with Cloud & Virtualization Live!, SQL Server Live!, and Visual Studio Live!. You can customize your conference agenda and attend ANY sessions from all four events. Register now: http://bit.ly/SPSPK14Reg

15May/126

International SharePoint Conference 2012 Follow-up

First off I apologize for the delay in getting this post out – I’ve been fraught with injuries and illness since my return from London and just haven’t had the time or mental capacity to think about writing anything.

During my sessions at the ISC I demonstrated (along with Spence Harbar and Chan Kulathilake) how we could use PowerShell to provision the entire SharePoint 2010 Farm used throughout the IT track at the conference (yeah, no pressure – everyone presenting after me was depending on my scripts functioning). This Farm consisted of numerous servers beyond the SharePoint servers but my scripts were only responsible for getting SharePoint configured. For reference here’s a screenshot showing the server topology:

image

Before we could run the scripts on any servers there was some prep work that we had to do ahead of time. Specifically we did the following on each of the SharePoint servers (SP01-06):

  • Installed SharePoint 2010 and Office Web Applications with SP1 and the October 2011 CU
    • The installation of the bits could have been scripted but doing so during a live session at a conference just wasn’t practical due to time limitations.
  • Disable: UAC, Firewall, IE ESC
    • This isn’t something you’d typically do for a production environment but doing so reduced potential complications and annoyances when it came to doing demos. I typically will disable all these things when doing my provisioning and then selectively re-enable them after I’ve confirmed that the Farm is running properly – this makes troubleshooting much easier.
  • PowerShell Prep
    • I updated the all users all hosts profile on each server so that the SharePoint PowerShell snap-in would be loaded for any editor so that we wouldn’t be tied to the management shell. (We also installed the PowerShell ISE).
    • I enabled remoting on all servers so that I could build each server remotely, thereby foregoing the need to have to log into each server to kick off the script in parallel. I also created a custom configuration session which loaded the SharePoint PowerShell snap-in automatically and made sure the threading model was set appropriately. The following shows the commands I executed to enable remoting:
      Enable-PSRemoting
      Enable-WSmanCredSSP -Role Server
      Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024
      Register-PSSessionConfiguration -Name "SharePoint" -StartupScript "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\POWERSHELL\Registration\sharepoint.ps1" -Force -ThreadOptions ReuseThread
      Set-PSSessionConfiguration -Name "SharePoint" -ShowSecurityDescriptorUI -Force
      
    • Because the we’re using scripts and because those scripts are stored on a network share we had to set the PowerShell execution policy on each server to Bypass.
  • SPInstall Permissions
    • The scripts were all run using a single named account which we called SPInstall. In order to perform all the required tasks this account was made a local administrator on all the SharePoint servers (none of the others) and the account was granted the dbcreator and securityadmin roles on SQL01 and SQL02 (or primary and failover SQL instances).
  • Add SQL Alias and DNS Entries
    • The DNS servers were updated with the appropriate entries for all of our Web Applications (we did not use hosts files) and, to connect to SQL Server, we used a SQL alias which needed to be added on each of the SharePoint servers.

Controller Scripts

So that constitutes the extent of the prep work that we did before we could kick off the scripts that I put together. To do the provisioning I used a main “controller” script which I called ConfigureServer.ps1. This script was responsible for loading all the subsequent scripts which created the initial Farm (or connected to it), provisioned the Web Applications and Site Collections, and finally, created all the relevant Service Applications:

param(
  [switch]$Connect
)

Write-Host "Script Start Time: $(Get-Date)" -ForegroundColor Green

#Main Farm
. .\FarmCreation\Build-SPFarm.ps1
$farmConfigFile = Resolve-Path .\FarmCreation\FarmConfigurations.xml
try {
    if ($Connect) {
        Join-SPFarm $farmConfigFile
    } else {
        New-SPFarm $farmConfigFile
    }
} catch {
    Write-Warning "Unable to build or join Farm!"
    $_
    return
}

#Web Applications
if (!$Connect) {
    #Only need to do this once so do it when we create the farm initially and not for each connection.
    try {
        . .\WebApplications\Provision-WebApplications.ps1
        Provision-WebApplications (Resolve-Path .\WebApplications\WebApplications.xml)
    } catch {
        Write-Warning "Unable to create web applications!"
        $_
        return
    }
}

#Services
try {
    #State Service
    . .\ServiceApplications\StateServices\Start-StateServices.ps1
    Start-StateServices (Resolve-Path .\ServiceApplications\StateServices\StateServices.xml)

    #Usage Service
    . .\ServiceApplications\UsageService\Start-UsageService.ps1
    Start-UsageService (Resolve-Path .\ServiceApplications\UsageService\UsageService.xml)

    #Secure Store Services
    . .\ServiceApplications\SecureStoreServices\Start-SecureStoreServices.ps1
    Start-SecureStoreServices (Resolve-Path .\ServiceApplications\SecureStoreServices\SecureStoreServices.xml)

    #Web Analytics Services
    . .\ServiceApplications\WebAnalyticsServices\Start-WebAnalyticsServices.ps1
    Start-WebAnalyticsServices (Resolve-Path .\ServiceApplications\WebAnalyticsServices\WebAnalyticsServices.xml)

    #User Code Services
    . .\ServiceApplications\UserCodeServices\Start-UserCodeServices.ps1
    Start-UserCodeServices (Resolve-Path .\ServiceApplications\UserCodeServices\UserCodeServices.xml)

    #BCS Services
    . .\ServiceApplications\BCSServices\Start-BCSServices.ps1
    Start-BCSServices (Resolve-Path .\ServiceApplications\BCSServices\BCSServices.xml)

    #Excel Services
    . .\ServiceApplications\ExcelServices\Start-ExcelServices.ps1
    Start-ExcelServices (Resolve-Path .\ServiceApplications\ExcelServices\ExcelServices.xml)

    #Word Automation Services
    . .\ServiceApplications\WordAutomationServices\Start-WordAutomationServices.ps1
    Start-WordAutomationServices (Resolve-Path .\ServiceApplications\WordAutomationServices\WordAutomationServices.xml)

    #Metadata Services
    . .\ServiceApplications\MetadataServices\Start-MetadataServices.ps1
    Start-MetadataServices (Resolve-Path .\ServiceApplications\MetadataServices\MetadataServices.xml)

    #C2WTS
    . .\ServiceApplications\ClaimsToWindowsTokenServices\Start-ClaimsToWindowsTokenServices.ps1
    Start-ClaimsToWindowsTokenServices (Resolve-Path .\ServiceApplications\ClaimsToWindowsTokenServices\ClaimsToWindowsTokenServices.xml)

    #PowerPoint Services
    . .\ServiceApplications\PowerPointServices\Start-PowerPointServices.ps1
    Start-PowerPointServices (Resolve-Path .\ServiceApplications\PowerPointServices\PowerPointServices.xml)

    #Word Viewing Services
    . .\ServiceApplications\WordViewingServices\Start-WordViewingServices.ps1
    Start-WordViewingServices (Resolve-Path .\ServiceApplications\WordViewingServices\WordViewingServices.xml)
    
    #User Profile Services
    . .\ServiceApplications\UserProfileServices\Start-UserProfileServices.ps1
    Start-UserProfileServices (Resolve-Path .\ServiceApplications\UserProfileServices\UserProfileServices.xml)

    #Enterprise Search Services
    . .\ServiceApplications\EnterpriseSearchServices\Start-EnterpriseSearchServices.ps1
    Start-EnterpriseSearchServices (Resolve-Path .\ServiceApplications\EnterpriseSearchServices\EnterpriseSearchServices.xml)

} catch {
    Write-Warning "Service Application creation failed!"
    $_
    return
}
finally {
    Write-Host "Script End Time: $(Get-Date)" -ForegroundColor Green
}

 

This script was executed on each server, however, we ran it first on SP03 which is where the User Profile Synchronization Service was to be run. Due to issues with getting this guy to work using remoting we chose to RDP directly into the server and run this one script while logged into the server. Once this server was configured and the initial Farm created then we’d go ahead and run the script on the remaining servers by using PowerShell remoting to execute the script from a single location.

This is the script, which I named simply go.ps1, that I used to call the ConfigureServer.ps1 script for all the remaining SharePoint servers (everything other than SP03):

$servers = @("SP01","SP02","SP04","SP05","SP06")
$cred = Get-Credential "isclondon\spinstall"

foreach ($server in $servers) {
    Write-Host "---------------------------------------------------------------" -ForegroundColor Green
    Write-Host "$(Get-Date): Connecting to server $server." -ForegroundColor Green
    Write-Host "---------------------------------------------------------------" -ForegroundColor Green
    $session = New-PSSession -ComputerName $server -Authentication CredSSP -Credential $cred -ConfigurationName "SharePoint"
    Invoke-Command -Session $session -ScriptBlock {Set-ExecutionPolicy Bypass -Scope Process}
    Invoke-Command -Session $session -ScriptBlock {cd \\sp03\c$\Scripts}
    Invoke-Command -Session $session -ScriptBlock {. .\ConfigureServer.ps1 -Connect}
    Remove-PSSession $session
    Write-Host "$(Get-Date): Finished for server $server" -ForegroundColor Green
}

 

Build-SPFarm.ps1

So the preceding two scripts constitute the “controller” scripts that I used to call out to the core scripts which did all the real work. Now let’s look at the first and most important script as it is responsible for creating the Farm (or connecting to the Farm): Build-SPFarm.ps1.

This script, like all the remaining scripts is driven via an XML file; in other words, I store all the configuration information in a series of XML files that I pass into the function contained within the script. By taking this approach I can write a single script which is not tied to any particular environment and only change the contents of the XML file.

I think something that a lot of people forget when writing PowerShell scripts is that you are writing code, and just as you would never deploy code written by any old developer to your production environment without it first going through proper testing in a test environment, so should you never deploy (or execute in this case) scripts to production without first testing those scripts. And, with any code, if you make changes to that code you should follow proper testing practices and do a full regression test of any code that was modified (yes, this includes changing variable values because the value you specify could actually break the script if improperly formatted – think of someone forgetting a closing quote or adding a special character to the contents of a string variable). For these reasons, and many others, I encourage people to reduce the changes made to scripts by pulling the configuration information out into an XML file.

So lets look at the FarmConfigurations.xml XML file that I used for the Build-SPFarm.ps1 script:

<Farm ConfigDB="ISC_Config"
      ConfigDBFailoverDatabaseServer=""
      AdminContentDB="ISC_Content_CentralAdmin" 
      AdminContentDBFailoverDatabaseServer=""
      DatabaseServer="ISCSharePoint1" 
      Passphrase="p@ssw0rd"
      OutgoingEmailServer="192.168.1.101"
      OutgoingEmailFromAddr="administrator@isclondon.com"
      OutgoingEmailReplyToAddr="administrator@isclondon.com">
    <FarmAccount AccountName="isclondon\spfarm" AccountPassword="password" />
    <CentralAdmin Port="2010" AuthProvider="NTLM">
        <Servers>
            <Server Name="SP01" />
            <Server Name="SP02" />
            <Server Name="SP03" />
        </Servers>
    </CentralAdmin>
    <Services>
        <WebApplicationService>
            <Servers>
                <Server Name="SP01" />
                <Server Name="SP02" />
            </Servers>
        </WebApplicationService>
        <WorkflowTimerService>
            <Servers>
                <Server Name="SP01" />
                <Server Name="SP02" />
            </Servers>
        </WorkflowTimerService>
        <IncomingEmailService>
            <Servers>
                <Server Name="SP01" />
                <Server Name="SP02" />
            </Servers>
        </IncomingEmailService>
        <TraceService>
            <Account AccountName="isclondon\sptrace" AccountPassword="password" />
        </TraceService>
    </Services>
 </Farm>

 

Note that in this XML we’ve embedded the passwords used – I don’t actually recommend doing this, but if you do you should make sure the files are properly secured and, ideally, remove the password after provisioning is complete (we embedded the passwords so that we wouldn’t be continually prompted for credentials during the build as this would make it very difficult to talk through our presentation).

Before I show the contents of the Build-SPFarm.ps1 script I just wanted to point out one more design consideration which you may have clued in on by looking at the ConfigureServer.ps1 script. With rare exceptions (the ConfigureServer.ps1 script being one of them), I always make my scripts so that running the script will actually do nothing other than load a function in memory which must then be called in order to perform any action. This is to prevent someone from accidentally causing a script to execute (for example, you intend to edit the file so you double-click it and rather than open in an editor it opens in the console and is executed). The only reason the ConfigureServer.ps1 script does not work this way is because I wanted to show that you can pass parameters into a script file.

With that out of the way, let’s look at the Build-SPFarm.ps1 script:

function Join-SPFarm ([string]$settings = $(throw "-settings is required")) {
    Build-SPFarm $true $settings
}

function New-SPFarm ([string]$settings = $(throw "-settings is required")) {
    Build-SPFarm $false $settings
}

function Build-SPFarm ([bool]$connectToExisting = $false, [string]$settings = $(throw "-settings is required")) {   
    [xml]$config = Get-Content $settings

    if ([string]::IsNullOrEmpty($config.Farm.FarmAccount.AccountPassword)) {
        $farmAcct = Get-Credential $config.Farm.FarmAccount.AccountName
    } else {
        $farmAcct = New-Object System.Management.Automation.PSCredential $config.Farm.FarmAccount.AccountName, (ConvertTo-SecureString $config.Farm.FarmAccount.AccountPassword -AsPlainText -force)
    }

    $configDb = $config.Farm.ConfigDB
    $contentDB = $config.Farm.AdminContentDb
    $server = $config.Farm.DatabaseServer
    if ($config.Farm.Passphrase.Length -gt 0) {
        $passphrase = (ConvertTo-SecureString $config.Farm.Passphrase -AsPlainText -force)
    } else {
        Write-Warning "Using the Farm Admin's password for a passphrase"
        $passphrase = $farmAcct.Password
    }
    
    #Only build the farm if we don't currently have a farm created
    if ([Microsoft.SharePoint.Administration.SPFarm]::Local -eq $null) {
        psconfig -cmd upgrade -inplace b2b
        
        if ($connectToExisting) {
            #Connecting to farm
            Write-Host "Connecting to Farm..."
            Connect-SPConfigurationDatabase -DatabaseName $configDb -DatabaseServer $server -Passphrase $passphrase
        } else {
            #Creating new farm
            Write-Host "Creating Farm..."
            New-SPConfigurationDatabase `
                -DatabaseName $configDb `
                -DatabaseServer $server `
                -AdministrationContentDatabaseName $contentDB `
                -Passphrase $passphrase `
                -FarmCredentials $farmAcct
            
            if (![string]::IsNullOrEmpty($config.Farm.ConfigDBFailoverDatabaseServer)) {
                Set-FailoverDatabase $configDb $config.Farm.ConfigDBFailoverDatabaseServer
            }
            if (![string]::IsNullOrEmpty($config.Farm.AdminContentDBFailoverDatabaseServer)) {
                Set-FailoverDatabase $contentDB $config.Farm.AdminContentDBFailoverDatabaseServer
            }
        }
        #Verifying farm creation
        $spfarm = Get-SPFarm -ErrorAction SilentlyContinue
        if ($spfarm -eq $null) {
            throw "Unable to verify farm creation."
        }

        #ACLing SharePoint Resources
        Write-Host "Calling Initialize-SPResourceSecurity..."
        Initialize-SPResourceSecurity

        #Installing Services
        Write-Host "Calling Install-SPService..."
        Install-SPService

        #Installing Features
        Write-Host "Calling Install-SPFeature..."
        Install-SPFeature -AllExistingFeatures
        
        Remove-PsSnapin Microsoft.SharePoint.PowerShell
        Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
    } else {
        Write-Warning "Farm exists. Skipping creation."
    }
    
    if ((Get-Service sptimerv4).Status -ne "Running") {
        Write-Host "Starting SPTimerV4 Service"
        Start-Service sptimerv4
    }

    $scaConfig = $config.Farm.CentralAdmin
    $installSCA = (($scaConfig.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
    if ($installSCA) {
        $auth = $scaConfig.AuthProvider
        $port = $scaConfig.Port
        $url = "http://$($env:computername):$port"
        $sca = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup($url)
        if ($installSCA -and $sca -eq $null) {
            #Provisioning Central Administration
            Write-Host "Creating Central Admin Site at $url..."
            New-SPCentralAdministration -Port $port -WindowsAuthProvider $auth

            #Installing Help
            Write-Host "Calling Install-SPHelpCollection..."
            Install-SPHelpCollection -All

            #Installing Application Content
            Write-Host "Calling Install-SPApplicationContent..."
            Install-SPApplicationContent
        }
    }
    
    if (!$connectToExisting) {
        $server = $config.Farm.OutgoingEmailServer
        $from = $config.Farm.OutgoingEmailFromAddr
        $replyTo = $config.Farm.OutgoingEmailReplyToAddr
        $charSet = 65001
        $wa = [Microsoft.SharePoint.Administration.SPAdministrationWebApplication]::Local
        if ($wa -ne $null) {
            $wa.UpdateMailSettings($server, $from, $replyTo, $charSet)
        }
    }
    
    #Stop or Start key service instances
    $startWebAppSvc = (($config.Farm.Services.WebApplicationService.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
    Set-ServiceInstanceState "Microsoft SharePoint Foundation Web Application" $startWebAppSvc

    $startWorkflowSvc = (($config.Farm.Services.WorkflowTimerService.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
    Set-ServiceInstanceState "Microsoft SharePoint Foundation Workflow Timer Service" $startWorkflowSvc

    $startIncomingEmailSvc = (($config.Farm.Services.IncomingEmailService.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
    Set-ServiceInstanceState "Microsoft SharePoint Foundation Incoming E-Mail" $startIncomingEmailSvc
    
    
    #Set SPTraceV4 to run as domain account
    $accountNode = $config.Farm.Services.TraceService.Account
    if ($accountNode -ne $null -and ![string]::IsNullOrEmpty($accountNode.AccountName)) {
        if ([string]::IsNullOrEmpty($accountNode.AccountPassword)) {
            $svcAccount = Get-Credential $accountNode.AccountName
        } else {
            $svcAccount = New-Object System.Management.Automation.PSCredential $accountNode.AccountName, (ConvertTo-SecureString $accountNode.AccountPassword -AsPlainText -force)
        }
        $user = Get-SPManagedAccount -Identity $svcAccount.Username -ErrorAction SilentlyContinue
        if ($user -eq $null) {
            $user = New-SPManagedAccount -Credential $svcAccount
        }
        $tracingSvc = (Get-SPFarm).Services | ? {$_.Name -eq "SPTraceV4"}
        if ($tracingSvc.ProcessIdentity.Username -ne $user.Username) {
            $tracingSvc.ProcessIdentity.ManagedAccount = $user
            $tracingSvc.ProcessIdentity.CurrentIdentityType = "SpecificUser"
            $tracingSvc.ProcessIdentity.Update()
            $tracingSvc.ProcessIdentity.Deploy()
        }
        Write-Host "Adding `"$($user.Username)`" to Performance Log Users group..."
        $p = Start-Process -PassThru -FilePath "c:\windows\system32\net.exe" -ArgumentList "localgroup `"Performance Log Users`" `"$($user.Username)`" /add" -Wait -NoNewWindow
        if ($p.ExitCode -ne 0) { 
            Write-Warning "Unable to add `"$($user.Username)`" to Performance Log Users group!" 
        } else { 
            Restart-Service SPTraceV4
            iisreset
        }
    }

} 
function Set-ServiceInstanceState($typeName, $enable) {
    $svc = Get-SPServiceInstance -Server $env:computername | where {$_.TypeName -eq $typeName}
    if ($svc -eq $null) {
        throw "Unable to retrieve $typeName Service Instance."
    }
    if ($svc.Status -ne "Online" -and $enable) {
        Write-Host "Starting $typeName service instance..."
        $svc | Start-SPServiceInstance
    }
    if ($svc.Status -eq "Online" -and !$enable) {
        Write-Host "Stopping $typeName service instance..."
        $svc | Stop-SPServiceInstance -Confirm:$false
    }
}

function Set-FailoverDatabase($databaseName, [string]$failoverDbServer) {
    if (![string]::IsNullOrEmpty($failoverDbServer)) {
        $db = Get-SPDatabase | where {$_.Name -eq $databaseName}
        if ($db -eq $null) { 
            throw "Unable to retrieve the database to set the failover server: $databaseName" 
        }
        if (($db.FailoverServiceInstance -eq $null) -or ![string]::Equals($db.FailoverServiceInstance.NormalizedDataSource, $failoverDbServer, [StringComparison]::OrdinalIgnoreCase)) {
            try {
                Write-Host "Adding failover database instance..."
                $db.AddFailoverServiceInstance($failoverDbServer)
                $db.Update()
            } catch {
                Write-Warning "Unable to set failover database server. $_"
            }
        }
    }
}

 

I honestly don’t have the time to explain everything that this script is doing but suffice it to say it is doing a bit more than just creating or connecting to the Farm. One thing in particular is that I chose to put the enabling and disabling of some of the simple services (Web Application, Workflow, and Incoming Email) in this script rather than in the script(s) that provision the Service Applications (this was really just a decision of convenience for me and in my personal scripts this stuff is stored elsewhere).

One other thing I wanted to highlight was this line within the script: “psconfig -cmd upgrade -inplace b2b”. The reason this is there is because we installed the Office Web Applications which incorrectly sets a registry key that indicates that an upgrade is required. We can either fix the registry key or simply open and then close the configuration wizard on each server (without actually letting the wizard run) or we can just run this command on each server just prior to calling the New-SPConfigurationDatabase or Connect-SPConfigurationDatabase cmdlets. The command will actually generate an error but you can safely ignore it – all we care is that it fixes the registry key for us so that we can move on to the actual provisioning.

Provision-WebApplications.ps1

The next script I demoed during the first session was the Provision-WebApplications.ps1 script. This script, aside from the obvious of provisioning Web Applications, also provisioned any initial Site Collections and managed paths that we needed. The following illustration details the three Web Applications we created along with their Application Pool association and other relevant details:

image

Now let’s take a look at the XML file that I used to provide all the configuration information:

<Farm>
  <WebApplications>
    <WebApplication Name="SharePoint MySites" HostHeader="my.isclondon.local" Port="80" DefaultTimeZone="2" Path="c:\sharepoint\webs\mysites" Ssl="false" LoadBalancedUrl="http://my.isclondon.local" AllowAnonymous="false" SelfServiceSiteCreation="true" RequireContactForSsc="false" AuthenticationMode="Classic" EnableWindowsAuthentication="false" AuthenticationMethod="NTLM" EnableBasicAuthentication="false" EnableFormsBasedAuthentication="false">
      <ApplicationPool Name="SharePoint Collab" AccountName="isclondon\SPCollab" AccountPassword="password" />
      <PortalSuperUserAccount AccountName="isclondon\spcacheuser" AccountPassword="password" />
      <PortalSuperReaderAccount AccountName="isclondon\spcachereader" AccountPassword="password" />
      <ManagedPaths>
        <ManagedPath Explicit="false" RelativeURL="/personal" />
      </ManagedPaths>
      <UserPolicies>
        <UserPolicy UserLogin="isclondon\SPInstall" UserDisplayName="SharePoint Administrator" Zone="All">
          <Permission Name="Full Control" />
        </UserPolicy>
      </UserPolicies>
      <ProxyGroup Name="Default" />
      <SPDesigner AllowDesigner="true" AllowRevertFromTemplate="true" AllowMasterPageEditing="true" ShowURLStructure="true" />
      <ContentDatabases>
        <ContentDatabase Default="true" Server="ISCSharePoint1" FailoverDatabaseServer="" Name="ISC_Content_MySites" MaxSiteCount="15000" WarningSiteCount="9000">
          <SiteCollections>
            <SiteCollection Name="My Sites" Description="My Sites Host Site Collection" Url="http://my.isclondon.local" Template="SPSMSITEHOST#0" LCID="1033" OwnerLogin="isclondon\administrator" OwnerEmail="administrator@isclondon.com">
            </SiteCollection>
          </SiteCollections>
        </ContentDatabase>
      </ContentDatabases>
    </WebApplication>
    <WebApplication Name="SharePoint Team Sites" HostHeader="team.isclondon.local" Port="80" DefaultTimeZone="2" Path="c:\sharepoint\webs\teams" Ssl="false" LoadBalancedUrl="http://team.isclondon.local" AllowAnonymous="false" SelfServiceSiteCreation="false" RequireContactForSsc="false" AuthenticationMode="Classic" EnableWindowsAuthentication="false" AuthenticationMethod="NTLM" EnableBasicAuthentication="false" EnableFormsBasedAuthentication="false">
      <ApplicationPool Name="SharePoint Collab" AccountName="isclondon\SPCollab" AccountPassword="password" />
      <PortalSuperUserAccount AccountName="isclondon\spcacheuser" AccountPassword="password" />
      <PortalSuperReaderAccount AccountName="isclondon\spcachereader" AccountPassword="password" />
      <ManagedPaths>
      </ManagedPaths>
      <UserPolicies>
        <UserPolicy UserLogin="isclondon\SPInstall" UserDisplayName="SharePoint Administrator" Zone="All">
          <Permission Name="Full Control" />
        </UserPolicy>
      </UserPolicies>
      <ProxyGroup Name="Default" />
      <SPDesigner AllowDesigner="true" AllowRevertFromTemplate="false" AllowMasterPageEditing="true" ShowURLStructure="true" />
      <ContentDatabases>
        <ContentDatabase Default="true" Server="ISCSharePoint1" FailoverDatabaseServer="" Name="ISC_Content_TeamSites" MaxSiteCount="15000" WarningSiteCount="9000">
          <SiteCollections>
            <SiteCollection Name="Team Sites" Url="http://team.isclondon.local" Template="CMSPUBLISHING#0" LCID="1033" OwnerLogin="isclondon\administrator" OwnerEmail="administrator@isclondon.com">
            </SiteCollection>
          </SiteCollections>
        </ContentDatabase>
      </ContentDatabases>
      <Features />
    </WebApplication>
    <WebApplication Name="SharePoint Intranet" HostHeader="intranet.isclondon.local" Port="80" DefaultTimeZone="2" Path="c:\sharepoint\webs\intranet" Ssl="false" LoadBalancedUrl="http://intranet.isclondon.local" AllowAnonymous="false" SelfServiceSiteCreation="false" RequireContactForSsc="false" AuthenticationMode="Claims" EnableWindowsAuthentication="true" AuthenticationMethod="NTLM" EnableBasicAuthentication="false" EnableFormsBasedAuthentication="false">
      <ApplicationPool Name="SharePoint Content" AccountName="isclondon\SPContent" AccountPassword="password" />
      <PortalSuperUserAccount AccountName="isclondon\spcacheuser" AccountPassword="password" />
      <PortalSuperReaderAccount AccountName="isclondon\spcachereader" AccountPassword="password" />
      <ManagedPaths>
        <ManagedPath Explicit="true" RelativeURL="/CTHub" />
      </ManagedPaths>
      <UserPolicies>
        <UserPolicy UserLogin="isclondon\SPInstall" UserDisplayName="SharePoint Administrator" Zone="All">
          <Permission Name="Full Control" />
        </UserPolicy>
      </UserPolicies>
      <ProxyGroup Name="Default" />
      <SPDesigner AllowDesigner="true" AllowRevertFromTemplate="false" AllowMasterPageEditing="true" ShowURLStructure="true" />
      <ContentDatabases>
        <ContentDatabase Default="true" Server="ISCSharePoint1" FailoverDatabaseServer="" Name="ISC_Content_Intranet" MaxSiteCount="15000" WarningSiteCount="9000">
          <SiteCollections>
            <SiteCollection Name="Intranet" Url="http://intranet.isclondon.local" Template="STS#0" LCID="1033" OwnerLogin="isclondon\administrator" OwnerEmail="administrator@isclondon.com">
            </SiteCollection>
            <SiteCollection Name="Content Type Hub" Url="http://intranet.isclondon.local/CTHub" Template="STS#1" LCID="1033" OwnerLogin="isclondon\administrator" OwnerEmail="administrator@isclondon.com">
            </SiteCollection>
          </SiteCollections>
        </ContentDatabase>
      </ContentDatabases>
      <Features />
    </WebApplication>    
  </WebApplications>
</Farm>

 

At first glance the XML file, due to it’s size, may seem a bit overwhelming but hopefully as you go through it you’ll see that it really is a pretty natural and somewhat obvious structure. (Think about the possibilities if you had a great UI to manage this XML structure – wink, wink).

Okay, now for the code – and there’s a fair bit of it:

function Provision-WebApplications() {
    <#
    .Synopsis
        Creates the web applications.
    .Description
        Creates the web applications.
    .Example
        PS C:\> . .\Provision-WebApplications.ps1
        PS C:\> Provision-WebApplication -SettingsFile c:\WebApplications.xml
    .Example
        PS C:\> . .\Provision-WebApplications.ps1
        PS C:\> Provision-WebApplications -ConfigXml ([xml](Get-Content c:\farmconfiguration.xml))
    .Parameter SettingsFile
        The path to an XML file.
    #>
    [CmdletBinding(DefaultParameterSetName="FilePath")]
    param (
        [Parameter(Mandatory=$true, Position=0, ParameterSetName="XmlElement")]
        [ValidateNotNull()]
        [System.Xml.XmlElement]$ConfigElement,

        [Parameter(Mandatory=$true, Position=0, ParameterSetName="XmlDocument")]
        [ValidateNotNull()]
        [xml]$ConfigXml,

        [Parameter(Mandatory=$true, Position=0, ParameterSetName="FilePath")]
        [ValidateNotNullOrEmpty()]
        [string]$SettingsFile
    )
    switch ($PsCmdlet.ParameterSetName) { 
        "XmlDocument" { 
            if ($ConfigXml.Farm.WebApplications.WebApplication -ne $null) {
                foreach ($appConfig in $ConfigXml.Farm.WebApplications.WebApplication) {
                    Provision-WebApplications -ConfigElement $appConfig
                }
                return
            }
        }
        "FilePath" {
            Provision-WebApplications -ConfigXml ([xml](Get-Content $SettingsFile))
            return
        }
    }
    $config = $ConfigElement

    if ($config -eq $null) {
        Write-Warning "No web application defined. Skipping."
        return
    }

    
    $webApp = Get-SPWebApplication -Identity $config.Name -ErrorAction SilentlyContinue
    
    if ($webApp -eq $null) {
        $allowAnon = [bool]::Parse($config.AllowAnonymous.ToString())
        $ssl = [bool]::Parse($config.Ssl.ToString())

        $db = $null
        if ($config.ContentDatabases -eq $null) {
            throw "A content database configuration setting could not be found for `"$($config.Name)`"."
        }
        if ($config.ContentDatabases.ChildNodes.Count -gt 1) {
            $db = $config.ContentDatabases.ContentDatabase | where {$_.Default -eq "true"}
            if ($db -is [array]) {
                Write-Warning "Multiple content databases set as default for `"$($config.Name)`" (using first)"
                $db = $db[0]
            }
        } else {
            $db = $config.ContentDatabases.ContentDatabase
        }
        if ($db -eq $null) {
            throw "A content database configuration setting could not be found for `"$($config.Name)`"."
        }
        
        $poolName = $config.ApplicationPool.Name
        $poolAcctName = $config.ApplicationPool.AccountName
        $poolAcctPwd = $config.ApplicationPool.AccountPassword
        $poolAcct = $null
        
        #Check for existing Application Pool
        $pools = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.ApplicationPools
        if (($pools | ? {$_.Name -eq $poolName}) -eq $null) {
            $poolAcct = Get-SPManagedAccount $poolAcctName -ErrorAction SilentlyContinue
            if ($poolAcct -eq $null) {
                $securePwd = ConvertTo-SecureString $poolAcctPwd -AsPlainText -force
                $cred = New-Object System.Management.Automation.PSCredential $poolAcctName, $securePwd
                $poolAcct = New-SPManagedAccount -Credential $cred
            }
        }
        
        $loadBalancedUrl = $config.LoadBalancedUrl
        $port = $config.Port
        if (![string]::IsNullOrEmpty($loadBalancedUrl)) {
            $loadBalancedUri = New-Object System.Uri $config.LoadBalancedUrl
            $port = $loadBalancedUri.Port
            $loadBalancedUrl = "$($loadBalancedUri.Scheme)://$($loadBalancedUri.Host)/"
        } else {
            if (![string]::IsNullOrEmpty($config.HostHeader)) {
                $loadBalancedUrl = "http://$($config.HostHeader)"
            }
        }
        if ([string]::IsNullOrEmpty($port)) { $port = "80" }

        if ($config.AuthenticationMode -eq "Claims") {
            $authProviders = @()
            $enableWindowsAuthentication = $false
            if (![string]::IsNullOrEmpty($config.EnableWindowsAuthentication)) { $enableWindowsAuthentication = [bool]::Parse($config.EnableWindowsAuthentication) }
            $enableFormsBasedAuthentication = $false
            if (![string]::IsNullOrEmpty($config.EnableFormsBasedAuthentication)) { $enableFormsBasedAuthentication = [bool]::Parse($config.EnableFormsBasedAuthentication) }

            if ($enableWindowsAuthentication) {
                $enableBasicAuthentication = $false
                if (![string]::IsNullOrEmpty($config.EnableBasicAuthentication)) { $enableBasicAuthentication = [bool]::Parse($config.EnableBasicAuthentication) }
                $disableKerberos = $config.AuthenticationMethod -eq "NTLM"
                $authProviders += New-SPAuthenticationProvider `
                    -UseWindowsIntegratedAuthentication:$enableWindowsAuthentication `
                    -DisableKerberos:$disableKerberos `
                    -UseBasicAuthentication:$enableBasicAuthentication `
                    -AllowAnonymous:$allowAnon
            }
            if ($enableFormsBasedAuthentication) {
                $authProviders += New-SPAuthenticationProvider `
                    -ASPNETMembershipProvider $config.ASPNETMembershipProviderName `
                    -ASPNETRoleProviderName $config.ASPNETRoleManagerName 
            }
            Write-Host "Creating Web Application: `"$($config.Name)`""
            $webApp = New-SPWebApplication -SecureSocketsLayer:$ssl `
                -AllowAnonymousAccess:$allowAnon `
                -ApplicationPool $poolName `
                -ApplicationPoolAccount $poolAcct `
                -Name $config.Name `
                -AuthenticationProvider $authProviders `
                -DatabaseServer $db.Server `
                -DatabaseName $db.Name `
                -HostHeader $config.HostHeader `
                -Path $config.Path `
                -Port $port `
                -Url $loadBalancedUrl
        } else {
            $authMethod = "NTLM"
            if (![string]::IsNullOrEmpty($config.AuthenticationMethod)) { $authMethod = $config.AuthenticationMethod }

            Write-Host "Creating Web Application: `"$($config.Name)`""
            $webApp = New-SPWebApplication -SecureSocketsLayer:$ssl `
                -AllowAnonymousAccess:$allowAnon `
                -ApplicationPool $poolName `
                -ApplicationPoolAccount $poolAcct `
                -Name $config.Name `
                -AuthenticationMethod $authMethod `
                -DatabaseServer $db.Server `
                -DatabaseName $db.Name `
                -HostHeader $config.HostHeader `
                -Path $config.Path `
                -Port $port `
                -Url $loadBalancedUrl
        }

        Set-SPWebApplication -Identity $webApp -DefaultTimeZone $config.DefaultTimeZone

        #Re-get the web app to avoid update conflicts
        $webApp = Get-SPWebApplication -Identity  $webApp
        $webApp.SelfServiceSiteCreationEnabled = [bool]::Parse($config.SelfServiceSiteCreation)
        $webApp.RequireContactForSelfServiceSiteCreation = [bool]::Parse($config.RequireContactForSsc)
        if ($config.ProxyGroup -ne $null -and ![string]::IsNullOrEmpty($config.ProxyGroup.Name)) {
            $proxyGroupName = $config.ProxyGroup.Name
            if ($proxyGroupName -ne $null) {
                $webApp.ServiceApplicationProxyGroup = Get-ProxyGroup $proxyGroupName $true
            }
        }
        $webApp.Update()
        Write-Host """$($config.Name)"" successfully created."
    } else {
        #We got the web app so return it and don't attempt to recreate
        Write-Host """$($config.Name)"" already exists, skipping creation."
    }
    $setObjectCacheAccounts = $true
    if (![string]::IsNullOrEmpty($config.PortalSuperUserAccount) -and ![string]::IsNullOrEmpty($config.PortalSuperReaderAccount)) {
        if ($config.PortalSuperUserAccount.AccountName -eq $config.PortalSuperReaderAccount.AccountName) {
            Write-Warning "The Portal Super User Account and Portal Super Reader Account must not be the same account as this will result in a security hole. The accounts will not be set."
            $setObjectCacheAccounts = $false
        }
    }
    $claimsPrefix = ""
    if ($config.AuthenticationMode -eq "Claims") {
        $claimsPrefix = "i:0#.w|"
    }
    if (![string]::IsNullOrEmpty($config.PortalSuperUserAccount.AccountName) -and $setObjectCacheAccounts) {
        $webApp.Properties["portalsuperuseraccount"] = "$claimsPrefix$($config.PortalSuperUserAccount.AccountName)"
        Set-WebAppUserPolicy $webApp "$claimsPrefix$($config.PortalSuperUserAccount.AccountName)" $config.PortalSuperUserAccount.AccountName "Full Control"
    }
    if (![string]::IsNullOrEmpty($config.PortalSuperReaderAccount.AccountName) -and $setObjectCacheAccounts) {
        $webApp.Properties["portalsuperreaderaccount"] = "$claimsPrefix$($config.PortalSuperReaderAccount.AccountName)"
        Set-WebAppUserPolicy $webApp "$claimsPrefix$($config.PortalSuperReaderAccount.AccountName)" $config.PortalSuperReaderAccount.AccountName "Full Read"
    }


    if ($config.UserPolicies) {
        Write-Host "Creating user policies..."
        [bool]$updateRequired = $false
        foreach ($userPolicyConfig in $config.UserPolicies.UserPolicy) {
            if ($userPolicyConfig -eq $null) { continue }

            [string]$zoneName = $userPolicyConfig.Zone
            [Microsoft.SharePoint.Administration.SPPolicyCollection]$policies = $webApp.Policies
            if ($zoneName.ToLower() -ne "all") {
                $policies = $webApp.ZonePolicies($zoneName)
            }
            Write-Host "Adding user policy for: $claimsPrefix$($userPolicyConfig.UserLogin)..."
            [Microsoft.SharePoint.Administration.SPPolicy]$policy = $policies.Add("$claimsPrefix$($userPolicyConfig.UserLogin)", $userPolicyConfig.UserDisplayName)
            foreach ($permConfig in $userPolicyConfig.Permission) {
                [Microsoft.SharePoint.Administration.SPPolicyRole]$policyRole = $webApp.PolicyRoles | where {$_.Name -eq $permConfig.Name}
                if ($policyRole -ne $null) {
                    Write-Host "Adding policy role: $($permConfig.Name)..."
                    $policy.PolicyRoleBindings.Add($policyRole)
                }
            }
            $updateRequired = $true
        }
        if ($updateRequired) {
            $webApp.Update()
        }
    }
    
    
    if ($config.ManagedPaths) {
        Write-Host "Creating managed paths..."
        foreach ($mpConfig in $config.ManagedPaths.ManagedPath) {
            if ($mpConfig -eq $null) { continue }

            $path = Get-SPManagedPath -Identity $mpConfig.RelativeURL `
                -WebApplication $webApp -ErrorAction SilentlyContinue
            if ($path -eq $null) {
                Write-Host "Creating ""$($mpConfig.RelativeURL)""..."
                New-SPManagedPath -RelativeURL $mpConfig.RelativeURL `
                    -Explicit :( [bool]::Parse($mpConfig.Explicit)) -WebApplication $webApp
            } else {
                Write-Host "Managed path already exists: ""$($mpConfig.RelativeURL)""."
            }
        }
    }
    
    if ($config.SPDesigner) {
        Write-Host "Configuring SharePoint Designer Settings..."
        sleep 5
        #Get the web app again to avoid an update conflict
        $webApp = Get-SPWebApplication $webApp
        $webApp | Set-SPDesignerSettings `
            -AllowDesigner ([bool]::Parse($config.SPDesigner.AllowDesigner)) `
            -AllowRevertFromTemplate ([bool]::Parse($config.SPDesigner.AllowRevertFromTemplate)) `
            -AllowMasterPageEditing ([bool]::Parse($config.SPDesigner.AllowMasterPageEditing)) `
            -ShowURLStructure ([bool]::Parse($config.SPDesigner.ShowURLStructure))
        $webApp.Update()
    }

    foreach ($dbConfig in $config.ContentDatabases.ContentDatabase) {
        $db = Get-SPContentDatabase $dbConfig.Name -ErrorAction SilentlyContinue
        if ($db -eq $null) { 
            $db = New-SPContentDatabase -Name $dbConfig.Name `
                -WebApplication $webApp `
                -DatabaseServer $dbConfig.Server `
                -MaxSiteCount $dbConfig.MaxSiteCount `
                -WarningSiteCount $dbConfig.WarningSiteCount
        }
        $failoverDbServer = $dbConfig.FailoverDatabaseServer
        if (![string]::IsNullOrEmpty($failoverDbServer)) {
            if (($db.FailoverServiceInstance -eq $null) -or ![string]::Equals($db.FailoverServiceInstance.NormalizedDataSource, $failoverDbServer, [StringComparison]::OrdinalIgnoreCase)) {
                try {
                    Write-Host "Adding failover database instance..."
                    $db.AddFailoverServiceInstance($failoverDbServer)
                    $db.Update()
                } catch {
                    Write-Warning "Unable to set failover database server. $_"
                }
            }
        }
        foreach ($siteConfig in $dbConfig.SiteCollections.SiteCollection) {
            $site = Get-SPSite $siteConfig.Url -ErrorAction SilentlyContinue
            if ($site -ne $null) {
                Write-Host "Site Collection $($siteConfig.Url) already exists. Skipping."
                $site.Dispose()
                continue
            }
            
            $lcid = $siteConfig.LCID
            if ([string]::IsNullOrEmpty($lcid)) { $lcid = [Microsoft.SharePoint.SPRegionalSettings]::GlobalServerLanguage.LCID }

            $optionalParams = @{}
            if (![string]::IsNullOrEmpty($siteConfig.SecondaryLogin)) { 
                $optionalParams += @{"SecondaryOwnerAlias"=$siteConfig.SecondaryLogin} }
            if (![string]::IsNullOrEmpty($siteConfig.SecondaryEmail)) { 
                $optionalParams += @{"SecondaryEmail"=$siteConfig.SecondaryEmail} }
            if (![string]::IsNullOrEmpty($siteConfig.OwnerEmail)) { 
                $optionalParams += @{"OwnerEmail"=$siteConfig.OwnerEmail} }

            Write-Host "Creating Site Collection $($siteConfig.Url)..."
            $site = New-SPSite `
                -Url $siteConfig.Url `
                -Description $siteConfig.Description `
                -Language $lcid `
                -Name $siteConfig.Name `
                -OwnerAlias $siteConfig.OwnerLogin `
                -Template $siteConfig.Template `
                -ContentDatabase $db `
                -ErrorAction Continue @optionalParams

            if ($site -eq $null) { 
                Write-Warning "Site collection was not created!"
            } else {
                Write-Host "Site collection successfully created: $($siteConfig.Url)"
                Write-Host "Setting default associated security groups..."
                sleep 5
                $refreshedSite = $site | Get-SPSite
                $secondaryLogin = $siteConfig.SecondaryLogin
                if (![string]::IsNullOrEmpty($secondaryLogin)) { $secondaryLogin = "$claimsPrefix$secondaryLogin" }
                $refreshedSite.RootWeb.CreateDefaultAssociatedGroups("$claimsPrefix$($siteConfig.OwnerLogin)", $secondaryLogin, $siteConfig.Name)
                $refreshedSite.Dispose()
                $site.Dispose()
            }
        }
    }
}

function Get-ProxyGroup([string]$name, [bool]$createIfMissing = $true) {
    if ($name -eq "Default" -or [string]::IsNullOrEmpty($name)) { return [Microsoft.SharePoint.Administration.SPServiceApplicationProxyGroup]::Default }
    
    $proxyGroup = Get-SPServiceApplicationProxyGroup $name -ErrorAction SilentlyContinue -ErrorVariable err
    if ($err -and !$createIfMissing) { throw $err }
    if ($proxyGroup -eq $null) {
        $proxyGroup = New-SPServiceApplicationProxyGroup -Name $name
    }
    return $proxyGroup
}

function Set-WebAppUserPolicy($webApp, $userName, $userDisplayName, $perm) {
    [Microsoft.SharePoint.Administration.SPPolicyCollection]$policies = $webApp.Policies
    [Microsoft.SharePoint.Administration.SPPolicy]$policy = $policies.Add($userName, $userDisplayName)
    [Microsoft.SharePoint.Administration.SPPolicyRole]$policyRole = $webApp.PolicyRoles | where {$_.Name -eq $perm}
    if ($policyRole -ne $null) {
        $policy.PolicyRoleBindings.Add($policyRole)
    }
    $webApp.Update()
}

 

I think one of the more interesting things to point out about this script is that if you’re provisioning a Web Application for Claims versus Classic mode there are differences in how the New-SPWebApplication cmdlet must be called and, when using Claims, there’s more work that needs to be done to make sure the authentication providers are set properly. Outside of that I really think the script is pretty straightforward, if not a bit lengthy. One little hack/cheat that I wanted to point out is my use of the claims prefix, "i:0#.w|" – most of the time my little hack to get this prefix will be more than sufficient but technically, if I wanted my script to be truly generic, I should have done this to get the encoded username (as opposed to just assuming the prefix value and prepending it to the username):

$claim = [Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager]::CreateUserClaim("isclondon\spcacheuser", "Windows")
[Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager]::Local.EncodeClaim($claim)

SecureStoreServices.ps1

The last script that I walked through during the sessions was the SecureStoreServices.ps1 script. (It would have obviously been impossible to walk through all the Service Application related scripts during a single 1 hour presentation so the presentation itself focused on the general patterns, and lack thereof, and we then walked through this one script as a simple example which ties it all together).

Like all the other scripts this one was driven by another XML file:

<Farm>
    <Services>
        <SecureStoreServices>
          <Servers>
            <Server Name="SP03" />
            <Server Name="SP04" />
          </Servers>
          <SecureStoreServiceApplications>
            <SecureStoreServiceApplication 
                Name="ISC Secure Store Service" 
                DatabaseName="ISC_SecureStore" 
                DatabaseServer="ISCSharePoint1" 
                FailoverDatabaseServer=""
                AuditingEnabled="true" 
                AuditLogMaxSize="30" 
                Sharing="false" 
                KeyPassphrase="p@ssw0rd" 
                Partitioned="false">
              <ApplicationPool Name="SharePoint Services App Pool" 
                      AccountName="isclondon\SPServices" 
                    AccountPassword="password" />
              <Proxy Name="ISC Secure Store Service">
                <ProxyGroup Name="Default" />
              </Proxy>
            </SecureStoreServiceApplication>
          </SecureStoreServiceApplications>
        </SecureStoreServices>    
    </Services>
</Farm>

Hopefully you’ve picked up on the fact that the XML node structure is such that you could easily stitch all the XML files together into a single XML file which would be easier to maintain (think search and replace); I’ve intentionally structured my scripts and XML files for this conference so that they are all essentially standalone which made walking through the code in a presentation much easier as there was a lot less context switching. For my personal scripts I use a series of shared library scripts where I’ve put common functions and all my XML is contained in one XML file (which I manage using a custom WPF application that I’ve developed).

Time for the actual script:

function Start-SecureStoreServices {
    [CmdletBinding()]
    param 
    (  
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$SettingsFile
    )
    
    [xml]$config = Get-Content $settingsFile

    $install = (($config.Farm.Services.SecureStoreServices.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
    if (!$install) { 
        Write-Host "Machine not specified in Servers element, service will not be installed on this server."
        return
    }
    
    $svc = Get-SPServiceInstance -Server $env:computername | where {$_.TypeName -eq "Secure Store Service"}
    if ($svc -eq $null) {
        throw "Unable to retrieve Service Instance."
    }
    if ($svc.Status -ne "Online") {
        Write-Host "Starting service instance..."
        $svc | Start-SPServiceInstance
    
        #Make sure the service is online before attempting to add a svc app.
        while ($true) {
            Start-Sleep 2
            $svc = Get-SPServiceInstance -Server $env:computername | where {$_.TypeName -eq "Secure Store Service"}
            if ($svc.Status -eq "Online") { break }
        }
    }


    foreach ($appConfig in $config.Farm.Services.SecureStoreServices.SecureStoreServiceApplications.SecureStoreServiceApplication) {
        $app = Get-SPServiceApplication | where {$_.Name -eq $appConfig.Name}
        $updateKey = $false
        if ($app -eq $null) {
            $poolName = $appConfig.ApplicationPool.Name
            $identity = $appConfig.ApplicationPool.AccountName
            $password = $appConfig.ApplicationPool.AccountPassword
            $pool = Get-ServicePool $poolName $identity $password

            Write-Host "Creating secure store service application..."
            $app = New-SPSecureStoreServiceApplication -Name $appConfig.Name `
                -ApplicationPool $pool `
                -DatabaseServer $appConfig.DatabaseServer `
                -DatabaseName $appConfig.DatabaseName `
                -AuditingEnabled :( [bool]::Parse($appConfig.AuditingEnabled)) `
                -AuditLogMaxSize $appConfig.AuditLogMaxSize `
                -FailoverDatabaseServer $appConfig.FailoverDatabaseServer `
                -PartitionMode:([bool]::Parse($appConfig.Partitioned)) `
                -Sharing:([bool]::Parse($appConfig.Sharing))
            $updateKey = $true
        } else {
            Write-Host "Secure store service application already exists, skipping creation."
        }

        $proxy = Get-SPServiceApplicationProxy | where {$_.Name -eq $appConfig.Proxy.Name}
        if ($proxy -eq $null) {
            Write-Host "Creating secure store service application proxy..."
            $proxy = New-SPSecureStoreServiceApplicationProxy `
                -Name $appConfig.Proxy.Name `
                -ServiceApplication $app `
                -DefaultProxyGroup:$false
        } else {
            Write-Host "Secure store service application proxy already exists, skipping creation."
        }
        $proxy | Set-ProxyGroupsMembership $appConfig.Proxy.ProxyGroup

        
        if ($updateKey) {
            Update-SPSecureStoreMasterKey -ServiceApplicationProxy $proxy -Passphrase $appConfig.KeyPassphrase
            while ($true) {
                try {
                    Start-Sleep -Seconds 5
                    Update-SPSecureStoreApplicationServerKey -ServiceApplicationProxy $proxy -Passphrase $appConfig.KeyPassphrase
                    break
                } catch { }
            }
        }
        
    }
}



function Get-ServicePool($poolName, $identity, $password) {
    $pool = Get-SPServiceApplicationPool $poolName -ErrorAction SilentlyContinue
    if ($pool -eq $null) {
        if ($identity -ne $null) {
            $acct = Get-SPManagedAccount $identity -ErrorAction SilentlyContinue
        }
        if ($acct -eq $null) {
            if ([string]::IsNullOrEmpty($password)) {
                $cred = Get-Credential $identity
            } else {
                $cred = New-Object System.Management.Automation.PSCredential $identity, (ConvertTo-SecureString $password -AsPlainText -force)
            }
            $acct = Get-SPManagedAccount $cred.UserName -ErrorAction SilentlyContinue
            if ($acct -eq $null) {
                $acct = New-SPManagedAccount $cred
            }
        }
        Write-Host "Creating application pool..."
        $pool = New-SPServiceApplicationPool  -Name $poolName -Account $acct
    }
    return $pool
}

function Set-ProxyGroupsMembership([System.Xml.XmlElement[]]$groups, [Microsoft.SharePoint.Administration.SPServiceApplicationProxy[]]$InputObject) {
    begin {}
    process {
        if ($_ -eq $null -and $InputObject -ne $null) {
            $InputObject | Set-ProxyGroupsMembership $groups
            return
        }
        $proxy = $_
        
        #Clear any existing proxy group assignments
        Get-SPServiceApplicationProxyGroup | where {$_.Proxies -contains $proxy} | ForEach-Object {
            $proxyGroupName = $_.Name
            if ([string]::IsNullOrEmpty($proxyGroupName)) { $proxyGroupName = "Default" }
            $group = $null
            [bool]$matchFound = $false
            foreach ($g in $groups) {
                $group = $g.Name
                if ($group -eq $proxyGroupName) { 
                    $matchFound = $true
                    break 
                }
            }
            if (!$matchFound) {
                Write-Host "Removing ""$($proxy.DisplayName)"" from ""$proxyGroupName"""
                $_ | Remove-SPServiceApplicationProxyGroupMember -Member $proxy -Confirm:$false -ErrorAction SilentlyContinue
            }
        }
        
        foreach ($g in $groups) {
            $group = $g.Name

            $pg = $null
            if ($group -eq "Default" -or [string]::IsNullOrEmpty($group)) {
                $pg = [Microsoft.SharePoint.Administration.SPServiceApplicationProxyGroup]::Default
            } else {
                $pg = Get-SPServiceApplicationProxyGroup $group -ErrorAction SilentlyContinue -ErrorVariable err
                if ($pg -eq $null) {
                    $pg = New-SPServiceApplicationProxyGroup -Name $name
                }
            }
            
            $pg = $pg | where {$_.Proxies -notcontains $proxy}
            if ($pg -ne $null) { 
                Write-Host "Adding ""$($proxy.DisplayName)"" to ""$($pg.DisplayName)"""
                $pg | Add-SPServiceApplicationProxyGroupMember -Member $proxy 
            }
        }
    }
    end {}
}

 

Again, I’m not going to explain what’s going on in this script – my book covers it in detail.

Final Thoughts

So on that final note about my book I’d like to reiterate that everything I demoed during the conference and virtually everything that my scripts did to provision the conference Farm is detailed in great depth in my Automating Microsoft SharePoint 2010 Administration with Windows PowerShell 2.0 book (I don’t cover the Office Web Applications in the book). Not only that, but most of the scripts are also available for download as part of the book. Are all of them available? No. Why? Because I’m a self employed consultant who needs to be able to provide for my family and if I give away everything I ever produce then I’ll slowly be putting myself out of a job and my daughter can forget about going to college so please, please, please do not ask me to post the full scripts because it’s simply not going to happen – if you want the full scripts then hire me to build your Farm or talk to me about purchasing my SharePoint 2010 Farm Builder application. (I know during the conference people were tweeting and blogging that I would be providing all my scripts – I never said I would do this – I simply said that I would provide the scripts which I walked through during the session and this is what I have done in this post).

To those that attended my sessions I truly hope you got some value out of the content that Spence, Chan and I provided and I’d like to thank everyone (speakers, organizers, and attendees) for such a wonderful overall event and for making me feel so welcome so far from home. And of course a special thanks to Spence and Chan for all they did to help pull these sessions off.

-Gary

19Nov/110

SharePoint Saturday Denver 2011 Slide Decks

Last weekend I had a great time at SharePoint Friday/Saturday Denver 2011 – all the folks who helped organize the event did a fantastic job (and I have to say, it was nice this year not being one of the organizers as I was able to just enjoy the event for once which makes me appreciate their efforts even more). So, at the event I presented two talks, one on PowerShell (of course) and the other was the presentation I gave at the SharePoint Conference in Anaheim last month. The PowerShell talk was a new talk that I’d never done before and it was basically just a bunch of random tips that I’ve found useful over the years. My apologies to those who attended my sessions last weekend for not making these available sooner but I figure better late than never. Anyways, here’s the links to the decks – enjoy!

17Apr/111

European SharePoint Best Practices Conference Wrap Up

I just got back from London and all I can say is, “Wow!” This was the first time that myself and my wife and daughter have ever been out of the US and we had an absolute blast – we did so much walking that we literally wore our shoes thin – it’s truly an amazing place with incredible history everywhere you turn.

As for the conference itself, first off I want to thank Steve Smith and all those that were involved with organizing such a wonderful conference – as a speaker they definitely set the bar for other conference organizers and I hope that attendees saw the same attention to detail and overall quality that the speakers saw (this conference is truly unique amongst all the ones I’ve spoken at).

In regards to my two sessions – I totally blew my timing on both of them (more so on the developer one) and was unable to show everything that I planned but I do hope that those that attended got some useful information for the time spent (virtually nobody left when I ran late so I’m going to take that as a good sign that people were getting value out of the presentations). I plan to post some of the sample scripts that I demonstrated during the presentations but that will come over the next few weeks; for now I’ve posted my slide decks (saved with notes so you can see some of my examples) for you to download:

Windows PowerShell for SharePoint 2010 Administrators Windows PowerShell for SharePoint 2010 Administrators
Windows PowerShell for SharePoint 2010 Developers Windows PowerShell for SharePoint 2010 Developers

Don’t forget that you can also download the PowerShell cheat sheet that I provided during the sessions (see my earlier post).

Thanks again to everyone that attended my sessions and for all the great tweets that came out during and after the sessions – I know I still have lots to learn when it comes to public speaking so any kind of feedback is very much appreciated!

8Nov/103

November 2010 SharePoint Connections Decks and Code

The past few weeks have been very busy for me as I’ve just finished presenting at two different conferences, and as I don’t typically present at conferences this was kind of a big deal for me. You can get my slides for SPTechCon, the conference I spoke at in late October from my earlier post, and if you’re looking for the slides and code from SharePoint Connections, which I just returned from you can find them below. I know I promised several folks that I’d have these available Friday but I decided to take some long overdue family time instead.

November 2010 SharePoint Connections Slide Decks:

November 2010 SharePoint Connections Demo Code:

Now that I’m done with conferences for the year I guess it’s time to get back to writing my book :)

-Gary

22Oct/100

SPTechCon Decks

I just wrapped up a couple of quick days at SPTechCon in Boston and had a great time! I don’t make it to too many conferences so it was great to see old friends again and of course I loved visiting Boston as once upon a time I used to live not too far from Boston and worked in the heart of the Boston financial district for quite some time.

While at the conference I presented two talks – as promised you can download them below:

In a couple of weeks I’ll be at SharePoint Connections in Las Vegas – if you’re there please stop by one of my sessions and say hi!

-Gary

Tagged as: No Comments
27Mar/100

Webinars, Pod Shows, and User Group Meetings

I don't usually do a lot of speaking or presenting but recently a lot of opportunities have come my way so if you're interested in hearing me talk about SharePoint check out any of the following upcoming events:

SharePoint 2010 Readiness Webinar - 3/30/2010

This coming Tuesday, March 30th, I'll be co-presenting a webinar with Paul Stork, Fellow MVP and ShareSquared employee. We've got close to 1700 people registered for this webinar (yup, I'm freaking out just a bit)! You can get more details and register for the webinar here: http://www.sharesquared.com/resources/Pages/SharePoint2010ReadinessWebinar.aspx.

PowerScripting Podcast - 4/1/2010

Next Thursday, April 1st, I'll be interviewed by the guys at the PowerScripting Podcast. This will be a LIVE video streamed interview! Not sure how I feel about that :) The interview, which will focus on PowerShell for SharePoint 2010, will be at 9:30PM EDT. More information can be found here: http://powerscripting.wordpress.com/.

UPDATE: See the recorded show here http://www.ustream.tv/recorded/5889732

SharePoint Pod Show

So this isn't an "upcoming" event but one that I did back in February. I spent a few minutes with Rob Foster and did a quick interview for the SharePoint Pod Show. That interview recently went public - came out better than I thought it would :) You can download the interview from the Pod Show web site: http://www.sharepointpodshow.com/archive/2010/03/18/sharepoint-2010-and-powershell-episode-44.aspx

Colorado Springs SharePoint User Group Meeting - 4/14/2010

I'll be presenting at the Colorado Springs SharePoint User Group Meeting in April. The topic will be on upgrading from SharePoint 2007 to SharePoint 2010. I'll be covering a lot of the stuff that I go over during the Readiness Webinar but in much more depth. More information can be found on the COSPUG site: http://www.cospug.com/

SharePoint Saturday Atlanta - 4/17/2010

I'll be presenting at SharePoint Saturday Atlanta on April 17th, 2010. I'll be doing my usual PowerShell for SharePoint 2010 presentation so if you've missed it at any previous events I've done please try to drop in - lots of advanced PowerShell goodness :) You can get more information at the SPS site: http://www.sharepointsaturday.org/atlanta/

Microsoft 2010 Launch Event in Denver, CO - 4/20/2010

Microsoft is hosting a series of launch events for the 2010 suite of products, including SharePoint 2010. The Denver event will be at the Hyatt in the Denver Tech Center (7800 East Tufts Ave) on April 20th. I'll be there to demonstrate and answer questions about SharePoint Composer, a new product that I've been developing to help IT Administrators and Architects design and deploy their SharePoint 2007 and 2010 environment.

The Experts Conference 2010 - 4/25/2020 - 4/28/2010

As mentioned in my previous post, I'll be speaking at the SharePoint Conference at The Experts Conference (TEC) 2010. I'll be doing a white boarding session with Michael Noel and I'll be presenting on PowerShell with SharePoint 2010. To learn more about the conference see my previous post. Be sure to register using my unique registration code LAPSPMVP for a discount of $500!

While I'm at the TEC 2010 Conference I may drop by the LA SharePoint User Group Meeting (LASPUG) - so if you can't make it to the conference please drop by the user group meeting as there's a real good chance I'll be there :) You can get more information about LASPUG here: http://laspug.org/.

With the launch of SharePoint 2010 on May 12th, 2010 there's obviously a lot going on. Also, keep an eye out for the 2010 version of my extensions which I plan to release within a week or two of RTM - this release will not have every STSADM command migrated but will contain the more popular ones, all converted to PowerShell.

Thanks again to everyone for all the great feedback I've gotten through my blog - hopefully I'll be able to see some of you at one of these events!

Tagged as: No Comments
25Feb/100

I’m speaking at TEC 2010 – See you there!

I'm happy to announce that I will be speaking at the SharePoint Conference at The Experts Conference TEC 2010.

Dates: April 25-28

Location: JW Marriott Hotel Los Angeles at L.A. Live

Details: SharePoint The Experts Conference

Keynotes: Bill Baer SharePoint Team & MCM Instructor, Joel Oleson MCTS, Michael Noel MVP, Ben Curry MVP, Mike Watson former MCM Instructor

Featured Speakers: Gary Lapointe MVP, Dan Lewis, Zlatan Dzinic MVP, Rick Taylor MCT, Ram Gopinathan Microsoft MCM

Top Community Speakers and Leads: Dan Lewis, Paul Swider, Eric Harlan Microsoft PFE, Becky Isserman, Paul Swider, Philip Wicklund, Karuana

Top Analysts and Authors: Craig Roth, Susan Hanley, Christian Buckley

Community Virtual Event with End User SharePoint’s Mark Miller and Social Media guru Mike Gannotti!

This is the first ever SharePoint training curriculum for TEC and includes industry recognized speakers and trainers from across the globe providing deep technical sessions to include, but not limited to upgrade and migration, social computing, enterprise and web content management, development, security, and authentication  See the SharePoint Tracks session line up for more details.    To learn more about SharePoint training at TEC2010 visit or for additional information on registration.  I’m happy to announce as a speaker I can offer my readers a huge discount of $500 off the price at $1345. Simply add the code LAPSPMVP to get the discount.

Discount Code: LAPSPMVP

This Conference is going to get you closer to the SharePoint IT experts in the field than any other with a ratio that will impress you.  Not only is it 3 days of packed training with more than 3 tracks of 300-400 level SharePoint content, we have a special track dedicated to interactive Architecture and design sessions to help you get deeper with a NO PPT allowed track where SharePoint Architects get into whiteboard design mode with solution requirements driven by the attendees.  In addition there’s a preconference workshop day where you can get your hands dirty with SharePoint 2010 code and solutions management with top MCTs and MCMs.

Free Pass Contest: If you’re having an issue getting the funds, why not join in the contest to win a free pass?  To enter, simply post a blog explaining why you want to attend TEC 2010. Add a link and summary in the comments on the Official Free TEC 2010 Pass Contest post here.

New Social Networking Groups to get to know other attendees and speakers:

TEC2010 on twitter: @TECconf and #TEC2010 - http://twitter.com/TECconf

Join The Experts Community http://theexpertscommunity.com/

Join Facebook group http://www.facebook.com/group.php?gid=43761735876&ref=ts#/group.php?gid=43761735876

Join LinkedIn group http://www.linkedin.com/groups?gid=2381884&trk=myg_ugrp_ovr

19Dec/090

SharePoint Saturday Denver 2009 Slide Deck

I recently presented at the SharePoint Saturday Denver event where I, rather rapidly, went through some SharePoint 2010 PowerShell stuff - the presentation was not meant as a general PowerShell overview but instead assumed that you already had at least a basic knowledge of PowerShell in general so that we could focus on SharePoint specific things. In the presentation I went over how to find the SharePoint cmdlets, threading and disposal issues, usage scenarios, remoting, and building custom cmdlets. There was a ton of information but we managed to get through all of it and only ran about 5 minutes over.

If you're interested in viewing the slide deck you can download it here: PowerShell_with_SharePoint_2010.pptx

I've embedded the demo script that I used during the talk as well. If you look in the notes on the first slide and on the remoting slide you'll see the PowerShell that I went through during the demo. The farm creation and site structure creation scripts can be found in my previous posts.

For those that attended and are wondering how I got the demos to work without having to type check out this post on the PowerShell team blog: http://blogs.msdn.com/powershell/archive/2007/06/03/new-and-improved-start-demo.aspx.