How to run Android TV 9 on Windows

I used to use Android Studio with the various emulators to test my Android apps, however for Android TV 9 it doesn’t work properly because it doesn’t have Google Play Store on it.

To have a good version of Android TV 9:

  1. Downlad a VirtualBox image of it – I used the one shared in this video
  2. Install VirtualBox if you don’t have it yet
  3. Load the downloaded image with VirtualBox
  4. Configure it by going to the network section and select « Bridged Adapter » instead of « NAT »
  5. Once Android TV is started, you’ll have to connect to your Google account, but using the keyboard might be challenging – you can right click on the USB cable icon that appears at the bottom right of the VirtualBox window, and select your keyboard (be aware it’s a QWERTY map that is used)

After that, go to the Android TV settings, in the « About » section, and click several times on the Build Version to enable the Developer Mode. From the developer menu, you can enable the « Debug USB ».

Once everything is correctly configured, the Android TV should be visible on your local network, meaning you can use the ADB command to test your app.

Connect to SharePoint Online from NodeJS using an impersonate (application) Bearer Token

(duplicate of this StackOverflow answer)

Start a new project

  1. In a new folder, type npm init to start a new project.
  2. Make sure to use a Node >= v14 (I use Node v18 – and Volta can be useful to manage several versions of Node for Windows)
  3. Install some dependencies: npm install axios @azure/msal-node uuid

Certificate

In the past, we could use the add-in application feature to get a token, but Microsoft announced it will be retired.

We now need to pass through an Azure application to get it. But before creating the Azure app, we need to create a Private Certificate key.

  1. Create the file Create-SelfSignedCertificate.ps1 using the below code (source):
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Creates a Self Signed Certificate for use in server to server authentication
.DESCRIPTION
Source: https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread#setting-up-an-azure-ad-app-for-app-only-access
.EXAMPLE
.\Create-SelfSignedCertificate.ps1 -CommonName "MyCert" -StartDate 2015-11-21 -EndDate 2017-11-21
This will create a new self signed certificate with the common name "CN=MyCert". During creation you will be asked to provide a password to protect the private key.
.EXAMPLE
.\Create-SelfSignedCertificate.ps1 -CommonName "MyCert" -StartDate 2015-11-21 -EndDate 2017-11-21 -Password (ConvertTo-SecureString -String "MyPassword" -AsPlainText -Force)
This will create a new self signed certificate with the common name "CN=MyCert". The password as specified in the Password parameter will be used to protect the private key
.EXAMPLE
.\Create-SelfSignedCertificate.ps1 -CommonName "MyCert" -StartDate 2015-11-21 -EndDate 2017-11-21 -Force
This will create a new self signed certificate with the common name "CN=MyCert". During creation you will be asked to provide a password to protect the private key. If there is already a certificate with the common name you specified, it will be removed first.
#>
Param(

[Parameter(Mandatory=$true)]
   [string]$CommonName,

[Parameter(Mandatory=$true)]
   [DateTime]$StartDate,

[Parameter(Mandatory=$true)]
   [DateTime]$EndDate,

[Parameter(Mandatory=$false, HelpMessage="Will overwrite existing certificates")]
   [Switch]$Force,

[Parameter(Mandatory=$false)]
   [SecureString]$Password
)

# DO NOT MODIFY BELOW
function CreateSelfSignedCertificate(){
  #Remove and existing certificates with the same common name from personal and root stores
    #Need to be very wary of this as could break something
    if($CommonName.ToLower().StartsWith("cn="))
    {
        # Remove CN from common name
        $CommonName = $CommonName.Substring(3)
    }
    $certs = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object{$_.Subject -eq "CN=$CommonName"}
    if($certs -ne $null -and $certs.Length -gt 0)
    {
        if($Force)
        {

foreach($c in $certs)
            {
                remove-item $c.PSPath
            }
        } else {
            Write-Host -ForegroundColor Red "One or more certificates with the same common name (CN=$CommonName) are already located in the local certificate store. Use -Force to remove them";
            return $false
        }
    }

$name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
    $name.Encode("CN=$CommonName", 0)

$key = new-object -com "X509Enrollment.CX509PrivateKey.1"
    $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
    $key.KeySpec = 1
    $key.Length = 2048
    $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
    $key.MachineContext = 1
    $key.ExportPolicy = 1 # This is required to allow the private key to be exported
    $key.Create()

$serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
    $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1") # Server Authentication
    $ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
    $ekuoids.add($serverauthoid)
    $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
    $ekuext.InitializeEncode($ekuoids)

$cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
    $cert.InitializeFromPrivateKey(2, $key, "")
    $cert.Subject = $name
    $cert.Issuer = $cert.Subject
    $cert.NotBefore = $StartDate
    $cert.NotAfter = $EndDate
    $cert.X509Extensions.Add($ekuext)
    $cert.Encode()

$enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
    $enrollment.InitializeFromRequest($cert)
    $certdata = $enrollment.CreateRequest(0)
    $enrollment.InstallResponse(2, $certdata, 0, "")
    return $true
}

