Tuesday, December 9, 2014

SharePoint Form Validation via PreSaveAction Method 

Context: Conditional validation in SharePoint can be done via out of the box option i.e. List Validation. There are some use cases when one cannot make use of the List Validation. I also got a situation where I want to validate a column based on another column of type Taxonomy. Since Taxonomy type of columns doesnot appear in the List Validation, I used Javascript and overridden default PreSaveAction() Method to implement my custom conditional validation logic.

Scenario: Here, I am creating an example and this is not the actual implementation. 
I created a custom list and added two columns in it i.e. Country (Taxonomy) and City (Text). So, whenever a user enters ‘Germany’ in the country field, the city field will become a mandatory field and ask from the user to fill the value before he wants to save the list item.


Solution: I added a content editor webpart on the NewForm.aspx and later added code in it. The code consist of a method ‘PreSaveAction()’ which is actually overriding the default PreSaveAction method bind with the Click event of the ‘Save’ button on a NewForm.aspx.


The following is how my list looks like along with the column validation;


The following is how my conditional validation looks like once I added my code in the content editor webpart;


The JavaScript code for achieving the above result is as follows;


Wednesday, November 5, 2014

Render custom controls on NewForm using JsLink


Context: I did work on JS Link in the past but one of my fiend wants me to create an example in JsLink so that he can later use it as a foundation for his work. So I created a following example for him. When a user clicks on 'New Item' on a custom SharePoint list named as 'Quiz', he is going to see custom rendering of controls using JS Link .





First, I created a custom list 'Quiz' and added two columns and later attached my JsLink file to the Webpart by navigating to the properties on Default NewForm. I upload my script (JS Link code) to the SiteAssets/js/QuizTimeLimit.js. For reference, please follow the steps in the image;



The Javascript code to render my custom controls using JS Link is as follows;


(function () {

//   Initialize the variables for overrides objects
 var overrideCtx = {};
 overrideCtx.Templates = {};   
 overrideCtx.Templates.Fields = {
  'TimeLimit': { 'EditForm': renderJsLinkCtrls,
        'NewForm': renderJsLinkCtrls},
  'EnableTime':{'EditForm': renderJsLinkCtrls,
        'NewForm': renderJsLinkCtrls}
 };
  
    /*
     * Register the template overrides.
     */   
  SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
 
})();

// Main function to render the custom controls via JsLink 
function renderJsLinkCtrls(ctx)
{
 switch (ctx.CurrentFieldSchema.FieldType) { 
  case "DateTime": 
            return FieldTimeLimit_Display(ctx);
  case "Choice": 
   return FieldTimeEnabled_Display(ctx);
 }  
}

// Render TimeLimit field by adding custom input field with font color red.
function FieldTimeLimit_Display(ctx) {
 
 if (ctx == null || ctx.CurrentFieldValue == null)
  return '';

 var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
 if (formCtx == null || formCtx.fieldSchema == null)
 return '';

 var fldId = "txtTimeLimit";
 var fldName = formCtx.fieldName;
 
  // Register a callback just before submit. 
    formCtx.registerGetValueCallback(formCtx.fieldName, function () {           
  return  document.getElementById(fldId).value;  
    }); 

 var txtTimeLimit = "";
 return txtTimeLimit;
}

// Render EnableTime field by adding custom CheckBox field and registering its Click event.
function FieldTimeEnabled_Display(ctx) {
 
 if (ctx == null || ctx.CurrentFieldValue == null)
  return '';

 var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
 if (formCtx == null || formCtx.fieldSchema == null)
 return '';

 var fldId = "chkbxEnableTimeLimit";  
 // Register the click event of checkbox
 formCtx.registerInitCallback(formCtx.fieldName, function () {
  var chkbxEnableTimeLimit = document.getElementById(fldId); 
  chkbxEnableTimeLimit.addEventListener('click', TimeLimitChecked); 
 });
 
  // Register a callback just before submit. 
    formCtx.registerGetValueCallback(formCtx.fieldName, function () {           
  return  document.getElementById(fldId).value;  
    }); 

 var cbxEnableTimeLimit = "Enable Time Limit";
 return cbxEnableTimeLimit;
}

// If CheckBox marked then enable TimeLimit custom html control.
function TimeLimitChecked(){
 var oTimeLimit = document.getElementById("txtTimeLimit");
 var oChkbxEnableTimeLimit = document.getElementById("chkbxEnableTimeLimit"); 
 if(oChkbxEnableTimeLimit.checked == true){
  oTimeLimit.disabled = true;
 }
 else{
  oTimeLimit.disabled = false;
 }
}

Friday, October 31, 2014

SharePoint 2013 Warmup Script

