windows_pc_setup_script.md (15058B)
1 --- 2 title: Windows PC setupスクリプト 3 tags: 4 - Windows 5 private: false 6 updated_at: '' 7 id: null 8 organization_url_name: null 9 slide: false 10 --- 11 # 概要 12 Windowsのセットアップを自動化する方式を検討する記事です. 13 # 対象読者 14 - (ライトではない)windowsユーザー 15 - windowsが`\ちょっとできる`人 16 # 現行方式 17 ## 保管方法 18 19 ||| 20 |-|-| 21 |保管場所|GibHub Private リポジトリ| 22 |パッケージマネージャー|winget| 23 24 てな感じです. 25 26 ## 複数PC運用について 27 みなさん,Windows機を複数持ってますよね?(唐突) 28 それを全て一つのリポジトリにするのもなんだし,でも,2つのPCにしちゃうのもなんだかなぁという感じだと思います. 29 そこで思いついたのは,`メインPC`は`main`ブランチに,ノートPCは`note`ブランチに分割しちゃう方法です.開発用が在る方は`dev`ブランチとかできて面白いですね.ただし,この運用は,複数に共通する設定の変更が絶望的にやりにくいことです.私はWindows機が2台しかない民なのでこの運用で耐えてますが,流石に3つ4つ出てくると厳しいかもです. 30 31 ## winget 32 最初はいちいちインストールを回すのがめんどくさくて,wingetのexport/import機能を使ってインストールされているアプリを一致させようとした感じです.ただ,色々と問題があったのでそれについて書きます. 33 ### 既存パッケージ再インストール問題 34 最初から標準で入っているパッケージをwingetが再インストールしようとする問題がありました. 35 時間がかかって非常にだるいので,毎回それが起こるようなパッケージ.jsonをつくって,exportされたjsonから削除するようなpythonスクリプトを書きました. 36 ```python 37 import json 38 import os 39 from datetime import datetime 40 41 # --- Configuration --- 42 INPUT_FILE = "winget.json" 43 REMOVE_LIST_FILE = "wingetRemoveList.json" 44 OUTPUT_DIR = "winget" 45 # --------------------- 46 47 # Ensure the output directory exists 48 if not os.path.exists(OUTPUT_DIR): 49 os.makedirs(OUTPUT_DIR) 50 print(f"Created directory: {OUTPUT_DIR}") 51 52 # Read the main winget file 53 try: 54 with open(INPUT_FILE, "r", encoding="utf-8") as fs: 55 wingetJson = json.load(fs) 56 except FileNotFoundError: 57 print(f"Error: Input file not found at '{INPUT_FILE}'") 58 exit(1) 59 except json.JSONDecodeError: 60 print(f"Error: Could not decode JSON from '{INPUT_FILE}'") 61 exit(1) 62 63 # Read the remove list file 64 try: 65 with open(REMOVE_LIST_FILE, "r", encoding="utf-8") as fl: 66 wingetRemoveListJson = json.load(fl) 67 except FileNotFoundError: 68 print(f"Error: Remove list file not found at '{REMOVE_LIST_FILE}'") 69 exit(1) 70 except json.JSONDecodeError: 71 print(f"Error: Could not decode JSON from '{REMOVE_LIST_FILE}'") 72 exit(1) 73 74 remove_identifiers = set(wingetRemoveListJson.get("wingetRemoveList", [])) 75 original_packages = wingetJson.get("Sources", [{}])[0].get("Packages", []) 76 packages_to_keep = [] 77 removed_count = 0 78 79 for package in original_packages: 80 if package.get("PackageIdentifier") in remove_identifiers: 81 print(f"Removing: {package.get('PackageIdentifier')}") 82 removed_count += 1 83 else: 84 packages_to_keep.append(package) 85 86 if removed_count == 0: 87 print("No packages were removed from the list.") 88 89 wingetJson["Sources"][0]["Packages"] = packages_to_keep 90 91 # Generate the output filename with the current date 92 today_str = datetime.now().strftime("%Y%m%d") 93 output_filename = os.path.join(OUTPUT_DIR, f"winget-{today_str}.json") 94 95 # Write the modified data to the new file 96 with open(output_filename, "w", encoding="utf-8") as f: 97 json.dump(wingetJson, f, indent=4, ensure_ascii=False) 98 99 print(f"\nSuccessfully created formatted winget file at: {output_filename}") 100 ``` 101 コードはAIが書いたので,品質は保証しません.私の環境だと上手く動きました. 102 あと,現時点での`wingetRemoveList.json`の中身も貼っておきます. 103 ```json 104 { 105 "wingetRemoveList" : [ 106 "CPUID.CPU-Z.MSI", 107 "Microsoft.Office", 108 "Microsoft.VCRedist.2010.x64", 109 "Microsoft.VCRedist.2008.x64", 110 "Ubisoft.Connect", 111 "Python.Launcher", 112 "Microsoft.VCRedist.2015+.x64", 113 "Microsoft.VCRedist.2008.x86", 114 "Microsoft.VCRedist.2010.x86", 115 "Microsoft.DotNet.DesktopRuntime.8", 116 "Microsoft.VCRedist.2015+.x86", 117 "Python.Python.3.10", 118 "OffSec.KaliLinux", 119 "Microsoft.UI.Xaml.2.7", 120 "Microsoft.UI.Xaml.2.8", 121 "Microsoft.VCLibs.Desktop.14", 122 "Microsoft.WSL", 123 "Microsoft.WindowsTerminal" 124 ] 125 } 126 ``` 127 ## 種々の設定 128 私はこれら以外にも割と様々にWindowsの設定を施しているので,それらも入れたいなーとか思いました. 129 具体的に言うと, 130 - powershellの使い心地をbashに寄せるためのプロファイル適用 131 - タスクバーの時計に秒数を追加 132 - 高速スタートアップの無効化 133 - 視覚効果を無効化 134 - ダークモード 135 - システムサウンドの無効化 136 これらを全部やるようなスクリプトを作っちゃいました. 137 AIに書かせると楽ですね.この程度なら容易にレビューができるし,ハルシネーションの影響も割合,少なくて済むので有り難いです. 138 なんか`-Force`が多用されてて怖いですけどね.windowsはもともと品質がよろしくはないので良いかななんて() 139 `setup.ps1` 140 ```powershell 141 <# 142 .SYNOPSIS 143 PCのセットアップを自動化します。 144 .DESCRIPTION 145 このPowerShellスクリプトは、以下のセットアップ処理を実行します: 146 - Windowsテーマのダークモードへの変更 147 - PowerShellプロファイルの更新 148 - パフォーマンス向上のための視覚効果の無効化 149 - タスクバー時計への秒表示の有効化 150 - 高速スタートアップの無効化 151 - システムサウンドの無効化 152 - 最新のwinget JSON構成ファイルを使ったアプリケーションのインストール 153 - shortcutsフォルダのユーザーPATHへの追加 154 .NOTES 155 このスクリプトは管理者権限で実行する必要があります。 156 #> 157 158 # スクリプトが管理者として実行されているか確認 159 if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { 160 Write-Warning "このスクリプトは管理者権限が必要です。管理者として再起動します..." 161 # 管理者としてスクリプトを再実行 162 Start-Process pwsh -Verb RunAs -ArgumentList "-NoProfile -File `"$PSCommandPath`"" 163 exit 164 } 165 166 # スクリプトの場所を基準に動作するようにカレントディレクトリを変更 167 $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path 168 Set-Location -Path $scriptRoot 169 170 Write-Host "PCセットアップスクリプトを開始します..." -ForegroundColor Green 171 Write-Host "------------------------------------------------------------" 172 173 # 1. Set system and apps to Dark Mode 174 try { 175 Write-Host "[1/8] Windowsとアプリのテーマをダークモードに設定しています..." -ForegroundColor Cyan 176 $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" 177 # 0 = Dark, 1 = Light 178 Set-ItemProperty -Path $regPath -Name "AppsUseLightTheme" -Value 0 -ErrorAction Stop 179 Set-ItemProperty -Path $regPath -Name "SystemUsesLightTheme" -Value 0 -ErrorAction Stop 180 Write-Host " テーマをダークモードに設定しました。" 181 } 182 catch { 183 Write-Error "ダークモードへの設定に失敗しました: $_" 184 } 185 186 # 2. PowerShellプロファイルに内容を追記 187 try { 188 Write-Host "[2/8] PowerShellプロファイルを更新しています..." -ForegroundColor Cyan 189 $profileSourcePath = ".\Microsoft.PowerShell_profile.ps1" 190 191 if (-not(Test-Path -Path $profileSourcePath)) { 192 throw "プロファイルソースファイルが見つかりません: $profileSourcePath" 193 } 194 195 $profileContent = Get-Content -Path $profileSourcePath -Raw -ErrorAction Stop 196 197 # プロファイル用のディレクトリが存在しない場合は作成 198 $profileDir = Split-Path -Path $PROFILE -Parent 199 if (-not (Test-Path -Path $profileDir)) { 200 New-Item -Path $profileDir -ItemType Directory -Force | Out-Null 201 } 202 203 # プロファイルファイルが存在しないか、内容がまだ含まれていない場合に追記 204 if (-not (Test-Path $PROFILE) -or -not ((Get-Content $PROFILE -Raw) -like "*$($profileContent)*")) { 205 Add-Content -Path $PROFILE -Value $profileContent 206 Write-Host " PowerShellプロファイルを更新しました。" 207 } else { 208 Write-Host " プロファイルは既に更新済みです。スキップします。" -ForegroundColor Yellow 209 } 210 } 211 catch { 212 Write-Error "プロファイルの更新に失敗しました: $_" 213 } 214 215 # 3. パフォーマンスのために視覚効果を無効化 216 try { 217 Write-Host "[3/8] 視覚効果をパフォーマンス優先に設定しています..." -ForegroundColor Cyan 218 $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects" 219 Set-ItemProperty -Path $regPath -Name "VisualFxSetting" -Value 2 -ErrorAction Stop 220 Write-Host " 視覚効果を「パフォーマンスを優先する」に設定しました。" 221 } 222 catch { 223 Write-Error "視覚効果の無効化に失敗しました: $_" 224 } 225 226 # 4. タスクバーの時計に秒を表示 227 try { 228 Write-Host "[4/8] タスクバーの時計に秒を表示する設定を有効にしています..." -ForegroundColor Cyan 229 $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" 230 if (-not (Test-Path $regPath)) { 231 New-Item -Path $regPath -Force | Out-Null 232 } 233 Set-ItemProperty -Path $regPath -Name "ShowSecondsInSystemClock" -Value 1 -ErrorAction Stop 234 Write-Host " タスクバーの時計に秒が表示されるようになります。" 235 } 236 catch { 237 Write-Error "時計の秒表示設定に失敗しました: $_" 238 } 239 240 # 5. 高速スタートアップを無効化 241 try { 242 Write-Host "[5/8] 高速スタートアップを無効化しています..." -ForegroundColor Cyan 243 $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power" 244 Set-ItemProperty -Path $regPath -Name "HiberbootEnabled" -Value 0 -ErrorAction Stop 245 Write-Host " 高速スタートアップを無効化しました。" 246 } 247 catch { 248 Write-Error "高速スタートアップの無効化に失敗しました: $_" 249 } 250 251 # 6. システムサウンドを無効化 252 try { 253 Write-Host "[6/8] システムサウンドを無効化しています..." -ForegroundColor Cyan 254 $schemePath = "HKCU:\AppEvents\Schemes" 255 Set-ItemProperty -Path $schemePath -Name "(Default)" -Value ".None" -ErrorAction Stop 256 Write-Host " サウンド設定を「サウンドなし」に変更しました。" 257 } 258 catch { 259 Write-Error "システムサウンドの無効化に失敗しました: $_" 260 } 261 262 # 7. 最新のwinget JSONファイルからパッケージをインストール 263 try { 264 Write-Host "[7/8] Wingetによるアプリケーションのインストールを開始します..." -ForegroundColor Cyan 265 $latestWingetFile = Get-ChildItem -Path ".\winget" -Filter "winget-*.json" | Sort-Object Name -Descending | Select-Object -First 1 266 if ($latestWingetFile) { 267 Write-Host " 最新の構成ファイルが見つかりました: $($latestWingetFile.Name)" 268 Write-Host " インポートを開始します。完了まで時間がかかる場合があります..." 269 winget import -i $latestWingetFile.FullName --accept-package-agreements --accept-source-agreements 270 } else { 271 Write-Warning " 'winget-*.json' ファイルが見つかりませんでした。アプリケーションのインストールをスキップします。" 272 } 273 } 274 catch { 275 Write-Error "Wingetでのアプリケーションインストールに失敗しました: $_" 276 } 277 278 # 8. shortcutsフォルダをPATH環境変数に追加 279 try { 280 Write-Host "[8/8] 'shortcuts' フォルダをPATH環境変数に追加しています..." -ForegroundColor Cyan 281 $shortcutsPath = Join-Path -Path $scriptRoot -ChildPath "shortcuts" 282 283 if (-not (Test-Path -Path $shortcutsPath)) { 284 Write-Warning " 'shortcuts' フォルダが見つかりません。スキップします。" 285 } 286 else { 287 $currentUserPath = [System.Environment]::GetEnvironmentVariable("Path", "User") 288 if ($currentUserPath -notlike "*$shortcutsPath*") { 289 $newPath = $currentUserPath.TrimEnd(';') + ";" + $shortcutsPath 290 [System.Environment]::SetEnvironmentVariable("Path", $newPath, "User") 291 $env:Path = $newPath # 現在のセッションにも反映 292 Write-Host " 'shortcuts' フォルダをユーザーのPATHに追加しました。" 293 } else { 294 Write-Host " 'shortcuts' フォルダは既にユーザーPATHに存在します。スキップします。" -ForegroundColor Yellow 295 } 296 } 297 } 298 catch { 299 Write-Error "'shortcuts' フォルダのPATHへの追加に失敗しました: $_" 300 } 301 302 Write-Host "------------------------------------------------------------" 303 Write-Host "セットアップスクリプトが完了しました。" -ForegroundColor Green 304 Write-Host "UI関連の変更を適用するためにExplorerを再起動します。" -ForegroundColor Yellow 305 Stop-Process -Name explorer -Force 306 Write-Host "すべての変更を確実に適用するために、コンピューターの再起動を推奨します。" -ForegroundColor Yellow 307 ``` 308 私は画面が2つある関係でwallpaperの自動設定は諦めました.そもそもは動画をwallpaperにするよう試みたりしてたので,その辺りは今後気が向けばやります. 309 あとはpowershellのプロファイルの中にインストールが必要なモジュールがあった気がするので,それもこのスクリプトに入れておくと便利かななどと思ってます. 310 311 # まとめ 312 これでPC初期化は怖くない! 313 みなさんも1年くらいの周期で初期化していると思うので,この機会にぜひとも自動セットアップを構築して,やってみてください.私は今度は大量のVMのパッケージのバージョンを何とかするansibleを作るかもしれないです.乞うご期待!(のんびりやります)