Modify a SharePoint 2010 (probably 2007 as well) Wiki page with PowerShell
This turned out to be much harder than I expected, so I thought it would be worth sharing.
First, you ask, why Web Services? Simple, I wanted this script to work from anywhere. with any other methods of accessing SharePoint you need to be on the server for it to work, so Web Services it is.
First step, getting a handle on your SharePoint server. PowerShell has a cmdlet for that!
$proxy = New-WebServiceProxy ‘http://localhost/_vti_bin/lists.asmx’ -UseDefaultCredential
I just used local host for testing, but it works with any server and you have to specify the site, which in this case I used the default root site. I also passed my logged in creds but you can specify creds if need be.
NOTE: I found that the URL didnt always stay as it was suppose to so verify that and update it if need be.
If you want to modify/add/delete data you’ll likely want to use the List Web Service, but remember there are others (http://msdn.microsoft.com/en-us/library/ms479390(office.12).aspx )
Now that we have this List Web Service, how do we work with it? You can poke around a bit with PowerShell
$proxy | get-member
Whole lot of things there, but since its a Web Service, we want to look at methods (list of members here)
There are a handful of Get methods, but the GetListItems seems to be what we are looking for. If we check the signature we see that a few things are required.
Lot of info just to get a list item so lets start to tackle this.
ListName – Simple one, a string of the list we want to work with, in my case its “myWiki” but, as per the docs, its better to use the GUID (get in to that later).
ViewName – This is a string/GUID of the view we want to use, but its optional, so I found it was easy enough to leave blank for my needs.
Query – An XML node of CAML that defines what to return.
ViewFields – An XML node of more CAML to define what views we want to see (this one is important).
RowLimit – Pretty obvious, the default (all) is what we want.
QueryOptions – An XML node of CAML for the query options
Not too bad once we start to look at it, but whats this CAML?
CAML – Collaborative Application Markup Language, its just XML with named tags (same idea as HTML)
I dont know much about CAML, and I spent a good deal of time digging, but what I found is that there is a tool for it!
This tool was a HUGE help. I’m not going to go in to details because it was fairly easy to use, and when you see the CAML I provide it will make more sense.
Now that we have a method and a way to build the queries, lets get off and running.
Using the U2U Caml builder (tweaking it a bit) I came up with this to get back the one record I’d like to modify.
<mylistitemrequest> <Query> <Where> <Eq> <FieldRef ID="ID" /> <Value Type="Counter">5</Value> </Eq> </Where> </Query> <ViewFields> <FieldRef Name='WikiField'/> </ViewFields> <QueryOptions> <IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns> </QueryOptions> </mylistitemrequest>
You’ll notice there are a few key words there that stick out, Query, ViewFields and QueryOptions which nicely match up with our method arguments!
$str = @' <mylistitemrequest><Query><Where><Eq><FieldRef ID="ID" /> <Value Type="Counter">5</Value></Eq></Where> </Query><ViewFields><FieldRef Name='WikiField'/></ViewFields><QueryOptions> <IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns></QueryOptions></mylistitemrequest> '@ ($xml = [xml]"").LoadXml($str) $query = $xml.SelectSingleNode('//Query') $view = $xml.SelectSingleNode('//ViewFields') $options = $xml.SelectSingleNode('//QueryOptions') $wiki = $proxy.GetListItems("myWiki","",$query,$view,"",$options,"")
So we took that XML document object and ripped out the nodes and feed the nodes in to the method and stored its results in to $wiki.
Running Get-Member against our $wiki will show us that we have an XML document that we can dig in to.
$wiki.data will show us that there is one item in there, just what we were looking for.
$wiki.data.row will show us the fields of a wiki, the one we really care about is the content of that wiki which is ows_wikifield
Some pretty basic HTML in there, in my case I get back this
<div><table id="layoutsTable" style="width:100%"><tbody><tr style="vertical-align:top"><t d style="width:100%"><div style="width:100%"><div><p>here</p> <p>is some</p> <p>sample data</p> <p>i want to add</p> <p>after this</p></div></div></td></tr></tbody></table> <span id="layoutsData" style="display:none">false,false,1</span></div>
Now lets tack on some text to the end of our wiki
$content = $wiki.data.row.ows_WikiField $content = $content -replace "<p>after this</p>", "<p>after this</p><p>yay more data</p>"
We’ve updated the text, but now we have to inject that back in to our Wiki, which involves a new method and more CAML. I’m not going to bother explaining it because if you’ve followed whats above, the update will make sense.
$updatestr = @" <Batch OnError="Continue"> <Method ID='1' Cmd='Update'> <Field Name='ID'>3</Field> <Field Name='WikiField'> test </Field></Method></Batch> "@ ($xml= [xml]"").loadXml($updatestr) $xml.batch.Method.Field."#text" = $content $proxy.UpdateListItems("myWiki",$xml)
There is one part that should be making you scratch your head, and let me tell you, it took a while for me to figure out.
Why did i put “test” in the XML wikifield?
well, come to find out, if you dump HTML in there, it looks an aweful lot like XML. If you dont put anything then the #text field isnt generated, so i put “test” in there and then used the XML members to inject the actual content I wanted updated.
Was a bit of a trick figuring this out, so I hope it helps!