PowerShell: Fully Automated Image Factory for Microsoft Deployment Toolkit

UPDATE: This post is old and intended as a walk through on how my original script was put together for those wishing to learn PowerShell. For the finished, up-to-date script please check out this post and you can download the script from my Microsoft TechNet profile.

Recently I’ve been looking into automating my image build process further using PowerShell to create an Image Factory of sorts. There are other similar scripts that I’ve found online, but I like the relatively simple and straightforward method I’ve developed below. If like me you’re looking to automate your image build process so that you can have a fully up-to-date image library after patch Tuesday each month, then this may be for you.

I’m moving the change log and updates to this page, as this post is more about the script itself and how I put it together.

Prerequisites

To use this script you’ll need:

  • A Windows 10/Windows Server 2016/Windows 8.1/Windows Server 2012 R2 Hyper-V host
  • Microsoft Deployment Toolkit installed
  • Separate Build and Deployment shares
  • Fully automated reference image build Task Sequences

It’s helpful to run the script on the same computer that has Microsoft Deployment Toolkit installed – although the shares do not have to be on this computer. For example, I run this script on my workstation which has MDT installed but the Build/Deployment shares are on a remote server.

The Hyper-V host can be either a remote server or the computer that the script is being run on.

I use the current time and date to name VHDs, WIM files and so on to avoid naming conflicts and files getting overwritten.

If you do not have separate Build/Deployment MDT shares, the script can be tweaked to run with this set up, but you will not be able to deploy devices whilst the script is running. Similarly, if you do have separate shares, this script effectively takes over the Build share for the duration of it’s run. This is to automate the running of the Task Sequences by making the necessary changes to the CustomSettings.ini file of the Build share. Your Build share should be configured as below so that the process is completely automated. If slightly different settings are needed for your environment then the script can be tweaked to make those changes, but it’s important to remember that the purpose of building an image using Hyper-V is so that the image is as “clean” as it can be, containing only the most general data needed. If drivers are required, they should be a part of the deployment process. If you have any queries about making changes, please leave a comment or contact me on Twitter.

My Build share CustomSettings.ini. Please note: it’s important to have at least one blank line at the very end.

[Settings]
Priority=Model, Default, SetOSD
Properties=OSDPrefix

[Virtual Machine]
DriverGroup001=Virtual Machine
DriverSelectionProfile=nothing
OSDPrefix=VM

[Default]
_SMSTSORGNAME=Mike Galvin | Build Share
_SMSTSPackageName=%TaskSequenceName%
UserDataLocation=NONE
DoCapture=YES
ComputerBackupLocation=\\wds01\buildshare$\Captures
BackupFile=%TaskSequenceID%_#year(date) & "-" & month(date) & "-" & day(date) & "-" & hour(time) & "-" & minute(time)#.wim
OSInstall=Y
TimeZoneName=GMT Standard Time
KeyboardLocale=0809:00000809
UILanguage=en-GB
UserLocale=en-GB
KeyboardLocale=en-GB
BitsPerPel=32
VRefresh=60
XResolution=1
YResolution=1
WSUSServer=http://wsus01:8530
SkipAdminPassword=YES
SkipCapture=YES
SkipRoles=YES
SkipProductKey=YES
SkipUserData=YES
SkipComputerBackup=YES
SkipBitLocker=YES
SkipLocaleSelection=YES
SkipTimeZone=YES
SkipDomainMembership=YES
SkipSummary=YES
SkipFinalSummary=YES
FinishAction=SHUTDOWN
EventService=http://wds01:9800
SLShare=\\wds01\buildshare$\Logs

OSDComputerName=%OSDPrefix%-%TaskSequenceID%

Once you have your Build share CustomSettings.ini configured, you’ll need to have an up to date LiteTouch_x64.iso generated and placed on the computer you are using for Hyper-V. The LiteTouch_x64.iso can be found under BuildShare\Boot\LiteTouchPE_x64.iso. You can also use a LiteTouch_x86.iso if needed.

Of course we can’t forget about the BootStrap.ini settings either. For automated log in, it should look something like this:

[Settings]
Priority=Default

[Default]
DeployRoot=\\WDS01\BuildShare$
UserDomain=FQDN.domain.co.uk
UserID=mdt_admin
UserPassword=p@ssw0rd
SkipBDDWelcome=YES

