We have been having some great feedback over the past few weeks on the blog series and especially the BigDemo Script that Dave uses for his deployments.

 

So, in the spirit of community and the love of sharing information here is a nice breakdown of exactly how the script works.

 

First of all It can be downloaded from www.github.com/dkawula

 

How to build your Gold VHDx images using PowerShell

I figured that we would start with the basics in this script. You can’t have a lab with AD, DHCP, DNS, and Storage Spaces Direct without a Gold image.

 

This is where the Big_Demo script really shines.

 

We use it to build out lab environments for a variety of purposes and one of the key elements of building a lab is having a nice Gold Base VHDx.

We often work with Beta’s and Release Candidates of Operating systems from Microsoft and often have to change the labs based on the current release of an operating system.

 

Big_Demo has some functions in it that will help you build out Gold Base VHDx files from an ISO file using a built-in script called Convert-WindowsImage.PS1.

 

Before we get started we need to setup a few basic things in the Folder Structure:

 

  • Download a copy of the Windows Server 2016 ISO and place it in a working folder.
    • In our example, we will use: c:\clusterstorage\volume1\dcbuild1

 


Downloaded Windows Server 2016 ISO in a working directory

 

Define the Variables

We start off by defining a few variables that will be used to build our Gold Base VHDx’s.

$BaseVHDPath = 'C:\ClusterStorage\Volume1\DCBuild1'
$ServerISO = 'C:\ClusterStorage\Volume1\DCBuild1\en_windows_server_2016_x64_dvd_9327751.iso'
$workingdir = 'C:\ClusterStorage\Volume1\DCBuild1' 

 

Build $unnatendSource

 

Next, we want to Build an Unnattend.xml file that will be used injected into the Base Images during the creation process.

You will want to change the <ProductKey> for use in your own lab.


$unattendSource = @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <servicing></servicing>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <ComputerName>*</ComputerName>
            <ProductKey>xxxxx-xxxxx-xxxxx-xxxxx</ProductKey> 
            <RegisteredOrganization>Organization</RegisteredOrganization>
            <RegisteredOwner>Owner</RegisteredOwner>
            <TimeZone>TZ</TimeZone>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <HideLocalAccountScreen>true</HideLocalAccountScreen>
                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                <NetworkLocation>Work</NetworkLocation>
                <ProtectYourPC>1</ProtectYourPC>
            </OOBE>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>password</Value>
                    <PlainText>True</PlainText>
                </AdministratorPassword>
            </UserAccounts>
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>en-us</InputLocale>
            <SystemLocale>en-us</SystemLocale>
            <UILanguage>en-us</UILanguage>
            <UILanguageFallback>en-us</UILanguageFallback>
            <UserLocale>en-us</UserLocale>
        </component>
    </settings>
</unattend>
"@

 

Get-UnattendChunk

 

The next thing we want to be able to do is modify the values of our base unnatend.xml file on the fly based on parameters that we define during a script execution. To do this we use a function called Get-UnattendChunk.

function Get-UnattendChunk 
{
    param
    (
        [string] $pass, 
        [string] $component, 
         $unattend
    ) 
    
    return $unattend.unattend.settings |
    Where-Object -Property pass -EQ -Value $pass `
    |
    Select-Object -ExpandProperty component `
    |
    Where-Object -Property name -EQ -Value $component
} 

 

New-Unattendfile

 

The next step is to create an Unattend.xml file. To do this we will use the Get-UnattendChunk function that we created earlier to modify the values of the of the above Unattend file. As you can see below we will change values in the file like:

 

•    Registered Organization

•    Registered Owner

•    Time Zone

•    AdministratorPassword

 

