Skip to content

Commit 0f88eba

Browse files
Merge pull request #181 from Azure/rbao/deployment
Refine PrepareBootFromVHD.ps1
2 parents 6fee470 + c7bfd71 commit 0f88eba

1 file changed

Lines changed: 124 additions & 92 deletions

File tree

Deployment/PrepareBootFromVHD.ps1

Lines changed: 124 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -10,48 +10,48 @@ Prepare the host to boot from the Azure Stack virtual harddisk. 
1010
.DESCRIPTION 
1111
 
1212
PrepareBootFromVHD updates the boot configuration with an Azure Stack entry.
13-
It will verify if the disk that hosts the cloudbuilder.vhdx contains the required free disk space, Optionally copy drivers and an unattend.xml that does not require KVM access.
13+
It will verify if the disk that hosts the CloudBuilder.vhdx contains the required free disk space, Optionally copy drivers and an unattend.xml that does not require KVM access.
1414
1515
 
1616
.PARAMETER CloudBuilderDiskPath  
1717
 
18-
Path to the cloudbuilder.vhdx. This parameter is mandatory.
18+
Path to the CloudBuilder.vhdx. This parameter is mandatory.
1919
2020
.PARAMETER DriverPath  
2121
 
22-
This optional parameter allows you to add additional drivers for the host in the virtual harddisk. Specify the path that contains the drivers and all its content wil be copied the to cloudbuilder.vhdx.
22+
This optional parameter allows you to add additional drivers for the host in the virtual harddisk. Specify the path that contains the drivers and all its content wil be copied the to CloudBuilder.vhdx.
2323
 
2424
.PARAMETER ApplyUnattend 
2525
 
2626
ApplyUnattend is a switch parameter. If this parameter is specified, the configuration of the Operating System is automated.
27-
After the host reboots to the cloudbuilder.vhdx, the Operating System is automatically configured and you can connect to the host with RDP without KVM requirement.
27+
After the host reboots to the CloudBuilder.vhdx, the Operating System is automatically configured and you can connect to the host with RDP without KVM requirement.
2828
If this parameter is specified, you will be prompted for an local administrator password that is used for the unattend.xml.
29-
If this parameter is not specified, a minimal unattend.xml is used to enable Remote Desktop. KVM is required to configure the Operating System when booting from cloudbuilder.vhdx for the first time.
29+
If this parameter is not specified, a minimal unattend.xml is used to enable Remote Desktop. KVM is required to configure the Operating System when booting from vs.vhdx for the first time.
3030
3131
.PARAMETER AdminPassword  
3232
 
3333
The AdminPassword parameter is only used when the ApplyUnnatend parameter is set. The Password requires minimal 6 characters.
3434
 
3535
.EXAMPLE 
3636
 
37-
Prepare the host to boot from cloudbuilder.vhdx. This requires KVM access to the host for configuring the Operating System.
38-
.\PrepareBootFromVHD.ps1 -CloudBuilderDiskPath c:\CloudBuilder.vhdx -DriverPath c:\VhdDrivers
37+
Prepare the host to boot from CloudBuilder.vhdx. This requires KVM access to the host for configuring the Operating System.
38+
.\PrepareBootFromVHD.ps1 -CloudBuilderPath c:\CloudBuilder.vhdx -DriverPath c:\VhdDrivers
3939
 
4040
.EXAMPLE  
4141
 
42-
Prepare the host to boot from cloudbuilder.vhdx. The Operating System is automatically configured with an unattend.xml.
43-
.\PrepareBootFromVHD.ps1 -CloudBuilderDiskPath c:\CloudBuilder.vhdx -ApplyUnattend
42+
Prepare the host to boot from CloudBuilder.vhdx. The Operating System is automatically configured with an unattend.xml.
43+
.\PrepareBootFromVHD.ps1 -CloudBuilderPath c:\CloudBuilder.vhdx -ApplyUnattend
4444
  
4545
.NOTES 
4646
 