The script below will require some configuration for your environment, but when configured it will take a list of Task Sequence IDs, generate VMs for them, boot the VMs into the Task Sequence, run the Task Sequence to completion, capture the WIM, then import the WIMs into the Deploy share, ready for you to test and deploy them.

# -------------------------------------------
# Script: image-factory_v2-1.ps1
# Version: 2.1
# Author: Mike Galvin twitter.com/digressive
# Date: 24/04/2017
# -------------------------------------------

##Set MDT Custom Settings.ini location
$csini = "\\wds01\BuildShare$\Control"

##List Task Sequence IDs to run
$tsid = "REF-W101607","REF-WS2016-STD"

##Log file location
$log = "E:\imagefactory.log"

##Configure Hyper-V
$vmhost = "vs01"
$vhdpath = "G:\Hyper-V\VHD"
$vhdremotepath = "\\$vmhost\g$\Hyper-V\VHD"
$bootmedia = "E:\iso\LiteTouchPE_x64-build.iso"
$vmnic = "v-NIC"

##Configure MDT
$mdt = "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1"
$mdtpath = "\\wds01\DeploymentShare$"
$captures = "\\wds01\BuildShare$\Captures"

##Configure mail for log file
$toaddress = "it@contoso.com"
$fromaddress = "image-factory@contoso.com"
$subject = "Image Factory Log"
$mailserver = "mail.contoso.com"

##Start Log
Start-Transcript $log

##Import old Hyper V Module for WS2012R2
#Import-Module C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Hyper-V\1.1\Hyper-V.psd1 -Verbose

##Import MDT Module
Import-Module $mdt -Verbose

ForEach ($id in $tsid) {

##Setup MDT Custom Settings for VM
Copy-Item $csini\CustomSettings.ini $csini\CustomSettings-backup.ini -Verbose
Start-Sleep -s 5
Add-Content $csini\CustomSettings.ini "TaskSequenceID=$id" -Verbose
Add-Content $csini\CustomSettings.ini "SkipTaskSequence=YES" -Verbose
Add-Content $csini\CustomSettings.ini "SkipComputerName=YES" -Verbose
(Get-Content $csini\CustomSettings.ini).replace('OSDComputerName=%OSDPrefix%-%TaskSequenceID%', ';OSDComputerName=%OSDPrefix%-%TaskSequenceID%') | Set-Content $csini\CustomSettings.ini -Verbose

##Create VM
$vmname = ("build-{0:yyyy-MM-dd-HH-mm}" -f (get-date))
New-VM -name $vmname -MemoryStartupBytes 4096MB -BootDevice CD -Generation 1 -NewVHDPath $vhdpath\$vmname.vhdx -NewVHDSizeBytes 130048MB -SwitchName $vmnic -ComputerName $vmhost -Verbose
Set-VM $vmname -ProcessorCount 2 -StaticMemory -ComputerName $vmhost -Verbose
Set-VMDvdDrive -VMName $vmname -ControllerNumber 1 -ControllerLocation 0 -Path $bootmedia -ComputerName $vmhost -Verbose
Start-VM $vmname -ComputerName $vmhost -Verbose

##Wait for VM to stop
while ((get-vm -name $vmname -ComputerName $vmhost).state -ne 'Off') { start-sleep -s 5 }

##Remove VM
Remove-VM $vmname -ComputerName $vmhost -Force -Verbose
Remove-Item $vhdremotepath\$vmname.vhdx -Verbose

##Reset MDT Custom Settings
Remove-Item $csini\CustomSettings.ini -Verbose
Move-Item $csini\CustomSettings-backup.ini $csini\CustomSettings.ini -Verbose
Start-Sleep -s 5
}

##Connect to MDT Production
New-PSDrive -Name "DS002" -PSProvider MDTProvider -Root $mdtpath -Verbose
##Get files
$wims = Get-ChildItem $captures\*.wim
##Import the Captured REF Image into MDT Production
ForEach ($file in $wims) { 
    Import-MDTOperatingSystem -path "DS002:\Operating Systems" -SourceFile $file -DestinationFolder $file.Name -Verbose
}

##Remove Captured WIMs
Remove-Item $captures\*.wim -Verbose

##Stop Log
Stop-Transcript

##Send Mail
$body = Get-Content -Path $log | Out-String
Send-MailMessage -To $toaddress -From $fromaddress -Subject $subject -Body $body -SmtpServer $mailserver

##END