Context: People were complaining over the performance of their SharePoint sites. So, I decided to write a SharePoint 2013 warmup script. I followed Jasonth script and changed it according to my needs by adding a xml file where I specified all the configurations. The configuration file consist of information, which webapplications should be invoked fully (all Site collections) and which specific site collections should be invoked.
The following is my PowerShell warmup script for Sharepoint 2013;
clear

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null)
{
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

$configurationFilePath = 'D:\Workspace\PS-Scripts\WarmupSettings.xml';

Start-SPAssignment -Global

if (test-path $configurationFilePath) {
    
    [xml]$configurationFile = Get-Content $configurationFilePath;

    Write-Host "Invoking WebApplications with all site(s)...." -ForegroundColor Cyan;
    # Get All WebApplications whose all sites will be invoked
    $appUrls = Select-Xml -Xml $configurationFile -XPath "//WebAplication[@AllSites='TRUE']" |
                    ForEach-Object { $_.Node.url }
                              
    foreach ($appUrl in $appUrls) {        
        $sites = get-spsite -webapplication $appUrl -Limit ALL
        write-host 'WebApplication: ' $appUrl -foregroundcolor "magenta";
        foreach ($site in $sites) {
            write-host $site.Url;
            $r = Invoke-WebRequest -URI $site.Url -UseDefaultCredentials
            $r.StatusCode
        }        
    }

    Write-Host "Invoking Specific Site Collections......." -ForegroundColor Cyan;
    # Invoke Specific Site Collections
    $apps = Select-Xml -Xml $configurationFile -XPath "//WebAplication[@AllSites='FALSE']";
    foreach ($app in $apps) {
         <#$appUrl = $app.Node.url;
         $app = $apps.Node[0]
         $siteUrls = Select-Xml -Xml $app -XPath "/SiteCollection" |
                     ForEach-Object { $_.Node.InnerText }#>         
         
         $appUrl = $app.Node.url;
         write-host 'WebApplication: ' $app.Node.url -foregroundcolor "magenta";
         foreach($siteUrl in $app.Node.SiteCollection){
            $siteCollectionUrl = $appUrl + $siteUrl
            Write-Host $siteCollectionUrl
            $r = Invoke-WebRequest -URI $siteCollectionUrl -UseDefaultCredentials
            $r.StatusCode
         }                                       
    }

                    
 }

Stop-SPAssignment -Global

Write-Host "Finished Invoking Sites....." -ForegroundColor DarkYellow;
The xml file named as ‘WarmupSettings.xml’ is as follows. It is important to note that each WebApplication element has a property ‘AllSites’. When AllSites is set to TRUE, the PS script is going to invoke each site collection once. When AllSites element is set to false, PS Script will invoke site collections which are specified in the SiteCollection element.

  
 
 
 
  /sites/Help  
 
 
  /sites/test1
  /sites/test2
  /sites/test3
  
   

Thursday, October 30, 2014

Caml Query with Managed Metadata, Date Time and Content Type in PowerShell

Context: I had to fetch list items from a list based on a CAML query. The caml query should fetch records based on two different column types(Managed metadata, datetime) and a contenttype.
The following is the PS script that I wrote to accomplish this task.

$listName = "List Name"
$webURL = "http://sharepoint/sites/sharepoint";
$para = "Managed metadata value";
$today = [Microsoft.SharePoint.Utilities.SPUtility]
         ::CreateISO8601DateTimeFromSystemDateTime([DateTime]::Today)
$ContentType = "custom contenttype"; 

$query = New-Object Microsoft.SharePoint.SPQuery
$camlQuery = [string]:: Format('
            
                
                    
                    {0}
                
                
                    
                        
                        {1}
                    
                     
                        
                        {2}
                        
                    
                           
            
        ',$ContentType , $para, $today);

$query.Query = $camlQuery
$listItem = $list.GetItems($query);

foreach($item in $listItem){ 
    Write-Host 'ID: ' $item['ID'] 
    Write-Host 'Title: ' $item['Title'] 
} 

Tuesday, September 23, 2014

Access Denied: Activate Site Collection feature

Context: I deployed a SharePoint solution via PS using Add-SPSolution and Install-SPSolution cmdlets. Once the solution was deployed, I navigated to the site collection for activating site collection feature (Site Settings -> Site Collection features). When I clicked on the respective feature for activating it, I got an error saying that Error: Access Denied. It was strange because I had the necessary right.

Solution: The solution I came up with is to activate the site collection feature by PowerShell (PS). So I wrote the following script;

$mySite = Get-SPSite http://sharepointsite.com
Enable-SPFeature -Identity "Feature Name" -Url $mySite.Url
$mySite.Dispose()


Once I executed the above command, a new error occurred saying that “The Feature is not a Farm Level Feature and is not found in a Site level defined by the Url http://sharepointsite.com”.
In order to solve this issue, I decided to execute the above command(Enable-SPFeature) by providing the feature id. As we know .wsp is a cab file so I decided to extract .wsp in order to get feature.xml file. From their, I got the feature id of the respective feature and executed the command by providing feature id without double quotes. It executed flawlessly and my webpart was ready to be used in the site. Here is the following command that I used;

Enable-SPFeature -Identity  22224bdf-0raa-4a7a-b96e-5b2fd65e3604  -Url $mySite.Url


Cheers… J..I hope it helps.

Monday, September 15, 2014

Working with Network drive via PowerShell(2.0, 3.0 & 4.0)


In Windows PowerShell 3.0 & 4.0, you can use New-PSDrive to access the file system location that can be a remote or on a local computer.
$pass = $password | ConvertTo-SecureString -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PsCredential($user_name,$pass)
New-PSDrive -name CSV-PATH -Root $path -Credential $cred -PSProvider filesystem 
$path = $path + "\" + "file.csv"; 

if(Test-Path $path){
   Write-Host "File found." 
}
Remove-PSDrive -Name CSV-PATH; 

On the other hand,  PowerSehll 2.0. doesnot support New-PSDrive cmdlet. In that case you can use one of the following two ways to access a network drive for reading, creating, updating and deleting a file;
$net.mapnetworkdrive($drive, $path, $true, $username, $password)
$path = $path + "\" + "file.csv"; 
Remove-item $path;
$net.RemoveNetworkDrive($drive)

$uncServer = "\\Servername";
        net use $uncServer $password /USER:$username
$path = $path + "\" + "file.csv";
Remove-item $path; 
net use $uncServer /delete

I hope this post will help someone..Cheers..J

Monday, September 8, 2014

Create and Update Site Collections by reading from Csv files(PowerShell)


Context: The requirement from the customer was to create a Power Shell script that should create Site Collections by reading data from a csv file. Creation of Site collection should include adding default groups and site owners once the site collection is created. The PS script should compile its results (success & failure)  in a csv file at the end. Once the PS script is ready, it should be scheduled via Windows task scheduler. An important requirement that have to be considered while designing the PS Script is to  resume the processing of a Site Collection where it was left (error occurred) in the last run.

Solution: By considering the requirements, It wrote a PS script that creates and processes the  site collection creation by reading data from a csv file. The script is capable of creating of site collections along with the default user groups and site owners. For instance; consider a scenario when script produces an error after creating a site collection. Let's say that error was related to the site owners. In that case, PS script is going to execute next time from the last state. To acheive such a flexibility, different status were introduced which specifies which block of code should run based on the status.
I have used Functions to ensure the usability of the code. As described earlier, the PS script generates a csv file as a log file that describes which site collection were processed successfully and which site collection had problem (error).


clear

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
{
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

# String Constants
$site_status_requested = "Site Requested";
$site_status_creating = "Creating Site Collection";
$site_status_creating_resolved = "Creating Site Collection - Resolved";
$site_status_url_error = "Url Not Resolved";
$site_status_url_error_resolved = "Url Issue - Resolved";
$site_status_addingGroups = "Adding Site Collection Groups";
$site_status_addingGroups_resolved = "Adding Site Collection Groups - Resolved";
$site_status_addingOwners = "Adding Site Owners";
$site_status_addingOwners_resolved = "Adding Site Owners - Resolved";
$site_status_ready = "Site Ready";


$path_csv_read = "C:\ExportCsv\Internal Team Site Requests - Export.csv";
$path_csv_write = "C:\ExportCsv\Site Collection Requests - Status.csv";
$webapplication_url = "https://test/";

# Site Collection Default values
$site_url = "https://WebApp/sites/"
$web_template = "Team Site";
$site_admin_primary = "domain\username";
$site_admin_secondary = ""; 
$site_language = 1033; # English
$site_compatibilitylevel = 15 #For SharePoint 2013


if(Test-Path $path_csv_read)
{            

    $csv_objects= Import-Csv $path_csv_read;

    $items_status_array = @()

    foreach($row in $csv_objects){

        $_sitestatus = $row."Site Status";
        $row_new = $row.PsObject.Copy();    
       
        StartProcessing -row $row -row_new $row_new;
   
        Write-Host "---------Adding in the Csv--------"; 
        Write-host $row_new.'URL Team Site'; 
        Write-host $row_new.'Status Description'; 
        Write-Host $row_new.'Site Status';
        Write-Host "-----------------";
    
        $items_status_array += $row_new;
    }

    if($items_status_array.length -gt 0)
    {        
        #Delete the export file once read.
        Remove-item $path_csv_read;
        #export the items in the csv file                 
        $items_status_array | Export-Csv -path $path_csv_write
        Write-Host "Status of all requested site collection are updated to a a csv file at : " + $path_csv_write;
    }
}

Function StartProcessing($row, $row_new){
    Try
    {        
        switch($_sitestatus)
        {            
            $site_status_requested
            {            
                Start-ProcessingNewSites -row $row -row_new $row_new;      
                
            }

            $site_status_creating
            {   
                Write-Host "site_status_creating block";         
                Start-ProcessingNewSites -row $row -row_new $row_new;      
                
            }
            $site_status_creating_resolved
            {   
                Write-Host "site_status_creating_resolved block";      
                Start-ProcessingNewSites -row $row -row_new $row_new;      
                
            }

            $site_status_url_error
            {
                
                Write-Host "site_status_url_error block";  
                StartProcessingNewSites -row $row -row_new $row_new;
            }
            $site_status_url_error_resolved
            {
                
                Write-Host "site_status_url_error_resolved block";  
                StartProcessingNewSites -row $row -row_new $row_new;
            }


            $site_status_addingGroups
            {                
                Write-Host "site_status_addingGroups block";  
                Process-AddingSiteCollectionGroups -row $row -row_new $row_new;
            }
            $site_status_addingGroups_resolved
            {                
                Write-Host "site_status_addingGroups_resolved block";  
                Process-AddingSiteCollectionGroups -row $row -row_new $row_new;
            }

            $site_status_addingOwners
            {                
                Write-Host "site_status_addingOwners block";  
                Process-AddingSiteOwners -row $row -row_new $row_new;
            } 
             $site_status_addingOwners_resolved
            {                
                Write-Host "site_status_addingOwners_resolved block";  
                Process-AddingSiteOwners -row $row -row_new $row_new;
            }             
         }        
    }
    Catch
    {
        $row_new.'Status Description' = $_.Exception.Message;
    }
}

Function Start-ProcessingNewSites($row, $row_new){  
    $_siteurl = $row.'URL Team Site';             
    if($_siteurl){                                                            
        if($_siteurl -eq $site_url){
            set_SiteStatus -row_new $row_new -site_status $site_status_url_error;
        }
        else{                  
            Process-SiteRequested -row $row -row_new $row_new;
        }
    }
    else{                                     
        set_SiteStatus -row_new $row_new -site_status $site_status_url_error;                             
    }       
}

Function Process-SiteRequested($row, $row_new){   
   Try
    {         

        $_siteurl = get_SiteUrl -siteurl $row.'URL Team Site';
        $_sitetemplate = get_WebTemplateByTitle; 

        set_SiteStatus -row_new $row_new -site_status $site_status_creating;
                              
        Try 
        {         
            $ErrorActionPreference = "Stop"; #Make all errors terminating        
            New-SPSite $_siteurl -OwnerAlias $site_Admin -Language $site_language -Template $_sitetemplate `
                     -Name $row.'Title of TeamSite' -CompatibilityLevel $site_compatibilitylevel `
                     -Description $row.'Site Description';

            $row_new.'URL Team Site' = $_siteurl;

        
            set_SiteStatus -row_new $row_new -site_status $site_status_created; 
                                 
            Create-GroupsInSite -siteurl  $_siteurl -row_new $row_new;

            # Add Site Owners to the Site collection.
            Add-UserOwners -siteurl $_siteurl -row_new $row_new;

            set_SiteStatus -row_new $row_new -site_status $site_status_ready;
         }
         Catch
         {            
            Write-Host "Caught the thrown exception";
            Write-Host $Error[0].Exception;
            $row_new.'Status Description' = $_.Exception.Message;
        }
        Finally
        {
            $ErrorActionPreference = "Continue"; #Reset the error action pref to default
        }                                               
                
    }
    Catch{
        $row_new.'Status Description' = $_.Exception.Message;              
    }
}

# Start Processing a request has a status 'Adding Site Collection Groups'
Function Process-AddingSiteCollectionGroups($row, $row_new){
    Try
    {
        $ErrorActionPreference = "Stop"; #Make all errors terminating

        $_siteurl = $row_new.'URL Team Site';
        Create-GroupsInSite -siteurl  $_siteurl -row_new $row_new;        
        # Add Site Owners to the Site collection.
        Add-UserOwners -siteurl $_siteurl -row_new $row_new;

        set_SiteStatus -row_new $row_new -site_status $site_status_ready;
    }
    Catch
    {
        $row_new.'Status Description' = $_.Exception.Message; 
    }
    Finally
    {
        $ErrorActionPreference = "Continue"; #Reset the error action pref to default
    } 
}

# Start Processing a request has a status 'Adding Site Owners'
Function Process-AddingSiteOwners($row, $row_new){
    Try
    {
        $ErrorActionPreference = "Stop"; #Make all errors terminating

        $_siteurl = $row_new.'URL Team Site';        
        # Add Site Owners to the Site collection.
        Add-UserOwners -siteurl $_siteurl -row_new $row_new;

        set_SiteStatus -row_new $row_new -site_status $site_status_ready;
    }
    Catch
    {
        $row_new.'Status Description' = $_.Exception.Message; 
    }
    Finally
    {
        $ErrorActionPreference = "Continue"; #Reset the error action pref to default
    } 
}


# The purpose of this function is to create default groups into the newly created site.
Function Create-GroupsInSite($siteurl, $row_new){
    Try
    {        
        set_SiteStatus -row_new $row_new -site_status $site_status_addingGroups; 
        $web = Get-SPWeb $siteurl
        $web.CreateDefaultAssociatedGroups($site_admin_primary, $site_admin_secondary, "");
        $web.Dispose();
        set_SiteStatus -row_new $row_new -site_status $site_status_addedGroups;        
    }
    Catch
    {                
        throw $Error[0].Exception.Message;
    }
}

Function Add-UserOwners($siteurl, $row_new){
    Try
    {        
        $web = Get-SPWeb $siteurl;

        #$row_new.'Name of Site Owner' = "fdsa";                                    

        #Full Control Role Definition
        $FullControl = $web.RoleDefinitions["Full Control"]; 
        
        set_SiteStatus -row_new $row_new -site_status $site_status_addingOwners; 
        
        #Get all Owner groups with Full Control permission
        $web.Groups|?{$_.Name -match "Owners"}|%{
 
            $IsGroupFullControl = $_.Roles|?{$_.Name -eq $FullControl.Name;}
            if($IsGroupFullControl)
            {
                $SPGroup = $web.SiteGroups[$_.Name]
                if($SPGroup){      
                    $_user = $web.EnsureUser($row_new.'Name of Site Owner');
                    $_user1 = $web.EnsureUser($row_new.'Name of Site Owner 2');
                    #$_user1 = "www";
                    #Write-Host "User 1 found: " $_user1;
                    #If the both Site Owners are same users.
                    if($_user -eq $_user1)
                    {
                        $SPGroup.AddUser($_user) ;
                    }
                    else
                    {                                                                                                          
                        $SPGroup.AddUser($_user) ;                                                                                                    
                        $SPGroup.AddUser($_user1);
                    }
                }
            } 
        }
        
        set_SiteStatus -row_new $row_new -site_status $site_status_addedOwners;
      
    }
    Catch
    {
        $_errorMessage = $_.Exception.Message;             
        Throw $_errorMessage;
    }
    Finally
    {
        $web.Dispose();
    }
}


Function get_SiteUrl($siteurl){
    Try
    {       
        $status_split = $siteurl.Split("/");
        $site_name =  $status_split[$status_split.Length - 1];
        $url = $site_url + $site_name;    
        return $url;
    }
    Catch{
        set_SiteStatus -row_new $row_new -site_status $site_status_url_error;        
    }   
}

Function get_WebTemplateByTitle(){
    return Get-SPWebTemplate | Where-Object{$_.Title -eq $web_template}
}


Function set_SiteStatus($row_new, $site_status){
    $row_new.'Site Status' = $site_status;
    $row_new.'Status Description' = $site_status    
}

Read Csv File and Update SharePoint List via PowerShell

    clear
    if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
    {
        Add-PSSnapin "Microsoft.SharePoint.PowerShell"
    }

    # String Constants
    $path_csv_read = "C:\Users\maliks\Desktop\ExportCsv\Items - Status.csv";
    $list_name = "Internal Team Site Requests";
    $spweb_name = "https://test/sites/82411";
    $csv_objects= Import-Csv $path_csv_read

    $web = Get-SPWeb $spweb_name;
    $list = (Get-SPWeb $spweb_name).Lists.TryGetList($list_name);


    foreach($row in $csv_objects){
        $item_object = $list.Items.GetItemById($row.ID);
        $item_object["Site Status"] = $row."Site Status";
        $item_object["Status Description"] = $row.'Status Description';
        $item_object["URL Team Site"] = $row."URL Team Site";
        $item_object.Update();
    }
    $web.Dispose();
    Remove-Item $path_csv_read 

Read List items and write them into a csv file via PowerShell


I wrote a PowerShell script which can read list items whose status is false in the ‘site created’ column of my list and later write them into a csv file. The following is the PowerShell script I wrote to achieve this task.


    clear
    if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
    {
        Add-PSSnapin "Microsoft.SharePoint.PowerShell"
    }

    $spweb_name = "https://test/sites/4511";
    $list_name = "Site Collection Request list";
    $path_csv_write = "C:\Users\malik\Desktop\PSScripts\ExportCsv\";

    $web = Get-SPWeb $spweb_name;
    $list = (Get-SPWeb $spweb_name).Lists.TryGetList($list_name);
    $items = $list.Items;

    $exportlist = @()

    $filtereditems = $list.Items | Where-Object{[System.Convert]::ToBoolean($_["Site created"]) -eq $False}
    Write-Host "Number of items found: " $filtereditems.Count;  

    foreach($item in $filtereditems){        
           
        $ps_obj = New-Object PSObject -Property @{
                   “ID” = $item["ID"]
                   “Site Title” = $item["Site Title"]
                   "Url Team Site" = $item["URL Team Site"]
                   "Site Owner" = $item["Site Owner"]
                   
                    
        }
    $exportlist += $ps_obj 
    }

    $expath = $path_csv_write + $list_name + ' - Export.csv' 

    if(Test-Path $expath)
    {
        Remove-Item $expath
    }

    if($exportlist.length -gt 0){
             
        $exportlist | Export-Csv -path $expath
        Write-Host "New requests for Team Sites are exported to a a csv file at : " + $expath;
    }
    $web.Dispose();
I hope this will help some one.
Add and Update List Item into Replica List via List Workflow

Context: The customer has a SharePoint list which manages requests for creating new site collection. In this regard, a mechanism was needed by which new or existing list item can be copied to another internal list (Replica list).

Solution: It can be achieved by writing an event receiver on the list but the customer was not interested in a custom solution. So a workflow was created and attached to the list on item added and item updated events.

The following snapshots will show you, how I have achieved the respective results;




Open SharePoint Designer 2013 and select your list. In my case it is ‘Site Collection Request list’. Now create new list workflow.
  1. Add the ‘if current item field….’ Condition and set parameters as shown in the following figure;


2. Now add ‘Update ListItem’ object from actions object list and configure it as follows;


3. Once you are done with it, the workflow is capable of updating the list items into the replica list whenever any change occur in the original list's list item.
Follow the preceding process for making the workflow capable to create new list items into the replica list. The whole workflow is shown in the following figure;




Monday, July 21, 2014


Find Site collection(Root site) by Title using PowerShell
Add-PSSnapin microsoft.sharepoint.powershell -ErrorAction SilentlyContinue
$allsites = Get-SPSite -WebApplication http://portal.kunde.net -Limit All

foreach($site in $allsites)
{
    if($site.Rootweb.Title -contains 'Site Collection Title to search')
    {
        Write-Host $site.Url
    }
}

Tuesday, June 17, 2014

Search documents from a Document library by calling List.asmx webservice via JavaScript

Context: The requirement from the customer was to provide a search facility on the welcome page of the site where they can find documents by adding a keyword or text. The customer doesn’t want to use the Search service provided by SharePoint and wants the dedicated component only applied to a single document library where he has thousands of documents stored.

Solution: The solution that I proposed is to create a content editor webpart which would be binded to an html page and javascript files. I used JavaScript to call the list.asmx webservice and the following code that I wrote to accomplish this task.
function SearchDocs() {

    var soapEnv =
            "" +
             "" +
             "" +
             "ListName" +
             "" +
             "" +
             "TRUE" +
             "" +
             "TRUE" +
             " " +
             " " +
             " " +
             " " +
             "" + $('#txtExactPhrase').val() + "" +
             "" + $('#txtExactPhrase').val() + " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             "";


    $.ajax({
        url: "http://sharepointsite/_vti_bin/lists.asmx",
        type: "POST",
        dataType: "xml",
        data: soapEnv,
        complete: RenderResults,
        contentType: "text/xml; charset=\"utf-8\""
    });


function RenderResults(xData, status) {
    var hdrTable = "";

    var rowTable = "";
    $(xData.responseXML).find("z\\:row").each(function () {
        var arrPath = $(this).attr("ows_FileRef").split(';#');
        var docname = ExtractValue($(this).attr("ows_FileLeafRef"), 1, ';#');
        var datelastmodified = ExtractValue($(this).attr("ows_Last_x0020_Modified"), 1, ';#');
        datelastmodified = moment(datelastmodified).format('MM/DD/YYYY HH:mm');

        var type = "";
        if ($(this).attr("ows_DocIcon") != null) {
            type = $(this).attr("ows_DocIcon");
        }
        else if ($(this).attr("ows_HTML_x0020_File_x0020_Type") != null) {
            type = "Document set";
        }
        rowTable = rowTable + "";

    });
    var renderTable = hdrTable + rowTable + "
TypeTitleNameLast Modified Date
" + type + "" + $(this).attr("ows_Title") + "" + docname + "" + datelastmodified + "
"; $("#divRenderResults").empty(); $("#divRenderResults").append(renderTable); } }
The following is how the search content editor works when a keyword is searched.


Wednesday, June 4, 2014

SharePoint: Read Word document from document libray, make changes and save as a new Word document in a Document set using C#.Net.

Context: I had to read a word template document consisting of content controls from a document library. The next step, I had to do is to read properties of the document set and place data into the content controls. I then have to create a new document based on the word template and changes and later save into the document.


Solution: I used OpenXml to read the word document from the document libarary. Later I made the changes by replacing the values in the XElement objects. Once I have the changes made in the memory, I created a new word document using Microsoft.SharePoint Api(Server object model) and save it into the respective document set. The following is the code that I wrote to achieve this task.



private void ReadTemplateDoc(SPFile spFile, string folderPath)
        {
            byte[] byteArray = spFile.OpenBinary();
            using (MemoryStream mem = new MemoryStream())
            {
                mem.Write(byteArray, 0, (int)byteArray.Length);

                using (WordprocessingDocument wordDoc =
                    WordprocessingDocument.Open(mem, true))
                {
                    XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
                    XDocument xdoc = wordDoc.MainDocumentPart.Annotation();

                    using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
                    using (XmlReader xr = XmlReader.Create(sr))
                        xdoc = XDocument.Load(xr);
                    wordDoc.MainDocumentPart.AddAnnotation(xdoc);
                    XElement bodyElement = xdoc
                                            .Element(w + "document")
                                            .Element(w + "body");
                    IEnumerable contentControlsTags = bodyElement.Descendants(w + "sdt");

                    foreach (XElement contentControlsTag in contentControlsTags)
                    {
                        string strSpFldName = contentControlsTag.Element(w + "sdtPr").Element(w + "tag").Attribute(w + "val").Value.ToString();
                        string strValue = contentControlsTag.Element(w + "sdtContent").Element(w + "r").Element(w + "t").Value.ToString();
                        //Save the metadata into Templates
                        if (hashtableMetaData[strSpFldName] != null)
                        {
                            string[] separator = { ";#" };
                            string valueMetaData = Utility.GetStringBySplit(hashtableMetaData[strSpFldName].ToString(), 1, separator);
                            contentControlsTag.Element(w + "sdtContent").Element(w + "r").Element(w + "t").SetValue(valueMetaData);
                        }
                    }

                    wordDoc.MainDocumentPart.PutXDocument();

                    string filePath = string.Empty;
                    string fileName = string.Empty;

                    using (SPWeb currentWeb = SPContext.Current.Web)
                    {
                        currentWeb.AllowUnsafeUpdates = true;
                        SPList spList = currentWeb.Lists[listId];
                        string documentSetName = spList.GetItemById(itemId).Name;
                        fileName = documentSetName + " - MetaDataReport.docx";
                        filePath = itemUrl + "/" + fileName;
                        spFile.ParentFolder.Files.Add(filePath, mem, true);
                        currentWeb.AllowUnsafeUpdates = false;
                        //Page.ClientScript.RegisterOnSubmitStatement(typeof(Page), "closePage", "window.onunload = CloseWindow();");
                    }

                    Page.Response.Clear();
                    string htmlMessage = "alert('Metadata report generated named as : " + fileName + "');";
                    this.Page.Response.Write("");
                    Page.Response.End();

                  
                }
            }
        }


public class Utility
    {
        public static string GetStringBySplit(string stringWords, int returnPostion, string[] splitChar)
        {
            string[] split = stringWords.Split(splitChar, StringSplitOptions.RemoveEmptyEntries);
            if (split.Length > 1)
                return split[returnPostion];

            return stringWords;
        }
    }
SharePoint: Read Metadata(properties) values of a document set in a document library using C#.Net.


Solution: I used methods from sever object model(Microsoft.SharePoint Api) to read a particular document set as I had list id and item id(document set id). Once I got the document set object, I used hashtable to contain fields and its values. I used that hashtable for further processing. I hope this would help some one...Cheers…J

Hashtable hashtableMetaData = null;

 private void ReadDocumentSet()
        {
            //Get the document library based on list ID
            using (SPWeb currentWeb = SPContext.Current.Web)
            {
                SPList spList = currentWeb.Lists[listId];
                //Get the document set based on item id
                DocumentSet documentSet = DocumentSet.GetDocumentSet(spList.GetItemById(itemId).Folder);

                SPListItem spListItem = documentSet.Item;

                //Collect MetaData
                hashtableMetaData = new Hashtable();

                string str = string.Empty;
                foreach (SPField field in spListItem.Fields)
                {
                    if (spListItem[field.Title] == null)
                    {
                        hashtableMetaData[field.Title] = null;
                    }
                    else
                    {
                        hashtableMetaData[field.Title] = spListItem[field.Title].ToString();
                    }
                }

            }
        }
SharePoint: Show Application page as ‘popup’ when an item(Custom Action) clicked from Document set’s ECB(Edit control block).

Context: The requirement of the customer was to make an option available in the edit control block of the document set from where a specific word document will be generated.

Solution: To accomplish that I had to write the server side code (C#.Net) using an application page. I had to read a word document using OpenXml sdk and further create a new document in the document set via C#.Net. The solution that I proposed is to add a new custom action in the edit control block of a document set. I passed the important information such as ListId, ItemId in the query string while calling the application page. When the user clicks a particular item in the EDC of a document set, an application page will be called and server side (C#.Net) code written will be executed.


 The following snapshots will further help you understand what I am talking about;

Once the user click the 'Generate Metadata Document', the server side code will be executed and the application page will be shown as popup window.




  
    
  


As I want to make the custom action to be part of a document set edit control block(ECD), so I used registrationtype='contenttype' and registrationid='0x0120D520' which is the base document set content type id. The url consist of the application page along with the query string. The querystring is used on the application page to identify the list and the list item using C#.Net.

Tuesday, June 3, 2014

SharePoint: Call new document content type popup page of a document library on button click.

Context: Customer wants to have a button on the home which will be used to open the new document. If the customer wants to create a new document or a document set, he has to navigate to the document library and later clicking the documents tab and selecting a new Document option. In our scenario, the document library consist of documents sets and a document set consist of a content type.

Solution: The solution is based on the content editor webpart. The webpart refers to the html page from the SiteAssets document library. In order to get the Url of the popup new document page, go to the document library settings -> Advance settings-> Launch forms in Dialog (No). After these settings, click again the new document and you can get the url of the popup page. Use that url in your code for invoking the default popup page. Also do remember to revert the settings to Launch forms in Dialog (Yes), once u get the url.

After getting the url, the following code I wrote in a html file to call new document popup;




 
  
        
        
 
 
 
 

Later I deployed the html file in the Asset library. Once I had the html file, I added a content editor webpart on the Welcome page of the site and referred the html file..Thats it..Happy coding..J

Monday, June 2, 2014

SharePoint 2013/2010: Generate custom graphical workflows from SharePoint list data via JSOM & JSPlumb ApI

Context: Customer wants to visualize his data in a graphical workflow as he is working in quick Edit mode with a SharePoint List.


Solution: I used JSOM and JS Plumb API to accomplish this task. I found JSPlumb (JavaScript graphic API) as an excellent API to accomplish my task. It is very well documented and one can find very nice and interactive examples at their site. So the scenario would be whenever the customer leaves the SharePoint list row, I am refreshing the workflow and the customer immediately sees the changes that he has made in the list. 
I created a page and added the List alongside with the content editor webpart and then referenced the html page. The following is the snapshot how it looks like;



The following is another snapshot of how the SharePoint list is automated to work with the graphical workflow based content editor webpart;

The following code I wrote to achieve this task;
First of all there is a html file which consist of some divs and referencing js + css files from Site Assets;














The Sharepoint.js consist of following line of codes. An important idea here to understand is that workflow is generated everytime from the scratch by calling retreiveListItems function once the user click a row i.e. tr. The other line of codes are related to the JSOM and describing how to retrieve the SharePoint list data using JSOM;

$(window).load(function () {    
    $(document).ready(function () {             
        jsPlumb.ready(function () {
            makeSetup();
            retrieveListItems();

            $('tr').click(function () {
                retrieveListItems();
            });
        });
    });
});


function retrieveListItems() {
    
    
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle('Course schedule');

    var camlQuery = new SP.CamlQuery();
    var strCamlQuery = '';

    strCamlQuery += '' +
                    '' +
                    '' +
                    '' +
                    '' +
                    '' +                    
                    '';

    camlQuery.set_viewXml(strCamlQuery);
    this.collListItem = oList.getItems(camlQuery);
    clientContext.load(collListItem);    
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));

}

function onQuerySucceeded(sender, args) {
    
    var listItemInfo = '';

    var listItemEnumerator = collListItem.getEnumerator();
    makeSetup();
    while (listItemEnumerator.moveNext()) {
        var oListItem = listItemEnumerator.get_current();
        //oListItem.get_item('Task_x0020_Type').get_lookupValue()
        
        var arrayListItem = [oListItem.get_id(), oListItem.get_item('Title'), oListItem.get_item('Task_x0020_Type'), oListItem.get_item('Publish_x0020_Date'), oListItem.get_item('Due_x0020_Date')];        
        collectInformation(arrayListItem);

    }

    drawConnectors();

}

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

The purpose of Dynamic.js is generate the windows(divs) using the JSPlumb library in a sequential workflow. The following are the lines of codes that I wrote to generate sequential workflow;
var propertiesArray = []; //All Divs(Window) Array
var connectorArray = []; //JSPlumb Connector Array
var windowCounter = 0;
var currentWindowType = 'Default';
var top = 10;
var left = 30;


function makeSetup() {
    jsPlumb.deleteEveryEndpoint();  // Delete all Endpoints
    removeAllWindowsOnCanvas(); // Delete all Windows
        
    $("
Start").insertAfter("#assessmentJsPlumb").parent(); propertiesArray = []; propertiesArray.push({ 'windowID': 'windowStart', 'title': 'Start', 'type': 'Default', 'publishDate': "01/01/2013", 'dueDate': "01/02/2013" }); $(document).tooltip(); top = 10; left = 30; } function removeAllWindowsOnCanvas() { for (var i = 0; i < propertiesArray.length; i++) { $("#" + propertiesArray[i]['windowID']).remove(); } } //Collect all the assessment items information into the propertiesArray Array Object. function collectInformation(arrayListItem) { var windowID = "window-" + arrayListItem[0]; //Format Dates arrayListItem[3] = arrayListItem[3].format("mm/dd/yyyy"); //Format Publish Date arrayListItem[4] = arrayListItem[4].format("mm/dd/yyyy"); //Format Due Date propertiesArray.push({ 'windowID': windowID, 'title': arrayListItem[1], 'type': arrayListItem[2], 'publishDate': arrayListItem[3], 'dueDate': arrayListItem[4] }); //, , 'dueDate': $("#txtDueDate").val(), , 'Marks': $("#txtMarks").val() } //Calculate the positions of the window and add it. function addWindowsToCanvas() { for (var i = 1; i < propertiesArray.length; i++) { if (i + 1 >= propertiesArray.length) { left = 30; createWindows(i); top = top + 10; } else { if (propertiesArray[i]['dueDate'] == propertiesArray[i + 1]['dueDate']) { left = left - 10; createWindows(i); for (var j = i + 1 ; j < propertiesArray.length; j++) { if (propertiesArray[i]['dueDate'] == propertiesArray[j]['dueDate']) { left = left + 20; top = top - 5; i = j; createWindows(j); } else { i = j - 1; left = 30; top = top + 10; break; } } } else { createWindows(i); top = top + 10; } } } } //Add the windows on the canvas function createWindows(index) { var tooptip = "Publish date: " + propertiesArray[index]['publishDate'] + " Due date: " + propertiesArray[index]['dueDate']; var window = "
"; var previndex = index - 1; var prvWindowID = propertiesArray[previndex]['windowID']; //Create a new div (window) $(window).insertAfter("#" + prvWindowID); // Make the new div draggable jsPlumb.draggable($(".window")); } /* JsPlumb Methods */ //Draw the JsPlumb connectors between the windows. function drawConnectors() { jsPlumb.ready(function () { //jsPlumb.detachEveryConnection(); propertiesArray.sort(function (a, b) { var a1 = a.dueDate, b1 = b.dueDate; if (a1 == b1) return 0; return a1 > b1 ? 1 : -1; }); addWindowsToCanvas(); //jsPlumb.removeEveryEndpoint(); for (var i = 0; i < propertiesArray.length; i++) { for (var j = i + 1; j < propertiesArray.length; j++) { if (propertiesArray[i]['dueDate'] != propertiesArray[j]['dueDate']) { connectorArray.push( jsPlumb.connect({ source: propertiesArray[i]['windowID'], target: propertiesArray[j]['windowID'], anchor: "Continuous", connector: ["StateMachine", { curviness: 1 }], maxConnections: 1 }) ); for (var k = j + 1; k < propertiesArray.length; k++) { if (propertiesArray[j]['dueDate'] == propertiesArray[k]['dueDate']) { connectorArray.push( jsPlumb.connect({ source: propertiesArray[i]['windowID'], target: propertiesArray[k]['windowID'], anchor: "Continuous", connector: ["StateMachine", { curviness: 1 }], maxConnections: 1 }) ); } else { break; } } break; } } } }); } jsPlumb.ready(function () { // helper method to generate a color from a cycle of colors. var curColourIndex = 1, maxColourIndex = 24, nextColour = function () { var R, G, B; R = parseInt(128 + Math.sin((curColourIndex * 3 + 0) * 1.3) * 128); G = parseInt(128 + Math.sin((curColourIndex * 3 + 1) * 1.3) * 128); B = parseInt(128 + Math.sin((curColourIndex * 3 + 2) * 1.3) * 128); curColourIndex = curColourIndex + 1; if (curColourIndex > maxColourIndex) curColourIndex = 1; return "rgb(" + R + "," + G + "," + B + ")"; }; // setup some defaults for jsPlumb. jsPlumb.importDefaults({ Endpoint: ["Dot", { radius: 2 }], HoverPaintStyle: { strokeStyle: "#42a62c", lineWidth: 2 }, ConnectionOverlays: [ ["Arrow", { location: 1, id: "arrow", length: 14, foldback: 0.8 }] ] }); jsPlumb.bind("connection", function (info) { info.connection.setPaintStyle({ strokeStyle: "blue", lineWidth: 3 }); }); });
Note: It is not the actual solution but the foundation from where I started building the bigger solution. Please do let me know if you have any comments and if you need help... I hope it would help someone. Enjoy & Cheers…J

Friday, May 30, 2014

SharePoint 2013/2010: Management of Active Directory users via SharePoint List and Event Receivers (C#.Net).

Context: Customer wants to manage Active Directory User Accounts directly in SharePoint. That means I need a custom SharePoint list to add, modify and delete user accounts. The list has to contain the following fields: Firstname, Lastname, Username, E-Mail and Password.

Solution: I used 'System.DirectoryServices.AccountManagement' API for adding, updating and deleting the active directory users. SharePoint event recievers were used to capture the events when the user of the list wants to delete, update and add an active directory user.

The following code I wrote to achieve this task;


PrincipalContext principalContext = null;
        UserPrincipal _userPrincipal = null;
        String _email = null;
        String _firstName = null;
        String _lastName = null;
        String _userName = null;
        String _password = null;

        /// 
        /// An user is being added in the active directory.
        /// 
        public override void ItemAdding(SPItemEventProperties properties)
        {
            try
            {
                _email = (String)properties.AfterProperties["EMail"];
                _firstName = (String)properties.AfterProperties["FirstName"];
                _lastName = (String)properties.AfterProperties["LastName"];
                _userName = (String)properties.AfterProperties["Username"];
                _password = (String)properties.AfterProperties["Password"];

                Validator _validator = new Validator();
                if (!_validator.IsValidEmail(_email)) // If it is not valid, cancel the current operation
                {
                    properties.Cancel = true;
                    properties.ErrorMessage = "Invalid email value!";
                }
                else
                {                    
                    principalContext = getPrincipalContext();
                    _userPrincipal = new UserPrincipal(principalContext, _userName, _password, true);

                    //User Information
                    _userPrincipal.Name = _firstName + " " + _lastName;
                    _userPrincipal.Description = "This is the user account created from SharePoint list";
                    _userPrincipal.EmailAddress = _email;
                    _userPrincipal.SetPassword(_password);
                    try { _userPrincipal.Save(); }
                    catch (Exception) { }
                    base.ItemAdding(properties);
                }
            }
            catch (Exception ex)
            {
                properties.Cancel = true;
                properties.ErrorMessage = ex.Message;
            }
            finally
            {
                if (principalContext != null)
                    principalContext.Dispose();
                if (_userPrincipal != null)
                    _userPrincipal.Dispose();
            }
        }

        /// 
        /// An user is being updated in the active directory..
        /// 
        public override void ItemUpdating(SPItemEventProperties properties)
        {
            try
            {
                _userName = (String)properties.ListItem["Username"];
                if ((String)properties.AfterProperties["EMail"] != null)
                    _email = (String)properties.AfterProperties["EMail"];
                else
                    _email = (String)properties.ListItem["EMail"];

                if ((String)properties.AfterProperties["FirstName"] != null)
                    _firstName = (String)properties.AfterProperties["FirstName"];
                else
                    _firstName = (String)properties.ListItem["FirstName"];


                if ((String)properties.AfterProperties["LastName"] != null)
                    _lastName = (String)properties.AfterProperties["LastName"];
                else
                    _lastName = (String)properties.ListItem["LastName"];

                if ((String)properties.AfterProperties["Password"] != null)
                    _password = (String)properties.AfterProperties["Password"];
                else
                    _password = (String)properties.ListItem["Password"];

                
                principalContext = getPrincipalContext();
                _userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, _userName);
                _userPrincipal.Delete();
                _userPrincipal.Dispose();
                _userPrincipal = new UserPrincipal(principalContext, _userName, _password, true);
                _userPrincipal.Name = _firstName + " " + _lastName;
                _userPrincipal.Description = "This is the user account created from SharePoint list";
                _userPrincipal.EmailAddress = _email;
                _userPrincipal.SetPassword(_password);
                try { _userPrincipal.Save(); }
                catch (Exception) { }
                base.ItemUpdating(properties);
            }
            catch (Exception ex)
            {
                properties.Cancel = true;
                properties.ErrorMessage = ex.InnerException.Message;
            }
            finally
            {
                if (principalContext != null)
                    principalContext.Dispose();
                if (_userPrincipal != null)
                    _userPrincipal.Dispose();
            }
        }

        /// 
        /// An user is being deleted in the active directory..
        /// 
        public override void ItemDeleting(SPItemEventProperties properties)
        {
            try
            {
                _userName = (String)properties.ListItem["Username"];
                principalContext = getPrincipalContext();
                _userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, _userName);
                _userPrincipal.Delete();
                base.ItemDeleting(properties);
            }
            catch (Exception ex)
            {
                properties.Cancel = true;
                properties.ErrorMessage = ex.InnerException.Message;
            }
            finally
            {
                if (principalContext != null)
                    principalContext.Dispose();
                if (_userPrincipal != null)
                    _userPrincipal.Dispose();
            }

        }

        // Please change username and Password that is allowed to create users in Active Directory
        private PrincipalContext getPrincipalContext()
        {
            string _domainName = getDomainName();
            PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, _domainName,
                        "CN=Users,DC=Contoso,DC=com", ContextOptions.SimpleBind, "salman@contoso.com", "Dev#123");

            return principalContext;
        }

        //Get Domain Name
        private string getDomainName()
        {
            return System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
        }