Adventure in SPWonderland

Take apart and put back together

NAVIGATION - SEARCH

List Windows Groups in a SharePoint site and convert to DirectoryEntry

 

On a Claims enabled site I needed to get a Windows DirectoryEntry object for each group and list its members

Using the LDAP Sid syntax and the ADSI operator allows for a quick lookup to AD

$ClaimMan=Get-SPClaimProviderManager

$web=get-spweb http://flxdev2010:25555/sitecol1/sub6/Test3

# Get User List

$Users=$web.RoleAssignments  | % { $_.Member.Users }

# Filter to get only windows groups – return Sid’s

$WindowsGroups= $Users | ? { $_.IsDomainGroup -eq $true
        -and $ClaimMan.DecodeClaim($_.UserLogin).OriginalIssuer -eq "Windows"}  | % { $ClaimMan.DecodeClaim($_.UserLogin).Value }

# RAW Sids here
$WindowsGroups

# DirectoryEntry list here
$GroupEntries=$WindowsGroups | % { [ADSI]"LDAP://<SID=$($_)>"  }

# List members of group
$GroupEntries | % { $_.Properties["member"] }

Update User Emails in SharePoint from Active Directory

 

With SharePoint once a user is added to a SharePoint site the users email is saved at that point in time and only updated if you have the User Profile Service setup and running in which case a timer job updates user information in all site collections in the Farm.

Sometimes farms are created that stand alone and do not have the User Profile service setup or it might be a foundation Farm which has no User Profile Service. In that case this script might be useful.

It gets the Email address from AD for every user in all site collections within a Web Application and updates it if needed.

From a Powershell prompt

To Report on out of date  user emails
  .\FixUserEmails.ps1 http://sharepointurl

To update emails 
  .\FixUserEmails.ps1 http://sharepointurl $true

 

Should work for 2007 and 2010.

Its not guaranteed to work across multiple domains as the sAMAccountName attribute does not contain the domain, otherwise let me know of any issues.

FixUserEmails.zip (2.82 KB)

PowerShell quickie: Extract the feature IDs used in large SharePoint projects

You know how it is, you start developing a project and then 6 months later you look back and realise you have to document everything you've produced.

I've just gone through that process and need a quick list of all the features ids scattered around various subdirectories of a large project.

sample:

<Feature  Id="886f12cf-97ca-4789-baf8-6f13f9f2cedf"
          Title="PGPSO Contract Management Project Upgrade"
          Description="Feature that upgrades Project Sites for Contract Management."
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          DefaultResourceFile="core"
          ReceiverAssembly="PGPSO_CM_Project_Upgrade, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa0408b86137366a"
          ReceiverClass="PGPSO_CM_Project_Upgrade.PGPSO_CM_Project_Upgrade"
          xmlns="http://schemas.microsoft.com/sharepoint/">

Firing up Powershell navigating to the root of the solution folders and running this command gets me the list

gci -recurse -filter  feature.xml | % { $contents=get-content $_.fullname; $x=[XML]$contents; "{0} {1}" -f $x.Feature.Id, $x.feature.title }

result:

 

gci is an alias for get-childitem which allow you to recurse subfolders and provide a filter parameter. Then use get-content to open the file, convert to an XML object and then directly reference the Id and title of the feature.xml file.

 

 

UK Community Day at Microsoft Reading

The UK Microsoft User Groups have organised another huge gathering at Microsoft Reading next week.

This time it's split over 2 days April 8th and 9th and features a lot of great sessions. User groups that I'm active in such as the UK SharePoint user group, PowerShell user group and the Vista squad will be there along with the user groups for Exchange, SQL Server and others.

It's not often you get a host of experts on these subjects in one location so check out the agenda and see if any sessions take your fancy.

I'm doing a session on day 2 crossing over two groups SharePoint and PowerShell on moving data in SharePoint using the Content Deployment API (aka Prime API) and PowerShell. I'll go through some of the functions and cmdlets I've written to make moving data easier, samples include moving list items, lists, webs and site collections within and between farms.

PowerShell: Generating a proxy for all the SharePoint WebServices

Zach Rosenfield has a nice post on calling WebServices from PowerShell. The steps are pretty simple: generate a proxy cs file, compile it into a DLL and then load that DLL up into Powershell AppDomain.

Well I've been doing some work with quite a few of the WebServices and I wanted to compile all the proxys into a single DLL.

Use Zach's post to setup the PowerShell environment variables needed to call the Visual Studio SDK utilities wsdl.exe and csc.exe

Heres the script to compile all the available SharePoint Webservice's into one DLL.

It simply enumerates all the ASMX files in the ISAPI directory and passes each item in that list to WSDL.exe which generates the proxy cs files.

You need to change the URL in this script to point to a valid SharePoint site and make sure there are no other .cs files in the directory before running this script.

$asmxlist= dir "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI" *.asmx | select name

$asmxlist | foreach-object {
write-host "Generating SharePoint Proxy Library for $($_.name)" -foregroundcolor green 

$outputfilename="FlexnetConsult.SharePoint.$($_.name).cs"
$namespace=[IO.Path]::GetFileNameWithoutExtension($_.name)

wsdl "http://portal.contoso.com/matters/sites/_vti_bin/$($_.name)" /o:$outputfilename /namespace:$namespace

}

write-host "Compiling SharePoint Proxy Library" -foregroundcolor green 
csc /t:library /out:FlexnetConsult.SharePoint.WebServices.dll *.cs 

 

So the output should be something like this

 

image

 

Now three of the WebServices generate an error SlideShow.asmx. FormserverProxy.asmx and contentareatoolboxservice.asmx but as I'm unlikely to use them I'm not going to worry about those.

So now we have a DLL called FlexnetConsult.SharePoint.WebServices.dll in our directory that we can use to call the (almost) any SharePoint Web Service.

I've attached the compiled dll and script.

In my next post I'll use the DLL to do something I've wanted to do for ages and that's list, add and delete Web Parts on a page using PowerShell.

Now your probably shouting, hey why not just call the object model and GetLimitedWebPartManager, yep but that doesn't work as without a web.config and possibly a HttpContext all you get back are Error WebParts as the WebPart safecontrollist cannot be accessed.

 

 

Sharepoint and Powershell Virtual lab

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.

EVO Community Day - June 21st: Microsoft Campus Thames Valley Park Reading

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/

 

Presenting at the UK PowerShell User Group 22 March Meeting

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.

SharePoint/PowerShell 8: The one with the Contact Web Part

 

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.

SharePoint/PowerShell 7: Put the User in the Picture

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.