May 31, 2009

IronPython In Action

For about six years I felt in love with .NET and SharePoint. In the last year I’ve also learned to love PowerShell. I was really impressed of the dynamic and power you can gain with a scripting language in combination with the .NET Framework. So I’ve started to combine SharePoint and PowerShell through embedding PowerShell and developed SharePoint PowerActivity, Power EventReceiver and Power WebPart. It was really amazing to see how fast you can develop small SharePoint apps with this stuff. But as a C# developer you sometime miss the object orientation in PowerShell. On the BASTA! Spring I first meet Michael J Foord after his talk about the DLR and embedding IronPython. The talk was similar to my talk about embedding PowerShell. Embedding IronPython seemed to be almost as simple as embedding PowerShell, but with a dynamic and object oriented language. I was really excited - for sure I’ve to learn IronPython. Unfortunately resources about IronPython are very limited. Luckily Michael and Christian Muirhead has just written IronPython In Action.

IronPython In Action covers all you need to getting started with IronPython for an .NET or Python developer. It is written coherent without needless ballast, well suited to the philosophy of the language itself. My favorite chapters are “Sliverlight: IronPython in the browser”, “Agile testing where dynamic typing shines” and “Embedding the IronPython engine”. The book has inspired me to found the CodePlex project IronSharePoint (coming soon) showing how you can develop SharePoint apps with IronPython. I’m convinced that in the near future dynamic languages will get more attention again, just think at the “dynamic” keyword in C# 4.0. Absolutely worth to keep an eye on it!

The power of combining statically and dynamically typed languages are really incredible!

May 10, 2009

Power WebPart 3.0 – Runas-System

In general code in SharePoint runs with the privileges of the current user, but sometimes you need to run code with elevated privileges. The SharePoint object model covers this scenario with the SPSecurity.RunWithElevatedPrivileges. In Power WebPart you can use the “Runas-System” function to run script with elevated privileges. Usage: Runas-System –script
Example 1: Output Current Identity
function render($writer)
{
   $identiy1 = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
   $identiy2 = Runas-System "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name"

   $writer.Write($identiy1)
   $writer.Write($identiy2)
}
Example 2: Call a Function with Parameters
function Render-Identity($writer)
{
    $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
    $writer.Write($identity)
}  
function render($writer)
{
   Render-Identity $writer
   Runas-System "Render-Identity `$writer"
}
Example 3: Call a Function with Return Value
function Get-Identity
{
    return [System.Security.Principal.WindowsIdentity]::GetCurrent().Name 
}

function render($writer)
{
   $identity1 = Get-Identity
   $identity2 = Runas-System "Get-Identity"

   $writer.Write($identity1)
   $writer.Write($identity2)
}
Example 4: Impersonate SPWeb
function render($writer)
{
   $writer.Write($web.CurrentUser.LoginName)

    $impersonatedWeb = Runas-System "Get-SPWeb $($web.Url)"
    $writer.Write($impersonatedWeb.CurrentUser.LoginName)
    $impersonatedWeb.Dispose()
}

May 2, 2009

Power Tag Cloud - SharePoint Tag Cloud written in PowerShell

I'm a tag cloud fan but unfortunately there isn’t any in SharePoint 2007 out of the box. So let’s write a reusable tag cloud as an example for PowerWebPart. The Power Tag Cloud consists of two web parts, the tag cloud itself and the tag browser.

Tag an item. You can either use a text field with a semicolon as separator or any lookup field. Maybe I show in following post how you can pimp the tag field with a jQuery AJAX auto completion script and PowerWebPart.

image

Configure how your cloud should looks like and behaves. The editor panel is written in PowerShell too!

image

The tag cloud

image

When you click on a tag the tag browser shows all items tagged with this tag within the chosen scope.

image

All this stuff is written in PowerShell!

To get a feeling how PowerWebPart scripts look like:

Power Tag Cloud Script

#init parameters
$scope = Init-Parameter "Parameter1" [String]::Empty
$tagField = Init-Parameter "Parameter2" "Tags" -defaultOnEmpty
$maxFontSize = Init-Parameter "Parameter3" "40" -defaultOnEmpty
$minFontSize = Init-Parameter "Parameter4" "6" -defaultOnEmpty
$color1 = Init-Parameter "Parameter5" "#003399" -defaultOnEmpty
$color2 = Init-Parameter "Parameter6" "#CCCCCC" -defaultOnEmpty
$listType = Init-Parameter "Parameter7" "0" -defaultOnEmpty
$tagBrowser = Init-Parameter "Parameter8" [String]::Empty

# calculate gradient
$r1 = [Convert]::ToInt32($color1.Substring(1,2),16)
$g1 = [Convert]::ToInt32($color1.Substring(3,2),16)
$b1 = [Convert]::ToInt32($color1.Substring(5,2),16)