function ExportPFXFile()
{
    if($CommonName.ToLower().StartsWith("cn="))
    {
        # Remove CN from common name
        $CommonName = $CommonName.Substring(3)
    }
    if($Password -eq $null)
    {
        $Password = Read-Host -Prompt "Enter Password to protect private key" -AsSecureString
    }
    $cert = Get-ChildItem -Path Cert:\LocalMachine\my | where-object{$_.Subject -eq "CN=$CommonName"}

    Export-PfxCertificate -Cert $cert -Password $Password -FilePath "$($CommonName).pfx"
    Export-Certificate -Cert $cert -Type CERT -FilePath "$CommonName.cer"
}

function RemoveCertsFromStore()
{
    # Once the certificates have been been exported we can safely remove them from the store
    if($CommonName.ToLower().StartsWith("cn="))
    {
        # Remove CN from common name
        $CommonName = $CommonName.Substring(3)
    }
    $certs = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object{$_.Subject -eq "CN=$CommonName"}
    foreach($c in $certs)
    {
        remove-item $c.PSPath
    }
}

if(CreateSelfSignedCertificate)
{
    ExportPFXFile
    RemoveCertsFromStore
}
  1. Open a PowerShell console as an administrator
  2. Create the certificate with a command like .\Create-SelfSignedCertificate.ps1 -CommonName "SharePointOnlinePrivateKey" -StartDate 2024-04-01 -EndDate 2035-01-01 (if you receive an error, make sure to allow running this kind of script with the command Set-ExecutionPolicy RemoteSigned)
  3. A password is required (e.g. "HereIsMyPass1223")
  4. Two files are created: SharePointOnlinePrivateKey.pfx and SharePointOnlinePrivateKey.cer

We're going to install OpenSSL to convert the pfx file to a pem:

  1. Install OpenSSL (e.g. the light version for Win64) as an administrator
  2. Find the OpenSSL installation directory (e.g. C:\Program Files\OpenSSL-Win64\)
  3. Open the start.bat file from this OpenSSL directory
  4. A command window opens – go to the directory where the SharePointOnlinePrivateKey.pfx file is (e.g. cd C:\Users\Aymeric\Documents\nodejs\spo-experiments)
  5. In the OpenSSL command window, type openssl pkcs12 -in SharePointOnlinePrivateKey.pfx -out SharePointOnlinePrivateKey.pem (the password entered in the previous steps will be asked three times)

We should now have a file called SharePointOnlinePrivateKey.pem

Azure Application

It's time to create the related Azure application:

  1. Go to https://portal.azure.com
  2. Go to the Microsoft Entra ID section
  3. Go to the "App Registrations", and click on "New registration"
  4. I won't detail all the steps to create the app
  5. Give a name to the app (e.g. "SharePoint Online Remote Access")

Get Thumbprint

We need the thumbprint:

  1. Go to "Certificates & secrets" section
  2. Go to "Certificates" tab (see how) and upload the SharePointOnlinePrivateKey.cer file you created before
  3. Once uploaded, it will provide "Thumbprint" (e.g. "F7D8D4F2F140E79B215899BD93A14D0790947789") – copy this value for a later use.

Get Client Id and Tenant Id

We need the clientId and the tenantId:

  1. From the overview page of your app, copy the "Application (client) Id" (e.g. "75284292-7515-4e2f-aae9-d1777527dd7b") and the "Directory (tenant) ID" (e.g. "945c177a-83a2-4e80-9f8c-5a91be5752dd")

Platform configuration

Additional configuration required for the application:

  1. Go to "Authentication" menu, and under "Platform configurations", click on "Add a Platform"
  2. Choose "Web" and enter https://localhost for "Redirect URLs"
  3. Choose "Access Token" and "ID Token" in the "Implicit grant and hybrid flows" section

