Microsoft Office Update Deployment via msupdate binary

Chrisdahfur
New Contributor II

If you have downloaded Microsoft Autoupdate v3.18 and above, you will find that it comes with a handy binary "msupdate" to help us IT folk deploy updates for Microsoft Office 2016 for Mac.

I wrote a handy script and created a package that does the following:
1. checks the OS of the computer
2. Checks if Office 365 is installed (with my lazy way of checking)
3. Checks if msupdate binary is installed. If not it will install MAU 4.0 that comes with binary.
4. Checks for the proper configured com.microsoft.autoupdate2.plist file that allows the binary
to shoot up to cloud what MS Office applications have been registered on the computer and check for updates. If it is not properly configured, it will basically copy and paste the plist into the local user's Preference folder.
5. It will then use the binary to check and install updates.

I have been working on this code for about two weeks. It is not very clean as I am an amateur programmer, but these have been my findings testing this new binary package from Microsoft:

  • The com.microsoft.autoupdate2 file is installed under /Users/~/Library/Application Support/Preferences/com.microsoft.autoupdate2
  • The binary is installed in /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app/Contents/MacOS/msupdate
  • I am unable to catch updates for MS Office applications even after copying and pasting the correct plist file that has the registered applications in the Preferences folder. You can test this by doing a clean install of Microsoft Office 2016 and the Auto Update 3.18 or above. Then with using the binary or the Autoupdate app that is located in /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app check for updates. It will show NO UPDATES AVAILABLE....
  • Paul Bowden from Microsoft was able to direct me to another way of correcting the plist file that should help msupdate find updates correctly but when I tested his method it ended up corrupting the plist file. =/ I am not sure why. Maybe if someone else tested this could they see if it works fine? I attached a screenshot of his solution here: 831717e623bf48cf926d8ca9e3bbe61d

It is a lengthy script...I feel like it can be a lot shorter...I added a lot of echo's for testing. It is working fine and does find updates only if a user had previously opened a Microsoft Office app and then it will find updates. This is my main problem right now.

