Last active
January 31, 2017 21:40
-
-
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.
This file contains hidden or 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
#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 <[email protected]> | |
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