Tuesday, May 19, 2020

Fix javascript heap out of memory error when running local SPFx web server via gulp

If you develop SPFx web part for Sharepoint and run local web server on dev env:

gulp serve –nobrowser

then you may face with famous “JaavaScript heap out of memory” error:

In order to avoid this error we need to increase nodejs heap size. On Windows it can be done by the following method:

  1. Run the following command in your terminal client:
    setx -m NODE_OPTIONS "--max-old-space-size=8192"

    (after that NOTE_OPTIONS should be added to PC > Advanced properties > Environment variables)
  2. Restart terminal session which is used for running gulp to ensure that new env variables will be applied.
  3. Run web server:
         gulp serve --nobrowser

After that your solution should be built without heap out of memory error.

Monday, May 18, 2020

Query limits in Azure Table storage API

As you probably know Table API used by Azure table storage is limited by the following operators which can be used in queries:

  • =
  • <>
  • >
  • <
  • <=
  • >=

We needed to run analogue of NOT IN Sql operator against RowKey column:

RowKey NOT IN (id1, id2, …, id_n)

where these ids were guids represented by strings. In order to do that with mentioned operators we needed to use not equal operator (<>) and construct query like that:

RowKey <> id1 AND RowKey <> id2 AND … RowKey <> id_n

The problem is that this list of guids didn’t have fixed size and could be potentially big. So it was interesting is there limit for max query length and if yes – what is exact value of this maximum query length.

For testing I created test app and generated conditions over guids like that (in addition to mentioned conditions on RowKey we needed to get groups from particular partition only – so this extra condition was added there as well. Exact partition name is not important – we only need to know that its length was 8 symbols – it will be used for overall query length calculation below):

var tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration());

var table = tableClient.GetTableReference("MyTable");
var queryConditions = new List();
queryConditions.Add(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Test1234"));
int numOfConditions = 50;
for (int i = 0; i < numOfConditions; i++)
{
 queryConditions.Add(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.NotEqual, Guid.NewGuid().ToString().ToLower()));
}
string filter = CombineUsingAnd(queryConditions);
var query = new TableQuery().Where(filter);

var watch = System.Diagnostics.Stopwatch.StartNew();
var result = table.ExecuteQuery(query).ToList();
watch.Stop();


public static string CombineUsingAnd(List queryConditions)
{
 if (queryConditions == null || queryConditions.Count == 0)
 {
  return string.Empty;
 }
 string filter = string.Empty;
 foreach (string queryCondition in queryConditions)
 {
  if (string.IsNullOrEmpty(queryCondition))
  {
   continue;
  }

  if (string.IsNullOrEmpty(filter))
  {
   filter = queryCondition;
  }
  else
  {
   filter = TableQuery.CombineFilters(filter, TableOperators.And, queryCondition);
  }
 }

 return filter;
}

E.g. here how query looks like with condition on 50 guids:

