Skip to content

Instantly share code, notes, and snippets.

@Hexalon
Last active January 31, 2017 21:40
Show Gist options
  • Save Hexalon/df61e25edbf5144d287205368fb93cf7 to your computer and use it in GitHub Desktop.
Save Hexalon/df61e25edbf5144d287205368fb93cf7 to your computer and use it in GitHub Desktop.
Creates a Hyper-V compatible vhdx virtual hard drive from a custom WIM.
#requires -version 4.0
#requires -modules Hyper-V,Storage,DISM
#Requires -RunAsAdministrator
<#
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127
Created on: 8/12/2016 13:50
Created by: Colin Squier <Hexalon@gmail.com>
Filename: Prepare-Gen2VM.ps1
===========================================================================
.DESCRIPTION
Creates a Hyper-V compatible vhdx virtual hard drive from a custom WIM.
#>
 
[CmdletBinding()]
Param ([string]$ImageBuild = "06")
 
function Copy-File
{
param ([string]$from,
[string]$to)
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
Write-Progress `
-Activity "Copying file" `
-status ($from.Split("\") | Select-Object -last 1) `
-PercentComplete 0
try
{
$sw = [System.Diagnostics.Stopwatch]::StartNew();
[byte[]]$buff = new-object byte[] (4096 * 1024)
[long]$total = [long]$count = 0
do
{
$count = $ffile.Read($buff, 0, $buff.Length)
$tofile.Write($buff, 0, $count)
$total += $count
[int]$pctcomp = ([int]($total/$ffile.Length * 100));
[int]$secselapsed = [int]($sw.elapsedmilliseconds.ToString())/1000;
if ($secselapsed -ne 0)
{
[single]$xferrate = (($total/$secselapsed)/1mb);
}
else
{
[single]$xferrate = 0.0
}
if ($total % 1mb -eq 0)
{
if ($pctcomp -gt 0)`
{
[int]$secsleft = ((($secselapsed/$pctcomp) * 100) - $secselapsed);
}
else
{
[int]$secsleft = 0
};
Write-Progress `
-Activity ($pctcomp.ToString() + "% Copying file @ " + "{0:n2}" -f $xferrate + " MB/s")`
-status ($from.Split("\") | Select-Object -last 1) `
-PercentComplete $pctcomp `
-SecondsRemaining $secsleft;
}
}
while ($count -gt 0)
$sw.Stop();
$sw.Reset();
}
finally
{
Write-Verbose (($from.Split("\") | Select-Object -last 1) + `
" copied in " + $secselapsed + " seconds at " + `
"{0:n2}" -f [int](($ffile.length/$secselapsed)/1mb) + " MB/s.");
$ffile.Close();
$tofile.Close();
}
}
 
#Return VHD Partition drive letter info
function Get-VHDInfo
{
[Cmdletbinding()]
param (
[string]$VhdPath
)
#Get partition drive letters
$driveLetters = [array](Get-VHD -Path $VhdPath | Get-Disk | Get-Partition | Get-Volume | Sort-Object Size).DriveLetter #Drive Order is not assured if not sorted
if ($driveLetters.Count -eq 2) #CreateReservedPartition specified
{
$bootRootDir = "{0}:\" -f $driveLetters[0]
$osRootDir = "{0}:\" -f $driveLetters[1]
}
else
{
$bootRootDir = "{0}:\" -f $driveLetters[0]
$osRootDir = "{0}:\" -f $driveLetters[0]
}
#Check PSDrive can access(in some condition it failed)
if (!(Test-Path -Path $osRootDir))
{
Write-Error "Can't access mounted VHD PSDrive. please try reset PowerShell runspace"
}
return [pscustomobject]@{ BootRootDir = $bootRootDir; OSRootDir = $osRootDir }
}
 
function New-Gen2Vhd
{
[CmdletBinding()]
param (
[ValidateScript({ -not (Test-Path $_) })]
[ValidatePattern("\.vhd(x)?$")]
[string]$VhdPath,
[ValidateRange(25GB, 64TB)]
[UInt64]$Size = 25GB,
[ValidateSet(2MB, 256MB)]
[UInt32]$BlockSize = 2MB,
[ValidateSet(512, 4096)]
[Uint32]$LogicalSectorSize = 4096,
[ValidateSet(512, 4096)]
[Uint32]$PhysicalSectorSize = 4096
)
<#
if ($VerbosePreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
{
$VerbosePreference = 'SilentlyContinue'
}
else
{
$VerbosePreference = 'Continue'
}
#>
if ($VhdPath -eq '')
{
$guid = [Guid]::NewGuid()
$VhdPath = ".\temp_$guid.vhdx"
}
try
{
# Create VHD container
$VhdOptions = @{
Path = $VhdPath
SizeBytes = $Size
Dynamic = $true
BlockSizeBytes = $BlockSize
LogicalSectorSizeBytes = $LogicalSectorSize
PhysicalSectorSizeBytes = $PhysicalSectorSize
}
Write-Verbose "Creating Hyper-V Virtual Disk (VHD) $($VhdOptions | Format-Table | Out-String)"
New-VHD @VhdOptions | Out-Null
# Mount VHD
Write-Verbose "Mounting VHD '$VhdPath'"
Mount-DiskImage -ImagePath (Resolve-Path $VhdPath) -Access ReadWrite
$DiskNumber = (Get-DiskImage (Resolve-Path $VhdPath) | Get-Disk).Number
$Disk = Get-Disk -Number $DiskNumber
Write-Verbose "VHD Layout $(Get-Partition -Disk $Disk | Out-String)"
# Initialise GUID Partition Table (GPT)
Write-Verbose "Initialise GUID Partition Table (GPT)"
Initialize-Disk -Number $DiskNumber -PartitionStyle GPT | Out-Null
# GPT disks that are used to boot the Windows operating system, the Extensible Firmware Interface (EFI)
# system partition must be the first partition on the disk, followed by the Microsoft Reserved partition.
Write-Verbose "Create Extensible Firmware Interface (EFI) partition"
New-EfiPartition -DiskNumber $DiskNumber -Size 260MB | Out-Null
# Create OS partition
Write-Verbose "Create OS partition"
New-Partition -DiskNumber $DiskNumber -UseMaximumSize -AssignDriveLetter |
Format-Volume -FileSystem NTFS -NewFileSystemLabel "Windows VHD" -confirm:$false | Out-Null
$Drive = $(Get-Partition -Disk $Disk).AccessPaths[3]
Write-Verbose "$Drive has been assigned to the Boot Volume"
Write-Verbose "VHD Layout $(Get-Partition -Disk $Disk | Out-String)"
}
catch
{
Throw "Failed to create $VhdPath. $($_.Exception.Message)"
}
finally
{
if ($VhdPath -ne '')
{
Dismount-DiskImage -ImagePath (Resolve-Path $VhdPath) -ErrorAction SilentlyContinue | Out-Null
}
}
return $VhdPath
}
 
function New-EfiPartition
{
param (
[Parameter(Position = 0, Mandatory)]
[UInt32]$DiskNumber,
[ValidateRange(100MB, 300MB)]
[UInt64]$Size = 100MB
)
# Create EFI partition and a basic data partition (BDP)
$Disk = Get-Disk -Number $DiskNumber
$partitionSystem = New-Partition -DiskNumber $DiskNumber -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' -Size $Size
$partitionSystemNumber = $partitionSystem.PartitionNumber
@"
select disk $DiskNumber
select partition $partitionSystemNumber
format quick fs=fat32 label=System
exit
"@ | diskpart | ForEach-Object{ Write-Verbose "[DiskPart] $_" }
$partitionSystem | Add-PartitionAccessPath -AssignDriveLetter
$DriveSystem = $(Get-Partition -Disk $Disk).AccessPaths[1]
Write-Verbose "$DriveSystem has been assigned to the System Volume"
}
 
$VhdSize = 60GB
$ImageVhdFileName = "WinX_$ImageBuild.vhdx"
$ImageVhdPath = (Join-Path -Path "X:\Win10-Imaging" -ChildPath "Images\$ImageVhdFileName" -ErrorAction Stop)
 
New-Gen2Vhd -VhdPath $ImageVhdPath -Size $VhdSize
 
Mount-VHD -Path $ImageVhdPath | Out-Null
 
#Get partition drive letters
$VhdInfo = Get-VHDInfo -VhdPath $ImageVhdPath
$VhdDriveLetter = $VhdInfo.OSRootDir
$OSDrive = $VhdDriveLetter.Replace(":\", "")
 
$SourceImage = "X:\Win10-Imaging\Images\WinX_$ImageBuild.wim"
Write-Verbose -Message "Applying $($SourceImage) to $($VhdDriveLetter)"
Expand-WindowsImage -ImagePath $SourceImage -Index 1 -ApplyPath $VhdDriveLetter -Verify
 
$DiskNumber = (Get-DiskImage (Resolve-Path $ImageVhdPath) | Get-Disk).Number
$Disk = Get-Disk -Number $DiskNumber
 
Add-PartitionAccessPath -DiskNumber $DiskNumber -PartitionNumber 2 -AssignDriveLetter
$DriveSystem = $(Get-Partition -Disk $Disk).AccessPaths[1]
$Drive = $(Get-Partition -Disk $Disk).AccessPaths[2]
 
# Copy critical boot files to the system partition to create a new system BCD store
# "Self-Sustainable", i.e. contains a boot loader and does not depend on external files.
$DriveSystemVolumeLetter = $DriveSystem.TrimEnd('\')
$bcdBootParams = @(
"$($Drive)Windows"
"/s $($DriveSystemVolumeLetter)"
"/f UEFI"
)
 
Write-Verbose "VHD Layout $(Get-Partition -Disk $Disk | Out-String)"
 
Write-Verbose "Create a new system BCD store"
Start-Process "bcdboot.exe" -ArgumentList $bcdBootParams -NoNewWindow -Wait | Out-Null
 
$FSLabel = Get-Volume | Where-Object { $_.FileSystemlabel -eq "Windows VHD" }
 
if ($FSLabel.FileSystemLabel -eq "Windows VHD")
{
Write-Verbose -Message "Changing volume label from $($FSLabel.FileSystemLabel) to 'Windows 10'"
Set-Volume -NewFileSystemLabel "Windows 10" -DriveLetter $OSDrive
}
 
#Cleanup
Write-Verbose -Message "Dismounting '$ImageVhdPath'"
Dismount-DiskImage -ImagePath (Resolve-Path $ImageVhdPath) -ErrorAction SilentlyContinue | Out-Null
Write-Verbose -Message "Optimzing VHD file, '$ImageVhdPath'"
Optimize-VHD -Path $ImageVhdPath -Mode Quick
 
try
{
$Destination = "X:\Users\Public\Documents\Hyper-V\Virtual Hard Disks\$ImageVhdFileName"
Write-Verbose -Message "Copying $ImageVhdPath to $Destination"
Copy-File -from $ImageVhdPath -to $Destination
}
catch
{
Throw "Failed to copy $ImageVhdPath. $($_.Exception.Message)"
}
 
try
{
$VMName = "WinX_$ImageBuild"
$VMGeneration = 2
$VMStartupMem = 2048MB
$VMMinMem = 1024MB
$VMMaxMem = 4096MB
$VMSwitchName = "Hyper-V - External"
$VMCpuCount = 2
$VMGuestIntergrationService = "Guest Service Interface"
Write-Verbose -Message "Creating Generation '$VMGeneration' VM with '$VMCpuCount' CPU(s), connected to '$VMSwitchName'"
New-VM -VMName $VMName -MemoryStartupBytes $VMStartupMem -Generation $VMGeneration -SwitchName $VMSwitchName
Write-Verbose -Message "Adding hard drive to '$VMName'"
Add-VMHardDiskDrive -VMName $VMName -Path $Destination
Write-Verbose -Message "Adding CPU(s) to '$VMName'"
Set-VMProcessor –VMName $VMName –Count $VMCpuCount
Write-Verbose -Message "Configuring memory on '$VMName'"
Set-VMMemory $VMName -DynamicMemoryEnabled $true -MinimumBytes $VMMinMem -StartupBytes $VMStartupMem -MaximumBytes $VMMaxMem
$VMHardDrive = Get-VMHardDiskDrive -VMName $VMName
Write-Verbose -Message "Changing first boot device to be '$($VMHardDrive.Name)'"
Set-VMFirmware -VMName $VMName -FirstBootDevice $VMHardDrive
Write-Verbose -Message "Enabling '$VMGuestIntergrationService' for '$VMName'"
Enable-VMIntegrationService -VMName $VMName -Name $VMGuestIntergrationService
Write-Verbose -Message "Populating VM notes for '$VMName'"
Set-VM -VMName $VMName -Notes "OS image build $ImageBuild for Win 10"
Write-Verbose -Message "Starting '$VMName'"
Get-VM -VMName $VMName | Start-VM
}
catch
{
Throw "Failed to create $VMName VM. $($_.Exception.Message)"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment