Tuesday, March 18, 2008

While preparing for the presentation on Silverlight and SharePoint next week I've just discovered that the current Beta 1 of SilverLight 2 is unable to call SharePoint's native webservices.

The technical reason is that the WSDL SharePoint generates includes an asd:any declaration which is translated by the proxy generator as requiring the XmlElement class but Silverlight does not support this class. So you're not able to call GetList or GetListItems in Lists.asmx for instance.

I've posted on the Silverlight forums about this but the reply I got from Yavor Georgiev (Program Manager - Connected Services) was not clear as to whether this is a planned feature for SL 2.

There are a few ways around this which I'll go into in the presentation but none are very pleasant prospects.

There's probably still time to get this feature in v2. The best thing is to let MS know this is an important issue by adding support on the thread. Also worth keeping an eye on the Silverlight Web Services blog.

Tuesday, March 18, 2008 8:29:36 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Tuesday, March 11, 2008

I'm really stoked to be presenting at the UK SharePoint user group meeting in Basingstoke on the 27 March about Silverlight and SharePoint.

This presentation will explain what Silverlight is, what the benefits are over standard web development and how Silverlight can be hosted in and integrate with SharePoint.
With few slides and plenty of demos I'll walkthrough the creation of a Silverlight 2.0 video browser application that uses data from SharePoint and then deploy it to SharePoint. I'll also show how a commercial Silverlight 2.0 product for SharePoint is put together.

The other presentations are

Architecting a Highly Available MOSS Farm - by Lewis Baldwin, ICS,  Head of Infrastructure and Support

Governance: Protecting your SharePoint Investment by Symon Garfield, ICS,  SharePoint Practice Lead

