Shaving the Yak, leads me to create new module WindowsImageTools

My Yak needs shaving.

If your not familiar with the term.

http://www.hanselman.com/blog/YakShavingDefinedIllGetThatDoneAsSoonAsIShaveThisYak.aspx

It’s been quite a journey since my last post.

Windows 10 came out, and Convert-WindowsImage.ps1 was upgraded (braking my scripts I blogged about this summer) and Server 2016 Preview 3 was released.

Looking at the Nano folder on Preview 3 they are using a WIM and Convert-WindowsImage.ps1to create a VHDX. Good move Microsoft.

Now for the bad part.

Convert-WindowImage.ps1 is buggy, and not a module. But Microsoft is working hart to fix this. Not being one to wait. I decided to take the functionality I need and re-work the whole process as a module. (and found some underlying bugs in PowerShell in the process )  The results of my effort is documented below.

As for Windows 10.

There are a number of changes to DSC that have broken all my production configuration scripts. And Configurations created on Windows10 or WMF 5 preview have bugs when using depends on, that cause the LCM on 2012R2 to hang.

For Production this is a show stopper, but I’m already working to separate out the configuration’s based on target OS.

WindowsImageTools

Microsoft recently moved Convert-WindowsImage over to GitHub and added some nice features, but it’s still a script, not a module. They also nicely added an MIT licence to that repo. So taking advantage of that I started my own project based on that code. I’m calling it WindowsImageTools

So far there are four exported functions.

  1. Initialize-VHDPartition
    1. Create a VHD with correct partition for BIOS or UEFI with or without Recovery tools/image
  2. Set-VHDPartition
    1. take an ISO or WIM and populate the VHD. This detects the layout and acts accordingly.
    2. It also can add drivers, enable features, inject unattend.xml and inject additional files or folders
  3. Convert-Wim2VHD
    1. This is a wrapper functions around the first two
  4. New-UnattendXml
    1. Create an Unattend.xml that works with both 32 and 64 bit in a single file
    2. Sets the admin password and autologin count
    3. Creates then deletes a second user (for Windows7)
    4. Sets TimeZone
    5. Starts a PowerShell script to bootstrap the system configuration

That last one took quite some work to figure out. It only fully works with Volume media because it does not set the license key.

I also discovered that 64bit windows will run both the 32bit sections for adding users and running scripts, but not the part for skipping licensing and autologin. This is true from win7 forward. If your not familiar with Unattend.xml thoes parts are all under the same section in the xml.

If you want to give it a spin it’s available at the PowerShell Gallery https://www.powershellgallery.com/packages/WindowsImageTools/

Onward

So equipped with thease tools i’m now going to reword my auto patching and WIM creation script. and add that into the module.

Building the basics Part 1 | PKI: RootCA

edit:7/26/2015

  • Added xNetworking and removed requirement for DHCP.
  • Please NOTE that this method does not currently work with WMF5 as the Host when targeting a WMF4 WM.

Invoke-DSCBuild -Target PKI | Test-Lab

Before I can get into the really fun stuff with DSC I need the prerequisites for a secure DSC environment. This involves a few things. The minimum is a Certificate Authority, the other is a HTTPS pull server. For my Lab I’m also going to also have a Domain Controller, because most enterprises will have a domain, and because it simplifies permissions.

  1. RootCA
  2. Domain Controller
  3. Enterprise CA
  4. Pull Server

Initial server build and Self Signed Certificate

I’m going to use my patched image as a baseline. Now because xAdcsDeployment and File resources require Credentials, I will have to create a self signed certificate to secure the passwords. I don’t want to use plaintext in a .MOF file.

Now I created a helper function that does the following.

Start-Deploy.ps1

  1. Copy the patch VHDX
  2. Mount to a temporary folder
  3. copy in files
    1. unattnd.xml (same one I used for the Patched Image creation)
    2. FirstRun.ps1 script
    3. AtStartup.ps1 script (SCC-Init.ps1)
    4. any other supporting scripts (New-SelfSignedCertificateEx)
  4. Save VHDX
  5. Start VM
  6. Wait for VM to stop

FirstRun.sp1:

  1. Create the AtStartup job to run AtStartup.ps1
  2. reboot

SCC-Init.sp1

  1. Start a transcript to SCC-Init.log
  2. check if pfx was copied in, (and I did not)
    1. if PFX present install that.
  3. if not, use New-SelfSignedCertificateEx (from Script Center)to create a certificate
  4. Export the certificate to a CER file
  5. shutdown the VM

Extracting the Certificate

Once we have a self signed certificate for encrypting passwords we need to get if off the VM and back to the host.

Again I created a helper function for this.

Get-VMCert.ps1

  1. Verify the VM is off
    1. if on, run stop-vm
  2. Mount the VHDX to a temporary folder
  3. copy the file from the relative path inside the VHDX to the supplied path on the host
  4. Dis-Mount the VHDX discarding changes changes.

Creating the MOF

Now due to how the ISE reacts to the having a “Configuration” in a file I separated out the configuration to a .ps1(Intelisence tends to timeout in scripts with a configuration in the ISE in WMF 4)