Let’s go through each section of the script.

##Set MDT Custom Settings.ini location
$csini = "\\wds01\BuildShare$\Control"

##List Task Sequence IDs to run
$tsid = "REF-W101607","REF-WS2016-STD"

##Log file location
$log = "E:\imagefactory.log"

##Configure Hyper-V
$vmhost = "vs01"
$vhdpath = "G:\Hyper-V\VHD"
$vhdremotepath = "\\$vmhost\g$\Hyper-V\VHD"
$bootmedia = "E:\iso\LiteTouchPE_x64-build.iso"
$vmnic = "v-NIC"

##Configure MDT
$mdt = "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1"
$mdtpath = "\\wds01\DeploymentShare$"
$captures = "\\wds01\BuildShare$\Captures"

##Configure mail for log file
$toaddress = "it@contoso.com"
$fromaddress = "image-factory@contoso.com"
$subject = "Image Factory Log"
$mailserver = "mail.contoso.com"

This is where all the configuration of the script is. Here you set the locations of all the resources needed.

First, set the location of the Custom Settings.ini file in the MDT share that is being used for the building of the images. Then list the Task Sequence IDs for all the Task Sequences that need to be run. New for v2.1 of the script, you can set a log file for the script to output to, and optionally email it when the script completes. This does not replace MDT’s logging for the Task Sequences, that should still be enabled, this is logging just for the script.

Next up is the configuration of the resources for the VMs. The configuration of the Hyper-V $vhdpath and $bootmedia variables are the local paths that Hyper-V uses. For example, if you’re running the script on your admin PC and using a remote Hyper-V host, then the $vhdpath would be the local disk on the remote server. The $vhdremotepath is regardless of where the script is running from, the UNC path of the location of the VHD location. Added in v2.1 of the script is a variable to configure the Virtual Switch for the Network Adaptor to use. Previously this had to be configured lower down in the script and was an oversight. Here, I’ve corrected this.

The MDT share locations are self explanatory, the $mdt variable is the location of the MDT PowerShell module needed later on in the script – MDT should be installed on the device that this script is running on.

Added in v2.1 is the configuration for emailing the log file when the script completes.

##Import old Hyper V Module for backquards compatibility
#Import-Module C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Hyper-V\1.1\Hyper-V.psd1 -Verbose

This line is for backwards compatibility with previous versions of Hyper-V older than the device running the script. For example: if the device running the script is Windows 10 and the Hyper-V host is a remote server running Windows Server 2012 R2, then this line should be enabled. If the Hyper-V host is running the same version of Windows as the device running the script then you can leave this line commented out.

