Tuesday, May 1, 2018

Coding on CSOM - Performance

Server Object Model is really quite different from Client Object Model. In my opinion, one of the the major difference is "latency".

When migrate the same functionality from SharePoint on-premise Server Object Model to SharePoint Online Client Object Model, it's much slower. However, we can change the "pattern" to make it much faster.

Below is what I do for most of the functionalities in PowerShell. Other languages should be similar.

1. Download relevant data from remote data source (such as SharePoint Online lists) to hash table variables.

2. Process the data.

3. Upload (sync) the changed data back to the remote data source.

For the unchanged data, we can skip them to improve performance.

4. Delete the data from the remote data source as needed.

After migrating a few modules from SOM to COM, I realized that it's not hard at all. The best part: I don't need to worry about "SharePoint memory leaks" any more.

Wednesday, April 4, 2018

"TypeError: Unable to get property '{GUID}' of undefined or null reference" on Access Request list

Some site owners reported that they could not approve or reject "access requests". When they tried to click "ellipsis" button from http://SiteUrl/sites/SiteName/Access%20Requests/pendingreq.aspx , they got error message: "TypeError: Unable to get property '{GUID}' of undefined or null reference"

I checked it. In the field "permission" of the request item, it says "Can't display permissions in this view".

It seems someone (accidently) deleted the system list "access requests". This list is re-created automatically when new request arrives, but, something is wrong.

It's not so easy to trouble shoot. In the end, when I deleted the "access requests" list and then sent out a new request, I got the error message from ULS log.

To fix it is easy. We need to delete the relevant web properties after deleting the "access requests" list. Or else, it caused error "key is already in the web property bag", which stopped the remaining steps. This list could be deleted from SharePoint designer.

Below is the PowerShell script to delete those two web properties.

$WebURL = "http://SiteUrl/sites/SiteName"
$key1 = "_VTI_ACCESSREQUESTSLISTID"
$key2 = "_VTI_PENDINGREQUESTSVIEWID"

$Web = Get-SPWeb -Identity $WebURL
$Web.AllowUnsafeUpdates = $true
$Web.AllProperties.Remove($key1)
$Web.AllProperties.Remove($key2)

$Web.Update()
$Web.Dispose()

Friday, March 2, 2018

How to implement "GetItemsWithUniquePermissions" through PowerShell and CSOM

In C# or JavaScript, it's easy. As this link shows, we can do it through the script below:

var items = list.GetItems(CamlQuery.CreateAllItemsQuery());
ctx.Load(items, col => col.Include(i => i.HasUniqueRoleAssignments));
ctx.ExecuteQuery();
int itemCount = items.Where(i => i.HasUniqueRoleAssignments).Count;


However, can we do similar thing in PowerShell by ONE "ctx.ExecuteQuery()" submit?

The answer is YES.

Below is the script.

$query = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery()
$items = $list.GetItems($query)

$items | %{
$_.Retrieve("HasUniqueRoleAssignments")
$ctx.Load($_)
$ctx.Load($_.RoleAssignments)
}
$ctx.ExecuteQuery()

foreach($item in $items){
if ($item.HasUniqueRoleAssignments){
# your code here
}
}


If there are too many items in the list, we may see the error message:

"The request message is too big. The server does not allow messages larger than 2097152 bytes"

Based on my test, 1000 items is fine. In that case, we need to do it in batches. Below is the script.

$Global:_BatchRowLimit = 1000
$caml = ""
$viewFields = ""
$position = $null
$allItems = @()

Do{
$camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
$camlQuery.ViewXml = "$caml$viewFields$Global:_BatchRowLimit"
$camlQuery.ListItemCollectionPosition = $position

$listItems = $list.getItems($camlQuery)
$ctx.Load($listItems)
$ctx.ExecuteQuery()

$listItems | %{
$_.Retrieve("HasUniqueRoleAssignments")
$ctx.Load($_)
}
$ctx.ExecuteQuery()

$position = $listItems.ListItemCollectionPosition
$allItems += $listItems
}
Until($position -eq $null) 

Friday, February 16, 2018

SharePoint 2016 patch installation failure caused by Custom Tiles

During the installation of the latest patch, The Configuration Wizard throw out an error as below:

--------------

Failed to upgrade SharePoint Products.
An exception of type Microsoft.SharePoint.PostSetupConfiguration.PostSetupConfigurationTaskException was thrown.  Additional exception information: 
Feature upgrade action 'CustomUpgradeAction.AddSwitchField' threw an exception upgrading Feature 'CustomTiles' (Id: 15/'68642d38-a556-4384-888c-082844fbf224') in WebApplication 'SharePoint - 80': List |0