RootCA_Config.ps1

  1. Get the Certificate thumbprint from the CER file.
  2. Import Credentials from clixml for:
    1. Local Administrator (used to change the built-in Administrator and for setting up a Certificate Authority)
    2. Remote user needed to access the source.wim for adding features
  3. Create a hash table with AllNodes to pass the certificate thumbprint and CER file path with
  4. Configuration Block
    1. Set the IP Address
    2. Set Computer name
    3. Copy the source.wim from a remote share (my win8 workstation on the lab network)
      1. Now this point was something of a pain. In a production environment you don’t want to copy this file over. However due to how DISM (what WindowsFeature and install-WindowsFeatuere are wrappers for) Cant access a network share (even when guest in enabled) on WMF4. (WMF5 adds the ability to run each resource in specified user accounts and may fix this) The LCM runs as the local System account, In a Domain environment you can grant permissions via “Domain Computers” to a share and point the WindowsFeature resorce at the UNC to Source.WIM
    4. Install the Certificate Authority WindowsFeature
    5. Initialize the Certficate Authority useing xAdcsCertificationAuthority
    6. Set the location for the Certificate Revocation list and re-export
      1. This is done via a script resource, as xAdcsCertificationAuthority does not currently have a way to do this
  5. Create the LocalHost.Meta.MOF and LocalHost.MOF files

I had planed to set the IP address, but xNetworking was causing the DSC to hang in my tests.
So I had to turn on DHCP on my Voys router used in the Lab. I hope to track this down and submit a fix.

This issue was resolved and noted below

Starting DSC

Armed with the required .MOF files I need to get them copied into the VM and and start DSC. Once again a helper function, (pardon my poor naming conventions)

Set-VmLcm.ps1

  1. Stop the VM if it’s running
  2. Mount the VHDX to a temp folder
  3. Copy in a new AtStartup.ps1 (DSC-Init.ps1)
  4. If LocalHostMofFolder set
    1. Copy in LocalHost.mof and LocalHost.Meta.Mof
  5. Else copy
    1. CofnigMof as Pending.mof
  6. Create metaconfig.mof from supplied string
  7. Dismount VHDX saving changes

I initially was going to go with copy in Pending and set Metaconfig.mof directly then use  Invoke-CimMethod, as shown by the PowerShell team blog However after fighting with a failing config I went to Start-DscConfiguration. This did not fix my issue with xNetworking but I have had better luck with this method.

This issue actually had to do having WMF5 on the host. Removing WMF5 preview and returning to WMF4 and my problems were resolved. so I have added xNetworking back in.

However I’m staying with the current use of start-DSCConfiguration rather than a WMI call. The debugging is simpler with -verbose.

DSC-Init.ps1

  1. Starts a transcript to DSC-Init.log
  2. Unregister the AtStartup scheduled task
  3. Remove c:\Unattend.xml
    1. The plain text password contained inside will be changed by DSC but not point in leaving a mess.
  4. Enable the Analytic and Debug logs for DSC
  5. Run Set-DSCLocalConfigurationManager
  6. Run Start-DscConfiguration with -Wait -Verbose
  7. Stop-Transcript

Enabling the log channels and using -wait -verbose with a transcript helps a lot with debugging

Putting it all together

I put all the functions into a quickly made module to simplify importing them.

I created a wrapper script that will put all this together and build my Root-CA

Build-RootCA.ps1

  1. Set the a number of variables
    1. VM-Name
    2. Path to DSC Config Sript
    3. Start-Deploy parameters for splating
    4. Get-VmCert parameters for splating
    5. Set-VmMof Parameters fro splatting
    6. Create the clixml files containing the Passwords
  2. Import-Module on the sub folder holding all my functions
  3. Start-Deploy
  4. Get-VMCert
  5. Dot Source the DSC Config Script
  6. Set-VmLcm
  7. Start the VM
  8. Remove-Module -Name Functions

The reason for saving the passwords to XML avoids the need to enter credentials on subsequent runs of the script. I want to be able to tare down and rebuild the lab from scratch at any time.

One other file you will need is the unattend.xml

The folder structure for all of this is

G:\BuildLab
│   Build-DC.ps1
│   Build-RootCA.ps1
│   Root-CA.LocalAdminCred.xml
│   Root-CA.RemoteUserCred.xml
│   RootCA_Config.ps1
│   Unattend.xml
│
├───Functions
│       Functions.psd1
│       Functions.psm1
│       Get-VmCert.ps1
│       Set-VmLcm.ps1
│       Start-Deploy.ps1
│       Unattend.xml
│
├───Helpers
│       New-SelfSignedCertificateEx.ps1
│
├───Resorces_RootCA
│   ├───xAdcsDeployment
│   ├───xComputerManagement
│   ├───xDscDiagnostics
│   └───xNetworking
│
├───RootCA
│       localhost.meta.mof
│       localhost.mof
│
└───VMScripts
        DSC-Init.ps1
        FirstRun.ps1
        SSC-Init.ps1

Wrapping up

This script took a lot longer then planed and I got to use xDscDiagnostics a lot to track errors. That is outside of the scope of this post but I plan to write about it at some time. I did have to enable DHCP on the router, and that is something I hope to address and update this post. I want to use a DSC configured DHCP. but in this case I have a clasic chicken and egg issue. I need the CA, DC and Pull server first.

Creating a small footprint, base image Part 4 | Bringing it all together with automation

New-WindowsImage -size small | Test-Lab

One of the time consuming steps to deploying new VMs is the time spend managing Images and and applying patches. I’m not big on Golden images. I tend to use a fully patched VHDX or VMDK  and let DSC handle the configuration and software. This is not the fastest, and at scale you need to create more then one image based on what saves the most time.  (IIS, SQL, Exchange, etc…).

In this series, I’m going to go over how I create a a baseline image of 2012r2 with PowerShell. Also because Install-WindowsFeature has issues when the target VM has been patched, we are also going to create a fully patched WIM to use as -source.

