Friday, 21 December 2012

Workflow email task Link

So you've created a work flow in SharePoint Designer and now you want to let someone know they have a task, but you don't want to just tell them that they have a task and go find it you want to send them a link to the task. Let's face it odds are they're a middle tier manager who's responsibility extends to approving business card request and paid time off; a B.Com degree paying off.

So let's pretend that you have a workflow already set up and all you want to do is create this assignment email: 
In SharePoint Destroyer Designer navigate to your workflows

once you've selected your work flow, edit it.


now that you're ready to edit your work flow, add a step and inside that step create a "start custom task process" action. In the picture below the task is called "Business Card request Task" this is a name I gave it, originally it should start as "Task".


once you've opened the task, under Customization click "Change the behaviour of a single task"


this will load 5 default steps
  1. Before a task is assigned
  2. when a task is pending
  3. when a task expires
  4. when a task is deleted
  5. when a task is completed
You are concerned with the second one "Pending". Add the action email user.


Once you select the user add the appropriate fields


To Create the link address click the link button circled in green above.


Click the fx button to build the URL for your link

Select the current task, and pick the Form_URN, this will give you the task item url

Keep hitting ok followed by a publish and give it a try.

Wednesday, 19 December 2012

Find Text on Publishing Page

Sometimes you need to find a specific text string on your site, maybe it's a common typo, or it could be a name change lets say mr Chooch turned into Dr Chooch and you want to know all the pages that Mr Chooch appears on.


param($web_app_url=$(read-host "Please provide web app url of the list to be delete (EG:'http://SSRAP1'):"))

$TextToFind = $(read-host "Please Provide the text to find")
 
if($ListToDelete -eq $null -or $ListToDelete -eq '')
{
    throw "You must specify a text string to search for"
}

