Monday, October 22, 2012

Creating SharePoint Custom List Columns in PowerShell

 

I needed to create a sizable test in SharePoint using a custom list.  I did not want to manually configure each column so I build a tab delimited text file that held the column configuration options and a PowerShell script to read it.  The first column of the file provides the field name, the second provides the options (they were separated by a pipe delimiter), and the third column is the field description.  I wanted all of the questions to be Radio Button choice fields so those options were hard coded.  I also wanted a letter and appended to each answer so I created an array of letters up to G. 

$w = get-spweb http://spsite
$l = $w.lists["MyTest"]

$letters = "A|B|C|D|E|F|G"
$lttr = $letters.split("|")
$spFieldType = [Microsoft.SharePoint.SPFieldType]::Choice

$file = Get-Content c:\Questions.txt

foreach ($line in $file) {
  
    $fields = $line.ToString().Split("`t")
    $fName = $l.Fields.Add($fields[0],$spFieldType,$True)   
    write-host $fName
    $f = $l.Fields[$fName]
    $choices = $fields[1].split("|")

    for ($i=0; $i -lt $choices.count; $i++)
    {
        $choiceValue = $lttr[$i] + ". " + $choices[$i]
        $f.Choices.Add($choiceValue)
    }
    $f.description = $fields[2]
    $f.EditFormat = "RadioButtons"
    $f.update()

}

The script works like a champ, and I saved myself a little time and frustration of repetitively clicking the same options over and over again.

PowerShell to display SPN and Delegation Information for SharePoint Accounts

 

In troubleshooting Kerberos issues it is sometimes helpful to see the all the SPNs and delegate to settings for my various SharePoint accounts.  Since our SharePoint accounts are named in a consistent way this ended up being quite easy.  I set a filter that looks for accounts that start with the name PRD-SP and looked in the proper container in the Active Directory.  I piped the output to a file and I had a useful listing of all the SPNs and their delegate to settings.

$strFilter = "(&(objectCategory=User)(sAMAccountName=PRD-SP*))"

$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = "LDAP://OU=SharePoint,OU=Special Accounts,DC=domain,DC=com"
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"

$colProplist = "sAMAccountName","name","msDS-AllowedToDelegateTo","servicePrincipalName"

foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}

$colResults = $objSearcher.FindAll()

foreach ($objResult in $colResults) {
    #$objResult.Properties
    $objResult.Properties["name"]
    $objResult.Properties["samaccountname"]
    "================================================"
    "msDS-AllowedToDelegateTo"
    "------------------------------------------------"
    $objResult.Properties["msds-allowedtodelegateto"]
    "------------------------------------------------"
    "servicePrincipalName"
    "------------------------------------------------"
    $objResult.Properties["serviceprincipalname"]
    ""
    ""
}

Delete a Retention Information Management Policy that is Corrupt

 

We had a series of content type retention polices that for some reason ended up being corrupt.  When I would try to edit the policy through the user interface I would get an error:

image

No matter what I did through the user interface I would get that error.  To remedy this I wrote a little 4 line PowerShell script that deletes the policy.  The script accepts 3 arguments (site URL, list name, and content type). 

$w = Get-SPWeb $args[0]
$l = $w.lists[$args[1]]
$c = $l.ContentTypes[$args[2]]
[Microsoft.Office.RecordsManagement.InformationPolicy.Policy]::DeletePolicy($c)

The script deletes the policy and then the policy can be reconfigured through the site settings.

Force a Single User’s Profile to Sync With Active Directory

 

Occasionally we will have a user that changes their last name and wants it to reflect the change across SharePoint.  In order to aid this process I wrote this script to force a resynchronize of each instance of the user profile in the user information lists.  The script accepts one argument, which is the username to resync (“DOMAN\User”).

$webapps = Get-SPWebApplication
foreach ($webapp in $webapps) {
    [string] $login = $args[0]
    $sites = get-spsite -Limit All -WebApplication $webapp
    foreach ($s in $sites) {
        write-host $s.url
        $w = $s.RootWeb;
        $u = get-SPUser -Web $w -limit all | Where-Object {$_.userlogin -eq $login}
        if ($u -ne $null) {
            write-host "`t$($w.url)"
            Set-SPUser $u -SyncFromAD
            write-host "`tUpdated"
        }
    }
}

Showing left navigation for entire library of web part pages

 

I had a request to make a whole library full of web part pages show the left navigation.  These pages had been created and populated with web parts previously.  Instead of editing the template on each of these pages manually, I decided to script it using PowerShell. 

$w = get-spweb “http://sharepoint/sites/collection/subweb”
$f = $w.RootFolder.SubFolders["Pages"]
foreach ($p in $f.Files) {
    $enc = [system.Text.Encoding]::ASCII
    $b = $p.OpenBinary()
    $str = $enc.GetString($b)
    $new = $str -replace "<ContentTemplate>(.|\n)*</ContentTemplate>","<ContentTemplate></ContentTemplate>"
    $new = $new -replace "<asp:Content.*PlaceHolderLeftNavBar.*/asp:Content>",""
    $new = $new -replace "<asp:Content.*PlaceHolderLeftActions.*/asp:Content>",""
    $new = $new -replace "^\?\?\?",""
    $p.SaveBinary($enc.GetBytes($new))
}