Blogs in this Series

Get-Script | Set-Automation

This took a little longer than I had expected. However I’m satisfied with the results if not the quality of my code.

I shortened up the process in Part1 by pointing Convert-WindowsImage.ps1 at the ISO directly. And I added some code deal with working folder and cleaning files we dont need.

The basic process is as follows

  1. Create the VHDX from ISO
  2. Insert three files
    1. unattend.xml : sets up a silent OOBE, autologin and start the first script.
    2. FirstRun.ps1 : handles any windows features and sets creates an ‘atstartup’ task to run the next script
    3. AtStarup.ps1 : Installs windows updates, and reboots, once none left shuts down the computer
  3. Create a VM
    1. If WIM, attach the ISO
    2. start VM
    3. wait for VM to shutdown
  4. If it’s the Template
    1. copy the VHDX
    2. replace AtStartup with a script that cleans up extra files and runs sysprep
    3. createa new VMfor the sysprep
    4. start VM and wait for it to shutdown
    5. delete sysprep VM
  5. Create an WIM from VHDX
  6. If template
    1. create VHDX from WIM
  7. delete VM
  8. copy VHDX and for source the WIM to the OutFolder
  9. Create VM using VHDX in OutFolder
  10. Create a scheduled task to run every Wednesday to boot the VM’s and rerun steps 4-9 exporting the Template VHDX and Source.WIM to second output folder

The process takes a few hours based on the speed of your processor and hard drive.

Start-ImageBuild.ps1

There are two functions in this one.

Convert-WindowsImage is a wrapper around the ps1 file so I can use it multiple times.

Start-ImageBuild is an ungodly long function I created just so I can have parameters.

At the end it’ calls Start-ImageBuild with the values that match my test environment

now Start-ImageBuild can be provided a -WIMOnly parameter if I only want Source.WIM

Unattend.xml

This is a simple Unattend.xml that:

  • Sets the Language to US English
  • Accepts the License
  • Sets the the Administrator Password to P@ssword
  • Sets 1 autologon
  • runs the FirstRun.ps1 script on logon once.

Source-FirstRun.ps1

This will be copied into the image as FirstRun.ps1

  • Add and Windows Feature listed as ‘Removed’ using the attached ISO as source
  • Adds any Feature from Feature.txt
  • Adds any Feature with sub-feature from FeaturesIncludingSub.txt
  • Create AtStartup Task to run AtStartup.ps1
  • Reboot

Template-FirstRun.ps1

Same as Source-FirstRun.ps1, without adding any ‘Removed’ Features

Note: I don’t actually add any Features to Template VHDX in my example but the code is there in case it’s needed. You just have to add a Template-Features.txt and/or Template-FeaturesIncludingSub.txt

Source-Features.txt

Features to add to Sorce.WIM

Source-FeaturesIncludingSub.txt

Features including Sub-Features to add to Source.WIM

WinUpdate.ps1

Copied into both Template.VHDX and Source.VHDX as AtStarup.ps1. This scrip is a modification of Add-WindowsUpdate James O’Neil’s Blog with some additional parameters

  • -ForceRestart  to reboot after each patch run if it needs it or not
  • -ShutdownOnNoUpdate  to shutdown once there are no additional patches.

SysPrep.ps1

Copied into Templat_Sysprep.VHDX as AtStartup.ps1

  • Unregistered the AtStartup Task
  • Deletes c:\Unattent.xml
  • Deletes all other files in c:\pstemp but itself.
  • Removes all ‘Available’ Windows Features
  • Use Dism to remove any overridden patches.
  • Defrag and consolidate free space (still don’t think this is necessary, but it’s not going to hurt)
  • Sysprep
    • Silent
    • OOBE
    • leave Hyper-V drivers active (this speeds up first boot so long as the VHDX is ran on the same version of Hyper-V as the machine your building on.)
    • Reboot

Update-SourceAndTempldate.ps1

Script scheduled to run every Wednesday to update the Template and Source VHDX and WIM. This is a lot like Start-ImageBuild.ps1. you will have to edit the bottom of the script to match your environment

  • Has a wrapper for Convert-WindowsImage
  • Starts the Existing VM left in place by Start-ImageBuild.ps1
  • waits for VM to shutdown
  • gets the VHDX path from the VM configuration
  • for Template
    • Copies VHDX
    • Create’s new VM
    • copies in Sysprep.ps1
    • Starts VM
    • waits for it to stop
    • Delets VM
  • creates WIM form VHDX
  • for Template
    • Delets SysPrep VHDX
    • Converts WIM to VHDX
  • copies Templast_Production.VHDX and Source.WIM to OutPath

Now the Convert-WindowsImage i’m useing has been modified as noted in Part 3 of this Series

I have packaged all the files together, including the modified Convert-WindowsImage.ps1 in a single Download (This is a temporary location until I find a better place to store files.)

Creating a small footprint, base image Part 3 | SysPrep and Compacting Images

New-WindowsImage -size small | Test-Lab

One of the time consuming steps to deploying new VMs is the time spend managing Images and and applying patches. I’m not big on Golden images. I tend to use a fully patched VHDX or VMDK  and let DSC handle the configuration and software. This is not the fastest, and at scale you need to create more then one image based on what saves the most time.  (IIS, SQL, Exchange, etc…). In this series, I’m going to go over how I create a a baseline image of 2012r2 with PowerShell. Also because Install-WindowsFeature has issues when the target VM has been patched, we are also going to create a fully patched WIM to use as -source.

Blogs in this Series

