Now contributing to the Blue Chip Blog!


As you may know, I work for Blue Chip Consulting Group – a Microsoft Managed Partner based out of Cleveland, Ohio. The Blue Chip website has been updated quite a bit over the last few months, and we’ve now launched the Blue Chip Blog as well. My first post on the Blue Chip blog site is now live, please feel free to check it out!

In the post, titled Automate Your Way to SharePoint Online Using Windows PowerShell, I discuss the history of PowerShell as it relates to SharePoint Server, and now SharePoint Online. I also include some details on a Blue Chip-developed tool, called BluePrint, which helps to automate SharePoint Online deployments by providing additional PowerShell Cmdlets beyond the native SPO cmdlets.

Happy Scripting!

DogFoodCon 2014 to feature dedicated PowerShell track!


For several years now, the IT conference slate in Central Ohio has been dominated by the ever popular Dog Food Conference. That trend will continue this year, when Columbus once again hosts the 2-day Microsoft Dog Food Conference on September 29th and 30th. Multiple technologies will be featured, including Windows PowerShell.

I will be presenting on Managing SharePoint Anywhere using PowerShell, alongside these nationally recognized PowerShell Experts:

The event will certainly be phenomenal once again, and I hope to see you there!

cloud

 

Completely Delete SharePoint Online Sites using PowerShell


This will be a quick and dirty post. If you’ve administered SharePoint Online at all, you know that when you delete a Site Collection it goes into a Site Collection Recycle Bin. There are times when you may want to completely remove the SPOSite immediately, including removing it from the Recycle Bin. There are two commands to accomplish this:

  1. Remove-SPOSite
  2. Remove-SPODeletedSite

That’s great, and to delete the sites you just need to run them in that order. However, I’m sort of lazy at times and want to run something ONCE and have it do the work for me. Aren’t all scripters like this?

To make this functionality possible using one call, I wrote a function which I’ll share below.

Essentially it just takes one parameter for URL, and it uses SupportsShouldProcess and ConfirmImpact to prompt you for confirmation. Assuming you oblige, it will run the Remove-SPOSite first, with Confirmation suppressed (-Confirm:$false) and with no wait. After that completes, it will use Start-Sleep and a do/while loop to wait until the SPODeletedSite status is ‘Recycled’. Once that returns true, it will run Remove-SPODeletedSite to get rid of the recycle bin item.

Here’s the Delete-SPOSite function:

Function Delete-SPOSite {
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
Param(
[Parameter(Mandatory)][System.String]$Url
)
    If($PSCmdlet.ShouldProcess($Url))
    {
        Remove-SPOSite -Identity $Url -Confirm:$false -NoWait
        Write-Verbose "Waiting for site to be added to Recycle Bin"
        do {Start-Sleep -Seconds 3}
        while((Get-SPODeletedSite -Identity $Url).Status -ne 'Recycled')
        Remove-SPODeletedSite -Identity $Url -Confirm:$false
    }
}

Using PowerShell to manage SharePoint Online


Anybody who knows me or has ever read a single post of mine knows I’m a big PowerShell geek. I like it, I love it and I always want some more of it. Bad song lyrics aside, I quite literally use PowerShell every day for one task or another. Over the past 6 months or so I’ve been quietly working on creating some Cmdlets/Functions that will allow me to run PowerShell against Office 365 SharePoint Online. This might sound easy – but spoiler alert – it’s not. That’s not to say it’s difficult, if you’re a developer who is comfortable with CSOM, you’ll be just fine. However, I didn’t have those skills when I took off on this adventure; so needless to say it’s been a bit of a learning experience, combined with a lot of trial & error.

There are really a few key steps which must be taken prior to getting SharePoint Online and PowerShell to talk to one another.

  1. Set up the SharePoint Online Management Shell Windows PowerShell environment
  2. Run Connect-SPOService to connect to a SharePoint Online Administration Center

