DEV Community

Bruce Mcpherson
Bruce Mcpherson

Posted on

Yes – you can execute native Apps Script with Office 365 back end

gas-fakes emulates the Google Apps Script (GAS) environment natively within a Node.js runtime. By translating standard GAS service calls into granular API requests, it provides a high-fidelity, local sandbox for debugging, automated testing, and execution without the constraints of the Google Cloud IDE.

Microsoft as an Apps Script backend

Apps Script: A ‘Lingua Franca’ for the Multi-Cloud Era introduces the concept of replacing Apps Script's regular Workspace backend with ksuite. You write Apps Script code as normal, but behind the scenes gas-fakes translates the code into ksuite API requests.

I’m now adding the Microsoft Graph (Msgraph) backend. This represents a strategic evolution for the project. This addition allows developers to apply the familiar Apps Script programming model directly to the Microsoft 365 ecosystem.

By acting as a “lingua franca” for workspace platforms, gas-fakes enables you to treat the underlying productivity suite as a pluggable component.

This allows for the maintenance of a single business logic codebase that can target both Google Workspace and Microsoft 365.

Configuring the Microsoft Graph Backend

As usual, handling auth is the trickiest part of all this. However, the gas-fakes cli handles initializing and authentication against Azure to allow access to Msgraph. Just provide a normal apps script manifest that contains all the scopes you want to use.

gas-fakes init and auth will handle setting up the necessary app registration (this is similar to the Google Service Account). Access delegation is a little like Google domain wide delegation (DWD).

gas-fakes automatically translates the google oauthScopes section in your manifest into their Azure equivalents.

Initializing the platforms you ever want to use

Simply list the backends you want to be able to use in the init phase. The default is of course just “google”.

For this example, we want Apps Script to be able to access all 3 supported backends from the same project. You need az (the azure cli ) and gcloud (the google cli) installed.

gas-fakes init -b "google,msgraph,ksuite"

This will ask a series of questions and create any required registrations and service accounts, and create variables in your selected .env file

Authing the platform your project want to use

The auth phase will read the information you provided in the init phase, and execute the selected auth process for the selected back end platforms.

gas-fakes auth -b "msgraph,google"

This will set any permissions and scopes on the service accounts and registrations.

Keyless auth

In both google and microsoft, the authentication processes are ‘keyless’ – meaning gas-fakes doesn’t store service account credentials locally.

With Google, you have the choice of Application Default Credential (–auth-type adc) or the default domain wide delegation (the default –-auth-type dwd).

Microsoft is a kind of hybrid. Caveat: there are many variations depending on whether you have a SPO license, and using a business or consumer license. There are additional complications around multi tenants and other weird things.

I don’t have any of those. So I have only been able at this time to test the consumer account track in the auth process. This means you may get an occassional consent screen popping up occassionally even after the auth stage as the consumer track does not fully support delegation in the way that google does.

Development Experience

When targeting the msgraph backend, gas-fakes maps familiar GAS-style service synchronous calls to the Microsoft Graph API. You can now target OneDrive and Excel data without bothering to learn the nuances of the Microsoft Graph SDK.

Because the environment emulates the global GAS objects, the same code to process a Google Sheet can be applied to an Excel workbook on OneDrive without modification.

At the time of writing , I’ve only implemented a subset of the msgraph methods for OneDrive and Excel. I’ll add more over time.

Example: recursively list the entire contents of multiple platforms

Here’s an example app showing how you can combine platforms using the same Apps Script code. Here we are recursively list all the files in Drive, OneDrive and Ksuite

import '@mcpher/gas-fakes'

// run explore on each platform
const dual = () => {
  ScriptApp.__platform = 'msgraph'
  const rootFolder0 = DriveApp.getRootFolder()
  console.log('--- msgraph Recursive Explorer ---')
  explore(rootFolder0)

  ScriptApp.__platform = 'ksuite'
  const rootFolder = DriveApp.getRootFolder()
  console.log('--- KSuite Recursive Explorer ---')
  explore(rootFolder)

  ScriptApp.__platform = 'google'
  const rootFolder2 = DriveApp.getRootFolder()
  console.log('--- Google Workspace Recursive Explorer ---')
  explore(rootFolder2)

}

const explore = (folder, depth = 0) => {
  const indent = ' '.repeat(depth)
  console.log(`${indent}FOLDER: ${folder.getName()} (ID: ${folder.getId()})`)

// Show files in this folder
  const files = folder.getFiles()
  while (files.hasNext()) {
    const file = files.next()
    console.log(`${indent} FILE: ${file.getName()} (ID: ${file.getId()})`)
  }

  // Drill into subfolders
  const folders = folder.getFolders()
  while (folders.hasNext()) {
    explore(folders.next(), depth + 1)
  }
}

dual ()

Enter fullscreen mode Exit fullscreen mode

Example: copying folder contents between platforms

Here we use the same code to copy all the files in a given folder between various combinations of platforms, and validate that each file content has been successfully written

import '@mcpher/gas-fakes'

const demoTransfer = () => {
  // copy the files from ksuite
  copyFiles({
    sourcePlatform: 'ksuite',
    targetPlatform: 'google',
    sourceFolderName: 'gas-fakes-assets',
    targetFolderName: 'from-ksuite-to-google'
  })

  // and back again
  copyFiles({
    sourcePlatform: 'google',
    targetPlatform: 'ksuite',
    sourceFolderName: 'from-ksuite-to-google',
    targetFolderName: 'from-google-to-ksuite'
  })

  // now copy them from google to ms-graph
  copyFiles({
    sourcePlatform: 'google',
    targetPlatform: 'msgraph',
    sourceFolderName: 'from-ksuite-to-google',
    targetFolderName: 'from-google-to-ms-graph'
  })

  // and back again
  copyFiles({
    sourcePlatform: 'msgraph',
    targetPlatform: 'ksuite',
    sourceFolderName: 'from-google-to-ms-graph',
    targetFolderName: 'from-ms-graph-to-ksuite'
  })

  // check that the final files in ksuite match the original
  const sourceBlobs = getBlobs({
    sourcePlatform: 'ksuite',
    sourceFolderName: 'gas-fakes-assets'
  })
  const finalBlobs = getBlobs({
    sourcePlatform: 'ksuite',
    sourceFolderName: 'from-ms-graph-to-ksuite'
  })

  // check blobs by checking their digest
  if (sourceBlobs.length !== finalBlobs.length) {
    throw new Error(`expected ${sourceBlobs.length} blobs but got ${finalBlobs.length}`)
  }

  sourceBlobs.forEach((b, i) => {
    const sourceDigest = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, b.getBytes()))
    const finalDigest = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, finalBlobs[i].getBytes()))

    if (sourceDigest !== finalDigest) {
      console.log(`${b.getName()} blob mismatch with ${finalBlobs[i].getName()}`)
    }
  })
}

const getBlobs = ({ sourcePlatform, sourceFolderName }) => {
  // set which platform to use
  ScriptApp.__platform = sourcePlatform
  const sourceFolders = DriveApp.getFoldersByName(sourceFolderName)

  if (!sourceFolders.hasNext()) {
    throw new Error(`Source folder ${sourceFolderName} not found`)
  }

  // get the files in that source folder
  const files = sourceFolders.next().getFiles()
  const blobsToCopy = []
  while (files.hasNext()) {
    const file = files.next()
    blobsToCopy.push(file.getBlob())
  }
  return blobsToCopy
}

const copyFiles = ({ sourcePlatform, targetPlatform, sourceFolderName, targetFolderName }) => {
  const blobsToCopy = getBlobs({ sourcePlatform, sourceFolderName })

  // now use an alternative platform
  ScriptApp.__platform = targetPlatform

  // create the folder if it doesn't exist
  const targetFolders = DriveApp.getFoldersByName(targetFolderName)
  let targetFolder = targetFolders.hasNext() 
    ? targetFolders.next() 
    : DriveApp.createFolder(targetFolderName)

  // now copy the blobs to the target folder
  blobsToCopy.forEach(blob => {
    targetFolder.createFile(blob)
  })
  return blobsToCopy
}

demoTransfer()
Enter fullscreen mode Exit fullscreen mode

Advanced Feature: Leveraging Native Apps Script Libraries

gas-fakes further bridges the platform gap by allowing the execution of native Apps Script libraries within the Node.js emulation layer. gas-fakes manages the loading and execution of external library dependencies. Any libraries mentioned in your Apps Script manifest will be loaded and available.

Example 3: Using a live apps script library across platforms

In this case, we’ll use the bmPreFiddler library to manipulate sheet contents in both platforms. Again we are leveraging gas-fakes sandbox to both clean up and limit access to intended files.

import '@mcpher/gas-fakes'

/**
 * Creates a spreadsheet on the specified platform.
 * @param {Object} params
 * @param {string} params.platform - The target platform (e.g., 'google', 'msgraph').
 * @param {string} params.title - The name of the new spreadsheet.
 */
const createSpreadsheet = ({ platform = "google", title }) => {
  ScriptApp.__platform = platform
  const ss = SpreadsheetApp.create(title)
  console.log(`Created spreadsheet ${ss.getName()} on ${platform}`)
  return ss
}

/**
 * Sets the active platform for ScriptApp.
 */
const setPlatform = (name) => ScriptApp.__platform = name

/**
 * Copies a sheet's data between two platforms and verifies the result.
 * @param {Object} params
 * @param {Object} params.source - Source details {platform, id, sheetName}.
 * @param {Object} params.target - Target details {platform, title}.
 */
const copySheetBetweenPlatforms = ({ source, target }) => {
  // Get a fiddler for the source
  setPlatform(source.platform)
  const fiddler = bmPreFiddler.PreFiddler().getFiddler(source)

  // Create the output spreadsheet on the target platform
  setPlatform(target.platform)
  const dst = createSpreadsheet(target)

  // Get a fiddler for the destination
  const dstFiddler = bmPreFiddler.PreFiddler().getFiddler({
    id: dst.getId(),
    sheetName: source.sheetName,
    createIfMissing: true
  })

  // Copy the data and dump to target
  dstFiddler.setData(fiddler.getData()).dumpValues()

  // Verify that both sheets match using fingerprints
  const after = bmPreFiddler.PreFiddler().getFiddler({
    id: dstFiddler.getSheet().getParent().getId(),
    sheetName: dstFiddler.getSheet().getName()
  })

  if (after.fingerPrint === fiddler.fingerPrint) {
    console.log('Bingo: Data matches perfectly')
  } else {
    console.log('Error: Data fingerprint mismatch')
  }
}

// load any libraries
LibHandlerApp.load()

// enable sandbox mode
ScriptApp.__behavior.sandBoxMode = true;

// create some spreadsheets with data and copy between them
const source = {
  id: "1h9IGIShgVBVUrUjjawk5MaCEQte_7t32XeEP1Z5jXKQ",
  platform: "google",
  sheetName: 'airport list'
}

// add that to sanbox for read without marking it for trashing
ScriptApp.__behavior.whitelistFile(source.id)

copySheetBetweenPlatforms({ source, target: {platform: 'msgraph', title: 'test-msgraph-libraries'}})

// cleanup any files created
ScriptApp.__behavior.trash()

Enter fullscreen mode Exit fullscreen mode

Help us develop gas-fakes

gas-fakes is an open-source project. We encourage developers to collaborate, contribute to the extension of supported services, and help refine this bridge between the world’s most popular workspace platforms.

This would be especially helpful if you have Microsoft knowledge and would like to help develop the msgraph connection. Ping me on bruce@mcpher.com if you want to get involved.

Links

GitHub: gas-fakes
GitHub: gas-fakes-containers
More gas-fakes articles: desktop liberation

What is gas-fakes

gas-fakes is a powerful emulation layer that lets you run Apps Script projects on Node.js as if they were native. By translating GAS service calls into granular Google API requests, it provides a secure, high-speed sandbox for local debugging and automated testing.

Built for the modern stack, it features plug-and-play containerization—allowing you to package your scripts as portable microservices or isolated workers. Coupled with automated identity management, gas-fakes handles the heavy lifting of OAuth and credential cycling, enabling your scripts to act on behalf of users or service accounts without manual intervention. It’s the missing link for building robust, scalable Google Workspace automations and AI-driven workflows.

Watch the video

Top comments (0)