$r2 = [Convert]::ToInt32($color2.Substring(1,2),16)
$g2 = [Convert]::ToInt32($color2.Substring(3,2),16)
$b2 = [Convert]::ToInt32($color2.Substring(5,2),16)

$diff_r = $r2-$r1
$diff_g = $g2-$g1
$diff_b = $b2-$b1

# css
$defaultCss =@"
.power-cloud A {
TEXT-DECORATION: none
}
.power-cloud A:hover {
TEXT-DECORATION: underline
}
.power-cloud UL {
PADDING-RIGHT: 0px;
PADDING-LEFT: 0px;
PADDING-BOTTOM: 0px;
MARGIN: 0px;
PADDING-TOP: 0px;
LIST-STYLE-TYPE: none;
TEXT-ALIGN: center
}
.power-cloud LI
{
PADDING-RIGHT: 0px;
DISPLAY: inline;
PADDING-LEFT: 0px;
BACKGROUND-IMAGE: none !important;
PADDING-BOTTOM: 0px;
MARGIN: 0px;
PADDING-TOP: 0px;
TEXT-ALIGN: justify
}
"@

$css = Init-Parameter "Parameter9" $defaultCss -defaultOnEmpty

Register-CSSBlock $css

function CreateChildControls($controls)
{
#find all tagged list items
$query = New-Object "Microsoft.SharePoint.SPSiteDataQuery"
$query.Query = @"





"@
$query.ViewFields = ""
#set query scope
if([String]::IsNullOrEmpty($scope) -eq $false){ $query.Webs=""}

#set list type
$query.Lists = ""

$dataTable = $web.GetSiteData($query)

# extract all tags in a list
$global:tags = New-Object System.Collections.ArrayList
$dataTable | Select-Object Tags | %{$_.Tags.Split(';')} | %{$tags.Add($_.Trim().TrimStart('#'))}

# group tags
$global:tagGroups = $tags | Group-Object | Sort-Object Count -Descending
}