Once you’ve completed those two simple tasks, you’ve got the baseline environmental requirements in place to start using PowerShell with SharePoint Online. However, if I was writing a blogpost to tell you how to run the 30 built-in cmdlets you get by loading the Microsoft.Online.SharePoint.PowerShell module; well, you’d be left wanting more.

To really start doing the fun developer stuff, you will want to make sure you load the Microsoft.SharePoint.Client and Microsoft.SharePoint.Client.Runtime assemblies into your session. I’ve written a nice little function for this, which looks like:

function Add-SPOAssemblies {
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime") | Out-Null
}

Then, to run it – just use:

Add-SPOAssemblies

Pretty simple, and it just loads those two assemblies so you can access all the goodies that come with them.

Once you’ve gotten there, you can start to do things like create ClientContext against an SPWeb:

$webUrl = "SPOWebUrl"
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl)
$creds = Get-Credential -Message "Please enter your Office 365 Administrative credentials"
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
$spoweb = $ctx.Web
$ctx.Load($spoweb)
$ctx.ExecuteQuery()

So that’s cool, but it hasn’t done anything yet! Well try this:

$spoweb.Title

Ohhhh… So now that’s cool! Once you’ve gotten this far, i’m sure you can start to see the possibilities.

In case you want to wrap that up into a nice function, here’s a simple example that I think works well:

function New-SPOServiceContext {
[CmdletBinding()]
Param(
[System.String]$EmailAddress,
[System.String]$SPOWebUrl
)
try {
$assemblies=Add-SPOAssemblies
}
catch {
Write-Error "Unable to load Microsoft.SharePoint.Client assemblies"
}
if($assemblies){
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SPOWebUrl)
$creds = Get-Credential -Message "Please enter your Office 365 Administrative credentials" -UserName $EmailAddress
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
$spoweb = $ctx.Web
$ctx.Load($spoweb)
$ctx.ExecuteQuery()
}
}

And of course to run it, use:

New-SPOServiceContext -EmailAddress your.email@your-company.com -SPOWebUrl https://something.sharepoint.com

“But wait, at the beginning you said this was challenging – this was way too easy!?”

Well, I wasn’t used to having to create a context object, then a credential object, followed by a web variable, and then I have to load and execute a query?! All new to me, but again – I haven’t done very much (read: any) development on the client side.

I could share loads of other cool things I’ve done, but you’ll have to wait while I clean up and finish some of the functions I’ve been working on. Until then, happy PowerShelling!

Upgrade multiple SharePoint content databases using PowerShell and XML


As a SharePoint Consultant, a lot of the work I do is around upgrades and migrations. I’ve done quite a few of these, and I’ve done them from 2003 to 2007, 2007 to 2010, and now 2010 to 2013. I’ve done several migrations where a 3rd party tool was used to migrate the content, but a lot of the time I end up using the standard database attach approach to move the content databases from the old farm to the new farm. In doing so, obviously PowerShell is required – but more times than not there are multiple content databases. Manually testing and mounting each one can be extremely time consuming, especially if you’re going to end up deploying the content to multiple farms (Dev, QA, DR, Production). What I’ve decided to do is actually build some PowerShell and XML that can be used both to run Test-SPContentDatabase as well as Mount-SPContentDatabase to allow you to do these in bulk.

When writing this script, I had a few things in mind that I wanted to accomplish:

  1. Provide 100% of the input to the script in the form of XML, allowing the script to be 100% separate from the parameters and input.
  2. Provide options to use Test OR Mount, depending on where I’m at in the migration. (It’s always recommended to run Test-SPContentDatabase BEFORE Mount-SPContentDatabase!)
  3. Provide readable output and progress indication to know where the script is in it’s execution.

To accomplish these 3 goals, I made sure to:

  • Include ALL of the available options when running Test-SPContentDatabase AND Mount-SPContentDatabase in the XML and Script (empty values are simply ignored).
  • Include SWITCH parameters for Test and Mount – to allow the user to specify their intention at runtime.
  • Include Write-Progress and Write-Output to provide clear output to the executing user.

