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;
        }


SharePoint 2013/2010: Filter items from list via CAML & JSOM based on Title and created by fields.

Context: An example to load all items (including all fields) from the  following  list  which  are  containing  the  title  “SP”  and  are  created  by  “Muhammad Salman Malik”.


Solution: I wrote the following html and ECMA script(JSOM) to fetch filtered items based on the criteria defined in the context. The following is the code that I wrote to achieve it;
Title Category Created Created By
$(window).load(function () { $(document).ready(function () { retrieveDocList(); }); }); function retrieveDocList() { var clientContext = new SP.ClientContext.get_current(); var oList = clientContext.get_web().get_lists().getByTitle('SPDocs'); //Replace ListName here. var camlQuery = new SP.CamlQuery(); var strCamlQuery = ''; //fetch the records whose title contains 'SP' and created by 'Steffen Kabus'. strCamlQuery += 'SP' + 'Muhammad Salman Malik' + '' + '' + '' + '' + '' + '' + ''; 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(); var tr_start = ""; var tr_end = " "; var table_html = null; while (listItemEnumerator.moveNext()) { var oListItem = listItemEnumerator.get_current(); table_html += tr_start + oListItem.get_item('Title') + " " + " " + oListItem.get_item('Category') + " " + "" + oListItem.get_item('Created').format('dd.mm.yyyy') + " " + "" + oListItem.get_item('Author').get_lookupValue() + tr_end; } $("#tblSPDocs").append(table_html); } function onQueryFailed(sender, args) { alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace()); }
I have created the following list in a site;
The following is the output that was generated after the development;

SharePoint 2010: Read content controls values from Microsoft Word via OpenXml and save as Metadata (properties) of a Document Set on Event Reciever (C#.Net) 

Context: The customer wants to use the Microsoft Word to fill up his data on a daily basis. They want a functionality in SharePoint that when a document is uploaded into the document set of a document library then all the data from a word document should be synchronized into the document set.

Solution: The solution I proposed is to use content controls in the word document and later I will use event receiver to read the information from a word document and update that information into the (metadata) properties of the document set.

I used ‘ItemAdded’ event receiver for reading the word document via Open Xml sdk 2.0. The following is the code for reading the word document and updating the information of a word document into the document set properties;
     /// 
        /// An item was added.
        /// 
        public override void ItemAdded(SPItemEventProperties properties)
        {
            if (properties.ListTitle == "ListName")
            {
                if (IsFolder(properties.ListItem) == false)
                {
                    DocumentSet documentSet = GetDocumentSet(properties);
                    SPFile spfile = properties.ListItem.File;
                    if (spfile.Name.Contains(".doc"))
                    {
                        if (!spfile.Name.Contains("MetaDataReport"))
                        {
                            
                            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();
                                    
                                    #region "Read word document and find & get content controls"

                                    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");

                                    #endregion
                                    //Check whether the user is uploading in Document Set or on a Library
                                    SPListItem spListItem = null;
                                    if (documentSet != null)
                                        spListItem = documentSet.Item;
                                    else
                                        spListItem = properties.ListItem;

                                    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();

                                        if (spListItem.Fields.ContainsField(strSpFldName))
                                            spListItem[strSpFldName] = strValue;
                                    }
                                    spListItem.Update();
                                }
                            }
                        }
                    }
                }
            }

        }

        /// 
        /// Get document set Object
        /// 
        private DocumentSet GetDocumentSet(SPItemEventProperties properties)
        {
            SPFile spfile = properties.ListItem.File;
            DocumentSet documentSet = DocumentSet.GetDocumentSet(spfile.ParentFolder);
            if (documentSet.ParentFolder.Name == properties.List.Title)
            {
                if (documentSet != null)
                    return documentSet;
            }
            return null;
        }

        /// 
        /// Check if the item is a folder
        /// 
        public bool IsFolder(SPListItem item)
        {
            return item.Folder != null;
        }

public static class LocalExtensions
    {
        public static XDocument GetXDocument(this OpenXmlPart part)
        {
            XDocument xdoc = part.Annotation();
            if (xdoc != null)
                return xdoc;
            using (StreamReader sr = new StreamReader(part.GetStream()))
            using (XmlReader xr = XmlReader.Create(sr))
                xdoc = XDocument.Load(xr);
            part.AddAnnotation(xdoc);
            return xdoc;
        }

        public static void PutXDocument(this OpenXmlPart part)
        {
            XDocument xdoc = part.GetXDocument();
            if (xdoc != null)
            {
                // Serialize the XDocument object back to the package.
                using (XmlWriter xw =
                    XmlWriter.Create(part.GetStream
                   (FileMode.Create, FileAccess.Write)))
                {
                    xdoc.Save(xw);
                }
            }
        }

        public static string StringConcatenate(
            this IEnumerable source)
        {
            return source.Aggregate(
                new StringBuilder(),
                (s, i) => s.Append(i),
                s => s.ToString());
        }
    }

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;
        }
    }


