diff --git a/scripts/Upgrade-PowerShell.ps1 b/scripts/Upgrade-PowerShell.ps1 index e217535..3078d3a 100644 --- a/scripts/Upgrade-PowerShell.ps1 +++ b/scripts/Upgrade-PowerShell.ps1 @@ -47,9 +47,8 @@ # 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. +# A log of this process is created in '$env:temp\upgrade_powershell.log'. +# This log can be used to see how did the script worked after an automatic reboot. # # See https://github.com/jborean93/ansible-windows/tree/master/scripts for more # details. @@ -64,11 +63,15 @@ # [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 domain +# [string] - fully qualified domain name (FQDN) of the computer domain. # .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 +# .PARAMETER force +# [switch] - Forces script to reboot automatically without user confirmation # .EXAMPLE # # upgrade from powershell 1.0 to 3.0 with automatic login and reboots # Set-ExecutionPolicy Unrestricted -Force @@ -83,9 +86,12 @@ Param( [string]$version = "5.1", [string]$username, + [string]$domain, [string]$password, - [switch]$verbose = $false + [switch]$verbose = $false, + [switch]$force ) + $ErrorActionPreference = 'Stop' if ($verbose) { $VerbosePreference = "Continue" @@ -100,7 +106,7 @@ 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" + $log_file = Join-Path -Path "$tmp_dir" -ChildPath 'upgrade_powershell.log' Write-Verbose -Message $log_entry Add-Content -Path $log_file -Value $log_entry } @@ -108,7 +114,7 @@ Function Write-Log($message, $level="INFO") { 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" + $ps_path = Join-Path -Path "$PSHOME" -ChildPath 'powershell.exe' $arguments = "-version $version" if ($username -and $password) { $arguments = "$arguments -username `"$username`" -password `"$password`"" @@ -117,27 +123,24 @@ Function Reboot-AndResume { $arguments = "$arguments -Verbose" } - $command = "$ps_path -ExecutionPolicy ByPass -File $script_path $arguments" + $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" + Set-AutoLogon -Enable + } + if ($Force -eq $false) { $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 + $msg = "please reboot server manually to continue upgrade process, the script will restart on the next login automatically" + Write-Log -Message $msg + exit 0 } } - + + Write-Log -Message 'rebooting server to continue powershell upgrade' if (Get-Command -Name Restart-Computer -ErrorAction SilentlyContinue) { Restart-Computer -Force } else { @@ -152,9 +155,9 @@ Function Run-Process($executable, $arguments) { $psi.FileName = $executable $psi.Arguments = $arguments Write-Log -message "starting new process '$executable $arguments'" - $process.Start() | Out-Null + $process.Start() > $null - $process.WaitForExit() | Out-Null + $process.WaitForExit() > $null $exit_code = $process.ExitCode Write-Log -message "process completed with exit code '$exit_code'" @@ -167,62 +170,68 @@ Function Download-File($url, $path) { $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 Set-AutoLogon { + param ( + [switch] $Enable, + [switch] $Disable + ) + $reg_winlogon_path = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon' + + if ($Enable) { + Write-Log -Message 'setting auto logon registry properties' + 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 + if ($domain) { + Set-ItemProperty -Path $reg_winlogon_path -Name DefaultDomain -Value $domain + } + } elseif ($Disable) { + 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 + Remove-ItemProperty -Path $reg_winlogon_path -Name DefaultDomain -ErrorAction SilentlyContinue + } else { + $error_msg = 'ambiguous calling of Set-Autolon: expecting a parameter -Enable or -Disable, none has been provided' + Write-Log -Message $error_msg -Level 'ERROR' + throw $error_msg + } } 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" + $zip_url = [uri]"http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win7AndW2K8R2-KB3191566-x64.zip" } 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" + $zip_url = [uri]"http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win7-KB3191566-x86.zip" } + $filename = $zip_url.Segments[-1] + $file = Join-Path -Path "$tmp_dir" -ChildPath $filename.Replace('.zip', '.msu') if (Test-Path -Path $file) { return $file } - $filename = $zip_url.Split("/")[-1] - $zip_file = "$tmp_dir\$filename" + $zip_file = Join-Path -Path "$tmp_dir" -ChildPath "$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) - } + Add-Type -AssemblyName System.IO.Compression.FileSystem > $null + [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) { +if ($null -eq $PSVersionTable) { 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" + $url = [uri]"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" + $url = [uri]"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" + $filename = $url.Segments[-1] + $file = Join-Path -Path "$tmp_dir" -ChildPath "$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) { @@ -237,7 +246,7 @@ if ($PSVersionTable -eq $null) { $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 + Set-AutoLogon -Disable exit 0 } @@ -289,20 +298,8 @@ switch ($version) { # 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) { +$dotnet_version = Get-ItemProperty -Path $dotnet_path -Name Release -ErrorAction SilentlyContinue +if ($dotnet_version.Release -lt 379893) { $actions = @("dotnet") + $actions } @@ -315,7 +312,7 @@ foreach ($action in $actions) { 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" + $url = [uri]"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 @@ -334,9 +331,9 @@ foreach ($action in $actions) { "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" + $url = [uri]"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" + $url = [uri]"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 @@ -344,9 +341,9 @@ foreach ($action in $actions) { "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" + $url = [uri]"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" + $url = [uri]"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 @@ -358,13 +355,13 @@ foreach ($action in $actions) { $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" + $url = [uri]"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" + $url = [uri]"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" + $url = [uri]"http://download.microsoft.com/download/6/F/5/6F5FF66C-6775-42B0-86C4-47D41F2DA187/Win8.1-KB3191564-x86.msu" } } break @@ -375,16 +372,16 @@ foreach ($action in $actions) { } } - if ($file -eq $null) { - $filename = $url.Split("/")[-1] - $file = "$tmp_dir\$filename" + if ($null -eq $file) { + $filename = $url.Segments[-1] + $file = Join-Path -Path "$tmp_dir" -ChildPath "$filename" } - if ($url -ne $null) { + if ($null -ne $url) { Download-File -url $url -path $file } $exit_code = Run-Process -executable $file -arguments $arguments - if ($exit_code -ne 0 -and $exit_code -ne 3010) { + if (@(0, 3010) -notcontains $exit_code) { $log_msg = "$($error_msg): exit code $exit_code" Write-Log -message $log_msg -level "ERROR" throw $log_msg