-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path01-Setup-ConversionEnvironment.ps1
More file actions
247 lines (214 loc) · 11 KB
/
01-Setup-ConversionEnvironment.ps1
File metadata and controls
247 lines (214 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#Requires -RunAsAdministrator
#Requires -Modules Hyper-V, FailoverClusters
<#
.SYNOPSIS
Sets up the environment for Gen 1 → Gen 2 VM conversion on a Hyper-V failover cluster.
.DESCRIPTION
This script:
- Validates Hyper-V failover cluster health
- Checks required PowerShell modules (Hyper-V, FailoverClusters)
- Creates a working directory for conversion artifacts
- Inventories all Gen 1 VMs across cluster nodes
- Exports full VM configurations for use by subsequent scripts
No Azure connectivity or Arc registration is performed.
This script is for the Hyper-V path only.
.PARAMETER ClusterName
Name of the Hyper-V failover cluster.
.PARAMETER WorkingDirectory
Path for conversion artifacts (VHDX backups, logs, configs). Ideally a CSV or shared volume.
.EXAMPLE
.\01-Setup-ConversionEnvironment.ps1 `
-ClusterName "HV-Cluster01" `
-WorkingDirectory "C:\ClusterStorage\Volume01\Gen2Conversion"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ClusterName,
[Parameter(Mandatory = $true)]
[string]$WorkingDirectory
)
# ── Global Settings ──────────────────────────────────────────────────────────
$ErrorActionPreference = 'Stop'
$LogFile = Join-Path $WorkingDirectory "ConversionSetup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$entry = "[$timestamp] [$Level] $Message"
Write-Host $entry -ForegroundColor $(switch ($Level) { "ERROR" { "Red" } "WARN" { "Yellow" } "SUCCESS" { "Green" } default { "Cyan" } })
if (Test-Path (Split-Path $LogFile -Parent)) {
Add-Content -Path $LogFile -Value $entry
}
}
# ── Step 1: Create Working Directory ─────────────────────────────────────────
Write-Log "═══════════════════════════════════════════════════════════════"
Write-Log " Hyper-V Gen1 → Gen2 Conversion - Environment Setup"
Write-Log "═══════════════════════════════════════════════════════════════"
Write-Log "Creating working directory: $WorkingDirectory"
$subDirs = @("Backups", "Configs", "Logs", "Temp", "Scripts")
foreach ($dir in $subDirs) {
$path = Join-Path $WorkingDirectory $dir
if (-not (Test-Path $path)) {
New-Item -Path $path -ItemType Directory -Force | Out-Null
Write-Log " Created: $path"
}
}
# Re-set log file now that directory exists
$LogFile = Join-Path $WorkingDirectory "Logs\ConversionSetup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
# ── Step 2: Validate Required PowerShell Modules ─────────────────────────────
Write-Log "Checking required PowerShell modules..."
$requiredModules = @(
@{ Name = "Hyper-V"; MinVersion = $null },
@{ Name = "FailoverClusters"; MinVersion = $null }
)
foreach ($mod in $requiredModules) {
$installed = Get-Module -ListAvailable -Name $mod.Name | Sort-Object Version -Descending | Select-Object -First 1
if (-not $installed) {
Write-Log " MISSING: $($mod.Name) — this module is built into Windows Server and should already be present." -Level "ERROR"
throw "$($mod.Name) module not found. Run this script on a Hyper-V cluster node."
}
else {
Write-Log " OK: $($mod.Name) v$($installed.Version)"
}
}
# ── Step 3: Validate Cluster Health ──────────────────────────────────────────
Write-Log "Validating Hyper-V failover cluster: $ClusterName"
try {
$cluster = Get-Cluster -Name $ClusterName
Write-Log " Cluster found: $($cluster.Name)" -Level "SUCCESS"
# Check cluster nodes
$nodes = Get-ClusterNode -Cluster $ClusterName
foreach ($node in $nodes) {
$status = if ($node.State -eq "Up") { "SUCCESS" } else { "WARN" }
Write-Log " Node: $($node.Name) - State: $($node.State)" -Level $status
}
# Check CSV health
$csvs = Get-ClusterSharedVolume -Cluster $ClusterName
foreach ($csv in $csvs) {
$state = $csv.SharedVolumeInfo.FaultState
$status = if ($state -eq "NoFaults") { "SUCCESS" } else { "WARN" }
Write-Log " CSV: $($csv.Name) - Fault State: $state" -Level $status
}
}
catch {
Write-Log "Failed to connect to cluster '$ClusterName': $_" -Level "ERROR"
throw
}
# ── Step 4: Inventory Current Gen 1 VMs ─────────────────────────────────────
Write-Log "Inventorying Gen 1 VMs across cluster nodes..."
$allVMs = @()
foreach ($node in $nodes) {
try {
$vms = Get-VM -ComputerName $node.Name | Where-Object { $_.Generation -eq 1 }
foreach ($vm in $vms) {
$vmInfo = [PSCustomObject]@{
VMName = $vm.Name
VMId = $vm.VMId
Generation = $vm.Generation
State = $vm.State
Host = $node.Name
MemoryMB = $vm.MemoryAssigned / 1MB
ProcessorCount = $vm.ProcessorCount
DynamicMemory = $vm.DynamicMemoryEnabled
VHDs = ($vm | Get-VMHardDiskDrive | Select-Object -ExpandProperty Path) -join "; "
NICs = ($vm | Get-VMNetworkAdapter | Select-Object -ExpandProperty SwitchName) -join "; "
CheckpointsExist = ($vm | Get-VMCheckpoint).Count -gt 0
}
$allVMs += $vmInfo
}
}
catch {
Write-Log " Could not query VMs on node $($node.Name): $_" -Level "WARN"
}
}
Write-Log "Found $($allVMs.Count) Gen 1 VMs across the cluster"
# Export inventory
$inventoryPath = Join-Path $WorkingDirectory "Configs\Gen1_VM_Inventory_$(Get-Date -Format 'yyyyMMdd').csv"
$allVMs | Export-Csv -Path $inventoryPath -NoTypeInformation
Write-Log " Inventory exported: $inventoryPath" -Level "SUCCESS"
# Display summary
Write-Log ""
Write-Log "── Gen 1 VM Inventory Summary ──"
foreach ($vm in $allVMs) {
$checkpointWarn = if ($vm.CheckpointsExist) { " [HAS CHECKPOINTS - Must remove first!]" } else { "" }
Write-Log " $($vm.VMName) | $($vm.State) | Host: $($vm.Host) | VHDs: $($vm.VHDs)$checkpointWarn"
}
# ── Step 5: Export Detailed VM Configurations ────────────────────────────────
Write-Log ""
Write-Log "Exporting detailed VM configurations for conversion..."
$configExportPath = Join-Path $WorkingDirectory "Configs"
foreach ($vm in $allVMs) {
try {
$vmDetail = Get-VM -Name $vm.VMName -ComputerName $vm.Host
$config = [PSCustomObject]@{
VMName = $vmDetail.Name
VMId = $vmDetail.VMId.ToString()
Generation = $vmDetail.Generation
Host = $vm.Host
State = $vmDetail.State.ToString()
ProcessorCount = $vmDetail.ProcessorCount
MemoryStartupMB = $vmDetail.MemoryStartup / 1MB
MemoryMinimumMB = $vmDetail.MemoryMinimum / 1MB
MemoryMaximumMB = $vmDetail.MemoryMaximum / 1MB
DynamicMemory = $vmDetail.DynamicMemoryEnabled
AutomaticStartAction = $vmDetail.AutomaticStartAction.ToString()
AutomaticStopAction = $vmDetail.AutomaticStopAction.ToString()
Notes = $vmDetail.Notes
}
# Get disk details
$disks = Get-VMHardDiskDrive -VM $vmDetail
$diskConfigs = @()
foreach ($disk in $disks) {
$vhd = Get-VHD -Path $disk.Path -ComputerName $vm.Host -ErrorAction SilentlyContinue
$diskConfigs += [PSCustomObject]@{
ControllerType = $disk.ControllerType.ToString()
ControllerNumber = $disk.ControllerNumber
ControllerLocation = $disk.ControllerLocation
Path = $disk.Path
VhdType = if ($vhd) { $vhd.VhdType.ToString() } else { "Unknown" }
SizeGB = if ($vhd) { [math]::Round($vhd.Size / 1GB, 2) } else { 0 }
FileSizeGB = if ($vhd) { [math]::Round($vhd.FileSize / 1GB, 2) } else { 0 }
VhdFormat = if ($vhd) { $vhd.VhdFormat.ToString() } else { "Unknown" }
}
}
# Get NIC details
$nics = Get-VMNetworkAdapter -VM $vmDetail
$nicConfigs = @()
foreach ($nic in $nics) {
$nicConfigs += [PSCustomObject]@{
Name = $nic.Name
SwitchName = $nic.SwitchName
MacAddress = $nic.MacAddress
VlanId = (Get-VMNetworkAdapterVlan -VMNetworkAdapter $nic).AccessVlanId
IsLegacy = $nic.IsLegacy
IPAddresses = ($nic.IPAddresses -join ", ")
}
}
# Save full config as JSON
$fullConfig = @{
VM = $config
Disks = $diskConfigs
NICs = $nicConfigs
}
$jsonPath = Join-Path $configExportPath "$($vm.VMName)_config.json"
$fullConfig | ConvertTo-Json -Depth 5 | Out-File -FilePath $jsonPath -Encoding UTF8
Write-Log " Config exported: $jsonPath"
}
catch {
Write-Log " Failed to export config for $($vm.VMName): $_" -Level "ERROR"
}
}
# ── Summary ──────────────────────────────────────────────────────────────────
Write-Log ""
Write-Log "═══════════════════════════════════════════════════════════════"
Write-Log " Environment Setup Complete"
Write-Log "═══════════════════════════════════════════════════════════════"
Write-Log " Working Directory: $WorkingDirectory"
Write-Log " Gen 1 VMs Found: $($allVMs.Count)"
Write-Log " Inventory CSV: $inventoryPath"
Write-Log " VM Configs: $configExportPath"
Write-Log " Log File: $LogFile"
Write-Log ""
Write-Log " Next Step: Run 02-Convert-MBRtoGPT.ps1 inside each guest VM,"
Write-Log " then run 03-Convert-Gen1toGen2.ps1 from this cluster node."
Write-Log "═══════════════════════════════════════════════════════════════"