Friday, August 4, 2017

Update content type workflow association approver through PowerShell


I had a requirement to update an approver on a SharePoint workflow which was setup on Document Content Type at the library level. The workflow has been there since SharePoint 2010 and we upgraded to 2013 around 3 years ago. This was an OOB workflow created in UI  (not through SPD) with single task approver with CC user added (see the XML snippet below)

When I opened up workflow settings and in the "Change Workflow" page, I am not seeing plain "Approval" workflow template which is the actual workflow template used but not "Approval - SharePoint 2010". Also the text box is disabled/greyed out and the system isn't letting me change the template throwing the error prompt. Not sure if this is by design or a bug in SharePoint.  


Change workflow page -  Selection is disabled
System don't let you change the template

Anyways, I needed to update the workflow approver with a new employee because the existing one was leaving. When I figured I can't update through UI then I tried through SharePoint Designer which wasn't letting me do it either. So I have chosen PowerShell route to tweak the workflow association properties. 

Association Data in XML


PowerShell snippet.

Just retrieve the list content type's workflow association data which is an xml string and update the approver values.

             $web = Get-SPWeb "http://<SharePoint web URL>"                    
        $list = $web.Lists | Where-Object { $_.Title -eq 'List Title' }                       
        $ListWorkFlowAssns =  $list.WorkflowAssociations        
        foreach($ctype in $list.ContentTypes)
        {
            if ($ctype.Name -eq "Document")
            {                
                $wa = $ctype.WorkflowAssociations.GetAssociationByName("My Approval", $web.UICulture)
                if ($wa -ne $null)
                {                                                                                  
                    $parsedXml = [System.Xml.Linq.XElement]::Parse($wa.AssociationData)                
                    $nodes = $parsedXml.Descendants() | Where-Object {($_.Name.LocalName -eq "Reviewers")}                                    
                    $accountId = $nodes[0].Descendants() | Where-Object {($_.Name.LocalName -eq "AccountId")}
                    $displayName = $nodes[0].Descendants() | Where-Object {($_.Name.LocalName -eq "DisplayName")}
                    $accountId.SetValue("Domain\UserName") # replace value with the new approver AD username
                    $displayName.SetValue("User Full Name") # replace value with the new approver AD Full Name                 
                    $wa.AssociationData = $parsedXml.ToString()
                    $ctype.WorkflowAssociations.Update($wa)               
                    
                }               
            }            
        }

        $list.Update()




I made sure no current instances are running and no new instances are created while I am making these changes. 






Note: If you are following the same approach, please put additional checks and filters while updating. You don't want to update other content types mistakenly or workflow association properties that you are not intended. 

Monday, April 24, 2017

Attended SharePoint Fest DC 2017


Last week I attended the SharePoint conference in Washington DC- SharePoint Fest DC 2017. It has various tracks such ECM. Search, BI, Office 365, workflow, and development. I have chosen all the developer and implementation topics with the focus on Azure, Office 365 related to SharePoint. 




The development topics that I attended were touched on React, TypeScript, SPFx, SharPoint PnP. I had chance attend sessions from SharePoint's well known MVPs such as Joel Oleson, Asif Rehmani (remember SharePoint workflow vidoe's); few presenters were very young who seemed hard core javascripters. 

Here are the topics that I attended:
  • How to make use of All that’s included in Office 365 – By Asif Rehmani
  • SharePoint Frame work, Angular & Azure Functions: The modern SharePoint developer tool belt – By Sebastien Levert
  • Using Document Sets to implement Business Processes – By Tom Robbins
  • Not sure how you should REACT – By Ryan Schouten
  • Teams, Groups, SharePoint and Yammer….Oh My! – By Joel Oleson
  • Upgrading Legacy SharePoint Customizations to the Add-in Model – By Danny Jessee
  • SharePoint 2016 : What’s New and Why should I upgrade? – By Paul Stark
  • Supercharge your SharePoint Framework Webpart with React – By Eric Overfield
  • Extranets in SharePoint On Premises and Office 365 – By Peter Carson
  • Azure Active Directory (Azure AD) for Office 365 Developers – By Prashanth G Bhoyar