The good folks at ICS solutions are hosting us at their offices in Basingstoke (http://www.icssolutions.co.uk/pages/howtofindus.aspx).

Just post a reply with your full name on this thread http://suguk.org/forums/thread/8918.aspx (registration required)

Tuesday, March 11, 2008 1:06:22 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, February 25, 2008

I forgot to post this before but Microsoft has created a virtual lab based on my blog postings. If you're interested in Powershell and SharePoint its definitely worth checking out

http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032346304&EventCategory=3&culture=en-US&CountryCode=US

As noted on Ben Pierce's blog Powershell is now a CER common engineering requirement for all products released in 2009 and beyond, that should mean as Office 14 is due for release in 2009 PowerShell should be a mandatory feature in SharePoint.

Thats good news and a big change as when I'd asked the question about PowerShell support in SharePoint to Mike Fitzmaurice back in February last year the answer was they had no plan to do so.

Monday, February 25, 2008 12:12:27 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, February 24, 2008

Microsoft has recently pledged to release the documentation of a lot of its up-to-now internal server protocols.

'Microsoft is providing access through open connections to its high-volume products—Windows Vista (including the .NET Framework), Windows Server 2008, SQL Server 2008, Office 2007, Exchange Server 2007, and Office SharePoint Server 2007—so that software developers, business partners and competitors can better interact with these Microsoft products or invent new solutions for customers.'

Obviously the ones getting the most attention are the server SMB interfaces but also of interest in a real geeky way are ones for the Windows Search Service and WebDAV.

What's tweaked my curiosity is that SharePoint 2007 is mentioned on this list. What interfaces are they talking about? Client side protocols like the FrontPage RPC's?  Server side ones? Dunno but it could be interesting.

Personally given the time I spent a long time ago extracting data from a corrupt Microsoft MDB in 2k pages I'd like to see the Jet DB format released, just for old times sake.

Kudo's to Microsoft to releasing this information with a much dignity as anyone with their arm twisted up their back by the EU could.

 

UPDATE:

thanks to Sean Watson for explaining that the SharePoint related Open Protocols are three WebDAV and two FrontPage ones

MS-WDV  Web Distributed Authoring and Versioning (WebDAV) Protocol: Client Extensions

MS-WDVME  Web Distributed Authoring and Versioning (WebDAV) Protocol: Microsoft Extensions

MS-WDVRV World Wide Distributed Authoring and Versioning (WebDAV) MS-Author-Via Protocol Specification

MC-FPSEWM FrontPage Server Extensions: Website Management Specification

MS-FPSE FrontPage Server Extensions Remote Protocol Specification

for more details about the Microsoft Communications Protocol Program MCPP and the legal status of these documents see http://www.microsoft.com/about/legal/intellectualproperty/protocols/mcpp.mspx 

The gist of it seems to be that open source projects are free to use this information but commercial products would possibly be subject to licencing and patent requirements depending on what they implemented.

Sunday, February 24, 2008 8:33:48 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
 Friday, June 15, 2007

I’m presenting at the EVO (Exchange, Vista, Office) Community day next Thursday the 21st June.


I’ll be doing a session that shows how you can use Powershell to read information from Exchange and getting it into SharePoint 2007.
Its really hard to know how to pitch a presentation like this as the mix of experiences is probably going to be wide, from people seeing this stuff for the first time to old hands. Anyway I’m looking forward to it.

The talk will cover some Powershell basics, calling the new Exchange Web Services from PowerShell and why we might still need to use WebDAV(hint Public Folders) and of course how we write to SharePoint.
I'll start posting some of the code samples over the next week.

There’s lots of other sessions including ones from Steve Smith on SharePoint and Richard Siddaway on PowerShell, should be a good one.

Heres the details of the Event:
The Next EVO Community Day - June 21st: Microsoft Campus Thames Valley Park Reading

Event Agenda
09:30 Start
09:30 - 10:00 Keynote
10:00 - 10:25 Introductions to groups (Including LiveMeeting, Longhorn, Virtualisation)
10:25 - 10:55 Vista
10:55 - 11:10 Break
11:10 - 12:40 Office, SharePoint, Groove
12:40 - 13:25 Lunch
13:25 - 14:55 Break-out sessions
14:55 - 15:10 Break
15:10 - 15:40 OCS + UM
15:40 - 16:40 Exchange and PowerShell
16:40 - 16:55 Break
16:55 - 17:30 Q&A

Further agenda information can be found here:  http://www.ukusergroups.co.uk/Agenda.html

To sign up and more information: http://www.ukusergroups.co.uk/

 

Friday, June 15, 2007 9:57:30 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, March 14, 2007

I’m presenting at the next UK PowerShell User Group meeting on March 22nd doing an updated talk around using PowerShell with SharePoint featuring an intro to PowerShell,  Demos and roundup with SharePoint/PowerShell gotchas and 'tips and tricks'.

The talk should be be of interest of anyone who might like to talk to a managed API with Powershell.

I’m doing the first talk and Richard Siddaway is doing the second session with a talk on using ActiveDirectory with PowerShell.
If you need an intro to using PowerShell or want or ask more advanced questions this will be a good place to be.

Location:
Memphis Room
Microsoft Building 3
Thames Valley Park
Reading

Registration 18:00
Start           18:30

You need to register for attendance so follow the instructions in this link: http://www.culminisconnections.com/sites/get-psuguk/Lists/Events/DispForm.aspx?ID=2

Hope to see you there.

Wednesday, March 14, 2007 2:29:09 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Friday, February 23, 2007

I've posted a short piece about the SharePoint European conference on the SUGUK blog, reproduced here

SharePoint European Conference

This is the first SharePoint specific conference I’ve been to and I was impressed by the amount of interest shown by the European’s, with over 2000 attendees from 50 countries and with the UK providing around 160 delegates it was very busy, it seems that even MS was taken by surprise at the amount of interest.

The organisation was of a high standard as you’d expect from a conference hotel, lots of staff and lots of food the only omission as Eric Shupps has already mentioned being the lack of free WIFI access and the really slow speed of the paid for one, they were plainly not geared up for a WIFI swarm drain of techies popping open their laptops and trying to surf the web.

Most of the sessions were of a high standard with speakers either from Microsoft or their partners, the only real dud I attended was the business intelligence one which fell completely flat, poor content and speakers meant I ended up walking out something which I never normally do.

Kudo’s to Steve Heaney of Nintex who did 30 minutes of workflow coding in Visual Studio 205 and had it compile and run without errors, I think he was more surprised at this than anyone.

Here’s my take on a couple of sessions that opened my eyes to new features of the Office 2007 suite.

OpenXML

This session by Peter Koen described the OpenXML format of the new Office 2007 programs Word, Excel and PowerPoint. It showed how easy it is to modify existing document using the Packaging API in .Net 3.0 and pointed out how powerful this could be in conjunction with List Events and Workflow. The power of this API is that you do not need to automate any of the Office programs you just deal with the file itself, this is very important in server side code as most SharePoint code tends to be.

 A couple of examples, lets say you have a Document library called Draft and one called Confidential, what you would like is for all documents that are placed into these document libraries to have a watermark applied that says either DRAFT or CONFIDENTIAL applied to them. With list events and some fairly simple code this should be pretty easy to do.

Or lets say you have 100’s of documents in various document libraries that you send out to clients on a regular basis and each one has your logo in it along with your company name and details in the footer of each one, lets say you undergo a re-branding exercise or an office move, you are now faced with opening each one of those documents and changing the logo and footer by hand or perhaps automating Word with VBA. With the OpenXML format you can crack open the Docx file and with a few lines of code manipulate the parts of the document you need to, it would also be an order of magnitude faster than calling Word.

The one current hassle is you have to deal with the Word XML directly, there is no API available that maps say a Word object model onto the XML needed but apparently that is on the development timeline.

http://openxmldeveloper.org/default.aspx

 Groove

This was a really good session by Mark Ryan of Microsoft, clearly someone who has been there and got the T-shirt.

Groove is a program I’ve ignored up to now but this session makes clear its not something that can be ignored for much longer, it just too useful a program.

Groove supports offline distributed and replicating workspaces containing lists, discussions, files and custom data form. It basically allows you to do offline collaboration in the same way as SharePoint allows online collaboration.

The integration with SharePoint is limited at the moment as it currently only supports offline documents from document libraries with no support for lists, but if the Groove team gives the SharePoint team some ‘love’ this should improve in the next version.

As Groove is new to the Office stable it does not have the .Net/Visual Studio integration that it possibly should have and although you can embed InfoPath forms to create data entry forms Mark recommends to stick with Groove forms for now.

 One scenario mentioned was for offline workgroup collaboration on a design project involving images but perhaps the most interesting scenario is crisis management.

Picture this: you have a physical disaster at your main office, lets say a flood, all company VPN and mail communication through the head Office is down, with Groove you could have a nominated person who holds a predefined workspace containing the company contact list along with the disaster recovery plans and checklists, that user can quickly invite other needed parties into the workspace and as long as people have an internet connection they can stay informed and up to date on all activities as the company works through the steps to get back to normal. Even if you don’t use Groove on a day to day basis in a case like this it would be worth its weight in gold.

 Definitely check Groove out, http://office.microsoft.com/en-gb/groove/default.aspx

 Looking forward to the next one, don’t know when it will be but one thing’s for sure they are going to need much bigger venue.

 

Friday, February 23, 2007 5:59:42 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

The UK SharePoint User Group meetings are coming thick and fast, the next one is being held on 7th March in Ullesthorpe, Leicestershire.

This should not be missed as it features 3 speakers, Andrew Woodward, Bill English and Todd Bleeker.

I've seen both Bill and Todd speak and its an experience ;-)

Put your name on this thread to signup for the meeting http://suguk.org/forums/2300/ShowThread.aspx#2300

 

Friday, February 23, 2007 5:51:31 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, February 10, 2007

 

Following on from the work in my previous post where I set the users Picture property in their profile the next step is to add a Contact Web Part to our imported publishing pages. I’m using the default layout page to add the contact Web Part to, this page has the Web Part Zones underneath the content fields but you could use another layout that has a Web Part zones to the right of the content fields and of course you can use SharePoint Designer to create your own layout formats and embed the Contact control without using code.

This is how our publishing pages will look once we have added the Web Part to each page

An interesting feature of a Publishing page is that it has a contact property where you can assign user details to each page, you can either lookup the user from the picker in which case the page will pull the users details from the profile database or you can type in the details yourself.

 

The nice thing about the Contact Web Part is that once you have added it to a publishing page it will automatically pull the picture and the users description (if set in the Web Part) that has been assigned to the page from the profile database.

One bad thing about the part is you only get the choice to put the users name left or right not top or bottom.

So on to the code, I need a function that will take a site URL and then set the default Publishing Page’s contact property and then add the Web Part to the page.

 

 

# Function: Add-ContactWebPart # Description: Adds the Contact User Web Part to a publishing page # Parameters: SiteURL - Server relative URL of the Area # UserName - UserName to show as the contact in domain\user format # # Requirements: Needs to have the System.Web assembly loaded # function Add-ContactWebPart($SiteURL, $UserName) { $comment = "Contact WebPart Added" $site = new-object Microsoft.sharePoint.SPSite($SiteURL) $web=$site.OpenWeb() $user= $web.Users.get_item($UserName) $pubweb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web) $defaultpage=$pubweb.GetPublishingPages()[$pubweb.DefaultPage] $defaultpage.CheckOut() # Set Contact "Setting Contact on " + $pubweb.url + " to " + $user.Name $defaultpage.set_Contact($user) $defaultpage.Update() $webpartmanager=$web.GetLimitedWebPartManager($defaultpage.Url, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared) $webpart=new-object Microsoft.SharePoint.Portal.WebControls.ContactFieldControl $webpart.ChromeType=[System.Web.UI.WebControls.WebParts.PartChromeType]::TitleOnly; $webpart.Title="Page Contact" $webpart.PicturePosition=[Microsoft.SharePoint.Portal.WebControls.PictureDirection]::Left $webpart.IsDisplayJobTitle=$true $webpart.IsDisplayPicture=$true $webpartmanager.AddWebPart($webpart, "LeftColumnZone", 0); " Checking in page" $defaultpage.CheckIn($comment) # Publish if($defaultpage.listItem.ParentList.EnableMinorVersions -eq $true -and $publishingPage.ListItem.File.MinorVersion -ne 0) { " Publishing" $defaultpage.listItem.File.Publish($comment) } # If moderation is being used handle the approval if ($defaultpage.listItem.ParentList.EnableModeration) { $modInformation = $defaultpage.listItem.ModerationInformation " Moderation on, Current Status: " + $modInformation.Status # Check for pending approval if($modInformation.Status -ne [Microsoft.SharePoint.SPModerationStatusType]::Approved) { " Approving" $defaultpage.ListItem.File.Approve($comment) } } # Clean up $pubweb.Close() $web.Close() $site.Close() }

 

In this code we find the SPUser object for the given username and set the Contact property of the PublishingPage to it. We then add the web Part using the new SPLimitedWebPartManager class. Most of the code is concerned with checking the page out and back in and assumes the page is not already checked out.

An interesting line is

$defaultpage=$pubweb.GetPublishingPages()[$pubweb.DefaultPage]

I'm indexing into the collection return by GetPublishingPages because Powershell doesn't support generics in the current version.

Saturday, February 10, 2007 8:17:59 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, February 07, 2007

Don't forget the SharePoint user group meeting taking place tomorrow Feb 8th at Reading.

'Mr SharePoint community' Lawrence Lui is over from Redmond to take us through the Fab 40 Application Templates and lead a discussion on Office 14.

Sign ups and more detail here: http://suguk.org/forums/1/1901/ShowThread.aspx

Wednesday, February 07, 2007 1:08:50 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, January 28, 2007

Now we have the portal setup I want to add a Contact WebPart to each page but before I can do that I have to add a picture for each user I have imported, in order to keep this post a little shorter I’m only going to concentrate on using the UserProfile API’s to set our picture for each user.
(The Contact WebPart is MOSS WebPart that shows a users name and description and optionally their picture)
The field that we are going to set programmatically is accessed on the Users edit profile page as Picture:

Once set this will display the picture on the user profile like this


To do this I need to revisit our User.csv import file and add an extra field that gives the name of the JPG file that holds the users picture.
I’m going to assume that the previous blog post Upload a directory of files in 4 lines has already uploaded the users picture to the SiteCollectionImages picture library, in real life you’d probably use a separate picture library.


Ideally a profile import has occurred after we have added the users to Active Directory and populated the SharePoint profile database.

Now to set the Users Picture property we know to know the Property Name of the Picture Field.
To make this easy to find out here’s the first function for our toolbox Get-SPUserProfileConfigManager.
This function returns a UserProfileConfigManager (http://msdn2.microsoft.com/en-us/library/microsoft.office.server.userprofiles.userprofileconfigmanager.aspx), note this is the new class that resides in the Microsoft.Office.Server namespace not the v2 obsolete one that lives in Microsoft.sharePoint.Portal.UserProfiles.
Also ignore the sample currently given in the MSDN documentation above, it won’t work as it uses the old classes where you pass a PortalContext to the constructor whereas the new version of the classes take a ServerContext object.

 

# Function: Get-UserProfileConfigManager # Description: return a UserProfileConfigManager object which is used for management of MOSS User Profiles # Parameters: PortalURL URL for the Portal Site Collection # # function Get-UserProfileConfigManager([string]$PortalURL) { # Need to get a PortalContext object # as we do not have a HttpContext we need to source one the hard way $site=new-object Microsoft.SharePoint.SPSite($PortalURL) $servercontext=[Microsoft.Office.Server.ServerContext]::GetContext($site) $site.Dispose() # clean up # Return the UserProfileConfigManager new-object Microsoft.Office.Server.UserProfiles.UserProfileConfigmanager($servercontext) }

 

Once we get the UserProfileConfigManager we can call GetProperties and list the internal and display names for each profile property

$cm=get-userprofileconfigmanager "http://sps:2828" $cm.getproperties() | select name, displayname Name DisplayName ---- ----------- UserProfile_GUID Id SID SID ADGuid Active Directory Id AccountName Account name FirstName First name LastName Last name PreferredName Name WorkPhone Work phone Office Office Department Department Title Title Manager Manager AboutMe About me PersonalSpace Personal site PictureURL Picture UserName User name QuickLinks Quick links WebSite Web site PublicSiteRedirect Public site redirect SPS-Dotted-line Dotted-line Manager SPS-Peers Peers SPS-Responsibility Responsibilities SPS-Skills Skills SPS-PastProjects Past projects SPS-Interests Interests SPS-School Schools SPS-SipAddress SIP Address SPS-Birthday Birthday SPS-MySiteUpgrade My Site Upgrade SPS-DontSuggestList Don't Suggest List SPS-ProxyAddresses Proxy addresses SPS-HireDate Hire date SPS-LastColleagueAdded Last Colleague Added SPS-OWAUrl Outlook Web Access URL SPS-ResourceSID Resource Forest SID SPS-ResourceAccountName Resource Forest Account Name SPS-MasterAccountName Master Account Name Assistant Assistant WorkEmail Work e-mail CellPhone Mobile phone Fax Fax HomePhone Home phone

So from this list I see that I need to set the PictureURL property, to get a UserProfile we first need a UserProfileManager object:

# Function: Get-SPProfileManager # Description: Return a UserProfileManager object which is used for accessing MOSS User Profiles # Parameters: PortalURL URL for the Portal Site Collection # function Get-SPProfileManager([string]$PortalURL) { # Need to get a PortalContext object # as we do not have a HttpContext we need to source one the hard way $site=new-object Microsoft.SharePoint.SPSite($PortalURL) $servercontext=[Microsoft.Office.Server.ServerContext]::GetContext($site) $site.Dispose() # clean up # Return the UserProfileManager new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($servercontext) }

And then a helper function Get-SPUserProfile to obtain the UserProfile object itself:

# Function: Get-SPUserProfile # Description: Return a UserProfile object, this will be created if it does not exist # Parameters: PortalURL URL for the Portal Site Collection # DomainUser UserName in Domain\user format function Get-SPUserProfile([string]$PortalURL, [string] $DomainUser) { $upm= Get-SPProfileManager([string]$PortalURL) if ($upm.UserExists($DomainUser) -eq $false) { $upm.CreateUserProfile($DomainUser) } $upm.GetUserProfile($DomainUser) }

Note that this function will create the UserProfile if it does not already exist.

Now we just need a function that makes it easy to set a single UserProfile property, if you have multiple properties to set it would be best to do them all at once and then call commit.

# Function: Set-UserProfileProperty # Description: Sets a property on a User Profile # Parameters: UserName [optional] UserName in Domain\user format # PropertyName Property to set # PropertyValue Property Value to set # $UserProfile UserProfile object, if using this in a loop this should be set # function Set-UserProfileProperty([string]$UserName, [string] $PropertyName, [string] $PropertyValue, [Microsoft.Office.Server.UserProfiles.UserProfile] $UserProfile) { # If we are not passed a UserProfile object then create it # if ($UserProfile -eq $null) { $UserProfile = Get-SPUserProfile($UserName) } $UserProfile[$PropertyName].Value= $PropertyValue $UserProfile.Commit() }

Note this function can either be called with a pre-created userProfile object or a UserName.

Heres the updated Users.CSV with the Picture Field added at the end  

LoginName, DisplayName, FirstName, LastName, Email, Picture brianb, Brian Ballack, Brian, Ballack, brianb@contoso.com, cowner10.jpg walterf, Walter French, Walter, French, walterf@contoso.com, cowner12.jpg

Now a function to tie this all together, it imports the CSV files, locates the user profile by login name and updates the user’s Picture URL:

function Set-UserPictures([string] $PortalURL, [string] $UserFile, [string] $Domain ) { Import-Csv $UserFile | foreach-object { $name=$Domain + "\" + $_.LoginName; $fullURL=$PortalURL + "/" + $_.Picture; Set-UserProfileProperty $PortalURL $name "PictureURL" $fullURL } }

 And you can make use of all of the above code by running this command:

Set-UserPictures "http://sps:2828" "users.csv" "contoso"

Ok now we're all set to add the Contact WebPart to each publishing page in the next post.

Sunday, January 28, 2007 6:24:37 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, January 11, 2007

I just finished finalising the flights and hotels to attend the SharePoint 2007 European Conference in Berlin today when I realised I knew zip about the German language, yes I know the usual German words picked up from films, schnell, bitte etc. but I'd never spoken one single german phrase for real.

Whatever country I go to I try to speak a little of the local language, if nothing else it gives the waiters a laugh, so I needed a quick and easy starters course in German, a little Googling and I found this gem German Podcasts by Stephan Wiesner

This is fantastic, its a series of podcasts where Stephan takes you through a story teaching you German as he goes about a Hans a German progammer who flies into an airport, goes through customs and talks about a conference he's been at!

The story is nicely pitched at the complete beginner and Stephan has produced mp3's, pdfs to accompany and even a video.

The content is also posted at http://www.archive.org which lets you listen on the webpage itself via a Flash plugin.

I wonder can he do a podcast that teaches me how to say: 'What?? you're NOT using PowerShell to script SharePoint?'

 

 

 

Thursday, January 11, 2007 6:12:07 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
 Sunday, December 17, 2006

 

As a follow on from my previous post we can do some neat calculations on the amount of data stored in MOSS personal sites using Powershell.

Using the measure-object CmdLet against the StorageUsedMB property we can calculate the number of personal sites (+1 one as it includes the root site), average size of of each site, smallest, largest and total storage size of all sites.

Again assuming http://sps:20488 is where your personal sites are hosted

PoSH C:\demo> $output=stsadm -o enumsites -url "http://sps:20488"
PoSH C:\demo> $xml=[XML]$output
PoSH C:\demo> $xml.sites.site | measure-object storageusedmb -min -max -sum -average | format-table

Count Average Sum Maximum Minimum Property
----- ------- --- ------- ------- --------
5      0.62   3.1 0.7     0.4     StorageUs...

To get a list of the sites in question with the largest at the top use -descending on the sort CmdLet:

PoSH C:\demo> $xml.sites.site | sort storageusedmb -descending | select url, owner, storageusedMB | format-table

Url                         Owner                    StorageUsedMB
---                         -----                    -------------
http://sps:20488/person... CONTOSO\mike               0.7
http://sps:20488/person... CONTOSO\jeff               0.7
http://sps:20488/person... CONTOSO\administrator      0.7
http://sps:20488           CONTOSO\administrator      0.6
http://sps:20488/person... CONTOSO\brianb             0.4

And if you just want the top 2 offenders us the -first option on the select-object CmdLet

PoSH C:\demo> $xml.sites.site | sort storageusedmb -descending | select url, owner, storageusedMB -first 2 | format-table

Url Owner StorageUsedMB
--- ----- -------------
http://sps:20488/person... CONTOSO\mike 0.7
http://sps:20488/person... CONTOSO\jeff 0.7

 

Obviously in real life those figures would be a lot larger!

Sunday, December 17, 2006 4:58:38 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 

A point came up at the UK SharePoint User Group Chrismas drink, about Personal sites and whether they were top level sites i.e they exist as records in the Sites table and are the top level container for all its subwebs, I hadn’t checked this in v3 so a quick check with PowerShell and stsadm confirms that this is still the case as in V2

Assuming http://sps:20488 is where your personal sites are hosted calling stsadm -o enumsites and processing the returned XML in PowerShell gives us

PoSH C:\demo> $output=stsadm -o enumsites -url "http://sps:20488"
PoSH C:\demo> $xml=[XML]$output
PoSH C:\demo> $xml.sites.site

Url : http://sps:20488
Owner : CONTOSO\administrator
ContentDatabase : WSS_Content_11fba01f-f0c4-4a05-a3c3-868499fd31ce
StorageUsedMB : 0.6
StorageWarningMB : 0
StorageMaxMB : 0

Url : http://sps:20488/personal/administrator
Owner : CONTOSO\administrator
ContentDatabase : WSS_Content_11fba01f-f0c4-4a05-a3c3-868499fd31ce
StorageUsedMB : 0.7
StorageWarningMB : 80
StorageMaxMB : 100
Etc…

Tiding up the output a little

PoSH C:\demo> $xml.sites.site | select url, owner

Url Owner
--- -----
http://sps:20488                           CONTOSO\administrator
http://sps:20488/personal/administrator    CONTOSO\administrator
http://sps:20488/personal/brianb           CONTOSO\brianb
http://sps:20488/personal/jeff             CONTOSO\jeff
http://sps:20488/personal/mike             CONTOSO\mike
….

 

 


Sunday, December 17, 2006 4:40:58 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, December 03, 2006

Now that I have added some content to our portal I want to approve and publish all pages in each publishing web.

To do this I've created a function that takes an MOSS PublishingPage object and a comment that will be added when we check-in, approve and publish.

 

# Function: Approve-PublishingPage
# Description: Approve a single page in a Publishing Web
# Parameters: publishingPage PublishingPage object
# comment Comment to accompany the check-in/approve/publish
#
function Approve-PublishingPage ([Microsoft.SharePoint.Publishing.PublishingPage]$publishingPage, [string]$comment)
{
" Publishing Page: " + $publishingPage.Name

$listitemfile = $publishingPage.ListItem.File

# Check item if checked out
if ($listitemfile.Level -eq [Microsoft.SharePoint.SPFileLevel]::Checkout)
{
   " Checking in page"
   $listitemfile.CheckIn($comment,[Microsoft.SharePoint.SPCheckInType]::MajorCheckin )
}


# If moderation is being used then handle the approval and publishing
if ($publishingPage.ListItem.ParentList.EnableModeration)
{
   $modInformation = $publishingPage.ListItem.ModerationInformation

   " Moderation Enabled"

   # Check for pending approval
   if($modInformation.Status -eq [Microsoft.SharePoint.SPModerationStatusType]::Pending)
   {
      " Approving"

      $listitemfile.Approve($comment)
   }

   # Publish
   if($modInformation.Status -eq [Microsoft.SharePoint.SPModerationStatusType]::Draft)
   {
   " Publishing"
   $listitemfile.Publish($comment)
   }
}
}

this function will be called from Approve-AllPagesInSPWeb

# Function: Approve-AllPagesInSPWeb
# Description: Loop through all the pages in a Publishing Web and checkin and approve them
# Parameters: web SPWeb object
# comment Comment to accompany the checkin/approve/publish
#
function Approve-AllPagesInSPWeb([Microsoft.SharePoint.SPWeb]$web, [string]$comment)
{

# Check this is a publishing web
if ([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($web) -eq $true)
{

$pubweb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web);

"Checking $($pubweb.URL)" 

   $pubcollection=$pubweb.GetPublishingPages() 

   for($i=0; $i -lt $pubcollection.count; $i++)
   {
      Approve-PublishingPage $pubcollection[$i] $comment
   }

}

}

Now I'd like to use a foreach look around the GetPublishingPages collection but that's not possible due to lack of Generic support in PowerShell at the moment, so an index loop does the job.

To approve all pages in all webs we can pipe the SPWebs to a foreach loop and pass the SPWeb object to Approve-AllPagesInSPweb

$site = spsite "http://yourmossserver"
$site.allwebs | foreach-object {Approve-AllPagesInSPweb $_ "System Approval"}

Added as a function approveall it produces this output

 

In the next step I'll add a Contact WebPart to every page.

 

 

Sunday, December 03, 2006 9:01:15 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [6]  | 
 Sunday, November 19, 2006

Yes...Fitz is blogging again. At last Mike Fitzmaurice one of the primary links between the SharePoint team and the outside world is blogging again.

No...Maurice Prather one of the few hardcore SharePoint bloggers around has posted that he is leaving the SharePoint team. That's a real loss, overall compared to say the PowerShell team the state of blogging from the SharePoint team has been really poor over the last year, but as blogs come second best to shipping a product out the door perhaps that's understandable, hopefully things will improve once the RTM celebrations have finished. 

 

Sunday, November 19, 2006 3:34:24 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

In a previous post I've added content to the publishing pages in the legal and finance divisions and its useful to know what fields are in a list when working with the SharePoint API or say the Query By Content WebPart . Yes you can do using the UI but its a pain.

First get the SPWeb object for our site

$site=spweb "http://sps:2828/divisions/finance"
$web=$site.Openweb()

now show the lists on the site

$web.lists | select title,contenttypes

Title                                   ContentTypes                          
-----                                   ------------                          
Documents                               {Document, Folder}                    
Images                                  {Document, Folder}                    
Master Page Gallery                     {Master Page, Folder}                 
Pages                                   {Page, Article Page, Welcome Page, F...
Workflow History                        {Workflow History}                    
Workflow Tasks                          {Task, Folder}
                        


The Pages list holds the publising pages so let's list its fields


$web.lists["Pages"].Fields | select title, internalname, typedisplayname | sort title

Title                      InternalName               TypeDisplayName         
-----                      ------------               ---------------         
Approval                   Approval                   Workflow Status         
Approval Status            _ModerationStatus          Moderation Status       
Approver Comments          _ModerationComments        Multiple lines of text  
Article Date               ArticleStartDate           Date and Time           
Byline                     ArticleByLine              Single line of text     
Check In Comment           _CheckinComment            Lookup                  
Checked Out To             CheckedOutTitle            Lookup                  
Checked Out To             CheckoutUser               Person or Group         
Checked Out To             LinkCheckedOutTitle        Computed                
Client Limit               Client_x0020_Limit         Number                  
Collect Feedback           CollectF                   Workflow Status         
Collect Signatures         CollectS                   Workflow Status         
Contact                    PublishingContact          Person or Group         
Contact E-Mail Address     PublishingContactEmail     Single line of text     
Contact Name               PublishingContactName      Single line of text     
Contact Picture            PublishingContactPicture   Hyperlink or Picture    
Content Type               ContentType                Choice                  
Content Type ID            ContentTypeId              Content Type Id
       

etc...

There are a lot of fields, to get the count use $web.lists["Pages"].Fields | measure-object
That gives 92 fields for the Pages library.

To see the content publishing pages themselves use

$web.lists["Pages"].Items | select name, file, level

Name                       File                                           Level
----                       ----                                           -----
default.aspx               Pages/default.aspx                             Draft
Client1.aspx               Pages/Client1.aspx                             Published


and finally two functions for our toolbox to make calling these easier

function get-SPListFields([string]$URL, [string]$ListName)
{
$site=get-spweb $URL
$web=$site.OpenWeb()
$list=$web.Lists[$ListName]
$list.Fields
}

function get-SPListItems([string]$URL, [string]$ListName)
{
$site=get-spweb $URL
$web=$site.OpenWeb()
$list=$web.Lists[$ListName]
$list.Items
}

These functions will come in useful later when we approve pages in a site with PowerShell.

 

 

Sunday, November 19, 2006 3:09:58 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

One of the things that the PowerShell team has always pointed out is the importance of naming conventions in functions.
Most of the bult-in PowerShell functions have a verb-noun format i.e. get-process, get-service etc..
Keeping things consistent across the different types of providers helps with the learning process as you can almost guess what a cmdlet would be called once you know the noun.

Up to now I thought this was just a syntax sugar but this blog entry from the main PowerShell guy Jeffrey Snover points out an important benefit if you name your functions correctly.
When you type a function name, and PowerShell tries to match the correct code to call, it will automagically add a get- to the function name and try to match on that if it fails to find a function of the exact name.

Heres an example take the simple get-SPSite function

function get-SPSite([string]$url)
{
   new-object Microsoft.SharePoint.SPSite($url)
}

I've been using this as $a=get-spsite "http://server" but I could have used $a=spsite "http://server". Its a small thing but if you're typing on the command those extra four characters add up plus it almost looks like C# code not having to use a lengthy namespace prefix.

Likewise for get-SPWeb

function get-SPWeb([string]$url)
{
   $site=new-object Microsoft.SharePoint.SPSite($url)

   $site.OpenWeb()
}

 


 

Sunday, November 19, 2006 3:04:49 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, November 12, 2006

Ok now we have the main structure of our Portal with the new Areas we wanted and we need to start adding some content.

As the Area's we have created are Publishing sites we're going to start using the new Publishing API's in WSS to add content.

The first function to help us with this is called Add-Content. This function takes a Site Collection URL, Area URL, Contents Title, Content Text in HTML format and a check-in comment. This function adds the given text and title to the default content page in the Publishing site.
With a few changes you could modify it to add to a specific content page.

# Function: Add-Content
# Description: Add the given text and title to the default publishing page in the publishing web
# Parameters: SiteCollectionURL URL for the root of the Site Collection
# Area Relative URL to the site/subweb/area
# Title Title string for the page
# Text Content to publish
# Comment Checkin comment
function Add-Content($SiteCollectionURL, $Area, $Title, $Text, $Comment)
{

$url = $SiteCollectionURL + "/" + $Area

write-host "Adding content to " $url

$site = new-object Microsoft.SharePoint.SPSite($url)

$web = $site.OpenWeb()

$pubweb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web)

$pp=$pubweb.DefaultPage

$pp.CheckOut()

# Set the properties
$item=$pp.Item
$item.set_Item("Title",$Title)
$item.set_Item("PublishingPageContent",$Text)
$item.Update()

# Checkin, Approve and Publish
$pp.CheckIn( $Comment )
$pp.Approve( $Comment )
$pp.Publish( $Comment )


}

Note the set_Item syntax, the SharePoint item property seems to conflict with PowerShell's built in properties.
This code assumes Moderation is turned on the Publishing Site.

So we have a handy function to add content but again I want to source that content from an external source.
In this case it's an XML file called content.xml the start of which looks like this:
<contents>
<content>
<area>Divisions/Finance</area>
<title>
Finance Division
</title>
<text>&lt;b&gt;Lorem ipsum dolor sit amet, consectetuer adipiscing elit.&lt;/b&gt;&lt;hr/&gt;&lt;br/&gt; Nullam hendrerit lacinia purus. Proin vulputate porta nisl. Aliquam commodo lobortis lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed quis leo imperdiet nisl ultrices fringilla. Praesent enim est, commodo sed, pharetra mattis, condimentum ut, arcu. Morbi aliquet lacus vel elit. In quis nibh. Vestibulum tincidunt. Sed quis sem.
Maecenas lobortis convallis dolor. Nunc rutrum, nunc ac elementum tempor, velit est vehicula libero, eu fermentum lacus lacus ac diam. Curabitur risus quam, dignissim ut, mollis vel, nonummy at, metus. Mauris elit libero, interdum sit amet, sollicitudin nec, eleifend vel, magna. Aliquam at ipsum. Ut rutrum convallis turpis. Quisque urna quam, tincidunt id, pretium a, dignissim ac, neque. Donec ipsum. Sed ornare pretium diam. Phasellus massa. Morbi porttitor purus eget turpis. Sed vel lectus. Etiam egestas nibh vitae augue. Ut felis arcu, fermentum sed, venenatis eget, tincidunt et, lorem. Etiam commodo nisi ut nisl. Morbi augue enim, accumsan eu, dignissim a, scelerisque non, nunc.
Praesent mauris ante, pretium eget, volutpat quis, congue eget, odio.&lt;br/&gt; Praesent sed orci nec sapien molestie viverra. Aenean pulvinar dictum mauris. Quisque ligula est, vestibulum id, consequat vel, ullamcorper id, libero. In tristique. Duis turpis augue, egestas sed, semper eget, dignissim sed, nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur feugiat dolor cursus lacus. Curabitur dui augue, tempus id, sollicitudin vel, pharetra a, augue. Donec vestibulum dictum ligula. Ut velit dui, dignissim at, interdum quis, convallis a, arcu. Vivamus neque lorem, sollicitudin et, malesuada sed, rhoncus eu, pede. Aliquam hendrerit imperdiet lacus. Duis iaculis fringilla risus. Aliquam pretium ante quis arcu. Nam erat est, porttitor non, venenatis at, auctor sed, libero. Sed condimentum, ante vitae dignissim egestas, dolor nisl rutrum elit, nec ultrices lacus ipsum et pede. Duis dolor turpis, lacinia eu, facilisis ut, fermentum sit amet, nisl. Proin imperdiet mauris a lacus. Nullam suscipit imperdiet tellus.
</text>
........

Its a simple format that has the relative URL of the destination area, the content title and the contents in encoded HTML in its XML nodes. I've encoded the HTML as support for CDATA sections is not as easy as it should be in PowerShell at the moment.

Now I was going to use the import-xml cmdlet that was available in Monad but that seems to have disappeared in recent builds so to import this file I'm going to use the get-content cmdlet, create a variable from it (the $() syntax) and cast it to XML

$xml=[XML]$(get-content Content.xml)

The [XML] is a shorthand way to load the XML into an XMLdocument class
so

$xml | get-member


shows the typename as being the XmlDocument class. If you're used to using this in C# or VB.Net then you have access to the class as normal but the cool thing about PowerShell is the $xml variable now has extra properties that match the nodes in the XML file

$xml.contents.content


will list the content nodes and

$xml.contents.content[0].area


will show Divisions/Finance


The import-content function will do all the hard work.
I'm just piping the list of content nodes into the add-content function and referencing the node name as properties

# Function: import-content
# Description: Use the XMl import file to import some sample content into the Publishing Web
# Parameters: SiteCollectionURL URL for the root of the Site Collection
# ImportFile XML file containing the sample data
function import-content($SiteCollectionUrl,$ImportFile)
{

$xml=[XML]$(get-content $ImportFile)

# Loop through the xml items
$xml.Contents.Content | foreach-object { Add-Content $SiteCollectionUrl $_.Area $_.Title $_.Text }


}


So with a few lines of code this function is loading the contents of the XML file and piping the values of the nodes into our add-content function.

Lets have a look at what the Portal looks like so far


All this and we have not had to use the MOSS UI at all!

Now all those imported content pages will need to be approved. Do it by hand maybe? nope, thats our next PowerShell powered step.

 

 

Sunday, November 12, 2006 7:04:46 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, November 06, 2006

In this step I want to start filling out the Portal with some business areas from a list that has been defined for us

We're going to use a CSV file in the same way as we did for the users. The start of the file looks like this

AreaURL, AreaName, AreaTitle, AreaDescription, AreaTemplate
"Divisions","Divisions","Divisions Home Page", "This area includes links to content based on divisions in the company.", "BLANKINTERNET#2"
"Divisions/Sales","Sales","Sales Home Page", "This area includes information related to sales.", "BLANKINTERNET#2"
"Divisions/Support","Support","Support Home Page", "This area includes information related to support.", "BLANKINTERNET#2"
"Divisions/HumanResources","Human Resources","Human Resources Home Page", "This area includes information related to human resources.", "BLANKINTERNET#2"
"Divisions/Marketing","Marketing","Marketing Home Page", "This area includes information related to marketing.", "BLANKINTERNET#2"


We have a AreaURL column which is the URL relative to the site collection root and we also have the Area Name, Title and description.
The AreaTemplate field in this example is BLANKINTERNET#2 (format is WebTemplate#Configuration).
Now I know this is the Publishing Site template under the Publishing tab but we could just as easily create a team site, subsites of blogs or WIKI's etc.
The easist way to find out what template and configuration to use is to create a site of the type you want through the UI and use powershell to find out what template is used

Run these commands to get a list of the Templates in use on the portal

$sp=new-object microsoft.sharepoint.spsite("http://sps:2828")
$sp.allwebs | select serverrelativeurl, webtemplate, configuration


also checkout Dan Winter's list of the MOSS 2007 templates on his blog http://blogs.msdn.com/dwinter/archive/2006/07/07/659613.aspx


Now to the PowerShell functions
First a simple wrapper to add a new site to the Site Collection: add-spweb

# Function:         add-spweb
# Description:        Create a new Web
# Parameters:        SiteCollectionURL     URL for the root of the Site Collection
#            WebUrl         relative URl of the sub site
#            Title             Title string
#            Description         Description string
#            Template        Template to use
#
function add-spweb([string]$SiteCollectionUrl, [string]$WebUrl, [string]$Title, [string]$Description, [string]$Template)
{


    # Create our SPSite object
    $spsite=new-object Microsoft.SharePoint.SPSite $SiteCollectionUrl

    # Add a site
    $spsite.Allwebs.Add($WebUrl, $Title, $Description ,[int]1033, $Template, $false, $false)
    
    # Note: The new SPWeb will be returned from this call
}

And then a function that will import the CSV file, create a set of objects for each Area line in the CSV file and pipe the list to add-spweb to add a new site.


# Function:         Import-Sites
# Description:        Create a set of subwebs as listed in the import CSV file
# Parameters:        CSVFile         Location of the CSV file containing the list of webs
#            SiteCollectionURL     URL for the root of the Site Collection    
#    
function Import-Sites([string]$CSVFile, [string]$SiteCollectionURL)
{
    Import-Csv $CSVFile | foreach-object { add-spweb $SiteCollectionURL $_.AreaURL $_.AreaName $_.AreaDescription $_.AreaTemplate } | foreach-object {$_.Navigation.UseShared=$true; $_.Update() }
}

I'm also setting UseShared to true which tells the subareas to use the main portal navigation elements.

Use it like this

import-sites "ContosoAreas.CSV" http://sps:2828

The next step will be to import some content into our Publishing Areas...

 

Monday, November 06, 2006 9:32:42 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, November 01, 2006

Ok now we have the users in AD we can add them to the portal. Again we can use import-csv to add them from the users CSV file

heres the routine

# Function:         Add-UsersToSP
# Description:        Add each user in the import CSV file to the given role
# Parameters:        SiteCollectionURL     URL for the root of the Site Collection    
#            UserFile         Location of the CSV file containing the users
#            Domain            Users domain
#            Role             Name of the SharePoint Role e.g Reader, Contribute
#
function Add-UsersToSP([string]$SiteCollectionURL,[string]$UserFile, [string]$Domain, [string]$Role)
{
Import-Csv $UserFile | foreach-object {$spsite=new-object Microsoft.SharePoint.SPSite($SiteCollectionURL) } {$spsite.RootWeb.Roles[$Role].AddUser($Domain + "\" + $_.LoginName, $_.Email, $_.DisplayName, "") }
}

In this script we get a SPSite object, get its rootweb and index into the roles collection to return the role we want. We then use the AddUser method to add the user.

And we call it like this Add-UsersToSP "http://sps:2828" "users.csv" "contoso" "contribute"

Now there is something a little different going on in this script. In the foreach-object loop there are 2 scriptblocks (the curly braces). This is because you can have up to 3 scriptblocks in the foreach clause: Begin, Process and End

The Begin scriptblock will be called once at the start of the iteration, Process is called for each object in the collection and End will be called at yes the end of the iteration allowing you to clean up your objects. I don't have an end in this case but you could call dispose on the SPSite object.

In v3 the Roles collection is deprecated and you should start using the new SPRoleDefinition and SPRoleAssignment classes. These new classes have full support for the new security features but for now I'm keeping things as simple as I can. 

Now is a good time to point out how easy it is to explore the SharePoint OM interactively, in this case the Roles collection.

First of all from the command line do this

PS C:\demo> $spsite=new-object microsoft.sharepoint.spsite("http://sps:2828")
PS C:\demo> $spsite.rootweb.roles

this produces a listing like this

Name : Full Control
Users : {}
Groups : {SUGUK Intranet Owners}
Type : Administrator
Description : Has full control.
Xml : <Role ID="1073741829" Name="Full Control" Description="Has ful
l control."
Type="5" />
ID : 1073741829
PermissionMask : FullMask
ParentWeb : SUGUK Intranet

Name : Design
Users : {}
Groups : {Designers}
Type : WebDesigner
Description : Can view, add, update, delete, approve, and customize.
Xml : <Role ID="1073741828" Name="Design" Description="Can view, add
, update, delete, approve, and customize."
Type="4" />
ID : 1073741828
PermissionMask : 1012866047
ParentWeb : SUGUK Intranet

.....

You can use the select CmdLet to produce a simple table listing

PS C:\demo> $spsite.rootweb.roles | select name, users

Name                                    Users                                 
----                                    -----                                 
Full Control                            {}                                    
Design                                  {}                                    
Manage Hierarchy                   {}                                    
Approve                                {}                                    
Contribute                             {Brian Ballack, Walter French}        
Read                                    {}                                    
Restricted Read                      {}                                    
Limited Access                       {NT AUTHORITY\authenticated users, S...
View Only                              {}  

 

If we want to see the full object of say the RootWeb you can do this

PS C:\demo> $spsite.rootweb | get-member

or just its properties

PS C:\demo> $spsite.rootweb | get-member -membertype property

or just its methods

PS C:\demo> $spsite.rootweb | get-member -membertype methods

I find myself using this lot before coding C# against an object as I can try out the API and mess around with it before running up VS2005

The next step will be to create a host of portal areas as defined in a CSV file.

 

 

 

 

 

Wednesday, November 01, 2006 8:05:34 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, October 31, 2006

Step 2 in our series is to add some users to Active Directory.

First we need a simple routine to add users to Active Directory

Update 3-11-2006 : The original add-aduser would only work in versions of PowerShell pre RC2. RC2 included some breaking changes to how the DirectoryEntry object handled. I;ve added an RC2 compatible routine.

Pre RC2 version


function add-aduser([string]$LoginName, [string]$DisplayName, [string]$FirstName, [string]$LastName)
{

    $cn=$LoginName
    $sam=$LoginName
    $pw="P@ssword1"


    $ad= new-object System.DirectoryServices.DirectoryEntry
    $u = $ad.get_Children().Find("CN=Users")
    $NewUser = $u.get_Children().add("CN=$cn",'User')

    
    $NewUser.InvokeSet("sAMAccountName",$sam)
    $NewUser.InvokeSet("displayName",$DisplayName)
    $NewUser.InvokeSet("FirstName",$FirstName)
    $NewUser.InvokeSet("LastName",$LastName)
    
    $NewUser.CommitChanges()

    $ad=new-object System.DirectoryServices.DirectoryEntry
    $u = $ad.get_Children().Find("CN=Users")

    $NewUser= $u.get_Children().Find("CN=$cn");
    $NewUser.Invoke("SetPassword",$pw)
    $NewUser.InvokeSet("AccountDisabled",$false)

    # set that the password never expires
    $NewUser.userAccountControl[0] = $NewUser.userAccountControl[0] -bor (65536)

    $NewUser.CommitChanges()

}

 

RC2 Version


function add-aduser([string]$LoginName, [string]$DisplayName, [string]$FirstName, [string]$LastName)

{

   $cn=$LoginName
   $sam=$LoginName
   $pw="P@ssword1"

   # Get an ADSI object for the default domain
   $ad= [ADSI]""

   # Get the Users OU as default
   $ou = $ad.psbase.Children.Find("CN=Users")

   # Add the user
   $NewUser = $ou.psbase.Children.Add("CN=$cn",'User') 

   # Set the basic properties
   $NewUser.Put("sAMAccountName",$sam)
   $NewUser.Put("displayName",$DisplayName)
   $NewUser.Put("givenname",$FirstName)
   $NewUser.Put("sn",$LastName)

   # Commit changes 
   $NewUser.SetInfo() 

   
   # Set our password 
   $NewUser.psbase.Invoke("SetPassword",$pw) 

   # And enable the account
   $NewUser.psbase.InvokeSet("AccountDisabled",$false

   # set that the password never expires
   $NewUser.userAccountControl[0] = $NewUser.userAccountControl[0] -bor (65536) 

   # Commit changes
   $NewUser.SetInfo()


}



This gets us a function we can call to add test users. Note we're hardcoding the OU to users, to make this more production you'd want to parameterize the OU, passwords and set the AccountControl flags to Change Password on next login, there's a lot of other resources out there to help with that.

So If we put that in a script file loaded from our Profile we can call it like this

add-aduser "joeb" "joe blogs" "joe" "blogs"

That's nice but with PowerShell we can do better. We really want to data drive this and the built-in import-csv function will parse a CSV and create a collection of objects that match the CSV schema.

Take a simple User's CSV file like this

LoginName, DisplayName, FirstName, LastName, Email

brianb, Brian Ballack, Brian, Ballack, brianb
walterf, Walter French, Walter, French, walterf


 

Now if we run an import-xml command on the CSV file this is the output

 

Each line has been parsed and an object created. To see this more clearly we can use the get-member command to reflect over the object and see what the object looks like.

 

(Note: it will be PSCustomObject not MshCustomObject in post RC0 builds , I'm running Exchange 2007 on this VPC which requires PowerShell RC0)

As you can see the CSV columns have been added as a NoteProperty property (this is part of the extensible type system).

So now we can create a really simple function to import a CSV file of users, pipe the output to a foreach loop and add each user to Active Directory.

# Function:         Import-Users
# Description:        Create users in active directory as listed in the import CSV file
# Parameters:        UserFile         Location of the CSV file containing the users
#
function Import-Users([string]$UserFile)
{
    Import-Csv $UserFile | foreach-object { add-aduser $_.LoginName $_.DisplayName $_.FirstName $_.LastName }
}

Note that when you receiving a collection of objects in the pipeline in a scriptblock you have to loop over each object. The $_ is a special variable that gives you the current object in the loop which you can use to access properties and methods.

To call we just do

Import-Users Users.CSV

With this power 500 users are as easy as 1 user.

We're not limited to just using CSV files, I've just picked that format as I'm always amazed at the amount of data I get given in Excel spreadsheets and word documents and CSV is the easiest intermediate format to work with. There is also an import-xml command which we will use later to import an XML file containing portal content. As Powershell can call any .Net library the System.Data set of classes could also be used to pull information from databases.

The next entry will be adding our users to SharePoint.

Tuesday, October 31, 2006 9:13:15 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
 Monday, October 30, 2006

Hi,

Just like to thank all those who attended the Presentation last Thursday on controlling SharePoint with PowerShell and Extending the Administration Object model.

I know it was pretty fast paced and technical so I plan to follow it up with a series of blogs entries recapping the steps and scripts in greater detail.

Some of the topics covered were:

   Powershell Basics

   Exchange produces PowerShell samples from the new UI console

   Creating a new SharePoint WebApplication and applying a portal template

   Adding users defined in a CSV file to Active Directory

   Adding those users defined in a CSV file to SharePoint roles

   Adding in webs as defined in a CSV file

   Adding content in an XML file to a Publishing Web

   Uploading a directory of files in 4 lines of Script.

   Creating a custom STSAdm command

   Creating an application to sync User Profile properties back to Active Directory

I've attached the PowerShell scripts and PowerPoint Slides.

I'm not able to post the sample code of the AD Sync application as parts of the code is proprietary but I will post a sample application that does a scheduled backup using exactly the same template.

Presentation-SPPowerShell.zip (46.78 KB)

Colin Byrne

Flexnet Consultants

Monday, October 30, 2006 10:53:00 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 

A quickie.

Heres 4 lines of code to upload a whole directory of files, in this case pictures, to a SharePoint document or image library.

The directory pictures contains the images.

The destination is the image library SiteCollectionImages for the portal running on port 2828

$wc = new-object System.Net.WebClient
$wc.Credentials = [System.Net.CredentialCache]::DefaultCredentials
function getdestname($filename){ "http://sps:2828/sitecollectionimages/" + $(split-path -leaf $filename)}
dir "pictures" | % { $uploadname=getdestname $_; $wc.UploadFile($uploadname,"PUT", $_.FullName) }

Have I mentioned PowerShell just rocks?

Monday, October 30, 2006 10:03:35 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

In this PowerShell and SharePoint series I'd thought I'd spin through, in order of use, the commands used to create the demo Portal site in the SUGUK user group presentation.

First step: Creating a Web Application using the API.

The new SPWebApplicationBuilder object makes this easy. This object creates a Web Application using a default port and the default settings, you can override any settings you like in your script but for simplicity I'm just changing the default timezone.

SPWebApplicationBuilder also creates an AppPool to go along with the new IIS Website which has Network Service as the user. In Beta 2TR that account doesn't have the correct permissions so you need to change it to a local administrator. I tend to dev on a Virtual machine DC so I'd use the domain admin.

So we have three routines

   new-SpWebApplication - Create the IIS Website and SharePoint settings

   set-apidentity   - set the Application Pool credentials

   get-defaulttimezoneid   - returns the Greenwich Meantime TimeZone ID (adjust this one to suit)

We also then need to create the set of webs that will live inside the Web Application, this is done by calling the Add Method on the Sites method of the Web Application passing it various parameters the most important of which is the template ID, in this case SPSPORTAL to give us the default SharePoint Intranet portal look.

The Powershell commands to tie all this together are

$webapp=new-SPWebApplication

set-apIdentity "http://sps:yourportnumber" "contoso\administrator" "password"

#Create Portal Site Collection

$webapp.Sites.Add("/", "SUGUK Intranet","SUGUK Intranet",1033, "SPSPORTAL", "contoso\administrator", "administrator", "administrator@contoso.com")

Here's new-SPWebApplication

# Function:         new-SPWebApplication    
# Description:        Create and return a new SPWebApplication object
#             Use the default values which will create the web site at a random port
# Parameters:        none
#
function new-SPWebApplication()
{

    # Get our local SPFarm object
    $spfarm = [Microsoft.SharePoint.Administration.SPfarm]::Local

    # Use the new SPWebApplicationBuilder
    $appbuilder= new-object Microsoft.SharePoint.Administration.SPWebApplicationBuilder $spfarm

    # Create Web Application with the default settings
    
    $webapplication = $appbuilder.Create()

    # Set the timezone to Greenwich Mean Time
    $timezone=get-defaulttimezoneid
    $webapplication.DefaultTimeZone = $timezone.ID
    $webapplication.Update()

    # Actually queue the application for creation
    $webapplication.Provision()

    # return the SPWebApplication object
    $webapplication

}

 

# Return the Default TimeZone as used for the majority of our sites
#
function get-defaulttimezoneid
{
    [Microsoft.SharePoint.SPregionalSettings]::Globaltimezones | where-object { $_.Description -like "*greenwich*" }
}


 

Now to change the Credentials for the AppPool is also really simple in V3. The SPWebApplication object exposes a ApplicationPool property which you can use to set its credentials

# Function:         set-apidentity    
# Description:        Set the credentials for the application pool for the given Web Application
# Parameters:        Url        Site Collection URL
#            UserName     UserName
#            Password     Password
function set-apidentity([string]$SiteCollectionURL, [string]$UserName, [string]$Password)
{

        
    $webapp=get-spwebapplication $SiteCollectionURL
    
    
    $webapp.ApplicationPool.CurrentIdentityType= [Microsoft.SharePoint.Administration.IdentityType]::SpecificUser
    $webapp.ApplicationPool.UserName= $Username
    $webapp.ApplicationPool.Password= $Password
    
    # Save the settings
    $webapp.ApplicationPool.Update()

    # Roll the settings out via a Admin Job
    $webapp.ApplicationPool.Provision()


}

 

To me this is way easier than using the UI.

 

Monday, October 30, 2006 9:46:17 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, October 25, 2006

Hi,

I'm about to start off a new series of entries about using Powershell to control SharePoint.  There's a big gap in the ability to script SharePoint between stsadm commands and custom .net programs and PowerShell fills it nicely.

All my entries from now on will focus on SharePoint version 3.

PowerShell is 'DOS for the .NET generation' (c) :-) and its a fantastic way to both interactively control SharePoint via its Object Model from a command line and to create scripts that can be run in a batch. Its currently at version RC2 and is available from here http://support.microsoft.com/kb/925228
It installs as a windows update package rather than a separate install as it used to in earlier betas, thats a sign of its being a core element of Windows going forward.

Ok lets do some Powershell SharePoint basics.

First although Powershell can call any .net class the assembly must be loaded first.
By default Powershell loads a small list of assemblies, this command will get the list of loaded assemblies:

[System.AppDomain]::CurrentDomain.GetAssemblies()

The syntax for calling a .NET class's static method is for the brackets go around the Class name and the double colon between the class and the static method

The output from that is a little hard to read lets just list the files involved

[AppDomain]::CurrentDomain.GetAssemblies() | foreach-object { split-path $_.Location -leaf } | sort

Here's the list of files the above command gives on my machine

Microsoft.PowerShell.Commands.Management.dll
Microsoft.PowerShell.Commands.Utility.dll
Microsoft.PowerShell.Commands.Utility.resources.dll
Microsoft.PowerShell.ConsoleHost.dll
Microsoft.PowerShell.ConsoleHost.resources.dll
Microsoft.PowerShell.Security.dll
Microsoft.PowerShell.Security.resources.dll
mscorlib.dll
System.Configuration.Install.dll
System.Data.dll
System.DirectoryServices.dll
System.dll
System.Management.Automation.dll
System.Management.Automation.resources.dll
System.Management.dll
System.ServiceProcess.dll
System.Xml.dll

As you can see SharePoint is not one of those in the list, also missing that could be useful is the System.Web assembly

There are a few ways to load an assembly onto your AppDomain but the easiest is to call the static method LoadWithPartialName on the Assembly class.

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

LoadWithPartialName is great because you dont need to specify the full name to the assembly which in SharePoint v3 would be

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

and for MOSS

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

Now a lot of people will cry LoadWithPartialName has been deprecated as MS have got concerned how it has to make to many guesses over locating the assembly.
Personally I want to see a method that will simply load the latest version of the assembly I've told it to load, in a controlled environment like a SharePoint server thats exactly the behaviour I want to see. <rant> MS for god's sake just document the method properly and let devs choose whether to use it or not. </rant>

Anyway if you want to use the Load method take a look at this blog entry: http://www.leeholmes.com/blog/HowDoIEasilyLoadAssembliesWhenLoadWithPartialNameHasBeenDeprecated.aspx

Now you don't want to type the load commands each time so put them in your Powershell Profile script.
This changes location a lot depending on which version of Powershell your running so see this entry for the full details http://www.leeholmes.com/blog/TheStoryBehindTheNamingAndLocationOfPowerShellProfiles.aspx

So now what can we do with this?

Well first lets get a site collection we can play with

First create an SPSite object and pass it a valid site url


      $spsite= Microsoft.SharePoint.SPSite("http://portal.contoso.com")

to see the contents of the object just type the object on the command line

$spsite

To just get a list of the methods on the SPSite object

   $spsite | get-member -membertype method

To just get a list of the properties on the SPSite object

   $spsite | get-member -membertype property

To get a list of all sub sites

   $spsite.AllWebs

Get a list of webs ordered by the last time that the contents have been changed

   $spsite.allwebs | select LastItemModifiedDate, URL , Created | sort LastItemModifiedDate

There's lots more that you can do and I'll post them as I go.

 

Wednesday, October 25, 2006 10:01:53 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

Whoa, its been a while since I've blogged but sundry projects and SharePoint v3 has taken up all my time.

Just to let you know that I'm presenting at the UK SharePoint users group meeting this Thursday the 26th October.

I'm doing 2 sessions; one on using Powershell with SharePoint v3 in which I'll go through Powershell basics and how to use it to control SharePoint. Then I will attempt to build a portal without touching any settings in the UI! Wish me luck.

The second one will explore the new extensible admin object model.
This will demonstrate creating custom stsadm commands along with creating an application that syncs changed MOSS User profile information back to Active Directory.
It will be a small application integrated into SharePoint and built using the new SPService, SPServiceInstance and SPJobDefinition classes.
I'll be describing how they work together, its cool stuff.

Everybody is welcome (you just need to register on the suguk site) so if you are in London this Thursday it would be great to see you.  The meeting kicks off at 6.30pm

If you plan to attend just register on the SUGUK site and leave your name on this list
http://suguk.org/forums/thread/1490.aspx

cheers,
Colin

Wednesday, October 25, 2006 8:37:47 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, April 02, 2006

Flexnet Consultants have just released a brand new free Web Part.

The PhotoGrid WebPart will show a nXn grid of Picture Library images with a each image randomly chosen to be enlarged. This is similar to the Flikr Photobadge.

You can set the number of pictures on each row and the number of rows along with the time the enlarged picture is displayed (Display time).

Our Web Parts page uses a Flash embedded demo to show you how it would look.

http://www.flexnetconsult.co.uk/WebParts/WebParts.htm

Sunday, April 02, 2006 5:49:45 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 

Version 1.2 of our free Flash Slideshow Web Part is released.

The Slideshow Web Part will show a rotating display of all the pictures in the Picture Library with an adjustable fade transition between each picture.

This version has better support for SharePoint sites running on non standard http ports.

It now also supports all the image formats that the Picture Library does.

http://www.flexnetconsult.co.uk/WebParts/WebParts.htm

Sunday, April 02, 2006 5:43:48 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

Sometimes when working in client-side javascript you'd like to know your Windows username for instance to add to the root Outlook Web Access URL for say a contacts search i.e. http://flexnetowa/exchange/colinb/contacts/?cmd=search 

Now the stock answer whenever this comes up in the SharePoint newsgroups is: it can't be done, use server side code to render it out.

Thats fair enough and easy to do, I've created a WebPart that stores a users details as a JavaScript object so it can be referenced in code. But what if I don't want to install yet another server-side Web Part.

In the interests of providing code you won't get anywhere else here's the tip.

Now its true the SharePoint Web Services provide no method to tell you who you are (side-note: I suspect you may be able to do this by creating a CAML query against the userinfo table but I've not managed to get that working) but you can get your username by using the FrontPage RPC and calling author.dll, this is how Word displays who you are on its TaskPane when you open a Word document in a document library.

When you call the author.dll ISAPI DLL with the 'open service' method name it returns a host of information about the server and the services it provides.

I'm using the XmlHttpRequest object again which despite the name can also be used to retrieve a text document not just an XML one.

heres the returned information

<html><head><title>vermeer RPC packet</title></head>
<body>
<p>method=open service:6.0.2.6356
<p>service=
<ul>
<li>service_name=
<li>meta_info=
<ul>
<li>vti_defaultlanguage
<li>SW|en-us
<li>vti_usernames
<li>VR|
<li>vti_servercharsets
<li>VX|windows-1257 big5 windows-1252 windows-874 utf-8 windows-1251 windows-1256 euc-kr gb2312 windows-1253 windows-1258 koi8-r iso-8859-1 gb18030 iso-2022-jp ks_c_5601-1987 windows-1250 windows-1255 us-ascii euc-jp unicode unicodeFFFE windows-1254 iso-8859-2 iso-8859-15 shift_jis
<li>vti_scnoprompt
<li>IX|1
<li>vti_toolpaneurl
<li>SX|http://sharepoint.flexnet.ds/_layouts/1033/toolpane.aspx
<li>vti_assemblyversion
<li>SX|Microsoft.SharePoint, Version&#61;11.0.0.0, Culture&#61;neutral, PublicKeyToken&#61;71e9bce111e9429c
<li>vti_webtemplate
<li>IR|1
<li>vti_hasonetlayoutfiles
<li>BR|true
<li>vti_navbuttonnextlabel
<li>SR|Next
<li>vti_casesensitiveurls
<li>IX|0
<li>vti_htmlextensions
<li>SX|.html.htm.shtml.shtm.stm.htt.htx.asp.aspx.alx.asa.hta.htc.jsp.cfm.odc.dwt.php.phtml.php2.php3.php4.
<li>vti_approvallevels
<li>VR|Approved Denied Pending&#92; Review
<li>vti_themedefault
<li>SR|none
<li>vti_welcomenames
<li>VX|default.htm default.aspx
<li>vti_servertz
<li>SX|+0100
<li>vti_adminurl
<li>SX|http://sharepoint.flexnet.ds/_layouts/1033/settings.aspx
<li>vti_showhiddenpages
<li>IW|1
<li>vti_categories
<li>VR|Business Competition Expense&#92; Report Goals/Objectives Ideas In&#92; Process Miscellaneous Planning Schedule Travel VIP Waiting
<li>vti_featurelist
<li>VX|vti_RulesScript vti_ServerIndexServer vti_TimedDocEvents vti_ServiceMarkUrlDirExec vti_DocSaveToDB vti_ServiceMarkUrlDirBrowse vti_ACAll vti_ServerODBC vti_ServerASP vti_ServiceMarkUrlDirScript
<li>vti_hasfulltextsearch
<li>IX|1
<li>vti_defaultcharset
<li>SR|windows-1252
<li>vti_navbuttonuplabel
<li>SR|Up
<li>vti_httpdversion
<li>SX|Microsoft-IIS/6.0
<li>vti_serverlanguages
<li>VX|en-us
<li>vti_sourcecontrolproject
<li>SX|&#60;STS-based Locking&#62;
<li>vti_doclibwebviewenabled
<li>IX|1
<li>vti_extenderversion
<li>SR|6.0.2.6361
<li>vti_ignorekeyboard
<li>IR|0
<li>vti_navbuttonprevlabel
<li>SR|Back
<li>vti_longfilenames
<li>IX|1
<li>vti_sourcecontrolsystem
<li>SX|lw
<li>vti_username
<li>SX|FLEXNET&#92;colinb
<li>vti_navbuttonhomelabel
<li>SR|Home
<li>vti_sourcecontrolcookie
<li>SX|fp_internal
<li>vti_sitecollectionurl
<li>SX|/
<li>vti_language
<li>IR|1033
</ul>
</ul>
</body>
</html>

 

Notice my windows username is listed against the vti_username item.

Using the XmlHttpRequest object to call author.dll synchronously (we're going to wait for the call to return)

xmlrequest = "method=open service:6.0.2.6356&service_name=";

if (!xmlhttp) xmlhttp=GetHTTPObject();

xmlhttp.open("POST", "/_vti_bin/_vti_aut/author.dll ",false);
xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
xmlhttp.setRequestHeader("X-Vermeer-Content-Type","application/x-www-form-urlencoded");

xmlhttp.send(xmlrequest);

The responseText property on xmlhttp contains the returned document.

The hardest bit is parsing the result to extract the username.

Once that is done I show the username in a DIV element.

So the next time someone asks this question we'll be able to give them some options.

ClientSideUsername.dwp (2.92 KB)

Sunday, April 02, 2006 3:43:59 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

In part 1 we have got to the point where we have called the SharePoint lists.asmx Web Service and retrieved the list of all Lists on our SharePoint site.
We now need to filter down to only those lists that are based on the links list template, this is template type 103, we can use an Xpath expression to filter the items.

// Filter for the Links lists items
links_lists=ajaxsp_GetSoapResponseItems(xmlhttpLists.responseXML, "//sp:List[@ServerTemplate='" + ListTemplate + "']", moz_NSResolver1 );

We loop through the list items and use the getAttribute of the XML Dom node to retrieve the fields we are interested in and concatenate a string of <LI> items which we then set to the innerHTML property of a DIV element.

Each list item on the left is a hyperlink that calls the javascript function GetLinkItems when clicked.
This function is passed three parameters - the list Guid, list Title and the Default View URL which normally is the full list of links.

function GetLinkItems(ListGuid, ListName, ListDefaultView)


To get the list items we need to call the GetListItems method and pass it the listName parameter.

// Create SOAP Request
xmlrequest = SoapPrefix + '<GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">'
xmlrequest = xmlrequest + '<listName>' + ListGuid + '</listName></GetListItems>' + SoapPostfix;

To send the SOAP request we do the same as before only this time we set the GetListItems SOAP method

xmlhttpItems.open("POST", "_vti_bin/Lists.asmx?op=GetListItems ",true); xmlhttpItems.setRequestHeader("Content-Type","text/xml; charset=utf-8"); xmlhttpItems.setRequestHeader("SOAPAction","http://schemas.microsoft.com/sharepoint/soap/GetListItems ");
// Set event handler
xmlhttpItems.onreadystatechange=ajaxsp_GetLinkItemsEvent;
// Post request
xmlhttpItems.send(xmlrequest);

Our GetLinkItemsEvent parses the XML and formats the data into a TABLE element with the ms-summarycustombody style to match the theme of the SharePoint site.
Each hyperlink has the target=”_blank” attribute which causes the links to open in a new window, my preferred method of opening links. This can easily be changed.


Creating a new Item Form

Now we have displayed our list of links we need to look at adding a new item.
In the Web Part after the script blocks I have some HTML that contains a DIV element. The DIV element has a style that sets its position to be absolute and hidden.
Inside the DIV I’ve create a HTML table that sets the layout of the AddNew form.
To render the URL editing and Notes fields I use the built-in functions provided in the SharePoint javascript file ows.js to handle input fields on SharePoint forms.
The complete list of fields type that can be used are:

DateField
URLField
NumberField
BooleanField
NoteField
RichTextField
TextField
FileNameField
GridChoiceField
ChoiceField

I only need to use the URLField and NoteField type.


These functions are called in javascript like this
var fld = new URLField(frm,"URL","URL", "","");
fld.fRequired = true;
fld.IMEMode="";fld.BuildUI();

The URLField function takes parameters of a OWSForm, Internal Field Name, Display Name, Default URL Value , Default Description.
BuildUI takes care of creating the input HTML which is great as it automatically creates two input fields, one for the URL and one for the description.

I create a NoteField object like this.

fld = new NoteField(frm,"Comments","Notes","");
fld.stNumLines = "6";
fld.IMEMode="";
fld.BuildUI();

The NoteField functions parameters are a OWSForm, Internal Field Name, Display Name, Default Note Field Value


Heres the function prototype for creating a OWSForm object:

function OWSForm(stName, fUseDHTMLOverride, stPagePath)

Creating the OWSForm is tricky in that it’s easy to create the OWSForm object in itself but it expects the name of the onpage form used in the WebPart page postbacks, that’s fine but it overwrites the onsubmit function which causes problems for any other component like the Web Part settings toolbar that need to post to the server. The way around this is to pass it a dummy name and then after the call set the Form name to the genuine article.
The second parameter is fUseDHTMLOverride which is used to force the use of DHTML when rendering the controls.
stPagePath is used to locate any extra files needed such as the datepicker’s template htm page.

Heres how its called in code

// Pass a dummy form name so it does not overwrite the Submit event
var _WPQ_frm= new OWSForm("DummyName" , true, "_layouts/");
// Now assign the proper form name it as it’s needed for the fields to locate their values
_WPQ_frm.stName=MSOWebPartPageFormName;

We use the global variable MSOWebPartPageFormName here which is defined in ows.js and available on all WebPartPages and gives the name of the FORM element used for postback. Note we don’t postback ourselves but the OWS Field elements think that they might.

I’ve laid out the new item form with the Javascript to create the edit fields embedded in a HTML table. This table is wrapped in a DIV which is initially hidden. The code to show and center the DIV element is Javascript 101 so I won’t go into details but check the function ajaxsp_DisplayNewForm if you’re interested.

I want to set focus to the Http edit field so we need to a reference to the Edit fields.
We can use getElementById for this but we need to know the name of the field on the form.
The key to this is to understand how the OWS form elements name themselves.

Fields are named by the internal function FrmStFieldNameFactory which does this:
return "OWS:" + name + ":" + stPart;
This concatenates OWS with the name of the field and the subfield name.

The URL field type is composed of two sub fields named URL and Desc. In this case we’re referencing the URL field and the URL subpart of that field.

So to reference the URL sub part of the URL field we can use this code.

var fldURL=document.getElementById("OWS:URL:URL");
if (fldURL) fldURL.focus();

 

Adding a new record via SOAP services

Now that we can get the URL, Description and Notes field values we use the UpdateListItems method of the Lists.asmx web service to add the new record.
This method takes a CAML string to do adds, updates and deletes.
This is an example CAML batch string we send:

<Batch>
<Method ID='1' Cmd='New'>
<Field Name='URL'>http/www.google.com, Description here - note comma and space</Field>
<Field Name='Comments'>My Comment String</Field>
<Method>
</Batch>

The ajaxsp_AddListItem routine assembles this string, places it inside the UpdateListItems SOAP string and sends it to the web service. 
The ajaxsp_AddListItemEvent handles the returned SOAP string which may include a faultstring node if the SOAP call fails or an ErrorText node if the new record fails to add in SharePoint. Once the insert succeeds we hide the new item form and refresh the list.

Thats almost it, there is some extra code where I store the last displayed list in a cookie so if the page refreshes I can display the list items again. This cookie is designed to only last for the browser session.


 

Summary

This has been a deep dive into calling the SharePoint Web Services from client side javascript but the actual methods involved are pretty basic and simple to use, most of the complications and extra code come from handling non IE browsers.

Some of the other ways that Web Services could be used include adding field by field server-side validation, filling comboboxes from SQL Server tables, getting unread email counts information from Exchange via WebDav.

Feel free to download the DWP file and browse the code for yourselves.

LinksLists.dwp (16.2 KB)

Sunday, April 02, 2006 2:55:49 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, April 01, 2006

On my personal SharePoint site I use the SharePoint Links type lists a huge amount, I have at least 20 of these and I like to flick between them a lot, having the links to hand is a great productivity booster.
I find the standard navigation just too slow and although the double-click minimize/restore Web Part helps what I really wanted was a windows application usability for these lists.


This is a perfect job for client-side JavaScript making SOAP calls to the SharePoint WebServices using DHTML to display the items. This kind of code now has the nifty name of AJAX although this is really AJAX-lite as the size of our framework is tiny and doesn’t need to handle complex object serialization/deserialization or data binding.

The goal is to have a WebPart that lists the Links based lists (must be an easier way to say that) and with a click display the items on the list, I also wanted to have an easy way to add items into the list. All code would be client-side javascript and also needed to work in Mozilla based browsers such as FireFox as I also use that a lot. Once the initial page has been rendered as normal by SharePoint there are no other postbacks or page refreshes needed so response times are very fast.


The Javascript will be initially be hosted in a Content Editor Web Part but I plan to create a server side WebPart also. Here’s a screen shot.

List of Lists

 

Add a new Item


 
SharePoint already does some AJAX already, albeit in a limited fashion, to handle getting and setting Web Part properties while editing the Properties of Web Parts. It posts to the WebPartPages.asmx SOAP service using the SaveWebPart and GetWebPart SOAP methods. Note the built-in SharePoint SOAP calls are only made if the browser is IE 5 and up. This and the fact that the library calls are hard-coded to use the WebPartPages web service only meant I had to create my own routines.

Soap requests by hand

Ok down to the nitty-gritty.
I’m going to assume you already have some understanding of what SOAP is but basically it is the method of sending an XML formatted request to a URL via HTTP and getting back a XML formatted response.
The first thing our Web Part needs is the list of Links type lists on the SharePoint site we are on.
To do this we will call the GetListCollection method of the Lists.ASMX web Service.
If you add /_vti_bin/lists.asmx page to your site URL you will see the methods this service exposes. Click the GetListCollection method to see the format of the SOAP request it expects.

The function GetLinksList populates the list of links and is the entry point into our code. We hook into the windows load event like this:

if (window.addEventListener)
window.addEventListener('load', _WPQ_AjaxObject.GetLinksList, false);
else
window.attachEvent("onload",_WPQ_AjaxObject.GetLinksList);

Note the use of the _WPQ_ token, this is replaced by our Web Part ID by the CEWP at runtime so avoiding duplicate global variables when there are two instances of the same Web Part on a page.

So we send a POST request to the URL /_vti_bin/Lists.asmx?op=GetListCollection and make sure we have a SOAPAction header set to the correct operation which is http://schemas.microsoft.com/sharepoint/soap/GetListCollection.
This will give use a list of all lists which we can filter for the Links’s type.

The body of the request will look like this:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
<GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>
</soap:Body>
</soap:Envelope>

This is a very simple request string and notice the majority of the text is the boilerplate SOAP XML envelope that will never change, so in the code I’ve created vars that contain this text called SoapPrefix and SoapPostFix

To submit the request we create a XMLHttpRequest object and use its send method asynchronously, the function that calls the List Web Service looks like this

function ajaxsp_GetLinksList()
{
var xmlrequest;

xmlrequest = aSoapPrefix+ '<GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>'
xmlrequest += SoapPostfix;

if (!xmlhttpLists) xmlhttpLists= GetHTTPObject();

xmlhttpLists.open("POST, "_vti_bin/Lists.asmx?op=GetListCollection",true);
xmlhttpLists.setRequestHeader("
Content-Type","text/xml; charset=utf-8");
xmlhttpLists.setRequestHeader("
SOAPAction","http://schemas.microsoft.com/sharepoint/soap/GetListCollection");
xmlhttpLists.setRequestHeader("Content-Length", xmlrequest.length);


// set the callback event
xmlhttpLists.onreadystatechange=ajaxsp_GetLinksListEvent;
// Send the request
xmlhttpLists.send(xmlrequest);

return true;

}

Two important functions are GetHTTPObject which returns the HttpRequest object for both IE and Mozilla based browsers and the ajaxsp_GetLinksListEvent which does the heavy lifting of processing the returned XML

GetHTTPObject needs to create different objects for IE and Mozilla because Mozilla has a built-in native Javascript object called XMLHttpRequest whereas IE uses the ActiveX XMLHTTP  object contained in MSXML libraries, there a quite a few versions of this library and the ProgID changes with each one but I just use either Msxml2.XMLHTTP  of Microsoft.XMLHTTP which should cover most modern machines with IE5.0 and up, interestingly IE 7 has now gained the native XMLHttpRequest object.


Heres a snippet of the returned XML.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body><GetListCollectionResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<GetListCollectionResult>
<Lists>
<List DocTemplateUrl="" DefaultViewUrl="/Lists/Company Links/AllItems.aspx" ID="{60E01147-6A7A-47F0-8CA1-4D88426280DE}" Title="1 Company Links" Description="" ImageUrl="/_layouts/images/itlink.gif" Name="{60E01147-6A7A-47F0-8CA1-4D88426280DE}" BaseType="0" ServerTemplate="103" Created="20050225 11:43:06" Modified="20060222 09:58:24" LastDeleted="20060220 17:42:54" Version="1" Direction="none" ThumbnailSize="" WebImageWidth="" WebImageHeight="" Flags="4105" ItemCount="16" AnonymousPermMask="" RootFolder="" ReadSecurity="1" WriteSecurity="1" Author="3" EventSinkAssembly="" EventSinkClass="" EventSinkData="" EmailInsertsFolder="" AllowDeletion="True" AllowMultiResponses="False" EnableAttachments="False" EnableModeration="False" EnableVersioning="False" Hidden="False" MultipleDataList="False" Ordered="True" ShowUser="True" />
<List DocTemplateUrl="" DefaultViewUrl="/Lists/Links/AllItems.aspx" ID="{233585BA-5972-40BB-85C8-67F793F22F67}" Title="2 Links" Description="Use the Links list for links to Web pages that your team members will find interesting or useful." ImageUrl="/_layouts/images/itlink.gif" Name="{233585BA-5972-40BB-85C8-67F793F22F67}" BaseType="0" ServerTemplate="103" Created="20041008 14:10:07" Modified="20060224 10:43:07" LastDeleted="20050302 08:40:12" Version="1" Direction="none" ThumbnailSize="" WebImageWidth="" WebImageHeight="" Flags="4105" ItemCount="26" AnonymousPermMask="" RootFolder="" ReadSecurity="1" WriteSecurity="1" Author="3" EventSinkAssembly="" EventSinkClass="" EventSinkData="" EmailInsertsFolder="" AllowDeletion="True" AllowMultiResponses="False" EnableAttachments="False" EnableModeration="False" EnableVersioning="False" Hidden="False" MultipleDataList="False" Ordered="True" ShowUser="True" />


As you can see a large amount of information is returned for each list but I’m most interested in four attributes of each list:

Name –List Name for display
ServerTemplate – for filtering
DefaultViewURL – to allow jumping to the standard SharePoint links page
Hidden - for hiding hidden lists.

The ServerTemplate is used to filter out only those lists that are Links based list which have a list template of 103. I use the constant LISTTEMPLATE_LINKS which is defined in ows.js.

The routines for parsing this XML into DOM nodes brings out the second major difference between IE and Mozilla : IE uses the MSXML ActiveX objects whereas Mozilla has the XML parsing javascript objects built-in.
I’ve encapsulated the conversion of the response XML into a set of DOM nodes into the GetSoapResponseItems routine

// Cross Browser helper to filter the XML results
// Returns array of Node objects
function ajaxsp_GetSoapResponseItems(SoapResponseXML, XPathFilter, NSResolver)
{
var m_Items=[];

// Mozilla
if (window.addEventListener)
{
    var aItems = xmlhttpLists.responseXML.evaluate(XPathFilter, SoapResponseXML, NSResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
    for( var i = 0; i < aItems.snapshotLength; i++){m_Items[i] = aItems.snapshotItem(i);}
}
else
{ // IE
    var xmlDom=new ActiveXObject("MSXML2.DOMDocument.3.0");
    xmlDom.async=false;
    xmlDom.loadXML(SoapResponseXML.xml);
    m_Items=xmlDom.selectNodes(XPathFilter.replace("sp:",""));
}

return m_Items;

}

In IE you create a MSXML.DomDocument ActiveX object and use its LoadXML method to load the XML and use selectNodes with an XPath expression to get back the list of items
Mozilla has support for a lot of the DOM Level 3 XPath  (http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html)  which allows XPath expression to be run against the XML data. The responseXML property is actually an object that has parsed the XML but unfortunately it doesn’t have support for selectNodes so you have to use the evaluate function to get a XpathResult and with that you can add the individual Dom nodes to an array.
One wrinkle with Mozilla is that’s its very picky about NameSpaces so you must specifiy a prefix in your XPath filter for default namespaces and also you need a routine that converts the prefix to the full Namespace, there are a couple of ways of doing this but a simple function returning a string is the easiest.

function moz_NSResolver1(NSPrefix)
{
switch(NSPrefix)
{
case "soap" : return "http://schemas.xmlsoap.org/soap/envelope/"; break;
default : return "http://schemas.microsoft.com/sharepoint/soap/";
}
}

As this function needs to vary depending on the XML I pass this in as function pointer in parameter NSResolver.
IE does some guessing for you so we can strip the prefix from the XPath filter.

Now once we have the Links Lists I create a set of ListItems (LI) in an Unordered List (UL) and display them by setting the innerHTML of a DIV element.

Whew, thats enough for one entry, In part 2 I’ll delve into displaying the actual links and creating a form for adding a new item using the client-side OWS Field objects and post the DWP file that contains all the code and which can be imported into your Web Part page.

Saturday, April 01, 2006 2:43:13 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [5]  | 
 Monday, March 13, 2006
Using the SharePoint Portal Search COM libraries.
Monday, March 13, 2006 9:26:27 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, February 09, 2006

Here's something I thought I'd throw out there to demonstrate the much underrated Content Editor Web Part (CEWP). It's a client-side JavaScript Color Picker based on code from http://www.mickweb.com/javascript/colours/websafe.html 

The CEWP allows you to either embed formatted Rich Text into the web page or pure HTML/JavaScript so I could very quickly create a Web Part using the copied javascript. It just took a few mods such as getting rid of the image dependency by using a SPAN to form the cells instead.

The code's not much to talk about its just plain JavaScript creating a HTML <table> with <td>'s for the color cells. One interesting point is that the color table is dynamically generated. This is done by placing an empty block element, <p> in this case, giving it an ID and then set its innerHTML property to the generated table string.

function AttachColorTable(){
document.getElementById("'colourtable'").innerHTML= table_maker(36,6,'Select Colour');
}

This code is called on the load event of the page. I use attachEvent so I don't override any other code that may be using the onLoad event.

window.attachEvent('onload',AttachColorTable);

You can either import the DWP file on a single page or upload to the Site or Virtual Server galleries using instructions given by Todd Bleaker in a cool tip here http://mindsharpblogs.com/todd/archive/2005/08/04/646.aspx

Also check out the Google search Web Part also using the CEWP by Todd http://mindsharpblogs.com/todd/archive/2005/08/04/647.aspx

 

Download : Color Picker1.zip (1.36 KB)

Thursday, February 09, 2006 10:19:02 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, February 07, 2006

I tend to keep an eye on the sharepoint newsgroups mainly because I can jump in and help someone out with something I've had problems with in the past. One question today on the newsgroup was about adding a hidden field to a sharepoint list via the object model. I've hit this before and until you find out whats going on you feel a bit like you're in a maze of twisty passages all alike.

Often you might want an internal tracking flag or number handled via code that you don't want exposed in the edit list UI that some unsupecting user might then delete by accident.

In theory this should simple.

        SPSite spsite=new SPSite(http://localhost:4455);
        SPWeb web=spsite.OpenWeb();

        SPList list =web.Lists["Announcements"];

        string id=list.Fields.Add("NewField",SPFieldType.Boolean,false);
        SPField spfield=(SPField)list.Fields.GetField(id);

        spfield.Hidden=true;
        spfield.Update();

 

But if you try this you'll hit an exception - the reason? The code for the Hidden property checks to see if the property CanToggleHidden property is true and throws an exception if its not.

Fair enough,all we do then is set the CanToggleHidden  property to true and do the update but there is no such property on the object model.

There are two ways around this but its best to know some internal details about the SPField object. This way the internal data for a field is stored is in an XML format like this

<Field DisplayName="NewField" Type="Boolean" Required="FALSE" Name="NewField" ColName="bit4" CanToggleHidden="TRUE" Hidden="TRUE"/>

You can see here the CanToggleHiden and the Hidden properties. Note not all fields will have these extra properties if they are not set. The properties are read from /written to via the .dotnet XML classes.

This field definition is a subset of the whole list schema which is taken from the tp_Fields column of the Lists table in the SharePoint content database. The ColName refers to the column used to store the data in the UserData table. You don't need to add this column by hand its automatically calculated from the field type and number of fields of that type already in use.

Now the SPField object internally gets and set its properties into this XML fragment using some helper functions one of which is SetFieldBoolValue for boolean fields, there is also SetFieldIntValue and SetFieldAttributeValue.

The first way to get around the limitation is to add the field directly as XML using the AddFieldAsXml method on the SPFieldCollection using the XML format as above but leaving out the columnname.

   SPList list =web.Lists["Announcements"];

   list.Fields.AddFieldAsXml("<Field DisplayName=\"NewField\" Type=\"Boolean\" Required=\"FALSE\" Name=\"NewField\" CanToggleHidden=\"TRUE\" Hidden=\"TRUE\"/>");

Thats the supported way but one other interesting method is to toggle the internal CanToggleHidden internal property with reflection.

Add a reference to the System.Reflection namespace and you can do this

   Type type = spfield.GetType(); 
   MethodInfo mi= type.GetMethod("SetFieldBoolValue", BindingFlags.NonPublic | BindingFlags.Instance);
   mi.Invoke(spfield,new object[]{"CanToggleHidden", true});

you can then set the Hidden property via the OM without getting an exception

   spfield.Hidden=true;
   spfield.Update();


Either way both of these are hacks but at least one is supported.
 

Tuesday, February 07, 2006 10:10:34 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, February 02, 2006

This entry was prompted by a question on a newsgroup about hiding screen elements so you can get the whole contents of a page fitting onto a printout without the top/left nav links.

Personally I don't like the printer friendly buttons you see on web pages mainly because you often find heaps of javascript dynamically altering DOM element's styles or the developer has coded a special version of the page just for the printer.
Sometimes that may be needed but I'd just recently done a website where this feature was requested and came across an aspect of css that I'd missed before. That was the media=print attribute. A few tweaks to the css file to hide navigation divs and margins and voila the printouts came out perfectly.

In CSS 2 you can specify different stylesheets for the different output devices the document may be rendered to, see http://www.w3.org/TR/REC-CSS2/media.html for the full details.
You can either reference a separate css file like this

<LINK rel="stylesheet" type="text/css" href="print.css" media="print">

or you can inline the style like this

@media print { .classname   {display :none; visibility:hidden } }

So to alter how a SharePoint page looks when printed we can either alter the css files that SharePoint uses : ows.css and sps.css with SP Portal : or inline the stylesheet in the page.
We can inline by adding a Content Editor Web Part and using its source edit button to add this text

<style type="text/css">
@media print
{
.ms-bannerframe   {display :none; visibility:hidden }
.ms-nav   {display :none; visibility:hidden }

}
</style>

Go to File-Print Preview and you should see that the top and left nav bars are now hidden.
On the Portal sites you probably need to add the ms-navframe style to the list above as that's used in the left nav bar.

 

Thursday, February 02, 2006 9:27:52 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, January 22, 2006

Towards the end of last year our company Flexnet Consultants decided to release a set of free Web Parts that we thought might be useful to have installed in WSS/SPS sites. The first of these was a Flash slideshow based on the pictures held in a SharePoint picture library http://www.flexnetconsult.co.uk/WebParts/FlashSlideShow.htm.

We like Flash a lot as its a great way to liven up dull static pages and the content can be a lot more than 'eye-candy'

 

The first thing you need is some way to create the swf file that Flash is compiled into, in-house we use Flash MX. The actual slideshow view of the pictures wasn't a real problem as that's exactly the kind of thing Flash is good at. We use ActionScript to control the action on the Flash stage. 

The main issue was how to get a list of the pictures held in the library.

We looked at a few possible ways, one would be to pass a list of pictures to Flash using its URL or the FlashVars property, not pretty and not scaleable, the next idea was to talk to the SharePoint Web Services, this would have been ideal except Flash only really got proper support for talking to generic Web Services with the recent version 8 and we wanted to support Flash players version 6 and upwards to allow for the widest audience. We will probably create a version that supports 8 later.

That left us with one final method and it turns out to be a very easy one, the old SharePoint URL protocol available since version 1. The docs for these calls are here http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spsdk11/Client_API/spURLProtocol.asp , there's a lack of useful examples but its pretty easy to use.

You pass parameters via URL to the SharePoint ISAPI DLL called owssvr.dll in the _vti_bin directory and it returns xml information either formated in a CAML style or raw data rowset style. We want raw Data so we put XMLDATA=true in the URL

As an example if you pass http://sharepoint.flexnet.ds/_vti_bin/owssvr.dll?CS=109&Cmd=Display&List={89ECD870-30EE-4E6E-B39B-B2C8CC548213}&XMLDATA=TRUE then you get this XML data back

- <xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
- <s:Schema id="RowsetSchema">
- <s:ElementType name="row" content="eltOnly" rs:CommandTimeout="30">
- <s:AttributeType name="ows_SelectedFlag" rs:name="Selection Checkbox" rs:number="1">
  <s:datatype dt:type="ui1" dt:maxLength="1" />
  </s:AttributeType>
- <s:AttributeType name="ows_DocIcon" rs:name="Type" rs:number="2">
  <s:datatype dt:type="string" dt:maxLength="512" />
  </s:AttributeType>
- <s:AttributeType name="ows_NameOrTitle" rs:name="Name" rs:number="3">
  <s:datatype dt:type="string" dt:maxLength="512" />
  </s:AttributeType>
- <s:AttributeType name="ows_ImageSize" rs:name="Picture Size" rs:number="4">
  <s:datatype dt:type="i4" dt:maxLength="4" />
  </s:AttributeType>
- <s:AttributeType name="ows_FileSizeDisplay" rs:name="File Size" rs:number="5">
  <s:datatype dt:type="i4" dt:maxLength="4" />
  </s:AttributeType>
- <s:AttributeType name="ows_RequiredField" rs:name="" rs:number="6">
  <s:datatype dt:type="string" dt:maxLength="1073741823" />
  </s:AttributeType>
  </s:ElementType>
  </s:Schema>
- <rs:data>
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London01.jpg" ows_ImageSize="180" ows_FileSizeDisplay="10587" ows_RequiredField="Flash Test/London01.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London02.jpg" ows_ImageSize="180" ows_FileSizeDisplay="7675" ows_RequiredField="Flash Test/London02.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London03.jpg" ows_ImageSize="180" ows_FileSizeDisplay="14242" ows_RequiredField="Flash Test/London03.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London04.jpg" ows_ImageSize="180" ows_FileSizeDisplay="6755" ows_RequiredField="Flash Test/London04.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London05.jpg" ows_ImageSize="180" ows_FileSizeDisplay="19926" ows_RequiredField="Flash Test/London05.jpg" />
  :row ows_SelectedFlag="1" ows_NameOrTitle="SubFolder1" ows_RequiredField="Flash Test/SubFolder1" />
  </rs:data>
  </xml>
 
 
As you can see the data holds information such as the filesize and the subfolders, this could be used to recurse into the sub folders if you wanted.
 
This format is exactly what we need as Flash has a XML object that can load XML data from a URL.
Example code for this would be

var x=new XML();
x.ignoreWhiteSpace = true;
x.onLoad = LoadDocs;
x.Load("http://sharepoint.flexnet.ds/_vti_bin/owssvr.dll?CS=109&Cmd=Display&List={89ECD870-30EE-4E6E-B39B-B2C8CC548213}&XMLDATA=TRUE");

The XML object will call the LoadDocs function when it has completed the data download.
 
All thats left is to iterate the XML nodes and extract the picture names from the attribute ows_RequiredField
 
function LoadDocs(success)
{
   if (success){
   var eXmlRoot = this.firstChild;

   // allow for /r/n in the XML stream
   var eXmlSchema= eXmlRoot.childNodes[1];
   var eXmlData= eXmlRoot.childNodes[3];
   var eRows= eXmlData.childNodes;

   arrImages=new Array();

   for (i=0; i< eRows.length; i++){
      if (eRows[i].nodeType==1 && eRows[i].nodeName.toLowerCase()=="z:row"){

         //Skip subfolders and only show jpgs
         // to only show jpgs use (eRows[i].attributes.ows_DocIcon == 'jpg')
         if(eRows[i].attributes.ows_SelectedFlag=="0"
         {
            arrImages.push(eRows[i].attributes.ows_RequiredField);
         }
      }
   }

   }
// The list of pictures are now loaded
}
 
Now that we have the list of pictures we can then use ActionScript to rotate through them.
 
Once you have created your Flash SWF the final thing that needs to be done is to render out the OBJECT tag that will host the Flash file in the RenderWebPart event, this will look like this on the SharePoint page:
 
<OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" WIDTH=180 HEIGHT=120 id="FlexnetSlides_WPQ4" ALIGN="">
<PARAM NAME=movie VALUE="http://sharepoint/wpresources/FlexnetConsultants.Slideshow/Flexnet-SlideShow1_2.swf">
<PARAM NAME=FlashVars VALUE="ServerBase=http%3a%2f%2fsharepoint%3a80&SiteURL=http%3a%2f%2fsharepoint&PicLib=dcebbba5-5e94-49a1-88b7-03f7625dd8e4&RootFolder=&Transition=Fade&Displaytime=5&Transitiontime=3&ScaleMode=0&MaxCachedImages=30">
<PARAM NAME=menu VALUE=false>
<PARAM NAME=quality VALUE=high>
<PARAM NAME=scale VALUE=noscale>
<PARAM NAME=bgcolor VALUE=#FFFFFF>
<EMBED src="http://sharepoint/wpresources/FlexnetConsultants.Slideshow/Flexnet-SlideShow1_2.swf" menu=false scale=noscale quality=high bgcolor=#ffffff FlashVars="ServerBase=http%3a%2f%2fsharepoint%3a80&SiteURL=http%3a%2f%2fsharepoint&PicLib=dcebbba5-5e94-49a1-88b7-03f7625dd8e4&RootFolder=&Transition=Fade&Displaytime=5&Transitiontime=3&ScaleMode=0&MaxCachedImages=30" WIDTH="180" HEIGHT="120" NAME="FlexnetSlides_WPQ4" ALIGN=""TYPE="application/x-shockwave-flash" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer">
</EMBED>
</OBJECT>
 
The FlashVars method allow you to reference parameters as variables in the ActionScript code e.g. _root.ServerBase and _root.SiteURL
 
Future Web Parts in the pipeline are an improved Slideshow, a PhotoStrip with dynamic pictures (like the Flikr badge) and a analog/digital clock.
 
 
Sunday, January 22, 2006 4:52:53 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, January 16, 2006

I've just been going through the process of upgrading my development SharePoint sites to run on ASP.NET 2.0 and SQL server 2005.

First I installed the WSS Service Pack 2. No problems there.

Then I upgraded one of my sites to run on ASP.NET 2.0 using the nice new IIS property tab and running the stsadm -o upgrade -forceupgrade -url http://portalsiteurl command . The ststadm command updates the web.config in the IIS root directory and it also runs the contents of Web Server Extensions\60\TEMPLATE\SQL\STOREUP.SQL. This adds some new fields to tables and updates a lot of the core stored procedures.

An IISRESET later and I could browse to the WSS sites running on ASP.NET 2.0 with no problems.

The real problem came when I try to access another set of WSS sites that were running on a different IIS virtual server that gave me the dreaded Server Application Unavailable error

 

No panic as I knew from previous experience this is almost always a problem with Web.Config parsing or Application Pool configuration/identity .

Checking the Event Log gave me this

 

 

 

Fantastic, a helpful error message, it pointed me straight to the fact that the same Application Pool was in use by both Virtual Server applications and ASP.NET doesn't support loading a second version of .NET in the same worker process.

The fix is to create a new App Pool for the applications you want to run under ASP.NET 2.0. This would include any .Net applications that share an AppPool not just SharePoint.

 

 

Create a new App Pool based on the existing SharePoint one, give it a meaningful name

 

Assign it to the Virtual Server running SharePoint

an iisreset later and you should be good to go.

I'm so glad I don't try this stuff out on live servers, anymore.

Monday, January 16, 2006 11:43:12 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, January 15, 2006

One Click Minimize/Restore for WebParts

One anoyance I have with WebPart pages that have a lot of WebParts is the need to click the WebPart menu to select Minimize or Restore when you want a little more screen real estate or you need to expand a previously minimized WebPart.

To solve this niggle I've developed a simple WebPart that using client side Javascript that traps the double click on a WebPart title and toggles the state of the WebPart. It integrates with the SharePoint client side object model so any changes are saved to the site and persists across sessions.

Unzip the DoubleClick WebPart Minimize/Restore DWP file to a directory of your choice

Browse to the SharePoint WebPart page - Click the 'Modify My Page' - 'Add Web Parts' - 'Import menu' option

Click the Browse button and select the DWP file.

Click the upload button - once uploaded you can drag the WebPart to anywhere on the page.

 


Here's a little more detail of the SharePoint client side scripting.

First I needed to get a list of WebParts on a page. Thats easy - every WebPart page that holds WebParts has a global object WPSC that is initialized like this WPSC.Init(document). The object WPSC is defined in IE55up.js/IE50.js/NonIe.js depending on browser. This is the Web Part Page Services Component which handles the client side WebPart infrastructure, it provides services such as loading/saving properties and handles page events see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spptsdk/html/wpscaOverview_SV01098760.asp  for more details.

OnPage javascript adds every WebPart on the page to its WebPartPage.Parts collection e.g.

WPSC.WebPartPage.Parts.Register('WPQ1','8db7d8d8-b1e0-4e9d-84ff-0b266b8c0beb',document.all.item('WebPartWPQ1'))

To get a list of all the WebParts on the Page you can do this

for(var i=0; i< WPSC.WebPartPage.Parts.Count;i++)
  {
    wp=WPSC.WebPartPage.Parts.Item(i);
}

What we really need is the WebPartQualifierproperty of the WebPart which is the unique ID for each WebPart on the Page, various parts of the WebPart are then suffixed with this identifier e.g WebPartTitleWPQ2 for the title div and WebPartWPQ2 for the actual serverside rendered HTML div container.

So to hook the DblClick event we can modify the inner loop like so

    wpq=WPSC.WebPartPage.Parts.Item(i).WebPartQualifier;
    var oTitle=document.getElementById('WebPartTitle' + wpq);
  
    if (oTitle)
    { 
       oTitle.attachEvent('ondblclick',MinRestoreWP);
    }

Here we're setting the double click event to fire the MinRestoreWP function. this is defined as

function MinRestoreWP()
{
var WPQId, WPObject;
var titleid,  WPQPos;

// Clear the selection the double click produces
document.selection.empty();
window.event.cancelBubble = true;
window.event.returnValue = false;

titleid=window.event.srcElement.parentNode.id;
WPQPos=titleid.lastIndexOf("WPQ");
if (WPQPos>0)
{
   WPQId='WebPart' + titleid.substring(WPQPos, titleid.length);
   WPObject=document.getElementById(WPQId);
   // call the MSO api to do the work
   if (WPObject) MSOLayout_MinimizeRestore(WPObject);
}
 
return false;
}

Here we get the WebPart to collapse or expand by going to the parent element of the Title bar which holds the WebPart title id which we can parse to get the WPQ id. This allows us to get the WebPart object we need.

The expand collapse is handled by a SharePoint function MSOLayout_MinimizeRestore which simply takes a WebPart object and does the hard work of hidding or showing the webpart by changing the WebPart.style.display CSS property. It also logs the change so the property is saved back to the WebPartPages webservice and is remembered if you close the browser and revisit the page.

DoubleClickWebPartMinimizeRestore.zip (1.35 KB)
Sunday, January 15, 2006 6:21:03 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [6]  |