Add disks to a VM

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:

  1. Use Invoke-Command
    1. I will be able to connect to different versions of Hyper-V
    2. 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.
    3. This will also work from PowerShell Core
  2. Gather some basic info about disks of given VM.
    1. This will give me current VM path location
    2. And also the number of current VHDs. (remember the <VMName>_disk<number>.vhdx part?
  3. Create VHD and then attach it to a VM.

Here’s the code:


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!


#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:


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 : 7acbb7e4efc4422293c9f75618873037
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 : D5F483DAA1C74A01BDBECBBD989D2F43
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 : 0323298f534c4be19580e7fa74734c6e
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{b9306c9aa1894c5fa103737d77963206}\
AllocationUnitSize : 4096
DedupMode : 4
DriveLetter : E
DriveType : 3
FileSystem : NTFS
FileSystemLabel : Data
FileSystemType : 14
HealthStatus : 0
OperationalStatus : {2}
Path : \\?\Volume{b9306c9aa1894c5fa103737d77963206}\
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 : 92ff5a28a7cb4b0fb0030e38d1d82608
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 : 74742985C2B34B01AB80E4B81006A489
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 : ea37cf4230b440a9b94d9e8e2d6dcec3
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{0c1fa464eb8c432898cbbc09a31e3385}\
AllocationUnitSize : 4096
DedupMode : 4
DriveLetter : F
DriveType : 3
FileSystem : NTFS
FileSystemLabel : Data
FileSystemType : 14
HealthStatus : 0
OperationalStatus : {2}
Path : \\?\Volume{0c1fa464eb8c432898cbbc09a31e3385}\
Size : 107356352512
SizeRemaining : 107240054784
VERBOSE: Removing PSSession to Computer {OSName1}

view raw

Transcript.ps1

hosted with ❤ by GitHub

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s