ForEach ($id in $tsid) {

##Setup MDT Custom Settings for VM
Copy-Item $csini\CustomSettings.ini $csini\CustomSettings-backup.ini -Verbose
Start-Sleep -s 5
Add-Content $csini\CustomSettings.ini "TaskSequenceID=$id" -Verbose
Add-Content $csini\CustomSettings.ini "SkipTaskSequence=YES" -Verbose
Add-Content $csini\CustomSettings.ini "SkipComputerName=YES" -Verbose
(Get-Content $csini\CustomSettings.ini).replace('OSDComputerName=%OSDPrefix%-%TaskSequenceID%', ';OSDComputerName=%OSDPrefix%-%TaskSequenceID%') | Set-Content $csini\CustomSettings.ini -Verbose

Now the script is going to go through each Task Sequence ID that has been configured and run the image build process, one at a time. Here the CustomSettings.ini file is backed up and edited so that the VMs will boot automatically into each Task Sequence.

##Create VM
$vmname = ("build-{0:yyyy-MM-dd-HH-mm}" -f (get-date))
New-VM -name $vmname -MemoryStartupBytes 4096MB -BootDevice CD -Generation 1 -NewVHDPath $vhdpath\$vmname.vhdx -NewVHDSizeBytes 130048MB -SwitchName $vmnic -ComputerName $vmhost -Verbose
Set-VM $vmname -ProcessorCount 2 -StaticMemory -ComputerName $vmhost -Verbose
Set-VMDvdDrive -VMName $vmname -ControllerNumber 1 -ControllerLocation 0 -Path $bootmedia -ComputerName $vmhost -Verbose
Start-VM $vmname -ComputerName $vmhost -Verbose

##Wait for VM to stop
while ((get-vm -name $vmname -ComputerName $vmhost).state -ne 'Off') { start-sleep -s 5 }

##Remove VM
Remove-VM $vmname -ComputerName $vmhost -Force -Verbose
Remove-Item $vhdremotepath\$vmname.vhdx -Verbose

This is the creation, start up and deletion of the VMs. When the VM starts it will auto boot from the LiteTouch_x64-build.iso, auto login to the Build share and run the configured task sequence. It will use an automatically generated computer name, install the applications specified, run Windows Update from WSUS, then capture the image and finally shutdown, then the script removes the VM and it’s VHD file.

##Connect to MDT Production
New-PSDrive -Name "DS002" -PSProvider MDTProvider -Root $mdtpath -Verbose
##Get files
$wims = Get-ChildItem $captures\*.wim
##Import the Captured REF Image into MDT Production
ForEach ($file in $wims) { 
    Import-MDTOperatingSystem -path "DS002:\Operating Systems" -SourceFile $file -DestinationFolder $file.Name -Verbose
}

##Remove Captured WIMs
Remove-Item $captures\*.wim -Verbose

##Stop Log
Stop-Transcript

##Send Mail
$body = Get-Content -Path $log | Out-String
Send-MailMessage -To $toaddress -From $fromaddress -Subject $subject -Body $body -SmtpServer $mailserver

Here the script connects to MDT, imports all the WIMs in the Capture folder to the Operating Systems folder on the Deploy share then deletes the source capture files. Added in v2.1 the logging is stopped and then emailed to an address of your choice.

After the script has run to completion, you should have all the specified Task Sequences’ WIMs imported into your Deploy share ready to be tested and deployed – they will have a long name as per usual with importing custom images.

I actually tried to automate this part of the process as well, so new Task Sequences would be created for the new images, but alas, I hit a few road blocks. I figured that it wasn’t the end of the world and I would still like to test the images before making them widely available.

This has been a big post and I know that this script is largely dependant on a lot of factors which could be very unique to your circumstances. It may be hard work to fully automate your process but I believe it’s worth it. My team and I have been enjoying coming into the office to fresh images in the morning, instead of having to remember to manually set them off.

-Mike

Follow Mike on Twitter: @Digressive

6 thoughts on “PowerShell: Fully Automated Image Factory for Microsoft Deployment Toolkit

  1. Problem I am running into with this is on step 43. Keeps telling me that my computer or account does not have permission to the folder. Went and gave myself and the server full access to the folder I made for this but every time the script runs it removes those permissions and fails. It does build the VHD but nothing more.

    New-VM : ‘build-2017-04-05-17-26’ failed to add device ‘Virtual Hard Disk’. (Virtual machine ID 22CC22E5-79BD-4F05-A7B7-7ADF88E8D4D6)

    ‘build-2017-04-05-17-26’: The Machine Account ‘Contoso\ServerName$’ or the user initiating the VM management operation or both do not have the required access to the file share
    ‘\ServerName\d$\VmBuild\build-2017-04-05-17-26.vhdx’. Please ensure that the computer machine account and the user initiating the VM management operation have full access to the file share as
    well as the file system folder backing the file share. Error: ‘General access denied error’ (0x80070005). (Virtual machine ID 22CC22E5-79BD-4F05-A7B7-7ADF88E8D4D6)

    You do not have permission to perform the operation. Contact your administrator if you believe you should have permission to perform this operation.
    At line:2 char:1
    + New-VM -Name $vmname -MemoryStartupBytes 4096MB -BootDevice CD -Gener …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : PermissionDenied: (Microsoft.HyperV.PowerShell.VMTask:VMTask) [New-VM], VirtualizationOperationFailedException
    + FullyQualifiedErrorId : AccessDenied,Microsoft.HyperV.PowerShell.Commands.NewVMCommand

    Like

    1. Have you tried running the script from an elevated command prompt? When I run the script I open command line as administrator, then do PowerShell.exe -executionpolicy bypass E:\image_factory_v2-0.ps1. Even though my account is a domain admin, I still need to run the script from an elevated command prompt.

      Liked by 1 person

      1. Yeah ran it elevated, tried using the execution policy as well. Oddly it does actually create the drive. I am looking straight at it right now.

        Wait… did you happen to turn the folder you put the build image in into a share by chance?

        Liked by 1 person

      2. Yep looks like I got it. Forgot to make the folder on my HV host a share so it couldnt write to it properly.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s