The script opens each file in the “Pages” library that housed the web part pages and strips out the code that hides the left navigation.

Friday, October 19, 2012

Embedding Javascript in Custom Actions

 

SharePoint 2010 added the ability to tie custom actions to a list or list item:

image

Custom Actions can be added to these menus/ribbons:

image

These custom actions can start workflows, navigate to a form, or navigate to a URL.  This gives some limited customization that can be do to how a list behaves, and what actions can be taken, but with little creativity this functionality can be extended.

The first thing that needs to be understood to extend this functionality is that there a some key tokens that can be used with building URLs.  These tokens are:

  • {ItemId} – ID (GUID) taken from the list view.
  • {ItemUrl} – Web-relative URL of the list item (Url).
  • {SiteUrl} – The fully qualified URL to the site (Url).
  • {ListId} – ID (GUID) of the list (ID).
  • {ListUrlDir} – Server-relative URL of the site plus the list's folder.
  • {Source} – Fully qualified request URL.
  • {SelectedListId} – ID (GUID) of the list that is currently selected from a list view.
  • {SelectedItemId} – ID of the item that is currently selected from the list view.

Source: <http://msdn.microsoft.com/en-us/library/ff458385.aspx>

The other key piece of information needed is that inside the URL can instead be a Javascript function reference like this:

image

Then all that is needed is to create a script library with that function and either reference it on the page (List View), the masterpage, or in the SPWeb property: CustomJavaScriptFileUrl.  For my example I used the following code to create a function that will update any value in the target list item.  The parameters are:

  1. Item ID
  2. List ID
  3. Column Name
  4. New Column Value

The finished Javascript looks like this:

function updateListItem(ItemId, ListId, FieldName, FieldValue) {
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getById(ListId);
    var oListItem = oList.getItemById(ItemId);
    oListItem.set_item(FieldName, FieldValue)
    oListItem.update();
    clientContext.load(oListItem);
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}


function onQuerySucceeded() {
    window.location.reload(false);
}

function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

With just a little effort now I have a list action that dynamically completes the list item without the overhead of a workflow or making the user open and edit the item:

image

image

SharePoint 2010 CSS Classes

 

SharePoint 2010 includes a series of CSS Classes that allow you to consume the theme colors.  This allows you to have custom built HTML content that dynamically recolors with the SharePoint theme:

The following table shows the background color CSS classes and their corresponding color on the default theme:

CSS Class Color

ms-rteThemeBackColor-1-0

#FFFFFF

ms-rteThemeBackColor-2-0

#000000

ms-rteThemeBackColor-3-0

#F5F6F7

ms-rteThemeBackColor-4-0

#182738

ms-rteThemeBackColor-5-0

#0072BC

ms-rteThemeBackColor-6-0

#EC008C

ms-rteThemeBackColor-7-0

#00ADEE

ms-rteThemeBackColor-8-0

#FD9F08

ms-rteThemeBackColor-9-0

#36B000

ms-rteThemeBackColor-10-0

#FAE032

These match up exactly with the 10 theme colors:

image

There is a similar set of CSS tags to match the fore color to the theme colors.  Following the same pattern:

CSS Class Color

ms-rteThemeForeColor-1-0

#FFFFFF

ms-rteThemeForeColor-2-0

#000000

ms-rteThemeForeColor-3-0

#F5F6F7

ms-rteThemeForeColor-4-0

#182738

ms-rteThemeForeColor-5-0

#0072BC

ms-rteThemeForeColor-6-0

#EC008C

ms-rteThemeForeColor-7-0

#00ADEE

ms-rteThemeForeColor-8-0

#FD9F08

ms-rteThemeForeColor-9-0

#36B000

ms-rteThemeForeColor-10-0

#FAE032

There is also a way to get a few shades lighter and darker of each theme color.  The image below demonstrates this for the 10th theme color:

image

There are also classes to change the font face:

image

and font size:

image

Another great resource for other themed CSS classes can be found here:

http://silverpointsoftware.wordpress.com/themed-css-classes/

Microsoft SharePoint Patterns and Practices Logging Provider

 

Part of the patterns & practices SharePoint guidance published by Microsoft is a framework for doing logging (http://spg.codeplex.com/).  The DLL provided (Microsoft.Practices.SharePoint.Common.dll) includes logger class that allows you to log messages easily to the ULS.  The one challenge is that it registers custom logging areas to accomplish this.  We were struggling to get one of these areas registered because the .NET feature trying to register the area did not have the proper privileges at runtime.  To avoid giving the feature the required farm wide permissions it needed, I instead opted to register the logging area via PowerShell.  Two PowerShell commands later, voilĂ :

PS C:\> [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Practices.SharePoint.Common")

GAC Version Location
--- ------- --------
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.Practices.SharePoint.Common\2.0.0.0__ef4330804b3c4129\Microsoft.Practices.SharePoint.Common.dll

PS C:\> [Microsoft.Practices.SharePoint.Common.Logging.DiagnosticsService]::Register()