Functions/Initialize-VHDPartition.ps1
function Initialize-VHDPartition { <# .Synopsis Create VHD(X) with partitions needed to be bootable .DESCRIPTION This command will create a VHD or VHDX file. Supported layours are: BIOS, UEFO or WindowsToGo. To create a recovery partitions use -RecoveryTools and -RecoveryImage .EXAMPLE Initialize-VHDPartition d:\disks\disk001.vhdx -dynamic -size 30GB -DiskLayout BIOS .EXAMPLE Initialize-VHDPartition d:\disks\disk001.vhdx -dynamic -size 40GB -DiskLayout UEFI -RecoveryTools .NOTES General notes #> [CmdletBinding(SupportsShouldProcess, PositionalBinding = $false, ConfirmImpact = 'Medium')] Param ( # Path to the new VHDX file (Must end in .vhdx) [Parameter(Position = 0,Mandatory, HelpMessage = 'Enter the path for the new VHD/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, # Size in Bytes (Default 40B) [ValidateRange(25GB,64TB)] [uint64]$Size = 40GB, # Create Dynamic disk [switch]$Dynamic, # Specifies whether to build the image for BIOS (MBR), UEFI (GPT), or WindowsToGo (MBR). # Generation 1 VMs require BIOS (MBR) images. Generation 2 VMs require UEFI (GPT) images. # Windows To Go images will boot in UEFI or BIOS [Parameter(Mandatory)] [Alias('Layout')] [string] [ValidateNotNullOrEmpty()] [ValidateSet('BIOS', 'UEFI', 'WindowsToGo')] $DiskLayout, # Output the disk image object [switch]$Passthru, # Create the Recovery Environment Tools Partition. Only valid on UEFI layout [switch]$RecoveryTools, # Create the Recovery Environment Tools and Recovery Image Partitions. Only valid on UEFI layout [switch]$RecoveryImage, # Force the overwrite of existing files [switch]$force ) Begin { if ($pscmdlet.ShouldProcess("[$($MyInvocation.MyCommand)] Create partition structure for Bootable vhd(x) on [$Path]", "Replace existing file [$Path] ? ", 'Overwrite WARNING!')) { if((-not (Test-Path $Path)) -Or $force -Or ((Test-Path $Path) -and $pscmdlet.ShouldContinue("TargetFile [$Path] exists! Any existin data will be lost!", 'Warning'))) { #region Validate input # Recovery Image requires the Recovery Tools if ($RecoveryImage) { $RecoveryTools = $true } $VHDFormat = ([IO.FileInfo]$Path).Extension.split('.')[-1] if (($DiskLayout -eq 'UEFI')-and ($VHDFormat -eq 'VHD')) { throw 'UEFI disks must be in VHDX format. Please change the path to end in VHDX' } # Choose smallest supported block size for dynamic VHD(X) $BlockSize = 1MB # Enforce max VHD size. if ('VHD' -ilike $VHDFormat) { if ($Size -gt 2040GB) { Write-Warning -Message 'For the VHD file format, the maximum file size is ~2040GB. Reseting size to 2040GB.' $Size = 2040GB } $BlockSize = 512KB } $SysSize = 200MB $MSRSize = 128MB $RESize = 0 $RecoverySize = 0 if ($RecoveryTools) { $RESize = 350MB } if ($RecoveryImage) { $RecoverySize = 15GB } $fileName = Split-Path -Leaf -Path $Path # make paths absolute $Path = $Path | Get-FullFilePath #endregion # if we get this far it's ok to delete existing files if (Test-Path -Path $Path) { Remove-Item -Path $Path } Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Creating" #region Create VHD Try { if ($VHDCmdlets) { $vhdParams = @{ ErrorAction = 'Stop' Path = $Path SizeBytes = $Size Dynamic = $Dynamic BlockSizeBytes = $BlockSize } Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : @vhdParms" Write-Verbose -Message ($vhdParams | Out-String) $null = New-VHD @vhdParams } else { $vhdParams = @{ VHDFormat = $VHDFormat Path = $Path SizeBytes = $Size } Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Params for [WIM2VHD.VirtualHardDisk]::CreateSparseDisk()" Write-Verbose -Message ($vhdParams | Out-String) [WIM2VHD.VirtualHardDisk]::CreateSparseDisk( $VHDFormat, $Path, $Size, $true ) } } catch { Throw "Failed to create $Path. $($_.Exception.Message)" } #endregion if (Test-Path -Path $Path) { #region Mount Image try { Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Mounting disk image" $disk = Mount-DiskImage -ImagePath $Path -PassThru | Get-DiskImage | Get-Disk } catch { throw $_.Exception.Message } #endregion #region create partitions try { $disknumber = $disk.Number switch ($DiskLayout) { 'BIOS' { Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Initializing disk [$disknumber] as MBR" Initialize-Disk -Number $disknumber -PartitionStyle MBR Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Clearing disk partitions to start all over" Get-Disk -Number $disknumber -ErrorAction Stop | Get-Partition -ErrorAction Stop | Remove-Partition -Confirm:$false -ErrorAction Stop # Create the Windows/system partition # Refresh $disk to update free space $disk = Get-DiskImage -ImagePath $Path | Get-Disk Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Creating single partition of [$($disk.LargestFreeExtent)] bytes" $windowsPartition = New-Partition -DiskNumber $disknumber -UseMaximumSize -MbrType IFS -IsActive #-Size $disk.LargestFreeExtent $systemPartition = $windowsPartition Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Formatting windows volume" $null = Format-Volume -Partition $windowsPartition -FileSystem NTFS -Force -Confirm:$false } 'UEFI' { Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Initializing disk [$disknumber] as GPT" Initialize-Disk -Number $disk.Number -PartitionStyle GPT Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Clearing disk partitions to start all over" Get-Disk -Number $disknumber -ErrorAction Stop | Get-Partition -ErrorAction Stop | Remove-Partition -Confirm:$false -ErrorAction Stop if ($RecoveryTools) { Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Recovery tools : Creating partition of [$RESize] bytes" $recoveryToolsPartition = New-Partition -DiskNumber $disk.Number -Size $RESize -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}' Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Recovery tools : Formatting NTFS" $null = Format-Volume -Partition $recoveryToolsPartition -FileSystem NTFS -NewFileSystemLabel 'Windows RE Tools' -Force -Confirm:$false #run diskpart to set GPT attribute to prevent partition removal #the here string must be left justified $null = @" select disk $($disk.Number) select partition $($recoveryToolsPartition.partitionNumber) gpt attributes=0x8000000000000001 exit "@ | diskpart.exe } # Create the system partition. Create a data partition so we can format it, then change to ESP Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : EFI system : Creating partition of [$SysSize] bytes" $systemPartition = New-Partition -DiskNumber $disk.Number -Size $SysSize -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : EFI system : Formatting FAT32" $null = Format-Volume -Partition $systemPartition -FileSystem FAT32 -Force -Confirm:$false Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : EFI system : Setting system partition as ESP" $systemPartition | Set-Partition -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' # Create the reserved partition Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : MSR : Creating partition of [$MSRSize] bytes" $null = New-Partition -DiskNumber $disk.Number -Size $MSRSize -GptType '{e3c9e316-0b5c-4db8-817d-f92df00215ae}' # Create the Windows partition # Refresh $disk to update free space $disk = Get-DiskImage -ImagePath $Path | Get-Disk Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Windows : Creating partition of [$($disk.LargestFreeExtent - $RecoverySize)] bytes" $windowsPartition = New-Partition -DiskNumber $disk.Number -Size ($disk.LargestFreeExtent - $RecoverySize) -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Windows : Formatting volume NTFS" $null = Format-Volume -Partition $windowsPartition -NewFileSystemLabel 'OS' -FileSystem NTFS -Force -Confirm:$false if ($RecoveryImage) { Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Recovery Image : Creating partition using remaing free space" $recoveryImagePartition = New-Partition -DiskNumber $disk.Number -UseMaximumSize -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}' Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Recovery Image : Formatting volume NTFS" $null = Format-Volume -Partition $recoveryImagePartition -NewFileSystemLabel 'Windows Recovery' -FileSystem NTFS -Force -Confirm:$false #run diskpart to set GPT attribute to prevent partition removal #the here string must be left justified $null = @" select disk $($disk.Number) select partition $($recoveryImagePartition.partitionNumber) gpt attributes=0x8000000000000001 exit "@ | diskpart.exe } } 'WindowsToGo' { Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Initializing disk [$disknumber] as MBR" Initialize-Disk -Number $disk.Number -PartitionStyle MBR Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Clearing disk partitions to start all over" Get-Disk -Number $disknumber -ErrorAction Stop | Get-Partition -ErrorAction Stop | Remove-Partition -Confirm:$false -ErrorAction Stop # Create the system partition Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : System : Creating partition of [$SysSize] bytes" $systemPartition = New-Partition -DiskNumber $disk.Number -Size $SysSize -MbrType FAT32 -IsActive Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : EFI system : Formatting FAT32" $null = Format-Volume -Partition $systemPartition -FileSystem FAT32 -Force -Confirm:$false # Create the Windows partition Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Windows : Creating partition useing remaning space" $windowsPartition = New-Partition -DiskNumber $disk.Number -Size $disk.LargestFreeExtent -MbrType IFS Write-Verbose "[$($MyInvocation.MyCommand)] [$fileName] : Windows : Formatting volume NTFS" $null = Format-Volume -Partition $windowsPartition -FileSystem NTFS -Force -Confirm:$false } } } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] [$fileName] : Creating Partitions" throw $_.Exception.Message } #endregion create partitions #region Dismount finally { Write-Verbose -Message "[$($MyInvocation.MyCommand)] [$fileName] : Dismounting disk image" Dismount-DiskImage -ImagePath $Path } #endregion if ($Passthru) { #write the new disk object to the pipeline Get-DiskImage -ImagePath $Path } }#end if disk else { throw "Unable to create or mount $Path" } } } } } |