Note: It is not the actual solution but the foundation from where I started building the solution.

Cheers…. J

Thursday, May 29, 2014

SharePoint 2010: Read & save metadata(properties) of a Document set via Content Editor Web Part (JSOM)

Context: The customer wants me to customize the welcome page of each document set in a document library and make some of the properties available on the welcome page. SharePoint allows its users to change the values of the properties by clicking the default ‘edit properties’ option but it was costing the customer one click. The solution I proposed is to make these properties available at the welcome page by adding a content editor webpart.
Solution: The following is the snapshot how the welcome page of each document set in a document library looks like after the adding content editor webpart on the welcome page.




So my content editor web part was referring the html file which is as follows;




    EditMetadata
 
    
 
 
    
    


  

MetaData Properties

Name: Title
Claim Number: Order Number
Vendor Name: Vendor Company
I used ECMA script (Client object model for JavaScript) to read and write the properties of a document set. The following Ecma script, I wrote to fulfill the requirement;
$(document).ready(function () {
    // Wait until SP.JS has loaded before calling getDocSetData   
    ExecuteOrDelayUntilScriptLoaded(getDocSetData, "sp.js");

    $("#btnSave").bind("click", saveDocSetChanges);
});

var context = null;
var web = null;
var docSet = null;

function getDocSetData() {
    context = new SP.ClientContext.get_current();
    // Get the current web     
    web = context.get_web();

    // Get the ID from url
    JSRequest.EnsureSetup();
    var docsetid = JSRequest.QueryString["ID"];

    //Get the document library     
    docSet = web.get_lists().getByTitle("DocumentLibraryName").getItemById(docsetid);
    context.load(docSet);
    //Make a query call to execute the above statements     
    context.executeQueryAsync(OnGetPropertiesSucceeded, OnGetPropertiesFailed);
}

function OnGetPropertiesSucceeded() {
    $("#txtName").val(docSet.get_item('Title'));
    $("#txtTitle").val(docSet.get_item('FileLeafRef'));
    $("#txtClaimNumber").val(docSet.get_item("Claim_x0020_Number"));
    $("#txtOrderNumber").val(docSet.get_item("Order_x0020_Number"));
    $("#txtVendorName").val(docSet.get_item("Vendor_x0020_Name"));
    $("#txtVendorCompany").val(docSet.get_item("Vendor_x0020_Company"));
}

function saveDocSetChanges(){
    docSet.set_item("Title", $("#txtTitle").val());
    docSet.set_item("FileLeafRef", $("#txtName").val());
    docSet.set_item("Order_x0020_Number", $("#txtOrderNumber").val());
    docSet.set_item("Claim_x0020_Number", $("#txtClaimNumber").val());
    docSet.set_item("Vendor_x0020_Name", $("#txtVendorName").val());
    docSet.set_item("Vendor_x0020_Company", $("#txtVendorCompany").val());

    docSet.update();
    context.executeQueryAsync(OnUpdateChanges, OnUpdateChangesFailed);
}

function OnUpdateChanges(){
    alert("Metadata updated.");
}

// Error handler 
function OnGetPropertiesFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
} 

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

Note: It is not the actual solution but the foundation from where I started building the content editor web part. Do also attach your html into the Content editor webpart on the welcome page of a document set once you are ready to reference your html and javascript file.
I hope it would help someone.. J

Wednesday, May 28, 2014

SharePoint 2010: Create Document Sets via PowerShell script.

Context: The customer wanted me to create nearly 5000 document sets in a document library of a SharePoint 2010 site by using a 'Csv file'.


Solution: I wrote a power shell script which uses the ‘Microsoft.SharePoint.PowerShell’ to create document sets. The following Power Shell script that I wrote;

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null)   
{
    Add-PSSnapin Microsoft.SharePoint.PowerShell
}
### Load SharePoint Object Model   
[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint”)   

### Get web and list   
$web = Get-SPWeb http://sp10dev:2011/sites/spsite 
$list = $web.Lists["ListName"]
$cType = $list.ContentTypes["ContentTypeName"]

### Import Csv 
$csv = Import-Csv desktop\SampleData.csv

### Get document name and create document set one by one
foreach($row in $csv)
{
    $docsetname = $row.DocumentSetName
    #write-Host $docsetname

$newDocumentSet = [Microsoft.Office.DocumentManagement.DocumentSets.DocumentSet]::Create($list.RootFolder,$docsetname,$cType.Id,$docsetProperties)
}

$web.Dispose()

Tuesday, May 27, 2014

Error: Error occurred in deployment step 'Retract Solution': Cannot start service SPUserCodeV4 on computer.

Solution: 
In the central administration, goto System Settings section, click Manage services on server. Make sure to select the server on which you want to enable enable sand box solution. Now, search for Microsoft SharePoint Foundation Sandboxed Code Service and click 'start'. Here you go, deploy your solution again and you are ready to go.