I’m sure most of you know that I wear many hats. Sys admin, DBA, Storage admin etc etc. One of the things we’ve recently done here is to setup a central SQL Server because we noticed a lot of the servers had SQL or SQL Express that were not being backed up correctly or just not known about so to manage this we’ve moved them all to one DB cluster.
Because of this I now have to create DB’s on a more regular basis which involved a new DB, a SQL account and documenting it. You ask, why SQL accounts? It’s a slow process to get domain service accounts here, too much of a pain!
So there are 3 steps in this task.
- Create a DB
- Create the SQL Account (with random password)
- Insert data in to company WIKI (This resulted in my modifing a sharepoint wiki post)
I’m not a huge fan of using modules in scripts just to prevent there being any dependencies on them so we’ll need to load up a couple of .NET assemblies for this.
SQL and the Web reference.
The System.Web is used to create the password. Sherif Talaat has a neat post on this.
Lets connect in and create that DB. The code assumes you’ve got a db name/instance (just a server name or server\instance name) stored in $dbinstance and it that $dbname has the name of the db you’d like to create.
$dbServer = new-object Microsoft.SqlServer.Management.Smo.Server ($dbInstance) $db = new-object Microsoft.SqlServer.Management.Smo.Database $dbserver, $dbname $db.create(); $login = new-object Microsoft.sqlserver.management.smo.login $dbserver, $dbname $login.LoginType = 'SqlLogin' $login.DefaultDatabase = $dbname $pass = [System.Web.Security.Membership]::GeneratePassword(10,0) $login.Create($pass) $db.SetOwner($dbname)
This makes a connection to the SQL server and then creates a new DB object and runs the Create method which injects it in to the SQL Server.
We then go about making a new SQL login and setting its default DB and login type. You’ll notice we use the DBName as the login name, this just makes it easier for tracking what belongs to what. It would be easy enough to change this.
Then we use the static GeneratePassword method in the web.secuirty.membership class to create a password for us and assign that to the login and set the login’s ownership of that DB.
Now we just take the username/db name and password and send it over to our wiki.
I wont repeat the code, since i’ve already documented it but you’ll want to do some string manipulation to make sure it ends up where you want it to. I use a simple template entry I search for in my wiki and then replace it with the template entry and new entry (ie, append)
The point here is that its really easy to make your life easier if you start thinking about some of the small tasks that need to be done and how easily (sometimes) they can be automated!
I’d love to hear about some of the tasks you’ve automated so drop a comment!
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!