SysPrep and Compacting Images

Sysprep the Core Image

Now I need to sysprep the core image before it’s usable. but I don’t want to repeat this whole process from scratch next month. So I’m going to copy the VHDX and create a new VM that I will sysprep.  So back to the host

copy .\CorePatched.vhdx CoreSysprep.vhdx
new-vm -Name CoreSysprep -MemoryStartupBytes 512MB -BootDevice VHD -VHDPath G:\temp\CoreSysprep.vhdx -Generation 2 -SwitchName TestLab
Name        State CPUUsage(%) MemoryAssigned(M) Uptime   Status
 ----        ----- ----------- ----------------- ------   ------
 CoreSysprep Off   0           0                 00:00:00 Operating normally
start-vm coreSysprep
vmconnect localhost coresysprep

Now on the new VM run

c:\windows\System32\Sysprep\sysprep.exe /quiet /oobe /generalize /shutdown

Once the VM is shutdown it’s time to look at our progress on the host.

Compacting the Images

dir core*
    Directory: G:\temp
Mode                LastWriteTime         Length Name
 ----                -------------         ------ ----
 -a----        5/14/2015   9:20 PM     5641338880 CoreFromIso.vhdx
 -a----        5/15/2015   4:10 PM     9265217536 CorePatched.vhdx
 -a----        5/15/2015   4:27 PM     9265217536 CoreSysprep.vhdx

Wow, almost 10 gigs. not exactly small. Well we are not done. We need to convert both our images to WIM and the core one back to VHDX. The transfer from VHDX to WMI and back to VHDX does a better job then using a utility to write all 0’s to the disk and then compact it. This is partly due to the swap file. Back on the host we need to create a folder to mount the VHDX into and then create the WIM.

mkdir g:\mount
    Directory: G:\
Mode                LastWriteTime         Length Name
 ----                -------------         ------ ----
 d-----        5/15/2015   8:54 PM                mount
Mount-WindowsImage -ImagePath G:\temp\CoreSysprep.vhdx -Path G:\Mount -Index 1
Path           : G:\Mount
 Online         : False
 Restart Needed : False
New-WindowsImage -CapturePath G:\Mount -Name 2012r2_Core -ImagePath G:\Temp\2012r2Core.wim -Description "2012r2 Core Patched May 2015" -Verify
LogPath : C:\Windows\Logs\DISM\dism.log
Dismount-WindowsImage -Path G:\mount -Discard
LogPath : C:\Windows\Logs\DISM\dism.log
Mount-WindowsImage -ImagePath G:\temp\GUIPatched.vhdx -Path G:\Mount -Index 1
Path           : G:\Mount
 Online         : False
 Restart Needed : False
New-WindowsImage -CapturePath G:\Mount -Name 2012r2_Source -ImagePath G:\Temp\2012r2Source.wim -Description "2012r2 Source Patched May 2015" -Verify
LogPath : C:\Windows\Logs\DISM\dism.log
Dismount-WindowsImage -Path G:\mount -Discard
LogPath : C:\Windows\Logs\DISM\dism.log

Converting the Core WIM back to VHDX is going to require that we make a change to Convert-WindowsImage.ps1. The logic of the script looks at the WIM and if there is only one image it uses that one. But it does so using metadata that does not exist when a WIM is created using New-WindowsImage. So down on line 4020 we nee to comment out a few lines

        # If there's only one image in the WIM, just selected that.
        #if ($openWim.Images.Count -eq 1) { 
        #    $Edition   = $openWim.Images[0].ImageFlags
        #    $openImage = $openWim[$Edition]
        #} else {

            if ([String]::IsNullOrEmpty($Edition)) {
                Write-W2VError "You must specify an Edition or SKU index, since the WIM has more than one image."
                Write-W2VError "Valid edition names are:"
                $openWim.Images | %{ Write-W2VError "  $($_.ImageFlags)" }
                throw
            } 

            if ([Int32]::TryParse($Edition, [ref]$null)) {
                $openImage = $openWim[[Int32]$Edition]    
            } else {
                $openImage = $openWim[$Edition]
            }        
        #}

Now it will work using -edition 1 (There is only one OS inside the WIM)

G:\temp\Convert-WindowsImage.ps1 -SourcePath G:\temp\2012r2Core.wim -VHDPath G:\temp\2012R2Core.vhdx -SizeBytes 40gb -VHDType Dynamic -VHDFormat VHDX -VHDPartitionStyle GPT -Edition 1 -Verbose
Windows(R) Image to Virtual Hard Disk Converter for Windows(R) 8
 Copyright (C) Microsoft Corporation.  All rights reserved.
 Version 6.3.9600.7.amd64fre.fbl_core1_hyp_dev(mikekol).140217-3000 Release to Web
 VERBOSE: isUserAdmin? True
 VERBOSE: isWindows8? True
 VERBOSE: Temporary VHDX path is : G:\temp\faf6d4fa-fb64-41d7-95f3-5a1ab3825940.vhdx
 INFO   : Image 1 selected ()...
 INFO   : Creating sparse disk...
 INFO   : Attaching VHDX...
 INFO   : Disk initialized with GPT...
 INFO   : Disk partitioned with two Volumes...
 INFO   : System Volume formatted (with DiskPart)...
 INFO   : Boot Volume formatted (with Format-Volume)...
 INFO   : Access path (L:\) has been assigned to the System Volume...
 INFO   : Access path (M:\) has been assigned to the Boot Volume...
 INFO   : Applying image to VHDX. This could take a while...
 INFO   : Signing disk...
 INFO   : Image applied. Making image bootable...
 VERBOSE: Running bcdboot.exe M:\Windows /s L:\ /v /f UEFI
 VERBOSE: Return code was 0.
 INFO   : Drive is bootable. Cleaning up...
 INFO   : Closing VHDX...
 INFO   : Closing Windows image...
 INFO   : Done.
