|
#requires -Version 3 -Modules Dism, Hyper-V, ScheduledTasks |
|
|
|
#Wrapper arround Convert-WindowsImage script to it acts like a function. |
|
function Convert-WindowsImage |
|
{ |
|
Param |
|
( |
|
[Parameter(ParameterSetName = 'SRC', Mandatory = $true, ValueFromPipeline = $true)] |
|
[Alias('WIM')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateScript({ |
|
Test-Path -Path $(Resolve-Path $_) |
|
} |
|
)] |
|
$SourcePath, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('VHD')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
$VHDPath, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('WorkDir')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateScript({ |
|
Test-Path $_ |
|
} |
|
)] |
|
$WorkingDirectory = $pwd, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('Size')] |
|
[UInt64] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateRange(512MB, 64TB)] |
|
$SizeBytes = 40GB, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('Format')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateSet('VHD', 'VHDX')] |
|
$VHDFormat = 'VHD', |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('DiskType')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateSet('Dynamic', 'Fixed')] |
|
$VHDType = 'Dynamic', |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('Unattend')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateScript({ |
|
Test-Path -Path $(Resolve-Path $_) |
|
} |
|
)] |
|
$UnattendPath, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
$Feature, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Alias('SKU')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
$Edition, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Parameter(ParameterSetName = 'UI')] |
|
[string] |
|
$BCDBoot = 'bcdboot.exe', |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Parameter(ParameterSetName = 'UI')] |
|
[switch] |
|
$Passthru, |
|
|
|
[Parameter(ParameterSetName = 'UI')] |
|
[switch] |
|
$ShowUI, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Parameter(ParameterSetName = 'UI')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateSet('None', 'Serial', '1394', 'USB', 'Local', 'Network')] |
|
$EnableDebugger = 'None', |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateSet('MBR', 'GPT')] |
|
$VHDPartitionStyle = 'MBR', |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateSet('NativeBoot', 'VirtualMachine')] |
|
$BCDinVHD = 'VirtualMachine', |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Switch] |
|
$ExpandOnNativeBoot = $true, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[Switch] |
|
$RemoteDesktopEnable = $False, |
|
|
|
[Parameter(ParameterSetName = 'SRC')] |
|
[string] |
|
[ValidateNotNullOrEmpty()] |
|
[ValidateScript({ |
|
Test-Path -Path $(Resolve-Path $_) |
|
} |
|
)] |
|
$Driver |
|
|
|
) |
|
#$psboundparameters |
|
|
|
. "$($PSScriptRoot)\Convert-WindowsImage.ps1" @psboundparameters |
|
} |
|
|
|
#Creates fully patched and compatcted CORE VHDX and Fully Patched GUI WIM with -Features installed. and Places them in -OutPath |
|
function Start-ImageBuild |
|
{ |
|
[CmdletBinding()] |
|
[Alias()] |
|
Param |
|
( |
|
# OutPath |
|
[Parameter(Mandatory = $true)] |
|
[ValidateNotNullOrEmpty()] |
|
[Alias('op')] |
|
[String] |
|
$OutPath, |
|
|
|
# ISO Path |
|
[Parameter(Mandatory = $true)] |
|
[ValidateNotNullOrEmpty()] |
|
[Alias('ip')] |
|
[String] |
|
$IsoPath, |
|
|
|
# VMSwitch to attach to |
|
[Parameter(Mandatory = $true)] |
|
[ValidateNotNullOrEmpty()] |
|
[Alias('vs')] |
|
[String] |
|
$VmSwitch, |
|
|
|
# Create WIM Only |
|
[switch] |
|
$WimOnly, |
|
|
|
# Iso Core Edition |
|
[int] |
|
[Alias('CoreEdition')] |
|
$IsoCoreEdition = 3, |
|
|
|
# Iso GUI Edition |
|
[int] |
|
[Alias('GuiEdition')] |
|
$IsoGuiEdition = 4, |
|
|
|
#features used by get-windowsFeature that you want installed on the WIM |
|
# [string] |
|
# [ValidateNotNullOrEmpty()] |
|
# $Feature, |
|
|
|
#Save patched vhdx for use with update-ImageBuild |
|
[switch] |
|
$SavedPatchVHDX, |
|
|
|
# Working Folder |
|
[Parameter()] |
|
[Alias('wf')] |
|
[String] |
|
$WorkingFolder = $OutPath |
|
) |
|
#region validate input and dependent files |
|
Try |
|
{ |
|
Write-Verbose -Message "Testing $OutPath" |
|
if (-not (Test-Path $OutPath)) |
|
{ |
|
Write-Verbose -Message "Creating $OutPath" |
|
New-Item -ItemType directory -Path $OutPath -ErrorAction Stop |
|
} |
|
Write-Verbose -Message "Testing $WorkingFolder" |
|
if (-not (Test-Path $WorkingFolder)) |
|
{ |
|
Write-Verbose -Message "Creating $WorkingFolder" |
|
New-Item -ItemType directory -Path $WorkingFolder -ErrorAction Stop |
|
} |
|
Write-Verbose -Message "Testing $VmSwitch" |
|
$null = Get-VMSwitch $VmSwitch -ErrorAction Stop |
|
Write-Verbose -Message "Testing $IsoPath" |
|
$null = Test-Path -Path $IsoPath -ErrorAction stop |
|
Write-Verbose -Message "Testing $PSScriptRoot\unattend.xml" |
|
$null = Test-Path -Path $PSScriptRoot\unattend.xml -ErrorAction stop |
|
Write-Verbose -Message "Testing $PSScriptRoot\Convert-WindowsImage.ps1" |
|
$null = Test-Path -Path $PSScriptRoot\Convert-WindowsImage.ps1 -ErrorAction stop |
|
} |
|
catch |
|
{ |
|
$msg = "Failed $($_.Exception.Message)" |
|
Write-Error $msg |
|
throw 'Input validation failed' |
|
} |
|
#endregion |
|
|
|
|
|
|
|
if ($WimOnly) |
|
{ |
|
$vmSet = 'Source' |
|
} |
|
else |
|
{ |
|
$vmSet = 'Source', 'Template' |
|
} |
|
|
|
foreach ($vmType in $vmSet ) |
|
{ |
|
#region Create VM |
|
$vhdFile = "$($vmType)_Patch.vhdx" |
|
$vmName = "$($vmType)" |
|
|
|
#<# |
|
# Remove any previous VMs |
|
if (Test-Path -Path "$WorkingFolder\$($vmSet).wim") |
|
{ |
|
Write-Warning -Message "Removinmg old WIM file: $WorkingFolder\$($vmSet).wim" |
|
Remove-Item -Path "$WorkingFolder\$($vmSet).wim" -Force |
|
} |
|
|
|
if (Test-Path -Path $WorkingFolder\$vhdFile) |
|
{ |
|
Write-Warning -Message "Removinmg old vhdx file: $WorkingFolder\$vhdFile" |
|
Remove-Item -Path "$WorkingFolder\$vhdFile" -Force |
|
} |
|
if (Get-VM -Name "$($vmType)" -ErrorAction SilentlyContinue) |
|
{ |
|
Write-Warning -Message "Removinmg old vm $($vmType)" |
|
Remove-VM -Name "$($vmType)" -Force |
|
} |
|
|
|
Write-Verbose -Message "Start creation of $vmType" |
|
|
|
if ($vmType -eq 'Template') |
|
{ |
|
$UseEdition = $IsoCoreEdition |
|
} |
|
else |
|
{ |
|
$UseEdition = $IsoGuiEdition |
|
} |
|
|
|
$CwiParamaters = @{ |
|
SourcePath = $IsoPath |
|
VHDPath = "$WorkingFolder\$vhdFile" |
|
SizeBytes = 40GB |
|
VHDFormat = 'VHDX' |
|
VHDPartitionStyle = 'GPT' |
|
VHDType = 'Dynamic' |
|
UnattendPath = "$PSScriptRoot\unattend.xml" |
|
Edition = $UseEdition |
|
} |
|
$CwiParamaters |Format-Table |
|
|
|
Write-Verbose -Message 'Creating VHDX from ISO' |
|
#. "$($PSScriptRoot)\Convert-WindowsImage.ps1" @Paramaters -Passthru |
|
Convert-WindowsImage @CwiParamaters -Passthru |
|
|
|
if (-not (Test-Path -Path "$WorkingFolder\Mount" )) |
|
{ |
|
mkdir -Path "$WorkingFolder\Mount" -Verbose |
|
} |
|
Mount-WindowsImage -ImagePath "$WorkingFolder\$vhdFile" -Path "$WorkingFolder\Mount" -Index 1 |
|
if (-not (Test-Path -Path "$WorkingFolder\Mount\PSTemp")) |
|
{ |
|
mkdir -Path "$WorkingFolder\Mount\PSTemp" -Verbose |
|
} |
|
Copy-Item -Path "$PSScriptRoot\$($vmType)-FirstRun.ps1" -Destination "$WorkingFolder\Mount\PSTemp\FirstRun.ps1" -Verbose |
|
Copy-Item -Path "$PSScriptRoot\$($vmType)-Features.txt" -Destination "$WorkingFolder\Mount\PSTemp\Features.txt" -ErrorAction SilentlyContinue -Verbose |
|
Copy-Item -Path "$PSScriptRoot\$($vmType)-FeaturesIncludingSub.txt" -Destination "$WorkingFolder\Mount\PSTemp\FeaturesIncludingSub.txt" -ErrorAction SilentlyContinue -Verbose |
|
Copy-Item -Path "$PSScriptRoot\WinUpdate.ps1" -Destination "$WorkingFolder\Mount\PSTemp\AtStartup.ps1" -Verbose |
|
|
|
Dismount-WindowsImage -Path "$WorkingFolder\Mount" -Save |
|
|
|
Write-Verbose -Message "Creating $vmName" |
|
New-VM -Name $vmName -VHDPath "$WorkingFolder\$vhdFile" -MemoryStartupBytes 1024MB -SwitchName $VmSwitch -Generation 2 -Verbose| |
|
Set-VMProcessor -Count 2 -Verbose |
|
|
|
if ($vmType -eq 'Source') |
|
{ |
|
Add-VMDvdDrive -Path $IsoPath -VMName $vmName -Verbose |
|
} |
|
Write-Verbose -Message "Starting Patchrun on $vmName" |
|
Start-VM $vmName -Verbose |
|
#endregion |
|
|
|
#region Wait for Patch |
|
while (Get-VM $vmName | Where-Object -Property state -EQ -Value 'running') |
|
{ |
|
Write-Verbose -Message "Wating for $vmName to stop" |
|
Start-Sleep -Seconds 30 |
|
} |
|
#endregion |
|
|
|
#region Sysprep |
|
if ($vmType -eq 'Template') |
|
{ |
|
Write-Verbose -Message "Copying $($vmType)_Patch.vhdx to$($vmType)_Sysprep.vhdx" |
|
Copy-Item -Path "$WorkingFolder\$($vmType)_Patch.vhdx" -Destination "$WorkingFolder\$($vmType)_Sysprep.vhdx" -Force -Verbose |
|
$vhdFile = "$($vmType)_Sysprep.vhdx" |
|
$vmName = "$($vmType)_Sysprep" |
|
New-VM -Name $vmName -VHDPath "$WorkingFolder\$vhdFile" -MemoryStartupBytes 1024MB -Generation 2 -Verbose | |
|
Set-VMProcessor -Count 2 -Verbose |
|
|
|
Write-Verbose -Message "Adding SysPrep script to $WorkingFolder\$vhdFile" |
|
Mount-WindowsImage -ImagePath "$WorkingFolder\$vhdFile" -Path "$WorkingFolder\Mount" -Index 1 -Verbose |
|
Copy-Item -Path "$PSScriptRoot\SysPrep.ps1" -Destination "$WorkingFolder\Mount\PSTemp\AtStartup.ps1" -Force -Verbose |
|
Dismount-WindowsImage -Path "$WorkingFolder\Mount" -Save -Verbose |
|
Write-Verbose -Message "Starting Cleanup and Sysprep of $vmName" |
|
Start-VM $vmName -Verbose |
|
while (Get-VM $vmName | Where-Object -Property state -EQ -Value 'running') |
|
{ |
|
Write-Verbose -Message "Wating for $vmName to stop" |
|
Start-Sleep -Seconds 30 |
|
} |
|
Remove-VM $vmName -Force -Verbose |
|
} |
|
|
|
#endregion |
|
|
|
#region Create WIM |
|
Write-Verbose -Message "Creating WIM from $WorkingFolder\$vhdFile" |
|
Mount-WindowsImage -ImagePath "$WorkingFolder\$vhdFile" -Path "$WorkingFolder\Mount" -Index 1 -Verbose |
|
|
|
New-WindowsImage -CapturePath "$WorkingFolder\Mount" -Name "2012r2_$vmType" -ImagePath "$WorkingFolder\$($vmType).wim" -Description "2012r2 $vmType Patched $(Get-Date)" -Verify -Verbose |
|
Dismount-WindowsImage -Path "$WorkingFolder\Mount" -Discard -Verbose |
|
|
|
|
|
if ($vmType -eq 'Template') |
|
{ |
|
$vhdFile = "$($vmType)_Sysprep.vhdx" |
|
$CwiParamaters = @{ |
|
SourcePath = "$WorkingFolder\$($vmType).wim" |
|
VHDPath = "$WorkingFolder\$($vmType)_Production.vhdx" |
|
SizeBytes = 40GB |
|
VHDFormat = 'VHDX' |
|
VHDPartitionStyle = 'GPT' |
|
VHDType = 'Dynamic' |
|
Edition = 1 |
|
} |
|
$CwiParamaters | Format-Table |
|
|
|
Write-Verbose -Message " Creating VHDX from WIM : $WorkingFolder\$($vmType).wim" |
|
Convert-WindowsImage @CwiParamaters -Passthru -Verbose |
|
Write-Verbose -Message 'Removing Temp files' |
|
Remove-Item -Path "$WorkingFolder\$($vmType).wim" -Force |
|
Remove-Item -Path "$WorkingFolder\$($vmType)_Sysprep.vhdx" -Force |
|
} |
|
#endregion |
|
|
|
#region Cleanup |
|
if ($OutPath -ne $WorkingFolder) |
|
{ |
|
Write-Verbose -Message "Moving $vmType to $OutPath" |
|
Remove-VM $vmType -Force -Verbose |
|
Copy-Item "$WorkingFolder\$($vmType)*.vhdx" $OutPath -Force -Verbose |
|
Copy-Item "$WorkingFolder\$($vmType).wim" $OutPath -Force -Verbose |
|
Write-Verbose -Message "Creating vm : $vmType" |
|
New-VM -Name $vmType -VHDPath "$OutPath\$($vmType)_Patch.vhdx" -MemoryStartupBytes 1024MB -SwitchName $VmSwitch -Generation 2 | |
|
Set-VMProcessor -Count 2 |
|
} |
|
#endregion |
|
} |
|
#region remove working if diferent from Out |
|
if ($OutPath -ne $WorkingFolder) |
|
{ |
|
Write-Verbose -Message "Cleandup of $WorkingFolder" |
|
Remove-Item -Path $WorkingFolder -Recurse -Force -Verbose |
|
} |
|
#endregion |
|
|
|
#region setup Monthly update |
|
if ( -not (Get-ScheduledTask -TaskName UpdateSourceAndTemplate)) |
|
{ |
|
$Paramaters = @{ |
|
Action = New-ScheduledTaskAction -Execute '%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -File $PSScriptRoot\Update-SourceAndTemplate.ps1" |
|
Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Wednesday -At 1AM |
|
Settings = New-ScheduledTaskSettingsSet |
|
} |
|
$TaskObject = New-ScheduledTask @Paramaters -Verbose |
|
Register-ScheduledTask UpdateSourceAndTemplate -InputObject $TaskObject -User 'nt authority\system' -Verbose |
|
} |
|
#endregion |
|
} |
|
|
|
Start-Transcript -Path $env:ALLUSERSPROFILE\logs\ImageBuild.log |
|
|
|
# Production |
|
#Start-ImageBuild -OutPath 'D:\BuildOut' -WorkingFolder 'd:\BuildWorking' -IsoPath 'D:\ISO\WindowsServer\Win_Svr_2012_R2_64Bit_English.ISO' -VmSwitch Isolated1 -Verbose |
|
# Lab |
|
Start-ImageBuild -OutPath 'g:\BuildOut' -WorkingFilder 'd:\BuildWorking' -IsoPath 'C:\iso\Server2012R2.ISO' -VmSwitch TestLab -Verbose |
|
|
|
Stop-Transcript |