400 lines
16 KiB
PowerShell
400 lines
16 KiB
PowerShell
# 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") {
|
|
# this url not working
|
|
#$url = "https://download.microsoft.com/download/2/8/6/28686477-3242-4E96-9009-30B16BED89AF/Windows6.0-KB968930-x64.msu"
|
|
$url = "http://download.windowsupdate.com/msdownload/update/software/updt/2011/02/windows6.0-kb968930-x64_4de013d593181a2a04217ce3b0e7536ab56995aa.msu"
|
|
} else {
|
|
# this url not working
|
|
#$url = "https://download.microsoft.com/download/F/9/E/F9EF6ACB-2BA8-4845-9C10-85FC4A69B207/Windows6.0-KB968930-x86.msu"
|
|
$url = "http://download.windowsupdate.com/msdownload/update/software/updt/2011/02/windows6.0-kb968930-x86_16fd2e93be2e7265821191119ddfc0cdaa6f4243.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
|
|
}
|
|
} |