Monday, February 8, 2016

Using javascript and REST API to create a sub-site cross site collection

The most important thing is to impersonate the current logged user to create a sub-site in another site collection. And that is also a limitation of REST API when execute the code outside the context. In this article, I create a sample code to create a sub-site in another site collection with the same context and using currently digest to authenticate.

Step 1: get context info

If you are using JSOM, you don't need to get this digest because JSOM is already authenticated to the site and just execute the code in the current context. Only if execute the code outside the context and perform cross-domain or outside SharePoint context, this step is required.

$.ajax({
url: me.rootUrl + "/_api/contextinfo",
method: "POST",
headers: {
 "Accept": "application/json; odata=verbose"
},
success: function(data) {
 __REQUESTDIGEST = data.d.GetContextWebInformation.FormDigestValue;
                        },
                        error: function(e) {
                        }
               });

Step 2: create a sub-site

$.ajax({
 url: rootUrl + "/_api/web/webinfos/add",
 type: "POST",
 headers: {
"Authorization" : "BEARER " + __REQUESTDIGEST,
"Accept": "application/json;odata=verbose",
"Content-Type": "application/json;odata=verbose",
"X-RequestDigest": __REQUESTDIGEST
 },
 data: JSON.stringify({
'parameters': {
 '__metadata': {
'type': 'SP.WebInfoCreationInformation'
 },
 'Url': "YOUR_URL",
 'Title': "YOUR_TITLE",
 'Description': "YOUR_DESCRIPTION",
 'Language': 1033,
 'WebTemplate': "YOUR_SITETEMPLATE",
 'UseUniquePermissions': true|fale
}
 }),
 success: function(data) {

 },
error: function(data, errorCode, errorMessage) {

}
});

With JavaScript, you must define the key "Authorization" : "BEARER " + __REQUESTDIGEST to authenticate the validation logged. If you work with SharePoint Designer, it should be "Authorization" : "BEARER ". You will get the error "invalid format of Authorization" if you  add the digest after Bearer. To avoid the error in SharePoint Designer, you could define the key Authorization with empty string or leave it with Bearer, not add the digest.

Good luck!

Sunday, February 7, 2016

Fix some errors when working with REST API

Response Code: "400 BadRequest"
Response Message: "A node of type 'PrimitiveValue' was read from the JSON reader when trying to read the start of an entry. A 'StartObject' node was expected"

or

Response Message: {“odata.error”:{“code”:”-1, Microsoft.SharePoint.Client.InvalidClientQueryException”,”message”:{“lang”:”en-US”,”value”:”The property ‘__metadata’ does not exist on type ‘SP.User’. Make sure to only use property names that are defined by the type.”}}}

or

Response Message: {“error”:{“code”:”-1, Microsoft.SharePoint.Client.InvalidClientQueryException”,”message”:{“lang”:”en-US”,”value”:”An entry without a type name was found, but no expected type was specified. To allow entries without type information, the expected type must also be specified when the model is specified.”}}}

This error is very common and used to get from result of REST API in workflow or JSON. The special thing of REST API in SharePoint is no having the same as formatted of command structure.

See example:

Upload file to library

url: http://site url/_api/web/GetFolderByServerRelativeUrl('/Folder Name')/Files/Add(url='file name', overwrite=true)
method: POST
body: contents of binary file
headers:
    Authorization: "Bearer " + accessToken
    X-RequestDigest: form digest value
    content-type: "application/json;odata=verbose"
    content-length:length of post body

Create a list

url: http://site url/_api/web/lists
method: POST
body: { '__metadata': { 'type': 'SP.List' }, 'AllowContentTypes': true, 'BaseTemplate': 100,
 'ContentTypesEnabled': true, 'Description': 'My list description', 'Title': 'Test' }
Headers: 
    Authorization: "Bearer " + accessToken
    X-RequestDigest: form digest value
    accept: "application/json;odata=verbose"
    content-type: "application/json;odata=verbose"
    content-length:length of post body

The difference of those structure is a number of parameters and position of parameters. Some parameters must be transfer by query string and some of properties is passed from body of message. Assume that to upload the file name with the path is too long (over 256 characters), it will throw the error.

In the body of message, sometime we must define the variable named "parameters" to pass them to REST API and sometime it is not necessary. The most important thing is to understand of formatted snippet of REST API for separately command.

 

Example above also throws the error and I have a final solution is to remove the "parameters" from body of message. If you set the method is PUT, that means you must provide all properties of endpoint and X-HTTP-Method = MERGE is not affected. If you need to set some properties and leave some properties to be default or not set, you must set the method to POST and define X-HHTP-Method = MERGE

See the result



Good luck!



Stop/Start workflow for SharePoint Online using PowerShell Script

With SharePoint Online, PowerShell is as a connected bridge so that it itself brings a big dedication. I always think about PS script whenever get a new request.

Start/Start workflow for list item of large list is one of tasks that I have been worked on and it was so excited. This article is just sharing the script code with a little explanation to avoid the error while it's executing.

Common Script

$username = "YOUR_TENANTACC" 
$password = "YOUR_PASS" 
$url = "YOUR_SITE_URL"

$securePassword = ConvertTo-SecureString $Password -AsPlainText -Force 

# the path here may need to change if you used e.g. C:\Lib.. 
Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll" 
Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll" 
Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.WorkflowServices.dll" 

# connect/authenticate to SharePoint Online and get ClientContext object.. 
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url) 
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword) 
$clientContext.Credentials = $credentials 

$web = $clientContext.Web
$clientContext.Load($web)
$clientContext.ExecuteQuery()