Overall, I liked the conference; came out with one aspiration: do more JavaScript, get on the cloud....

Thursday, January 26, 2017

SharePoint Custom WCF Service with Automatic Formatting



Recently I was writing a custom SharePoint WCF service for using in client side JavaScript libraries (jquery, angular) and C# server side call HttpWebRequest without worrying adding references and proxy classes. Majority of the articles I referred to were only showing JSON output; I wanted this service to provide both JSON and Xml data formats. If the requirement is to output both JSON and Xml formats, I found people writing two method calls:one for each JSON  and Xml. Also, I have seen articles where response was driven through method or query string parameter to get requirement format (WebMessageFormat.Xml, WebMessageFormat.Json) If I follow the former approach as shown below, the amount of code will grow twice the code that I actually need which is very inefficient and demand more maintenance. 

[ServiceContract]
interface IPersonService

    {
         [WebGet(UriTemplate = "person/{id}",  ResponseFormat = WebMessageFormat.Json)]
          Person GetPersonJSON(int id); //get JSON output

         [WebGet(UriTemplate = "person/{id}",  ResponseFormat = WebMessageFormat.Xml)]
          Person GetPersonXml(int id);  //get Xml output
    }

All I wanted was automatic formating based on some configuration or flag set through code. While searching for options. I found this technet article. helpful.
WCF Web HTTP Automatic and Explicit  Formatting options
https://msdn.microsoft.com/en-us/library/ee476510(v=vs.110).aspx

(snippet from technet)

Automatic Formatting

The WCF Web HTTP programming model allows you to dynamically determine the best format for a service operation to return its response in. Two methods for determining an appropriate format are supported: automatic and explicit.
When enabled, automatic formatting chooses the best format in which to return the response. It determines the best format by checking the following, in order:
  1. The media types in the request message’s Accept header.
  2. The content-type of the request message.
  3. The default format setting in the operation.
  4. The default format setting in the WebHttpBehavior
(/snippet from technet)

Solution:
  1. Added web.config file to my custom wcf custom service. 
  2. C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\isapi\<MyCustomWCFService>
  3. Added System.ServiceModel section as show below.
         <configuration>
     <system.serviceModel>
      <behaviors>  
        <endpointBehaviors>        

          <behavior>          

            <webHttp automaticFormatSelectionEnabled="true" />
          </behavior>
        </endpointBehaviors>
      </behaviors>
      </system.serviceModel>
    </configuration>

     
Have only service contract method.


[ServiceContract]
interface IPersonService
    {
         [WebGet(UriTemplate = "person/{id}")]
          Person GetPerson(int id);
   }

Now I can the call same method through jquery/ajax by specifying the ContentType or Accept Header and get the output format that I need. 

JavaScript - JSON Output 


function getPerson() {
            var serviceUri = _spPageContextInfo.webAbsoluteUrl" + "/_vti_bin/<MyCustomService>/PersonService.svc/person/" + id;
            $.ajax({
                type: "GET",
                contentType: "application/json",
                url: serviceUri,
                dataType: "json"
            }).done(function (data) {
                //do something here
            }).fail(function (error) {
            });

        }

JavaScript - Xml Output

function getPerson() {
            var serviceUri = _spPageContextInfo.webAbsoluteUrl" + "/_vti_bin/<MyCustomService>/PersonService.svc/person/" + id;
            $.ajax({
                type: "GET",
                contentType: "application/xml",
                url: serviceUri,
                dataType: "xml"
            }).done(function (data) {
                //do something here
            }).fail(function (error) {
            });

        }

Server Side C# - HttpWebRequest - JSON Output


HttpWebRequest endpointRequest = (HttpWebRequest)HttpWebRequest.Create(serviceUri);
endpointRequest.Method = "GET";
endpointRequest.Accept = "application/json;odata=verbose";



Server Side C# - HttpWebRequest - Xml Output


HttpWebRequest endpointRequest = (HttpWebRequest)HttpWebRequest.Create(serviceUri);
endpointRequest.Method = "GET";
endpointRequest.Accept = "application/xml;odata=verbose";