├── 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 |
--------------------------------------------------------------------------------