function LoadSharePointPowerShellEnviroment
{
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

function ScrubPages($nodeWeb2)
{
    $pubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($nodeWeb2)
   
    try
    {
        $pubPages = $pubWeb.GetPublishingPages($pubWeb)
        foreach($pp in $pubPages)
        {
            $content = $pp.ListItem["Page Content"]
       
            if($content -ne $null)
            {
                if ($content.Contains($TextToFind))
                {
                    $data = @{"url" = $pubWeb.url.ToString() + "/pages/" + $pp.name.ToString() }
                    New-Object PSObject -Property $data         
                }
            }
        }
    }
    catch [Exception]
    {
         #It wasn't a publishing page
    } 

}

function RecurseNav([Microsoft.SharePoint.SPWeb]$nodeWeb)
{
    $subWebs = $nodeWeb.GetSubwebsForCurrentUser()
    ScrubPages($nodeWeb)

    foreach ($nextweb in $subWebs)
    {
        RecurseNav($nextWeb)
    }
}


try
{
    if($web_app_url -eq $null -or $web_app_url -eq '')
    {
        throw "You must specify your server name. Value provided was null/empty."
    }
  
    $web = Get-SPWeb $web_app_url

    RecurseNav($web) | Out-GridView
}
catch [Exception]
{
    Write-Host -ForegroundColor Red "Error: $_"


Saturday, 1 December 2012

Validate & Verify a Date

When I say Verify, I mean make sure that it's in a correct format; this can very easily be accomplished using regex; if you don't know what that is, it's basically an expression to check formatting of a string against. I suggest reading the following site:

Zytrax this is an excellent source for regex knowledge, I use it regularly.

The following JavaScript function takes in a string and checks it against the following pattern ##/##/####. you may notice it doesn't make sure that the month is between 1-12 or the day is less then 31, it just makes sure that you entered 3 numbers and that they're separated by forward-slashes.

function validdateDate(dateString)
{
    var datePattern = /^\d{2}[/]\d{2}[/]\d{4}$/;
    return datePattern.test(dateString);
}

Now Validate, well that's a bit more tricky. First lets talk about what I mean by validate, if you run the date 02/29/2011 against a simple date Regex it will come back as valid, but it's clearly not since that date never occurred.

At first I thought I could just load the individual month day year into a constructor for a JavaScript Date object and just get an invalid date exception, but no such luck, lovely JavaScript  will just bump the date up to 03/01/2011. Good bad it doesn't really matter, what matters is that I still haven't validated my Date. It's high school programming time.

To resolve this issue first we need a function to check if we're dealing with a leap year, simple enough you can grab the algorithm from numerous sites; I got this one from Wikipedia or if your super lazy it's below.

function isLeapYear(year)
{
    if(year%400==0)
        return true;
    else if(year%100==0)
        return false;
    else if(year%4==0)
        return true;
    return false;
}

Now that we can tell if we're dealing with a leap year lets check to make sure that our date is an actual date. that has occurred.

function verifyDate(dateString)
{
    var daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];

    if(dateString.length != 10)
        return false;

    var da = dateString.split("/");
  
    if(da[2] <= new Date().getFullYear())
    {
        if(da[0] > 0 && da[0] < 13)
        {
            if(da[0] == 2 && isLeapYear(da[2]))
                return (da[1] > 0 && da[1] < 30);
            else
                return (da[1] > 0 && da[1] < daysInMonth[da[0]-1]);
        }
    }
    return false;
}

Well there you have it, are there other much more robust ways to check date, sure there are, could I have written this function to take in some sort of pattern to check against rather then just month/day/year, absolutely, but i'm not writing an API, I'm just making a one off function to filter out shitty data before it hits my server.

Monday, 19 November 2012

CSS selector

When it comes to selecting html tags by their ID or Class specifically in SharePoint you have id's that look like this"WebPartctl00_m_g_e7bf4283_529c_413e_884c_10c2adb8690a", now wtf is the jargon after webpart? I don't want to create a css selector that looks like

div#WebPartctl00_m_g_e7bf4283_529c_413e_884c_10c2adb8690a

so instead i'd use an attribute selector

div[id^="WebPart"] {
  font-size:1.5em
}

or i could use

div[id*="Part"] {
  font-size:1.5em
}

or if i hate myself

div[$="ctl00_m_g_e7bf4283_529c_413e_884c_10c2adb8690a"] {
  font-size=1.5em
}

so to sum it up

^= "selects text at the beginning of the attribute"
*= "selects text that's contained in our attribute"
$= "selects text at the end of the attribute"

Friday, 16 November 2012

Simple jQuery & JavaScript Bubble Sort

jQuery is pretty powerful, and you should leverage it as much as possible. Someone might say but what if the client has javascript disabled? My response is simple, they don't even deserve to sort. anyway here's a quick example.

Here's the HTML

<html>
  <head>
    <title>Date Example</title>
    <script src = "jquery-1.6.1.js"></script>
    <script src = "script.js"></script>
  </head>
  <body>
    <input type = "button" value = "sort" onclick="sort();" />
    <div>Nov 12, 2012 1:00 PM EST</div>
    <div>Oct 09, 2012 5:00 PM EDT</div>
    <div>Nov 29, 2012 2:00 PM EST</div>
    <div>Oct 09, 2012 5:00 PM EDT</div>
    <div>Nov 09, 2012 2:00 PM EST</div>
    <div>Nov 19, 2012 2:00 PM EST</div>
    <div>Nov 09, 2012 2:00 PM EST</div>
    <div>Nov 29, 2012 2:00 PM EST</div>
    <div>Nov 05, 2012 12:00 AM EST</div>
    <div>Nov 08, 2012 9:10 AM EST</div>
  </body>
</html>

and followed by the JQuery

function sort()
{
  var swap = false
  var prev = null;
 
  do {
    prev = null;
    swap = false;
    $('div').each(
    function (indexelement)
    {
      if(prev != null && shouldSwap(prev,element))
      {
        $(this).after($(prev));
        swap = true;
      } 
  
      prev = element;
    });
  }while(swap)
}

function shouldSwap(d1d2)
{
  var dateOne = new Date($(d1).text());
  var dateTwo = new Date($(d2).text());
 
  return dateOne > dateTwo;
}

or if you prefer vanilla JavaScript (which I now do, since I haven't written JQuery in years)

function sort() {
  let swap = false
  let prev = null;
  let predicate = (d1d2=> new Date(d1) > new Date(d2);
  
  do { 
    prev = null;
    swap = false;
    const divs = [...document.getElementsByTagName("div")];
    
    divs.forEach((elementindex=> {
        if(prev != null && predicate(prev.innerText, element.innerText)) {
          element.after(prev);
          swap = true;
        } 
        prev = element;
   });
 } while(swap)
}

now you may notice this is not the most efficient bubble sort, it's a simple example to prove a point not efficiently sort 1'000'000 records.

Tuesday, 13 November 2012

Populate List with Attachment

once in a while you'll need to create a list that contains an attachment; why not use a document library or a documents set, well i'll tell you why. When I came up with this i was a nOOb and had no idea what I was doing. So this example is by no means a best practice, but hey maybe one day I'll come across a scenario where it'll come in handy, so hey toss it into the old toolbox for later.



$RootURL = "http://ssrdevinter.corp.windsor/"

function LoadSharePointPowerShellEnviroment
{
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

function AddAttachment($item, $filePath)
{
    $bytes = [System.IO.File]::ReadAllBytes($filePath)
    $item.Attachments.Add([System.IO.Path]::GetFileName($filePath), $bytes)
    $item.Update()
}
     
function CreateJobPosting($newJobPost, $Title, $PostingID, $StartDate, $EndTime, $DepartmentIndex, $DescriptionPath)  
{
    $newJobPost["Title"] = $Title
    $newJobPost["PostingID"] = $PostingID
    $newJobPost["StartDate"] =  $StartDate
    $newJobPost["EndDate"] = $EndTime
    $newJobPost["Department"] = 1
    AddAttachment $newJobPost $DescriptionPath
    $newJobPost.update()
   
    Write-Host -ForegroundColor Green Job Posting: $Title Completed
}
 
try
{
    LoadSharePointPowerShellEnviroment
    $RootSite = new-object Microsoft.SharePoint.SPSite($RootURL)
    $jobWeb = $RootSite.rootweb.webs["cityhall"].webs["work-for-windsor"]
    $JobPostingList = $jobWeb.lists["JobPostingsListInstance"]
 
    CreateJobPosting $JobPostingList.items.Add() "It Weather Widget Updater" "IT100" ([DateTime]::Now) ([DateTime]::Now.addDays(15)) 1 "c:\test.txt"
    CreateJobPosting $JobPostingList.items.Add() "Barista" "IT001" ([DateTime]::Now) ([DateTime]::Now.addDays(5)) 1 "c:\test.txt"
    CreateJobPosting $JobPostingList.items.Add() "Bard" "IT002" ([DateTime]::Now) ([DateTime]::Now.addDays(5)) 1 "c:\test.txt"
    CreateJobPosting $JobPostingList.items.Add() "Easter Bunny" "IT003" ([DateTime]::Now) ([DateTime]::Now.addDays(5)) 1 "c:\test.txt"

}
catch [Exception]
{
    Write-Host -ForegroundColor Red "Error in  Create job Postings$_"
}
finally
{
    $jobWeb.Dispose()
}  

Thursday, 8 November 2012

List non published pages

This is a PowerShell snippet that lists all non published pages.


param($web_app_url=$(read-host "Please provide web app url of the list to be delete (EG:'http://SSRAP1'):"))
 
function LoadSharePointPowerShellEnviroment
{
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

function NonPublishedPagesReport($nodeWeb2)
{
    $pubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($nodeWeb2)
    $pubPages = $PubWeb.GetPublishingPages()

    foreach($pp in $pubPages)
    {
        if ($pp.ListItem.File.level -ne "Published")
        {
            $data = @{"url" = $pubWeb.url.ToString() + "/pages/"
                      "name" = $pp.name.ToString()
                      "Status" = $pp.ListItem.File.level.ToString()
                      "user" = $pp.ListItem.File.CheckedOutByUser.LoginName
                      }
             New-Object PSObject -Property $data        
        }
    }
}

function RecurseNav([Microsoft.SharePoint.SPWeb]$nodeWeb)
{
    $subWebs = $nodeWeb.GetSubwebsForCurrentUser()
    NonPublishedPagesReport($nodeWeb)

    foreach ($nextweb in $subWebs)
    {
        RecurseNav($nextWeb)
    }
}


try
{
    if($web_app_url -eq $null -or $web_app_url -eq '')
    {
        throw "You must specify your server name. Value provided was null/empty."
    }
 
    $web = Get-SPWeb $web_app_url

    RecurseNav($web) | Out-GridView
}
catch [Exception]
{
    Write-Host -ForegroundColor Red "Error: $_"
}

Tuesday, 6 November 2012

Elevated Privileges

In SharePoint once in a while you need to run something in an anonymous environment that requires user credentials not very promising is it? well luckily you can run your request using elevated privileges, you just need to remember to grab your site and your web again inside your delegate.

 string welcomePageURL = null;

 if (PublishingWeb.IsPublishingWeb(SPContext.Current.Web))
 {
       SPSecurity.RunWithElevatedPrivileges(delegate()
       {
           using (SPSite elevatedSite = new SPSite(SPContext.Current.Site.ID))
           {
               using (SPWeb elevatedWeb = elevatedSite.OpenWeb(SPContext.Current.Web.ID))
               {
                   PublishingWeb pWeb = PublishingWeb.GetPublishingWeb(elevatedWeb);
                   welcomePageURL= pWeb.DefaultPage.ServerRelativeUrl;
               }
           }
       });
   }

Friday, 2 November 2012

Remove user From all Groups

PowerShell script to remove a user from all groups in a web app


param($web_app_url=$(read-host "Please provide web app url of the list to be delete (EG:'http://Wingtip'):"))

function LoadSharePointPowerShellEnviroment
{
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

try
{
    if($web_app_url -eq $null -or $web_app_url -eq '')
    {
        throw "You must specify your server name. Value provided was null/empty."
    }
   
    $UserToRemove = $(read-host "Please Provide the User to remove; include the domain eg: domain/smithj")
   
    if($UserToRemove -eq $null -or $UserToRemove -eq '')
    {
        throw "You must specify your user to remove. Value provided was null/empty."
    }
   
    LoadSharePointPowerShellEnviroment
   
    $site = new-object Microsoft.SharePoint.SPSite($web_app_url)
    $web = $site.OpenWeb()
   
    $web.SiteUsers.Remove($UserToRemove)
}
catch [Exception]
{
    Write-Host -ForegroundColor Red "Error: $_"
}  
finally
{
    $web.Dispose()
    $site.Dispose()
}

Delete All Groups

This script Deletes all SharePoint groups


param($web_app_url=$(read-host "Please provide web app url of the list to be delete (EG:'http://Wingtip'):"))

function LoadSharePointPowerShellEnviroment
{
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

try
{
    if($web_app_url -eq $null -or $web_app_url -eq '')
    {
        throw "You must specify your server name. Value provided was null/empty."
    }
   
    LoadSharePointPowerShellEnviroment
   
    $site = new-object Microsoft.SharePoint.SPSite($web_app_url)
    $web = $site.OpenWeb()
    $groups = $web.sitegroups
    $GroupsToDelete = @()
   
    foreach ($groupToDelete in $groups)
    {
        write-host "Group to Delete : ",$groupToDelete -foregroundcolor Magenta
        $GroupsToDelete += $groupToDelete.Name
    }
   
   
    foreach ($delgroup in $GroupsToDelete)
    {
        $web.SiteGroups.Remove($delgroup)
        write-host "Group Deleted : ",$delgroup -foregroundcolor Green
    }
   
}
catch [Exception]
{
    Write-Host -ForegroundColor Red "Error: $_"
}  
finally
{
    $web.Dispose()
    $site.Dispose()
}

Tuesday, 16 October 2012

Delete a List


Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
             
<#
.Synopsis
   Delete Sharepoint list(s) from entire site
.DESCRIPTION
   Delete SharePoint list instance(s) from an entire site, all subwebs
.EXAMPLE
   delete-cSharePointList -ListTitle ListName -webUrl http://wingtipserver
.EXAMPLE
   delete-cSharePointList -ListTitle ListName -webUrl http://wingtipserver -subwebs
.EXAMPLE
   delete-SharePointList -ListTitle_1, listTitle_2, listTitle_n -webUrl http://wingtipserver
.EXAMPLE
   delete-SharePointList -ListTitle_1, listTitle_2, listTitle_n -webUrl http://wingtipserver -subwebs
#>
function delete-SharePointList
{
    [CmdletBinding(SupportsShouldProcess=$true, confirmImpact='High')]
    Param
    (
        # an array of list titles to delete
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [System.String[]] $ListTitle,
              # The url of the web the lists reside in
              [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
              [ValidateScript({Get-SPSite -Identity $_ | Select-Object -Property Exists -ErrorAction SilentlyContinue})]
              [System.String] $siteUrl,
              # switch to specify whether to delete list from all webs
              [Parameter(ValueFromPipelineByPropertyName=$true, Position=2)]
              [switch] $subwebs
    )

    Begin
    {
              $SPSite = Get-SPSite $siteUrl
    }
   
       Process
    {
              if($subwebs)
              {
                     $SPSite.AllWebs | ForEach-object{
                           deleteList $_ $ListTitle
                     }
              }
              else
              {
                     deleteList $spsite.RootWeb $ListTitle
              }
       }
      
    End
    {
              $SPSite.Dispose()
    }
      
      
}

function deleteList($currentWeb, $listTitle)
{
       $listTitle | ForEach-Object{
              $listInstance = $currentWeb.Lists.TryGetList($_)
                          
              if($listInstance -ne $null)
              {
                     Write-Output ("found a list with the title {0} in web {1}" -f $_, $currentWeb.Url)
             
                     If ($psCmdlet.shouldProcess("$listInstance in web $webUrl", "Delete List"))
                     {
                           $listInstance.Delete()
                           Write-Output ("Deleted list {0} in {1}" -f $_, $currentWeb.Url)
                     }
                     else
                     {
                           Write-Output ("Did not delete list {0} in {1}" -f $_, $currentWeb.Url)
                     }
              }
              else
              {
                     Write-Output ("Failed to find list with title {0} in web {1}" -f $_, $currentWeb.Url)
              }
       }

}

Find Deployed Feature

If you're getting an error that refers to the oh so helpful GUID, here's a handy little script that lists Features and filters them
By Guid:
$results=Get-ChildItem "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\" feature.xml -rec

foreach ($File in $results)
{
   [xml]$feat=gc $file.pspath
   if($feat.feature.id -Like '3752ccf3*')
   {
        $feat.feature | Select ID, Title, Scope, Name
   }
}

By Title:
$results=Get-ChildItem "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\" feature.xml -rec

foreach ($File in $results)
{
   [xml]$feat=gc $file.pspath
   if($feat.feature.title-Like 'TEC*')
   {
        $feat.feature | Select ID, Title, Scope, Name
   }
}

Monday, 15 October 2012

Rewire Content Database

this is the final post in reference to restoring your content database. Here I'll cover how to rewire the db permissions so that your user can actually do something with the db.

lets set our focus back to Microsoft sql server management studio.

Check under security for your machine and make sure that the user account you’re concerned with is available. in my case it's ssrap7/Administrator and to remind you my content database is WSS_Content. as you can see below, ssrap7/administrator is not included in my content database so I'm going to go ahead and add it.

to add a new user simply right click on your content database and hit add user, just make sure to select db_owner, refer to image below.

now with with db_owner checked off click the OK button. Next run the following Powershell


function LoadSharePointPowerShellEnviroment
{
                write-host
                write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
                Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
                Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}
LoadSharePointPowerShellEnviroment
Mount-SPContentDatabase "WSS_Content" -DatabaseServer "SSRAP7" -WebApplication
http://SSRAP7/

make sure to specify your database name, database server, and web application URL.

once you've run the power shell, you must add your account as local site admin

Open central administration-> application management ->Select Change site Collection administrators

This will show you the site collection administrators set the primary site collection administrator to your local administrator


hit ok, and BAM, you're done.

Restore a Content Database

In my previous post I demonstrated how to back a content database up, which is all fine and dandy but ultimately useless if you can't restore it.

Go to the manage content databases screen in central administration.
Once there make sure you have the correct web application selected, and click on your content database.
Scroll down to the bottom of the options for your content database and check off "Remove content database" then click OK
When complete, you will be redirected to the “manage content database” screen in central administration with your content database no longer available.

Do the following before the restore:
Stop IIS; use iisreset /stop in the command prompt
Stop the following services
  • SharePoint 2010 Administration
  • SharePoint 2010 Timer
Open up SQL Server Management studio
Right click on the Database you just removed
Go to Tasks-> Restore-> Database...


On the restore Database dialogue, under Source for restore check From device

Then click the browse (...) all the way to the right of the from device: radio control. This will bring up the Specify Backup dialogue.

here select the back up file you wish to restore, refer to my previous post to find out how to make one.

Once you’re back to the restore database dialogue, make sure to actually select your back up file, by checking it off.


With your back up file selected click the options page in the left hand pane. This will bring up the options page.
Make sure to check off overwrite the existing Data base, then hit OK.

This will now restore the content database with production data; this process takes a while so now would be a great time to do some documentation or a coffee break.

Now remember, start up the two services you disabled previously
  • SharePoint 2010 Administration
  • SharePoint 2010 Timer
and start up IIS again iisreset

In the next post I discuss how to rewire your content database to work with your current user