-
Notifications
You must be signed in to change notification settings - Fork 79
/
deploy_wsus_offline_updates.ps1
287 lines (230 loc) · 14 KB
/
deploy_wsus_offline_updates.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<#
Purpose: Pulls down updates using WSUS Offline ( http://download.wsusoffline.net/ ), unpacks them, uploads them
to the deployment server, and then updates the PDQ job file.
Requirements: 1. Write-access to the repo (network share) on the deployment server
2. 7z.exe
3. RunAll.cmd update script, generated by WSUS Offline. This tells WSUS offline what to grab
4. Exported XML job file from PDQ Deploy to update (e.g. "Microsoft Offline Updates.xml")
5. This script assumes WSUS Offline pulls down updates for ALL products at the same time. That is, when
it goes to update the PDQ job file, it assumes all packages were refreshed at the same time.
Author: reddit.com/user/vocatus ( [email protected] ) // PGP key: 0x07d1490f82a211a2
History: 1.5.3 * Minor cleanup
1.5.2 * Convert standalone variables to parameter block to support CLI use
1.5.1 - Remove requirement and directory structure for patch date. This was kind of pointless since we always want the latest patch set anyway
1.5.0 * Convert stage 2: directory staging section to an array loop instead of static sequential commands
1.4.0 - Remove Windows XP as an option due to Microsoft End-of-Life
1.3.1 + Add line to echo what date we detected the last update occured on
1.3.0 + Add functionality to update package deployment date in the PDQ job file
+ Add section to check for existence of required files (PDQ xml job file, 7z.exe,
and RunAll.cmd) before executing
1.2.0 / Change all instances of "-f" to "-f"
1.1.0 + Add code to Step 2 to pre-clear the area on the server before uploading (remove old updates)
+ Add Step 5: Re-calculate PDQ hashes
1.0.0 + Initial write
Usage: Step 0: Run WSUS Offline at least once, choose your options, and generate the update script
Step 1: Configure the variables below to reflect your set up (log location, repo location, etc)
Step 2: Run this script
Step 3: Re-import the XML job file in PDQ
#>
#############
# VARIABLES # -- Set these to your desired values or supply from the shell ---------------------- #
#############
# Rules for variables:
# * Quotes are required (e.g.: "c:\directory\path" )
# * NO trailing slashes on paths! (bad: "c:\directory\" )
# * Spaces are okay (okay: "c:\my folder\with spaces" )
# * Network paths are okay (okay: "\\server\share name" )
# ( "\\172.16.1.5\share name" )
param (
# Logging information
[string] $LOGPATH = $env:systemdrive + "\Logs",
#[string] $LOGFILE = $env:computername + "_wsus_offline_update_deployer.log",
[string] $LOGFILE = "deploy_wsus_offline_updates.log",
# WSUS Offline root directory (where UpdateGenerator.exe is)
[string] $WSUS_DIRECTORY = "p:\scripts\wsusoffline", # e.g. "c:\wsusoffline"
# Path to the repo root on the server
[string] $REPO_ROOT = "\\thebrain\Downloads\seeders\btsync\pdq_pack_microsoft_offline_updates\repository", # e.g. "\\server\repo"
# Path to the sub-directory containing updates
[string] $REPO_UPDATES = $REPO_ROOT + "\microsoft_offline_updates", # e.g. "\\server\repo\microsoft_offline_updates"
# Full path to the export of the PDQ job file for the updates. # e.g. "\\server\repo\pdq_job_files_backup\Microsoft Offline Updates.xml"
[string] $PDQ_JOB_FILE = "\\thebrain\Downloads\seeders\btsync\pdq_pack_microsoft_offline_updates\job files\Microsoft Offline Updates.xml",
# Path to 7z.exe
[string] $SEVENZIP = "C:\Program Files\7-Zip\7z.exe",
# Cleanup? (Delete source ISOs and the files unpacked from them). If set to anything but "yes" then cleanup is skipped.
[string] $CLEANUP = "yes"
)
# List of products to act against (array). Don't edit this unless you have a pressing need to
# Basically each of these folders will be renamed according to the new name in the PRODUCTS_NEW_NAMES array
$PRODUCTS_OLD_NAMES = "wsusoffline-ofc-enu","wsusoffline-w61-x64","wsusoffline-w63-x64","wsusoffline-w100-x64"
$PRODUCTS_NEW_NAMES = "office_2k7-2k16","windows_7_and_server_2008-R2","windows_8.1_and_server_2012-R2","windows_10_and_server_2016"
# ----------------------------- Don't edit anything below this line ----------------------------- #
########
# Prep #
########
$SCRIPT_VERSION = "1.5.3"
$SCRIPT_UPDATED = "2015-11-18"
$CUR_DATE = get-date -f "yyyy-MM-dd"
# Get the date of the last update so we find-replace the date in the PDQ job file later
pushd $REPO_UPDATES; pushd $PRODUCTS_NEW_NAMES[2]
#$PREVIOUS_UPDATE = ls -d | ForEach-Object{$_.Name} | sort -Descending | select -f 1 # This is the old method based on reading the directory name into a variable
$PREVIOUS_UPDATE = gc ".\builddate.txt"
popd; popd
#################
# SANITY CHECKS #
#################
# Test for existence of 7-Zip
if (!$SEVENZIP) {
write-host ""
write-host -n " ["; write-host -n "ERROR" -f red; write-host -n "]";
write-host " Couldn't find 7z.exe at the location specified ( $SEVENZIP )"
write-host " Edit this script and change the `$SEVENZIP variable to point to 7z's location"
}
# Test for existence of RunAll.cmd, the WSUS update script
if (!(test-path $WSUS_DIRECTORY\cmd\custom\RunAll.cmd)) {
write-host ""
write-host -n " ["; write-host -n "ERROR" -f red; write-host -n "]";
write-host " Couldn't find RunAll.cmd, the WSUS Offline update script."
write-host " If you haven't already done so, you need to run WSUS Offline at least"
write-host " once and generate a RunAll.cmd based on the settings you choose."
}
# Test for existence of the PDQ Deploy job file
if (!$PDQ_JOB_FILE) {
write-host -n " ["; write-host -n "ERROR" -f red; write-host -n "]";
write-host " Couldn't find the PDQ job file at the location specified:"
write-host ""
write-host " $PDQ_JOB_FILE"
write-host ""
write-host " If you haven't already done so, you need to launch PDQ Deploy and export"
write-host " this job file, then edit this script and change the `$PDQ_JOB_FILE variable"
write-host " to point to the location."
write-host ""
}
#############
# EXECUTION #
#############
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " WSUS Offline Package Deployer" -f green
write-host " Version: $SCRIPT_VERSION ($SCRIPT_UPDATED)" -f darkgray
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Last pulldown detected on $PREVIOUS_UPDATE" -f green
# logging
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " WSUS Offline Package Deployer" >> $LOGPATH\$LOGFILE
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Version: $SCRIPT_VERSION ($SCRIPT_UPDATED)" >> $LOGPATH\$LOGFILE
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Last pulldown detected on $PREVIOUS_UPDATE" >> $LOGPATH\$LOGFILE
##########
# Step 1 # -- Pull down the updates
##########
# Declare and log what we're doing. This is overly complex but basically just so we can have fancy colors
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Starting deployment. Invoking update script..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Starting deployment. Invoking update script..." >> $LOGPATH\$LOGFILE
# Run the WSUS update script
cd $WSUS_DIRECTORY\cmd\custom\
.\RunAll.cmd
# Done
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done." >> $LOGPATH\$LOGFILE
##########
# Step 2 # -- Unpack the ISOs and create the directory structure for upload
##########
cd $WSUS_DIRECTORY\iso
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Unpacking ISOs..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Unpacking ISOs..." >> $LOGPATH\$LOGFILE
# Unpack the ISOs
& $SEVENZIP x $WSUS_DIRECTORY\iso\*.iso -o* -y
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done." >> $LOGPATH\$LOGFILE
# Create directory structure for upload
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Staging directory structure for upload..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Staging directory structure for upload..." >> $LOGPATH\$LOGFILE
# Pre-stage the directories since "move-item" is stupid and has no option to create them on the fly
foreach ($i in $PRODUCTS_NEW_NAMES) {
mkdir .\$i\ -force | out-null
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Staged $i." -f darkgray
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Staged $i." >> $LOGPATH\$LOGFILE
}
# Move everything into the new directories
for ($i=0; $i -lt $PRODUCTS_OLD_NAMES.Count; $i++) {
mv .\$($PRODUCTS_OLD_NAMES[$i])\* .\$($PRODUCTS_NEW_NAMES[$i])
}
# Delete the old directories (local)
foreach ($i in $PRODUCTS_OLD_NAMES) {
remove-item $i -force -recurse
}
# Done
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done." >> $LOGPATH\$LOGFILE
# Delete the old directories (server-side)
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Clearing upload target area..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Clearing upload target area..." >> $LOGPATH\$LOGFILE
foreach ($i in $PRODUCTS_NEW_NAMES) {
remove-item $REPO_UPDATES\$i -force -recurse | out-null
}
# Done
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done." >> $LOGPATH\$LOGFILE
##########
# Step 3 # -- Upload the updates to their respective directories on the server
##########
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Uploading files to repository at $REPO_ROOT..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Uploading files to repository at $REPO_ROOT..." >> $LOGPATH\$LOGFILE
# This loop iterates through the list of directory names and uploads them using Robocopy
foreach ($i in $PRODUCTS_NEW_NAMES) {
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Uploading $i..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Uploading $i..." >> $LOGPATH\$LOGFILE
robocopy .\$i $REPO_UPDATES\$i /R:3 /W:5 /mir /NP /NJH /NJS >> $LOGPATH\$LOGFILE
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
}
# Done with all uploads
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " All uploads done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " All uploads done." >> $LOGPATH\$LOGFILE
###########
# Step 3a # -- cleanup
###########
IF ($CLEANUP -eq "yes") {
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " `$CLEANUP variable set to yes. Performing cleanup." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " `$CLEANUP variable set to yes. Performing cleanup." >> $LOGPATH\$LOGFILE
del * -force -recurse
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done. Deleted everything in $WSUS_DIRECTORY\iso" -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done. Deleted everything in $WSUS_DIRECTORY\iso" >> $LOGPATH\$LOGFILE
} ELSE {
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " `$CLEANUP variable not set to yes. Skipping cleanup." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " `$CLEANUP variable not set to yes. Skipping cleanup." >> $LOGPATH\$LOGFILE
}
##########
# Step 4 # -- Update PDQ xml job file
##########
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Updating PDQ job file to current date..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Updating PDQ job file to current date..." >> $LOGPATH\$LOGFILE
# Do a find-replace on the date string in the PDQ job file
(gc "$PDQ_JOB_FILE") | Foreach-Object {$_ -replace ($PREVIOUS_UPDATE), "$CUR_DATE"} | sc $PDQ_JOB_FILE -force
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done." >> $LOGPATH\$LOGFILE
##########
# Step 5 # -- Recalculate hashes
##########
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Recalculating hashes, please wait..." -f green
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Recalculating hashes, please wait..." >> $LOGPATH\$LOGFILE
cd $REPO_ROOT
cd ..
cd "integrity verification"
del checksum* -force -recurse | out-null
del $env:temp\checksum* -force -recurse | out-null
cd ..
hashdeep -c md5 -r -l * > $env:temp\checksums.txt
mv $env:temp\checksums.txt "integrity verification\checksums.txt"
# Done
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " Done." -f darkgreen
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " Done." >> $LOGPATH\$LOGFILE
############
# Finished #
############
write-host $CUR_DATE (get-date -f hh:mm:ss) -n -f darkgray; write-host " DONE. Make sure to re-import the updated PDQ job file." -f green
write-host " Working directory: $WSUS_DIRECTORY" -f darkgray
write-host " Target repo: $REPO_ROOT" -f darkgray
write-host " PDQ job file: $PDQ_JOB_FILE" -f darkgray
write-host " Log file: $LOGPATH\$LOGFILE" -f darkgray
write-host " Cleanup performed?: $CLEANUP" -f darkgray
"$CUR_DATE "+ $(get-date -f hh:mm:ss) + " DONE. Make sure to re-import the updated PDQ job file." >> $LOGPATH\$LOGFILE
echo " Working directory: $WSUS_DIRECTORY" >> $LOGPATH\$LOGFILE
echo " Target repo: $REPO_ROOT" >> $LOGPATH\$LOGFILE
echo " PDQ job file: $PDQ_JOB_FILE" >> $LOGPATH\$LOGFILE
echo " Log file: $LOGPATH\$LOGFILE" >> $LOGPATH\$LOGFILE
echo " Cleanup performed?: $CLEANUP" >> $LOGPATH\$LOGFILE