An Intune Package Factory for the Microsoft 365 Apps

An Intune Package Factory for the Microsoft 365 Apps

Using PowerShell and GitHub Actions to automate the packaging of the Microsoft 365 Apps and import into an Intune tenant.

Deploying the Microsoft 365 Apps via Microsoft Intune is as simple as using using the built in tools to create a package without having to manually package any binaries.

Creating a Microsoft 365 Apps package in Intune

Creating a Microsoft 365 Apps package in the Microsoft Intune admin center

This approach works great for new devices, particularly PCs deployed via Windows Autopilot. However, there are challenges with the in-built package solution.

The Problem

The in-built Microsoft 365 Apps package doesn’t consistently upgrade older versions of Microsoft Office. At Insentra seen issues with failed deployments due to a failure in the package upgrading over existing Microsoft Office installations in several customer environments.

Additionally, the in-built Microsoft 365 Apps package cannot be used as a dependency by another Win32 application package (e.g., ensure the Microsoft 365 Apps is installed before an add-in package is installed).

If your environment experiences upgrade issues or you need to use the Dependencies feature, you’ll need to create a custom Win32 application package to deploy the Microsoft 365 Apps.

For small environments, creating a custom package could be a one off action, thus the package can be created manually; however, for larger environments you could create multiple packages, and could have a team of engineers creating packages. This could be in house engineers, or consultant or managed services engineers working across multiple customer environments. In these environments, it’s important to ensure consistency across multiple packages - without packages built to a common standard, your devices could experience inconsistent deployments and you’ll spend more time troubleshooting issues.

How do we ensure standardisation and a simple method for creating Microsoft 365 Apps packages? With automation, of course.

Anatomy of a Microsoft 365 Apps Win32 Package

Let’s start by taking a look at what should be included in a custom Microsoft 365 Apps Win32 package:

  • A configuration.xml that defines the Microsoft 365 Apps package. Create the configuration XMl files in the Office Customization Tool
  • An uninstall.xml that defines removal of the Microsoft 365 Apps from a target PC
  • setup.exe from the Office Deployment Tool. This will process the configuration.xml and the uninstall.xml to install or uninstall the Microsoft 365 Apps
  • A detection method for Intune to determine whether the application is installed. Microsoft lists the existence of the HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\O365ProPlusRetail - en-us registry key in the documentation; however, this is a very simple approach and you may want instead use registry keys or values unique to your package. The registry key HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Configuration includes several values useful for detection rules.
  • For upgrade scenarios, Microsoft provides scripts that are useful for uninstalling and cleaning up older versions of Microsoft Office. These VBScripts provide a more consistent result than relying on the Office Deployment Tool to complete the uninstall and upgrade.
  • Finally, wrapping the install package with the PSAppDeployToolkit provides additional install logic and handling, particularly for in-place upgrades on existing devices.

The Solution

To solve this challenge, I’ve built PowerShell packaging factory for the Microsoft 365 Apps and Microsoft Intune, that can be run locally or via GitHub Actions.

The Microsoft 365 Apps packager repository consists of the following:

  • New-Microsoft365AppsPackage.ps1 - This is the key script that creates and imports a Microsoft 365 Apps package into Intune. The script can be run on a Windows machine in a copy of the repository or via GitHub Actions (if you clone the repository)
  • Create-Win32App.ps1 imports the intunewin package into the target Intune tenant, using App.json as the template. This script uses the IntuneWin32App PowerShell module and is called by New-Microsoft365AppsPackage.ps1 to import the package into an Intune tenant
  • Template Microsoft 365 Apps deployment configurations - deployment configurations are created in the Microsoft 365 Apps admin center, but every organisation is going to deploy a similar configuration, so these templates should be suitable for the most common deployments
  • A GitHub workflow that uses New-Microsoft365AppsPackage.ps1 to package the Microsoft 365 Apps on a GitHub hosted runner - the workflow can import the package into an Intune tenant. It also uploads the generated Microsoft 365 Apps package a workflow artifact for importing into Intune manually

Requirements

New-Microsoft365AppsPackage.ps1 must be run on a supported Windows version, and has been written for PowerShell 5.1. Parameters for New-Microsoft365AppsPackage.ps1 are:

ParameterDescriptionRequired
PathPath to the top level directory of the m365apps repository on a local Windows machine.No
ConfigurationFileFull path to the Microsoft 365 Apps package configuration file. Specify the full path to a configuration file included in the repository or the path to an external configuration file.Yes
ChannelA supported Microsoft 365 Apps release channel.No. Defaults to MonthlyEnterprise
CompanyNameCompany name to include in the configuration.xml.No. Defaults to stealthpuppy
TenantIdThe tenant id (GUID) of the target Azure AD tenant.Yes
ClientIdThe client id (GUID) of the target Azure AD app registration.No
ClientSecretClient secret used to authenticate against the app registration.No
ImportSwitch parameter to specify that the the package should be imported into the Microsoft Intune tenant.No

