Thursday, February 9, 2012

PowerShell Automatic Publishing Page Builder for SharePoint 2010

I’ve recently created a handy PowerShell script to create publishing pages for my SharePoint sites.  During every project, there comes the time when having a decent sized set of randomized page data will help for various lifecycle phases.  I created the script when my team realized a load test of pages was required.  We weren’t talking about a couple pages, either.  This load test needed to handle up to 1,000 pages in order to collect the data we needed to make educated decisions on particular functionality. 

Creating 1,000 pages by hand is beyond tedious.  Enough said…

As I thought about my page creator, it had to meet several criteria:

  1. Use script vs. code.  Script can be “live” edited, pushed to various systems without complicated governance,  and doesn’t need compiling.  Since PowerShell can utilize almost any .NET object, it is a clear winner.  
  2. Reusable on other projects.  Having an auto page builder script in your toolbox can be handy for many types of projects.  The script I created should be able to be used on any SharePoint 2010 deployment without the need for extensive editing.
  3. Support a decent level of randomness.   It would be pretty easy to create pages, and copy them using SharePoint Designer.  You’d end up with lots of files with “Copy” in the name and title.   The auto page builder creates random names and titles based on the system time variable.  It also inserts random field data when available.  See the “Nuts and Bolts” below for more details.
  4. Support many field types.   Most of the pain in creating any tool that inserts SharePoint data is getting the data formatting right for different fields.  The auto page builder so far handles Text, Note, HTML, Taxonomy, DateTime, User, URL, Summary Links.  Future releases should be able to handle Lookup and Choice fields.
  5. Easy to use.   With script comments, hopefully the auto page builder should be easy to use for all types of administrative users.

 

Nuts and Bolts

The automatic page builder was created on the simplicity principle.   You feed it a publishing web and a page layout, and the script will try its best to do the rest for you.  From the page layout the script determines the content type.  The script iterates through each column (or field) in the defined content type and attempts to add data to that column on a new page based on its column type.  In many cases this data is random. 

Managed Metadata

The auto page builder supports single and multi-select taxonomy fields types.  It will select an random value for these fields.  I left some script lines commented out in the managed metadata field propagation function that can add every possible value to a multi-select taxonomy field, in case you want that type of data.

People and Groups

The auto page builder can find an select a single random user account from SharePoint user profile manager.  This code is commented out by default in favor of a single guaranteed good account:  "SharePoint\System”.   (The auto page builder does not yet support groups assigned to a People and Groups field.)

Ratings

The auto page builder can attempt to add a random rating record for each page built.  This rating will come from the administrative local account running the script.  Warning!  Ratings are not immediately available and depend on the social rating synchronization timer job which runs every 60 minutes.

 

Notes on Script

Line 52:  The script really begins here.   The first block is a function to handle data for taxonomy fields.

Variables to Set (I should have used args, but I use the PS ISE exclusively)

Line 56:  The “iterations” variable.  How many iterations, or pages do you wish to create?

Line 57:  The “weburl” variable.  This will be the path to publishing web. 

Line 58:  The “siteurl” variable.  This is the path to the root web.

Line 59:  The “pagelayoutname” variable.  This is the Title of the page layout you want to use to create pages.   From the page layout, the script determines the Content Type and therefore the subset of columns to try to fill with data.

Other Notes

Lines 14 – 20: This is a commented out piece of example code that will add each piece of available managed metadata to a field.

Line 136:  There is a reference to “demohtml.txt” which is a text file with HTML markup to insert into an HTML field type.  An example of this file is pasted below.

Lines 156-161:  This is a commented out piece of example code that will drop a random SPUser object into a “User” field.

Download the script -> HERE