function Render($writer)
{
$gradientSteps = $tagGroups.Count

#begin cloud
$writer.Write("
    ")

    for($i=0; $i -lt $tagGroups.Count; $i++ )
    {
    # calculate font size
    $size = [Math]::Round(($tagGroups[$i].Count * $maxFontSize) / $tagGroups[0].Count)
    if($size -lt $minFontSize){$size = $minFontSize}

    # calculate color
    $gradientFactor = $i / $gradientSteps
    $r = $r1 + $diff_r * $gradientFactor
    $g = $g1 + $diff_g * $gradientFactor
    $b = $b1 + $diff_b * $gradientFactor
    $color = "rgb($r, $g, $b)"

    $tagName = $tagGroups[$i].Name
    $tagUrl = "{0}?tag={1}&listType={2}&scope={3}&tagField={4}" -f $tagBrowser, $tagName, $listType ,$scope, $tagField

    #render tag
    $writer.Write("
  • `n$tagName`n")
    }

    # end cloud
    $writer.Write("
")
}


Power Tag Cloud Editor Script
$txtMaxFontSize = New-Object System.Web.UI.WebControls.TextBox
$txtMaxFontSize = New-Object System.Web.UI.WebControls.TextBox
$txtMinFontSize = New-Object System.Web.UI.WebControls.TextBox
$txtMaxColor = New-Object System.Web.UI.WebControls.TextBox
$txtMinColor = New-Object System.Web.UI.WebControls.TextBox
$txtTagField = New-Object System.Web.UI.WebControls.TextBox
$txtTagBrowser = New-Object System.Web.UI.WebControls.TextBox
$drpdScope = New-Object System.Web.UI.WebControls.DropDownList
$drpdLists = New-Object System.Web.UI.WebControls.DropDownList
$txtCss = New-Object iLoveSharePoint.WebControls.SimpleTextEditor

function CreateChildControls($controls)
{
$drpdScope.Items.Add([String]::Empty)
$drpdScope.Items.Add("Recursive")
$drpdScope.Items.Add("SiteCollection")


$drpdLists.Items.Add($(New-Object System.Web.UI.WebControls.ListItem("Generic list","0")))
$drpdLists.Items.Add($(New-Object System.Web.UI.WebControls.ListItem("Document Library","1")))
$drpdLists.Items.Add($(New-Object System.Web.UI.WebControls.ListItem("Discussion forum","3")))
$drpdLists.Items.Add($(New-Object System.Web.UI.WebControls.ListItem("Vote or Survey","4")))
$drpdLists.Items.Add($(New-Object System.Web.UI.WebControls.ListItem("Issues List","5")))

$controls.Add($drpdScope)
$controls.Add($drpdLists)
$controls.Add($txtTagField)
$controls.Add($txtMaxFontSize)
$controls.Add($txtMinFontSize)
$controls.Add($txtMaxColor)
$controls.Add($txtMinColor)
$controls.Add($txtTagBrowser)

$txtCss.DisplayText = "Edit CSS"
$controls.Add($txtCss)
}


function OnSyncChanges()
{
$this.EnsureChildControls()

$drpdScope.SelectedValue = $webpart.Parameter1
$txtTagField.Text = $webpart.Parameter2
$txtMaxFontSize.Text = $webpart.Parameter3
$txtMinFontSize.Text = $webpart.Parameter4
$txtMaxColor.Text = $webpart.Parameter5
$txtMinColor.Text = $webpart.Parameter6
$drpdLists.SelectedValue = $webpart.Parameter7
$txtTagBrowser.Text = $webpart.Parameter8
$txtCss.Text = $webpart.Parameter9
}

function OnApplyChanges()
{
$this.EnsureChildControls()

$webpart.Parameter1 = $drpdScope.SelectedValue
$webpart.Parameter2 = $txtTagField.Text
$webpart.Parameter3 = $txtMaxFontSize.Text
$webpart.Parameter4 = $txtMinFontSize.Text
$webpart.Parameter5 = $txtMaxColor.Text
$webpart.Parameter6 = $txtMinColor.Text
$webpart.Parameter7 = $drpdLists.SelectedValue
$webpart.Parameter8 = $txtTagBrowser.Text
$webpart.Parameter9 = $txtCss.Text

return $true
}

function Render($writer)
{
$writer.Write("")

Render-Row "Scope" $drpdScope $writer
Render-Row "List Type" $drpdLists $writer
Render-Row "Tag Column Name" $txtTagField $writer
Render-Row "Tag Browser Url" $txtTagBrowser $writer
Render-Row "Max font size" $txtMaxFontSize $writer
Render-Row "Min font size" $txtMinFontSize $writer
Render-Row "Max color (hex)" $txtMaxColor $writer
Render-Row "Min color (hex)" $txtMinColor $writer
Render-Row "Style" $txtCss $writer

$writer.Write("
")

}

function Render-Row($text, $control, $writer)
{
$writer.Write("")
$writer.Write("")
$writer.Write("
$text
")
$control.RenderControl($writer)
$writer.Write("")
$writer.Write("")
}

Power Tag Cloud Browser Script
function CreateChildControls($controls)
{
$global:tag = $page.Request["tag"].ToString()
$global:scope = $page.Request["scope"]
$global:listType = $page.Request["listType"].ToString()
$global:tagField = $page.Request["tagField"].ToString()

$query = New-Object "Microsoft.SharePoint.SPSiteDataQuery"

#set query scope
if([String]::IsNullOrEmpty($scope) -eq $false){ $query.Webs=""}

$query.Lists = ""

$query.Query = @"


$tag


"@
$query.ViewFields = ""
if($listType -ne "1"){$query.ViewFields +=""}

$global:table = $web.GetSiteData($query)
}

function Render($writer)
{
$writer.Write("

Tag: $tag

")
foreach($row in $table)
{
$writer.Write("
")
$writer.Write($(Build-Headline $row))
$writer.Write($(Build-Body $row))
$writer.Write("

")
}
}

function Build-Headline($row)
{
$siteId = $site.ID
$href = "/_layouts/CopyUtil.aspx?Use=id&Action=dispform&ItemId={0}&ListId={1}&WebId={2}&SiteId=$siteId" -f $row["Id"],$row["ListId"],$row["WebId"]
if($global:table.Columns.Contains('Title') -eq $false -or [String]::IsNullOrEmpty($row["Title"]))
{
$title = $row["FileLeafRef"].Split('#')[1]
}
else
{
$title = $row["Title"]
}

$html = "" -f $href, $title

return $html
}

function Build-Body($row)
{
$tags = $row[$tagField].Split(';')

$html = "Tags: "

foreach($tag in $tags)
{
$tag = $tag.Trim().TrimStart('#')
$html += "$tag "
}

$html += "

"

return $html
}

function OnError($ex, $writer)
{
$writer.Write("Please ensure that tag, listType, tagField and scope url parameters are provided.")
}

You can download the two wepart files on the PowerWebPart 3.0 release homepage and then import them into the web part gallery or just copy and paste the scripts. An exported PowerWebPart .webart file self contains it script and digital signature, so you can simply export and import PowerWebParts. An end user will never notice if the web part is written in PowerShell or C#. Only farm admins can see and change the scripts. Note that when you import a PowerWebPart from a foreign SharePoint farm the signature will be invalid. A farm admin can simply resign the script through a click on “Apply” in the web part’s editor panel.