PowerShell modules

These PowerShell modules are required:

If you are running the packager locally, install the modules with:

Install-Module -Name Evergreen, MSAL.PS, IntuneWin32App -SkipPublisherCheck

Configuration Files

Microsoft 365 Apps configuration files are included in the repository - these files can be used to create packages for any target tenant as some key options will be updated dynamically by New-Microsoft365AppsPackage.ps1.

  • O365BusinessRetail.xml - Configuration file for Microsoft 365 Apps for business
  • O365BusinessRetail-VDI.xml - Configuration file for Microsoft 365 Apps for business with shared licensing enabled, and OneDrive and Teams excluded
  • O365ProPlus.xml - Configuration file for Microsoft 365 Apps for enterprise
  • O365ProPlus-VDI.xml - Configuration file for Microsoft 365 Apps for enterprise with shared licensing enabled, and OneDrive and Teams excluded
  • O365ProPlusVisioProRetailProjectProRetail.xml - Configuration file for Microsoft 365 Apps for enterprise, Visio, and Project
  • O365ProPlusVisioProRetailProjectProRetail-VDI.xml - Configuration file for Microsoft 365 Apps for enterprise, Visio, and Project with shared licensing enabled, and OneDrive and Teams excluded
  • Uninstall-Microsoft365Apps.xml - A configuration that will uninstall all Microsoft 365 Apps

When the package is generated, the following properties will be updated:

  • Company Name - this is the organisation name that sets the Company property on Office documents
  • Tenant Id - the target Azure AD tenant ID
  • Channel - the Microsoft 365 Apps update channel

Using the Packager

If you’re looking to download and use the Packager locally, follow these steps:

Clone the repository

If you’re not familiar with clone a repository, use GitHub Desktop to clone the repository and keep your local copy up to date with changes to the source repository.

Usage via Administrator Sign-in

Use New-Microsoft365AppsPackage.ps1 by authenticating with an Intune Administrator account before running the script. Run Connect-MSIntuneGraph to authenticate with administrator credentials using a sign-in window or device login URL.

Connect-MSIntuneGraph -TenantID "lab.stealthpuppy.com"
$params = @{
    Path             = "E:\project\m365apps"
    ConfigurationFile = "E:\project\m365apps\configs\O365ProPlus.xml"
    Channel          = "Current"
    CompanyName      = "stealthpuppy"
    TenantId         = "6cdd8179-23e5-43d1-8517-b6276a8d3189"
    Import           = $true 
}
.\New-Microsoft365AppsPackage.ps1 @params

Which will look similar to this when run in Terminal on Windows 11:

Running New-Microsoft365AppsPackage.ps1 on Windows 11

Running New-Microsoft365AppsPackage.ps1 on Windows 11

When New-Microsoft365AppsPackage.ps1 has successfully completed, the package\output folder will contain the setup.intunewin package, a copy of the configuration XML file in the package, and m365apps.json that is used by Create-Win32App.ps1 to import the package into Intune.

Package output

Contents of the package\output folder once the package has been created

If -Import is specified when running New-Microsoft365AppsPackage.ps1, a standardised Microsoft 365 Apps package will be imported into the target Intune tenant:

The Microsoft 365 Apps package imported into Intune

The Microsoft 365 Apps package imported into Intune

If -Import is not specified, the package can be imported into Intune manually or by running Create-Win32App.ps1:

$params = @{
    Json        = "E:\project\m365apps\output\m365apps.json"
    PackageFile = "E:\project\m365apps\output\setup.intunewin"
}
& "E:\project\m365apps\scripts\Create-Win32App.ps1" @params

Usage via App Registration

Use New-Microsoft365AppsPackage.ps1 to create a new package by passing credentials to an Azure AD app registration (see below) that has rights to import applications into Microsoft Intune:

$params = @{
    Path             = "E:\project\m365Apps"
    ConfigurationFile = "E:\project\m365Apps\configs\O365ProPlus.xml"
    Channel          = "MonthlyEnterprise"
    CompanyName      = "stealthpuppy"
    TenantId         = "6cdd8179-23e5-43d1-8517-b6276a8d3189"
    ClientId         = "60912c81-37e8-4c94-8cd6-b8b90a475c0e"
    ClientSecret     = "<secret>"
    Import           = $true 
}
.\New-Microsoft365AppsPackage.ps1 @params

