This page looks best with JavaScript enabled

Image and Task Sequence Testing for Microsoft Deployment Toolkit and Hyper-V

In a previous post I wrote about my Image Factory PowerShell script for MDT. This post is about a script derived from that which I setup to test my images and my deployment task sequences. Since this script is only an edit of the previous one I don’t intend to release it “officially” on the TechNet Gallery or the PowerShell Gallery. To explain succinctly what this script does, it turns Task Sequences into Virtual Machines. I take great care to test my ideas and make sure my articles are accurate before posting, however mistakes do slip through sometimes. If you’d like to get in touch with me please use the comments, Twitter (you can tweet me and my DMs are open) or my contact form. I hope this article helps you out, please consider supporting my work here. Thank you.

-Mike

Features and Requirements

  • The script is designed to run on a device with MDT installed.
  • The device must also have Hyper-V management tools installed.
  • The MDT shares can be local or on a remote device.
  • The Hyper-V host can be local or on a remote device.

The script has been tested on Hyper-V installations on Windows 10, Windows Server 2016, and Windows Server 2012 R2 and MDT installations on Windows 10 and Windows Server 2016. When run, the script will:

  1. Create and configure a Hyper-V Virtual Machine for image deployment.
  2. Boot it from the MDT LiteTouch boot media.
  3. Run the specified Task Sequence.
  4. When the TS completes, the Virtual Machine will be configured for general usage.
  5. Move on to the next specified task sequence.
  6. Do steps 1-5 for all specified TS.
  7. Optionally create a log file and email it to an address of your choice.

The script should be run with the -remote switch when the Hyper-V host is a remote device. The script should be run with the -compat switch when the Hyper-V host a remote server running Windows Server 2012 R2.  

Configuration

I’ve changed the configuration to be done via command line switches, instead of having to edit the script itself. Here’s a list of all the switches and example configurations

Command Line Switch Description Example
-Deploy Location of the deployment share, it can be a local or UNC path. \\server\deploymentshare$ OR C:\DeploymentShare
-Vh Name of the Hyper-V host. Can be a local or remote device. VS01
-Vhd The path relative to the Hyper-V server of where to put the VHD file for the VM(s) that will be generated. C:\Hyper-V\VHD
-Boot The path relative to the Hyper-V server of where the ISO file is to boot from. C:\iso\LiteTouchPE_x64.iso
-Vnic Name of the virtual switch that the virtual machine should use to communicate with the network. If the name of the switch contains a space encapsulate with single or double quotes. vSwitch-Ext
-Ts The¬†comma-separated¬†list¬†of¬†task¬†sequence¬†ID’s¬†to¬†build. W10-1809,WS19-DC
-Compat Use this switch if the Hyper-V server is Windows Server 2012 R2 and the script is running on Windows 10 or Windows Server 2016/2019. This loads the older version of the Hyper-V module, so it can manage WS2012 R2 Hyper-V VMs. N/A
-Remote Use this switch if the Hyper-V server is a remote device. Do not use this switch if the script is running on the same device as Hyper-V. N/A
-L Location to store the optional log file. The name of the log file is automatically generated. C:\foo
-SendTo The e-mail address the log should be sent to. me@contoso.com
-From The e-mail address the log should be sent from. ImgFactory@contoso.com
-Smtp The DNS name or IP address of the SMTP server. smtp.live.com OR smtp.office365.com
-User The user account to authenticate to the SMTP server. example@contoso.com
-Pwd The password required for the user being used for SMTP authentication. P@ssw0rd
-UseSsl Configures the utility to connect to the SMTP server using SSL. N/A

This script can be run in a variety of configurations. You could run it on a Windows 10 device with MDT and Hyper-V Management tools installed or a single MDT deployment share on a file server¬†and a remote Hyper-V host running Windows 10, Windows Server 2016 or Windows Server 2012 R2. The script makes changes (shown below) to your MDT customsettings.ini file - after making a backup, of course! These changes are necessary so that the process runs completely automated.¬†Depending on your environment, you may need to make additional changes. Also most important: running this script will it will tie up your deployment share until it’s completed. Below are the settings you’ll need to add to your Bootstrap.ini to auto login to your Build Share. I recommend make these changes for auto-login, regenerate the boot images, copying the LiteTouch.iso to a safe place, then changing the settings back to how they were. This maintains the security of your deployment share, whilst giving you the necessary automated ISO to boot from.

