Consume SharePoint Online REST service using .NET

Overview

Since the introduction of REST interface in SharePoint 2010 you have probably used WebClient or HttpWebRequest in .NET applications.
HttpClient is a modern HTTP client for .NET, it provides a flexible and extensible API for accessing resources via HTTP(S).
HttpClient offers some advantages over WebClient/HttpWebRequest such as:

  1. An HttpClient instance is the place to configure extensions, set default headers, cancel outstanding requests and more.
  2. You can issue as many requests as you like through a single HttpClient instance.
  3. HttpClients are not tied to particular HTTP server or host; you can submit any HTTP request using the same HttpClient instance.
  4. You can derive from HttpClient to create specialized clients for particular sites or patterns
  5. HttpClient uses the new Task-oriented pattern for handling asynchronous requests making it dramatically easier to manage and coordinate multiple outstanding requests.

HttpClient for SharePoint Online

The solution consists of:

  1. SPHttpClient class that inherits from HttpClient and provides some additional SharePoint specific functionaly such as getting request digest
  2. SPHttpClientHandler class that basically hides all the intricacies related to SharePoint Online authentication

SharePoint Online client implementation

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace SharePoint.Client
{
/// <summary>
/// Http client for SharePoint Online
/// </summary>
public class SPHttpClient : HttpClient
{
public SPHttpClient(Uri webUri, string userName, string password) : base(new SPHttpClientHandler(webUri, userName, password))
{
BaseAddress = webUri;
}
/// <summary>
/// Execure request method
/// </summary>
/// <param name="requestUri"></param>
/// <param name="method"></param>
/// <param name="headers"></param>
/// <param name="payload"></param>
/// <returns></returns>
public JObject ExecuteJson<T>(string requestUri, HttpMethod method, IDictionary<string, string> headers, T payload)
{
HttpResponseMessage response;
switch (method.Method)
{
case "POST":
var requestContent = new StringContent(JsonConvert.SerializeObject(payload));
requestContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");
DefaultRequestHeaders.Add("X-RequestDigest", RequestFormDigest());
if (headers != null)
{
foreach (var header in headers)
{
DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
response = PostAsync(requestUri, requestContent).Result;
break;
case "GET":
response = GetAsync(requestUri).Result;
break;
default:
throw new NotSupportedException(string.Format("Method {0} is not supported", method.Method));
}
response.EnsureSuccessStatusCode();
var responseContent = response.Content.ReadAsStringAsync().Result;
return String.IsNullOrEmpty(responseContent) ? new JObject() : JObject.Parse(responseContent);
}
public JObject ExecuteJson<T>(string requestUri, HttpMethod method, T payload)
{
return ExecuteJson(requestUri, method, null, payload);
}
public JObject ExecuteJson(string requestUri)
{
return ExecuteJson(requestUri, HttpMethod.Get, null, default(string));
}
/// <summary>
/// Request Form Digest
/// </summary>
/// <returns></returns>
public string RequestFormDigest()
{
var endpointUrl = string.Format("{0}/_api/contextinfo", BaseAddress);
var result = this.PostAsync(endpointUrl, new StringContent(string.Empty)).Result;
result.EnsureSuccessStatusCode();
var content = result.Content.ReadAsStringAsync().Result;
var contentJson = JObject.Parse(content);
return contentJson["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
}
}
}

view raw
SPHttpClient.cs
hosted with ❤ by GitHub

SharePoint Online HTTP handler implementation

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
namespace SharePoint.Client
{
public class SPHttpClientHandler : HttpClientHandler
{
public SPHttpClientHandler(Uri webUri, string userName, string password)
{
CookieContainer = GetAuthCookies(webUri, userName, password);
FormatType = FormatType.JsonVerbose;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
if (FormatType == FormatType.JsonVerbose)
{
//request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json;odata=verbose"));
request.Headers.Add("Accept", "application/json;odata=verbose");
}
return base.SendAsync(request, cancellationToken);
}
/// <summary>
/// Retrieve SPO Auth Cookies
/// </summary>
/// <param name="webUri"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <returns></returns>
private static CookieContainer GetAuthCookies(Uri webUri, string userName, string password)
{
var securePassword = new SecureString();
foreach (var c in password) { securePassword.AppendChar(c); }
var credentials = new SharePointOnlineCredentials(userName, securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
var cookieContainer = new CookieContainer();
cookieContainer.SetCookies(webUri, authCookie);
return cookieContainer;
}
public FormatType FormatType { get; set; }
}
public enum FormatType
{
JsonVerbose,
Xml
}
}

Working with list items with REST

The following example shows how to retrieve all of a list’s items:

using (var client = new SPHttpClient(webUri, userName, password))
{
var listTitle = "Tasks";
var endpointUrl = string.Format("{0}/_api/web/lists/getbytitle('{1}')/items",webUri,listTitle);
var data = client.ExecuteJson(endpointUrl);
foreach (var item in data["value"])
{
Console.WriteLine(item["Title"]);
}
}

The following example shows how to retrieve a specific list item:

using (var client = new SPHttpClient(webUri, userName, password))
{
var listTitle = "Tasks";
var itemId = 1;
var endpointUrl = string.Format("{0}/_api/web/lists/getbytitle('{1}')/items({2})",webUri,listTitle,itemId);
var data = client.ExecuteJson(endpointUrl);
Console.WriteLine(data["Title"]);
}

The following example shows how to create a list item:

using (var client = new SPHttpClient(webUri, userName, password))
{
var listTitle = "Tasks";
var itemPayload = new { __metadata = new { type = "SP.Data.TasksListItem" }, Title = "Approval Task"};
var endpointUrl = string.Format("{0}/_api/web/lists/getbytitle('{1}')/items",webUri,listTitle);
var data = client.ExecuteJson(endpointUrl,HttpMethod.Post,itemPayload);
Console.WriteLine("Task item '{0}' has been created",data["Title"]);
}

The following example shows how to update a list item:

using (var client = new SPHttpClient(webUri, userName, password))
{
var listTitle = "Tasks";
var itemId = 1;
var itemPayload = new { __metadata = new { type = "SP.Data.TasksListItem" }, Title = "Approval Task"};
var endpointUrl = string.Format("{0}/_api/web/lists/getbytitle('{1}')/items({2})",webUri,listTitle,itemId);
var headers = new Dictionary<string,string>();
headers["IF-MATCH"] = "*";
headers["X-HTTP-Method"] = "MERGE";
client.ExecuteJson(endpointUrl,HttpMethod.Post,headers, itemPayload);
Console.WriteLine("Task item has been updated");
}

The following example shows how to delete a list item:

using (var client = new SPHttpClient(webUri, userName, password))
{
var listTitle = "Tasks";
var itemId = 2;
var endpointUrl = string.Format("{0}/_api/web/lists/getbytitle('{1}')/items({2})",webUri,listTitle,itemId);
var headers = new Dictionary<string,string>();
headers["IF-MATCH"] = "*";
headers["X-HTTP-Method"] = "DELETE";
client.ExecuteJson(endpointUrl,HttpMethod.Post,null,headers);
Console.WriteLine("Task item has been deleted");
}

Consuming the SharePoint Online REST API from PowerShell: Part 2

Introduction

A while back ago we already discussed how to  consume SharePoint Online (SPO) REST in PowerShell. Here is a brief recap:

This time I would like demonstrate another approach, in particular  how PowerShell can gain authorization to SharePoint resources by passing an access token to SharePoint with each HTTP request. To issue an access token from Microsoft Azure Access Control Service (ACS) that allows the app access to the resources in the SharePoint tenancy we will implement the corresponding PowerShell function. Let’s get started.

Getting Access Token from Microsoft Azure Access Control Service

The Get-SPOAccessToken function demonstrates how to obtain the access token from a Microsoft Azure Access Control Service (ACS) account that is associated with the customer’s Microsoft Office 365 tenancy:

<#
.Synopsis
Obtain an app-only access token from ACS.
.DESCRIPTION
Retrieves an app-only access token from ACS to call the specified principal
at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
null, the "Realm" setting in web.config will be used instead
.EXAMPLE
Get-SPOAccessToken -Url "https://contoso.sharepoint.com/_api/web&quot; -ClientId "" -ClientSecret ""
#>
Function Get-SPOAccessToken([string]$ClientId,[string]$ClientSecret,[Uri]$WebUri){
$SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000"
$realm = GetRealmFromTargetUrl TargetApplicationUri $WebUri
$accessToken = GetAppOnlyAccessToken ClientId $ClientId ClientSecret $ClientSecret TargetPrincipalName $SharePointPrincipal TargetHost $WebUri.Authority TargetRealm $realm
return $accessToken.access_token
}
function GetRealmFromTargetUrl([Uri]$TargetApplicationUri)
{
$url = $WebUrl + "/_vti_bin/client.svc"
$headers = @{}
$headers.Add('Authorization','Bearer')
try {
$response = Invoke-WebRequest Uri $TargetApplicationUri Headers $headers Method Get
}
catch [Net.WebException] {
$authResponseHeader = $_.Exception.Response.Headers["WWW-Authenticate"]
#$bearerKey = "Bearer realm="
$bearer = $authResponseHeader.Split(",")[0]
$targetRealm = $bearer.Split("=")[1]
return $targetRealm.Substring(1,$targetRealm.Length2)
}
return $null
}
Function GetAppOnlyAccessToken([string]$ClientId,[string]$ClientSecret,[string]$TargetPrincipalName,[string]$TargetHost,[string]$TargetRealm)
{
$resource = GetFormattedPrincipal PrincipalName $TargetPrincipalName HostName $TargetHost Realm $TargetRealm
$ClientId = GetFormattedPrincipal PrincipalName $ClientId Realm $TargetRealm
$contentType = 'application/x-www-form-urlencoded'
$stsUrl = GetSecurityTokenServiceUrl Realm $TargetRealm
$oauth2Request = CreateAccessTokenRequestWithClientCredentials ClientId $ClientId ClientSecret $ClientSecret Scope $resource
$oauth2Response = Invoke-RestMethod Method Post Uri $stsUrl ContentType $contentType Body $oauth2Request
return $oauth2Response
}
Function GetSecurityTokenServiceUrl([string]$Realm)
{
return "https://accounts.accesscontrol.windows.net/$Realm/tokens/OAuth/2"
}
Function CreateAccessTokenRequestWithClientCredentials([string]$ClientId,[string]$ClientSecret,[string]$Scope)
{
$oauth2Request = @{
'grant_type' = 'client_credentials';
'client_id' = $ClientId;
'client_secret' = $ClientSecret;
'scope' = $Scope;
'resource' = $Scope
}
return $oauth2Request
}
function GetFormattedPrincipal([string]$PrincipalName, [string]$HostName, [string]$Realm)
{
if ($HostName)
{
return "$PrincipalName/$HostName@$Realm"
}
return "$PrincipalName@$Realm"
}

Get-SPOAccessToken function is intended for requesting an access token from Azure ACS, it accepts  Client Id and Client Secret parameters that are generated while App registration with Azure ACS (see “How to register App” for a more details).

Using Invoke-RestMethod in Office 365

Invoke-SPORestMethod function demonstrates how to  include the access token to make a REST API call  to SharePoint, passing the OAuth access token in the HTTP Authorization header:

.\Get-SPOAccessToken.ps1
<#
.Synopsis
Sends an HTTP or HTTPS request to a SharePoint Online REST-compliant web service.
.DESCRIPTION
This function sends an HTTP or HTTPS request to a Representational State
Transfer (REST)-compliant ("RESTful") SharePoint Online web service.
.EXAMPLE
Invoke-SPORestMethod -Uri "https://contoso.sharepoint.com/_api/web&quot; -AccessToken $accessToken
#>
Function Invoke-SPORestMethod()
{
Param(
[Uri]$Uri,
[Object]$Body,
[Hashtable]$Headers,
[Microsoft.PowerShell.Commands.WebRequestMethod]$Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,
[string]$AccessToken
)
$contentType = 'application/json;odata=verbose'
if(-not $Headers) {
$Headers = @{}
}
$Headers["Accept"] = "application/json;odata=verbose"
$Headers.Add('Authorization','Bearer ' + $AccessToken)
Invoke-RestMethod Method $Method Uri $Uri ContentType $contentType Headers $Headers Body $Body
}

Examples

The following example demonstrates how to retrieve List resource properties:

param (
$ClientId,
$ClientSecret,
$WebUri,
$ListTitle
)
.\Get-SPOAccessToken.ps1
Function Get-SPOList([Uri]$WebUri,[string]$ClientId,[string]$ClientSecret,[Uri]$ListTitle)
{
$accessToken = Get-SPOAccessToken ClientId $ClientId ClientSecret $ClientSecret WebUri $WebUri
$endpointUri = "$WebUri/_api/web/lists/getbytitle('$ListTitle')"
$data = Invoke-SPORestMethod Uri $endpointUri AccessToken $accessToken
return $data.d
}
Get-SPOList WebUri $WebUri ClientId $ClientId ClientSecret $ClientSecret ListTitle $ListTitle

view raw
Get-SPOList.ps1
hosted with ❤ by GitHub

But before running the specified script we need to perform one more step in order to grant permissions to the app principal otherwise the unauthorized error will occur as shown on picture below:
Rest401

  • Navigate to http://<SharePointWebsite>/_layouts/15/AppInv.aspx
  • Look up the app based on the Client ID that you just generated and click Lookup, it will find the app principal.  Then paste the AppPermissionRequests XML into the Permissions text box and click CreateAppInv
    Once you click Create, the Trust dialog will appear, click Trust
    AppInv_Trust

That’s it.  Now, after executing the specified script, the output will look like shown below

Get-SPOList-Results

How to register App

Below is provided a step by step instruction how to register an App, for a complete guide follow this article:

  • To create the app identity, navigate to http://<SharePointWebsite>/_layouts/15/AppRegNew.aspx on the tenancy or farm
  • Enter values for the form fields as shown below on picture
    AppNewReg_NewForm
    where
    App ID: App ID, also known as client ID, is a GUID that can be generated (when you click Generate) or pasted into AppRegNew.aspx. The value must be unique for each app, and must be lower case
    App Secret: The app secret, also known as the client secret, is an opaque string. It is generated on the AppRegNew.aspx page by using the Generate button. The following is an example of an app secret: Ywjaoz7DJBGhoLQ2t0IbVCA5pfqqI722ZIVt+ENLk0g=
    Title: Choose your own user-friendly title; for example, PowerShell Console
    App Domain:
    The host name of the remote component of the app for SharePoint
    Redirect URI: The endpoint in your remote application or service to which ACS sends an authentication code
  • Click Create on the form. The page will reload and show a confirmation of the values you entered as shown on picture below
    AppNewReg
  • Save Client Id and Client Secret values. After that you could verify whether Get-SPOAccessToken function returns access token. The picture below shows  the output after executing the command:
    Get-SPOAccessToken -ClientId “1523cf05-b437-4e73-9ad1-a652da8f2ae5” -ClientSecret “Ywjaoz7DJBGhoLQ2t0IbVCA5pfqqI722ZIVt+ENLk0g=” -WebUri “https://contoso.sharepoint.com/&#8221;
    ISE_AccessToken

References

Enterprise Keywords management in Office 365 via CSOM

Overview

SharePoint 2013 has introduced Microsoft.SharePoint.Client.Taxonomy namespace for metadata management via CSOM. Since Taxonomy API also allows to manage Keywords, i would like to demonstrate how to get/set Enterprise Keywords field values in this post.

Adding Enterprise Keywords column into List

The first example demonstrates how to add Enterprise Keywords column into List:

private static void AddTaxkeywordFieldInList(List list)
{
var ctx = list.Context as ClientContext;
var taxKeywordField = ctx.Site.RootWeb.Fields.GetByInternalNameOrTitle("TaxKeyword");
var taxKeywordFieldInList = list.Fields.Add(taxKeywordField);
ctx.ExecuteQuery();
}

Setting  Enterprise Keywords field value

The below example demonstrates how to set Enterprise Keywords field value:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;
namespace SharePoint.Client.Taxonomy
{
/// <summary>
/// Enterpise Keyword Manager
/// </summary>
public class KeywordsManager
{
/// <summary>
/// Set Enterprise Keyword Value
/// </summary>
/// <param name="item">List Item</param>
/// <param name="values">Keyword values</param>
public static void SetTaxKeywordValue(ListItem item,string[] values)
{
var ctx = item.Context;
var list = item.ParentList;
var field = list.Fields.GetByInternalNameOrTitle(TaxKeywordFieldName);
var taxKeywordField = ctx.CastTo<TaxonomyField>(field);
var keywords = values.Select(value => EnsureKeyword(taxKeywordField, value)).ToList();
taxKeywordField.SetFieldValueByValueCollection(item, new TaxonomyFieldValueCollection(ctx, GetTermsString(keywords), taxKeywordField));
}
/// <summary>
/// Ensure Keyword
/// </summary>
/// <param name="taxField"></param>
/// <param name="name"></param>
/// <returns></returns>
private static Term EnsureKeyword(TaxonomyField taxField, string name)
{
var ctx = taxField.Context;
var taxSession = TaxonomySession.GetTaxonomySession(ctx);
var termStore = taxSession.GetDefaultKeywordsTermStore();
var keywords = termStore.KeywordsTermSet.GetAllTerms();
var result = ctx.LoadQuery(keywords.Where(k => k.Name == name));
ctx.ExecuteQuery();
var keyword = result.FirstOrDefault();
if (keyword != null)
{
return keyword;
}
keyword = termStore.KeywordsTermSet.CreateTerm(name, DefaultLanguage, Guid.NewGuid());
ctx.Load(keyword);
ctx.ExecuteQuery();
return keyword;
}
/// <summary>
/// Retrieve formatted Term string
/// </summary>
/// <param name="term"></param>
/// <returns></returns>
private static string GetTermString(Term term)
{
return string.Format("-1;#{0}{1}{2}", term.Name, TaxonomyGuidLabelDelimiter,term.Id);
}
private static string GetTermsString(IEnumerable<Term> terms)
{
var termsString = terms.Select(GetTermString).ToList();
return string.Join(";#", termsString);
}
private const string TaxKeywordFieldName = "TaxKeyword";
private const int DefaultLanguage = 1033;
private const string TaxonomyGuidLabelDelimiter = "|";
}
}

view raw
KeywordsManager.cs
hosted with ❤ by GitHub

Key points:

  • The operation of setting Enterprise Keywords field value consists of two steps, first one to resolve Keyword in Managed Metadata service application (MMS), this what  EnsureKeyword method is intended for.  After Keyword has been retrieved or created if it not existed, the value of Enterprise Keyword field is set (TaxonomyFieldValueCollection type).
Usage
using (var ctx = new ClientContext(webUri))
{
ctx.Credentials = CreateSPOCredentials(userName, password);
var list = ctx.Web.Lists.GetByTitle(listTitle);
var item = list.GetItemById(itemId);
KeywordsManager.SetTaxKeywordValue(item,new []{"2013","2010"});
item.Update();
ctx.ExecuteQuery();
}
public static SharePointOnlineCredentials CreateSPOCredentials(string userName,string password)
{
var securePassword = new SecureString();
foreach (var ch in password)
{
securePassword.AppendChar(ch);
}
return new SharePointOnlineCredentials(userName, securePassword);
}

Results

DocumentsWithTaxKeyword TermStore_Keywords

Manage User Custom Actions in Office 365

Overview

Custom Actions offer a flexible way to extend capabilities of the SharePoint. The possibilities span the range of including custom JavaScript on every page to extending the Ribbon. In SharePoint 2013/SharePoint Online you can leverage the CSOM/REST  to manage custom actions. Below are demonstrated two simple examples of using custom actions in real world scenarios and I hope you you’ll find them useful.

Example 1. Enable jQuery

Let’s get started with an example that demonstrate how to add jQuery library to Office 365/SharePoint Online site. Unfortunately  it is not supported to reference external resources, for example from Microsoft Ajax Content Delivery Network (CDN) that hosts popular third party JavaScript libraries including jQuery. The prerequisite for referencing JavaScript files is that they could only be accesible when located within the site collection. So, the first step would be to save a jQuery library into Style Library: /Style Library/Scripts/jQuery/jquery-2.1.1.js.

The following Activate-JQuery.ps1 script  demonstrates how to enable jQuery library  in Office 365/SharePoint Online site

param(
[string]$SiteUrl,
[string]$UserName,
[string]$Password
)
. ".\UserCustomActions.ps1"
<#
.SYNOPSIS
Enable jQuery Library
.DESCRIPTION
Enable jQuery Library in Office 365/SharePoint Online site
.EXAMPLE
.\Activate-JQuery.ps1 -SiteUrl "https://tenant-public.sharepoint.com&quot; -UserName "username@tenant.onmicrosoft.com" -Password "password"
#>
Function Activate-JQuery([string]$SiteUrl,[string]$UserName,[string]$Password)
{
$context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$context.Credentials = Get-SPOCredentials UserName $UserName Password $Password
$sequenceNo = 1482
$jQueryUrl = "~SiteCollection/Style Library/Scripts/jQuery/jquery-2.1.1.js"
Add-ScriptLinkAction Context $Context ScriptSrc $jQueryUrl Sequence $sequenceNo
$context.Dispose()
}
ActivateJQuery SiteUrl $SiteUrl UserName $UserName Password $Password

view raw
Activate-JQuery.ps1
hosted with ❤ by GitHub

Dependencies: UserCustomActions.ps1

Example 2. Enable Google Analytics

The following Activate-GoogleAnalytics.ps1 script demonstrates how to activate tracking code in Office 365/SharePoint Online site

param(
[string]$SiteUrl,
[string]$UserName,
[string]$Password
)
. ".\UserCustomActions.ps1"
<#
.SYNOPSIS
Activate Google Analytics
.DESCRIPTION
Activate Google Analytics in Office 365/SharePoint Online site
.EXAMPLE
.\Activate-GoogleAnalytics.ps1 -SiteUrl "https://tenant-public.sharepoint.com&quot; -UserName "username@tenant.onmicrosoft.com" -Password "password"
#>
Function Activate-GoogleAnalytics([string]$SiteUrl,[string]$UserName,[string]$Password)
{
$context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$context.Credentials = Get-SPOCredentials UserName $UserName Password $Password
$sequenceNo = 1480
#Insert your Tracking code here or specify valid Tracking ID
$TrackingCode = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-X', 'auto');
ga('send', 'pageview');"
Add-ScriptLinkAction Context $Context ScriptBlock $TrackingCode Sequence $sequenceNo
$context.Dispose()
}
ActivateGoogleAnalytics SiteUrl $SiteUrl UserName $UserName Password $Password

Dependencies: UserCustomActions.ps1

Follow these instructions to use Google Analytics to collect data from Office 365/SharePoint Online sites.

To set up the web tracking code:

  1. Find the tracking code snippet for your property.
    Sign in to your Google Analytics account, and click Admin in the top menu bar. From the Account and Propertycolumns, select the property you’re working with. Click Tracking Info / Tracking Code.
    SNP_8C6378625835DB93E9293E76F24E9AC45177_3517951_en_v4
  2. Find your tracking code snippet. It’s in a box with several lines of JavaScript in it. Everything in this box is your tracking code snippet. It starts with <script> and ends with </script>.
    The tracking code contains a unique ID that corresponds to each Google Analytics property. Don’t mix up tracking code snippets from different properties, and don’t reuse the same tracking code snippet on multiple domains. Click to expand this image and see where the tracking code snippet is in the interface.
  3. Open , paste the tracking code into $TrackingCode variable and run the script to register tracking code in Office 365/SharePoint Online site
  4. Check your set up.
    Make sure that the tracking snippet installed on your website matches the code shown in the view, and see more ways you can verify your set up.