Automating the Packager

The Microsoft 365 Apps Packager can be automated in multiple ways; however, the repository includes a method based on workflows and GitHub Actions. To use this approach fork the repository and configure in your own GitHub account.

Azure AD App Registration

The workflows must authenticate to the Microsoft Graph API using a non-interactive authentication method. Create an Azure AD app registration and enable the DeviceManagementApps.ReadWrite.All permission.

The app registration requires the following API permissions:

API / Permissions nameTypeDescriptionAdmin consent required
DeviceManagementApps.ReadAllApplicationRead Microsoft Intune appsYes
DeviceManagementApps.ReadWriteAllApplicationRead and write Microsoft Intune appsYes

Assigning the DeviceManagementApps.ReadWrite.All API to the app registration

Assigning the DeviceManagementApps.ReadWrite.All API to the app registration

Git and GitHub Actions

A Git repository is a natural choice for teams managing the Microsoft 365 Apps to maintain a library of configurations and track changes to packages. A repository could be hosted on several providers; however, Azure DevOps or GitHub are my go-to for hosting Git repositories. For internal teams, Azure DevOps could be the better choice for authentication and authorisation via Azure AD.

GitHub Workflows is an easy platform built into GitHub repositories for automating a workflow that creates a Win32 package in intunewin format and imports it into an Intune tenant. The approach in this article could be used with Azure Pipelines if preferred; however, Azure Pipelines variables are not as flexible as inputs in GitHub Workflows.

This solution includes two pipelines:

  1. update-binaries - this workflow is scheduled to run weekly, and will update the repository with new versions of the Office Deployment Tool, the Microsoft Win32 Content Prep Tool, and the PSAppDeployToolkit
  2. new-package - this workflow will create a package for the Microsoft 365 Apps, import the package into an Intune tenant (with app registration details stored securely in repository secrets), and upload the package as a workflow artifact

New Package Workflow

Here’s what running the new-package workflow looks like - the repository hosts several configurations from which a package can be created.

Microsoft 365 Apps configuration packages in the repository

Microsoft 365 Apps configuration packages in the repository

The new-package workflow is run from the Actions tab on the repository on GitHub. The Run workflow action will provide a prompt for several inputs:

  1. Configuration XML - select from a list of configuration files stored in the repository
  2. Update channel - select the Microsoft 365 Apps update channel to apply to the package
  3. Company name - a string that will be injected into the Company value in the configuration XML file
  4. Import - choose to import the package into the target Intune tenant

Starting the workflow and selecting inputs

Starting the workflow and selecting inputs

The workflow should run to create the Microsoft 365 Apps package and import it into the target tenant.

The workflow run after completing successfully

The workflow run after completing successfully

Once the workflow has run successfully, you should see a new Win32 package in your Intune tenant.

A Microsoft 365 Apps package imported into Microsoft Intune

A Microsoft 365 Apps package imported into Microsoft Intune

Finally, once the workflow is finished, the result and details of the package it created, are saved to the workflow summary:

Workflow summary results

The workflow is updated with a summary of the results of that run including details of the package.

Update Binaries Workflow

The repository includes copies of the following binaries and support files that are automatically kept updated with the latest versions:

If you have cloned this repository, ensure that you synchronise changes to update binaries to the latest version releases.

Workflow Secrets

The new-package workflow that will package and import the Microsoft 365 Apps package into a single tenant each time the workflow is run. The following secrets are required for this workflow:

  • TENANT_ID - the target tenant ID
  • CLIENT_ID - the Azure AD app registration client ID used to authenticate to the target tenent
  • CLIENT_SECRET - password used by to authenticate to the target tenent

The update-binaries workflow will update executables and scripts required by the solution and commit changes to the repository, thus signed commits are recommended. Signing commits ensures that commits to the repository from people in your team adding, Microsoft 365 Apps configurations to the repository, are verified.

This workflow uses the following secrets to configure and sign commits:

  • COMMIT_EMAIL - email address used for commits
  • COMMIT_NAME - user name used for commits
  • GPGKEY - GPG key to sign commits
  • GPGPASSPHRASE - passphrase to unlock the GPG key

Wrap Up

While Intune includes a simple solution to creating a Microsoft 365 Apps package to deploy to Windows devices. Using that in-built solution is not without its drawbacks and limitations. Deploying the Microsoft 365 Apps to managed devices via a Win32 package will provide a more consistent result.

The Microsoft 365 Apps packager for Intune, provides a consistent and repeatable process for creating a Win32 version of the package, whether you’re importing packages into a single Intune tenant or multiple tenants.