Public/New-DataVHD.ps1
function New-DataVHD { <# .SYNOPSIS Creates a VHD or VHDX data drive with GPT partitions. .DESCRIPTION This command creates a VHD or VHDX file with a GPT partition table, formatted as ReFS (default) or NTFS. You must supply the path to the VHD/VHDX file. Use -Force to overwrite an existing file (ACLs will be copied to the new file). Supports dynamic disks, allocation unit size, reserved partition size, and more. .PARAMETER Path The path to the new VHD or VHDX file. Must end in .vhd or .vhdx. .PARAMETER DataFormat The file system format for the data drive. Valid values are NTFS or ReFS. Default is ReFS. .PARAMETER AllocationUnitSize The allocation unit size to use when formatting the primary partition. Valid values are 4kb, 8kb, 16kb, 32kb, 64kb, 128kb, 256kb, 512kb, 1024kb, 2048kb. .PARAMETER Size The size of the VHD(X) in bytes. Default is 40GB. Minimum is 100MB, maximum is 64TB. .PARAMETER ReservedSize The size of the MS Reserved partition in MB. Default is 128MB. .PARAMETER Dynamic If specified, creates a dynamic disk. .PARAMETER Force If specified, overwrites any existing file. .EXAMPLE New-DataVHD -Path c:\Data.vhdx -Size 20GB -Dynamic Creates a new 20GB dynamic Data VHDX formatted as ReFS. .EXAMPLE New-DataVHD -Path c:\data.vhdx -Size 100GB -DataFormat NTFS Creates a new 100GB Data VHDX formatted as NTFS. .NOTES Author: WindowsImageTools Team Requires: Administrator privileges #> [CmdletBinding(SupportsShouldProcess = $true, PositionalBinding = $false, ConfirmImpact = 'Medium')] Param ( # Path to the new VHDX file (Must end in .vhdx) [Parameter(Position = 0, Mandatory = $true, HelpMessage = 'Enter the path for the new VHDX file')] [ValidateNotNullOrEmpty()] [ValidatePattern(".\.vhdx?$")] [ValidateScript( { if (Get-FullFilePath -Path $_ | Split-Path | Resolve-Path ) { $true } else { Throw "Parent folder for $_ does not exist." } })] [string]$Path, # Format drive as NTFS or ReFS (Only applies when DiskLayout = Data) [string] [ValidateNotNullOrEmpty()] [ValidateSet('NTFS', 'ReFS')] $DataFormat = 'ReFS', # Allocation Unit Size to format the primary partition [int] [ValidateSet(4kb, 8kb, 16kb, 32kb, 64kb, 128kb, 256kb, 512kb, 1024kb, 2048kb)] $AllocationUnitSize, # Size in Bytes (Default 40B) [ValidateRange(100mb, 64TB)] [long]$Size = 40GB, # MS Reserved Partition Size (Default : 128MB) [int]$ReservedSize, # Create Dynamic disk [switch]$Dynamic, # Force the overwrite of existing files [switch]$force ) $Path = $Path | Get-FullFilePath $VhdxFileName = Split-Path -Leaf -Path $Path if ($PsCmdlet.ShouldProcess("[$($MyInvocation.MyCommand)] : Overwrite partitions inside [$Path] with GPT Data Partitions", "Overwrite partitions inside [$Path] with GPT Data Partitions ? ", 'Overwrite WARNING!')) { if ((-not (Test-Path $Path)) -Or $force -Or $PsCmdlet.ShouldContinue('Are you sure? Any existing data will be lost!', 'Warning')) { $ParametersToPass = @{ } foreach ($key in ('WhatIf', 'Verbose', 'Debug')) { if ($PSBoundParameters.ContainsKey($key)) { $ParametersToPass[$key] = $PSBoundParameters[$key] } } $InitializeVHDPartitionParam = @{ 'Size' = $Size 'Path' = $Path 'force' = $true 'DiskLayout' = 'Data' 'DataFormat' = $DataFormat } if ($Dynamic) { $InitializeVHDPartitionParam.add('Dynamic', $true) } if ($ReservedSize) { $InitializeVHDPartitionParam.add('ReservedSize', $ReservedSize) } if ($AllocationUnitSize) { $InitializeVHDPartitionParam.add('AllocationUnitSize', $AllocationUnitSize) } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : InitializeVHDPartitionParam" Write-Verbose -Message ($InitializeVHDPartitionParam | Out-String) Write-Verbose -Message "[$($MyInvocation.MyCommand)] : ParametersToPass" Write-Verbose -Message ($ParametersToPass | Out-String) Try { Initialize-VHDPartition @InitializeVHDPartitionParam @ParametersToPass } Catch { throw "$($_.Exception.Message) at $($_.Exception.InvocationInfo.ScriptLineNumber)" } #region mount the VHDX file try { Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$VhdxFileName] : Mounting disk image [$Path]" $disk = Mount-DiskImage -ImagePath $Path -PassThru | Get-DiskImage | Get-Disk $DiskNumber = $disk.Number } catch { throw $_.Exception.Message } #endregion try { #! Workaround for new drive letters in script modules $null = Get-PSDrive #region Assign Drive Letters (disable explorer popup and reset afterwords) $DisableAutoPlayOldValue = (Get-ItemProperty -path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers -name DisableAutoplay).DisableAutoplay Set-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers -Name DisableAutoplay -Value 1 foreach ($partition in (Get-Partition -DiskNumber $DiskNumber | where-object -FilterScript { $_.Type -eq 'IFS' -or $_.type -eq 'basic' })) { $partition | Add-PartitionAccessPath -AssignDriveLetter -ErrorAction Stop } #! Workaround for new drive letters in script modules $null = Get-PSDrive Set-ItemProperty -Path hkcu:\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers -Name DisableAutoplay -Value $DisableAutoPlayOldValue } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] [$VhdxFileName] : Error Adding Drive Letter " throw $_.Exception.Message } finally { #dismount Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$VhdxFileName] : Dismounting" $null = Dismount-DiskImage -ImagePath $Path if ($isoPath -and (Get-DiskImage $isoPath).Attached) { $null = Dismount-DiskImage -ImagePath $isoPath [System.GC]::Collect() } Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$VhdxFileName] : Finished" } } } } |