The Script

   1:  function GetRandomMetadata ($item, $site, $taxString)
   2:  {
   3:      # 1) Set a taxonomy field to the Title of a taxonomy field in the list
   4:      $taxField = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$item.Fields[$taxString];
   5:      
   6:      # 2) From properties of the taxField determine the term store and term set from the site
   7:      $taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site);
   8:      $termStore = $taxSession.TermStores[$taxField.SspId];
   9:      $termSet = $termStore.GetTermSet($taxField.TermSetId);
  10:      
  11:      # 3) Set a taxonomy field collection to collect values
  12:      $taxCollection = new-object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection($taxField);
  13:      
  14:      # 4) Add all values to the collection (Commented out)
  15:      #$termSet.GetAllTerms() | ForEach-Object {
  16:      #    $taxonomyFieldValue = new-object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($taxField);
  17:      #    $taxonomyFieldValue.TermGuid = $_.Id;
  18:      #    $taxonomyFieldValue.Label = $_.Name;
  19:      #    $taxCollection.Add($taxonomyFieldValue);
  20:      #}
  21:      
  22:      # 4 Alt)  Pick a random term to set.
  23:      $allTerms = $termSet.GetAllTerms();
  24:      
  25:      # Gets random term in the termset collection.  (Cool factor 10!)
  26:      $thisTerm = $allterms | Get-Random;
  27:      
  28:      $taxonomyFieldValue = new-object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($taxField);
  29:      $taxonomyFieldValue.TermGuid = $thisTerm.Id;
  30:      $taxonomyFieldValue.Label = $thisTerm.Name;
  31:      $taxCollection.Add($taxonomyFieldValue);
  32:      
  33:      $thisName = $thisTerm.Name;
  34:      
  35:      #Write-Host "Metadata for field '$taxString' set to $thisName ..."
  36:      
  37:      # 5) Set the value of the field in SharePoint
  38:      # Note:  this handles two types of MMD fields, the single and the multiselect
  39:      if ($taxField.TypeAsString -eq "TaxonomyFieldType")
  40:      {
  41:          #Write-Host "Taxonomy Singleton Type Found!";
  42:          $taxField.SetFieldValue($item, $taxonomyFieldValue);
  43:      }
  44:      else
  45:      {
  46:          $taxField.SetFieldValue($item, $taxCollection);
  47:      }
  48:  }
  49:  # END *Get Random Metadata* #
  50:   
  51:   
  52:  # MAIN ##################################################
  53:   
  54:  # Set VARS
  55:   
  56:  $iterations = 1
  57:  $weburl = “http://wef.local/pubtest2/” #ensure trailing "/"
  58:  $siteurl = “http://wef.local/”
  59:  $pagelayoutname = "Resource"
  60:   
  61:   
  62:  # Constants: don't change
  63:  $loremText = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  64:   
  65:  #ensure sharepoint
  66:  Write-Host("Loading SharePoint...");
  67:  if((Get-PSSnapin Microsoft.Sharepoint.Powershell -ErrorAction SilentlyContinue) -eq $null)
  68:  {
  69:      #load snapin
  70:      Add-PSSnapin Microsoft.SharePoint.Powershell;
  71:  }
  72:   
  73:  # Get publishing web
  74:  $SPWeb = Get-SPWeb -Identity $weburl
  75:  $site = New-Object Microsoft.SharePoint.SPSite($siteurl)
  76:  $web = $site.rootweb
  77:  $pSite = New-Object Microsoft.SharePoint.Publishing.PublishingSite($site)
  78:  $pWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($SPWeb)
  79:  $serviceContext = Get-SPServiceContext $site
  80:  $pages = $pWeb.GetPublishingPages($pWeb)
  81:   
  82:  # Determine Content Type and Page Layout
  83:  $pl = $pSite.GetPageLayouts($false) | Where { $_.Title -eq $pagelayoutname }
  84:  if (-not($pl)) { throw "Page Layout not found!" }
  85:  $plName = $pl.Name;
  86:  Write-Host "Page Layout defined as $plName";
  87:   
  88:  $contentType = $pl.AssociatedContentType
  89:   
  90:  $i = 1
  91:   
  92:  while ($i -le $iterations) {
  93:   
  94:  Write-Host "Iteration $i of $iterations"
  95:   
  96:  #Set URL of new page to UNIX time var with concat
  97:  $startTime = Get-Date -Uformat %s
  98:  $uTime = $startTime.ToString().Replace(".","-");
  99:  $PageUrl = "RandomPage-" + $uTime + ".aspx";
 100:  $PageUrl = $PageUrl.Replace("/", "-");
 101:   
 102:  $page = $pages.Add($PageUrl, $pl) #URL plus Page Layout
 103:  $page.Update();
 104:   
 105:  $item = $page.ListItem;
 106:   
 107:  # iterate through each content type field
 108:  $contentType.Fields | ForEach-Object {
 109:      
 110:      # ignore read-only and hidden fields
 111:      if ($_.ReadOnlyField -eq "True" -or $_.Hidden -eq "True") { return; }
 112:   
 113:      #Set Title because it is special...
 114:      if ($_.StaticName -eq "Title")
 115:      {
 116:          $item[$_.StaticName] = $PageUrl;
 117:          return;
 118:      }
 119:          
 120:      #Determine the field type to set appropriate data.
 121:      if ($_.TypeAsString -eq "Text" -and $_.StaticName -ne "Title") 
 122:      {
 123:          #Set to Lorem based on $_.MaxLength variable
 124:          $item[$_.StaticName] = $loremText.Substring(0,($_.MaxLength - 1));
 125:          return;
 126:      }
 127:      
 128:      if ($_.TypeAsString -eq "Note")
 129:      {
 130:          $item[$_.StaticName] = $loremText;
 131:          return;
 132:      }
 133:      
 134:      if ($_.TypeAsString -eq "HTML")
 135:      {
 136:          $item[$_.StaticName] = Get-Content demohtml.txt | out-string;
 137:          return;
 138:      }
 139:      
 140:      if ($_.TypeAsString -eq "TaxonomyFieldTypeMulti" -or $_.TypeAsString -eq "TaxonomyFieldType")
 141:      {
 142:          GetRandomMetadata $item $site $_.Title;
 143:          return;
 144:      }
 145:      
 146:      if ($_.TypeAsString -eq "DateTime")
 147:      {
 148:          #$thisTime = Get-Date -format s
 149:          #$item[$_.StaticName] = $thisTime;
 150:          $item[$_.StaticName] = [DateTime]::Today.ToString("yyyy-MM-ddTHH:mm:ssZ")
 151:          return;
 152:      }
 153:      
 154:      if ($_.TypeAsString -eq "User")
 155:      {
 156:          # Set a random user field
 157:          #$profileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($serviceContext)
 158:          #$userProfiles = $profileManager.GetEnumerator();
 159:          #$thisProfile = $userProfiles | Get-Random;
 160:          #$userName = $thisProfile.DisplayName;
 161:          #[Microsoft.SharePoint.SPUser]$spuser = $web.EnsureUser($userName);         
 162:          
 163:          # Or set a static user
 164:          [Microsoft.SharePoint.SPUser]$spuser = $web.EnsureUser("SharePoint\System");         
 165:          $item[$_.StaticName] =  $spuser; 
 166:          return;
 167:      }
 168:      
 169:      if ($_.TypeAsString -eq "URL")
 170:      {
 171:          $urlField = New-Object Microsoft.SharePoint.SPFieldURLValue
 172:          $urlField.Description = "Rightpoint"
 173:          $urlField.URL = "https://www.rightpoint.com"
 174:          $item[$_.StaticName] =  $urlField; 
 175:          return;
 176:      }
 177:      
 178:      if ($_.TypeAsString -eq "SummaryLinks")
 179:      {
 180:          $linkField = New-Object Microsoft.SharePoint.Publishing.Fields.SummaryLinkFieldValue
 181:          
 182:          $sumLink = New-Object Microsoft.SharePoint.Publishing.SummaryLink Rightpoint
 183:          $sumLink.LinkURL = "https://www.rightpoint.com"
 184:          $linkField.SummaryLinks.Add($sumLink)
 185:          
 186:          $sumLink2 = New-Object Microsoft.SharePoint.Publishing.SummaryLink Google
 187:          $sumLink2.LinkURL = "http://www.google.com"
 188:          $linkField.SummaryLinks.Add($sumLink2)
 189:          
 190:          $item[$_.StaticName] =  $linkField; 
 191:          return;
 192:      }
 193:      
 194:      if ($_.TypeAsString -eq "RatingCount")
 195:      {
 196:          # We have a social rating enabled list.  Let's give the item some ratings to help display stars
 197:          $SocialRatingManager = New-Object Microsoft.Office.Server.SocialData.SocialRatingManager($serviceContext);
 198:          [Object]$random = New-Object System.Random
 199:          $rating = $random.Next(1,6);
 200:          $itemURI = $weburl + $item.URL;
 201:          [void]$SocialRatingManager.SetRating($itemURI, $rating)
 202:      }
 203:  }
 204:   
 205:  $item.Update();
 206:  Write-Host "Created $($PageUrl)"
 207:   
 208:  # Check-in and publish page
 209:  $item.File.CheckIn("");
 210:  $item.File.Publish("");
 211:  if ($item.ListItems.List.EnableModeration) { $item.File.Approve(""); }
 212:   
 213:  $endTime = Get-Date -Uformat %s
 214:  $timings = $endTime - $startTime;
 215:  Write-Host "Page $i of $iterations created in $timings seconds"
 216:   
 217:  $i++
 218:  } # end iterations

 