$lists = $web.Lists
$clientContext.Load($lists)
$clientContext.ExecuteQuery()

$lst = $lists.GetByTitle("YOUR_LIST")
$clientContext.Load($lst)
$clientContext.ExecuteQuery()

#get all workflow subscription in your web/sub-site
$wfServicesManager = New-Object Microsoft.SharePoint.Client.WorkflowServices.WorkflowServicesManager($clientContext, $web)

$wfSubscriptionService = $wfServicesManager.GetWorkflowSubscriptionService()
$wfSubscriptions = $wfSubscriptionService.EnumerateSubscriptions()
$wfInstanceSevice = $wfServicesManager.GetWorkflowInstanceService()

$clientContext.Load($wfSubscriptions)
$clientContext.Load($wfInstanceSevice)
$clientContext.ExecuteQuery()

#initalize the parameters for workflow
$dictionary = New-Object 'system.collections.generic.dictionary[string,object]'
$dictionary.Add("YOUR_VARIABLE", "YOUR_VALUE")

#in case, if you need to start workflow on some items, you could execute the query and start on those items    
$qry = New-Object Microsoft.SharePoint.Client.CamlQuery
$qry.ViewXML = "<View><Query><Where>YOUR_QUERY</Where></Query></View>"
                
$items = $lst.GetItems($qry)
$clientContext.Load($items)
$clientContext.ExecuteQuery()

$srb = $wfSubscriptions | Where-Object { $_.Name -eq "YOUR_WORKFLOW_NAME" }

Start Workflow

$cnt = 0

foreach($itm in $items)
{
    $workflowInstances = $wfInstanceSevice.EnumerateInstancesForListItem($lst.ID, $itm.ID)
    $clientContext.Load($workflowInstances)
    $clientContext.ExecuteQuery()

    # bypass the item which the workflow instance was started
    $instance = $workflowInstances | Where-Object { $_.WorkflowSubscriptionId -eq  $srb.Id -and $_.Status -ne "Started" }
    
    if ($instance -ne $null)
    {
        $resultid = $wfInstanceSevice.StartWorkflowOnListItem($srb, $itm.Id, $dictionary)
        $clientContext.ExecuteQuery()
        Write-Host "Started workfkow [" $srb.Name  "] on item ["  $itm.Id  "]: " $resultid.Value -foregroundcolor Green
        Start-Sleep -s 1
        $cnt++
    }
}

Write-Host $cnt " are effected - Done!"

Stop Workflow 

$cnt = 0

foreach($itm in $items)
{
    $workflowInstances = $wfInstanceSevice.EnumerateInstancesForListItem($lst.ID, $itm.ID)
    $clientContext.Load($workflowInstances)
    $clientContext.ExecuteQuery()

    # bypass the item which the workflow instance was started
    $instance = $workflowInstances | Where-Object { $_.WorkflowSubscriptionId -eq  $srb.Id -and $_.Status -eq "Started" }
    
    if ($instance -ne $null)
        {
            $wfInstanceSevice.CancelWorkflow($instance)
            $clientContext.ExecuteQuery()
            Write-Host "Cancel Workfkow [" $instance.Id  "] on item ["  $itm.Id  "] " $instance.Status -foregroundcolor Red
            $cnt++
            Start-Sleep -s 1
        }
}

Write-Host $cnt " are effected - Done!"

Notes
  • Regarding to limitation on SharePoint Online, it only can start 5 workflows instance in a second. So, it should be delayed 1 second for each loop statement.
  • Just start a workflow if the workflow instance did not start and stop workflow if the workflow instance is running.

Saturday, February 6, 2016

Fix the error "Cannot find an overload for "Load" and the argument count: "1"" in PowerShell

PowerShell script is very useful for end-user who wants to work with behind SharePoint (both of on-premise and online). However, user usually gets some troubles if it throws the error during executing the script. The difficulties of coding and working with PowerShell script is to hard debugging and checking somehow.

This error is an example:  

Cannot find an overload for "Load" and the argument count: "1".

It's so weird because the code is always correct (in developer though). If you know that this error only come from the unique reason so that you have no strange and surprise with the funny mistake.

Here is code:

$web = $clientContext.Web
$clientContext.Load($web)
$clientContext.ExecuteQuery()


$lists = $web.Lists
$clientContext.Load($lists)
$clientContext.ExecuteQuery()


$lst = $list.GetByTitle("CheckList")
$clientContext.Load($lst)
$clientContext.ExecuteQuery()


$qry = New-Object Microsoft.SharePoint.Client.CamlQuery
$qry.ViewXML = "<View><Query><Where><And><Gt><FieldRef Name='Due_x0020_Date'/><Value Type='DateTime'><Today OffsetDays='0'/></Value></Gt><IsNull><FieldRef Name='Actual_x0020_Completion_x0020_Da'/></IsNull></And></Where></Query></View>"


$items = $lst.GetItems($qry)
$clientContext.Load($items)
$clientContext.ExecuteQuery()


Be sure it will happen error when it is executed. The error looks like:

Cannot find an overload for "Load" and the argument count: "1".
At C:\Users\...\Downloads\PSScript\Retrieve.ps1:55 char:1
+ $clientContext.Load($item)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodCountCouldNotFindBest

The root cause of this error come from retrieving the null-able object. That is the variable as a parameter of Load method is null or delivery of that variable is null.

$a = ()
$b = $a.Z()
$c = $b.get_T()
$context.Load($c)

In this example, if $a or $b or $c is null, the error will happen at $context.Load($c) with the message. above.

To fix this, just only take overview of the code and prepare all variables and make sure the name of methods and properties are not miss-spelled and correctly grammar.

Good luck!