API Permissions

We want to give the permissions to the Azure app to get access to only one (or more) specific Sites Collection, but not all of the tenant site collections. To do so, we only need the Sites.Selected permissions (ref1 and ref2).

First, let's select it from a Microsoft Graph perspective:

  1. Go to the "API Permissions" section from the left navigation
  2. Click on "Add a permission"
  3. Select "Microsoft Graph"
  4. Then "Application Permissions"
  5. Select "Sites.Selected"

Then, let's select it for SharePoint Online REST perspective:

  1. From the same section, select "SharePoint"
  2. Then "Application Permissions"
  3. Select "Sites.Selected"

API Permissions screen
Remark: you might need to contact the IT team to get your API Permissions approved/granted.

Get Client Secret

We need the clientSecret:

  1. Go to the "Certificates and Secrets" section from the left navigation
  2. Go to "Client Secret" and click on "New client secret"
  3. In "Description" you can put something like "SharePoint Online Remote Access", and choose 365 days for the expiration
  4. Once done, make sure to copy Value (e.g. "rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt") that is our clientSecret

App permissions on Site Collection

It's time to indicate that the Azure application can have access to a specific Site Collection (e.g. https://mycompany.sharepoint.com/sites/contoso).

To proceed, we need the siteId:

  1. Go to the SharePoint website (e.g. https://mycompany.sharepoint.com/sites/contoso)
  2. Click right in the page to see the source (or CTRL U)
  3. Search for the variable siteId (e.g. "7d6d9e18-e8de-4b7c-9582-3f978726f356")

To give the permissions to the app, a special command must be entered (see this video for more information). In theory, the site collection admin can do it, but it might be restricted in your organization, and you'll need the assistance of a tenant admin.

We need the AppId (as known as clientId) and DisplayName (as known as the Azure app name):

$targetSiteUri = 'https://mycompany.sharepoint.com/sites/contoso'
Connect-PnpOnline $targetSiteUri
Grant-PnPAzureADAppSitePermission -AppId '75284292-7515-4e2f-aae9-d1777527dd7b' -DisplayName 'SharePoint Online Remote Access' -Site $targetSiteUri -Permissions Write

Interact with SharePoint

Microsoft Graph only needs a client_secret, while SharePoint REST API needs a client_assertion from the Certificate Private key.

So, let's start with Microsoft Graph to verify our Azure app has the correct access permissions.

Microsoft Graph

Below is the script we can use:

const axios = require('axios');

// our constants that we found previously
// they should not be hard-coded in your script, but store in a safe place
const tenantId = '945c177a-83a2-4e80-9f8c-5a91be5752dd';
const clientId = '75284292-7515-4e2f-aae9-d1777527dd7b';
const clientSecret = 'rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt';
const siteId = '7d6d9e18-e8de-4b7c-9582-3f978726f356';

async function getAccessToken() {
  const resource = 'https://graph.microsoft.com';

  try {
    const response = await axios.post(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: clientId,
      client_secret: clientSecret,
      scope: `${resource}/.default`
    }), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    return response.data.access_token;
  } catch (error) {
    console.error('Error getting access token:', error);
    throw error;
  }
}

// get a SharePoint item using Graph
getAccessToken()
.then(token => {
  // we need to find the siteId for each level
  // we could use `https://graph.microsoft.com/v1.0/sites/${siteId}/sites` to find the children sites Id
  // mycompany.sharepoint.com/sites/contoso/Toolbox -> 919f3ff8-2cfd-469d-ac2c-cf58475ee72a
  // mycompany.sharepoint.com/sites/contoso/Toolbox/Demo -> 37af7205-ebd1-49e5-a770-cdb182d2ae81
  return axios.get(`https://graph.microsoft.com/v1.0/sites/${siteId}/sites/919f3ff8-2cfd-469d-ac2c-cf58475ee72a/sites/37af7205-ebd1-49e5-a770-cdb182d2ae81/lists/Assets/items/123?expand=fields(select=Title)`, {
    headers:{
      'Authorization':'Bearer '+token,
      'Accept': 'application/json'
    }
  })
})
.then(res => {
  console.log(res.data);
})
.catch(err => {
  console.log("err => ", err.response.data)
})

SharePoint Online REST API

If it worked with the Graph version, it means we can now test with the SharePoint REST API:

