前回のWindows10Upgrade自動化スクリプト と組み合わせて通常のWindowsUpdate含め全自動でWindows10を最新状態にしたい。
ということで欲しい機能を色々まとめたものを作ったのでGithubにアップしております。
概要
できること
- 最新のWindows10メジャーバージョンまでアップグレード
- 最新の状態です になるまでWindows Updateを繰り返す
- 再起動後に自動でスクリプトを実行
- Webhookによりチャット(Slack/ChatWork/Teams/hangout)へ完了報告
- 不要になったファイルのクリーンアップ
実行手順
- USB メモリなどに AutoWinUpdate フォルダを作成して各ファイルを配置
- 実行対象端末の C ドライブ直下に AutoWinUpdate フォルダをコピー
- 「Run-PS.bat」を管理者権限で実行
- 完了通知が来るまで放置
中身の解説
Config.json
項目 | 値 |
---|---|
upgradeWindows | true アップグレードが実行される、 false アップデートのみ |
setupUser.name | 自動ログオンのためのユーザID |
setupUser.pass | 自動ログオンのためのユーザパスワード |
notifier.chat | slack/chatwork/teams/hangoutから選択 |
notifier.url | WebHookURLをセット |
notifier.token | chatworkのみtokenをセットする |
[ { "upgradeWindows": true, "setupUser": { "name": "setup", "pass": "Setup1234" }, "notifier": { "chat": "slack", "url": "https://hooks.slack.com/services/xxxx/xxxx/xxxxx", "token": "" } } ]
Run-PS.bat
管理者権限で実行されているかチェックしてからPowerShellを呼び出す
@echo off openfiles > NUL 2>&1 if NOT %ERRORLEVEL% EQU 0 ( echo 管理者権限で実行されていません pause ) else ( echo 管理者権限で実行されていることを確認しました echo PowerShellスクリプトを実行します powershell -executionpolicy RemoteSigned -file C:\AutoWinUpdate\Main.ps1 -verb runas )
Main.ps1
自動ログオンを設定、アップグレードおよびアップデートを実行する。
再起動が必要な場合はRun-PS.batをタスクスケジューラに登録しておくことで起動時に自動実行。
更新プログラムが0件になると不要になったファイルを削除、チャットに完了通知を送信してから終了する。
# ログ出力開始 Start-Transcript "$PSScriptRoot/AutoWinUpdate.log" -append Write-Host @" ********************************************************* * * Windows10 Auto Updating Script / Main.ps1 * バージョン : 1.20 * 最終更新日 : 2020/10/13 * "@ -ForeGroundColor green Write-Host "$(Get-Date -Format g) 実行中のユーザ : " $env:USERNAME # 設定ファイルの読み込み Write-Host "$(Get-Date -Format g) 設定ファイル読み込み : $($PSScriptRoot)/Config.json" $config = Get-Content "$PSScriptRoot/Config.json" -Encoding UTF8 | ConvertFrom-Json # 関数の読み込み Write-Host "$(Get-Date -Format g) 関数ファイル読み込み : $($PSScriptRoot)/Functions.ps1" . $PSScriptRoot/Functions.ps1 # 自動ログオン設定 Enable-AutoLogon $config.setupuser.name $config.setupuser.pass # スケジューラにログオンスクリプト登録 Register-Task "AutoWinUpdate" "$PSScriptRoot\Run-PS.bat" $config.setupuser.name $config.setupuser.pass if ($config.upgradeWindows) { $winver = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId if (1909 -gt $winver) { # 1909未満のバージョンの場合1909をインストール Write-Host "$(Get-Date -Format g) Windows10 $($winver) → 1909アップグレード実行" Start-Process -FilePath ($PSScriptRoot + "/1909/setup.exe") -argumentList "/Auto Upgrade" -Wait } } Write-Host "`r`n***************** 最新までWindows Update *****************" -ForeGroundColor green Install-Module -Name PSWindowsUpdate -Force Import-Module -Name PSWindowsUpdate Install-WindowsUpdate -AcceptAll -AutoReboot # Run-LegacyWindowsUpdate "Full" # Run-WindowsUpdate # Taskを削除 if (Test-Task "AutoWinUpdate") { Remove-Task "AutoWinUpdate" Write-Host "$(Get-Date -Format g) ログオンスクリプトを解除" } # 自動ログオン無効化 Disable-AutoLogon # AutoWinUpdate.log 以外の AutoWinUpdateフォルダ配下を削除 Remove-Item C:\AutoWinUpdate\* -Exclude AutoWinUpdate.log -Recurse Write-Host "$(Get-Date -Format g) C:\AutoWinUpdate\フォルダを削除" $compliteMsg = @" [$($env:COMPUTERNAME)] Windows Update 完了 詳細ログは対象PCの C:\AutoWinUpdate\AutoWinUpdate.log をご確認ください "@ # キッティング完了をチャットに通知 Send-Chat $compliteMsg $config.notifier.chat $config.notifier.url $config.notifier.token # ログ出力終了 Stop-Transcript Send-Chat $compliteMsg $config.notifier.chat $config.notifier.url $config.notifier.token # ログ出力終了 Stop-Transcript
Functions.ps1
自動ログオンon/off、タスクスケジューラ登録、自動Windows Updateなど色々まとめ。
################################################ # 自動ログオン有効化 ################################################ function Enable-AutoLogon($LogonUser, $LogonPass, $LogonDomain) { <# .SYNOPSIS Enable AutoLogon .DESCRIPTION #> $AutoAdminLogon = Get-Registry "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "AutoAdminLogon" $DefaultUsername = Get-Registry "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "DefaultUsername" if (($AutoAdminLogon -ne 1) -Or ($DefaultUsername -ne $LogonUser)) { Write-Host "$(Get-Date -Format g) ユーザー$($LogonUser)の自動ログオンを有効化" $RegLogonKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Set-ItemProperty -path $RegLogonKey -name "AutoAdminLogon" -value 1 Set-ItemProperty -path $RegLogonKey -name "DefaultUsername" -value $LogonUser Set-ItemProperty -path $RegLogonKey -name "DefaultPassword" -value $LogonPass if ($LogonDomain -ne "") { Set-ItemProperty -path $RegLogonKey -name "DefaultDomainName" -value $LogonDomain } } } ################################################ # 自動ログオン無効化 ################################################ function Disable-AutoLogon() { <# .SYNOPSIS Disable AutoLogon .DESCRIPTION #> $RegLogonKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Set-ItemProperty -path $RegLogonKey -name "AutoAdminLogon" -value 0 Set-ItemProperty -path $RegLogonKey -name "DefaultUsername" -value "" Set-ItemProperty -path $RegLogonKey -name "DefaultPassword" -value "" } ################################################ # タスクの存在チェック ################################################ function Test-Task($TaskName) { <# .SYNOPSIS タスクの存在チェック .DESCRIPTION タスク名を受け取ってタスクスケジューラ内に存在するかチェック 存在する場合は%true、存在しない場合は$falseを返します .EXAMPLE Test-Task "自動ログオン" .PARAMETER TaskName String型でタスクの名前を指定 #> $Task = $null if ((Get-WmiObject Win32_OperatingSystem).version -eq "6.1.7601") { $Task = schtasks /query /fo csv | ConvertFrom-Csv | Where-Object { $_."Taskname" -eq $TaskName } } else { $Task = Get-ScheduledTask | Where-Object { $_.TaskName -match $TaskName } } if ($Task) { return $true } else { return $false } } ################################################ # タスクスケジューラ登録 ################################################ function Register-Task($TaskName, $exePath, $TaskExecuteUser, $TaskExecutePass, $visble) { if (-not (Test-Task $TaskName)) { Write-Host "$(Get-Date -Format g) タスクスケジューラに登録:$($TaskName)" $trigger = New-ScheduledTaskTrigger -AtLogon $action = New-ScheduledTaskAction -Execute $exePath $principal = New-ScheduledTaskPrincipal -UserID $TaskExecuteUser -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -MultipleInstances Parallel Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Settings $settings -Principal $principal } } ################################################ # タスクスケジューラ削除 ################################################ function Remove-Task($TaskName) { if ((Get-WmiObject Win32_OperatingSystem).version -eq "6.1.7601") { schtasks /delete /tn $TaskName } else { Get-ScheduledTask | Where-Object { $_.TaskName -match $TaskName } | Unregister-ScheduledTask -Confirm:$false } Write-Output "$(Get-Date -Format g) $($TaskName)をタスクスケジューラから削除" } ################################################ # 自動でWindowsアップデートを最新まで実行 ################################################ function Start-WindowsUpdate($Option) { # 最大適用更新数 $G_MaxUpdateNumber = 100 Write-Host "$(Get-Date -Format g) --- Running Windows Update ---" Write-Host "$(Get-Date -Format g) 更新プログラムを確認..." $updateSession = new-object -com "Microsoft.Update.Session" $updateSearcher = $updateSession.CreateupdateSearcher() # アップデートタイプコントロール if ( $Option -match "ful" ) { Write-Host "$(Get-Date -Format g) Type: Full Update" $Option = "Full" $searchResult = $updateSearcher.Search("IsInstalled=0 and Type='Software'") } else { Write-Host "$(Get-Date -Format g) Type: Minimum Update" $Option = "Minimum" $searchResult = $updateSearcher.Search("IsInstalled=0 and Type='Software' and AutoSelectOnWebSites=1") } Write-Host "$(Get-Date -Format g) 利用可能な更新プログラム:" if ($searchResult.Updates.Count -eq 0) { Write-Host "$(Get-Date -Format g) 利用可能な更新プログラムはありません" Write-Host "$(Get-Date -Format g) Windows Update 完了" } else { $downloadReq = $False $i = 0 foreach ($update in $searchResult.Updates) { $i++ if ( $update.IsDownloaded ) { $UpdateTitol = $update.Title Write-Host "$(Get-Date -Format g) $i : $UpdateTitol (downloaded)" } else { $downloadReq = $true $UpdateTitol = $update.Title Write-Host "$(Get-Date -Format g) $i : $UpdateTitol (not downloaded)" } } if ( $downloadReq ) { Write-Host "$(Get-Date -Format g) ダウンロードリストを作成..." $updatesToDownload = new-object -com "Microsoft.Update.UpdateColl" foreach ($update in $searchResult.Updates) { $updatesToDownload.Add($update) | out-null } Write-Host "$(Get-Date -Format g) ダウンロード..." $downloader = $updateSession.CreateUpdateDownloader() $downloader.Updates = $updatesToDownload $downloader.Download() Write-Host "$(Get-Date -Format g) ダウンロード済み:" $i = 0 foreach ($update in $searchResult.Updates) { $i++ if ( $update.IsDownloaded ) { $UpdateTitol = $update.Title Write-Host "$(Get-Date -Format g) $i : $UpdateTitol (downloaded)" } else { $UpdateTitol = $update.Title Write-Host "$(Get-Date -Format g) $i : $UpdateTitol (not downloaded)" } } } else { Write-Host "$(Get-Date -Format g) 全ての更新プログラムをダウンロードしました" } $updatesToInstall = new-object -com "Microsoft.Update.UpdateColl" Write-Host "$(Get-Date -Format g) インストールリストを作成..." $i = 0 foreach ($update in $searchResult.Updates) { if ( $update.IsDownloaded ) { $updatesToInstall.Add($update) | out-null $i++ $UpdateTitol = $update.Title Write-Host "$(Get-Date -Format g) $i / $G_MaxUpdateNumber : $UpdateTitol (Install)" if ( $i -ge $G_MaxUpdateNumber ) { Write-Host "$(Get-Date -Format g) 更新数の上限: $G_MaxUpdateNumber" break } } } if ( $updatesToInstall.Count -eq 0 ) { Write-Host "$(Get-Date -Format g) インストールの準備ができていません" Write-Host "$(Get-Date -Format g) 異常終了" } else { $InstallCount = $updatesToInstall.Count Write-Host "$(Get-Date -Format g) $InstallCount 件のアップデート..." $installer = $updateSession.CreateUpdateInstaller() $installer.Updates = $updatesToInstall $installationResult = $installer.Install() if ( $installationResult.ResultCode -eq 2 ) { Write-Host "$(Get-Date -Format g) 全ての更新プログラムをインストール完了" } else { Write-Host "$(Get-Date -Format g) 一部の更新プログラムをインストール出来ませんでした" } if ( $installationResult.RebootRequired ) { Write-Host "$(Get-Date -Format g) 一つ以上のアップデートで再起動が必要です 10秒後に再起動します" Start-Sleep 10 Restart-Computer -Force } else { Write-Host "$(Get-Date -Format g) Windows Update を完了しました。再起動は必要ありません。" Write-Host "$(Get-Date -Format g) =-=-=-=-=- Windows Update finished -=-=-=-=-=" } } } } ################################################ # チャット送信 ################################################ function Send-Chat($msg, $chat, $url, $token) { $enc = [System.Text.Encoding]::GetEncoding('ISO-8859-1') $utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($msg) if ($chat -eq "slack") { $notificationPayload = @{text = $enc.GetString($utf8Bytes) } Invoke-RestMethod -Uri $url -Method Post -Body (ConvertTo-Json $notificationPayload) } elseif ($chat -eq "chatwork") { $body = $enc.GetString($utf8Bytes) Invoke-RestMethod -Uri $url -Method POST -Headers @{"X-ChatWorkToken" = $token } -Body "body=$body" } elseif ($chat -eq "teams") { $body = ConvertTo-JSON @{text = $msg } $postBody = [Text.Encoding]::UTF8.GetBytes($body) Invoke-RestMethod -Uri $url -Method Post -ContentType 'application/json' -Body $postBody } elseif ($chat -eq "hangouts") { $notificationPayload = @{text = $msg } Invoke-RestMethod -Uri $url -Method Post -ContentType 'application/json; charset=UTF-8' -Body (ConvertTo-Json $notificationPayload) } }