function New-UnattendFile 
{
    param
    (
        [string] $filePath
    ) 

    # Reload template - clone is necessary as PowerShell thinks this is a "complex" object
    $unattend = $unattendSource.Clone()
     
    # Customize unattend XML
    Get-UnattendChunk 'specialize' 'Microsoft-Windows-Shell-Setup' $unattend | ForEach-Object -Process {
        $_.RegisteredOrganization = 'Azure Sea Class Covert Trial' #TR-Egg
    }
    Get-UnattendChunk 'specialize' 'Microsoft-Windows-Shell-Setup' $unattend | ForEach-Object -Process {
        $_.RegisteredOwner = 'Thomas Rayner - @MrThomasRayner - workingsysadmin.com' #TR-Egg
    }
    Get-UnattendChunk 'specialize' 'Microsoft-Windows-Shell-Setup' $unattend | ForEach-Object -Process {
        $_.TimeZone = 'Pacific Standard Time'
    }
    Get-UnattendChunk 'oobeSystem' 'Microsoft-Windows-Shell-Setup' $unattend | ForEach-Object -Process {
        $_.UserAccounts.AdministratorPassword.Value = 'P@ssw0rd'
    }

    $unattend.Save($filePath)



 

New-BaseImage

 

The last function that we use is called New-BaseImage and it leverages a PowerShell Script called Convert-WindowsImage.PS1 that will be extracted from the Windows Server 2016 ISO to build our Gold Base VHDx files for both Windows Server 2016 Full GUI and Core.

This Function New-BaseImage will do the following:

 

  • Mount the Windows Server ISO
  • Copy Convert-WindowsImage.PS1 to the working directory
  • Create a new Unnatend.xml file it in the working directory
  • Check to see if the file VMServerBaseCore.vhdx exists
    • If it does not exist we continue execution and define the structure of the new vhdx
    • Once done then we execute Convert-WindowsImage.PS1 to build the Gold Image
  • Step 4 repeats to build VMServerBase.vhdx which is the full UI version of Windows Server 2016.

Function New-BaseImage
{



    Mount-DiskImage $ServerISO
    $DVDDriveLetter = (Get-DiskImage $ServerISO | Get-Volume).DriveLetter
    Copy-Item -Path "$($DVDDriveLetter):\NanoServer\NanoServerImageGenerator\Convert-WindowsImage.ps1" -Destination "$($WorkingDir)\Convert-WindowsImage.ps1" -Force
   
             
    New-UnattendFile "$WorkingDir\unattend.xml"


    #Build the Windows 2016 Core Base VHDx for the Lab
    
            if (!(Test-Path "$($BaseVHDPath)\VMServerBaseCore.vhdx")) 
                        {
            

            Set-Location $workingdir 

            # Load (aka "dot-source) the Function 
            . .\Convert-WindowsImage.ps1 
            # Prepare all the variables in advance (optional) 
            $ConvertWindowsImageParam = @{  
                SourcePath          = $ServerISO
                RemoteDesktopEnable = $True  
                Passthru            = $True  
                Edition    = "ServerDataCenterCore"
                VHDFormat = "VHDX"
                SizeBytes = 60GB
                WorkingDirectory = $workingdir
                VHDPath = "$($BaseVHDPath)\VMServerBaseCore.vhdx"
                DiskLayout = 'UEFI'
                UnattendPath = "$($workingdir)\unattend.xml" 
            }

            $VHDx = Convert-WindowsImage @ConvertWindowsImageParam

            }


            #Build the Windows 2016 Full UI Base VHDx for the Lab
    
            if (!(Test-Path "$($BaseVHDPath)\VMServerBase.vhdx")) 
                        {
            

            Set-Location $workingdir 

            # Load (aka "dot-source) the Function 
            . .\Convert-WindowsImage.ps1 
            # Prepare all the variables in advance (optional) 
            $ConvertWindowsImageParam = @{  
                SourcePath          = $ServerISO
                RemoteDesktopEnable = $True  
                Passthru            = $True  
                Edition    = "ServerDataCenter"
                VHDFormat = "VHDX"
                SizeBytes = 60GB
                WorkingDirectory = $workingdir
                VHDPath = "$($BaseVHDPath)\VMServerBase.vhdx"
                DiskLayout = 'UEFI'
                UnattendPath = "$($workingdir)\unattend.xml" 
            }

            $VHDx = Convert-WindowsImage @ConvertWindowsImageParam

            }
            
    
    Dismount-DiskImage $ServerISO 
   

} 

 

The finished result will look like this:

 


Base Gold VHDx images created with PowerShell

 

Now you are ready to go build your lab on Hyper-V.

 

Happy learning….

 

Thanks,

 

Cristal

 

Cristal Kawula

Cristal Kawula is the co-founder of MVPDays Community Roadshow and #MVPHour live Twitter Chat. She was also a member of the Gridstore Technical Advisory board and is the President of TriCon Elite Consulting. Cristal is also only the 2nd woman in the world to receive the prestigious Veeam Vanguard award.

 

BLOG: http://www.checkyourlogs.net

Twitter: @supercristal1 / @mvpdays / #mvphour

Check out www.mvpdays.com to see where the MVPDays Roadshow will be next. Maybe it will be in a city near you.