/
Upgrade-PowerShell.ps1
396 lines (369 loc) · 15.4 KB
/
Upgrade-PowerShell.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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# PSScriptInfo
# .VERSION 1.0
# .GUID 23743bae-7604-459d-82c5-a23d36b0820e
# .AUTHOR
# Jordan Borean <jborean93@gmail.com>
# .COPYRIGHT
# Jordan Borean 2017
# .TAGS
# PowerShell,Ansible
# .LICENSEURI https://github.com/jborean93/ansible-windows/blob/master/LICENSE
# .PROJECTURI https://github.com/jborean93/ansible-windows
# .RELEASENOTES
# Version 1.0: 2017-09-27
# Initial script created
# .DESCRIPTION
# The script will upgrade the powershell version to whatever is supplied as
# the 'version' on the host. The current versions can be set as the target
# 'version':
# - 3.0
# - 4.0
# - 5.1 (default if -Version not set)
#
# This script can be run on the following OS'
# Windows Server 2008 (with SP2) - only supported version 3.0
# Windows Server 2008 R2 (with SP1)
# Windows Server 2012
# Windows Server 2012 R2
# Windows Server 2016
#
# Windows 7 (with SP1)
# Windows 8.1
# Windows 10
#
# All OS' can be upgraded to 5.1 except for Windows Server 2008. If running
# on Powershell 1.0 then this script will first upgrade the version to 2.0
# before running the checks. This is because a lot of the upgrade paths need
# this version installed as a baseline. If the .NET Framework version
# installed is less than 4.5.2, it will be upgraded to 4.5.2 as this is
# supported on all hosts and is required for v5.0.
#
# As multiple packages can be installed in this process, multiple reboots may
# be required to continue with the install. If a reboot is required the
# script will detect if the 'username' and 'password' parameters have been
# supplied. If they have been supplied it will automatically reboot and login
# to continue the install process until it is all complete. If these
# parameters are not set then it will prompt the user for a reboot and
# require the user to log back in manually after the reboot before
# continuing.
#
# A log of this process is created in
# $env:SystemDrive\temp\upgrade_powershell.log which is usually C:\temp\. This
# log can used to see how the script faired after an automatic reboot.
#
# See https://github.com/jborean93/ansible-windows/tree/master/scripts for more
# details.
# .PARAMETER version
# [string] - The target powershell version to upgrade to. This can be;
# 3.0,
# 4.0, or
# 5.1 (default)
# Depending on the circumstances, the process to reach the target version
# may require multiple reboots.
# .PARAMETER username
# [string] - The username of a local admin user that will be automatically
# logged in after a reboot to continue the script install. The 'password'
# parameter is also required if this is set.
# .PARAMETER password
# [string] - The password for 'username', this is required if the 'username'
# parameter is also set.
# .PARAMETER Verbose
# [switch] - Whether to display Verbose logs on the console
# .EXAMPLE
# # upgrade from powershell 1.0 to 3.0 with automatic login and reboots
# Set-ExecutionPolicy Unrestricted -Force
# &.\Upgrade-PowerShell.ps1 -version 3.0 -username "Administrator" -password "Password" -Verbose
# .EXAMPLE
# # upgrade to 5.1 with defaults and manual login and reboots
# powershell.exe -ExecutionPolicy ByPass -File Upgrade-PowerShell.ps1
# .EXAMPLE
# # upgrade to powershell 4.0 with automatic login and reboots
# powershell.exe -ExecutionPolicy ByPass -File Upgrade-PowerShell.ps1 -version 4.0 -username "Administrator" -password "Password" -Verbose
Param(
[string]$version = "5.1",
[string]$username,
[string]$password,
[switch]$verbose = $false
)
$ErrorActionPreference = 'Stop'
if ($verbose) {
$VerbosePreference = "Continue"
}
$tmp_dir = $env:temp
if (-not (Test-Path -Path $tmp_dir)) {
New-Item -Path $tmp_dir -ItemType Directory > $null
}
Function Write-Log($message, $level="INFO") {
# Poor man's implementation of Log4Net
$date_stamp = Get-Date -Format s
$log_entry = "$date_stamp - $level - $message"
$log_file = "$tmp_dir\upgrade_powershell.log"
Write-Verbose -Message $log_entry
Add-Content -Path $log_file -Value $log_entry
}
Function Reboot-AndResume {
Write-Log -message "adding script to run on next logon"
$script_path = $script:MyInvocation.MyCommand.Path
$ps_path = "$env:SystemDrive\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$arguments = "-version $version"
if ($username -and $password) {
$arguments = "$arguments -username `"$username`" -password `"$password`""
}
if ($verbose) {
$arguments = "$arguments -Verbose"
}
$command = "$ps_path -ExecutionPolicy ByPass -File $script_path $arguments"
$reg_key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
$reg_property_name = "ps-upgrade"
Set-ItemProperty -Path $reg_key -Name $reg_property_name -Value $command
if ($username -and $password) {
$reg_winlogon_path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
Set-ItemProperty -Path $reg_winlogon_path -Name AutoAdminLogon -Value 1
Set-ItemProperty -Path $reg_winlogon_path -Name DefaultUserName -Value $username
Set-ItemProperty -Path $reg_winlogon_path -Name DefaultPassword -Value $password
Write-Log -message "rebooting server to continue powershell upgrade"
} else {
Write-Log -message "need to reboot server to continue powershell upgrade"
$reboot_confirmation = Read-Host -Prompt "need to reboot server to continue powershell upgrade, do you wish to proceed (y/n)"
if ($reboot_confirmation -ne "y") {
$error_msg = "please reboot server manually and login to continue upgrade process, the script will restart on the next login automatically"
Write-Log -message $error_msg -level "ERROR"
throw $error_msg
}
}
if (Get-Command -Name Restart-Computer -ErrorAction SilentlyContinue) {
Restart-Computer -Force
} else {
# PS v1 (Server 2008) doesn't have the cmdlet Restart-Computer, use el-traditional
shutdown /r /t 0
}
}
Function Run-Process($executable, $arguments) {
$process = New-Object -TypeName System.Diagnostics.Process
$psi = $process.StartInfo
$psi.FileName = $executable
$psi.Arguments = $arguments
Write-Log -message "starting new process '$executable $arguments'"
$process.Start() | Out-Null
$process.WaitForExit() | Out-Null
$exit_code = $process.ExitCode
Write-Log -message "process completed with exit code '$exit_code'"
return $exit_code
}
Function Download-File($url, $path) {
Write-Log -message "downloading url '$url' to '$path'"
$client = New-Object -TypeName System.Net.WebClient
$client.DownloadFile($url, $path)
}
Function Clear-AutoLogon {
$reg_winlogon_path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
Write-Log -message "clearing auto logon registry properties"
Set-ItemProperty -Path $reg_winlogon_path -Name AutoAdminLogon -Value 0
Remove-ItemProperty -Path $reg_winlogon_path -Name DefaultUserName -ErrorAction SilentlyContinue
Remove-ItemProperty -Path $reg_winlogon_path -Name DefaultPassword -ErrorAction SilentlyContinue
}
Function Download-Wmf5Server2008($architecture) {
if ($architecture -eq "x64") {
$zip_url = "http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win7AndW2K8R2-KB3191566-x64.zip"
$file = "$tmp_dir\Win7AndW2K8R2-KB3191566-x64.msu"
} else {
$zip_url = "http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win7-KB3191566-x86.zip"
$file = "$tmp_dir\Win7-KB3191566-x86.msu"
}
if (Test-Path -Path $file) {
return $file
}
$filename = $zip_url.Split("/")[-1]
$zip_file = "$tmp_dir\$filename"
Download-File -url $zip_url -path $zip_file
Write-Log -message "extracting '$zip_file' to '$tmp_dir'"
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem > $null
$legacy = $false
} catch {
$legacy = $true
}
if ($legacy) {
$shell = New-Object -ComObject Shell.Application
$zip_src = $shell.NameSpace($zip_file)
$zip_dest = $shell.NameSpace($tmp_dir)
$zip_dest.CopyHere($zip_src.Items(), 1044)
} else {
[System.IO.Compression.ZipFile]::ExtractToDirectory($zip_file, $tmp_dir)
}
return $file
}
Write-Log -message "starting script"
# on PS v1.0, upgrade to 2.0 and then run the script again
if ($PSVersionTable -eq $null) {
Write-Log -message "upgrading powershell v1.0 to v2.0"
$architecture = $env:PROCESSOR_ARCHITECTURE
if ($architecture -eq "AMD64") {
$url = "https://download.microsoft.com/download/2/8/6/28686477-3242-4E96-9009-30B16BED89AF/Windows6.0-KB968930-x64.msu"
} else {
$url = "https://download.microsoft.com/download/F/9/E/F9EF6ACB-2BA8-4845-9C10-85FC4A69B207/Windows6.0-KB968930-x86.msu"
}
$filename = $url.Split("/")[-1]
$file = "$tmp_dir\$filename"
Download-File -url $url -path $file
$exit_code = Run-Process -executable $file -arguments "/quiet /norestart"
if ($exit_code -ne 0 -and $exit_code -ne 3010) {
$error_msg = "failed to update Powershell from 1.0 to 2.0: exit code $exit_code"
Write-Log -message $error_msg -level "ERROR"
throw $error_msg
}
Reboot-AndResume
}
# exit if the target version is the same as the actual version
$current_ps_version = [version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
if ($current_ps_version -eq [version]$version) {
Write-Log -message "current and target PS version are the same, no action is required"
Clear-AutoLogon
exit 0
}
$os_version = [Version](Get-Item -Path "$env:SystemRoot\System32\kernel32.dll").VersionInfo.ProductVersion
$architecture = $env:PROCESSOR_ARCHITECTURE
if ($architecture -eq "AMD64") {
$architecture = "x64"
} else {
$architecture = "x86"
}
$actions = @()
switch ($version) {
"3.0" {
$actions += "3.0"
break
}
"4.0" {
if ($os_version -lt [version]"6.1") {
$error_msg = "cannot upgrade Server 2008 to Powershell v4, v3 is the latest supported"
Write-Log -message $error_msg -level "ERROR"
throw $error_msg
}
$actions += "4.0"
break
}
"5.1" {
if ($os_version -lt [version]"6.1") {
$error_msg = "cannot upgrade Server 2008 to Powershell v5.1, v3 is the latest supported"
Write-Log -message $error_msg -level "ERROR"
throw $error_msg
}
# check if WMF 3 is installed, need to be uninstalled before 5.1
if ($os_version.Minor -lt 2) {
$wmf3_installed = Get-Hotfix -Id "KB2506143" -ErrorAction SilentlyContinue
if ($wmf3_installed) {
$actions += "remove-3.0"
}
}
$actions += "5.1"
break
}
default {
$error_msg = "version '$version' is not supported in this upgrade script"
Write-Log -message $error_msg -level "ERROR"
throw $error_msg
}
}
# detect if .NET 4.5.2 is not installed and add to the actions
$dotnet_path = "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"
if (-not (Test-Path -Path $dotnet_path)) {
$dotnet_upgrade_needed = $true
} else {
$dotnet_version = Get-ItemProperty -Path $dotnet_path -Name Release -ErrorAction SilentlyContinue
if ($dotnet_version) {
# 379893 == 4.5.2
if ($dotnet_version.Release -lt 379893) {
$dotnet_upgrade_needed = $true
}
} else {
$dotnet_upgrade_needed = $true
}
}
if ($dotnet_upgrade_needed) {
$actions = @("dotnet") + $actions
}
Write-Log -message "The following actions will be performed: $($actions -join ", ")"
foreach ($action in $actions) {
$url = $null
$file = $null
$arguments = "/quiet /norestart"
switch ($action) {
"dotnet" {
Write-Log -message "running .NET update to 4.5.2"
$url = "https://download.microsoft.com/download/E/2/1/E21644B5-2DF2-47C2-91BD-63C560427900/NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
$error_msg = "failed to update .NET to 4.5.2"
$arguments = "/q /norestart"
break
}
"remove-3.0" {
# this is only run before a 5.1 install on Windows 7/2008 R2, the
# install zip needs to be downloaded and extracted before
# removing 3.0 as then the FileSystem assembly cannot be loaded
Write-Log -message "downloading WMF/PS v5.1 and removing WMF/PS v3 before version 5.1 install"
Download-Wmf5Server2008 -architecture $architecture > $null
$file = "wusa.exe"
$arguments = "/uninstall /KB:2506143 /quiet /norestart"
break
}
"3.0" {
Write-Log -message "running powershell update to version 3"
if ($os_version.Minor -eq 1) {
$url = "https://download.microsoft.com/download/E/7/6/E76850B8-DA6E-4FF5-8CCE-A24FC513FD16/Windows6.1-KB2506143-$($architecture).msu"
} else {
$url = "https://download.microsoft.com/download/E/7/6/E76850B8-DA6E-4FF5-8CCE-A24FC513FD16/Windows6.0-KB2506146-$($architecture).msu"
}
$error_msg = "failed to update Powershell to version 3"
break
}
"4.0" {
Write-Log -message "running powershell update to version 4"
if ($os_version.Minor -eq 1) {
$url = "https://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows6.1-KB2819745-$($architecture)-MultiPkg.msu"
} else {
$url = "https://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows8-RT-KB2799888-x64.msu"
}
$error_msg = "failed to update Powershell to version 4"
break
}
"5.1" {
Write-Log -message "running powershell update to version 5.1"
if ($os_version.Minor -eq 1) {
# Server 2008 R2 and Windows 7, already downloaded in remove-3.0
$file = Download-Wmf5Server2008 -architecture $architecture
} elseif ($os_version.Minor -eq 2) {
# Server 2012
$url = "http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/W2K12-KB3191565-x64.msu"
} else {
# Server 2012 R2 and Windows 8.1
if ($architecture -eq "x64") {
$url = "http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win8.1AndW2K12R2-KB3191564-x64.msu"
} else {
$url = "http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win8.1-KB3191564-x86.msu"
}
}
break
}
default {
$error_msg = "unknown action '$action'"
Write-Log -message $error_msg -level "ERROR"
}
}
if ($file -eq $null) {
$filename = $url.Split("/")[-1]
$file = "$tmp_dir\$filename"
}
if ($url -ne $null) {
Download-File -url $url -path $file
}
$exit_code = Run-Process -executable $file -arguments $arguments
if ($exit_code -ne 0 -and $exit_code -ne 3010) {
$log_msg = "$($error_msg): exit code $exit_code"
Write-Log -message $log_msg -level "ERROR"
throw $log_msg
}
if ($exit_code -eq 3010) {
Reboot-AndResume
break
}
}