Why
Adding a disk to a VM is not a big issue. I can use Hyper-V console or Windows Admin Center or PowerShell. But because I need to do it on a frequent basis and it’s boring I wanted to find a better way.
Just a reminder – how to add VHD to a VM:
-
In Hyper-V we use Settings menu for given VM:
Select New, and then name it, select path and proper options.
-
In Windows Admin Center we need to get to VMs menu, Inventory, select given VM and go to disks:
Then again, fill in proper path and options.
In PowerShell – it’s sligthly better:
There are two problems with this. First – I need to know WHERE to put the files for given VM. In general I have all disks on the same CSV – along with VM configuration. Second – it’s boring 🙂
The Idea
So, I wanted to speed this up a bit. What I know?
- In general I name disks according to this template: <VMName>_disk<number>.vhdx.
- Disks are in the same location as other VM files (like configuration).
- When disk is attached in correct order – it usually gets the same disk number within the Windows OS. This simplifies a bit later on.
- I use Hyper-V hosts from 2012R2 version to 2019.
- Some Hyper-V hosts are in different domains – meaning I need to use Credential parameter.
- Sometimes VHD is in a different location.
- Sometimes I want to manually name the VHD.
- I use dynamic disks now, but I want to support other types later on as well.
Knowing this I ended up with a function that will:
-
Use Invoke-Command
- I will be able to connect to different versions of Hyper-V
- I will be able to use optional Credential parameter to connect to Hyper-V hosts in different domains. Or run it from unprivilged powershell session.
- This will also work from PowerShell Core
-
Gather some basic info about disks of given VM.
- This will give me current VM path location
- And also the number of current VHDs. (remember the <VMName>_disk<number>.vhdx part?
- Create VHD and then attach it to a VM.
Here’s the code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Add-AHVMDisk { | |
<# | |
.SYNOPSIS | |
Adds a new vhdx to Hyper-V VM | |
.DESCRIPTION | |
Creates a new VHDX in VM's default disk location with given size (VHDSize). Attaches to a VM. | |
.PARAMETER ComputerName | |
ComputerName of a HyperV Computer where VM is. | |
.PARAMETER VMName | |
VMName to which attach a new VHDX | |
.PARAMETER VHDSize | |
Size of the VHDX file to create. | |
.PARAMETER VHDType | |
Type of VHDX file. Currently only Dynamic is supported | |
.PARAMETER Credential | |
Alternate credentials to use to connect to ComputerName | |
.EXAMPLE | |
Add-AHVMDisk -ComputerName OBJPLWHV1 -VMName OBJPLWCON0 -VHDSize 100GB -VHDType Dynamic -Credential (Get-Credential) | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, | |
ValueFromPipelineByPropertyName = $True)] | |
[string] | |
$ComputerName, | |
[Parameter(Mandatory = $True, | |
ValueFromPipelineByPropertyName = $True)] | |
[string] | |
$VMName, | |
[Parameter(Mandatory = $True, | |
ValueFromPipelineByPropertyName = $True)] | |
[uint64] | |
$VHDSize, | |
[Parameter(Mandatory = $True, | |
ValueFromPipelineByPropertyName = $True)] | |
[ValidateSet('Dynamic')] | |
[string] | |
$VHDType, | |
[Parameter(Mandatory = $false, | |
ValueFromPipelineByPropertyName = $True)] | |
[string] | |
$VHDName, | |
[Parameter(Mandatory = $false, | |
ValueFromPipelineByPropertyName = $True)] | |
[System.Management.Automation.CredentialAttribute()] | |
$Credential | |
) | |
Process { | |
$invokeProps = @{ | |
ComputerName = $ComputerName | |
} | |
if ($PSBoundParameters.ContainsKey('Credential')) { | |
Write-Verbose –Message "Credential {$($Credential.UserName)} will be used to connect to Computer {$ComputerName}" | |
$invokeProps.Credential = $Credential | |
} | |
else { | |
Write-Verbose –Message "Processing Computer {$ComputerName} with default credentials of user {$($env:USERNAME)}" | |
} | |
Write-Verbose –Message "Processing adding new VHDX Disk to VM {$VMName} on Computer {$ComputerName}" | |
$vhdProps = @{ | |
Path = '' | |
SizeBytes = $VHDSize | |
ComputerName = $ComputerName | |
} | |
Write-Verbose –Message "Getting VM {$VMName} properties from Computer {$ComputerName} to set Path for new VHDX" | |
$VMCurrentHDD = Invoke-command @invokeProps –ScriptBlock { Get-VMHardDiskDrive –VMName $USING:VMName } | |
$VMBaseHDDPath = $VMCurrentHDD[0].Path | |
$VMCurrentVHDCount = ($VMCurrentHDD | Measure-Object ).Count | |
$VMParentPath = Split-Path $VMBaseHDDPath –Parent | |
if ($PSBoundParameters.ContainsKey('VHDName')) { | |
$VHDFileName = $VHDName | |
} | |
else { | |
$VHDFileName = "{0}_disk{1}.vhdx" -f $VMName, $VMCurrentVHDCount | |
} | |
$VHDPath = Join-Path $VMParentPath –ChildPath $VHDFileName | |
$vhdProps.Path = $VHDPath | |
Write-Verbose –Message "Setting disk type to {$VHDType} for {$VMName} on Computer {$ComputerName}" | |
if ($VHDType -eq 'Dynamic') { | |
$vhdProps.Dynamic = $true | |
} | |
Write-Verbose –Message "Creating new VHDX in {$($vhdProps.Path)} for {$VMName} on Computer {$ComputerName}" | |
Invoke-Command @invokeProps –ScriptBlock { | |
$vhdProps = $USING:vhdProps | |
New-VHD @vhdProps | |
} | |
Write-Verbose –Message "Attaching new VHD for {$VMName} on Computer {$ComputerName}" | |
Invoke-Command @invokeProps –ScriptBlock { | |
Add-VMHardDiskDrive –VMName $USING:VMName –Path $USING:VHDPath | |
} | |
Write-Verbose –Message "Attached new VHD for {$VMName} on Computer {$ComputerName}" | |
} | |
} |
What’s next? Well a VHD needs to be formatted right? Booring:) Glad I have this Format Drive. Remotely.
Final approach
Well if I combine both of these functions I can do it in one swing. For multiple disks at once!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#optional | |
#$Creds = Get-Credential #Credki do VM – moga byc domenowe lub lokalne | |
$NewVMDiskProps = @{ | |
VMProps = @{ | |
ComputerName = 'HyperV' | |
VMName = 'VMName1' | |
} | |
VHDProps = @( | |
@{ | |
VHDSize = 100GB | |
VHDType = 'Dynamic' | |
NewFileSystemLabel = 'Data' | |
FileSystem = 'NTFS' | |
}, | |
@{ | |
VHDSize = 150GB | |
VHDType = 'Dynamic' | |
NewFileSystemLabel = 'Data1' | |
FileSystem = 'NTFS' | |
} | |
) | |
OSName = 'OSName1' | |
} | |
foreach ($disk in $NewVMDiskProps.VHDProps) { | |
$addDiskProps = $NewVMDiskProps.VMProps | |
$addDiskProps.VHDSize = $disk.VHDSize | |
$addDiskProps.VHDType = $disk.VHDType | |
if($creds) { | |
$addDiskProps.Credential = $creds | |
} | |
Add-AHVMDisk @addDiskProps –Verbose | |
$formatDriveProps = @{ | |
ComputerName = $NewVMDiskProps.OSName | |
NewFileSystemLabel = $disk.NewFileSystemLabel | |
FileSystem = $disk.FileSystem | |
} | |
if($creds) { | |
$formatDriveProps.Credential = $creds | |
} | |
Format-RemoteDrive @formatDriveProps –Verbose | |
} |
Here’s the output:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
VERBOSE: Processing Computer {HyperV} with default credentials of user {mczerniawski_admin} | |
VERBOSE: Processing adding new VHDX Disk to VM {VMName1} on Computer {HyperV} | |
VERBOSE: Getting VM {VMName1} properties from Computer {HyperV} to set Path for new VHDX | |
VERBOSE: Setting disk type to {Dynamic} for {VMName1} on Computer {HyperV} | |
VERBOSE: Creating new VHDX in {C:\AdminTools\VMName1\Virtual Hard Disks\VMName1_disk1.vhdx} for {VMName1} on Computer {HyperV} | |
Number : | |
PSComputerName : HyperV | |
RunspaceId : 7acbb7e4–efc4–4222–93c9–f75618873037 | |
ComputerName : HyperV | |
Path : C:\AdminTools\VMName1\Virtual Hard Disks\VMName1_disk1.vhdx | |
VhdFormat : VHDX | |
VhdType : Dynamic | |
FileSize : 4194304 | |
Size : 107374182400 | |
MinimumSize : | |
LogicalSectorSize : 512 | |
PhysicalSectorSize : 4096 | |
BlockSize : 33554432 | |
ParentPath : | |
DiskIdentifier : D5F483DA–A1C7–4A01–BDBE–CBBD989D2F43 | |
FragmentationPercentage : 0 | |
Alignment : 1 | |
Attached : False | |
DiskNumber : | |
VERBOSE: Attaching new VHD for {VMName1} on Computer {HyperV} | |
VERBOSE: Attached new VHD for {VMName1} on Computer {HyperV} | |
VERBOSE: Processing computer {OSName1} | |
VERBOSE: Creating PSSession to computer {OSName1} | |
VERBOSE: Checking if there are any not initialized disks on {OSName1} | |
VERBOSE: Found #{1} raw disk on {OSName1} | |
VERBOSE: Processing disks on {OSName1} | |
PSComputerName : OSName1 | |
RunspaceId : 0323298f–534c–4be1–9580–e7fa74734c6e | |
ObjectId : {1}\\OSName1\root/Microsoft/Windows/Storage/Providers_v2\WSP_Volume.ObjectId="{6e4dd0d4-9b56-11e8-a29a-806e6f6e6963}:VO:\\?\Volume{ | |
b9306c9a-a189-4c5f-a103-737d77963206}\" | |
PassThroughClass : | |
PassThroughIds : | |
PassThroughNamespace : | |
PassThroughServer : | |
UniqueId : \\?\Volume{b9306c9a–a189–4c5f–a103–737d77963206}\ | |
AllocationUnitSize : 4096 | |
DedupMode : 4 | |
DriveLetter : E | |
DriveType : 3 | |
FileSystem : NTFS | |
FileSystemLabel : Data | |
FileSystemType : 14 | |
HealthStatus : 0 | |
OperationalStatus : {2} | |
Path : \\?\Volume{b9306c9a–a189–4c5f–a103–737d77963206}\ | |
Size : 107356352512 | |
SizeRemaining : 107240054784 | |
VERBOSE: Removing PSSession to Computer {OSName1} | |
VERBOSE: Processing Computer {HyperV} with default credentials of user {mczerniawski_admin} | |
VERBOSE: Processing adding new VHDX Disk to VM {VMName1} on Computer {HyperV} | |
VERBOSE: Getting VM {VMName1} properties from Computer {HyperV} to set Path for new VHDX | |
VERBOSE: Setting disk type to {Dynamic} for {VMName1} on Computer {HyperV} | |
VERBOSE: Creating new VHDX in {C:\AdminTools\VMName1\Virtual Hard Disks\VMName1_disk2.vhdx} for {VMName1} on Computer {HyperV} | |
Number : | |
PSComputerName : HyperV | |
RunspaceId : 92ff5a28–a7cb–4b0f–b003–0e38d1d82608 | |
ComputerName : HyperV | |
Path : C:\AdminTools\VMName1\Virtual Hard Disks\VMName1_disk2.vhdx | |
VhdFormat : VHDX | |
VhdType : Dynamic | |
FileSize : 4194304 | |
Size : 107374182400 | |
MinimumSize : | |
LogicalSectorSize : 512 | |
PhysicalSectorSize : 4096 | |
BlockSize : 33554432 | |
ParentPath : | |
DiskIdentifier : 74742985–C2B3–4B01–AB80–E4B81006A489 | |
FragmentationPercentage : 0 | |
Alignment : 1 | |
Attached : False | |
DiskNumber : | |
VERBOSE: Attaching new VHD for {VMName1} on Computer {HyperV} | |
VERBOSE: Attached new VHD for {VMName1} on Computer {HyperV} | |
VERBOSE: Processing computer {OSName1} | |
VERBOSE: Creating PSSession to computer {OSName1} | |
VERBOSE: Checking if there are any not initialized disks on {OSName1} | |
VERBOSE: Found #{1} raw disk on {OSName1} | |
VERBOSE: Processing disks on {OSName1} | |
PSComputerName : OSName1 | |
RunspaceId : ea37cf42–30b4–40a9–b94d–9e8e2d6dcec3 | |
ObjectId : {1}\\OSName1\root/Microsoft/Windows/Storage/Providers_v2\WSP_Volume.ObjectId="{6e4dd0d4-9b56-11e8-a29a-806e6f6e6963}:VO:\\?\Volume{ | |
0c1fa464-eb8c-4328-98cb-bc09a31e3385}\" | |
PassThroughClass : | |
PassThroughIds : | |
PassThroughNamespace : | |
PassThroughServer : | |
UniqueId : \\?\Volume{0c1fa464–eb8c–4328–98cb–bc09a31e3385}\ | |
AllocationUnitSize : 4096 | |
DedupMode : 4 | |
DriveLetter : F | |
DriveType : 3 | |
FileSystem : NTFS | |
FileSystemLabel : Data | |
FileSystemType : 14 | |
HealthStatus : 0 | |
OperationalStatus : {2} | |
Path : \\?\Volume{0c1fa464–eb8c–4328–98cb–bc09a31e3385}\ | |
Size : 107356352512 | |
SizeRemaining : 107240054784 | |
VERBOSE: Removing PSSession to Computer {OSName1} |