const axios = require('axios');
const fs = require("fs");
const crypto = require("crypto");
const msal = require("@azure/msal-node");
const { v4: uuid } = require('uuid');

// our constants that we found previously
// they should not be hard-coded in your script, but store in a safe place
const tenantId = '945c177a-83a2-4e80-9f8c-5a91be5752dd';
const clientId = '75284292-7515-4e2f-aae9-d1777527dd7b';
const clientSecret = 'rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt';
const resource = 'https://mycompany.sharepoint.com';
const privateKeyPassPhrase = "HereIsMyPass1223";
const thumbprint = "F7D8D4F2F140E79B215899BD93A14D0790947789";

// generate the client_assertion
function getClientAssertion () {
  // source: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/certificate-credentials.md
  // decrypt the private key
  const privateKeySource = fs.readFileSync("./SharePointOnlinePrivateKey.pem");
  const privateKeyObject = crypto.createPrivateKey({
      key: privateKeySource,
      passphrase: privateKeyPassPhrase,
      format: "pem",
  });

  const privateKey = privateKeyObject.export({
      format: "pem",
      type: "pkcs8",
  });
  
  const config = {
    auth: {
      clientId: clientId,
      authority: `https://login.microsoftonline.com/${tenantId}`,
      clientCertificate: {
        thumbprint: thumbprint, // a 40-digit hexadecimal string
        privateKey: privateKey,
      },
    },
  };

  // Create msal application object
  const cca = new msal.ConfidentialClientApplication(config);
  const helper = {
    createNewGuid:uuid
  }
  const issuer = clientId;
  const jwtAudience = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
  return cca.clientAssertion.getJwt(helper, issuer, jwtAudience);
}