Sample “demohtml.txt”

   1:  <style type="text/css">
   2:   table.tabular{width:95%;border-bottom:1px solid #ccc;background-color:#eee;border-collapse:collapse;margin:20px}
   3:   table.tabular td,table.tabular th{border-top:1px solid #ccc;border-right:1px solid #ccc;padding:3px 20px 3px 3px}
   4:   table.tabular td:first-child,table.tabular th:first-child{border-left:1px solid #ccc}
   5:  </style>
   6:   
   7:  <table width="100%" cellspacing="1" cellpadding="3" border="0" class="tabular">
   8:      <thead>
   9:          <tr><th>Description</th><th>Result</th></tr>
  10:      </thead>
  11:   
  12:      <tbody>
  13:          <tr>
  14:              <td>Headings</td>
  15:              <td><h1 style="border:none">Heading 1</h1><h2 style="border:none">Heading 2</h2><h3>Heading 3</h3><h4>Heading 4</h4><h5>Heading 5</h5><h6>Heading 6</h6></td>
  16:          </tr>
  17:          <tr>
  18:              <td>Paragraphs</td>
  19:              <td><p>Normal paragraph text.</p></td>
  20:          </tr>
  21:          <tr>
  22:              <td>Line Break</td>
  23:              <td>I feel a line break coming... <br /> ...up.</td>
  24:         </tr>