PowerShell – Friend to every MOSS developer


Recently I’ve made the effort to integrate PowerShell with MOSS in my development environment. I recommend that every one of you do the same 🙂

Why?

Because you get the ability to jump right into your local farm and use the object model directly – no need to write small programs, and jump into the debugger, just to inspect if an SPItem’s folder object is null or not (note: It will be null for non-folder type items). What that means is:

  • It’s scripting that understands .Net. If you’ve ever had to do “normal” scripting you will love this. Every .Net object is accessible and usually very fast to prototype against.
  • You can very easily explore your farms stranger aspects and change what you don’t like, i.e. did you know that a number of lists are hidden and that you can easily make them visible by just flicking the switch?
  • It is a super fast way to prototype and/or troubleshoot errors
  • What is the current query for a given view?
  • What does your content type really look like?
  • What is really stored in a given items metadata? Was it null or was it an empty string? How should a lookup value look?
  • As with all scripting you get pretty much unlimited power. You can chain operations as you like, as complex as needed. You can write small procedures that batch updates pretty much whatever you like – but please be careful with this…
  • Objects can be manipulated through chaining, combine that with filtering/selection, and you got something that pretty much resembles Linq for SharePoint.

Why not? Because it’s hard. The learning curve is rather steep at first, which is why I’m posting some of my learnings and tips here. It will be much slower the first few times and it will be frustrating at times.

I’m currently using PowerShell only in my various dev and test environments, I do not do any batch scripting in my production environments. I simply don’t feel that confident (“with great power comes great responsibility” 😉 ) and besides it shouldn’t be necessary. Pretty much the same argument that restricts me from just accessing the content database directly and update the faulty content type on a couple of thousand documents though I know I can do it. What could possibly go wrong?

Either pessimism or experience…

Now I’ll not claim to be an expert PowerShell user, very far from it, and I will not create a full PS primer and explain everything to you. When you hit a problem google it and the help will surprisingly often be right there. How do I select the items where the creator is from my local domain? I used to not know, now I know that there is a “where-object” statement that will help me.

I suggest that you follow my simple steps below to get a crack at accessing the SharePoint OM and then head off to some of the PowerShell references at the bottom that will explain all the things I neglected (or simply don’t know).

Anyway, let’s get started with the first baby steps in PowerShell and then move on to the few scripts I created to make my life that much easier.

Getting Started

Install the damn thing. It’s easy, it’s a small download and it’s unfortunately not a portable application (my main complaint). Install from here.

PowerShell scripts use the “.ps1” extension and be default PowerShell will not execute any script for you (what the hell?). To remedy that:

  1. Start powershell
  2. Type “Set-ExecutionPolicy RemoteSigned”

Now it will execute all local scripts and only remote scripts that have been signed. The long story is available by executing “get-help Set-ExecutionPolicy”.

Accessing MOSS

PowerShell knows .Net and as such is able to execute code to get you a SPFarm, SPSite or SPWeb in much the same way you do in C# (there are some syntax differences). The caveat here is that you’ll have to load/reference the SharePoint assembly into PowerShell first. It’s hard to remember how, but the correct statement is:

[Reflection.Assembly]::Load("Microsoft.SharePoint,
   Version=12.0.0.0,
   Culture=neutral,
   PublicKeyToken=71e9bce111e9429c")

The good news is that you don’t have to remember it, I’ll save it in the default profile so it’s always available, when I start PowerShell. Let’s also stuff a number of WSS specific methods in there while we’re at it.

What is the statement doing? It’s simply accessing the static Load method of the Reflection.Assembly object with the full assembly name of the Microsoft.SharePoint.dll, which is usually invoked for you by the .Net framework when you reference the assembly in your project. You can also use some of the other load methods, e.g. LoadWithPartialName, if you like that better.

Next I’ve stolen a script from Darrin Bishop that I heard speak at the SharePoint Conference in Seattle ’08. You really need to read his post that goes with the scripts as he explains a lot of powershell details that I’ve neglected:

[Reflection.Assembly]::Load("Microsoft.SharePoint,
     Version=12.0.0.0,
     Culture=neutral,
     PublicKeyToken=71e9bce111e9429c")
function Get-LocalFarm{
  return [Microsoft.SharePoint.Administration.SPFarm]::Local
}
filter Get-WebService{
   $webServices = new-object
           Microsoft.SharePoint.Administration.SPWebServiceCollection($_)
   $webServices
}
filter Get-WebApplication
   $_.WebApplications
}
filter Get-SiteCollection {
   $_.Sites
}
filter Get-Web {
   $_.AllWebs
}

These few, but rather obscure for the unenlightened, allows you to access all of the SharePoint object model in a somewhat convenient way. To use it you need to chain the methods (using the script pipe operator “|”), e.g. to get all the SPWebs in your site execute:

get-localfarm |get-webservice | get-webapplication | get-sitecollection | get-web

It will return an array of SPWeb objects and dump all their properties. It will be slow (but speed is hardly an issue here) and it will be very long. You can also filter it to get only the webs that has anonymous access enabled and format the result with a table with title, url and the site template used:

Get-localfarm |get-webservice | get-webapplication | get-sitecollection | get-web | Where-Object{$_.allowAnoymousAccess -eq $true} | format-table title, url, WebTemplate

Whew! That’s some PowerShell statement to just throw out. Key notes: 1) Use Format-table to save you to look through hundreds of lines and only show what you want to see, 2) Use Where-Object {} to query and use “$_” to access the current object in the script block. I haven’t listed any of the options where-object supports, but be careful not to use “=” for equality. In our case it would enable anonymous access on all webs. SharePoint fortunately (in this case) use the pattern of calling Update() when you need to commit changes, which you obviously should not call in this case. As I didn’t store the SPWebs I actually cannot even do that.