async function getAccessToken() {
  // see https://github.com/SharePoint/sp-dev-docs/issues/5889 and https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate
  try {
    const response = await axios.post(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, new URLSearchParams({
      client_id: clientId,
      scope: `${resource}/.default`,
      client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      client_assertion: getClientAssertion(),
      grant_type: 'client_credentials',
    }), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    return response.data.access_token;
  } catch (error) {
    console.error('Error getting access token:', error);
    throw error;
  }
}

// run the script
getAccessToken()
.then(token => {
  return axios.get(`https://mycompany.sharepoint.com/sites/contoso/Toolbox/Demo/_api/web/lists/getbytitle('Assets')/items(123)?$select=Title`, {
    headers:{
      'Authorization':'Bearer '+token,
      'Accept': 'application/json; odata=verbose'
    }
  })
})
.then(response => {
  console.log(response.data)
})
.catch(console.log)

Manual Installation of ExplorerPatcher

ExplorerPatcher permits to change the behavior of the Windows taskbar. If for some reasons the automatic install doesn’t work, you can proceed manually:

  1. Install it on a computer where the automatic install works
  2. On the target computer, download ep_setup.exe from the official website
  3. Open a Terminal and extract the files from the setup using the command ep_setup.exe /extract
  4. Several files have been extracted from the setup
  5. Move all the files to C:\Program Files\ExplorerPatcher
  6. Kill Windows Explorer from the Task Manager
  7. From the computer in step 1, copy the file C:\Windows\dxgi.dll and paste it to the target computer at the same location
  8. Restart Windows Explorer from a Terminal by typing explorer.exe

Power Automate: how to verify if a property belongs to an object (apply to SharePoint Date too)

We can use this kind of formula (notice the questionmark):
if(empty(variables('params')?[variables('fieldName')]), 'fieldName is not part of the object params', variables('params')?[variables('fieldName')])

Then if it either returns « fieldName is not part of the object params » or the value.

We can use it to check if a date field is empty in a SharePoint List, because when getting the data from the SP List, when the date field is empty, it’s not in the result returned – the below code will return true if the date field is empty:
empty(item()?['dateFieldNameId'])

Power Automate returns an error about « InvokerConnectionOverrideFailed » and « header.X-MS-APIM-Tokens »

While calling a « Run a Child Flow » from a Power Automate Flow, you could get an error about « InvokerConnectionOverrideFailed » and « header.X-MS-APIM-Tokens ».

After investigating, to resolve this issue you need to open the « details » view of your child flow, and click on the « Edit » button from the « Run only users » card:

Then in the Connections Used section, make sure to select « Use this connection (your_username@domain.com) » instead of the default « Provided by run-only user ». It will permit to run this child flow with your rights.

Supprimer la commande Ctrl+Alt+Supp pour ouvrir Windows 11

Il suffit de modifier le registre de Windows (regedit), en passant à 1 la variable DisableCad dans ces deux emplacements :

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

Search and restore an item from a SharePoint Online Recycle Bin

It might be difficult to search for an item in a SharePoint recycle bin. I was using the end point _api/site/RecycleBin as a Site Collection Administrator, but in some cases it returns an error « The attempted operation is prohibited because it exceeds the list view threshold. ».

The solution is to use another end point _api/site/getrecyclebinitems (see the documentation for the various parameters):

fetch("https://tenant.sharepoint.com/sites/YourSite/_api/site/getrecyclebinitems?rowLimit=%2770000%27&itemState=0&orderby=3&isAscending=false", {
  "headers": {
    "accept": "application/json",
    "content-type": "application/json",
    "x-requestdigest": document.querySelector("#__REQUESTDIGEST").value
  },
  "method": "GET",
})
.then(res => res.json())
.then(json => {
  console.log(json.value);
  // it will show an array with an object that contains several interesting properties:
  // AuthorEmail, AuthorName, DeletedByEmail, DeletedDate, DirName, Id, LeafName, Title, …
})

You can then filter the result to get what you need, for example if you’re looking for an item from the list “Projects” that has the ID 1981:

json.value.filter(item => item.DirName.includes("Lists/Projects") && item.LeafName === "1981_.000");

Then, to restore an item, we need the Id from the previous result. The famous endpoint to restore an item is _api/site/RecycleBin('Id')/restore, however it could also return the error « The attempted operation is prohibited because it exceeds the list view threshold ». In that case, we can use this other endpoint _api/site/RecycleBin/RestoreByIds:

fetch("https://tenant.sharepoint.com/sites/YourSite/_api/site/RecycleBin/RestoreByIds", {
  "headers": {
    "accept": "application/json",
    "content-type": "application/json",
    "x-requestdigest": document.querySelector("#__REQUESTDIGEST").value
  },
  "method": "POST",
  "body":JSON.stringify({
    "ids": [
      "4f855ee7-472b-414a-a482-4317a114c1a2" // Id to restore
    ],
    "bRenameExistingItems": true
  })
})

Calculating HMAC SHA-1 in the Browser

If you’re looking for the equivalent of hash_hmac('sha1', 'string', 'secret'); in JavaScript, then here you go:

async function hmac_sha1 (str, secret) {
  // see https://stackoverflow.com/a/47332317/1134119
  let enc = new TextEncoder("utf-8");
  let key = await window.crypto.subtle.importKey(
    "raw", // raw format of the key - should be Uint8Array
    enc.encode(secret),
    { // algorithm details
      name: "HMAC",
      hash: {name: "SHA-1"}
    },
    false, // export = false
    ["sign", "verify"] // what this key can do
  );
  let signature = await window.crypto.subtle.sign(
    "HMAC",
    key,
    enc.encode(str)
  );
  let b = new Uint8Array(signature);
  return Array.prototype.map.call(b, x => x.toString(16).padStart(2, '0')).join("");
}

Connect to SharePoint Online using an app clientId and clientSecret

Get `clientId` and `clientSecret`

(source)

You’ll need credentials:

  • `clientId` – required string, client id obtained when registering the addin
  • `clientSecret` – required string, client secret obtained when registering the addin
  • `realm` – your SharePoint Online tenant id. The easiest way to find tenant is to open SharePoint Online site collection, click Site SettingsSite App Permissions. Under this page you wll see at least one app « Microsoft.SharePoint ». The tenant id (realm) is highlighted in the image below:

Example of the expected result:

{
  clientId: '28bq7e56-8c3a-487d-hbfb-ef1a74539cbe',
  clientSecret: 's6LZ4VvoeKOS+MyAhklcavsyJBF4XhWo06OgY6czYJ0=',
  realm: '85e5f09b-4c17-4d80-afea-260bb171c456'
}

To get the credentials, you need to register a new addin inside SharePoint Online, by fellowing these steps:

  1. Open SharePoint Online app registration page, e.g. https://contoso.sharepoint.com/sites/dev/_layouts/15/appregnew.aspx
  2. Click on « Generate » for Client id and Client Secret, fill in Title, App Domain, Redirect URI (you can type in any values you want)
  3. Click on « Create » and save generated Client Id and Client Secret
  4. [IF YOU HAVE TENANT RIGHTS] Now you need to apply permissions to the newly registered app. If you want to register the app once and use it for any site collection, it’s better to apply tenant scope permissions, so you can use the credentials everywhere inside your SharePoint tenant. To apply tenant scoped permissions, open AppInv.aspx page under SharePoint adminstration web site, e.g. https://[YOUR_ORGANIZATION]-admin.sharepoint.com/_layouts/15/appinv.aspx, copy paste Client Id from step n°3 into App Id field and click « Lookup ».
  5. [IF YOU HAVE TENANT RIGHTS] You will see your registered app, paste in the following XML into the « Permission Request XML » field and click « Create »:
        <AppPermissionRequests AllowAppOnlyPolicy="true">
          <AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
        </AppPermissionRequests>
    

  6. [IF YOU ARE NOT A TENANT] If you only want to give permissions on 1 site collection, you can register the app on a regular site collection by using url https://contoso.sharepoint.com/sites/dev/_layouts/15/appinv.aspx. In this case you are not able to use tenant scoped permissions and can only apply site collection permissions:
        <AppPermissionRequests AllowAppOnlyPolicy="true">
          <AppPermissionRequest Scope="http://sharepoint/content/sitecollection" Right="FullControl" />
        </AppPermissionRequests>
    
  7. You will see addin « Trust » confirmation, click on « Trust It »:

    if trust-it button is not enabled and you get a red label saying tenant admin needs to trust the app, go back and try again in a few minutes.
  8. Now you can use client id and client secret to send authenticated http requests.

To know more about the XML permissions, you can check the Microsoft documentation.

Get Access Token

(you can find a C# code as an example)

You need to do a POST request to https://accounts.accesscontrol.windows.net/[YOUR_TENANT_REALM]/tokens/OAuth/2 with a « Content-Type » header that has the value « application/x-www-form-urlencoded », and the body parameters that must be:

  • "grant_type":"client_credentials"
  • "client_id":"[YOUR_CLIENT_ID]@[YOUR_TENANT_REALM]"
  • "client_secret":"[YOUR_CLIENT_SECRET]"
  • "resource":"00000003-0000-0ff1-ce00-000000000000/dell.sharepoint.com@[YOUR_TENANT_REALM]"

See below an example in PHP:

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "https://accounts.accesscontrol.windows.net/[YOUR_TENANT_REALM]/tokens/OAuth/2");
curl_setopt($curl, CURLOPT_HTTPHEADER, [ "Content-Type: application/x-www-form-urlencoded" ]);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query([
  "grant_type" => "client_credentials",
  "client_id" => "[YOUR_CLIENT_ID]@[YOUR_TENANT_REALM]",
  "client_secret" => "[YOUR_CLIENT_SECRET]",
  "resource" => "00000003-0000-0ff1-ce00-000000000000/dell.sharepoint.com@[YOUR_TENANT_REALM]"
]));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($curl));
curl_close($curl);