The package contains a pre-configured plist file for helping msupdate properly (but this method doesn't work), and it also contains a Microsoft Auto Update 4.0 installer. These two items are used in the script.

You can find the package I use for this script on my github here:
https://github.com/chrisdahfur/JSS-Deployment/tree/master/Microsoft%20Office%202016%20Update%20Script

#!/bin/bash

## DESCRIPTION OF SCRIPT

: <<'COMMENT'

This script was written by Christian Orellana.

== PURPOSE ==
This script is designed to check and update Microsoft Office to the latest available version. 

This script will only install Ms Office 2016 updates to computers that have an operating system higher or equal to 10.10.

== WHAT'S IN JSS ==
For JSS, a package was created that contains the following: com.microsoft.autoupdatev2 plist file with this script and the Binary Installer.

Each function is activated with a boolean variable. Depending on the previously assigned boolean, it will either cause an error for the script and close out with a 1, or continue down the pipeline to install and update microsoft office
until it can exit with a zero.

== WHAT THE FUNCTIONS DO ==
mainProgram - This function goes ahead and runs the first function down the pipeline that will trigger certain functions accordingly.

checkProductVersion - Designed to check the OS Version of the computer. This will determine whether or not the computer is compatible 
to receive the install of MS Office 2016. If the computer is on an OS older than Yosemite (10.10) then the script will close out and exit 1.

checkOfficeinstall - This will check if Microsof Office 2016 is already installed on the computer. If it is not, it will go ahead 
and run the function to check if the packages for the installer are available on the computer. 

checkMSBinary - This is designed to check if the "msupdate" binary has been installed on the computer. If it is not, then it will run a function 
to install the binary. If it is installed it will then run the function to check if the com.microsoft.autoupdatev2 plist is installed. This plist can be found in Preferences folder in Library/Application Support.
The msupdate binary comes with Microsoft after Microsoft Auto Update reaches 3.18. Older versions of Auto Update will not have msupdate binary to help admins administer Microsoft Office.

checkForceAdd - This function will check if the autoupdatev2 plist is installed. If it is not it will run a function to install the plist necessary to find the available updates without 
having to run the applications. How the msupdate binary works is that it registers a Microsoft Application to the autoupdatev2 plist file. When this application is added, msupdate will run
an internal function to check the plist and see that the application is there and then check what update is available. Without the proper configured plist file, msupdate will not find any
available updates since the application was not added to the plist file. When the Auto Update package is installed, it installs the autoupdatev2 plist, but it will be blank, not containing
any of the Microsoft Apps as a valid application to find updates for. So this function works by checking the text "Word" or "Excel" or any of the other apps in the plist to see if the application is registered in the plist. If it is, then most likely the plist was
properly configured and it can find updates for Microsoft Office. If it is not it will then run the function to add the correct plist.

forceAddinstall - This function will install a pre-configured plist file with the proper settings for msupdate to work properly. When the plist is installed it is is copied over from a folder hidden on the computer
and not accessible to users. The owner is then changed on the plist from the root user to the locally logged in user. This needs to happen because then the msupdate binary cannot properly
check for updates because it is not allowed to access the plist. After this it will go ahead and run the checkForceAdd function to see if the plist is properly configured.

checkbinaryinstaller - This function will check if the binary installer package is on the computer. What is actually being installed is Microsoft Auto Update 4.0, which also will include
the msupdate binary necessary for updating. If the package is available then it will return a boolean for ensuring that an install of the packager can occur.

installMSBinary - This function will run the packager to install the msupdate binary. After it is installed it will run the checkMSBinary function to ensure that the binary was installed.

checkForUpdates - This function will only run once the binary is installed and the proper plist has been configured for finding updates. This will use the msupdate binary to
check for any available updates using the flag --list. If it finds any available updates it will then run the installUpdates function. If there are no updates, it will run the
exitProgram function for ending the script.

installUpdates - This function will go ahead and run the --install flag for the msupdate binary. This will only run if the script found available updates through msupdate --list. 
When msupdate --install runs, what happens is that it will download and queue updates for MS Office applications and install each one by one. This function is designed to keep track of
this queued process to make sure it finishes and check if there are no more available updates. If there are no more updates being installed it will then run the checkForUpdates function
which will then close out the script with the exitProgram function.

exitProgram - This function is solely written to isolate the final exit 0 call so that the script can end and return a succesful run to the JSS server.

COMMENT

## START OF SCRIPT

## VARIABLES PATHS
osvers=$(sw_vers -productVersion | awk -F. '{print $2}')
operatingsystemversion=$(sw_vers -productVersion)
updater4Direct="/.temp/office/mou4installer.pkg"
msbinary="msupdate"
loggedInUser=$(stat -f%Su /dev/console)

## VARIABLE COUNTERS
installcounter=0
forcecounter=0
wordplist=0
excelplist=0
outlookplist=0
powerpointplist=0
onenoteplist=0
plistTotal=0

## VARIABLE BOOLEANS
productVersionGood=false
wordinstalled=false
excelinstalled=false
msOfficeinstall=false

binaryexists=false
checkadd=false
forceadd=false
binaryinstaller=false
updatesavailable=false


## FUNCTIONS

mainProgram() {

    checkProductVersion

}

checkProductVersion() {

    echo "Running the checkProductVersion Function..."
    echo "We will now check the OS Version of this computer"
    echo "The operating system version is "$operatingsystemversion""
    if [[ ${osvers} -lt 10 ]]; then
        echo "Microsoft Office 2016 cannot be installed on this computer."
        osascript -e 'display notification "Microsoft Office cannot be installed onto this computer" with title "Cannot install MS Office."'
        echo "Closing the script now with exit 1"
        exit 1
    elif [[ ${osvers} -gt 10 || ${osvers} -eq 10 ]]; then
        echo "Microsoft Office can be installed to this computer."
        productVersionGood=true
        checkOfficeinstall
    fi

}

checkOfficeinstall() {

    echo "Running the checkOfficeinstall Function..."
    echo "The product version is higher than Os 10.10 is $productVersionGood"
    if [[ $productVersionGood == true ]]; then
        echo "We will now check if Microsoft Office is already installed."

        if [[ -d /Applications/Microsoft Word.app ]]; then
            echo "Word exists."
            wordinstalled=true
        else
            echo "Word deosn't exist."
        fi

        if [[ -d /Applications/Microsoft Excel.app ]]; then
            echo "Excel exists."
            excelinstalled=true
        else
            echo "Excel doesn't exist."
        fi

        if [[ $wordinstalled == true && $excelinstalled == true ]]; then
            echo "MS Office is installed"
            msOfficeinstall=true
            checkMSBinary
        else
            echo "MS Office is not installed"
            echo "Need to install MS Office"
            osascript -e 'display notification "Please contact Mac IT for help." with title "Microsoft Office is not installed."'
            exit 1
        fi
    else
        echo "We will now cancel the script due to the OS Version not being valid for install."
        exit 1
    fi

}

checkMSBinary() {

    echo "Running the checkmsbinary Function..."
    echo "Microsoft Office is installed on the computer is $msOfficeinstall"

    if [[ $msOfficeinstall == true ]]; then
        echo "We will check if the MS Office Update Binary is installed."

        if [[ -f /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app/Contents/MacOS/msupdate ]]; then
            echo "The binary is installed, we can run updates."
            binaryexists=true
            checkForceAdd
        else
            echo "The binary does not exist, we need to install it."
            checkbinaryinstaller
        fi

    else
        echo "We cannot run updates because office is not installed."
        exit 1
    fi

}

checkForceAdd() {

    echo "Running the checkforceadd function to see if the plist for updating is set correctly.."
    echo "The binary on the computer exists is $binaryexists."

    if [[ $binaryexists == true ]]; then
        echo "We will check if the plist is configured correctly."

        if grep 'Word' /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist; then
            wordplist=1
        fi

        if grep 'Excel' /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist; then
            excelplist=1
        fi

        if grep 'PowerPoint' /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist; then
            powerpointplist=1
        fi

        if grep 'OneNote' /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist; then
            onenoteplist=1
        fi

        if grep 'Outlook' /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist; then
            outlookplist=1
        fi

        let plistTotal=wordplist+excelplist+powerpointplist+onenoteplist+outlookplist
        echo "$plistTotal"

        if [[ $plistTotal == 5 ]]; then
            echo "The correct plist is running on the computer."
            echo "We will now check for updates."
            forceadd=true
            checkForUpdates
        else
            echo "The plist is not on the computer."
            echo "We will go ahead and install the Plist."
            checkadd=true
            forceAddinstall
        fi

    else
        echo "The binary doesn't exist. We will now close the script."
        exit 1
    fi

}

forceAddinstall() {

    echo "Running the forceaddapplications function..."
    echo "The check add exists in $checkadd"

    if [[ $checkadd == true ]]; then
        echo "We are going to make sure the binary can see the updates."

        cp /.temp/office/com.microsoft.autoupdate2.plist /Users/$loggedInUser/Library/Preferences

        chown $loggedInUser /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist

        if [[ -f /Users/$loggedInUser/Library/Preferences/com.microsoft.autoupdate2.plist ]]; then
            echo "The plist file is installed."
            forceadd=true

            let forcecounter=forcecounter+1

            if [[ forcecounter -eq 3 ]]; then
                echo "Closing the script due to the force counter."
                exit 1
            fi

            checkForceAdd

        else
            echo "The plist file was not installed."
            exit 1
        fi

        echo "Finished force adding applications to catch updates."
    fi

}

checkbinaryinstaller() {

    echo "Running the checkbinaryinstaller function..."
    echo "The binaryexists is $binaryexists."

    if [[ $binaryexists == false ]]; then

        if [[ -a $updater4Direct ]]; then
            echo "The binary installer is on the computer."
            binaryinstaller=true
            installMSBinary
        else
            echo "The installer for the binary is not on the computer. Please contact Mac IT."
            osascript -e 'display notification "Please contact Mac IT for help." with title "Packages were not installed."'
            exit 1
        fi

    else
        echo "No need to check if the binary package. The binary already exists."
    fi

}

installMSBinary() {

    echo "Running the installmsbinaryinstall Function..."
    echo "The binary on the computer exists is $binaryexists"

    if [[ $binaryinstaller == true ]]; then
        installer -pkg "$updater4Direct" -target /
        echo "The binary has been installed."
        checkMSBinary
    else 
        echo "No need to install the binary package."
    fi

}

checkForUpdates() {

    echo "Running the checkforupdates Function..."
    echo "The binary on the computer exists is $binaryexists"

    if [[ $forceadd == true ]]; then
        echo "We are going to check for Updates"

        if /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app/Contents/MacOS/msupdate --list | grep "No updates available"; then
            echo "${binaryruncheck}"
            echo "There are no updates available."
            osascript -e 'display notification "There are no updates available." with title "MS Office is up to date!"'
            echo "This script will now close."
            exitProgram
        else
            echo "There are updates available"
            osascript -e 'display notification "There are updates available for your Microsoft Office" with title "Updates Available!"'
            updatesavailable=true
            installUpdates
        fi

    fi

}

installUpdates() {

    echo "Running the installupdates Function..."
    echo "Updates are available is $updatesavailable"

        if [[ $updatesavailable == true ]]; then

            echo "We are going to install the updates available for MS Office 2016."

            /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app/Contents/MacOS/msupdate --install


            while /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app/Contents/MacOS/msupdate --list | grep "Updates available:"; do
                echo "There are updates currently queued up and being installed."
                sleep 300
                let installcounter=installcounter+1
                echo "$installcounter"

                if /Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app/Contents/MacOS/msupdate --list | grep "No updates available"; then

                    echo "There are no more updates available. We are breaking the while loop."
                    break

                fi

                if [[ installacounter -gt 5 ]]; then

                    echo "Please check if updates were installed. We are now closing the script."
                    exit 1
                fi

                osascript -e 'display notification "Updates are still being installed onto the computer for Microsoft Office." with title "Please wait as updates install..."'

            done

            checkForUpdates
        fi

}

exitProgram() {

    echo "Thank you for keeping Microsoft Office up to date! We will now exit the script."
    osascript -e 'display notification "There are no updates available. Thank for your keeping Microsoft Office up to date." with title "MS Office is up to date!"'
    exit 0

}


## MAIN CODE RUN

mainProgram


exit 0

## END OF SCRIPT

Any input would be greatly appreciated. I help administer about 150+ macs with JAMF and this script would be a game changer. Apologies for the length of the post and script! haha if you want to rewrite please feel free. Thanks!

2 REPLIES 2

noahdowd
Contributor

This might seem nitpicky or out of the scope of the intent of the thread, but my first assessment is that while it's great to get in the habit of using functions to be more object-oriented in the way you approach coding, you're kind of just using them as GOTOs in this script. It might be a little more readable to do something like this (this is barely psuedo-code btw):

func checkOS() {
osVer=$(get the version of the OS)
if [ $osVer -ge $osVerGood ]; then
   return 1
else
  return 0
fi
}

func checkExcel() {
isExcelInstalled=$(well is it??)
if [ $isExcelInstalled == true ]; then
   return 1
else
   return 0
fi
}

func willExit() {
   exitCode=$1
   exitMessage="$2"
   echo "$exitMessage"
   log "Exited with code $exitCode: $exitMessage" 
   exit $exitCode
}

### You could even eliminate the variables and just check the returns if you want to get really frugal with your memory.
isOSRight=$(checkOS)
if [ isOSRight == 0 ]; then
   willExit 1 "Wrong OS."
fi

isExcelRight=$(checkExcel)
if [ isExcelRight == 0 ]; then
   willExit 1 "No Excel found."
fi

willExit 0 "Success!"

et c.
I'm sure there's a metaphor out there for thinking about this but I can't really think of any good ones. Think of functions as rungs on a ladder instead of links in a chain? Squish like grape? I don't know.

Chrisdahfur
New Contributor II

@noahdowd Hey, I appreciate your feedback tbh. I am still getting around the ropes for cleaner code. I will rewrite this one to make it a lot cleaner.