PowerShell 101

One of the best things to learn in PS is to use variables to save you from typing the same over and over all the time and to enable you to change properties through later Update() calls.

To store all the SPWebs from the previous example:

$webs = get-localfarm |get-webservice | get-webapplication | get-sitecollection | get-web

If the statement returns more than one SPWeb $webs will be an array. To get the first SPWeb type:

$webs[0]

To get a list of all methods and properties that the SPWeb supports, type:

$webs[0] | get-members

To iterate over each object in the pipeline use:

$webs | foreach-object { $_.Lists} | Format-table Name, Url

This will go through all the webs and all their lists and finally output the name and URLs in a simple table.

Combine the foreach statement with the where statement and then you can solve pretty much any batch update task. One fairly easy and useful task would be to write a one-liner that will publish all draft documents in every pages library. You might want to learn a bit more PowerShell first though 😉

Accessing MOSS 2

I created the following 4 functions that I continue to use every time I need to access my local farm:

function Find-Webs([string] $urlpattern) 
   Get-localfarm | get-webservice | get-webapplication |
         get-sitecollection |
         get-web  | Where-Object{$_.url -like "$urlpattern"}
}
function Get-Site([string] $url){
    $site = New-Object Microsoft.SharePoint.SPSite($url)
    return $site
}
function Get-Web([string] $url){
    $u = New-Object Uri($url)
    $site = Get-Site($url)
    $web = $site.OpenWeb($u.AbsolutePath)
    return $web
}
function Select-ItemFields($item) {
    #primitive formatting
    $item.Fields | ForEach-Object { new-object
       System.Collections.DictionaryEntry($_.title, $item[$_.Title] ) }
}

Find-Webs will locate any web that matches the url pattern, so to get any of my “news” webs (in the entire farm):

$news = Find-Webs(“*/news”)

Usually I use it with a specific enough url to get at only one specific site. It is a bit slow because it relies on the above methods from Darrin Bishop that iterates through all web apps/site collections/sites and it does require you to be local admin on your farm (which you probably are anyway).

Get-Site and Get-Web will obviously get a SPSite or SPWeb object given a full url. It is faster and almost as easy to use as Find-Webs.

Select-ItemFields is a small method that will list every metadata field of a given SPListItem. I simply got tired of writing

$item[“field1”]

to read specific fields ($item holds the SPListItem). I found no really easy way to iterate over the values in the default indexer of a given object, so I just wrote the few lines required. I’m sure there is a PowerShell wizkid out there that will say, Idiot! Use the built-in in Select-XXX instead. Please speak up; I’ll like to know…

These four functions actually cover my MOSS PowerShell needs. I keep coming back to them to get the first hook into the OM and then perform the task at hand (often a small test) on a given list item, content type, field etc.

Your mileage may vary, so by all means add 10 more convenience methods.

Note: To test my method, just paste the content of the entire script file into your PowerShell window – you don’t need to create fancy scripts or setup profiles.

Making it Convenient

Finally I like stuff to be convenient and easy.

To that end I’ve stuffed all of the above scripting in my profile file, so it’s always loaded when I start PowerShell, in particular the Microsoft.SharePoint.dll is loaded (download my profile script file here. Note: Strip away the “.gif” extension – crappy wordpress restrictions).

To add it to your profile:

  1. Go to your “My Documents” folder
  2. If there is no directory named “WindowsPowerShell” create it and navigate to it
  3. Create/edit a file named “profile.ps1” and paste my script into it

That’s it! When you launch PowerShell the script should be loaded. To verify, look for the assembly loaded confirmation message, when you start PowerShell. And of course: Execute some of the functions!

One last tip: F7 is your friend. Try it.

References

Darrin Bishops original scripts. He has also written an additional blog post that will help you with filtering.

The basics are coveraged better than I could possibly do on webmat’s blog.

Deeper coverage, when you need to know what’s really going on: Learn PowerShell in Your Launch Break Day 1 and Day 2 (and just keep going…)

And finally, given a problem or a difficult task ask the experts 😉

Advertisements