├── README.md └── InstallerCache /README.md: -------------------------------------------------------------------------------- 1 | # InstallerCache 2 | Microsoft Office Installer Cache 3 | 4 | Purpose: Downloads Office installer packages from the Office CDN to a shared folder
5 | Usage: `InstallerCache --CachePath: [--CheckInterval:] [--PurgeOldInstallers]`
6 | Example: `InstallerCache --CachePath:/Volumes/web --CheckInterval:60`
7 | 8 | The shared folder path needs to be exposed as a web folder, such as http://webserver/folder
9 | Clients can install packages by navigating to a URL, such as http://webserver/folder/InstallWord
10 | Web install points created by the tool:
11 | - /InstallMAU 12 | - /InstallOffice 13 | - /InstallWord 14 | - /InstallExcel 15 | - /InstallPowerPoint 16 | - /InstallOutlook 17 | - /InstallOneNote 18 | 19 | - /InstallOffice2016 20 | - /InstallWord2016 21 | - /InstallExcel2016 22 | - /InstallPowerPoint2016 23 | - /InstallOutlook2016 24 | - /InstallOneNote2016 25 | 26 | - /InstallOneDrive 27 | - /InstallSkypeBusiness 28 | - /InstallCompanyPortal 29 | - /InstallRemoteDesktop 30 | -------------------------------------------------------------------------------- /InstallerCache: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -x 3 | 4 | TOOL_NAME="Microsoft Office Installer Cache" 5 | TOOL_VERSION="1.1" 6 | 7 | ## Copyright (c) 2018 Microsoft Corp. All rights reserved. 8 | ## Scripts are not supported under any Microsoft standard support program or service. The scripts are provided AS IS without warranty of any kind. 9 | ## Microsoft disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a 10 | ## particular purpose. The entire risk arising out of the use or performance of the scripts and documentation remains with you. In no event shall 11 | ## Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever 12 | ## (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary 13 | ## loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility 14 | ## of such damages. 15 | ## Feedback: pbowden@microsoft.com 16 | 17 | # External Constants - IT admins should feel free to customize this section to use different web folder paths 18 | WEBFOLDER_MAU="InstallMAU" 19 | WEBFOLDER_OFFICE="InstallOffice" 20 | WEBFOLDER_WORD="InstallWord" 21 | WEBFOLDER_EXCEL="InstallExcel" 22 | WEBFOLDER_POWERPOINT="InstallPowerPoint" 23 | WEBFOLDER_OUTLOOK="InstallOutlook" 24 | WEBFOLDER_ONENOTE="InstallOneNote" 25 | WEBFOLDER_OFFICE2016="InstallOffice2016" 26 | WEBFOLDER_WORD2016="InstallWord2016" 27 | WEBFOLDER_EXCEL2016="InstallExcel2016" 28 | WEBFOLDER_POWERPOINT2016="InstallPowerPoint2016" 29 | WEBFOLDER_OUTLOOK2016="InstallOutlook2016" 30 | WEBFOLDER_ONENOTE2016="InstallOneNote2016" 31 | WEBFOLDER_ONEDRIVE="InstallOneDrive" 32 | WEBFOLDER_SKYPEBUSINESS="InstallSkypeBusiness" 33 | WEBFOLDER_COMPANYPORTAL="InstallCompanyPortal" 34 | WEBFOLDER_REMOTEDESKTOP="InstallRemoteDesktop" 35 | WEBFOLDER_FILE="default.html" 36 | 37 | # Internal Constants - Should not be customized 38 | INSTALLER_MAU="https://go.microsoft.com/fwlink/?linkid=830196" 39 | INSTALLER_OFFICE="https://go.microsoft.com/fwlink/?linkid=525133" 40 | INSTALLER_WORD="https://go.microsoft.com/fwlink/?linkid=525134" 41 | INSTALLER_EXCEL="https://go.microsoft.com/fwlink/?linkid=525135" 42 | INSTALLER_POWERPOINT="https://go.microsoft.com/fwlink/?linkid=525136" 43 | INSTALLER_OUTLOOK="https://go.microsoft.com/fwlink/?linkid=525137" 44 | INSTALLER_ONENOTE="https://go.microsoft.com/fwlink/?linkid=820886" 45 | 46 | INSTALLER_OFFICE2016="https://go.microsoft.com/fwlink/?linkid=871743" 47 | INSTALLER_WORD2016="https://go.microsoft.com/fwlink/?linkid=871748" 48 | INSTALLER_EXCEL2016="https://go.microsoft.com/fwlink/?linkid=871750" 49 | INSTALLER_POWERPOINT2016="https://go.microsoft.com/fwlink/?linkid=871751" 50 | INSTALLER_OUTLOOK2016="https://go.microsoft.com/fwlink/?linkid=871753" 51 | INSTALLER_ONENOTE2016="https://go.microsoft.com/fwlink/?linkid=871755" 52 | 53 | INSTALLER_ONEDRIVE="https://go.microsoft.com/fwlink/?linkid=823060" 54 | INSTALLER_SKYPEBUSINESS="https://go.microsoft.com/fwlink/?linkid=831677" 55 | INSTALLER_COMPANYPORTAL="https://go.microsoft.com/fwlink/?linkid=869655" 56 | INSTALLER_REMOTEDESKTOP="https://go.microsoft.com/fwlink/?linkid=868963" 57 | 58 | # Platform detection - allows this script to run on macOS and *nix variants 59 | PLATFORM=$(uname -s) 60 | 61 | # Shows tool usage and parameters 62 | ShowUsage() { 63 | echo $TOOL_NAME - $TOOL_VERSION 64 | echo "Purpose: Downloads Office installer packages from the Office CDN to a shared folder" 65 | echo "Usage: InstallerCache --CachePath: [--CheckInterval:] [--PurgeOldInstallers]" 66 | echo "Example: InstallerCache --CachePath:/Volumes/web --CheckInterval:60" 67 | echo " The needs to be exposed as a web folder, such as http://webserver/folder" 68 | echo " Clients can install packages by navigating to a URL, such as http://webserver/folder/InstallWord" 69 | echo 70 | exit 0 71 | } 72 | 73 | # Builds an array of all the installers we want to download - IT admins should feel free to remove items they don't care about 74 | BuildInstallerArray() { 75 | APP=() 76 | APP+=("$INSTALLER_MAU") 77 | APP+=("$INSTALLER_OFFICE") 78 | APP+=("$INSTALLER_WORD") 79 | APP+=("$INSTALLER_EXCEL") 80 | APP+=("$INSTALLER_POWERPOINT") 81 | APP+=("$INSTALLER_OUTLOOK") 82 | APP+=("$INSTALLER_ONENOTE") 83 | APP+=("$INSTALLER_ONEDRIVE") 84 | APP+=("$INSTALLER_OFFICE2016") 85 | APP+=("$INSTALLER_WORD2016") 86 | APP+=("$INSTALLER_EXCEL2016") 87 | APP+=("$INSTALLER_POWERPOINT2016") 88 | APP+=("$INSTALLER_OUTLOOK2016") 89 | APP+=("$INSTALLER_ONENOTE2016") 90 | APP+=("$INSTALLER_SKYPEBUSINESS") 91 | APP+=("$INSTALLER_COMPANYPORTAL") 92 | APP+=("$INSTALLER_REMOTEDESKTOP") 93 | } 94 | 95 | # Performs a reverse look-up from ID to friendly name 96 | GetAppNameFromID() { 97 | case "$1" in 98 | $INSTALLER_MAU) APPNAME="Microsoft AutoUpdate";; 99 | $INSTALLER_OFFICE) APPNAME="Microsoft Office for Mac";; 100 | $INSTALLER_WORD) APPNAME="Microsoft Word for Mac";; 101 | $INSTALLER_EXCEL) APPNAME="Microsoft Excel for Mac";; 102 | $INSTALLER_POWERPOINT) APPNAME="Microsoft PowerPoint for Mac";; 103 | $INSTALLER_OUTLOOK) APPNAME="Microsoft Outlook for Mac";; 104 | $INSTALLER_ONENOTE) APPNAME="Microsoft OneNote for Mac";; 105 | $INSTALLER_OFFICE2016) APPNAME="Microsoft Office 2016 for Mac";; 106 | $INSTALLER_WORD2016) APPNAME="Microsoft Word 2016 for Mac";; 107 | $INSTALLER_EXCEL2016) APPNAME="Microsoft Excel 2016 for Mac";; 108 | $INSTALLER_POWERPOINT2016) APPNAME="Microsoft PowerPoint 2016 for Mac";; 109 | $INSTALLER_OUTLOOK2016) APPNAME="Microsoft Outlook 2016 for Mac";; 110 | $INSTALLER_ONENOTE2016) APPNAME="Microsoft OneNote 2016 for Mac";; 111 | $INSTALLER_ONEDRIVE) APPNAME="Microsoft OneDrive for Mac";; 112 | $INSTALLER_SKYPEBUSINESS) APPNAME="Microsoft Skype for Business for Mac";; 113 | $INSTALLER_COMPANYPORTAL) APPNAME="Microsoft Intune Company Portal for Mac";; 114 | $INSTALLER_REMOTEDESKTOP) APPNAME="Microsoft Remote Desktop v10 for Mac";; 115 | esac 116 | echo "$APPNAME" 117 | } 118 | 119 | # Performs a reverse look-up from ID to web folder path 120 | GetWebFolderFromID() { 121 | case "$1" in 122 | $INSTALLER_MAU) FOLDER="$WEBFOLDER_MAU";; 123 | $INSTALLER_OFFICE) FOLDER="$WEBFOLDER_OFFICE";; 124 | $INSTALLER_WORD) FOLDER="$WEBFOLDER_WORD";; 125 | $INSTALLER_EXCEL) FOLDER="$WEBFOLDER_EXCEL";; 126 | $INSTALLER_POWERPOINT) FOLDER="$WEBFOLDER_POWERPOINT";; 127 | $INSTALLER_OUTLOOK) FOLDER="$WEBFOLDER_OUTLOOK";; 128 | $INSTALLER_ONENOTE) FOLDER="$WEBFOLDER_ONENOTE";; 129 | $INSTALLER_OFFICE2016) FOLDER="$WEBFOLDER_OFFICE2016";; 130 | $INSTALLER_WORD2016) FOLDER="$WEBFOLDER_WORD2016";; 131 | $INSTALLER_EXCEL2016) FOLDER="$WEBFOLDER_EXCEL2016";; 132 | $INSTALLER_POWERPOINT2016) FOLDER="$WEBFOLDER_POWERPOINT2016";; 133 | $INSTALLER_OUTLOOK2016) FOLDER="$WEBFOLDER_OUTLOOK2016";; 134 | $INSTALLER_ONENOTE2016) FOLDER="$WEBFOLDER_ONENOTE2016";; 135 | $INSTALLER_ONEDRIVE) FOLDER="$WEBFOLDER_ONEDRIVE";; 136 | $INSTALLER_SKYPEBUSINESS) FOLDER="$WEBFOLDER_SKYPEBUSINESS";; 137 | $INSTALLER_COMPANYPORTAL) FOLDER="$WEBFOLDER_COMPANYPORTAL";; 138 | $INSTALLER_REMOTEDESKTOP) FOLDER="$WEBFOLDER_REMOTEDESKTOP";; 139 | esac 140 | echo "$CACHEPATH/$FOLDER" 141 | } 142 | 143 | # Resolves an FWLink to absolute download URL 144 | ResolveFWLink() { 145 | URL="$1" 146 | local LOCATION=$(curl --head -s $URL | awk -v RS='\r' '/Location: / {print $2}') 147 | if [ "$LOCATION" == "" ]; then 148 | echo "$URL" 149 | else 150 | echo "$LOCATION" 151 | fi 152 | } 153 | 154 | # Gets the size of a file based on its header, then strips non-numeric characters 155 | GetDownloadSize() { 156 | URL="$1" 157 | local CONTENTHTTPLENGTH=$(curl --head -s $URL | awk '/Content-Length/' | cut -d ' ' -f2) 158 | CONTENTLENGTH=$(echo ${CONTENTHTTPLENGTH//[!0-9]/}) 159 | echo $CONTENTLENGTH 160 | } 161 | 162 | # Gets the size of a file from the local disk 163 | GetLocalSize() { 164 | local FILENAME="$1" 165 | local FOLDER="$2" 166 | # The stat command works differently between macOS and other Linux platforms like RHEL 167 | if [ "$PLATFORM" == "Darwin" ]; then 168 | local FILELENGTH=($(cd "$FOLDER" && stat -f%z "$FILENAME" 2>/dev/null)) 169 | else 170 | local FILELENGTH=($(cd "$FOLDER" && stat -c%s "$FILENAME" 2>/dev/null)) 171 | fi 172 | echo $FILELENGTH 173 | } 174 | 175 | # Downloads the specified installer package 176 | DownloadPackage() { 177 | local URL="$1" 178 | local APPLICATION="$2" 179 | local SIZE="$3" 180 | local FOLDER="$4" 181 | local PACKAGE=$(basename "$1") 182 | echo "=================================================================================================" 183 | echo Application: "$APPLICATION" 184 | echo Package: "$PACKAGE" 185 | echo Size: "$SIZE" MB 186 | echo URL: "$URL" 187 | (cd "$FOLDER" && curl --progress-bar --remote-name --location $URL) 188 | } 189 | 190 | # Create a client URL redirector 191 | CreateRedirector() { 192 | local FILE="$1" 193 | local PKG="$2" 194 | local FOLDER="$3" 195 | if [ -f "$FOLDER/$FILE" ]; then 196 | (cd "$FOLDER" && rm -f "$FILE") 197 | fi 198 | (cd "$FOLDER" && touch "$FILE") 199 | echo "" >> "$FOLDER/$FILE" 200 | echo "" >> "$FOLDER/$FILE" 201 | echo "" >> "$FOLDER/$FILE" 202 | echo "" >> "$FOLDER/$FILE" 203 | } 204 | 205 | # Removes older versions of installer packages if they exist 206 | PurgeOldInstallers() { 207 | local LATESTPKG="$1" 208 | local FOLDER="$2" 209 | for package in $FOLDER/*.pkg; do 210 | local FILE=$(basename "$package") 211 | if [ "$FILE" != "$LATESTPKG" ]; then 212 | echo "Removing old installer $FILE" 213 | rm "$FOLDER/$FILE" 214 | fi 215 | done 216 | } 217 | 218 | # Evaluate command-line arguments 219 | if [[ $# = 0 ]]; then 220 | ShowUsage 221 | else 222 | for KEY in "$@" 223 | do 224 | case $KEY in 225 | --Help|-h|--help) 226 | ShowUsage 227 | shift # past argument 228 | ;; 229 | --CachePath:*|-c:*|--cachepath:*) 230 | CACHEPATH=${KEY#*:} 231 | shift # past argument 232 | ;; 233 | --CheckInterval:*|-i:*|--checkinterval:*) 234 | CHECKINTERVAL=${KEY#*:} 235 | shift # past argument 236 | ;; 237 | --PurgeOldInstallers|-p|--purgeoldinstallers) 238 | PURGEOLD=true 239 | shift # past argument 240 | ;; 241 | *) 242 | ShowUsage 243 | ;; 244 | esac 245 | shift # past argument or value 246 | done 247 | fi 248 | 249 | ## Main 250 | while : 251 | do 252 | # Build an array of packages to download 253 | BuildInstallerArray 254 | # Build an array of each package location and download those packages 255 | for a in "${APP[@]}" 256 | do 257 | # Get the friendly app name for display purposes 258 | APPNAME=$(GetAppNameFromID "$a") 259 | # Get the folder name on the local web server where the installer package should reside 260 | WEBFOLDERPATH=$(GetWebFolderFromID "$a") 261 | # Create the folder name on the local web server if it doesn't exist 262 | if [ ! -d "$WEBFOLDERPATH" ]; then 263 | mkdir -p "$WEBFOLDERPATH" 264 | fi 265 | # Resolve the FWLink to the actual download URL 266 | PACKAGEURL=$(ResolveFWLink "$a") 267 | # Get the installer filename 268 | PACKAGENAME=$(basename "$PACKAGEURL") 269 | # Get the size of the installer on the CDN 270 | PACKAGESIZECDN=$(GetDownloadSize "$PACKAGEURL") 271 | # Get the size of the installer on the local web server, if it exists 272 | PACKAGESIZELOCAL=$(GetLocalSize "$PACKAGENAME" "$WEBFOLDERPATH") 273 | # Convert the package size from bytes to megabytes 274 | PACKAGESIZECDNMEG=$(expr $PACKAGESIZECDN / 1024 / 1024) 275 | # Test whether we already have the installer downloaded 276 | if [ -f "$WEBFOLDERPATH/$PACKAGENAME" ]; then 277 | # Test whether the downloaded installer got interrupted when downloading 278 | if [ "$PACKAGESIZELOCAL" == "$PACKAGESIZECDN" ]; then 279 | # The installer is already downloaded and whole. We can stop here and move to the next package 280 | echo "Package $PACKAGENAME already exists in the cache ...skipping" 281 | continue 282 | else 283 | # The installer is present, but it's not whole, so lets remove it 284 | echo "Package $PACKAGENAME exists in the cache but is corrupt ...removing" 285 | (cd "$WEBFOLDERPATH" && rm -f "$PACKAGENAME") 286 | fi 287 | fi 288 | # Download the installer package 289 | DownloadPackage "$PACKAGEURL" "$APPNAME" "$PACKAGESIZECDNMEG" "$WEBFOLDERPATH" 290 | # Create a default.html file in the web folder so that browser requests auto-navigate to the correct package 291 | CreateRedirector "$WEBFOLDER_FILE" "$PACKAGENAME" "$WEBFOLDERPATH" 292 | # Remove older installers if desired 293 | if [ $PURGEOLD ]; then 294 | PurgeOldInstallers "$PACKAGENAME" "$WEBFOLDERPATH" 295 | fi 296 | done 297 | 298 | # If CheckInterval wasn't specified on the command-line, just run once 299 | if [ "$CHECKINTERVAL" == '' ]; then 300 | exit 0 301 | else 302 | # Otherwise, sleep for the specified number of minutes before checking again 303 | echo "Sleeping for $CHECKINTERVAL minutes..." 304 | CHECKINTERVALSECS=$(expr $CHECKINTERVAL \* 60) 305 | # Wait until the next check interval 306 | sleep "$CHECKINTERVALSECS" 307 | fi 308 | done 309 | 310 | exit 0 311 | --------------------------------------------------------------------------------