Now that I’ve explained my intentions with writing this code, I’ll share it!

Here is an example of how you could use the XML:

<?xml version="1.0" encoding="utf-8" ?>
<WebApps>
	<WebApp Url="http://portal.adventureworks.com">
		<Databases>
			<Database Name="SP2010_Pub_Content_1" WarningSiteCount="0" MaxSiteCount="1" AssignNewDatabaseId="false" ChangeSyncKnowledge="false" ClearChangeLog="false" DatabaseCredentials="" DatabaseServer="SPSQL\Publishing" NoB2BSiteUpgrade="false" SkipIntegrityChecks="false"/>
			<Database Name="SP2010_Pub_Content_2" WarningSiteCount="5" MaxSiteCount="10" AssignNewDatabaseId="false" ChangeSyncKnowledge="false" ClearChangeLog="false" DatabaseCredentials="" DatabaseServer="SPSQL\Publishing" NoB2BSiteUpgrade="false" SkipIntegrityChecks="false"/>
		</Databases>
	</WebApp>
	<WebApp Url="http://collab.adventureworks.com">
		<Databases>
			<Database Name="SP2010_Collab_Content_1" WarningSiteCount="25" MaxSiteCount="30" AssignNewDatabaseId="false" ChangeSyncKnowledge="false" ClearChangeLog="false" DatabaseCredentials="" DatabaseServer="SPSQL\Collab" NoB2BSiteUpgrade="false" SkipIntegrityChecks="false"/>
			<Database Name="SP2010_Collab_Content_2" WarningSiteCount="50" MaxSiteCount="100" AssignNewDatabaseId="false" ChangeSyncKnowledge="false" ClearChangeLog="false" DatabaseCredentials="" DatabaseServer="SPSQL\Collab" NoB2BSiteUpgrade="false" SkipIntegrityChecks="false"/>
		</Databases>
	</WebApp>
</WebApps>

Here is the PowerShell code:

<#
.Synopsis
    This script leverages an XML file as the input - and based on the XML contents will attach one or more 
	content databases to one or more SharePoint 2010/2013 Web Applications.
.Description
    This PowerShell Script uses Get-Content, ForEach-Object and Mount-SPContentDatabase cmdlets along with 
	some other various code snippets to iterate through one or more web applications.
    While attaching content databases to a SharePoint 2010/2013 farm, it will use Write-Progress to provide an update in the PowerShell window.
.Example
    Mount-SPContentDatabasesFromXml.ps1 -XmlInput C:\ContentDatabases.xml
   
    This example will mount all content databases to all web applications specified in an XML file at C:\ContentDatabases.xml.
   
    Example XML syntax:
    <?xml version="1.0" encoding="utf-8" ?>
    <WebApps>
        <WebApp Url="http://igs">
            <Databases>
                <Database Name="SP2010_Content_1" WarningLevel="5" MaxSites="10"/>
                <Database Name="SP2010_Content_2" WarningLevel="50" MaxSites="100"/>
            </Databases>
        </WebApp>
    </WebApps>
.Notes
    Name: Mount-SPContentDatabasesFromXml.ps1
    Author: Ryan Dennis
    Last Edit: 9/16/2013
    Keywords: Mount-SPContentDatabase, ForEach-Object, Get-Content, SharePoint Upgrade/Migration
.Link
    http://www.sharepointryan.com
    ryan@sharepointryan.com
    http://twitter.com/sharepointryan   
#Requires -Version 2.0
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)][System.String]$XmlInput,
[Parameter(Mandatory=$false,ParameterSetName="Test")][Switch]$Test,
[Parameter(Mandatory=$false,ParameterSetName="Mount")][Switch]$Mount
)
# Look for the SharePoint Snapin and add it if not already added #
if ((Get-PSSnapin Microsoft.SharePoint.PowerShell) -eq $null)
{
    Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
}
$FarmVersion = (Get-SPFarm).BuildVersion
if ($FarmVersion.Major -eq '15') {$FarmVersion = "2013"} 
elseif ($FarmVersion.Major -eq '14') {$FarmVersion = "2010"} 
else {$FarmVersion = ""}