1
2
3
4
5
6
7
8
9
[Settings]
Priority=Default

[Default]
DeployRoot=\\mdt01\e$\BuildShare
UserDomain=corp.contoso.com
UserID=mdt_admin
UserPassword=P@ssw0rd
SkipBDDWelcome=YES

The script adds the following lines to the end of your customsettings.ini. It sets the current task sequence to run, tells the WinPE deployment environment to skip asking for a Task Sequence and skip asking for a computer name. The computer name and Virtual Machine name is set to the Task Sequence ID.

1
2
3
TaskSequenceID=$id
SkipTaskSequence=YES
SkipComputerName=YES

PowerShell Code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# -------------------------------------------
# Script: Image-Factory-Deploy.ps1
# Version: 2.4
# Author: Mike Galvin twitter.com/mikegalvin_
# Date: 18/08/2017
# -------------------------------------------
 
[CmdletBinding()]
Param(
    [parameter(Mandatory=$true)]
    [alias("deploy")]
    $mdtdeploypath,
    [parameter(Mandatory=$true)]
    [alias("ts")]
    $tsid,
    [parameter(Mandatory=$true)]
    [alias("vh")]
    $vmhost,
    [parameter(Mandatory=$true)]
    [alias("vhd")]
    $vhdpath,
    [parameter(Mandatory=$true)]
    [alias("boot")]
    $bootmedia,
    [parameter(Mandatory=$true)]
    [alias("vnic")]
    $vmnic,
    [alias("l")]
    $logpath,
    [alias("sendto")]
    $mailto,
    [alias("from")]
    $mailfrom,
    [alias("smtp")]
    $smtpserver,
    [alias("user")]
    $smtpuser,
    [alias("pwd")]
    $smtppwd,
    [switch]$usessl,
    [switch]$compat,
    [switch]$remote)
 
## If logging is configured, start log
If ($LogPath) {
    $LogFile = "image-factory.log"
    $Log = "$LogPath\$LogFile"
    $LogT = Test-Path -Path $Log
 
## If the log file already exists, clear it
    If ($LogT) {
        Clear-Content -Path $Log
    }
 
    Add-Content -Path $Log -Value "****************************************"
    Add-Content -Path $Log -Value "$(Get-Date -format g) Log started"
    Add-Content -Path $Log -Value ""
}
 
## If compat is configured, load the older Hyper-V PS module
If ($compat) {
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Importing Hyper-V 1.1 PowerShell Module"
    }
    Write-Host "$(Get-Date -format g) Importing Hyper-V 1.1 PowerShell Module"
    Import-Module $env:windir\System32\WindowsPowerShell\v1.0\Modules\Hyper-V\1.1\Hyper-V.psd1
}
 
## Import MDT PS module
If ($LogPath) {
    Add-Content -Path $Log -Value "$(Get-Date -format g) Importing MDT PowerShell Module"
}
Write-Host "$(Get-Date -format g) Importing MDT PowerShell Module"
$mdt = "$env:programfiles\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1"
Import-Module $mdt
 