Feature upgrade incomplete for Feature 'CustomTiles' (Id: 15/'68642d38-a556-4384-888c-082844fbf224') in WebApplication 'SharePoint - 80'. Exception: List |0

Feature upgrade action 'CustomUpgradeAction.AddSwitchField' threw an exception upgrading Feature 'CustomTiles' (Id: 15/'68642d38-a556-4384-888c-082844fbf224') in WebApplication 'SharePoint - SPTest': List |0

Feature upgrade incomplete for Feature 'CustomTiles' (Id: 15/'68642d38-a556-4384-888c-082844fbf224') in WebApplication 'SharePoint - SPTest'. Exception: List |0

Upgrade completed with errors.  Review the upgrade log file located in C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\LOGS\Upgrade-20180216-083525-624-c026758ad0924bb8ae1431288b75f172.log.  The number of errors and warnings is listed


Microsoft.SharePoint.PostSetupConfiguration.PostSetupConfigurationTaskException: Exception of type 'Microsoft.SharePoint.PostSetupConfiguration.PostSetupConfigurationTaskException' was thrown.
   at Microsoft.SharePoint.PostSetupConfiguration.UpgradeTask.Run()
   at Microsoft.SharePoint.PostSetupConfiguration.TaskThread.ExecuteTask()

--------------

Google quickly leads me to this link, which says:

"CustomTiles is a standard SharePoint Feature. It's neither missing nor faulty. It seems that the feature upgrade code has a bug though. The upgrade doesn't work if the hidden CustomTiles lists have never been created. These lists get created when you enable the feature. So what you have to do is enabling the CustomTiles feature on every web application in your farm.
You can do so using Powershell: Enable-SPFeature -Identity CustomTiles -Url UrlOfYourWebApplication -Force 
After enabling the feature (which creates the hidden list) the upgrade worked for us without any errors. 
If you want to know more about CustomTiles before enabling the feature see this TechNet article: https://technet.microsoft.com/en-us/library/mt790697(v=office.16).aspx "

Now things are easy to handle. I wrote some PowerShell script to resolve it:

# resolve the "Custom Tiles" error
$WebApplicationUrlObjects = @(Get-SPWebApplication -IncludeCentralAdministration | Select Url)
foreach ($url in $WebApplicationUrlObjects){
    Enable-SPFeature -Identity CustomTiles -Url $url.Url -Force
}

# upgrade content database schema
Get-SPWebApplication -IncludeCentralAdministration | Get-SPContentDatabase | ?{$_.NeedsUpgrade –eq $true} | Upgrade-SPContentDatabase -Confirm:$false

This script needs to be run between the installation of the new patch and "SharePoint 2016 Products Configuration Wizard".

Thursday, February 8, 2018

How to handle "429" error in PowerShell

Sometimes we got error "The remote server returned an error: (429) Too Many Requests", when accessing SharePoint Online through PowerShell script.

Below is how I handle it:

$Global:_retryCount = 1000
$Global:_retryInterval = 10

for($retryAttempts=0; $retryAttempts -lt $Global:_retryCount; $retryAttempts++){
Try{
$ctx.ExecuteQuery()
break
}
Catch [system.exception]{
Start-Sleep -s $Global:_retryInterval
}
}

Friday, January 19, 2018

Simple way to get absolute URL of a list object through PowerShell and CSOM

There is no absolute URL property in list object.

Below is the relevant attribute values:

$oList.RootFolder.ServerRelativeUrl: /sites/SPAdmin/Lists/testList1
$oWeb.Url: https://company.sharepoint.com/sites/SPAdmin
$oList.ParentWebUrl: /sites/SPAdmin

So, we can get the url here:

$url = $oWeb.Url + $oList.RootFolder.ServerRelativeUrl.Replace($oList.ParentWebUrl, "")

The result is:

https://company.sharepoint.com/sites/SPAdmin/Lists/testList1


Hope this script saves you a few minutes.

[update 20180123]

If the user account has SharePoint admin rights, we can do it through tenant "RootSiteUrl" property.

$oTenant = New-Object Microsoft.Online.SharePoint.TenantAdministration.Tenant($ctx)
$Global:_RootSiteUrl = $oTenant.RootSiteUrl
$url = $Global:_RootSiteUrl + $oList.RootFolder.ServerRelativeUrl

Friday, November 3, 2017

How to check available properties of CSOM client object in PowerShell?

The script is quite simple, but it took me quite a while to figure it out.

The variable "$obj" could be any client object, such as "web", "content type", etc.

$obj.psobject.properties | ?{$obj.IsPropertyAvailable($_.Name)} | %{
 Write-Host "$($_.Name): $($_.Value)"
}