((((((((((((((((((((((((((((((((((((((((((((((((((PartitionKey eq 'Teamwork') and (RowKey ne '60712ffe-061b-4a46-8402-01381177c4da')) and (RowKey ne '85694313-2776-485c-833d-ae78c185adba')) and (RowKey ne 'dac6f7fd-1198-486c-b3ea-e90337ee9711')) and (RowKey ne '5faf85cb-4662-4843-87f6-ac6dc20b04e1')) and (RowKey ne '8f524940-9822-46e1-b961-59a2e8fa51b4')) and (RowKey ne 'bacd765e-64d3-464a-a57e-bb30eeca0c2f')) and (RowKey ne '75ba6126-f529-4b30-ae41-882f2b223bff')) and (RowKey ne '5e9950ac-e611-4d58-8c91-8fcf16f2886a')) and (RowKey ne '8f08bd7b-7d6e-4c21-8b94-d5ca2a15f849')) and (RowKey ne 'a965f6f6-f03c-4b4a-91be-98481a35ba11')) and (RowKey ne '5858de75-addb-4f5a-acc1-6f9f55078b4f')) and (RowKey ne '9ada1d04-dd03-4b1f-ae26-bc51b7f35ec3')) and (RowKey ne 'cbb976e7-87c9-4b83-8007-355f3ce1061d')) and (RowKey ne '59e06da8-f343-4a98-9382-0bbf1ce21d3f')) and (RowKey ne 'ea0763d9-cb64-4750-a7e0-c1869ca82a15')) and (RowKey ne '926106f9-bf64-42db-b27f-be9bcb45907e')) and (RowKey ne '3cdcaf1e-ff36-4243-b865-9424e1f46427')) and (RowKey ne '8fcfc4ca-7510-4fa6-95d7-3ff2999de509')) and (RowKey ne '21b04d3a-32e0-4a59-900a-30babfa8fbd2')) and (RowKey ne 'df181ee8-4dc0-4232-b2bc-7e13dc8f2abf')) and (RowKey ne '6a0038d0-40a4-4a11-b626-9850b8178caa')) and (RowKey ne 'ffb78a96-85c9-4648-aba7-3baa56be6c1f')) and (RowKey ne '0c56afd8-164e-4574-9054-8824e6fc1969')) and (RowKey ne '51d95602-cfee-40bf-b9e3-b0f935083742')) and (RowKey ne 'f286d1fb-b9bb-4850-a911-58a77ffc7991')) and (RowKey ne '50280089-e61b-4874-8f99-8dc437f74181')) and (RowKey ne '05f72847-3936-4efc-9902-540a9d9c5ca8')) and (RowKey ne '066fcbe4-afee-46ff-b6d2-18e072f850a0')) and (RowKey ne '602f7cd4-7697-49ca-af51-e96c70eefed5')) and (RowKey ne '6858f832-a58e-4bcb-b57b-7c40d32cbcb6')) and (RowKey ne 'f9e2cc4f-a579-46f4-820b-e7d8ab9711c8')) and (RowKey ne 'cbb68c38-7e73-463b-8839-bef040803c37')) and (RowKey ne '1bf0d7ff-ece6-4ef1-9d48-ae424feea108')) and (RowKey ne 'de1dbdbf-8a75-40b4-bb9b-81e95ff4a542')) and (RowKey ne '31f5c94e-3eea-4c5a-8e60-8899eddc7829')) and (RowKey ne '94bc24a8-28e4-4b6d-965e-9360104bff21')) and (RowKey ne '93d7f84e-eb4b-4c7d-826b-196e8b46255e')) and (RowKey ne 'bd0d0989-7256-4ffc-8904-6135955b8aae')) and (RowKey ne '9c38865a-913c-468c-bd26-25e606f9b73a')) and (RowKey ne 'c2f9b899-76c1-4b04-aa68-fb3f903d81d4')) and (RowKey ne '9b478051-78b2-4a67-a25f-6cc09251ec33')) and (RowKey ne '908f80ea-e31f-4a85-9ff8-8cb80b601434')) and (RowKey ne '7775f8d4-2b9c-4903-a191-b54d214e380a')) and (RowKey ne '55b24a87-3259-4ca4-8869-f6d68f9c95c3')) and (RowKey ne '0476263d-cf8a-49b3-89f1-0bc8df9fd906')) and (RowKey ne 'f7a45c30-eba7-4944-b08b-a875d4b871e0')) and (RowKey ne '4136dc3c-10d3-4d58-afb9-23d8b422822e')) and (RowKey ne 'b6a40f8e-5284-4cbd-9356-2296df415650')) and (RowKey ne 'd63ecb62-5f48-4314-8a15-08c0b62ee835')) and (RowKey ne 'e3acdc4f-70a5-4a73-b023-1845ccf9ea51')

Testing results are summarized in the following table:

Num of conditions Filter length Query elpsed time
50 2876 12075
100 5726 10550
112 6410 10361
113 6467 10967
114 6524 Bad request
> 114   Bad request

So Table storage query maximum length is between 6467 and 6524 symbols. For us this knowledge was enough to proceed. Hope that this information will help someone.

Tuesday, May 12, 2020

How to run handler on ribbon open event and access ribbon elements via javascript in Sharepoint

Sometimes we need to run code which will be executed when Sharepoint ribbon is opened. In many cases ribbon is closed by default and you can’t access its elements in regular document.ready handler because ribbon elements are not created in the DOM yet. In this case you need to subscribe somehow on ribbon.open even and access elements there. This is how it can be done:

