<# Goodly ディストロ図鑑 — 環境の記録とスクリーンショット(計測の手順 m1・ホスト側) 手順: https://goodly.jp/distro-criteria/#cr-measure ホスト(Windows)の PowerShell で動かします。VirtualBox の VBoxManage を読むだけで、VM の設定は変えません。 .\env-m1.ps1 -VM "VM-A" 環境の記録(JSON)を出す → 図鑑データの「環境」に貼る .\env-m1.ps1 -VM "VM-A" -Send コード 環境の記録を図鑑データに送る(コードは「検証の手順書」で出します) .\env-m1.ps1 -VM "VM-A" -Iso D:\iso\x.iso 使った ISO の sha256 を出す → 検証記録に貼る .\env-m1.ps1 -VM "VM-A" -Shot desktop VM の画面を desktop-01.png として保存(連番は自動) .\env-m1.ps1 -Scenes 場面の名前の一覧 スクリーンショットは VM の画面の解像度そのまま(手順 m1 は 1440×900)で、Guest Additions は要りません。 保存先は「ピクチャ\goodly-shots\VM名-日付」。検証記録の画面にまとめてドラッグすると、場面と連番を名前から読みます。 実行が止められたときは、このファイルを右クリック →「プロパティ」→「許可する」にチェックするか、 powershell -ExecutionPolicy Bypass -File .\env-m1.ps1 -VM "VM-A" で動かしてください。 #> param( [string]$VM, [string]$Iso, [string]$Shot, [string]$OutDir = (Join-Path ([Environment]::GetFolderPath('MyPictures')) 'goodly-shots'), [string]$Send, [string]$Base = 'https://goodly.jp', [switch]$Scenes ) $ErrorActionPreference = 'Stop' # 案内は標準エラーに出す(標準出力は JSON だけにして、ファイルに保存・パイプできるように) $Protocol = 'm1' $SceneKeys = @('desktop', 'app_grid', 'store', 'settings', 'terminal', 'ime', 'installer_welcome', 'installer_language', 'installer_keyboard', 'installer_partition', 'installer_user', 'installer_done', 'login', 'welcome', 'files', 'update', 'boot_menu') if ($Scenes) { $SceneKeys | ForEach-Object { Write-Output $_ } exit 0 } function Find-VBoxManage { $candidates = @() if ($env:VBOX_MSI_INSTALL_PATH) { $candidates += (Join-Path $env:VBOX_MSI_INSTALL_PATH 'VBoxManage.exe') } $candidates += (Join-Path $env:ProgramFiles 'Oracle\VirtualBox\VBoxManage.exe') foreach ($c in $candidates) { if (Test-Path $c) { return $c } } $cmd = Get-Command VBoxManage.exe -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } throw 'VBoxManage.exe が見つかりません(VirtualBox が入っていないか、場所が違います)。' } # ISO の sha256 だけ(VM は要らない) if ($Iso) { if (-not (Test-Path $Iso)) { throw "ファイルがありません: $Iso" } [Console]::Error.WriteLine('sha256 を計算しています(数分かかることがあります)…') $hash = (Get-FileHash -Algorithm SHA256 -Path $Iso).Hash.ToLowerInvariant() Write-Output $hash try { Set-Clipboard -Value $hash; [Console]::Error.WriteLine('クリップボードにも入れました。') } catch { } if (-not $VM -and -not $Shot) { exit 0 } } if (-not $VM) { throw '-VM に VirtualBox の VM の名前を入れてください(VBoxManage list vms で確かめられます)。' } $vbm = Find-VBoxManage # スクリーンショット: VM の画面をそのまま PNG で保存する if ($Shot) { if ($Shot -notmatch '^[a-z_]+$') { throw "場面の名前は英小文字と _ だけです(例: desktop / app_grid)。一覧は -Scenes。" } if ($SceneKeys -notcontains $Shot) { [Console]::Error.WriteLine("注意: $Shot は場面の一覧にありません。取り込むと未割り当てに入ります。") } $dir = Join-Path $OutDir ("{0}-{1}" -f ($VM -replace '[^\w\-]', '_'), (Get-Date -Format 'yyyyMMdd')) New-Item -ItemType Directory -Force -Path $dir | Out-Null $n = 1 while (Test-Path (Join-Path $dir ("{0}-{1:D2}.png" -f $Shot, $n))) { $n++ } $file = Join-Path $dir ("{0}-{1:D2}.png" -f $Shot, $n) & $vbm controlvm $VM screenshotpng $file if ($LASTEXITCODE -ne 0) { throw 'スクリーンショットを撮れませんでした(VM が動いていますか)。' } Add-Type -AssemblyName System.Drawing $img = [System.Drawing.Image]::FromFile($file) $size = "{0}x{1}" -f $img.Width, $img.Height $img.Dispose() Write-Output $file if ($size -ne '1440x900') { [Console]::Error.WriteLine("注意: 画面が $size です(手順 m1 は 1440x900)。VBoxManage controlvm `"$VM`" setvideomodehint 1440 900 32 で合わせられます。") } exit 0 } # 環境の記録 $info = @{} foreach ($line in (& $vbm showvminfo $VM --machinereadable)) { if ($line -match '^"?([^"=]+)"?="?(.*?)"?$') { $info[$Matches[1]] = $Matches[2] } } if ($LASTEXITCODE -ne 0 -or -not $info.ContainsKey('name')) { throw "VM「$VM」が見つかりません(VBoxManage list vms で名前を確かめてください)。" } # 実行モード: VT-x / AMD-V を直接使うか、Windows のハイパーバイザー(Hyper-V)経由か。直近のログから読む。 $engine = 'unknown' $engineLine = '' try { $log = (& $vbm showvminfo $VM --log 0) -join "`n" $nem = [regex]::Match($log, '(?m)^.*(fall back to NEM|NEMR3Init|WinHvPlatform|Hyper-V).*$') $hm = [regex]::Match($log, '(?m)^.*HMR3Init:.*(VT-x|AMD-V).*$') if ($nem.Success -and $log -match 'NEM') { $engine = 'hyperv'; $engineLine = $nem.Value.Trim() } elseif ($hm.Success) { $engine = 'native'; $engineLine = $hm.Value.Trim() } } catch { $engine = 'unknown' } $cs = Get-CimInstance Win32_ComputerSystem $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 $os = Get-CimInstance Win32_OperatingSystem $plan = '' try { $plan = ((powercfg /getactivescheme) -join ' ') -replace '^.*\((.*)\).*$', '$1' } catch { } $report = [ordered]@{ format = 'goodly-env' protocol = $Protocol captured_at = (Get-Date).ToString('yyyy-MM-ddTHH:mm:sszzz') hypervisor = 'virtualbox' hypervisor_version = ((& $vbm --version) -join '').Trim() execution_engine = $engine execution_log = $engineLine vm_name = $info['name'] vcpu = [int]$info['cpus'] memory_mb = [int]$info['memory'] vram_mb = [int]$info['vram'] firmware = $info['firmware'] graphics = $info['graphicscontroller'] accelerate_3d = $info['accelerate3d'] paravirt = $info['effparavirtprovider'] nic1 = $info['nic1'] guest_additions = $(if ($info.ContainsKey('GuestAdditionsVersion')) { $info['GuestAdditionsVersion'] } else { '' }) host_cpu = $cpu.Name.Trim() host_cores = [int]$cpu.NumberOfCores host_threads = [int]$cpu.NumberOfLogicalProcessors host_ram_gb = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1) host_os = ('{0} {1}' -f $os.Caption, $os.BuildNumber) host_hypervisor_present = [bool]$cs.HypervisorPresent host_power_plan = $plan } $warn = @() if ($report.vcpu -ne 4) { $warn += "vCPU が $($report.vcpu) です(手順 m1 は 4)。" } if ($report.memory_mb -ne 8192) { $warn += "メモリが $($report.memory_mb)MB です(手順 m1 は 8192MB)。" } if ($report.firmware -ne 'EFI') { $warn += 'ファームウェアが UEFI ではありません(手順 m1 は UEFI)。' } if ($report.graphics -ne 'vmsvga') { $warn += "グラフィックが $($report.graphics) です(手順 m1 は VMSVGA)。" } if ($report.accelerate_3d -eq 'on') { $warn += '3D アクセラレーションが有効です(手順 m1 は無効)。' } if ($report.nic1 -ne 'nat') { $warn += "ネットワークが $($report.nic1) です(手順 m1 は NAT)。" } if ($engine -eq 'hyperv') { $warn += 'VirtualBox は Windows のハイパーバイザー経由(Hyper-V)で動いています。計測の値は、この実行モードの環境として比べます。' } if ($engine -eq 'unknown') { $warn += '実行モードが分かりません(VM を一度起動してから、もう一度動かしてください)。' } $report.warnings = $warn $json = $report | ConvertTo-Json -Depth 3 Write-Output $json try { Set-Clipboard -Value $json; [Console]::Error.WriteLine('クリップボードにも入れました。図鑑データの「環境」に貼ってください。') } catch { } foreach ($w in $warn) { [Console]::Error.WriteLine("注意: $w") } # 図鑑データに送る(受け取りコードで人を決める。届いたものは「環境」の「+ 環境」で選べる) if ($Send) { if ($Send -notmatch '^[A-Za-z2-9]{4}-[A-Za-z2-9]{4}$') { throw "コードの形ではありません: $Send(例: K7Q2-9XMA)" } $tmp = Join-Path ([IO.Path]::GetTempPath()) ('goodly-env-{0}.json' -f [guid]::NewGuid().ToString('N')) [IO.File]::WriteAllText($tmp, $json, (New-Object Text.UTF8Encoding $false)) $url = '{0}/wp-json/linuxpro/v1/distro-db/collect-inbox?code={1}' -f $Base.TrimEnd('/'), $Send.ToUpperInvariant() $res = (& curl.exe -sS -H 'Content-Type: application/json' --data-binary "@$tmp" $url) -join '' Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue if ($res -match '"received":true') { [Console]::Error.WriteLine('図鑑データに送りました。「環境」の「+ 環境」で、届いた記録を選んでください。') } else { [Console]::Error.WriteLine("送れませんでした: $res(クリップボードの中身を貼っても登録できます)") } }