ForEach ($id in $tsid) {
    ## Setup MDT custom settings for VM auto deploy
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Backing up current MDT CustomSettings.ini"
    }
    Write-Host "$(Get-Date -format g) Backing up current MDT CustomSettings.ini"
 
    Copy-Item $mdtdeploypath\Control\CustomSettings.ini $mdtdeploypath\Control\CustomSettings-backup.ini
    Start-Sleep -s 5
 
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Setting up MDT CustomSettings.ini for Task Sequence ID: $id"
    }
    Write-Host "$(Get-Date -format g) Setting MDT CustomSettings.ini for Task Sequence ID: $id"
 
    Add-Content $mdtdeploypath\Control\CustomSettings.ini "TaskSequenceID=$id"
    Add-Content $mdtdeploypath\Control\CustomSettings.ini "SkipTaskSequence=YES"
    Add-Content $mdtdeploypath\Control\CustomSettings.ini "SkipComputerName=YES"
 
    ## Create VM
    $vmname = $id
 
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Creating VM: $vmname on $vmhost"
        Add-Content -Path $Log -Value "$(Get-Date -format g) Adding VHD: $vhdpath\$vmname.vhdx"
        Add-Content -Path $Log -Value "$(Get-Date -format g) Adding Virtual NIC: $vmnic"
    }
    Write-Host "$(Get-Date -format g) Creating VM: $vmname on $vmhost"
    Write-Host "$(Get-Date -format g) Adding VHD: $vhdpath\$vmname.vhdx"
    Write-Host "$(Get-Date -format g) Adding Virtual NIC: $vmnic"
 
    New-VM -name $vmname -MemoryStartupBytes 4096MB -BootDevice CD -Generation 1 -NewVHDPath $vhdpath\$vmname.vhdx -NewVHDSizeBytes 130048MB -SwitchName $vmnic -ComputerName $vmhost
 
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Configuring VM Processor Count"
        Add-Content -Path $Log -Value "$(Get-Date -format g) Configuring VM Static Memory"
        Add-Content -Path $Log -Value "$(Get-Date -format g) Configuring VM to boot from $bootmedia"
    }
    Write-Host "$(Get-Date -format g) Configuring VM Processor Count"
    Write-Host "$(Get-Date -format g) Configuring VM Static Memory"
    Write-Host "$(Get-Date -format g) Configuring VM to boot from $bootmedia"
 
    Set-VM $vmname -ProcessorCount 2 -StaticMemory -ComputerName $vmhost
    Set-VMDvdDrive -VMName $vmname -ControllerNumber 1 -ControllerLocation 0 -Path $bootmedia -ComputerName $vmhost
 
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Starting $vmname on $vmhost with $id"
    }
    Write-Host "$(Get-Date -format g) Starting $vmname on $vmhost with $id"
 
    Start-VM $vmname -ComputerName $vmhost
 
    ## Wait for VM to stop
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Waiting for $vmname to build $id"
    }
    Write-Host "$(Get-Date -format g) Waiting for $vmname to build $id"
 
    While ((Get-VM -Name $vmname -ComputerName $vmhost).state -ne 'Off') {Start-Sleep -s 5}
 
    ## Change config back
    Set-VMDvdDrive -VMName $vmname -ControllerNumber 1 -ControllerLocation 0 -Path $null -ComputerName $vmhost
    Set-VM -Name $VMName -DynamicMemory -MemoryStartupBytes 1GB -MemoryMinimumBytes 100MB -MemoryMaximumBytes 4GB -ComputerName $vmhost
 
    ## Restore MDT custom settings
    If ($LogPath) {
        Add-Content -Path $Log -Value "$(Get-Date -format g) Restoring MDT CustomSettings.ini from backup"
    }
    Write-Host "$(Get-Date -format g) Restoring MDT CustomSettings.ini from backup"
 
    Remove-Item $mdtdeploypath\Control\CustomSettings.ini
    Move-Item $mdtdeploypath\Control\CustomSettings-backup.ini $mdtdeploypath\Control\CustomSettings.ini
    Start-Sleep -s 5
}
 
## If log was configured stop the log
If ($LogPath) {
    Add-Content -Path $Log -Value ""
    Add-Content -Path $Log -Value "$(Get-Date -format g) Log finished"
    Add-Content -Path $Log -Value "****************************************"
 
    ## If email was configured, set the variables for the email subject and body
    If ($smtpserver) {
        $mailsubject = "Lab: Image Factory Deploy Log"
        $mailbody = Get-Content -Path $log | Out-String
 
        ## If an email password was configured, create a variable with the username and password
        If ($smtppwd) {
            $smtpcreds = New-Object System.Management.Automation.PSCredential -ArgumentList $smtpuser, $($smtppwd | ConvertTo-SecureString -AsPlainText -Force)
 
            ## If ssl was configured, send the email with ssl
            If ($usessl) {
                Send-MailMessage -To $mailto -From $mailfrom -Subject $mailsubject -Body $mailbody -SmtpServer $smtpserver -UseSsl -Credential $smtpcreds
            }
 
            ## If ssl wasn't configured, send the email without ssl
            Else {
                Send-MailMessage -To $mailto -From $mailfrom -Subject $mailsubject -Body $mailbody -SmtpServer $smtpserver -Credential $smtpcreds
            }
        }
 
        ## If an email username and password were not configured, send the email without authentication
        Else {
            Send-MailMessage -To $mailto -From $mailfrom -Subject $mailsubject -Body $mailbody -SmtpServer $smtpserver
        }
    }
}
 
## End
Share on
Support the author with