47-
You will need at least (120GB - Size of the cloudbuilder.vhdx file) of free disk space on the disk that contains the CloudBuilder.vhdx.
47+
You will need at least (120GB - Size of the CloudBuilder.vhdx file) of free disk space on the disk that contains the CloudBuilder.vhdx.
4848
 
4949
#>
5050

5151
[CmdletBinding()]
5252
Param (
5353
[Parameter(Mandatory=$true)]
54-
$CloudBuilderDiskPath,
54+
[String]$CloudBuilderDiskPath,
5555

5656
[Parameter(Mandatory=$false)]
5757
[string[]]$DriverPath = $null,
@@ -66,123 +66,155 @@ Param (
6666
[String]$VHDLanguage = "en-US"
6767
)
6868

69+
$error.Clear()
70+
6971
#region Allow for manual override of PS (replace $PSScriptRoot with a path)
70-
$currentPath = $PSScriptRoot
71-
# $currentPath = 'C:\ForRTMBuilds'
72-
# $CloudBuilderDiskPath = 'C:\CloudBuilder.vhdx'
72+
$currentPath = $PSScriptRoot
73+
# $currentPath = 'C:\ForRTMBuilds'
74+
# $CloudBuilderDiskPath = 'C:\CloudBuilder.vhdx'
7375
#endregion
7476

75-
if (!(Test-Path -Path $CloudBuilderDiskPath))
76-
{
77-
Write-Host "Can't find cloudbuilder vhd"
77+
#region Check parameters and prerequisites
78+
79+
if (-not (Test-Path -Path $CloudBuilderDiskPath)) {
80+
Write-Host "Can't find CloudBuilder.vhdx." -ForegroundColor Red
7881
Exit
79-
}
82+
}
8083

81-
if ((Get-DiskImage -ImagePath $CloudBuilderDiskPath).Attached)
82-
{
83-
Write-Host "VHD Already mounted"
84+
if ((Get-DiskImage -ImagePath $CloudBuilderDiskPath).Attached) {
85+
Write-Host "CloudBuilder.vhdx is already mounted." -ForegroundColor Red
8486
Exit
85-
}
87+
}
8688

87-
if ((get-disk | where {$_.isboot -eq $true}).Model -match 'Virtual Disk')
88-
{
89-
Write-Host "The server is currently already booted from a virtual hard disk, to boot the server from the CloudBuilder.vhdx you will need to run this script on an Operating System that is installed on the physical disk of this server."
89+
# Validate the CloudBuilder.vhdx is in a physical disk
90+
$cbhDriveLetter = (Get-Item $CloudBuilderDiskPath).PSDrive.Name
91+
92+
if (-not $cbhDriveLetter) {
93+
Write-Host "The given CloudBuilder.vhdx path is not a local path." -ForegroundColor Red
9094
Exit
95+
}
96+
else {
97+
$hostDisk = Get-Partition -DriveLetter $cbhDriveLetter | Get-Disk
98+
if ($hostDisk.Model -match 'Virtual Disk') {
99+
Write-Host "The CloudBuilder.vhdx is in a virtual hard disk, please place it in a physical disk." -ForegroundColor Red
100+
Exit
91101
}
102+
}
92103

93-
if (($ApplyUnattend) -and (!($AdminPassword)))
94-
{
95-
while ($SecureAdminPassword.Length -le 6) {
96-
[System.Security.SecureString]$SecureAdminPassword = read-host 'Password for the local administrator account of the Azure Stack host. Requires 6 characters minimum' -AsSecureString
104+
if ($ApplyUnattend) {
105+
# Check if unattend_NoKVM.xml is downloaded
106+
$unattendRawFile = Join-Path $currentPath 'unattend_NoKVM.xml'
107+
if (-not (Test-Path $unattendRawFile)) {
108+
Write-Host "-ApplyUnattend is specified, but unattend_NoKVM.xml is not downloaded to the same directory of this script." -ForegroundColor Red
109+
Exit
110+
}
111+
112+
# Check Admin password for unattend
113+
if(-not $AdminPassword) {
114+
while ($SecureAdminPassword.Length -le 6) {
115+
[System.Security.SecureString]$SecureAdminPassword = read-host 'Password for the local administrator account of the Azure Stack host. Requires 6 characters minimum' -AsSecureString
97116
}
98-
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureAdminPassword)
99-
$AdminPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
100-
Write-Host "The following password will be configured for the local administrator account of the Azure Stack host:"
101-
Write-Host $AdminPassword -ForegroundColor Cyan
117+
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureAdminPassword)
118+
$AdminPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
119+
Write-Host "The following password will be configured for the local administrator account of the Azure Stack host:"
120+
Write-Host $AdminPassword -ForegroundColor Cyan
102121
}
122+
}
103123

104-
#region Validate disk space for expanding cloudbuilder.vhdx
105-
$cbhDriveLetter = (Get-Item $CloudBuilderDiskPath).PSDrive.Name
124+
# Validate disk space for expanding cloudbuilder.vhdx
106125
$cbhDiskSize = [math]::truncate((get-volume -DriveLetter $cbhDriveLetter).Size / 1GB)
107126
$cbhDiskRemaining = [math]::truncate((get-volume -DriveLetter $cbhDriveLetter).SizeRemaining / 1GB)
108127
$cbDiskSize = [math]::truncate((Get-Item $CloudBuilderDiskPath).Length / 1GB)
109128
$cbDiskSizeReq = 120
110-
if (($cbDiskSizeReq - $cbDiskSize) -ge $cbhDiskRemaining)
111-
{
129+
if (($cbDiskSizeReq - $cbDiskSize) -ge $cbhDiskRemaining) {
112130
Write-Host 'Error: Insufficient disk space' -BackgroundColor Red
113131
Write-Host 'Cloudbuilder.vhdx is placed on' ((Get-Item $CloudBuilderDiskPath).PSDrive.Root) -ForegroundColor Yellow
114132
Write-Host 'When you boot from CloudBuilder.vhdx the virtual hard disk will be expanded to its full size of' $cbDiskSizeReq 'GB.' -ForegroundColor Yellow
115133
Write-Host ((Get-Item $CloudBuilderDiskPath).PSDrive.Root) 'does not contain enough free space.' -ForegroundColor Yellow
116134
Write-Host 'You need' ($cbDiskSizeReq - $cbDiskSize) 'GB of free disk space for a succesfull boot from CloudBuilder.vhdx, but' ((Get-Item $CloudBuilderDiskPath).PSDrive.Root) 'only has' $cbhDiskRemaining 'GB remaining.' -ForegroundColor Yellow
117135
Write-Host 'Ensure Cloudbuilder.vhdx is placed on a local disk that contains enough free space and rerun this script.' -ForegroundColor Yellow
118136
Write-Host 'Exiting..' -ForegroundColor Yellow
119-
Break
120-
}
121-
#endregion
122-
123-
#region Remove boot from previous deployment
124-
$bootOptions = bcdedit /enum | Select-String 'path' -Context 2,1
125-
$bootOptions | ForEach {
126-
if ((($_.Context.PreContext[1] -replace '^device +') -like '*CloudBuilder.vhdx*') -and ((($_.Context.PostContext[0] -replace '^description +') -eq 'AzureStack TP2') -or (($_.Context.PostContext[0] -replace '^description +') -eq 'Azure Stack')))
127-
{
128-
Write-Host 'The boot configuration contains an existing CloudBuilder.vhdx entry' -ForegroundColor Cyan
129-
Write-Host 'Description:' ($_.Context.PostContext[0] -replace '^description +') -ForegroundColor Cyan
130-
Write-Host 'Device:' ($_.Context.PreContext[1] -replace '^device +') -ForegroundColor Cyan
131-
Write-Host 'Removing the old entry'
132-
$BootID = '"' + ($_.Context.PreContext[0] -replace '^identifier +') + '"'
133-
Write-Host 'bcdedit /delete' $BootID -ForegroundColor Yellow
134-
bcdedit /delete $BootID
135-
}
136-
137+
Exit
137138
}
138139
#endregion
139140

140-
#region Add boot entry for VHDX
141-
$cbdisk = Mount-DiskImage -ImagePath $CloudBuilderDiskPath -PassThru | Get-DiskImage | Get-Disk
142-
$partitions = $cbdisk | Get-Partition | Sort-Object -Descending -Property Size
143-
$CBDriveLetter = $partitions[0].DriveLetter
144-
Write-Host 'Creating new boot entry for CloudBuilder.vhdx' -ForegroundColor Cyan
145-
Write-Host 'Running command: bcdboot' $CBDriveLetter':\Windows' -ForegroundColor Yellow
146-
bcdboot $CBDriveLetter':\Windows'
147-
148-
$bootOptions = bcdedit /enum | Select-String 'path' -Context 2,1
149-
$bootOptions | ForEach {
150-
if (((($_.Context.PreContext[1] -replace '^device +') -eq ('partition='+$CBDriveLetter+':') -or (($_.Context.PreContext[1] -replace '^device +') -like '*CloudBuilder.vhdx*')) -and (($_.Context.PostContext[0] -replace '^description +') -ne 'Azure Stack')))
151-
{
152-
Write-Host 'Updating description for the boot entry' -ForegroundColor Cyan
153-
Write-Host 'Description:' ($_.Context.PostContext[0] -replace '^description +') -ForegroundColor Cyan
154-
Write-Host 'Device:' ($_.Context.PreContext[1] -replace '^device +') -ForegroundColor Cyan
155-
$BootID = '"' + ($_.Context.PreContext[0] -replace '^identifier +') + '"'
156-
Write-Host 'bcdedit /set' $BootID 'description "Azure Stack"' -ForegroundColor Yellow
157-
bcdedit /set $BootID description "Azure Stack"
158-
}
159-
}
160-
#endregion
141+
#region Prepare Azure Stack virtual harddisk
142+
143+
Write-Host "Preparing Azure Stack virtual harddisk"
144+
$cbdisk = Mount-DiskImage -ImagePath $CloudBuilderDiskPath -PassThru | Get-DiskImage | Get-Disk
145+
$partitions = $cbdisk | Get-Partition | Sort-Object -Descending -Property Size
146+
$CBDriveLetter = $partitions[0].DriveLetter
161147

162-
#region ApplyUnatted
163-
    if($ApplyUnattend)
164-
    {
148+
# ApplyUnattend
149+
if($ApplyUnattend) {
150+
Write-Host "Apply unattend.xml with given password and language" -ForegroundColor Cyan
165151
$UnattendedFile = Get-Content ($currentPath + '\unattend_NoKVM.xml')
166152
$UnattendedFile = ($UnattendedFile).Replace('%productkey%', '74YFP-3QFB3-KQT8W-PMXWJ-7M648')
167153
$UnattendedFile = ($UnattendedFile).Replace('%locale%', $VHDLanguage)
168154
$UnattendedFile = ($UnattendedFile).Replace('%adminpassword%', $AdminPassword)
169155
$UnattendedFile | Out-File ($CBDriveLetter+":\unattend.xml") -Encoding ascii
170-
    }
171-
    else
172-
     {
173-
Copy-Item ($currentPath + '\Unattend.xml') -Destination ($CBDriveLetter + ':\') -Force
174-
     }
156+
}
157+
else {
158+
# Apply defautl unattend.xml if it exists
159+
$defaultUnattend = Join-Path $currentPath 'Unattend.xml'
160+
if (Test-Path $defaultUnattend) {
161+
Write-Host "Apply default unattend.xml to disable IE-ESC and enable remote desktop" -ForegroundColor Cyan
162+
Copy-Item $defaultUnattend -Destination ($CBDriveLetter + ':\') -Force
163+
}
164+
}
165+
166+
# Add drivers
167+
if (-not $DriverPath) {
168+
Write-Host "Apply given drivers" -ForegroundColor Cyan
169+
foreach ($subdirectory in $DriverPath) {
170+
Add-WindowsDriver -Driver $subdirectory -Path "$($CBDriveLetter):\" -Recurse
171+
}
172+
}
175173
#endregion
176174

177-
#region Add drivers
178-
if ($DriverPath -ne $null)
179-
{
180-
foreach ($subdirectory in $DriverPath)
181-
{
182-
Add-WindowsDriver -Driver $subdirectory -Path "$($CBDriveLetter):\" -Recurse
183-
}
175+
#region Configure boot options
176+
177+
# Remove boot from previous deployment
178+
$bootOptions = bcdedit /enum | Select-String 'path' -Context 2,1
179+
$bootOptions | ForEach {
180+
if ((($_.Context.PreContext[1] -replace '^device +') -like '*CloudBuilder.vhdx*') `
181+
-and ((($_.Context.PostContext[0] -replace '^description +') -eq 'AzureStack TP2') `
182+
-or (($_.Context.PostContext[0] -replace '^description +') -eq 'Azure Stack'))) {
183+
Write-Host 'The boot configuration contains an existing CloudBuilder.vhdx entry' -ForegroundColor Cyan
184+
Write-Host 'Description:' ($_.Context.PostContext[0] -replace '^description +') -ForegroundColor Cyan
185+
Write-Host 'Device:' ($_.Context.PreContext[1] -replace '^device +') -ForegroundColor Cyan
186+
Write-Host 'Removing the old entry'
187+
$BootID = '"' + ($_.Context.PreContext[0] -replace '^identifier +') + '"'
188+
Write-Host 'bcdedit /delete' $BootID -ForegroundColor Yellow
189+
bcdedit /delete $BootID
190+
}
191+
}
192+
193+
# Add boot entry for CloudBuilder.vhdx
194+
Write-Host 'Creating new boot entry for CloudBuilder.vhdx' -ForegroundColor Cyan
195+
Write-Host 'Running command: bcdboot' $CBDriveLetter':\Windows' -ForegroundColor Yellow
196+
bcdboot $CBDriveLetter':\Windows'
197+
198+
$bootOptions = bcdedit /enum | Select-String 'path' -Context 2,1
199+
$bootOptions | ForEach {
200+
if ((($_.Context.PreContext[1] -replace '^device +') -eq ('partition='+$CBDriveLetter+':') `
201+
-or (($_.Context.PreContext[1] -replace '^device +') -like '*CloudBuilder.vhdx*')) `
202+
-and (($_.Context.PostContext[0] -replace '^description +') -ne 'Azure Stack')) {
203+
Write-Host 'Updating description for the boot entry' -ForegroundColor Cyan
204+
Write-Host 'Description:' ($_.Context.PostContext[0] -replace '^description +') -ForegroundColor Cyan
205+
Write-Host 'Device:' ($_.Context.PreContext[1] -replace '^device +') -ForegroundColor Cyan
206+
$BootID = '"' + ($_.Context.PreContext[0] -replace '^identifier +') + '"'
207+
Write-Host 'bcdedit /set' $BootID 'description "Azure Stack"' -ForegroundColor Yellow
208+
bcdedit /set $BootID description "Azure Stack"
184209
}
210+
}
185211
#endregion
186212

187-
### Restart Computer ###
213+
if(-not $error) {
214+
Write-Host "Restart computer to boot from Azure Stack virtual harddisk" -ForegroundColor Cyan
215+
### Restart Computer ###
188216
Restart-Computer -Confirm
217+
}
218+
else {
219+
Write-Host "Fail to prepare CloudBuilder.vhdx. Errors: $($error)" -ForegroundColor Red
220+
}

0 commit comments

Comments
 (0)