dir *core* | sort -Property LastWriteTime
    Directory: G:\temp
 Mode                LastWriteTime         Length Name
 ----                -------------         ------ ----
 -a----        5/14/2015   9:20 PM     5641338880 CoreFromIso.vhdx
 -a----        5/15/2015   4:10 PM     9265217536 CorePatched.vhdx
 -a----        5/15/2015   8:00 PM     9265217536 CoreSysprep.vhdx
 -a----        5/15/2015   9:39 PM     2376356767 2012r2Core.wim
 -a----        5/15/2015  10:15 PM     4936695808 2012R2Core.vhdx
dir *gui*, *source* | sort -Property LastWriteTime
    Directory: G:\temp
 Mode                LastWriteTime         Length Name
 ----                -------------         ------ ----
 -a----        5/14/2015   9:29 PM     9130999808 GUIFromIso.vhdx
 -a----        5/15/2015   3:56 PM    16143876096 GuiPatched.vhdx
 -a----        5/15/2015  10:03 PM     6153716725 2012r2Source.wim

As you can see our Source.WIM is 6.1Gig and our Core VHDX is only 4.9gig. fully patched it’s smaller then the baseline Core image. We can place the WIM on a share that is accessible to new VM’s and they can use that for adding features. In another blog I will go over how to update the WIM file automatically each month.

Creating a small footprint, base image Part 2 | Patching and Cleanup via PowerShell

New-WindowsImage -size small | Test-Lab

One of the time consuming steps to deploying new VMs is the time spend managing Images and and applying patches. I’m not big on Golden images. I tend to use a fully patched VHDX or VMDK  and let DSC handle the configuration and software. This is not the fastest, and at scale you need to create more then one image based on what saves the most time.  (IIS, SQL, Exchange, etc…). In this series, I’m going to go over how I create a a baseline image of 2012r2 with PowerShell. Also because Install-WindowsFeature has issues when the target VM has been patched, we are also going to create a fully patched WIM to use as -source.

Blogs in this Series

Patching and Cleanup via PowerShell

Patching The Core Image

Starting with our Core image we are going to Patch and clean up extra files. At the command prompt I’m going to type: start Powershell Now I could use sconfig to patch, but we are doing this via PowerShell. Using James Oniel’s blog as a reference. I have cut the code down to just the needed commands because the paste keystrokes has a limited size. And sometimes opening a fresh VM does not give me the options for an Advanced Session. I’m going to take the code below and “Paste from clipboard” into PowerShell to download the all patches, including optional patches

$Criteria = "IsInstalled=0 and Type='Software'"
$updateSession = New-Object -ComObject 'Microsoft.Update.Session'
$updates = $updateSession.CreateupdateSearcher().Search($Criteria).Updates
$downloader = $updateSession.CreateUpdateDownloader()   
$downloader.Updates = $updates  
"Downloading $($downloader.Updates.count) updates"
$Result = $downloader.Download() 
if (($Result.Hresult -eq 0) -and (($Result.resultCode -eq 2) -or ($Result.resultCode -eq 3)) ) 
{
    $updatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl'

    $updates |
    Where-Object -FilterScript {
        $_.isdownloaded
    } |
    ForEach-Object -Process {
        $null = $updatesToInstall.Add($_)
    }
    $installer = $updateSession.CreateUpdateInstaller()
    $installer.Updates = $updatesToInstall


    "Installing $($installer.Updates.count) updates"
    $installationResult = $installer.Install()
    $Global:counter = -1
    $installer.updates | Format-Table -AutoSize -Property Title, EulaAccepted, @{
        label      = 'Result'
        expression = {
            $resultcode[$installationResult.GetUpdateResult($Global:counter++).resultCode ]
        }
    } 
}

$installationResult

This takes a while. Once it’s done I’m going to reboot the VM and paste the script again until there are 0 updates. It can take 2-3 times depending on the age of your ISO.

Restart-Computer

Once we have updated Core with every patch, it’s time to remove unneeded file. If you going install any features, special software or setup boot time scripts. now is the time to do so. I’m not going to this time. I will start by removing the source file for adding windows features. We will be creating a WIM from our GUI image for that purpose.

Get-WindowsFeature | where-object{$_.Installed -eq 0 -and $_.InstallState -eq 'Available'} | uninstall-windowsfeature -remove
dism /online /cleanup-image /StartComponentCleanup /ResetBase
defrag c: /UVX
  • First we removes the source from SxS
  • Second we remove any “superseded” items in SxS. So any older patches that have been updated with a newer one will be removed.
  • Third, we defrag the drive and consolidate free space (this probably is not necessary)

Preparing and Patching the GUI Image

With that done lets move to the GUI VM. I’m going to first make sure every feature is available

get-windowsfeature | Where InstallState -eq Removed
Display Name                                            Name                       Install State
 ------------                                            ----                       -------------
     [ ] .NET Framework 3.5 (includes .NET 2.0 and 3.0)  NET-Framework-Core               Removed
     [ ] Windows PowerShell 2.0 Engine                   PowerShell-V2                    Removed

So .NET 3.5 and PowerShell 2.0 are removed. If your never going to use them that is ok. I find that .NET 3.5 is frequently needed so to be safe I’m going to add them both to SxS.

get-windowsfeature | Where InstallState -eq Removed | Install-WindowsFeature -Source D:\sources\sxs
Success Restart Needed Exit Code      Feature Result
 ------- -------------- ---------      --------------
 True    No             Success        {.NET Framework 3.5 (includes .NET 2.0 and...
 WARNING: Windows automatic updating is not enabled. To ensure that your newly-installed role or feature is
 automatically updated, turn on Windows Update.

Because we want the WIM to be a source when adding to VM’s created from our Core Image, and we want the feature to be patched from the get-go. They need to be installed and patched on this VM. If they are not all installed, that is OK, it will still work as a source so long as the installed features in the WIM are the same version or newer then the VM your adding the feature to.So if we don’t mind patching the new items after the fact when we can just remove the GUI, patch and move on. I’m going to keep the GUI, and add some features I know I will be using in my lab. (now before you go and think I’ll just run Get-WindowsFeature | Add-WindowsFeature, that does not work because some features are mutually exclusive)

Install-WindowsFeature DNS, DHCP, File-Services, RSAT -IncludeAllSubFeature
Install-WindowsFeature AD-Domain-Services, AD-Certificate, ADCS-Cert-Authority

There will be a few warnings but everything should install Now I’m going to use the same code used on the Core VM to run updates. Once that is patched and shutdown it’s ready for Part 3.

Creating a small footprint, base image Part 1 | VHDX from ISO

New-WindowsImage -size small | Test-Lab

One of the time consuming steps to deploying new VMs is the time spend managing Images and and applying patches. I’m not big on Golden images. I tend to use a fully patched VHDX or VMDK  and let DSC handle the configuration and software. This is not the fastest, and at scale you need to create more then one image based on what saves the most time.  (IIS, SQL, Exchange, etc…).

In this series, I’m going to go over how I create a a baseline image of 2012r2 with PowerShell. Also because Install-WindowsFeature has issues when the target VM has been patched, we are also going to create a fully patched WIM to use as -source.

Blogs in this Series

VHDX from ISO

First you need a 20112r2 ISO, at work you would get this from the Volume License Service center. Every time Microsoft releases a roll-up, they usually update the ISO file so it’s a good idea check every few months for an updated ISO. Now for a home lab, unless you have a personal MSDN, your going to need an ISO from another source.  I’m just going with the latest trial from the TechNet Evaluation Center. Don’t download the VHD. We are going to need both Core and GUI from the ISO. While we could get there from the VHD, It’s not the path I’m taking.

I’m saving the ISO in c:\ISO\ and renaming it Server2012R2.ISO

Now what I’m going to do is create two VHDX one core and one with a GUI. I could use the ISO to manually install but I want to limit my use of the mouse, (and screen shots are a pain to insert in the blog)

Once I have that downloaded I can mount it on My windows 8.1 hyper-v host that contains the Test Lab. The reason for this is I will need the VHDX on the host. The WMI file can be on any share accessible by the VM’s.

PS C:\> Mount-DiskImage -ImagePath c:\ISO\Server2012R2.ISO

This will mount it as it’s own drive. in my case I:

Next I’m going to need a way to extract the contents of Install.wim and create a properly formatted VHDX. Now many of the examples of this online are for Gen1 machines. I want Gen2. so I’m going to get a copy of Convert-WindowsImage.ps1
Unfortunately this is not a module but and advanced script.  I’m not going to go in-depth on how Convert-WindowsImage works. Check it’s help and open it up if your curious.

Unblock-File G:\temp\Convert-WindowsImage.ps1
G:\temp\Convert-WindowsImage.ps1 -SourcePath I:\sources\install.wim -VHDPath G:\temp\CoreFromIso.vhdx -SizeBytes 40gb -VHDType Dynamic -VHDFormat VHDX -VHDPartitionStyle GPT -Edition 3 -Verbose
Windows(R) Image to Virtual Hard Disk Converter for Windows(R) 8
 Copyright (C) Microsoft Corporation.  All rights reserved.
 Version 6.3.9600.7.amd64fre.fbl_core1_hyp_dev(mikekol).140217-3000 Release to Web
 VERBOSE: isUserAdmin? True
 VERBOSE: isWindows8? True
 VERBOSE: Temporary VHDX path is : c:\temp\a585fb17-2bfe-4db6-8440-034c561e53ce.vhdx
 INFO   : Image 3 selected (ServerDataCenterEvalCore)...
 INFO   : Creating sparse disk...
 INFO   : Attaching VHDX...
 INFO   : Disk initialized with GPT...
 INFO   : Disk partitioned with two Volumes...
 INFO   : System Volume formatted (with DiskPart)...
 INFO   : Boot Volume formatted (with Format-Volume)...
 INFO   : Access path (J:\) has been assigned to the System Volume...
 INFO   : Access path (K:\) has been assigned to the Boot Volume...
 INFO   : Applying image to VHDX. This could take a while...
 INFO   : Signing disk...
 INFO   : Image applied. Making image bootable...
 VERBOSE: Running bcdboot.exe K:\Windows /s J:\ /v /f UEFI
 VERBOSE: Return code was 0.
 INFO   : Drive is bootable. Cleaning up...
 INFO   : Closing VHDX...
 INFO   : Closing Windows image...
 INFO   : Done.
G:\temp\Convert-WindowsImage.ps1 -SourcePath G:\sources\install.wim -VHDPath G:\temp\GUIFromIso.vhdx -SizeBytes 40gb -VHDType Dynamic -VHDFormat VHDX -VHDPartitionStyle GPT -Edition 4 -Verbose
Windows(R) Image to Virtual Hard Disk Converter for Windows(R) 8
 Copyright (C) Microsoft Corporation.  All rights reserved.
 Version 6.3.9600.7.amd64fre.fbl_core1_hyp_dev(mikekol).140217-3000 Release to Web
 VERBOSE: isUserAdmin? True
 VERBOSE: isWindows8? True
 VERBOSE: Temporary VHDX path is : G:\temp\dd0ac2d0-b05c-461f-b071-d73b59522bb4.vhdx
 INFO   : Image 4 selected (ServerDataCenterEval)...
 INFO   : Creating sparse disk...
 INFO   : Attaching VHDX...
 INFO   : Disk initialized with GPT...
 INFO   : Disk partitioned with two Volumes...
 INFO   : System Volume formatted (with DiskPart)...
 INFO   : Boot Volume formatted (with Format-Volume)...
 INFO   : Access path (K:\) has been assigned to the System Volume...
 INFO   : Access path (L:\) has been assigned to the Boot Volume...
 INFO   : Applying image to VHDX. This could take a while...
 INFO   : Signing disk...
 INFO   : Image applied. Making image bootable...
 VERBOSE: Running bcdboot.exe L:\Windows /s K:\ /v /f UEFI
 VERBOSE: Return code was 0.
 INFO   : Drive is bootable. Cleaning up...
 INFO   : Closing VHDX...
 INFO   : Closing Windows image...
 INFO   : Done.
disMount-DiskImage -ImagePath c:\ISO\Server2012R2.ISO
dir
Directory: C:\temp
Mode                LastWriteTime     Length Name
 ----                -------------     ------ ----
 -a---         5/14/2015   3:16 PM     187332 Convert-WindowsImage.ps1
 -a---         5/14/2015   3:29 PM 5104467968 CoreFromIso.vhdx
 -a---         5/14/2015   3:42 PM 8157921280 GUIFromIso.vhdx

 We now have two VHDX files however they are still a little out of date, and we can still trim them up a bit.

I’m going to copy the VHDX files, just so we can see how the size changes on each step.

copy .\CoreFromIso.vhdx CorePatched.vhdx

copy .\GUIFromIso.vhdx GuiPatched.vhdx

Now we will create VM’s and attach the two drives.  I will also add the ISO to the GUI version as we need it for making sure all windows features are ‘Available”

new-vm -Name CorePatch -MemoryStartupBytes 1024MB -BootDevice VHD -VHDPath G:\temp\CorePatched.vhdx -Generation 2 -SwitchName TestLab
Name      State CPUUsage(%) MemoryAssigned(M) Uptime   Status
 ----      ----- ----------- ----------------- ------   ------
 CorePatch Off   0           0                 00:00:00 Operating normally
new-vm -Name GuiPatch -MemoryStartupBytes 1024MB -BootDevice VHD -VHDPath G:\temp\GuiPatched.vhdx -Generation 2 -SwitchName TestLab
Name     State CPUUsage(%) MemoryAssigned(M) Uptime   Status
 ----     ----- ----------- ----------------- ------   ------
 GuiPatch Off   0           0                 00:00:00 Operating normally
Add-VMDvdDrive -Path C:\ISO\Server2012R2.ISO -VMName guipatch

Start-VM *patch

vmconnect localhost corepatch
vmconnect localhost guipatch

Those last two command connect me to the servers.

Now I’m going to go on the assumption that you have experience setting up server and you can handle the Out of Box experience on your own.

In Part 2 we will cover Windows Updates with PowerShell, along with cleaning up unneeded files.

Test-Lab | Update-GitHub

Posts in this series

  1. Test-HomeLab -InputObject ‘The Plan’
  2. Get-Posh-Git | Test-Lab
  3. Get-DSCFramework | Test-Lab
  4. Invoke-DscBuild | Test-Lab
  5. Test-Lab | Update-GitHub

Last time we were able to get the sample DSC config to build. The problem is we had to modify the sample script and the instructions were not clear on the setup of the files.

So to day I plan to update the SampleBuild.ps1, SampleConfiguration.psm1 and readme.md with my changes and submit a pull request.

The first thing I’m going to modify is SampleConfiguration.psm1, now this file worked as is with the previous blog. but some of the things we did were to get arround an issue with this file.

The line i’m talking about is here

 Import-Module DscConfiguration -ErrorAction Stop

This imports a module that is already loaded by SampleBuild.ps1 but is also not in the path at this point of Invoke-DscBuild, unless the DscConfiguration module is added to a path in -ModulePath used by Invoke-DscBuild.  Now if your going to run import-module SampleConfiguration crate the ConfigurationData hash table your self and call SampleConfiguration then it makes sense to have that here. I don’t imagine anyone doing that but stranger things have happen, so I’m going to keep that line but wrap it in something to avoid the requirement of placing extra modules in DSC_Tools

if (-not (Get-Module DscConfiguration)) {
 Import-Module DscConfiguration -ErrorAction Stop
}

This checks to see if DscConfiguration is already loaded into memory and skips the import is it is.

Now I don’t need any modules in DSC_Tooling. Unless I have modules used by my scripts that I dont import prior to calling Invoke-DscBuild

Next up is SampleBuild.ps1, I dont have any changes to make from what I did last time. so i will add that to my git repo in just a bit.

Now Readme.md in the examples folder needs some serious work.

Example DSC Build
------

This folder contains some very basic examples of what a DSC configurationData folder structure, script, and call to Invoke-DscBuild might look like.  If you want to execute SampleBuild.ps1, there are a few dependencies you need to set up ahead of time:

- You must install all of the DSC tooling modules (content of \Tools minus the example folder) from this repository into your PSModulePath (typically into C:\Program Files\WindowsPowerShell\Modules\)
- You must also copy the Tooling\Examples\SampleConfiguration folder to the PSModulePath.
- You must copy [Pester](https://github.com/pester/Pester) (version 3.0.0 or later) and [ProtectedData](https://github.com/dlwyatt/ProtectedData) (version 2.1 or later) into the PSModulePath.
- You should create a DSC_Resources folder in the same directory as SampleBuild.ps1 and DSC_Configuration.  Copy the following modules into that DSC_Resources folder:
  - [StackExchangeResources](https://github.com/PowerShellOrg/StackExchangeResources)
  - [cWebAdministration](https://github.com/PowerShellOrg/cWebAdministration)
  - [cSmbShare](https://github.com/PowerShellOrg/cSmbShare)

Create a folder to place all the files into. i.e. c:\DSC, inside that folder create folders named BuildOutput, DSC_Configuration, DSC_Resorces, DSC_Script, DSC_Tooling. 

the folder structure should look like this
C:\DSC                # copy SampleBuild.ps1 here
+---BuldOutput        # Where the MOF files and ziped modules end up
+---DSC_Configuration # Copy \Tooling\Examples\DSC_Configuration\*  here
+---DSC_Resources     # copy StackExchangeResources, cSmbShare and cWebAdministration here
+---DSC_Script        # copy \Tooling\Examples\SampleConfiguration here
+---DSC_Tooling       # This is for any modules that may be used in a Configuration script, in the case of SampleConfiguration it would be empty.

If you plan on modifying SampleConfiguration.psm1 inside of DSC_Script you will also want to add the content of DSC_Modules to C:\Program Files\WindowsPowerShell\Modules\ but that is not necessary if your just building configurations that are authored on another machine. 

Once these dependencies are set up, you can execute SampleBuild.ps1.  It will run tests against the 3 modules in your DSC_Resources folder, compile your configuration into MOF documents, produce zip files for the resource modules, generate checksums for everything and copy them into BuildOutput

_Note:  The SampleBuild.ps1 file currently just dumps DSC_Tooling modules into the temporary folder, since I wasn't using that feature.  We'll build on these examples soon to show off some of the other functionality in the DscBuild and DscConfiguration modules, such as encrypting credentials in source control._

If you compare with my last post you will notice I removed the line about placing SampleConfguration into C:\Program Files\WindowsPowerShell\Modules\. this path is no longer in the psmodulepath when inovke-DscBuild runs, as it used to be. This is due to needing to keep psmodulepath clean so that DSC_Resorces are accessible for configuration building but not necessarily needed.

I also added a section showing the required folder structure and what each folder is used for, along with what needs to be done if your going to author on the same machine you build on.

Now that that is done. I’m going to copy the files into C:\GitHub\PshOrgDSC\Tooling\Examples one at a time and add and commit changes.

copy SampleConfiguration.psm1 first. Notice the prompt change thanks to posh-git, (you will not see the color change)

C:\GitHub\PshOrgDSC\Tooling\Examples\SampleConfiguration [development +0 ~1 -0]> git add *

C:\GitHub\PshOrgDSC\Tooling\Examples\SampleConfiguration [development +0 ~1 -0]> git commit -m 'Fixed issue with loading scamplescript moduel not loading if module is already loaded but no longer in path'
[development a5301a8] Fixed issue with loading scamplescript moduel not loading if module is already loaded but no longer in path
 1 file changed, 3 insertions(+), 1 deletion(-)

C:\GitHub\PshOrgDSC\Tooling\Examples\SampleConfiguration [development]> 

next up SampleBuild.ps1

C:\GitHub\PshOrgDSC\Tooling\Examples [development +0 ~1 -0]> git add *

C:\GitHub\PshOrgDSC\Tooling\Examples [development +0 ~1 -0]> git commit -m 'updated SampleBuild.ps1 to include missing value to invoke-DSCBuild. and added -verbose'
[development 5c35462] updated SampleBuild.ps1 to include missing value to invoke-DSCBuild. and added -verbose
 1 file changed, 4 insertions(+), 3 deletions(-)

C:\GitHub\PshOrgDSC\Tooling\Examples [development]>

last up readme.md

C:\GitHub\PshOrgDSC\Tooling\Examples [development +0 ~1 -0]> git add *

C:\GitHub\PshOrgDSC\Tooling\Examples [development +0 ~1 -0]> git commit -m 'updated \Tooling\Example\Readme.md to matche the current state of development, and included seciton showing required folder structure'
[development 3aa570d] updated \Tooling\Example\Readme.md to matche the current state of development, and included seciton showing required folder structure
 1 file changed, 14 insertions(+), 2 deletions(-)

C:\GitHub\PshOrgDSC\Tooling\Examples [development]>

finaly I push the commits to my fork

C:\GitHub\PshOrgDSC\Tooling\Examples [development]> git push
git : To https://github.com/BladeFireLight/DSC.git

Now I can subit a pull request.

I head over to github https://github.com/BladeFireLight/DSC/tree/development

compaireandpull

Clicking Compare and pull request gives me a diff of each file and it’s changes.

exampleDiff

It also tells me there are no issues merging my commits into the upstream Development branch.

ExamplePull

I’m submitting the pull request and will update this post with the results.