ExecuteOrDelayUntilScriptLoaded(function() {
 SP.Ribbon.PageManager.get_instance().add_ribbonInited(function(){
  console.log("Ribbon is opened");
 });
}, "sp.ribbon.js");

Interesting that even inside this handler you can’t access elements via jQuery selector because it doesn’t find these dynamically added elements. So e.g. the following code which tries to access New document tab in doclib will return 0:

$("#Ribbon.Documents.New").length

In order to access these dynamically added ribbon elements we need to use pure javascript call document.getElementById. Here is the full code:

ExecuteOrDelayUntilScriptLoaded(function() {
 SP.Ribbon.PageManager.get_instance().add_ribbonInited(function(){
  var el = document.getElementById("Ribbon.Documents.New");
  if (el) {
   console.log("New document tab is found");
  }
 });
}, "sp.ribbon.js");

Using this approach you will be able to access and manipulate ribbon elements via javascript.

Monday, April 20, 2020

Create Azure Notification Hub with configured Apple APNS and Google FCM using PowerShell and ARM templates

If you have Azure notification hub with configured Apple APNS and Google FCM and try to export ARM template of this notification hub you will find that APNS and FCM configuration won’t be included to this template. In order to add APNS and FCM you need to add the following properties to ARM template:

  • GcmCredential for FCM
  • ApnsCredential for APNS

If you use Token authentication type and Production endpoint for Apple APNS template will look like this:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "hubName": {
            "type": "string"
        },
        "namespaceName": {
            "type": "string"
        },
        "googleApiKey": {
            "type": "string"
        },
        "appId": {
            "type": "string"
        },
        "appName": {
            "type": "string"
        },
        "keyId": {
            "type": "string"
        },
        "token": {
            "type": "string"
        },
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.NotificationHubs/namespaces/notificationHubs",
            "apiVersion": "2017-04-01",
            "name": "[concat(parameters('namespaceName'), '/', parameters('hubName'))]",
            "location": "North Europe",
            "properties": {
                "authorizationRules": [],
    "GcmCredential": {
     "properties": {
     "googleApiKey": "[parameters('googleApiKey')]",
     "gcmEndpoint": "https://android.googleapis.com/gcm/send"
     }
    },
    "ApnsCredential": {
     "properties": {
      "appId": "[parameters('appId')]",
      "appName": "[parameters('appName')]",
      "keyId": "[parameters('keyId')]",
      "token": "[parameters('token')]",
      "endpoint": "https://api.push.apple.com:443/3/device"
     }
    }
            }
        },
        {
            "type": "Microsoft.NotificationHubs/namespaces/notificationHubs/authorizationRules",
            "apiVersion": "2017-04-01",
            "name": "[concat(parameters('namespaceName'), '/', parameters('hubName'), '/DefaultFullSharedAccessSignature')]",
            "dependsOn": [
                "[resourceId('Microsoft.NotificationHubs/namespaces/notificationHubs', parameters('namespaceName'), parameters('hubName'))]"
            ],
            "properties": {
                "rights": [
                    "Listen",
                    "Manage",
                    "Send"
                ]
            }
        },
        {
            "type": "Microsoft.NotificationHubs/namespaces/notificationHubs/authorizationRules",
            "apiVersion": "2017-04-01",
            "name": "[concat(parameters('namespaceName'), '/', parameters('hubName'), '/DefaultListenSharedAccessSignature')]",
            "dependsOn": [
                "[resourceId('Microsoft.NotificationHubs/namespaces/notificationHubs', parameters('namespaceName'), parameters('hubName'))]"
            ],
            "properties": {
                "rights": [
                    "Listen"
                ]
            }
        }
    ]
}

In order to create new notification hub using this ARM template we need to call New-AzResourceGroupDeployment cmdlet and provide params specified in parameters section of the template:

$resourceGroupName = ...
$hubNamespaceName = ...
$hubName = ...
$googleApiKey = ...
$appId = ...
$appName = ...
$keyId = ...
$token = ...

New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile ./template.json -hubName $hubName -namespaceName $hubNamespaceName -googleApiKey $googleApiKey -appId $appId -appName $appName -keyId $keyId -token $token

It will create new Azure notification hub with specified name in specified namespace with configured Apple APNS (Token authentication type) and Googke FCM.

Thursday, April 16, 2020

