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]  | 
 Wednesday, November 15, 2006

Everyone and their dog will have this link but I'll post it anyway.

Windows PowerShell 1.0 has been released. This page has a list of the download links for XP, Windows Server 2003 and Vista .

http://www.microsoft.com/windowsserver2003/technologies/management/powershell/download.mspx

You might have to be patient as the links were a bit flakey when I tried them.

A point to note is that the Vista version is at RC2 status. Jeffrey Snover explains the Vista release delay here http://blogs.msdn.com/powershell/archive/2006/11/15/windows-powershell-windows-vista.aspx. Checking the comments it seems some people are getting hot under the collar about the delay but as I see it the PowerShell team have done a great job and by all accounts the only difference between RC2 and RTM is the installer. Personally given my bad beta experiences of Vista I'm happier installing the RC2 of PowerShell than the RTM of Vista.

Also worth a download is the Documentation Pack as it contains a Getting Started Guide and a User Guide.

 

 

 

Wednesday, November 15, 2006 9:57:09 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 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]  |