Working with SharePoint UPS in Node.js

This article is a logical continuation of my previous post where I described Node.js way of dealing with Managed Metadata services. This post covers some problems of SharePoint API usage when working with another super popular service the User Profiles Services (UPS).

User Profiles services' REST API is much richer than MMD's one, at least it exists. UPS's PeopleManager and ProfileLoader covers a lot of functionality. Unfortunately, not each and every CSOM capability is there at the place. Most methods expose automation connected with a personal profile, most are getters but not the setters.

In our scenario, we're going to build a mechanism for writing user profile properties in Node.js. As it was with MMD, we'll be using CSOM/JSOM and SOAP capabilities to enrich program interface on server-side JavaScript field. Let's assume that there is a 3rd party data source with properties which should be synchronized with user profiles, synchronization should run in a schedule and Node.js is the only option to perform operations.

SOAP

Legacy SOAP API is hidden inside /vtibin/UserProfileService.asmx endpoint. The service has a variety of methods, actually, there are 43 methods. Pretty nice, isn't it?

The method we're interested in now is ModifyUserPropertyByAccountName. It allows writing single and multivalued properties to the specific user profile.

Just like in MMD's scenario, sp-request and node-sp-auth will be in charge of the communication layer over the wires and protocols.

Let's dive deeper with ModifyUserPropertyByAccountName method. SOAP package format is:

<?xml version="1.0" encoding="utf-8"?>  
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ModifyUserPropertyByAccountName
   xmlns="http://microsoft.com/webservices/SharePointPortalServer/UserProfileService">
            <accountName>{{ accountName }}</accountName>
            <newData>
                {{#newData}}
                <PropertyData>
                    <IsPrivacyChanged>{{ isPrivacyChanged }}</IsPrivacyChanged>
                    <IsValueChanged>{{ isValueChanged }}</IsValueChanged>
                    <Name>{{ name }}</Name>
                    <Privacy>{{ privacy }}</Privacy>
                    <Values>
                        {{#values}}
                        <ValueData>
                            <Value xsi:type="xsd:string">{{ this }}</Value>
                        </ValueData>
                        {{/values}}
                    </Values>
                </PropertyData>
                {{/newData}}
            </newData>
        </ModifyUserPropertyByAccountName>
    </soap:Body>
</soap:Envelope>  

The dynamic part is:

  • accountName - full account name for user profile (i:0#.f|membership|user.name@contoso.com)
  • newData - array of properties
    • name - property name
    • values - array of property values

After wrapping SOAP request it can be called like this:

let Screwdriver = require('sp-scredriver');  
let context = require('./path_to_private_settings');  
let screw = new Screwdriver(context);

let data = {  
    baseUrl: context.siteUrl,
    accountName: config.ups.accountName,
    newData: [{
        isPrivacyChanged: false,
        isValueChanged: true,
        privacy: 'NotSet',
        name: 'SPS-Birthday',
        values: [ '10.03' ]
    }, {
        isPrivacyChanged: false,
        isValueChanged: true,
        privacy: 'NotSet',
        name: 'SPS-Department',
        values: [ 'Administration' ]
    }]
};

screw.ups.modifyUserPropertyByAccountName(data)  
    .then(response => {
        console.log('Response:', response.body);
    })
    .catch(err => console.log('Error:', err.message));

Open on GitHub

Here we're done with the SOAP, but I do not like SOAP actually, so I need yet another alternative, which is CSOM way.

CSOM/JSOM

In CSOM API there are who methods for writing properties setSingleValueProfileProperty and setMultiValuedProfileProperty. To master them in Node.js application, we're doing the hack described in the previous post:

  • executing JSOM version of the script in chrome
  • and watching the traffic in fiddler.

So, for example, JSOM script is:

var clientContext = new SP.ClientContext.get_current();  
var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);

peopleManager.setMultiValuedProfileProperty(  
   `i:0#.f|membership|${_spPageContextInfo.userLoginName}`, 
   'SPS-Skills', 
   [ 'Git', 'Node.js', 'JavaScript', 'SharePoint' ]
);

clientContext.executeQueryAsync(  
    () => console.log('Done'), 
    (sender, args) => console.log('Error:', args.get_message())
);

After a call with use of fiddler, we're getting our client side object model package:

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="15.0.0.0" ApplicationName="Javascript Library">  
    <Actions>
        <ObjectPath Id="82" ObjectPathId="81" />
        <Method Name="SetMultiValuedProfileProperty" Id="83" ObjectPathId="81">
            <Parameters>
                <Parameter Type="String">{{ accountName }}</Parameter>
                <Parameter Type="String">{{ propertyName }}</Parameter>
                <Parameter Type="Array">
                    {{#propertyValues}}
                    <Object Type="String">{{ this }}</Object>
                    {{/propertyValues}}
                </Parameter>
            </Parameters>
        </Method>
    </Actions>
    <ObjectPaths>
        <Constructor Id="81" TypeId="{cf560d69-0fdb-4489-a216-b6b47adf8ef8}" />
    </ObjectPaths>
</Request>  

Where:

  • accountName - full account name for user profile (i:0#.f|membership|user.name@contoso.com)
  • propertyName - property name
  • propertyValues - array of property values

And the usage in Node.js is possible like:

let Screwdriver = require('sp-scredriver');  
let context = require('./path_to_private_settings');  
let screw = new Screwdriver(context);

let data = {  
    baseUrl: context.siteUrl,
    accountName: config.ups.accountName,
    propertyName: 'SPS-Skills',
    propertyValues: [ 'Git', 'Node.js', 'JavaScript', 'SharePoint' ]
};

screw.ups.setMultiValuedProfileProperty(data)  
    .then(response => {
        console.log('Response:', JSON.parse(response.body));
    })
    .catch(err => console.log('Error:', err.message));

setSingleValueProfileProperty is almost identical and even simpler.

Sources with implementation can be found on GitHub. It's a part of sp-screwdriver library, library, which was published as an example for similar tasks of extending Node.js apps and services to deal with SharePoint's SOAP and CSOM/JSOM APIs as well.