# Load XML document #
[System.Xml.XmlDocument]$xml = Get-Content $($XmlInput)
if ($xml -eq $null)
{
    throw "XML input not found"
}

# Iterate through each WebApps.WebApp in the XML document and mount databases #
$i=0
$dbCount = $xml.GetElementsByTagName("Database").Count
$xml.WebApps.WebApp | ForEach-Object {
$waUrl = $_.Url

    $_.Databases.Database | ForEach-Object {
        $dbName = $_.Name
		if (!([string]::IsNullOrEmpty($_.WarningSiteCount)))
		{$paramWarningSites = @{WarningSiteCount = $_.WarningSiteCount}}
		else{$paramWarningSites = ""}
		
		if (!([string]::IsNullOrEmpty($_.MaxSiteCount)))
		{$paramMaxSites = @{MaxSiteCount = $_.MaxSiteCount}}
		else{$paramMaxSites = ""}
		
		if (!([string]::IsNullOrEmpty($_.AssignNewDatabaseId)))
		{$paramNewDbId = @{AssignNewDatabaseId = $true}}
		else{$paramNewDbId = ""}
		
		if (!([string]::IsNullOrEmpty($_.ChangeSyncKnowledge)))
		{$paramChangeSync = @{ChangeSyncKnowledge = $true}}
		else{$paramChangeSync = ""}
		
		if (!([string]::IsNullOrEmpty($_.ClearChangeLog)))
		{$paramClearLog = @{ClearChangeLog = $true}}
		else{$paramClearLog = ""}
		
		if (!([string]::IsNullOrEmpty($_.DatabaseCredentials)))
		{$paramDbCreds = (Get-Credential -Message "Enter Database Credentials")}
		else{$paramDbCreds = ""}
		
		if (!([string]::IsNullOrEmpty($_.DatabaseServer)))
		{
			if($Test){
				$paramDbServer = @{ServerInstance = $_.DatabaseServer}
			}
			if($Mount){
				$paramDbServer = @{DatabaseServer = $_.DatabaseServer}
			}
		}
		else{$paramDbServer = ""}
				
		if (!([string]::IsNullOrEmpty($_.NoB2BSiteUpgrade)))
		{$paramNoB2B = @{NoB2BSiteUpgrade = $true}}
		else{$paramNoB2B = ""}
		
		if (!([string]::IsNullOrEmpty($_.SkipIntegrityChecks)))
		{$paramSkipChecks = @{SkipIntegrityChecks = $true}}
		else{$paramSkipChecks = ""}
		
        try
        {
            $i++
            # If Mount switch param is included, mount the databases #
            if($Mount){
				Write-Progress -Activity "Mounting $dbName to web application $($waUrl)" -PercentComplete ($i/$dbCount*100) -Status "Mounting content databases to SharePoint $FarmVersion"
				Mount-SPContentDatabase -Name $dbName -WebApplication $waUrl @paramNewDbId @paramChangeSync @paramClearLog @paramDbCreds @paramDbServer @paramMaxSites @paramNoB2B @paramSkipChecks @paramWarningSites
        	}
			# If Test switch param is included, test the databases #
			if($Test){
				Write-Progress -Activity "Testing $dbName" -PercentComplete ($i/$dbCount*100) -Status "Testing content databases with SharePoint $FarmVersion"
				Write-Output "::Performing operation Test-SPContentDatabase on database: $dbName`n"
				Test-SPContentDatabase -Name $dbName -WebApplication $waUrl @paramDbCreds @paramDbServer
			}
		}
        catch
        {
            Write-Error $_
        }
    } #endDbForEach
} #endForEach