-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHyperV-Manager.ps1
More file actions
1190 lines (1031 loc) · 51.8 KB
/
HyperV-Manager.ps1
File metadata and controls
1190 lines (1031 loc) · 51.8 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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<#
.SYNOPSIS
Enhanced menu-driven toolkit for managing Hyper-V virtual machines.
.DESCRIPTION
Provides an interactive CLI menu to list VMs, view paths, start/stop VMs, and more.
Features comprehensive error handling, proper memory display, VHD file validation,
and optional report generation. Designed for safe handoff and future-proofing
with clear prompts and validation.
All functionality is wrapped in the Start-HyperVToolkit function for modularity
and compatibility with both file execution and copy/paste usage.
.PARAMETER NoExit
Prevents the script from pausing at the end when run in console mode.
.FEATURES
- Administrator privilege verification
- Hyper-V module availability checking
- Colored output for better readability
- Proper memory formatting (MB/GB)
- VHD file existence validation with sizes
- Optional report export with timestamps
- Enhanced VM state management
- Graceful error handling throughout
- Function-based architecture for modularity
.EXAMPLES
# Run the script file
.\HyperV-ListVMs.ps1
# Run with no exit pause
.\HyperV-ListVMs.ps1 -NoExit
# Copy/paste into PowerShell and run
Start-HyperVToolkit
# Run in PowerShell ISE or VS Code (no pause needed)
Start-HyperVToolkit -NoExit
.NOTES
- Requires Hyper-V feature enabled and PowerShell module installed
- Requires Administrator privileges
- Easily extendable with new menu items
- All functions use approved PowerShell verbs
- Function-based design allows flexible execution methods
.AUTHOR
Mike Brown
.VERSION
2.0 - Enhanced with comprehensive error handling and improved UX
.DATE
2025-10-09
#>
# Script startup and execution policy check
param(
[switch]$NoExit
)
#region Configuration - File Locations and URLs
# ============================================================================
# CONFIGURATION SECTION - Customize these paths and URLs as needed
# ============================================================================
# Base directory for all VM files (VMs, VHDs, and ISOs)
$script:VMBaseDirectory = "Z:\Hyper-V\Active_VMs"
# Download URLs for operating system ISOs and images
# Note: Amazon Linux uses a pre-configured VHDX in a ZIP file (AWS Hyper-V specific)
$script:ISODownloadUrls = @{
UbuntuServer = "https://releases.ubuntu.com/24.04.3/ubuntu-24.04.3-live-server-amd64.iso"
AmazonLinux = "https://cdn.amazonlinux.com/os-images/2.0.20250929.2/hyperv/amzn2-hyperv-2.0.20250929.2-x86_64.xfs.gpt.vhdx.zip"
}
# ISO file naming patterns (and VHDX for Amazon Linux)
$script:ISOFileNames = @{
UbuntuServer = "ubuntu-24.04.3-live-server-amd64.iso"
AmazonLinux = "amzn2-hyperv-2.0.20250929.2-x86_64.xfs.gpt.vhdx.zip"
}
# Helper function to get VM directory path
function Get-VMDirectory {
param([string]$VMName)
return Join-Path $script:VMBaseDirectory $VMName
}
# Helper function to get ISO file path
function Get-ISOPath {
param(
[string]$VMName,
[ValidateSet("UbuntuServer", "AmazonLinux")]
[string]$OSType
)
$vmDir = Get-VMDirectory -VMName $VMName
return Join-Path $vmDir $script:ISOFileNames[$OSType]
}
# Helper function to get Amazon Linux VHDX path (extracted from zip)
function Get-AmazonLinuxVHDXPath {
param([string]$VMName)
$vmDir = Get-VMDirectory -VMName $VMName
return Join-Path $vmDir "amzn2-hyperv-2.0.20250929.2-x86_64.xfs.gpt.vhdx"
}
# Helper function to get VHD file path
function Get-VHDPath {
param([string]$VMName)
$vmDir = Get-VMDirectory -VMName $VMName
return Join-Path $vmDir "$VMName.vhdx"
}
#endregion Configuration
function Start-HyperVToolkit {
[CmdletBinding()]
param(
[switch]$NoExit
)
# Check PowerShell execution policy
$executionPolicy = Get-ExecutionPolicy
if ($executionPolicy -eq 'Restricted') {
Write-Host "PowerShell execution policy is set to 'Restricted'." -ForegroundColor Red
Write-Host "This script cannot run with the current execution policy." -ForegroundColor Red
Write-Host "To fix this, run PowerShell as Administrator and execute:" -ForegroundColor Yellow
Write-Host "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser" -ForegroundColor Cyan
Write-Host "`nPress Enter to exit..." -ForegroundColor Yellow
Read-Host
return
}
Write-Host "=== Hyper-V VM Toolkit Starting ===" -ForegroundColor Cyan
Write-Host "Execution Policy: $executionPolicy" -ForegroundColor Gray
function Confirm-AdminMode {
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "`n[ERROR] This script must be run as Administrator." -ForegroundColor Red
Write-Host "`nTo run PowerShell as Administrator:" -ForegroundColor Yellow
Write-Host "1. Close this PowerShell window" -ForegroundColor White
Write-Host "2. Press Win+X and select 'Windows PowerShell (Admin)' or 'Terminal (Admin)'" -ForegroundColor White
Write-Host "3. Or search 'PowerShell' in Start Menu, right-click, and select 'Run as Administrator'" -ForegroundColor White
Write-Host "4. Navigate back to this script location and run it again" -ForegroundColor White
Write-Host "`nAlternatively, you can run this command to restart PowerShell as Admin:" -ForegroundColor Cyan
Write-Host "Start-Process PowerShell -Verb RunAs" -ForegroundColor Green
Write-Host "`nScript execution stopped." -ForegroundColor Red
Wait-ForKeyPress
throw "Administrator privileges required"
} else {
Write-Host "[SUCCESS] Running in Administrator mode." -ForegroundColor Green
}
}
function Test-HyperVModule {
Write-Host "Checking Hyper-V availability..." -ForegroundColor Cyan
try {
# Check if we're on Windows
if ($PSVersionTable.Platform -eq 'Unix') {
throw "This script only works on Windows systems with Hyper-V."
}
# Check Windows edition (Hyper-V is not available on Home editions)
$windowsEdition = (Get-CimInstance -ClassName Win32_OperatingSystem).Caption
Write-Host "Windows Edition: $windowsEdition" -ForegroundColor Gray
if ($windowsEdition -match "Home") {
throw "Hyper-V is not available on Windows Home editions. You need Windows Pro, Enterprise, or Education."
}
# Check if Hyper-V feature is enabled
Write-Host "Checking Hyper-V Windows feature..." -ForegroundColor Gray
$hyperVEnabled = $false
# Try multiple methods to check Hyper-V status
try {
# Method 1: Try Get-WindowsOptionalFeature with timeout
Write-Host " Attempting Windows feature check (this may take a moment)..." -ForegroundColor DarkGray
$job = Start-Job -ScriptBlock {
Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All -ErrorAction Stop
}
# Wait for job with timeout (2 minutes) and show progress
$timeout = 120
$elapsed = 0
$completed = $null
while ($elapsed -lt $timeout -and -not $completed) {
$completed = Wait-Job -Job $job -Timeout 1
if (-not $completed) {
$elapsed++
if ($elapsed % 15 -eq 0) {
Write-Host " Still checking... $elapsed seconds" -ForegroundColor DarkGray
}
}
}
if ($completed) {
$hyperVFeature = Receive-Job -Job $job
Remove-Job -Job $job
if ($hyperVFeature.State -eq "Enabled") {
Write-Host " Hyper-V feature is enabled (confirmed via Windows Features)." -ForegroundColor Green
$hyperVEnabled = $true
} else {
Write-Host " Hyper-V feature status: $($hyperVFeature.State)" -ForegroundColor Red
throw "Hyper-V feature is not enabled. Please enable it through Windows Features."
}
} else {
# Job timed out
Remove-Job -Job $job -Force
Write-Host " Windows feature check timed out (this is common and OK)" -ForegroundColor Yellow
throw "Windows feature check timed out"
}
} catch {
Write-Host " Primary feature check failed: $($_.Exception.Message)" -ForegroundColor Yellow
# Method 2: Try alternative detection via registry/services
Write-Host " Trying alternative Hyper-V detection methods..." -ForegroundColor DarkGray
try {
# Check if Hyper-V service exists
$hvService = Get-Service -Name "vmms" -ErrorAction SilentlyContinue
if ($hvService) {
Write-Host " Hyper-V Management Service found (service method)." -ForegroundColor Green
$hyperVEnabled = $true
} else {
# Check via DISM as last resort
Write-Host " Checking via DISM command..." -ForegroundColor DarkGray
$dismResult = & dism /online /get-featureinfo /featurename:Microsoft-Hyper-V-All 2>$null
if ($dismResult -and ($dismResult -join "" -match "State.*Enabled")) {
Write-Host " Hyper-V feature found enabled (DISM method)." -ForegroundColor Green
$hyperVEnabled = $true
}
}
} catch {
Write-Host " Alternative detection methods also failed." -ForegroundColor Yellow
}
}
if (-not $hyperVEnabled) {
Write-Host "`nHyper-V does not appear to be properly installed or enabled." -ForegroundColor Red
Write-Host "To install Hyper-V, run these commands:" -ForegroundColor Yellow
Write-Host "1. Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All" -ForegroundColor Cyan
Write-Host "2. Restart your computer" -ForegroundColor Cyan
Write-Host "3. Run this script again" -ForegroundColor Cyan
throw "Hyper-V feature verification failed."
}
# Check if Hyper-V PowerShell module is available
Write-Host "Checking Hyper-V PowerShell module..." -ForegroundColor Gray
$hyperVModule = Get-Module -ListAvailable -Name Hyper-V -ErrorAction SilentlyContinue
if (-not $hyperVModule) {
Write-Host "Hyper-V PowerShell module not found." -ForegroundColor Red
Write-Host "Please install Hyper-V Management Tools:" -ForegroundColor Yellow
Write-Host "1. Open 'Turn Windows features on or off'" -ForegroundColor White
Write-Host "2. Expand 'Hyper-V' and check 'Hyper-V Management Tools'" -ForegroundColor White
Write-Host "3. Or run: Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Tools-All" -ForegroundColor Cyan
throw "Hyper-V PowerShell module is not available."
}
Write-Host "Hyper-V module found. Version: $($hyperVModule.Version)" -ForegroundColor Green
# Import the module
Write-Host "Loading Hyper-V module..." -ForegroundColor Gray
try {
Import-Module Hyper-V -ErrorAction Stop
Write-Host "Hyper-V module loaded successfully." -ForegroundColor Green
} catch {
Write-Host "Failed to import Hyper-V module." -ForegroundColor Red
Write-Host "This often means Hyper-V Management Tools are not properly installed." -ForegroundColor Yellow
throw "Could not import Hyper-V module: $($_.Exception.Message)"
}
# Test basic Hyper-V connectivity
Write-Host "Testing Hyper-V service..." -ForegroundColor Gray
try {
$null = Get-VMHost -ErrorAction Stop
Write-Host "Hyper-V service is accessible." -ForegroundColor Green
} catch {
Write-Host "Cannot access Hyper-V service." -ForegroundColor Red
# Check if it's a "Class not registered" error
if ($_.Exception.Message -like "*Class not registered*") {
Write-Host "`nThis 'Class not registered' error usually means:" -ForegroundColor Yellow
Write-Host "1. Hyper-V Management Tools are not installed" -ForegroundColor White
Write-Host "2. Windows needs a restart after Hyper-V installation" -ForegroundColor White
Write-Host "3. Some Hyper-V components are missing" -ForegroundColor White
Write-Host "`nTo fix this:" -ForegroundColor Cyan
Write-Host "1. Run: Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell" -ForegroundColor Green
Write-Host "2. Run: Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Tools-All" -ForegroundColor Green
Write-Host "3. Restart your computer" -ForegroundColor Green
Write-Host "4. Run this script again" -ForegroundColor Green
}
throw "Hyper-V service test failed: $($_.Exception.Message)"
}
} catch {
Write-Host "`nHyper-V Check Failed:" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
if ($_.Exception.Message -like "*Access is denied*") {
Write-Host "`nThis might be because:" -ForegroundColor Yellow
Write-Host "1. Script is not running as Administrator" -ForegroundColor Yellow
Write-Host "2. Hyper-V service is not running" -ForegroundColor Yellow
Write-Host "3. User is not in Hyper-V Administrators group" -ForegroundColor Yellow
}
Write-Host "`nPress Enter to exit..." -ForegroundColor Yellow
Read-Host
throw $_
}
}
# Call initialization functions
try {
Write-Host "Initializing Hyper-V VM Toolkit..." -ForegroundColor Cyan
Confirm-AdminMode
Test-HyperVModule
Write-Host "Initialization complete. Starting main menu..." -ForegroundColor Green
} catch {
Write-Host "`nCRITICAL ERROR during initialization:" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host "`nPress Enter to exit..." -ForegroundColor Yellow
Read-Host
return
}
# Main loop with error handling
$exitLoop = $false
try {
do {
Show-Menu
$choice = Read-Host "Select an option (1-6)"
switch ($choice) {
"1" { Get-VMList }
"2" { Show-VMPaths }
"3" { Start-VMInteractive }
"4" { Stop-VMInteractive }
"5" { New-VMFromTemplate }
"6" {
$confirm = Read-Host "Are you sure you want to exit? (Y/N)"
if ($confirm -match '^[Yy]$') {
Write-Host "Exiting Hyper-V VM Toolkit..." -ForegroundColor Green
$exitLoop = $true
}
}
default {
Write-Host "Invalid selection. Please choose 1-6." -ForegroundColor Red
Wait-ForKeyPress
}
}
} while (-not $exitLoop)
} catch {
Write-Host "`nUNEXPECTED ERROR in main loop:" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host "Stack Trace:" -ForegroundColor Yellow
Write-Host $_.ScriptStackTrace -ForegroundColor Yellow
Write-Host "`nPress Enter to exit..." -ForegroundColor Yellow
Read-Host
return
}
Write-Host "`nThank you for using Hyper-V VM Toolkit!" -ForegroundColor Cyan
# Prevent window from closing if script was run directly (not from ISE or VS Code)
if (-not $NoExit -and $Host.Name -eq "ConsoleHost") {
Write-Host "Press Enter to close..." -ForegroundColor Gray
Read-Host
}
}
function Show-Menu {
Clear-Host
Write-Host "Hyper-V VM Toolkit"
Write-Host "==================="
Write-Host "1. List all registered VMs"
Write-Host "2. Show VM paths"
Write-Host "3. Start a VM"
Write-Host "4. Stop a VM"
Write-Host "5. Create new VM from template"
Write-Host "6. Exit"
Write-Host ""
}
function Wait-ForKeyPress {
Write-Host "`nPress Enter to continue..." -ForegroundColor Gray
[void][System.Console]::ReadLine()
}
function Get-VMList {
try {
Write-Host "Retrieving VM information..." -ForegroundColor Cyan
$vms = Get-VM -ErrorAction Stop
if ($vms.Count -eq 0) {
Write-Host "No VMs found on this system." -ForegroundColor Yellow
} else {
Write-Host "`nRegistered Virtual Machines:" -ForegroundColor Green
Write-Host "============================" -ForegroundColor Green
# Create custom objects with properly formatted memory
$vmList = $vms | ForEach-Object {
$memoryMB = [math]::Round($_.MemoryStartup / 1MB, 0)
[PSCustomObject]@{
Name = $_.Name
State = $_.State
'Memory (MB)' = $memoryMB
Generation = $_.Generation
'Uptime' = if ($_.State -eq 'Running') { $_.Uptime.ToString("dd\.hh\:mm\:ss") } else { "N/A" }
}
}
$vmList | Format-Table -AutoSize
Write-Host "Total VMs: $($vms.Count)" -ForegroundColor Cyan
}
} catch {
Write-Host "Error retrieving VM information: $_" -ForegroundColor Red
}
Wait-ForKeyPress
}
function Show-VMPaths {
try {
Write-Host "Retrieving VM path information..." -ForegroundColor Cyan
$vms = Get-VM -ErrorAction Stop
if ($vms.Count -eq 0) {
Write-Host "No VMs found on this system." -ForegroundColor Yellow
Wait-ForKeyPress
return
}
$report = @()
foreach ($vm in $vms) {
try {
$vhdDrives = Get-VMHardDiskDrive -VMName $vm.Name -ErrorAction Stop
$vhdPaths = @()
foreach ($drive in $vhdDrives) {
$path = $drive.Path
if ($path) {
if (Test-Path $path -ErrorAction SilentlyContinue) {
$fileSize = (Get-Item $path -ErrorAction SilentlyContinue).Length
$fileSizeGB = if ($fileSize) { [math]::Round($fileSize / 1GB, 2) } else { "Unknown" }
$vhdPaths += "$path (${fileSizeGB} GB)"
} else {
$vhdPaths += "[MISSING] $path"
}
}
}
$joinedPaths = if ($vhdPaths.Count -gt 0) { $vhdPaths -join "`n " } else { "No VHD files found" }
$memoryMB = [math]::Round($vm.MemoryStartup / 1MB, 0)
Write-Host "`nVM: $($vm.Name)" -ForegroundColor White
Write-Host " State: $($vm.State)" -ForegroundColor Gray
Write-Host " Memory: ${memoryMB} MB" -ForegroundColor Gray
Write-Host " Generation: $($vm.Generation)" -ForegroundColor Gray
Write-Host " Config Path: $($vm.ConfigurationLocation)" -ForegroundColor Gray
Write-Host " VHD Path(s): $joinedPaths" -ForegroundColor Gray
Write-Host "---------------------------------------------" -ForegroundColor DarkGray
$report += [PSCustomObject]@{
Name = $vm.Name
State = $vm.State
'Memory (MB)' = $memoryMB
Generation = $vm.Generation
ConfigPath = $vm.ConfigurationLocation
VHDPaths = ($vhdPaths -join "; ")
}
} catch {
Write-Host "Error processing VM '$($vm.Name)': $_" -ForegroundColor Red
}
}
# Ask user if they want to export to file
$exportChoice = Read-Host "`nWould you like to export this information to a file? (Y/N)"
if ($exportChoice -match '^[Yy]') {
try {
$reportPath = "$env:USERPROFILE\Desktop\VM_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
$report | Format-Table -AutoSize | Out-File $reportPath -Encoding UTF8
Write-Host "Report exported to: $reportPath" -ForegroundColor Green
} catch {
Write-Host "Failed to export report: $_" -ForegroundColor Red
}
}
} catch {
Write-Host "Error retrieving VM path information: $_" -ForegroundColor Red
}
Wait-ForKeyPress
}
function Select-VMByNumber {
try {
$vms = Get-VM -ErrorAction Stop
if ($vms.Count -eq 0) {
Write-Host "No VMs found on this system." -ForegroundColor Yellow
return $null
}
Write-Host "`nAvailable Virtual Machines:" -ForegroundColor Green
Write-Host "===========================" -ForegroundColor Green
for ($i = 0; $i -lt $vms.Count; $i++) {
$stateColor = switch ($vms[$i].State) {
'Running' { 'Green' }
'Off' { 'Gray' }
'Saved' { 'Yellow' }
default { 'White' }
}
Write-Host "$($i + 1). " -NoNewline -ForegroundColor Cyan
Write-Host "$($vms[$i].Name) " -NoNewline -ForegroundColor White
Write-Host "[$($vms[$i].State)]" -ForegroundColor $stateColor
}
do {
$selection = Read-Host "`nEnter the number of the VM (1-$($vms.Count)) or 'C' to cancel"
if ($selection -match '^[Cc]$') {
return $null
}
if ($selection -match '^\d+$' -and $selection -ge 1 -and $selection -le $vms.Count) {
return $vms[$selection - 1].Name
} else {
Write-Host "Invalid selection. Please enter a number between 1 and $($vms.Count), or 'C' to cancel." -ForegroundColor Red
}
} while ($true)
} catch {
Write-Host "Error retrieving VM list: $_" -ForegroundColor Red
return $null
}
}
function Start-VMInteractive {
$vmName = Select-VMByNumber
if ($vmName) {
try {
$vm = Get-VM -Name $vmName -ErrorAction Stop
if ($vm.State -eq 'Running') {
Write-Host "VM '$vmName' is already running." -ForegroundColor Yellow
} else {
Write-Host "Starting VM '$vmName'..." -ForegroundColor Cyan
Start-VM -Name $vmName -ErrorAction Stop
Write-Host "VM '$vmName' started successfully." -ForegroundColor Green
}
} catch {
Write-Host "Failed to start VM '$vmName': $_" -ForegroundColor Red
}
} else {
Write-Host "Operation cancelled." -ForegroundColor Yellow
}
Wait-ForKeyPress
}
function Stop-VMInteractive {
$vmName = Select-VMByNumber
if ($vmName) {
try {
$vm = Get-VM -Name $vmName -ErrorAction Stop
if ($vm.State -eq 'Off') {
Write-Host "VM '$vmName' is already stopped." -ForegroundColor Yellow
} else {
# Ask for confirmation before force stopping
$forceStop = Read-Host "Force stop VM '$vmName'? This may cause data loss. (Y/N)"
if ($forceStop -match '^[Yy]$') {
Write-Host "Stopping VM '$vmName'..." -ForegroundColor Cyan
Stop-VM -Name $vmName -Force -ErrorAction Stop
Write-Host "VM '$vmName' stopped successfully." -ForegroundColor Green
} else {
Write-Host "Stop operation cancelled." -ForegroundColor Yellow
}
}
} catch {
Write-Host "Failed to stop VM '$vmName': $_" -ForegroundColor Red
}
} else {
Write-Host "Operation cancelled." -ForegroundColor Yellow
}
Wait-ForKeyPress
}
function New-VMFromTemplate {
Write-Host "VM Template Creator" -ForegroundColor Cyan
Write-Host "===================" -ForegroundColor Cyan
Write-Host "1. Ubuntu Server (Latest LTS)"
Write-Host "2. Amazon Linux 2"
Write-Host "3. Custom ISO"
Write-Host "4. Cancel"
Write-Host ""
$choice = Read-Host "Select template (1-4)"
switch ($choice) {
"1" { New-UbuntuServerVM }
"2" { New-AmazonLinuxVM }
"3" { New-CustomISOVM }
"4" { Write-Host "Operation cancelled." -ForegroundColor Yellow }
default {
Write-Host "Invalid selection." -ForegroundColor Red
}
}
Wait-ForKeyPress
}
function New-UbuntuServerVM {
# Always use the latest LTS version (Ubuntu 24.04)
$Version = "24.04"
Write-Host "`nCreating Ubuntu Server $Version VM..." -ForegroundColor Green
# Get VM details from user
$vmName = Read-Host "Enter VM name (e.g., Ubuntu-Server-$Version-Test)"
if ([string]::IsNullOrWhiteSpace($vmName)) {
Write-Host "VM name cannot be empty." -ForegroundColor Red
return
}
# Check if VM already exists
if (Get-VM -Name $vmName -ErrorAction SilentlyContinue) {
Write-Host "VM '$vmName' already exists." -ForegroundColor Red
return
}
$memoryMB = Read-Host "Enter memory in MB (default: 2048)"
if ([string]::IsNullOrWhiteSpace($memoryMB)) { $memoryMB = "2048" }
$cpuCores = Read-Host "Enter number of CPU cores (default: 2)"
if ([string]::IsNullOrWhiteSpace($cpuCores)) { $cpuCores = "2" }
$diskSizeGB = Read-Host "Enter disk size in GB (default: 20)"
if ([string]::IsNullOrWhiteSpace($diskSizeGB)) { $diskSizeGB = "20" }
# Set up paths using centralized configuration
$vmPath = Get-VMDirectory -VMName $vmName
$vhdPath = Get-VHDPath -VMName $vmName
# Ubuntu Server 24.04 LTS download URL and path
$isoUrl = $script:ISODownloadUrls.UbuntuServer
$isoPath = Get-ISOPath -VMName $vmName -OSType "UbuntuServer"
try {
# Create VM directory
Write-Host "Creating VM directory..." -ForegroundColor Cyan
if (-not (Test-Path $vmPath)) {
New-Item -Path $vmPath -ItemType Directory -Force | Out-Null
}
# Download ISO if not exists
if (-not (Test-Path $isoPath)) {
Write-Host "Downloading Ubuntu Server $Version ISO (this may take several minutes)..." -ForegroundColor Yellow
Write-Host "Download URL: $isoUrl" -ForegroundColor Gray
try {
# Use BITS transfer for better progress and resume capability
Import-Module BitsTransfer -ErrorAction SilentlyContinue
if (Get-Module BitsTransfer) {
Start-BitsTransfer -Source $isoUrl -Destination $isoPath -DisplayName "Ubuntu Server $Version Download"
} else {
# Fallback to Invoke-WebRequest
Invoke-WebRequest -Uri $isoUrl -OutFile $isoPath -UseBasicParsing
}
Write-Host "Download completed successfully." -ForegroundColor Green
} catch {
Write-Host "Download failed: $_" -ForegroundColor Red
Write-Host "You can manually download from: $isoUrl" -ForegroundColor Yellow
Write-Host "Save it as: $isoPath" -ForegroundColor Yellow
return
}
} else {
Write-Host "Using existing ISO file: $isoPath" -ForegroundColor Green
}
# Create the VM
Write-Host "Creating virtual machine..." -ForegroundColor Cyan
$vm = New-VM -Name $vmName -MemoryStartupBytes ([int64]$memoryMB * 1MB) -Path $vmPath -Generation 2
# Create and attach VHD
Write-Host "Creating virtual hard disk ($diskSizeGB GB)..." -ForegroundColor Cyan
New-VHD -Path $vhdPath -SizeBytes ([int64]$diskSizeGB * 1GB) -Dynamic
Add-VMHardDiskDrive -VM $vm -Path $vhdPath
# Attach ISO
Write-Host "Attaching ISO file..." -ForegroundColor Cyan
Add-VMDvdDrive -VM $vm -Path $isoPath
# Configure VM settings for Linux
Write-Host "Configuring VM settings..." -ForegroundColor Cyan
Set-VMProcessor -VM $vm -Count ([int]$cpuCores)
Set-VMMemory -VM $vm -DynamicMemoryEnabled $false
# Set firmware settings for Generation 2 VM
Set-VMFirmware -VM $vm -EnableSecureBoot Off
$dvdDrive = Get-VMDvdDrive -VM $vm
Set-VMFirmware -VM $vm -FirstBootDevice $dvdDrive
# Configure network adapter for internet connectivity
Write-Host "Configuring network adapter for internet connectivity..." -ForegroundColor Cyan
try {
# Get the default switch (Hyper-V's built-in NAT switch)
$defaultSwitch = Get-VMSwitch -Name "Default Switch" -ErrorAction SilentlyContinue
if ($defaultSwitch) {
Connect-VMNetworkAdapter -VM $vm -SwitchName "Default Switch"
Write-Host "Network adapter connected to Default Switch for internet access." -ForegroundColor Green
} else {
# If Default Switch doesn't exist, try to find any external switch
$externalSwitches = Get-VMSwitch | Where-Object { $_.SwitchType -eq "External" }
if ($externalSwitches.Count -gt 0) {
$switchName = $externalSwitches[0].Name
Connect-VMNetworkAdapter -VM $vm -SwitchName $switchName
Write-Host "Network adapter connected to external switch: $switchName" -ForegroundColor Green
} else {
Write-Host "WARNING: No suitable virtual switch found. VM may not have internet access." -ForegroundColor Yellow
Write-Host "After VM creation, connect the network adapter to a virtual switch in Hyper-V Manager." -ForegroundColor Yellow
}
}
} catch {
Write-Host "WARNING: Could not configure network adapter: $_" -ForegroundColor Yellow
Write-Host "You may need to manually connect the network adapter to a virtual switch." -ForegroundColor Yellow
}
# Enable integration services
Enable-VMIntegrationService -VM $vm -Name "Guest Service Interface"
Enable-VMIntegrationService -VM $vm -Name "Heartbeat"
Enable-VMIntegrationService -VM $vm -Name "Key-Value Pair Exchange"
Enable-VMIntegrationService -VM $vm -Name "Shutdown"
Enable-VMIntegrationService -VM $vm -Name "Time Synchronization"
Enable-VMIntegrationService -VM $vm -Name "VSS"
Write-Host "`nVM '$vmName' created successfully!" -ForegroundColor Green
Write-Host "VM Path: $vmPath" -ForegroundColor Gray
Write-Host "ISO Path: $isoPath" -ForegroundColor Gray
Write-Host "Memory: $memoryMB MB" -ForegroundColor Gray
Write-Host "CPU Cores: $cpuCores" -ForegroundColor Gray
Write-Host "Disk: $diskSizeGB GB" -ForegroundColor Gray
$startNow = Read-Host "`nWould you like to start the VM now? (Y/N)"
if ($startNow -match '^[Yy]$') {
Start-VM -VM $vm
Write-Host "VM started. Connect via Hyper-V Manager to complete Ubuntu installation." -ForegroundColor Green
}
} catch {
Write-Host "Failed to create VM: $_" -ForegroundColor Red
# Cleanup on failure
if (Get-VM -Name $vmName -ErrorAction SilentlyContinue) {
Remove-VM -Name $vmName -Force
}
if (Test-Path $vhdPath) {
Remove-Item $vhdPath -Force
}
}
}
function New-AmazonLinuxVM {
Write-Host "`nCreating Amazon Linux 2 VM (following AWS documentation)..." -ForegroundColor Green
Write-Host "This process will create a seed.iso for initial configuration as per AWS requirements." -ForegroundColor Yellow
# Get VM details from user
$vmName = Read-Host "Enter VM name (e.g., AmazonLinux-2-Test)"
if ([string]::IsNullOrWhiteSpace($vmName)) {
Write-Host "VM name cannot be empty." -ForegroundColor Red
return
}
# Check if VM already exists
if (Get-VM -Name $vmName -ErrorAction SilentlyContinue) {
Write-Host "VM '$vmName' already exists." -ForegroundColor Red
return
}
$memoryMB = Read-Host "Enter memory in MB (default: 2048)"
if ([string]::IsNullOrWhiteSpace($memoryMB)) { $memoryMB = "2048" }
$cpuCores = Read-Host "Enter number of CPU cores (default: 2)"
if ([string]::IsNullOrWhiteSpace($cpuCores)) { $cpuCores = "2" }
# Get user configuration for seed.iso
Write-Host "`nConfiguring initial VM settings (required for Amazon Linux 2):" -ForegroundColor Cyan
$hostname = Read-Host "Enter VM hostname (default: amazonlinux-vm)"
if ([string]::IsNullOrWhiteSpace($hostname)) { $hostname = "amazonlinux-vm" }
$ec2Password = Read-Host "Enter password for ec2-user account (default: AmazonLinux123!)"
if ([string]::IsNullOrWhiteSpace($ec2Password)) { $ec2Password = "AmazonLinux123!" }
$useStaticIP = Read-Host "Configure static IP? (Y/N, default: N - use DHCP)"
$staticIPConfig = ""
if ($useStaticIP -match '^[Yy]$') {
$ipAddress = Read-Host "Enter IP address (e.g., 192.168.1.100)"
$netmask = Read-Host "Enter netmask (default: 255.255.255.0)"
$gateway = Read-Host "Enter gateway (e.g., 192.168.1.1)"
if ([string]::IsNullOrWhiteSpace($netmask)) { $netmask = "255.255.255.0" }
$staticIPConfig = @"
network-interfaces: |
auto eth0
iface eth0 inet static
address $ipAddress
netmask $netmask
gateway $gateway
"@
}
# Set up paths using centralized configuration
$vmPath = Get-VMDirectory -VMName $vmName
$zipUrl = $script:ISODownloadUrls.AmazonLinux
$zipPath = Get-ISOPath -VMName $vmName -OSType "AmazonLinux"
$vhdxPath = Get-AmazonLinuxVHDXPath -VMName $vmName
$seedConfigPath = Join-Path $vmPath "seedconfig"
$seedIsoPath = Join-Path $vmPath "seed.iso"
try {
# Create VM directory
Write-Host "Creating VM directory..." -ForegroundColor Cyan
if (-not (Test-Path $vmPath)) {
New-Item -Path $vmPath -ItemType Directory -Force | Out-Null
}
# Create seed.iso configuration (Step 1 from AWS docs)
Write-Host "Creating seed.iso configuration files..." -ForegroundColor Cyan
if (-not (Test-Path $seedConfigPath)) {
New-Item -Path $seedConfigPath -ItemType Directory -Force | Out-Null
}
# Create meta-data file
$metaDataContent = @"
local-hostname: $hostname
$staticIPConfig
"@
$metaDataPath = Join-Path $seedConfigPath "meta-data"
Set-Content -Path $metaDataPath -Value $metaDataContent -Encoding UTF8
# Create user-data file
$userDataContent = @"
#cloud-config
#vim:syntax=yaml
users:
# A user by the name `ec2-user` is created in the image by default.
- default
chpasswd:
list: |
ec2-user:$ec2Password
# In the above line, do not add any spaces after 'ec2-user:'.
write_files:
- path: /etc/cloud/cloud.cfg.d/80_disable_network_after_firstboot.cfg
content: |
# Disable network configuration after first boot
network:
config: disabled
"@
$userDataPath = Join-Path $seedConfigPath "user-data"
Set-Content -Path $userDataPath -Value $userDataContent -Encoding UTF8
# Create seed.iso using PowerShell (Windows alternative to genisoimage)
Write-Host "Creating seed.iso boot image..." -ForegroundColor Cyan
try {
# Use OSCDIMG from Windows SDK/ADK if available, otherwise provide manual instructions
$oscdimgPath = @(
"${env:ProgramFiles(x86)}\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe",
"${env:ProgramFiles}\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe",
"oscdimg.exe"
) | Where-Object { Test-Path $_ -ErrorAction SilentlyContinue } | Select-Object -First 1
if ($oscdimgPath) {
& $oscdimgPath -l"cidata" -m -n "$seedConfigPath" "$seedIsoPath"
if ($LASTEXITCODE -eq 0) {
Write-Host "seed.iso created successfully using oscdimg." -ForegroundColor Green
} else {
throw "oscdimg failed with exit code $LASTEXITCODE"
}
} else {
Write-Host "OSCDIMG not found. Creating seed.iso manually..." -ForegroundColor Yellow
Write-Host "Please install Windows ADK or create seed.iso manually using these files:" -ForegroundColor Yellow
Write-Host "meta-data: $metaDataPath" -ForegroundColor Gray
Write-Host "user-data: $userDataPath" -ForegroundColor Gray
Write-Host "You can use a tool like ImgBurn to create an ISO with volume label 'cidata'" -ForegroundColor Yellow
# Create a simple batch file for manual ISO creation
$batchContent = @"
@echo off
echo Creating seed.iso for Amazon Linux 2 VM
echo.
echo Use ImgBurn or similar tool to create an ISO with:
echo - Source folder: $seedConfigPath
echo - Volume label: cidata
echo - Output file: $seedIsoPath
echo.
echo Files to include:
echo - meta-data
echo - user-data
pause
"@
$batchPath = Join-Path $vmPath "create_seed_iso.bat"
Set-Content -Path $batchPath -Value $batchContent -Encoding ASCII
Write-Host "Created helper batch file: $batchPath" -ForegroundColor Cyan
$continueWithoutSeed = Read-Host "Continue without seed.iso? VM may not boot properly (Y/N)"
if ($continueWithoutSeed -notmatch '^[Yy]$') {
Write-Host "Operation cancelled. Please create seed.iso manually and run the script again." -ForegroundColor Yellow
return
}
}
} catch {
Write-Host "Failed to create seed.iso: $_" -ForegroundColor Red
Write-Host "You can create it manually using the files in: $seedConfigPath" -ForegroundColor Yellow
$continueAnyway = Read-Host "Continue without seed.iso? (Y/N)"
if ($continueAnyway -notmatch '^[Yy]$') {
return
}
}
# Download and extract VHDX if not exists (Step 2 from AWS docs)
if (-not (Test-Path $vhdxPath)) {
# Download ZIP file if not exists
if (-not (Test-Path $zipPath)) {
Write-Host "Downloading Amazon Linux 2 VHDX archive (this may take several minutes)..." -ForegroundColor Yellow
Write-Host "Download URL: $zipUrl" -ForegroundColor Gray
try {
# Use BITS transfer for better progress
Import-Module BitsTransfer -ErrorAction SilentlyContinue
if (Get-Module BitsTransfer) {
Start-BitsTransfer -Source $zipUrl -Destination $zipPath -DisplayName "Amazon Linux 2 VHDX Download"
} else {
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
}
Write-Host "Download completed successfully." -ForegroundColor Green
} catch {
Write-Host "Download failed: $_" -ForegroundColor Red
Write-Host "You can manually download from: $zipUrl" -ForegroundColor Yellow
Write-Host "Save it as: $zipPath" -ForegroundColor Yellow
return
}
} else {
Write-Host "Using existing ZIP file: $zipPath" -ForegroundColor Green
}
# Extract VHDX from ZIP
Write-Host "Extracting VHDX from archive..." -ForegroundColor Cyan
try {
# Use .NET method to extract
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($zipPath)
# Find the VHDX file in the archive
$vhdxEntry = $zip.Entries | Where-Object { $_.Name -like "*.vhdx" } | Select-Object -First 1
if ($vhdxEntry) {
Write-Host "Found VHDX file: $($vhdxEntry.Name)" -ForegroundColor Gray
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($vhdxEntry, $vhdxPath, $true)
Write-Host "VHDX extraction completed." -ForegroundColor Green
} else {
throw "No VHDX file found in the archive"
}
$zip.Dispose()
# Optionally remove ZIP file to save space
$removeZip = Read-Host "Remove ZIP file to save space? (Y/N, default: Y)"
if ($removeZip -notmatch '^[Nn]$') {
Remove-Item $zipPath -Force
Write-Host "ZIP file removed." -ForegroundColor Gray
}
} catch {
Write-Host "Failed to extract VHDX: $_" -ForegroundColor Red
Write-Host "You can manually extract the VHDX file from: $zipPath" -ForegroundColor Yellow
Write-Host "Extract to: $vhdxPath" -ForegroundColor Yellow
return
}
} else {
Write-Host "Using existing VHDX file: $vhdxPath" -ForegroundColor Green
}
# Create the VM (Step 3 from AWS docs - Generation 1 for Amazon Linux compatibility)
Write-Host "Creating virtual machine..." -ForegroundColor Cyan
$vm = New-VM -Name $vmName -MemoryStartupBytes ([int64]$memoryMB * 1MB) -Path $vmPath -Generation 1
# Attach the pre-built VHDX (primary boot drive)
Write-Host "Attaching Amazon Linux VHDX..." -ForegroundColor Cyan
Add-VMHardDiskDrive -VM $vm -Path $vhdxPath
# Attach seed.iso if it exists
if (Test-Path $seedIsoPath) {
Write-Host "Attaching seed.iso for initial configuration..." -ForegroundColor Cyan
Add-VMDvdDrive -VM $vm -Path $seedIsoPath
} else {
Write-Host "No seed.iso found - VM may require manual configuration." -ForegroundColor Yellow
}