Fix ResourceUnavailable error when try to install PowerShell module via Install-Module cmdlet

If you try to install PowerShell module via Install-Module cmdlet, e.g.:

Install-Module -Name SharePointPnPPowerShellOnline

you may get the following error:

WARNING: Source Location 'https://www.powershellgallery.com/api/v2/package/SharePointPnPPowerShellOnline/3.20.2004' is not valid.
PackageManagement\Install-Package : Package 'SharePointPnPPowerShellOnline' failed to download.
     + CategoryInfo          : ResourceUnavailable: (C:\Users\Develo...20.2004.0.nupkg:String) [Install-Package], Exception
     + FullyQualifiedErrorId : PackageFailedInstallOrDownload,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage

In order to fix it run the following command in your PowerShell session:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

After that run Install-Module again – error should disappear.

Sunday, April 5, 2020

Fix problem “Security token service cannot be activated” in Sharepoint farm after inplace upgrade Windows Server 2012 to R2

If you performed inplace upgrade of Windows Server 2012 to Windows Server 2012 R2 with Sharepoint Server running you may face with the following error after upgrade will be completed: when you will try to open any Sharepoint web application the following exception will be shown:

WebHost failed to process a request.
  Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/12547953
  Exception: System.ServiceModel.ServiceActivationException: The service '/SecurityTokenServiceApplication/securitytoken.svc' cannot be activated due to an exception during compilation.  The exception message is: Exception has been thrown by the target of an invocation.. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: certificate
    at System.IdentityModel.Tokens.X509SecurityToken..ctor(X509Certificate2 certificate, String id, Boolean clone, Boolean disposable)
    at System.IdentityModel.Tokens.X509SecurityToken..ctor(X509Certificate2 certificate)


The error says that certificate for Secure token service is not specified. In order to fix this error you need to replace certificate for STS:

  1. Open IIS manager > Server certificates > Create Self-Signed Certificate
  2. After that export created certificate to local folder:

Next run the following PowerShell script which will update certificate for STS:

$pfxPath = "path to pfx"
$pfxPass = "certificate password"
$stsCertificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $pfxPath, $pfxPass, 20
Set-SPSecurityTokenServiceConfig -ImportSigningCertificate $stsCertificate
certutil -addstore -enterprise -f -v root $stsCertificate
iisreset
net stop SPTimerV4
net start SPTimerV4

After that open Sharepoint web app again.

Host React web app on Azure

In this article I will show how to host React web app on Azure cloud platform. We will create new test React web app from scratch, will build it, then will create in Azure new Resource group and App service and after that will transfer our React web app there. Before to start ensure that you have working Azure account with valid subscription (you may create free subscription for 1 month to try things out).

First of all we need to create React app. Before to do that you need to install nodejs on your PC. By default it will also install popular npm package manager and will add its folders to PATH env variable. When it is done run the following command in your working folder:

npx create-react-app test-react-app

It will create test-react-app folder and new test React app inside it. In order to test it on local dev web server run the following command:

npm start

It will launch web server and will open default browser with React app running on http://localhost:3000:

In order to host our React app on Azure we need to build it first. In order to do that run the following command:

npm run build

It will create ready for deployment build subfolder and will copy web app files there.

Once React app is ready we need to configure Azure for hosting it. Go to Azure portal https://portal.azure.com > Resource groups > Create resource group. On the opened window give name for the new resource group and select closer location:

When new resource group will be created choose New > Web App:

On the opened Window choose created resource group, give name for new web app, set the following parameters:
Publish = Code
Runtime stack = Node 10.14
Operating system = Windows
also select region (usually the same region which was used for creating resource group) and App service plan name. As we create new Web app for testing purposes we will use Free app service plan: under Sku and size click Change size and change App service plan to Dev/Test > F1 free:

and click Create. After that go to created App service > Deployment center > FTP > Dashboard > App credentials and copy FTP host name, username and password:

Use your favorite FTP client in order to connect to Azure app service with credentials copied on previous step. When connection will be established copy files from local /test-react-app/build folder to FTP /site/wwwroot folder. When it will be done try to open https://{name-of-your-web-app}.azurewebsites.net in web browser (instead of {name-of-your-web-app} use name of Azure web app which was used during it’s creation). If everything was done correctly you will see your React web app hosted in Azure: