import * as fs from "fs" ; import * as jszip from "jszip" ; import { AddInType , getAddInTypeForManifestOfficeAppType , getOfficeAppName , getOfficeAppsForManifestHosts , ManifestInfo , OfficeApp , OfficeAddinManifest , } from "office-addin-manifest" ; import open = require ( "open" ) ; import semver = require ( "semver" ) ; import * as os from "os" ; import * as path from "path" ; import * as util from "util" ; import { AppType } from "./appType" ; import { registerAddIn } from "./dev-settings" ; import { startDetachedProcess } from "./process" ; import { chooseOfficeApp } from "./prompt" ; import * as registry from "./registry" ; import { usageDataObject } from "./defaults" ; import { ExpectedError } from "office-addin-usage-data" ; const readFileAsync = util . promisify ( fs . readFile ) ; param app param manifest returns export async function generateSideloadFile ( app , manifest , document ? ) { if ( ! manifest . id ) { throw new ExpectedError ( "The manifest does not contain the id for the add-in." ) ; } if ( ! manifest . officeAppType ) { throw new ExpectedError ( "The manifest does not contain the OfficeApp xsi:type." ) ; } if ( ! manifest . version ) { throw new ExpectedError ( "The manifest does not contain the version for the add-in." ) ; } const addInType = getAddInTypeForManifestOfficeAppType ( manifest . officeAppType ) ; if ( ! addInType ) { throw new ExpectedError ( "The manifest contains an unsupported OfficeApp xsi:type." ) ; } const documentWasProvided = document && document !== "" ; const templatePath = documentWasProvided ? path . resolve ( document ) : getTemplatePath ( app , addInType ) ; if ( ! templatePath ) { throw new ExpectedError ( `Sideload is not supported for apptype: ${ addInType }.` ) ; } const templateBuffer = await readFileAsync ( templatePath ) ; const zip = await jszip . loadAsync ( templateBuffer ) ; const appName = getOfficeAppName ( app ) ; const extension = path . extname ( templatePath ) ; const pathToWrite = makePathUnique ( path . join ( os . tmpdir ( ) , `${ appName } add-in ${ manifest . id }${ extension }` ) , true ) ; if ( ! documentWasProvided ) { const webExtensionPath = getWebExtensionPath ( app , addInType ) ; if ( ! webExtensionPath ) { throw new ExpectedError ( "Don't know the webextension path." ) ; } const zipFile = zip . file ( webExtensionPath ) ; if ( ! zipFile ) { throw new ExpectedError ( "webextension was not found." ) ; } const webExtensionXml = ( await zipFile . async ( "text" ) ) . replace ( /00000000-0000-0000-0000-000000000000/g , manifest . id ) . replace ( /1.0.0.0/g , manifest . version ) ; zip . file ( webExtensionPath , webExtensionXml ) ; } await new Promise ( ( resolve , reject ) => { zip . generateNodeStream ( { type : "nodebuffer" , streamFiles : true } ) . pipe ( fs . createWriteStream ( pathToWrite ) ) . on ( "error" , reject ) . on ( "finish" , resolve ) ; } ) ; return pathToWrite ; } param manifestPath param documentUrl param isTest returns export async function generateSideloadUrl ( manifestFileName , manifest , documentUrl , isTest = false ) { const testQueryParam = "&wdaddintest=true" ; if ( documentUrl === undefined || documentUrl === "" ) { return undefined ; } if ( ! manifest . id ) { throw new ExpectedError ( "The manifest does not contain the id for the add-in." ) ; } if ( manifest . defaultSettings === undefined || manifest . defaultSettings . sourceLocation === undefined ) { throw new ExpectedError ( "The manifest does not contain the SourceLocation for the add-in" ) ; } const sourceLocationUrl = new URL ( manifest . defaultSettings . sourceLocation ) ; if ( sourceLocationUrl . protocol . indexOf ( "https" ) === - 1 ) { throw new ExpectedError ( "The SourceLocation in the manifest does not use the HTTPS protocol." ) ; } if ( sourceLocationUrl . host . indexOf ( "localhost" ) === - 1 && sourceLocationUrl . host . indexOf ( "127.0.0.1" ) === - 1 ) { throw new ExpectedError ( "The hostname specified by the SourceLocation in the manifest is not supported for sideload. The hostname should be 'localhost' or 127.0.0.1." ) ; } let queryParms = `&wdaddindevserverport=${ sourceLocationUrl . port }&wdaddinmanifestfile=${ manifestFileName }&wdaddinmanifestguid=${ manifest . id }` ; if ( isTest ) { queryParms = `${ queryParms }${ testQueryParam }` ; } return `${ documentUrl }${ queryParms }` ; } param app param addInType export function getTemplatePath ( app , addInType ) { switch ( app ) { case OfficeApp . Excel : switch ( addInType ) { case AddInType . Content : return path . resolve ( __dirname , "../templates/ExcelWorkbookWithContent.xlsx" ) ; case AddInType . TaskPane : return path . resolve ( __dirname , "../templates/ExcelWorkbookWithTaskPane.xlsx" ) ; } break ; case OfficeApp . PowerPoint : switch ( addInType ) { case AddInType . Content : return path . resolve ( __dirname , "../templates/PowerPointPresentationWithContent.pptx" ) ; case AddInType . TaskPane : return path . resolve ( __dirname , "../templates/PowerPointPresentationWithTaskPane.pptx" ) ; } break ; case OfficeApp . Word : switch ( addInType ) { case AddInType . TaskPane : return path . resolve ( __dirname , "../templates/WordDocumentWithTaskPane.docx" ) ; } break ; } } param app param addInType function getWebExtensionPath ( app , addInType ) { switch ( app ) { case OfficeApp . Excel : return "xl/webextensions/webextension.xml" ; case OfficeApp . PowerPoint : switch ( addInType ) { case AddInType . Content : return "ppt/slides/udata/data.xml" ; case AddInType . TaskPane : return "ppt/webextensions/webextension.xml" ; } break ; case OfficeApp . Word : return "word/webextensions/webextension.xml" ; } } function isSideloadingSupportedForDesktopHost ( app ) { if ( app === OfficeApp . Excel || ( app === OfficeApp . Outlook && process . platform === "win32" ) || app === OfficeApp . PowerPoint || app === OfficeApp . Word ) { return true ; } return false ; } function isSideloadingSupportedForWebHost ( app ) { if ( app === OfficeApp . Excel || app === OfficeApp . PowerPoint || app === OfficeApp . Project || app === OfficeApp . Word ) { return true ; } return false ; } function hasOfficeVersion ( targetVersion , currentVersion ) { return semver . gte ( currentVersion , targetVersion ) ; } async function getOutlookVersion ( ) { try { const key = new registry . RegistryKey ( `HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Office\\ClickToRun\\Configuration` ) ; const outlookInstallVersion = await registry . getStringValue ( key , "ClientVersionToReport" ) ; const outlookSmallerVersion = outlookInstallVersion ?. split ( `.` , 3 ) . join ( `.` ) ; return outlookSmallerVersion ; } catch ( err ) { return undefined ; } } async function getOutlookExePath ( ) { try { const OutlookInstallPathRegistryKey = `HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\OUTLOOK.EXE` ; const key = new registry . RegistryKey ( `${ OutlookInstallPathRegistryKey }` ) ; const outlookExePath = await registry . getStringValue ( key , "" ) ; return outlookExePath ; } catch ( err ) { const errorMessage = `Unable to find Outlook install location: \n${ err }` ; throw new Error ( errorMessage ) ; } } param tryToDelete function makePathUnique ( originalPath , tryToDelete = false ) { let currentPath = originalPath ; let parsedPath = null ; let suffix = 1 ; while ( fs . existsSync ( currentPath ) ) { let deleted = false ; if ( tryToDelete ) { try { fs . unlinkSync ( currentPath ) ; deleted = true ; } catch ( err ) { } } if ( ! deleted ) { ++ suffix ; if ( parsedPath == null ) { parsedPath = path . parse ( originalPath ) ; } currentPath = path . join ( parsedPath . dir , `${ parsedPath . name }.${ suffix }${ parsedPath . ext }` ) ; } } return currentPath ; } param manifestPath param app param canPrompt export async function sideloadAddIn ( manifestPath , app ? , canPrompt = false , appType ? , document ? ) { try { if ( appType === undefined ) { appType = AppType . Desktop ; } const manifest = await OfficeAddinManifest . readManifestFile ( manifestPath ) ; const appsInManifest = getOfficeAppsForManifestHosts ( manifest . hosts ) ; const isTest = process . env . WEB_SIDELOAD_TEST !== undefined ; if ( app ) { if ( appsInManifest . indexOf ( app ) < 0 ) { throw new ExpectedError ( `The Office Add-in manifest does not support ${ getOfficeAppName ( app ) }.` ) ; } } else { switch ( appsInManifest . length ) { case 0 : throw new ExpectedError ( "The manifest does not support any Office apps." ) ; case 1 : app = appsInManifest [ 0 ] ; break ; default : if ( canPrompt ) { app = await chooseOfficeApp ( appsInManifest ) ; } break ; } } if ( ! app ) { throw new ExpectedError ( "Please specify the Office app." ) ; } let sideloadFile ; switch ( appType ) { case AppType . Desktop : if ( ! isSideloadingSupportedForDesktopHost ( app ) ) { throw new ExpectedError ( `Sideload to the ${ getOfficeAppName ( app ) } app is not supported.` ) ; } await registerAddIn ( manifestPath ) ; if ( app == OfficeApp . Outlook ) { const version = await getOutlookVersion ( ) ; if ( version && ! hasOfficeVersion ( "16.0.13709" , version ) ) { throw new ExpectedError ( `The current version of Outlook does not support sideload. Please use version 16.0.13709 or greater.` ) ; } sideloadFile = await getOutlookExePath ( ) ; } else { sideloadFile = await generateSideloadFile ( app , manifest , document ) ; } break ; case AppType . Web : { if ( ! document ) { throw new ExpectedError ( `For sideload to web, you need to specify a document url.` ) ; } if ( ! isSideloadingSupportedForWebHost ( app ) ) { throw new ExpectedError ( `Sideload to the ${ getOfficeAppName ( app ) } web app is not supported.` ) ; } const manifestFileName = path . basename ( manifestPath ) ; sideloadFile = await generateSideloadUrl ( manifestFileName , manifest , document , isTest ) ; break ; } default : throw new ExpectedError ( "Sideload is not supported for the specified app type." ) ; } if ( sideloadFile ) { if ( app === OfficeApp . Outlook ) { if ( sideloadFile . indexOf ( " " ) >= 0 ) { sideloadFile = `"${ sideloadFile }"` ; } startDetachedProcess ( sideloadFile ) ; } else { await open ( sideloadFile , { wait : false } ) ; } } usageDataObject . reportSuccess ( "sideloadAddIn()" ) ; } catch ( err ) { usageDataObject . reportException ( "sideloadAddIn()" , err ) ; throw err ; } }