echo $response->access_token;

The response should contain an access token. Example:

{
  "token_type":"Bearer",
  "expires_in":"86399",
  "not_before":"1679393911",
  "expires_on":"1679480611",
  "resource":"00000003-0000-0ff1-ce00-000000000000/dell.sharepoint.com@[YOUR_TENANT_REALM]",
  "access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSU[...]SxXA5Lqbk1OcOVdwQ"
}

Finally, you can do your REST API request to SharePoint Online with passing the header « Authorization » that has the value « Bearer [YOUR_ACCESS_TOKEN] ».

Mount a SMB Network Drive with Pivotal Cloud Foundry (pcf) and access it using NodeJS

Connect to the Apps Manager and navigate to the space where you want to mount the drive. Look at the members, and make sure your user has the correct roles:

Then in a console, connect and log into the pcf space (something like cf login and cf -s APPNAME).

Next, try the command cf create-service smb Existing Files_Share -c '{\"share\":\"//server.name.domain.com/Files_Share\"}' (the backslashes are important if you’re on Windows).

Now you can bind the service with cf bind-service APPNAME Files_Share -c '{\"username\":\"username_without_domain\",\"password\":\"network_password\",\"mount\":\"/mnt/Files_Share\"}'

For more info, see https://docs.cloudfoundry.org/devguide/services/using-vol-services.html#smb